@voltro/plugin-flags 0.81.0 → 0.83.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +1806 -6
  2. package/dist/index.js +137 -131
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ 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
- }, E = (e, t) => T(e, t) % 100, D = (e, t) => T(`${e}:variant`, t) % 1e4, ee = (e, t) => {
12
+ }, E = (e, t) => T(e, t) % 100, ee = (e, t) => T(`${e}:variant`, t) % 1e4, D = (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 ?? {};
@@ -44,43 +44,43 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
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) => ee(e, n))) return !1;
47
+ if (i.targeting && i.targeting.length > 0 && !i.targeting.some((e) => D(e, n))) return !1;
48
48
  let o = a ?? i.rollout ?? 100;
49
49
  if (o < 100 && o > 0) {
50
50
  if (!(E(e, k(i, n)) < o)) return !1;
51
51
  } else if (o <= 0) return !1;
52
- return i.variants && i.variants.length > 0 && i.offVariant != null ? M(e, i, n).name !== i.offVariant : !0;
53
- }, te = (e) => {
52
+ return i.variants && i.variants.length > 0 && i.offVariant != null ? N(e, i, n).name !== i.offVariant : !0;
53
+ }, M = (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
- }, M = (e, t, n) => {
56
+ }, N = (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 = D(e, k(t, n)), a = te(r);
59
+ let i = ee(e, k(t, n)), a = M(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
- }, N = (e, t, n, r = Date.now()) => {
62
+ }, P = (e, t, n, r = Date.now()) => {
63
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,
67
67
  enabled: a
68
68
  };
69
- let o = M(e, i, n);
69
+ let o = N(e, i, n);
70
70
  return {
71
71
  name: o.name,
72
72
  value: o.value,
73
73
  enabled: a
74
74
  };
75
- }, ne = (e, t, n = Date.now()) => {
75
+ }, te = (e, t, n = Date.now()) => {
76
76
  let r = {};
77
77
  for (let [i, a] of Object.entries(e)) r[i] = j(i, a, t, n);
78
78
  return r;
79
- }, re = (e, t, n = Date.now()) => {
79
+ }, ne = (e, t, n = Date.now()) => {
80
80
  let r = {};
81
- for (let [i, a] of Object.entries(e)) r[i] = N(i, a, t, n);
81
+ for (let [i, a] of Object.entries(e)) r[i] = P(i, a, t, n);
82
82
  return r;
83
- }, P = "_voltro_feature_flag_audit", F = "_voltro_feature_flags", I = x(F, {
83
+ }, F = "_voltro_feature_flag_audit", I = "_voltro_feature_flags", L = x(I, {
84
84
  id: h({ prefix: "flag" }),
85
85
  key: S().unique(),
86
86
  enabled: f().default(!0),
@@ -92,14 +92,14 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
92
92
  schedule: _().nullable(),
93
93
  description: S().nullable(),
94
94
  updatedAt: C().default("now").onUpdate("now")
95
- }), L = x(P, {
95
+ }), R = x(F, {
96
96
  id: h({ prefix: "flagaud" }),
97
97
  flag: S(),
98
98
  enabled: f(),
99
99
  previousEnabled: f().nullable(),
100
100
  actorId: S().nullable(),
101
101
  at: C().default("now")
102
- }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), ie = (e, t) => {
102
+ }).index("byFlagAuditFlag", ["flag"]).index("byFlagAuditAt", ["at"]), re = (e, t) => {
103
103
  let n = { ...e };
104
104
  for (let e of t) {
105
105
  let t = {
@@ -115,7 +115,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
115
115
  n[e.key] = t;
116
116
  }
117
117
  return n;
118
- }, R = new class {
118
+ }, z = new class {
119
119
  flags = {};
120
120
  set(e) {
121
121
  this.flags = e;
@@ -126,18 +126,18 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
126
126
  get(e) {
127
127
  return this.flags[e];
128
128
  }
129
- }(), ae = (e, t) => z(e, t) !== null, z = (e, t) => {
130
- let n = R.get(e);
129
+ }(), B = (e, t) => V(e, t) !== null, V = (e, t) => {
130
+ let n = z.get(e);
131
131
  if (n === void 0) return null;
132
132
  let r = w(n), i = r.enabled !== !1;
133
- return R.set({
134
- ...R.all(),
133
+ return z.set({
134
+ ...z.all(),
135
135
  [e]: {
136
136
  ...r,
137
137
  enabled: t
138
138
  }
139
139
  }), i;
140
- }, B = (e) => Object.entries(e).map(([e, t]) => {
140
+ }, H = (e) => Object.entries(e).map(([e, t]) => {
141
141
  let n = w(t);
142
142
  return {
143
143
  key: e,
@@ -147,7 +147,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
147
147
  scheduled: n.schedule != null,
148
148
  description: n.description
149
149
  };
150
- }), V = (e, t) => {
150
+ }), ie = (e, t) => {
151
151
  let n = w(t);
152
152
  return {
153
153
  key: e,
@@ -160,8 +160,8 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
160
160
  schedule: n.schedule ?? null,
161
161
  description: n.description ?? null
162
162
  };
163
- }, oe = (e) => ({
164
- loadOverrides: async () => (await e.query(v(I).descriptor)).map((e) => ({
163
+ }, ae = (e) => ({
164
+ loadOverrides: async () => (await e.query(v(L).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,10 +173,10 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
173
173
  description: e.description ?? null
174
174
  })),
175
175
  upsertOverride: async (t) => {
176
- let n = se(t);
177
- await e.upsert(F, n, { conflictColumns: ["key"] });
176
+ let n = U(t);
177
+ await e.upsert(I, n, { conflictColumns: ["key"] });
178
178
  }
179
- }), se = (e) => ({
179
+ }), U = (e) => ({
180
180
  id: p("flag", e.key),
181
181
  key: e.key,
182
182
  enabled: e.enabled,
@@ -188,15 +188,15 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
188
188
  schedule: e.schedule,
189
189
  description: e.description,
190
190
  updatedAt: /* @__PURE__ */ new Date()
191
- }), ce = (e) => ({
191
+ }), oe = (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
- }), H = (e) => ({
197
+ }), se = (e) => ({
198
198
  record: async (t) => {
199
- await e.insert(P, {
199
+ await e.insert(F, {
200
200
  id: `flagaud_${t.at.getTime()}_${Math.random().toString(36).slice(2, 10)}`,
201
201
  flag: t.flag,
202
202
  enabled: t.enabled,
@@ -206,21 +206,21 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
206
206
  });
207
207
  },
208
208
  list: async (t) => {
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);
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
211
  }
212
- }), le = async (e, t, n, r, i) => {
212
+ }), ce = async (e, t, n, r, i) => {
213
213
  await e.transactional(async (e) => {
214
- await e.insertIgnore(F, {
215
- ...se(V(t, n)),
214
+ await e.insertIgnore(I, {
215
+ ...U(ie(t, n)),
216
216
  id: p("flag", crypto.randomUUID())
217
217
  }, { conflictColumns: ["key"] });
218
- let a = (await e.query(v(I).where(m("key", t)).forUpdate().descriptor))[0];
218
+ let a = (await e.query(v(L).where(m("key", t)).forUpdate().descriptor))[0];
219
219
  if (!a) throw Error("flag override disappeared during toggle");
220
- await e.update(F, String(a.id), {
220
+ await e.update(I, String(a.id), {
221
221
  enabled: r,
222
222
  updatedAt: /* @__PURE__ */ new Date()
223
- }), await H(e).record({
223
+ }), await se(e).record({
224
224
  flag: t,
225
225
  enabled: r,
226
226
  previousEnabled: a.enabled === !0 || a.enabled === 1,
@@ -228,7 +228,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
228
228
  at: /* @__PURE__ */ new Date()
229
229
  });
230
230
  });
231
- }, U = (e, t) => {
231
+ }, W = (e, t) => {
232
232
  let n = new Map(t.map((e) => [e.name, e])), r = [];
233
233
  for (let t of e) {
234
234
  let e = t.experiment;
@@ -267,12 +267,12 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
267
267
  }
268
268
  }
269
269
  return r;
270
- }, W = (e, t) => {
271
- let n = U(e, t);
270
+ }, le = (e, t) => {
271
+ let n = W(e, t);
272
272
  if (n.length === 0) return;
273
273
  let r = n.map((e) => ` · flag "${e.flag}" → experiment "${e.experiment}": ${e.problem}`);
274
274
  throw Error(`@voltro/plugin-flags: ${n.length} flag→experiment link problem(s):\n${r.join("\n")}`);
275
- }, G = 864e5, K = (e) => typeof e == "number" ? e : Date.parse(e), ue = (e, t) => {
275
+ }, ue = 864e5, G = (e) => typeof e == "number" ? e : Date.parse(e), K = (e, t) => {
276
276
  let n = w(e);
277
277
  if (n.enabled === !1) return {
278
278
  shape: "constantOff",
@@ -280,11 +280,11 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
280
280
  };
281
281
  if (n.schedule) {
282
282
  let { activateAt: e, deactivateAt: r } = n.schedule;
283
- if (r != null && t >= K(r)) return {
283
+ if (r != null && t >= G(r)) return {
284
284
  shape: "expired",
285
285
  reason: `schedule.deactivateAt (${String(r)}) has passed — the flag can never be on again`
286
286
  };
287
- if (e != null && t < K(e)) return {
287
+ if (e != null && t < G(e)) return {
288
288
  shape: "notYetActive",
289
289
  reason: `schedule.activateAt (${String(e)}) is in the future — pending, not dead`
290
290
  };
@@ -294,7 +294,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
294
294
  shape: "constantOff",
295
295
  reason: `rollout: ${r} — nobody is in the rollout`
296
296
  };
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));
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));
298
298
  return r >= 100 && !i && !a && !o ? {
299
299
  shape: "constantOn",
300
300
  reason: "rollout 100 with no targeting, no variants and no live schedule — resolves true for every caller"
@@ -314,7 +314,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
314
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.",
315
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."
316
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;
317
+ let t = e.now ?? Date.now(), n = e.staleAfterDays, r = t - n * ue, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o;
318
318
  for (let t of e.usage) {
319
319
  let e = t.lastAt.getTime();
320
320
  if (!Number.isFinite(e)) continue;
@@ -322,7 +322,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
322
322
  let n = t.source === "bulk" ? a : i, r = n.get(t.flag);
323
323
  (r === void 0 || e > r) && n.set(t.flag, e);
324
324
  }
325
- let s = o === void 0 ? 0 : Math.floor((t - o) / G);
325
+ let s = o === void 0 ? 0 : Math.floor((t - o) / ue);
326
326
  return {
327
327
  coverage: {
328
328
  tracking: e.tracking,
@@ -332,7 +332,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
332
332
  staleAfterDays: n
333
333
  },
334
334
  findings: Object.entries(e.flags).map(([o, c]) => {
335
- let { shape: l, reason: u } = ue(c, t), d = i.get(o), f = a.get(o), p = [u], m;
335
+ let { shape: l, reason: u } = K(c, t), d = i.get(o), f = a.get(o), p = [u], m;
336
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"));
337
337
  let h = l === "constantOn" || l === "constantOff" || l === "expired";
338
338
  return h && p.push("the definition alone makes this flag a constant — it selects nothing"), {
@@ -458,14 +458,14 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
458
458
  envVar: "VOLTRO_FLAG_USAGE_TTL_HOURS"
459
459
  }), n;
460
460
  }, Z = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, Q = (e, t) => {
461
- let n = R.get(t);
461
+ let n = z.get(t);
462
462
  return X(t, "targeted"), n !== void 0 && j(t, n, Z(e));
463
463
  }, xe = (e, t) => Q(e, t) ? l.void : l.fail(new c({ flag: t })), Se = (e, t) => {
464
- let n = R.get(t.key);
465
- return X(t.key, "targeted"), n === void 0 ? t.defaultValue : i(t, N(t.key, n, Z(e)));
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
466
  }, Ce = (e, t) => {
467
- let n = R.get(t.key);
468
- return X(t.key, "targeted"), n === void 0 ? null : N(t.key, n, Z(e)).name;
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
469
  }, we = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), $ = e, Te = (e = {}) => {
470
470
  let i = e.gatedBy ?? {}, o = d({
471
471
  base: $,
@@ -477,71 +477,87 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
477
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.`);
478
478
  f.set(t.key, t);
479
479
  }
480
- s.some((e) => e.experiment !== void 0) && W(s, e.experiments ?? []);
480
+ s.some((e) => e.experiment !== void 0) && le(s, e.experiments ?? []);
481
481
  let p = { ...e.flags ?? {} };
482
482
  for (let e of s) p[e.key] = e.definition;
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 = [];
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
489
  for (let [e, r] of f) {
490
490
  let i = t[e];
491
491
  if (i === void 0) continue;
492
492
  let o = a(r, typeof i == "boolean" ? { enabled: i } : i);
493
493
  o.ok || (n.push(o.refusal), t[e] = r.definition);
494
494
  }
495
- b = n, R.set(t);
496
- }, E = 0, D = async () => {
495
+ S = n, z.set(t);
496
+ }, D = 0, O = async () => {
497
497
  if (!g) return;
498
- let e = ++E, t = await g.loadOverrides();
499
- e === E && T(t);
500
- }, ee = (e) => {
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,
508
+ log: { warn: (t, n) => e.warn(t, n) }
509
+ });
510
+ }
511
+ for (let e of S) b.logger.warn("feature flag override REFUSED — code-declared definition stands", {
512
+ flag: e.key,
513
+ reason: e.reason
514
+ });
515
+ }
516
+ }, A = (e) => {
501
517
  if (i[e] !== void 0) return i[e];
502
518
  for (let [t, n] of Object.entries(i)) if (t !== e && we(t, e)) return n;
503
- }, O = (e, t) => {
504
- let n = ee(t.tag);
519
+ }, j = (e, t) => {
520
+ let n = A(t.tag);
505
521
  return n === void 0 || Q({ subject: t.subject }, n) ? e : l.fail(new c({ flag: n }));
506
- }, k = Object.keys(i).length > 0, A = (e) => {
522
+ }, M = Object.keys(i).length > 0, N = (e) => {
507
523
  for (let t of e) X(t, "bulk");
508
- }, j = {
524
+ }, P = {
509
525
  ...t,
510
526
  description: "Resolve every feature flag for the calling subject.",
511
527
  execute: (e, t) => l.sync(() => {
512
- let e = R.all();
513
- return A(Object.keys(e)), ne(e, t.request.subject ?? {});
528
+ let e = z.all();
529
+ return N(Object.keys(e)), te(e, t.request.subject ?? {});
514
530
  })
515
- }, te = {
531
+ }, F = {
516
532
  ...n,
517
533
  description: "Resolve every feature flag to its served variant for the calling subject.",
518
534
  execute: (e, t) => l.sync(() => {
519
- let e = R.all();
520
- return A(Object.keys(e)), re(e, t.request.subject ?? {});
535
+ let e = z.all();
536
+ return N(Object.keys(e)), ne(e, t.request.subject ?? {});
521
537
  })
522
- }, M = (e) => ({
538
+ }, I = (e) => ({
523
539
  kind: "json",
524
540
  data: e
525
- }), N = (e, t) => ({
541
+ }), B = (e, t) => ({
526
542
  kind: "json",
527
543
  status: e,
528
544
  data: { error: t }
529
- }), P = async () => {
545
+ }), ie = async () => {
530
546
  let e = [];
531
- if (S && v) try {
547
+ if (w && v) try {
532
548
  e = await v.load();
533
549
  } catch {
534
550
  e = [];
535
551
  }
536
552
  return fe({
537
- flags: R.all(),
553
+ flags: z.all(),
538
554
  usage: e,
539
- tracking: S,
555
+ tracking: w,
540
556
  liveCounts: Y.liveTargetedCounts(),
541
- staleAfterDays: C,
542
- retentionDays: w
557
+ staleAfterDays: T,
558
+ retentionDays: E
543
559
  });
544
- }, F = () => {
560
+ }, U = () => {
545
561
  let e = {};
546
562
  for (let [t, n] of f) e[t] = {
547
563
  valueType: n.valueType,
@@ -549,19 +565,20 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
549
565
  experiment: n.experiment ?? null
550
566
  };
551
567
  return e;
552
- }, ae = [
568
+ }, oe = [
553
569
  {
554
570
  method: "GET",
555
571
  path: "/list",
556
572
  scope: "process",
557
573
  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.",
558
574
  handler: () => l.promise(async () => {
559
- let e = await P();
560
- return M({
561
- flags: B(R.all()),
575
+ let e = await ie();
576
+ return I({
577
+ flags: H(z.all()),
578
+ writeScope: m ? "shared-store" : "process",
562
579
  lifecycle: e,
563
- typed: F(),
564
- overrideRefusals: b
580
+ typed: U(),
581
+ overrideRefusals: S
565
582
  });
566
583
  })
567
584
  },
@@ -574,21 +591,21 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
574
591
  try {
575
592
  t = JSON.parse(e.body || "{}");
576
593
  } catch {
577
- return N(400, "invalid JSON body");
594
+ return B(400, "invalid JSON body");
578
595
  }
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}"`);
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}"`);
582
599
  if (m) {
583
600
  let e = h;
584
- if (!e) return N(503, "flag store is not ready");
601
+ if (!e) return B(503, "flag store is not ready");
585
602
  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({
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({
590
607
  ok: !0,
591
- flags: B(R.all())
608
+ flags: H(z.all())
592
609
  });
593
610
  })
594
611
  },
@@ -598,7 +615,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
598
615
  scope: m ? "shared-store" : "declaration",
599
616
  description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
600
617
  handler: (e) => l.gen(function* () {
601
- if (!_) return M({
618
+ if (!_) return I({
602
619
  audit: [],
603
620
  note: "audit trail requires store: \"postgres\""
604
621
  });
@@ -606,7 +623,7 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
606
623
  ...n == null ? {} : { flag: n },
607
624
  limit: i
608
625
  }));
609
- return M({ audit: a.map((e) => ({
626
+ return I({ audit: a.map((e) => ({
610
627
  flag: e.flag,
611
628
  enabled: e.enabled,
612
629
  previousEnabled: e.previousEnabled,
@@ -615,8 +632,8 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
615
632
  })) });
616
633
  })
617
634
  }
618
- ], V = [
619
- ...k ? [
635
+ ], W = [
636
+ ...M ? [
620
637
  "rpc:intercept:mutation",
621
638
  "rpc:intercept:query",
622
639
  "rpc:intercept:action",
@@ -630,10 +647,10 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
630
647
  name: o,
631
648
  baseName: $,
632
649
  description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
633
- permissions: V,
634
- routes: [j, te],
650
+ permissions: W,
651
+ routes: [P, F],
635
652
  rpcClientDescriptors: r,
636
- inspectEndpoints: ae,
653
+ inspectEndpoints: oe,
637
654
  errorSchemas: [{
638
655
  schema: c,
639
656
  import: {
@@ -641,45 +658,34 @@ var w = (e) => typeof e == "boolean" ? { enabled: e } : e, T = (e, t) => {
641
658
  name: "FlagDisabled"
642
659
  }
643
660
  }],
644
- ...k ? {
645
- interceptMutation: O,
646
- interceptQuery: O,
647
- interceptAction: O,
648
- interceptWorkflow: O
661
+ ...M ? {
662
+ interceptMutation: j,
663
+ interceptQuery: j,
664
+ interceptAction: j,
665
+ interceptWorkflow: j
649
666
  } : {},
650
- ...m ? { extendSchema: { tables: S ? [
651
- I,
667
+ ...m ? { extendSchema: { tables: w ? [
652
668
  L,
669
+ R,
653
670
  J
654
- ] : [I, L] } } : {},
655
- ...m ? { bindDataStore: (e) => {
656
- h = e, g = oe(h), _ = H(e), S && (v = _e(e));
671
+ ] : [L, R] } } : {},
672
+ ...m ? { bindDataStore: async (e) => {
673
+ h = e, g = ae(h), _ = se(e), w && (v = _e(e)), await k();
657
674
  } } : {},
658
- ...m ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => D()) : l.void } : {},
675
+ ...m ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? l.tryPromise(() => O()) : l.void } : {},
659
676
  onActivate: (t) => l.gen(function* () {
660
- R.set(p), m && g && (yield* l.promise(D));
661
- let n;
662
- S && v && (n = be(process.env, w), Y.enable(), y = ye({
663
- store: v,
664
- intervalMs: x.flushIntervalMs ?? 3e5,
665
- log: { warn: (e, n) => t.logger.warn(e, n) }
666
- }));
667
- for (let e of b) t.logger.warn("feature flag override REFUSED — the code-declared definition stands", {
668
- flag: e.key,
669
- reason: e.reason
670
- });
671
- t.logger.info("feature flags active", {
677
+ z.set(p), b = t, g && (yield* l.promise(k)), t.logger.info("feature flags active", {
672
678
  count: Object.keys(p).length,
673
679
  typed: f.size,
674
680
  gated: Object.keys(i).length,
675
681
  store: e.store ?? "memory",
676
- usage: S ? `tracking (stale after ${C}d, observations kept ${Math.round((n ?? 0) / 864e5)}d)` : "off — no dead-flag evidence is collected"
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"
677
683
  });
678
684
  }),
679
685
  onDeactivate: () => l.promise(async () => {
680
- await y?.stop(), y = void 0, Y.disable(), R.set({});
686
+ b = void 0, await y?.stop(), y = void 0, Y.disable(), z.set({});
681
687
  })
682
688
  });
683
689
  };
684
690
  //#endregion
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 };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-flags",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "Feature flags — per-subject / per-tenant targeting, deterministic % rollouts, kill-switch. Gate mutations/queries/actions declaratively or guard in-handler; evaluate flags client-side for UI gating. Config-as-code (memory) or runtime-toggleable (postgres).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -48,10 +48,10 @@
48
48
  "node": ">=24.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@voltro/client": "0.81.0",
52
- "@voltro/database": "0.81.0",
53
- "@voltro/logger": "0.81.0",
54
- "@voltro/protocol": "0.81.0"
51
+ "@voltro/client": "0.83.0",
52
+ "@voltro/database": "0.83.0",
53
+ "@voltro/logger": "0.83.0",
54
+ "@voltro/protocol": "0.83.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "effect": "^3.22.2"