@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.
@@ -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 };