@guidelytech/flags 0.0.0-stage → 0.8.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,355 @@
1
+ import { JsonValue, EvaluationResult, Attributes, FlagsPayload, RemoteEvaluationResponse, EncryptedFlagsPayload, StickyBucketStore, ExperimentAssignment, StickyAssignments } from './evaluate.cjs';
2
+ export { ARRAY_OPERATORS, ATTRIBUTE_OPERATORS, AttributeCondition, AttributeOperator, BOOLEAN_OPERATORS, Condition, ConditionSegment, DATE_OPERATORS, EvaluateOptions, EvaluationReason, ExperimentNamespace, ExperimentRule, ExperimentVariation, FlagComparisonOperator, FlagCondition, FlagDefinition, FlagEventsBatch, FlagRule, FlagRuleType, FlagValueType, ForceRule, GroupCondition, HASHED_OPERATORS, HoldoutDefinition, ListSegment, NUMBER_OPERATORS, PRESENCE_OPERATORS, Prerequisite, RampStep, RolloutRule, RuleSchedule, SEMVER_OPERATORS, SegmentCondition, SegmentDefinition, SplitBucket, SplitRule, TEXT_OPERATORS, compareSemver, effectiveCoverage, evaluateAllFlags, evaluateFlag, fnv1a32, hashBucket, hashConfidential, hashConfidentialAffix, instantOf, isOn, isScheduleActive, jsonEquals, lookupAttribute, numberOf, parseSemver, sha256Hex, textOf } from './evaluate.cjs';
3
+
4
+ /** The value type a getter returns for a fallback: literals widen (`"blue"` → `string`), JSON types stay. */
5
+ type FlagValue<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
6
+ /** An evaluation result whose `value` is the caller's fallback when the flag is missing, null or of another type. */
7
+ interface FlagDetails<T = JsonValue> extends Omit<EvaluationResult, "value"> {
8
+ value: T;
9
+ /** The fallback was served (flag missing, value null or of the wrong type). */
10
+ usedFallback: boolean;
11
+ }
12
+ /** Synchronous flag getters. They never throw: problems give the fallback. */
13
+ interface FlagsEvaluator {
14
+ /** Boolean flag value; `fallback` (default `false`) when missing or not a boolean. */
15
+ isEnabled(key: string, fallback?: boolean): boolean;
16
+ /** Flag value; `fallback` when missing, null or of a different JSON type than `fallback`. */
17
+ getValue<T extends JsonValue>(key: string, fallback: T): FlagValue<T>;
18
+ getString(key: string, fallback: string): string;
19
+ getNumber(key: string, fallback: number): number;
20
+ getBoolean(key: string, fallback: boolean): boolean;
21
+ /** Object or array value. */
22
+ getJson<T = JsonValue>(key: string, fallback: T): T;
23
+ /** Value plus why it was served (reason, rule, variation, experiment). */
24
+ getDetails<T extends JsonValue>(key: string, fallback: T): FlagDetails<FlagValue<T>>;
25
+ /** Every flag's value for these attributes (no exposures or usage are recorded). */
26
+ getAll(): Record<string, JsonValue>;
27
+ /** The attributes these getters evaluate with. */
28
+ getAttributes(): Attributes;
29
+ }
30
+ /** What framework bindings read from: an evaluator that can say whether it has loaded and notify changes. */
31
+ interface FlagsReader extends FlagsEvaluator {
32
+ isReady(): boolean;
33
+ /** Calls `listener` whenever values may have changed. Returns an unsubscribe function. */
34
+ subscribe(listener: () => void): () => void;
35
+ }
36
+ interface StaticFlagsInput {
37
+ /** A payload (or remote-evaluation results) from `client.getPayload()` on the server. */
38
+ payload?: FlagsPayload | RemoteEvaluationResponse | unknown;
39
+ attributes?: Attributes;
40
+ now?: Date | number;
41
+ }
42
+ /**
43
+ * A network-free, read-only snapshot over a bootstrap payload. Framework bindings render with it
44
+ * on the server and during hydration so the HTML matches; it records no exposures or usage.
45
+ * Encrypted payloads cannot be read synchronously and give fallbacks.
46
+ */
47
+ declare function createStaticFlags(input?: StaticFlagsInput): FlagsReader;
48
+
49
+ declare const FLAGS_SDK_NAME = "@guidelytech/flags";
50
+ declare const FLAGS_SDK_VERSION = "0.8.0";
51
+ declare const DEFAULT_FLAGS_API_URL = "https://guidelytech.com";
52
+ /**
53
+ * - `auto` — fetch at start, then every `pollIntervalSeconds`.
54
+ * - `lazy` — fetch when a flag is read and the cache is older than `cacheTtlSeconds` (serverless, edge).
55
+ * - `manual` — only `refresh()` fetches.
56
+ * - `stream` — server-sent events push changes; polls while the stream is down.
57
+ */
58
+ type PollingMode = "auto" | "lazy" | "manual" | "stream";
59
+ /** `localOnly`: never fetch; serve overrides, then the bootstrap. `localOverRemote`: overrides win. `remoteOverLocal`: overrides fill in flags missing remotely. */
60
+ type OverrideBehaviour = "localOnly" | "localOverRemote" | "remoteOverLocal";
61
+ /** Key/value storage for the last payload: `localStorage`-like or AsyncStorage-like (sync or async). */
62
+ interface FlagsStorage {
63
+ getItem(key: string): string | null | undefined | Promise<string | null | undefined>;
64
+ setItem(key: string, value: string): void | Promise<void>;
65
+ removeItem?(key: string): void | Promise<void>;
66
+ }
67
+ interface FlagsLogger {
68
+ debug?(...args: unknown[]): void;
69
+ warn?(...args: unknown[]): void;
70
+ error?(...args: unknown[]): void;
71
+ }
72
+ /** What a bootstrap / `getPayload()` can hold. */
73
+ type FlagsBootstrap = FlagsPayload | EncryptedFlagsPayload | RemoteEvaluationResponse;
74
+ interface FlagsClientOptions {
75
+ /** Environment public key `pk_…` (the `projectId` used for tracking) or, on servers only, a secret `gly_sk_…` key. */
76
+ sdkKey?: string;
77
+ /** Alias of `sdkKey`. */
78
+ projectId?: string;
79
+ /** Base URL of your Guidely deployment (default `https://guidelytech.com`). */
80
+ apiUrl?: string;
81
+ /** `development` | `staging` | `production`. Required with a `gly_sk_` key. */
82
+ environment?: string;
83
+ /** Default attributes, merged under per-call (`forUser`) attributes. */
84
+ attributes?: Attributes;
85
+ /**
86
+ * Attributes supplied by a Guidely SDK integration (identity, page, device). App attributes
87
+ * (`attributes`, `setAttributes`) are merged over them.
88
+ */
89
+ contextAttributes?: Attributes;
90
+ pollingMode?: PollingMode;
91
+ /** `auto` / `stream` fallback polling interval (default 60). */
92
+ pollIntervalSeconds?: number;
93
+ /** `lazy` cache lifetime (default 60). */
94
+ cacheTtlSeconds?: number;
95
+ /** Bootstrap: a payload (plain or encrypted) or remote-evaluation results, used immediately. */
96
+ payload?: FlagsBootstrap | null;
97
+ /** Bootstrap for remote evaluation: `{ version, flags }` or a map of results. */
98
+ remoteResults?: RemoteEvaluationResponse | Record<string, EvaluationResult> | null;
99
+ /** Base64 AES-256 key for environments that encrypt their payload. */
100
+ decryptionKey?: string;
101
+ /** Ask the server to evaluate (`POST /flags/evaluate`) instead of downloading rules. Needed when the environment hides rules. */
102
+ remoteEval?: boolean;
103
+ /** Debounce for re-evaluating remotely after attribute changes (default 50 ms). */
104
+ remoteEvalDebounceMs?: number;
105
+ /** Local flag values (tests, QA, kill switches) applied per `overrideBehaviour`. */
106
+ overrides?: Record<string, JsonValue> | null;
107
+ overrideBehaviour?: OverrideBehaviour;
108
+ /**
109
+ * Read dev overrides from outside the code (default `true`): `GUIDELY_FLAG_OVERRIDES`,
110
+ * `GUIDELY_FLAG_OVERRIDES_FILE` and `GUIDELY_FLAG_OVERRIDE_BEHAVIOUR` environment variables, and
111
+ * the `__GUIDELY_FLAG_OVERRIDES__` / `__GUIDELY_FLAG_OVERRIDE_BEHAVIOUR__` globals (browser tests).
112
+ * They win over `overrides`, and their behaviour over `overrideBehaviour`.
113
+ */
114
+ devOverrides?: boolean;
115
+ /** Keeps experiment assignments stable (`InMemoryStickyBucketStore`, `LocalStorageStickyBucketStore`). */
116
+ stickyBucketService?: StickyBucketStore;
117
+ /** Persists the last payload. Default: `localStorage` in browsers, none elsewhere. `false` disables. */
118
+ storage?: FlagsStorage | null | false;
119
+ /** Browser only: read `?guidely_flags=key:value,key2:"text"` from the URL as overrides (QA). */
120
+ enableUrlOverrides?: boolean;
121
+ /** Called once per (experiment, user, variation) when a user is bucketed into an experiment. */
122
+ trackingCallback?: (experiment: ExperimentAssignment, result: EvaluationResult) => void;
123
+ /** Send experiment exposures to Guidely (`POST /flags/events`). Default: `true` unless `trackingCallback` is set. */
124
+ trackExposures?: boolean;
125
+ /** Send aggregated evaluation counts to Guidely at most every 60 s (default `true`). */
126
+ reportUsage?: boolean;
127
+ onReady?: () => void;
128
+ /** New payload (or remote results) applied. */
129
+ onChange?: (data: FlagsPayload | RemoteEvaluationResponse) => void;
130
+ onFlagEvaluated?: (result: EvaluationResult) => void;
131
+ onError?: (error: Error) => void;
132
+ /** Start without network activity (cached / bootstrapped data keeps serving). */
133
+ offline?: boolean;
134
+ fetch?: typeof fetch;
135
+ /**
136
+ * Defaults to `console`: `error` for failed requests (once per streak), `warn` for misuse and,
137
+ * in development, reads of flags that do not exist. `null` silences the client.
138
+ */
139
+ logger?: FlagsLogger | null;
140
+ /** Evaluation clock (tests). */
141
+ now?: () => Date | number;
142
+ /** Reported with usage (set by Guidely SDK integrations). */
143
+ sdk?: {
144
+ name: string;
145
+ version: string;
146
+ };
147
+ }
148
+ /**
149
+ * Feature flags for one environment. Create with {@link createFlagsClient}; one per app (browser)
150
+ * or per process (server — use {@link FlagsClient.forUser} per request).
151
+ */
152
+ declare class FlagsClient implements FlagsReader {
153
+ readonly sdkKey: string;
154
+ readonly apiUrl: string;
155
+ readonly environment: string | undefined;
156
+ private readonly o;
157
+ private readonly cfg;
158
+ private readonly secret;
159
+ private readonly log;
160
+ private readonly storage;
161
+ private readonly storageKey;
162
+ private readonly getters;
163
+ private payload;
164
+ /** The payload as received (possibly encrypted): what `getPayload()` hands to browsers. */
165
+ private raw;
166
+ private etag;
167
+ private remote;
168
+ private results;
169
+ private resultsKey;
170
+ private readonly resultCache;
171
+ private context;
172
+ private attributes;
173
+ private overrides;
174
+ private readonly devOverrides;
175
+ private readonly urlOverrides;
176
+ private offline;
177
+ private destroyed;
178
+ private ready_;
179
+ private resolveReady;
180
+ private readonly readyPromise;
181
+ private readonly listeners;
182
+ private inflight;
183
+ private lastFetchAt;
184
+ private failures;
185
+ private retryAt;
186
+ private pollTimer;
187
+ private remoteTimer;
188
+ private flushTimer;
189
+ private flushDue;
190
+ private stream;
191
+ private streamConnected;
192
+ private streamFailures;
193
+ private reconnectTimer;
194
+ private readonly exposed;
195
+ private readonly usage;
196
+ private exposures;
197
+ private readonly warned;
198
+ private cleanups;
199
+ private lastError;
200
+ constructor(options: FlagsClientOptions);
201
+ /** Boolean flag value; `fallback` (default `false`) when missing or not a boolean. */
202
+ isEnabled(key: string, fallback?: boolean): boolean;
203
+ /** Flag value; `fallback` when missing, null or of a different JSON type than `fallback`. */
204
+ getValue<T extends JsonValue>(key: string, fallback: T): FlagValue<T>;
205
+ getString(key: string, fallback: string): string;
206
+ getNumber(key: string, fallback: number): number;
207
+ getBoolean(key: string, fallback: boolean): boolean;
208
+ getJson<T = JsonValue>(key: string, fallback: T): T;
209
+ /** Value plus reason, rule, variation and experiment assignment. */
210
+ getDetails<T extends JsonValue>(key: string, fallback: T): FlagDetails<FlagValue<T>>;
211
+ /** Every flag's value for the current attributes (records no exposures or usage). */
212
+ getAll(): Record<string, JsonValue>;
213
+ /**
214
+ * Getters bound to other attributes (merged over the client's), without changing the client:
215
+ * the per-request pattern on servers. With remote evaluation they serve results already cached
216
+ * for these attributes, else fallbacks.
217
+ */
218
+ forUser(attributes: Attributes): FlagsEvaluator;
219
+ /** Replaces the app attributes. */
220
+ setAttributes(attributes: Attributes): void;
221
+ /** Merges into the app attributes. */
222
+ updateAttributes(partial: Attributes): void;
223
+ /** Replaces the integration-supplied attributes (see `contextAttributes`). */
224
+ setContextAttributes(attributes: Attributes): void;
225
+ /** Context attributes with app attributes merged over them. */
226
+ getAttributes(): Attributes;
227
+ /** Replaces the overrides (`null` clears them). Dev and URL overrides stay. */
228
+ setOverrides(overrides: Record<string, JsonValue> | null): void;
229
+ private changeAttributes;
230
+ /** Resolves once the first load settled (bootstrap, network success or failure). Never rejects. */
231
+ ready(): Promise<void>;
232
+ isReady(): boolean;
233
+ /** Calls `listener` whenever values may have changed (payload, attributes, overrides, readiness). */
234
+ subscribe: (listener: () => void) => (() => void);
235
+ /** `true` stops all network activity; cached data keeps serving. `false` resumes and refreshes. */
236
+ setOffline(offline: boolean): void;
237
+ /** Fetches now (payload, or remote evaluation). Resolves when done; never rejects. */
238
+ refresh(): Promise<void>;
239
+ /**
240
+ * The current data for server-side rendering: pass it as `payload` to the browser client (or
241
+ * framework provider) to render the same values without a flash. Encrypted payloads stay
242
+ * encrypted; with remote evaluation it returns the results. `null` before anything loaded.
243
+ */
244
+ getPayload(): FlagsBootstrap | null;
245
+ /** Sends pending usage counts and exposures now. */
246
+ flush(): Promise<void>;
247
+ /** Stops timers and streams and sends pending usage. The client keeps serving cached values. */
248
+ destroy(): void;
249
+ private overrideFor;
250
+ private resolve;
251
+ private evaluateRemoteOrLocal;
252
+ private all;
253
+ private now;
254
+ private record;
255
+ private warnOnce;
256
+ private setPayload;
257
+ private setResults;
258
+ /** Decrypts when needed and applies. Returns false (keeping the previous payload) when unusable. */
259
+ private applyRaw;
260
+ private loadStored;
261
+ private persist;
262
+ private url;
263
+ private headers;
264
+ private get fetcher();
265
+ private request;
266
+ private fetchNow;
267
+ private fetchPayload;
268
+ private fetchRemote;
269
+ private scheduleRemote;
270
+ private lazyRefresh;
271
+ private start;
272
+ private settle;
273
+ /** Arms the poll timer / stream for the current mode. */
274
+ private schedule;
275
+ private schedulePoll;
276
+ private stopNetwork;
277
+ private onStreamEvent;
278
+ private openStream;
279
+ /** The stream dropped: poll meanwhile and reconnect with backoff (`permanent`: never retry). */
280
+ private streamDown;
281
+ private scheduleFlush;
282
+ private takeBatch;
283
+ private requeue;
284
+ /** Page hide / destroy: `sendBeacon` for public keys, else a `keepalive` fetch. */
285
+ private flushOnExit;
286
+ private wireBrowser;
287
+ private notify;
288
+ private reportError;
289
+ }
290
+ /** Creates a flags client and starts loading (per `pollingMode`). */
291
+ declare function createFlagsClient(options: FlagsClientOptions): FlagsClient;
292
+
293
+ /**
294
+ * Dev overrides (docs/feature-flags-spec.md §4.1): flag values pinned from outside the code, so CI
295
+ * and tests can switch flags without editing the app. Read once, when a client is created.
296
+ * Runtime-neutral: environment variables via `process.env` (Node, Bun, Next.js server) or
297
+ * `Deno.env`, files via `process.getBuiltinModule("node:fs")` or `Deno`, and a global that test
298
+ * runners set in the page (`page.addInitScript`).
299
+ */
300
+
301
+ declare const OVERRIDES_ENV = "GUIDELY_FLAG_OVERRIDES";
302
+ declare const OVERRIDES_FILE_ENV = "GUIDELY_FLAG_OVERRIDES_FILE";
303
+ declare const OVERRIDE_BEHAVIOUR_ENV = "GUIDELY_FLAG_OVERRIDE_BEHAVIOUR";
304
+ declare const OVERRIDES_GLOBAL = "__GUIDELY_FLAG_OVERRIDES__";
305
+ declare const OVERRIDE_BEHAVIOUR_GLOBAL = "__GUIDELY_FLAG_OVERRIDE_BEHAVIOUR__";
306
+ interface DevOverrides {
307
+ /** Merged values: file, then environment variable, then global (later wins per key). */
308
+ values: Record<string, JsonValue>;
309
+ /** Replaces the configured `overrideBehaviour` when set. */
310
+ behaviour?: OverrideBehaviour;
311
+ /** Where values or the behaviour came from, for the start-up notice. */
312
+ sources: string[];
313
+ /** Unreadable or malformed sources (ignored). */
314
+ errors: string[];
315
+ }
316
+ /** Reads every dev-override source. Never throws: problems land in `errors`. */
317
+ declare function readDevOverrides(): DevOverrides;
318
+
319
+ /** Sticky bucket stores (docs/feature-flags-spec.md §3.5): keep a user's experiment variation across config changes. */
320
+
321
+ /** Keeps assignments in memory for the life of the process (servers, tests). */
322
+ declare class InMemoryStickyBucketStore implements StickyBucketStore {
323
+ private readonly docs;
324
+ get(docKey: string): StickyAssignments | null;
325
+ set(docKey: string, assignments: StickyAssignments): void;
326
+ }
327
+ /** Persists assignments in `localStorage` (browsers). Falls back to memory when storage is unavailable. */
328
+ declare class LocalStorageStickyBucketStore implements StickyBucketStore {
329
+ private readonly prefix;
330
+ private readonly memory;
331
+ constructor(prefix?: string);
332
+ private get storage();
333
+ get(docKey: string): StickyAssignments | null;
334
+ set(docKey: string, assignments: StickyAssignments): void;
335
+ }
336
+
337
+ /** Small runtime-neutral helpers shared by the flags client. No Node built-ins, no DOM at module scope. */
338
+
339
+ /** JSON with object keys sorted: equal attribute sets give equal strings (remote-eval cache keys). */
340
+ declare function stableStringify(value: unknown): string;
341
+ declare function isEncryptedPayload(x: unknown): x is EncryptedFlagsPayload;
342
+ /** A remote-evaluation response (`{ version, flags: { key: EvaluationResult } }`) rather than a payload. */
343
+ declare function isRemoteResults(x: unknown): x is RemoteEvaluationResponse;
344
+ declare function isFlagsPayload(x: unknown): x is FlagsPayload;
345
+ /** `?guidely_flags=key:value,key2:"str"` → overrides (text as in {@link parseOverrides}; invalid text gives none). */
346
+ declare function parseUrlOverrides(search: string, param?: string): Record<string, JsonValue>;
347
+ /**
348
+ * Overrides text (docs/feature-flags-spec.md §4.1), as held by `GUIDELY_FLAG_OVERRIDES`, an
349
+ * overrides file or `?guidely_flags=`: a JSON object (`{"new-checkout":true}`) or a list
350
+ * `key:value` separated by commas or newlines (`new-checkout:true,limit:5,theme:"dark"`), where each
351
+ * value is JSON when it parses and text otherwise. `null` when the text is a malformed JSON object.
352
+ */
353
+ declare function parseOverrides(text: string): Record<string, JsonValue> | null;
354
+
355
+ export { Attributes, DEFAULT_FLAGS_API_URL, type DevOverrides, EncryptedFlagsPayload, EvaluationResult, ExperimentAssignment, FLAGS_SDK_NAME, FLAGS_SDK_VERSION, type FlagDetails, type FlagValue, type FlagsBootstrap, FlagsClient, type FlagsClientOptions, type FlagsEvaluator, type FlagsLogger, FlagsPayload, type FlagsReader, type FlagsStorage, InMemoryStickyBucketStore, JsonValue, LocalStorageStickyBucketStore, OVERRIDES_ENV, OVERRIDES_FILE_ENV, OVERRIDES_GLOBAL, OVERRIDE_BEHAVIOUR_ENV, OVERRIDE_BEHAVIOUR_GLOBAL, type OverrideBehaviour, type PollingMode, RemoteEvaluationResponse, type StaticFlagsInput, StickyAssignments, StickyBucketStore, createFlagsClient, createStaticFlags, isEncryptedPayload, isFlagsPayload, isRemoteResults, parseOverrides, parseUrlOverrides, readDevOverrides, stableStringify };