Skip to content

@stratum-hq/sdk

@stratum-hq/sdk provides an HTTP client for the Stratum control plane with built-in LRU caching, Express middleware, and a Fastify plugin. It resolves tenant context from incoming requests and attaches it to the request object.

Installation

Terminal window
npm install @stratum-hq/sdk @stratum-hq/core

Quick Start

import { stratum } from "@stratum-hq/sdk";
const s = stratum({
controlPlaneUrl: "http://localhost:3001",
apiKey: "sk_live_your_key",
});
// Express
app.use(s.middleware());
// Fastify
app.register(s.plugin());

StratumClient

The HTTP client for the control plane API with built-in LRU caching:

import { StratumClient } from "@stratum-hq/sdk";
const client = new StratumClient({
controlPlaneUrl: "http://localhost:3001",
apiKey: "sk_live_your_key",
cache: {
enabled: true, // default: true
ttlMs: 60000, // default: 60 seconds
maxSize: 100, // default: 100 entries
},
});

Client Methods

MethodReturnsDescription
resolveTenant(id)TenantContextResolve full context (cached)
getTenantTree(rootId?)TenantNode[]List tenants or subtree
createTenant(input)TenantNodeCreate tenant
getTenant(id)TenantNodeGet single tenant
updateTenant(id, input)TenantNodeUpdate tenant
moveTenant(id, input)TenantNodeMove in hierarchy
archiveTenant(id)TenantNodeSoft delete
deleteTenant(id)voidDelete tenant
rotateApiKey(keyId, name?)CreatedApiKeyRotate an API key
listApiKeys(tenantId?)ApiKeyRecord[]List API keys
listDormantKeys(days?)ApiKeyRecord[]Find unused keys
createWebhook(input)WebhookCreate webhook
listWebhooks(tenantId?)Webhook[]List webhooks
listRegions()Region[]List all regions
createRegion(input)RegionCreate a region
invalidateCache(id)voidInvalidate single entry
clearCache()voidClear all cached data

Framework Integration

import express from "express";
import { StratumClient, expressMiddleware } from "@stratum-hq/sdk";
const app = express();
const client = new StratumClient({
controlPlaneUrl: "http://localhost:3001",
apiKey: "sk_live_your_key",
});
app.use(expressMiddleware(client, {
jwtClaimPath: "tenant_id",
jwtSecret: process.env.JWT_SECRET,
headerName: "X-Tenant-ID",
onError: (err, req) => console.error(err),
}));
app.get("/data", (req, res) => {
const ctx = req.tenant;
res.json({
tenantId: ctx.tenant_id,
config: ctx.resolved_config,
});
});

Middleware Options

interface MiddlewareOptions {
jwtClaimPath?: string; // JWT claim path for tenant ID
jwtSecret?: string; // JWT verification secret (HS256)
jwtVerify?: (token: string) => Record<string, unknown> | null;
headerName?: string; // Default: "X-Tenant-ID"
resolvers?: TenantResolver[]; // Custom resolution functions
onError?: (err: Error, req: unknown) => void;
}

Tenant Resolution Order

The middleware resolves the tenant ID in this order:

  1. JWT — extracts tenant ID from a Bearer token claim at jwtClaimPath
  2. Header — reads X-Tenant-ID header (or custom headerName)
  3. Custom resolvers — your async functions, tried in order

If no tenant ID is found, returns 400 MISSING_TENANT.

Custom Resolvers

app.use(s.middleware({
resolvers: [
{
// Resolve from subdomain
resolve: async (req) => {
const subdomain = req.hostname.split(".")[0];
return subdomain !== "www" ? subdomain : null;
},
},
{
// Resolve from query parameter
resolve: async (req) => req.query.tenant_id ?? null,
},
],
}));

AsyncLocalStorage Context

For code that does not have access to the request object (services, repositories):

import { getTenantContext, runWithTenantContext } from "@stratum-hq/sdk";
function myService() {
const ctx = getTenantContext(); // throws if no context
console.log(ctx.tenant_id);
}
// Or run code with explicit context
await runWithTenantContext(tenantContext, async () => {
const ctx = getTenantContext(); // available here
});

LRU Cache

The built-in cache reduces control plane round-trips:

  • Default max size: 100 entries
  • Default TTL: 60 seconds
  • Auto-invalidated on mutations (create, update, delete)
  • Configurable or disableable via client options
// Disable cache
const client = new StratumClient({
controlPlaneUrl: "http://localhost:3001",
apiKey: "sk_live_your_key",
cache: { enabled: false },
});

Error Handling

The SDK throws typed errors from @stratum-hq/core:

import { TenantNotFoundError, UnauthorizedError } from "@stratum-hq/core";
try {
await client.resolveTenant("bad-id");
} catch (err) {
if (err instanceof TenantNotFoundError) {
// Tenant doesn't exist
}
if (err instanceof UnauthorizedError) {
// Invalid API key
}
}