@franklabs/platform-sdk
v0.1.2
Published
Frank Labs platform sdk: the shared cookie contract, RPC wrappers, error mapping and generated database types every client app on *.apps.franklabs.io uses
Readme
@franklabs/platform-sdk
The shared contract between the Frank Labs platform (apps.franklabs.io) and
every client app on a *.apps.franklabs.io subdomain. One cookie contract, one
set of RPC wrappers, one error mapping, one set of generated database types.
pnpm add @franklabs/platform-sdkWhy it exists
A client app is a separate repo and a separate Vercel project, but it is not a separate security domain: it reads the same Supabase project through the same signed-in session, scoped by the same row-level security. That only works if the cookie names, options and domain are byte-identical on both sides. This package is that "both sides", so neither can drift.
The five exports a client app actually uses
| Export | What it does |
|---|---|
| createMiddlewareClient(url, key, env, req, res) | The client for proxy.ts. Reads cookies off the request, writes refreshed ones (and @supabase/ssr's Cache-Control: no-store headers) onto the response. |
| createServerClient(url, key, env, store) | The client for server components and server actions. Same cookie options; writes go through the store you hand it. |
| currentBusiness(supabase, businessId) | The signed-in user's membership in that business — null when they are not a member, which is the app's cue to 403. |
| appEnabled(supabase, businessId, appSlug) | Substrate §6 rule 4. false for a non-member, because row-level security hides the row rather than raising. |
| can(supabase, businessId, permission) | One permission check, evaluated in the database. Never trust a copy held in the app. |
Also exported: PlatformClient (the typed Supabase client, so an app never
imports @supabase/* even for a type), PlatformError / mapDbError (the
four-code error contract: forbidden, not_found, conflict, unknown),
CHANNEL_KINDS / kindsForChannel, parseCookieHeader / serializeCookies /
forwardedCookieStore, listConnections / listConnectionsResult, and
SDK_VERSION.
The rule that has no exception
No client app ever holds a secret key. There is no service_role key, no
sb_secret_* value, and no supabase/ directory in a client app repo — only
the two NEXT_PUBLIC_SUPABASE_* values, which are public by design. Anything
that needs a secret runs on the platform deploy and is reached over HTTP with
the user's own session forwarded. The template's scripts/contract-lint.sh and
scripts/secret-grep.sh fail the build if that ever stops being true.
The full contract is substrate spec §6 (docs/superpowers/specs/2026-08-31-platform-substrate-design.md
in the super-intelligence repo).
