@revturbine/sdk 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -17171,11 +17171,14 @@ interface RevTurbineTreatmentInteractionInput {
17171
17171
  * `PlacementOutput.rule_id` — projected onto the `placement_interaction`
17172
17172
  * clickstream payload as `rule_handle` (BL-0182).
17173
17173
  *
17174
- * Unlike its neighbours this does NOT reach the wire
17175
- * `TreatmentInteractionInputSchema` / `placement_presentations`: that path is
17176
- * column-shaped and would need a datasource migration. The clickstream
17177
- * carries payload fields inside a JSON column, so the rule slice for
17178
- * interactions is readable the moment producers send it.
17174
+ * It reaches the wire `TreatmentInteractionInputSchema` too, as of BL-0200.
17175
+ * It deliberately did not under BL-0182: that path is column-shaped and the
17176
+ * rule would have needed a datasource migration, while the clickstream
17177
+ * carries payload fields inside a JSON column and was readable at once.
17178
+ * `placement_exposure_attribution.rule_handle` exists now (web ledger 015),
17179
+ * so the base exposure row records the rule the exposure was DECIDED by
17180
+ * rather than waiting for attribution to reconstruct one when a conversion
17181
+ * lands — which is what makes presentation-grain rule cuts servable.
17179
17182
  *
17180
17183
  * Omitted, never sent empty, when no decision is in scope — a bare
17181
17184
  * `trackTreatmentInteraction` caller supplies no rule, and absence is what
@@ -20673,6 +20676,16 @@ interface RevTurbineEndpointOverrides {
20673
20676
  */
20674
20677
  legacyInteractions?: string;
20675
20678
  placementTypes: string;
20679
+ /**
20680
+ * Optional sink for registered surface slots.
20681
+ *
20682
+ * RevTurbine hosts **no default route** for this verb (BL-0197). Slot
20683
+ * registration is client-local and discovery is ingestion-driven — the
20684
+ * pipeline derives discovered slots from the `slot_*` telemetry the SDK
20685
+ * already emits through `/api/track`. Set this only when you run your own
20686
+ * slot-inventory service; unset, `registerSurfaceSlot()` performs no
20687
+ * network write.
20688
+ */
20676
20689
  surfaceSlots: string;
20677
20690
  }
20678
20691
  /** Client-side clickstream batching policy (plan 95 TASK-6). */
@@ -21329,6 +21342,12 @@ declare class RevTurbineCustomerSdk {
21329
21342
  * redundant segment re-evaluation.
21330
21343
  */
21331
21344
  private lastServerContextHash?;
21345
+ /**
21346
+ * The plan move an optimistic `convert()` made ahead of the server (BL-0004),
21347
+ * held only until a `client-context` read stops reporting the old plan. See
21348
+ * {@link dropStalePostConversionPlan}.
21349
+ */
21350
+ private pendingConversionPlan?;
21332
21351
  private usageBalances;
21333
21352
  private localDecisionsByPlacementId;
21334
21353
  private localPlacementsByLookupKey;
@@ -22126,6 +22145,30 @@ declare class RevTurbineCustomerSdk {
22126
22145
  * @public
22127
22146
  */
22128
22147
  registerPlacement(config: RevTurbinePlacementConfig): Promise<string>;
22148
+ /**
22149
+ * Mirror a registered surface slot to an explicitly configured backend.
22150
+ *
22151
+ * **Requires an explicit `surfaceSlots` endpoint override** (BL-0197).
22152
+ * There is no default route for this verb and there never was a correct
22153
+ * one: the previous default pointed at `/api/placements`, RevTurbine's
22154
+ * *authored-config* CRUD. That route validates against `PlacementSchema`
22155
+ * (which requires `handle` and `category`), so the legacy surface-slot body
22156
+ * this method sends could only ever be rejected — in production every
22157
+ * non-`local_only` registration failed with
22158
+ * `surface_slot_create_failed:422` and the React runtime turned that into a
22159
+ * `slot_error`.
22160
+ *
22161
+ * It was also wrong in principle. On a same-origin integration the signed-in
22162
+ * user's session cookie authenticates the request, so a body the route *did*
22163
+ * accept would have let merely loading a gated page create a draft placement
22164
+ * in the tenant's Playbook. Only the 422 prevented that.
22165
+ *
22166
+ * Surface-slot **discovery is ingestion-driven**: the SDK emits its
22167
+ * `slot_*` / `placement_*` telemetry through `/api/track` and the pipeline
22168
+ * derives discovered slots from it. Registration itself is client-local.
22169
+ *
22170
+ * @internal
22171
+ */
22129
22172
  private upsertSurfaceSlot;
22130
22173
  /**
22131
22174
  * Build the internal placement record for a surface-slot config.
@@ -22148,6 +22191,18 @@ declare class RevTurbineCustomerSdk {
22148
22191
  * @internal
22149
22192
  */
22150
22193
  private commitSurfaceSlotRecord;
22194
+ /**
22195
+ * Register a surface slot with the local runtime and return its placement id.
22196
+ *
22197
+ * **Client-local by default** (BL-0197): this performs no network write. The
22198
+ * slot is recorded in the in-process registry, and the pipeline discovers it
22199
+ * from the `slot_*` / `placement_*` telemetry the SDK emits through
22200
+ * `/api/track`. A write happens only when `custom_endpoints.surfaceSlots`
22201
+ * points at a slot-inventory service you run; there is no fallback to
22202
+ * `/api/placements`.
22203
+ *
22204
+ * @public
22205
+ */
22151
22206
  registerSurfaceSlot(config: RevTurbineSurfaceSlotConfig): Promise<string>;
22152
22207
  /**
22153
22208
  * Persist authored placement types to a configured backend.
@@ -22437,12 +22492,16 @@ declare class RevTurbineCustomerSdk {
22437
22492
  /** Map the client-safe context response into a UserContext patch (plan 157). */
22438
22493
  private mapClientSafeContext;
22439
22494
  /**
22440
- * Apply a server-derived UserContext patch (plan 157) — mirrors setUserContext
22441
- * but accepts a partial. The patch's RevTurbine-authoritative fields (trial,
22442
- * billing signals) overlay the held context; undefined fields are skipped by
22443
- * the merge, so app-set values are never nulled.
22495
+ * Apply a UserContext patch — mirrors setUserContext but accepts a partial.
22496
+ * RevTurbine-authoritative fields (trial, billing signals, server plan)
22497
+ * overlay the held context; undefined fields are skipped by the merge, so
22498
+ * app-set values are never nulled.
22499
+ *
22500
+ * Two callers: the server client-context fetch (plan 157) and the optimistic
22501
+ * post-conversion plan move (BL-0004). Both notify subscribers, which is what
22502
+ * makes mounted slots and gates re-resolve.
22444
22503
  */
22445
- private applyServerContextPatch;
22504
+ private applyUserContextPatch;
22446
22505
  /**
22447
22506
  * Record that a placement was rendered to the user. Plan 43 TASK-9.
22448
22507
  *
@@ -22619,13 +22678,73 @@ declare class RevTurbineCustomerSdk {
22619
22678
  */
22620
22679
  snooze(outputId: string, seconds?: number): Promise<void>;
22621
22680
  /**
22622
- * Record a confirmed CTA completion and retire the decision's placement
22623
- * permanently — future calls to `getPlacementDecision` for it will not
22624
- * return it again.
22681
+ * Record a confirmed CTA completion, then reload the UserContext so every
22682
+ * mounted surface re-decides against the plan the user just converted to.
22683
+ *
22684
+ * Conversion is **not** a suppression: it writes no cooldown and no permanent
22685
+ * retirement (spec §"interaction" table; Kent's plan 254 D-9 ruling). What
22686
+ * makes a converted upgrade banner leave the screen is the user no longer
22687
+ * matching it — so a conversion that did not refresh the context left the
22688
+ * placement mounted until something else happened to re-resolve it. Kent,
22689
+ * 2026-09-25 (BL-0004): *"Converting should trigger a reloading of UserContext
22690
+ * with the new plan/billing state the user has converted to."*
22691
+ *
22692
+ * Ordering, and why it is this way round:
22693
+ *
22694
+ * 1. The interaction is recorded (analytics + attribution), as before.
22695
+ * 2. **Optimistically**, locally: when the converted output's CTA names a
22696
+ * target plan (`cta_path.plan_handle`), the held context moves to that plan
22697
+ * immediately. Billing truth arrives by Stripe webhook, so
22698
+ * `GET /api/sdk/client-context` can still report the OLD plan for seconds
22699
+ * after a successful checkout — waiting for it would leave the upgrade
22700
+ * prompt on screen exactly when the user has just paid.
22701
+ * 3. Subscribers are notified either way — a conversion whose CTA names no
22702
+ * plan still re-decides mounted slots and re-checks mounted gates, which is
22703
+ * what turns a plan change the host applied itself into a re-render.
22704
+ * 4. **Then** the server refresh (`fetchClientContext()`), which overlays the
22705
+ * RevTurbine-authoritative trial / billing-health / plan fields and
22706
+ * notifies again if it changed anything. It is best-effort and never
22707
+ * throws, so awaiting it cannot fail a conversion; with no client-session
22708
+ * minter configured it is a no-op and the optimistic state stands.
22709
+ *
22710
+ * Mounted `PlacementController`s re-decide and mounted `EntitlementGate`s
22711
+ * re-check through their `watchUserContext()` subscriptions — which the React
22712
+ * bindings attach for you. Re-resolution emits the normal lifecycle events
22713
+ * once per final decision; the transient states emit nothing.
22714
+ * `PlacementController.ctaComplete()` is unchanged and remains the
22715
+ * component-level path.
22625
22716
  *
22626
22717
  * @public
22627
22718
  */
22628
22719
  convert(outputId: string): Promise<void>;
22720
+ /**
22721
+ * The BL-0004 post-conversion context reload: optimistic local plan move,
22722
+ * then the server refresh. See {@link convert} for the ordering rationale.
22723
+ */
22724
+ private reloadUserContextAfterConversion;
22725
+ /**
22726
+ * Keep a stale server plan from undoing an optimistic conversion (BL-0004).
22727
+ *
22728
+ * Billing truth arrives by Stripe webhook, so a `client-context` read that
22729
+ * races the webhook reports the PRE-conversion plan — and the server plan
22730
+ * deliberately overlays the app-supplied one (plan 179 TASK-1), so applying
22731
+ * that read verbatim would put the upgrade prompt straight back on screen
22732
+ * seconds after the user paid.
22733
+ *
22734
+ * So while a conversion is pending, a server plan equal to the plan converted
22735
+ * AWAY from is dropped — only the plan fields, never the trial or billing-health
22736
+ * signals, which are authoritative regardless. Any other value (the new plan,
22737
+ * or a third one the user was moved to out-of-band) clears the pending state
22738
+ * and applies, so this can suppress at most the one value it was created for
22739
+ * and cannot outlive the lag it exists to cover.
22740
+ */
22741
+ private dropStalePostConversionPlan;
22742
+ /**
22743
+ * The display name the loaded Playbook gives a plan handle, else the handle.
22744
+ * Keeps an optimistic plan move from putting a raw handle in front of a user
22745
+ * when the Playbook already knows the plan's name.
22746
+ */
22747
+ private planNameForHandle;
22629
22748
  /**
22630
22749
  * Emit a canonical trigger event. There is no in-SDK consumer that matches
22631
22750
  * on trigger names — the shipped behavior is ingest to `events_clickstream`
@@ -23089,12 +23208,41 @@ declare class PlacementController {
23089
23208
  private _awaitingConfig;
23090
23209
  private _configRetries;
23091
23210
  private _playbookUnsubscribe;
23211
+ private _userContextUnsubscribe;
23092
23212
  constructor(sdk: RevTurbineCustomerSdk, options: PlacementControllerOptions);
23093
23213
  /**
23094
- * Stop listening for Playbook loads. Idempotent; call it when the owning
23095
- * component unmounts (`usePlacement` does).
23214
+ * Stop listening for Playbook loads and user-context changes. Idempotent; call
23215
+ * it when the owning component unmounts (`usePlacement` does).
23096
23216
  */
23097
23217
  dispose(): void;
23218
+ /**
23219
+ * Drop only the Playbook-load subscription — the within-load-cycle teardown
23220
+ * (BL-0177). A mounted controller keeps watching the user context (BL-0004).
23221
+ */
23222
+ private unsubscribePlaybook;
23223
+ /**
23224
+ * Re-decide whenever the user context changes (BL-0004).
23225
+ *
23226
+ * The counterpart to {@link EntitlementGate.watchUserContext}, and the reason
23227
+ * `sdk.convert()` now removes a converted placement in place: conversion writes
23228
+ * no suppression — eligibility is a function of the user's CURRENT plan and
23229
+ * targeting — so the only thing that can take a converted upgrade banner off
23230
+ * screen is a re-decide against the reloaded context. Without this the
23231
+ * placement stayed mounted until something else happened to re-resolve it, and
23232
+ * the host had to call `refresh()` itself or use the component's
23233
+ * `ctaComplete()` (the limitation plan 236 TASK-13 recorded).
23234
+ *
23235
+ * Only re-decides a controller that has already resolved something: before the
23236
+ * first `load()` there is nothing on screen to be stale, and firing then would
23237
+ * turn every startup `identify()` into a duplicate decision.
23238
+ *
23239
+ * Calls `load()` rather than `refresh()` on purpose — `refresh()` clears
23240
+ * impression/exposure tracking, so a context change would re-credit a
23241
+ * presentation the user never saw again.
23242
+ *
23243
+ * Returns an unsubscribe function; {@link dispose} also drops it.
23244
+ */
23245
+ watchUserContext(): () => void;
23098
23246
  /**
23099
23247
  * Is this decision a race against a Playbook load rather than a verdict?
23100
23248
  *
@@ -23312,12 +23460,18 @@ declare class EntitlementGate {
23312
23460
  private _awaitedResult;
23313
23461
  private _configRetries;
23314
23462
  private _playbookUnsubscribe;
23463
+ private _userContextUnsubscribe;
23315
23464
  constructor(sdk: RevTurbineCustomerSdk, options: EntitlementGateOptions);
23316
23465
  /**
23317
- * Stop listening for Playbook loads. Idempotent; call it when the owning
23318
- * component unmounts (`useEntitlement` does).
23466
+ * Stop listening for Playbook loads and user-context changes. Idempotent; call
23467
+ * it when the owning component unmounts (`useEntitlement` does).
23319
23468
  */
23320
23469
  dispose(): void;
23470
+ /**
23471
+ * Drop only the Playbook-load subscription — the within-check-cycle teardown
23472
+ * (BL-0179). A mounted gate keeps watching the user context (BL-0004).
23473
+ */
23474
+ private unsubscribePlaybook;
23321
23475
  /** Current state snapshot. */
23322
23476
  get state(): EntitlementGateState;
23323
23477
  /** Convenience: `true` when the entitlement is allowed. */
@@ -23417,8 +23571,13 @@ declare class EntitlementGate {
23417
23571
  * `check()` there is nothing on screen to be stale, and firing then would
23418
23572
  * turn every `identify()` at startup into a redundant evaluation.
23419
23573
  *
23574
+ * `sdk.convert()` reloads the UserContext (BL-0004), so this is also what
23575
+ * flips a mounted `useCan` gate to allowed after a conversion.
23576
+ *
23420
23577
  * Returns an unsubscribe function. Call it when the gate is discarded —
23421
23578
  * without that, a gate outlives its consumer and keeps re-checking.
23579
+ * Idempotent: a second call returns the first subscription rather than
23580
+ * orphaning it, and {@link dispose} drops it too.
23422
23581
  */
23423
23582
  watchUserContext(): () => void;
23424
23583
  private notify;