@smthrs/plan 1.0.0-rc.5 → 1.0.0-rc.6

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +5 -5
  3. package/THIRD_PARTY_NOTICES.md +48 -0
  4. package/dist/cjs/KeyMaterial.d.ts +1 -1
  5. package/dist/cjs/KeyMaterial.js.map +1 -1
  6. package/dist/cjs/Node.d.ts +71 -0
  7. package/dist/cjs/Node.d.ts.map +1 -1
  8. package/dist/cjs/Node.js +7 -0
  9. package/dist/cjs/Node.js.map +2 -2
  10. package/dist/cjs/StepKey.d.ts +1 -1
  11. package/dist/cjs/StepKey.js.map +1 -1
  12. package/dist/cjs/internal/SchemaIdentity.d.ts +140 -0
  13. package/dist/cjs/internal/SchemaIdentity.d.ts.map +1 -0
  14. package/dist/cjs/internal/SchemaIdentity.js +718 -0
  15. package/dist/cjs/internal/SchemaIdentity.js.map +7 -0
  16. package/dist/cjs/internal/node.d.ts +1 -1
  17. package/dist/cjs/internal/node.d.ts.map +1 -1
  18. package/dist/cjs/internal/node.js +2 -1
  19. package/dist/cjs/internal/node.js.map +2 -2
  20. package/dist/esm/KeyMaterial.d.ts +1 -1
  21. package/dist/esm/KeyMaterial.js +1 -1
  22. package/dist/esm/KeyMaterial.js.map +1 -1
  23. package/dist/esm/Node.d.ts +71 -0
  24. package/dist/esm/Node.d.ts.map +1 -1
  25. package/dist/esm/Node.js +46 -0
  26. package/dist/esm/Node.js.map +1 -1
  27. package/dist/esm/StepKey.d.ts +1 -1
  28. package/dist/esm/StepKey.js +1 -1
  29. package/dist/esm/StepKey.js.map +1 -1
  30. package/dist/esm/internal/SchemaIdentity.d.ts +140 -0
  31. package/dist/esm/internal/SchemaIdentity.d.ts.map +1 -0
  32. package/dist/esm/internal/SchemaIdentity.js +1002 -0
  33. package/dist/esm/internal/SchemaIdentity.js.map +1 -0
  34. package/dist/esm/internal/node.d.ts +1 -1
  35. package/dist/esm/internal/node.d.ts.map +1 -1
  36. package/dist/esm/internal/node.js +3 -2
  37. package/dist/esm/internal/node.js.map +1 -1
  38. package/docs/README.md +1 -1
  39. package/docs/api.md +11 -1
  40. package/docs/concepts/authoring-ast.md +2 -1
  41. package/docs/installation.md +3 -3
  42. package/package.json +9 -8
  43. package/src/KeyMaterial.ts +1 -1
  44. package/src/Node.ts +79 -0
  45. package/src/StepKey.ts +1 -1
  46. package/src/internal/SchemaIdentity.ts +1096 -0
  47. package/src/internal/node.ts +3 -2
@@ -0,0 +1,1002 @@
1
+ /**
2
+ * The schema identity projection: the JSON-Schema-shaped document a schema
3
+ * contributes to a step key.
4
+ *
5
+ * A call node folds what its callee accepts and produces into the material a
6
+ * step key is derived from, and a catch node folds its filter the same way. A
7
+ * projection that printed differently under another `effect` release would
8
+ * re-key every recorded step in every journal at once. `effect` is a peer
9
+ * range, so the application picks the release; the projection therefore
10
+ * cannot be the installed `Schema.toJsonSchemaDocument`. `effect@4.0.4`
11
+ * already changed what that emitter prints for `Schema.Int`, `isMinLength`,
12
+ * `isPattern`, repeated bounds, `oneOf` unions and shared definitions.
13
+ *
14
+ * This module owns the bytes instead. It reads the schema through two public
15
+ * functions, `Schema.toCodecJson` and `Schema.toRepresentation`, and prints
16
+ * the result with the emitter and the check fragments fixed below, which
17
+ * reproduce `Schema.toJsonSchemaDocument(schema, options)` of
18
+ * `effect@4.0.0-rc.115` for both values of `onExcessProperty`.
19
+ *
20
+ * It is for identity only. Where a JSON Schema is shown to a model or a
21
+ * person, the installed emitter is the right one.
22
+ *
23
+ * What still moves an identity, by design: a release that changes a schema's
24
+ * JSON codec or its representation changes what the schema accepts on the
25
+ * wire. Three limits follow from what the representation carries:
26
+ *
27
+ * - A built-in check contributes the constraint its id and payload name. A
28
+ * `toJsonSchema` annotation passed to a built-in check constructor is not
29
+ * consulted; one on a `makeFilter` check is.
30
+ * - A built-in check this module does not list falls back to the installed
31
+ * check's own `toJsonSchema` annotation.
32
+ * - `Schema.brand` is not part of the representation from `effect@4.0.4` on.
33
+ * Under rc.115 a schema that takes a definition (an `identifier` or a
34
+ * recursive schema) and appeared both branded and unbranded in one document
35
+ * got two definitions; it now gets one.
36
+ *
37
+ * Provenance: {@link fromRepresentation} and its helpers are adapted from
38
+ * `effect@4.0.0-rc.115` (MIT, Copyright (c) 2023 Effectful Technologies Inc;
39
+ * the notice is in this package's `THIRD_PARTY_NOTICES.md`), files
40
+ * `src/internal/schema/toJsonSchemaDocument.ts`, `src/JsonSchema.ts`
41
+ * (`rewriteRefs`), `src/JsonPointer.ts` and `src/SchemaAST.ts` (`isJson`).
42
+ * Changes: `onExcessProperty` is the only option; one root; a built-in
43
+ * check's fragment comes from {@link builtinChecks}, not from
44
+ * the installed annotation; an approximate check result contributes its
45
+ * schema.
46
+ *
47
+ * @since 1.0.0
48
+ */
49
+ import * as Schema from "effect/Schema";
50
+ import * as SchemaRepresentation from "effect/SchemaRepresentation";
51
+ /**
52
+ * A schema a step key cannot be derived from: one with no JSON Schema form or
53
+ * no representation, or one whose built-in check the installed `effect`
54
+ * describes differently from the releases this package supports. The message
55
+ * is the one `effect@4.0.0-rc.115` gave for the same schema, where it gave one.
56
+ *
57
+ * @since 1.0.0
58
+ * @category errors
59
+ */
60
+ export class SchemaIdentityError extends Schema.TaggedError()("@smthrs/plan/SchemaIdentityError", {
61
+ message: Schema.String
62
+ }) {
63
+ }
64
+ /** @private */
65
+ const invalid = (message, path) => new SchemaIdentityError({
66
+ message: path.length === 0
67
+ ? message
68
+ : `${message}\n at ${path.map((key) => `[${typeof key === "string" ? JSON.stringify(key) : key}]`).join("")}`
69
+ });
70
+ /** @private */
71
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
72
+ /**
73
+ * Assigns an own property, `__proto__` included: a struct may declare a field
74
+ * by that name, and plain assignment would set the prototype instead.
75
+ *
76
+ * @private
77
+ */
78
+ const assignProperty = (target, key, value) => {
79
+ if (key === "__proto__") {
80
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
81
+ }
82
+ else {
83
+ target[key] = value;
84
+ }
85
+ };
86
+ /**
87
+ * Whether a value is JSON: finite numbers, plain objects, no cycles. An
88
+ * annotation that is not JSON never reaches the document.
89
+ *
90
+ * @private
91
+ */
92
+ const isJson = (value, path = new Set()) => {
93
+ if (typeof value !== "object" || value === null) {
94
+ return value === null || typeof value === "string" || typeof value === "boolean" ||
95
+ (typeof value === "number" && Number.isFinite(value));
96
+ }
97
+ if (path.has(value))
98
+ return false;
99
+ const members = Array.isArray(value) ? Array.from(value) : plainValues(value);
100
+ if (members === undefined)
101
+ return false;
102
+ const inner = new Set(path).add(value);
103
+ return members.every((member) => isJson(member, inner));
104
+ };
105
+ /** @private */
106
+ const plainValues = (value) => {
107
+ const prototype = Object.getPrototypeOf(value);
108
+ // A plain object from another realm has a different `Object.prototype`,
109
+ // but that prototype still has a null prototype.
110
+ return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null
111
+ ? Object.values(value)
112
+ : undefined;
113
+ };
114
+ /**
115
+ * Structural equality of two emitted schemas, ignoring key order. `NaN`
116
+ * equals itself: an author's fragment may carry one, and two definitions that
117
+ * differ in nothing else are one definition.
118
+ *
119
+ * @private
120
+ */
121
+ const sameJson = (left, right) => {
122
+ if (Array.isArray(left) && Array.isArray(right)) {
123
+ return left.length === right.length && left.every((member, index) => sameJson(member, right[index]));
124
+ }
125
+ if (isObject(left) && isObject(right)) {
126
+ const keys = Object.keys(left);
127
+ return keys.length === Object.keys(right).length &&
128
+ keys.every((key) => Object.hasOwn(right, key) && sameJson(left[key], right[key]));
129
+ }
130
+ return left === right || (left !== left && right !== right);
131
+ };
132
+ /** @private */
133
+ const escapeRegExp = (text) => text.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&");
134
+ /** @private */
135
+ const unescapeToken = (token) => token.replace(/~1/g, "/").replace(/~0/g, "~");
136
+ /** @private */
137
+ const definitionReference = (key) => `#/$defs/${encodeURI(key.replace(/~/g, "~0").replace(/\//g, "~1")).replace(/#/g, "%23")}`;
138
+ /**
139
+ * The JSON Pointer a URI fragment names, or `undefined` when it names none.
140
+ *
141
+ * @private
142
+ */
143
+ const decodeUriFragment = (fragment) => {
144
+ if (fragment.length === 0 || fragment === "#")
145
+ return "";
146
+ if (!fragment.startsWith("#"))
147
+ return undefined;
148
+ const encoded = fragment.slice(1);
149
+ try {
150
+ if (encodeURI(encoded).replace(/%25/g, "%").replace(/#/g, "%23") !== encoded)
151
+ return undefined;
152
+ const pointer = decodeURIComponent(encoded);
153
+ return pointer.startsWith("/") && !/~(?:[^01]|$)/.test(pointer) ? pointer : undefined;
154
+ }
155
+ catch {
156
+ return undefined;
157
+ }
158
+ };
159
+ /**
160
+ * Copies a schema, rewriting every `$ref` and `$dynamicRef` in a schema
161
+ * position. Values that are data (`enum`, `const`, `default`, `examples`) are
162
+ * not walked.
163
+ *
164
+ * @private
165
+ */
166
+ const rewriteRefs = (node, rewrite) => {
167
+ const walk = (value) => isObject(value) ? rewriteRefs(value, rewrite) : value;
168
+ const out = {};
169
+ for (const key of Object.keys(node)) {
170
+ const value = node[key];
171
+ let transformed = value;
172
+ switch (key) {
173
+ case "$defs":
174
+ case "properties":
175
+ case "patternProperties":
176
+ case "dependentSchemas":
177
+ if (isObject(value)) {
178
+ const mapped = {};
179
+ for (const name of Object.keys(value))
180
+ assignProperty(mapped, name, walk(value[name]));
181
+ transformed = mapped;
182
+ }
183
+ break;
184
+ case "allOf":
185
+ case "anyOf":
186
+ case "oneOf":
187
+ case "prefixItems":
188
+ if (Array.isArray(value))
189
+ transformed = value.map(walk);
190
+ break;
191
+ case "not":
192
+ case "additionalProperties":
193
+ case "propertyNames":
194
+ case "unevaluatedProperties":
195
+ case "items":
196
+ case "contains":
197
+ case "unevaluatedItems":
198
+ case "if":
199
+ case "then":
200
+ case "else":
201
+ case "contentSchema":
202
+ transformed = walk(value);
203
+ }
204
+ assignProperty(out, key, transformed);
205
+ }
206
+ if (typeof out["$ref"] === "string")
207
+ out["$ref"] = rewrite(out["$ref"]);
208
+ if (typeof out["$dynamicRef"] === "string")
209
+ out["$dynamicRef"] = rewrite(out["$dynamicRef"]);
210
+ return out;
211
+ };
212
+ /**
213
+ * The annotations a JSON Schema states, in the order rc.115 printed them.
214
+ *
215
+ * @private
216
+ */
217
+ const ordinaryAnnotations = (annotations) => {
218
+ if (annotations === undefined)
219
+ return undefined;
220
+ const out = {};
221
+ if (typeof annotations["title"] === "string")
222
+ out["title"] = annotations["title"];
223
+ if (typeof annotations["description"] === "string")
224
+ out["description"] = annotations["description"];
225
+ if (isJson(annotations["default"]))
226
+ out["default"] = annotations["default"];
227
+ if (Array.isArray(annotations["examples"]) && isJson(annotations["examples"])) {
228
+ out["examples"] = annotations["examples"];
229
+ }
230
+ if (typeof annotations["readOnly"] === "boolean")
231
+ out["readOnly"] = annotations["readOnly"];
232
+ if (typeof annotations["writeOnly"] === "boolean")
233
+ out["writeOnly"] = annotations["writeOnly"];
234
+ if (typeof annotations["format"] === "string")
235
+ out["format"] = annotations["format"];
236
+ if (typeof annotations["contentEncoding"] === "string")
237
+ out["contentEncoding"] = annotations["contentEncoding"];
238
+ if (typeof annotations["contentMediaType"] === "string")
239
+ out["contentMediaType"] = annotations["contentMediaType"];
240
+ if (isJson(annotations["contentSchema"]))
241
+ out["contentSchema"] = annotations["contentSchema"];
242
+ return Object.keys(out).length === 0 ? undefined : out;
243
+ };
244
+ /** @private */
245
+ const finitePattern = "[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?";
246
+ /** @private */
247
+ const stringPattern = "[\\s\\S]*?";
248
+ /** @private */
249
+ const bigIntPattern = "^-?\\d+$";
250
+ /** @private */
251
+ const symbolPattern = "^Symbol\\(([\\s\\S]*)\\)$";
252
+ /** @private */
253
+ const uuidPattern = (version) => version === null
254
+ ? "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|" +
255
+ "00000000-0000-0000-0000-000000000000|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$"
256
+ : `^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`;
257
+ /**
258
+ * One field of a built-in check's payload. A release that renamed or retyped
259
+ * it would otherwise print `undefined` into an identity, so it is refused.
260
+ *
261
+ * @private
262
+ */
263
+ const field = (id, payload, name, is) => {
264
+ const value = isObject(payload) ? payload[name] : undefined;
265
+ if (!is(value)) {
266
+ throw new SchemaIdentityError({
267
+ message: `Cannot derive a schema identity: check ${id} carries no usable "${name}" in its payload. ` +
268
+ "The installed effect release describes this check differently from the releases @smthrs/plan supports."
269
+ });
270
+ }
271
+ return value;
272
+ };
273
+ /** @private */
274
+ const isNumber = (value) => typeof value === "number";
275
+ /** @private */
276
+ const isString = (value) => typeof value === "string";
277
+ /** @private */
278
+ const isVersion = (value) => value === null || typeof value === "number";
279
+ /** @private */
280
+ const isFlag = (value) => value === undefined || typeof value === "boolean";
281
+ /** @private */
282
+ const pattern = (source) => () => ({ pattern: source });
283
+ /** @private */
284
+ const literalPattern = (name, source) => (id, payload) => ({
285
+ pattern: new RegExp(source(escapeRegExp(field(id, payload, name, isString)))).source
286
+ });
287
+ /** @private */
288
+ const bound = (keyword, name = keyword) => (id, payload) => ({
289
+ [keyword]: field(id, payload, name, isNumber)
290
+ });
291
+ /** @private */
292
+ const length = (name, array, string) => (id, payload, input) => ({
293
+ [input.type === "array" ? array : string]: field(id, payload, name, isNumber)
294
+ });
295
+ /** @private */
296
+ const empty = () => ({});
297
+ /**
298
+ * Every built-in check, keyed by the id its representation carries, with the
299
+ * fragment `effect@4.0.0-rc.115` printed for it.
300
+ *
301
+ * The three code-point checks did not exist under rc.115; they print what
302
+ * `effect@4.0.4` introduced them with. The date, big integer and size checks
303
+ * print nothing but still carry their own annotations, which is what the
304
+ * empty fragment keeps.
305
+ *
306
+ * @private
307
+ */
308
+ const builtinChecks = new Map([
309
+ ["effect/schema/isTrimmed", pattern("^\\S[\\s\\S]*\\S$|^\\S$|^$")],
310
+ ["effect/schema/isPattern", (id, payload) => ({ pattern: field(id, payload, "source", isString) })],
311
+ ["effect/schema/isStringFinite", pattern(`^${finitePattern}$`)],
312
+ ["effect/schema/isStringBigInt", pattern(bigIntPattern)],
313
+ ["effect/schema/isStringSymbol", pattern(symbolPattern)],
314
+ ["effect/schema/isUUID", (id, payload) => ({
315
+ pattern: uuidPattern(field(id, payload, "version", isVersion)),
316
+ format: "uuid"
317
+ })],
318
+ ["effect/schema/isGUID", pattern("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$")],
319
+ ["effect/schema/isULID", pattern("^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$")],
320
+ ["effect/schema/isBase64", pattern("^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$")],
321
+ ["effect/schema/isBase64Url", pattern("^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$")],
322
+ ["effect/schema/isStartingWith", literalPattern("startsWith", (escaped) => `^${escaped}`)],
323
+ ["effect/schema/isEndingWith", literalPattern("endsWith", (escaped) => `${escaped}$`)],
324
+ ["effect/schema/isIncluding", literalPattern("includes", (escaped) => escaped)],
325
+ ["effect/schema/isUppercased", pattern("^[^a-z]*$")],
326
+ ["effect/schema/isLowercased", pattern("^[^A-Z]*$")],
327
+ ["effect/schema/isCapitalized", pattern("^(?:[^a-z][\\s\\S]*)?$")],
328
+ ["effect/schema/isUncapitalized", pattern("^(?:[^A-Z][\\s\\S]*)?$")],
329
+ ["effect/schema/isFinite", () => ({ type: "number" })],
330
+ ["effect/schema/isInt", () => ({ type: "integer" })],
331
+ ["effect/schema/isGreaterThan", bound("exclusiveMinimum")],
332
+ ["effect/schema/isGreaterThanOrEqualTo", bound("minimum")],
333
+ ["effect/schema/isLessThan", bound("exclusiveMaximum")],
334
+ ["effect/schema/isLessThanOrEqualTo", bound("maximum")],
335
+ ["effect/schema/isBetween", (id, payload) => ({
336
+ [field(id, payload, "exclusiveMinimum", isFlag) ? "exclusiveMinimum" : "minimum"]: field(id, payload, "minimum", isNumber),
337
+ [field(id, payload, "exclusiveMaximum", isFlag) ? "exclusiveMaximum" : "maximum"]: field(id, payload, "maximum", isNumber)
338
+ })],
339
+ ["effect/schema/isMultipleOf", bound("multipleOf", "divisor")],
340
+ ["effect/schema/isGreaterThanDate", empty],
341
+ ["effect/schema/isGreaterThanOrEqualToDate", empty],
342
+ ["effect/schema/isLessThanDate", empty],
343
+ ["effect/schema/isLessThanOrEqualToDate", empty],
344
+ ["effect/schema/isBetweenDate", empty],
345
+ ["effect/schema/isGreaterThanBigInt", empty],
346
+ ["effect/schema/isGreaterThanOrEqualToBigInt", empty],
347
+ ["effect/schema/isLessThanBigInt", empty],
348
+ ["effect/schema/isLessThanOrEqualToBigInt", empty],
349
+ ["effect/schema/isBetweenBigInt", empty],
350
+ ["effect/schema/isMinLength", length("minLength", "minItems", "minLength")],
351
+ ["effect/schema/isMaxLength", length("maxLength", "maxItems", "maxLength")],
352
+ ["effect/schema/isBetweenLength", (id, payload, input) => {
353
+ const minimum = field(id, payload, "minimum", isNumber);
354
+ const maximum = field(id, payload, "maximum", isNumber);
355
+ return input.type === "array"
356
+ ? { allOf: [{ minItems: minimum }, { maxItems: maximum }] }
357
+ : { allOf: [{ minLength: minimum }, { maxLength: maximum }] };
358
+ }],
359
+ ["effect/schema/isMinCodePoints", bound("minLength", "minCodePoints")],
360
+ ["effect/schema/isMaxCodePoints", bound("maxLength", "maxCodePoints")],
361
+ ["effect/schema/isBetweenCodePoints", (id, payload) => ({
362
+ allOf: [
363
+ { minLength: field(id, payload, "minimum", isNumber) },
364
+ { maxLength: field(id, payload, "maximum", isNumber) }
365
+ ]
366
+ })],
367
+ ["effect/schema/isMinSize", empty],
368
+ ["effect/schema/isMaxSize", empty],
369
+ ["effect/schema/isBetweenSize", empty],
370
+ ["effect/schema/isMinProperties", bound("minProperties")],
371
+ ["effect/schema/isMaxProperties", bound("maxProperties")],
372
+ ["effect/schema/isBetweenProperties", (id, payload) => ({
373
+ minProperties: field(id, payload, "minimum", isNumber),
374
+ maxProperties: field(id, payload, "maximum", isNumber)
375
+ })],
376
+ ["effect/schema/isPropertyNames", (_id, _payload, input) => ({ propertyNames: input.schemas[0] })],
377
+ ["effect/schema/isUnique", () => ({ uniqueItems: true })],
378
+ ["effect/schema/isUniqueKey", null]
379
+ ]);
380
+ /**
381
+ * The ids {@link builtinChecks} lists, for the test that holds the list
382
+ * against the checks the installed `effect` can revive.
383
+ *
384
+ * @since 1.0.0
385
+ * @private
386
+ */
387
+ export const builtinCheckIds = [...builtinChecks.keys()];
388
+ /** @private */
389
+ const isCheckCallback = (value) => typeof value === "function";
390
+ /** @private */
391
+ const isApproximate = (output) => Array.isArray(output);
392
+ /**
393
+ * What prints a check, if anything does: the owned fragment for a built-in
394
+ * check, otherwise the author's own `toJsonSchema` annotation.
395
+ *
396
+ * @private
397
+ */
398
+ const fragmentSource = (check) => {
399
+ const representation = check.representation;
400
+ const builtin = representation === undefined ? undefined : builtinChecks.get(representation.id);
401
+ if (representation !== undefined && builtin !== undefined) {
402
+ return builtin === null ? undefined : (input) => builtin(representation.id, representation.payload, input);
403
+ }
404
+ const callback = check.annotations?.["toJsonSchema"];
405
+ if (!isCheckCallback(callback))
406
+ return undefined;
407
+ return (input) => {
408
+ const output = callback(input);
409
+ return isApproximate(output) ? output[0] : output;
410
+ };
411
+ };
412
+ /** @private */
413
+ const extractNumberType = (schema) => {
414
+ let type = schema["type"] === "number" || schema["type"] === "integer"
415
+ ? schema["type"]
416
+ : undefined;
417
+ let out = schema;
418
+ if (type !== undefined) {
419
+ out = { ...schema };
420
+ delete out["type"];
421
+ }
422
+ const allOf = out["allOf"];
423
+ if (Array.isArray(allOf)) {
424
+ const members = [];
425
+ let changed = false;
426
+ for (const member of allOf) {
427
+ const extracted = extractNumberType(member);
428
+ if (extracted.type !== undefined) {
429
+ changed = true;
430
+ if (type === undefined || extracted.type === "integer")
431
+ type = extracted.type;
432
+ }
433
+ if (Object.keys(extracted.schema).length > 0)
434
+ members.push(extracted.schema);
435
+ }
436
+ if (changed) {
437
+ const { allOf: _, ...rest } = out;
438
+ out = members.length === 0 ? rest : { ...rest, allOf: members };
439
+ }
440
+ }
441
+ return { type, schema: out };
442
+ };
443
+ /** @private */
444
+ const isNumberEncoding = (schema) => {
445
+ const anyOf = schema["anyOf"];
446
+ if (!Array.isArray(anyOf) || anyOf.length !== 2 || !isObject(anyOf[0]) || anyOf[0]["type"] !== "number")
447
+ return false;
448
+ const special = anyOf[1];
449
+ if (!isObject(special) || special["type"] !== "string")
450
+ return false;
451
+ const values = special["enum"];
452
+ return Array.isArray(values) && values.length === 3 && values.includes("NaN") && values.includes("Infinity") &&
453
+ values.includes("-Infinity");
454
+ };
455
+ // Keep this allowlist closed: applicators and dependent keywords can change meaning when moved across schema objects.
456
+ /** @private */
457
+ const inlineableCheckKeywords = "|type|format|pattern|multipleOf|minimum|maximum|exclusiveMinimum|exclusiveMaximum|minLength|maxLength|minItems|maxItems|uniqueItems|minProperties|maxProperties|propertyNames|";
458
+ // `format` and `content*` can affect validation, so they are not treated as pure annotations.
459
+ /** @private */
460
+ const promotableAnnotationKeywords = "|title|description|default|examples|readOnly|writeOnly|";
461
+ /** @private */
462
+ const inlineableAnnotatedCheckKeywords = inlineableCheckKeywords + promotableAnnotationKeywords;
463
+ /** @private */
464
+ const hasOnlyKeywords = (schema, allowed) => Object.keys(schema).every((key) => allowed.includes(`|${key}|`));
465
+ /** @private */
466
+ const hasNoCollisions = (left, rightKeys) => typeof left["$ref"] !== "string" && rightKeys.every((key) => !Object.hasOwn(left, key));
467
+ /**
468
+ * Adds a constraint to a schema: inline when no keyword collides, otherwise
469
+ * under `allOf`. A repeated bound is never merged into the tighter one.
470
+ *
471
+ * @private
472
+ */
473
+ const appendJsonSchema = (left, right, inlineCheck) => {
474
+ if (Object.keys(left).length === 0)
475
+ return right;
476
+ const rightKeys = Object.keys(right);
477
+ if (rightKeys.length === 0)
478
+ return left;
479
+ const leftType = left["type"] === "number" || left["type"] === "integer" ? left["type"] : undefined;
480
+ const numberEncoding = isNumberEncoding(left);
481
+ if (leftType !== undefined || numberEncoding) {
482
+ const extracted = extractNumberType(right);
483
+ if (extracted.type !== undefined) {
484
+ const type = leftType === "integer" || extracted.type === "integer" ? "integer" : "number";
485
+ const base = { ...left, type };
486
+ if (numberEncoding)
487
+ delete base["anyOf"];
488
+ const extractedKeys = Object.keys(extracted.schema);
489
+ if (extractedKeys.length === 0)
490
+ return base;
491
+ return hasOnlyKeywords(extracted.schema, promotableAnnotationKeywords) && hasNoCollisions(base, extractedKeys)
492
+ ? { ...base, ...extracted.schema }
493
+ : appendJsonSchema(base, extracted.schema, inlineCheck);
494
+ }
495
+ }
496
+ if (inlineCheck && hasNoCollisions(left, rightKeys))
497
+ return { ...left, ...right };
498
+ const rightAllOf = right["allOf"];
499
+ const members = Array.isArray(rightAllOf) && rightKeys.length === 1 ? rightAllOf : [right];
500
+ const leftAllOf = left["allOf"];
501
+ if (Array.isArray(leftAllOf))
502
+ return { ...left, allOf: [...leftAllOf, ...members] };
503
+ if (typeof left["$ref"] === "string")
504
+ return { allOf: [left, ...members] };
505
+ return { ...left, allOf: members };
506
+ };
507
+ /** @private */
508
+ const jsonSchemaTypes = new Set([
509
+ "string",
510
+ "number",
511
+ "boolean",
512
+ "array",
513
+ "object",
514
+ "null",
515
+ "integer"
516
+ ]);
517
+ /** @private */
518
+ const isJsonSchemaType = (input) => jsonSchemaTypes.has(input);
519
+ /**
520
+ * Collapses a union of single-type enums into one enum, the way a union of
521
+ * literals reads.
522
+ *
523
+ * @private
524
+ */
525
+ const compactEnums = (schemas) => {
526
+ let sharedType = undefined;
527
+ const values = [];
528
+ for (const schema of schemas) {
529
+ const members = schema["enum"];
530
+ if (Object.keys(schema).length !== 2 || schema["type"] === undefined || !Array.isArray(members) ||
531
+ members.length === 0) {
532
+ return undefined;
533
+ }
534
+ if (sharedType === undefined)
535
+ sharedType = schema["type"];
536
+ else if (schema["type"] !== sharedType)
537
+ return undefined;
538
+ for (const value of members) {
539
+ if (values.includes(value))
540
+ return undefined;
541
+ values.push(value);
542
+ }
543
+ }
544
+ return { type: sharedType, enum: values };
545
+ };
546
+ /** @private */
547
+ const partPattern = (part) => {
548
+ switch (part._tag) {
549
+ case "Literal":
550
+ return escapeRegExp(String(part.literal));
551
+ case "String":
552
+ return stringPattern;
553
+ case "Number":
554
+ return finitePattern;
555
+ case "TemplateLiteral":
556
+ return part.parts.map(partPattern).join("");
557
+ case "Union":
558
+ return `(?:${part.types.map(partPattern).join("|")})`;
559
+ default:
560
+ throw invalid("Invalid schema representation document", []);
561
+ }
562
+ };
563
+ /** @private */
564
+ const neverSchema = { not: {} };
565
+ /**
566
+ * Refuses a representation kind this module does not print. A later `effect`
567
+ * release that adds one fails here by name, not as a missing property.
568
+ *
569
+ * @private
570
+ */
571
+ const unsupported = (representation, path) => {
572
+ throw invalid(`Unsupported schema representation ${representation._tag}`, path);
573
+ };
574
+ /**
575
+ * Prints a representation document as the JSON Schema document rc.115
576
+ * printed for it under the same options.
577
+ *
578
+ * Reserved for {@link schemaIdentity} and for the tests that hand it a
579
+ * representation no schema produces.
580
+ *
581
+ * @since 1.0.0
582
+ * @private
583
+ */
584
+ export const fromRepresentation = (document, options) => {
585
+ const closed = options?.onExcessProperty === "error";
586
+ const references = document.references;
587
+ // A key maps to its compiled schema, to the key it is an alias of, or to
588
+ // `null` while it is compiling.
589
+ const definitionStates = new Map();
590
+ const compiledRepresentations = new WeakMap();
591
+ const fallbackDefinitions = new Map();
592
+ const compileDefinition = (key, path) => {
593
+ const compiled = definitionStates.get(key);
594
+ if (compiled !== undefined)
595
+ return typeof compiled === "string" ? compiled : key;
596
+ const representation = Object.hasOwn(references, key) ? references[key] : undefined;
597
+ if (representation === undefined)
598
+ throw invalid(`Invalid reference ${key}`, [...path, "$ref"]);
599
+ definitionStates.set(key, null);
600
+ const schema = recur(representation, ["references", key]);
601
+ const fallback = identifierFallback(representation);
602
+ if (fallback !== undefined) {
603
+ const candidates = fallbackDefinitions.get(fallback);
604
+ const match = candidates?.find((candidate) => sameJson(definitionStates.get(candidate), schema));
605
+ if (match === undefined) {
606
+ if (candidates === undefined)
607
+ fallbackDefinitions.set(fallback, [key]);
608
+ else
609
+ candidates.push(key);
610
+ }
611
+ else {
612
+ definitionStates.set(key, match);
613
+ return match;
614
+ }
615
+ }
616
+ definitionStates.set(key, schema);
617
+ return key;
618
+ };
619
+ const finalize = (schema) => rewriteRefs(schema, (reference) => {
620
+ const pointer = decodeUriFragment(reference);
621
+ if (pointer === undefined) {
622
+ if (/^#(?:\/|%2f)/i.test(reference)) {
623
+ throw new SchemaIdentityError({ message: `Invalid JSON Pointer URI fragment ${JSON.stringify(reference)}` });
624
+ }
625
+ return reference;
626
+ }
627
+ if (!pointer.startsWith("/$defs/"))
628
+ return reference;
629
+ const separator = pointer.indexOf("/", 7);
630
+ const canonical = definitionStates.get(unescapeToken(pointer.slice(7, separator < 0 ? undefined : separator)));
631
+ if (typeof canonical !== "string")
632
+ return reference;
633
+ // URI-encoded slashes separate pointer tokens; only ~1 represents a slash within a token.
634
+ return reference.replace(/^#(?:\/|%2f).*?(?:\/|%2f).*?(?=\/|%2f|$)/i, definitionReference(canonical));
635
+ });
636
+ const identifierFallback = (representation) => {
637
+ if (representation._tag === "Reference")
638
+ return undefined;
639
+ const last = representation.checks.at(-1);
640
+ const annotations = last === undefined ? representation.annotations : last.annotations;
641
+ const fallback = annotations?.["~identifier"];
642
+ return typeof annotations?.["identifier"] !== "string" && typeof fallback === "string" ? fallback : undefined;
643
+ };
644
+ const compileCheck = (check, type, path) => {
645
+ const source = fragmentSource(check);
646
+ if (source !== undefined) {
647
+ const schemas = check.representation?.schemas?.map((schema, index) => recur(schema, [...path, "representation", "schemas", index])) ?? [];
648
+ const fragment = source({ type, schemas });
649
+ const ordinary = ordinaryAnnotations(check.annotations);
650
+ const schema = ordinary === undefined ? fragment : { ...fragment, ...ordinary };
651
+ const allowed = ordinary === undefined ? inlineableCheckKeywords : inlineableAnnotatedCheckKeywords;
652
+ return check._tag === "Filter" && hasOnlyKeywords(schema, allowed) &&
653
+ (ordinary === undefined || hasOnlyKeywords(ordinary, promotableAnnotationKeywords))
654
+ ? [schema, true]
655
+ : [schema];
656
+ }
657
+ if (check._tag === "Filter")
658
+ return undefined;
659
+ const allOf = [];
660
+ check.checks.forEach((child, index) => {
661
+ const compiled = compileCheck(child, type, [...path, "checks", index]);
662
+ if (compiled !== undefined)
663
+ allOf.push(compiled[0]);
664
+ });
665
+ if (allOf.length === 0)
666
+ return undefined;
667
+ const ordinary = ordinaryAnnotations(check.annotations);
668
+ return [ordinary === undefined ? { allOf } : { allOf, ...ordinary }];
669
+ };
670
+ const recur = (representation, path) => {
671
+ if (representation._tag === "Reference") {
672
+ return { $ref: definitionReference(compileDefinition(representation.$ref, path)) };
673
+ }
674
+ const cached = compiledRepresentations.get(representation);
675
+ if (cached !== undefined)
676
+ return cached;
677
+ let output = on(representation, path);
678
+ const ordinary = ordinaryAnnotations(representation.annotations);
679
+ if (ordinary !== undefined)
680
+ output = { ...output, ...ordinary };
681
+ representation.checks.forEach((check, index) => {
682
+ const compiled = compileCheck(check, isJsonSchemaType(output["type"]) ? output["type"] : undefined, [...path, "checks", index]);
683
+ if (compiled !== undefined)
684
+ output = appendJsonSchema(output, ...compiled);
685
+ });
686
+ compiledRepresentations.set(representation, output);
687
+ return output;
688
+ };
689
+ const annotated = (compiled, annotations) => {
690
+ const ordinary = ordinaryAnnotations(annotations);
691
+ return ordinary === undefined ? compiled : appendJsonSchema(compiled, ordinary);
692
+ };
693
+ const on = (representation, path) => {
694
+ switch (representation._tag) {
695
+ case "Any":
696
+ case "Unknown":
697
+ case "Declaration":
698
+ return {};
699
+ case "ObjectKeyword":
700
+ return { anyOf: [{ type: "object" }, { type: "array" }] };
701
+ case "Void":
702
+ case "Undefined":
703
+ case "Null":
704
+ return { type: "null" };
705
+ case "BigInt":
706
+ return { type: "string", allOf: [{ pattern: bigIntPattern }] };
707
+ case "Symbol":
708
+ return { type: "string", allOf: [{ pattern: symbolPattern }] };
709
+ case "UniqueSymbol":
710
+ return Symbol.keyFor(representation.symbol) === undefined
711
+ ? { not: {} }
712
+ : { type: "string", enum: [String(representation.symbol)] };
713
+ case "Suspend":
714
+ return recur(representation.thunk, [...path, "thunk"]);
715
+ case "Never":
716
+ return { not: {} };
717
+ case "String":
718
+ return { type: "string" };
719
+ case "Number":
720
+ return { anyOf: [{ type: "number" }, { type: "string", enum: ["NaN", "Infinity", "-Infinity"] }] };
721
+ case "Boolean":
722
+ return { type: "boolean" };
723
+ case "Literal": {
724
+ const literal = representation.literal;
725
+ return typeof literal === "bigint"
726
+ ? { type: "string", enum: [String(literal)] }
727
+ : { type: typeof literal, enum: [literal] };
728
+ }
729
+ case "Enum": {
730
+ const types = representation.enums.map(([title, literal], index) => {
731
+ if (typeof literal === "number" && !Number.isFinite(literal)) {
732
+ throw invalid(`Invalid numeric enum value ${String(literal)}`, [...path, "enums", index, 1]);
733
+ }
734
+ return { type: typeof literal, enum: [literal], title };
735
+ });
736
+ return types.length === 0 ? { not: {} } : { anyOf: types };
737
+ }
738
+ case "TemplateLiteral":
739
+ return { type: "string", pattern: `^${representation.parts.map(partPattern).join("")}$` };
740
+ case "Arrays": {
741
+ const rest = representation.rest[0];
742
+ if (representation.rest.length > 1) {
743
+ throw invalid("Invalid schema representation document", [...path, "rest"]);
744
+ }
745
+ const out = { type: "array" };
746
+ let minItems = representation.elements.length;
747
+ const prefixItems = representation.elements.map((element, index) => {
748
+ if (element.isOptional)
749
+ minItems--;
750
+ return annotated(recur(element.type, [...path, "elements", index, "type"]), element.annotations);
751
+ });
752
+ if (prefixItems.length > 0) {
753
+ out["prefixItems"] = prefixItems;
754
+ out["maxItems"] = representation.elements.length;
755
+ if (minItems > 0)
756
+ out["minItems"] = minItems;
757
+ }
758
+ else {
759
+ out["items"] = false;
760
+ }
761
+ if (rest !== undefined) {
762
+ delete out["maxItems"];
763
+ const items = recur(rest, [...path, "rest", 0]);
764
+ if (Object.keys(items).length > 0)
765
+ out["items"] = items;
766
+ else
767
+ delete out["items"];
768
+ }
769
+ return out;
770
+ }
771
+ case "Objects": {
772
+ if (representation.propertySignatures.length === 0 && representation.indexSignatures.length === 0) {
773
+ return { not: { type: "null" } };
774
+ }
775
+ const out = { type: "object" };
776
+ const properties = {};
777
+ const required = [];
778
+ representation.propertySignatures.forEach((property, index) => {
779
+ const name = property.name;
780
+ if (typeof name !== "string") {
781
+ throw invalid("Invalid schema representation document", [...path, "propertySignatures", index, "name"]);
782
+ }
783
+ assignProperty(properties, name, annotated(recur(property.type, [...path, "propertySignatures", index, "type"]), property.annotations));
784
+ if (!property.isOptional)
785
+ required.push(name);
786
+ });
787
+ if (representation.propertySignatures.length > 0)
788
+ out["properties"] = properties;
789
+ if (required.length > 0)
790
+ out["required"] = required;
791
+ const patternProperties = {};
792
+ const additionalPropertySchemas = [];
793
+ const indexValueSchemas = [];
794
+ let hasUnrepresentableIndexPattern = false;
795
+ representation.indexSignatures.forEach((signature, index) => {
796
+ const compiled = recur(signature.type, [...path, "indexSignatures", index, "type"]);
797
+ const type = sameJson(compiled, neverSchema) ? false : compiled;
798
+ indexValueSchemas.push(type);
799
+ const patterns = parameterPatterns(signature.parameter, [...path, "indexSignatures", index, "parameter"], new Set());
800
+ if (patterns === undefined) {
801
+ hasUnrepresentableIndexPattern = true;
802
+ }
803
+ else if (patterns.length === 0) {
804
+ additionalPropertySchemas.push(type);
805
+ }
806
+ else {
807
+ for (const key of patterns) {
808
+ const previous = Object.hasOwn(patternProperties, key) ? patternProperties[key] : undefined;
809
+ assignProperty(patternProperties, key, previous === undefined
810
+ ? type
811
+ : previous === false || type === false
812
+ ? false
813
+ : appendJsonSchema(previous, type));
814
+ }
815
+ }
816
+ });
817
+ const hasPatternProperties = Object.keys(patternProperties).length > 0;
818
+ if (hasPatternProperties)
819
+ out["patternProperties"] = patternProperties;
820
+ const additional = additionalPropertySchemas.map((type) => ({ type: "object", additionalProperties: type }));
821
+ if (hasUnrepresentableIndexPattern && closed) {
822
+ const propertyNames = [];
823
+ if (representation.propertySignatures.length > 0)
824
+ propertyNames.push({ enum: Object.keys(properties) });
825
+ representation.indexSignatures.forEach((signature, index) => {
826
+ propertyNames.push(recur(signature.parameter, [...path, "indexSignatures", index, "parameter"]));
827
+ });
828
+ out["propertyNames"] = propertyNames.length === 1 ? propertyNames[0] : { anyOf: propertyNames };
829
+ out["additionalProperties"] = indexValueSchemas.length === 1
830
+ ? indexValueSchemas[0]
831
+ : { anyOf: indexValueSchemas };
832
+ if (additional.length > 0)
833
+ out["allOf"] = additional;
834
+ }
835
+ else if (additional.length === 0) {
836
+ out["additionalProperties"] = hasUnrepresentableIndexPattern || !closed;
837
+ }
838
+ else if (additionalPropertySchemas.length === 1 && representation.propertySignatures.length === 0 &&
839
+ !hasPatternProperties) {
840
+ out["additionalProperties"] = additionalPropertySchemas[0];
841
+ }
842
+ else {
843
+ out["allOf"] = additional;
844
+ }
845
+ const additionalProperties = out["additionalProperties"];
846
+ if (isObject(additionalProperties) && Object.keys(additionalProperties).length === 0) {
847
+ delete out["additionalProperties"];
848
+ }
849
+ return out;
850
+ }
851
+ case "Union": {
852
+ const types = representation.types.map((type, index) => recur(type, [...path, "types", index]));
853
+ if (types.length === 0)
854
+ return { not: {} };
855
+ const mode = representation.options?.mode ?? "anyOf";
856
+ if (mode === "anyOf" && types.length > 1) {
857
+ const compacted = compactEnums(types);
858
+ if (compacted !== undefined)
859
+ return compacted;
860
+ }
861
+ return mode === "anyOf" ? { anyOf: types } : { oneOf: types };
862
+ }
863
+ default:
864
+ return unsupported(representation, path);
865
+ }
866
+ };
867
+ const parameterPatterns = (parameter, path, seenReferences) => {
868
+ switch (parameter._tag) {
869
+ case "Reference": {
870
+ const target = Object.hasOwn(references, parameter.$ref) ? references[parameter.$ref] : undefined;
871
+ if (target === undefined)
872
+ throw invalid(`Invalid reference ${parameter.$ref}`, [...path, "$ref"]);
873
+ compileDefinition(parameter.$ref, path);
874
+ if (seenReferences.has(parameter.$ref))
875
+ return undefined;
876
+ return parameterPatterns(target, ["references", parameter.$ref], new Set(seenReferences).add(parameter.$ref));
877
+ }
878
+ case "String": {
879
+ const check = parameter.checks[0];
880
+ if (check === undefined)
881
+ return [];
882
+ if (parameter.checks.length !== 1 || check._tag !== "Filter" || fragmentSource(check) === undefined) {
883
+ return undefined;
884
+ }
885
+ const schema = recur(parameter, path);
886
+ const key = schema["pattern"];
887
+ return schema["type"] === "string" && typeof key === "string" &&
888
+ hasOnlyKeywords(schema, "|type|pattern|title|description|default|examples|readOnly|writeOnly|")
889
+ ? [key]
890
+ : undefined;
891
+ }
892
+ case "TemplateLiteral":
893
+ return parameter.checks.length === 0 ? [`^${parameter.parts.map(partPattern).join("")}$`] : undefined;
894
+ case "Union": {
895
+ if (parameter.types.length === 0 || parameter.checks.length > 0 || parameter.options?.mode === "oneOf") {
896
+ return undefined;
897
+ }
898
+ const patterns = [];
899
+ for (const [index, member] of parameter.types.entries()) {
900
+ const memberPatterns = parameterPatterns(member, [...path, "types", index], seenReferences);
901
+ if (memberPatterns === undefined)
902
+ return undefined;
903
+ if (memberPatterns.length === 0)
904
+ return [];
905
+ patterns.push(...memberPatterns);
906
+ }
907
+ return patterns;
908
+ }
909
+ default:
910
+ throw invalid("Invalid schema representation document", path);
911
+ }
912
+ };
913
+ const referenceKeys = Object.keys(references);
914
+ for (const key of referenceKeys)
915
+ compileDefinition(key, ["references", key]);
916
+ const schema = finalize(recur(document.representation, ["representation"]));
917
+ const definitions = {};
918
+ for (const key of referenceKeys) {
919
+ const compiled = definitionStates.get(key);
920
+ if (isObject(compiled))
921
+ assignProperty(definitions, key, finalize(compiled));
922
+ }
923
+ return { dialect: "draft-2020-12", schema, definitions };
924
+ };
925
+ /**
926
+ * The identity a schema contributes: a pure function of the schema and the
927
+ * option, and of nothing the installed `effect` release prints. It takes the
928
+ * place of `Schema.toJsonSchemaDocument(schema, options)` wherever the
929
+ * document is hashed, compared or pinned.
930
+ *
931
+ * Throws what rc.115 threw for a schema with no JSON Schema form: more than
932
+ * one rest element, a record key that is neither a string nor a template, a
933
+ * `toJsonSchema` annotation that throws.
934
+ *
935
+ * @since 1.0.0
936
+ * @private
937
+ */
938
+ export const schemaIdentity = (schema, options) => fromRepresentation(Schema.toRepresentation(Schema.toCodecJson(schema)), options);
939
+ /**
940
+ * The six built-in checks `effect` renamed after `4.0.0-rc.115`, from the id
941
+ * a later release writes to the id rc.115 wrote. Their payloads and
942
+ * annotations did not change.
943
+ *
944
+ * @private
945
+ */
946
+ const rc115CheckIds = new Map([
947
+ ["effect/schema/isStartingWith", "effect/schema/isStartsWith"],
948
+ ["effect/schema/isEndingWith", "effect/schema/isEndsWith"],
949
+ ["effect/schema/isIncluding", "effect/schema/isIncludes"],
950
+ ["effect/schema/isBetweenLength", "effect/schema/isLengthBetween"],
951
+ ["effect/schema/isBetweenSize", "effect/schema/isSizeBetween"],
952
+ ["effect/schema/isBetweenProperties", "effect/schema/isPropertiesLengthBetween"]
953
+ ]);
954
+ /**
955
+ * A copy of an encoded representation with every renamed check id written
956
+ * back as rc.115 wrote it. Only an element of a `checks` array is a check.
957
+ * `annotations` and `payload` hold an author's data, which may have any
958
+ * shape, so they are copied as they are. Key order is kept, so the bytes
959
+ * match.
960
+ *
961
+ * @private
962
+ */
963
+ const withRc115CheckIds = (value, isCheck = false) => {
964
+ if (Array.isArray(value))
965
+ return value.map((member) => withRc115CheckIds(member, isCheck));
966
+ if (!isObject(value))
967
+ return value;
968
+ const out = {};
969
+ for (const [key, member] of Object.entries(value)) {
970
+ if (key === "annotations" || key === "payload") {
971
+ assignProperty(out, key, member);
972
+ continue;
973
+ }
974
+ const renamed = isCheck && key === "representation" && isObject(member) && typeof member["id"] === "string"
975
+ ? rc115CheckIds.get(member["id"])
976
+ : undefined;
977
+ assignProperty(out, key, renamed === undefined
978
+ ? withRc115CheckIds(member, key === "checks")
979
+ : withRc115CheckIds({ ...member, id: renamed }));
980
+ }
981
+ return out;
982
+ };
983
+ /**
984
+ * The representation identity of a schema AST: what
985
+ * `SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(ast))`
986
+ * returned under `effect@4.0.0-rc.115`, key order included. A sealed action's
987
+ * step key hashes it, so it must not move with the installed `effect`.
988
+ *
989
+ * The installed release encodes the representation; this writes back the six
990
+ * check ids renamed after rc.115. One difference cannot be undone: from
991
+ * `effect@4.0.4` on, `Schema.brand` is not part of the AST, so the `brands`
992
+ * annotation rc.115 wrote is absent and a declaration with a brand re-keys
993
+ * once.
994
+ *
995
+ * Throws what rc.115 threw for an AST with no representation, such as one
996
+ * whose check carries no `representation` annotation.
997
+ *
998
+ * @since 1.0.0
999
+ * @private
1000
+ */
1001
+ export const representationIdentity = (ast) => withRc115CheckIds(SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(ast)));
1002
+ //# sourceMappingURL=SchemaIdentity.js.map