feat: modules and resources domains, drop removed domains, regenerated models

This commit is contained in:
edmand46
2026-09-17 12:08:03 +03:00
parent f45261439f
commit 6f37b00fb4
71 changed files with 516 additions and 4184 deletions
+20 -93
View File
@@ -1,8 +1,7 @@
# @rudder/js-sdk
LiveOps Web SDK for browser games — device auth, observable player state,
stores, remote config, leaderboards, quests, battle pass, and a scenario runtime
surfaced as typed effects.
catalog currencies, storage, generic resources, and module function calls.
## Install
@@ -29,22 +28,18 @@ const client = new RudderClient({
projectKey: 'your-project-key',
// tokenStore is optional: defaults to localStorage with an in-memory
// fallback where localStorage is unavailable (SSR, private mode).
appVersion: '1.4.2', // optional; sent with platform 'web' on login and refresh
});
await client.auth.loginWithDevice(); // region 'global', language 'en'
// or: await client.auth.loginWithDevice({ region: 'eu', language: 'de', nickname: 'Bob' });
// Observable domains — read `.data`, subscribe with `onChange`, force `reload()`.
const profile = client.player.data;
const speed = client.remoteConfig.get('player_speed', 200);
const gold = profile?.wallets?.find((w) => w.currency === 'gold')?.balance;
client.stores.onChange(({ data: stores }) => renderStores(stores ?? []));
const items = await client.resources('items').list();
const result = await client.modules.call('quests', 'list');
// Scenario-driven UI arrives through effects.
client.effects.onStoreOffer((offer) => showOffer(offer));
client.effects.onScenarioFailed(({ error }) => console.error('scenario failed', error));
// Tear down background work (sync poll, wait timers) on unmount / HMR.
client.dispose();
```
@@ -54,75 +49,15 @@ client.dispose();
|---|---|
| Auth | `client.auth.loginWithDevice()`, `client.auth.loginWithCustom({ customData })`, `client.auth.logout()`, `client.auth.isAuthenticated`, `client.auth.onAuthStateChange(cb)` |
| Player / wallets | `client.player` (observable) |
| Inventory | `client.inventory` (observable, catalog-merged) |
| Catalog | `client.catalog` (observable) |
| Stores | `client.stores` (observable) + `client.stores.purchase(slug, offerId)` |
| Remote config | `client.remoteConfig.get(key, default)` (observable, typed) |
| Catalog currencies | `client.catalog` (observable) |
| Storage | `client.storage` (observable) + `.save(items)` / `.delete(type)` |
| Leaderboards | `client.leaderboards.findBySlug(slug)` → `handle.submit(score)` / `handle.list(limit?)` |
| Battle pass | `client.battlePass` (getProgress / addXp / claimReward / purchasePremium) |
| Quests | `client.quests.list()` / `client.quests.claim(id)` / `client.quests.reportProgress(metric, amount)` |
| Scenario effects | `client.effects.on*` |
| Project storage | `client.projectStorage` (observable) + `.save(items)` |
| Modules | `client.modules.call(module, fn, args?)` |
| Resources | `client.resources(slug)` → `list` / `get` / `create` / `update` / `delete` |
Type the remote config for `client.remoteConfig`:
```ts
interface GameConfig extends Record<string, unknown> {
player_speed: number;
feature_x: boolean;
}
const client = new RudderClient<GameConfig>({ /* … */ });
client.remoteConfig.get('player_speed', 200); // number
```
## Quests
`client.quests` covers the player's global quests — list with per-objective
progress, claim, and metric reports. These are distinct from scenario quest
nodes, which advance through the `onQuest` effect's `QuestSession`. Global
quests have no live sync: re-list after a claim or report.
```ts
const quests = await client.quests.list();
for (const quest of quests) {
if (quest.status === 'completed' && quest.id) {
await client.quests.claim(quest.id);
}
}
// Custom metrics advance matching objectives server-side; the call returns
// the ids of quests completed by this report.
const completedIds = await client.quests.reportProgress('kills', 1);
```
Purchase metrics are reported automatically when a purchase goes through
`client.stores`; the `QuestMetrics` helpers name the format so quest configs
and client code agree on it:
```ts
import { QuestMetrics } from '@rudder/js-sdk';
QuestMetrics.purchaseOffer('starter-pack'); // "purchase.offer:starter-pack"
QuestMetrics.purchaseItem('moonberry'); // "purchase.item:moonberry"
```
## Scenario effects
The scenario runtime is not exposed directly; scenario nodes surface through
`client.effects`:
- `onNotification`, `onStoreOffer`, `onLeaderboard`, `onConfigChanged`
- `onWait`, `onQuest`, `onBattlePass`, `onBattlePassLevel`
- `onScenarioCompleted`, `onScenarioFailed`
The server executes the scenario graph. The SDK is a thin effects client
(trigger, pending poll, callbacks) loaded lazily on first login — a client
that only reads domains never pulls it into the page. `onConfigChanged`
stays on the surface, but scenario `remote_config_override` nodes are
applied server-side and no longer emit that effect.
Errors thrown inside effect handlers are reported via the `onEffectError`
client option (default: `console.error`).
Game-design features (quests, stores, inventory, leaderboards, battle pass,
remote config) live in installed modules. Call them through
`client.modules.call` and read their rows through `client.resources(slug)`.
## Error handling
@@ -135,7 +70,6 @@ import {
RudderNetworkError,
RudderHttpError,
RudderAuthError,
RudderErrorCodes,
} from '@rudder/js-sdk';
try {
@@ -146,9 +80,6 @@ try {
// cleared tokens and emitted 'signed-out' via onAuthStateChange.
showLogin();
} else if (error instanceof RudderHttpError) {
if (error.code === RudderErrorCodes.runExpired) {
// typed server error code
}
console.error(error.status, error.code, error.body);
} else if (error instanceof RudderNetworkError) {
// fetch failed / timeout — the underlying error is in error.cause
@@ -171,15 +102,14 @@ client.auth.onAuthStateChange((state) => {
## Data liveness model
Observable domains (`player`, `inventory`, `catalog`, `stores`, `remoteConfig`,
`storage`) are warmed once at login and then kept fresh by a revision poll:
the client asks the gateway for entity revisions every **30 s (±20% jitter)**
and reloads only the entities whose revision grew. Polling **pauses while the
tab is hidden** and resumes on `visibilitychange`. Mutations (purchases,
storage writes, scenario callbacks) invalidate the affected domains
immediately, so subscribers see fresh data without waiting for the next poll.
Expect data to be eventually consistent within one poll interval; call
`reload()` when you need a guarantee.
Observable domains (`player`, `catalog`, `storage`, `projectStorage`) are
warmed at login (`player` and `catalog`) or on first use, then kept fresh by
a revision poll: the client asks the gateway for entity revisions every
**30 s (±20% jitter)** and reloads only the entities whose revision grew.
Polling **pauses while the tab is hidden** and resumes on `visibilitychange`.
Storage writes invalidate the affected domain immediately. Expect data to be
eventually consistent within one poll interval; call `reload()` when you need
a guarantee.
## React recipe
@@ -202,9 +132,6 @@ function Wallet() {
}
```
The same pattern works for `client.remoteConfig`, `client.stores`, and
`client.auth.onAuthStateChange`.
## Development
```bash