@voltro/plugin-flags 0.86.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.
@@ -0,0 +1,250 @@
1
+ import { Schema } from 'effect';
2
+ import { YieldableError } from 'effect/Cause';
3
+
4
+ /** `TypedFlag` with the value type erased — what the plugin and the registry
5
+ * hold. Sound because `TypedFlag` is covariant in `A` (see the annotation). */
6
+ declare type AnyTypedFlag = TypedFlag<unknown>;
7
+
8
+ /** Capture a whole untrusted SQL override before evaluator callbacks run. */
9
+ export declare const captureConfigFlag: (entry: ConfigEntry<unknown>, input: FlagValue) => FlagDefinition;
10
+
11
+ /** Config is bounded JSON. No Dates, bytes, accessors, prototypes or cycles. */
12
+ export declare const captureConfigValue: (input: unknown) => unknown;
13
+
14
+ export declare const captureDefinedConfig: (input: DefinedConfig) => DefinedConfig;
15
+
16
+ export declare const configData: (input: unknown, keys: readonly string[]) => Readonly<Record<string, unknown>>;
17
+
18
+ export declare interface ConfigEntry<out A> {
19
+ readonly key: string;
20
+ readonly flag: TypedFlag<A>;
21
+ readonly public: boolean;
22
+ readonly restartRequired: 'room' | 'live';
23
+ readonly min: number | null;
24
+ readonly max: number | null;
25
+ }
26
+
27
+ /** Typed Config failures contain no private value, subject or decode body. */
28
+ declare class ConfigError extends ConfigError_base {
29
+ }
30
+
31
+ export declare const configError: (code: ConfigError["code"]) => ConfigError;
32
+
33
+ declare const ConfigError_base: Schema.Class<ConfigError, Schema.TaggedStruct<"ConfigError", {
34
+ readonly code: Schema.Literals<readonly ["invalid_input", "invalid_value", "not_found", "unavailable"]>;
35
+ }>, YieldableError>;
36
+
37
+ export declare const configKey: (input: unknown) => string;
38
+
39
+ export declare interface ConfigKeySummary {
40
+ readonly key: string;
41
+ readonly valueType: string;
42
+ readonly public: boolean;
43
+ readonly restartRequired: 'room' | 'live';
44
+ readonly min: number | null;
45
+ readonly max: number | null;
46
+ }
47
+
48
+ export declare interface ConfigSpec<S extends Schema.Top> {
49
+ readonly schema: S;
50
+ readonly default: NoInfer<Schema.Schema.Type<S>>;
51
+ readonly min?: number;
52
+ readonly max?: number;
53
+ readonly public?: boolean;
54
+ readonly restartRequired?: 'room' | 'live';
55
+ readonly variants?: DefineFlagSpec<S>['variants'];
56
+ readonly targeting?: readonly FlagTargetingRule[];
57
+ readonly rollout?: number;
58
+ readonly rolloutBy?: 'subject' | 'tenant';
59
+ readonly schedule?: FlagSchedule;
60
+ }
61
+
62
+ export declare const configSummary: (config: DefinedConfig) => readonly ConfigKeySummary[];
63
+
64
+ export declare const defineConfig: <const Schemas extends Readonly<Record<string, Schema.Top>>>(input: { readonly [Key in keyof Schemas]: ConfigSpec<Schemas[Key]>; }) => DefinedConfig<{ readonly [Key in keyof Schemas]: Schema.Schema.Type<Schemas[Key]>; }>;
65
+
66
+ export declare interface DefinedConfig<Values extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>> {
67
+ readonly entries: {
68
+ readonly [Key in keyof Values]: ConfigEntry<Values[Key]>;
69
+ };
70
+ readonly keys: readonly (keyof Values & string)[];
71
+ readonly typedFlags: readonly AnyTypedFlag[];
72
+ }
73
+
74
+ declare interface DefineFlagSpec<S extends Schema.Top> {
75
+ /** The flag key — `dotted.camelCase`, matching the `flags: { … }` record. */
76
+ readonly key: string;
77
+ /** The value Schema. Everything else in this spec is checked against it. */
78
+ readonly value: S;
79
+ /**
80
+ * The value served when the flag does not resolve to a variant.
81
+ *
82
+ * A wrong value here is a COMPILE ERROR. `NoInfer` is one of three reasons
83
+ * why and, measured, the redundant one — see the header for the other two and
84
+ * for why it is kept anyway.
85
+ */
86
+ readonly default: NoInfer<Schema.Schema.Type<S>>;
87
+ /** The value arms, each checked against the Schema. */
88
+ readonly variants?: ReadonlyArray<TypedFlagVariant<NoInfer<Schema.Schema.Type<S>>>>;
89
+ /** Variant name treated as the "off" state for boolean resolution. */
90
+ readonly offVariant?: string;
91
+ /** Master switch. `false` ⇒ off for everyone. Default true. */
92
+ readonly enabled?: boolean;
93
+ /** Percentage rollout 0..100. Omit ⇒ 100. */
94
+ readonly rollout?: number;
95
+ /** OR-of-rules targeting. */
96
+ readonly targeting?: ReadonlyArray<FlagTargetingRule>;
97
+ /** Bucket on the tenant id instead of the subject id. */
98
+ readonly rolloutBy?: 'subject' | 'tenant';
99
+ /** Time-boxed / ramping schedule. */
100
+ readonly schedule?: FlagSchedule;
101
+ /** Surfaced in the dashboard. */
102
+ readonly description?: string;
103
+ /** IN-11 — the `defineExperiment` name this flag's arms report uplift for. */
104
+ readonly experiment?: string;
105
+ }
106
+
107
+ /** The result of decoding an UNTRUSTED value (a DB override, a dashboard edit)
108
+ * against a flag's Schema. Deliberately not an exception: the caller's correct
109
+ * response is to keep the code-declared value and say so, not to unwind. */
110
+ declare type FlagDecodeResult<A> = {
111
+ readonly ok: true;
112
+ readonly value: A;
113
+ } | {
114
+ readonly ok: false;
115
+ readonly reason: string;
116
+ };
117
+
118
+ declare interface FlagDefinition {
119
+ /** Master switch. `false` → off for everyone regardless of rollout/targeting. Default true. */
120
+ readonly enabled?: boolean;
121
+ /** Percentage rollout 0..100 — deterministic per (key, subject/tenant) so a
122
+ * caller stays in/out consistently. Omit = 100 (everyone the targeting allows). */
123
+ readonly rollout?: number;
124
+ /** OR-of-rules targeting. When present, the caller must match at least one
125
+ * rule (in addition to passing the rollout). Omit = everyone. */
126
+ readonly targeting?: ReadonlyArray<FlagTargetingRule>;
127
+ /** Bucket on the tenant id instead of the subject id (rollout by tenant). */
128
+ readonly rolloutBy?: 'subject' | 'tenant';
129
+ /** Multivariate flag — a set of named variants the flag resolves ONE of per
130
+ * subject, deterministically weighted (see `FlagVariant.weight`). A subject
131
+ * stays in the same variant across calls. When present, `variant.evaluate`
132
+ * serves the resolved variant; the boolean resolution is `enabled && variant
133
+ * is served` (the flag is "on" for the caller unless a variant named in
134
+ * `offVariant` is served — omit ⇒ any served variant counts as on). */
135
+ readonly variants?: ReadonlyArray<FlagVariant>;
136
+ /** Variant name treated as the "off" state for boolean resolution. Omit ⇒
137
+ * passing targeting+rollout with any served variant counts as on. */
138
+ readonly offVariant?: string;
139
+ /** Time-boxed / ramping schedule, evaluated against the current time. */
140
+ readonly schedule?: FlagSchedule;
141
+ /** Surfaced in the dashboard / `flags.evaluate`. */
142
+ readonly description?: string;
143
+ }
144
+
145
+ /** A time-boxed / ramping schedule. Evaluated against the current time.
146
+ * Order: outside [activateAt, deactivateAt) ⇒ off; a `ramp` interpolates the
147
+ * effective rollout % linearly across its window. */
148
+ declare interface FlagSchedule {
149
+ /** Flag is off before this instant (epoch millis or ISO string). Omit ⇒ no lower bound. */
150
+ readonly activateAt?: number | string;
151
+ /** Flag is off at/after this instant (epoch millis or ISO string). Omit ⇒ no upper bound. */
152
+ readonly deactivateAt?: number | string;
153
+ /** A ramping rollout: exposure grows linearly from `from`% to `to`% across
154
+ * [`startAt`, `endAt`). Before `startAt` ⇒ `from`%, after `endAt` ⇒ `to`%.
155
+ * The interpolated % REPLACES the flag's static `rollout` while active. */
156
+ readonly ramp?: {
157
+ readonly from: number;
158
+ readonly to: number;
159
+ readonly startAt: number | string;
160
+ readonly endAt: number | string;
161
+ };
162
+ }
163
+
164
+ /** One targeting rule — ANDs its conditions; a flag's `targeting` is an OR of rules. */
165
+ declare interface FlagTargetingRule {
166
+ /** Match specific subject ids. */
167
+ readonly subjectIds?: ReadonlyArray<string>;
168
+ /** Match specific tenant ids. */
169
+ readonly tenantIds?: ReadonlyArray<string>;
170
+ /** Match `subject.type` (user / apiKey / serviceAccount / anonymous). */
171
+ readonly subjectTypes?: ReadonlyArray<string>;
172
+ /** Match `subject.metadata[key] === value` for every entry. */
173
+ readonly metadata?: Record<string, string>;
174
+ }
175
+
176
+ /** A flag value as authored: bare boolean (kill-switch) OR a rich definition. */
177
+ declare type FlagValue = boolean | FlagDefinition;
178
+
179
+ /** One multivariate variant: a named value with an optional rollout weight. */
180
+ declare interface FlagVariant {
181
+ /** Stable variant name (the resolution returns this). */
182
+ readonly name: string;
183
+ /** The value served for this variant (string / number / boolean / JSON). */
184
+ readonly value: FlagVariantValue;
185
+ /** Relative allocation weight. Omit ⇒ equal split of the remaining weight.
186
+ * Weights are normalised, so `[3, 1]` is 75% / 25%. */
187
+ readonly weight?: number;
188
+ }
189
+
190
+ /** A concrete variant value — string / number / boolean / JSON. Kept browser-safe. */
191
+ declare type FlagVariantValue = string | number | boolean | null | ReadonlyArray<unknown> | Record<string, unknown>;
192
+
193
+ /**
194
+ * A flag with a value type.
195
+ *
196
+ * `out A` is load-bearing rather than decorative: the plugin holds these in one
197
+ * `ReadonlyArray<TypedFlag<unknown>>`, which is only sound while every member
198
+ * that mentions `A` is a read position. If you add a field that CONSUMES an `A`
199
+ * (a `(value: A) => …` callback, say), that annotation is what will stop you —
200
+ * and the fix is to erase it behind a function returning a result, the way
201
+ * `decode` already does, not to drop the annotation.
202
+ */
203
+ declare interface TypedFlag<out A> {
204
+ /** Brand — lets the plugin discriminate a typed flag from a bare `FlagValue`. */
205
+ readonly _voltroTypedFlag: true;
206
+ /** The flag key. Shares the flag namespace with `flags: { … }` entries; a
207
+ * duplicate is refused at plugin construction. */
208
+ readonly key: string;
209
+ /** Human label of the value Schema (`number`, `"a" | "b"`, …) — for the
210
+ * dashboard panel and for the refusal message when an override fails to
211
+ * decode. Derived from the Schema's AST, so it cannot drift from it. */
212
+ readonly valueType: string;
213
+ /** The value served whenever the flag does not resolve to a variant: it is
214
+ * off for this caller, or it declares no variants at all. Compile-checked
215
+ * against the Schema. */
216
+ readonly defaultValue: A;
217
+ /** The value arms. Empty ⇒ the flag always serves `defaultValue` (which the
218
+ * lifecycle report classifies as `constantOn`/`constantOff` — a flag that
219
+ * cannot serve anything else is one to delete). */
220
+ readonly variants: ReadonlyArray<TypedFlagVariant<A>>;
221
+ /** Decode an untrusted value against the flag's Schema — the runtime half of
222
+ * the contract `default` gets at compile time. */
223
+ readonly decode: (input: unknown) => FlagDecodeResult<A>;
224
+ /** The evaluator-facing definition. This is what goes into the registry the
225
+ * existing `evaluateFlag` / `resolveVariant` already read; nothing about
226
+ * evaluation changes because a flag is typed. */
227
+ readonly definition: FlagDefinition;
228
+ /**
229
+ * IN-11 — the `defineExperiment` this flag's variants report uplift for.
230
+ *
231
+ * A NAME rather than the definition object, on purpose: `defineExperiment`
232
+ * lives in `@voltro/runtime`, which is server-only, and this module has to
233
+ * stay loadable in a browser bundle. The link is validated at BOOT, where
234
+ * both sides are known (`assertFlagExperimentLinkage`) — a flag naming an
235
+ * experiment that does not exist, or whose arms disagree with the flag's,
236
+ * refuses to start rather than reporting uplift for arms nobody is served.
237
+ */
238
+ readonly experiment?: string;
239
+ }
240
+
241
+ /** One named arm of a typed flag. `value` is checked against the flag's Schema
242
+ * at COMPILE time. Weights behave exactly as `FlagVariant.weight`. */
243
+ declare interface TypedFlagVariant<out A> {
244
+ readonly name: string;
245
+ readonly value: A;
246
+ /** Relative allocation weight. Omit ⇒ equal split. */
247
+ readonly weight?: number;
248
+ }
249
+
250
+ export { }
package/dist/config.js ADDED
@@ -0,0 +1,235 @@
1
+ import { ConfigError as e } from "./errors.js";
2
+ import { defineFlag as t } from "./define.js";
3
+ import { Schema as n } from "effect";
4
+ //#region src/config.ts
5
+ var r = (t) => new e({ code: t }), i = /* @__PURE__ */ new WeakSet(), a = (e) => {
6
+ if (!e || typeof e != "object" || !i.has(e)) throw r("invalid_input");
7
+ return e;
8
+ }, o = /^[A-Za-z][A-Za-z0-9._-]{0,119}$/, s = (e) => {
9
+ if (typeof e != "string" || !o.test(e)) throw r("invalid_input");
10
+ return e;
11
+ }, c = (e, t) => {
12
+ if (!e || typeof e != "object" || Array.isArray(e) || ![null, Object.prototype].includes(Object.getPrototypeOf(e))) throw r("invalid_input");
13
+ let n = Object.create(null);
14
+ for (let i of Reflect.ownKeys(e)) {
15
+ let a = Object.getOwnPropertyDescriptor(e, i);
16
+ if (typeof i != "string" || !t.includes(i) || !a?.enumerable || !("value" in a)) throw r("invalid_input");
17
+ n[i] = a.value;
18
+ }
19
+ return Object.freeze(n);
20
+ }, l = (e) => {
21
+ let t = 0, n = /* @__PURE__ */ new Set(), i = (e, a) => {
22
+ if (++t > 4096 || a > 16) throw r("invalid_value");
23
+ if (e === null || typeof e == "boolean" || typeof e == "number" && Number.isFinite(e)) return e;
24
+ if (typeof e == "string") {
25
+ if (e.length > 16384) throw r("invalid_value");
26
+ return e;
27
+ }
28
+ if (!e || typeof e != "object" || n.has(e)) throw r("invalid_value");
29
+ n.add(e);
30
+ try {
31
+ if (Array.isArray(e)) {
32
+ if (e.length > 1024 || Reflect.ownKeys(e).length !== e.length + 1) throw r("invalid_value");
33
+ let t = [];
34
+ for (let n = 0; n < e.length; n++) {
35
+ let o = Object.getOwnPropertyDescriptor(e, String(n));
36
+ if (!o || !("value" in o) || !o.enumerable) throw r("invalid_value");
37
+ t.push(i(o.value, a + 1));
38
+ }
39
+ return Object.freeze(t);
40
+ }
41
+ let t = Reflect.ownKeys(e);
42
+ if (t.length > 256 || t.some((e) => typeof e != "string" || e.length > 120)) throw r("invalid_value");
43
+ let n = c(e, t), o = Object.create(null);
44
+ for (let [e, t] of Object.entries(n)) o[e] = i(t, a + 1);
45
+ return Object.freeze(o);
46
+ } finally {
47
+ n.delete(e);
48
+ }
49
+ }, a = i(e, 0);
50
+ if (new TextEncoder().encode(JSON.stringify(a)).byteLength > 16384) throw r("invalid_value");
51
+ return a;
52
+ }, u = (e) => {
53
+ if (typeof e != "number" || !Number.isFinite(e) || e < 0 || e > 100) throw r("invalid_input");
54
+ return e;
55
+ }, d = (e) => {
56
+ let t = typeof e == "string" ? Date.parse(e) : e;
57
+ if (typeof t != "number" || !Number.isSafeInteger(t) || t < 0 || t > 864e13) throw r("invalid_input");
58
+ return t;
59
+ }, f = (e) => {
60
+ let t = c(l(e), [
61
+ "activateAt",
62
+ "deactivateAt",
63
+ "ramp"
64
+ ]), n = t.activateAt === void 0 ? void 0 : d(t.activateAt), i = t.deactivateAt === void 0 ? void 0 : d(t.deactivateAt);
65
+ if (n !== void 0 && i !== void 0 && i <= n) throw r("invalid_input");
66
+ let a;
67
+ if (t.ramp !== void 0) {
68
+ let e = c(t.ramp, [
69
+ "from",
70
+ "to",
71
+ "startAt",
72
+ "endAt"
73
+ ]), n = d(e.startAt), i = d(e.endAt);
74
+ if (i <= n) throw r("invalid_input");
75
+ a = Object.freeze({
76
+ from: u(e.from),
77
+ to: u(e.to),
78
+ startAt: n,
79
+ endAt: i
80
+ });
81
+ }
82
+ return Object.freeze({
83
+ ...n === void 0 ? {} : { activateAt: n },
84
+ ...i === void 0 ? {} : { deactivateAt: i },
85
+ ...a === void 0 ? {} : { ramp: a }
86
+ });
87
+ }, p = (e) => {
88
+ let t = l(e);
89
+ if (!Array.isArray(t) || t.length > 32) throw r("invalid_input");
90
+ return Object.freeze(t.map((e) => {
91
+ let t = c(e, [
92
+ "subjectIds",
93
+ "tenantIds",
94
+ "subjectTypes",
95
+ "metadata"
96
+ ]);
97
+ for (let e of [
98
+ "subjectIds",
99
+ "tenantIds",
100
+ "subjectTypes"
101
+ ]) if (t[e] !== void 0 && (!Array.isArray(t[e]) || t[e].length < 1 || !t[e].every((e) => typeof e == "string" && e.length > 0 && e.length <= 120))) throw r("invalid_input");
102
+ if (t.metadata !== void 0 && (!t.metadata || typeof t.metadata != "object" || Array.isArray(t.metadata) || !Object.values(t.metadata).every((e) => typeof e == "string"))) throw r("invalid_input");
103
+ return t;
104
+ }));
105
+ }, m = (e) => {
106
+ if (!e || typeof e != "object") throw r("invalid_input");
107
+ let a = Reflect.ownKeys(e);
108
+ if (a.length < 1 || a.length > 128 || a.some((e) => typeof e != "string")) throw r("invalid_input");
109
+ let o = c(e, a), u = Object.create(null);
110
+ for (let [e, i] of Object.entries(o)) {
111
+ s(e);
112
+ let a = c(i, [
113
+ "schema",
114
+ "default",
115
+ "min",
116
+ "max",
117
+ "public",
118
+ "restartRequired",
119
+ "variants",
120
+ "targeting",
121
+ "rollout",
122
+ "rolloutBy",
123
+ "schedule"
124
+ ]);
125
+ if (!n.isSchema(a.schema) || a.public !== void 0 && typeof a.public != "boolean" || a.restartRequired !== void 0 && a.restartRequired !== "room" && a.restartRequired !== "live" || a.rolloutBy !== void 0 && a.rolloutBy !== "tenant" && a.rolloutBy !== "subject") throw r("invalid_input");
126
+ for (let e of ["min", "max"]) if (a[e] !== void 0 && (typeof a[e] != "number" || !Number.isFinite(a[e]))) throw r("invalid_input");
127
+ let o = a.min === void 0 ? null : a.min, d = a.max === void 0 ? null : a.max;
128
+ if (o !== null && (typeof o != "number" || !Number.isFinite(o)) || d !== null && (typeof d != "number" || !Number.isFinite(d)) || typeof o == "number" && typeof d == "number" && o > d) throw r("invalid_input");
129
+ let m = a.schema.pipe(n.check(n.makeFilter((e) => o === null && d === null || typeof e == "number" && Number.isFinite(e) && (o === null || e >= o) && (d === null || e <= d)))), h = l(a.default), g = l(a.variants ?? [{
130
+ name: "configured",
131
+ value: h
132
+ }]);
133
+ if (!Array.isArray(g) || g.length < 1 || g.length > 32) throw r("invalid_input");
134
+ let _;
135
+ try {
136
+ _ = t({
137
+ key: e,
138
+ value: m,
139
+ default: h,
140
+ variants: g,
141
+ ...a.targeting === void 0 ? {} : { targeting: p(a.targeting) },
142
+ ...a.rollout === void 0 ? {} : { rollout: a.rollout },
143
+ ...a.rolloutBy === void 0 ? {} : { rolloutBy: a.rolloutBy },
144
+ ...a.schedule === void 0 ? {} : { schedule: f(a.schedule) }
145
+ });
146
+ } catch {
147
+ throw r("invalid_value");
148
+ }
149
+ let v = _.decode;
150
+ _ = Object.freeze({
151
+ ..._,
152
+ definition: l(_.definition),
153
+ decode: (e) => {
154
+ try {
155
+ let t = v(l(e));
156
+ return t.ok ? {
157
+ ok: !0,
158
+ value: l(t.value)
159
+ } : {
160
+ ok: !1,
161
+ reason: "Value does not satisfy the bounded Config contract"
162
+ };
163
+ } catch {
164
+ return {
165
+ ok: !1,
166
+ reason: "Value does not satisfy the bounded Config contract"
167
+ };
168
+ }
169
+ }
170
+ }), u[e] = Object.freeze({
171
+ key: e,
172
+ flag: _,
173
+ public: a.public ?? !1,
174
+ restartRequired: a.restartRequired ?? "room",
175
+ min: o,
176
+ max: d
177
+ });
178
+ }
179
+ let d = Object.freeze({
180
+ entries: Object.freeze(u),
181
+ keys: Object.freeze(Object.keys(u)),
182
+ typedFlags: Object.freeze(Object.values(u).map((e) => e.flag))
183
+ });
184
+ return i.add(d), d;
185
+ }, h = (e, t) => {
186
+ let n = c(typeof t == "boolean" ? { enabled: t } : t, [
187
+ "enabled",
188
+ "rollout",
189
+ "rolloutBy",
190
+ "variants",
191
+ "offVariant",
192
+ "targeting",
193
+ "schedule",
194
+ "description"
195
+ ]);
196
+ if (n.enabled !== void 0 && typeof n.enabled != "boolean" || n.rolloutBy !== void 0 && n.rolloutBy !== "subject" && n.rolloutBy !== "tenant" || n.description !== void 0 && (typeof n.description != "string" || n.description.length > 1024)) throw r("invalid_value");
197
+ let i = l(n.variants ?? []);
198
+ if (!Array.isArray(i) || i.length > 32) throw r("invalid_value");
199
+ let a = /* @__PURE__ */ new Set(), o = i.map((t) => {
200
+ let n = c(t, [
201
+ "name",
202
+ "value",
203
+ "weight"
204
+ ]), i = s(n.name), o = e.flag.decode(n.value);
205
+ if (!o.ok || a.has(i) || n.weight !== void 0 && (typeof n.weight != "number" || !Number.isFinite(n.weight) || n.weight <= 0)) throw r("invalid_value");
206
+ return a.add(i), Object.freeze({
207
+ name: i,
208
+ value: l(o.value),
209
+ ...n.weight === void 0 ? {} : { weight: n.weight }
210
+ });
211
+ });
212
+ if (n.offVariant !== void 0 && (typeof n.offVariant != "string" || !a.has(n.offVariant))) throw r("invalid_value");
213
+ return Object.freeze({
214
+ ...n.enabled === void 0 ? {} : { enabled: n.enabled },
215
+ ...n.rollout === void 0 ? {} : { rollout: u(n.rollout) },
216
+ ...n.rolloutBy === void 0 ? {} : { rolloutBy: n.rolloutBy },
217
+ variants: Object.freeze(o),
218
+ ...n.offVariant === void 0 ? {} : { offVariant: n.offVariant },
219
+ ...n.targeting === void 0 ? {} : { targeting: p(n.targeting) },
220
+ ...n.schedule === void 0 ? {} : { schedule: f(n.schedule) },
221
+ ...n.description === void 0 ? {} : { description: n.description }
222
+ });
223
+ }, g = (e) => Object.freeze(e.keys.map((t) => {
224
+ let n = e.entries[t];
225
+ return Object.freeze({
226
+ key: t,
227
+ valueType: n.flag.valueType,
228
+ public: n.public,
229
+ restartRequired: n.restartRequired,
230
+ min: n.min,
231
+ max: n.max
232
+ });
233
+ }));
234
+ //#endregion
235
+ export { h as captureConfigFlag, l as captureConfigValue, a as captureDefinedConfig, c as configData, r as configError, s as configKey, g as configSummary, m as defineConfig };
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { Schema } from 'effect';
2
2
  import { YieldableError } from 'effect/Cause';
3
3
 
4
+ /** Typed Config failures contain no private value, subject or decode body. */
5
+ export declare class ConfigError extends ConfigError_base {
6
+ }
7
+
8
+ declare const ConfigError_base: Schema.Class<ConfigError, Schema.TaggedStruct<"ConfigError", {
9
+ readonly code: Schema.Literals<readonly ["invalid_input", "invalid_value", "not_found", "unavailable"]>;
10
+ }>, YieldableError>;
11
+
4
12
  /** Raised when a flag-gated call hits a disabled flag. Merged into every
5
13
  * procedure's wire-error union so the client decodes it typed. */
6
14
  export declare class FlagDisabled extends FlagDisabled_base {
@@ -10,4 +18,14 @@ declare const FlagDisabled_base: Schema.Class<FlagDisabled, Schema.TaggedStruct<
10
18
  readonly flag: Schema.String;
11
19
  }>, YieldableError>;
12
20
 
21
+ /** A refused kill-switch flip: unknown key, a typed Config value (it has its own writer),
22
+ * a store that is not bound yet, or a write whose commit could not be confirmed. */
23
+ export declare class FlagToggleRefused extends FlagToggleRefused_base {
24
+ }
25
+
26
+ declare const FlagToggleRefused_base: Schema.Class<FlagToggleRefused, Schema.TaggedStruct<"FlagToggleRefused", {
27
+ readonly key: Schema.String;
28
+ readonly reason: Schema.Literals<readonly ["unknown_flag", "config_value", "not_ready", "unconfirmed"]>;
29
+ }>, YieldableError>;
30
+
13
31
  export { }
package/dist/errors.js CHANGED
@@ -1,5 +1,18 @@
1
1
  import { Schema as e } from "effect";
2
2
  //#region src/errors.ts
3
- var t = class extends e.TaggedError()("FlagDisabled", { flag: e.String }) {};
3
+ var t = class extends e.TaggedError()("FlagDisabled", { flag: e.String }) {}, n = class extends e.TaggedError()("FlagToggleRefused", {
4
+ key: e.String,
5
+ reason: e.Literals([
6
+ "unknown_flag",
7
+ "config_value",
8
+ "not_ready",
9
+ "unconfirmed"
10
+ ])
11
+ }) {}, r = class extends e.TaggedError()("ConfigError", { code: e.Literals([
12
+ "invalid_input",
13
+ "invalid_value",
14
+ "not_found",
15
+ "unavailable"
16
+ ]) }) {};
4
17
  //#endregion
5
- export { t as FlagDisabled };
18
+ export { r as ConfigError, t as FlagDisabled, n as FlagToggleRefused };