@mixmaxhq/context-sdk
v1.0.0
Published
Typed client for Mixmax context-api — list/get/create/update/delete/bundle context blocks. Server-side callers use SigV4-signed Function URL; browser callers use the cookie-authenticated public gateway. Transport is swappable.
Maintainers
Keywords
Readme
@mixmaxhq/context-sdk
Typed client for Mixmax context-api. Six operations covering CRUD over context blocks plus a bundle convenience for AI cold-start. Server-side callers (Lambda/ECS) go through the SigV4-signed Function URL; browser callers go through the cookie-authenticated public gateway. The transport is swappable — future migrations (VPC Lattice, mTLS) drop in without changing the public method surface.
See docs/adr-inter-service-auth.md in monorepo-context for the architectural decisions behind the SDK.
Install
npm i @mixmaxhq/context-sdkGetting started (server-side)
For a Lambda/ECS service that calls context-api on behalf of a user:
import { ContextSDK } from '@mixmaxhq/context-sdk';
import EnvironmentLib from '@mixmaxhq/environment';
const env = EnvironmentLib.get();
export async function draftReplyHandler(event) {
const { userId, workspaceId } = event.requestContext.authorizer;
const ctx = new ContextSDK({ userId, workspaceId }, env);
// Pull everything in one call — personal + workspace + per-team blocks
const { personal, workspace } = await ctx.bundle();
// ...feed `personal`/`workspace` into your LLM prompt
}You also need to grant the caller's Lambda role lambda:InvokeFunctionUrl on context-api's Function URL ARN. Use the shipped Terraform module:
module "context_caller" {
source = "github.com/mixmaxhq/monorepo-context//libs/terraform-aws-context-api-caller?ref=v1"
environment = var.environment
role_name = aws_iam_role.my_lambda.name
}That's all the infra setup. The IAM grant scales linearly per caller — no SG mesh, no shared secret, no central state.
Getting started (browser)
For the SPA, use the useGateway: true mode:
import { ContextSDK } from '@mixmaxhq/context-sdk';
import EnvironmentLib from '@mixmaxhq/environment';
const ctx = new ContextSDK({ useGateway: true }, EnvironmentLib.get());
// Same method surface, different transport under the hood
const { blocks } = await ctx.list({ scope: 'personal' });The browser path hits the existing /v1/context/blocks* routes on the public gateway with credentials: 'include'. The gateway's Lambda authorizer validates the user's session cookie.
Cookbook (the job, not the method)
Pull context for a draft-reply Lambda
const { personal, workspace } = await ctx.bundle();
const prompt = renderPrompt({ personal, workspace, email });Surface only blocks visible to the user's team
const { blocks } = await ctx.list({ scope: 'workspace', teamId: identity.teamId });Save a user's new ICP description as a workspace block
const { block } = await ctx.create({
scope: 'workspace',
section: 'ICP',
title: 'Series B SaaS founders',
body: '...',
teamIds: ['team-AE-mid-market'],
});Refresh a brand-voice block without touching other fields
// Partial patch — omitted fields are unchanged.
await ctx.update(blockId, { scope: 'workspace', body: newBody });Clear team narrowing on a block (make it visible to all teams)
await ctx.update(blockId, { scope: 'workspace', teamIds: null });Soft-delete a deprecated block
await ctx.delete(blockId, { scope: 'workspace' });
// Auto-purged after 90 days. To restore within the window, ping on-call.Error handling
All errors are subclasses of ContextApiError with a .status field.
import { ContextSDK, NotFoundError, ForbiddenError } from '@mixmaxhq/context-sdk';
try {
await ctx.update(blockId, { scope: 'personal', body });
} catch (err) {
if (err instanceof NotFoundError) {
// Block was deleted between read and write. Fall back to create or skip.
return;
}
if (err instanceof ForbiddenError) {
// User can't update this block (e.g. personal block owned by another user).
throw new UserFacingError('You can only update your own context blocks.');
}
throw err;
}Retry behavior
Transient failures (429, 5xx, network/timeout) are retried automatically with decorrelated jitter (max 3 retries, capped at 5s total budget). The SDK honors a server-provided retryAfter field in the response body when present. Non-retryable errors (401, 403, 422) surface immediately.
Transports (advanced)
The SDK ships two transports out of the box and accepts a custom one:
import { ContextSDK } from '@mixmaxhq/context-sdk';
import type { Transport } from '@mixmaxhq/context-sdk/transport';
class MyCustomTransport implements Transport {
async send(req) { /* ... */ }
}
const ctx = new ContextSDK({ userId, workspaceId }, env, {
transport: new MyCustomTransport(),
});Why this matters: when AWS announces VPC Lattice (or mTLS or anything else) as the next-generation inter-service transport, the migration is a new Transport class + a SDK minor bump. The callers' code does not change.
Versioning
Strict semver. Patch releases for bug fixes and transport-internal changes. Minor for new methods and backward-compatible widenings. Major for breaking changes — deprecation window of 90 days minimum, with the old method shipped alongside the new for one minor release.
Reporting issues
File an issue at monorepo-context or ping #brain-context on Slack.
