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
| Package | npm name | Primary backend |
|---|---|---|
| sdk-core | @platform/sdk-core | API Gateway |
| sdk-auth | @platform/sdk-auth | IAM service |
| sdk-data | @platform/sdk-data | Data Layer |
| sdk-events | @platform/sdk-events | Event Bus |
| sdk-notify | @platform/sdk-notify | Notify service |
| sdk-files | @platform/sdk-files | File Storage |
| sdk-money | @platform/sdk-money | Money service |
| sdk-audit | @platform/sdk-audit | Audit Log |
| sdk-flags | @platform/sdk-flags | Feature Flags (GoFeatureFlag) |
| sdk-ui | @platform/sdk-ui | Design System (no backend) |
| sdk-testing | @platform/sdk-testing | Test utilities (no backend) |
| sdk-codegen | @platform/sdk-codegen | Code generation (no backend) |
10 frontend packages
| Package | Purpose |
|---|---|
ui-shell | Vite 8 + React 19 + Module Federation 2.0 host application |
create-module | npx @platform/create-module — module scaffolding CLI |
embed-loader | Web Component loader for embeddable module widgets |
event-bridge | Browser CustomEvents bridge for cross-MFE communication |
build-config | Shared Vite, tsconfig, and ESLint configurations |
dev-server | Local development server with hot reload |
api-docs | API documentation generation utilities |
test-module | Reference module implementation for testing |
tsconfig | Shared 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
fetchwith retry logic, automaticAuthorization: Bearerheader injection, and RFC 9457 error parsing. PlatformErrorclass — typed error withtype,status,detail,traceId, anderrors[]fields.- Shared TypeScript types —
TenantId,UserId,CursorMeta,ListResponse<T>, and all primitive domain types. kernelfactory — the entry point that returns namespaced primitive interfaces afterkernel.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
| Export | Type | Description |
|---|---|---|
useAuth() | hook | Returns { user, isLoading, login, logout, refresh } |
useRBAC() | hook | Returns { hasPermission(key), roles } |
requireRole(permission) | HOC | Wraps a component; redirects to /403 if permission absent |
verifyEmail(token) | async fn | Verifies email confirmation token |
resetPassword(token, newPassword) | async fn | Completes password reset flow |
inviteUser(email, roleId) | async fn | Sends 72-hour invite link |
registerProvider(config) | async fn | Registers OAuth/SSO provider for tenant |
listProviders() | async fn | Lists configured auth providers |
@platform/sdk-data
Install: pnpm add @platform/sdk-data
| Export | Type | Description |
|---|---|---|
useQuery(model, params?) | hook | Reactive list with cursor pagination |
useRecord(model, id) | hook | Reactive single record |
useMutation(model, options?) | hook | Create / update / delete with optimistic updates |
useRealtime(model, handler) | hook | CDC-backed real-time subscription |
kernel.data().create(model, data) | async fn | Create record |
kernel.data().retrieve(model, id) | async fn | Retrieve record by ID |
kernel.data().list(model, params?) | async fn | List with filters and cursor |
kernel.data().update(model, id, patch) | async fn | Partial update |
kernel.data().delete(model, id) | async fn | Soft delete |
@platform/sdk-events
Install: pnpm add @platform/sdk-events
| Export | Type | Description |
|---|---|---|
kernel.events().publish(type, data) | async fn | Publish domain event (manifest-declared only) |
kernel.events().subscribe(type, handler) | fn | Subscribe to event; returns Unsubscribe callback |
kernel.events().onEvent(type) | observable | RxJS 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
| Export | Type | Description |
|---|---|---|
kernel.notify().send(params) | async fn | Send single notification (202 Accepted) |
kernel.notify().sendBatch(notifications) | async fn | Send up to 500; returns jobId if async |
kernel.notify().sendFromTemplate(templateId, vars) | async fn | Template-based multi-channel send |
kernel.notify().registerChannel(config) | async fn | Register custom notification channel |
kernel.notify().listChannels() | async fn | List available channels for tenant |
@platform/sdk-files
Install: pnpm add @platform/sdk-files
| Export | Type | Description |
|---|---|---|
kernel.files().upload(file, options?) | async fn | Multipart upload with staging pipeline |
kernel.files().download(id) | async fn | Direct download blob |
kernel.files().getUrl(id, options?) | async fn | Presigned download URL |
kernel.files().uploadImage(file, cropData?) | async fn | Upload with server-side libvips processing |
kernel.files().processImage(id, params) | async fn | Post-upload image cropping and resizing |
kernel.files().getThumbnail(id, preset) | async fn | Fetch 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.
| Export | Type | Description |
|---|---|---|
kernel.money().credit(walletId, amount, currency, opts) | async fn | Credit wallet (requires Idempotency-Key) |
kernel.money().debit(walletId, amount, currency, opts) | async fn | Debit wallet (requires Idempotency-Key) |
kernel.money().transfer(fromId, toId, amount, currency, opts) | async fn | Atomic transfer between wallets |
kernel.money().getBalance(walletId) | async fn | Returns { available, pending, frozen } |
kernel.money().listTransactions(walletId, params?) | async fn | Paginated transaction history |
@platform/sdk-audit
Install: pnpm add @platform/sdk-audit
| Export | Type | Description |
|---|---|---|
kernel.audit().record(action, entityType, entityId, diff?) | async fn | Non-blocking audit write (Kafka-backed) |
kernel.audit().getHistory(entityType, entityId, params?) | async fn | Entity 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
| Export | Type | Description |
|---|---|---|
kernel.flags().isEnabled(flagKey) | fn | Boolean — evaluates in-memory (nanoseconds) |
kernel.flags().getVariant(flagKey) | fn | Returns variant string or default |
kernel.flags().snapshot() | fn | Full 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.