@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/dist/index.js CHANGED
@@ -1,91 +1,219 @@
1
- import { BASE_NAME as e, evaluateDescriptor as t, evaluateVariantsDescriptor as n, flagsRpcClientImports as r } from "./rpc.js";
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,
24
- alias: e.alias,
25
- instance: e.name
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);
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
- 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) {
42
- let i = t[e];
43
- if (i === void 0) continue;
44
- let a = b(r, typeof i == "boolean" ? { enabled: i } : i);
45
- a.ok || (n.push(a.refusal), t[e] = r.definition);
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
- 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,
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 k) E.logger.warn("feature flag override REFUSED — code-declared definition stands", {
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
- }, 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 = {
77
- ...t,
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) => z.sync(() => {
80
- let e = _.all();
81
- return Z(Object.keys(e)), i(e, t.request.subject ?? {});
207
+ execute: (e, t) => P.sync(() => {
208
+ let e = j();
209
+ return Fe(Object.keys(e)), te(e, t.request.subject ?? {});
82
210
  })
83
- }, se = {
84
- ...n,
211
+ }, Le = {
212
+ ...s,
85
213
  description: "Resolve every feature flag to its served variant for the calling subject.",
86
- execute: (e, t) => z.sync(() => {
87
- let e = _.all();
88
- return Z(Object.keys(e)), a(e, t.request.subject ?? {});
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
- }), ce = async () => {
225
+ }), Re = async () => {
98
226
  let e = [];
99
- if (L && w) try {
100
- e = await w.load();
227
+ if (G && z) try {
228
+ e = await z.load();
101
229
  } catch {
102
230
  e = [];
103
231
  }
104
- return D({
105
- flags: _.all(),
232
+ return fe({
233
+ flags: j(),
106
234
  usage: e,
107
- tracking: L,
108
- liveCounts: j.liveTargetedCounts(),
109
- staleAfterDays: V,
110
- retentionDays: U
235
+ tracking: G,
236
+ liveCounts: T.liveTargetedCounts(),
237
+ staleAfterDays: K,
238
+ retentionDays: q
111
239
  });
112
- }, le = () => {
240
+ }, ze = () => {
113
241
  let e = {};
114
- for (let [t, n] of l) e[t] = {
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
- }, ue = [
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: () => z.promise(async () => {
127
- let e = await ce();
254
+ handler: () => P.promise(async () => {
255
+ let e = await Re();
128
256
  return Q({
129
- flags: f(_.all()),
130
- writeScope: v ? "shared-store" : "process",
257
+ flags: f(j()),
258
+ writeScope: M ? "shared-store" : "process",
131
259
  lifecycle: e,
132
- typed: le(),
133
- overrideRefusals: k
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) => z.gen(function* () {
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 = 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);
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(_.all())
299
+ flags: f(j())
162
300
  });
163
301
  })
164
302
  },
165
303
  {
166
304
  method: "GET",
167
305
  path: "/audit",
168
- scope: v ? "shared-store" : "declaration",
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) => z.gen(function* () {
171
- if (!S) return Q({
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* z.promise(() => S.list({
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
- ], de = [
189
- ...X ? [
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
- ...v ? ["store:write", "store:changes:read"] : [],
333
+ ...M ? ["store:write", "store:changes:read"] : [],
196
334
  "inspect:read",
197
335
  "inspect:write"
198
336
  ];
199
- return ne({
200
- name: s,
201
- baseName: K,
202
- description: "Feature flags — targeting, % rollout, kill-switch, declarative gating + client evaluation.",
203
- permissions: de,
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
- ...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();
227
- } } : {},
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,
234
- store: e.store ?? "memory",
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"
236
- });
237
- }),
238
- onDeactivate: () => z.promise(async () => {
239
- E = void 0, await T?.stop(), T = void 0, j.disable(), _.set({});
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
- //#endregion
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 };
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
- //#region src/lifecycle.ts
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<{