@sylphx/contract
v0.5.0
Published
Sylphx Platform contract — Effect Schema SSOT for every API endpoint (ADR-084).
Readme
@sylphx/contract
The Effect Schema SSOT for every Sylphx API endpoint.
Part of ADR-084 — Effect Schema Contract.
Installation
bun add @sylphx/contract effect
# or
npm install @sylphx/contract effect
# or
pnpm add @sylphx/contract effecteffect is a peer dependency — consumers bring their own version.
Why this exists
One contract drives four consumers:
@sylphx/contract
(Effect Schema + endpoint tuples)
│
┌──────────────┬───┴────────────┐
▼ ▼ ▼
apps/api @sylphx/management @sylphx/sdk
(Hono routes) (Management SDK) (BaaS SDK)
│
▼
scripts/generate-openapi.ts
│
▼
openapi.json (derivation)- Hono routes in
apps/apiwire each endpoint tuple throughhono-openapi(Standard Schema adapter). - Published SDKs (
@sylphx/management,@sylphx/sdk) derive their typed request/response signatures here — no hand-written duplicates (ADR-084 Rule 2). Effect types are stripped from the.d.tsat publish time so external consumers see a cleanPromise<T>surface. - OpenAPI 3.1 spec is a derivation via
scripts/generate-openapi.ts, not the source. Advanced cross-language consumers (Python, Go, Rust) get codegen output from the spec.
Quick start
import { Project, endpoints } from '@sylphx/contract'
import { Schema } from 'effect'
// Decode an API payload using Effect Schema directly.
const project = Schema.decodeUnknownSync(Project)(payload)
// Inspect endpoint metadata (method, path, request / response schemas).
endpoints.projects.list.method // 'GET'
endpoints.projects.list.path // '/projects'
endpoints.projects.list.plane // 'management'
endpoints.projects.list.response // Schema.Struct({ data: Schema.Array(Project) })
// Bridge to any Standard Schema v1 consumer (hono-openapi, MCP SDK, …).
const standardProject = Schema.standardSchemaV1(Project)
const result = standardProject['~standard'].validate(payload)
// ^ { value: Project } | { issues: [...] }Design rules
- Effect Schema only. No Zod, no manual TS interfaces, no OpenAPI hand-editing.
- Standard Schema compliant. Every schema value exposes
~standardviaSchema.standardSchemaV1(...)— validator-library swap is free if the bet ever needs unwinding. - Framework-free. No Hono, Next.js, or runtime dependencies. Consumers decide how to wire.
- Branded IDs. Entity IDs (
ProjectId,EnvironmentId, …) are branded strings; mixing across entities is a type error. - Tagged errors. Failures use discriminated unions on
_tag— consumers pattern-match, never string-compare. - Plane-aware. Each endpoint carries a
plane: 'management' | 'baas' | 'admin'hint so the OpenAPI splitter and SDK generator scope correctly. - Agent-hint-aware. Each endpoint may carry
agentHint: string— emitted asx-agent-hintin the OpenAPI derivation for downstream LLM tool-callers.
Structure
src/
endpoint.ts — defineEndpoint() + Endpoint<...> type
errors.ts — ApiError tagged union (400/401/403/404/409/422/429/500)
index.ts — barrel + flat `endpoints` map
schemas/ — one file per entity type (82 files)
endpoints/ — one file per namespace (70 files)Coverage
Today: 70 endpoint groups, 82 schema files, ~745 endpoint definitions across three planes.
Management plane (api.sylphx.com/v1)
Platform-owner ops consumed by Console, CLI, CI integrations, and service-token automation (Bearer slx_* user / sk_* service).
projects, deployments, environments, databases, envVars, domains, organizations, secrets, users, ciSettings, github, billingSettings, serviceTokens, serviceTokenRequests, runners, backups, webhooks, tasks, storage, storageAdmin, realtime, realtimeAdmin, saml, oidc, security, monitoring, monitoringAdmin, analytics, analyticsAdmin, flags, flagsAdmin, experiments, newsletter, referralsAdmin, engagementAdmin, aiAdmin, search, sessionReplay, email, emailAdmin, notifications, notificationsAdmin, consentAdmin, privacy, plans, billingConsole.
BaaS plane (<tenant-slug>.api.sylphx.com/v1, ADR-083.1)
Runtime data-plane verbs consumed by deployed applications (sk_* + end-user JWT or pk_* + RLS).
auth, refresh, challenge, kv, kvAdmin, email (transactional send), notifications (push / in-app), flags.evaluate, realtime (pub/sub), storage, search.
Admin plane
Operator-only Console routes that customers never consume.
adminAiPlayground, adminAnomalies, adminApm, adminAudit, adminBilling, adminBroadcasts, adminConfig, adminConsent, adminImpersonation, adminInvitations, adminJwtKeys, adminLogs, adminPlans, adminProjects, adminProjectUsers, adminQuotas, adminTasks, adminTraces, adminUsers.
Defining a new endpoint
// packages/contract/src/endpoints/tasks.ts
import { Schema } from 'effect'
import { defineEndpoint } from '../endpoint.js'
import { Task, TaskId } from '../schemas/task.js'
export const tasksEndpoints = {
list: defineEndpoint({
method: 'GET',
path: '/tasks',
response: Schema.Struct({ data: Schema.Array(Task) }),
summary: 'List tasks across the current org.',
tags: ['tasks'],
plane: 'management',
agentHint: 'Call this before triggering a new run to avoid duplicate work.',
}),
create: defineEndpoint({
method: 'POST',
path: '/tasks',
body: Schema.Struct({ name: Schema.String }),
response: Task,
plane: 'management',
}),
get: defineEndpoint({
method: 'GET',
path: '/tasks/:id',
params: Schema.Struct({ id: TaskId }),
response: Task,
plane: 'management',
}),
} as constRegister the group in src/index.ts and every downstream consumer (API, both SDKs, OpenAPI spec) picks it up. CI (scripts/check-contract-first.ts) rejects net-new @hono/zod-openapi routes and hand-written SDK types.
Subpath imports
Tree-shake a single entity schema or endpoint group without pulling the entire barrel:
import { Project, ProjectId } from '@sylphx/contract/schemas/project'
import { projectsEndpoints } from '@sylphx/contract/endpoints/projects'Related packages
@sylphx/management— Promise client for the Management plane, types derived from this contract.@sylphx/sdk— runtime BaaS SDK for deployed apps.@sylphx/cli— interactive CLI built on@sylphx/management.
License
MIT — see LICENSE.
