@infaris/flags 0.0.0-stage → 0.1.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/dist/client.js ADDED
@@ -0,0 +1,110 @@
1
+ import {
2
+ FlagsError,
3
+ readEnv,
4
+ trimSlash
5
+ } from "./chunk-LLGINDZS.js";
6
+ import "./chunk-TRTQSARU.js";
7
+
8
+ // src/client.ts
9
+ var BrowserFlags = class {
10
+ url;
11
+ key;
12
+ environment;
13
+ options;
14
+ fetchImpl;
15
+ state;
16
+ loaded = false;
17
+ loading = null;
18
+ source = null;
19
+ listeners = /* @__PURE__ */ new Set();
20
+ constructor(options = {}) {
21
+ const env = readEnv();
22
+ const url = options.url ?? env?.NEXT_PUBLIC_INFARIS_FLAGS_URL;
23
+ const key = options.publishableKey ?? env?.NEXT_PUBLIC_INFARIS_FLAGS_KEY;
24
+ if (!url) throw new FlagsError("Missing url (set NEXT_PUBLIC_INFARIS_FLAGS_URL)", null, "missing_url");
25
+ if (!key) throw new FlagsError("Missing publishable key (set NEXT_PUBLIC_INFARIS_FLAGS_KEY)", null, "missing_key");
26
+ if (key.startsWith("ifr_fsk_")) throw new FlagsError("Never use the secret key in a browser; use the publishable key (ifr_fpk_\u2026)", null, "wrong_key");
27
+ this.url = trimSlash(url);
28
+ this.key = key;
29
+ this.environment = options.environment ?? "production";
30
+ this.options = options;
31
+ this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
32
+ this.state = options.initial ?? { version: 0, flags: {}, items: {} };
33
+ this.loaded = !!options.initial;
34
+ }
35
+ get version() {
36
+ return this.state.version;
37
+ }
38
+ /** The current answer; stable between changes (safe for `useSyncExternalStore`). */
39
+ snapshot() {
40
+ return this.state;
41
+ }
42
+ async load() {
43
+ if (this.loading) return this.loading;
44
+ this.loading = (async () => {
45
+ try {
46
+ const token = await this.options.getAccessToken?.();
47
+ const res = await this.fetchImpl(`${this.url}/evaluate`, {
48
+ method: "POST",
49
+ headers: {
50
+ authorization: `Bearer ${this.key}`,
51
+ "content-type": "application/json",
52
+ ...token ? { "x-infaris-auth-token": token } : {}
53
+ },
54
+ body: JSON.stringify({ environment: this.environment, ...this.options.anonymousId ? { anonymousId: this.options.anonymousId } : {} })
55
+ });
56
+ if (!res.ok) throw new FlagsError(`Flags evaluate failed (HTTP ${res.status})`, res.status, "http_error");
57
+ this.state = await res.json();
58
+ this.loaded = true;
59
+ for (const listener of this.listeners) listener();
60
+ } finally {
61
+ this.loading = null;
62
+ }
63
+ })();
64
+ return this.loading;
65
+ }
66
+ getFlag(key, defaultValue = false) {
67
+ return Object.prototype.hasOwnProperty.call(this.state.flags, key) ? this.state.flags[key] : defaultValue;
68
+ }
69
+ getConfig(key, defaultValue) {
70
+ return Object.prototype.hasOwnProperty.call(this.state.items, key) ? this.state.items[key] : defaultValue;
71
+ }
72
+ /** Loads once, listens for new versions while anyone is subscribed. */
73
+ subscribe(listener) {
74
+ this.listeners.add(listener);
75
+ if (!this.loaded) void this.load().catch(() => void 0);
76
+ this.openStream();
77
+ return () => {
78
+ this.listeners.delete(listener);
79
+ if (this.listeners.size === 0) {
80
+ this.source?.close();
81
+ this.source = null;
82
+ }
83
+ };
84
+ }
85
+ /** Ask again, e.g. after sign-in or sign-out (the token changed). */
86
+ reload() {
87
+ return this.load();
88
+ }
89
+ openStream() {
90
+ if (this.source || this.options.stream === false) return;
91
+ const ES = globalThis.EventSource;
92
+ if (!ES) return;
93
+ const source = new ES(`${this.url}/stream?key=${encodeURIComponent(this.key)}`);
94
+ source.addEventListener("version", (event) => {
95
+ try {
96
+ const { version } = JSON.parse(event.data);
97
+ if (version > this.state.version) void this.load().catch(() => void 0);
98
+ } catch {
99
+ }
100
+ });
101
+ this.source = source;
102
+ }
103
+ };
104
+ function createBrowserFlags(options = {}) {
105
+ return new BrowserFlags(options);
106
+ }
107
+ export {
108
+ BrowserFlags,
109
+ createBrowserFlags
110
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,364 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // ../hosting-sdk/dist/index.cjs
38
+ var require_dist = __commonJS({
39
+ "../hosting-sdk/dist/index.cjs"(exports2, module2) {
40
+ "use strict";
41
+ var __defProp2 = Object.defineProperty;
42
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
43
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
44
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
45
+ var __export2 = (target, all) => {
46
+ for (var name in all)
47
+ __defProp2(target, name, { get: all[name], enumerable: true });
48
+ };
49
+ var __copyProps2 = (to, from, except, desc) => {
50
+ if (from && typeof from === "object" || typeof from === "function") {
51
+ for (let key of __getOwnPropNames2(from))
52
+ if (!__hasOwnProp2.call(to, key) && key !== except)
53
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
54
+ }
55
+ return to;
56
+ };
57
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
58
+ var index_exports2 = {};
59
+ __export2(index_exports2, {
60
+ InfarisError: () => InfarisError3
61
+ });
62
+ module2.exports = __toCommonJS2(index_exports2);
63
+ var errorBrand = /* @__PURE__ */ Symbol.for("@infaris/hosting-sdk/error");
64
+ var InfarisError3 = class _InfarisError extends Error {
65
+ // SDK bundles contain separate copies; catch the shared base across bundles and module formats.
66
+ static [Symbol.hasInstance](value) {
67
+ if (this !== _InfarisError) return Function.prototype[Symbol.hasInstance].call(this, value);
68
+ return value instanceof Error && errorBrand in value && value[errorBrand] === true;
69
+ }
70
+ code;
71
+ status;
72
+ constructor(message, code = "sdk_error", status = null) {
73
+ super(message);
74
+ Object.defineProperty(this, errorBrand, { value: true });
75
+ this.name = "InfarisError";
76
+ this.code = code;
77
+ this.status = status;
78
+ }
79
+ };
80
+ }
81
+ });
82
+
83
+ // src/index.ts
84
+ var index_exports = {};
85
+ __export(index_exports, {
86
+ FlagsClient: () => FlagsClient,
87
+ FlagsError: () => FlagsError,
88
+ InfarisError: () => import_hosting_sdk2.InfarisError,
89
+ createClient: () => createFlagsClient,
90
+ createFlagsClient: () => createFlagsClient,
91
+ detectEnvironment: () => detectEnvironment,
92
+ evaluateFlag: () => evaluateFlag,
93
+ fnv1a32: () => fnv1a32,
94
+ rolloutBucket: () => rolloutBucket,
95
+ ruleMatches: () => ruleMatches
96
+ });
97
+ module.exports = __toCommonJS(index_exports);
98
+
99
+ // src/evaluate.ts
100
+ function fnv1a32(input) {
101
+ const bytes = new TextEncoder().encode(input);
102
+ let hash = 2166136261;
103
+ for (const byte of bytes) {
104
+ hash ^= byte;
105
+ hash = Math.imul(hash, 16777619) >>> 0;
106
+ }
107
+ return hash >>> 0;
108
+ }
109
+ function rolloutBucket(flagKey, userId) {
110
+ return fnv1a32(`${flagKey}:${userId}`) % 1e4;
111
+ }
112
+ function attributeOf(ctx, attribute) {
113
+ switch (attribute) {
114
+ case "email":
115
+ return ctx.email ? ctx.email.trim().toLowerCase() : void 0;
116
+ case "emailDomain": {
117
+ const at = ctx.email ? ctx.email.lastIndexOf("@") : -1;
118
+ return at >= 0 ? ctx.email.slice(at + 1).trim().toLowerCase() : void 0;
119
+ }
120
+ case "userId":
121
+ return ctx.userId || void 0;
122
+ case "org":
123
+ return ctx.org || void 0;
124
+ case "role":
125
+ return ctx.role || void 0;
126
+ }
127
+ }
128
+ var caseless = (attribute) => attribute === "email" || attribute === "emailDomain";
129
+ function ruleMatches(rule, ctx) {
130
+ const actual = attributeOf(ctx, rule.attribute);
131
+ if (actual === void 0) return false;
132
+ const values = caseless(rule.attribute) ? rule.values.map((v) => v.trim().toLowerCase()) : rule.values;
133
+ switch (rule.operator) {
134
+ case "in":
135
+ return values.includes(actual);
136
+ case "not_in":
137
+ return !values.includes(actual);
138
+ case "ends_with":
139
+ return values.some((v) => v.length > 0 && actual.endsWith(v));
140
+ }
141
+ }
142
+ function evaluateFlag(flag, environment, ctx) {
143
+ if (flag.killed) return { value: false, reason: "killed" };
144
+ const env = flag.environments[environment];
145
+ if (!env || !env.enabled) return { value: false, reason: "off" };
146
+ for (const rule of env.rules) {
147
+ if (ruleMatches(rule, ctx)) return { value: rule.serve, reason: "rule" };
148
+ }
149
+ if (env.rollout === null || env.rollout >= 100) return { value: true, reason: "fallthrough" };
150
+ if (env.rollout <= 0) return { value: false, reason: "rollout" };
151
+ if (!ctx.userId) return { value: false, reason: "no_user" };
152
+ return { value: rolloutBucket(flag.key, ctx.userId) < Math.round(env.rollout * 100), reason: "rollout" };
153
+ }
154
+
155
+ // src/types.ts
156
+ var import_hosting_sdk = __toESM(require_dist(), 1);
157
+ var FlagsError = class extends import_hosting_sdk.InfarisError {
158
+ constructor(message, status, code) {
159
+ super(message, code, status);
160
+ this.status = status;
161
+ this.code = code;
162
+ this.name = "FlagsError";
163
+ }
164
+ status;
165
+ code;
166
+ };
167
+ function detectEnvironment(env = readEnv()) {
168
+ const raw = (env?.INFARIS_ENV ?? env?.VERCEL_ENV ?? env?.NODE_ENV ?? "production").toLowerCase();
169
+ if (raw === "preview") return "preview";
170
+ if (raw === "development" || raw === "dev" || raw === "test" || raw === "local") return "development";
171
+ return "production";
172
+ }
173
+ function readEnv() {
174
+ const p = globalThis.process;
175
+ return p?.env;
176
+ }
177
+ function parseSseVersions(buffer) {
178
+ const versions = [];
179
+ const events = buffer.split(/\r?\n\r?\n/);
180
+ const rest = events.pop() ?? "";
181
+ for (const event of events) {
182
+ let name = "message";
183
+ let data = "";
184
+ for (const line of event.split(/\r?\n/)) {
185
+ if (line.startsWith("event:")) name = line.slice(6).trim();
186
+ else if (line.startsWith("data:")) data += line.slice(5).trim();
187
+ }
188
+ if (name !== "version" || !data) continue;
189
+ try {
190
+ const parsed = JSON.parse(data);
191
+ if (typeof parsed.version === "number") versions.push(parsed.version);
192
+ } catch {
193
+ }
194
+ }
195
+ return { versions, rest };
196
+ }
197
+ function trimSlash(url) {
198
+ return url.replace(/\/+$/, "");
199
+ }
200
+
201
+ // src/server.ts
202
+ var FlagsClient = class {
203
+ environment;
204
+ url;
205
+ key;
206
+ fetchImpl;
207
+ refreshMs;
208
+ onError;
209
+ snapshot = null;
210
+ etag = null;
211
+ checkedAt = 0;
212
+ loading = null;
213
+ streamAbort = null;
214
+ streaming = false;
215
+ closed = false;
216
+ listeners = /* @__PURE__ */ new Set();
217
+ constructor(options = {}) {
218
+ const env = readEnv();
219
+ const url = options.url ?? env?.INFARIS_FLAGS_URL;
220
+ const key = options.secretKey ?? env?.INFARIS_FLAGS_SECRET_KEY;
221
+ if (!url) throw new FlagsError("Missing url (set INFARIS_FLAGS_URL)", null, "missing_url");
222
+ if (!key) throw new FlagsError("Missing secret key (set INFARIS_FLAGS_SECRET_KEY)", null, "missing_key");
223
+ if (!key.startsWith("ifr_fsk_")) throw new FlagsError("The server client needs the secret key (ifr_fsk_\u2026)", null, "wrong_key");
224
+ this.url = trimSlash(url);
225
+ this.key = key;
226
+ this.environment = options.environment ?? detectEnvironment(env);
227
+ this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
228
+ this.refreshMs = options.refreshIntervalMs ?? 3e4;
229
+ this.onError = options.onError ?? (() => void 0);
230
+ if (options.stream ?? true) this.streaming = true;
231
+ }
232
+ /** The version in memory (0 before the first load). */
233
+ get version() {
234
+ return this.snapshot?.version ?? 0;
235
+ }
236
+ /** Loads the first snapshot (and opens the stream). Every getter calls it, so calling it yourself is optional. */
237
+ async ready() {
238
+ if (this.snapshot) {
239
+ if (Date.now() - this.checkedAt > this.refreshMs && !this.loading) void this.refresh().catch(this.onError);
240
+ return;
241
+ }
242
+ await this.refresh();
243
+ if (this.streaming && !this.streamAbort) this.openStream();
244
+ }
245
+ /** Fetches the snapshot when it changed (`If-None-Match`). */
246
+ async refresh() {
247
+ if (this.loading) return this.loading;
248
+ this.loading = (async () => {
249
+ try {
250
+ const res = await this.fetchImpl(`${this.url}/snapshot`, {
251
+ headers: { authorization: `Bearer ${this.key}`, ...this.etag ? { "if-none-match": this.etag } : {} }
252
+ });
253
+ this.checkedAt = Date.now();
254
+ if (res.status === 304 && this.snapshot) return;
255
+ if (!res.ok) throw new FlagsError(`Flags snapshot failed (HTTP ${res.status})`, res.status, "http_error");
256
+ const next = await res.json();
257
+ const changed = next.version !== this.snapshot?.version;
258
+ this.snapshot = next;
259
+ this.etag = res.headers.get("etag");
260
+ if (changed) for (const listener of this.listeners) listener(next.version);
261
+ } finally {
262
+ this.loading = null;
263
+ }
264
+ })();
265
+ return this.loading;
266
+ }
267
+ /** The value of a flag for this user; `defaultValue` for an unknown flag or before Hosting answered once. */
268
+ async getFlag(key, context = {}, defaultValue = false) {
269
+ try {
270
+ await this.ready();
271
+ } catch (error) {
272
+ this.onError(error);
273
+ if (!this.snapshot) return defaultValue;
274
+ }
275
+ return this.getFlagSync(key, context, defaultValue);
276
+ }
277
+ /** From memory only (no request): for hot paths once `ready()` resolved. */
278
+ getFlagSync(key, context = {}, defaultValue = false) {
279
+ const result = this.evaluate(key, context);
280
+ return result ? result.value : defaultValue;
281
+ }
282
+ /** Value and reason, or null for an unknown flag. */
283
+ evaluate(key, context = {}) {
284
+ const flag = this.snapshot?.flags.find((f) => f.key === key);
285
+ return flag ? evaluateFlag(flag, this.environment, context) : null;
286
+ }
287
+ /** Every flag for this user. */
288
+ async getAll(context = {}) {
289
+ await this.ready().catch(this.onError);
290
+ const out = {};
291
+ for (const flag of this.snapshot?.flags ?? []) out[flag.key] = evaluateFlag(flag, this.environment, context).value;
292
+ return out;
293
+ }
294
+ /** An Edge Config value. */
295
+ async getConfig(key, defaultValue) {
296
+ await this.ready().catch(this.onError);
297
+ const items = this.snapshot?.items ?? {};
298
+ return Object.prototype.hasOwnProperty.call(items, key) ? items[key] : defaultValue;
299
+ }
300
+ /** Called with the new version after every change that reached this process. */
301
+ onChange(listener) {
302
+ this.listeners.add(listener);
303
+ return () => this.listeners.delete(listener);
304
+ }
305
+ close() {
306
+ this.closed = true;
307
+ this.streamAbort?.abort();
308
+ this.streamAbort = null;
309
+ }
310
+ openStream() {
311
+ if (this.closed) return;
312
+ const abort = new AbortController();
313
+ this.streamAbort = abort;
314
+ void (async () => {
315
+ let delay = 1e3;
316
+ while (!abort.signal.aborted) {
317
+ try {
318
+ const res = await this.fetchImpl(`${this.url}/stream`, {
319
+ headers: { authorization: `Bearer ${this.key}`, accept: "text/event-stream" },
320
+ signal: abort.signal
321
+ });
322
+ if (!res.ok || !res.body) throw new FlagsError(`Flags stream failed (HTTP ${res.status})`, res.status, "stream_failed");
323
+ delay = 1e3;
324
+ const reader = res.body.getReader();
325
+ const decoder = new TextDecoder();
326
+ let buffer = "";
327
+ for (; ; ) {
328
+ const { done, value } = await reader.read();
329
+ if (done) break;
330
+ buffer += decoder.decode(value, { stream: true });
331
+ const parsed = parseSseVersions(buffer);
332
+ buffer = parsed.rest;
333
+ if (parsed.versions.some((v) => v > this.version)) await this.refresh().catch(this.onError);
334
+ else this.checkedAt = Date.now();
335
+ }
336
+ } catch (error) {
337
+ if (abort.signal.aborted) return;
338
+ this.onError(error);
339
+ }
340
+ await new Promise((resolve) => setTimeout(resolve, delay));
341
+ delay = Math.min(delay * 2, 3e4);
342
+ }
343
+ })();
344
+ }
345
+ };
346
+ function createFlagsClient(options = {}) {
347
+ return new FlagsClient(options);
348
+ }
349
+
350
+ // src/index.ts
351
+ var import_hosting_sdk2 = __toESM(require_dist(), 1);
352
+ // Annotate the CommonJS export names for ESM import in node:
353
+ 0 && (module.exports = {
354
+ FlagsClient,
355
+ FlagsError,
356
+ InfarisError,
357
+ createClient,
358
+ createFlagsClient,
359
+ detectEnvironment,
360
+ evaluateFlag,
361
+ fnv1a32,
362
+ rolloutBucket,
363
+ ruleMatches
364
+ });
@@ -0,0 +1,61 @@
1
+ import { a as FlagEnvironment, b as FetchLike, F as FlagContext, c as FlagReason, C as ConfigValue } from './types-DMIusvFt.cjs';
2
+ export { E as EvaluatedFlags, d as FlagDefinition, e as FlagsError, f as FlagsSnapshot, I as InfarisError, g as detectEnvironment, h as evaluateFlag, i as fnv1a32, r as rolloutBucket, j as ruleMatches } from './types-DMIusvFt.cjs';
3
+
4
+ interface FlagsClientOptions {
5
+ /** `INFARIS_FLAGS_URL` by default (`https://…/flags/v1/<ref>`). */
6
+ url?: string;
7
+ /** `INFARIS_FLAGS_SECRET_KEY` by default. */
8
+ secretKey?: string;
9
+ /** `INFARIS_ENV`, `VERCEL_ENV` or `NODE_ENV` by default. */
10
+ environment?: FlagEnvironment;
11
+ /** Keep a stream open for push invalidation (default true). Off in short-lived functions if you prefer polling. */
12
+ stream?: boolean;
13
+ /** Longest a snapshot is used without asking for a newer version (default 30 s). */
14
+ refreshIntervalMs?: number;
15
+ fetch?: FetchLike;
16
+ onError?: (error: unknown) => void;
17
+ }
18
+ declare class FlagsClient {
19
+ readonly environment: FlagEnvironment;
20
+ private readonly url;
21
+ private readonly key;
22
+ private readonly fetchImpl;
23
+ private readonly refreshMs;
24
+ private readonly onError;
25
+ private snapshot;
26
+ private etag;
27
+ private checkedAt;
28
+ private loading;
29
+ private streamAbort;
30
+ private streaming;
31
+ private closed;
32
+ private listeners;
33
+ constructor(options?: FlagsClientOptions);
34
+ /** The version in memory (0 before the first load). */
35
+ get version(): number;
36
+ /** Loads the first snapshot (and opens the stream). Every getter calls it, so calling it yourself is optional. */
37
+ ready(): Promise<void>;
38
+ /** Fetches the snapshot when it changed (`If-None-Match`). */
39
+ refresh(): Promise<void>;
40
+ /** The value of a flag for this user; `defaultValue` for an unknown flag or before Hosting answered once. */
41
+ getFlag(key: string, context?: FlagContext, defaultValue?: boolean): Promise<boolean>;
42
+ /** From memory only (no request): for hot paths once `ready()` resolved. */
43
+ getFlagSync(key: string, context?: FlagContext, defaultValue?: boolean): boolean;
44
+ /** Value and reason, or null for an unknown flag. */
45
+ evaluate(key: string, context?: FlagContext): {
46
+ value: boolean;
47
+ reason: FlagReason;
48
+ } | null;
49
+ /** Every flag for this user. */
50
+ getAll(context?: FlagContext): Promise<Record<string, boolean>>;
51
+ /** An Edge Config value. */
52
+ getConfig<T extends ConfigValue = ConfigValue>(key: string, defaultValue?: T): Promise<T | undefined>;
53
+ /** Called with the new version after every change that reached this process. */
54
+ onChange(listener: (version: number) => void): () => void;
55
+ close(): void;
56
+ private openStream;
57
+ }
58
+ /** One client per process: create it at module level and reuse it. */
59
+ declare function createFlagsClient(options?: FlagsClientOptions): FlagsClient;
60
+
61
+ export { ConfigValue, FetchLike, FlagContext, FlagEnvironment, FlagReason, FlagsClient, type FlagsClientOptions, createFlagsClient as createClient, createFlagsClient };
@@ -0,0 +1,61 @@
1
+ import { a as FlagEnvironment, b as FetchLike, F as FlagContext, c as FlagReason, C as ConfigValue } from './types-DMIusvFt.js';
2
+ export { E as EvaluatedFlags, d as FlagDefinition, e as FlagsError, f as FlagsSnapshot, I as InfarisError, g as detectEnvironment, h as evaluateFlag, i as fnv1a32, r as rolloutBucket, j as ruleMatches } from './types-DMIusvFt.js';
3
+
4
+ interface FlagsClientOptions {
5
+ /** `INFARIS_FLAGS_URL` by default (`https://…/flags/v1/<ref>`). */
6
+ url?: string;
7
+ /** `INFARIS_FLAGS_SECRET_KEY` by default. */
8
+ secretKey?: string;
9
+ /** `INFARIS_ENV`, `VERCEL_ENV` or `NODE_ENV` by default. */
10
+ environment?: FlagEnvironment;
11
+ /** Keep a stream open for push invalidation (default true). Off in short-lived functions if you prefer polling. */
12
+ stream?: boolean;
13
+ /** Longest a snapshot is used without asking for a newer version (default 30 s). */
14
+ refreshIntervalMs?: number;
15
+ fetch?: FetchLike;
16
+ onError?: (error: unknown) => void;
17
+ }
18
+ declare class FlagsClient {
19
+ readonly environment: FlagEnvironment;
20
+ private readonly url;
21
+ private readonly key;
22
+ private readonly fetchImpl;
23
+ private readonly refreshMs;
24
+ private readonly onError;
25
+ private snapshot;
26
+ private etag;
27
+ private checkedAt;
28
+ private loading;
29
+ private streamAbort;
30
+ private streaming;
31
+ private closed;
32
+ private listeners;
33
+ constructor(options?: FlagsClientOptions);
34
+ /** The version in memory (0 before the first load). */
35
+ get version(): number;
36
+ /** Loads the first snapshot (and opens the stream). Every getter calls it, so calling it yourself is optional. */
37
+ ready(): Promise<void>;
38
+ /** Fetches the snapshot when it changed (`If-None-Match`). */
39
+ refresh(): Promise<void>;
40
+ /** The value of a flag for this user; `defaultValue` for an unknown flag or before Hosting answered once. */
41
+ getFlag(key: string, context?: FlagContext, defaultValue?: boolean): Promise<boolean>;
42
+ /** From memory only (no request): for hot paths once `ready()` resolved. */
43
+ getFlagSync(key: string, context?: FlagContext, defaultValue?: boolean): boolean;
44
+ /** Value and reason, or null for an unknown flag. */
45
+ evaluate(key: string, context?: FlagContext): {
46
+ value: boolean;
47
+ reason: FlagReason;
48
+ } | null;
49
+ /** Every flag for this user. */
50
+ getAll(context?: FlagContext): Promise<Record<string, boolean>>;
51
+ /** An Edge Config value. */
52
+ getConfig<T extends ConfigValue = ConfigValue>(key: string, defaultValue?: T): Promise<T | undefined>;
53
+ /** Called with the new version after every change that reached this process. */
54
+ onChange(listener: (version: number) => void): () => void;
55
+ close(): void;
56
+ private openStream;
57
+ }
58
+ /** One client per process: create it at module level and reuse it. */
59
+ declare function createFlagsClient(options?: FlagsClientOptions): FlagsClient;
60
+
61
+ export { ConfigValue, FetchLike, FlagContext, FlagEnvironment, FlagReason, FlagsClient, type FlagsClientOptions, createFlagsClient as createClient, createFlagsClient };
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import {
2
+ FlagsClient,
3
+ createFlagsClient,
4
+ evaluateFlag,
5
+ fnv1a32,
6
+ rolloutBucket,
7
+ ruleMatches
8
+ } from "./chunk-RZ6DL2QZ.js";
9
+ import {
10
+ FlagsError,
11
+ detectEnvironment,
12
+ require_dist
13
+ } from "./chunk-LLGINDZS.js";
14
+ import {
15
+ __toESM
16
+ } from "./chunk-TRTQSARU.js";
17
+
18
+ // src/index.ts
19
+ var import_hosting_sdk = __toESM(require_dist(), 1);
20
+ var export_InfarisError = import_hosting_sdk.InfarisError;
21
+ export {
22
+ FlagsClient,
23
+ FlagsError,
24
+ export_InfarisError as InfarisError,
25
+ createFlagsClient as createClient,
26
+ createFlagsClient,
27
+ detectEnvironment,
28
+ evaluateFlag,
29
+ fnv1a32,
30
+ rolloutBucket,
31
+ ruleMatches
32
+ };
package/dist/react.cjs ADDED
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ FlagsProvider: () => FlagsProvider,
24
+ useConfig: () => useConfig,
25
+ useFlag: () => useFlag,
26
+ useFlags: () => useFlags
27
+ });
28
+ module.exports = __toCommonJS(react_exports);
29
+ var import_react = require("react");
30
+ var FlagsContext = (0, import_react.createContext)(null);
31
+ function FlagsProvider({ client, children }) {
32
+ return (0, import_react.createElement)(FlagsContext.Provider, { value: client }, children);
33
+ }
34
+ function useFlagsState() {
35
+ const client = (0, import_react.useContext)(FlagsContext);
36
+ if (!client) throw new Error("useFlag needs a <FlagsProvider client={\u2026}> above it");
37
+ return (0, import_react.useSyncExternalStore)(
38
+ (onChange) => client.subscribe(onChange),
39
+ () => client.snapshot(),
40
+ () => client.snapshot()
41
+ );
42
+ }
43
+ function useFlag(key, defaultValue = false) {
44
+ const state = useFlagsState();
45
+ return Object.prototype.hasOwnProperty.call(state.flags, key) ? state.flags[key] : defaultValue;
46
+ }
47
+ function useFlags() {
48
+ return useFlagsState();
49
+ }
50
+ function useConfig(key, defaultValue) {
51
+ const state = useFlagsState();
52
+ return Object.prototype.hasOwnProperty.call(state.items, key) ? state.items[key] : defaultValue;
53
+ }
54
+ // Annotate the CommonJS export names for ESM import in node:
55
+ 0 && (module.exports = {
56
+ FlagsProvider,
57
+ useConfig,
58
+ useFlag,
59
+ useFlags
60
+ });