@atscript/typescript 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.mjs CHANGED
@@ -1,914 +1,5 @@
1
+ import { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas } from "./json-schema-0UUPoHud.mjs";
1
2
 
2
- //#region packages/typescript/src/validator.ts
3
- function _define_property(obj, key, value) {
4
- if (key in obj) Object.defineProperty(obj, key, {
5
- value,
6
- enumerable: true,
7
- configurable: true,
8
- writable: true
9
- });
10
- else obj[key] = value;
11
- return obj;
12
- }
13
- const regexCache = new Map();
14
- var Validator = class {
15
- isLimitExceeded() {
16
- if (this.stackErrors.length > 0) {
17
- const top = this.stackErrors[this.stackErrors.length - 1];
18
- return top !== null && top.length >= this.opts.errorLimit;
19
- }
20
- return this.errors.length >= this.opts.errorLimit;
21
- }
22
- push(name) {
23
- this.stackPath.push(name);
24
- this.stackErrors.push(null);
25
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
26
- }
27
- pop(saveErrors) {
28
- this.stackPath.pop();
29
- const popped = this.stackErrors.pop();
30
- if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
31
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
32
- return popped;
33
- }
34
- clear() {
35
- this.stackErrors[this.stackErrors.length - 1] = null;
36
- }
37
- error(message, path, details) {
38
- let errors = this.stackErrors[this.stackErrors.length - 1];
39
- if (!errors) if (this.stackErrors.length > 0) {
40
- errors = [];
41
- this.stackErrors[this.stackErrors.length - 1] = errors;
42
- } else errors = this.errors;
43
- const error = {
44
- path: path || this.cachedPath,
45
- message
46
- };
47
- if (details?.length) error.details = details;
48
- errors.push(error);
49
- }
50
- throw() {
51
- throw new ValidatorError(this.errors);
52
- }
53
- /**
54
- * Validates a value against the type definition.
55
- *
56
- * Acts as a TypeScript type guard — when it returns `true`, the value
57
- * is narrowed to `DataType`.
58
- *
59
- * @param value - The value to validate.
60
- * @param safe - If `true`, returns `false` on failure instead of throwing.
61
- * @returns `true` if the value matches the type definition.
62
- * @throws {ValidatorError} When validation fails and `safe` is not `true`.
63
- */ validate(value, safe, context) {
64
- this.errors = [];
65
- this.stackErrors = [];
66
- this.stackPath = [""];
67
- this.cachedPath = "";
68
- this.context = context;
69
- const passed = this.validateSafe(this.def, value);
70
- this.pop(!passed);
71
- this.context = undefined;
72
- if (!passed) {
73
- if (safe) return false;
74
- this.throw();
75
- }
76
- return true;
77
- }
78
- validateSafe(def, value) {
79
- if (this.isLimitExceeded()) return false;
80
- if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
81
- if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
82
- if (def.optional && value === undefined) return true;
83
- for (const plugin of this.opts.plugins) {
84
- const result = plugin(this, def, value);
85
- if (result === false || result === true) return result;
86
- }
87
- return this.validateAnnotatedType(def, value);
88
- }
89
- get path() {
90
- return this.cachedPath;
91
- }
92
- validateAnnotatedType(def, value) {
93
- switch (def.type.kind) {
94
- case "": {
95
- if (def.type.designType === "phantom") return true;
96
- return this.validatePrimitive(def, value);
97
- }
98
- case "object": return this.validateObject(def, value);
99
- case "array": return this.validateArray(def, value);
100
- case "union": return this.validateUnion(def, value);
101
- case "intersection": return this.validateIntersection(def, value);
102
- case "tuple": return this.validateTuple(def, value);
103
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
104
- }
105
- }
106
- validateUnion(def, value) {
107
- let i = 0;
108
- const popped = [];
109
- for (const item of def.type.items) {
110
- this.push(`[${item.type.kind || item.type.designType}(${i})]`);
111
- if (this.validateSafe(item, value)) {
112
- this.pop(false);
113
- return true;
114
- }
115
- const errors = this.pop(false);
116
- if (errors) popped.push(...errors);
117
- i++;
118
- }
119
- this.clear();
120
- const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
121
- this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
122
- return false;
123
- }
124
- validateIntersection(def, value) {
125
- for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
126
- return true;
127
- }
128
- validateTuple(def, value) {
129
- if (!Array.isArray(value) || value.length !== def.type.items.length) {
130
- this.error(`Expected array of length ${def.type.items.length}`);
131
- return false;
132
- }
133
- let i = 0;
134
- for (const item of def.type.items) {
135
- this.push(String(i));
136
- if (!this.validateSafe(item, value[i])) {
137
- this.pop(true);
138
- return false;
139
- }
140
- this.pop(false);
141
- i++;
142
- }
143
- return true;
144
- }
145
- validateArray(def, value) {
146
- if (!Array.isArray(value)) {
147
- this.error("Expected array");
148
- return false;
149
- }
150
- const minLength = def.metadata.get("expect.minLength");
151
- if (minLength) {
152
- const length = typeof minLength === "number" ? minLength : minLength.length;
153
- if (value.length < length) {
154
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} items, got ${value.length} items`;
155
- this.error(message);
156
- return false;
157
- }
158
- }
159
- const maxLength = def.metadata.get("expect.maxLength");
160
- if (maxLength) {
161
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
162
- if (value.length > length) {
163
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} items, got ${value.length} items`;
164
- this.error(message);
165
- return false;
166
- }
167
- }
168
- const uniqueItems = def.metadata.get("expect.array.uniqueItems");
169
- if (uniqueItems) {
170
- const separator = "▼↩";
171
- const seen = new Set();
172
- const keyProps = new Set();
173
- if (def.type.of.type.kind === "object") {
174
- for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
175
- }
176
- for (let idx = 0; idx < value.length; idx++) {
177
- const item = value[idx];
178
- let key;
179
- if (keyProps.size > 0) {
180
- key = "";
181
- for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
182
- } else key = JSON.stringify(item);
183
- if (seen.has(key)) {
184
- this.push(String(idx));
185
- this.error(uniqueItems.message || "Duplicate items are not allowed");
186
- this.pop(true);
187
- return false;
188
- }
189
- seen.add(key);
190
- }
191
- }
192
- let i = 0;
193
- let passed = true;
194
- for (const item of value) {
195
- this.push(String(i));
196
- if (!this.validateSafe(def.type.of, item)) {
197
- passed = false;
198
- this.pop(true);
199
- if (this.isLimitExceeded()) return false;
200
- } else this.pop(false);
201
- i++;
202
- }
203
- return passed;
204
- }
205
- validateObject(def, value) {
206
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
207
- this.error("Expected object");
208
- return false;
209
- }
210
- let passed = true;
211
- const valueKeys = new Set(Object.keys(value));
212
- const typeKeys = new Set();
213
- let skipList;
214
- if (this.opts.skipList) {
215
- const path = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
216
- for (const item of this.opts.skipList) if (item.startsWith(path)) {
217
- const key = item.slice(path.length);
218
- if (!skipList) skipList = new Set();
219
- skipList.add(key);
220
- valueKeys.delete(key);
221
- }
222
- }
223
- let partialFunctionMatched = false;
224
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
225
- for (const [key, item] of def.type.props.entries()) {
226
- if (skipList && skipList.has(key) || isPhantomType(item)) continue;
227
- typeKeys.add(key);
228
- if (value[key] === undefined) {
229
- if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
230
- }
231
- this.push(key);
232
- if (this.validateSafe(item, value[key])) this.pop(false);
233
- else {
234
- passed = false;
235
- this.pop(true);
236
- if (this.isLimitExceeded()) return false;
237
- }
238
- }
239
- for (const key of valueKeys)
240
- /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
241
- const matched = [];
242
- for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
243
- pattern,
244
- def: propDef
245
- });
246
- if (matched.length > 0) {
247
- this.push(key);
248
- let keyPassed = false;
249
- for (const { def: propDef } of matched) {
250
- if (this.validateSafe(propDef, value[key])) {
251
- keyPassed = true;
252
- break;
253
- }
254
- this.clear();
255
- }
256
- if (!keyPassed) {
257
- this.validateSafe(matched[0].def, value[key]);
258
- this.pop(true);
259
- passed = false;
260
- if (this.isLimitExceeded()) return false;
261
- } else this.pop(false);
262
- } else if (this.opts.unknownProps !== "ignore") {
263
- if (this.opts.unknownProps === "error") {
264
- this.push(key);
265
- this.error(`Unexpected property`);
266
- this.pop(true);
267
- if (this.isLimitExceeded()) return false;
268
- passed = false;
269
- } else if (this.opts.unknownProps === "strip") delete value[key];
270
- }
271
- }
272
- return passed;
273
- }
274
- validatePrimitive(def, value) {
275
- if (def.type.value !== undefined) {
276
- if (value !== def.type.value) {
277
- this.error(`Expected ${def.type.value}, got ${value}`);
278
- return false;
279
- }
280
- return true;
281
- }
282
- const typeOfValue = Array.isArray(value) ? "array" : typeof value;
283
- switch (def.type.designType) {
284
- case "never": {
285
- this.error(`This type is impossible, must be an internal problem`);
286
- return false;
287
- }
288
- case "any": return true;
289
- case "string": {
290
- if (typeOfValue !== def.type.designType) {
291
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
292
- return false;
293
- }
294
- return this.validateString(def, value);
295
- }
296
- case "number": {
297
- if (typeOfValue !== def.type.designType) {
298
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
299
- return false;
300
- }
301
- return this.validateNumber(def, value);
302
- }
303
- case "boolean": {
304
- if (typeOfValue !== def.type.designType) {
305
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
306
- return false;
307
- }
308
- return this.validateBoolean(def, value);
309
- }
310
- case "undefined": {
311
- if (value !== undefined) {
312
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
313
- return false;
314
- }
315
- return true;
316
- }
317
- case "null": {
318
- if (value !== null) {
319
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
320
- return false;
321
- }
322
- return true;
323
- }
324
- default: throw new Error(`Unknown type "${def.type.designType}"`);
325
- }
326
- }
327
- validateString(def, value) {
328
- const filled = def.metadata.get("meta.required");
329
- if (filled) {
330
- if (value.trim().length === 0) {
331
- const message = typeof filled === "object" && filled.message ? filled.message : `Must not be empty`;
332
- this.error(message);
333
- return false;
334
- }
335
- }
336
- const minLength = def.metadata.get("expect.minLength");
337
- if (minLength) {
338
- const length = typeof minLength === "number" ? minLength : minLength.length;
339
- if (value.length < length) {
340
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} characters, got ${value.length} characters`;
341
- this.error(message);
342
- return false;
343
- }
344
- }
345
- const maxLength = def.metadata.get("expect.maxLength");
346
- if (maxLength) {
347
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
348
- if (value.length > length) {
349
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} characters, got ${value.length} characters`;
350
- this.error(message);
351
- return false;
352
- }
353
- }
354
- const patterns = def.metadata.get("expect.pattern");
355
- for (const { pattern, flags, message } of patterns || []) {
356
- if (!pattern) continue;
357
- const cacheKey = `${pattern}//${flags || ""}`;
358
- let regex = regexCache.get(cacheKey);
359
- if (!regex) {
360
- regex = new RegExp(pattern, flags);
361
- regexCache.set(cacheKey, regex);
362
- }
363
- if (!regex.test(value)) {
364
- this.error(message || `Value is expected to match pattern "${pattern}"`);
365
- return false;
366
- }
367
- }
368
- return true;
369
- }
370
- validateNumber(def, value) {
371
- const int = def.metadata.get("expect.int");
372
- if (int && value % 1 !== 0) {
373
- const message = typeof int === "object" && int.message ? int.message : `Expected integer, got ${value}`;
374
- this.error(message);
375
- return false;
376
- }
377
- const min = def.metadata.get("expect.min");
378
- if (min) {
379
- const minValue = typeof min === "number" ? min : min.minValue;
380
- if (value < minValue) {
381
- const message = typeof min === "object" && min.message ? min.message : `Expected minimum ${minValue}, got ${value}`;
382
- this.error(message);
383
- return false;
384
- }
385
- }
386
- const max = def.metadata.get("expect.max");
387
- if (max) {
388
- const maxValue = typeof max === "number" ? max : max.maxValue;
389
- if (value > maxValue) {
390
- const message = typeof max === "object" && max.message ? max.message : `Expected maximum ${maxValue}, got ${value}`;
391
- this.error(message);
392
- return false;
393
- }
394
- }
395
- return true;
396
- }
397
- validateBoolean(def, value) {
398
- const filled = def.metadata.get("meta.required");
399
- if (filled) {
400
- if (value !== true) {
401
- const message = typeof filled === "object" && filled.message ? filled.message : `Must be checked`;
402
- this.error(message);
403
- return false;
404
- }
405
- }
406
- return true;
407
- }
408
- constructor(def, opts) {
409
- _define_property(this, "def", void 0);
410
- _define_property(this, "opts", void 0);
411
- /** Validation errors collected during the last {@link validate} call. */ _define_property(this, "errors", void 0);
412
- _define_property(this, "stackErrors", void 0);
413
- _define_property(this, "stackPath", void 0);
414
- _define_property(this, "cachedPath", void 0);
415
- _define_property(this, "context", void 0);
416
- this.def = def;
417
- this.errors = [];
418
- this.stackErrors = [];
419
- this.stackPath = [];
420
- this.cachedPath = "";
421
- this.opts = {
422
- partial: false,
423
- unknownProps: "error",
424
- errorLimit: 10,
425
- ...opts,
426
- plugins: opts?.plugins || []
427
- };
428
- }
429
- };
430
- var ValidatorError = class extends Error {
431
- constructor(errors) {
432
- super(`${errors[0].path ? errors[0].path + ": " : ""}${errors[0].message}`), _define_property(this, "errors", void 0), _define_property(this, "name", void 0), this.errors = errors, this.name = "Validation Error";
433
- }
434
- };
435
-
436
- //#endregion
437
- //#region packages/typescript/src/annotated-type.ts
438
- const COMPLEX_KINDS = new Set([
439
- "union",
440
- "intersection",
441
- "tuple"
442
- ]);
443
- const NON_PRIMITIVE_KINDS = new Set(["array", "object"]);
444
- /** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
445
- return new Validator(this, opts);
446
- }
447
- function createAnnotatedTypeNode(type, metadata, opts) {
448
- return {
449
- __is_atscript_annotated_type: true,
450
- type,
451
- metadata,
452
- validator: validatorMethod,
453
- id: opts?.id,
454
- optional: opts?.optional
455
- };
456
- }
457
- function isAnnotatedType(type) {
458
- return type && type.__is_atscript_annotated_type;
459
- }
460
- function annotate(metadata, key, value, asArray) {
461
- if (!metadata) return;
462
- if (asArray) if (metadata.has(key)) {
463
- const a = metadata.get(key);
464
- if (Array.isArray(a)) a.push(value);
465
- else metadata.set(key, [a, value]);
466
- } else metadata.set(key, [value]);
467
- else metadata.set(key, value);
468
- }
469
- function cloneRefProp(parentType, propName) {
470
- if (parentType.kind !== "object") return;
471
- const objType = parentType;
472
- const existing = objType.props.get(propName);
473
- if (!existing) return;
474
- const clonedType = cloneTypeDef(existing.type);
475
- objType.props.set(propName, createAnnotatedTypeNode(clonedType, new Map(existing.metadata), {
476
- id: existing.id,
477
- optional: existing.optional
478
- }));
479
- }
480
- function cloneTypeDef(type) {
481
- if (type.kind === "object") {
482
- const obj = type;
483
- const props = new Map();
484
- for (const [k, v] of obj.props) props.set(k, createAnnotatedTypeNode(v.type, new Map(v.metadata), {
485
- id: v.id,
486
- optional: v.optional
487
- }));
488
- return {
489
- kind: "object",
490
- props,
491
- propsPatterns: [...obj.propsPatterns],
492
- tags: new Set(obj.tags)
493
- };
494
- }
495
- if (type.kind === "array") {
496
- const arr = type;
497
- return {
498
- kind: "array",
499
- of: arr.of,
500
- tags: new Set(arr.tags)
501
- };
502
- }
503
- if (type.kind === "union" || type.kind === "intersection" || type.kind === "tuple") {
504
- const complex = type;
505
- return {
506
- kind: type.kind,
507
- items: [...complex.items],
508
- tags: new Set(complex.tags)
509
- };
510
- }
511
- return {
512
- ...type,
513
- tags: new Set(type.tags)
514
- };
515
- }
516
- function defineAnnotatedType(_kind, base) {
517
- const kind = _kind || "";
518
- const type = base?.type || {};
519
- type.kind = kind;
520
- if (COMPLEX_KINDS.has(kind)) type.items = [];
521
- if (kind === "object") {
522
- type.props = new Map();
523
- type.propsPatterns = [];
524
- }
525
- type.tags = new Set();
526
- const metadata = base?.metadata || new Map();
527
- const payload = {
528
- __is_atscript_annotated_type: true,
529
- metadata,
530
- type,
531
- validator: validatorMethod
532
- };
533
- base = base ? Object.assign(base, payload) : payload;
534
- const handle = {
535
- $type: base,
536
- $def: type,
537
- $metadata: metadata,
538
- tags(...tags) {
539
- for (const tag of tags) this.$def.tags.add(tag);
540
- return this;
541
- },
542
- designType(value) {
543
- this.$def.designType = value;
544
- return this;
545
- },
546
- value(value) {
547
- this.$def.value = value;
548
- return this;
549
- },
550
- of(value) {
551
- this.$def.of = value;
552
- return this;
553
- },
554
- item(value) {
555
- this.$def.items.push(value);
556
- return this;
557
- },
558
- prop(name, value) {
559
- this.$def.props.set(name, value);
560
- return this;
561
- },
562
- propPattern(pattern, def) {
563
- this.$def.propsPatterns.push({
564
- pattern,
565
- def
566
- });
567
- return this;
568
- },
569
- optional(value = true) {
570
- this.$type.optional = value;
571
- return this;
572
- },
573
- copyMetadata(fromMetadata, ignore) {
574
- for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
575
- return this;
576
- },
577
- refTo(type$1, chain) {
578
- if (!isAnnotatedType(type$1)) throw new Error(`${type$1} is not annotated type`);
579
- let newBase = type$1;
580
- const typeName = type$1.name || "Unknown";
581
- if (chain) for (let i = 0; i < chain.length; i++) {
582
- const c = chain[i];
583
- if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
584
- else {
585
- const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
586
- throw new Error(`Can't find prop ${typeName}${keys}`);
587
- }
588
- }
589
- this.$type = createAnnotatedTypeNode(newBase.type, metadata, { id: newBase.id });
590
- return this;
591
- },
592
- annotate(key, value, asArray) {
593
- annotate(this.$metadata, key, value, asArray);
594
- return this;
595
- },
596
- id(value) {
597
- this.$type.id = value;
598
- return this;
599
- }
600
- };
601
- return handle;
602
- }
603
- function isPhantomType(def) {
604
- return def.type.kind === "" && def.type.designType === "phantom";
605
- }
606
- function isAnnotatedTypeOfPrimitive(t) {
607
- if (NON_PRIMITIVE_KINDS.has(t.type.kind)) return false;
608
- if (!t.type.kind) return true;
609
- if (COMPLEX_KINDS.has(t.type.kind)) {
610
- for (const item of t.type.items) if (!isAnnotatedTypeOfPrimitive(item)) return false;
611
- return true;
612
- }
613
- return false;
614
- }
615
-
616
- //#endregion
617
- //#region packages/typescript/src/traverse.ts
618
- function forAnnotatedType(def, handlers) {
619
- switch (def.type.kind) {
620
- case "": {
621
- const typed = def;
622
- if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
623
- return handlers.final(typed);
624
- }
625
- case "object": return handlers.object(def);
626
- case "array": return handlers.array(def);
627
- case "union": return handlers.union(def);
628
- case "intersection": return handlers.intersection(def);
629
- case "tuple": return handlers.tuple(def);
630
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
631
- }
632
- }
633
-
634
- //#endregion
635
- //#region packages/typescript/src/json-schema.ts
636
- /**
637
- * Detects a discriminator property across union items.
638
- *
639
- * Scans all items for object-typed members that share a common property
640
- * with distinct const/literal values. If exactly one such property exists,
641
- * it is returned as the discriminator.
642
- */ function detectDiscriminator(items) {
643
- if (items.length < 2) return null;
644
- for (const item of items) if (item.type.kind !== "object") return null;
645
- const firstObj = items[0].type;
646
- const candidates = [];
647
- for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
648
- let result = null;
649
- for (const candidate of candidates) {
650
- const values = new Set();
651
- const indexMapping = {};
652
- let valid = true;
653
- for (let i = 0; i < items.length; i++) {
654
- const obj = items[i].type;
655
- const prop = obj.props.get(candidate);
656
- if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
657
- valid = false;
658
- break;
659
- }
660
- const val = prop.type.value;
661
- if (values.has(val)) {
662
- valid = false;
663
- break;
664
- }
665
- values.add(val);
666
- indexMapping[String(val)] = i;
667
- }
668
- if (valid) {
669
- if (result) return null;
670
- result = {
671
- propertyName: candidate,
672
- indexMapping
673
- };
674
- }
675
- }
676
- return result;
677
- }
678
- function buildJsonSchema(type) {
679
- const defs = {};
680
- let hasDefs = false;
681
- const buildObject = (d) => {
682
- const properties = {};
683
- const required = [];
684
- for (const [key, val] of d.type.props.entries()) {
685
- if (isPhantomType(val)) continue;
686
- properties[key] = build$1(val);
687
- if (!val.optional) required.push(key);
688
- }
689
- const schema$1 = {
690
- type: "object",
691
- properties
692
- };
693
- if (required.length > 0) schema$1.required = required;
694
- return schema$1;
695
- };
696
- const build$1 = (def) => {
697
- if (def.id && def.type.kind === "object" && def !== type) {
698
- const name = def.id;
699
- if (!defs[name]) {
700
- hasDefs = true;
701
- defs[name] = {};
702
- defs[name] = buildObject(def);
703
- }
704
- return { $ref: `#/$defs/${name}` };
705
- }
706
- const meta = def.metadata;
707
- return forAnnotatedType(def, {
708
- phantom() {
709
- return {};
710
- },
711
- object(d) {
712
- return buildObject(d);
713
- },
714
- array(d) {
715
- const schema$1 = {
716
- type: "array",
717
- items: build$1(d.type.of)
718
- };
719
- const minLength = meta.get("expect.minLength");
720
- if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
721
- const maxLength = meta.get("expect.maxLength");
722
- if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
723
- return schema$1;
724
- },
725
- union(d) {
726
- const disc = detectDiscriminator(d.type.items);
727
- if (disc) {
728
- const oneOf = d.type.items.map(build$1);
729
- const mapping = {};
730
- for (const [val, idx] of Object.entries(disc.indexMapping)) {
731
- const item = d.type.items[idx];
732
- mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
733
- }
734
- return {
735
- oneOf,
736
- discriminator: {
737
- propertyName: disc.propertyName,
738
- mapping
739
- }
740
- };
741
- }
742
- return { anyOf: d.type.items.map(build$1) };
743
- },
744
- intersection(d) {
745
- return { allOf: d.type.items.map(build$1) };
746
- },
747
- tuple(d) {
748
- return {
749
- type: "array",
750
- items: d.type.items.map(build$1),
751
- additionalItems: false
752
- };
753
- },
754
- final(d) {
755
- const schema$1 = {};
756
- if (d.type.value !== undefined) schema$1.const = d.type.value;
757
- if (d.type.designType && d.type.designType !== "any") {
758
- schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
759
- if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
760
- }
761
- if (schema$1.type === "string") {
762
- if (meta.get("meta.required")) schema$1.minLength = 1;
763
- const minLength = meta.get("expect.minLength");
764
- if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
765
- const maxLength = meta.get("expect.maxLength");
766
- if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
767
- const patterns = meta.get("expect.pattern");
768
- if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
769
- else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
770
- }
771
- if (schema$1.type === "number" || schema$1.type === "integer") {
772
- const min = meta.get("expect.min");
773
- if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
774
- const max = meta.get("expect.max");
775
- if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
776
- }
777
- return schema$1;
778
- }
779
- });
780
- };
781
- const schema = build$1(type);
782
- if (hasDefs) return {
783
- ...schema,
784
- $defs: defs
785
- };
786
- return schema;
787
- }
788
- function fromJsonSchema(schema) {
789
- const defsSource = schema.$defs || schema.definitions || {};
790
- const resolved = new Map();
791
- const convert = (s) => {
792
- if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
793
- if (s.$ref) {
794
- const refName = s.$ref.replace(/^#\/(\$defs|definitions)\//, "");
795
- if (resolved.has(refName)) return resolved.get(refName);
796
- if (defsSource[refName]) {
797
- const placeholder = defineAnnotatedType().designType("any").$type;
798
- resolved.set(refName, placeholder);
799
- const type = convert(defsSource[refName]);
800
- resolved.set(refName, type);
801
- return type;
802
- }
803
- throw new Error(`Unresolvable $ref: ${s.$ref}`);
804
- }
805
- if ("const" in s) {
806
- const val = s.const;
807
- const dt = val === null ? "null" : typeof val;
808
- return defineAnnotatedType().designType(dt).value(val).$type;
809
- }
810
- if (s.enum) {
811
- const handle = defineAnnotatedType("union");
812
- for (const val of s.enum) {
813
- const dt = val === null ? "null" : typeof val;
814
- handle.item(defineAnnotatedType().designType(dt).value(val).$type);
815
- }
816
- return handle.$type;
817
- }
818
- if (s.anyOf) {
819
- const handle = defineAnnotatedType("union");
820
- for (const item of s.anyOf) handle.item(convert(item));
821
- return handle.$type;
822
- }
823
- if (s.oneOf) {
824
- const handle = defineAnnotatedType("union");
825
- for (const item of s.oneOf) handle.item(convert(item));
826
- return handle.$type;
827
- }
828
- if (s.allOf && !s.type) {
829
- const handle = defineAnnotatedType("intersection");
830
- for (const item of s.allOf) handle.item(convert(item));
831
- return handle.$type;
832
- }
833
- if (Array.isArray(s.type)) {
834
- const handle = defineAnnotatedType("union");
835
- for (const t of s.type) handle.item(convert({
836
- ...s,
837
- type: t
838
- }));
839
- return handle.$type;
840
- }
841
- if (s.type === "object") {
842
- const handle = defineAnnotatedType("object");
843
- const required = new Set(s.required || []);
844
- if (s.properties) for (const [key, propSchema] of Object.entries(s.properties)) {
845
- const propType = convert(propSchema);
846
- if (!required.has(key)) propType.optional = true;
847
- handle.prop(key, propType);
848
- }
849
- return handle.$type;
850
- }
851
- if (s.type === "array") {
852
- if (Array.isArray(s.items)) {
853
- const handle$1 = defineAnnotatedType("tuple");
854
- for (const item of s.items) handle$1.item(convert(item));
855
- return handle$1.$type;
856
- }
857
- const itemType = s.items ? convert(s.items) : defineAnnotatedType().designType("any").$type;
858
- const handle = defineAnnotatedType("array").of(itemType);
859
- if (typeof s.minItems === "number") handle.annotate("expect.minLength", { length: s.minItems });
860
- if (typeof s.maxItems === "number") handle.annotate("expect.maxLength", { length: s.maxItems });
861
- return handle.$type;
862
- }
863
- if (s.type === "string") {
864
- const handle = defineAnnotatedType().designType("string").tags("string");
865
- if (typeof s.minLength === "number") handle.annotate("expect.minLength", { length: s.minLength });
866
- if (typeof s.maxLength === "number") handle.annotate("expect.maxLength", { length: s.maxLength });
867
- if (s.pattern) handle.annotate("expect.pattern", { pattern: s.pattern }, true);
868
- if (s.allOf) {
869
- for (const item of s.allOf) if (item.pattern) handle.annotate("expect.pattern", { pattern: item.pattern }, true);
870
- }
871
- return handle.$type;
872
- }
873
- if (s.type === "integer") {
874
- const handle = defineAnnotatedType().designType("number").tags("number");
875
- handle.annotate("expect.int", true);
876
- if (typeof s.minimum === "number") handle.annotate("expect.min", { minValue: s.minimum });
877
- if (typeof s.maximum === "number") handle.annotate("expect.max", { maxValue: s.maximum });
878
- return handle.$type;
879
- }
880
- if (s.type === "number") {
881
- const handle = defineAnnotatedType().designType("number").tags("number");
882
- if (typeof s.minimum === "number") handle.annotate("expect.min", { minValue: s.minimum });
883
- if (typeof s.maximum === "number") handle.annotate("expect.max", { maxValue: s.maximum });
884
- return handle.$type;
885
- }
886
- if (s.type === "boolean") return defineAnnotatedType().designType("boolean").tags("boolean").$type;
887
- if (s.type === "null") return defineAnnotatedType().designType("null").tags("null").$type;
888
- return defineAnnotatedType().designType("any").$type;
889
- };
890
- return convert(schema);
891
- }
892
- function mergeJsonSchemas(types) {
893
- const mergedDefs = {};
894
- const schemas = {};
895
- for (const type of types) {
896
- const name = type.id;
897
- if (!name) throw new Error("mergeJsonSchemas: all types must have an id");
898
- const schema = buildJsonSchema(type);
899
- if (schema.$defs) {
900
- for (const [defName, defSchema] of Object.entries(schema.$defs)) if (!mergedDefs[defName]) mergedDefs[defName] = defSchema;
901
- const { $defs: _,...rest } = schema;
902
- schemas[name] = rest;
903
- } else schemas[name] = schema;
904
- }
905
- return {
906
- schemas,
907
- $defs: mergedDefs
908
- };
909
- }
910
-
911
- //#endregion
912
3
  //#region packages/typescript/src/default-value.ts
913
4
  /**
914
5
  * Attempts to resolve a value from the mode for the given annotated type.
@@ -923,7 +14,7 @@ function mergeJsonSchemas(types) {
923
14
  return undefined;
924
15
  }
925
16
  if (mode === "db") {
926
- const dbValue = prop.metadata.get("db.default.value");
17
+ const dbValue = prop.metadata.get("db.default");
927
18
  if (dbValue !== undefined) {
928
19
  const parsed$1 = parseRawValue(dbValue, prop);
929
20
  if (parsed$1 !== undefined && prop.validator({ unknownProps: "ignore" }).validate(parsed$1, true)) return { value: parsed$1 };
@@ -1019,6 +110,7 @@ function throwFeatureDisabled(feature, option, annotation) {
1019
110
  function flattenAnnotatedType(type, options) {
1020
111
  const flatMap = new Map();
1021
112
  const skipPhantom = !!options?.excludePhantomTypes;
113
+ const visitedIds = new Set();
1022
114
  function addFieldToFlatMap(name, def) {
1023
115
  const existing = flatMap.get(name);
1024
116
  if (existing) {
@@ -1032,8 +124,17 @@ else flatUnion.item(existing);
1032
124
  } else flatMap.set(name, def);
1033
125
  }
1034
126
  function flattenArray(def, name) {
127
+ const resolvedId = def.id;
128
+ if (resolvedId) {
129
+ if (visitedIds.has(resolvedId)) return;
130
+ visitedIds.add(resolvedId);
131
+ }
1035
132
  switch (def.type.kind) {
1036
133
  case "object": {
134
+ if (!resolvedId && def.id) {
135
+ if (visitedIds.has(def.id)) return;
136
+ visitedIds.add(def.id);
137
+ }
1037
138
  const items = Array.from(def.type.props.entries());
1038
139
  for (const [key, value] of items) {
1039
140
  if (skipPhantom && isPhantomType(value)) continue;
@@ -1055,7 +156,24 @@ else flatUnion.item(existing);
1055
156
  }
1056
157
  }
1057
158
  function flattenType(def, prefix = "", inComplexTypeOrArray = false) {
1058
- switch (def.type.kind) {
159
+ let typeId = def.id;
160
+ if (typeId && visitedIds.has(typeId)) {
161
+ addFieldToFlatMap(prefix || "", def);
162
+ if (prefix) options?.onField?.(prefix, def, def.metadata);
163
+ return;
164
+ }
165
+ if (typeId) visitedIds.add(typeId);
166
+ const kind = def.type.kind;
167
+ if (!typeId && def.id) {
168
+ typeId = def.id;
169
+ if (visitedIds.has(typeId)) {
170
+ addFieldToFlatMap(prefix || "", def);
171
+ if (prefix) options?.onField?.(prefix, def, def.metadata);
172
+ return;
173
+ }
174
+ visitedIds.add(typeId);
175
+ }
176
+ switch (kind) {
1059
177
  case "object": {
1060
178
  addFieldToFlatMap(prefix || "", def);
1061
179
  for (const [key, value] of def.type.props.entries()) {
@@ -1101,20 +219,31 @@ else flatUnion.item(existing);
1101
219
  //#region packages/typescript/src/serialize.ts
1102
220
  const SERIALIZE_VERSION = 1;
1103
221
  function serializeAnnotatedType(type, options) {
1104
- const result = serializeNode(type, [], options);
222
+ const visited = new Set();
223
+ const result = serializeNode(type, [], options, visited);
1105
224
  result.$v = SERIALIZE_VERSION;
1106
225
  return result;
1107
226
  }
1108
- function serializeNode(def, path, options) {
227
+ function serializeNode(def, path, options, visited) {
228
+ if (def.id && visited.has(def.id)) return {
229
+ type: {
230
+ kind: "$ref",
231
+ id: def.id
232
+ },
233
+ metadata: {},
234
+ ...def.optional ? { optional: true } : {},
235
+ id: def.id
236
+ };
237
+ if (def.id) visited.add(def.id);
1109
238
  const result = {
1110
- type: serializeTypeDef(def, path, options),
239
+ type: serializeTypeDef(def, path, options, visited),
1111
240
  metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
1112
241
  };
1113
242
  if (def.optional) result.optional = true;
1114
243
  if (def.id) result.id = def.id;
1115
244
  return result;
1116
245
  }
1117
- function serializeTypeDef(def, path, options) {
246
+ function serializeTypeDef(def, path, options, visited) {
1118
247
  return forAnnotatedType(def, {
1119
248
  phantom(d) {
1120
249
  return {
@@ -1134,13 +263,13 @@ function serializeTypeDef(def, path, options) {
1134
263
  },
1135
264
  object(d) {
1136
265
  const props = {};
1137
- for (const [key, val] of d.type.props.entries()) props[key] = serializeNode(val, [...path, key], options);
266
+ for (const [key, val] of d.type.props.entries()) props[key] = serializeNode(val, [...path, key], options, visited);
1138
267
  const propsPatterns = d.type.propsPatterns.map((pp) => ({
1139
268
  pattern: {
1140
269
  source: pp.pattern.source,
1141
270
  flags: pp.pattern.flags
1142
271
  },
1143
- def: serializeNode(pp.def, path, options)
272
+ def: serializeNode(pp.def, path, options, visited)
1144
273
  }));
1145
274
  return {
1146
275
  kind: "object",
@@ -1152,28 +281,28 @@ function serializeTypeDef(def, path, options) {
1152
281
  array(d) {
1153
282
  return {
1154
283
  kind: "array",
1155
- of: serializeNode(d.type.of, path, options),
284
+ of: serializeNode(d.type.of, path, options, visited),
1156
285
  tags: Array.from(d.type.tags)
1157
286
  };
1158
287
  },
1159
288
  union(d) {
1160
289
  return {
1161
290
  kind: "union",
1162
- items: d.type.items.map((item) => serializeNode(item, path, options)),
291
+ items: d.type.items.map((item) => serializeNode(item, path, options, visited)),
1163
292
  tags: Array.from(d.type.tags)
1164
293
  };
1165
294
  },
1166
295
  intersection(d) {
1167
296
  return {
1168
297
  kind: "intersection",
1169
- items: d.type.items.map((item) => serializeNode(item, path, options)),
298
+ items: d.type.items.map((item) => serializeNode(item, path, options, visited)),
1170
299
  tags: Array.from(d.type.tags)
1171
300
  };
1172
301
  },
1173
302
  tuple(d) {
1174
303
  return {
1175
304
  kind: "tuple",
1176
- items: d.type.items.map((item) => serializeNode(item, path, options)),
305
+ items: d.type.items.map((item) => serializeNode(item, path, options, visited)),
1177
306
  tags: Array.from(d.type.tags)
1178
307
  };
1179
308
  }
@@ -1213,7 +342,7 @@ function deserializeNode(data) {
1213
342
  return result;
1214
343
  }
1215
344
  function deserializeTypeDef(t) {
1216
- const tags = new Set(t.tags);
345
+ const tags = "tags" in t ? new Set(t.tags) : new Set();
1217
346
  switch (t.kind) {
1218
347
  case "": {
1219
348
  const result = {
@@ -1250,6 +379,12 @@ function deserializeTypeDef(t) {
1250
379
  items: t.items.map((item) => deserializeNode(item)),
1251
380
  tags
1252
381
  };
382
+ case "$ref": return {
383
+ kind: "object",
384
+ props: new Map(),
385
+ propsPatterns: [],
386
+ tags
387
+ };
1253
388
  default: throw new Error(`Unknown serialized type kind "${t.kind}"`);
1254
389
  }
1255
390
  }