@sylphx/sdk
v0.9.0
Published
Sylphx SDK - State-of-the-art platform SDK with pure functions
Readme
@sylphx/sdk
Auth, billing, analytics, AI, storage, and more — in one SDK.
📖 Full docs: sylphx.com/docs
Installation
npm install @sylphx/sdk
# or
pnpm add @sylphx/sdk
bun add @sylphx/sdkQuick Start (Next.js)
1. Environment Variables
Your server connection URL and environment app ID from your Platform Console:
# .env.local
SYLPHX_URL=sylphx://sk_dev_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@bold-river-a1b2c3.api.sylphx.com
NEXT_PUBLIC_SYLPHX_APP_ID=app_dev_xxxxxxxxxxxxThat's it. No other config needed.
Key formats
sylphx://sk_*@<tenant-slug>.api.sylphx.com— Server connection URL (server only, never expose)app_dev_*/app_stg_*/app_prod_*— App ID (safe for client-side)Get both from Console → Your App → API Keys. The hosted BaaS API is always addressed as
<tenant-slug>.api.sylphx.com.
2. Middleware
Handles auth routes (/auth/callback, /auth/signout) and route protection automatically.
// middleware.ts
import { createSylphxMiddleware } from '@sylphx/sdk/nextjs'
export default createSylphxMiddleware({
publicRoutes: ['/', '/about', '/pricing', '/login'],
})
export const config = {
matcher: ['/((?!_next|.*\\..*).*)', '/'],
}No manual /api/auth/* routes needed — the middleware handles everything.
3. Root Layout
Fetch config server-side once, pass to the provider:
// app/layout.tsx
import { getAppConfig } from '@sylphx/sdk/server'
import { SylphxProvider } from '@sylphx/sdk/react'
import { createServerClient } from '@sylphx/sdk'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const sylphx = createServerClient(process.env.SYLPHX_URL!)
const apiUrl = sylphx.baseUrl.replace(/\/v[0-9]+$/, '')
const config = await getAppConfig({
secretKey: sylphx.secretKey!,
appId: process.env.NEXT_PUBLIC_SYLPHX_APP_ID!,
platformUrl: apiUrl,
})
return (
<html>
<body>
<SylphxProvider
config={config}
appId={process.env.NEXT_PUBLIC_SYLPHX_APP_ID!}
platformUrl={apiUrl}
>
{children}
</SylphxProvider>
</body>
</html>
)
}4. Protect Pages (Server Components)
// app/dashboard/page.tsx
import { currentUser } from '@sylphx/sdk/nextjs'
import { redirect } from 'next/navigation'
export default async function Dashboard() {
const user = await currentUser()
if (!user) redirect('/login')
return <h1>Hello, {user.name}</h1>
}5. Auth UI (Client Components)
'use client'
import { useUser, SignedIn, SignedOut, UserButton } from '@sylphx/sdk/react'
export default function Header() {
const { user } = useUser()
return (
<header>
<SignedOut>
<a href="/login">Sign in</a>
</SignedOut>
<SignedIn>
<span>Hello, {user?.name}</span>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</header>
)
}Server-Side
Get Current User
import { auth, currentUser, currentUserId } from '@sylphx/sdk/nextjs'
// Full auth state
const { userId, user, sessionToken } = await auth()
// Just the user object (null if not signed in)
const user = await currentUser()
// Just the user ID
const userId = await currentUserId()Server API Client
import { createServerClient, getPlans, track } from '@sylphx/sdk'
const sylphx = createServerClient(process.env.SYLPHX_URL!)
// Billing
const plans = await getPlans(sylphx)
// Analytics
await track(sylphx, { event: 'purchase', properties: { amount: 99 } })Prefetch App Config
import {
getAppConfig, // All config in one call (recommended)
getPlans, // Billing plans
getFeatureFlags, // Feature flag definitions
getConsentTypes, // GDPR consent config
} from '@sylphx/sdk/server'
import { createServerClient } from '@sylphx/sdk'
const sylphx = createServerClient(process.env.SYLPHX_URL!)
const config = await getAppConfig({
secretKey: sylphx.secretKey!,
appId: process.env.NEXT_PUBLIC_SYLPHX_APP_ID!,
platformUrl: sylphx.baseUrl.replace(/\/v[0-9]+$/, ''),
})
// config.plans, config.featureFlags, config.oauthProviders, config.consentTypesVerify Webhooks
import { verifyWebhook } from '@sylphx/sdk/server'
export async function POST(request: Request) {
const body = await request.text()
const result = await verifyWebhook({
payload: body,
signatureHeader: request.headers.get('x-webhook-signature'),
secret: process.env.SYLPHX_SECRET_KEY!,
})
if (!result.valid) {
return new Response('Unauthorized', { status: 401 })
}
const { event, data } = result.payload!
// handle event...
return Response.json({ received: true })
}Or use the handler factory:
import { createWebhookHandler } from '@sylphx/sdk/server'
export const POST = createWebhookHandler({
secret: process.env.SYLPHX_SECRET_KEY!,
handlers: {
'user.created': async (data) => { /* ... */ },
'subscription.updated': async (data) => { /* ... */ },
},
})JWT Verification
import { verifyAccessToken } from '@sylphx/sdk/server'
const payload = await verifyAccessToken(token, {
secretKey: process.env.SYLPHX_SECRET_KEY!,
})
// payload.sub, payload.email, payload.role, payload.app_idReact Hooks
Auth
import { useUser, useAuth } from '@sylphx/sdk/react'
const { user, isLoading, isSignedIn } = useUser()
const { signIn, signUp, signOut, forgotPassword } = useAuth()
await signIn({ email: '[email protected]', password: '...' })
await signOut()Billing
import { useBilling } from '@sylphx/sdk/react'
const { subscription, isPremium, plans, createCheckout, openPortal } = useBilling()
// Check access
if (!isPremium) return <UpgradePrompt />
// Start checkout
const url = await createCheckout('pro', 'monthly')
window.location.href = url
// Manage subscription
await openPortal()Analytics
import { useAnalytics } from '@sylphx/sdk/react'
const { track, identify, page } = useAnalytics()
track('button_clicked', { button: 'upgrade' })
identify({ name: 'John', email: '[email protected]' })Feature Flags
import { useFeatureFlag } from '@sylphx/sdk/react'
const { isEnabled } = useFeatureFlag('new-dashboard')
if (isEnabled) return <NewDashboard />AI
import { useChat, useCompletion } from '@sylphx/sdk/react'
const { messages, send, isLoading } = useChat({
model: 'anthropic/claude-3.5-sonnet',
})
await send('What is the meaning of life?')Storage
import { useStorage } from '@sylphx/sdk/react'
const { upload, uploadAvatar, isUploading, progress } = useStorage()
const url = await upload(file, { path: 'documents/' })
const avatarUrl = await uploadAvatar(imageFile)More Hooks
import { useConsent } from '@sylphx/sdk/react' // GDPR consent
import { useFeatureFlags } from '@sylphx/sdk/react' // All flags at once
import { useNotifications } from '@sylphx/sdk/react' // In-app notifications
import { useReferral } from '@sylphx/sdk/react' // Referral program
import { useOrganization } from '@sylphx/sdk/react' // Multi-tenant orgs
import { useJobs } from '@sylphx/sdk/react' // Background jobs
import { useErrorTracking } from '@sylphx/sdk/react' // Error captureUI Components
Auth
import { SignIn, SignUp, UserButton, SignedIn, SignedOut } from '@sylphx/sdk/react'
<SignedOut><SignIn mode="embedded" afterSignInUrl="/dashboard" /></SignedOut>
<SignedIn><UserButton afterSignOutUrl="/" /></SignedIn>Billing
import { PricingTable, CheckoutButton } from '@sylphx/sdk/react'
<PricingTable plans={plans} />
<CheckoutButton planSlug="pro" interval="monthly">Upgrade</CheckoutButton>Route Protection
import { Protect } from '@sylphx/sdk/react'
<Protect role="admin">
<AdminPanel />
</Protect>Pure Functions (Server or Client)
For non-React environments or maximum control:
import { createClient, signIn, track, getPlans } from '@sylphx/sdk'
const config = createClient(process.env.SYLPHX_URL!)
// Auth
const tokens = await signIn(config, { email, password })
const authedConfig = withToken(config, tokens.accessToken)
// Analytics
await track(config, { event: 'purchase', properties: { amount: 99 } })
// Billing
const plans = await getPlans(config)Entry Points
| Import path | Use for |
|---|---|
| @sylphx/sdk | Pure functions (server or client, no React) |
| @sylphx/sdk/react | React hooks, components, SylphxProvider |
| @sylphx/sdk/server | JWT verification, webhook verification, server client |
| @sylphx/sdk/nextjs | createSylphxMiddleware, auth(), currentUser() |
| @sylphx/sdk/web-analytics | Standalone web-analytics tracker (rrweb + web-vitals) |
| @sylphx/sdk/health | Multi-signal health score for the sylphx-health-agent sidecar (ADR-111 Phase B) |
@sylphx/sdk/health — Phase B health score (ADR-111)
Apps register signals (event-loop lag, queue depth, error rate, memory pressure)
and the SDK folds them into a continuous score in [0, 1]. The
sylphx-health-agent sidecar polls the score and decides liveness /
readiness / drain via the three-tier gate. See
src/health/README.md for the full guide.
import { sylphxHealth, eventLoopLagSignal, queueDepthSignal } from '@sylphx/sdk/health'
const health = sylphxHealth({
signals: [
eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 }),
queueDepthSignal({ getter: () => queue.size, fullThreshold: 1000 }),
],
})
app.get('/healthz', health.handler())
// Or — Unix-socket transport for the sidecar:
health.serveUnixSocket() // → /var/run/sylphx/health.sockTypeScript
All types are fully inferred. Import them directly:
import type { User, Plan, Subscription, AppConfig } from '@sylphx/sdk'
import type { AuthResult } from '@sylphx/sdk/nextjs'Self-Hosting / Custom Deployment
If you're running your own Sylphx Platform deployment, configure the base URL via the CLI:
SYLPHX_API_URL=https://platform.your-domain.com sylphx deployOr in the SDK via an explicit custom-domain connection URL:
import { createServerClient } from '@sylphx/sdk'
const config = createServerClient(
'sylphx://sk_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@acme.api.example.com',
)Note: hosted Sylphx uses
<tenant-slug>.api.sylphx.com. Only use a custom host for self-hosted deployments or a documented legacy migration.
Architecture — contract-first
This SDK is part of the two-plane architecture (ADR-083) with a single source of truth (ADR-084):
@sylphx/contract(Effect Schema) drives the types you see here. The types are not hand-written and not separately maintained — add an endpoint to the contract and both this SDK and the sibling@sylphx/managementinherit it. Hand-written shims are a bug.- Promise surface, Effect-free. Internally
@sylphx/contractis Effect Schema, but the published.d.tsis stripped of Effect imports (strip-at-publish CI guard). You get cleanPromise<User>/Promise<Plan[]>signatures with zero Effect dependency in your bundle. - Standard Schema compliant. Every input schema in the contract exposes the
~standardinterface — compatible with Zod, Valibot, ArkType, TypeBox, Effect Schema, and any future Standard-Schema-compliant validator. - Agent surface via the CLI. The
sylphxCLI is the first-class agent entry point (Claude Code, Cursor, ChatGPT Apps). If you want a custom MCP, compose it on top of@sylphx/managementyourself — Sylphx does not ship a standalone stdio MCP binary.
See ADR-084 for the full design rationale.
