SeptemCore LogoSeptemCore
Platform Concepts

SDK Overview

Architecture of the SeptemCore SDK ecosystem: sdk-core as the shared foundation, 8 domain SDKs, 10 frontend packages, the kernel.primitive() pattern, frontend hooks vs imperative API, sdk-testing, and sdk-codegen.

The SeptemCore SDK is a monorepo of TypeScript packages that gives module developers a single, consistent interface to all 7 platform primitives. Every package follows the same design — imperative calls for backend interactions, React hooks for reactive UI state, and a shared HTTP client with automatic error handling.


Package Ecosystem

12 SDK packages

Packagenpm namePrimary backend
sdk-core@platform/sdk-coreAPI Gateway
sdk-auth@platform/sdk-authIAM service
sdk-data@platform/sdk-dataData Layer
sdk-events@platform/sdk-eventsEvent Bus
sdk-notify@platform/sdk-notifyNotify service
sdk-files@platform/sdk-filesFile Storage
sdk-money@platform/sdk-moneyMoney service
sdk-audit@platform/sdk-auditAudit Log
sdk-flags@platform/sdk-flagsFeature Flags (GoFeatureFlag)
sdk-ui@platform/sdk-uiDesign System (no backend)
sdk-testing@platform/sdk-testingTest utilities (no backend)
sdk-codegen@platform/sdk-codegenCode generation (no backend)

10 frontend packages

PackagePurpose
ui-shellVite 8 + React 19 + Module Federation 2.0 host application
create-modulenpx @platform/create-module — module scaffolding CLI
embed-loaderWeb Component loader for embeddable module widgets
event-bridgeBrowser CustomEvents bridge for cross-MFE communication
build-configShared Vite, tsconfig, and ESLint configurations
dev-serverLocal development server with hot reload
api-docsAPI documentation generation utilities
test-moduleReference module implementation for testing
tsconfigShared TypeScript base configuration package
sdk-integrations@platform/sdk-integrations — Integration Hub client

Architecture: sdk-core → Domain SDKs

All domain packages depend on @platform/sdk-core. sdk-core provides:

  • Typed HTTP client — wraps fetch with retry logic, automatic Authorization: Bearer header injection, and RFC 9457 error parsing.
  • PlatformError class — typed error with type, status, detail, traceId, and errors[] fields.
  • Shared TypeScript typesTenantId, UserId, CursorMeta, ListResponse<T>, and all primitive domain types.
  • kernel factory — the entry point that returns namespaced primitive interfaces after kernel.init().
@platform/sdk-core
  ├── @platform/sdk-auth
  ├── @platform/sdk-data
  ├── @platform/sdk-events
  ├── @platform/sdk-notify
  ├── @platform/sdk-files
  ├── @platform/sdk-money
  ├── @platform/sdk-audit
  └── @platform/sdk-flags

The kernel.primitive() Pattern

Every SDK interaction goes through the kernel singleton. The pattern is intentional: it prevents modules from constructing raw fetch calls, ensures all requests carry the correct tenant context, and allows the platform to swap the underlying transport without breaking modules.

// module entry point — initialize once
import { kernel } from '@platform/sdk-core';

await kernel.init({
  tenantId: import.meta.env.VITE_TENANT_ID,
  // token is managed automatically via the shell's auth context
});

// access any primitive via the same gateway
const contact = await kernel.data().retrieve('contacts', 'rec_01j9...');
const wallet  = await kernel.money().retrieve('01j9p3...');
await kernel.audit().record('crm.contact.viewed', 'contact', contact.id);

Each kernel.{primitive}() call returns an interface scoped to the current tenant. Cross-tenant calls are structurally impossible — the tenant ID is stored in sdk-core's internal context and injected into every request header automatically.


Frontend Hooks vs Imperative API

The SDK provides two surfaces for every primitive:

Hooks — reactive, for React components

Hooks use TanStack Query v5 under the hood. They handle loading states, background refetching, and cache invalidation automatically.

import { useQuery, useMutation } from '@platform/sdk-data';

export function ContactList() {
  const { data, isLoading } = useQuery('contacts');

  const create = useMutation('contacts', {
    onSuccess: () => {
      // TanStack Query invalidates the 'contacts' cache automatically
    },
  });

  if (isLoading) return <Spinner />;

  return (
    <>
      {data.data.map((c) => <ContactRow key={c.id} contact={c} />)}
      <button onClick={() => create.mutate({ name: 'New contact' })}>
        Add
      </button>
    </>
  );
}

Imperative API — for business logic

Useful in event handlers, background jobs, lifecycle hooks, and anywhere outside a React component tree.

import { kernel } from '@platform/sdk-core';

// In an event handler registered at module startup
kernel.events().subscribe('crm.contact.created', async (event) => {
  const contact = await kernel.data().retrieve('contacts', event.data.contactId);
  await kernel.notify().send({
    userId: event.data.assignedTo,
    channel: 'in_app',
    title: 'New contact assigned',
    body: contact.name,
  });
  await kernel.audit().record(
    'crm.contact.auto_notified',
    'contact',
    contact.id,
    { after: { notified: true } },
  );
});

SDK Package Reference

@platform/sdk-auth

Install: pnpm add @platform/sdk-auth

ExportTypeDescription
useAuth()hookReturns { user, isLoading, login, logout, refresh }
useRBAC()hookReturns { hasPermission(key), roles }
requireRole(permission)HOCWraps a component; redirects to /403 if permission absent
verifyEmail(token)async fnVerifies email confirmation token
resetPassword(token, newPassword)async fnCompletes password reset flow
inviteUser(email, roleId)async fnSends 72-hour invite link
registerProvider(config)async fnRegisters OAuth/SSO provider for tenant
listProviders()async fnLists configured auth providers

@platform/sdk-data

Install: pnpm add @platform/sdk-data

ExportTypeDescription
useQuery(model, params?)hookReactive list with cursor pagination
useRecord(model, id)hookReactive single record
useMutation(model, options?)hookCreate / update / delete with optimistic updates
useRealtime(model, handler)hookCDC-backed real-time subscription
kernel.data().create(model, data)async fnCreate record
kernel.data().retrieve(model, id)async fnRetrieve record by ID
kernel.data().list(model, params?)async fnList with filters and cursor
kernel.data().update(model, id, patch)async fnPartial update
kernel.data().delete(model, id)async fnSoft delete

@platform/sdk-events

Install: pnpm add @platform/sdk-events

ExportTypeDescription
kernel.events().publish(type, data)async fnPublish domain event (manifest-declared only)
kernel.events().subscribe(type, handler)fnSubscribe to event; returns Unsubscribe callback
kernel.events().onEvent(type)observableRxJS observable stream of typed events

Events are automatically scoped to the current tenant. Attempting to publish a type not in manifest.events.publishes[] returns 403 Forbidden.

@platform/sdk-notify

Install: pnpm add @platform/sdk-notify

ExportTypeDescription
kernel.notify().send(params)async fnSend single notification (202 Accepted)
kernel.notify().sendBatch(notifications)async fnSend up to 500; returns jobId if async
kernel.notify().sendFromTemplate(templateId, vars)async fnTemplate-based multi-channel send
kernel.notify().registerChannel(config)async fnRegister custom notification channel
kernel.notify().listChannels()async fnList available channels for tenant

@platform/sdk-files

Install: pnpm add @platform/sdk-files

ExportTypeDescription
kernel.files().upload(file, options?)async fnMultipart upload with staging pipeline
kernel.files().download(id)async fnDirect download blob
kernel.files().getUrl(id, options?)async fnPresigned download URL
kernel.files().uploadImage(file, cropData?)async fnUpload with server-side libvips processing
kernel.files().processImage(id, params)async fnPost-upload image cropping and resizing
kernel.files().getThumbnail(id, preset)async fnFetch thumbnail URL by preset

@platform/sdk-money

Install: pnpm add @platform/sdk-money

All money amounts are integers in the minor currency unit (cents for USD). Floating-point values are rejected at the schema level.

ExportTypeDescription
kernel.money().credit(walletId, amount, currency, opts)async fnCredit wallet (requires Idempotency-Key)
kernel.money().debit(walletId, amount, currency, opts)async fnDebit wallet (requires Idempotency-Key)
kernel.money().transfer(fromId, toId, amount, currency, opts)async fnAtomic transfer between wallets
kernel.money().getBalance(walletId)async fnReturns { available, pending, frozen }
kernel.money().listTransactions(walletId, params?)async fnPaginated transaction history

@platform/sdk-audit

Install: pnpm add @platform/sdk-audit

ExportTypeDescription
kernel.audit().record(action, entityType, entityId, diff?)async fnNon-blocking audit write (Kafka-backed)
kernel.audit().getHistory(entityType, entityId, params?)async fnEntity history from ClickHouse

record() is fire-and-forget. The SDK returns immediately; the record is written asynchronously via Kafka → ClickHouse.

@platform/sdk-flags

Install: pnpm add @platform/sdk-flags

ExportTypeDescription
kernel.flags().isEnabled(flagKey)fnBoolean — evaluates in-memory (nanoseconds)
kernel.flags().getVariant(flagKey)fnReturns variant string or default
kernel.flags().snapshot()fnFull flag state for the current tenant session

Flags are evaluated from an in-memory snapshot refreshed every 15 seconds. No network call per evaluation. If GoFeatureFlag is unavailable, the SDK uses the stale in-memory cache; if the cache is empty, all flags return false (fail-closed).


sdk-testing — Test Utilities

Install: pnpm add -D @platform/sdk-testing

Provides factory functions and mock implementations for every SDK package, so module tests run without a real backend:

import { kernel } from '@platform/sdk-testing';

// kernel from sdk-testing is a full mock — no HTTP calls
const mockContact = kernel.data().mockFactory('contacts', {
  name: 'Test Contact',
  email: '[email protected]',
});

// All SDK methods are jest-compatible spies
kernel.money().credit.mockResolvedValue({ id: 'tx_01j9...' });

Built-in fixtures:

import { fixtures } from '@platform/sdk-testing';

const tenant  = fixtures.tenant();      // TenantRow with valid UUID v7
const user    = fixtures.user();        // UserRow with email and roles
const wallet  = fixtures.wallet();      // WalletRow (USD, zero balance)
const file    = fixtures.file();        // FileRow (status: available)

sdk-codegen — Type Generation Pipeline

Install: pnpm add -D @platform/sdk-codegen

Generates TypeScript types and client code from platform specs:

# Generate types from module's OpenAPI spec
npx sdk-codegen openapi --input ./api/openapi.yaml --output ./src/gen/api.ts

# Generate event types from Avro schemas in Event Bus
npx sdk-codegen events --input services/event-bus/schemas/ --output ./src/gen/events.ts

The output files are type-safe, tree-shakeable, and imported directly into the module's TypeScript codebase. The codegen step runs in CI on every PR that modifies openapi.yaml or event schemas.


Installation Summary

# Install the primitives your module uses
pnpm add @platform/sdk-core @platform/sdk-auth @platform/sdk-data

# Add dev utilities
pnpm add -D @platform/sdk-testing @platform/sdk-codegen

# Or scaffold a new module with everything pre-configured
npx @platform/create-module my-module

After scaffolding, kernel.init() is called once at module bootstrap. All SDK packages read tenant context from the same shared Zustand store injected by the UI Shell at login.

On this page