@sorb/juice reference
When you finish this page you know every sorb CLI command and its
options, every public HTTP route the bridge serves, the library's exports,
and the MCP tools an AI agent gets from sorb mcp. You need Node 20 and a
sorb.config.json (sorb init writes one). For what sorb dev actually
does and why, read The bridge first — this page
is the reference.
npm install -D @sorb/juice
@sorb/juice is a dev dependency — it runs the bridge on your machine (or a
self-host you run yourself) and never ships to production.
CLI commands
Seven commands, dev is the default (sorb alone == sorb dev).
| Command | Description |
|---|---|
sorb dev (default) | Start the local token bridge server |
sorb serve | Run the hosted bridge server (config from env; no sorb.config.json) |
sorb init | Create a sorb.config.json in the current directory |
sorb commit | Open a GitHub PR with the current token file |
sorb handshake | Generate a shareable invite that auto-configures a designer's Figma plugin |
sorb check | Re-run the token build and report evidence of drift, binding-mismatch, or deprecation; exits non-zero on findings (for CI) |
sorb mcp | Run the Sorb MCP server (stdio) — resolved tokens + component bindings for AI agents |
sorb dev
Start the local token bridge server
| Option | Description | Default |
|---|---|---|
-p, --port <port> | port to listen on (overrides config) | |
--cloud | serve the resolved token map from sorb-cloud HEAD instead of local .sorb/resolved.json | |
--cloud-url <url> | sorb-cloud base URL (or env SORB_CLOUD_URL / CLOUD_API) | |
--cloud-set <id> | cloud token-set id to serve (or env SORB_CLOUD_SET) | |
--cloud-key <key> | API key for the cloud request (or env SORB_CLOUD_KEY) |
sorb serve
Run the hosted bridge server (config from env; no sorb.config.json)
No options.
sorb init
Create a sorb.config.json in the current directory
No options.
sorb commit
Open a GitHub PR with the current token file
| Option | Description | Default |
|---|---|---|
--owner <owner> | GitHub org or user | |
--repo <repo> | GitHub repo name | |
--pat <pat> | GitHub personal access token | |
--message <message> | PR / commit title | Update design tokens |
sorb handshake
Generate a shareable invite that auto-configures a designer's Figma plugin
| Option | Description | Default |
|---|---|---|
--app <url> | App URL the preview opens (default: config.appUrl or http://localhost:5173) | |
--gh <url> | GitHub edit URL of the tokens file (default: config.gh or from git remote) | |
--pk <key> | Read-only sorb_pk_ for a hosted bridge (default: blank = keyless-local) | |
--origin <url> | Bridge origin override (default: http://127.0.0.1:<port from config>) | |
--exp <days> | Days until the invite expires (default: 30; pass 0 for no expiry) | |
--copy | Copy the paste code to the clipboard | |
--link-only | Print only the sorb:// link | |
--code-only | Print only the paste code |
sorb check
Re-run the token build and report evidence of drift, binding-mismatch, or deprecation; exits non-zero on findings (for CI)
| Option | Description | Default |
|---|---|---|
--resolved <path> | resolved token snapshot to check | .sorb/resolved.json |
--baseline <path> | previous resolved snapshot to diff against | .sorb/baseline.json |
--live <path> | live-captured cssVar→value map to diff the build against | |
--computed <path> | computed-styles map (property→value) for the hardcoded-value scan | |
--format <fmt> | output format: text | json | text |
--no-build | skip the Style Dictionary rebuild; check the existing snapshot only |
sorb mcp
Run the Sorb MCP server (stdio) — resolved tokens + component bindings for AI agents
| Option | Description | Default |
|---|---|---|
--dir <path> | project dir whose .sorb/ is served | process.cwd() |
--bridge <url> | live bridge base URL for gated write previews (e.g. http://localhost:7777) | |
--http | run the hosted HTTP/SSE MCP endpoint (auth + tenant scoping; requires DATABASE_URL) | |
-p, --port <port> | port for the hosted --http endpoint | 7878 |
--base-dir <path> | per-project snapshot root for hosted --http mode | process.env.SORB_MCP_BASE_DIR || './.sorb-projects' |
--gh-owner <owner> | GitHub org or user for open_pr | |
--gh-repo <repo> | GitHub repo name for open_pr | |
--gh-pat <pat> | GitHub personal access token for open_pr | |
--gh-token-path <path> | token file path open_pr writes to | tokens/component.json |
HTTP routes
The bridge's public HTTP surface — everything except /enterprise/*. In
local mode (no databaseUrl configured — the default for sorb dev)
every route below is open, unauthenticated, and un-tenant-scoped, byte for
byte what the free bridge has always done. In hosted mode (a self-host or
Sorb Cloud with databaseUrl set) every route except GET /health and
GET /ready requires a Bearer API key, and two routes additionally require a
secret key rather than a read-only publishable one:
| Route | Hosted-mode key requirement |
|---|---|
POST /verify | Secret key only — publishable is rejected with 403 {error: 'Publishable keys are read-only', code: 'read_only'}. |
POST /tokens/figma | Secret key only — same 403 as above. |
Every other route below (including POST/PUT/DELETE /preview*) | Any authenticated key, publishable or secret. |
That second row is easy to misread from the preview routes' names: PUT and
DELETE /preview/:id look like writes that should need a secret key, but the
bridge deliberately accepts either scope there — the account-pairing front
door mints a read-only publishable key for the Figma plugin, and previews are
ephemeral and non-destructive (they never touch the committed token set), so
gating them on secret would 403 the plugin's whole preview loop. The
committed-token-set writes (POST /tokens/figma) and the plugin's geometry
report (POST /verify) are the two routes that actually need secret.
| Method | Path | What it does |
|---|---|---|
GET | /orgs/:orgId/preview/:id/subscribe | E1 — GET /orgs/:orgId/preview/:id/subscribe (SSE push) The leaf SDK subscribes here to receive live preview updates instead of polling GET /preview/:id. |
POST | /preview | Figma plugin POSTs a proposed token set here. |
GET | /preview/latest | The project's most-recently-pushed live preview (#4b dependency — lets sorb-cloud snapshot "what's currently being previewed" without knowing an id up front). |
GET | /preview/:id | React app polls this while a preview is active. |
PUT | /preview/:id | Plugin updates an existing preview in-place (live edit mode). |
DELETE | /preview/:id | Plugin clears a preview when designer exits without committing. |
POST | /verify | Plugin posts the post-layout geometry of an inserted component so the canvas can be reconciled against the captured artifact. |
GET | /verify/latest | The most recently reported verification. |
GET | /verify/figma | FIGMA-vs-DTCG drift check: diffs the latest Figma Variables export (POST /tokens/figma) against the committed resolved map (GET /tokens/resolved), matched by cssVar. |
GET | /verify/:id | A specific verification by id. |
POST | /verify/activity | Record that the current Figma user just ran a preview. |
GET | /verify/activity | Returns the most recent other user's activity entry (exclude= self). |
GET | /tokens/latest | Returns the latest committed tokens from disk. |
GET | /tokens/resolved | The resolved bindable token map produced by Style Dictionary — one entry per token: { id, cssVar, value, tier, type }. |
POST | /verify/app | RUNNING-APP token verification (roadmap §3 / e2e-fix W2): the app reports the values it actually resolved for a set of tokens (@sorb/leaf's verifyResolved reads them off :root), and the bridge diffs them against the committed resolved map. |
POST | /tokens/figma | The Figma plugin (sorb-canopy "Export variables" action) POSTs its exported Variables here as a resolved-map artifact: { fileKey: string|null, exportedAt: string|null, tokens: [{id,cssVar,value,tier,type}] } Stored as the single latest Figma-side snapshot (no history) — GET /tokens/figma and GET /verify/figma read it back. |
GET | /tokens/figma | The latest Figma Variables export, as posted by the plugin. |
GET | /artifacts | The captured-component index — list of components/stories with hashes, produced by sorb-seed capture. |
GET | /artifact | Lookup by id in the index — never accepts an arbitrary filesystem path. |
GET | /usages | Reverse query over the binding graph: which captured components/roles render a token. |
GET | /blast | Like /usages but expands the alias chain: editing a primitive reports the semantic/component tokens that alias it (same resolved value+type) AND their bound components. |
POST | /graft/plan | Plan a theme graft from one component onto another, reconciled BY ROLE (same role key; tie-break by TIER_RANK). |
GET | /health | Infra probe — open in ALL modes (no auth, no tenant scope). |
GET | /ready | Readiness probe: 200 only when every configured backend is reachable. |
GET /orgs/:orgId/preview/:id/subscribe
E1 — GET /orgs/:orgId/preview/:id/subscribe (SSE push) The leaf SDK subscribes here to receive live preview updates instead of polling GET /preview/:id. Ported/promoted from the relay-spike (experiments/sorb-bridge-modes/relay-spike). Registered FIRST — before the hosted header-auth + CORS middleware below — because the browser EventSource API cannot set request headers, so this route authenticates via a ?key= query param and must not be 401'd by the header middleware. Being registered first, its Response short-circuits the middleware chain. AUTH (hosted mode): ?key=<publishable-or-secret-key>. Missing/invalid → 401. Any tenant mismatch → 404 (never 403) so cross-tenant existence is never revealed — same posture as GET /preview/:id. LOCAL mode (no databaseUrl): OPEN, no key required, :orgId is ignored — byte-for-byte the free bridge's no-auth behavior. FRAME CONTRACT (each SSE frame's data: is a JSON object): - on connect: { type: 'snapshot', tokens } - on store change: { type: 'update', tokens } (put + update both → 'update') { type: 'delete', tokens: null } - idle keepalive: { type: 'ping' } every sseHeartbeatMs Frames use the default (message) SSE event so EventSource.onmessage receives them; each carries a monotonically increasing id: from 1.
POST /preview
Figma plugin POSTs a proposed token set here. Returns a short preview ID the React app can use via ?preview=<id> BODY SHAPE (phase-2 dark mode): EITHER - legacy flat — a plain map { "--token": "value", ... } (today's shape), OR - mode-aware — { tokens: {...}, darkTokens?: {...}, darkMode?: {...} }, a wrapper object carrying a tokens key (detection: presence of a tokens key on the body means mode-aware; a flat map never has one, since CSS custom-property names always start with --). The bridge stores + returns the body VERBATIM in either shape — it never inspects, flattens, or strips fields. Store + routes are shape-agnostic by construction (see src/store/memory.js / redis.js: tokens is opaque JSON to both), so this needed no store change. Detection/interpretation of the shape is entirely a leaf/cloud-side concern.
GET /preview/latest
The project's most-recently-pushed live preview (#4b dependency — lets sorb-cloud snapshot "what's currently being previewed" without knowing an id up front). MUST be registered before /preview/:id or Hono treats "latest" as an :id param. Read-only: allowed for BOTH publishable and secret scope in hosted mode (mirrors GET /preview/:id). Response contract (matched to the cloud agent's toResolved()): 200 { id, tokens } — tokens is exactly the shape POST/PUT /preview stored (whatever the plugin pushed), under a tokens field. 404 { error: 'no_preview' } — no current preview for the caller. DARK-MODE ENVELOPE SEAM (phase-2): the stored body may itself be either a legacy flat map ({ "--token": "value" }) or a mode-aware wrapper ({ tokens, darkTokens, darkMode }) — see server.js's POST /preview comment. This route does NOT special-case that: tokens here is always the WHOLE stored body, unmodified — for a mode-aware preview that means the response is { id, tokens: { tokens, darkTokens, darkMode } }, i.e. this route's outer tokens field is "the preview body", not "the light-mode token map". Chosen over introducing a new top-level field (e.g. preview) to keep this route's response shape byte-for-byte unchanged for legacy flat previews (today's only consumers). Downstream (leaf/cloud): apply the SAME mode-aware detection ('tokens' in body) to this route's tokens field that you'd apply to a GET /preview/:id body directly.
GET /preview/:id
React app polls this while a preview is active. Allowed for BOTH read (publishable) and write (secret) scope in hosted mode.
PUT /preview/:id
Plugin updates an existing preview in-place (live edit mode). WRITE op.
DELETE /preview/:id
Plugin clears a preview when designer exits without committing. WRITE op.
POST /verify
Plugin posts the post-layout geometry of an inserted component so the canvas can be reconciled against the captured artifact. Returns a short id. WRITE op. In hosted mode: a best-effort INSERT into verify_events is written after the store write so the beta can measure weekly active verify runs per project (moat-proof retention metric R-0607-16). Like preview bookkeeping, a failed insert does not fail the request.
GET /verify/latest
The most recently reported verification. MUST be registered before /verify/:id or Hono treats "latest" as an :id param.
GET /verify/figma
FIGMA-vs-DTCG drift check: diffs the latest Figma Variables export (POST /tokens/figma) against the committed resolved map (GET /tokens/resolved), matched by cssVar. This route only FLAGS drift — it never resolves it: the DTCG token source is truth, the Figma export is a mirror. Compares format-insensitively (lowercase + collapse whitespace), same as POST /verify/app. ok mirrors /verify/app's contract (mismatches-only); missingInFigma/extraInFigma are reported but don't themselves flip ok, since a partial/in-progress export is a normal transient state. MUST be registered before /verify/:id or Hono treats "figma" as :id.
POST /verify/activity
Record that the current Figma user just ran a preview. Non-critical — no auth gate (same as /health) since it carries no secrets (display name only).
GET /tokens/latest
Returns the latest committed tokens from disk. Plugin fetches this on open to pre-populate the editor.
GET /tokens/resolved
The resolved bindable token map produced by Style Dictionary — one entry per token: { id, cssVar, value, tier, type }. The plugin uses it to create grouped Variables and to bind captured values; capture annotates against it. 404 if it hasn't been built yet.
POST /verify/app
RUNNING-APP token verification (roadmap §3 / e2e-fix W2): the app reports the values it actually resolved for a set of tokens (@sorb/leaf's verifyResolved reads them off :root), and the bridge diffs them against the committed resolved map. "verified" = the LIVE app resolves to the bound token values — not the Figma-side bbox geometry that POST /verify records. Reads the same resolved map as GET /tokens/resolved and writes nothing; in hosted mode the global auth middleware still gates it (only /health + /ready are open). NOTE: getResolvedTokens() is a single global map (no tenant scope) — fine for the local bridge; revisit before any multi-tenant hosted rollout.
POST /tokens/figma
The Figma plugin (sorb-canopy "Export variables" action) POSTs its exported Variables here as a resolved-map artifact: { fileKey: string|null, exportedAt: string|null, tokens: [{id,cssVar,value,tier,type}] } Stored as the single latest Figma-side snapshot (no history) — GET /tokens/figma and GET /verify/figma read it back. WRITE op, same gate as /preview and /verify.
GET /tokens/figma
The latest Figma Variables export, as posted by the plugin. 404 until the plugin has exported at least once.
GET /artifacts
The captured-component index — list of components/stories with hashes, produced by sorb-seed capture. 404 until seed has run.
GET /usages
Reverse query over the binding graph: which captured components/roles render a token. READ-ONLY blast radius (roadmap §7 P1). 400 on missing/both params. An unknown token returns count:0 + components:[] (NOT a 404 — "rendered nowhere" is a valid answer). Response (pinned): { id, cssVar, count, components: [{storyId, role}] } - ?id= → id is the queried token id, cssVar resolved from the map. - ?cssVar= → id is the resolved token id (first match), cssVar echoes the queried var; when a cssVar maps to multiple ids the components UNION and count reflects the union.
GET /blast
Like /usages but expands the alias chain: editing a primitive reports the semantic/component tokens that alias it (same resolved value+type) AND their bound components. READ-ONLY (roadmap §7 P2). Response (pinned): { id, aliasGroup:[tokenId], count, components:[{storyId, role, via}] }
POST /graft/plan
Plan a theme graft from one component onto another, reconciled BY ROLE (same role key; tie-break by TIER_RANK). COMPUTE-ONLY: returns a changeset + conflict list, never writes (apply is canopy P4 via /preview). Body: { from, to, roles? } (from/to = storyId) Response (pinned): { from, to, changeset: [{role, sourceTokenId, targetTokenId}], conflicts: [{role, reason}] } A target role with no compatible token, or a type mismatch (e.g. color onto a dimension role), appears in conflicts — never silently dropped.
GET /ready
Readiness probe: 200 only when every configured backend is reachable. Open in ALL modes (no auth). The in-memory store always pings true; a null db (local mode) is "not configured" and skipped. Returns 503 with { ok:false, checks } when any configured backend is unreachable.
Library exports
For embedding the bridge yourself instead of running the CLI:
| Export | Kind | Description | Source |
|---|---|---|---|
createServer | value | Build the Hono bridge app. | src/server.js:94 |
createStore | function | Create the appropriate Store for the given config. | src/store/index.js:20 |
createMemoryStore | function | Create the in-memory Store. | src/store/memory.js:28 |
loadConfig | function | Load the bridge configuration from env (defaults to process.env), applying the safe local defaults from DEFAULTS. | src/config.js:103 |
createDb | function | Create the Postgres durable layer over a connection pool. | src/db/index.js:23 |
watchTokenFile | function | Watch a single legacy flat tokens file and call onChange with its parsed contents on every change; returns { read, stop }. | src/watch.js:17 |
runStyleDictionary | function | Runs a Style Dictionary build after tokens change. | src/transform.js:13 |
openTokenPR | function | Creates a branch with the updated token file(s) and opens a PR. | src/github.js:32 |
createServer
const createServer = (
Build the Hono bridge app. All preview/verify state lives in the injected store (see storeInterface) — this module keeps NO in-memory Maps and no prune timers (pruning now lives in the store). Id generation (nanoid(8)) stays here; the store never mints ids. ## Two modes — gated entirely on config.databaseUrl LOCAL mode (config.databaseUrl UNSET): behaves EXACTLY as the free local bridge always has — no auth, open CORS ('*' or CORS_ORIGINS), in-memory store, all routes open and un-tenant-scoped. Zero new behavior is registered. HOSTED mode (config.databaseUrl SET): the bridge reads sorb-cloud's shared Postgres for auth + entitlements: - scoped CORS sourced from the project's allowed_origins, - a Bearer API key is required on every route except /health + /ready, - publishable keys are read-only ONLY for POST /verify and POST /tokens/figma (403 read_only, via requireWrite) — every other route, including the /preview writes, accepts any authenticated key, since the account-pairing front door mints a read-only key for the Figma plugin and previews are ephemeral/non-destructive (requirePreviewWrite is a deliberate no-op; see its comment below), - entitlements are enforced (maxActivePreviews → 402, preview TTL, sharing-gated actions → 402), with past_due/canceled orgs degraded to Free.
| Parameter | Type | Description |
|---|---|---|
options | Object | |
options.store | import('./types').Store | Required. A Store instance from src/store/index.js. All preview + verify handlers delegate to it (awaited). |
options.config | import('./types').Config | Required. Config from src/config.js. Supplies namespace (surfaced in /health), corsOrigins, databaseUrl (the hosted-mode switch) and the TTL/prune windows the store honors. |
[options.db] | import('./types').DbHandle | null | Optional Postgres handle (or null in local mode). /ready pings it only when it is non-null. In hosted mode it is the source for auth + entitlements + the per-project active-preview count, and MUST be non-null. |
[options.getLatestTokens] | () => import('./types').TokenSet | Latest committed tokens for /tokens/latest. Defaults to () => ({}). |
[options.getResolvedTokens] | () => (Array<{id:string,cssVar:string,value:string,tier:string,type:string}> | null) | Resolved bindable token map (.sorb/resolved.json), or null. Defaults to () => null. |
[options.getArtifactIndex] | () => (object | null) | Captured-component index (.sorb/index.json), or null. Defaults to () => null. |
[options.getArtifact] | (storyId: string) => (object | null) | Artifact JSON for a story id (looked up via the index — never a raw path), or null. Defaults to () => null. |
[options.onError] | (err: unknown, context?: Record<string, unknown>) => void | Optional error sink for the DB-error / best-effort catch paths. Threaded in (dependency-injection style, mirroring db/store) so this module stays Sentry-import-free and fake-db testable. Defaults to a no-op; cli.js serve passes captureError from src/sentry.js (itself a no-op when SENTRY_DSN is unset). The test harness omits it, so capture stays a no-op under node --test. |
Returns import('hono').Hono: the Hono app (synchronous — /ready does its async backend checks per-request).
createStore
const createStore = async (config)
Create the appropriate Store for the given config.
| Parameter | Type | Description |
|---|---|---|
config | import('../types').Config |
Returns Promise<import('../types').Store>.
createMemoryStore
const createMemoryStore = (config)
Create the in-memory Store.
| Parameter | Type | Description |
|---|---|---|
config | import('../types').Config |
Returns import('../types').Store.
loadConfig
const loadConfig = (env = process.env)
Load the bridge configuration from env (defaults to process.env), applying the safe local defaults from DEFAULTS. The result is deeply frozen so downstream units (store, db, server) can treat it as immutable. Local mode (no REDIS_URL / DATABASE_URL) yields: { redisUrl: undefined, databaseUrl: undefined, hosted: false, ... } which keeps the in-memory store + no-DB path active.
| Parameter | Type | Description |
|---|---|---|
[env] | NodeJS.ProcessEnv | Environment source. Defaults to process.env. |
Returns import('./types').Config.
createDb
async function createDb(config)
Create the Postgres durable layer over a connection pool.
| Parameter | Type | Description |
|---|---|---|
config | import('../types.js').Config | Loaded config (env + sorb.config.json). |
Returns Promise<import('../types.js').DbHandle \| null>: A DbHandle when config.databaseUrl is set; null in local mode (no Postgres).
watchTokenFile
const watchTokenFile = (tokenPath, onChange)
Watch a single legacy flat tokens file and call onChange with its parsed contents on every change; returns { read, stop }.
| Parameter | Type | Description |
|---|---|---|
tokenPath | string | |
onChange | (tokens: import('./types').TokenSet) => void |
Returns Watcher.
runStyleDictionary
const runStyleDictionary = (configPath)
Runs a Style Dictionary build after tokens change. Only runs if a config file exists — non-fatal if it doesn't.
| Parameter | Type | Description |
|---|---|---|
configPath | string |
Returns boolean.
openTokenPR
const openTokenPR = async (opts)
Creates a branch with the updated token file(s) and opens a PR. Called by the CLI when the designer hits "commit" in the Figma plugin.
| Parameter | Type | Description |
|---|---|---|
opts | CommitOptions |
Returns Promise<string>.
sorb mcp — Model Context Protocol server
sorb mcp runs an MCP server over stdio that exposes a project's resolved
tokens and captured component bindings to an MCP-capable agent (Claude Code,
Cursor, Copilot). It reads the local .sorb/ map — no sorb dev needed for
the read tools.
Read tools: list_tokens, get_token, resolve_value, list_components,
get_component_bindings, find_token_usages, find_components_by_token.
Gated write tools — proposal-only, never auto-apply: propose_token_change
(a diff + blast-radius, plus a live preview when --bridge is set),
rebind_component (proposes a binding change), open_pr (opens a token PR
when GitHub credentials are configured). Each returns a proposal, a preview
reference, or a PR URL — accepting or merging stays a human step.
{
"mcpServers": {
"sorb": { "command": "sorb", "args": ["mcp", "--dir", "/path/to/your/app"] }
}
}
With no --bridge/--gh-* flags set, the read tools work fully and the
write tools return a "config needed" message instead of failing silently.
sorb mcp --http runs the hosted HTTP/SSE variant instead (auth + tenant
scoping; requires DATABASE_URL) — see the CLI table above for its options.
Types
JSDoc typedefs. Import them in your own JSDoc with import('@sorb/juice').Name.
TokenSet
A flat map of token name → value.
type TokenSet = Object.<string, string | number>
Source: src/types.js:4
PreviewEntry
| Property | Type | Description |
|---|---|---|
tokens | TokenSet | |
createdAt | number |
Source: src/types.js:9
SorbCliConfig
| Property | Type | Description |
|---|---|---|
namespace | string | Matches the namespace in your SorbProvider config. |
tokenSources (optional) | string[] | DTCG token source files to watch (re-runs Style Dictionary on change). Preferred over tokenPath for the 3-tier taxonomy. |
tokenPath (optional) | string | Legacy: path to a single flat tokens.json. If present, served at /tokens/latest; otherwise that endpoint derives a flat map from the SD-built .sorb/resolved.json. Also used as a fallback watch source. |
styleDictionaryConfig (optional) | string | Optional path to style-dictionary config — runs build on startup + token change. |
port (optional) | number | Port for the local server. Defaults to 7777. |
appUrl (optional) | string | Optional. The running app page the preview opens (e.g. http://localhost:5173). Read by sorb handshake to assemble an invite; blank/absent is fine. Additive, back-compatible — configs without it still work. |
gh (optional) | string | Optional. GitHub edit URL of the tokens file (the Open-PR target) baked into a handshake invite. When absent, sorb handshake derives it from git remote. |
figmaFileKey (optional) | string | Optional. The Figma file key this project's Variables live in. Informational only — surfaced by GET /verify/figma as configuredFileKey, never enforced. The SORB_FIGMA_FILE_KEY env var takes precedence when both are set. |
Source: src/types.js:15
Config
12-factor runtime config produced by loadConfig() (src/config.js), merged with sorb.config.json. Shared contract between the config, store, db and server units.
| Property | Type | Description |
|---|---|---|
port | number | HTTP listen port (default 7777). |
namespace | string | Tenant/app namespace surfaced in /health. |
redisUrl | string | undefined | redis: URL. Presence switches the store factory to the Redis impl; undefined → in-memory store. |
databaseUrl | string | undefined | postgres: URL. Presence makes createDb build a Pool; undefined → no DB (local mode). |
corsOrigins | string[] | '*' | Allowed CORS origins, or '*' (open). |
allowedWriteOrigins (optional) | string[] | Extra origins permitted to make cross-site WRITES to /preview* in LOCAL mode (on top of the built-in localhost/127.0.0.1 + Figma allowlist). Used by the P0.3b CSRF guard. |
previewTtlMs | number | TTL for previews + verifications, in ms. |
pruneIntervalMs | number | In-memory prune interval, in ms. |
sseHeartbeatMs (optional) | number | Interval between ping frames on GET /preview/:id/events (E1). Defaults to 20_000 in server.js when unset — not currently sourced from env, just an injectable override for tests. |
figmaFileKey | string | undefined | Optional Figma file key (SORB_FIGMA_FILE_KEY) this project's Variables live in. Informational only — surfaced by GET /verify/figma, never enforced. |
Source: src/types.js:42
VerificationEntry
A stored verification entry (post-layout geometry self-reported by the Figma plugin), as returned by the store.
| Property | Type | Description |
|---|---|---|
storyId | string | |
bbox | object | |
meta | object | |
createdAt | number |
Source: src/types.js:67
FigmaExportedToken
A single Figma-exported token, matching @sorb/core's ResolvedToken shape (the same one returned by GET /tokens/resolved).
| Property | Type | Description |
|---|---|---|
id | string | Dotted token id (e.g. "color.action.primary"). |
cssVar | string | CSS custom-property name (e.g. "--color-action-primary"). |
value | string | |
tier (optional) | string | "primitive" | "semantic" | "component", when known. |
type | string |
Source: src/types.js:77
FigmaArtifact
The latest Figma Variables export POSTed by the plugin (sorb-canopy) via POST /tokens/figma. Stored as a single "latest" snapshot per store instance (no history) — same persistence pattern as previews/verifications above.
| Property | Type | Description |
|---|---|---|
fileKey | string | null | Figma file key the export was taken from, or null. |
exportedAt | string | null | ISO timestamp the plugin recorded at export time, or null. |
tokens | FigmaExportedToken[] | |
receivedAt | number | Server-side receipt timestamp (Date.now()) — juice's own bookkeeping. |
Source: src/types.js:88
Store
The async Store surface shared by the in-memory and Redis impls. The server only ever awaits these methods — it never reaches into internal maps. See the frozen storeInterface contract.
| Property | Type | Description |
|---|---|---|
putPreview | (id: string, tokens: TokenSet) => Promise<void> | |
getPreview | (id: string) => Promise<PreviewEntry | null> | |
hasPreview | (id: string) => Promise<boolean> | |
updatePreview | (id: string, tokens: TokenSet) => Promise<boolean> | |
deletePreview | (id: string) => Promise<void> | |
countPreviews | () => Promise<number> | |
getLatestPreview | () => Promise<({id: string} & PreviewEntry) | null> | The most-recently put/updated preview (LOCAL mode only — this pointer is process-global, not tenant-scoped; hosted mode's GET /preview/latest derives "latest" from the tenant-scoped previews DB table instead and never calls this). Backs the cloud-snapshot dependency (#4b). |
putVerification | (id: string, entry: { storyId: string, bbox: object, meta: object }) => Promise<void> | |
getVerification | (id: string) => Promise<VerificationEntry | null> | |
getLatestVerification | () => Promise<VerificationEntry | null> | |
countVerifications | () => Promise<number> | |
putFigmaArtifact | (artifact: FigmaArtifact) => Promise<void> | |
getFigmaArtifact | () => Promise<FigmaArtifact | null> | |
onUpdate | (id: string, listener: (evt: PreviewUpdateEvent) => void) => (() => void) | Subscribe to put/update/delete events for one preview id. Returns an unsubscribe function. This is the push primitive the SSE route (E1, GET /preview/:id/events) is built on — it replaces the leaf SDK's poll loop. Both the memory and Redis stores implement it (EventEmitter bus vs. Redis pub/sub over a duplicated connection), so callers never know which backend is behind it. |
ping | () => Promise<boolean> | |
close | () => Promise<void> |
Source: src/types.js:99
PreviewUpdateEvent
An event pushed by Store.onUpdate when a preview is created, updated, or deleted. tokens is null for a delete event.
| Property | Type | Description |
|---|---|---|
type | 'put' | 'update' | 'delete' | |
tokens | TokenSet | null |
Source: src/types.js:132
BindingGraph
The in-memory binding-graph index built by buildBindingGraph (src/graph/index.js). READ/COMPUTE only — never mutates source. Lazily built + cached in server.js.
| Property | Type | Description |
|---|---|---|
tokenById | Map<string, {id:string,cssVar:string,value:string|number,tier:string,type:string}> | tokenId → ResolvedToken (the resolved bindable token). |
idsByCssVar | Map<string, string[]> | cssVar → token id(s) that emit it (normally 1:1; union defensively). |
usagesByToken | Map<string, Array<{storyId:string, role:string}>> | tokenId → the (story, role) bindings that render it (the reverse edge set). |
stories | Map<string, {storyId:string, component:string|undefined, name:string|undefined, bindings:Array<{role:string, tokenId:string}>}> | storyId → flattened bindings across that story's whole LayerNode tree. |
Source: src/types.js:140
DbHandle
The Postgres durable layer returned by createDb (src/db/index.js), or null in local mode when DATABASE_URL is unset.
| Property | Type | Description |
|---|---|---|
query | (text: string, params?: any[]) => Promise<any> | |
getClient | () => Promise<any> | |
ping | () => Promise<boolean> | |
close | () => Promise<void> | |
runMigrations | () => Promise<void> |
Source: src/types.js:154
Next
- The bridge — what
sorb devdoes and why, in prose. - Hosted vs. local — which key type does what, and what stays on your machine.
@sorb/leaf— the SDK that talks to these routes from your running app.
Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.