@ops-ai/react-router-toggly 1.0.0
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 +17 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/client.cjs +832 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +603 -0
- package/dist/client.d.ts +603 -0
- package/dist/client.js +787 -0
- package/dist/client.js.map +1 -0
- package/dist/server.cjs +2324 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +645 -0
- package/dist/server.d.ts +645 -0
- package/dist/server.js +2294 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry/grpc.cjs +159 -0
- package/dist/telemetry/grpc.cjs.map +1 -0
- package/dist/telemetry/grpc.d.cts +29 -0
- package/dist/telemetry/grpc.d.ts +29 -0
- package/dist/telemetry/grpc.js +149 -0
- package/dist/telemetry/grpc.js.map +1 -0
- package/package.json +107 -0
- package/proto/google/protobuf/timestamp.proto +17 -0
- package/proto/metrics.proto +54 -0
- package/proto/usage.proto +49 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { LocalGate } from '@ops-ai/toggly-local-gates';
|
|
2
|
+
import { EvaluatedDefinitions, TogglyEntityContext } from '@ops-ai/toggly-hooks-types';
|
|
3
|
+
export { fromHttpRequest } from '@ops-ai/toggly-eval';
|
|
4
|
+
|
|
5
|
+
interface MetricsFeatureOptions {
|
|
6
|
+
/** Optional feature key for experiment-style subcounts. */
|
|
7
|
+
feature?: string;
|
|
8
|
+
/** Variant name (default: enabled). */
|
|
9
|
+
variant?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface UsageSender {
|
|
13
|
+
sendStats(request: Record<string, unknown>): Promise<unknown>;
|
|
14
|
+
close?(): void;
|
|
15
|
+
}
|
|
16
|
+
interface MetricsSender {
|
|
17
|
+
sendMetrics(request: Record<string, unknown>): Promise<unknown>;
|
|
18
|
+
close?(): void;
|
|
19
|
+
}
|
|
20
|
+
type TelemetryTransport = 'grpc' | 'https';
|
|
21
|
+
interface TelemetryConfig {
|
|
22
|
+
appKey: string;
|
|
23
|
+
environment: string;
|
|
24
|
+
metricsBaseUrl?: string;
|
|
25
|
+
enableUsageTracking?: boolean;
|
|
26
|
+
enableMetrics?: boolean;
|
|
27
|
+
usageFlushInterval?: number;
|
|
28
|
+
metricsFlushInterval?: number;
|
|
29
|
+
instanceName?: string;
|
|
30
|
+
appVersion?: string;
|
|
31
|
+
debug?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Transport used when clients are not injected.
|
|
34
|
+
* - `grpc`: expect injected usage/metrics clients (server creates them)
|
|
35
|
+
* - `https`: build soft-fail fetch clients to api/usage/stats + api/metrics
|
|
36
|
+
*/
|
|
37
|
+
transport?: TelemetryTransport;
|
|
38
|
+
/**
|
|
39
|
+
* When true (default for Node/server), attach beforeExit/SIGTERM/SIGINT flush.
|
|
40
|
+
* Edge runtimes must set false — signal handlers are not available / unsafe.
|
|
41
|
+
*/
|
|
42
|
+
attachProcessHandlers?: boolean;
|
|
43
|
+
/** When true, restore batches if HTTPS/send returns soft-fail (default for https). */
|
|
44
|
+
restoreOnSendFailure?: boolean;
|
|
45
|
+
fetchImpl?: typeof fetch;
|
|
46
|
+
/** Injected clients for tests or gRPC transport. */
|
|
47
|
+
usageClient?: UsageSender | null;
|
|
48
|
+
metricsClient?: MetricsSender | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Feature requirement - whether all or any features must be enabled
|
|
53
|
+
*/
|
|
54
|
+
type FeatureRequirement = 'all' | 'any';
|
|
55
|
+
/**
|
|
56
|
+
* Where feature evaluation happens for definitions fetches.
|
|
57
|
+
* - `remote` (default): fetch `evaluated-signed` with context query params
|
|
58
|
+
* - `local`: fetch `definitions-signed` (rules only) and evaluate with `@ops-ai/toggly-eval`
|
|
59
|
+
*/
|
|
60
|
+
type EvaluationMode = 'local' | 'remote';
|
|
61
|
+
/**
|
|
62
|
+
* Configuration options for Toggly
|
|
63
|
+
*/
|
|
64
|
+
interface TogglyConfig {
|
|
65
|
+
/** Toggly application key */
|
|
66
|
+
appKey?: string;
|
|
67
|
+
/** Environment name (e.g., 'Production', 'Staging') */
|
|
68
|
+
environment?: string;
|
|
69
|
+
/** Base URL for Toggly API */
|
|
70
|
+
baseUrl?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Evaluation rail. Defaults to `remote` for backward compatibility.
|
|
73
|
+
* Use `local` for definitions-signed + call-site evaluation (server multi-tenant).
|
|
74
|
+
*/
|
|
75
|
+
evaluationMode?: EvaluationMode;
|
|
76
|
+
/** Default feature values for offline/fallback mode */
|
|
77
|
+
featureDefaults?: Record<string, boolean>;
|
|
78
|
+
/** Request timeout in milliseconds */
|
|
79
|
+
timeout?: number;
|
|
80
|
+
/** Enable debug logging */
|
|
81
|
+
debug?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* When true, verify ES256 signed definition envelopes via JWKS before applying flags.
|
|
84
|
+
*/
|
|
85
|
+
verifySignatures?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Optional allow-list of JWKS `kid` values when verifySignatures is enabled.
|
|
88
|
+
*/
|
|
89
|
+
allowedKeyIds?: string[];
|
|
90
|
+
/**
|
|
91
|
+
* Reject envelopes whose `timestamp` is older than this many seconds.
|
|
92
|
+
* Unset or <= 0 disables freshness checks.
|
|
93
|
+
*/
|
|
94
|
+
maxSignatureAgeSeconds?: number;
|
|
95
|
+
/** Device-local gates applied as a read-time AND on worker-evaluated booleans */
|
|
96
|
+
localGates?: LocalGate[];
|
|
97
|
+
/** Optional SDK error callback for reporting fetch/evaluation failures. */
|
|
98
|
+
onError?: (message: string, error?: unknown) => void;
|
|
99
|
+
/** Initial browser identity; hydrated server identity takes precedence. */
|
|
100
|
+
identity?: string;
|
|
101
|
+
/** User groups for targeting */
|
|
102
|
+
groups?: string[];
|
|
103
|
+
/** Custom claims for targeting */
|
|
104
|
+
claims?: Record<string, string>;
|
|
105
|
+
/**
|
|
106
|
+
* Base URL for usage/metrics transport (default: https://app.toggly.io/).
|
|
107
|
+
* Separate from `baseUrl`, which is for definitions/JWKS.
|
|
108
|
+
*/
|
|
109
|
+
metricsBaseUrl?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Enable feature usage tracking (Usage.SendStats / api/usage/stats).
|
|
112
|
+
* Defaults to false in core; react-router-toggly server enables when appKey is set.
|
|
113
|
+
*/
|
|
114
|
+
enableUsageTracking?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Enable business metrics (Metrics.SendMetrics / api/metrics).
|
|
117
|
+
* Defaults to false in core; react-router-toggly server enables when appKey is set.
|
|
118
|
+
*/
|
|
119
|
+
enableMetrics?: boolean;
|
|
120
|
+
/** Usage flush interval in ms (default: 60000). 0 disables the timer. */
|
|
121
|
+
usageFlushInterval?: number;
|
|
122
|
+
/** Metrics flush interval in ms (default: 60000). 0 disables the timer. */
|
|
123
|
+
metricsFlushInterval?: number;
|
|
124
|
+
/** Hostname/instance name reported with usage/metrics payloads. */
|
|
125
|
+
instanceName?: string;
|
|
126
|
+
/** Application version reported with usage payloads. */
|
|
127
|
+
appVersion?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Telemetry transport when clients are not injected.
|
|
130
|
+
* Server should use `grpc` with injected clients; edge adapters use `https`.
|
|
131
|
+
*/
|
|
132
|
+
telemetryTransport?: 'grpc' | 'https';
|
|
133
|
+
/**
|
|
134
|
+
* Attach Node process signal handlers for best-effort flush (default: true for grpc).
|
|
135
|
+
* Edge adapters must set false.
|
|
136
|
+
*/
|
|
137
|
+
telemetryAttachProcessHandlers?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Injected usage sender (gRPC stub or test double).
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
usageClient?: UsageSender | null;
|
|
143
|
+
/**
|
|
144
|
+
* Injected metrics sender (gRPC stub or test double).
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
metricsClient?: MetricsSender | null;
|
|
148
|
+
/** Optional fetch override for HTTPS telemetry (edge/tests). */
|
|
149
|
+
telemetryFetch?: typeof fetch;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* User identity context for feature targeting
|
|
153
|
+
*/
|
|
154
|
+
interface IdentityContext {
|
|
155
|
+
/** Unique user identifier */
|
|
156
|
+
identity?: string;
|
|
157
|
+
/** User groups for targeting */
|
|
158
|
+
groups?: string[];
|
|
159
|
+
/** Custom traits for targeting */
|
|
160
|
+
traits?: Record<string, string | number | boolean>;
|
|
161
|
+
/** Custom claims for targeting */
|
|
162
|
+
claims?: Record<string, string>;
|
|
163
|
+
/** HTTP request segment fields (UA / Accept-Language / country) */
|
|
164
|
+
request?: {
|
|
165
|
+
userAgent?: string;
|
|
166
|
+
acceptLanguage?: string;
|
|
167
|
+
country?: string;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Feature flags state (boolean or entity gate per flag)
|
|
172
|
+
*/
|
|
173
|
+
type FeatureFlags = EvaluatedDefinitions;
|
|
174
|
+
/**
|
|
175
|
+
* Feature evaluation result
|
|
176
|
+
*/
|
|
177
|
+
interface EvaluationResult {
|
|
178
|
+
/** Whether the feature(s) are enabled */
|
|
179
|
+
enabled: boolean;
|
|
180
|
+
/** The feature keys that were evaluated */
|
|
181
|
+
featureKeys: string[];
|
|
182
|
+
/** The requirement type used */
|
|
183
|
+
requirement: FeatureRequirement;
|
|
184
|
+
/** Whether the result was negated */
|
|
185
|
+
negated: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Server-side feature context passed to client
|
|
189
|
+
*/
|
|
190
|
+
interface ServerFeatureContext {
|
|
191
|
+
/** Pre-fetched feature flags */
|
|
192
|
+
flags: FeatureFlags;
|
|
193
|
+
/** User identity (if any) */
|
|
194
|
+
identity?: string;
|
|
195
|
+
/** App key (for client-side refresh) */
|
|
196
|
+
appKey?: string;
|
|
197
|
+
/** Environment */
|
|
198
|
+
environment?: string;
|
|
199
|
+
/** Timestamp when flags were fetched */
|
|
200
|
+
fetchedAt: number;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Hook metadata
|
|
204
|
+
*/
|
|
205
|
+
interface HookMetadata {
|
|
206
|
+
name: string;
|
|
207
|
+
version?: string;
|
|
208
|
+
description?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Evaluation series data passed between before/after hooks
|
|
212
|
+
*/
|
|
213
|
+
interface EvaluationSeriesData {
|
|
214
|
+
[key: string]: unknown;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Identity series data passed between before/after hooks
|
|
218
|
+
*/
|
|
219
|
+
interface IdentitySeriesData {
|
|
220
|
+
[key: string]: unknown;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Hook interface for extending SDK behavior
|
|
224
|
+
*/
|
|
225
|
+
interface TogglyHook {
|
|
226
|
+
/** Get hook metadata */
|
|
227
|
+
getMetadata(): HookMetadata;
|
|
228
|
+
/** Called before feature evaluation */
|
|
229
|
+
beforeEvaluation?(flagKey: string, defaultValue?: boolean): Promise<EvaluationSeriesData | void> | EvaluationSeriesData | void;
|
|
230
|
+
/** Called after feature evaluation */
|
|
231
|
+
afterEvaluation?(flagKey: string, data: EvaluationSeriesData | void, result: boolean): Promise<void> | void;
|
|
232
|
+
/** Called before user identification */
|
|
233
|
+
beforeIdentify?(identity: string): Promise<IdentitySeriesData | void> | IdentitySeriesData | void;
|
|
234
|
+
/** Called after user identification */
|
|
235
|
+
afterIdentify?(identity: string, data: IdentitySeriesData | void): Promise<void> | void;
|
|
236
|
+
/** Called after feature flags are refreshed */
|
|
237
|
+
afterRefresh?(flags: FeatureFlags): Promise<void> | void;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Error types for Toggly SDK
|
|
241
|
+
*/
|
|
242
|
+
declare class TogglyError extends Error {
|
|
243
|
+
readonly code: string;
|
|
244
|
+
readonly cause?: unknown | undefined;
|
|
245
|
+
constructor(message: string, code: string, cause?: unknown | undefined);
|
|
246
|
+
}
|
|
247
|
+
declare class TogglyNetworkError extends TogglyError {
|
|
248
|
+
constructor(message: string, cause?: unknown);
|
|
249
|
+
}
|
|
250
|
+
declare class TogglyConfigError extends TogglyError {
|
|
251
|
+
constructor(message: string);
|
|
252
|
+
}
|
|
253
|
+
declare class TogglyTimeoutError extends TogglyError {
|
|
254
|
+
constructor(message: string);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Cookie/storage key names */
|
|
258
|
+
declare const STORAGE_KEYS: {
|
|
259
|
+
/** Identity cookie/storage key */
|
|
260
|
+
readonly IDENTITY: "toggly_identity";
|
|
261
|
+
/** Feature flags cache key */
|
|
262
|
+
readonly FLAGS: "toggly_flags";
|
|
263
|
+
/** Config cache key */
|
|
264
|
+
readonly CONFIG: "toggly_config";
|
|
265
|
+
/** Last fetch timestamp key */
|
|
266
|
+
readonly LAST_FETCH: "toggly_last_fetch";
|
|
267
|
+
};
|
|
268
|
+
/** HTTP header names */
|
|
269
|
+
declare const HEADERS: {
|
|
270
|
+
/** Identity header */
|
|
271
|
+
readonly IDENTITY: "x-toggly-identity";
|
|
272
|
+
/** Feature flags header */
|
|
273
|
+
readonly FLAGS: "x-toggly-flags";
|
|
274
|
+
/** Cache control */
|
|
275
|
+
readonly CACHE_CONTROL: "cache-control";
|
|
276
|
+
};
|
|
277
|
+
/** Meta key for loader data */
|
|
278
|
+
declare const TOGGLY_LOADER_KEY: "__toggly";
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Request-scoped ambient EvalContext for React Router loaders and actions.
|
|
282
|
+
* Never mutates the process-global client identity — bind overrides here only.
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Ambient IdentityContext for the current async scope, if any.
|
|
287
|
+
* Returns a shallow copy so callers cannot mutate the ALS store.
|
|
288
|
+
*/
|
|
289
|
+
declare function getAmbientEvalOverrides(): IdentityContext | undefined;
|
|
290
|
+
/**
|
|
291
|
+
* Run `fn` with the given EvalContext bound for the async scope.
|
|
292
|
+
* Nested calls replace (do not deep-merge) the ambient store for their duration.
|
|
293
|
+
*/
|
|
294
|
+
declare function runWithEvalContext<T>(ctx: IdentityContext, fn: () => T): T;
|
|
295
|
+
/**
|
|
296
|
+
* Merge ambient defaults with per-call IdentityContext. Explicit per-call
|
|
297
|
+
* fields win field-by-field via `??`, so a field only overrides ambient when
|
|
298
|
+
* its value is not `undefined` — a key present with an `undefined` value
|
|
299
|
+
* (e.g. from `{ claims: user.claims }` where `user.claims` is optional)
|
|
300
|
+
* falls back to ambient, same as an omitted key. No `IdentityContext` field
|
|
301
|
+
* is typed nullable, so `??` and an explicit `!== undefined` check are
|
|
302
|
+
* equivalent here; prefer `??` per lint (S6606).
|
|
303
|
+
*/
|
|
304
|
+
declare function mergeIdentityContext(ambient: IdentityContext | undefined, perCall: IdentityContext | undefined): IdentityContext | undefined;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Build ambient IdentityContext / EvalContext from a Remix Request.
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Providers that build ambient EvalContext for a Remix request.
|
|
312
|
+
*/
|
|
313
|
+
interface EvalContextProviders {
|
|
314
|
+
/** Extract identity from the request */
|
|
315
|
+
getIdentity?: (request: Request) => string | undefined | Promise<string | undefined>;
|
|
316
|
+
/** Extract identity from cookies (fallback when getIdentity is unset) */
|
|
317
|
+
getIdentityFromCookies?: (cookies: string | null) => string | undefined;
|
|
318
|
+
/** Extract group memberships for Targeting / Percentage filters */
|
|
319
|
+
getGroups?: (request: Request) => string[] | undefined | Promise<string[] | undefined>;
|
|
320
|
+
/** Extract principal / JWT-style claims for UserClaims filters */
|
|
321
|
+
getClaims?: (request: Request) => Record<string, string> | undefined | Promise<Record<string, string> | undefined>;
|
|
322
|
+
/**
|
|
323
|
+
* Full ambient context. When provided, returned fields are used;
|
|
324
|
+
* missing `request` keys are still filled from headers via `fromHttpRequest`.
|
|
325
|
+
*/
|
|
326
|
+
getContext?: (request: Request) => IdentityContext | Promise<IdentityContext>;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Resolve ambient IdentityContext for a Remix Request.
|
|
330
|
+
* Uses `getContext` when provided; otherwise getIdentity / getGroups / getClaims.
|
|
331
|
+
* Always fills missing `request` fields from headers via `fromHttpRequest`.
|
|
332
|
+
* UserClaims must be supplied via `claims` (not `traits`).
|
|
333
|
+
*/
|
|
334
|
+
declare function extractEvalContext(request: Request, providers?: EvalContextProviders): Promise<IdentityContext>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Server-side Toggly client
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Server-side Toggly client for fetching and evaluating feature flags
|
|
342
|
+
*/
|
|
343
|
+
declare class TogglyServerClient {
|
|
344
|
+
private readonly config;
|
|
345
|
+
private readonly logger;
|
|
346
|
+
/** Compatibility snapshot of locally evaluated booleans (hydration / afterRefresh). */
|
|
347
|
+
private flags;
|
|
348
|
+
/** Raw definitions-signed models keyed by featureKey. */
|
|
349
|
+
private definitions;
|
|
350
|
+
private hooks;
|
|
351
|
+
private initialized;
|
|
352
|
+
/** Dedupes concurrent cold-start definition fetches. */
|
|
353
|
+
private definitionsLoadPromise;
|
|
354
|
+
/** Single-flight for definition GET so concurrent joiners are not double-counted. */
|
|
355
|
+
private definitionsFetchInFlight;
|
|
356
|
+
private ws;
|
|
357
|
+
private wsConnected;
|
|
358
|
+
private wsReconnectTimer;
|
|
359
|
+
private wsReconnectAttempt;
|
|
360
|
+
private refreshDebounceTimer;
|
|
361
|
+
private cachedDefinitionsRevision;
|
|
362
|
+
private pendingDefinitionsPin;
|
|
363
|
+
private refreshTimer;
|
|
364
|
+
private identity?;
|
|
365
|
+
private localGates;
|
|
366
|
+
private localGateIndex;
|
|
367
|
+
private readonly localGatesListeners;
|
|
368
|
+
private telemetry;
|
|
369
|
+
constructor(config: TogglyConfig);
|
|
370
|
+
private startTelemetry;
|
|
371
|
+
private recordCheckForKey;
|
|
372
|
+
/** Soft-fail wrapper — definition refresh must not throw from telemetry. */
|
|
373
|
+
private noteDefinitionCacheHit;
|
|
374
|
+
private noteDefinitionCacheMiss;
|
|
375
|
+
private normalizeDefinitionsRevision;
|
|
376
|
+
private definitionsRevisionsMatch;
|
|
377
|
+
/**
|
|
378
|
+
* Add a hook to the client
|
|
379
|
+
*/
|
|
380
|
+
addHook(hook: TogglyHook): void;
|
|
381
|
+
/**
|
|
382
|
+
* Remove a hook by name
|
|
383
|
+
*/
|
|
384
|
+
removeHook(name: string): boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Register device-local post-filter gates
|
|
387
|
+
*/
|
|
388
|
+
setLocalGates(gates: LocalGate[]): void;
|
|
389
|
+
/**
|
|
390
|
+
* Notify subscribers that local gate state changed (no network)
|
|
391
|
+
*/
|
|
392
|
+
notifyLocalGatesChanged(): void;
|
|
393
|
+
/**
|
|
394
|
+
* Subscribe to local gate changes
|
|
395
|
+
*/
|
|
396
|
+
subscribeLocalGatesChanged(listener: () => void): () => void;
|
|
397
|
+
private buildEvalContext;
|
|
398
|
+
/**
|
|
399
|
+
* Evaluate a boolean snapshot for a specific identity without relying on
|
|
400
|
+
* shared mutable `this.flags` / `this.identity` (safe under concurrency).
|
|
401
|
+
*/
|
|
402
|
+
snapshotFlags(identityOverride?: IdentityContext): FeatureFlags;
|
|
403
|
+
private getEffectiveFlag;
|
|
404
|
+
private getDefinitionsRevision;
|
|
405
|
+
private cacheDefinitionsRevision;
|
|
406
|
+
private scheduleDebouncedRefresh;
|
|
407
|
+
private handleWsSyncMessage;
|
|
408
|
+
private handleWsUpdateMessage;
|
|
409
|
+
private evaluateGateEffective;
|
|
410
|
+
/**
|
|
411
|
+
* Whether the WebSocket connection is active
|
|
412
|
+
*/
|
|
413
|
+
get isWsConnected(): boolean;
|
|
414
|
+
/**
|
|
415
|
+
* Initialize the client by fetching feature definitions.
|
|
416
|
+
* When already initialized, rebinds identity (including clearing it) and
|
|
417
|
+
* re-snapshots flags without re-fetching (definitions are identity-agnostic).
|
|
418
|
+
* Concurrent cold starts share one fetch; each caller still receives a
|
|
419
|
+
* request-local snapshot for its identity.
|
|
420
|
+
*/
|
|
421
|
+
init(identity?: string): Promise<FeatureFlags>;
|
|
422
|
+
/**
|
|
423
|
+
* Fetch feature definitions from the API (definitions-signed; no identity query).
|
|
424
|
+
* Counts one definition-refresh hit/miss per performed attempt; concurrent
|
|
425
|
+
* in-flight joiners await the same fetch and are not counted.
|
|
426
|
+
*/
|
|
427
|
+
fetchFlags(identity?: string): Promise<FeatureFlags>;
|
|
428
|
+
private performDefinitionsFetch;
|
|
429
|
+
/**
|
|
430
|
+
* Get all flags
|
|
431
|
+
*/
|
|
432
|
+
getFlags(): FeatureFlags;
|
|
433
|
+
/**
|
|
434
|
+
* Check if a feature is enabled
|
|
435
|
+
*/
|
|
436
|
+
isEnabled(featureKey: string, context?: IdentityContext, defaultValue?: boolean, entity?: TogglyEntityContext | Record<string, unknown> | null, kind?: string): Promise<boolean>;
|
|
437
|
+
registerContext<T>(kind: string, mapper: (entity: T) => TogglyEntityContext): void;
|
|
438
|
+
/**
|
|
439
|
+
* Check if a feature is disabled
|
|
440
|
+
*/
|
|
441
|
+
isDisabled(featureKey: string, context?: IdentityContext, defaultValue?: boolean, entity?: TogglyEntityContext | Record<string, unknown> | null, kind?: string): Promise<boolean>;
|
|
442
|
+
/**
|
|
443
|
+
* Evaluate a feature gate (multiple features)
|
|
444
|
+
*/
|
|
445
|
+
evaluateGate(featureKeys: string[], requirement?: 'all' | 'any', negate?: boolean, defaultValue?: boolean, entity?: TogglyEntityContext | Record<string, unknown> | null, kind?: string, identityOverride?: IdentityContext): Promise<boolean>;
|
|
446
|
+
/**
|
|
447
|
+
* Get the server context for client hydration
|
|
448
|
+
*/
|
|
449
|
+
getServerContext(): {
|
|
450
|
+
flags: FeatureFlags;
|
|
451
|
+
appKey?: string;
|
|
452
|
+
environment?: string;
|
|
453
|
+
fetchedAt: number;
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* Start WebSocket connection for live updates
|
|
457
|
+
*/
|
|
458
|
+
private startWebSocket;
|
|
459
|
+
/**
|
|
460
|
+
* Schedule WebSocket reconnection
|
|
461
|
+
*/
|
|
462
|
+
private scheduleReconnect;
|
|
463
|
+
/**
|
|
464
|
+
* Stop WebSocket connection and cleanup
|
|
465
|
+
*/
|
|
466
|
+
private stopWebSocket;
|
|
467
|
+
/**
|
|
468
|
+
* Close the client and cleanup all resources
|
|
469
|
+
*/
|
|
470
|
+
close(): void;
|
|
471
|
+
/** Record a feature "used" interaction (usage telemetry). */
|
|
472
|
+
recordUsage(featureKey: string, identity?: string, variant?: string): void;
|
|
473
|
+
/** Record a feature "viewed" event (usage telemetry). */
|
|
474
|
+
recordView(featureKey: string, identity?: string, variant?: string): void;
|
|
475
|
+
measure(metricKey: string, value: number, options?: MetricsFeatureOptions): void;
|
|
476
|
+
incrementCounter(metricKey: string, value?: number, options?: MetricsFeatureOptions): void;
|
|
477
|
+
observe(metricKey: string, value: number, options?: MetricsFeatureOptions): void;
|
|
478
|
+
flushTelemetry(): Promise<void>;
|
|
479
|
+
private executeBeforeEvaluation;
|
|
480
|
+
private executeAfterEvaluation;
|
|
481
|
+
private executeBeforeIdentify;
|
|
482
|
+
private executeAfterIdentify;
|
|
483
|
+
private executeAfterRefresh;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Create a new server client instance
|
|
487
|
+
*/
|
|
488
|
+
declare function createServerClient(config: TogglyConfig): TogglyServerClient;
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* React Router loader utilities for Toggly
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/** Structural args for framework-mode loaders (RR7/RR8). */
|
|
495
|
+
interface LoaderFunctionArgs {
|
|
496
|
+
request: Request;
|
|
497
|
+
params?: Record<string, string | undefined>;
|
|
498
|
+
context?: unknown;
|
|
499
|
+
}
|
|
500
|
+
interface TogglyLoaderOptions extends TogglyConfig, EvalContextProviders {
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Create a loader helper for fetching feature flags
|
|
504
|
+
*/
|
|
505
|
+
declare function createTogglyLoader(options: TogglyLoaderOptions): {
|
|
506
|
+
/**
|
|
507
|
+
* Get the Toggly client
|
|
508
|
+
*/
|
|
509
|
+
getClient(): TogglyServerClient;
|
|
510
|
+
/**
|
|
511
|
+
* Load feature flags for a loader function.
|
|
512
|
+
* Builds ambient EvalContext (identity / groups / claims / request) for
|
|
513
|
+
* the snapshot. Prefer `run` when calling `client.isEnabled('X')` without
|
|
514
|
+
* an IdentityContext for the full loader duration.
|
|
515
|
+
*/
|
|
516
|
+
load(args: LoaderFunctionArgs): Promise<ServerFeatureContext>;
|
|
517
|
+
/**
|
|
518
|
+
* Bind ambient EvalContext for the full loader duration, then run `fn`.
|
|
519
|
+
* Prefer this when calling `client.isEnabled('X')` without an IdentityContext.
|
|
520
|
+
*/
|
|
521
|
+
run<T>(args: LoaderFunctionArgs, fn: (ctx: ServerFeatureContext & {
|
|
522
|
+
client: TogglyServerClient;
|
|
523
|
+
}) => T | Promise<T>): Promise<T>;
|
|
524
|
+
/**
|
|
525
|
+
* Create loader data with feature context
|
|
526
|
+
*/
|
|
527
|
+
getLoaderData<T extends Record<string, unknown>>(args: LoaderFunctionArgs, additionalData?: T): Promise<T & {
|
|
528
|
+
[TOGGLY_LOADER_KEY]: ServerFeatureContext;
|
|
529
|
+
}>;
|
|
530
|
+
/**
|
|
531
|
+
* Check if a feature is enabled.
|
|
532
|
+
* Ambient EvalContext (from `run` / `load` ALS scope) is merged;
|
|
533
|
+
* per-call string identity or IdentityContext overrides field-by-field.
|
|
534
|
+
*/
|
|
535
|
+
isEnabled(featureKey: string, defaultValue?: boolean, identityOrContext?: string | IdentityContext): Promise<boolean>;
|
|
536
|
+
/**
|
|
537
|
+
* Check if a feature is disabled
|
|
538
|
+
*/
|
|
539
|
+
isDisabled(featureKey: string, defaultValue?: boolean, identityOrContext?: string | IdentityContext): Promise<boolean>;
|
|
540
|
+
/**
|
|
541
|
+
* Evaluate a feature gate
|
|
542
|
+
*/
|
|
543
|
+
evaluateGate(featureKeys: string[], requirement?: "all" | "any", negate?: boolean, identityOrContext?: string | IdentityContext): Promise<boolean>;
|
|
544
|
+
/**
|
|
545
|
+
* Get all flags
|
|
546
|
+
*/
|
|
547
|
+
getFlags(): FeatureFlags;
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* Standalone function to get feature flags in a loader
|
|
551
|
+
*/
|
|
552
|
+
declare function getFeatureFlags(request: Request, options: TogglyLoaderOptions): Promise<ServerFeatureContext>;
|
|
553
|
+
/**
|
|
554
|
+
* Check if a single feature is enabled (standalone)
|
|
555
|
+
*/
|
|
556
|
+
declare function isFeatureEnabled(request: Request, featureKey: string, options: TogglyLoaderOptions, defaultValue?: boolean): Promise<boolean>;
|
|
557
|
+
/**
|
|
558
|
+
* Type helper for loader data with Toggly context
|
|
559
|
+
*/
|
|
560
|
+
type WithTogglyContext<T> = T & {
|
|
561
|
+
[TOGGLY_LOADER_KEY]: ServerFeatureContext;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* React Router action utilities for Toggly
|
|
566
|
+
*/
|
|
567
|
+
|
|
568
|
+
/** Structural args for framework-mode actions (RR7/RR8). */
|
|
569
|
+
interface ActionFunctionArgs {
|
|
570
|
+
request: Request;
|
|
571
|
+
params?: Record<string, string | undefined>;
|
|
572
|
+
context?: unknown;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Options for feature-gated actions
|
|
576
|
+
*/
|
|
577
|
+
interface FeatureGatedActionOptions extends TogglyLoaderOptions {
|
|
578
|
+
/** Feature key(s) required for this action */
|
|
579
|
+
requiredFeatures?: string | string[];
|
|
580
|
+
/** Feature requirement type */
|
|
581
|
+
requirement?: 'all' | 'any';
|
|
582
|
+
/** Response when feature is disabled */
|
|
583
|
+
onFeatureDisabled?: (request: Request, featureKeys: string[]) => Response | Promise<Response>;
|
|
584
|
+
/** Redirect URL when feature is disabled */
|
|
585
|
+
redirectTo?: string;
|
|
586
|
+
/** Status code for JSON error response */
|
|
587
|
+
errorStatus?: number;
|
|
588
|
+
/** Error message for JSON error response */
|
|
589
|
+
errorMessage?: string;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Action context with Toggly client
|
|
593
|
+
*/
|
|
594
|
+
interface TogglyActionContext {
|
|
595
|
+
/** Toggly server client */
|
|
596
|
+
client: TogglyServerClient;
|
|
597
|
+
/** Feature flags */
|
|
598
|
+
flags: FeatureFlags;
|
|
599
|
+
/** Ambient EvalContext bound for this action */
|
|
600
|
+
context: IdentityContext;
|
|
601
|
+
/** Check if feature is enabled (uses ambient; optional override) */
|
|
602
|
+
isEnabled: (featureKey: string, defaultValue?: boolean, override?: IdentityContext) => Promise<boolean>;
|
|
603
|
+
/** Check if feature is disabled */
|
|
604
|
+
isDisabled: (featureKey: string, defaultValue?: boolean, override?: IdentityContext) => Promise<boolean>;
|
|
605
|
+
/** Evaluate feature gate */
|
|
606
|
+
evaluateGate: (featureKeys: string[], requirement?: 'all' | 'any', negate?: boolean, override?: IdentityContext) => Promise<boolean>;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Create a feature-gated action handler
|
|
610
|
+
*/
|
|
611
|
+
declare function createFeatureGatedAction<T>(options: FeatureGatedActionOptions, handler: (args: ActionFunctionArgs, toggly: TogglyActionContext) => Promise<T> | T): (args: ActionFunctionArgs) => Promise<T | Response>;
|
|
612
|
+
/**
|
|
613
|
+
* Create a Toggly-aware action helper
|
|
614
|
+
*/
|
|
615
|
+
declare function createTogglyAction(options: TogglyLoaderOptions): {
|
|
616
|
+
/**
|
|
617
|
+
* Get the Toggly client
|
|
618
|
+
*/
|
|
619
|
+
getClient(): TogglyServerClient;
|
|
620
|
+
/**
|
|
621
|
+
* Initialize for an action request.
|
|
622
|
+
* Returned helpers close over ambient EvalContext so `toggly.isEnabled('X')`
|
|
623
|
+
* needs no IdentityContext. Prefer `run` when using `client.isEnabled('X')`
|
|
624
|
+
* directly for the full action duration.
|
|
625
|
+
*/
|
|
626
|
+
init(request: Request): Promise<TogglyActionContext>;
|
|
627
|
+
/**
|
|
628
|
+
* Bind ambient EvalContext and run an action handler.
|
|
629
|
+
*/
|
|
630
|
+
run<T>(args: ActionFunctionArgs, fn: (args: ActionFunctionArgs, toggly: TogglyActionContext) => T | Promise<T>): Promise<T>;
|
|
631
|
+
/**
|
|
632
|
+
* Wrap an action with feature checks
|
|
633
|
+
*/
|
|
634
|
+
requireFeature<T>(featureKey: string, handler: (args: ActionFunctionArgs, toggly: TogglyActionContext) => Promise<T> | T, onDisabled?: () => Response | Promise<Response>): (args: ActionFunctionArgs) => Promise<Response | T>;
|
|
635
|
+
/**
|
|
636
|
+
* Wrap an action with feature gate checks
|
|
637
|
+
*/
|
|
638
|
+
requireFeatures<T>(featureKeys: string[], requirement: "all" | "any", handler: (args: ActionFunctionArgs, toggly: TogglyActionContext) => Promise<T> | T, onDisabled?: () => Response | Promise<Response>): (args: ActionFunctionArgs) => Promise<Response | T>;
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* Higher-order function to require a feature for an action
|
|
642
|
+
*/
|
|
643
|
+
declare function requireFeature(featureKey: string, options: TogglyLoaderOptions, onDisabled?: () => Response | Promise<Response>): <T>(handler: (args: ActionFunctionArgs, toggly: TogglyActionContext) => Promise<T> | T) => (args: ActionFunctionArgs) => Promise<Response | T>;
|
|
644
|
+
|
|
645
|
+
export { type EvalContextProviders, type EvaluationResult, type EvaluationSeriesData, type FeatureFlags, type FeatureGatedActionOptions, type FeatureRequirement, HEADERS, type HookMetadata, type IdentityContext, type IdentitySeriesData, type MetricsFeatureOptions, type MetricsSender, STORAGE_KEYS, type ServerFeatureContext, TOGGLY_LOADER_KEY, type TelemetryConfig, type TelemetryTransport, type TogglyActionContext, type TogglyConfig, TogglyConfigError, TogglyError, type TogglyHook, type TogglyLoaderOptions, TogglyNetworkError, TogglyServerClient, TogglyTimeoutError, type UsageSender, type WithTogglyContext, createFeatureGatedAction, createServerClient, createTogglyAction, createTogglyLoader, extractEvalContext, getAmbientEvalOverrides, getFeatureFlags, isFeatureEnabled, mergeIdentityContext, requireFeature, runWithEvalContext };
|