@revturbine/sdk 0.11.12 → 0.11.16
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/CHANGELOG.md +548 -0
- package/dist/growthbook.d.ts +1 -1
- package/dist/headless.d.ts +10912 -582
- package/dist/headless.js +2 -2
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +10952 -584
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/optimizely.d.ts +1 -1
- package/dist/types/server-node/client.d.ts +211 -3
- package/dist/types/server-node/client.d.ts.map +1 -1
- package/dist/types/server-node/index.d.ts +15 -3
- package/dist/types/server-node/index.d.ts.map +1 -1
- package/dist/types/server-node/local-server.d.ts +15 -2
- package/dist/types/server-node/local-server.d.ts.map +1 -1
- package/dist/types/server-node/types.d.ts +67 -2
- package/dist/types/server-node/types.d.ts.map +1 -1
- package/dist/types/web-sdk/config-artifact.d.ts +7 -2
- package/dist/types/web-sdk/config-artifact.d.ts.map +1 -1
- package/dist/types/web-sdk/controllers.d.ts +16 -0
- package/dist/types/web-sdk/controllers.d.ts.map +1 -1
- package/dist/types/web-sdk/customer-side.d.ts +179 -31
- package/dist/types/web-sdk/customer-side.d.ts.map +1 -1
- package/dist/types/web-sdk/headless.d.ts +2 -0
- package/dist/types/web-sdk/headless.d.ts.map +1 -1
- package/dist/types/web-sdk/local-builtin-segments.d.ts +46 -0
- package/dist/types/web-sdk/local-builtin-segments.d.ts.map +1 -0
- package/dist/types/web-sdk/react/RevTurbineProvider.d.ts.map +1 -1
- package/dist/types/web-sdk/react/RevTurbineRoute.d.ts +45 -0
- package/dist/types/web-sdk/react/RevTurbineRoute.d.ts.map +1 -0
- package/dist/types/web-sdk/react/index.d.ts +1 -0
- package/dist/types/web-sdk/react/index.d.ts.map +1 -1
- package/dist/types/web-sdk/react/usePlacement.d.ts.map +1 -1
- package/dist/types/web-sdk/slot-route.d.ts +83 -0
- package/dist/types/web-sdk/slot-route.d.ts.map +1 -0
- package/dist/types/web-sdk/theme/theme-loader.d.ts +12 -2
- package/dist/types/web-sdk/theme/theme-loader.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -47,6 +47,554 @@ also require a changelog entry.
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
+
## 0.11.16
|
|
51
|
+
|
|
52
|
+
### Build fix, part two: the npm declaration build resolves `openapi-fetch` too (no API change)
|
|
53
|
+
|
|
54
|
+
**What changed.** 0.11.15 fixed the bundle step, but the public npm publish
|
|
55
|
+
then failed one command later, in the declaration emit
|
|
56
|
+
(`tsc -p tsconfig.build.json`). It reported
|
|
57
|
+
`../server-node/client.ts(29,43): error TS2307: Cannot find module 'openapi-fetch'`,
|
|
58
|
+
for the same reason as before: TypeScript resolves the bare import by walking
|
|
59
|
+
up from `server-node/`, and the public repo installs the package only under
|
|
60
|
+
`web-sdk/`. `web-sdk/tsconfig.build.json` now maps `openapi-fetch` to
|
|
61
|
+
`./node_modules/openapi-fetch`.
|
|
62
|
+
|
|
63
|
+
**npm users:** the 0.11.14 changes first reach npm in 0.11.16. Neither 0.11.14
|
|
64
|
+
nor 0.11.15 was published to npm (both are on GitHub Packages, PyPI and
|
|
65
|
+
crates.io). There is no behavior change across 0.11.14 through 0.11.16.
|
|
66
|
+
|
|
67
|
+
**Landed in:** 0.11.16.
|
|
68
|
+
**Fail-closed in:** n/a (build only).
|
|
69
|
+
**Proving test:** the public repo's `release-npm.yml` build step for
|
|
70
|
+
`v0.11.16`. It was reproduced locally by hiding the root `openapi-fetch` link
|
|
71
|
+
to match the public layout: the declaration emit failed with the same TS2307
|
|
72
|
+
before this change, and tsup, the declaration emit and rollup dts all pass
|
|
73
|
+
after it.
|
|
74
|
+
|
|
75
|
+
## 0.11.15
|
|
76
|
+
|
|
77
|
+
### Build fix: the npm package publishes again (no API change)
|
|
78
|
+
|
|
79
|
+
**What changed.** 0.11.14 was published to GitHub Packages, PyPI and
|
|
80
|
+
crates.io, but the public npm publish of `@revturbine/sdk` failed at the bundle
|
|
81
|
+
step: `Could not resolve "openapi-fetch"` from `server-node/client.ts`, which
|
|
82
|
+
the `headless` entry re-exports. `openapi-fetch` has been bundled since BL-0354
|
|
83
|
+
(0.11.14 was the first release to carry it). A bare import resolves by walking
|
|
84
|
+
up from the importing file, so from `server-node/` it never reached
|
|
85
|
+
`web-sdk/node_modules`, which is the only place the public repo installs it.
|
|
86
|
+
`web-sdk/tsup.config.ts` now gives esbuild `web-sdk/node_modules` as a fallback
|
|
87
|
+
`nodePaths` entry, so every file the SDK bundles resolves the SDK's own
|
|
88
|
+
dependencies.
|
|
89
|
+
|
|
90
|
+
**npm users:** everything listed under 0.11.14 below first reaches npm in
|
|
91
|
+
0.11.15. There is no behavior change between 0.11.14 and 0.11.15. PyPI and
|
|
92
|
+
crates.io get 0.11.15 in lockstep, with no change.
|
|
93
|
+
|
|
94
|
+
**Landed in:** 0.11.15.
|
|
95
|
+
**Fail-closed in:** n/a (build only).
|
|
96
|
+
**Proving test:** the public repo's `release-npm.yml` bundle step (tsup +
|
|
97
|
+
declarations) for `v0.11.15`. Locally, `web-sdk/tsup.config.ts` builds the
|
|
98
|
+
bundle, and an esbuild probe confirms `nodePaths` resolves a bare import from a
|
|
99
|
+
sibling directory that has no `node_modules` of its own.
|
|
100
|
+
|
|
101
|
+
## 0.11.14
|
|
102
|
+
|
|
103
|
+
This release carries the BL-0335 change below plus every entry that main held
|
|
104
|
+
under "Unreleased" since 0.11.13 (BL-0381, BL-0382, BL-0366, BL-0375, BL-0369,
|
|
105
|
+
BL-0352, BL-0354 and BL-0351).
|
|
106
|
+
|
|
107
|
+
### `tenantId` is optional on a browser init with a `publicKey`; the key is the sole arbiter of the tenant (BL-0335, D-49)
|
|
108
|
+
|
|
109
|
+
**What changed.** Before, a browser init needed both a `tenantId` and a
|
|
110
|
+
`publicKey`. The public key is bound to exactly one tenant, and the control
|
|
111
|
+
plane already resolved the tenant from the key alone, so the id was redundant
|
|
112
|
+
configuration, and a wrong one was silently ignored. Now:
|
|
113
|
+
|
|
114
|
+
- `RevTurbineInitOptions.tenantId` is **optional**. The minimal hosted init is
|
|
115
|
+
`{ publicKey, endpoint }`.
|
|
116
|
+
- A key-only integration sends **no** `x-tenant-id`. It reads the tenant from
|
|
117
|
+
the key-authenticated Playbook delivery: the signed manifest's `tenant_id`
|
|
118
|
+
and the Playbook's own `tenant_id`. Until the first Playbook loads, the
|
|
119
|
+
tenant is unknown, so `getUserContext().tenant_id` and the event envelope's
|
|
120
|
+
`tenant_id` read `''` and the `/api/track` row omits `tenant_id`. The
|
|
121
|
+
control plane stamps the key's tenant regardless.
|
|
122
|
+
- A **configured** `tenantId` that matches the key's tenant behaves exactly as
|
|
123
|
+
before: it is still sent, and every locally persisted key (anonymous id,
|
|
124
|
+
decision cache, caps, interaction state) is unchanged.
|
|
125
|
+
- A **configured** `tenantId` that does **not** match the key's tenant is no
|
|
126
|
+
longer silently ignored, and it is not refused either (D-49). When the
|
|
127
|
+
key-authenticated delivery names another tenant, the SDK logs one warning
|
|
128
|
+
per init and adopts the key's tenant:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
[RevTurbine] tenantId <configured> does not match the key's tenant <key tenant>; using <key tenant>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The signed manifest is activated (before, a manifest whose tenant differed
|
|
135
|
+
from the configured one was refused and the SDK fell back to the legacy
|
|
136
|
+
config fetch). After adoption, `getUserContext().tenant_id`, event envelopes
|
|
137
|
+
and the `x-tenant-id` the SDK still sends all carry the key's tenant. Local
|
|
138
|
+
storage keys stay namespaced by the configured id, so nothing persisted is
|
|
139
|
+
lost.
|
|
140
|
+
- A key-only integration keys local state by a short hash of the public key
|
|
141
|
+
(`revturbine:pk-…:`), so rotating the key starts a fresh local namespace,
|
|
142
|
+
just as changing `tenantId` always did.
|
|
143
|
+
- The theme loader (`fetchThemeOverride`) no longer sends the `'local'`
|
|
144
|
+
placeholder as a tenant header. `ThemeLoaderOptions.tenantId` is optional,
|
|
145
|
+
and the new `storageScope` option keeps the persisted-theme key.
|
|
146
|
+
- `ConfigTargetDefaults.tenantId` is optional. Without it, the artifact's own
|
|
147
|
+
`tenant_id` is used.
|
|
148
|
+
- Init now fails loudly when no tenant can be identified. It throws when
|
|
149
|
+
neither `publicKey` nor `tenantId` is present, and when a `local_only` init
|
|
150
|
+
has no `tenantId` and no `localRuntime.playbook`. With a local Playbook,
|
|
151
|
+
`local_only` still defaults the tenant to `'local'`, and a local Playbook's
|
|
152
|
+
`tenant_id` stays a guard behind the init option (plan 233 REQ-2): there is
|
|
153
|
+
no key there to arbitrate.
|
|
154
|
+
|
|
155
|
+
**Server side (web #927).** The control plane uses the key's tenant
|
|
156
|
+
unconditionally on every public-key lane. A tenant id that IS sent, whether as
|
|
157
|
+
a header or as `tenant_id` in an event or interaction body, is diagnostic only:
|
|
158
|
+
a mismatch is logged server-side and ignored. No request is refused for it.
|
|
159
|
+
|
|
160
|
+
**Landed in:** 0.11.14 (`web-sdk` only; `server-python`/`server-rust` bumped
|
|
161
|
+
in lockstep with no behavior change: the server ports authenticate with the
|
|
162
|
+
server key and build a per-request context with a `tenant_id`, and they have
|
|
163
|
+
no browser `publicKey` init option to relax).
|
|
164
|
+
**Fail-closed in:** 0.11.14. An init with no identifiable tenant throws;
|
|
165
|
+
before, it was a type error only, and JS callers got `undefined`. A mismatching
|
|
166
|
+
configured tenant warns from 0.11.14; before, it was silent.
|
|
167
|
+
**Proving test:**
|
|
168
|
+
- `web-sdk/customer-side-tenant-optional.test.ts`: key-only init on both
|
|
169
|
+
delivery lanes (no tenant header; tenant adopted from the manifest and
|
|
170
|
+
Playbook), configured matching tenant unchanged (header sent, storage keys
|
|
171
|
+
byte-identical), a mismatching configured tenant warned once and the key's
|
|
172
|
+
tenant adopted on both lanes, the key-hash namespace, the init guards, and
|
|
173
|
+
the theme loader.
|
|
174
|
+
- `web-sdk/init-options-exactness.test-d.ts`: a key-only init compiles.
|
|
175
|
+
|
|
176
|
+
### server-node `setBuiltinDimensions()` — your backend sets built-in dimension values, with an `override` flag (BL-0382, rulings D-46 / D-47)
|
|
177
|
+
|
|
178
|
+
**What changed.** Additive. The customer's backend can now SET built-in
|
|
179
|
+
segment dimension values for a user, not just read the control plane's
|
|
180
|
+
(D-46: "the customer's backend calling revturbine to set the dimension").
|
|
181
|
+
|
|
182
|
+
- **server-node** — `RevTurbineServer.setBuiltinDimensions(userId, dimensions,
|
|
183
|
+
{ override })` calls the widened `upsertServerUserContext`
|
|
184
|
+
(`POST /api/sdk/user-contexts`) with `{ user_id, builtin_dimensions,
|
|
185
|
+
override }` and the server key. Settable: `subscription_state`,
|
|
186
|
+
`trial_type`, `billing_health`, `activity_level`, `buyer_role`,
|
|
187
|
+
`email_type`, `region`, `device_type` (typed as
|
|
188
|
+
`ServerBuiltinDimensionsWrite`, each a closed vocabulary; `null` clears a
|
|
189
|
+
dimension). `seat_type` stays `assignSeatType`. It resolves to the stored
|
|
190
|
+
state — `seat_type_handle`, the customer-set `builtin_dimensions`, the
|
|
191
|
+
pinned `overrides`, `updated_at`. A refusal throws the new
|
|
192
|
+
`RevTurbineDimensionWriteError` (`status`, `requestId`, machine `code`,
|
|
193
|
+
typed `reason`: `invalid_request` / `unauthorized` / `forbidden` /
|
|
194
|
+
`unknown_dimension_value` / `request_failed`); it never carries the key.
|
|
195
|
+
- **`override` (D-47).** `false` (default) is an UPDATE: the value is delivered
|
|
196
|
+
until a newer RevTurbine enrichment change supersedes it (last writer wins,
|
|
197
|
+
enrichment included), and writing a pinned dimension without the flag
|
|
198
|
+
unpins it. `true` is an OVERRIDE: the dimension is pinned and enrichment
|
|
199
|
+
never replaces it until you write it again without the flag or clear it.
|
|
200
|
+
The control plane applies this before delivery, so the browser SDK's client
|
|
201
|
+
context and `getBuiltinDimensions()` both return the result; the SDK
|
|
202
|
+
delivery rule and the parity goldens are unchanged.
|
|
203
|
+
- **`assignSeatType(userId, handle, { builtinDimensions, override })`** can
|
|
204
|
+
write dimension values in the same upsert. Its `SeatAssignmentErrorReason`
|
|
205
|
+
gains `unknown_dimension_value` (a 422 with code `UNKNOWN_DIMENSION_VALUE`).
|
|
206
|
+
Its response now also carries `builtin_dimensions` and `overrides`.
|
|
207
|
+
- **Python and Rust get no HTTP client** (unchanged: neither port has a
|
|
208
|
+
server-key transport). A backend on those stacks calls the endpoint itself.
|
|
209
|
+
- Browser input types are untouched: `builtin_dimensions` is never a browser
|
|
210
|
+
write path.
|
|
211
|
+
|
|
212
|
+
The `@revt-eng/*` pins move to `0.1.374`, which widens the operation
|
|
213
|
+
(scaffold #431).
|
|
214
|
+
|
|
215
|
+
**Landed in:** 0.11.14.
|
|
216
|
+
**Fail-closed in:** n/a (additive).
|
|
217
|
+
**Proving test:** `tests/server-node-set-builtin-dimensions.test.ts`,
|
|
218
|
+
`tests/server-node-assign-seat-type.test.ts`.
|
|
219
|
+
|
|
220
|
+
### Behaviour change: the browser SDK ignores app-set `builtin_dimensions` (BL-0381, ruling D-46)
|
|
221
|
+
|
|
222
|
+
**What changed.** Trust in a built-in dimension value is decided by *party*
|
|
223
|
+
(Kent, D-46): the browser client never asserts one, the customer's server may,
|
|
224
|
+
and RevTurbine's own enrichment is optional. `builtin_dimensions` therefore
|
|
225
|
+
joins `seat_type_handle`, `activity_score` and `activity_score_computed_at`
|
|
226
|
+
as a server-assigned key in the browser SDK.
|
|
227
|
+
|
|
228
|
+
- **Types.** `builtin_dimensions` is omitted from `RevTurbineUserContext`,
|
|
229
|
+
`RevTurbineUpdateInput` and `IdentifyContextInput`, so
|
|
230
|
+
`update({ builtin_dimensions })`, `setUserContext({ builtin_dimensions })`,
|
|
231
|
+
`identify(id, { builtin_dimensions })` and `user: { builtin_dimensions }`
|
|
232
|
+
no longer compile.
|
|
233
|
+
- **Runtime.** Every browser entry point drops the key and warns with the
|
|
234
|
+
server-assigned diagnostic (`update()`, `identify()`, `setUserContext()`,
|
|
235
|
+
the `user` init option). A persisted `local_only` state blob that carries it
|
|
236
|
+
is purged on restore, and a server-action result that echoes it is stripped
|
|
237
|
+
silently. Before this change, 0.11.12 and 0.11.13 merged an app-set value
|
|
238
|
+
through `setUserContext()` and the `user` option. An app could then claim
|
|
239
|
+
any built-in segment (for example `rt.subscription_state.paid`) from the
|
|
240
|
+
browser.
|
|
241
|
+
- **The one browser source** is the authenticated
|
|
242
|
+
`GET /api/sdk/client-context` delivery (`fetchClientContext`, or a
|
|
243
|
+
`clientSession` minter). Its values overlay per leaf exactly as before.
|
|
244
|
+
`getUserContext()` still returns the held value.
|
|
245
|
+
- **`local_only`.** Built-in segment *definitions* are still synthesized for
|
|
246
|
+
a local Playbook (BL-0365), so chips and rules naming `rt.*` segments stay
|
|
247
|
+
valid. Their *values* no longer come from the app: **a browser `local_only`
|
|
248
|
+
app can no longer target built-ins unless a server-provided context
|
|
249
|
+
delivers them.** With no delivery every `rt_*` trait is absent and the
|
|
250
|
+
built-in segments fail closed (PD-4). `rt.registration_state.*` still
|
|
251
|
+
resolves from the identified id alone.
|
|
252
|
+
- **Unchanged: server SDKs.** They are the customer's server. Their overlay
|
|
253
|
+
inputs stay: server-node's `createStaticProviders({ userContext:
|
|
254
|
+
{ builtin_dimensions }, serverBuiltinDimensions })`, Python's
|
|
255
|
+
`builtin_dimensions` / `server_builtin_dimensions` and Rust's
|
|
256
|
+
`builtin_dimensions` / `server_builtin_dimensions`. Parity goldens are
|
|
257
|
+
unchanged.
|
|
258
|
+
|
|
259
|
+
This supersedes the part of BL-0352 (below) that made `update()` apply
|
|
260
|
+
`builtin_dimensions`. That change never shipped.
|
|
261
|
+
|
|
262
|
+
**Landed in:** 0.11.14.
|
|
263
|
+
**Fail-closed in:** the same release. The compile-time omission and the
|
|
264
|
+
runtime drop ship together.
|
|
265
|
+
**Proving test:**
|
|
266
|
+
- `web-sdk/update-keys-exhaustiveness.test-d.ts` (`pnpm check:types:exact`):
|
|
267
|
+
`builtin_dimensions` is in the server-assigned set.
|
|
268
|
+
- `web-sdk/identify-guardrails.test.ts`: describe block
|
|
269
|
+
`builtin_dimensions never enters the browser context from the app`.
|
|
270
|
+
- `web-sdk/local-builtin-segments.test.ts`: `chip … with app-set … fails
|
|
271
|
+
closed` (and the delivered cases still match).
|
|
272
|
+
- `web-sdk/customer-side-rt-entitlement-gate.test.ts`: `denies when the app
|
|
273
|
+
sets builtin_dimensions itself via update()`.
|
|
274
|
+
- `web-sdk/customer-side-client-context.test.ts`: `holds exactly the
|
|
275
|
+
delivered value; an app-set value never reaches the context`.
|
|
276
|
+
|
|
277
|
+
### Server SDKs get the control plane's built-in dimensions: `getBuiltinDimensions()` + a server overlay (BL-0366, plan 279 PD-3)
|
|
278
|
+
|
|
279
|
+
**What changed.** Additive. Before this, a server-side decision never saw the
|
|
280
|
+
control plane's Subscription State, Trial Type, Activity State, Seat Type or
|
|
281
|
+
Buyer Role. The Python and Rust ports stamped `rt_*` traits only from the
|
|
282
|
+
app-supplied `builtin_dimensions`, and the fetch that delivers the server's
|
|
283
|
+
values existed only in the browser SDK's client-context lane.
|
|
284
|
+
|
|
285
|
+
- **server-node** — `RevTurbineServer.getBuiltinDimensions(userId)` calls
|
|
286
|
+
`getServerUserBuiltinDimensions`
|
|
287
|
+
(`GET /api/sdk/user-contexts/{userId}/builtin-dimensions`) through the
|
|
288
|
+
generated typed client with the server key the client already holds. It
|
|
289
|
+
resolves to `{ tenant_id, user_id, builtin_dimensions, resolved_at }`
|
|
290
|
+
(re-exported as `ServerUserBuiltinDimensions`). The tenant comes from the
|
|
291
|
+
key: it is not a parameter, and no tenant header is sent. A refusal throws
|
|
292
|
+
the new `RevTurbineBuiltinDimensionsError` (`status`, `requestId`, machine
|
|
293
|
+
`code`, typed `reason`: `invalid_request` / `unauthorized` / `forbidden` /
|
|
294
|
+
`request_failed`). It never carries the key.
|
|
295
|
+
- **The overlay (all three ports).** `createStaticProviders` takes
|
|
296
|
+
`serverBuiltinDimensions` (from `@revt-eng/core`, re-exported by server-node
|
|
297
|
+
together with `applyServerBuiltinDimensions`). Python's
|
|
298
|
+
`create_static_providers(server_builtin_dimensions=…)` and the
|
|
299
|
+
`RevTurbineCustomerSdk` user context key `server_builtin_dimensions` take
|
|
300
|
+
the same object. So do Rust's `StaticProviderOptions::server_builtin_dimensions`
|
|
301
|
+
and `sdk::UserContext::server_builtin_dimensions`, plus the
|
|
302
|
+
`apply_server_builtin_dimensions` helper in both ports. The object is
|
|
303
|
+
overlaid per key on the app-set `builtin_dimensions` before segment
|
|
304
|
+
membership is evaluated. A delivered leaf wins over the app's value (PD-3),
|
|
305
|
+
and an app-set leaf the server did not deliver is kept. This is the overlay
|
|
306
|
+
the browser SDK already applies to a client-context delivery. Without a user
|
|
307
|
+
context it is ignored, because nothing is evaluated then.
|
|
308
|
+
- **Python and Rust get no HTTP client.** Neither port has a transport or a
|
|
309
|
+
server-key client (BL-0354), and this change does not add one. The app's
|
|
310
|
+
backend reads the endpoint itself (or through server-node) and passes the
|
|
311
|
+
response's `builtin_dimensions` in.
|
|
312
|
+
|
|
313
|
+
Rust callers that build `StaticProviderOptions` or `sdk::UserContext` as a
|
|
314
|
+
full struct literal without `..Default::default()` must add the new field.
|
|
315
|
+
|
|
316
|
+
The `@revt-eng/*` pins move to `0.1.373`, which adds the operation and the
|
|
317
|
+
core overlay (scaffold #430).
|
|
318
|
+
|
|
319
|
+
**Landed in:** 0.11.14.
|
|
320
|
+
**Fail-closed in:** n/a (additive).
|
|
321
|
+
**Proving test:** `tests/server-node-get-builtin-dimensions.test.ts`,
|
|
322
|
+
`server-python/tests/adapters/test_static.py`,
|
|
323
|
+
`server-rust/tests/static_providers.rs`, and the parity fixtures
|
|
324
|
+
`segment_membership_server_overlay_wins` / `segment_membership_server_overlay_demotes`.
|
|
325
|
+
|
|
326
|
+
### `RevTurbineProvider` disposes the previous SDK instance on an `options` change (BL-0375)
|
|
327
|
+
|
|
328
|
+
**What changed.** `RevTurbineProvider`'s init effect creates a new SDK instance
|
|
329
|
+
whenever the `options` prop (or `bootstrapPlacements`) changes identity, but its
|
|
330
|
+
cleanup previously only flipped an internal `mounted` flag — it never called
|
|
331
|
+
`dispose()` on the instance it was replacing. A host that rebuilds `options` on
|
|
332
|
+
every render (an unmemoized `options={{ ... }}` literal, or an object built in
|
|
333
|
+
an un-annotated `useMemo` with an unstable dependency — the shape web's dogfood
|
|
334
|
+
provider had until web #944) therefore re-initialized on every render and
|
|
335
|
+
leaked every earlier instance's flush-interval timer, `pagehide` /
|
|
336
|
+
`visibilitychange` listeners, and any buffered telemetry.
|
|
337
|
+
|
|
338
|
+
The cleanup now calls the previous instance's existing `dispose()` (flushes
|
|
339
|
+
pending telemetry, detaches its listeners and timer) before the next render's
|
|
340
|
+
instance is constructed. The re-init contract is unchanged: a new `options`
|
|
341
|
+
object identity — even one with identical content — still creates a new SDK
|
|
342
|
+
instance; that documented behavior was never the bug, the leak was. When a new
|
|
343
|
+
`options` object is recreated with the same content as the previous one, the
|
|
344
|
+
provider now also logs a one-time (per mounted provider lifetime) development
|
|
345
|
+
warning pointing integrators at memoizing `options`, in addition to the
|
|
346
|
+
existing every-time identity-change warning.
|
|
347
|
+
|
|
348
|
+
**Landed in:** no release — no signature changed; `dispose()` on
|
|
349
|
+
`RevTurbineCustomerSdk` already existed (documented for manual SPA-unmount
|
|
350
|
+
teardown) and is now also called automatically by the provider.
|
|
351
|
+
**Fail-closed in:** n/a — purely a leak fix; no customer-visible API changed.
|
|
352
|
+
**Proving test:**
|
|
353
|
+
`web-sdk/react/RevTurbineProvider.dispose-on-options-change.test.tsx`.
|
|
354
|
+
|
|
355
|
+
### Behaviour fix: server SDKs stop serving segment-chipped payloads to every user (BL-0369)
|
|
356
|
+
|
|
357
|
+
**What changed.** On the server lanes, every configured Playbook segment
|
|
358
|
+
counted as a segment the user was in. That covered the Python and Rust
|
|
359
|
+
`RevTurbineCustomerSdk`, `LocalRuntime`, and server-node's
|
|
360
|
+
`LocalEvaluationServer` fed by `createStaticProviders`. So a payload chipped
|
|
361
|
+
to any segment was shown to every user, and an entitlement rule targeted at
|
|
362
|
+
any segment granted to every user on the plan. This included the generated
|
|
363
|
+
built-in segments (`rt.<dimension>.<value>`, e.g.
|
|
364
|
+
`rt.subscription_state.paid`) that hosted Playbooks now export. The browser
|
|
365
|
+
SDK was not affected.
|
|
366
|
+
|
|
367
|
+
A user is now in a segment only when one of these is true, the same rule the
|
|
368
|
+
browser SDK applies:
|
|
369
|
+
|
|
370
|
+
- your app passed its handle in `segment_ids` (Python / Rust) or
|
|
371
|
+
`segmentIds` (`createStaticProviders`), or
|
|
372
|
+
- its predicates match the user's targeting traits. Those traits are built
|
|
373
|
+
from `custom`, the plan, and the reserved `rt_<dimension>` traits.
|
|
374
|
+
`rt_<dimension>` traits come **only** from `builtin_dimensions`, which the
|
|
375
|
+
server sets (plan 279 PD-3). For experiment segments, the user's
|
|
376
|
+
`experiments` assignments decide enrollment.
|
|
377
|
+
|
|
378
|
+
Anything else fails closed. An unset dimension, a trait the user doesn't
|
|
379
|
+
carry, or a chip that names an unknown segment serves nothing (PD-4).
|
|
380
|
+
|
|
381
|
+
New inputs (all additive): `custom`, `builtin_dimensions` and `experiments`
|
|
382
|
+
on the Python `UserContext` and the Rust `UserContext` struct. `segment_ids`
|
|
383
|
+
now also drives chips and rules, not just the catalog helpers.
|
|
384
|
+
`createStaticProviders` gets `segmentIds` and `userContext` from
|
|
385
|
+
`@revt-eng/core`. A side fix: the Python and Rust rules providers now carry
|
|
386
|
+
`segment_dimensions` and the Playbook's `format_version`, like TypeScript.
|
|
387
|
+
Without `segment_dimensions`, a rule targeted at segments in two dimensions
|
|
388
|
+
would have matched on either one once membership became real.
|
|
389
|
+
|
|
390
|
+
**Impact on existing integrations.** This is a correctness fix, but you may
|
|
391
|
+
notice it. If a server-side integration shows segment-chipped payloads or
|
|
392
|
+
grants segment-targeted entitlements, and it passes none of the inputs
|
|
393
|
+
above, those payloads now **stop showing** and those rules **stop
|
|
394
|
+
granting**. They were being over-served before. To keep serving users who
|
|
395
|
+
really are in the segment, pass `segment_ids` for segments your app
|
|
396
|
+
resolves, and `builtin_dimensions` / `custom` for predicate and built-in
|
|
397
|
+
segments. The Rust `UserContext` struct gains three fields. A struct literal
|
|
398
|
+
that does not end in `..Default::default()` needs them added.
|
|
399
|
+
|
|
400
|
+
The `@revt-eng/*` pins move to `0.1.372`: scaffold #429 is the BL-0369 core
|
|
401
|
+
fix, and it carries scaffold #428 (0.1.371: the `billing_health`
|
|
402
|
+
built-in dimension leaf, BL-0317, and `seat_type_handle` becoming
|
|
403
|
+
required-nullable on `ServerUserContextUpsert`, BL-0367). The vendored port
|
|
404
|
+
types follow: in Python, `ServerUserContextUpsert.seat_type_handle` loses its
|
|
405
|
+
default; in Rust, `ClientContextBuiltinDimensionsBillingHealth` is added.
|
|
406
|
+
|
|
407
|
+
**Landed in:** 0.11.14 (`@revt-eng/core` 0.1.372 carries
|
|
408
|
+
the scaffold BL-0369 fix).
|
|
409
|
+
**Fail-closed in:** the same release.
|
|
410
|
+
**Proving test:**
|
|
411
|
+
- `tests/parity/fixtures/segment_membership_{no_inputs,builtin_and_traits,explicit_ids}.json`
|
|
412
|
+
(TS == Python == Rust).
|
|
413
|
+
- `server-python/tests/adapters/test_static.py` and
|
|
414
|
+
`server-rust/tests/static_providers.rs` (segment membership tests).
|
|
415
|
+
- `tests/server-node-local-segment-membership.test.ts`.
|
|
416
|
+
|
|
417
|
+
### `update()` carries `experiments` and `builtin_dimensions`; server-computed keys are dropped at every browser entry point (BL-0352, ruling D-36)
|
|
418
|
+
|
|
419
|
+
**What changed.** `update()`'s exhaustiveness check never ran. It was an
|
|
420
|
+
`expectTypeOf` inside a vitest `.test.ts`, and vitest does not typecheck. Four
|
|
421
|
+
fields had reached `RevTurbineUpdateInput` without reaching
|
|
422
|
+
`RECOGNIZED_UPDATE_KEYS`, so `update()` accepted them at compile time and
|
|
423
|
+
silently dropped them at runtime. Each one now has an explicit disposition:
|
|
424
|
+
|
|
425
|
+
- *(`builtin_dimensions` superseded by BL-0381 above: it is server-assigned
|
|
426
|
+
and dropped.)* `experiments` and `builtin_dimensions` are **now applied**
|
|
427
|
+
by `update()`.
|
|
428
|
+
The app may supply experiment assignments it already knows
|
|
429
|
+
(`runtime/experiment-assignment.md` §3, plan 183), and it may seed built-in
|
|
430
|
+
dimensions, for example in `local_only` mode. The client-context overlay
|
|
431
|
+
still wins when it is present (`targeting-studio-ui.md` §4.1). Before this
|
|
432
|
+
change, `update({ experiments })` compiled and did nothing.
|
|
433
|
+
- `activity_score` and `activity_score_computed_at` are **server-computed**.
|
|
434
|
+
The control plane's hourly activity-score refresh writes them (plan 180),
|
|
435
|
+
and the browser receives only the derived `activity_level` dimension. Like
|
|
436
|
+
`seat_type_handle` (BL-0351), they are now omitted from
|
|
437
|
+
`RevTurbineUserContext`, `RevTurbineUpdateInput` and `IdentifyContextInput`.
|
|
438
|
+
- `setUserContext()` and the `user` init option now **drop server-assigned
|
|
439
|
+
keys at runtime** (`seat_type_handle`, `activity_score`,
|
|
440
|
+
`activity_score_computed_at`) and report them. `identify()` and `update()`
|
|
441
|
+
already dropped them, and now report them with a server-assigned warning
|
|
442
|
+
that names the backend as the writer, not the "pass it under `custom`"
|
|
443
|
+
hint. Before this change, a plain-JS `setUserContext({ seat_type_handle })`
|
|
444
|
+
merged the key into the held context, and local mode persisted it. A state
|
|
445
|
+
blob persisted before this change is purged of those keys when it is
|
|
446
|
+
restored. A server-action result that echoes them back is stripped without
|
|
447
|
+
a warning.
|
|
448
|
+
- New exported types `RecognizedUpdateKey` and `RecognizedIdentifyKey`
|
|
449
|
+
(additive) back a real compile-time derivation in
|
|
450
|
+
`web-sdk/update-keys-exhaustiveness.test-d.ts`, which
|
|
451
|
+
`pnpm check:types:exact` compiles. A future `UserContext` field fails the
|
|
452
|
+
build until it is added to the runtime list or omitted as server-assigned.
|
|
453
|
+
|
|
454
|
+
**Deferred to `0.12.0`:** `setUserContext()` still takes
|
|
455
|
+
`RevTurbineUserContext` without `Exact<>`, so other undeclared keys that
|
|
456
|
+
arrive through a variable are not rejected at compile time. Adding `Exact<>`
|
|
457
|
+
would break callers that pass a wider object today. `IdentifyContextInput`
|
|
458
|
+
also still declares 11 keys that `identify()` drops with a warning (`trial`,
|
|
459
|
+
`tiers`, `experiments`, …). The type-test pins that list, so it cannot grow
|
|
460
|
+
silently. Narrowing the type is a breaking change and waits for the minor.
|
|
461
|
+
|
|
462
|
+
The `@revt-eng/*` pins move to `0.1.370` (scaffold #427: discovery-owned
|
|
463
|
+
`SurfaceSlotSchema.slot_name`). That field now appears in the generated client
|
|
464
|
+
types and the vendored Python and Rust port types. The SDK does not write it.
|
|
465
|
+
|
|
466
|
+
**Landed in:** 0.11.14.
|
|
467
|
+
**Fail-closed in:** the same release. The compile-time omission and the
|
|
468
|
+
runtime drop at all four entry points both ship in it.
|
|
469
|
+
**Proving test:**
|
|
470
|
+
- `web-sdk/update-keys-exhaustiveness.test-d.ts` (`pnpm check:types:exact`).
|
|
471
|
+
- `web-sdk/identify-guardrails.test.ts`: describe blocks
|
|
472
|
+
`server-computed keys never enter the browser context`,
|
|
473
|
+
`setUserContext() drops server-assigned keys at its runtime boundary` and
|
|
474
|
+
`update() carries app-owned experiments` (renamed by BL-0381).
|
|
475
|
+
|
|
476
|
+
### server-node `assignSeatType()` writes a user's seat type (BL-0354, plan 279 TASK-16a, ruling D-36)
|
|
477
|
+
|
|
478
|
+
**What changed.** Additive. `RevTurbineServer` (server-node) gains
|
|
479
|
+
`assignSeatType(userId: string, seatTypeHandle: string | null)`. It calls
|
|
480
|
+
`upsertServerUserContext` (`POST /api/sdk/user-contexts`) through the generated
|
|
481
|
+
typed client with the server key the client already holds. The request body is
|
|
482
|
+
exactly `{ user_id, seat_type_handle }`. `null` clears the assignment. The
|
|
483
|
+
tenant comes from the key: it is not a parameter, and no tenant header is sent.
|
|
484
|
+
It resolves to the stored assignment
|
|
485
|
+
`{ tenant_id, user_id, updated_at, seat_type_handle }` (re-exported as
|
|
486
|
+
`ServerUserContextAssignment`).
|
|
487
|
+
|
|
488
|
+
A refusal throws the new `RevTurbineSeatAssignmentError`. It carries `status`,
|
|
489
|
+
`requestId`, the control plane's machine `code` when present, and a typed
|
|
490
|
+
`reason`: `unknown_seat_type` (422: the handle is not one of the tenant's seat
|
|
491
|
+
types), `forbidden` (403: the credential is not a server key), `unauthorized`
|
|
492
|
+
(401), `invalid_request` (400) or `request_failed`. Like
|
|
493
|
+
`RevTurbineClientSessionError`, it never carries the key or the free-text
|
|
494
|
+
response message.
|
|
495
|
+
|
|
496
|
+
Per D-36 the seat assignment is server-written only, so there is no browser SDK
|
|
497
|
+
surface for this. The Python and Rust ports have no HTTP client or server-key
|
|
498
|
+
client, only the vendored `ServerUserContextUpsert` / `ServerUserContextAssignment`
|
|
499
|
+
models, so they gain no helper.
|
|
500
|
+
|
|
501
|
+
The `@revt-eng/*` pins move to `0.1.369`, which adds that operation to the
|
|
502
|
+
external contract (scaffold #425).
|
|
503
|
+
|
|
504
|
+
**Landed in:** 0.11.14.
|
|
505
|
+
**Fail-closed in:** n/a (additive).
|
|
506
|
+
**Proving test:** `tests/server-node-assign-seat-type.test.ts`.
|
|
507
|
+
|
|
508
|
+
### `seat_type_handle` is server-assigned and never a browser input (BL-0351, plan 279 TASK-16a, ruling D-36)
|
|
509
|
+
|
|
510
|
+
**What changed.** Scaffold `0.1.368` adds `UserContextSchema.seat_type_handle`,
|
|
511
|
+
the user's persisted seat-type assignment. Per D-36 it has exactly two writers:
|
|
512
|
+
the tenant's backend (a server-key user-context upsert) and RevTurbine's own
|
|
513
|
+
server-side enrichment in hosted mode. The browser never writes it. Without a
|
|
514
|
+
change here the new field would have flowed into the browser input types
|
|
515
|
+
through `UserContextInput`. Instead:
|
|
516
|
+
|
|
517
|
+
- `RevTurbineUserContext` omits `seat_type_handle`. So do
|
|
518
|
+
`RevTurbineUpdateInput`, which derives from it, and `IdentifyContextInput`.
|
|
519
|
+
`identify()`, `update()` and the `user` init option reject the key at compile
|
|
520
|
+
time, including when it arrives through a variable (`Exact<…>`).
|
|
521
|
+
`setUserContext()` rejects it in an object literal.
|
|
522
|
+
- The key stays out of the runtime recognized-key lists, so a plain-JS caller's
|
|
523
|
+
`seat_type_handle` is dropped at the `identify()`/`update()` boundary and
|
|
524
|
+
reported by the existing unrecognized-key warning. It is never merged into the
|
|
525
|
+
held context.
|
|
526
|
+
- The browser receives the **resolved** seat type as
|
|
527
|
+
`builtin_dimensions.seat_type` / `builtin_dimensions.buyer_role` from the
|
|
528
|
+
client-context fetch (plan 279 TASK-16, web), unchanged here.
|
|
529
|
+
|
|
530
|
+
No released SDK type ever carried `seat_type_handle`, so nothing an
|
|
531
|
+
integration compiles today stops compiling. The `@revt-eng/*` pins move to
|
|
532
|
+
`0.1.368`. That version also adds `SeatTypeSchema.is_buyer` (default `false`),
|
|
533
|
+
which now appears in the vendored Python and Rust port types.
|
|
534
|
+
|
|
535
|
+
A server-node `assignSeatType(userId, handle | null)` helper was **not** in this
|
|
536
|
+
change, because the server-key operation it wraps (`upsertServerUserContext`,
|
|
537
|
+
`POST /api/sdk/user-contexts`, plan 279 TASK-15a) was not yet in the published
|
|
538
|
+
external contract. It shipped with scaffold `0.1.369` (see the BL-0354 entry
|
|
539
|
+
above).
|
|
540
|
+
|
|
541
|
+
**Landed in:** 0.11.14. This PR bumps pins only.
|
|
542
|
+
**Fail-closed in:** the same release (compile-time rejection; runtime drop).
|
|
543
|
+
**Proving test:**
|
|
544
|
+
- `web-sdk/user-context-exactness.test-d.ts`: the `@ts-expect-error` block
|
|
545
|
+
"Server-assigned fields" (`pnpm check:types:exact`).
|
|
546
|
+
- `web-sdk/identify-guardrails.test.ts`: describe block
|
|
547
|
+
`seat_type_handle never enters the browser context`.
|
|
548
|
+
|
|
549
|
+
## 0.11.13
|
|
550
|
+
|
|
551
|
+
### Slot lifecycle events carry the slot's `route` (BL-0207, ruling D-30)
|
|
552
|
+
|
|
553
|
+
**What changed.** Every slot lifecycle event (`slot_evaluated`, `slot_filled`,
|
|
554
|
+
`slot_empty`, `slot_suppressed`, `slot_error`) now carries `route`: the app
|
|
555
|
+
route the slot rendered on. Ingestion-driven surface-slot discovery reads it
|
|
556
|
+
and persists it on the discovered slot. There is no SDK write to any CRUD
|
|
557
|
+
route. The field is additive and optional in the scaffold contract
|
|
558
|
+
(`slotContextBase.route`).
|
|
559
|
+
|
|
560
|
+
A route is a **path, never a URL**. It has no origin, query string or
|
|
561
|
+
fragment. The React layer computes it at emission time:
|
|
562
|
+
|
|
563
|
+
- **Default:** `window.location.pathname`, with identifier-like segments
|
|
564
|
+
templated to `:id`. That covers numbers, UUIDs, long hex, prefixed ids like
|
|
565
|
+
`cus_…`, long opaque tokens and email-shaped values, raw or
|
|
566
|
+
percent-encoded. For example, `/accounts/4821/billing?coupon=X` reports
|
|
567
|
+
`/accounts/:id/billing`.
|
|
568
|
+
- **`<RevTurbineRoute route={…}>`** (new, renderless) declares the framework
|
|
569
|
+
route pattern for its subtree, and the innermost boundary wins.
|
|
570
|
+
- **Next.js App Router:** build the pattern with the new pure helper
|
|
571
|
+
`routePatternFromParams(usePathname(), useParams())`, which gives
|
|
572
|
+
`/projects/[projectId]/settings`. The SDK does not import `next/navigation`.
|
|
573
|
+
|
|
574
|
+
Also new:
|
|
575
|
+
|
|
576
|
+
- **Headless exports:** `normalizeSlotRoute`, `routePatternFromParams`,
|
|
577
|
+
`currentBrowserRoute`, `isIdentifierLikeSegment` and `SLOT_ROUTE_MAX_LENGTH`
|
|
578
|
+
(512).
|
|
579
|
+
- **React hook:** `useSlotRoute()`.
|
|
580
|
+
- **Headless controller option:** `PlacementControllerOptions.route` accepts a
|
|
581
|
+
string or a getter. When it is omitted, a headless controller emits
|
|
582
|
+
`route: null`.
|
|
583
|
+
|
|
584
|
+
**Landed in:** 0.11.13 (`web-sdk` only; `server-python`/`server-rust` bumped
|
|
585
|
+
in lockstep with no behavior change — no port emits slot events). The
|
|
586
|
+
`@revt-eng/*` pins move to `0.1.366` (the scaffold contract: `slotContextBase.route`,
|
|
587
|
+
`SurfaceSlotSchema.route`, and the `SurfaceSlotStatus` = `live | idle`
|
|
588
|
+
vocabulary), and the vendored port types are re-synced — `SurfaceSlot.status`
|
|
589
|
+
is now `SurfaceSlotStatus` (`live`/`idle`, default `idle`) in both ports.
|
|
590
|
+
**Fail-closed in:** n/a (additive telemetry field).
|
|
591
|
+
**Proving test:**
|
|
592
|
+
- `web-sdk/slot-route.test.ts`: normalization, PII/query stripping, the
|
|
593
|
+
Next.js params recipe, and controller emission including the
|
|
594
|
+
throwing-getter case.
|
|
595
|
+
- `web-sdk/react/usePlacement.route.test.tsx`: the plain-DOM pathname case and
|
|
596
|
+
the `<RevTurbineRoute>` Next.js pattern case.
|
|
597
|
+
|
|
50
598
|
## 0.11.12
|
|
51
599
|
|
|
52
600
|
### `builtin_dimensions` now reaches the browser SDK from the client-context fetch (BL-0312, plan 279 TASK-5)
|
package/dist/growthbook.d.ts
CHANGED
|
@@ -226,11 +226,11 @@ declare const UserPlanContextSchema: z.ZodObject<{
|
|
|
226
226
|
*/
|
|
227
227
|
declare const UserBuiltinDimensionsSchema: z.ZodObject<{
|
|
228
228
|
activity_level: z.ZodOptional<z.ZodEnum<{
|
|
229
|
-
inactive: "inactive";
|
|
230
229
|
new: "new";
|
|
231
230
|
high: "high";
|
|
232
231
|
medium: "medium";
|
|
233
232
|
low: "low";
|
|
233
|
+
inactive: "inactive";
|
|
234
234
|
}>>;
|
|
235
235
|
subscription_state: z.ZodOptional<z.ZodEnum<{
|
|
236
236
|
trial: "trial";
|