shipdit
v0.2.1
Published
shipd's typescript sdk
Readme
shipdit
TypeScript SDK for shipd feature flags.
Install
pnpm add shipditServer
import { createClient } from 'shipdit';
import type { FlagDefinitions } from './shipd.types';
const client = createClient<FlagDefinitions>({
sdkKey: process.env.SHIPD_SDK_KEY!,
endpoint: process.env.SHIPD_EDGE_URL!,
defaults: {
'new-checkout': false,
},
context: { userId: 'server' },
});
await client.ready();
if (client.isEnabled('new-checkout', { userId: user.id })) {
// ...
}
client.close();Browser
import { createClient } from 'shipdit/client';
const client = createClient({
sdkKey: 'shipd_sdk_client_...',
endpoint: 'https://edge.example.com',
// persists last-good snapshot to localStorage by default
});
await client.ready();Evaluation never throws. If the snapshot is unavailable, configured defaults are returned.
Polling + realtime
- Initial snapshot over
GET /sdk/v1/snapshot(ETag-aware). - Optional WebSocket to
streamEndpoint(/sdk/v1/stream) forsnapshot.updated/snapshot.resyncinvalidation, then the same ETag HTTP fetch. - Polling continues as a fallback (
refreshIntervalMs, default 30s). SetrefreshIntervalMs: 0to disable polling after the initial fetch.
If the WebSocket drops, the SDK reconnects with exponential backoff + jitter
(1s → 2s → 4s → … → max 60s, reset after a successful connect) and keeps
serving the last known snapshot while polling remains active.
Version policy (Phase 4 — full resync only):
- Stream jump
10 → 15fetches the current snapshot (no assumed 11–14 replay) - After a notify, HTTP is retried briefly while
version < notified(KV lag) - HTTP bodies with
version < localare ignored (no rollback from delayed responses) - Unsupported
schemaVersionis rejected withUnsupported snapshot schema version N; SDK supports version 1and the last valid snapshot (or defaults) is kept - Omit
streamEndpointto disable WebSocket and rely on polling only
Compatibility constants (also on VERSIONS):
| Constant | Meaning |
| ----------------------------- | --------------------------- |
| SNAPSHOT_SCHEMA_VERSION | Flag snapshot envelope |
| SDK_STREAM_PROTOCOL_VERSION | WebSocket frame protocol |
| SDK_PACKAGE_VERSION | Published shipdit package |
| CONTROL_PLANE_API_VERSION | Control-plane /api/v{n} |
const client = createClient({
sdkKey: process.env.SHIPD_SDK_KEY!,
endpoint: process.env.SHIPD_EDGE_URL!,
streamEndpoint: process.env.SHIPD_WS_URL, // e.g. http://localhost:8100
});Per-request HTTP fetches time out after fetchTimeoutMs (default 10s).
Events
Batched to POST /sdk/v1/events (evaluations, errors, unregistered flags, version skew, identify).
The edge accepts and logs them; identify events upsert evaluated_entities asynchronously.
Pass events: false to disable.
Identify
client.identify({ userId: 'user_123', traits: { plan: 'pro' } });
client.reset(); // logout — clears local identityReact
Bind hooks once in a client-only module. Typegen stays types-only so server
code can import FlagDefinitions without pulling React.
// src/lib/shipd.ts
import { createShipdReact } from 'shipdit/client/react';
import type { FlagDefinitions } from './generated/flags';
export const { ShipdProvider, useFlag, useVariant, useReady } = createShipdReact<FlagDefinitions>();import { createClient } from 'shipdit/client';
import { ShipdProvider, useFlag } from './lib/shipd';
const client = createClient<FlagDefinitions>({
sdkKey: 'shipd_sdk_client_...',
endpoint: 'https://edge.example.com',
});
function Checkout() {
const enabled = useFlag('new-checkout');
return enabled ? <NewCheckout /> : <OldCheckout />;
}
export function App() {
return (
<ShipdProvider client={client}>
<Checkout />
</ShipdProvider>
);
}useFlag / useVariant / useNumber / useJson re-render when the snapshot
updates or identify / reset changes the evaluation context.
