@voltro/plugin-flags 0.83.1 → 0.85.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/index.js CHANGED
@@ -1,25 +1,25 @@
1
1
  import { BASE_NAME as e, evaluateDescriptor as t, evaluateVariantsDescriptor as n, flagsRpcClientImports as r } from "./rpc.js";
2
- import { i, n as a, r as o, t as s } from "./define-D9Eet7wA.js";
2
+ import { i, n as a, r as o, t as s } from "./define-BHpLJh1F.js";
3
3
  import { FlagDisabled as c } from "./errors.js";
4
- import { Effect as l } from "effect";
5
- import { definePlugin as u, pluginInstanceName as d } from "@voltro/protocol";
6
- import { boolean as f, derivedRowId as p, eq as m, id as h, integer as g, json as _, queryFor as v, registerRetention as y, retentionTtlMsFromEnv as b, table as x, text as S, timestamp as C } from "@voltro/database";
4
+ import { Effect as l, Result as u } from "effect";
5
+ import { definePlugin as d, pluginInstanceName as f } from "@voltro/protocol";
6
+ import { boolean as p, derivedRowId as m, eq as h, id as g, integer as _, json as v, queryFor as y, registerRetention as b, retentionTtlMsFromEnv as x, table as S, text as C, timestamp as w } from "@voltro/database";
7
7
  //#region src/evaluate.ts
8
- var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
8
+ var T = (e) => typeof e == "boolean" ? { enabled: e } : e, E = (e, t) => {
9
9
  let n = 2166136261, r = `${e}:${t}`;
10
10
  for (let e = 0; e < r.length; e++) n ^= r.charCodeAt(e), n = Math.imul(n, 16777619);
11
11
  return n >>> 0;
12
- }, E = (e, t) => T(e, t) % 100, ee = (e, t) => T(`${e}:variant`, t) % 1e4, D = (e, t) => {
12
+ }, D = (e, t) => E(e, t) % 100, O = (e, t) => E(`${e}:variant`, t) % 1e4, k = (e, t) => {
13
13
  if (e.subjectIds && !(t.id != null && e.subjectIds.includes(t.id)) || e.tenantIds && !(t.tenantId != null && e.tenantIds.includes(t.tenantId)) || e.subjectTypes && !(t.type != null && e.subjectTypes.includes(t.type))) return !1;
14
14
  if (e.metadata) {
15
15
  let n = t.metadata ?? {};
16
16
  for (let [t, r] of Object.entries(e.metadata)) if (String(n[t]) !== r) return !1;
17
17
  }
18
18
  return !0;
19
- }, O = (e) => typeof e == "number" ? e : Date.parse(e), k = (e, t) => (e.rolloutBy === "tenant" ? t.tenantId : t.id) ?? "anon", A = (e, t) => {
20
- if (e.activateAt != null && t < O(e.activateAt) || e.deactivateAt != null && t >= O(e.deactivateAt)) return { active: !1 };
19
+ }, A = (e) => typeof e == "number" ? e : Date.parse(e), j = (e, t) => (e.rolloutBy === "tenant" ? t.tenantId : t.id) ?? "anon", M = (e, t) => {
20
+ if (e.activateAt != null && t < A(e.activateAt) || e.deactivateAt != null && t >= A(e.deactivateAt)) return { active: !1 };
21
21
  if (e.ramp) {
22
- let { from: n, to: r, startAt: i, endAt: a } = e.ramp, o = O(i), s = O(a);
22
+ let { from: n, to: r, startAt: i, endAt: a } = e.ramp, o = A(i), s = A(a);
23
23
  if (t <= o) return {
24
24
  active: !0,
25
25
  rollout: n
@@ -35,71 +35,73 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
35
35
  };
36
36
  }
37
37
  return { active: !0 };
38
- }, j = (e, t, n, r = Date.now()) => {
39
- let i = w(t);
40
- if (i.enabled === !1) return !1;
41
- let a;
42
- if (i.schedule) {
43
- let e = A(i.schedule, r);
38
+ }, N = (e, t, n, r = Date.now()) => {
39
+ let i = T(t);
40
+ return P(e, i, n, r) ? !i.variants?.length || i.offVariant == null || I(e, i, n).name !== i.offVariant : !1;
41
+ }, P = (e, t, n, r) => {
42
+ if (t.enabled === !1) return !1;
43
+ let i;
44
+ if (t.schedule) {
45
+ let e = M(t.schedule, r);
44
46
  if (!e.active) return !1;
45
- a = e.rollout;
47
+ i = e.rollout;
46
48
  }
47
- if (i.targeting && i.targeting.length > 0 && !i.targeting.some((e) => D(e, n))) return !1;
48
- let o = a ?? i.rollout ?? 100;
49
- if (o < 100 && o > 0) {
50
- if (!(E(e, k(i, n)) < o)) return !1;
51
- } else if (o <= 0) return !1;
52
- return i.variants && i.variants.length > 0 && i.offVariant != null ? N(e, i, n).name !== i.offVariant : !0;
53
- }, M = (e) => {
49
+ if (t.targeting && t.targeting.length > 0 && !t.targeting.some((e) => k(e, n))) return !1;
50
+ let a = i ?? t.rollout ?? 100;
51
+ if (a < 100 && a > 0) {
52
+ if (!(D(e, j(t, n)) < a)) return !1;
53
+ } else if (a <= 0) return !1;
54
+ return !0;
55
+ }, F = (e) => {
54
56
  let t = e.reduce((e, t) => e + (t.weight ?? 1), 0) || e.length, n = 0;
55
57
  return e.map((r, i) => (n += r.weight ?? 1, i === e.length - 1 ? 1e4 : Math.round(n / t * 1e4)));
56
- }, N = (e, t, n) => {
58
+ }, I = (e, t, n) => {
57
59
  let r = t.variants ?? [];
58
60
  if (r.length === 0) throw Error(`flag "${e}" has no variants`);
59
- let i = ee(e, k(t, n)), a = M(r);
61
+ let i = O(e, j(t, n)), a = F(r);
60
62
  for (let e = 0; e < r.length; e++) if (i < a[e]) return r[e];
61
63
  return r[r.length - 1];
62
- }, P = (e, t, n, r = Date.now()) => {
63
- let i = w(t), a = j(e, t, n, r);
64
+ }, L = (e, t, n, r = Date.now()) => {
65
+ let i = T(t), a = P(e, i, n, r);
64
66
  if (!i.variants || i.variants.length === 0) return {
65
67
  name: a ? "on" : "off",
66
68
  value: a,
67
69
  enabled: a
68
70
  };
69
- let o = N(e, i, n);
71
+ let o = I(e, i, n);
70
72
  return {
71
73
  name: o.name,
72
74
  value: o.value,
73
- enabled: a
75
+ enabled: a && o.name !== i.offVariant
74
76
  };
75
- }, te = (e, t, n = Date.now()) => {
77
+ }, ee = (e, t, n = Date.now()) => {
76
78
  let r = {};
77
- for (let [i, a] of Object.entries(e)) r[i] = j(i, a, t, n);
79
+ for (let [i, a] of Object.entries(e)) r[i] = N(i, a, t, n);
78
80
  return r;
79
- }, ne = (e, t, n = Date.now()) => {
81
+ }, te = (e, t, n = Date.now()) => {
80
82
  let r = {};
81
- for (let [i, a] of Object.entries(e)) r[i] = P(i, a, t, n);
83
+ for (let [i, a] of Object.entries(e)) r[i] = L(i, a, t, n);
82
84
  return r;
83
- }, F = "_voltro_feature_flag_audit", I = "_voltro_feature_flags", L = x(I, {
84
- id: h({ prefix: "flag" }),
85
- key: S().unique(),
86
- enabled: f().default(!0),
87
- rollout: g().nullable(),
88
- rolloutBy: S().nullable(),
89
- targeting: _().nullable(),
90
- variants: _().nullable(),
91
- offVariant: S().nullable(),
92
- schedule: _().nullable(),
93
- description: S().nullable(),
94
- updatedAt: C().default("now").onUpdate("now")
95
- }), R = x(F, {
96
- id: h({ prefix: "flagaud" }),
97
- flag: S(),
98
- enabled: f(),
99
- previousEnabled: f().nullable(),
100
- actorId: S().nullable(),
101
- at: C().default("now")
102
- }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), re = (e, t) => {
85
+ }, R = "_voltro_feature_flag_audit", z = "_voltro_feature_flags", B = S(z, {
86
+ id: g({ prefix: "flag" }),
87
+ key: C().unique(),
88
+ enabled: p().default(!0),
89
+ rollout: _().nullable(),
90
+ rolloutBy: C().nullable(),
91
+ targeting: v().nullable(),
92
+ variants: v().nullable(),
93
+ offVariant: C().nullable(),
94
+ schedule: v().nullable(),
95
+ description: C().nullable(),
96
+ updatedAt: w().default("now").onUpdate("now")
97
+ }), V = S(R, {
98
+ id: g({ prefix: "flagaud" }),
99
+ flag: C(),
100
+ enabled: p(),
101
+ previousEnabled: p().nullable(),
102
+ actorId: C().nullable(),
103
+ at: w().default("now")
104
+ }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), ne = (e, t) => {
103
105
  let n = { ...e };
104
106
  for (let e of t) {
105
107
  let t = {
@@ -115,7 +117,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
115
117
  n[e.key] = t;
116
118
  }
117
119
  return n;
118
- }, z = new class {
120
+ }, H = new class {
119
121
  flags = {};
120
122
  set(e) {
121
123
  this.flags = e;
@@ -126,19 +128,19 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
126
128
  get(e) {
127
129
  return this.flags[e];
128
130
  }
129
- }(), B = (e, t) => V(e, t) !== null, V = (e, t) => {
130
- let n = z.get(e);
131
+ }(), re = (e, t) => U(e, t) !== null, U = (e, t) => {
132
+ let n = H.get(e);
131
133
  if (n === void 0) return null;
132
- let r = w(n), i = r.enabled !== !1;
133
- return z.set({
134
- ...z.all(),
134
+ let r = T(n), i = r.enabled !== !1;
135
+ return H.set({
136
+ ...H.all(),
135
137
  [e]: {
136
138
  ...r,
137
139
  enabled: t
138
140
  }
139
141
  }), i;
140
- }, H = (e) => Object.entries(e).map(([e, t]) => {
141
- let n = w(t);
142
+ }, W = (e) => Object.entries(e).map(([e, t]) => {
143
+ let n = T(t);
142
144
  return {
143
145
  key: e,
144
146
  enabled: n.enabled !== !1,
@@ -148,7 +150,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
148
150
  description: n.description
149
151
  };
150
152
  }), ie = (e, t) => {
151
- let n = w(t);
153
+ let n = T(t);
152
154
  return {
153
155
  key: e,
154
156
  enabled: n.enabled !== !1,
@@ -161,7 +163,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
161
163
  description: n.description ?? null
162
164
  };
163
165
  }, ae = (e) => ({
164
- loadOverrides: async () => (await e.query(v(L).descriptor)).map((e) => ({
166
+ loadOverrides: async () => (await e.query(y(B).descriptor)).map((e) => ({
165
167
  key: String(e.key),
166
168
  enabled: e.enabled === !0 || e.enabled === 1,
167
169
  rollout: e.rollout == null ? null : Number(e.rollout),
@@ -173,11 +175,11 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
173
175
  description: e.description ?? null
174
176
  })),
175
177
  upsertOverride: async (t) => {
176
- let n = U(t);
177
- await e.upsert(I, n, { conflictColumns: ["key"] });
178
+ let n = G(t);
179
+ await e.upsert(z, n, { conflictColumns: ["key"] });
178
180
  }
179
- }), U = (e) => ({
180
- id: p("flag", e.key),
181
+ }), G = (e) => ({
182
+ id: m("flag", e.key),
181
183
  key: e.key,
182
184
  enabled: e.enabled,
183
185
  rollout: e.rollout,
@@ -196,7 +198,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
196
198
  at: e.at instanceof Date ? e.at : new Date(String(e.at))
197
199
  }), se = (e) => ({
198
200
  record: async (t) => {
199
- await e.insert(F, {
201
+ await e.insert(R, {
200
202
  id: `flagaud_${t.at.getTime()}_${Math.random().toString(36).slice(2, 10)}`,
201
203
  flag: t.flag,
202
204
  enabled: t.enabled,
@@ -206,18 +208,18 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
206
208
  });
207
209
  },
208
210
  list: async (t) => {
209
- let n = v(R).orderBy("at", "desc");
210
- return t?.flag != null && (n = n.where(m("flag", t.flag))), t?.limit != null && (n = n.limit(t.limit)), (await e.query(n.descriptor)).map(oe);
211
+ let n = y(V).orderBy("at", "desc");
212
+ return t?.flag != null && (n = n.where(h("flag", t.flag))), t?.limit != null && (n = n.limit(t.limit)), (await e.query(n.descriptor)).map(oe);
211
213
  }
212
214
  }), ce = async (e, t, n, r, i) => {
213
215
  await e.transactional(async (e) => {
214
- await e.insertIgnore(I, {
215
- ...U(ie(t, n)),
216
- id: p("flag", crypto.randomUUID())
216
+ await e.insertIgnore(z, {
217
+ ...G(ie(t, n)),
218
+ id: m("flag", crypto.randomUUID())
217
219
  }, { conflictColumns: ["key"] });
218
- let a = (await e.query(v(L).where(m("key", t)).forUpdate().descriptor))[0];
220
+ let a = (await e.query(y(B).where(h("key", t)).forUpdate().descriptor))[0];
219
221
  if (!a) throw Error("flag override disappeared during toggle");
220
- await e.update(I, String(a.id), {
222
+ await e.update(z, String(a.id), {
221
223
  enabled: r,
222
224
  updatedAt: /* @__PURE__ */ new Date()
223
225
  }), await se(e).record({
@@ -228,7 +230,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
228
230
  at: /* @__PURE__ */ new Date()
229
231
  });
230
232
  });
231
- }, W = (e, t) => {
233
+ }, le = (e, t) => {
232
234
  let n = new Map(t.map((e) => [e.name, e])), r = [];
233
235
  for (let t of e) {
234
236
  let e = t.experiment;
@@ -267,24 +269,24 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
267
269
  }
268
270
  }
269
271
  return r;
270
- }, le = (e, t) => {
271
- let n = W(e, t);
272
+ }, ue = (e, t) => {
273
+ let n = le(e, t);
272
274
  if (n.length === 0) return;
273
275
  let r = n.map((e) => ` · flag "${e.flag}" → experiment "${e.experiment}": ${e.problem}`);
274
276
  throw Error(`@voltro/plugin-flags: ${n.length} flag→experiment link problem(s):\n${r.join("\n")}`);
275
- }, ue = 864e5, G = (e) => typeof e == "number" ? e : Date.parse(e), K = (e, t) => {
276
- let n = w(e);
277
+ }, de = 864e5, K = (e) => typeof e == "number" ? e : Date.parse(e), fe = (e, t) => {
278
+ let n = T(e);
277
279
  if (n.enabled === !1) return {
278
280
  shape: "constantOff",
279
281
  reason: "enabled: false — resolves false for every caller"
280
282
  };
281
283
  if (n.schedule) {
282
284
  let { activateAt: e, deactivateAt: r } = n.schedule;
283
- if (r != null && t >= G(r)) return {
285
+ if (r != null && t >= K(r)) return {
284
286
  shape: "expired",
285
287
  reason: `schedule.deactivateAt (${String(r)}) has passed — the flag can never be on again`
286
288
  };
287
- if (e != null && t < G(e)) return {
289
+ if (e != null && t < K(e)) return {
288
290
  shape: "notYetActive",
289
291
  reason: `schedule.activateAt (${String(e)}) is in the future — pending, not dead`
290
292
  };
@@ -294,7 +296,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
294
296
  shape: "constantOff",
295
297
  reason: `rollout: ${r} — nobody is in the rollout`
296
298
  };
297
- let i = (n.targeting?.length ?? 0) > 0, a = (n.variants?.length ?? 0) > 1, o = n.schedule !== void 0 && (n.schedule.ramp !== void 0 || n.schedule.deactivateAt != null && t < G(n.schedule.deactivateAt));
299
+ let i = (n.targeting?.length ?? 0) > 0, a = (n.variants?.length ?? 0) > 1, o = n.schedule !== void 0 && (n.schedule.ramp !== void 0 || n.schedule.deactivateAt != null && t < K(n.schedule.deactivateAt));
298
300
  return r >= 100 && !i && !a && !o ? {
299
301
  shape: "constantOn",
300
302
  reason: "rollout 100 with no targeting, no variants and no live schedule — resolves true for every caller"
@@ -307,14 +309,14 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
307
309
  o ? "a live schedule" : void 0
308
310
  ].filter((e) => e !== void 0).join(", ")})`
309
311
  };
310
- }, de = [
312
+ }, pe = [
311
313
  "Reachability is not decided. \"Not evaluated since <date>\" is a measurement; \"this code path is dead\" is not decidable in general. A seasonal flag, a flag behind a route nobody visited this month, and a flag whose last call site was deleted are indistinguishable from here.",
312
314
  "Only SERVER-side reads are counted as use: isFlagEnabled / requireFlag / flagValue and a gatedBy interception. A flag consulted only in the browser via useFlag() / useFlagValue() reads from the bulk set the server already sent, so it never reaches this report as a named read.",
313
315
  "A \"bulk\" observation means the flag was INCLUDED in a flags.evaluate / flags.variants response. It does not mean any client read that key — one useFlags() poll evaluates the whole registry.",
314
316
  "The window is bounded by retention (see coverage.retentionDays). Nothing older is evidence: a flag last read BEFORE the window has no observation at all and reads \"neverObserved\", which is exactly what a flag declared this morning reads.",
315
317
  "\"neverObserved\" is not a removal candidate. A flag declared yesterday and a flag abandoned last year produce the identical row; only a flag that WAS observed and then stopped (\"stale\") is proven unused."
316
- ], fe = (e) => {
317
- let t = e.now ?? Date.now(), n = e.staleAfterDays, r = t - n * ue, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o;
318
+ ], me = (e) => {
319
+ let t = e.now ?? Date.now(), n = e.staleAfterDays, r = t - n * de, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o;
318
320
  for (let t of e.usage) {
319
321
  let e = t.lastAt.getTime();
320
322
  if (!Number.isFinite(e)) continue;
@@ -322,7 +324,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
322
324
  let n = t.source === "bulk" ? a : i, r = n.get(t.flag);
323
325
  (r === void 0 || e > r) && n.set(t.flag, e);
324
326
  }
325
- let s = o === void 0 ? 0 : Math.floor((t - o) / ue);
327
+ let s = o === void 0 ? 0 : Math.floor((t - o) / de);
326
328
  return {
327
329
  coverage: {
328
330
  tracking: e.tracking,
@@ -332,7 +334,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
332
334
  staleAfterDays: n
333
335
  },
334
336
  findings: Object.entries(e.flags).map(([o, c]) => {
335
- let { shape: l, reason: u } = K(c, t), d = i.get(o), f = a.get(o), p = [u], m;
337
+ let { shape: l, reason: u } = fe(c, t), d = i.get(o), f = a.get(o), p = [u], m;
336
338
  e.tracking ? d !== void 0 && d >= r ? (m = "evaluated", p.push(`last server-side read ${new Date(d).toISOString()}`)) : d === void 0 ? (m = "neverObserved", p.push(s > 0 ? `no server-side read in the ${s}-day observation window` : "no server-side read observed yet (the observation window is empty)"), f !== void 0 && p.push("the flag WAS delivered to a client in a bulk flags.evaluate response — which does not prove it was read")) : (m = "stale", p.push(`last server-side read ${new Date(d).toISOString()}, more than ${n} day(s) ago, over an observation window of ${s} day(s)`)) : (m = "untracked", p.push("evaluation tracking is off — no usage evidence exists for this deployment"));
337
339
  let h = l === "constantOn" || l === "constantOff" || l === "expired";
338
340
  return h && p.push("the definition alone makes this flag a constant — it selects nothing"), {
@@ -347,23 +349,23 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
347
349
  why: p
348
350
  };
349
351
  }),
350
- limits: de
352
+ limits: pe
351
353
  };
352
- }, q = "_voltro_feature_flag_usage", J = x(q, {
353
- id: h({ prefix: "flagusg" }),
354
- flag: S(),
355
- day: S(),
356
- source: S(),
357
- lastAt: C()
354
+ }, q = "_voltro_feature_flag_usage", J = S(q, {
355
+ id: g({ prefix: "flagusg" }),
356
+ flag: C(),
357
+ day: C(),
358
+ source: C(),
359
+ lastAt: w()
358
360
  }).unique([
359
361
  "flag",
360
362
  "day",
361
363
  "source"
362
- ]).index("byFlagUsageLastAt", ["lastAt"]).index("byFlagUsageFlag", ["flag"]), pe = (e) => e.toISOString().slice(0, 10), me = (e) => {
364
+ ]).index("byFlagUsageLastAt", ["lastAt"]).index("byFlagUsageFlag", ["flag"]), he = (e) => e.toISOString().slice(0, 10), ge = (e) => {
363
365
  let t = 2166136261;
364
366
  for (let n = 0; n < e.length; n++) t ^= e.charCodeAt(n), t = Math.imul(t, 16777619);
365
367
  return (t >>> 0).toString(16).padStart(8, "0");
366
- }, he = (e, t, n) => `flagusg_${t.replace(/-/g, "")}_${n === "targeted" ? "t" : "b"}_${me(e)}`, Y = new class {
368
+ }, _e = (e, t, n) => `flagusg_${t.replace(/-/g, "")}_${n === "targeted" ? "t" : "b"}_${ge(e)}`, Y = new class {
367
369
  on = !1;
368
370
  pending = /* @__PURE__ */ new Map();
369
371
  liveCounts = /* @__PURE__ */ new Map();
@@ -401,12 +403,12 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
401
403
  reset() {
402
404
  this.on = !1, this.pending.clear(), this.liveCounts.clear();
403
405
  }
404
- }(), X = (e, t, n) => Y.record(e, t, n), ge = (e) => e === "bulk" ? "bulk" : "targeted", _e = (e) => ({
406
+ }(), X = (e, t, n) => Y.record(e, t, n), ve = (e) => e === "bulk" ? "bulk" : "targeted", ye = (e) => ({
405
407
  upsertMany: async (t) => {
406
408
  for (let n of t) {
407
- let t = pe(n.lastAt);
409
+ let t = he(n.lastAt);
408
410
  await e.upsert(q, {
409
- id: he(n.flag, t, n.source),
411
+ id: _e(n.flag, t, n.source),
410
412
  flag: n.flag,
411
413
  day: t,
412
414
  source: n.source,
@@ -418,13 +420,13 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
418
420
  ] });
419
421
  }
420
422
  },
421
- load: async () => (await e.query(v(J).descriptor)).map((e) => ({
423
+ load: async () => (await e.query(y(J).descriptor)).map((e) => ({
422
424
  flag: String(e.flag),
423
425
  day: String(e.day),
424
- source: ge(e.source),
426
+ source: ve(e.source),
425
427
  lastAt: e.lastAt instanceof Date ? e.lastAt : new Date(String(e.lastAt))
426
428
  }))
427
- }), ve = 3e5, ye = (e) => {
429
+ }), be = 3e5, xe = (e) => {
428
430
  let t = e.intervalMs ?? 3e5, n = !1, r = async () => {
429
431
  let t = Y.drain();
430
432
  if (t.length !== 0) try {
@@ -448,9 +450,9 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
448
450
  n || (n = !0, clearInterval(i), await r());
449
451
  }
450
452
  };
451
- }, be = (e = process.env, t = 90) => {
452
- let n = b(e.VOLTRO_FLAG_USAGE_TTL_HOURS, 24 * t);
453
- return y({
453
+ }, Se = (e = process.env, t = 90) => {
454
+ let n = x(e.VOLTRO_FLAG_USAGE_TTL_HOURS, 24 * t);
455
+ return b({
454
456
  table: q,
455
457
  timeColumn: "lastAt",
456
458
  source: "plugin",
@@ -458,127 +460,127 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
458
460
  envVar: "VOLTRO_FLAG_USAGE_TTL_HOURS"
459
461
  }), n;
460
462
  }, Z = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, Q = (e, t) => {
461
- let n = z.get(t);
462
- return X(t, "targeted"), n !== void 0 && j(t, n, Z(e));
463
- }, xe = (e, t) => Q(e, t) ? l.void : l.fail(new c({ flag: t })), Se = (e, t) => {
464
- let n = z.get(t.key);
465
- return X(t.key, "targeted"), n === void 0 ? t.defaultValue : i(t, P(t.key, n, Z(e)));
466
- }, Ce = (e, t) => {
467
- let n = z.get(t.key);
468
- return X(t.key, "targeted"), n === void 0 ? null : P(t.key, n, Z(e)).name;
469
- }, we = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), $ = e, Te = (e = {}) => {
470
- let i = e.gatedBy ?? {}, o = d({
463
+ let n = H.get(t);
464
+ return X(t, "targeted"), n !== void 0 && N(t, n, Z(e));
465
+ }, Ce = (e, t) => Q(e, t) ? l.void : l.fail(new c({ flag: t })), we = (e, t) => {
466
+ let n = H.get(t.key);
467
+ return X(t.key, "targeted"), n === void 0 ? t.defaultValue : i(t, L(t.key, n, Z(e)));
468
+ }, Te = (e, t) => {
469
+ let n = H.get(t.key);
470
+ return X(t.key, "targeted"), n === void 0 ? null : L(t.key, n, Z(e)).name;
471
+ }, Ee = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), $ = e, De = (e = {}) => {
472
+ let i = e.gatedBy ?? {}, o = f({
471
473
  base: $,
472
474
  alias: e.alias,
473
475
  instance: e.name
474
- }), s = e.typedFlags ?? [], f = /* @__PURE__ */ new Map();
476
+ }), s = e.typedFlags ?? [], p = /* @__PURE__ */ new Map();
475
477
  for (let t of s) {
476
- if (f.has(t.key)) throw Error(`${$}: two typedFlags declare the key ${JSON.stringify(t.key)}`);
478
+ if (p.has(t.key)) throw Error(`${$}: two typedFlags declare the key ${JSON.stringify(t.key)}`);
477
479
  if (e.flags?.[t.key] !== void 0) throw Error(`${$}: ${JSON.stringify(t.key)} is declared BOTH in \`flags\` and in \`typedFlags\`. One key, one declaration — the typed one would otherwise be silently overwritten by the untyped baseline.`);
478
- f.set(t.key, t);
480
+ p.set(t.key, t);
479
481
  }
480
- s.some((e) => e.experiment !== void 0) && le(s, e.experiments ?? []);
481
- let p = { ...e.flags ?? {} };
482
- for (let e of s) p[e.key] = e.definition;
483
- let m = e.store === "postgres", h, g, _, v, y, b, x, S = [], C = e.usage ?? {}, w = C.track ?? m;
484
- if (w && !m) throw Error(`${$}: usage.track requires store: 'postgres' — there is nowhere to record an evaluation on the memory tier, and a dead-flag report that silently observed nothing is exactly the defect it exists to find.`);
485
- let T = C.staleAfterDays ?? 30, E = C.retentionDays ?? 90;
486
- if (T > E) throw Error(`${$}: usage.staleAfterDays (${T}) exceeds usage.retentionDays (${E}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
487
- let ee = (e) => {
488
- let t = re(p, e), n = [];
489
- for (let [e, r] of f) {
482
+ s.some((e) => e.experiment !== void 0) && ue(s, e.experiments ?? []);
483
+ let m = { ...e.flags ?? {} };
484
+ for (let e of s) m[e.key] = e.definition;
485
+ let h = e.store === "postgres", g, _, v, y, b, x, S, C = [], w = e.usage ?? {}, T = w.track ?? h;
486
+ if (T && !h) throw Error(`${$}: usage.track requires store: 'postgres' — there is nowhere to record an evaluation on the memory tier, and a dead-flag report that silently observed nothing is exactly the defect it exists to find.`);
487
+ let E = w.staleAfterDays ?? 30, D = w.retentionDays ?? 90;
488
+ if (E > D) throw Error(`${$}: usage.staleAfterDays (${E}) exceeds usage.retentionDays (${D}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
489
+ let O = (e) => {
490
+ let t = ne(m, e), n = [];
491
+ for (let [e, r] of p) {
490
492
  let i = t[e];
491
493
  if (i === void 0) continue;
492
494
  let o = a(r, typeof i == "boolean" ? { enabled: i } : i);
493
495
  o.ok || (n.push(o.refusal), t[e] = r.definition);
494
496
  }
495
- S = n, z.set(t);
496
- }, D = 0, O = async () => {
497
- if (!g) return;
498
- let e = ++D, t = await g.loadOverrides();
499
- e === D && ee(t);
500
- }, k = async () => {
501
- if (b && g) {
502
- if (await O(), w && v && !y) {
503
- x = be(process.env, E), Y.enable();
504
- let e = b.logger;
505
- y = ye({
506
- store: v,
507
- intervalMs: C.flushIntervalMs ?? 3e5,
497
+ C = n, H.set(t);
498
+ }, k = 0, A = async () => {
499
+ if (!_) return;
500
+ let e = ++k, t = await _.loadOverrides();
501
+ e === k && O(t);
502
+ }, j = async () => {
503
+ if (x && _) {
504
+ if (await A(), T && y && !b) {
505
+ S = Se(process.env, D), Y.enable();
506
+ let e = x.logger;
507
+ b = xe({
508
+ store: y,
509
+ intervalMs: w.flushIntervalMs ?? 3e5,
508
510
  log: { warn: (t, n) => e.warn(t, n) }
509
511
  });
510
512
  }
511
- for (let e of S) b.logger.warn("feature flag override REFUSED — code-declared definition stands", {
513
+ for (let e of C) x.logger.warn("feature flag override REFUSED — code-declared definition stands", {
512
514
  flag: e.key,
513
515
  reason: e.reason
514
516
  });
515
517
  }
516
- }, A = (e) => {
518
+ }, M = (e) => {
517
519
  if (i[e] !== void 0) return i[e];
518
- for (let [t, n] of Object.entries(i)) if (t !== e && we(t, e)) return n;
519
- }, j = (e, t) => {
520
- let n = A(t.tag);
520
+ for (let [t, n] of Object.entries(i)) if (t !== e && Ee(t, e)) return n;
521
+ }, N = (e, t) => {
522
+ let n = M(t.tag);
521
523
  return n === void 0 || Q({ subject: t.subject }, n) ? e : l.fail(new c({ flag: n }));
522
- }, M = Object.keys(i).length > 0, N = (e) => {
524
+ }, P = Object.keys(i).length > 0, F = (e) => {
523
525
  for (let t of e) X(t, "bulk");
524
- }, P = {
526
+ }, I = {
525
527
  ...t,
526
528
  description: "Resolve every feature flag for the calling subject.",
527
529
  execute: (e, t) => l.sync(() => {
528
- let e = z.all();
529
- return N(Object.keys(e)), te(e, t.request.subject ?? {});
530
+ let e = H.all();
531
+ return F(Object.keys(e)), ee(e, t.request.subject ?? {});
530
532
  })
531
- }, F = {
533
+ }, L = {
532
534
  ...n,
533
535
  description: "Resolve every feature flag to its served variant for the calling subject.",
534
536
  execute: (e, t) => l.sync(() => {
535
- let e = z.all();
536
- return N(Object.keys(e)), ne(e, t.request.subject ?? {});
537
+ let e = H.all();
538
+ return F(Object.keys(e)), te(e, t.request.subject ?? {});
537
539
  })
538
- }, I = (e) => ({
540
+ }, R = (e) => ({
539
541
  kind: "json",
540
542
  data: e
541
- }), B = (e, t) => ({
543
+ }), z = (e, t) => ({
542
544
  kind: "json",
543
545
  status: e,
544
546
  data: { error: t }
545
- }), ie = async () => {
547
+ }), re = async () => {
546
548
  let e = [];
547
- if (w && v) try {
548
- e = await v.load();
549
+ if (T && y) try {
550
+ e = await y.load();
549
551
  } catch {
550
552
  e = [];
551
553
  }
552
- return fe({
553
- flags: z.all(),
554
+ return me({
555
+ flags: H.all(),
554
556
  usage: e,
555
- tracking: w,
557
+ tracking: T,
556
558
  liveCounts: Y.liveTargetedCounts(),
557
- staleAfterDays: T,
558
- retentionDays: E
559
+ staleAfterDays: E,
560
+ retentionDays: D
559
561
  });
560
- }, U = () => {
562
+ }, ie = () => {
561
563
  let e = {};
562
- for (let [t, n] of f) e[t] = {
564
+ for (let [t, n] of p) e[t] = {
563
565
  valueType: n.valueType,
564
566
  variants: n.variants.map((e) => e.name),
565
567
  experiment: n.experiment ?? null
566
568
  };
567
569
  return e;
568
- }, oe = [
570
+ }, G = [
569
571
  {
570
572
  method: "GET",
571
573
  path: "/list",
572
574
  scope: "process",
573
575
  description: "Every resolved flag (key, enabled, rollout, description) plus the dead-flag lifecycle report: per-flag shape + usage verdict, the observation coverage window, and the limits of what the report can prove.",
574
576
  handler: () => l.promise(async () => {
575
- let e = await ie();
576
- return I({
577
- flags: H(z.all()),
578
- writeScope: m ? "shared-store" : "process",
577
+ let e = await re();
578
+ return R({
579
+ flags: W(H.all()),
580
+ writeScope: h ? "shared-store" : "process",
579
581
  lifecycle: e,
580
- typed: U(),
581
- overrideRefusals: S
582
+ typed: ie(),
583
+ overrideRefusals: C
582
584
  });
583
585
  })
584
586
  },
@@ -591,39 +593,40 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
591
593
  try {
592
594
  t = JSON.parse(e.body || "{}");
593
595
  } catch {
594
- return B(400, "invalid JSON body");
596
+ return z(400, "invalid JSON body");
595
597
  }
596
- if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return B(400, "key (string) + enabled (boolean) required");
597
- let n = t.key, r = t.enabled, i = z.get(n);
598
- if (i === void 0) return B(404, `unknown flag "${n}"`);
599
- if (m) {
600
- let e = h;
601
- if (!e) return B(503, "flag store is not ready");
602
- if ((yield* l.tryPromise(async () => {
603
- await ce(e, n, i, r, typeof t.actor == "string" ? t.actor : null), await O();
604
- }).pipe(l.either))._tag === "Left") return yield* l.tryPromise(O).pipe(l.ignore), B(503, "flag update could not be confirmed; reload the flag state before retrying");
605
- } else V(n, r);
606
- return I({
598
+ if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return z(400, "key (string) + enabled (boolean) required");
599
+ let n = t.key, r = t.enabled, i = H.get(n);
600
+ if (i === void 0) return z(404, `unknown flag "${n}"`);
601
+ if (h) {
602
+ let e = g;
603
+ if (!e) return z(503, "flag store is not ready");
604
+ let a = yield* l.tryPromise(async () => {
605
+ await ce(e, n, i, r, typeof t.actor == "string" ? t.actor : null), await A();
606
+ }).pipe(l.result);
607
+ if (u.isFailure(a)) return yield* l.tryPromise(A).pipe(l.ignore), z(503, "flag update could not be confirmed; reload the flag state before retrying");
608
+ } else U(n, r);
609
+ return R({
607
610
  ok: !0,
608
- flags: H(z.all())
611
+ flags: W(H.all())
609
612
  });
610
613
  })
611
614
  },
612
615
  {
613
616
  method: "GET",
614
617
  path: "/audit",
615
- scope: m ? "shared-store" : "declaration",
618
+ scope: h ? "shared-store" : "declaration",
616
619
  description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
617
620
  handler: (e) => l.gen(function* () {
618
- if (!_) return I({
621
+ if (!v) return R({
619
622
  audit: [],
620
623
  note: "audit trail requires store: \"postgres\""
621
624
  });
622
- let t = new URL(e.url, "http://local"), n = t.searchParams.get("flag"), r = t.searchParams.get("limit"), i = r != null && Number.isFinite(Number(r)) ? Number(r) : 100, a = yield* l.promise(() => _.list({
625
+ let t = new URL(e.url, "http://local"), n = t.searchParams.get("flag"), r = t.searchParams.get("limit"), i = r != null && Number.isFinite(Number(r)) ? Number(r) : 100, a = yield* l.promise(() => v.list({
623
626
  ...n == null ? {} : { flag: n },
624
627
  limit: i
625
628
  }));
626
- return I({ audit: a.map((e) => ({
629
+ return R({ audit: a.map((e) => ({
627
630
  flag: e.flag,
628
631
  enabled: e.enabled,
629
632
  previousEnabled: e.previousEnabled,
@@ -632,25 +635,25 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
632
635
  })) });
633
636
  })
634
637
  }
635
- ], W = [
636
- ...M ? [
638
+ ], oe = [
639
+ ...P ? [
637
640
  "rpc:intercept:mutation",
638
641
  "rpc:intercept:query",
639
642
  "rpc:intercept:action",
640
643
  "rpc:intercept:workflow"
641
644
  ] : [],
642
- ...m ? ["store:write", "store:changes:read"] : [],
645
+ ...h ? ["store:write", "store:changes:read"] : [],
643
646
  "inspect:read",
644
647
  "inspect:write"
645
648
  ];
646
- return u({
649
+ return d({
647
650
  name: o,
648
651
  baseName: $,
649
652
  description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
650
- permissions: W,
651
- routes: [P, F],
653
+ permissions: oe,
654
+ routes: [I, L],
652
655
  rpcClientDescriptors: r,
653
- inspectEndpoints: oe,
656
+ inspectEndpoints: G,
654
657
  errorSchemas: [{
655
658
  schema: c,
656
659
  import: {
@@ -658,34 +661,34 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
658
661
  name: "FlagDisabled"
659
662
  }
660
663
  }],
661
- ...M ? {
662
- interceptMutation: j,
663
- interceptQuery: j,
664
- interceptAction: j,
665
- interceptWorkflow: j
664
+ ...P ? {
665
+ interceptMutation: N,
666
+ interceptQuery: N,
667
+ interceptAction: N,
668
+ interceptWorkflow: N
666
669
  } : {},
667
- ...m ? { extendSchema: { tables: w ? [
668
- L,
669
- R,
670
+ ...h ? { extendSchema: { tables: T ? [
671
+ B,
672
+ V,
670
673
  J
671
- ] : [L, R] } } : {},
672
- ...m ? { bindDataStore: async (e) => {
673
- h = e, g = ae(h), _ = se(e), w && (v = _e(e)), await k();
674
+ ] : [B, V] } } : {},
675
+ ...h ? { bindDataStore: async (e) => {
676
+ g = e, _ = ae(g), v = se(e), T && (y = ye(e)), await j();
674
677
  } } : {},
675
- ...m ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => O()) : l.void } : {},
678
+ ...h ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => A()) : l.void } : {},
676
679
  onActivate: (t) => l.gen(function* () {
677
- z.set(p), b = t, g && (yield* l.promise(k)), t.logger.info("feature flags active", {
678
- count: Object.keys(p).length,
679
- typed: f.size,
680
+ H.set(m), x = t, _ && (yield* l.promise(j)), t.logger.info("feature flags active", {
681
+ count: Object.keys(m).length,
682
+ typed: p.size,
680
683
  gated: Object.keys(i).length,
681
684
  store: e.store ?? "memory",
682
- usage: w ? y ? `tracking (stale after ${T}d, observations kept ${Math.round((x ?? 0) / 864e5)}d)` : "waiting for store binding" : "off — no dead-flag evidence is collected"
685
+ usage: T ? b ? `tracking (stale after ${E}d, observations kept ${Math.round((S ?? 0) / 864e5)}d)` : "waiting for store binding" : "off — no dead-flag evidence is collected"
683
686
  });
684
687
  }),
685
688
  onDeactivate: () => l.promise(async () => {
686
- b = void 0, await y?.stop(), y = void 0, Y.disable(), z.set({});
689
+ x = void 0, await b?.stop(), b = void 0, Y.disable(), H.set({});
687
690
  })
688
691
  });
689
692
  };
690
693
  //#endregion
691
- export { ve as DEFAULT_FLAG_USAGE_FLUSH_MS, de as FLAG_LIFECYCLE_LIMITS, q as FLAG_USAGE_TABLE, c as FlagDisabled, le as assertFlagExperimentLinks, W as checkFlagExperimentLinks, K as classifyShape, _e as dataStoreFlagUsageStore, s as defineFlag, H as describeFlags, te as evaluateAll, ne as evaluateAllVariants, j as evaluateFlag, A as evaluateSchedule, P as evaluateVariant, L as featureFlagsTable, R as flagAuditTable, fe as flagLifecycleReport, z as flagRegistry, Y as flagUsageBuffer, he as flagUsageRowId, J as flagUsageTable, Se as flagValue, Ce as flagVariant, Te as flagsPlugin, V as flipFlagEnabled, a as gateOverride, Q as isFlagEnabled, o as isTypedFlag, re as mergeOverrides, X as recordFlagEvaluation, be as registerFlagUsageRetention, xe as requireFlag, N as resolveVariant, B as setFlagEnabled, ye as startFlagUsageFlush, w as toDefinition, i as typedValueOf, pe as utcDay };
694
+ export { be as DEFAULT_FLAG_USAGE_FLUSH_MS, pe as FLAG_LIFECYCLE_LIMITS, q as FLAG_USAGE_TABLE, c as FlagDisabled, ue as assertFlagExperimentLinks, le as checkFlagExperimentLinks, fe as classifyShape, ye as dataStoreFlagUsageStore, s as defineFlag, W as describeFlags, ee as evaluateAll, te as evaluateAllVariants, N as evaluateFlag, M as evaluateSchedule, L as evaluateVariant, B as featureFlagsTable, V as flagAuditTable, me as flagLifecycleReport, H as flagRegistry, Y as flagUsageBuffer, _e as flagUsageRowId, J as flagUsageTable, we as flagValue, Te as flagVariant, De as flagsPlugin, U as flipFlagEnabled, a as gateOverride, Q as isFlagEnabled, o as isTypedFlag, ne as mergeOverrides, X as recordFlagEvaluation, Se as registerFlagUsageRetention, Ce as requireFlag, I as resolveVariant, re as setFlagEnabled, xe as startFlagUsageFlush, T as toDefinition, i as typedValueOf, he as utcDay };