@voltro/plugin-flags 0.74.0 → 0.76.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
@@ -3,23 +3,23 @@ import { i, n as a, r as o, t as s } from "./define-D9Eet7wA.js";
3
3
  import { FlagDisabled as c } from "./errors.js";
4
4
  import { Effect as l } from "effect";
5
5
  import { definePlugin as u, pluginInstanceName as d } from "@voltro/protocol";
6
- import { boolean as f, derivedRowId as p, id as m, integer as h, json as g, queryFor as _, registerRetention as v, retentionTtlMsFromEnv as y, table as b, text as x, timestamp as S } from "@voltro/database";
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";
7
7
  //#region src/evaluate.ts
8
- var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
8
+ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (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
- }, T = (e, t) => w(e, t) % 100, ee = (e, t) => w(`${e}:variant`, t) % 1e4, E = (e, t) => {
12
+ }, E = (e, t) => T(e, t) % 100, D = (e, t) => T(`${e}:variant`, t) % 1e4, ee = (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
- }, D = (e) => typeof e == "number" ? e : Date.parse(e), O = (e, t) => (e.rolloutBy === "tenant" ? t.tenantId : t.id) ?? "anon", k = (e, t) => {
20
- if (e.activateAt != null && t < D(e.activateAt) || e.deactivateAt != null && t >= D(e.deactivateAt)) return { active: !1 };
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 };
21
21
  if (e.ramp) {
22
- let { from: n, to: r, startAt: i, endAt: a } = e.ramp, o = D(i), s = D(a);
22
+ let { from: n, to: r, startAt: i, endAt: a } = e.ramp, o = O(i), s = O(a);
23
23
  if (t <= o) return {
24
24
  active: !0,
25
25
  rollout: n
@@ -35,32 +35,32 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
35
35
  };
36
36
  }
37
37
  return { active: !0 };
38
- }, A = (e, t, n, r = Date.now()) => {
39
- let i = C(t);
38
+ }, j = (e, t, n, r = Date.now()) => {
39
+ let i = w(t);
40
40
  if (i.enabled === !1) return !1;
41
41
  let a;
42
42
  if (i.schedule) {
43
- let e = k(i.schedule, r);
43
+ let e = A(i.schedule, r);
44
44
  if (!e.active) return !1;
45
45
  a = e.rollout;
46
46
  }
47
- if (i.targeting && i.targeting.length > 0 && !i.targeting.some((e) => E(e, n))) return !1;
47
+ if (i.targeting && i.targeting.length > 0 && !i.targeting.some((e) => ee(e, n))) return !1;
48
48
  let o = a ?? i.rollout ?? 100;
49
49
  if (o < 100 && o > 0) {
50
- if (!(T(e, O(i, n)) < o)) return !1;
50
+ if (!(E(e, k(i, n)) < o)) return !1;
51
51
  } else if (o <= 0) return !1;
52
52
  return i.variants && i.variants.length > 0 && i.offVariant != null ? M(e, i, n).name !== i.offVariant : !0;
53
- }, j = (e) => {
53
+ }, te = (e) => {
54
54
  let t = e.reduce((e, t) => e + (t.weight ?? 1), 0) || e.length, n = 0;
55
55
  return e.map((r, i) => (n += r.weight ?? 1, i === e.length - 1 ? 1e4 : Math.round(n / t * 1e4)));
56
56
  }, M = (e, t, n) => {
57
57
  let r = t.variants ?? [];
58
58
  if (r.length === 0) throw Error(`flag "${e}" has no variants`);
59
- let i = ee(e, O(t, n)), a = j(r);
59
+ let i = D(e, k(t, n)), a = te(r);
60
60
  for (let e = 0; e < r.length; e++) if (i < a[e]) return r[e];
61
61
  return r[r.length - 1];
62
62
  }, N = (e, t, n, r = Date.now()) => {
63
- let i = C(t), a = A(e, t, n, r);
63
+ let i = w(t), a = j(e, t, n, r);
64
64
  if (!i.variants || i.variants.length === 0) return {
65
65
  name: a ? "on" : "off",
66
66
  value: a,
@@ -72,34 +72,34 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
72
72
  value: o.value,
73
73
  enabled: a
74
74
  };
75
- }, te = (e, t, n = Date.now()) => {
75
+ }, ne = (e, t, n = Date.now()) => {
76
76
  let r = {};
77
- for (let [i, a] of Object.entries(e)) r[i] = A(i, a, t, n);
77
+ for (let [i, a] of Object.entries(e)) r[i] = j(i, a, t, n);
78
78
  return r;
79
- }, ne = (e, t, n = Date.now()) => {
79
+ }, re = (e, t, n = Date.now()) => {
80
80
  let r = {};
81
81
  for (let [i, a] of Object.entries(e)) r[i] = N(i, a, t, n);
82
82
  return r;
83
- }, P = "_voltro_feature_flag_audit", F = "_voltro_feature_flags", I = b(F, {
84
- id: m({ prefix: "flag" }),
85
- key: x().unique(),
83
+ }, P = "_voltro_feature_flag_audit", F = "_voltro_feature_flags", I = x(F, {
84
+ id: h({ prefix: "flag" }),
85
+ key: S().unique(),
86
86
  enabled: f().default(!0),
87
- rollout: h().nullable(),
88
- rolloutBy: x().nullable(),
89
- targeting: g().nullable(),
90
- variants: g().nullable(),
91
- offVariant: x().nullable(),
92
- schedule: g().nullable(),
93
- description: x().nullable(),
94
- updatedAt: S().default("now").onUpdate("now")
95
- }), L = b(P, {
96
- id: m({ prefix: "flagaud" }),
97
- flag: x(),
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
+ }), L = x(P, {
96
+ id: h({ prefix: "flagaud" }),
97
+ flag: S(),
98
98
  enabled: f(),
99
99
  previousEnabled: f().nullable(),
100
- actorId: x().nullable(),
101
- at: S().default("now")
102
- }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), re = (e, t) => {
100
+ actorId: S().nullable(),
101
+ at: C().default("now")
102
+ }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), ie = (e, t) => {
103
103
  let n = { ...e };
104
104
  for (let e of t) {
105
105
  let t = {
@@ -126,10 +126,10 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
126
126
  get(e) {
127
127
  return this.flags[e];
128
128
  }
129
- }(), z = (e, t) => B(e, t) !== null, B = (e, t) => {
129
+ }(), ae = (e, t) => z(e, t) !== null, z = (e, t) => {
130
130
  let n = R.get(e);
131
131
  if (n === void 0) return null;
132
- let r = C(n), i = r.enabled !== !1;
132
+ let r = w(n), i = r.enabled !== !1;
133
133
  return R.set({
134
134
  ...R.all(),
135
135
  [e]: {
@@ -137,8 +137,8 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
137
137
  enabled: t
138
138
  }
139
139
  }), i;
140
- }, V = (e) => Object.entries(e).map(([e, t]) => {
141
- let n = C(t);
140
+ }, B = (e) => Object.entries(e).map(([e, t]) => {
141
+ let n = w(t);
142
142
  return {
143
143
  key: e,
144
144
  enabled: n.enabled !== !1,
@@ -147,8 +147,8 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
147
147
  scheduled: n.schedule != null,
148
148
  description: n.description
149
149
  };
150
- }), ie = (e, t) => {
151
- let n = C(t);
150
+ }), V = (e, t) => {
151
+ let n = w(t);
152
152
  return {
153
153
  key: e,
154
154
  enabled: n.enabled !== !1,
@@ -160,8 +160,8 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
160
160
  schedule: n.schedule ?? null,
161
161
  description: n.description ?? null
162
162
  };
163
- }, ae = (e) => ({
164
- loadOverrides: async () => (await e.query(_(I).descriptor)).map((e) => ({
163
+ }, oe = (e) => ({
164
+ loadOverrides: async () => (await e.query(v(I).descriptor)).map((e) => ({
165
165
  key: String(e.key),
166
166
  enabled: e.enabled === !0 || e.enabled === 1,
167
167
  rollout: e.rollout == null ? null : Number(e.rollout),
@@ -173,28 +173,28 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
173
173
  description: e.description ?? null
174
174
  })),
175
175
  upsertOverride: async (t) => {
176
- let n = {
177
- id: p("flag", t.key),
178
- key: t.key,
179
- enabled: t.enabled,
180
- rollout: t.rollout,
181
- rolloutBy: t.rolloutBy,
182
- targeting: t.targeting,
183
- variants: t.variants,
184
- offVariant: t.offVariant,
185
- schedule: t.schedule,
186
- description: t.description,
187
- updatedAt: /* @__PURE__ */ new Date()
188
- };
176
+ let n = se(t);
189
177
  await e.upsert(F, n, { conflictColumns: ["key"] });
190
178
  }
191
- }), oe = (e) => ({
179
+ }), se = (e) => ({
180
+ id: p("flag", e.key),
181
+ key: e.key,
182
+ enabled: e.enabled,
183
+ rollout: e.rollout,
184
+ rolloutBy: e.rolloutBy,
185
+ targeting: e.targeting,
186
+ variants: e.variants,
187
+ offVariant: e.offVariant,
188
+ schedule: e.schedule,
189
+ description: e.description,
190
+ updatedAt: /* @__PURE__ */ new Date()
191
+ }), ce = (e) => ({
192
192
  flag: String(e.flag),
193
193
  enabled: e.enabled === !0 || e.enabled === 1,
194
194
  previousEnabled: e.previousEnabled == null ? null : e.previousEnabled === !0 || e.previousEnabled === 1,
195
195
  actorId: e.actorId ?? null,
196
196
  at: e.at instanceof Date ? e.at : new Date(String(e.at))
197
- }), se = (e) => ({
197
+ }), H = (e) => ({
198
198
  record: async (t) => {
199
199
  await e.insert(P, {
200
200
  id: `flagaud_${t.at.getTime()}_${Math.random().toString(36).slice(2, 10)}`,
@@ -206,12 +206,29 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
206
206
  });
207
207
  },
208
208
  list: async (t) => {
209
- let n = _(L).orderBy("at", "desc");
210
- t?.limit != null && (n = n.limit(t.limit));
211
- let r = (await e.query(n.descriptor)).map(oe);
212
- return t?.flag == null ? r : r.filter((e) => e.flag === t.flag);
209
+ let n = v(L).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(ce);
213
211
  }
214
- }), H = (e, t) => {
212
+ }), le = async (e, t, n, r, i) => {
213
+ await e.transactional(async (e) => {
214
+ await e.insertIgnore(F, {
215
+ ...se(V(t, n)),
216
+ id: p("flag", crypto.randomUUID())
217
+ }, { conflictColumns: ["key"] });
218
+ let a = (await e.query(v(I).where(m("key", t)).forUpdate().descriptor))[0];
219
+ if (!a) throw Error("flag override disappeared during toggle");
220
+ await e.update(F, String(a.id), {
221
+ enabled: r,
222
+ updatedAt: /* @__PURE__ */ new Date()
223
+ }), await H(e).record({
224
+ flag: t,
225
+ enabled: r,
226
+ previousEnabled: a.enabled === !0 || a.enabled === 1,
227
+ actorId: i,
228
+ at: /* @__PURE__ */ new Date()
229
+ });
230
+ });
231
+ }, U = (e, t) => {
215
232
  let n = new Map(t.map((e) => [e.name, e])), r = [];
216
233
  for (let t of e) {
217
234
  let e = t.experiment;
@@ -250,24 +267,24 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
250
267
  }
251
268
  }
252
269
  return r;
253
- }, U = (e, t) => {
254
- let n = H(e, t);
270
+ }, W = (e, t) => {
271
+ let n = U(e, t);
255
272
  if (n.length === 0) return;
256
273
  let r = n.map((e) => ` · flag "${e.flag}" → experiment "${e.experiment}": ${e.problem}`);
257
274
  throw Error(`@voltro/plugin-flags: ${n.length} flag→experiment link problem(s):\n${r.join("\n")}`);
258
- }, W = 864e5, G = (e) => typeof e == "number" ? e : Date.parse(e), K = (e, t) => {
259
- let n = C(e);
275
+ }, G = 864e5, K = (e) => typeof e == "number" ? e : Date.parse(e), ue = (e, t) => {
276
+ let n = w(e);
260
277
  if (n.enabled === !1) return {
261
278
  shape: "constantOff",
262
279
  reason: "enabled: false — resolves false for every caller"
263
280
  };
264
281
  if (n.schedule) {
265
282
  let { activateAt: e, deactivateAt: r } = n.schedule;
266
- if (r != null && t >= G(r)) return {
283
+ if (r != null && t >= K(r)) return {
267
284
  shape: "expired",
268
285
  reason: `schedule.deactivateAt (${String(r)}) has passed — the flag can never be on again`
269
286
  };
270
- if (e != null && t < G(e)) return {
287
+ if (e != null && t < K(e)) return {
271
288
  shape: "notYetActive",
272
289
  reason: `schedule.activateAt (${String(e)}) is in the future — pending, not dead`
273
290
  };
@@ -277,7 +294,7 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
277
294
  shape: "constantOff",
278
295
  reason: `rollout: ${r} — nobody is in the rollout`
279
296
  };
280
- 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));
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 < K(n.schedule.deactivateAt));
281
298
  return r >= 100 && !i && !a && !o ? {
282
299
  shape: "constantOn",
283
300
  reason: "rollout 100 with no targeting, no variants and no live schedule — resolves true for every caller"
@@ -290,14 +307,14 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
290
307
  o ? "a live schedule" : void 0
291
308
  ].filter((e) => e !== void 0).join(", ")})`
292
309
  };
293
- }, ce = [
310
+ }, de = [
294
311
  "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.",
295
312
  "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.",
296
313
  "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.",
297
314
  "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.",
298
315
  "\"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."
299
- ], le = (e) => {
300
- let t = e.now ?? Date.now(), n = e.staleAfterDays, r = t - n * W, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o;
316
+ ], fe = (e) => {
317
+ let t = e.now ?? Date.now(), n = e.staleAfterDays, r = t - n * G, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o;
301
318
  for (let t of e.usage) {
302
319
  let e = t.lastAt.getTime();
303
320
  if (!Number.isFinite(e)) continue;
@@ -305,7 +322,7 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
305
322
  let n = t.source === "bulk" ? a : i, r = n.get(t.flag);
306
323
  (r === void 0 || e > r) && n.set(t.flag, e);
307
324
  }
308
- let s = o === void 0 ? 0 : Math.floor((t - o) / W);
325
+ let s = o === void 0 ? 0 : Math.floor((t - o) / G);
309
326
  return {
310
327
  coverage: {
311
328
  tracking: e.tracking,
@@ -315,7 +332,7 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
315
332
  staleAfterDays: n
316
333
  },
317
334
  findings: Object.entries(e.flags).map(([o, c]) => {
318
- let { shape: l, reason: u } = K(c, t), d = i.get(o), f = a.get(o), p = [u], m;
335
+ let { shape: l, reason: u } = ue(c, t), d = i.get(o), f = a.get(o), p = [u], m;
319
336
  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"));
320
337
  let h = l === "constantOn" || l === "constantOff" || l === "expired";
321
338
  return h && p.push("the definition alone makes this flag a constant — it selects nothing"), {
@@ -330,23 +347,23 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
330
347
  why: p
331
348
  };
332
349
  }),
333
- limits: ce
350
+ limits: de
334
351
  };
335
- }, q = "_voltro_feature_flag_usage", J = b(q, {
336
- id: m({ prefix: "flagusg" }),
337
- flag: x(),
338
- day: x(),
339
- source: x(),
340
- lastAt: S()
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()
341
358
  }).unique([
342
359
  "flag",
343
360
  "day",
344
361
  "source"
345
- ]).index("byFlagUsageLastAt", ["lastAt"]).index("byFlagUsageFlag", ["flag"]), ue = (e) => e.toISOString().slice(0, 10), de = (e) => {
362
+ ]).index("byFlagUsageLastAt", ["lastAt"]).index("byFlagUsageFlag", ["flag"]), pe = (e) => e.toISOString().slice(0, 10), me = (e) => {
346
363
  let t = 2166136261;
347
364
  for (let n = 0; n < e.length; n++) t ^= e.charCodeAt(n), t = Math.imul(t, 16777619);
348
365
  return (t >>> 0).toString(16).padStart(8, "0");
349
- }, fe = (e, t, n) => `flagusg_${t.replace(/-/g, "")}_${n === "targeted" ? "t" : "b"}_${de(e)}`, Y = new class {
366
+ }, he = (e, t, n) => `flagusg_${t.replace(/-/g, "")}_${n === "targeted" ? "t" : "b"}_${me(e)}`, Y = new class {
350
367
  on = !1;
351
368
  pending = /* @__PURE__ */ new Map();
352
369
  liveCounts = /* @__PURE__ */ new Map();
@@ -384,12 +401,12 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
384
401
  reset() {
385
402
  this.on = !1, this.pending.clear(), this.liveCounts.clear();
386
403
  }
387
- }(), X = (e, t, n) => Y.record(e, t, n), pe = (e) => e === "bulk" ? "bulk" : "targeted", me = (e) => ({
404
+ }(), X = (e, t, n) => Y.record(e, t, n), ge = (e) => e === "bulk" ? "bulk" : "targeted", _e = (e) => ({
388
405
  upsertMany: async (t) => {
389
406
  for (let n of t) {
390
- let t = ue(n.lastAt);
407
+ let t = pe(n.lastAt);
391
408
  await e.upsert(q, {
392
- id: fe(n.flag, t, n.source),
409
+ id: he(n.flag, t, n.source),
393
410
  flag: n.flag,
394
411
  day: t,
395
412
  source: n.source,
@@ -401,13 +418,13 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
401
418
  ] });
402
419
  }
403
420
  },
404
- load: async () => (await e.query(_(J).descriptor)).map((e) => ({
421
+ load: async () => (await e.query(v(J).descriptor)).map((e) => ({
405
422
  flag: String(e.flag),
406
423
  day: String(e.day),
407
- source: pe(e.source),
424
+ source: ge(e.source),
408
425
  lastAt: e.lastAt instanceof Date ? e.lastAt : new Date(String(e.lastAt))
409
426
  }))
410
- }), he = 3e5, ge = (e) => {
427
+ }), ve = 3e5, ye = (e) => {
411
428
  let t = e.intervalMs ?? 3e5, n = !1, r = async () => {
412
429
  let t = Y.drain();
413
430
  if (t.length !== 0) try {
@@ -431,9 +448,9 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
431
448
  n || (n = !0, clearInterval(i), await r());
432
449
  }
433
450
  };
434
- }, _e = (e = process.env, t = 90) => {
435
- let n = y(e.VOLTRO_FLAG_USAGE_TTL_HOURS, 24 * t);
436
- return v({
451
+ }, be = (e = process.env, t = 90) => {
452
+ let n = b(e.VOLTRO_FLAG_USAGE_TTL_HOURS, 24 * t);
453
+ return y({
437
454
  table: q,
438
455
  timeColumn: "lastAt",
439
456
  source: "plugin",
@@ -442,14 +459,14 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
442
459
  }), n;
443
460
  }, Z = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, Q = (e, t) => {
444
461
  let n = R.get(t);
445
- return X(t, "targeted"), n !== void 0 && A(t, n, Z(e));
446
- }, ve = (e, t) => Q(e, t) ? l.void : l.fail(new c({ flag: t })), ye = (e, 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) => {
447
464
  let n = R.get(t.key);
448
465
  return X(t.key, "targeted"), n === void 0 ? t.defaultValue : i(t, N(t.key, n, Z(e)));
449
- }, be = (e, t) => {
466
+ }, Ce = (e, t) => {
450
467
  let n = R.get(t.key);
451
468
  return X(t.key, "targeted"), n === void 0 ? null : N(t.key, n, Z(e)).name;
452
- }, xe = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), $ = e, Se = (e = {}) => {
469
+ }, we = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), $ = e, Te = (e = {}) => {
453
470
  let i = e.gatedBy ?? {}, o = d({
454
471
  base: $,
455
472
  alias: e.alias,
@@ -460,69 +477,71 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
460
477
  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.`);
461
478
  f.set(t.key, t);
462
479
  }
463
- s.some((e) => e.experiment !== void 0) && U(s, e.experiments ?? []);
480
+ s.some((e) => e.experiment !== void 0) && W(s, e.experiments ?? []);
464
481
  let p = { ...e.flags ?? {} };
465
482
  for (let e of s) p[e.key] = e.definition;
466
- let m = e.store === "postgres", h, g, _, v, y = [], b = e.usage ?? {}, x = b.track ?? m;
467
- if (x && !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.`);
468
- let S = b.staleAfterDays ?? 30, C = b.retentionDays ?? 90;
469
- if (S > C) throw Error(`${$}: usage.staleAfterDays (${S}) exceeds usage.retentionDays (${C}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
470
- let w = (e) => {
471
- let t = re(p, e), n = [];
483
+ let m = e.store === "postgres", h, g, _, v, y, b = [], x = e.usage ?? {}, S = x.track ?? m;
484
+ if (S && !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 C = x.staleAfterDays ?? 30, w = x.retentionDays ?? 90;
486
+ if (C > w) throw Error(`${$}: usage.staleAfterDays (${C}) exceeds usage.retentionDays (${w}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
487
+ let T = (e) => {
488
+ let t = ie(p, e), n = [];
472
489
  for (let [e, r] of f) {
473
490
  let i = t[e];
474
491
  if (i === void 0) continue;
475
492
  let o = a(r, typeof i == "boolean" ? { enabled: i } : i);
476
493
  o.ok || (n.push(o.refusal), t[e] = r.definition);
477
494
  }
478
- y = n, R.set(t);
479
- }, T = async () => {
480
- h && w(await h.loadOverrides());
495
+ b = n, R.set(t);
496
+ }, E = 0, D = async () => {
497
+ if (!g) return;
498
+ let e = ++E, t = await g.loadOverrides();
499
+ e === E && T(t);
481
500
  }, ee = (e) => {
482
501
  if (i[e] !== void 0) return i[e];
483
- for (let [t, n] of Object.entries(i)) if (t !== e && xe(t, e)) return n;
484
- }, E = (e, t) => {
502
+ for (let [t, n] of Object.entries(i)) if (t !== e && we(t, e)) return n;
503
+ }, O = (e, t) => {
485
504
  let n = ee(t.tag);
486
505
  return n === void 0 || Q({ subject: t.subject }, n) ? e : l.fail(new c({ flag: n }));
487
- }, D = Object.keys(i).length > 0, O = (e) => {
506
+ }, k = Object.keys(i).length > 0, A = (e) => {
488
507
  for (let t of e) X(t, "bulk");
489
- }, k = {
508
+ }, j = {
490
509
  ...t,
491
510
  description: "Resolve every feature flag for the calling subject.",
492
511
  execute: (e, t) => l.sync(() => {
493
512
  let e = R.all();
494
- return O(Object.keys(e)), te(e, t.request.subject ?? {});
513
+ return A(Object.keys(e)), ne(e, t.request.subject ?? {});
495
514
  })
496
- }, A = {
515
+ }, te = {
497
516
  ...n,
498
517
  description: "Resolve every feature flag to its served variant for the calling subject.",
499
518
  execute: (e, t) => l.sync(() => {
500
519
  let e = R.all();
501
- return O(Object.keys(e)), ne(e, t.request.subject ?? {});
520
+ return A(Object.keys(e)), re(e, t.request.subject ?? {});
502
521
  })
503
- }, j = (e) => ({
522
+ }, M = (e) => ({
504
523
  kind: "json",
505
524
  data: e
506
- }), M = (e, t) => ({
525
+ }), N = (e, t) => ({
507
526
  kind: "json",
508
527
  status: e,
509
528
  data: { error: t }
510
- }), N = async () => {
529
+ }), P = async () => {
511
530
  let e = [];
512
- if (x && _) try {
513
- e = await _.load();
531
+ if (S && v) try {
532
+ e = await v.load();
514
533
  } catch {
515
534
  e = [];
516
535
  }
517
- return le({
536
+ return fe({
518
537
  flags: R.all(),
519
538
  usage: e,
520
- tracking: x,
539
+ tracking: S,
521
540
  liveCounts: Y.liveTargetedCounts(),
522
- staleAfterDays: S,
523
- retentionDays: C
541
+ staleAfterDays: C,
542
+ retentionDays: w
524
543
  });
525
- }, P = () => {
544
+ }, F = () => {
526
545
  let e = {};
527
546
  for (let [t, n] of f) e[t] = {
528
547
  valueType: n.valueType,
@@ -530,19 +549,19 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
530
549
  experiment: n.experiment ?? null
531
550
  };
532
551
  return e;
533
- }, F = [
552
+ }, ae = [
534
553
  {
535
554
  method: "GET",
536
555
  path: "/list",
537
556
  scope: "process",
538
557
  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.",
539
558
  handler: () => l.promise(async () => {
540
- let e = await N();
541
- return j({
542
- flags: V(R.all()),
559
+ let e = await P();
560
+ return M({
561
+ flags: B(R.all()),
543
562
  lifecycle: e,
544
- typed: P(),
545
- overrideRefusals: y
563
+ typed: F(),
564
+ overrideRefusals: b
546
565
  });
547
566
  })
548
567
  },
@@ -555,21 +574,21 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
555
574
  try {
556
575
  t = JSON.parse(e.body || "{}");
557
576
  } catch {
558
- return M(400, "invalid JSON body");
577
+ return N(400, "invalid JSON body");
559
578
  }
560
- if (!t.key || typeof t.enabled != "boolean") return M(400, "key (string) + enabled (boolean) required");
561
- let n = B(t.key, t.enabled);
562
- if (n === null) return M(404, `unknown flag "${t.key}"`);
563
- let r = h ? R.get(t.key) : void 0;
564
- return h && r !== void 0 && (yield* l.promise(() => h.upsertOverride(ie(t.key, r)))), g && (yield* l.promise(() => g.record({
565
- flag: t.key,
566
- enabled: t.enabled,
567
- previousEnabled: n,
568
- actorId: typeof t.actor == "string" ? t.actor : null,
569
- at: /* @__PURE__ */ new Date()
570
- }))), j({
579
+ if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return N(400, "key (string) + enabled (boolean) required");
580
+ let n = t.key, r = t.enabled, i = R.get(n);
581
+ if (i === void 0) return N(404, `unknown flag "${n}"`);
582
+ if (m) {
583
+ let e = h;
584
+ if (!e) return N(503, "flag store is not ready");
585
+ if ((yield* l.tryPromise(async () => {
586
+ await le(e, n, i, r, typeof t.actor == "string" ? t.actor : null), await D();
587
+ }).pipe(l.either))._tag === "Left") return yield* l.tryPromise(D).pipe(l.ignore), N(503, "flag update could not be confirmed; reload the flag state before retrying");
588
+ } else z(n, r);
589
+ return M({
571
590
  ok: !0,
572
- flags: V(R.all())
591
+ flags: B(R.all())
573
592
  });
574
593
  })
575
594
  },
@@ -579,15 +598,15 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
579
598
  scope: m ? "shared-store" : "declaration",
580
599
  description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
581
600
  handler: (e) => l.gen(function* () {
582
- if (!g) return j({
601
+ if (!_) return M({
583
602
  audit: [],
584
603
  note: "audit trail requires store: \"postgres\""
585
604
  });
586
- 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(() => g.list({
605
+ 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({
587
606
  ...n == null ? {} : { flag: n },
588
607
  limit: i
589
608
  }));
590
- return j({ audit: a.map((e) => ({
609
+ return M({ audit: a.map((e) => ({
591
610
  flag: e.flag,
592
611
  enabled: e.enabled,
593
612
  previousEnabled: e.previousEnabled,
@@ -596,8 +615,8 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
596
615
  })) });
597
616
  })
598
617
  }
599
- ], z = [
600
- ...D ? [
618
+ ], V = [
619
+ ...k ? [
601
620
  "rpc:intercept:mutation",
602
621
  "rpc:intercept:query",
603
622
  "rpc:intercept:action",
@@ -611,10 +630,10 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
611
630
  name: o,
612
631
  baseName: $,
613
632
  description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
614
- permissions: z,
615
- routes: [k, A],
633
+ permissions: V,
634
+ routes: [j, te],
616
635
  rpcClientDescriptors: r,
617
- inspectEndpoints: F,
636
+ inspectEndpoints: ae,
618
637
  errorSchemas: [{
619
638
  schema: c,
620
639
  import: {
@@ -622,30 +641,30 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
622
641
  name: "FlagDisabled"
623
642
  }
624
643
  }],
625
- ...D ? {
626
- interceptMutation: E,
627
- interceptQuery: E,
628
- interceptAction: E,
629
- interceptWorkflow: E
644
+ ...k ? {
645
+ interceptMutation: O,
646
+ interceptQuery: O,
647
+ interceptAction: O,
648
+ interceptWorkflow: O
630
649
  } : {},
631
- ...m ? { extendSchema: { tables: x ? [
650
+ ...m ? { extendSchema: { tables: S ? [
632
651
  I,
633
652
  L,
634
653
  J
635
654
  ] : [I, L] } } : {},
636
655
  ...m ? { bindDataStore: (e) => {
637
- h = ae(e), g = se(e), x && (_ = me(e));
656
+ h = e, g = oe(h), _ = H(e), S && (v = _e(e));
638
657
  } } : {},
639
- ...m ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => T()) : l.void } : {},
658
+ ...m ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => D()) : l.void } : {},
640
659
  onActivate: (t) => l.gen(function* () {
641
- R.set(p), m && h && (yield* l.promise(T));
660
+ R.set(p), m && g && (yield* l.promise(D));
642
661
  let n;
643
- x && _ && (n = _e(process.env, C), Y.enable(), v = ge({
644
- store: _,
645
- intervalMs: b.flushIntervalMs ?? 3e5,
662
+ S && v && (n = be(process.env, w), Y.enable(), y = ye({
663
+ store: v,
664
+ intervalMs: x.flushIntervalMs ?? 3e5,
646
665
  log: { warn: (e, n) => t.logger.warn(e, n) }
647
666
  }));
648
- for (let e of y) t.logger.warn("feature flag override REFUSED — the code-declared definition stands", {
667
+ for (let e of b) t.logger.warn("feature flag override REFUSED — the code-declared definition stands", {
649
668
  flag: e.key,
650
669
  reason: e.reason
651
670
  });
@@ -654,13 +673,13 @@ var C = (e) => typeof e == "boolean" ? { enabled: e } : e, w = (e, t) => {
654
673
  typed: f.size,
655
674
  gated: Object.keys(i).length,
656
675
  store: e.store ?? "memory",
657
- usage: x ? `tracking (stale after ${S}d, observations kept ${Math.round((n ?? 0) / 864e5)}d)` : "off — no dead-flag evidence is collected"
676
+ usage: S ? `tracking (stale after ${C}d, observations kept ${Math.round((n ?? 0) / 864e5)}d)` : "off — no dead-flag evidence is collected"
658
677
  });
659
678
  }),
660
679
  onDeactivate: () => l.promise(async () => {
661
- await v?.stop(), v = void 0, Y.disable(), R.set({});
680
+ await y?.stop(), y = void 0, Y.disable(), R.set({});
662
681
  })
663
682
  });
664
683
  };
665
684
  //#endregion
666
- export { he as DEFAULT_FLAG_USAGE_FLUSH_MS, ce as FLAG_LIFECYCLE_LIMITS, q as FLAG_USAGE_TABLE, c as FlagDisabled, U as assertFlagExperimentLinks, H as checkFlagExperimentLinks, K as classifyShape, me as dataStoreFlagUsageStore, s as defineFlag, V as describeFlags, te as evaluateAll, ne as evaluateAllVariants, A as evaluateFlag, k as evaluateSchedule, N as evaluateVariant, I as featureFlagsTable, L as flagAuditTable, le as flagLifecycleReport, R as flagRegistry, Y as flagUsageBuffer, fe as flagUsageRowId, J as flagUsageTable, ye as flagValue, be as flagVariant, Se as flagsPlugin, B as flipFlagEnabled, a as gateOverride, Q as isFlagEnabled, o as isTypedFlag, re as mergeOverrides, X as recordFlagEvaluation, _e as registerFlagUsageRetention, ve as requireFlag, M as resolveVariant, z as setFlagEnabled, ge as startFlagUsageFlush, C as toDefinition, i as typedValueOf, ue as utcDay };
685
+ export { ve as DEFAULT_FLAG_USAGE_FLUSH_MS, de as FLAG_LIFECYCLE_LIMITS, q as FLAG_USAGE_TABLE, c as FlagDisabled, W as assertFlagExperimentLinks, U as checkFlagExperimentLinks, ue as classifyShape, _e as dataStoreFlagUsageStore, s as defineFlag, B as describeFlags, ne as evaluateAll, re as evaluateAllVariants, j as evaluateFlag, A as evaluateSchedule, N as evaluateVariant, I as featureFlagsTable, L as flagAuditTable, fe as flagLifecycleReport, R as flagRegistry, Y as flagUsageBuffer, he as flagUsageRowId, J as flagUsageTable, Se as flagValue, Ce as flagVariant, Te as flagsPlugin, z as flipFlagEnabled, a as gateOverride, Q as isFlagEnabled, o as isTypedFlag, ie as mergeOverrides, X as recordFlagEvaluation, be as registerFlagUsageRetention, xe as requireFlag, M as resolveVariant, ae as setFlagEnabled, ye as startFlagUsageFlush, w as toDefinition, i as typedValueOf, pe as utcDay };