@voltro/plugin-flags 0.85.0 → 0.87.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 +5987 -0
- package/README.md +106 -0
- package/THIRD-PARTY-NOTICES.md +1125 -34
- package/dist/config-inspect.d.ts +33 -0
- package/dist/config-inspect.js +29 -0
- package/dist/config-port.d.ts +247 -0
- package/dist/config-port.js +125 -0
- package/dist/config-testing.d.ts +336 -0
- package/dist/config-testing.js +114 -0
- package/dist/config.d.ts +250 -0
- package/dist/config.js +235 -0
- package/dist/{define-BHpLJh1F.js → define.js} +1 -1
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +15 -2
- package/dist/evaluate.js +81 -0
- package/dist/experimentLink.js +48 -0
- package/dist/index.d.ts +164 -1
- package/dist/index.js +330 -609
- package/dist/lifecycle.js +82 -0
- package/dist/rpc.d.ts +14 -0
- package/dist/rpc.js +29 -14
- package/dist/store.js +190 -0
- package/dist/usage.js +113 -0
- package/dist/web.d.ts +24 -0
- package/dist/web.js +15 -9
- package/package.json +26 -6
package/dist/index.js
CHANGED
|
@@ -1,586 +1,275 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
1
|
+
import { ConfigError as e, FlagDisabled as t, FlagToggleRefused as n } from "./errors.js";
|
|
2
|
+
import { BASE_NAME as r, configMineDescriptor as i, configRpcClientImports as a, evaluateDescriptor as o, evaluateVariantsDescriptor as s, flagsRpcClientImports as ee } from "./rpc.js";
|
|
3
|
+
import { evaluateAll as te, evaluateAllVariants as ne, evaluateFlag as c, evaluateSchedule as l, evaluateVariant as u, resolveVariant as d, toDefinition as re } from "./evaluate.js";
|
|
4
|
+
import { dataStoreFlagAuditStore as ie, dataStoreFlagStore as ae, describeFlags as f, featureFlagsTable as p, flagAuditTable as m, flipFlagEnabled as oe, mergeOverrides as se, persistConfigValue as ce, persistFlagToggle as le, registry as h, setFlagEnabled as ue } from "./store.js";
|
|
5
|
+
import { defineFlag as g, gateOverride as de, isTypedFlag as _, typedValueOf as v } from "./define.js";
|
|
6
|
+
import { assertFlagExperimentLinks as y, checkFlagExperimentLinks as b } from "./experimentLink.js";
|
|
7
|
+
import { FLAG_LIFECYCLE_LIMITS as x, classifyShape as S, flagLifecycleReport as fe } from "./lifecycle.js";
|
|
8
|
+
import { DEFAULT_FLAG_USAGE_FLUSH_MS as C, FLAG_USAGE_TABLE as w, dataStoreFlagUsageStore as pe, flagUsageBuffer as T, flagUsageRowId as E, flagUsageTable as me, recordFlagEvaluation as D, registerFlagUsageRetention as he, startFlagUsageFlush as ge, utcDay as O } from "./usage.js";
|
|
9
|
+
import { captureConfigFlag as _e, captureDefinedConfig as ve, configData as ye, configError as k, configKey as be, configSummary as xe, defineConfig as A } from "./config.js";
|
|
10
|
+
import { ConfigPort as Se, captureConfigService as j, configValue as M, defaultConfigResolver as N, flagConfigService as Ce } from "./config-port.js";
|
|
11
|
+
import { Effect as P, Layer as we, Result as Te } from "effect";
|
|
12
|
+
import { definePlugin as Ee, pluginInstanceName as De } from "@voltro/protocol";
|
|
13
|
+
//#region src/index.ts
|
|
14
|
+
var F = (e) => "request" in e && e.request?.subject || "subject" in e && e.subject || {}, I = (e, t) => {
|
|
15
|
+
let n = h.get(t);
|
|
16
|
+
return D(t, "targeted"), n !== void 0 && c(t, n, F(e));
|
|
17
|
+
}, L = (e, n) => I(e, n) ? P.void : P.fail(new t({ flag: n })), R = (e, t) => {
|
|
18
|
+
let n = h.get(t.key);
|
|
19
|
+
return D(t.key, "targeted"), n === void 0 ? t.defaultValue : v(t, u(t.key, n, F(e)));
|
|
20
|
+
}, z = (e, t) => {
|
|
21
|
+
let n = h.get(t.key);
|
|
22
|
+
return D(t.key, "targeted"), n === void 0 ? null : u(t.key, n, F(e)).name;
|
|
23
|
+
}, Oe = (e, t) => e === t || e.startsWith("/") && e.endsWith("/") && new RegExp(e.slice(1, -1)).test(t), B = r, V = (r = {}) => {
|
|
24
|
+
let c = [
|
|
25
|
+
"config",
|
|
26
|
+
"configVersion",
|
|
27
|
+
"configResolver",
|
|
28
|
+
"configService",
|
|
29
|
+
"configQuerySources"
|
|
30
|
+
], l = Object.create(null);
|
|
31
|
+
for (let e of c) {
|
|
32
|
+
let t = Object.getOwnPropertyDescriptor(r, e);
|
|
33
|
+
if (t) {
|
|
34
|
+
if (!("value" in t)) throw k("invalid_input");
|
|
35
|
+
l[e] = t.value;
|
|
140
36
|
}
|
|
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
37
|
}
|
|
327
|
-
let
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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 });
|
|
38
|
+
let u = Object.freeze(l), d = r.gatedBy ?? {}, ue = De({
|
|
39
|
+
base: B,
|
|
40
|
+
alias: r.alias,
|
|
41
|
+
instance: r.name
|
|
42
|
+
}), g = u.config === void 0 ? void 0 : ve(u.config);
|
|
43
|
+
if (g) {
|
|
44
|
+
if (ye(g, [
|
|
45
|
+
"entries",
|
|
46
|
+
"keys",
|
|
47
|
+
"typedFlags"
|
|
48
|
+
]), !Object.isFrozen(g) || !Object.isFrozen(g.entries) || !Array.isArray(g.keys) || g.keys.length < 1 || g.keys.length > 128) throw k("invalid_input");
|
|
49
|
+
} else if (u.configVersion !== void 0 || u.configResolver !== void 0 || u.configService !== void 0 || u.configQuerySources !== void 0) throw k("invalid_input");
|
|
50
|
+
let _ = be(u.configVersion ?? "default.v1");
|
|
51
|
+
if ((u.configResolver !== void 0 || u.configService !== void 0) && _ === "default.v1" || u.configService && u.configResolver) throw k("invalid_input");
|
|
52
|
+
let v;
|
|
53
|
+
if (u.configService) {
|
|
54
|
+
let e = u.configQuerySources;
|
|
55
|
+
if (!Array.isArray(e) || e.length > 32 || Reflect.ownKeys(e).length !== e.length + 1) throw k("invalid_input");
|
|
56
|
+
let t = [];
|
|
57
|
+
for (let n = 0; n < e.length; n++) {
|
|
58
|
+
let r = Object.getOwnPropertyDescriptor(e, String(n));
|
|
59
|
+
if (!r || !("value" in r) || typeof r.value != "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,119}$/.test(r.value)) throw k("invalid_input");
|
|
60
|
+
t.push(r.value);
|
|
398
61
|
}
|
|
62
|
+
if (v = Object.freeze(t), new Set(v).size !== v.length) throw k("invalid_input");
|
|
63
|
+
} else if (u.configQuerySources !== void 0) throw k("invalid_input");
|
|
64
|
+
let b = new Set(g?.keys ?? []), x = [...r.typedFlags ?? [], ...u.configService ? [] : g?.typedFlags ?? []];
|
|
65
|
+
if (u.configService && (r.typedFlags?.some((e) => b.has(e.key)) || Object.keys(r.flags ?? {}).some((e) => b.has(e)))) throw k("invalid_input");
|
|
66
|
+
let S = /* @__PURE__ */ new Map();
|
|
67
|
+
for (let e of x) {
|
|
68
|
+
if (S.has(e.key)) throw Error(`${B}: two typedFlags declare the key ${JSON.stringify(e.key)}`);
|
|
69
|
+
if (r.flags?.[e.key] !== void 0) throw Error(`${B}: ${JSON.stringify(e.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.`);
|
|
70
|
+
S.set(e.key, e);
|
|
399
71
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
72
|
+
x.some((e) => e.experiment !== void 0) && y(x, r.experiments ?? []);
|
|
73
|
+
let C = { ...r.flags ?? {} };
|
|
74
|
+
for (let e of x) C[e.key] = e.definition;
|
|
75
|
+
let w = C, E = !1, O = 0, A = g ? Ce({
|
|
76
|
+
catalog: g,
|
|
77
|
+
flags: () => w,
|
|
78
|
+
active: () => E,
|
|
79
|
+
revision: () => O,
|
|
80
|
+
...u.configResolver === void 0 ? {} : { resolver: u.configResolver },
|
|
81
|
+
...u.configService === void 0 ? {} : { replacement: u.configService }
|
|
82
|
+
}) : void 0, j = () => g ? Object.fromEntries(Object.entries(h.all()).filter(([e]) => !b.has(e))) : h.all(), M = r.store === "postgres", F, L, R, z, V, H, ke, U = [], W = r.usage ?? {}, G = W.track ?? M;
|
|
83
|
+
if (G && !M) throw Error(`${B}: 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.`);
|
|
84
|
+
let K = W.staleAfterDays ?? 30, q = W.retentionDays ?? 90;
|
|
85
|
+
if (K > q) throw Error(`${B}: usage.staleAfterDays (${K}) exceeds usage.retentionDays (${q}), so the observation window can never be long enough to prove a flag stale and every verdict would be withheld.`);
|
|
86
|
+
let Ae = (e) => {
|
|
87
|
+
let t = se(C, e), n = g ? new Map(e.map((e) => [e.key, e])) : void 0, r = [];
|
|
88
|
+
for (let [e, i] of S) {
|
|
89
|
+
let a = t[e];
|
|
90
|
+
if (a === void 0) continue;
|
|
91
|
+
if (g && b.has(e)) try {
|
|
92
|
+
let r = n?.get(e), i = r ? {
|
|
93
|
+
enabled: r.enabled,
|
|
94
|
+
...Object.fromEntries([
|
|
95
|
+
"rollout",
|
|
96
|
+
"rolloutBy",
|
|
97
|
+
"targeting",
|
|
98
|
+
"variants",
|
|
99
|
+
"offVariant",
|
|
100
|
+
"schedule",
|
|
101
|
+
"description"
|
|
102
|
+
].flatMap((e) => {
|
|
103
|
+
let t = r[e];
|
|
104
|
+
return t === null ? [] : [[e, t]];
|
|
105
|
+
}))
|
|
106
|
+
} : a;
|
|
107
|
+
a = _e(g.entries[e], i), t[e] = a;
|
|
108
|
+
} catch {
|
|
109
|
+
r.push({
|
|
110
|
+
key: e,
|
|
111
|
+
reason: "Override does not satisfy the bounded Config contract"
|
|
112
|
+
}), t[e] = i.definition;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
let o = de(i, typeof a == "boolean" ? { enabled: a } : a);
|
|
116
|
+
o.ok || (r.push(o.refusal), t[e] = i.definition);
|
|
421
117
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
|
118
|
+
U = r, g && (w = t), h.set(t);
|
|
119
|
+
}, J = 0, Y = async () => {
|
|
120
|
+
if (!L) return;
|
|
121
|
+
let e = ++J, t = await L.loadOverrides();
|
|
122
|
+
e === J && Ae(t);
|
|
123
|
+
}, je = (t) => P.suspend(() => {
|
|
124
|
+
let n = g && typeof t?.key == "string" && b.has(t.key) ? g.entries[t.key] : void 0;
|
|
125
|
+
if (!g || !n) return P.fail(new e({ code: "not_found" }));
|
|
126
|
+
let r;
|
|
127
|
+
try {
|
|
128
|
+
r = _e(n, {
|
|
129
|
+
enabled: !0,
|
|
130
|
+
rollout: 100,
|
|
131
|
+
variants: [{
|
|
132
|
+
name: "set",
|
|
133
|
+
value: t.value
|
|
134
|
+
}]
|
|
442
135
|
});
|
|
136
|
+
} catch {
|
|
137
|
+
return P.fail(new e({ code: "invalid_value" }));
|
|
443
138
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
},
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (i === void 0) continue;
|
|
494
|
-
let o = a(r, typeof i == "boolean" ? { enabled: i } : i);
|
|
495
|
-
o.ok || (n.push(o.refusal), t[e] = r.definition);
|
|
496
|
-
}
|
|
497
|
-
C = n, H.set(t);
|
|
498
|
-
}, k = 0, A = async () => {
|
|
499
|
-
if (!_) return;
|
|
500
|
-
let e = ++k, t = await _.loadOverrides();
|
|
501
|
-
e === k && O(t);
|
|
502
|
-
}, j = async () => {
|
|
503
|
-
if (x && _) {
|
|
504
|
-
if (await A(), T && y && !b) {
|
|
505
|
-
S = Se(process.env, D), Y.enable();
|
|
506
|
-
let e = x.logger;
|
|
507
|
-
b = xe({
|
|
508
|
-
store: y,
|
|
509
|
-
intervalMs: w.flushIntervalMs ?? 3e5,
|
|
139
|
+
let i = n.key, a = r.variants[0].value;
|
|
140
|
+
if (!M) return w = {
|
|
141
|
+
...w,
|
|
142
|
+
[i]: r
|
|
143
|
+
}, O++, h.set({
|
|
144
|
+
...h.all(),
|
|
145
|
+
[i]: r
|
|
146
|
+
}), P.succeed({
|
|
147
|
+
key: i,
|
|
148
|
+
value: a
|
|
149
|
+
});
|
|
150
|
+
let o = F, s = h.get(i);
|
|
151
|
+
return !o || s === void 0 ? P.fail(new e({ code: "unavailable" })) : P.tryPromise(async () => {
|
|
152
|
+
await ce(o, i, s, a, typeof t.actor == "string" ? t.actor : null), await Y();
|
|
153
|
+
}).pipe(P.as({
|
|
154
|
+
key: i,
|
|
155
|
+
value: a
|
|
156
|
+
}), P.catch(() => P.tryPromise(Y).pipe(P.ignore, P.andThen(P.fail(new e({ code: "unavailable" }))))));
|
|
157
|
+
}), X = (e) => P.suspend(() => {
|
|
158
|
+
let t = e.key, r = e.enabled, i = (e) => P.fail(new n({
|
|
159
|
+
key: t,
|
|
160
|
+
reason: e
|
|
161
|
+
}));
|
|
162
|
+
if (typeof t != "string" || !t || typeof r != "boolean") return i("unknown_flag");
|
|
163
|
+
if (b.has(t)) return i("config_value");
|
|
164
|
+
let a = h.get(t);
|
|
165
|
+
if (a === void 0) return i("unknown_flag");
|
|
166
|
+
let o = re(a).enabled !== !1;
|
|
167
|
+
if (!M) return oe(t, r), P.succeed({
|
|
168
|
+
key: t,
|
|
169
|
+
enabled: r,
|
|
170
|
+
previous: o
|
|
171
|
+
});
|
|
172
|
+
let s = F;
|
|
173
|
+
return s ? P.tryPromise(async () => {
|
|
174
|
+
await le(s, t, a, r, typeof e.actor == "string" ? e.actor : null), await Y();
|
|
175
|
+
}).pipe(P.as({
|
|
176
|
+
key: t,
|
|
177
|
+
enabled: r,
|
|
178
|
+
previous: o
|
|
179
|
+
}), P.catch(() => P.tryPromise(Y).pipe(P.ignore, P.andThen(i("unconfirmed"))))) : i("not_ready");
|
|
180
|
+
}), Me = async () => {
|
|
181
|
+
if (H && L) {
|
|
182
|
+
if (await Y(), G && z && !V) {
|
|
183
|
+
ke = he(process.env, q), T.enable();
|
|
184
|
+
let e = H.logger;
|
|
185
|
+
V = ge({
|
|
186
|
+
store: z,
|
|
187
|
+
intervalMs: W.flushIntervalMs ?? 3e5,
|
|
510
188
|
log: { warn: (t, n) => e.warn(t, n) }
|
|
511
189
|
});
|
|
512
190
|
}
|
|
513
|
-
for (let e of
|
|
191
|
+
for (let e of U) H.logger.warn("feature flag override REFUSED — code-declared definition stands", {
|
|
514
192
|
flag: e.key,
|
|
515
193
|
reason: e.reason
|
|
516
194
|
});
|
|
517
195
|
}
|
|
518
|
-
},
|
|
519
|
-
if (
|
|
520
|
-
for (let [t, n] of Object.entries(
|
|
521
|
-
},
|
|
522
|
-
let
|
|
523
|
-
return
|
|
524
|
-
},
|
|
525
|
-
for (let t of e)
|
|
526
|
-
},
|
|
527
|
-
...
|
|
196
|
+
}, Ne = (e) => {
|
|
197
|
+
if (d[e] !== void 0) return d[e];
|
|
198
|
+
for (let [t, n] of Object.entries(d)) if (t !== e && Oe(t, e)) return n;
|
|
199
|
+
}, Z = (e, n) => {
|
|
200
|
+
let r = Ne(n.tag);
|
|
201
|
+
return r === void 0 || I({ subject: n.subject }, r) ? e : P.fail(new t({ flag: r }));
|
|
202
|
+
}, Pe = Object.keys(d).length > 0, Fe = (e) => {
|
|
203
|
+
for (let t of e) D(t, "bulk");
|
|
204
|
+
}, Ie = {
|
|
205
|
+
...o,
|
|
528
206
|
description: "Resolve every feature flag for the calling subject.",
|
|
529
|
-
execute: (e, t) =>
|
|
530
|
-
let e =
|
|
531
|
-
return
|
|
207
|
+
execute: (e, t) => P.sync(() => {
|
|
208
|
+
let e = j();
|
|
209
|
+
return Fe(Object.keys(e)), te(e, t.request.subject ?? {});
|
|
532
210
|
})
|
|
533
|
-
},
|
|
534
|
-
...
|
|
211
|
+
}, Le = {
|
|
212
|
+
...s,
|
|
535
213
|
description: "Resolve every feature flag to its served variant for the calling subject.",
|
|
536
|
-
execute: (e, t) =>
|
|
537
|
-
let e =
|
|
538
|
-
return
|
|
214
|
+
execute: (e, t) => P.sync(() => {
|
|
215
|
+
let e = j();
|
|
216
|
+
return Fe(Object.keys(e)), ne(e, t.request.subject ?? {});
|
|
539
217
|
})
|
|
540
|
-
},
|
|
218
|
+
}, Q = (e) => ({
|
|
541
219
|
kind: "json",
|
|
542
220
|
data: e
|
|
543
|
-
}),
|
|
221
|
+
}), $ = (e, t) => ({
|
|
544
222
|
kind: "json",
|
|
545
223
|
status: e,
|
|
546
224
|
data: { error: t }
|
|
547
|
-
}),
|
|
225
|
+
}), Re = async () => {
|
|
548
226
|
let e = [];
|
|
549
|
-
if (
|
|
550
|
-
e = await
|
|
227
|
+
if (G && z) try {
|
|
228
|
+
e = await z.load();
|
|
551
229
|
} catch {
|
|
552
230
|
e = [];
|
|
553
231
|
}
|
|
554
|
-
return
|
|
555
|
-
flags:
|
|
232
|
+
return fe({
|
|
233
|
+
flags: j(),
|
|
556
234
|
usage: e,
|
|
557
|
-
tracking:
|
|
558
|
-
liveCounts:
|
|
559
|
-
staleAfterDays:
|
|
560
|
-
retentionDays:
|
|
235
|
+
tracking: G,
|
|
236
|
+
liveCounts: T.liveTargetedCounts(),
|
|
237
|
+
staleAfterDays: K,
|
|
238
|
+
retentionDays: q
|
|
561
239
|
});
|
|
562
|
-
},
|
|
240
|
+
}, ze = () => {
|
|
563
241
|
let e = {};
|
|
564
|
-
for (let [t, n] of
|
|
242
|
+
for (let [t, n] of S) b.has(t) || (e[t] = {
|
|
565
243
|
valueType: n.valueType,
|
|
566
244
|
variants: n.variants.map((e) => e.name),
|
|
567
245
|
experiment: n.experiment ?? null
|
|
568
|
-
};
|
|
246
|
+
});
|
|
569
247
|
return e;
|
|
570
|
-
},
|
|
248
|
+
}, Be = [
|
|
571
249
|
{
|
|
572
250
|
method: "GET",
|
|
573
251
|
path: "/list",
|
|
574
252
|
scope: "process",
|
|
575
253
|
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:
|
|
254
|
+
handler: () => P.promise(async () => {
|
|
255
|
+
let e = await Re();
|
|
256
|
+
return Q({
|
|
257
|
+
flags: f(j()),
|
|
258
|
+
writeScope: M ? "shared-store" : "process",
|
|
581
259
|
lifecycle: e,
|
|
582
|
-
typed:
|
|
583
|
-
overrideRefusals:
|
|
260
|
+
typed: ze(),
|
|
261
|
+
overrideRefusals: U,
|
|
262
|
+
...g ? { config: {
|
|
263
|
+
bound: E,
|
|
264
|
+
backend: u.configService ? "custom" : "flags",
|
|
265
|
+
version: _,
|
|
266
|
+
resolver: u.configService ? "app-owned" : (u.configResolver ?? N) === N ? "default" : "custom",
|
|
267
|
+
keys: xe(g),
|
|
268
|
+
publicKeys: g.keys.filter((e) => g.entries[e].public),
|
|
269
|
+
maxValueBytes: 16384,
|
|
270
|
+
roomBindingInstalled: !1,
|
|
271
|
+
gmEditInstalled: !1
|
|
272
|
+
} } : {}
|
|
584
273
|
});
|
|
585
274
|
})
|
|
586
275
|
},
|
|
@@ -588,45 +277,44 @@ var T = (e) => typeof e == "boolean" ? { enabled: e } : e, E = (e, t) => {
|
|
|
588
277
|
method: "POST",
|
|
589
278
|
path: "/toggle",
|
|
590
279
|
description: "Flip a flag's kill-switch on/off (live registry; durable on the postgres tier). Logs an audit row.",
|
|
591
|
-
handler: (e) =>
|
|
280
|
+
handler: (e) => P.gen(function* () {
|
|
592
281
|
let t;
|
|
593
282
|
try {
|
|
594
283
|
t = JSON.parse(e.body || "{}");
|
|
595
284
|
} catch {
|
|
596
|
-
return
|
|
285
|
+
return $(400, "invalid JSON body");
|
|
286
|
+
}
|
|
287
|
+
if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return $(400, "key (string) + enabled (boolean) required");
|
|
288
|
+
let n = yield* P.result(X({
|
|
289
|
+
key: t.key,
|
|
290
|
+
enabled: t.enabled,
|
|
291
|
+
actor: typeof t.actor == "string" ? t.actor : null
|
|
292
|
+
}));
|
|
293
|
+
if (Te.isFailure(n)) {
|
|
294
|
+
let e = n.failure.reason;
|
|
295
|
+
return e === "config_value" ? $(400, "Config values require their typed writer; the flag toggle cannot change them") : e === "unknown_flag" ? $(404, `unknown flag "${t.key}"`) : e === "not_ready" ? $(503, "flag store is not ready") : $(503, "flag update could not be confirmed; reload the flag state before retrying");
|
|
597
296
|
}
|
|
598
|
-
|
|
599
|
-
let n = t.key, r = t.enabled, i = H.get(n);
|
|
600
|
-
if (i === void 0) return z(404, `unknown flag "${n}"`);
|
|
601
|
-
if (h) {
|
|
602
|
-
let e = g;
|
|
603
|
-
if (!e) return z(503, "flag store is not ready");
|
|
604
|
-
let a = yield* l.tryPromise(async () => {
|
|
605
|
-
await ce(e, n, i, r, typeof t.actor == "string" ? t.actor : null), await A();
|
|
606
|
-
}).pipe(l.result);
|
|
607
|
-
if (u.isFailure(a)) return yield* l.tryPromise(A).pipe(l.ignore), z(503, "flag update could not be confirmed; reload the flag state before retrying");
|
|
608
|
-
} else U(n, r);
|
|
609
|
-
return R({
|
|
297
|
+
return Q({
|
|
610
298
|
ok: !0,
|
|
611
|
-
flags:
|
|
299
|
+
flags: f(j())
|
|
612
300
|
});
|
|
613
301
|
})
|
|
614
302
|
},
|
|
615
303
|
{
|
|
616
304
|
method: "GET",
|
|
617
305
|
path: "/audit",
|
|
618
|
-
scope:
|
|
306
|
+
scope: M ? "shared-store" : "declaration",
|
|
619
307
|
description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
|
|
620
|
-
handler: (e) =>
|
|
621
|
-
if (!
|
|
308
|
+
handler: (e) => P.gen(function* () {
|
|
309
|
+
if (!R) return Q({
|
|
622
310
|
audit: [],
|
|
623
311
|
note: "audit trail requires store: \"postgres\""
|
|
624
312
|
});
|
|
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*
|
|
313
|
+
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* P.promise(() => R.list({
|
|
626
314
|
...n == null ? {} : { flag: n },
|
|
627
315
|
limit: i
|
|
628
316
|
}));
|
|
629
|
-
return
|
|
317
|
+
return Q({ audit: a.map((e) => ({
|
|
630
318
|
flag: e.flag,
|
|
631
319
|
enabled: e.enabled,
|
|
632
320
|
previousEnabled: e.previousEnabled,
|
|
@@ -635,60 +323,93 @@ var T = (e) => typeof e == "boolean" ? { enabled: e } : e, E = (e, t) => {
|
|
|
635
323
|
})) });
|
|
636
324
|
})
|
|
637
325
|
}
|
|
638
|
-
],
|
|
639
|
-
...
|
|
326
|
+
], Ve = [
|
|
327
|
+
...Pe ? [
|
|
640
328
|
"rpc:intercept:mutation",
|
|
641
329
|
"rpc:intercept:query",
|
|
642
330
|
"rpc:intercept:action",
|
|
643
331
|
"rpc:intercept:workflow"
|
|
644
332
|
] : [],
|
|
645
|
-
...
|
|
333
|
+
...M ? ["store:write", "store:changes:read"] : [],
|
|
646
334
|
"inspect:read",
|
|
647
335
|
"inspect:write"
|
|
648
336
|
];
|
|
649
|
-
return
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
routes: [I, L],
|
|
655
|
-
rpcClientDescriptors: r,
|
|
656
|
-
inspectEndpoints: G,
|
|
657
|
-
errorSchemas: [{
|
|
658
|
-
schema: c,
|
|
659
|
-
import: {
|
|
660
|
-
module: "@voltro/plugin-flags/errors",
|
|
661
|
-
name: "FlagDisabled"
|
|
662
|
-
}
|
|
663
|
-
}],
|
|
664
|
-
...P ? {
|
|
665
|
-
interceptMutation: N,
|
|
666
|
-
interceptQuery: N,
|
|
667
|
-
interceptAction: N,
|
|
668
|
-
interceptWorkflow: N
|
|
337
|
+
return {
|
|
338
|
+
toggle: X,
|
|
339
|
+
...A ? {
|
|
340
|
+
config: () => A,
|
|
341
|
+
setConfig: je
|
|
669
342
|
} : {},
|
|
670
|
-
...
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
343
|
+
...Ee({
|
|
344
|
+
name: ue,
|
|
345
|
+
baseName: B,
|
|
346
|
+
description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
|
|
347
|
+
permissions: Ve,
|
|
348
|
+
routes: [
|
|
349
|
+
Ie,
|
|
350
|
+
Le,
|
|
351
|
+
...A ? [{
|
|
352
|
+
...i,
|
|
353
|
+
...v === void 0 ? {} : { source: v },
|
|
354
|
+
execute: (e, t) => A.mine(t.request.subject)
|
|
355
|
+
}] : []
|
|
356
|
+
],
|
|
357
|
+
rpcClientDescriptors: [...ee, ...A ? a : []],
|
|
358
|
+
inspectEndpoints: Be,
|
|
359
|
+
errorSchemas: [{
|
|
360
|
+
schema: t,
|
|
361
|
+
import: {
|
|
362
|
+
module: "@voltro/plugin-flags/errors",
|
|
363
|
+
name: "FlagDisabled"
|
|
364
|
+
}
|
|
365
|
+
}, ...A ? [{
|
|
366
|
+
schema: e,
|
|
367
|
+
import: {
|
|
368
|
+
module: "@voltro/plugin-flags/errors",
|
|
369
|
+
name: "ConfigError"
|
|
370
|
+
}
|
|
371
|
+
}] : []],
|
|
372
|
+
...A ? { services: we.succeed(Se, A) } : {},
|
|
373
|
+
...Pe ? {
|
|
374
|
+
interceptMutation: Z,
|
|
375
|
+
interceptQuery: Z,
|
|
376
|
+
interceptAction: Z,
|
|
377
|
+
interceptWorkflow: Z
|
|
378
|
+
} : {},
|
|
379
|
+
...M ? { extendSchema: { tables: G ? [
|
|
380
|
+
p,
|
|
381
|
+
m,
|
|
382
|
+
me
|
|
383
|
+
] : [p, m] } } : {},
|
|
384
|
+
...M ? { bindDataStore: async (e) => {
|
|
385
|
+
F = e, L = ae(F), R = ie(e), G && (z = pe(e)), await Me();
|
|
386
|
+
} } : {},
|
|
387
|
+
...M ? { onChangeEvent: (e) => e.table === "_voltro_feature_flags" ? P.tryPromise(() => Y()) : P.void } : {},
|
|
388
|
+
onActivate: (e) => P.gen(function* () {
|
|
389
|
+
g && (E = !1, O++, J++, w = C), h.set(C), H = e, L && (yield* P.promise(Me)), g && (E = !0), e.logger.info("feature flags active", {
|
|
390
|
+
count: Object.keys(C).length,
|
|
391
|
+
typed: S.size,
|
|
392
|
+
gated: Object.keys(d).length,
|
|
393
|
+
store: r.store ?? "memory",
|
|
394
|
+
usage: G ? V ? `tracking (stale after ${K}d, observations kept ${Math.round((ke ?? 0) / 864e5)}d)` : "waiting for store binding" : "off — no dead-flag evidence is collected"
|
|
395
|
+
});
|
|
396
|
+
}),
|
|
397
|
+
onDeactivate: () => P.promise(async () => {
|
|
398
|
+
g && (E = !1, O++, J++), H = void 0, await V?.stop(), V = void 0, T.disable(), h.set({});
|
|
399
|
+
})
|
|
690
400
|
})
|
|
691
|
-
}
|
|
692
|
-
}
|
|
401
|
+
};
|
|
402
|
+
}, H = Object.freeze({
|
|
403
|
+
slots: { resolver: {
|
|
404
|
+
defaultExport: "defaultConfigResolver",
|
|
405
|
+
fixtureExport: "configResolverSystemFixture"
|
|
406
|
+
} },
|
|
407
|
+
ports: { config: {
|
|
408
|
+
tagExport: "ConfigPort",
|
|
409
|
+
fakeLayerExport: "configFakeLayer",
|
|
410
|
+
fixtureExport: "configPortSystemFixture"
|
|
411
|
+
} },
|
|
412
|
+
events: []
|
|
413
|
+
});
|
|
693
414
|
//#endregion
|
|
694
|
-
export {
|
|
415
|
+
export { e as ConfigError, Se as ConfigPort, C as DEFAULT_FLAG_USAGE_FLUSH_MS, x as FLAG_LIFECYCLE_LIMITS, w as FLAG_USAGE_TABLE, t as FlagDisabled, y as assertFlagExperimentLinks, j as captureConfigService, b as checkFlagExperimentLinks, S as classifyShape, xe as configSummary, H as configSystemContract, M as configValue, pe as dataStoreFlagUsageStore, N as defaultConfigResolver, A as defineConfig, g as defineFlag, f as describeFlags, te as evaluateAll, ne as evaluateAllVariants, c as evaluateFlag, l as evaluateSchedule, u as evaluateVariant, p as featureFlagsTable, m as flagAuditTable, Ce as flagConfigService, fe as flagLifecycleReport, h as flagRegistry, T as flagUsageBuffer, E as flagUsageRowId, me as flagUsageTable, R as flagValue, z as flagVariant, V as flagsPlugin, oe as flipFlagEnabled, de as gateOverride, I as isFlagEnabled, _ as isTypedFlag, se as mergeOverrides, D as recordFlagEvaluation, he as registerFlagUsageRetention, L as requireFlag, d as resolveVariant, ue as setFlagEnabled, ge as startFlagUsageFlush, re as toDefinition, v as typedValueOf, O as utcDay };
|