Server-side scenario execution: thin effects client replaces local engine
- engine/ (DagWalker, sessions, IndexedDbPlanStore) deleted; server owns the graph - trigger/callback/counter carry PendingEffect; GET /sdk/v1/scenarios/pending polled (30s jittered heartbeat paused on hidden tab + wait-deadline timers) - client.effects public API unchanged (done/buy/dismiss/end/claim/battlepass/quest) - planStateStore removed; loginEvent kept; reconcile() makes server the source of truth - generated models regenerated; skills/README/CHANGELOG updated
This commit is contained in:
@@ -34,7 +34,6 @@ interface AddBattlePassXpResponse {
|
||||
level?: number;
|
||||
leveledUp?: boolean;
|
||||
maxLevel?: boolean;
|
||||
plan?: ExecutionPlan; // scenario plan continuation, handled by the runtime
|
||||
xp?: number;
|
||||
}
|
||||
|
||||
@@ -67,7 +66,6 @@ interface PurchaseBattlePassPremiumRequest {
|
||||
}
|
||||
interface PurchaseBattlePassPremiumResponse {
|
||||
error?: string;
|
||||
plan?: ExecutionPlan;
|
||||
success?: boolean;
|
||||
}
|
||||
```
|
||||
@@ -82,6 +80,5 @@ interface PurchaseBattlePassPremiumResponse {
|
||||
in the response and the typed error `code` (`RudderErrorCodes`).
|
||||
- `purchasePremium` charges the player's wallet; idempotent; pass your own
|
||||
`idempotencyKey` for safe retries.
|
||||
- Mutations return `ExecutionPlan` continuations — when driving battle pass
|
||||
manually you are responsible for the scenario run state; prefer the
|
||||
`onBattlePass` effect session which handles this.
|
||||
- Prefer the `onBattlePass` effect session, which binds `scenarioId` /
|
||||
`nodeId` / `runId` and posts scenario callbacks for you.
|
||||
|
||||
@@ -56,8 +56,8 @@ already filters inactive configs out and may omit the flag).
|
||||
## Behavior notes
|
||||
|
||||
- Warmed at login.
|
||||
- Scenario `remote_config_override` nodes patch the local snapshot and fire
|
||||
`client.effects.onConfigChanged({ key })` — the patched value is what
|
||||
`get()` returns afterwards.
|
||||
- Scenario `remote_config_override` nodes are applied server-side and do not
|
||||
reach the client. Reload or wait for the config sync poll to observe the
|
||||
patched value via `get()`.
|
||||
- Without a `TConfig` type argument, `RemoteConfigShape` defaults to
|
||||
`Record<string, unknown>` and `get()` returns `unknown`.
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
# Scenarios + effects — `client.effects`
|
||||
|
||||
Scenarios are server-authored node graphs (configured in the dashboard) that
|
||||
run per player. The SDK's scenario runtime is deliberately **not** part of the
|
||||
public client surface: scenario nodes surface as typed effects through
|
||||
`client.effects` (source: `src/effects/EffectsCenter.ts`), and the engine
|
||||
itself loads lazily on first login.
|
||||
run per player. The server executes the graph. The SDK is a thin effects
|
||||
client: it sends trigger events, turns `PendingEffect` payloads into typed
|
||||
`client.effects` handlers, posts callbacks when the game completes an
|
||||
effect, and polls for pending effects. The runtime is deliberately **not**
|
||||
part of the public client surface (source: `src/effects/EffectsCenter.ts`)
|
||||
and loads lazily on first login.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- The runtime starts after login: scenario state is restored from persistence,
|
||||
then the login event (`'player_login'` by default) is fired to trigger
|
||||
event-driven scenarios.
|
||||
- Runs persist to IndexedDB by default, so waits and pending nodes survive
|
||||
page reloads. On restore, each run is reconciled with the server
|
||||
(`unknown_run` / `expired` runs are dropped).
|
||||
- `logout()` / `dispose()` clear all runs and persisted state.
|
||||
- After login the SDK fetches `GET /sdk/v1/scenarios/pending` (replacing any
|
||||
local restore), then fires the login event (`'player_login'` by default).
|
||||
- A heartbeat polls pending every ~30s (±20% jitter), paused while
|
||||
`document.hidden`. Each effect with a `waitDeadline` also schedules a
|
||||
local timer so waits fire on time.
|
||||
- An effect with an already-active `(runId, nodeId)` is not re-emitted.
|
||||
- `logout()` / `dispose()` stop the poll, cancel wait timers, and drop
|
||||
active effects. There is no local plan persistence.
|
||||
|
||||
### Runtime options (`RudderClientOptions.runtime`)
|
||||
|
||||
```ts
|
||||
runtime?: {
|
||||
// Scenario plan persistence. undefined = default IndexedDB store (created
|
||||
// lazily); null = disable persistence entirely; or pass a PlanStateStore.
|
||||
planStateStore?: PlanStateStore | null;
|
||||
|
||||
// Scenario event fired automatically after login.
|
||||
// undefined = 'player_login'; null = fire nothing.
|
||||
loginEvent?: string | null;
|
||||
@@ -60,6 +59,11 @@ interface Effects {
|
||||
}
|
||||
```
|
||||
|
||||
`onConfigChanged` remains on the public surface but scenario
|
||||
`remote_config_override` nodes no longer reach the client — the server
|
||||
applies them. Patched values show up through `client.remoteConfig` after
|
||||
sync/reload.
|
||||
|
||||
### `NotificationEffect` — a notification node became active
|
||||
|
||||
```ts
|
||||
@@ -81,19 +85,15 @@ interface Effects {
|
||||
### `LeaderboardEffect` — a leaderboard node became active
|
||||
|
||||
```ts
|
||||
{ end(): Promise<void>; rewardClaimed(): Promise<void> }
|
||||
{ end(): Promise<void>; claim(): Promise<void>; rewardClaimed(): Promise<void> }
|
||||
```
|
||||
|
||||
### `ConfigChangedEffect` — a `remote_config_override` node patched config
|
||||
|
||||
```ts
|
||||
{ readonly key: string } // client.remoteConfig.get(key) already returns the override
|
||||
```
|
||||
`rewardClaimed()` is a deprecated alias for `claim()`.
|
||||
|
||||
### `WaitEffect` — a wait node became active
|
||||
|
||||
```ts
|
||||
{ readonly deadlineUtc: Date } // run resumes automatically at the deadline
|
||||
{ readonly deadlineUtc: Date } // SDK polls pending at the deadline; the server advances the run
|
||||
```
|
||||
|
||||
### `QuestEffect` — a scenario quest node became active
|
||||
@@ -106,8 +106,8 @@ interface Effects {
|
||||
}
|
||||
```
|
||||
|
||||
The node auto-completes server-side once every objective is satisfied; the SDK
|
||||
crosses the `onComplete` boundary itself when the server reports completion.
|
||||
The node auto-completes server-side once every objective is satisfied; the
|
||||
counter response may carry the next `PendingEffect`.
|
||||
|
||||
### `BattlePassEffect` — a battle pass node became active
|
||||
|
||||
@@ -137,7 +137,7 @@ already bound — prefer them over calling the service manually.
|
||||
interface ScenarioCompletedEffect { readonly runId: string; readonly scenarioId: string }
|
||||
interface ScenarioFailedEffect {
|
||||
readonly runId: string; readonly scenarioId: string; readonly nodeId: string;
|
||||
readonly error: Error; // transport gave up, or unsupported node type
|
||||
readonly error: Error;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -146,15 +146,20 @@ as console warnings.
|
||||
|
||||
## Supported node types
|
||||
|
||||
`wait`, `remote_config_override`, `notification`, `store`, `leaderboard`,
|
||||
`quest`, `battlepass`, `battlepass_level`. Any other node type fails the run
|
||||
(surfaced via `onScenarioFailed`).
|
||||
`wait`, `notification`, `store`, `leaderboard`, `quest`, `battlepass`,
|
||||
`battlepass_level`. Any other node type fails the run (surfaced via
|
||||
`onScenarioFailed`). `remote_config_override` is applied server-side and is
|
||||
not delivered to the client.
|
||||
|
||||
## Reliability notes
|
||||
|
||||
- Node completion is idempotent client-side (completed handles are tracked).
|
||||
- Transient boundary failures (network error, 5xx) leave the node active for
|
||||
retry on reconnect; terminal server errors fail the run.
|
||||
- Completion methods POST `/sdk/v1/scenarios/callback` with
|
||||
`{scenarioId, runId, nodeId, handle}` using the handles `output`,
|
||||
`onPurchase`, `onDecline`, `onEnd`, `onClaim`, `onComplete`, `onLevelUp`,
|
||||
`onPremiumPurchase`.
|
||||
- Transient callback failures (network error, 5xx) leave the effect active
|
||||
for retry; `unknown_run` / `run_expired` drop that run's effects and fire
|
||||
`onScenarioFailed`.
|
||||
- Server scenario errors use the typed codes `run_expired`, `run_not_active`,
|
||||
`node_not_active`, `scenario_not_active`, `unknown_run`,
|
||||
`early_completion`, `objectives_incomplete`, `level_not_reached`,
|
||||
|
||||
Reference in New Issue
Block a user