@platform/sdk-core
@platform/sdk-core is the foundational package of the Platform SDK.
It is a transitive dependency of every other @platform/sdk-* package
and is never installed directly in normal module development. You only
interact with it when registering global interceptors or handling
PlatformError objects.
Installation
# Installed automatically as a dependency of other SDK packages.
# Direct install only when working with interceptors or PlatformError:
pnpm add @platform/sdk-core
kernel.init()
import { kernel } from '@platform/sdk-core';
await kernel.init({
apiUrl: 'https://api.septemcore.com/v1', // Platform API base URL
tenantId: 'YOUR_TENANT_ID', // ULID from tenant record
moduleId: '@scope/my-module', // Must match module.manifest.json
environment: 'production', // 'development' | 'staging' | 'production'
logLevel: 'warn', // 'debug' | 'info' | 'warn' | 'error'
});
| Parameter | Type | Required | Description |
|---|---|---|---|
apiUrl | string | ✅ | Base URL including version prefix (/v1) |
tenantId | string | ✅ | ULID of the current tenant |
moduleId | string | ✅ | Module identifier — must match name in module.manifest.json |
environment | string | ✅ | Affects logging verbosity and telemetry tagging |
logLevel | string | ☐ | Default: warn |
kernel.init() must resolve before any SDK method is called. Calling
any method before initialization throws KernelNotInitializedError.
HTTP Client
The SDK uses the native Fetch API with the following defaults:
| Behavior | Value |
|---|---|
| Base URL | Set by kernel.init({ apiUrl }) |
| Timeout | 30 seconds per request |
| Retry policy | 1 automatic retry with 100ms backoff for GET requests on network failure. POST/PATCH/DELETE — no automatic retry (use explicit idempotency keys). |
| Auth header | Authorization: Bearer {accessToken} injected on every request |
| Content-Type | application/json (default). Overridden to multipart/form-data for file uploads. |
| Trace header | X-Request-ID: {uuid} auto-generated per request for correlation |
Token Management
The SDK manages JWT access tokens and refresh tokens automatically. Module authors never handle tokens manually.
Token Lifecycle
User logs in (sdk-auth):
→ SDK receives { accessToken, refreshToken }
→ Stores in memory (accessToken) + localStorage (refreshToken)
→ Injects accessToken into every outgoing request
accessToken expires (default 15 min):
→ SDK detects 401 response
→ POST /auth/token/refresh { refreshToken }
→ Receives new { accessToken, refreshToken }
→ Retries original request with new accessToken
→ All other tabs notified via BroadcastChannel
Cross-Tab Coordination (BroadcastChannel)
Multiple browser tabs open to the same module may all detect a
token expiry simultaneously and race to call /auth/token/refresh.
The SDK prevents duplicate refresh calls:
Tab A: detects 401 → acquires "refresh lock" via BroadcastChannel
Tab B: detects 401 → sees lock held → waits for broadcast
Tab A: calls POST /auth/token/refresh → success
Tab A: broadcasts new accessToken to all tabs
Tab B: receives broadcast → updates local token → retries original request
Server-side protection (10-second grace window):
Refresh token remains valid for 10 seconds after first use.
If Tab A's broadcast fails and Tab B calls refresh independently
within 10s → gets the same new token pair (idempotent).
After 10s → token revoked → user must log in again.
| Parameter | Value |
|---|---|
| Cross-tab mechanism | BroadcastChannel API (platform_auth_channel) |
| Server-side grace | 10 seconds after first refresh use (AUTH_REFRESH_GRACE_WINDOW_SEC) |
| In-flight deduplication | Only one refresh call per tab group at a time |
Error Handling (RFC 9457)
All platform API errors follow
RFC 9457 — Problem Details for HTTP APIs.
The SDK parses Content-Type: application/problem+json responses into
typed PlatformError instances:
import { PlatformError } from '@platform/sdk-core';
try {
const result = await kernel.data().query('contacts', { limit: 20 });
} catch (error) {
if (error instanceof PlatformError) {
// Typed RFC 9457 problem
console.error(error.type); // e.g. "https://api.septemcore.com/problems/not-found"
console.error(error.title); // e.g. "Not Found"
console.error(error.status); // e.g. 404
console.error(error.detail); // Human-readable detail
console.error(error.instance); // e.g. "/api/v1/data/contacts"
console.error(error.traceId); // OpenTelemetry trace ID
console.error(error.errors); // Validation errors array (422 only)
} else {
// Raw network failure (no HTTP response)
console.error('Network error:', error);
}
}
Validation Errors
When the API returns 422 Unprocessable Entity, the errors[] array
contains per-field details:
{
"type": "https://api.septemcore.com/problems/validation-error",
"title": "Validation Error",
"status": 422,
"errors": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "roleId", "message": "Role not found" }
],
"traceId": "01j9ptr0000000000000015"
}
The SDK exposes error.errors as Array<{ field: string; message: string }>.
Request Interceptors
Interceptors allow module authors to instrument every outgoing request and every incoming response globally — useful for logging, custom headers, and telemetry:
import { kernel } from '@platform/sdk-core';
// Request interceptor: runs before every outgoing HTTP call
kernel.http.interceptors.request.use((config) => {
config.headers['X-Module-Version'] = '1.2.0';
return config;
});
// Response interceptor: runs after every HTTP response
kernel.http.interceptors.response.use(
(response) => {
// Success path: log or transform
console.debug(`${response.status} ${response.url}`);
return response;
},
(error: PlatformError) => {
// Error path: log, report, or rethrow
if (error.status === 503) {
myMonitoring.alert('Platform service unavailable', error.traceId);
}
throw error;
}
);
| Interceptor type | Runs on | Use cases |
|---|---|---|
request | Before every HTTP call | Add custom headers, log request metadata |
response (success) | After 2xx response | Transform data, log latency |
response (error) | After non-2xx response | Log errors to monitoring, conditional rethrow |
Interceptors are applied in registration order (FIFO for request, LIFO for response — matching standard Axios/Fetch interceptor semantics).
TypeScript Types
Core shared types exported from sdk-core:
// RFC 9457 error
interface PlatformError extends Error {
type: string;
title: string;
status: number;
detail: string;
instance: string;
traceId: string;
errors: Array<{ field: string; message: string }>;
}
// Pagination metadata (all list responses)
interface Meta {
total: number;
page: number;
limit: number;
hasMore: boolean;
}
// All paginated responses
interface PaginatedResponse<T> {
data: T[];
meta: Meta;
}
These types are re-exported from every domain SDK so you never need
to import from sdk-core directly for most use cases.