@ops-ai/react-router-toggly 1.2.1 → 1.5.2
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 +54 -0
- package/README.md +1 -1
- package/dist/client.cjs +114 -15
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +5 -2
- package/dist/client.d.ts +5 -2
- package/dist/client.js +116 -17
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +50 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +42 -4
- package/dist/server.js.map +1 -1
- package/dist/telemetry/grpc.cjs +1 -1
- package/dist/telemetry/grpc.cjs.map +1 -1
- package/dist/telemetry/grpc.js +1 -1
- package/dist/telemetry/grpc.js.map +1 -1
- package/package.json +24 -23
- package/proto/usage.proto +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
|
+
## 1.5.2
|
|
2
|
+
|
|
3
|
+
2026-11-08
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- The browser client unwraps `evaluated-signed` `{ defs }` envelopes before
|
|
7
|
+
storing flags. A bare flag map still works. Previously the signed envelope
|
|
8
|
+
was stored as the flag map, so flag keys never matched [OPS-2064].
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `engines.node` floor raised from `>=18` to `>=22` (Node 18 and 20 are EOL).
|
|
12
|
+
- CI unit matrix is Node 22 / 24 / 26; packed RR7 and RR8 hosts run on Node 24.
|
|
13
|
+
- Packed hosts pin React Router 7.18.5 + React 18 and React Router 8.5.1 + React 19.3.1.
|
|
14
|
+
- Non-breaking dependency bumps within existing major ranges (`typescript` 5.9,
|
|
15
|
+
`ts-jest`, `@grpc/*`, `@ops-ai/toggly-*` patches/minors, `ws`).
|
|
16
|
+
|
|
17
|
+
### Tests
|
|
18
|
+
- Added mocked HTTP API-contract evaluation coverage for `definitions-signed`
|
|
19
|
+
(local server eval) and `evaluated-signed` (browser client) response shapes,
|
|
20
|
+
including a `{ defs }` envelope and a bare flag map.
|
|
21
|
+
|
|
22
|
+
## 1.5.1
|
|
23
|
+
|
|
24
|
+
2026-10-15
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- After `dispose()`, `use()` and `captureCheck()` no longer create an
|
|
28
|
+
unmanaged reporter or attach leftover lifecycle listeners [OPS-1758].
|
|
29
|
+
|
|
30
|
+
## 1.5.0
|
|
31
|
+
|
|
32
|
+
2026-10-11
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- `use()` for timed feature usage scopes (attempt, duration, errors) via `@ops-ai/toggly-client-telemetry` [OPS-1758].
|
|
36
|
+
|
|
1
37
|
# Changelog
|
|
2
38
|
|
|
39
|
+
## [1.4.0] - 2026-10-11
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- Usage scope metrics on `VariantStats` (`errorCount`, `durationSumMs`,
|
|
43
|
+
`errorDurationSumMs`) and batcher APIs `recordUsageError`, `recordUsageDuration`,
|
|
44
|
+
`recordUsageScopeOutcome` (proto fields 5–7).
|
|
45
|
+
|
|
46
|
+
## [1.3.1] - 2026-10-11
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- Only call `useRouteLoaderData` when `routeId` is set and use `useInRouterContext` so SSR and non-data-router apps hydrate from `fallbackContext` / `TogglyScript` without invoking data-router hooks [OPS-1756].
|
|
50
|
+
|
|
51
|
+
## [1.3.0] - 2026-10-04
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- Server export of `createSegmentMembershipClient` for Backend-key targeting-list updates.
|
|
55
|
+
|
|
56
|
+
|
|
3
57
|
## [1.2.1] - 2026-09-27
|
|
4
58
|
|
|
5
59
|
### Fixed
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ npm install @ops-ai/react-router-toggly react-router react react-dom
|
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Peers: `react-router ^7 || ^8`, `react` / `react-dom ^18 || ^19`. Optional:
|
|
67
|
-
`@react-router/node`. Requires Node `>=
|
|
67
|
+
`@react-router/node`. Requires Node `>=22`.
|
|
68
68
|
|
|
69
69
|
## Quick start
|
|
70
70
|
|
package/dist/client.cjs
CHANGED
|
@@ -84,6 +84,13 @@ function buildDefinitionsUrl(config, context) {
|
|
|
84
84
|
function parseVariantDefsPayload(parsed) {
|
|
85
85
|
return togglySignedDefs.asVariantDefsRecord(togglySignedDefs.unwrapDefsPayload(parsed));
|
|
86
86
|
}
|
|
87
|
+
function parseEvaluatedFlagsPayload(parsed) {
|
|
88
|
+
const body = togglySignedDefs.unwrapDefsPayload(parsed);
|
|
89
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
87
94
|
function variantDefsToFlags(defs) {
|
|
88
95
|
const out = {};
|
|
89
96
|
for (const key of Object.keys(defs)) {
|
|
@@ -186,6 +193,7 @@ function createBrowserTelemetry(config) {
|
|
|
186
193
|
}
|
|
187
194
|
};
|
|
188
195
|
const getReporter = () => {
|
|
196
|
+
if (disposed) return void 0;
|
|
189
197
|
if (!reporter) {
|
|
190
198
|
reporter = togglyClientTelemetry.createTelemetryReporter({
|
|
191
199
|
appKey: config.appKey,
|
|
@@ -207,6 +215,7 @@ function createBrowserTelemetry(config) {
|
|
|
207
215
|
};
|
|
208
216
|
function replay(entry) {
|
|
209
217
|
const target = getReporter();
|
|
218
|
+
if (!target) return;
|
|
210
219
|
target.setContext(entry.context);
|
|
211
220
|
if (entry.kind === "feature") {
|
|
212
221
|
const methods = ["recordCheck", "recordUsage", "recordView"];
|
|
@@ -274,6 +283,28 @@ function createBrowserTelemetry(config) {
|
|
|
274
283
|
}
|
|
275
284
|
const api = {
|
|
276
285
|
recordUsage: (key, variant = "enabled") => feature(key, variant, 1),
|
|
286
|
+
use: (key, fn, variant = "enabled") => {
|
|
287
|
+
if (!usage || disposed) {
|
|
288
|
+
return togglyClientTelemetry.runFeatureUsageScope(
|
|
289
|
+
{ recordAttempt: () => {
|
|
290
|
+
}, recordSuccessDuration: () => {
|
|
291
|
+
}, recordFailure: () => {
|
|
292
|
+
} },
|
|
293
|
+
fn
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const target = getReporter();
|
|
297
|
+
if (!target) {
|
|
298
|
+
return togglyClientTelemetry.runFeatureUsageScope(
|
|
299
|
+
{ recordAttempt: () => {
|
|
300
|
+
}, recordSuccessDuration: () => {
|
|
301
|
+
}, recordFailure: () => {
|
|
302
|
+
} },
|
|
303
|
+
fn
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return target.use(key, fn, variant);
|
|
307
|
+
},
|
|
277
308
|
recordView: (key, variant = "enabled") => feature(key, variant, 2),
|
|
278
309
|
incrementCounter: (key, value = 1) => event({ partition, context, key, kind: "counter", values: [value] }),
|
|
279
310
|
setGauge: (key, value) => event({ partition, context, key, kind: "gauge", values: [value] }),
|
|
@@ -293,7 +324,10 @@ function createBrowserTelemetry(config) {
|
|
|
293
324
|
if (disposed || !usage) return () => {
|
|
294
325
|
};
|
|
295
326
|
if (active) {
|
|
296
|
-
const
|
|
327
|
+
const target = getReporter();
|
|
328
|
+
if (!target) return () => {
|
|
329
|
+
};
|
|
330
|
+
const record = target.captureCheck();
|
|
297
331
|
return (key, result) => record(key, checkVariantLabel(result));
|
|
298
332
|
}
|
|
299
333
|
const attribution = context;
|
|
@@ -320,7 +354,7 @@ function createBrowserTelemetry(config) {
|
|
|
320
354
|
|
|
321
355
|
// src/client/sdk-identity.ts
|
|
322
356
|
var SDK_ID = "react-router";
|
|
323
|
-
var SDK_VERSION = "1.
|
|
357
|
+
var SDK_VERSION = "1.5.0";
|
|
324
358
|
function appendSdkQueryParams(params) {
|
|
325
359
|
params.set("sdk", SDK_ID);
|
|
326
360
|
params.set("sdkVersion", SDK_VERSION);
|
|
@@ -520,7 +554,7 @@ function TogglyProviderOwner({
|
|
|
520
554
|
return newFlags2;
|
|
521
555
|
}
|
|
522
556
|
variantsRef.current = null;
|
|
523
|
-
const newFlags = payload
|
|
557
|
+
const newFlags = parseEvaluatedFlagsPayload(payload);
|
|
524
558
|
logger.debug(`Fetched ${Object.keys(newFlags).length} flags.`);
|
|
525
559
|
return newFlags;
|
|
526
560
|
} catch (error) {
|
|
@@ -1053,32 +1087,97 @@ function FeatureGate({
|
|
|
1053
1087
|
const enabled = useFeatureGate(featureKeys, requirement, negate, context, contextKind);
|
|
1054
1088
|
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : null;
|
|
1055
1089
|
}
|
|
1056
|
-
function
|
|
1090
|
+
function resolveServerContext(data, fallbackContext) {
|
|
1091
|
+
const serverContext = data?.[TOGGLY_LOADER_KEY];
|
|
1092
|
+
return serverContext ?? fallbackContext;
|
|
1093
|
+
}
|
|
1094
|
+
function buildMergedConfig(config, serverContext) {
|
|
1095
|
+
return config ?? (serverContext ? {
|
|
1096
|
+
appKey: serverContext.appKey,
|
|
1097
|
+
environment: serverContext.environment
|
|
1098
|
+
} : void 0);
|
|
1099
|
+
}
|
|
1100
|
+
function RouterTogglyProviderInner({
|
|
1101
|
+
children,
|
|
1102
|
+
serverContext,
|
|
1103
|
+
config,
|
|
1104
|
+
...props
|
|
1105
|
+
}) {
|
|
1106
|
+
const mergedConfig = buildMergedConfig(config, serverContext);
|
|
1107
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TogglyProvider, { serverContext, config: mergedConfig, ...props, children });
|
|
1108
|
+
}
|
|
1109
|
+
function RouterTogglyProviderWithData({
|
|
1110
|
+
children,
|
|
1111
|
+
data,
|
|
1112
|
+
fallbackContext,
|
|
1113
|
+
config,
|
|
1114
|
+
...props
|
|
1115
|
+
}) {
|
|
1116
|
+
const serverContext = resolveServerContext(data, fallbackContext);
|
|
1117
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RouterTogglyProviderInner, { serverContext, config, ...props, children });
|
|
1118
|
+
}
|
|
1119
|
+
function RouterTogglyProviderFromRoute({
|
|
1057
1120
|
children,
|
|
1058
1121
|
routeId,
|
|
1059
1122
|
fallbackContext,
|
|
1060
1123
|
config,
|
|
1061
1124
|
...props
|
|
1062
1125
|
}) {
|
|
1063
|
-
const routeData = reactRouter.useRouteLoaderData(routeId
|
|
1126
|
+
const routeData = reactRouter.useRouteLoaderData(routeId);
|
|
1127
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1128
|
+
RouterTogglyProviderWithData,
|
|
1129
|
+
{
|
|
1130
|
+
data: routeData,
|
|
1131
|
+
fallbackContext,
|
|
1132
|
+
config,
|
|
1133
|
+
...props,
|
|
1134
|
+
children
|
|
1135
|
+
}
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
function RouterTogglyProviderFromLoader({
|
|
1139
|
+
children,
|
|
1140
|
+
fallbackContext,
|
|
1141
|
+
config,
|
|
1142
|
+
...props
|
|
1143
|
+
}) {
|
|
1064
1144
|
const loaderData = reactRouter.useLoaderData();
|
|
1065
|
-
const data = routeId ? routeData : loaderData;
|
|
1066
|
-
const serverContext = data?.[TOGGLY_LOADER_KEY];
|
|
1067
|
-
const resolvedServerContext = serverContext ?? fallbackContext;
|
|
1068
|
-
const mergedConfig = config ?? (resolvedServerContext ? {
|
|
1069
|
-
appKey: resolvedServerContext.appKey,
|
|
1070
|
-
environment: resolvedServerContext.environment
|
|
1071
|
-
} : void 0);
|
|
1072
1145
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
-
|
|
1146
|
+
RouterTogglyProviderWithData,
|
|
1074
1147
|
{
|
|
1075
|
-
|
|
1076
|
-
|
|
1148
|
+
data: loaderData,
|
|
1149
|
+
fallbackContext,
|
|
1150
|
+
config,
|
|
1077
1151
|
...props,
|
|
1078
1152
|
children
|
|
1079
1153
|
}
|
|
1080
1154
|
);
|
|
1081
1155
|
}
|
|
1156
|
+
function RouterTogglyProviderStatic({
|
|
1157
|
+
children,
|
|
1158
|
+
fallbackContext,
|
|
1159
|
+
config,
|
|
1160
|
+
...props
|
|
1161
|
+
}) {
|
|
1162
|
+
const serverContext = fallbackContext ?? getWindowTogglyData();
|
|
1163
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RouterTogglyProviderInner, { serverContext, config, ...props, children });
|
|
1164
|
+
}
|
|
1165
|
+
function RouterTogglyProvider({
|
|
1166
|
+
children,
|
|
1167
|
+
routeId,
|
|
1168
|
+
fallbackContext,
|
|
1169
|
+
config,
|
|
1170
|
+
...props
|
|
1171
|
+
}) {
|
|
1172
|
+
const inRouter = reactRouter.useInRouterContext();
|
|
1173
|
+
if (routeId && inRouter) {
|
|
1174
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RouterTogglyProviderFromRoute, { routeId, fallbackContext, config, ...props, children });
|
|
1175
|
+
}
|
|
1176
|
+
if (inRouter) {
|
|
1177
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RouterTogglyProviderFromLoader, { fallbackContext, config, ...props, children });
|
|
1178
|
+
}
|
|
1179
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RouterTogglyProviderStatic, { fallbackContext, config, ...props, children });
|
|
1180
|
+
}
|
|
1082
1181
|
function useTogglyLoaderData() {
|
|
1083
1182
|
const loaderData = reactRouter.useLoaderData();
|
|
1084
1183
|
return {
|