@toggld/js 0.0.0-stage → 0.9.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/LICENSE +202 -0
- package/README.md +355 -2
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +243 -0
- package/dist/core-BQfyNOoT.mjs +921 -0
- package/dist/details-AiMdctXB.mjs +365 -0
- package/dist/effect.d.mts +17 -0
- package/dist/effect.mjs +75 -0
- package/dist/effectService-CGZnDaqS.d.mts +48 -0
- package/dist/effectService-DPD-fNCI.mjs +17 -0
- package/dist/index.d.mts +54 -0
- package/dist/index.mjs +127 -0
- package/dist/serverless-B_Vyg8Nd.mjs +259 -0
- package/dist/serverless-e__iCjpQ.d.mts +206 -0
- package/dist/serverless-effect.d.mts +51 -0
- package/dist/serverless-effect.mjs +121 -0
- package/dist/serverless.d.mts +3 -0
- package/dist/serverless.mjs +38 -0
- package/dist/types-BHn_vrQq.d.mts +211 -0
- package/dist/wire-CrkBeqnM.mjs +447 -0
- package/package.json +76 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { n as memoryCache, t as createServerlessCore } from "./serverless-B_Vyg8Nd.mjs";
|
|
2
|
+
import { t as ToggldClient } from "./effectService-DPD-fNCI.mjs";
|
|
3
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
4
|
+
// src/serverless-effect.ts
|
|
5
|
+
/**
|
|
6
|
+
* `@toggld/js/serverless/effect` — the serverless client (`@toggld/js/serverless`)
|
|
7
|
+
* as the same Effect service that `@toggld/js/effect` provides. Needs the
|
|
8
|
+
* optional `effect` peer dependency (`^4.0.5`), and no `node:` module.
|
|
9
|
+
*
|
|
10
|
+
* Program code is the same with either entry: `yield* ToggldClient`, then the
|
|
11
|
+
* same {@link ToggldClientShape}. Only the layer changes.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { Effect, ManagedRuntime } from "effect"
|
|
15
|
+
* import { ToggldClient, layer, withRequest } from "@toggld/js/serverless/effect"
|
|
16
|
+
*
|
|
17
|
+
* const program = Effect.gen(function* () {
|
|
18
|
+
* const flags = yield* ToggldClient
|
|
19
|
+
* return yield* flags.getBooleanValue("new-checkout", false, { targetingKey: "user-123" })
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* // Cloudflare Workers: one layer per isolate, one request scope per request.
|
|
23
|
+
* const runtime = ManagedRuntime.make(layer({ sdkKey, endpoint }))
|
|
24
|
+
* export default {
|
|
25
|
+
* fetch: (request, env, ctx) =>
|
|
26
|
+
* runtime.runPromise(program.pipe(withRequest(ctx))).then((on) => new Response(String(on))),
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* **One bulk request per request scope and context.** Inside
|
|
31
|
+
* {@link withRequest} (or {@link requestLayer}), the first evaluation for a
|
|
32
|
+
* context sends one bulk request, and every later one in that scope with the
|
|
33
|
+
* same context uses its answer, as a `forRequest` view does. Outside a request
|
|
34
|
+
* scope, each evaluation sends its own bulk request (like remote mode), so
|
|
35
|
+
* provide one per incoming request.
|
|
36
|
+
*
|
|
37
|
+
* The optional {@link WaitUntil} service runs follow-up work after the
|
|
38
|
+
* response: cache writes (inline and bounded without it) and telemetry sends
|
|
39
|
+
* (started without being awaited without it). `withRequest(ctx)` provides it
|
|
40
|
+
* when `ctx` has a `waitUntil`. Reads are counted for analytics as with
|
|
41
|
+
* `@toggld/js/serverless` (`telemetry` option); `flushTelemetry` sends the
|
|
42
|
+
* counts now.
|
|
43
|
+
*
|
|
44
|
+
* Interrupting an evaluation outside a request scope aborts its bulk request.
|
|
45
|
+
* Inside one, the request is shared by the scope's other evaluations, so it is
|
|
46
|
+
* not aborted; `timeoutMs` bounds it.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* The current request's `waitUntil`. Provide it per request (Workers, Vercel)
|
|
50
|
+
* with {@link withRequest}, {@link withWaitUntil}, or {@link waitUntilLayer};
|
|
51
|
+
* leave it out where the runtime freezes after the handler returns (AWS
|
|
52
|
+
* Lambda). It only affects cache writes and telemetry sends.
|
|
53
|
+
*/
|
|
54
|
+
var WaitUntil = class extends Context.Service()("@toggld/js/serverless/WaitUntil") {};
|
|
55
|
+
/**
|
|
56
|
+
* The evaluation state of one incoming request: its bulk results. Create it
|
|
57
|
+
* with {@link withRequest} or {@link requestLayer}, once per request; do not
|
|
58
|
+
* share it between requests.
|
|
59
|
+
*/
|
|
60
|
+
var RequestScope = class extends Context.Service()("@toggld/js/serverless/RequestScope") {};
|
|
61
|
+
/** The {@link WaitUntil} service of one request. Calls `source.waitUntil` as a method, as Workers requires. */
|
|
62
|
+
const service = (source) => ({ waitUntil: (promise) => source.waitUntil(promise) });
|
|
63
|
+
const hasWaitUntil = (ctx) => typeof ctx?.waitUntil === "function";
|
|
64
|
+
/**
|
|
65
|
+
* Run `self` as one incoming request: a fresh {@link RequestScope} (one bulk
|
|
66
|
+
* request per context), plus {@link WaitUntil} when `ctx` has a `waitUntil`
|
|
67
|
+
* (a Workers `ExecutionContext`, Vercel). Without `ctx` (AWS Lambda), cache
|
|
68
|
+
* writes run inline.
|
|
69
|
+
*/
|
|
70
|
+
const withRequest = (ctx) => (self) => Effect.provide(self, requestLayer(ctx));
|
|
71
|
+
/** {@link withRequest} as a layer. Build it once per incoming request. */
|
|
72
|
+
const requestLayer = (ctx) => {
|
|
73
|
+
const scope = Layer.sync(RequestScope, () => ({ views: /* @__PURE__ */ new Map() }));
|
|
74
|
+
return hasWaitUntil(ctx) ? Layer.merge(scope, Layer.succeed(WaitUntil, service(ctx))) : scope;
|
|
75
|
+
};
|
|
76
|
+
/** The same as {@link withRequest} with a `waitUntil` source: a request scope and its {@link WaitUntil}. */
|
|
77
|
+
const withWaitUntil = (source) => withRequest(source);
|
|
78
|
+
/** {@link WaitUntil} and a fresh {@link RequestScope} as a layer, for one request. */
|
|
79
|
+
const waitUntilLayer = (source) => requestLayer(source);
|
|
80
|
+
/** The current request's `waitUntil`, or none (inline cache writes). */
|
|
81
|
+
const currentWaitUntil = Effect.map(Effect.serviceOption(WaitUntil), (found) => Option.isSome(found) ? (promise) => found.value.waitUntil(promise.catch(() => {})) : void 0);
|
|
82
|
+
/** The view of the current request scope for this client, or a one-evaluation view outside one. */
|
|
83
|
+
const viewFor = (core) => Effect.flatMap(currentWaitUntil, (waitUntil) => Effect.map(Effect.serviceOption(RequestScope), (scope) => {
|
|
84
|
+
if (Option.isNone(scope)) return core.view(waitUntil, true);
|
|
85
|
+
let view = scope.value.views.get(core);
|
|
86
|
+
if (view === void 0) {
|
|
87
|
+
view = core.view(waitUntil);
|
|
88
|
+
scope.value.views.set(core, view);
|
|
89
|
+
}
|
|
90
|
+
return view;
|
|
91
|
+
}));
|
|
92
|
+
const fromCore = (core) => {
|
|
93
|
+
const run = (evaluate) => Effect.flatMap(viewFor(core), (view) => Effect.promise((signal) => evaluate(view, signal)));
|
|
94
|
+
const typed = (key, defaultValue, context, type) => run((view, signal) => core.typed(view, key, defaultValue, context, type, signal));
|
|
95
|
+
const value = (details) => Effect.map(details, (d) => d.value);
|
|
96
|
+
return {
|
|
97
|
+
ready: Effect.void,
|
|
98
|
+
flushTelemetry: Effect.promise(() => core.flushTelemetry()),
|
|
99
|
+
evaluate: (key, defaultValue, context) => run((view, signal) => core.evaluate(view, key, defaultValue, context, signal)),
|
|
100
|
+
evaluateAll: (context) => run((view, signal) => core.evaluateAll(view, context, signal)),
|
|
101
|
+
getBooleanDetails: (key, defaultValue, context) => typed(key, defaultValue, context, "boolean"),
|
|
102
|
+
getBooleanValue: (key, defaultValue, context) => value(typed(key, defaultValue, context, "boolean")),
|
|
103
|
+
getStringDetails: (key, defaultValue, context) => typed(key, defaultValue, context, "string"),
|
|
104
|
+
getStringValue: (key, defaultValue, context) => value(typed(key, defaultValue, context, "string")),
|
|
105
|
+
getNumberDetails: (key, defaultValue, context) => typed(key, defaultValue, context, "number"),
|
|
106
|
+
getNumberValue: (key, defaultValue, context) => value(typed(key, defaultValue, context, "number")),
|
|
107
|
+
getObjectDetails: (key, defaultValue, context) => typed(key, defaultValue, context, "object"),
|
|
108
|
+
getObjectValue: (key, defaultValue, context) => value(typed(key, defaultValue, context, "object"))
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* A `Layer` providing {@link ToggldClient} with one serverless client for the
|
|
113
|
+
* layer's scope. Build it once per isolate or process (a module-scope
|
|
114
|
+
* `ManagedRuntime`), not per request, and provide {@link withRequest} per
|
|
115
|
+
* request. Building it makes no request, and it holds no resource.
|
|
116
|
+
*
|
|
117
|
+
* Options that can never work (no `sdkKey`, a public token, or an invalid
|
|
118
|
+
* `endpoint`) make the layer die with a `TypeError`.
|
|
119
|
+
*/
|
|
120
|
+
const layer = (options) => Layer.effect(ToggldClient, Effect.sync(() => fromCore(createServerlessCore(options))));
|
|
121
|
+
export { RequestScope, ToggldClient, WaitUntil, layer, memoryCache, requestLayer, waitUntilLayer, withRequest, withWaitUntil };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as ErrorCode, c as FlagValue, l as Reason, o as EvaluationContext, s as FlagMetadata, t as EvaluationDetails } from "./types-BHn_vrQq.mjs";
|
|
2
|
+
import { a as createServerlessClient, c as CachedFlagFailure, d as MemoryCacheOptions, f as memoryCache, i as WaitUntilContext, l as CachedFlagSuccess, n as ServerlessClientOptions, o as CachedEvaluations, r as ServerlessRequestClient, s as CachedFlagEvaluation, t as ServerlessClient, u as EvaluationCache } from "./serverless-e__iCjpQ.mjs";
|
|
3
|
+
export { type CachedEvaluations, type CachedFlagEvaluation, type CachedFlagFailure, type CachedFlagSuccess, type ErrorCode, type EvaluationCache, type EvaluationContext, type EvaluationDetails, type FlagMetadata, type FlagValue, type MemoryCacheOptions, type Reason, ServerlessClient, ServerlessClientOptions, ServerlessRequestClient, WaitUntilContext, createServerlessClient, memoryCache };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { n as memoryCache, t as createServerlessCore } from "./serverless-B_Vyg8Nd.mjs";
|
|
2
|
+
// src/serverless.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create a serverless client. Call it once per isolate or process, in module
|
|
5
|
+
* scope (or lazily on the first request, where Worker bindings are
|
|
6
|
+
* available). It makes no request until the first evaluation.
|
|
7
|
+
*
|
|
8
|
+
* Throws a `TypeError` for options that can never work: no `sdkKey`, a public
|
|
9
|
+
* token (`ffp_…`), or an `endpoint` that is not an http(s) URL.
|
|
10
|
+
*/
|
|
11
|
+
const createServerlessClient = (options) => {
|
|
12
|
+
const core = createServerlessCore(options);
|
|
13
|
+
return {
|
|
14
|
+
forRequest: (ctx) => {
|
|
15
|
+
const context = ctx;
|
|
16
|
+
const waitUntil = typeof context?.waitUntil === "function" ? (promise) => context.waitUntil(promise.catch(() => {})) : void 0;
|
|
17
|
+
return makeRequestClient(core, core.view(waitUntil));
|
|
18
|
+
},
|
|
19
|
+
flushTelemetry: () => core.flushTelemetry()
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const makeRequestClient = (core, view) => {
|
|
23
|
+
const details = (key, defaultValue, context, type) => core.typed(view, key, defaultValue, context, type);
|
|
24
|
+
const value = async (key, defaultValue, context, type) => (await details(key, defaultValue, context, type)).value;
|
|
25
|
+
return {
|
|
26
|
+
evaluate: (key, defaultValue, context) => core.evaluate(view, key, defaultValue, context),
|
|
27
|
+
evaluateAll: (context) => core.evaluateAll(view, context),
|
|
28
|
+
getBooleanValue: (key, defaultValue, context) => value(key, defaultValue, context, "boolean"),
|
|
29
|
+
getBooleanDetails: (key, defaultValue, context) => details(key, defaultValue, context, "boolean"),
|
|
30
|
+
getStringValue: (key, defaultValue, context) => value(key, defaultValue, context, "string"),
|
|
31
|
+
getStringDetails: (key, defaultValue, context) => details(key, defaultValue, context, "string"),
|
|
32
|
+
getNumberValue: (key, defaultValue, context) => value(key, defaultValue, context, "number"),
|
|
33
|
+
getNumberDetails: (key, defaultValue, context) => details(key, defaultValue, context, "number"),
|
|
34
|
+
getObjectValue: (key, defaultValue, context) => value(key, defaultValue, context, "object"),
|
|
35
|
+
getObjectDetails: (key, defaultValue, context) => details(key, defaultValue, context, "object")
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export { createServerlessClient, memoryCache };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A resolved flag value. OpenFeature permits `boolean | string | number |
|
|
3
|
+
* structure`; we model the union as JSON, which also covers arrays and null for
|
|
4
|
+
* "object" typed flags.
|
|
5
|
+
*/
|
|
6
|
+
type FlagValue = null | boolean | number | string | ReadonlyArray<FlagValue> | {
|
|
7
|
+
readonly [key: string]: FlagValue;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Flag metadata: "arbitrary properties, with keys of type string, and values of
|
|
11
|
+
* type boolean, string, or number." (Requirement 2.2.10)
|
|
12
|
+
*/
|
|
13
|
+
type MetadataValue = string | number | boolean;
|
|
14
|
+
type FlagMetadata = {
|
|
15
|
+
readonly [key: string]: MetadataValue;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* An OpenFeature evaluation context: an optional `targetingKey` identifying the
|
|
19
|
+
* subject (Requirement 3.1.1) plus arbitrary custom JSON-valued attributes
|
|
20
|
+
* (Requirement 3.1.2).
|
|
21
|
+
*/
|
|
22
|
+
type EvaluationContext = {
|
|
23
|
+
readonly [attribute: string]: FlagValue;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Resolution reasons. (Requirement 2.2.5)
|
|
27
|
+
*/
|
|
28
|
+
type Reason = "STATIC" | "DEFAULT" | "TARGETING_MATCH" | "SPLIT" | "CACHED" | "DISABLED" | "UNKNOWN" | "STALE" | "ERROR";
|
|
29
|
+
/**
|
|
30
|
+
* OpenFeature error codes.
|
|
31
|
+
* @see https://openfeature.dev/specification/types
|
|
32
|
+
*/
|
|
33
|
+
type ErrorCode = "PROVIDER_NOT_READY" | "PROVIDER_FATAL" | "FLAG_NOT_FOUND" | "PARSE_ERROR" | "TYPE_MISMATCH" | "TARGETING_KEY_MISSING" | "INVALID_CONTEXT" | "GENERAL";
|
|
34
|
+
/**
|
|
35
|
+
* How the client resolves flags.
|
|
36
|
+
*
|
|
37
|
+
* - `"sync"` (default) — the client keeps a copy of the flag rules current in
|
|
38
|
+
* the background (a snapshot fetch plus a live WebSocket stream, with a
|
|
39
|
+
* safety poll) and evaluates flags **in your process** with the bundled
|
|
40
|
+
* engine: zero server requests per evaluation, microsecond latency,
|
|
41
|
+
* identical resolutions to the server, and it keeps working from its last
|
|
42
|
+
* copy if the API is unreachable. Needs a secret SDK key (`ffk_…`) and a
|
|
43
|
+
* long-lived process. On serverless and edge runtimes, use
|
|
44
|
+
* `@toggld/js/serverless` instead. Tune it with {@link ToggldClientOptions.sync}.
|
|
45
|
+
* - `"file"` — evaluates in your process from a flag file on disk
|
|
46
|
+
* ({@link ToggldClientOptions.file}), watched for edits. No network, key, or
|
|
47
|
+
* endpoint. For local development, tests, CI, and pinned rulesets.
|
|
48
|
+
* - `"remote"` — every evaluation is an HTTP request to the flag API's OFREP
|
|
49
|
+
* endpoints; the server evaluates and returns the value. For public tokens
|
|
50
|
+
* (`ffp_…`), which cannot read flag definitions, and runtimes that keep no
|
|
51
|
+
* memory between requests.
|
|
52
|
+
*
|
|
53
|
+
* `"local"` (from `@toggld/node`) was renamed: creating a client with it
|
|
54
|
+
* throws a `TypeError`. Use `"sync"`, or `"file"` for a flag file.
|
|
55
|
+
*/
|
|
56
|
+
type EvaluationMode = "sync" | "file" | "remote";
|
|
57
|
+
/** Tuning for the background sync (sync mode, the default). Ignored in file and remote modes. */
|
|
58
|
+
interface SyncOptions {
|
|
59
|
+
/**
|
|
60
|
+
* How updates reach the client.
|
|
61
|
+
*
|
|
62
|
+
* - `"ws"` (default) — a live WebSocket stream (`/sync/v1/stream`) pushes a
|
|
63
|
+
* fresh snapshot on every change, with the snapshot poll below as a
|
|
64
|
+
* safety net (it also serves runtimes where the stream is unavailable —
|
|
65
|
+
* the client degrades to polling automatically). The client sends a
|
|
66
|
+
* heartbeat every 30 seconds to servers that support it, and reconnects
|
|
67
|
+
* when no answer arrives within 10 seconds.
|
|
68
|
+
* - `"poll"` — no stream; only the periodic snapshot poll.
|
|
69
|
+
*/
|
|
70
|
+
readonly transport?: "ws" | "poll";
|
|
71
|
+
/**
|
|
72
|
+
* Delay after each snapshot poll, in milliseconds. Default `30000`.
|
|
73
|
+
* Polling stays active while the stream is healthy to recover changes from
|
|
74
|
+
* other Node instances and dropped announcements. With successful reads,
|
|
75
|
+
* allow this delay plus two request timeouts and scheduling delays.
|
|
76
|
+
* Use a positive, finite value. Until the first snapshot arrives, the client
|
|
77
|
+
* retries faster (exponential from 250 ms).
|
|
78
|
+
*/
|
|
79
|
+
readonly pollIntervalMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Delay after each snapshot poll while the stream is healthy **and** the
|
|
82
|
+
* server promises durable delivery, in milliseconds. Default `300000`
|
|
83
|
+
* (5 minutes). Values below {@link pollIntervalMs} use `pollIntervalMs`.
|
|
84
|
+
*
|
|
85
|
+
* The stream is healthy when it is open, has delivered a snapshot, and has
|
|
86
|
+
* answered a heartbeat. Durable delivery means the server cannot lose a
|
|
87
|
+
* change before it pushes it, as the hosted API does. Against a server that
|
|
88
|
+
* does not promise it, the client always polls every `pollIntervalMs`. When the stream drops or misses a heartbeat, the client
|
|
89
|
+
* returns to `pollIntervalMs` at once, counted from its last poll.
|
|
90
|
+
*/
|
|
91
|
+
readonly streamPollIntervalMs?: number;
|
|
92
|
+
/**
|
|
93
|
+
* How long `ToggldClient.create` (and building the Effect `layer`) waits for
|
|
94
|
+
* the first snapshot before resolving anyway, in milliseconds. Default
|
|
95
|
+
* `5000`. A client that isn't ready by then warns once, and evaluates to
|
|
96
|
+
* caller defaults (`PROVIDER_NOT_READY`) while sync keeps retrying in the
|
|
97
|
+
* background. (In file mode, `create` waits for the first read of the file
|
|
98
|
+
* instead.)
|
|
99
|
+
*/
|
|
100
|
+
readonly readyTimeoutMs?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Custom `WebSocket` constructor for the stream transport. Defaults to the
|
|
103
|
+
* global `WebSocket` (Node ≥ 22). Useful for tests or proxies.
|
|
104
|
+
*/
|
|
105
|
+
readonly webSocketConstructor?: typeof globalThis.WebSocket;
|
|
106
|
+
}
|
|
107
|
+
/** Configuration for a {@link https://toggld.io Toggld} client. */
|
|
108
|
+
interface ToggldClientOptions {
|
|
109
|
+
/**
|
|
110
|
+
* An **evaluation** (or management) API key, sent as `Authorization: Bearer
|
|
111
|
+
* <sdkKey>`. The key is scoped to one tenant + environment server-side, so a
|
|
112
|
+
* client always evaluates against exactly that environment. Sync mode, the
|
|
113
|
+
* default, requires a secret key (`ffk_…`): public browser tokens (`ffp_…`)
|
|
114
|
+
* cannot read flag definitions, so creating a sync-mode client with one
|
|
115
|
+
* throws a `TypeError`. Use `mode: "remote"` with a public token.
|
|
116
|
+
*
|
|
117
|
+
* Optional **only** in file mode, which reads its flags from disk and never
|
|
118
|
+
* authenticates. Required in sync and remote modes.
|
|
119
|
+
*/
|
|
120
|
+
readonly sdkKey?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Base URL of the flag API, e.g. `https://toggld-api.example.workers.dev` or
|
|
123
|
+
* `http://localhost:3000` in local development. No trailing path — the client
|
|
124
|
+
* appends the OFREP and sync routes (`/ofrep/v1/...`, `/sync/v1/...`).
|
|
125
|
+
*
|
|
126
|
+
* Optional **only** in file mode, which makes no requests. Required in sync
|
|
127
|
+
* and remote modes.
|
|
128
|
+
*/
|
|
129
|
+
readonly endpoint?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Evaluation mode: `"sync"` (default), `"file"`, or `"remote"`. Use
|
|
132
|
+
* `"remote"` with a public token, and `"file"` to evaluate from a flag file.
|
|
133
|
+
* On serverless and edge runtimes, use `@toggld/js/serverless`. See
|
|
134
|
+
* {@link EvaluationMode}.
|
|
135
|
+
*
|
|
136
|
+
* The `TOGGLD_FLAGS_FILE` environment variable, when set and not empty,
|
|
137
|
+
* makes any client read that file in file mode, so a deployed app can be
|
|
138
|
+
* redirected to a file with no code change. It overrides {@link file}.
|
|
139
|
+
*
|
|
140
|
+
* `"local"` was renamed and throws a `TypeError`: use `"sync"`, or
|
|
141
|
+
* `"file"` with {@link file}.
|
|
142
|
+
*/
|
|
143
|
+
readonly mode?: EvaluationMode;
|
|
144
|
+
/**
|
|
145
|
+
* The flag file to evaluate from in file mode (ADR-0013). Default
|
|
146
|
+
* `./toggld.flags.json`. The file is a flag snapshot on disk; generate one
|
|
147
|
+
* with `npx @toggld/js pull`. It is **watched**: edits are adopted live, with
|
|
148
|
+
* no restart, and the client also re-reads it every 30 seconds as a safety
|
|
149
|
+
* net (which picks the file up if it is created after the client starts).
|
|
150
|
+
* `create` waits for the first read; if it fails, the client warns and
|
|
151
|
+
* serves your fallback values until the file loads.
|
|
152
|
+
*
|
|
153
|
+
* Only valid with `mode: "file"`; with any other mode it throws a
|
|
154
|
+
* `TypeError`. `TOGGLD_FLAGS_FILE` overrides it.
|
|
155
|
+
*/
|
|
156
|
+
readonly file?: string;
|
|
157
|
+
/** Tuning for sync mode, the default. Ignored in file and remote modes. */
|
|
158
|
+
readonly sync?: SyncOptions;
|
|
159
|
+
/**
|
|
160
|
+
* Evaluation telemetry for flag analytics, on by default: grouped counts
|
|
161
|
+
* per flag, variant, reason, and minute (no context or values), sent to
|
|
162
|
+
* `POST /v1/telemetry` every `flushIntervalMs` (default `30000`) and on
|
|
163
|
+
* `close()`.
|
|
164
|
+
*
|
|
165
|
+
* - Sync mode reports every evaluation, including each `evaluateAll` entry.
|
|
166
|
+
* - Remote mode reports each entry `evaluateAll` returns: the API does not
|
|
167
|
+
* count bulk requests in analytics. Single evaluations are counted by the
|
|
168
|
+
* API, so they are not reported. Its flush timer does not keep the process
|
|
169
|
+
* alive, and it does not report on Workers or edge runtimes (use
|
|
170
|
+
* `@toggld/js/serverless` there).
|
|
171
|
+
* - File mode never reports.
|
|
172
|
+
*
|
|
173
|
+
* Set `false` to send nothing: those evaluations are then not in analytics.
|
|
174
|
+
*/
|
|
175
|
+
readonly telemetry?: false | {
|
|
176
|
+
readonly flushIntervalMs?: number;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Attributes merged into the evaluation context of **every** request, under
|
|
180
|
+
* any per-call context (per-call attributes win on key collision). Handy for
|
|
181
|
+
* process-wide dimensions such as `region` or `serviceVersion`.
|
|
182
|
+
*/
|
|
183
|
+
readonly defaultContext?: EvaluationContext;
|
|
184
|
+
/** Per-request timeout in milliseconds. Default `5000`. */
|
|
185
|
+
readonly timeoutMs?: number;
|
|
186
|
+
/**
|
|
187
|
+
* Custom `fetch` implementation. Defaults to the global `fetch`. Useful for
|
|
188
|
+
* tests, proxies, or injecting retries/instrumentation.
|
|
189
|
+
*/
|
|
190
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The outcome of evaluating a single flag — OpenFeature "evaluation details"
|
|
194
|
+
* with a value typed to the requested flag type. Mirrors {@link
|
|
195
|
+
* https://openfeature.dev/specification/sections/evaluation-details}.
|
|
196
|
+
*
|
|
197
|
+
* On any abnormal evaluation (network failure, unknown flag, type mismatch, a
|
|
198
|
+
* server-reported error) the call still resolves: `value` holds the caller's
|
|
199
|
+
* default, `reason` is `"ERROR"`, and `errorCode`/`errorMessage` describe what
|
|
200
|
+
* went wrong. Evaluation never throws.
|
|
201
|
+
*/
|
|
202
|
+
interface EvaluationDetails<T extends FlagValue> {
|
|
203
|
+
readonly flagKey: string;
|
|
204
|
+
readonly value: T;
|
|
205
|
+
readonly variant: string | undefined;
|
|
206
|
+
readonly reason: Reason;
|
|
207
|
+
readonly errorCode: ErrorCode | undefined;
|
|
208
|
+
readonly errorMessage: string | undefined;
|
|
209
|
+
readonly flagMetadata: FlagMetadata;
|
|
210
|
+
}
|
|
211
|
+
export { ErrorCode as a, FlagValue as c, ToggldClientOptions as i, Reason as l, EvaluationMode as n, EvaluationContext as o, SyncOptions as r, FlagMetadata as s, EvaluationDetails as t };
|