@voltro/plugin-flags 0.85.0 → 0.86.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/CHANGELOG.md +666 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/{define-BHpLJh1F.js → define.js} +1 -1
- package/dist/evaluate.js +81 -0
- package/dist/experimentLink.js +48 -0
- package/dist/index.js +143 -593
- package/dist/lifecycle.js +82 -0
- package/dist/store.js +154 -0
- package/dist/usage.js +113 -0
- package/dist/web.js +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,586 +1,136 @@
|
|
|
1
1
|
import { BASE_NAME as e, evaluateDescriptor as t, evaluateVariantsDescriptor as n, flagsRpcClientImports as r } from "./rpc.js";
|
|
2
|
-
import { i,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
active: !0,
|
|
25
|
-
rollout: n
|
|
26
|
-
};
|
|
27
|
-
if (t >= s || s <= o) return {
|
|
28
|
-
active: !0,
|
|
29
|
-
rollout: r
|
|
30
|
-
};
|
|
31
|
-
let c = (t - o) / (s - o);
|
|
32
|
-
return {
|
|
33
|
-
active: !0,
|
|
34
|
-
rollout: n + (r - n) * c
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return { active: !0 };
|
|
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);
|
|
46
|
-
if (!e.active) return !1;
|
|
47
|
-
i = e.rollout;
|
|
48
|
-
}
|
|
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) => {
|
|
56
|
-
let t = e.reduce((e, t) => e + (t.weight ?? 1), 0) || e.length, n = 0;
|
|
57
|
-
return e.map((r, i) => (n += r.weight ?? 1, i === e.length - 1 ? 1e4 : Math.round(n / t * 1e4)));
|
|
58
|
-
}, I = (e, t, n) => {
|
|
59
|
-
let r = t.variants ?? [];
|
|
60
|
-
if (r.length === 0) throw Error(`flag "${e}" has no variants`);
|
|
61
|
-
let i = O(e, j(t, n)), a = F(r);
|
|
62
|
-
for (let e = 0; e < r.length; e++) if (i < a[e]) return r[e];
|
|
63
|
-
return r[r.length - 1];
|
|
64
|
-
}, L = (e, t, n, r = Date.now()) => {
|
|
65
|
-
let i = T(t), a = P(e, i, n, r);
|
|
66
|
-
if (!i.variants || i.variants.length === 0) return {
|
|
67
|
-
name: a ? "on" : "off",
|
|
68
|
-
value: a,
|
|
69
|
-
enabled: a
|
|
70
|
-
};
|
|
71
|
-
let o = I(e, i, n);
|
|
72
|
-
return {
|
|
73
|
-
name: o.name,
|
|
74
|
-
value: o.value,
|
|
75
|
-
enabled: a && o.name !== i.offVariant
|
|
76
|
-
};
|
|
77
|
-
}, ee = (e, t, n = Date.now()) => {
|
|
78
|
-
let r = {};
|
|
79
|
-
for (let [i, a] of Object.entries(e)) r[i] = N(i, a, t, n);
|
|
80
|
-
return r;
|
|
81
|
-
}, te = (e, t, n = Date.now()) => {
|
|
82
|
-
let r = {};
|
|
83
|
-
for (let [i, a] of Object.entries(e)) r[i] = L(i, a, t, n);
|
|
84
|
-
return r;
|
|
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) => {
|
|
105
|
-
let n = { ...e };
|
|
106
|
-
for (let e of t) {
|
|
107
|
-
let t = {
|
|
108
|
-
enabled: e.enabled,
|
|
109
|
-
...e.rollout == null ? {} : { rollout: e.rollout },
|
|
110
|
-
...e.rolloutBy === "tenant" || e.rolloutBy === "subject" ? { rolloutBy: e.rolloutBy } : {},
|
|
111
|
-
...Array.isArray(e.targeting) ? { targeting: e.targeting } : {},
|
|
112
|
-
...Array.isArray(e.variants) ? { variants: e.variants } : {},
|
|
113
|
-
...e.offVariant == null ? {} : { offVariant: e.offVariant },
|
|
114
|
-
...e.schedule != null && typeof e.schedule == "object" ? { schedule: e.schedule } : {},
|
|
115
|
-
...e.description == null ? {} : { description: e.description }
|
|
116
|
-
};
|
|
117
|
-
n[e.key] = t;
|
|
118
|
-
}
|
|
119
|
-
return n;
|
|
120
|
-
}, H = new class {
|
|
121
|
-
flags = {};
|
|
122
|
-
set(e) {
|
|
123
|
-
this.flags = e;
|
|
124
|
-
}
|
|
125
|
-
all() {
|
|
126
|
-
return this.flags;
|
|
127
|
-
}
|
|
128
|
-
get(e) {
|
|
129
|
-
return this.flags[e];
|
|
130
|
-
}
|
|
131
|
-
}(), re = (e, t) => U(e, t) !== null, U = (e, t) => {
|
|
132
|
-
let n = H.get(e);
|
|
133
|
-
if (n === void 0) return null;
|
|
134
|
-
let r = T(n), i = r.enabled !== !1;
|
|
135
|
-
return H.set({
|
|
136
|
-
...H.all(),
|
|
137
|
-
[e]: {
|
|
138
|
-
...r,
|
|
139
|
-
enabled: t
|
|
140
|
-
}
|
|
141
|
-
}), i;
|
|
142
|
-
}, W = (e) => Object.entries(e).map(([e, t]) => {
|
|
143
|
-
let n = T(t);
|
|
144
|
-
return {
|
|
145
|
-
key: e,
|
|
146
|
-
enabled: n.enabled !== !1,
|
|
147
|
-
rollout: n.rollout ?? 100,
|
|
148
|
-
variants: n.variants && n.variants.length > 0 ? n.variants.map((e) => e.name) : void 0,
|
|
149
|
-
scheduled: n.schedule != null,
|
|
150
|
-
description: n.description
|
|
151
|
-
};
|
|
152
|
-
}), ie = (e, t) => {
|
|
153
|
-
let n = T(t);
|
|
154
|
-
return {
|
|
155
|
-
key: e,
|
|
156
|
-
enabled: n.enabled !== !1,
|
|
157
|
-
rollout: n.rollout ?? null,
|
|
158
|
-
rolloutBy: n.rolloutBy ?? null,
|
|
159
|
-
targeting: n.targeting ?? null,
|
|
160
|
-
variants: n.variants ?? null,
|
|
161
|
-
offVariant: n.offVariant ?? null,
|
|
162
|
-
schedule: n.schedule ?? null,
|
|
163
|
-
description: n.description ?? null
|
|
164
|
-
};
|
|
165
|
-
}, ae = (e) => ({
|
|
166
|
-
loadOverrides: async () => (await e.query(y(B).descriptor)).map((e) => ({
|
|
167
|
-
key: String(e.key),
|
|
168
|
-
enabled: e.enabled === !0 || e.enabled === 1,
|
|
169
|
-
rollout: e.rollout == null ? null : Number(e.rollout),
|
|
170
|
-
rolloutBy: e.rolloutBy ?? null,
|
|
171
|
-
targeting: e.targeting ?? null,
|
|
172
|
-
variants: e.variants ?? null,
|
|
173
|
-
offVariant: e.offVariant ?? null,
|
|
174
|
-
schedule: e.schedule ?? null,
|
|
175
|
-
description: e.description ?? null
|
|
176
|
-
})),
|
|
177
|
-
upsertOverride: async (t) => {
|
|
178
|
-
let n = G(t);
|
|
179
|
-
await e.upsert(z, n, { conflictColumns: ["key"] });
|
|
180
|
-
}
|
|
181
|
-
}), G = (e) => ({
|
|
182
|
-
id: m("flag", e.key),
|
|
183
|
-
key: e.key,
|
|
184
|
-
enabled: e.enabled,
|
|
185
|
-
rollout: e.rollout,
|
|
186
|
-
rolloutBy: e.rolloutBy,
|
|
187
|
-
targeting: e.targeting,
|
|
188
|
-
variants: e.variants,
|
|
189
|
-
offVariant: e.offVariant,
|
|
190
|
-
schedule: e.schedule,
|
|
191
|
-
description: e.description,
|
|
192
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
193
|
-
}), oe = (e) => ({
|
|
194
|
-
flag: String(e.flag),
|
|
195
|
-
enabled: e.enabled === !0 || e.enabled === 1,
|
|
196
|
-
previousEnabled: e.previousEnabled == null ? null : e.previousEnabled === !0 || e.previousEnabled === 1,
|
|
197
|
-
actorId: e.actorId ?? null,
|
|
198
|
-
at: e.at instanceof Date ? e.at : new Date(String(e.at))
|
|
199
|
-
}), se = (e) => ({
|
|
200
|
-
record: async (t) => {
|
|
201
|
-
await e.insert(R, {
|
|
202
|
-
id: `flagaud_${t.at.getTime()}_${Math.random().toString(36).slice(2, 10)}`,
|
|
203
|
-
flag: t.flag,
|
|
204
|
-
enabled: t.enabled,
|
|
205
|
-
previousEnabled: t.previousEnabled,
|
|
206
|
-
actorId: t.actorId,
|
|
207
|
-
at: t.at
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
list: async (t) => {
|
|
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);
|
|
213
|
-
}
|
|
214
|
-
}), ce = async (e, t, n, r, i) => {
|
|
215
|
-
await e.transactional(async (e) => {
|
|
216
|
-
await e.insertIgnore(z, {
|
|
217
|
-
...G(ie(t, n)),
|
|
218
|
-
id: m("flag", crypto.randomUUID())
|
|
219
|
-
}, { conflictColumns: ["key"] });
|
|
220
|
-
let a = (await e.query(y(B).where(h("key", t)).forUpdate().descriptor))[0];
|
|
221
|
-
if (!a) throw Error("flag override disappeared during toggle");
|
|
222
|
-
await e.update(z, String(a.id), {
|
|
223
|
-
enabled: r,
|
|
224
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
225
|
-
}), await se(e).record({
|
|
226
|
-
flag: t,
|
|
227
|
-
enabled: r,
|
|
228
|
-
previousEnabled: a.enabled === !0 || a.enabled === 1,
|
|
229
|
-
actorId: i,
|
|
230
|
-
at: /* @__PURE__ */ new Date()
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
}, le = (e, t) => {
|
|
234
|
-
let n = new Map(t.map((e) => [e.name, e])), r = [];
|
|
235
|
-
for (let t of e) {
|
|
236
|
-
let e = t.experiment;
|
|
237
|
-
if (e === void 0) continue;
|
|
238
|
-
let i = n.get(e);
|
|
239
|
-
if (i === void 0) {
|
|
240
|
-
r.push({
|
|
241
|
-
flag: t.key,
|
|
242
|
-
experiment: e,
|
|
243
|
-
problem: `no experiment named ${JSON.stringify(e)} was passed to flagsPlugin({ experiments }). Pass the defineExperiment() definitions the app declares, or drop the \`experiment\` link.`
|
|
244
|
-
});
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
(i.variantFrom === void 0 || i.variantFrom === null) && r.push({
|
|
248
|
-
flag: t.key,
|
|
249
|
-
experiment: e,
|
|
250
|
-
problem: `the experiment assigns its own arms (\`subject\` mode), so it would measure a split this flag never served — the two bucketings are independent hashes. Persist the served arm (\`flagVariant(ctx, ${JSON.stringify(t.key)})\`) on the watched table and declare the experiment with \`variantFrom: <that column>\`.`
|
|
251
|
-
}), typeof i.holdout == "number" && i.holdout > 0 && r.push({
|
|
252
|
-
flag: t.key,
|
|
253
|
-
experiment: e,
|
|
254
|
-
problem: `holdout ${i.holdout} is carved at ASSIGNMENT, and this flag does the assigning — the experiment can never place a row there. Declare the holdout as a flag variant instead.`
|
|
255
|
-
});
|
|
256
|
-
let a = new Set(t.variants.map((e) => e.name)), o = new Set(i.variants.map((e) => e.name));
|
|
257
|
-
if (a.size === 0) r.push({
|
|
258
|
-
flag: t.key,
|
|
259
|
-
experiment: e,
|
|
260
|
-
problem: "the flag declares no variants, so it serves one value to everyone and there is nothing to compare."
|
|
261
|
-
});
|
|
262
|
-
else {
|
|
263
|
-
let n = [...a].filter((e) => !o.has(e)), i = [...o].filter((e) => !a.has(e));
|
|
264
|
-
(n.length > 0 || i.length > 0) && r.push({
|
|
265
|
-
flag: t.key,
|
|
266
|
-
experiment: e,
|
|
267
|
-
problem: `the arms disagree — ${n.length > 0 ? `served by the flag but not declared by the experiment: ${n.join(", ")}` : ""}${n.length > 0 && i.length > 0 ? "; " : ""}${i.length > 0 ? `declared by the experiment but never served: ${i.join(", ")}` : ""}. A row assigned to an arm the experiment does not declare is EXCLUDED, so the mismatch shows up as an empty arm rather than an error.`
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return r;
|
|
272
|
-
}, ue = (e, t) => {
|
|
273
|
-
let n = le(e, t);
|
|
274
|
-
if (n.length === 0) return;
|
|
275
|
-
let r = n.map((e) => ` · flag "${e.flag}" → experiment "${e.experiment}": ${e.problem}`);
|
|
276
|
-
throw Error(`@voltro/plugin-flags: ${n.length} flag→experiment link problem(s):\n${r.join("\n")}`);
|
|
277
|
-
}, de = 864e5, K = (e) => typeof e == "number" ? e : Date.parse(e), fe = (e, t) => {
|
|
278
|
-
let n = T(e);
|
|
279
|
-
if (n.enabled === !1) return {
|
|
280
|
-
shape: "constantOff",
|
|
281
|
-
reason: "enabled: false — resolves false for every caller"
|
|
282
|
-
};
|
|
283
|
-
if (n.schedule) {
|
|
284
|
-
let { activateAt: e, deactivateAt: r } = n.schedule;
|
|
285
|
-
if (r != null && t >= K(r)) return {
|
|
286
|
-
shape: "expired",
|
|
287
|
-
reason: `schedule.deactivateAt (${String(r)}) has passed — the flag can never be on again`
|
|
288
|
-
};
|
|
289
|
-
if (e != null && t < K(e)) return {
|
|
290
|
-
shape: "notYetActive",
|
|
291
|
-
reason: `schedule.activateAt (${String(e)}) is in the future — pending, not dead`
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
let r = n.rollout ?? 100;
|
|
295
|
-
if (r <= 0) return {
|
|
296
|
-
shape: "constantOff",
|
|
297
|
-
reason: `rollout: ${r} — nobody is in the rollout`
|
|
298
|
-
};
|
|
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));
|
|
300
|
-
return r >= 100 && !i && !a && !o ? {
|
|
301
|
-
shape: "constantOn",
|
|
302
|
-
reason: "rollout 100 with no targeting, no variants and no live schedule — resolves true for every caller"
|
|
303
|
-
} : {
|
|
304
|
-
shape: "conditional",
|
|
305
|
-
reason: `still selects between callers (${[
|
|
306
|
-
r < 100 ? `rollout ${r}%` : void 0,
|
|
307
|
-
i ? `${n.targeting?.length} targeting rule(s)` : void 0,
|
|
308
|
-
a ? `${n.variants?.length} variants` : void 0,
|
|
309
|
-
o ? "a live schedule" : void 0
|
|
310
|
-
].filter((e) => e !== void 0).join(", ")})`
|
|
311
|
-
};
|
|
312
|
-
}, pe = [
|
|
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.",
|
|
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.",
|
|
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.",
|
|
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.",
|
|
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."
|
|
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;
|
|
320
|
-
for (let t of e.usage) {
|
|
321
|
-
let e = t.lastAt.getTime();
|
|
322
|
-
if (!Number.isFinite(e)) continue;
|
|
323
|
-
(o === void 0 || e < o) && (o = e);
|
|
324
|
-
let n = t.source === "bulk" ? a : i, r = n.get(t.flag);
|
|
325
|
-
(r === void 0 || e > r) && n.set(t.flag, e);
|
|
326
|
-
}
|
|
327
|
-
let s = o === void 0 ? 0 : Math.floor((t - o) / de);
|
|
328
|
-
return {
|
|
329
|
-
coverage: {
|
|
330
|
-
tracking: e.tracking,
|
|
331
|
-
observedSince: o === void 0 ? null : new Date(o).toISOString().slice(0, 10),
|
|
332
|
-
observedDays: s,
|
|
333
|
-
retentionDays: e.retentionDays,
|
|
334
|
-
staleAfterDays: n
|
|
335
|
-
},
|
|
336
|
-
findings: Object.entries(e.flags).map(([o, c]) => {
|
|
337
|
-
let { shape: l, reason: u } = fe(c, t), d = i.get(o), f = a.get(o), p = [u], m;
|
|
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"));
|
|
339
|
-
let h = l === "constantOn" || l === "constantOff" || l === "expired";
|
|
340
|
-
return h && p.push("the definition alone makes this flag a constant — it selects nothing"), {
|
|
341
|
-
key: o,
|
|
342
|
-
shape: l,
|
|
343
|
-
shapeReason: u,
|
|
344
|
-
usage: m,
|
|
345
|
-
lastTargetedAt: d === void 0 ? null : new Date(d).toISOString(),
|
|
346
|
-
lastBulkAt: f === void 0 ? null : new Date(f).toISOString(),
|
|
347
|
-
liveTargetedEvaluations: e.liveCounts?.get(o) ?? 0,
|
|
348
|
-
removalCandidate: h || m === "stale",
|
|
349
|
-
why: p
|
|
350
|
-
};
|
|
351
|
-
}),
|
|
352
|
-
limits: pe
|
|
353
|
-
};
|
|
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()
|
|
360
|
-
}).unique([
|
|
361
|
-
"flag",
|
|
362
|
-
"day",
|
|
363
|
-
"source"
|
|
364
|
-
]).index("byFlagUsageLastAt", ["lastAt"]).index("byFlagUsageFlag", ["flag"]), he = (e) => e.toISOString().slice(0, 10), ge = (e) => {
|
|
365
|
-
let t = 2166136261;
|
|
366
|
-
for (let n = 0; n < e.length; n++) t ^= e.charCodeAt(n), t = Math.imul(t, 16777619);
|
|
367
|
-
return (t >>> 0).toString(16).padStart(8, "0");
|
|
368
|
-
}, _e = (e, t, n) => `flagusg_${t.replace(/-/g, "")}_${n === "targeted" ? "t" : "b"}_${ge(e)}`, Y = new class {
|
|
369
|
-
on = !1;
|
|
370
|
-
pending = /* @__PURE__ */ new Map();
|
|
371
|
-
liveCounts = /* @__PURE__ */ new Map();
|
|
372
|
-
enable() {
|
|
373
|
-
this.on = !0;
|
|
374
|
-
}
|
|
375
|
-
disable() {
|
|
376
|
-
this.on = !1, this.pending.clear();
|
|
377
|
-
}
|
|
378
|
-
get enabled() {
|
|
379
|
-
return this.on;
|
|
380
|
-
}
|
|
381
|
-
record(e, t, n = Date.now()) {
|
|
382
|
-
if (!this.on) return;
|
|
383
|
-
let r = `${t}\u0000${e}`, i = this.pending.get(r);
|
|
384
|
-
i ? i.lastAt = n : this.pending.set(r, {
|
|
385
|
-
flag: e,
|
|
386
|
-
source: t,
|
|
387
|
-
lastAt: n
|
|
388
|
-
}), t === "targeted" && this.liveCounts.set(e, (this.liveCounts.get(e) ?? 0) + 1);
|
|
389
|
-
}
|
|
390
|
-
drain() {
|
|
391
|
-
let e = [...this.pending.values()];
|
|
392
|
-
return this.pending.clear(), e;
|
|
393
|
-
}
|
|
394
|
-
restore(e) {
|
|
395
|
-
if (this.on) for (let t of e) {
|
|
396
|
-
let e = `${t.source}\u0000${t.flag}`, n = this.pending.get(e);
|
|
397
|
-
(!n || n.lastAt < t.lastAt) && this.pending.set(e, { ...t });
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
liveTargetedCounts() {
|
|
401
|
-
return this.liveCounts;
|
|
402
|
-
}
|
|
403
|
-
reset() {
|
|
404
|
-
this.on = !1, this.pending.clear(), this.liveCounts.clear();
|
|
405
|
-
}
|
|
406
|
-
}(), X = (e, t, n) => Y.record(e, t, n), ve = (e) => e === "bulk" ? "bulk" : "targeted", ye = (e) => ({
|
|
407
|
-
upsertMany: async (t) => {
|
|
408
|
-
for (let n of t) {
|
|
409
|
-
let t = he(n.lastAt);
|
|
410
|
-
await e.upsert(q, {
|
|
411
|
-
id: _e(n.flag, t, n.source),
|
|
412
|
-
flag: n.flag,
|
|
413
|
-
day: t,
|
|
414
|
-
source: n.source,
|
|
415
|
-
lastAt: n.lastAt
|
|
416
|
-
}, { conflictColumns: [
|
|
417
|
-
"flag",
|
|
418
|
-
"day",
|
|
419
|
-
"source"
|
|
420
|
-
] });
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
load: async () => (await e.query(y(J).descriptor)).map((e) => ({
|
|
424
|
-
flag: String(e.flag),
|
|
425
|
-
day: String(e.day),
|
|
426
|
-
source: ve(e.source),
|
|
427
|
-
lastAt: e.lastAt instanceof Date ? e.lastAt : new Date(String(e.lastAt))
|
|
428
|
-
}))
|
|
429
|
-
}), be = 3e5, xe = (e) => {
|
|
430
|
-
let t = e.intervalMs ?? 3e5, n = !1, r = async () => {
|
|
431
|
-
let t = Y.drain();
|
|
432
|
-
if (t.length !== 0) try {
|
|
433
|
-
await e.store.upsertMany(t.map((e) => ({
|
|
434
|
-
flag: e.flag,
|
|
435
|
-
source: e.source,
|
|
436
|
-
lastAt: new Date(e.lastAt)
|
|
437
|
-
})));
|
|
438
|
-
} catch (n) {
|
|
439
|
-
Y.restore(t), e.log?.warn?.("flag usage flush failed — observations retained for the next pass", {
|
|
440
|
-
pending: t.length,
|
|
441
|
-
cause: n.message
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}, i = setInterval(() => {
|
|
445
|
-
r();
|
|
446
|
-
}, t);
|
|
447
|
-
return typeof i.unref == "function" && i.unref(), {
|
|
448
|
-
flushNow: r,
|
|
449
|
-
stop: async () => {
|
|
450
|
-
n || (n = !0, clearInterval(i), await r());
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
}, Se = (e = process.env, t = 90) => {
|
|
454
|
-
let n = x(e.VOLTRO_FLAG_USAGE_TTL_HOURS, 24 * t);
|
|
455
|
-
return b({
|
|
456
|
-
table: q,
|
|
457
|
-
timeColumn: "lastAt",
|
|
458
|
-
source: "plugin",
|
|
459
|
-
ttlMs: n,
|
|
460
|
-
envVar: "VOLTRO_FLAG_USAGE_TTL_HOURS"
|
|
461
|
-
}), n;
|
|
462
|
-
}, Z = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, Q = (e, t) => {
|
|
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({
|
|
473
|
-
base: $,
|
|
2
|
+
import { evaluateAll as i, evaluateAllVariants as a, evaluateFlag as o, evaluateSchedule as s, evaluateVariant as c, resolveVariant as l, toDefinition as u } from "./evaluate.js";
|
|
3
|
+
import { dataStoreFlagAuditStore as ee, dataStoreFlagStore as d, describeFlags as f, featureFlagsTable as p, flagAuditTable as m, flipFlagEnabled as h, mergeOverrides as g, persistFlagToggle as te, registry as _, setFlagEnabled as v } from "./store.js";
|
|
4
|
+
import { defineFlag as y, gateOverride as b, isTypedFlag as x, typedValueOf as S } from "./define.js";
|
|
5
|
+
import { assertFlagExperimentLinks as C, checkFlagExperimentLinks as w } from "./experimentLink.js";
|
|
6
|
+
import { FLAG_LIFECYCLE_LIMITS as T, classifyShape as E, flagLifecycleReport as D } from "./lifecycle.js";
|
|
7
|
+
import { DEFAULT_FLAG_USAGE_FLUSH_MS as O, FLAG_USAGE_TABLE as k, dataStoreFlagUsageStore as A, flagUsageBuffer as j, flagUsageRowId as M, flagUsageTable as N, recordFlagEvaluation as P, registerFlagUsageRetention as F, startFlagUsageFlush as I, utcDay as L } from "./usage.js";
|
|
8
|
+
import { FlagDisabled as R } from "./errors.js";
|
|
9
|
+
import { Effect as z, Result as B } from "effect";
|
|
10
|
+
import { definePlugin as ne, pluginInstanceName as re } from "@voltro/protocol";
|
|
11
|
+
//#region src/index.ts
|
|
12
|
+
var V = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, H = (e, t) => {
|
|
13
|
+
let n = _.get(t);
|
|
14
|
+
return P(t, "targeted"), n !== void 0 && o(t, n, V(e));
|
|
15
|
+
}, U = (e, t) => H(e, t) ? z.void : z.fail(new R({ flag: t })), W = (e, t) => {
|
|
16
|
+
let n = _.get(t.key);
|
|
17
|
+
return P(t.key, "targeted"), n === void 0 ? t.defaultValue : S(t, c(t.key, n, V(e)));
|
|
18
|
+
}, G = (e, t) => {
|
|
19
|
+
let n = _.get(t.key);
|
|
20
|
+
return P(t.key, "targeted"), n === void 0 ? null : c(t.key, n, V(e)).name;
|
|
21
|
+
}, ie = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), K = e, q = (e = {}) => {
|
|
22
|
+
let o = e.gatedBy ?? {}, s = re({
|
|
23
|
+
base: K,
|
|
474
24
|
alias: e.alias,
|
|
475
25
|
instance: e.name
|
|
476
|
-
}),
|
|
477
|
-
for (let t of
|
|
478
|
-
if (
|
|
479
|
-
if (e.flags?.[t.key] !== void 0) throw Error(`${
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
let
|
|
484
|
-
for (let e of
|
|
485
|
-
let
|
|
486
|
-
if (
|
|
487
|
-
let
|
|
488
|
-
if (
|
|
489
|
-
let
|
|
490
|
-
let t =
|
|
491
|
-
for (let [e, r] of
|
|
26
|
+
}), c = e.typedFlags ?? [], l = /* @__PURE__ */ new Map();
|
|
27
|
+
for (let t of c) {
|
|
28
|
+
if (l.has(t.key)) throw Error(`${K}: two typedFlags declare the key ${JSON.stringify(t.key)}`);
|
|
29
|
+
if (e.flags?.[t.key] !== void 0) throw Error(`${K}: ${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.`);
|
|
30
|
+
l.set(t.key, t);
|
|
31
|
+
}
|
|
32
|
+
c.some((e) => e.experiment !== void 0) && C(c, e.experiments ?? []);
|
|
33
|
+
let u = { ...e.flags ?? {} };
|
|
34
|
+
for (let e of c) u[e.key] = e.definition;
|
|
35
|
+
let v = e.store === "postgres", y, x, S, w, T, E, O, k = [], M = e.usage ?? {}, L = M.track ?? v;
|
|
36
|
+
if (L && !v) throw Error(`${K}: 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.`);
|
|
37
|
+
let V = M.staleAfterDays ?? 30, U = M.retentionDays ?? 90;
|
|
38
|
+
if (V > U) throw Error(`${K}: usage.staleAfterDays (${V}) exceeds usage.retentionDays (${U}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
|
|
39
|
+
let W = (e) => {
|
|
40
|
+
let t = g(u, e), n = [];
|
|
41
|
+
for (let [e, r] of l) {
|
|
492
42
|
let i = t[e];
|
|
493
43
|
if (i === void 0) continue;
|
|
494
|
-
let
|
|
495
|
-
|
|
44
|
+
let a = b(r, typeof i == "boolean" ? { enabled: i } : i);
|
|
45
|
+
a.ok || (n.push(a.refusal), t[e] = r.definition);
|
|
496
46
|
}
|
|
497
|
-
|
|
498
|
-
},
|
|
499
|
-
if (!
|
|
500
|
-
let e = ++
|
|
501
|
-
e ===
|
|
502
|
-
},
|
|
503
|
-
if (
|
|
504
|
-
if (await
|
|
505
|
-
|
|
506
|
-
let e =
|
|
507
|
-
|
|
508
|
-
store:
|
|
509
|
-
intervalMs:
|
|
47
|
+
k = n, _.set(t);
|
|
48
|
+
}, G = 0, q = async () => {
|
|
49
|
+
if (!x) return;
|
|
50
|
+
let e = ++G, t = await x.loadOverrides();
|
|
51
|
+
e === G && W(t);
|
|
52
|
+
}, J = async () => {
|
|
53
|
+
if (E && x) {
|
|
54
|
+
if (await q(), L && w && !T) {
|
|
55
|
+
O = F(process.env, U), j.enable();
|
|
56
|
+
let e = E.logger;
|
|
57
|
+
T = I({
|
|
58
|
+
store: w,
|
|
59
|
+
intervalMs: M.flushIntervalMs ?? 3e5,
|
|
510
60
|
log: { warn: (t, n) => e.warn(t, n) }
|
|
511
61
|
});
|
|
512
62
|
}
|
|
513
|
-
for (let e of
|
|
63
|
+
for (let e of k) E.logger.warn("feature flag override REFUSED — code-declared definition stands", {
|
|
514
64
|
flag: e.key,
|
|
515
65
|
reason: e.reason
|
|
516
66
|
});
|
|
517
67
|
}
|
|
518
|
-
},
|
|
519
|
-
if (
|
|
520
|
-
for (let [t, n] of Object.entries(
|
|
521
|
-
},
|
|
522
|
-
let n =
|
|
523
|
-
return n === void 0 ||
|
|
524
|
-
},
|
|
525
|
-
for (let t of e)
|
|
526
|
-
},
|
|
68
|
+
}, ae = (e) => {
|
|
69
|
+
if (o[e] !== void 0) return o[e];
|
|
70
|
+
for (let [t, n] of Object.entries(o)) if (t !== e && ie(t, e)) return n;
|
|
71
|
+
}, Y = (e, t) => {
|
|
72
|
+
let n = ae(t.tag);
|
|
73
|
+
return n === void 0 || H({ subject: t.subject }, n) ? e : z.fail(new R({ flag: n }));
|
|
74
|
+
}, X = Object.keys(o).length > 0, Z = (e) => {
|
|
75
|
+
for (let t of e) P(t, "bulk");
|
|
76
|
+
}, oe = {
|
|
527
77
|
...t,
|
|
528
78
|
description: "Resolve every feature flag for the calling subject.",
|
|
529
|
-
execute: (e, t) =>
|
|
530
|
-
let e =
|
|
531
|
-
return
|
|
79
|
+
execute: (e, t) => z.sync(() => {
|
|
80
|
+
let e = _.all();
|
|
81
|
+
return Z(Object.keys(e)), i(e, t.request.subject ?? {});
|
|
532
82
|
})
|
|
533
|
-
},
|
|
83
|
+
}, se = {
|
|
534
84
|
...n,
|
|
535
85
|
description: "Resolve every feature flag to its served variant for the calling subject.",
|
|
536
|
-
execute: (e, t) =>
|
|
537
|
-
let e =
|
|
538
|
-
return
|
|
86
|
+
execute: (e, t) => z.sync(() => {
|
|
87
|
+
let e = _.all();
|
|
88
|
+
return Z(Object.keys(e)), a(e, t.request.subject ?? {});
|
|
539
89
|
})
|
|
540
|
-
},
|
|
90
|
+
}, Q = (e) => ({
|
|
541
91
|
kind: "json",
|
|
542
92
|
data: e
|
|
543
|
-
}),
|
|
93
|
+
}), $ = (e, t) => ({
|
|
544
94
|
kind: "json",
|
|
545
95
|
status: e,
|
|
546
96
|
data: { error: t }
|
|
547
|
-
}),
|
|
97
|
+
}), ce = async () => {
|
|
548
98
|
let e = [];
|
|
549
|
-
if (
|
|
550
|
-
e = await
|
|
99
|
+
if (L && w) try {
|
|
100
|
+
e = await w.load();
|
|
551
101
|
} catch {
|
|
552
102
|
e = [];
|
|
553
103
|
}
|
|
554
|
-
return
|
|
555
|
-
flags:
|
|
104
|
+
return D({
|
|
105
|
+
flags: _.all(),
|
|
556
106
|
usage: e,
|
|
557
|
-
tracking:
|
|
558
|
-
liveCounts:
|
|
559
|
-
staleAfterDays:
|
|
560
|
-
retentionDays:
|
|
107
|
+
tracking: L,
|
|
108
|
+
liveCounts: j.liveTargetedCounts(),
|
|
109
|
+
staleAfterDays: V,
|
|
110
|
+
retentionDays: U
|
|
561
111
|
});
|
|
562
|
-
},
|
|
112
|
+
}, le = () => {
|
|
563
113
|
let e = {};
|
|
564
|
-
for (let [t, n] of
|
|
114
|
+
for (let [t, n] of l) e[t] = {
|
|
565
115
|
valueType: n.valueType,
|
|
566
116
|
variants: n.variants.map((e) => e.name),
|
|
567
117
|
experiment: n.experiment ?? null
|
|
568
118
|
};
|
|
569
119
|
return e;
|
|
570
|
-
},
|
|
120
|
+
}, ue = [
|
|
571
121
|
{
|
|
572
122
|
method: "GET",
|
|
573
123
|
path: "/list",
|
|
574
124
|
scope: "process",
|
|
575
125
|
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.",
|
|
576
|
-
handler: () =>
|
|
577
|
-
let e = await
|
|
578
|
-
return
|
|
579
|
-
flags:
|
|
580
|
-
writeScope:
|
|
126
|
+
handler: () => z.promise(async () => {
|
|
127
|
+
let e = await ce();
|
|
128
|
+
return Q({
|
|
129
|
+
flags: f(_.all()),
|
|
130
|
+
writeScope: v ? "shared-store" : "process",
|
|
581
131
|
lifecycle: e,
|
|
582
|
-
typed:
|
|
583
|
-
overrideRefusals:
|
|
132
|
+
typed: le(),
|
|
133
|
+
overrideRefusals: k
|
|
584
134
|
});
|
|
585
135
|
})
|
|
586
136
|
},
|
|
@@ -588,45 +138,45 @@ var T = (e) => typeof e == "boolean" ? { enabled: e } : e, E = (e, t) => {
|
|
|
588
138
|
method: "POST",
|
|
589
139
|
path: "/toggle",
|
|
590
140
|
description: "Flip a flag's kill-switch on/off (live registry; durable on the postgres tier). Logs an audit row.",
|
|
591
|
-
handler: (e) =>
|
|
141
|
+
handler: (e) => z.gen(function* () {
|
|
592
142
|
let t;
|
|
593
143
|
try {
|
|
594
144
|
t = JSON.parse(e.body || "{}");
|
|
595
145
|
} catch {
|
|
596
|
-
return
|
|
146
|
+
return $(400, "invalid JSON body");
|
|
597
147
|
}
|
|
598
|
-
if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return
|
|
599
|
-
let n = t.key, r = t.enabled, i =
|
|
600
|
-
if (i === void 0) return
|
|
601
|
-
if (
|
|
602
|
-
let e =
|
|
603
|
-
if (!e) return
|
|
604
|
-
let a = yield*
|
|
605
|
-
await
|
|
606
|
-
}).pipe(
|
|
607
|
-
if (
|
|
608
|
-
} else
|
|
609
|
-
return
|
|
148
|
+
if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return $(400, "key (string) + enabled (boolean) required");
|
|
149
|
+
let n = t.key, r = t.enabled, i = _.get(n);
|
|
150
|
+
if (i === void 0) return $(404, `unknown flag "${n}"`);
|
|
151
|
+
if (v) {
|
|
152
|
+
let e = y;
|
|
153
|
+
if (!e) return $(503, "flag store is not ready");
|
|
154
|
+
let a = yield* z.tryPromise(async () => {
|
|
155
|
+
await te(e, n, i, r, typeof t.actor == "string" ? t.actor : null), await q();
|
|
156
|
+
}).pipe(z.result);
|
|
157
|
+
if (B.isFailure(a)) return yield* z.tryPromise(q).pipe(z.ignore), $(503, "flag update could not be confirmed; reload the flag state before retrying");
|
|
158
|
+
} else h(n, r);
|
|
159
|
+
return Q({
|
|
610
160
|
ok: !0,
|
|
611
|
-
flags:
|
|
161
|
+
flags: f(_.all())
|
|
612
162
|
});
|
|
613
163
|
})
|
|
614
164
|
},
|
|
615
165
|
{
|
|
616
166
|
method: "GET",
|
|
617
167
|
path: "/audit",
|
|
618
|
-
scope:
|
|
168
|
+
scope: v ? "shared-store" : "declaration",
|
|
619
169
|
description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
|
|
620
|
-
handler: (e) =>
|
|
621
|
-
if (!
|
|
170
|
+
handler: (e) => z.gen(function* () {
|
|
171
|
+
if (!S) return Q({
|
|
622
172
|
audit: [],
|
|
623
173
|
note: "audit trail requires store: \"postgres\""
|
|
624
174
|
});
|
|
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*
|
|
175
|
+
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* z.promise(() => S.list({
|
|
626
176
|
...n == null ? {} : { flag: n },
|
|
627
177
|
limit: i
|
|
628
178
|
}));
|
|
629
|
-
return
|
|
179
|
+
return Q({ audit: a.map((e) => ({
|
|
630
180
|
flag: e.flag,
|
|
631
181
|
enabled: e.enabled,
|
|
632
182
|
previousEnabled: e.previousEnabled,
|
|
@@ -635,60 +185,60 @@ var T = (e) => typeof e == "boolean" ? { enabled: e } : e, E = (e, t) => {
|
|
|
635
185
|
})) });
|
|
636
186
|
})
|
|
637
187
|
}
|
|
638
|
-
],
|
|
639
|
-
...
|
|
188
|
+
], de = [
|
|
189
|
+
...X ? [
|
|
640
190
|
"rpc:intercept:mutation",
|
|
641
191
|
"rpc:intercept:query",
|
|
642
192
|
"rpc:intercept:action",
|
|
643
193
|
"rpc:intercept:workflow"
|
|
644
194
|
] : [],
|
|
645
|
-
...
|
|
195
|
+
...v ? ["store:write", "store:changes:read"] : [],
|
|
646
196
|
"inspect:read",
|
|
647
197
|
"inspect:write"
|
|
648
198
|
];
|
|
649
|
-
return
|
|
650
|
-
name:
|
|
651
|
-
baseName:
|
|
199
|
+
return ne({
|
|
200
|
+
name: s,
|
|
201
|
+
baseName: K,
|
|
652
202
|
description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
|
|
653
|
-
permissions:
|
|
654
|
-
routes: [
|
|
203
|
+
permissions: de,
|
|
204
|
+
routes: [oe, se],
|
|
655
205
|
rpcClientDescriptors: r,
|
|
656
|
-
inspectEndpoints:
|
|
206
|
+
inspectEndpoints: ue,
|
|
657
207
|
errorSchemas: [{
|
|
658
|
-
schema:
|
|
208
|
+
schema: R,
|
|
659
209
|
import: {
|
|
660
210
|
module: "@voltro/plugin-flags/errors",
|
|
661
211
|
name: "FlagDisabled"
|
|
662
212
|
}
|
|
663
213
|
}],
|
|
664
|
-
...
|
|
665
|
-
interceptMutation:
|
|
666
|
-
interceptQuery:
|
|
667
|
-
interceptAction:
|
|
668
|
-
interceptWorkflow:
|
|
214
|
+
...X ? {
|
|
215
|
+
interceptMutation: Y,
|
|
216
|
+
interceptQuery: Y,
|
|
217
|
+
interceptAction: Y,
|
|
218
|
+
interceptWorkflow: Y
|
|
669
219
|
} : {},
|
|
670
|
-
...
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
] : [
|
|
675
|
-
...
|
|
676
|
-
|
|
220
|
+
...v ? { extendSchema: { tables: L ? [
|
|
221
|
+
p,
|
|
222
|
+
m,
|
|
223
|
+
N
|
|
224
|
+
] : [p, m] } } : {},
|
|
225
|
+
...v ? { bindDataStore: async (e) => {
|
|
226
|
+
y = e, x = d(y), S = ee(e), L && (w = A(e)), await J();
|
|
677
227
|
} } : {},
|
|
678
|
-
...
|
|
679
|
-
onActivate: (t) =>
|
|
680
|
-
|
|
681
|
-
count: Object.keys(
|
|
682
|
-
typed:
|
|
683
|
-
gated: Object.keys(
|
|
228
|
+
...v ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? z.tryPromise(() => q()) : z.void } : {},
|
|
229
|
+
onActivate: (t) => z.gen(function* () {
|
|
230
|
+
_.set(u), E = t, x && (yield* z.promise(J)), t.logger.info("feature flags active", {
|
|
231
|
+
count: Object.keys(u).length,
|
|
232
|
+
typed: l.size,
|
|
233
|
+
gated: Object.keys(o).length,
|
|
684
234
|
store: e.store ?? "memory",
|
|
685
|
-
usage:
|
|
235
|
+
usage: L ? T ? `tracking (stale after ${V}d, observations kept ${Math.round((O ?? 0) / 864e5)}d)` : "waiting for store binding" : "off — no dead-flag evidence is collected"
|
|
686
236
|
});
|
|
687
237
|
}),
|
|
688
|
-
onDeactivate: () =>
|
|
689
|
-
|
|
238
|
+
onDeactivate: () => z.promise(async () => {
|
|
239
|
+
E = void 0, await T?.stop(), T = void 0, j.disable(), _.set({});
|
|
690
240
|
})
|
|
691
241
|
});
|
|
692
242
|
};
|
|
693
243
|
//#endregion
|
|
694
|
-
export {
|
|
244
|
+
export { O as DEFAULT_FLAG_USAGE_FLUSH_MS, T as FLAG_LIFECYCLE_LIMITS, k as FLAG_USAGE_TABLE, R as FlagDisabled, C as assertFlagExperimentLinks, w as checkFlagExperimentLinks, E as classifyShape, A as dataStoreFlagUsageStore, y as defineFlag, f as describeFlags, i as evaluateAll, a as evaluateAllVariants, o as evaluateFlag, s as evaluateSchedule, c as evaluateVariant, p as featureFlagsTable, m as flagAuditTable, D as flagLifecycleReport, _ as flagRegistry, j as flagUsageBuffer, M as flagUsageRowId, N as flagUsageTable, W as flagValue, G as flagVariant, q as flagsPlugin, h as flipFlagEnabled, b as gateOverride, H as isFlagEnabled, x as isTypedFlag, g as mergeOverrides, P as recordFlagEvaluation, F as registerFlagUsageRetention, U as requireFlag, l as resolveVariant, v as setFlagEnabled, I as startFlagUsageFlush, u as toDefinition, S as typedValueOf, L as utcDay };
|