Skip to main content

SDK Overview

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',
});

// 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.