@pieai/swimmer-backend-client
v0.7.2
Published
Thin TypeScript client for shared SwimmerBackend authentication, wallet, and entitlement-grant contracts.
Downloads
814
Readme
@pieai/swimmer-backend-client
A deliberately small TypeScript client for stable SwimmerBackend authentication, wallet, and entitlement-grant contracts. It accepts a Supabase-compatible client supplied by the product and never embeds project URLs or keys.
pnpm add @pieai/[email protected]import { createAuthClient } from '@pieai/swimmer-backend-client';
const auth = createAuthClient(supabase);
const session = await auth.signInAnonymously();Server routes that receive a bearer token can verify it through the same shared identity contract without decoding or returning the token:
import { verifyAccessToken } from '@pieai/swimmer-backend-client';
const user = await verifyAccessToken(serverSupabase, accessToken);
if (!user) throw new Error('Invalid access token');The supplied provider must implement auth.getUser(accessToken). Provider
errors are surfaced so the product can choose its own 401/503 policy; a missing
or malformed user returns null.
For an authenticated API request, read the current access token immediately before sending the request so Supabase can refresh its session when needed:
const accessToken = await auth.getAccessToken();
if (!accessToken) throw new Error('Authentication required');
const response = await fetch('/api/example', {
headers: { Authorization: `Bearer ${accessToken}` },
});Do not log, persist, or copy the token into application session state. The
public AuthSessionRecord intentionally contains only the user identity; the
client never exposes refresh tokens or the provider's raw session.
Email sign-in, sign-up, magic-link requests, and anonymous-account linking are separate explicit methods. A magic-link request must receive the product's allow-listed redirect URL. The package never turns one failed action into a different account action; products own that UX policy.
Server-authoritative products can use the additive wallet subpath without
reimplementing RPC names or parsing Postgres bigint responses:
import { createWalletClient } from '@pieai/swimmer-backend-client/wallet';
const wallet = createWalletClient(serverSupabase, 'anvil');
const reservation = await wallet.reserve({
amountPowerUnits: '250',
idempotencyKey: 'run:123:reserve',
userId,
});After a product payment adapter has confirmed a provider-side refund, dispute, or payment reversal, it can reverse an exact committed top-up source. A balance shortfall is explicit and does not consume reserved funds:
const reversal = await wallet.reverseGrant({
amountPowerUnits: '250',
idempotencyKey: 'refund:provider-event-id:attempt:1',
reason: 'refund',
sourceGrantId,
});Do not pass raw webhook states directly to this method. Provider signature verification, event ordering, pending/failed refunds, and dispute reinstatement remain the payment adapter's responsibility.
Products can read a server-selected current plan grant through the additive
entitlements subpath. The product owns the RPC name and maps planId to its
own feature policy; this reader never exposes grant-history metadata or a write
method:
import { createEntitlementClient } from '@pieai/swimmer-backend-client/entitlements';
const entitlements = createEntitlementClient(
serverSupabase.schema('university'),
'university_read_plan_grant',
);
const grant = await entitlements.readGrant(userId);
// { planId: 'member', validFrom: '...', validUntil: '...' }The RPC must be protected by the product's authenticated-user boundary. Grant issuance remains a trusted server/payment-fulfillment operation, never a browser call.
The facade does not create a Supabase client or manage a service-role key. Use it only from an already trusted server boundary. Never put credentials, prompts, generated content, or provider responses in wallet metadata.
Shared avatar profile
createProfileClient from @pieai/swimmer-backend-client/profiles reads the
current user's core.profiles avatar and sends explicit updates through the
Backend-owned profile-avatar gateway. This release candidate is not yet
published or deployed. Products must not copy this implementation or install an
unpublished replacement in production.
The gateway verifies the user, fully validates the published AvatarKit recipe,
and derives its compact portrait. It never accepts an owner ID from the browser.
Updates carry an operation UUID and the exact updatedAt string from the last
read; preserve microseconds rather than rebuilding that value with Date.
Repeated operations are reconciled; stale writes do not replace newer profiles.
Supabase PostgreSQL TLS
Node services can import supabasePostgresTls from @pieai/swimmer-backend-client/postgres-tls
and pass its options as the driver's ssl configuration. The helper pins the public
Supabase Root 2021 CA selected by the official production dashboard, retains certificate
and hostname verification, and requires TLS 1.2 or newer. It owns no connection, password,
role or user context. Products must still verify the expected project and use an isolated
runtime LOGIN. Do not replace it with rejectUnauthorized: false or ssl: 'require'.
Source: Supabase's apps/studio/hooks/custom-content/custom-content.json at
26585dd4a4d6db8910a595214c9f6e8fdd206768, and the public certificate URL documented in
postgres-tls.ts. The certificate expires in April 2031; vendor rotation requires a reviewed
package update. This public trust anchor is not a credential or copied application code.
This shared owner is independent of University progress and product work ownership.
Deploy the two profile RPCs and the configured gateway before consuming this
subpath. SWIMMER_PROFILE_AVATAR_ORIGINS and SWIMMER_PROFILE_AVATAR_APPS are
explicit allowlists; missing configuration fails closed. The platform JWT check
stays enabled and the gateway independently verifies the user through Auth.
Product schemas, game rooms, seats, pricing policy, prompts, and UI state do not
belong in this package. They remain owned by their product repositories. The
commercial subpath contains transport types only; it does not calculate fees
or choose a payment provider.
