docs: README, CHANGELOG and skill for @rudder/sdk 0.1.0 with generated module clients
This commit is contained in:
@@ -1,30 +1,31 @@
|
||||
---
|
||||
name: rudder-web-sdk
|
||||
description: Use when working with the Rudder TypeScript/JavaScript SDK (@rudder/js-sdk) — the browser/player-facing SDK for the Rudder LiveOps platform. Covers client setup, device/google/apple/custom auth, identity linking, observable kernel domains (player, catalog, storage), modules.call, resources(slug), and error handling. Load this whenever code imports from @rudder/js-sdk or RudderClient appears.
|
||||
description: Use when working with the Rudder TypeScript/JavaScript client SDK (@rudder/sdk) for web games. Covers createClient, device/google/apple/custom auth, identity linking, state objects (player with wallets and counters, catalog, storage), module calls through clients generated by `rudder client generate`, idempotent retries, and error handling. Load this whenever code imports from @rudder/sdk or a generated rudder.modules.ts.
|
||||
---
|
||||
|
||||
# Rudder Web SDK (@rudder/js-sdk)
|
||||
# Rudder Web SDK (@rudder/sdk)
|
||||
|
||||
Browser-facing player SDK for the Rudder LiveOps platform. Source of truth:
|
||||
`liveops-web-sdk/src` (generated wire types in `src/generated` come from
|
||||
`liveops-backend/apigen` — never edit them by hand).
|
||||
Client SDK for web games on the Rudder LiveOps platform. Source of truth:
|
||||
`liveops-web-sdk/src` (wire types in `src/generated` come from
|
||||
`liveops-backend/apigen`; never edit them by hand).
|
||||
|
||||
2.0 kernel surface: auth, player (wallets), catalog currencies, storage,
|
||||
project storage, revision sync, `client.modules.call(module, fn, args)`,
|
||||
and `client.resources(slug)`. Module-specific methods (battle pass, quests,
|
||||
leaderboards, stores, inventory, remote config, scenarios) are gone.
|
||||
0.1.0 covers only the kernel: auth, player, catalog, storage, module calls and
|
||||
internal revision sync. Game features are installed modules, reached through a
|
||||
typed client generated from their manifests. There is no `resources(slug)` and
|
||||
no `projectStorage`; game-facing configuration comes from modules such as
|
||||
`remote_config`.
|
||||
|
||||
## Install
|
||||
|
||||
Published to a private registry, not npmjs. Point the `@rudder` scope at it in
|
||||
`.npmrc` (anonymous read, no token):
|
||||
Published to the Gitea registry at hub.rudder.build, not npmjs. Point the
|
||||
`@rudder` scope at it in `.npmrc` (anonymous read, no token):
|
||||
|
||||
```
|
||||
@rudder:registry=https://hub.rudder.build/api/packages/rudder/npm/
|
||||
```
|
||||
|
||||
```bash
|
||||
npm install @rudder/js-sdk
|
||||
npm install @rudder/sdk
|
||||
```
|
||||
|
||||
ESM + CJS + `.d.ts`; `type: module`; zero runtime dependencies.
|
||||
@@ -32,22 +33,21 @@ ESM + CJS + `.d.ts`; `type: module`; zero runtime dependencies.
|
||||
## Init
|
||||
|
||||
```ts
|
||||
import { RudderClient } from '@rudder/js-sdk';
|
||||
import { createClient } from '@rudder/sdk';
|
||||
|
||||
const client = new RudderClient({
|
||||
baseUrl: 'https://api.rudder.build', // required
|
||||
projectKey: 'your-project-key', // required
|
||||
const client = createClient({
|
||||
projectKey: 'your-project-key', // required; selects staging or prod
|
||||
// baseUrl?: string — default 'https://api.rudder.build'
|
||||
// tokenStore?: TokenStore — default: localStorage, in-memory fallback
|
||||
// appVersion?: string — sent with platform 'web' on login/refresh
|
||||
// requestTimeoutMs?: number — default 10000
|
||||
// syncIntervalMs?: number — revision poll, default 30000 (±20% jitter)
|
||||
});
|
||||
```
|
||||
|
||||
Missing `baseUrl`/`projectKey` throws `RudderError` with
|
||||
`code: 'sdk/invalid-options'` from the constructor.
|
||||
|
||||
Call `client.dispose()` on unmount/HMR to stop the sync poll and drop cached
|
||||
state.
|
||||
A missing `projectKey` throws `RudderError` with
|
||||
`code: 'sdk/invalid-options'`. Call `client.dispose()` on unmount/HMR to stop
|
||||
the sync poll and drop cached state.
|
||||
|
||||
## Auth essentials
|
||||
|
||||
@@ -65,12 +65,9 @@ client.auth.isAuthenticated;
|
||||
client.auth.onAuthStateChange(cb);
|
||||
```
|
||||
|
||||
Linked identities are listed on the profile: `client.player.data.identities`
|
||||
(`PlayerProfileIdentity { provider, subject, createdAt }`).
|
||||
|
||||
Tokens are saved to the `TokenStore` automatically. The transport injects
|
||||
`Authorization: Bearer <token>`, retries GETs, and single-flight refreshes on
|
||||
401; a failed refresh clears tokens and emits `signed-out`. Details:
|
||||
`Authorization: Bearer <token>` and single-flight refreshes on 401; a failed
|
||||
refresh clears tokens and emits `signed-out`. Details:
|
||||
[reference/auth.md](reference/auth.md).
|
||||
|
||||
## Client surface
|
||||
@@ -78,64 +75,68 @@ Tokens are saved to the `TokenStore` automatically. The transport injects
|
||||
| Area | Access | Kind |
|
||||
|---|---|---|
|
||||
| Auth | `client.auth` | service |
|
||||
| Player profile + wallets | `client.player` | observable `SyncedState<PlayerProfile>` |
|
||||
| Catalog currencies | `client.catalog` | observable `SyncedState<Map<string, CatalogCurrency>>` |
|
||||
| Player storage | `client.storage` + `.save(items)` / `.delete(type)` | observable + mutations |
|
||||
| Project storage | `client.projectStorage` + `.save(items)` | observable + mutation |
|
||||
| Modules | `client.modules.call(module, fn, args?)` | `CallModuleFunctionClientResponse` (`{ result }`) |
|
||||
| Resources | `client.resources(slug)` | `list` / `get` / `create` / `update` / `delete` |
|
||||
| Player | `client.player` (`player`, `identities`, `wallets`, `counters`) | `SyncedState<PlayerProfile>` |
|
||||
| Catalog currencies | `client.catalog` | `SyncedState<Map<string, CatalogCurrency>>` |
|
||||
| Player storage | `client.storage` + `.save(type, data)` / `.delete(type)` | `SyncedState<GetStorageResponse>` + mutations |
|
||||
| Modules | `client.modules.call<T>(module, fn, args?, { moduleVersion? })` | resolves to `result` as `T` |
|
||||
|
||||
Wallets live on `client.player.data.wallets`. Game features are installed
|
||||
modules, not SDK services (first-party modules: items, inventory, reward,
|
||||
remote_config, leaderboards, quests, store, battlepass). Call them with
|
||||
`client.modules.call('my_module', 'hello', { ... })` and read their rows with
|
||||
`client.resources(slug)`. Details: [reference/modules.md](reference/modules.md),
|
||||
[reference/resources.md](reference/resources.md).
|
||||
## Module calls through generated clients
|
||||
|
||||
## Observable domains — the shared pattern
|
||||
```bash
|
||||
RUDDER_URL=... RUDDER_TOKEN=... RUDDER_PROJECT=... \
|
||||
rudder client generate --lang ts --out src/rudder.modules.ts [--environment staging|prod]
|
||||
```
|
||||
|
||||
`player`, `catalog`, `storage`, and `projectStorage` all extend
|
||||
`SyncedState<T>`:
|
||||
```ts
|
||||
import { modules } from './rudder.modules';
|
||||
|
||||
- `.data: T | undefined`, `.status: 'idle' | 'loading' | 'ready' | 'error'`,
|
||||
const m = modules(client);
|
||||
await m.leaderboards.submit({ slug: 'weekly', score: 120 });
|
||||
const top = await m.leaderboards.top({ slug: 'weekly', limit: 10 });
|
||||
```
|
||||
|
||||
The generated file is a factory over `client.modules.call`, with argument and
|
||||
result types, and a `<Module><Fn>Error` code union per function. Regenerate
|
||||
and commit it when installed modules change. Details:
|
||||
[reference/modules.md](reference/modules.md).
|
||||
|
||||
## State objects: the shared pattern
|
||||
|
||||
`player`, `catalog` and `storage` extend `SyncedState<T>`:
|
||||
|
||||
- `.value: T | undefined`, `.status: 'idle' | 'loading' | 'ready' | 'error'`,
|
||||
`.error?: Error`
|
||||
- `onChange(cb: (snapshot: SyncedSnapshot<T>) => void): () => void` — fires
|
||||
immediately with the current snapshot; maps onto React `useSyncExternalStore`
|
||||
- `load(): Promise<T>` — deduplicated, no-op when ready
|
||||
- `reload(): Promise<T>` — forced refetch
|
||||
- `onChange(cb: (snapshot: SyncedSnapshot<T>) => void): () => void`: fires
|
||||
immediately with `{ status, value, error }`; maps onto React
|
||||
`useSyncExternalStore`
|
||||
- `load(): Promise<T>`: deduplicated, no-op when ready
|
||||
- `reload(): Promise<T>`: forced refetch
|
||||
|
||||
`player` and `catalog` are warmed once at login; `storage` and
|
||||
`projectStorage` load on first use. All observable domains are then kept fresh
|
||||
by a revision poll every 30 s (±20% jitter); polling pauses while the tab is
|
||||
hidden. Storage mutations invalidate their domain immediately. Call `reload()`
|
||||
when you need a freshness guarantee.
|
||||
`player` and `catalog` are warmed at login; `storage` loads on first use. A
|
||||
revision poll (`GET /sdk/v1/sync`, keys `profile`, `catalog`, `storage`) runs
|
||||
every 30 s (±20% jitter), pauses while the tab is hidden, and reloads the
|
||||
states whose revision grew. Storage mutations invalidate immediately. Call
|
||||
`player.reload()` after a module call that changes wallets or counters when
|
||||
you need fresh values right away.
|
||||
|
||||
## Errors
|
||||
|
||||
All SDK errors extend `RudderError` (optional machine-readable `code`):
|
||||
|
||||
- `RudderNetworkError` — fetch failed / timeout (`error.cause` holds the original)
|
||||
- `RudderHttpError` — non-2xx, carries `status`, `statusText`, `body`, `code`
|
||||
- `RudderAuthError extends RudderHttpError` — 401 after failed refresh; tokens
|
||||
already cleared, `signed-out` already emitted
|
||||
- `RudderNetworkError`: fetch failed or timed out (`error.cause` holds the original)
|
||||
- `RudderHttpError`: non-2xx, carries `status`, `statusText`, `body`, `code`
|
||||
- `RudderAuthError extends RudderHttpError`: 401 after a failed refresh;
|
||||
tokens already cleared, `signed-out` already emitted
|
||||
- `RudderModuleError<C> extends RudderHttpError`: a module call returned 422
|
||||
`{ error, code, requestId }`; exposes `code: C`, `message`, `requestId`
|
||||
- `RudderError` with `code: 'sdk/invalid-options'` (`SDK_ERROR_INVALID_OPTIONS`)
|
||||
— constructor validation
|
||||
|
||||
The platform error codes are `forbidden` and `row_referenced`
|
||||
(`src/generated/errors.ts`, not exported from the package entry); compare
|
||||
`error.code === 'forbidden'`.
|
||||
|
||||
`client.modules.call` resolves to `{ result }` only; console output and the
|
||||
kernel call log never reach players. A module business error rejects with
|
||||
`RudderHttpError` whose `status` is 422 and whose `code` is the module's own
|
||||
code (the body is `{ error, code, requestId }`). Module codes are open strings
|
||||
with no generated constants, so compare against the codes the module
|
||||
documents. A module trap is a 500 and a timeout a 504, both without details.
|
||||
Compare `error.code` with string literals. A module trap is a 500 and a
|
||||
timeout a 504, both without details.
|
||||
|
||||
## Reference files
|
||||
|
||||
- [reference/auth.md](reference/auth.md) — login flows, options, token store, session lifecycle
|
||||
- [reference/player.md](reference/player.md) — profile + wallets (`PlayerProfile`, `Wallet`)
|
||||
- [reference/storage.md](reference/storage.md) — player + project storage, limits
|
||||
- [reference/modules.md](reference/modules.md) — `modules.call`, response, module errors
|
||||
- [reference/resources.md](reference/resources.md) — `resources(slug)` row operations
|
||||
- [reference/auth.md](reference/auth.md): login flows, options, token store, session lifecycle
|
||||
- [reference/player.md](reference/player.md): profile, wallets, counters
|
||||
- [reference/storage.md](reference/storage.md): player storage, limits
|
||||
- [reference/modules.md](reference/modules.md): generated clients, `modules.call`, idempotency, retries, errors
|
||||
|
||||
Reference in New Issue
Block a user