@elaraai/east 1.0.70 → 1.0.71

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 (28) hide show
  1. package/dist/src/location.d.ts +12 -0
  2. package/dist/src/location.d.ts.map +1 -1
  3. package/dist/src/location.js +15 -3
  4. package/dist/src/location.js.map +1 -1
  5. package/dist/src/serialization/index.d.ts +2 -0
  6. package/dist/src/serialization/index.d.ts.map +1 -1
  7. package/dist/src/serialization/index.js +2 -0
  8. package/dist/src/serialization/index.js.map +1 -1
  9. package/dist/src/serialization/json.d.ts.map +1 -1
  10. package/dist/src/serialization/json.js +31 -1
  11. package/dist/src/serialization/json.js.map +1 -1
  12. package/dist/src/serialization/json_schema.d.ts +76 -0
  13. package/dist/src/serialization/json_schema.d.ts.map +1 -0
  14. package/dist/src/serialization/json_schema.js +290 -0
  15. package/dist/src/serialization/json_schema.js.map +1 -0
  16. package/dist/src/serialization/json_schema.spec.d.ts +2 -0
  17. package/dist/src/serialization/json_schema.spec.d.ts.map +1 -0
  18. package/dist/src/serialization/json_schema.spec.js +327 -0
  19. package/dist/src/serialization/json_schema.spec.js.map +1 -0
  20. package/dist/src/serialization/json_schema_to_type.d.ts +74 -0
  21. package/dist/src/serialization/json_schema_to_type.d.ts.map +1 -0
  22. package/dist/src/serialization/json_schema_to_type.js +500 -0
  23. package/dist/src/serialization/json_schema_to_type.js.map +1 -0
  24. package/dist/src/serialization/json_schema_to_type.spec.d.ts +2 -0
  25. package/dist/src/serialization/json_schema_to_type.spec.d.ts.map +1 -0
  26. package/dist/src/serialization/json_schema_to_type.spec.js +372 -0
  27. package/dist/src/serialization/json_schema_to_type.spec.js.map +1 -0
  28. package/package.json +1 -1
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { toEastTypeValue } from "../type_of_type.js";
6
+ import { isVariant } from "../containers/variant.js";
7
+ const SCHEMA_URI = {
8
+ "2020-12": "https://json-schema.org/draft/2020-12/schema",
9
+ "draft-07": "http://json-schema.org/draft-07/schema#",
10
+ // OpenAPI 3.0 schema objects live inside an OpenAPI document and carry no
11
+ // $schema of their own; stamping one would make the fragment invalid.
12
+ "openapi-3.0": null,
13
+ };
14
+ /** i64 bounds, as the decoder enforces them. */
15
+ const I64_MAX = "9223372036854775807";
16
+ const I64_MIN_ABS = "9223372036854775808";
17
+ /**
18
+ * A regex alternation matching every decimal string from `"1"` to `max`, with
19
+ * no leading zeros. Zero is excluded so the sign can be attached without
20
+ * admitting `"-0"`, which the encoder never emits.
21
+ *
22
+ * @param max - The inclusive upper bound, as decimal digits
23
+ * @returns An un-anchored alternation body
24
+ *
25
+ * @remarks
26
+ * Generated rather than hand-written because the obvious approximation —
27
+ * `[1-9][0-9]{0,18}` for i64 — accepts every 19-digit value up to
28
+ * 9999999999999999999, so an unsigned 64-bit id passes a producer's validator
29
+ * and then fails on receipt. The construction fixes each prefix of `max` in
30
+ * turn and lets the digit at that position range below `max`'s.
31
+ */
32
+ function boundedDigitPattern(max) {
33
+ const k = max.length;
34
+ const alts = [];
35
+ // Every shorter length is unconditionally below the bound; there are none
36
+ // to add when the bound is itself a single digit.
37
+ if (k >= 2)
38
+ alts.push(k === 2 ? "[1-9]" : `[1-9][0-9]{0,${k - 2}}`);
39
+ let prefix = "";
40
+ for (let i = 0; i < k; i++) {
41
+ const digit = max.charCodeAt(i) - 48;
42
+ const lo = i === 0 ? 1 : 0;
43
+ if (digit > lo) {
44
+ const cls = digit - 1 === lo ? `${lo}` : `[${lo}-${digit - 1}]`;
45
+ const rest = k - i - 1;
46
+ alts.push(`${prefix}${cls}${rest === 0 ? "" : rest === 1 ? "[0-9]" : `[0-9]{${rest}}`}`);
47
+ }
48
+ prefix += max[i];
49
+ }
50
+ alts.push(max);
51
+ return alts.join("|");
52
+ }
53
+ /** The exact accepted form of East JSON's `Integer` encoding. */
54
+ function integerPattern() {
55
+ return `^(?:0|(?:${boundedDigitPattern(I64_MAX)})|-(?:${boundedDigitPattern(I64_MIN_ABS)}))$`;
56
+ }
57
+ /**
58
+ * The canonical text `DateTime` encodes to — always UTC, always three
59
+ * fractional digits, always an explicit `+00:00` offset.
60
+ *
61
+ * @remarks
62
+ * Stricter than the decoder, deliberately: the decoder also accepts a `Z`
63
+ * suffix and any numeric offset, neither of which the encoder ever emits.
64
+ * The year is pinned to `0001`–`9999`, the range every runtime reads (python's
65
+ * datetime starts at year 1), so `0000` is refused by the validator rather
66
+ * than only by the reader. Every digit class is spelled `[0-9]`, never `\d`:
67
+ * a validator built on python's `re` reads `\d` as any Unicode digit, so a
68
+ * timestamp written in Arabic-Indic digits would pass a partner's check and
69
+ * then fail on receipt. Calendar-impossible dates such as `2026-02-30` still
70
+ * match — no regex a schema can carry rules them out — and are rejected when
71
+ * the date is constructed.
72
+ */
73
+ const DATETIME_PATTERN = "^(?:000[1-9]|00[1-9][0-9]|0[1-9][0-9]{2}|[1-9][0-9]{3})" +
74
+ "-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])" +
75
+ "T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\\.[0-9]{3}\\+00:00$";
76
+ /** The canonical text `Blob` encodes to — `0x` and an even count of lowercase hex. */
77
+ const BLOB_PATTERN = "^0x(?:[0-9a-f]{2})*$";
78
+ /** The non-finite floats JSON cannot hold, as the encoder spells them. Sorted for determinism. */
79
+ const FLOAT_SPECIALS = ["-0.0", "-Infinity", "Infinity", "NaN"];
80
+ /**
81
+ * The exact lexical forms East JSON's scalar encodings take.
82
+ *
83
+ * @remarks
84
+ * Published so a reader can enforce precisely what {@link jsonSchemaFor}
85
+ * describes — the contract and the check are then one definition, not two that
86
+ * have to be kept in step by hand. Each is stricter than the historic decoder,
87
+ * which also accepts hexadecimal and whitespace-padded integers, a `Z` suffix
88
+ * or any numeric offset on a timestamp, and uppercase hex blobs.
89
+ */
90
+ export const EAST_JSON_PATTERNS = {
91
+ /** Decimal i64, no leading zeros, no sign on zero. */
92
+ get integer() { return integerPattern(); },
93
+ /** RFC 3339 in UTC with three fractional digits and an explicit `+00:00`. */
94
+ datetime: DATETIME_PATTERN,
95
+ /** `0x` followed by an even count of lowercase hex digits. */
96
+ blob: BLOB_PATTERN,
97
+ /** The non-finite floats, as strings, in the order the schema lists them. */
98
+ floatSpecials: FLOAT_SPECIALS,
99
+ };
100
+ /** Where definitions live, and how they are referenced, in a given release. */
101
+ function defsKeyword(draft) {
102
+ return draft === "2020-12" ? "$defs" : "definitions";
103
+ }
104
+ /**
105
+ * Emits a JSON Schema describing the East-JSON encoding of an East type.
106
+ *
107
+ * @param type - The East type to describe
108
+ * @param options - Which release to emit
109
+ * @returns A JSON Schema document
110
+ * @throws {Error} When the type has no JSON form — `Never`, `Function` or
111
+ * `AsyncFunction` — naming the offending type
112
+ *
113
+ * @remarks
114
+ * The schema describes what `East.String.printJson` emits and what a strict
115
+ * reader accepts, so a producer validating against it cannot send a payload
116
+ * that would then be rejected. It pins the **encoder's** canonical output
117
+ * rather than the decoder's tolerance: the decoder accepts hexadecimal and
118
+ * whitespace-padded integers, a `Z` suffix on timestamps and uppercase hex
119
+ * blobs, and none of those appear here.
120
+ *
121
+ * The document is deterministic — key order, `$defs` names and case order are
122
+ * fixed by the type, not by process state — so the TypeScript and Python
123
+ * implementations emit byte-identical bytes for the same type and release.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const ReadingType = StructType({ sensor: StringType, litres: IntegerType });
128
+ * const schema = jsonSchemaFor(ArrayType(ReadingType), { draft: "draft-07" });
129
+ * writeFileSync("contract.schema.json", JSON.stringify(schema, null, 2));
130
+ * ```
131
+ */
132
+ export function jsonSchemaFor(type, options = {}) {
133
+ const draft = options.draft ?? "2020-12";
134
+ const typeValue = isVariant(type) ? type : toEastTypeValue(type);
135
+ const ctx = { draft, defs: {}, names: new Map() };
136
+ const body = schemaOf(typeValue, ctx);
137
+ const out = {};
138
+ const uri = SCHEMA_URI[draft];
139
+ if (uri !== null)
140
+ out["$schema"] = uri;
141
+ for (const [k, v] of Object.entries(body))
142
+ out[k] = v;
143
+ if (Object.keys(ctx.defs).length > 0)
144
+ out[defsKeyword(draft)] = ctx.defs;
145
+ return out;
146
+ }
147
+ /** The schema for one type node, accumulating any recursive definitions into `ctx`. */
148
+ function schemaOf(t, ctx) {
149
+ switch (t.type) {
150
+ case "Never":
151
+ throw new Error("jsonSchemaFor cannot describe Never — it has no values, so no JSON document satisfies it");
152
+ case "Function":
153
+ throw new Error("jsonSchemaFor cannot describe Function — JSON has no function form");
154
+ case "AsyncFunction":
155
+ throw new Error("jsonSchemaFor cannot describe AsyncFunction — JSON has no function form");
156
+ case "Null":
157
+ // OpenAPI 3.0 predates the "null" type; `nullable` plus a closed enum is
158
+ // the documented equivalent.
159
+ return ctx.draft === "openapi-3.0"
160
+ ? { nullable: true, enum: [null] }
161
+ : { type: "null" };
162
+ case "Boolean":
163
+ return { type: "boolean" };
164
+ case "String":
165
+ return { type: "string" };
166
+ case "Integer":
167
+ // A JSON number cannot round-trip the upper half of i64, so East JSON
168
+ // encodes Integer as a decimal string and the pattern pins the range.
169
+ return { type: "string", pattern: integerPattern(), "x-east-type": "Integer" };
170
+ case "Float":
171
+ return {
172
+ oneOf: [{ type: "number" }, { type: "string", enum: [...FLOAT_SPECIALS] }],
173
+ "x-east-type": "Float",
174
+ };
175
+ case "DateTime":
176
+ return {
177
+ type: "string",
178
+ format: "date-time",
179
+ pattern: DATETIME_PATTERN,
180
+ "x-east-type": "DateTime",
181
+ };
182
+ case "Blob":
183
+ return { type: "string", pattern: BLOB_PATTERN, "x-east-type": "Blob" };
184
+ case "Array":
185
+ return { type: "array", items: schemaOf(t.value, ctx) };
186
+ case "Set":
187
+ return {
188
+ type: "array",
189
+ items: schemaOf(t.value, ctx),
190
+ uniqueItems: true,
191
+ "x-east-type": "Set",
192
+ };
193
+ case "Vector":
194
+ return {
195
+ type: "array",
196
+ items: schemaOf(t.value, ctx),
197
+ "x-east-type": "Vector",
198
+ };
199
+ case "Matrix":
200
+ // Rows are equal-length, which no release can express without $data; the
201
+ // reader enforces it.
202
+ return {
203
+ type: "array",
204
+ items: { type: "array", items: schemaOf(t.value, ctx) },
205
+ "x-east-type": "Matrix",
206
+ };
207
+ case "Ref": {
208
+ // A Ref encodes as a one-element array. The encoder ALSO writes
209
+ // `{"$ref": …}` once the same target has been written before, but no
210
+ // reader resolves that back — a streaming reader cannot, having discarded
211
+ // what the pointer refers to — so advertising it would describe documents
212
+ // the reader then rejects. It is excluded for the same reason aliasing is
213
+ // excluded for Array/Set/Dict, and with the same consequence, stated in
214
+ // the docs: a value with shared references does not validate against its
215
+ // own published schema.
216
+ return {
217
+ type: "array",
218
+ items: schemaOf(t.value, ctx),
219
+ minItems: 1,
220
+ maxItems: 1,
221
+ "x-east-type": "Ref",
222
+ };
223
+ }
224
+ case "Dict": {
225
+ const d = t.value;
226
+ return {
227
+ type: "array",
228
+ items: {
229
+ type: "object",
230
+ properties: { key: schemaOf(d.key, ctx), value: schemaOf(d.value, ctx) },
231
+ required: ["key", "value"],
232
+ additionalProperties: false,
233
+ },
234
+ uniqueItems: true,
235
+ "x-east-type": "Dict",
236
+ };
237
+ }
238
+ case "Struct": {
239
+ const fields = t.value;
240
+ const properties = {};
241
+ for (const f of fields)
242
+ properties[f.name] = schemaOf(f.type, ctx);
243
+ return {
244
+ type: "object",
245
+ properties,
246
+ required: fields.map(f => f.name),
247
+ additionalProperties: false,
248
+ };
249
+ }
250
+ case "Variant": {
251
+ const cases = t.value;
252
+ return {
253
+ oneOf: cases.map(c => ({
254
+ type: "object",
255
+ properties: {
256
+ // draft-04 (and so OpenAPI 3.0) has no `const`; a single-valued
257
+ // enum asserts the same thing.
258
+ type: ctx.draft === "openapi-3.0" ? { enum: [c.name] } : { const: c.name },
259
+ value: schemaOf(c.type, ctx),
260
+ },
261
+ required: ["type", "value"],
262
+ additionalProperties: false,
263
+ })),
264
+ };
265
+ }
266
+ case "Recursive": {
267
+ const rec = t.value;
268
+ if (rec.type === "ref") {
269
+ const name = ctx.names.get(rec.value);
270
+ if (name === undefined) {
271
+ throw new Error(`jsonSchemaFor: unresolved recursive reference ${rec.value}`);
272
+ }
273
+ return { $ref: `#/${defsKeyword(ctx.draft)}/${name}` };
274
+ }
275
+ const w = rec.value;
276
+ // Named by first-encounter order, never by type id: ids come from a
277
+ // process-global counter, so using them would make the document differ
278
+ // between runs and between languages.
279
+ const name = `Recursive${ctx.names.size + 1}`;
280
+ ctx.names.set(w.id, name);
281
+ // Reserve the slot before recursing so a back-reference resolves.
282
+ ctx.defs[name] = {};
283
+ ctx.defs[name] = schemaOf(w.inner, ctx);
284
+ return { $ref: `#/${defsKeyword(ctx.draft)}/${name}` };
285
+ }
286
+ default:
287
+ throw new Error(`jsonSchemaFor: unhandled type ${t.type}`);
288
+ }
289
+ }
290
+ //# sourceMappingURL=json_schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_schema.js","sourceRoot":"","sources":["../../../src/serialization/json_schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=json_schema.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_schema.spec.d.ts","sourceRoot":"","sources":["../../../src/serialization/json_schema.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { describe, test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { createHash } from "node:crypto";
8
+ import { ArrayType, AsyncFunctionType, BlobType, BooleanType, DateTimeType, DictType, FloatType, FunctionType, IntegerType, MatrixType, NeverType, NullType, OptionType, RecursiveType, RefType, SetType, StringType, StructType, VariantType, VectorType, } from "../types.js";
9
+ import { EAST_JSON_PATTERNS, jsonSchemaFor } from "./json_schema.js";
10
+ import { toJSONFor } from "./json.js";
11
+ /** The pattern a leaf type's schema pins, as a compiled regex. */
12
+ function patternOf(schema) {
13
+ const pattern = schema["pattern"];
14
+ assert.equal(typeof pattern, "string", "expected the schema to carry a pattern");
15
+ return new RegExp(pattern);
16
+ }
17
+ /** A schema with `$schema` dropped, for comparing the body alone. */
18
+ function body(schema) {
19
+ const { $schema: _drop, ...rest } = schema;
20
+ return rest;
21
+ }
22
+ describe("jsonSchemaFor", () => {
23
+ describe("primitives", () => {
24
+ test("describes Null, Boolean and String directly", () => {
25
+ assert.deepEqual(body(jsonSchemaFor(NullType)), { type: "null" });
26
+ assert.deepEqual(body(jsonSchemaFor(BooleanType)), { type: "boolean" });
27
+ assert.deepEqual(body(jsonSchemaFor(StringType)), { type: "string" });
28
+ });
29
+ test("describes Float as a number or one of the non-finite spellings", () => {
30
+ assert.deepEqual(body(jsonSchemaFor(FloatType)), {
31
+ oneOf: [
32
+ { type: "number" },
33
+ { type: "string", enum: ["-0.0", "-Infinity", "Infinity", "NaN"] },
34
+ ],
35
+ "x-east-type": "Float",
36
+ });
37
+ });
38
+ test("stamps $schema for the releases that carry one", () => {
39
+ assert.equal(jsonSchemaFor(StringType)["$schema"], "https://json-schema.org/draft/2020-12/schema");
40
+ assert.equal(jsonSchemaFor(StringType, { draft: "draft-07" })["$schema"], "http://json-schema.org/draft-07/schema#");
41
+ // An OpenAPI 3.0 schema object lives inside an OpenAPI document and
42
+ // has no $schema of its own.
43
+ assert.equal(jsonSchemaFor(StringType, { draft: "openapi-3.0" })["$schema"], undefined);
44
+ });
45
+ });
46
+ describe("Integer", () => {
47
+ const pattern = patternOf(jsonSchemaFor(IntegerType));
48
+ test("accepts exactly the i64 range", () => {
49
+ for (const ok of ["0", "1", "-1", "42", "9223372036854775807", "-9223372036854775808"]) {
50
+ assert.ok(pattern.test(ok), `${ok} should be accepted`);
51
+ }
52
+ for (const bad of ["9223372036854775808", "-9223372036854775809"]) {
53
+ assert.ok(!pattern.test(bad), `${bad} should be rejected`);
54
+ }
55
+ });
56
+ test("rejects an unsigned 64-bit id", () => {
57
+ // The naive `^(0|-?[1-9][0-9]{0,18})$` admits this, so a producer
58
+ // would validate it and we would then reject it on receipt.
59
+ assert.ok(!pattern.test("18446744073709551615"));
60
+ assert.ok(!pattern.test("9999999999999999999"));
61
+ });
62
+ test("rejects everything the decoder tolerates but the encoder never emits", () => {
63
+ // BigInt() accepts all of these; the published contract must not.
64
+ for (const bad of ["0x10", "0b101", "0o17", " 7 ", "+7", "007", "-0", "", "1e3", "7.5"]) {
65
+ assert.ok(!pattern.test(bad), `${JSON.stringify(bad)} should be rejected`);
66
+ }
67
+ });
68
+ test("accepts everything the encoder emits", () => {
69
+ const encode = toJSONFor(IntegerType);
70
+ const values = [
71
+ 0n, 1n, -1n, 7n, -7n, 10n, 99n, 100n, 12345n, -12345n,
72
+ 9007199254740993n, -9007199254740993n,
73
+ 9223372036854775807n, -9223372036854775808n,
74
+ ];
75
+ for (const v of values) {
76
+ assert.ok(pattern.test(encode(v)), `encoder output for ${v} should validate`);
77
+ }
78
+ });
79
+ });
80
+ describe("DateTime", () => {
81
+ const pattern = patternOf(jsonSchemaFor(DateTimeType));
82
+ test("accepts the canonical form the encoder emits", () => {
83
+ const encode = toJSONFor(DateTimeType);
84
+ for (const d of [new Date(0), new Date("2022-06-29T13:43:00.123Z"), new Date("2026-12-31T23:59:59.999Z")]) {
85
+ assert.ok(pattern.test(encode(d)), `${encode(d)} should validate`);
86
+ }
87
+ });
88
+ test("rejects the offsets the decoder tolerates", () => {
89
+ // The decoder takes a Z suffix or any numeric offset; the encoder
90
+ // only ever writes +00:00, so the contract pins that.
91
+ assert.ok(!pattern.test("2022-06-29T13:43:00.123Z"));
92
+ assert.ok(!pattern.test("2022-06-29T13:43:00.123+05:00"));
93
+ assert.ok(!pattern.test("2022-06-29T13:43:00.123-08:00"));
94
+ });
95
+ test("rejects out-of-range calendar fields", () => {
96
+ for (const bad of [
97
+ "0000-01-01T00:00:00.000+00:00", // year 0, below the range every runtime reads
98
+ "2022-13-29T13:43:00.123+00:00", // month 13
99
+ "2022-06-32T13:43:00.123+00:00", // day 32
100
+ "2022-06-29T24:43:00.123+00:00", // hour 24
101
+ "2022-06-29T13:60:00.123+00:00", // minute 60
102
+ "2022-06-29T13:43:60.123+00:00", // second 60
103
+ "2022-06-29T13:43:00+00:00", // no milliseconds
104
+ "2022-06-29 13:43:00.123+00:00", // space, not T
105
+ ]) {
106
+ assert.ok(!pattern.test(bad), `${bad} should be rejected`);
107
+ }
108
+ });
109
+ test("accepts the first and last years the reader does", () => {
110
+ assert.ok(pattern.test("0001-01-01T00:00:00.000+00:00"));
111
+ assert.ok(pattern.test("9999-12-31T23:59:59.999+00:00"));
112
+ });
113
+ });
114
+ describe("digit classes", () => {
115
+ test("spells every digit as [0-9], never \\d", () => {
116
+ // A validator built on python's `re` reads \d as any Unicode digit,
117
+ // so a timestamp in Arabic-Indic digits would pass a partner's check
118
+ // and then fail on receipt. The contract has to read the same on
119
+ // every regex engine a partner might use.
120
+ for (const p of [EAST_JSON_PATTERNS.integer, EAST_JSON_PATTERNS.datetime, EAST_JSON_PATTERNS.blob]) {
121
+ assert.ok(!p.includes("\\d"), `${p} must not use \\d`);
122
+ }
123
+ });
124
+ });
125
+ describe("Blob", () => {
126
+ const pattern = patternOf(jsonSchemaFor(BlobType));
127
+ test("accepts the lowercase hex the encoder emits", () => {
128
+ const encode = toJSONFor(BlobType);
129
+ assert.ok(pattern.test(encode(new Uint8Array([]))));
130
+ assert.ok(pattern.test(encode(new Uint8Array([1, 3, 3, 7]))));
131
+ assert.ok(pattern.test(encode(new Uint8Array([0xde, 0xad, 0xbe, 0xef]))));
132
+ });
133
+ test("rejects uppercase hex, which only the decoder allows", () => {
134
+ assert.ok(!pattern.test("0xDEADBEEF"));
135
+ assert.ok(!pattern.test("0xAb"));
136
+ });
137
+ test("rejects a missing prefix or an odd digit count", () => {
138
+ assert.ok(!pattern.test("deadbeef"));
139
+ assert.ok(!pattern.test("0x123"));
140
+ assert.ok(!pattern.test("0xgg"));
141
+ });
142
+ });
143
+ describe("collections", () => {
144
+ test("describes Array as an array of its element", () => {
145
+ assert.deepEqual(body(jsonSchemaFor(ArrayType(StringType))), {
146
+ type: "array",
147
+ items: { type: "string" },
148
+ });
149
+ });
150
+ test("marks Set unique", () => {
151
+ assert.deepEqual(body(jsonSchemaFor(SetType(StringType))), {
152
+ type: "array",
153
+ items: { type: "string" },
154
+ uniqueItems: true,
155
+ "x-east-type": "Set",
156
+ });
157
+ });
158
+ test("describes Dict as its array-of-entries encoding", () => {
159
+ assert.deepEqual(body(jsonSchemaFor(DictType(StringType, BooleanType))), {
160
+ type: "array",
161
+ items: {
162
+ type: "object",
163
+ properties: { key: { type: "string" }, value: { type: "boolean" } },
164
+ required: ["key", "value"],
165
+ additionalProperties: false,
166
+ },
167
+ uniqueItems: true,
168
+ "x-east-type": "Dict",
169
+ });
170
+ });
171
+ test("closes Struct to its declared fields, all required", () => {
172
+ const schema = body(jsonSchemaFor(StructType({ a: StringType, b: BooleanType })));
173
+ assert.equal(schema["type"], "object");
174
+ assert.deepEqual(schema["required"], ["a", "b"]);
175
+ assert.equal(schema["additionalProperties"], false);
176
+ });
177
+ test("describes Vector and Matrix as arrays", () => {
178
+ assert.deepEqual(body(jsonSchemaFor(VectorType(FloatType)))["type"], "array");
179
+ const m = body(jsonSchemaFor(MatrixType(IntegerType)));
180
+ assert.equal(m["type"], "array");
181
+ assert.equal(m["items"]["type"], "array");
182
+ });
183
+ test("describes Ref as a one-element array, without the aliasing form", () => {
184
+ // The encoder ALSO writes {"$ref": …} for a target it has already
185
+ // written, but no reader resolves that back — a streaming reader has
186
+ // discarded what the pointer refers to — so advertising it would
187
+ // describe documents the reader then rejects. Excluded for the same
188
+ // reason as Array/Set/Dict aliasing, with the same stated
189
+ // consequence: a value with shared references does not validate
190
+ // against its own published schema.
191
+ assert.deepEqual(body(jsonSchemaFor(RefType(StringType))), {
192
+ type: "array",
193
+ items: { type: "string" },
194
+ minItems: 1,
195
+ maxItems: 1,
196
+ "x-east-type": "Ref",
197
+ });
198
+ });
199
+ });
200
+ describe("Variant", () => {
201
+ test("pins each case's tag and closes the object", () => {
202
+ const schema = body(jsonSchemaFor(VariantType({ ok: IntegerType, err: StringType })));
203
+ const alternatives = schema["oneOf"];
204
+ // VariantType sorts its cases, so the order is fixed by the type.
205
+ assert.equal(alternatives.length, 2);
206
+ const tags = alternatives.map(a => a["properties"]["type"]["const"]);
207
+ assert.deepEqual(tags, ["err", "ok"]);
208
+ for (const a of alternatives) {
209
+ assert.deepEqual(a["required"], ["type", "value"]);
210
+ assert.equal(a["additionalProperties"], false);
211
+ }
212
+ });
213
+ test("uses a single-valued enum where the release has no const", () => {
214
+ const schema = body(jsonSchemaFor(OptionType(StringType), { draft: "openapi-3.0" }));
215
+ const alternatives = schema["oneOf"];
216
+ const tag = alternatives[0]["properties"]["type"];
217
+ assert.deepEqual(tag["enum"], ["none"]);
218
+ assert.equal(tag["const"], undefined);
219
+ });
220
+ });
221
+ describe("recursive types", () => {
222
+ const LinkedListType = RecursiveType((self) => VariantType({
223
+ nil: NullType,
224
+ cons: StructType({ head: IntegerType, tail: self }),
225
+ }));
226
+ test("lifts the body into $defs and refers to it", () => {
227
+ const schema = jsonSchemaFor(LinkedListType);
228
+ assert.deepEqual(body(schema)["$ref"], "#/$defs/Recursive1");
229
+ const defs = schema["$defs"];
230
+ assert.ok(defs["Recursive1"] !== undefined);
231
+ });
232
+ test("names definitions by encounter order, not by type id", () => {
233
+ // Type ids come from a process-global counter, so a document keyed
234
+ // on them would differ between runs and between languages.
235
+ const a = JSON.stringify(jsonSchemaFor(LinkedListType));
236
+ const b = JSON.stringify(jsonSchemaFor(LinkedListType));
237
+ assert.equal(a, b);
238
+ assert.ok(a.includes("Recursive1"));
239
+ assert.ok(!/Recursive[0-9]{2,}/.test(a));
240
+ });
241
+ test("uses the release's definitions keyword", () => {
242
+ const seven = jsonSchemaFor(LinkedListType, { draft: "draft-07" });
243
+ assert.deepEqual(body(seven)["$ref"], "#/definitions/Recursive1");
244
+ assert.ok(seven["definitions"] !== undefined);
245
+ assert.equal(seven["$defs"], undefined);
246
+ });
247
+ });
248
+ describe("releases", () => {
249
+ test("spells Null without a null type on OpenAPI 3.0", () => {
250
+ assert.deepEqual(body(jsonSchemaFor(NullType, { draft: "openapi-3.0" })), {
251
+ nullable: true,
252
+ enum: [null],
253
+ });
254
+ });
255
+ test("pins the same integer range in every release", () => {
256
+ const p2020 = jsonSchemaFor(IntegerType)["pattern"];
257
+ const p07 = jsonSchemaFor(IntegerType, { draft: "draft-07" })["pattern"];
258
+ const pOas = jsonSchemaFor(IntegerType, { draft: "openapi-3.0" })["pattern"];
259
+ assert.equal(p2020, p07);
260
+ assert.equal(p07, pOas);
261
+ });
262
+ });
263
+ describe("types with no JSON form", () => {
264
+ test("refuses Never, naming it", () => {
265
+ assert.throws(() => jsonSchemaFor(NeverType), /cannot describe Never/);
266
+ });
267
+ test("refuses functions, naming them", () => {
268
+ assert.throws(() => jsonSchemaFor(FunctionType([], IntegerType)), /cannot describe Function/);
269
+ assert.throws(() => jsonSchemaFor(AsyncFunctionType([], IntegerType)), /cannot describe AsyncFunction/);
270
+ });
271
+ test("refuses a function nested inside a collection", () => {
272
+ assert.throws(() => jsonSchemaFor(StructType({ f: FunctionType([], IntegerType) })), /cannot describe Function/);
273
+ });
274
+ });
275
+ test("matches the cross-language corpus digest", () => {
276
+ // The python twin asserts this same digest over the same corpus in the
277
+ // same order (east-py tests/serialization/test_json_schema.py). Two
278
+ // languages agreeing on one hash is what keeps a partner from being
279
+ // handed different contracts; changing the emitted bytes deliberately
280
+ // means updating both constants, which is the point.
281
+ const RecursiveCorpusType = RecursiveType((self) => VariantType({
282
+ nil: NullType,
283
+ cons: StructType({ head: IntegerType, tail: self }),
284
+ }));
285
+ const corpus = [
286
+ ["Null", NullType], ["Boolean", BooleanType], ["Integer", IntegerType],
287
+ ["Float", FloatType], ["String", StringType], ["DateTime", DateTimeType],
288
+ ["Blob", BlobType],
289
+ ["Array", ArrayType(IntegerType)], ["Set", SetType(StringType)],
290
+ ["Dict", DictType(StringType, IntegerType)],
291
+ ["Struct", StructType({ a: StringType, b: IntegerType, c: DateTimeType })],
292
+ ["Variant", VariantType({ ok: IntegerType, err: StringType })],
293
+ ["Option", OptionType(StringType)],
294
+ ["Ref", RefType(IntegerType)],
295
+ ["Vector", VectorType(FloatType)], ["Matrix", MatrixType(IntegerType)],
296
+ ["nested", ArrayType(StructType({
297
+ id: IntegerType, tags: SetType(StringType),
298
+ note: OptionType(StringType), when: DateTimeType,
299
+ }))],
300
+ ["recursive", RecursiveCorpusType],
301
+ ["arrayRecursive", ArrayType(RecursiveCorpusType)],
302
+ ];
303
+ const lines = [];
304
+ for (const draft of ["2020-12", "draft-07", "openapi-3.0"]) {
305
+ for (const [name, type] of corpus) {
306
+ lines.push(`${draft}|${name}=${JSON.stringify(jsonSchemaFor(type, { draft }))}`);
307
+ }
308
+ }
309
+ assert.equal(lines.length, 57);
310
+ assert.equal(createHash("sha256").update(lines.join("\n")).digest("hex"), "7083a9ae6f830e8724c707c0f0636a57be01fe2085874e83fea00883401c1a6b");
311
+ });
312
+ test("emits byte-identical documents for the same type and release", () => {
313
+ const T = StructType({
314
+ id: IntegerType,
315
+ at: DateTimeType,
316
+ tags: SetType(StringType),
317
+ note: OptionType(StringType),
318
+ blob: BlobType,
319
+ });
320
+ for (const draft of ["2020-12", "draft-07", "openapi-3.0"]) {
321
+ const first = JSON.stringify(jsonSchemaFor(ArrayType(T), { draft }));
322
+ const second = JSON.stringify(jsonSchemaFor(ArrayType(T), { draft }));
323
+ assert.equal(first, second, `${draft} output should be stable`);
324
+ }
325
+ });
326
+ });
327
+ //# sourceMappingURL=json_schema.spec.js.map