@voltro/plugin-flags 0.86.0 → 0.88.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 +5484 -0
- package/README.md +106 -0
- package/THIRD-PARTY-NOTICES.md +6 -230
- package/dist/config-inspect.d.ts +33 -0
- package/dist/config-inspect.js +28 -0
- package/dist/config-port.d.ts +247 -0
- package/dist/config-port.js +124 -0
- package/dist/config-testing.d.ts +336 -0
- package/dist/config-testing.js +113 -0
- package/dist/config.d.ts +250 -0
- package/dist/config.js +234 -0
- package/dist/define.js +1 -2
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +16 -4
- package/dist/evaluate.js +1 -2
- package/dist/experimentLink.js +1 -2
- package/dist/index.d.ts +164 -1
- package/dist/index.js +328 -158
- package/dist/lifecycle.js +1 -2
- package/dist/rpc.d.ts +14 -0
- package/dist/rpc.js +30 -16
- package/dist/store.js +38 -3
- package/dist/usage.js +1 -2
- package/dist/web.d.ts +24 -0
- package/dist/web.js +15 -10
- package/package.json +27 -7
package/dist/index.js
CHANGED
|
@@ -1,91 +1,219 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
// 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;
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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);
|
|
46
61
|
}
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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);
|
|
71
|
+
}
|
|
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);
|
|
117
|
+
}
|
|
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
|
+
}]
|
|
135
|
+
});
|
|
136
|
+
} catch {
|
|
137
|
+
return P.fail(new e({ code: "invalid_value" }));
|
|
138
|
+
}
|
|
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,
|
|
60
188
|
log: { warn: (t, n) => e.warn(t, n) }
|
|
61
189
|
});
|
|
62
190
|
}
|
|
63
|
-
for (let e of
|
|
191
|
+
for (let e of U) H.logger.warn("feature flag override REFUSED — code-declared definition stands", {
|
|
64
192
|
flag: e.key,
|
|
65
193
|
reason: e.reason
|
|
66
194
|
});
|
|
67
195
|
}
|
|
68
|
-
},
|
|
69
|
-
if (
|
|
70
|
-
for (let [t, n] of Object.entries(
|
|
71
|
-
},
|
|
72
|
-
let
|
|
73
|
-
return
|
|
74
|
-
},
|
|
75
|
-
for (let t of e)
|
|
76
|
-
},
|
|
77
|
-
...
|
|
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,
|
|
78
206
|
description: "Resolve every feature flag for the calling subject.",
|
|
79
|
-
execute: (e, t) =>
|
|
80
|
-
let e =
|
|
81
|
-
return
|
|
207
|
+
execute: (e, t) => P.sync(() => {
|
|
208
|
+
let e = j();
|
|
209
|
+
return Fe(Object.keys(e)), te(e, t.request.subject ?? {});
|
|
82
210
|
})
|
|
83
|
-
},
|
|
84
|
-
...
|
|
211
|
+
}, Le = {
|
|
212
|
+
...s,
|
|
85
213
|
description: "Resolve every feature flag to its served variant for the calling subject.",
|
|
86
|
-
execute: (e, t) =>
|
|
87
|
-
let e =
|
|
88
|
-
return
|
|
214
|
+
execute: (e, t) => P.sync(() => {
|
|
215
|
+
let e = j();
|
|
216
|
+
return Fe(Object.keys(e)), ne(e, t.request.subject ?? {});
|
|
89
217
|
})
|
|
90
218
|
}, Q = (e) => ({
|
|
91
219
|
kind: "json",
|
|
@@ -94,43 +222,54 @@ var V = (e) => "request" in e && e.request?.subject || "subject" in e && e.subje
|
|
|
94
222
|
kind: "json",
|
|
95
223
|
status: e,
|
|
96
224
|
data: { error: t }
|
|
97
|
-
}),
|
|
225
|
+
}), Re = async () => {
|
|
98
226
|
let e = [];
|
|
99
|
-
if (
|
|
100
|
-
e = await
|
|
227
|
+
if (G && z) try {
|
|
228
|
+
e = await z.load();
|
|
101
229
|
} catch {
|
|
102
230
|
e = [];
|
|
103
231
|
}
|
|
104
|
-
return
|
|
105
|
-
flags:
|
|
232
|
+
return fe({
|
|
233
|
+
flags: j(),
|
|
106
234
|
usage: e,
|
|
107
|
-
tracking:
|
|
108
|
-
liveCounts:
|
|
109
|
-
staleAfterDays:
|
|
110
|
-
retentionDays:
|
|
235
|
+
tracking: G,
|
|
236
|
+
liveCounts: T.liveTargetedCounts(),
|
|
237
|
+
staleAfterDays: K,
|
|
238
|
+
retentionDays: q
|
|
111
239
|
});
|
|
112
|
-
},
|
|
240
|
+
}, ze = () => {
|
|
113
241
|
let e = {};
|
|
114
|
-
for (let [t, n] of
|
|
242
|
+
for (let [t, n] of S) b.has(t) || (e[t] = {
|
|
115
243
|
valueType: n.valueType,
|
|
116
244
|
variants: n.variants.map((e) => e.name),
|
|
117
245
|
experiment: n.experiment ?? null
|
|
118
|
-
};
|
|
246
|
+
});
|
|
119
247
|
return e;
|
|
120
|
-
},
|
|
248
|
+
}, Be = [
|
|
121
249
|
{
|
|
122
250
|
method: "GET",
|
|
123
251
|
path: "/list",
|
|
124
252
|
scope: "process",
|
|
125
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.",
|
|
126
|
-
handler: () =>
|
|
127
|
-
let e = await
|
|
254
|
+
handler: () => P.promise(async () => {
|
|
255
|
+
let e = await Re();
|
|
128
256
|
return Q({
|
|
129
|
-
flags: f(
|
|
130
|
-
writeScope:
|
|
257
|
+
flags: f(j()),
|
|
258
|
+
writeScope: M ? "shared-store" : "process",
|
|
131
259
|
lifecycle: e,
|
|
132
|
-
typed:
|
|
133
|
-
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
|
+
} } : {}
|
|
134
273
|
});
|
|
135
274
|
})
|
|
136
275
|
},
|
|
@@ -138,7 +277,7 @@ var V = (e) => "request" in e && e.request?.subject || "subject" in e && e.subje
|
|
|
138
277
|
method: "POST",
|
|
139
278
|
path: "/toggle",
|
|
140
279
|
description: "Flip a flag's kill-switch on/off (live registry; durable on the postgres tier). Logs an audit row.",
|
|
141
|
-
handler: (e) =>
|
|
280
|
+
handler: (e) => P.gen(function* () {
|
|
142
281
|
let t;
|
|
143
282
|
try {
|
|
144
283
|
t = JSON.parse(e.body || "{}");
|
|
@@ -146,33 +285,32 @@ var V = (e) => "request" in e && e.request?.subject || "subject" in e && e.subje
|
|
|
146
285
|
return $(400, "invalid JSON body");
|
|
147
286
|
}
|
|
148
287
|
if (!t || typeof t.key != "string" || !t.key || typeof t.enabled != "boolean") return $(400, "key (string) + enabled (boolean) required");
|
|
149
|
-
let n =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
} else h(n, r);
|
|
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");
|
|
296
|
+
}
|
|
159
297
|
return Q({
|
|
160
298
|
ok: !0,
|
|
161
|
-
flags: f(
|
|
299
|
+
flags: f(j())
|
|
162
300
|
});
|
|
163
301
|
})
|
|
164
302
|
},
|
|
165
303
|
{
|
|
166
304
|
method: "GET",
|
|
167
305
|
path: "/audit",
|
|
168
|
-
scope:
|
|
306
|
+
scope: M ? "shared-store" : "declaration",
|
|
169
307
|
description: "The kill-switch flip audit trail, newest-first. `?flag=<key>` filters; `?limit=<n>` caps (postgres tier only).",
|
|
170
|
-
handler: (e) =>
|
|
171
|
-
if (!
|
|
308
|
+
handler: (e) => P.gen(function* () {
|
|
309
|
+
if (!R) return Q({
|
|
172
310
|
audit: [],
|
|
173
311
|
note: "audit trail requires store: \"postgres\""
|
|
174
312
|
});
|
|
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*
|
|
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({
|
|
176
314
|
...n == null ? {} : { flag: n },
|
|
177
315
|
limit: i
|
|
178
316
|
}));
|
|
@@ -185,60 +323,92 @@ var V = (e) => "request" in e && e.request?.subject || "subject" in e && e.subje
|
|
|
185
323
|
})) });
|
|
186
324
|
})
|
|
187
325
|
}
|
|
188
|
-
],
|
|
189
|
-
...
|
|
326
|
+
], Ve = [
|
|
327
|
+
...Pe ? [
|
|
190
328
|
"rpc:intercept:mutation",
|
|
191
329
|
"rpc:intercept:query",
|
|
192
330
|
"rpc:intercept:action",
|
|
193
331
|
"rpc:intercept:workflow"
|
|
194
332
|
] : [],
|
|
195
|
-
...
|
|
333
|
+
...M ? ["store:write", "store:changes:read"] : [],
|
|
196
334
|
"inspect:read",
|
|
197
335
|
"inspect:write"
|
|
198
336
|
];
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
routes: [oe, se],
|
|
205
|
-
rpcClientDescriptors: r,
|
|
206
|
-
inspectEndpoints: ue,
|
|
207
|
-
errorSchemas: [{
|
|
208
|
-
schema: R,
|
|
209
|
-
import: {
|
|
210
|
-
module: "@voltro/plugin-flags/errors",
|
|
211
|
-
name: "FlagDisabled"
|
|
212
|
-
}
|
|
213
|
-
}],
|
|
214
|
-
...X ? {
|
|
215
|
-
interceptMutation: Y,
|
|
216
|
-
interceptQuery: Y,
|
|
217
|
-
interceptAction: Y,
|
|
218
|
-
interceptWorkflow: Y
|
|
337
|
+
return {
|
|
338
|
+
toggle: X,
|
|
339
|
+
...A ? {
|
|
340
|
+
config: () => A,
|
|
341
|
+
setConfig: je
|
|
219
342
|
} : {},
|
|
220
|
-
...
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
})
|
|
240
400
|
})
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
});
|
|
414
|
+
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 };
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toDefinition as e } from "./evaluate.js";
|
|
2
|
-
|
|
2
|
+
// src/lifecycle.ts
|
|
3
3
|
var t = 864e5, n = (e) => typeof e == "number" ? e : Date.parse(e), r = (t, r) => {
|
|
4
4
|
let i = e(t);
|
|
5
5
|
if (i.enabled === !1) return {
|
|
@@ -78,5 +78,4 @@ var t = 864e5, n = (e) => typeof e == "number" ? e : Date.parse(e), r = (t, r) =
|
|
|
78
78
|
limits: i
|
|
79
79
|
};
|
|
80
80
|
};
|
|
81
|
-
//#endregion
|
|
82
81
|
export { i as FLAG_LIFECYCLE_LIMITS, r as classifyShape, a as flagLifecycleReport };
|
package/dist/rpc.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PluginRpcClientDescriptor } from '@voltro/protocol';
|
|
2
2
|
import { QueryProcedureDescriptor } from '@voltro/protocol';
|
|
3
3
|
import { Schema } from 'effect';
|
|
4
|
+
import { YieldableError } from 'effect/Cause';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The plugin's canonical package name — the key an app's `alias` is recorded
|
|
@@ -14,6 +15,19 @@ import { Schema } from 'effect';
|
|
|
14
15
|
*/
|
|
15
16
|
export declare const BASE_NAME = "@voltro/plugin-flags";
|
|
16
17
|
|
|
18
|
+
/** Typed Config failures contain no private value, subject or decode body. */
|
|
19
|
+
declare class ConfigError extends ConfigError_base {
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare const ConfigError_base: Schema.Class<ConfigError, Schema.TaggedStruct<"ConfigError", {
|
|
23
|
+
readonly code: Schema.Literals<readonly ["invalid_input", "invalid_value", "not_found", "unavailable"]>;
|
|
24
|
+
}>, YieldableError>;
|
|
25
|
+
|
|
26
|
+
/** Only explicitly public Config keys, evaluated for the authenticated caller. */
|
|
27
|
+
export declare const configMineDescriptor: QueryProcedureDescriptor<"config.mine", Schema.Struct<{}>, Schema.$Record<Schema.String, Schema.Unknown>, typeof ConfigError>;
|
|
28
|
+
|
|
29
|
+
export declare const configRpcClientImports: readonly PluginRpcClientDescriptor[];
|
|
30
|
+
|
|
17
31
|
export declare const evaluateDescriptor: QueryProcedureDescriptor<"flags.evaluate", Schema.Struct<{}>, Schema.$Record<Schema.String, Schema.Boolean>, Schema.Never>;
|
|
18
32
|
|
|
19
33
|
export declare const evaluateVariantsDescriptor: QueryProcedureDescriptor<"flags.variants", Schema.Struct<{}>, Schema.$Record<Schema.String, Schema.Struct<{
|