@toggld/node 0.2.0 → 0.4.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/README.md +109 -10
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +235 -0
- package/dist/{core-Bx0PVoEm.mjs → core-CezJWPV4.mjs} +280 -512
- package/dist/effect.d.mts +6 -7
- package/dist/effect.mjs +1 -2
- package/dist/index.d.mts +5 -6
- package/dist/index.mjs +8 -7
- package/dist/src-B_M-Oix8.mjs +638 -0
- package/dist/{types-DItF1mIU.d.mts → types-hdTQWEM5.d.mts} +41 -12
- package/package.json +7 -4
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
// ../schema/src/audit.ts
|
|
3
|
+
/**
|
|
4
|
+
* Audit log wire contract (ADR-0009). Every management/control-plane mutation is
|
|
5
|
+
* recorded as one append-only {@link AuditEvent}; a tenant reads its own trail
|
|
6
|
+
* over `GET /api/v1/audit-log`. Evaluation (OFREP) reads are deliberately *not*
|
|
7
|
+
* audited — this is a record of configuration *changes*, not traffic.
|
|
8
|
+
*/
|
|
9
|
+
/** Who performed the change. See the server's `Actor` for how each id is sourced. */
|
|
10
|
+
const AuditActor = Schema.Struct({
|
|
11
|
+
type: Schema.Literals([
|
|
12
|
+
"apiKey",
|
|
13
|
+
"user",
|
|
14
|
+
"service"
|
|
15
|
+
]),
|
|
16
|
+
id: Schema.String
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Closed vocabulary of audited actions — a closed set (not free text) so filters
|
|
20
|
+
* and downstream exports stay stable as resources are added. `flag.update` covers
|
|
21
|
+
* both replace (PUT) and patch (PATCH); the before/after states show what moved.
|
|
22
|
+
*/
|
|
23
|
+
const AuditAction = Schema.Literals([
|
|
24
|
+
"flag.create",
|
|
25
|
+
"flag.update",
|
|
26
|
+
"flag.delete",
|
|
27
|
+
"segment.create",
|
|
28
|
+
"segment.update",
|
|
29
|
+
"segment.delete",
|
|
30
|
+
"environment.update",
|
|
31
|
+
"environment.create",
|
|
32
|
+
"environment.delete",
|
|
33
|
+
"apiKey.create",
|
|
34
|
+
"apiKey.revoke",
|
|
35
|
+
"tenant.create",
|
|
36
|
+
"tenant.update",
|
|
37
|
+
"tenant.delete"
|
|
38
|
+
]);
|
|
39
|
+
/** The kind of resource an event concerns; pairs with `resourceId`. */
|
|
40
|
+
const AuditResourceType = Schema.Literals([
|
|
41
|
+
"flag",
|
|
42
|
+
"segment",
|
|
43
|
+
"environment",
|
|
44
|
+
"apiKey",
|
|
45
|
+
"tenant"
|
|
46
|
+
]);
|
|
47
|
+
Schema.Struct({
|
|
48
|
+
id: Schema.String,
|
|
49
|
+
/** Owning tenant; null only for control-plane events (tenant provisioning). */
|
|
50
|
+
tenantId: Schema.NullOr(Schema.String),
|
|
51
|
+
/** The environment touched; null for tenant-wide / definition-level / control-plane events. */
|
|
52
|
+
environmentId: Schema.NullOr(Schema.String),
|
|
53
|
+
actor: AuditActor,
|
|
54
|
+
action: AuditAction,
|
|
55
|
+
resourceType: AuditResourceType,
|
|
56
|
+
/** Flag key, environment id, API-key id, or tenant id. */
|
|
57
|
+
resourceId: Schema.String,
|
|
58
|
+
before: Schema.NullOr(Schema.Unknown),
|
|
59
|
+
after: Schema.NullOr(Schema.Unknown),
|
|
60
|
+
/** Non-authoritative request context (request id, source ip, …); empty in v0. */
|
|
61
|
+
metadata: Schema.Record(Schema.String, Schema.Unknown),
|
|
62
|
+
/** ISO 8601 timestamp the change was recorded. */
|
|
63
|
+
occurredAt: Schema.String
|
|
64
|
+
});
|
|
65
|
+
// ../schema/src/auth.ts
|
|
66
|
+
/**
|
|
67
|
+
* Authorization role carried by an API key.
|
|
68
|
+
*
|
|
69
|
+
* - `management` — full CRUD on the tenant's flags, plus evaluation.
|
|
70
|
+
* - `evaluation` — read-only evaluation (OFREP) of the tenant's flags.
|
|
71
|
+
*/
|
|
72
|
+
const Role = Schema.Literals(["management", "evaluation"]);
|
|
73
|
+
Schema.Struct({
|
|
74
|
+
id: Schema.String,
|
|
75
|
+
name: Schema.String,
|
|
76
|
+
createdAt: Schema.String,
|
|
77
|
+
defaultEnvironmentId: Schema.optional(Schema.NullOr(Schema.String)),
|
|
78
|
+
/**
|
|
79
|
+
* Max number of flag *definitions* the tenant may hold; `null` = unlimited.
|
|
80
|
+
* The free tier is 10 (the DB column default). The flag API treats this as an
|
|
81
|
+
* opaque number — the web app owns the plan→limit mapping and pushes changes
|
|
82
|
+
* over the control plane (provision + PATCH). Enforced in `FlagService.create`.
|
|
83
|
+
*/
|
|
84
|
+
flagLimit: Schema.NullOr(Schema.Int)
|
|
85
|
+
});
|
|
86
|
+
Schema.Struct({
|
|
87
|
+
id: Schema.String,
|
|
88
|
+
tenantId: Schema.String,
|
|
89
|
+
isProduction: Schema.optional(Schema.Boolean),
|
|
90
|
+
isDefault: Schema.optional(Schema.Boolean),
|
|
91
|
+
/** Key, unique within the tenant (e.g. `production`). */
|
|
92
|
+
key: Schema.String,
|
|
93
|
+
name: Schema.String,
|
|
94
|
+
createdAt: Schema.String
|
|
95
|
+
});
|
|
96
|
+
/** Body for creating an environment (POST /admin/v1/tenants/:tenantId/environments). */
|
|
97
|
+
const CreateEnvironmentInput = Schema.Struct({
|
|
98
|
+
isProduction: Schema.optional(Schema.Boolean),
|
|
99
|
+
makeDefault: Schema.optional(Schema.Boolean),
|
|
100
|
+
key: Schema.String.check(Schema.isMinLength(1)),
|
|
101
|
+
name: Schema.String.check(Schema.isMinLength(1))
|
|
102
|
+
});
|
|
103
|
+
Schema.Struct({
|
|
104
|
+
key: Schema.optional(Schema.String.check(Schema.isMinLength(1))),
|
|
105
|
+
name: Schema.optional(Schema.String.check(Schema.isMinLength(1))),
|
|
106
|
+
isProduction: Schema.optional(Schema.Boolean),
|
|
107
|
+
makeDefault: Schema.optional(Schema.Boolean)
|
|
108
|
+
});
|
|
109
|
+
Schema.Struct({
|
|
110
|
+
id: Schema.String,
|
|
111
|
+
tenantId: Schema.String,
|
|
112
|
+
/** The single environment this token is scoped to. */
|
|
113
|
+
environmentId: Schema.String,
|
|
114
|
+
/** The full token value — intentionally exposed; it is non-secret. */
|
|
115
|
+
token: Schema.String,
|
|
116
|
+
createdAt: Schema.String
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Public view of an API key — safe to list. Never includes the secret token or
|
|
120
|
+
* its hash; only a non-secret prefix for human identification.
|
|
121
|
+
*/
|
|
122
|
+
const ApiKeyInfo = Schema.Struct({
|
|
123
|
+
id: Schema.String,
|
|
124
|
+
tenantId: Schema.String,
|
|
125
|
+
/** The single environment this key is scoped to. */
|
|
126
|
+
environmentId: Schema.String,
|
|
127
|
+
name: Schema.optionalKey(Schema.String),
|
|
128
|
+
role: Role,
|
|
129
|
+
prefix: Schema.String,
|
|
130
|
+
createdAt: Schema.String
|
|
131
|
+
});
|
|
132
|
+
Schema.Struct({
|
|
133
|
+
...ApiKeyInfo.fields,
|
|
134
|
+
token: Schema.String
|
|
135
|
+
});
|
|
136
|
+
Schema.Struct({
|
|
137
|
+
id: Schema.optionalKey(Schema.String.check(Schema.isMinLength(1))),
|
|
138
|
+
name: Schema.String.check(Schema.isMinLength(1)),
|
|
139
|
+
environments: Schema.optionalKey(Schema.Array(CreateEnvironmentInput)),
|
|
140
|
+
/** Flag cap for the new tenant; omitted → the free-tier default (10). */
|
|
141
|
+
flagLimit: Schema.optionalKey(Schema.Int)
|
|
142
|
+
});
|
|
143
|
+
Schema.Struct({
|
|
144
|
+
name: Schema.optionalKey(Schema.String.check(Schema.isMinLength(1))),
|
|
145
|
+
flagLimit: Schema.optionalKey(Schema.NullOr(Schema.Int))
|
|
146
|
+
});
|
|
147
|
+
Schema.Struct({
|
|
148
|
+
role: Role,
|
|
149
|
+
name: Schema.optionalKey(Schema.String),
|
|
150
|
+
environment: Schema.optionalKey(Schema.String.check(Schema.isMinLength(1)))
|
|
151
|
+
});
|
|
152
|
+
// ../schema/src/value.ts
|
|
153
|
+
/**
|
|
154
|
+
* The OpenFeature flag value types. Every flag declares one of these; variant
|
|
155
|
+
* values and resolved values are validated against it.
|
|
156
|
+
*
|
|
157
|
+
* @see https://openfeature.dev/specification/types
|
|
158
|
+
*/
|
|
159
|
+
const FlagType = Schema.Literals([
|
|
160
|
+
"boolean",
|
|
161
|
+
"string",
|
|
162
|
+
"number",
|
|
163
|
+
"object"
|
|
164
|
+
]);
|
|
165
|
+
/**
|
|
166
|
+
* A resolved flag value. OpenFeature permits `boolean | string | number |
|
|
167
|
+
* structure`; we store the union as JSON, which also covers arrays and null
|
|
168
|
+
* for "object" typed flags.
|
|
169
|
+
*/
|
|
170
|
+
const FlagValue = Schema.Json;
|
|
171
|
+
/**
|
|
172
|
+
* Flag metadata: "a structure supporting the definition of arbitrary
|
|
173
|
+
* properties, with keys of type string, and values of type boolean, string,
|
|
174
|
+
* or number." (Requirement 2.2.10)
|
|
175
|
+
*/
|
|
176
|
+
const MetadataValue = Schema.Union([
|
|
177
|
+
Schema.String,
|
|
178
|
+
Schema.Finite,
|
|
179
|
+
Schema.Boolean
|
|
180
|
+
]);
|
|
181
|
+
const FlagMetadata = Schema.Record(Schema.String, MetadataValue);
|
|
182
|
+
// ../schema/src/context.ts
|
|
183
|
+
/**
|
|
184
|
+
* An OpenFeature evaluation context: an optional `targetingKey` "identifying
|
|
185
|
+
* the subject of the flag evaluation" (Requirement 3.1.1) plus arbitrary
|
|
186
|
+
* custom fields (Requirement 3.1.2). We accept any JSON-valued attributes.
|
|
187
|
+
*/
|
|
188
|
+
const EvaluationContext = Schema.Record(Schema.String, FlagValue);
|
|
189
|
+
Schema.Error("EnvironmentConflict")({
|
|
190
|
+
_tag: Schema.tag("EnvironmentConflict"),
|
|
191
|
+
message: Schema.String
|
|
192
|
+
});
|
|
193
|
+
Schema.Error("FlagNotFound")({
|
|
194
|
+
_tag: Schema.tag("FlagNotFound"),
|
|
195
|
+
flagKey: Schema.String,
|
|
196
|
+
message: Schema.String
|
|
197
|
+
});
|
|
198
|
+
Schema.Error("FlagConflict")({
|
|
199
|
+
_tag: Schema.tag("FlagConflict"),
|
|
200
|
+
flagKey: Schema.String,
|
|
201
|
+
message: Schema.String
|
|
202
|
+
});
|
|
203
|
+
Schema.Error("FlagAlreadyExists")({
|
|
204
|
+
_tag: Schema.tag("FlagAlreadyExists"),
|
|
205
|
+
flagKey: Schema.String,
|
|
206
|
+
message: Schema.String
|
|
207
|
+
});
|
|
208
|
+
Schema.Error("FlagValidationError")({
|
|
209
|
+
_tag: Schema.tag("FlagValidationError"),
|
|
210
|
+
flagKey: Schema.String,
|
|
211
|
+
issues: Schema.Array(Schema.String),
|
|
212
|
+
message: Schema.String
|
|
213
|
+
});
|
|
214
|
+
Schema.Error("FlagLimitExceeded")({
|
|
215
|
+
_tag: Schema.tag("FlagLimitExceeded"),
|
|
216
|
+
/** The tenant's current flag cap. */
|
|
217
|
+
limit: Schema.Int,
|
|
218
|
+
/** How many flags the tenant has now (>= limit). */
|
|
219
|
+
current: Schema.Int,
|
|
220
|
+
message: Schema.String
|
|
221
|
+
});
|
|
222
|
+
Schema.Error("Unauthorized")({
|
|
223
|
+
_tag: Schema.tag("Unauthorized"),
|
|
224
|
+
message: Schema.String
|
|
225
|
+
});
|
|
226
|
+
Schema.Error("Forbidden")({
|
|
227
|
+
_tag: Schema.tag("Forbidden"),
|
|
228
|
+
message: Schema.String
|
|
229
|
+
});
|
|
230
|
+
Schema.Error("TenantNotFound")({
|
|
231
|
+
_tag: Schema.tag("TenantNotFound"),
|
|
232
|
+
tenantId: Schema.String,
|
|
233
|
+
message: Schema.String
|
|
234
|
+
});
|
|
235
|
+
Schema.Error("TenantNotEmpty")({
|
|
236
|
+
_tag: Schema.tag("TenantNotEmpty"),
|
|
237
|
+
tenantId: Schema.String,
|
|
238
|
+
message: Schema.String
|
|
239
|
+
});
|
|
240
|
+
Schema.Error("TenantAlreadyExists")({
|
|
241
|
+
_tag: Schema.tag("TenantAlreadyExists"),
|
|
242
|
+
tenantId: Schema.String,
|
|
243
|
+
message: Schema.String
|
|
244
|
+
});
|
|
245
|
+
Schema.Error("EnvironmentNotFound")({
|
|
246
|
+
_tag: Schema.tag("EnvironmentNotFound"),
|
|
247
|
+
environment: Schema.String,
|
|
248
|
+
message: Schema.String
|
|
249
|
+
});
|
|
250
|
+
Schema.Error("EnvironmentAlreadyExists")({
|
|
251
|
+
_tag: Schema.tag("EnvironmentAlreadyExists"),
|
|
252
|
+
environment: Schema.String,
|
|
253
|
+
message: Schema.String
|
|
254
|
+
});
|
|
255
|
+
Schema.Error("ApiKeyNotFound")({
|
|
256
|
+
_tag: Schema.tag("ApiKeyNotFound"),
|
|
257
|
+
keyId: Schema.String,
|
|
258
|
+
message: Schema.String
|
|
259
|
+
});
|
|
260
|
+
Schema.Error("SegmentNotFound")({
|
|
261
|
+
_tag: Schema.tag("SegmentNotFound"),
|
|
262
|
+
segmentKey: Schema.String,
|
|
263
|
+
message: Schema.String
|
|
264
|
+
});
|
|
265
|
+
Schema.Error("SegmentAlreadyExists")({
|
|
266
|
+
_tag: Schema.tag("SegmentAlreadyExists"),
|
|
267
|
+
segmentKey: Schema.String,
|
|
268
|
+
message: Schema.String
|
|
269
|
+
});
|
|
270
|
+
Schema.Error("SegmentConflict")({
|
|
271
|
+
_tag: Schema.tag("SegmentConflict"),
|
|
272
|
+
segmentKey: Schema.String,
|
|
273
|
+
message: Schema.String
|
|
274
|
+
});
|
|
275
|
+
Schema.Error("SegmentValidationError")({
|
|
276
|
+
_tag: Schema.tag("SegmentValidationError"),
|
|
277
|
+
segmentKey: Schema.String,
|
|
278
|
+
issues: Schema.Array(Schema.String),
|
|
279
|
+
message: Schema.String
|
|
280
|
+
});
|
|
281
|
+
Schema.Error("SegmentReferenced")({
|
|
282
|
+
_tag: Schema.tag("SegmentReferenced"),
|
|
283
|
+
segmentKey: Schema.String,
|
|
284
|
+
/** Keys of the flags whose targeting still references the segment. */
|
|
285
|
+
flagKeys: Schema.Array(Schema.String),
|
|
286
|
+
message: Schema.String
|
|
287
|
+
});
|
|
288
|
+
// ../schema/src/flag.ts
|
|
289
|
+
/**
|
|
290
|
+
* Operators usable in a targeting condition. Comparisons coerce sensibly:
|
|
291
|
+
* numeric operators require numbers, string operators require strings, and
|
|
292
|
+
* `in`/`notIn` test membership in an array literal.
|
|
293
|
+
*/
|
|
294
|
+
const Operator = Schema.Literals([
|
|
295
|
+
"eq",
|
|
296
|
+
"ne",
|
|
297
|
+
"in",
|
|
298
|
+
"notIn",
|
|
299
|
+
"lt",
|
|
300
|
+
"lte",
|
|
301
|
+
"gt",
|
|
302
|
+
"gte",
|
|
303
|
+
"contains",
|
|
304
|
+
"startsWith",
|
|
305
|
+
"endsWith",
|
|
306
|
+
"exists",
|
|
307
|
+
"notExists"
|
|
308
|
+
]);
|
|
309
|
+
/**
|
|
310
|
+
* A single targeting condition tested against an attribute of the evaluation
|
|
311
|
+
* context (e.g. `targetingKey`, `email`, `plan`). All conditions in a rule are
|
|
312
|
+
* ANDed together.
|
|
313
|
+
*/
|
|
314
|
+
const Condition = Schema.Struct({
|
|
315
|
+
attribute: Schema.String,
|
|
316
|
+
operator: Operator,
|
|
317
|
+
/** Comparison operand. Ignored for `exists`/`notExists`. */
|
|
318
|
+
value: Schema.optionalKey(FlagValue)
|
|
319
|
+
});
|
|
320
|
+
/** A weighted slice of a percentage rollout. */
|
|
321
|
+
const Split = Schema.Struct({
|
|
322
|
+
variant: Schema.String,
|
|
323
|
+
/** Relative weight; the engine normalises weights across all splits. */
|
|
324
|
+
weight: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))
|
|
325
|
+
});
|
|
326
|
+
/** Serve a single fixed variant when the rule matches → `TARGETING_MATCH`. */
|
|
327
|
+
const ServeVariant = Schema.Struct({
|
|
328
|
+
type: Schema.Literal("variant"),
|
|
329
|
+
variant: Schema.String
|
|
330
|
+
});
|
|
331
|
+
/**
|
|
332
|
+
* Serve a deterministic percentage rollout when the rule matches → `SPLIT`.
|
|
333
|
+
* Bucketing is consistent for a given subject (`bucketBy`, default
|
|
334
|
+
* `targetingKey`) so a subject always lands in the same slice.
|
|
335
|
+
*/
|
|
336
|
+
const ServeRollout = Schema.Struct({
|
|
337
|
+
type: Schema.Literal("rollout"),
|
|
338
|
+
bucketBy: Schema.optionalKey(Schema.String),
|
|
339
|
+
splits: Schema.Array(Split)
|
|
340
|
+
});
|
|
341
|
+
const Serve = Schema.Union([ServeVariant, ServeRollout]);
|
|
342
|
+
/** A reference to a reusable {@link Segment} from a targeting rule (ADR-0006). */
|
|
343
|
+
const SegmentRef = Schema.Struct({
|
|
344
|
+
segment: Schema.String,
|
|
345
|
+
negate: Schema.optionalKey(Schema.Boolean)
|
|
346
|
+
});
|
|
347
|
+
/** An ordered targeting rule. The first rule whose conditions all match wins. */
|
|
348
|
+
const Rule = Schema.Struct({
|
|
349
|
+
id: Schema.String,
|
|
350
|
+
description: Schema.optionalKey(Schema.String),
|
|
351
|
+
/** ANDed conditions. An empty list always matches (useful as a catch-all). */
|
|
352
|
+
conditions: Schema.Array(Condition),
|
|
353
|
+
/** Reusable segments, ANDed with `conditions` (ADR-0006). */
|
|
354
|
+
segments: Schema.optionalKey(Schema.Array(SegmentRef)),
|
|
355
|
+
serve: Serve
|
|
356
|
+
});
|
|
357
|
+
/**
|
|
358
|
+
* A feature flag definition.
|
|
359
|
+
*
|
|
360
|
+
* Evaluation order: if `enabled` is false → serve `offVariant` (default
|
|
361
|
+
* `defaultVariant`) with reason `DISABLED`; otherwise walk `targeting` in
|
|
362
|
+
* order and serve the first matching rule (`TARGETING_MATCH`/`SPLIT`); if no
|
|
363
|
+
* rule matches, serve `defaultVariant` with reason `STATIC`.
|
|
364
|
+
*/
|
|
365
|
+
const Flag = Schema.Struct({
|
|
366
|
+
key: Schema.String,
|
|
367
|
+
name: Schema.optionalKey(Schema.String),
|
|
368
|
+
description: Schema.optionalKey(Schema.String),
|
|
369
|
+
type: FlagType,
|
|
370
|
+
enabled: Schema.Boolean,
|
|
371
|
+
/** Named values; every served variant name must be a key here. */
|
|
372
|
+
variants: Schema.Record(Schema.String, FlagValue),
|
|
373
|
+
/** Variant served on a normal evaluation with no targeting match. */
|
|
374
|
+
defaultVariant: Schema.String,
|
|
375
|
+
/** Variant served when the flag is disabled. Defaults to `defaultVariant`. */
|
|
376
|
+
offVariant: Schema.optionalKey(Schema.String),
|
|
377
|
+
targeting: Schema.Array(Rule),
|
|
378
|
+
metadata: FlagMetadata,
|
|
379
|
+
createdAt: Schema.String,
|
|
380
|
+
updatedAt: Schema.String
|
|
381
|
+
});
|
|
382
|
+
/** How a segment's enumerated member list is distributed for evaluation. */
|
|
383
|
+
const SegmentListMode = Schema.Literals([
|
|
384
|
+
"none",
|
|
385
|
+
"inline",
|
|
386
|
+
"remote"
|
|
387
|
+
]);
|
|
388
|
+
/** A single segment membership rule: ANDed conditions, no `serve` (ADR-0006). */
|
|
389
|
+
const SegmentRule = Schema.Struct({
|
|
390
|
+
id: Schema.String,
|
|
391
|
+
description: Schema.optionalKey(Schema.String),
|
|
392
|
+
conditions: Schema.Array(Condition)
|
|
393
|
+
});
|
|
394
|
+
/**
|
|
395
|
+
* A reusable audience definition as it rides the sync bundle (ADR-0006). Inline
|
|
396
|
+
* member keys are present only for `inline` segments; `remote` segments omit
|
|
397
|
+
* them and resolve via a backing-store lookup.
|
|
398
|
+
*/
|
|
399
|
+
const Segment = Schema.Struct({
|
|
400
|
+
key: Schema.String,
|
|
401
|
+
name: Schema.optionalKey(Schema.String),
|
|
402
|
+
description: Schema.optionalKey(Schema.String),
|
|
403
|
+
keyAttribute: Schema.String,
|
|
404
|
+
rules: Schema.Array(SegmentRule),
|
|
405
|
+
listMode: SegmentListMode,
|
|
406
|
+
members: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
407
|
+
version: Schema.Number,
|
|
408
|
+
createdAt: Schema.String,
|
|
409
|
+
updatedAt: Schema.String
|
|
410
|
+
});
|
|
411
|
+
/**
|
|
412
|
+
* Freeform organization labels for a flag (ADR-0008). A flag may carry any
|
|
413
|
+
* number; they group/filter flags in the dashboard and have **no** effect on
|
|
414
|
+
* evaluation. Deliberately separate from the spec `Flag` (and `metadata`) so
|
|
415
|
+
* they never reach the OFREP wire or the engine.
|
|
416
|
+
*/
|
|
417
|
+
const Tags = Schema.Array(Schema.String.check(Schema.isMinLength(1)));
|
|
418
|
+
Schema.Struct({
|
|
419
|
+
...Flag.fields,
|
|
420
|
+
tags: Tags,
|
|
421
|
+
version: Schema.String
|
|
422
|
+
});
|
|
423
|
+
// ../schema/src/resolution.ts
|
|
424
|
+
/**
|
|
425
|
+
* Resolution reasons. The provider "SHOULD populate the resolution details
|
|
426
|
+
* structure's reason field with STATIC, DEFAULT, TARGETING_MATCH, SPLIT,
|
|
427
|
+
* CACHED, DISABLED, UNKNOWN, STALE, ERROR or some other string."
|
|
428
|
+
* (Requirement 2.2.5)
|
|
429
|
+
*/
|
|
430
|
+
const Reason = Schema.Literals([
|
|
431
|
+
"STATIC",
|
|
432
|
+
"DEFAULT",
|
|
433
|
+
"TARGETING_MATCH",
|
|
434
|
+
"SPLIT",
|
|
435
|
+
"CACHED",
|
|
436
|
+
"DISABLED",
|
|
437
|
+
"UNKNOWN",
|
|
438
|
+
"STALE",
|
|
439
|
+
"ERROR"
|
|
440
|
+
]);
|
|
441
|
+
/**
|
|
442
|
+
* OpenFeature error codes.
|
|
443
|
+
* @see https://openfeature.dev/specification/types
|
|
444
|
+
*/
|
|
445
|
+
const ErrorCode = Schema.Literals([
|
|
446
|
+
"PROVIDER_NOT_READY",
|
|
447
|
+
"PROVIDER_FATAL",
|
|
448
|
+
"FLAG_NOT_FOUND",
|
|
449
|
+
"PARSE_ERROR",
|
|
450
|
+
"TYPE_MISMATCH",
|
|
451
|
+
"TARGETING_KEY_MISSING",
|
|
452
|
+
"INVALID_CONTEXT",
|
|
453
|
+
"GENERAL"
|
|
454
|
+
]);
|
|
455
|
+
Schema.Struct({ context: Schema.optionalKey(EvaluationContext) });
|
|
456
|
+
/** A successful single/bulk evaluation result. */
|
|
457
|
+
const OfrepSuccess = Schema.Struct({
|
|
458
|
+
key: Schema.String,
|
|
459
|
+
value: FlagValue,
|
|
460
|
+
reason: Reason,
|
|
461
|
+
variant: Schema.optionalKey(Schema.String),
|
|
462
|
+
metadata: Schema.optionalKey(FlagMetadata)
|
|
463
|
+
});
|
|
464
|
+
/** A per-flag failure embedded in a bulk response. */
|
|
465
|
+
const OfrepFailure = Schema.Struct({
|
|
466
|
+
key: Schema.String,
|
|
467
|
+
errorCode: ErrorCode,
|
|
468
|
+
errorDetails: Schema.optionalKey(Schema.String),
|
|
469
|
+
metadata: Schema.optionalKey(FlagMetadata)
|
|
470
|
+
});
|
|
471
|
+
/** Bulk evaluation response: a mixed list of successes and failures. */
|
|
472
|
+
const OfrepBulkResponse = Schema.Struct({
|
|
473
|
+
flags: Schema.Array(Schema.Union([OfrepSuccess, OfrepFailure])),
|
|
474
|
+
metadata: Schema.optionalKey(FlagMetadata)
|
|
475
|
+
});
|
|
476
|
+
Schema.Error("OfrepFlagNotFound")({
|
|
477
|
+
key: Schema.String,
|
|
478
|
+
errorCode: Schema.Literal("FLAG_NOT_FOUND"),
|
|
479
|
+
errorDetails: Schema.optionalKey(Schema.String)
|
|
480
|
+
});
|
|
481
|
+
Schema.Error("OfrepEvaluationError")({
|
|
482
|
+
key: Schema.String,
|
|
483
|
+
errorCode: ErrorCode,
|
|
484
|
+
errorDetails: Schema.optionalKey(Schema.String)
|
|
485
|
+
});
|
|
486
|
+
Schema.Struct({
|
|
487
|
+
key: Schema.String.check(Schema.isMinLength(1)),
|
|
488
|
+
name: Schema.optionalKey(Schema.String),
|
|
489
|
+
description: Schema.optionalKey(Schema.String),
|
|
490
|
+
type: FlagType,
|
|
491
|
+
enabled: Schema.optionalKey(Schema.Boolean),
|
|
492
|
+
variants: Schema.Record(Schema.String, FlagValue),
|
|
493
|
+
defaultVariant: Schema.String,
|
|
494
|
+
offVariant: Schema.optionalKey(Schema.String),
|
|
495
|
+
targeting: Schema.optionalKey(Schema.Array(Rule)),
|
|
496
|
+
metadata: Schema.optionalKey(FlagMetadata),
|
|
497
|
+
/** Freeform organization labels (ADR-0008). Normalized server-side. */
|
|
498
|
+
tags: Schema.optionalKey(Tags)
|
|
499
|
+
});
|
|
500
|
+
Schema.Struct({
|
|
501
|
+
expectedVersion: Schema.optionalKey(Schema.String),
|
|
502
|
+
name: Schema.optionalKey(Schema.String),
|
|
503
|
+
description: Schema.optionalKey(Schema.String),
|
|
504
|
+
type: FlagType,
|
|
505
|
+
enabled: Schema.optionalKey(Schema.Boolean),
|
|
506
|
+
variants: Schema.Record(Schema.String, FlagValue),
|
|
507
|
+
defaultVariant: Schema.String,
|
|
508
|
+
offVariant: Schema.optionalKey(Schema.String),
|
|
509
|
+
targeting: Schema.optionalKey(Schema.Array(Rule)),
|
|
510
|
+
metadata: Schema.optionalKey(FlagMetadata),
|
|
511
|
+
tags: Schema.optionalKey(Tags)
|
|
512
|
+
});
|
|
513
|
+
Schema.Struct({
|
|
514
|
+
expectedVersion: Schema.optionalKey(Schema.String),
|
|
515
|
+
name: Schema.optionalKey(Schema.String),
|
|
516
|
+
description: Schema.optionalKey(Schema.String),
|
|
517
|
+
type: Schema.optionalKey(FlagType),
|
|
518
|
+
enabled: Schema.optionalKey(Schema.Boolean),
|
|
519
|
+
variants: Schema.optionalKey(Schema.Record(Schema.String, FlagValue)),
|
|
520
|
+
defaultVariant: Schema.optionalKey(Schema.String),
|
|
521
|
+
offVariant: Schema.optionalKey(Schema.String),
|
|
522
|
+
targeting: Schema.optionalKey(Schema.Array(Rule)),
|
|
523
|
+
metadata: Schema.optionalKey(FlagMetadata),
|
|
524
|
+
tags: Schema.optionalKey(Tags)
|
|
525
|
+
});
|
|
526
|
+
Schema.Struct({
|
|
527
|
+
key: Schema.String,
|
|
528
|
+
name: Schema.optionalKey(Schema.String),
|
|
529
|
+
description: Schema.optionalKey(Schema.String),
|
|
530
|
+
keyAttribute: Schema.String,
|
|
531
|
+
rules: Schema.Array(SegmentRule),
|
|
532
|
+
listMode: Segment.fields.listMode,
|
|
533
|
+
/** Number of enumerated members in the list (0 for a rules-only segment). */
|
|
534
|
+
memberCount: Schema.Number,
|
|
535
|
+
version: Schema.Number,
|
|
536
|
+
/** Keys of the flags whose targeting references this segment, in any environment. */
|
|
537
|
+
usedBy: Schema.Array(Schema.String),
|
|
538
|
+
createdAt: Schema.String,
|
|
539
|
+
updatedAt: Schema.String
|
|
540
|
+
});
|
|
541
|
+
Schema.Struct({
|
|
542
|
+
key: Schema.String.check(Schema.isMinLength(1)),
|
|
543
|
+
name: Schema.optionalKey(Schema.String),
|
|
544
|
+
description: Schema.optionalKey(Schema.String),
|
|
545
|
+
/** Defaults to `targetingKey` server-side when omitted. */
|
|
546
|
+
keyAttribute: Schema.optionalKey(Schema.String),
|
|
547
|
+
rules: Schema.optionalKey(Schema.Array(SegmentRule))
|
|
548
|
+
});
|
|
549
|
+
Schema.Struct({
|
|
550
|
+
/** Reject a stale definition draft when supplied. */
|
|
551
|
+
expectedVersion: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
552
|
+
name: Schema.optionalKey(Schema.String),
|
|
553
|
+
description: Schema.optionalKey(Schema.String),
|
|
554
|
+
keyAttribute: Schema.optionalKey(Schema.String),
|
|
555
|
+
rules: Schema.optionalKey(Schema.Array(SegmentRule))
|
|
556
|
+
});
|
|
557
|
+
Schema.Struct({
|
|
558
|
+
add: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
559
|
+
remove: Schema.optionalKey(Schema.Array(Schema.String))
|
|
560
|
+
});
|
|
561
|
+
/** One enumerated member of a segment's list. */
|
|
562
|
+
const SegmentMember = Schema.Struct({
|
|
563
|
+
key: Schema.String,
|
|
564
|
+
addedAt: Schema.String
|
|
565
|
+
});
|
|
566
|
+
Schema.Struct({
|
|
567
|
+
members: Schema.Array(SegmentMember),
|
|
568
|
+
/** Total members matching the (optional) query — the count line in the UI. */
|
|
569
|
+
total: Schema.Number
|
|
570
|
+
});
|
|
571
|
+
// ../schema/src/sync.ts
|
|
572
|
+
/**
|
|
573
|
+
* Definition-sync wire schemas (ADR-0001 §4): the versioned full snapshot the
|
|
574
|
+
* sync surface serves to locally-evaluating SDKs, and the messages exchanged on
|
|
575
|
+
* the live sync stream. Like the OFREP schemas, these are shared by the server
|
|
576
|
+
* (producer) and `@toggld/node` (consumer) so the contract cannot drift.
|
|
577
|
+
*
|
|
578
|
+
* `version` is opaque and equality-compared (ADR-0005 phase 1); the ordered
|
|
579
|
+
* `config_version` lands in this same field later without a wire change.
|
|
580
|
+
*/
|
|
581
|
+
/** A versioned full snapshot of one environment's resolved flag set. */
|
|
582
|
+
const FlagSnapshot = Schema.Struct({
|
|
583
|
+
version: Schema.String,
|
|
584
|
+
flags: Schema.Array(Flag),
|
|
585
|
+
/** Segment definitions referenced by the flags (ADR-0006). */
|
|
586
|
+
segments: Schema.optionalKey(Schema.Array(Segment))
|
|
587
|
+
});
|
|
588
|
+
/** Server→client: the current snapshot (connect greeting and change pushes). */
|
|
589
|
+
const SyncSnapshotMessage = Schema.Struct({
|
|
590
|
+
type: Schema.Literal("snapshot"),
|
|
591
|
+
version: Schema.String,
|
|
592
|
+
flags: Schema.Array(Flag),
|
|
593
|
+
segments: Schema.optionalKey(Schema.Array(Segment))
|
|
594
|
+
});
|
|
595
|
+
/**
|
|
596
|
+
* Every server→client sync-stream message. A single-member union today; the
|
|
597
|
+
* `type` discriminant is the extension point.
|
|
598
|
+
*/
|
|
599
|
+
const SyncServerMessage = Schema.Union([SyncSnapshotMessage]);
|
|
600
|
+
Schema.Struct({ type: Schema.Literal("resync") });
|
|
601
|
+
// ../schema/src/telemetry.ts
|
|
602
|
+
const Identifier = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
|
|
603
|
+
const EvaluationRollup = Schema.Struct({
|
|
604
|
+
flagKey: Identifier,
|
|
605
|
+
variant: Schema.String.check(Schema.isMaxLength(256)),
|
|
606
|
+
reason: Reason,
|
|
607
|
+
bucketStart: Schema.String.check(Schema.makeFilter((s) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:00\.000Z$/.test(s) && Number.isFinite(Date.parse(s)) && new Date(s).toISOString() === s)),
|
|
608
|
+
count: Schema.Number.check(Schema.isInt(), Schema.isBetween({
|
|
609
|
+
minimum: 1,
|
|
610
|
+
maximum: 1e9
|
|
611
|
+
}))
|
|
612
|
+
});
|
|
613
|
+
Schema.Struct({
|
|
614
|
+
sdkInstanceId: Identifier,
|
|
615
|
+
windowId: Identifier,
|
|
616
|
+
rollups: Schema.Array(EvaluationRollup).check(Schema.isMinLength(1), Schema.isMaxLength(1e3))
|
|
617
|
+
});
|
|
618
|
+
Schema.Struct({
|
|
619
|
+
range: Schema.Literals([
|
|
620
|
+
"1h",
|
|
621
|
+
"24h",
|
|
622
|
+
"7d"
|
|
623
|
+
]),
|
|
624
|
+
from: Schema.String,
|
|
625
|
+
to: Schema.String,
|
|
626
|
+
total: Schema.Number,
|
|
627
|
+
errors: Schema.Number,
|
|
628
|
+
lastEvaluatedAt: Schema.NullOr(Schema.String),
|
|
629
|
+
timeline: Schema.Array(Schema.Struct({
|
|
630
|
+
bucketStart: Schema.String,
|
|
631
|
+
count: Schema.Number
|
|
632
|
+
})),
|
|
633
|
+
variants: Schema.Array(Schema.Struct({
|
|
634
|
+
variant: Schema.String,
|
|
635
|
+
count: Schema.Number
|
|
636
|
+
}))
|
|
637
|
+
});
|
|
638
|
+
export { OfrepSuccess as a, OfrepFailure as i, SyncServerMessage as n, OfrepBulkResponse as r, FlagSnapshot as t };
|