@atscript/typescript 0.1.87 → 0.1.89
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/cli.cjs +427 -389
- package/dist/index.cjs +4 -5
- package/dist/index.mjs +1 -2
- package/dist/{json-schema-3mti4G4J.cjs → json-schema-DXEiCfe1.cjs} +206 -97
- package/dist/{json-schema-B64qclVa.mjs → json-schema-DycaD0Rm.mjs} +207 -98
- package/dist/{plugin-CYzZm3rk.mjs → plugin-BOoquMkD.mjs} +117 -152
- package/dist/{plugin-CZ5g5D9I.cjs → plugin-DMBkdYCf.cjs} +180 -211
- package/dist/test-utils.cjs +16 -9
- package/dist/test-utils.mjs +9 -3
- package/dist/utils.cjs +176 -106
- package/dist/utils.mjs +153 -83
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
Object.
|
|
2
|
-
const require_plugin = require('./plugin-
|
|
3
|
-
require('./json-schema-3mti4G4J.cjs');
|
|
1
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
|
|
2
|
+
const require_plugin = require('./plugin-DMBkdYCf.cjs');
|
|
4
3
|
|
|
5
|
-
exports.default = require_plugin.tsPlugin
|
|
6
|
-
exports.tsPlugin = require_plugin.tsPlugin
|
|
4
|
+
exports.default = require_plugin.tsPlugin;
|
|
5
|
+
exports.tsPlugin = require_plugin.tsPlugin;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
|
|
3
2
|
//#region packages/typescript/src/runtime/validator.ts
|
|
4
3
|
function _define_property(obj, key, value) {
|
|
@@ -8,11 +7,31 @@ function _define_property(obj, key, value) {
|
|
|
8
7
|
configurable: true,
|
|
9
8
|
writable: true
|
|
10
9
|
});
|
|
11
|
-
else obj[key] = value;
|
|
10
|
+
else obj[key] = value;
|
|
12
11
|
return obj;
|
|
13
12
|
}
|
|
14
|
-
const regexCache = new Map();
|
|
15
|
-
|
|
13
|
+
const regexCache = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Validates values against an {@link TAtscriptAnnotatedType} definition.
|
|
16
|
+
*
|
|
17
|
+
* `DataType` is automatically inferred from the type definition's phantom generic,
|
|
18
|
+
* enabling the {@link validate} method to act as a type guard.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // From a generated interface class:
|
|
23
|
+
* const validator = new Validator(MyInterface)
|
|
24
|
+
* if (validator.validate(data, true)) {
|
|
25
|
+
* data // narrowed to MyInterface
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // Or use the built-in factory:
|
|
29
|
+
* MyInterface.validator().validate(data)
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @typeParam T - The annotated type definition.
|
|
33
|
+
* @typeParam DataType - The TypeScript type that `validate` narrows to (auto-inferred).
|
|
34
|
+
*/ var Validator = class {
|
|
16
35
|
buildPath() {
|
|
17
36
|
if (this.depth <= 0) return "";
|
|
18
37
|
let path = this.pathSegments[0];
|
|
@@ -27,7 +46,7 @@ var Validator = class {
|
|
|
27
46
|
pop(saveErrors) {
|
|
28
47
|
this.depth--;
|
|
29
48
|
const popped = this.stackErrors.pop();
|
|
30
|
-
if (saveErrors && popped !== null && popped !==
|
|
49
|
+
if (saveErrors && popped !== null && popped !== void 0 && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
31
50
|
return popped;
|
|
32
51
|
}
|
|
33
52
|
clear() {
|
|
@@ -68,7 +87,7 @@ var Validator = class {
|
|
|
68
87
|
this.limitExceeded = false;
|
|
69
88
|
this.context = context;
|
|
70
89
|
const passed = this.validateSafe(this.def, value);
|
|
71
|
-
this.context =
|
|
90
|
+
this.context = void 0;
|
|
72
91
|
if (!passed) {
|
|
73
92
|
if (safe) return false;
|
|
74
93
|
this.throw();
|
|
@@ -79,13 +98,13 @@ var Validator = class {
|
|
|
79
98
|
if (this.limitExceeded) return false;
|
|
80
99
|
if (this.hasReplace) {
|
|
81
100
|
let replaced = this.replaceCache.get(def);
|
|
82
|
-
if (replaced ===
|
|
101
|
+
if (replaced === void 0) {
|
|
83
102
|
replaced = this.opts.replace(def, this.buildPath());
|
|
84
103
|
this.replaceCache.set(def, replaced);
|
|
85
104
|
}
|
|
86
105
|
def = replaced;
|
|
87
106
|
}
|
|
88
|
-
if (def.optional && (value ===
|
|
107
|
+
if (def.optional && (value === void 0 || value === null)) return true;
|
|
89
108
|
if (this.hasPlugins) for (const plugin of this.opts.plugins) {
|
|
90
109
|
const result = plugin(this, def, value);
|
|
91
110
|
if (result === false || result === true) return result;
|
|
@@ -97,10 +116,9 @@ var Validator = class {
|
|
|
97
116
|
}
|
|
98
117
|
validateAnnotatedType(def, value) {
|
|
99
118
|
switch (def.type.kind) {
|
|
100
|
-
case "":
|
|
119
|
+
case "":
|
|
101
120
|
if (def.type.designType === "phantom") return true;
|
|
102
121
|
return this.validatePrimitive(def, value);
|
|
103
|
-
}
|
|
104
122
|
case "object": return this.validateObject(def, value);
|
|
105
123
|
case "array": return this.validateArray(def, value);
|
|
106
124
|
case "union": return this.validateUnion(def, value);
|
|
@@ -121,10 +139,10 @@ var Validator = class {
|
|
|
121
139
|
const branchErrors = this.stackErrors.pop();
|
|
122
140
|
if (this.limitExceeded) this.limitExceeded = false;
|
|
123
141
|
if (branchErrors) if (details) for (const err of branchErrors) details.push(err);
|
|
124
|
-
else details = branchErrors;
|
|
142
|
+
else details = branchErrors;
|
|
125
143
|
}
|
|
126
144
|
const expected = items.map((item, i) => `[${item.type.kind || item.type.designType}(${i})]`).join(", ");
|
|
127
|
-
this.error(`Value does not match any of the allowed types: ${expected}`,
|
|
145
|
+
this.error(`Value does not match any of the allowed types: ${expected}`, void 0, details);
|
|
128
146
|
return false;
|
|
129
147
|
}
|
|
130
148
|
validateIntersection(def, value) {
|
|
@@ -174,8 +192,8 @@ else details = branchErrors;
|
|
|
174
192
|
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
175
193
|
if (uniqueItems) {
|
|
176
194
|
const separator = "▼↩";
|
|
177
|
-
const seen = new Set();
|
|
178
|
-
const keyProps = new Set();
|
|
195
|
+
const seen = /* @__PURE__ */ new Set();
|
|
196
|
+
const keyProps = /* @__PURE__ */ new Set();
|
|
179
197
|
if (def.type.of.type.kind === "object") {
|
|
180
198
|
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
181
199
|
}
|
|
@@ -220,7 +238,7 @@ else details = branchErrors;
|
|
|
220
238
|
const path = this.depth > 0 ? `${this.buildPath()}.` : "";
|
|
221
239
|
for (const item of this.opts.skipList) if (item.startsWith(path)) {
|
|
222
240
|
const key = item.slice(path.length);
|
|
223
|
-
if (!skipList) skipList = new Set();
|
|
241
|
+
if (!skipList) skipList = /* @__PURE__ */ new Set();
|
|
224
242
|
skipList.add(key);
|
|
225
243
|
}
|
|
226
244
|
}
|
|
@@ -228,12 +246,12 @@ else details = branchErrors;
|
|
|
228
246
|
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.buildPath());
|
|
229
247
|
for (const [key, item] of def.type.props.entries()) {
|
|
230
248
|
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
231
|
-
if (value[key] ===
|
|
249
|
+
if (value[key] === void 0) {
|
|
232
250
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.depth === 0) continue;
|
|
233
251
|
}
|
|
234
252
|
this.push(key);
|
|
235
253
|
if (this.validateSafe(item, value[key])) this.pop(false);
|
|
236
|
-
else {
|
|
254
|
+
else {
|
|
237
255
|
passed = false;
|
|
238
256
|
this.pop(true);
|
|
239
257
|
if (this.limitExceeded) return false;
|
|
@@ -283,7 +301,7 @@ else {
|
|
|
283
301
|
return passed;
|
|
284
302
|
}
|
|
285
303
|
validatePrimitive(def, value) {
|
|
286
|
-
if (def.type.value !==
|
|
304
|
+
if (def.type.value !== void 0) {
|
|
287
305
|
if (value !== def.type.value) {
|
|
288
306
|
this.error(`Expected ${def.type.value}, got ${value}`);
|
|
289
307
|
return false;
|
|
@@ -292,47 +310,41 @@ else {
|
|
|
292
310
|
}
|
|
293
311
|
const typeOfValue = Array.isArray(value) ? "array" : typeof value;
|
|
294
312
|
switch (def.type.designType) {
|
|
295
|
-
case "never":
|
|
313
|
+
case "never":
|
|
296
314
|
this.error(`This type is impossible, must be an internal problem`);
|
|
297
315
|
return false;
|
|
298
|
-
}
|
|
299
316
|
case "any": return true;
|
|
300
|
-
case "string":
|
|
317
|
+
case "string":
|
|
301
318
|
if (typeOfValue !== def.type.designType) {
|
|
302
319
|
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
303
320
|
return false;
|
|
304
321
|
}
|
|
305
322
|
return this.validateString(def, value);
|
|
306
|
-
|
|
307
|
-
case "number": {
|
|
323
|
+
case "number":
|
|
308
324
|
if (typeOfValue !== def.type.designType) {
|
|
309
325
|
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
310
326
|
return false;
|
|
311
327
|
}
|
|
312
328
|
return this.validateNumber(def, value);
|
|
313
|
-
|
|
314
|
-
case "boolean": {
|
|
329
|
+
case "boolean":
|
|
315
330
|
if (typeOfValue !== def.type.designType) {
|
|
316
331
|
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
317
332
|
return false;
|
|
318
333
|
}
|
|
319
334
|
return this.validateBoolean(def, value);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (value !== undefined) {
|
|
335
|
+
case "undefined":
|
|
336
|
+
if (value !== void 0) {
|
|
323
337
|
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
324
338
|
return false;
|
|
325
339
|
}
|
|
326
340
|
return true;
|
|
327
|
-
|
|
328
|
-
case "null": {
|
|
341
|
+
case "null":
|
|
329
342
|
if (value !== null) {
|
|
330
343
|
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
331
344
|
return false;
|
|
332
345
|
}
|
|
333
346
|
return true;
|
|
334
|
-
|
|
335
|
-
case "decimal": {
|
|
347
|
+
case "decimal":
|
|
336
348
|
if (typeOfValue !== "string") {
|
|
337
349
|
this.error(`Expected string (decimal), got ${typeOfValue}`);
|
|
338
350
|
return false;
|
|
@@ -342,7 +354,6 @@ else {
|
|
|
342
354
|
return false;
|
|
343
355
|
}
|
|
344
356
|
return true;
|
|
345
|
-
}
|
|
346
357
|
default: throw new Error(`Unknown type "${def.type.designType}"`);
|
|
347
358
|
}
|
|
348
359
|
}
|
|
@@ -457,10 +468,10 @@ else {
|
|
|
457
468
|
};
|
|
458
469
|
this.hasPlugins = this.opts.plugins.length > 0;
|
|
459
470
|
this.hasReplace = typeof this.opts.replace === "function";
|
|
460
|
-
if (this.hasReplace) this.replaceCache = new WeakMap();
|
|
471
|
+
if (this.hasReplace) this.replaceCache = /* @__PURE__ */ new WeakMap();
|
|
461
472
|
}
|
|
462
473
|
};
|
|
463
|
-
var ValidatorError = class extends Error {
|
|
474
|
+
/** Error thrown by {@link Validator.validate} when validation fails. Contains structured error details. */ var ValidatorError = class extends Error {
|
|
464
475
|
constructor(errors) {
|
|
465
476
|
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";
|
|
466
477
|
}
|
|
@@ -477,7 +488,10 @@ const NON_PRIMITIVE_KINDS = new Set(["array", "object"]);
|
|
|
477
488
|
/** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
|
|
478
489
|
return new Validator(this, opts);
|
|
479
490
|
}
|
|
480
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Creates a minimal annotated type node from a type def and metadata map.
|
|
493
|
+
* Centralises the shape so callers never duplicate the boilerplate.
|
|
494
|
+
*/ function createAnnotatedTypeNode(type, metadata, opts) {
|
|
481
495
|
return {
|
|
482
496
|
__is_atscript_annotated_type: true,
|
|
483
497
|
type,
|
|
@@ -488,19 +502,27 @@ function createAnnotatedTypeNode(type, metadata, opts) {
|
|
|
488
502
|
ref: opts?.ref
|
|
489
503
|
};
|
|
490
504
|
}
|
|
491
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Type Guard to check if a type is atscript-annotated
|
|
507
|
+
*/ function isAnnotatedType(type) {
|
|
492
508
|
return type && type.__is_atscript_annotated_type;
|
|
493
509
|
}
|
|
494
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Standalone annotate function that handles both replace and append (array) strategies.
|
|
512
|
+
* Used by the handle's .annotate() method and by generated mutation statements.
|
|
513
|
+
*/ function annotate(metadata, key, value, asArray) {
|
|
495
514
|
if (!metadata) return;
|
|
496
515
|
if (asArray) if (metadata.has(key)) {
|
|
497
516
|
const a = metadata.get(key);
|
|
498
517
|
if (Array.isArray(a)) a.push(value);
|
|
499
|
-
else metadata.set(key, [a, value]);
|
|
518
|
+
else metadata.set(key, [a, value]);
|
|
500
519
|
} else metadata.set(key, [value]);
|
|
501
|
-
else metadata.set(key, value);
|
|
520
|
+
else metadata.set(key, value);
|
|
502
521
|
}
|
|
503
|
-
|
|
522
|
+
/**
|
|
523
|
+
* Clones a property's type tree in-place so mutations don't leak to shared refs.
|
|
524
|
+
* Used by mutating annotate codegen when paths cross ref boundaries.
|
|
525
|
+
*/ function cloneRefProp(parentType, propName) {
|
|
504
526
|
if (parentType.kind !== "object") return;
|
|
505
527
|
const objType = parentType;
|
|
506
528
|
const existing = objType.props.get(propName);
|
|
@@ -514,7 +536,7 @@ function cloneRefProp(parentType, propName) {
|
|
|
514
536
|
function cloneTypeDef(type) {
|
|
515
537
|
if (type.kind === "object") {
|
|
516
538
|
const obj = type;
|
|
517
|
-
const props = new Map();
|
|
539
|
+
const props = /* @__PURE__ */ new Map();
|
|
518
540
|
for (const [k, v] of obj.props) props.set(k, createAnnotatedTypeNode(v.type, new Map(v.metadata), {
|
|
519
541
|
id: v.id,
|
|
520
542
|
optional: v.optional
|
|
@@ -547,18 +569,36 @@ function cloneTypeDef(type) {
|
|
|
547
569
|
tags: new Set(type.tags)
|
|
548
570
|
};
|
|
549
571
|
}
|
|
550
|
-
|
|
572
|
+
/**
|
|
573
|
+
* Creates a builder handle for constructing a {@link TAtscriptAnnotatedType} at runtime.
|
|
574
|
+
*
|
|
575
|
+
* This is primarily used by generated `.as.js` code. The returned handle provides
|
|
576
|
+
* a fluent API for setting the type definition, metadata, and properties.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```ts
|
|
580
|
+
* const handle = defineAnnotatedType('object')
|
|
581
|
+
* .prop('name', defineAnnotatedType().designType('string').$type)
|
|
582
|
+
* .prop('age', defineAnnotatedType().designType('number').$type)
|
|
583
|
+
*
|
|
584
|
+
* handle.$type // the resulting TAtscriptAnnotatedType
|
|
585
|
+
* ```
|
|
586
|
+
*
|
|
587
|
+
* @param _kind - The kind of type to create (e.g. `'object'`, `'array'`, `'union'`). Defaults to `''` (primitive/final).
|
|
588
|
+
* @param base - Optional existing object to augment with annotated type fields.
|
|
589
|
+
* @returns A builder handle for fluent type construction.
|
|
590
|
+
*/ function defineAnnotatedType(_kind, base) {
|
|
551
591
|
const kind = _kind || "";
|
|
552
|
-
const baseProvided = base !==
|
|
592
|
+
const baseProvided = base !== void 0;
|
|
553
593
|
const type = base?.type || {};
|
|
554
594
|
type.kind = kind;
|
|
555
595
|
if (COMPLEX_KINDS.has(kind)) type.items = [];
|
|
556
596
|
if (kind === "object") {
|
|
557
|
-
type.props = new Map();
|
|
597
|
+
type.props = /* @__PURE__ */ new Map();
|
|
558
598
|
type.propsPatterns = [];
|
|
559
599
|
}
|
|
560
|
-
type.tags = new Set();
|
|
561
|
-
const metadata = base?.metadata || new Map();
|
|
600
|
+
type.tags = /* @__PURE__ */ new Set();
|
|
601
|
+
const metadata = base?.metadata || /* @__PURE__ */ new Map();
|
|
562
602
|
const payload = {
|
|
563
603
|
__is_atscript_annotated_type: true,
|
|
564
604
|
metadata,
|
|
@@ -566,7 +606,7 @@ function defineAnnotatedType(_kind, base) {
|
|
|
566
606
|
validator: validatorMethod
|
|
567
607
|
};
|
|
568
608
|
base = base ? Object.assign(base, payload) : payload;
|
|
569
|
-
|
|
609
|
+
return {
|
|
570
610
|
$type: base,
|
|
571
611
|
$def: type,
|
|
572
612
|
$metadata: metadata,
|
|
@@ -609,15 +649,15 @@ function defineAnnotatedType(_kind, base) {
|
|
|
609
649
|
for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
|
|
610
650
|
return this;
|
|
611
651
|
},
|
|
612
|
-
refTo(type
|
|
652
|
+
refTo(type, chain) {
|
|
613
653
|
const node = this.$type;
|
|
614
|
-
if (isAnnotatedType(type
|
|
615
|
-
let newBase = type
|
|
616
|
-
const typeName = type
|
|
654
|
+
if (isAnnotatedType(type)) {
|
|
655
|
+
let newBase = type;
|
|
656
|
+
const typeName = type.name || "Unknown";
|
|
617
657
|
if (chain) for (let i = 0; i < chain.length; i++) {
|
|
618
658
|
const c = chain[i];
|
|
619
659
|
if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
|
|
620
|
-
else {
|
|
660
|
+
else {
|
|
621
661
|
const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
|
|
622
662
|
throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
623
663
|
}
|
|
@@ -625,11 +665,11 @@ else {
|
|
|
625
665
|
node.type = baseProvided ? cloneTypeDef(newBase.type) : newBase.type;
|
|
626
666
|
node.id = node.id ?? newBase.id;
|
|
627
667
|
if (chain && chain.length > 0) node.ref = {
|
|
628
|
-
type: () => type
|
|
668
|
+
type: () => type,
|
|
629
669
|
field: chain.join(".")
|
|
630
670
|
};
|
|
631
|
-
} else if (typeof type
|
|
632
|
-
const lazyType = type
|
|
671
|
+
} else if (typeof type === "function") {
|
|
672
|
+
const lazyType = type;
|
|
633
673
|
const ownId = node.id;
|
|
634
674
|
node.ref = {
|
|
635
675
|
type: lazyType,
|
|
@@ -649,7 +689,7 @@ else {
|
|
|
649
689
|
}
|
|
650
690
|
let target = t;
|
|
651
691
|
if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
|
|
652
|
-
else return t.type;
|
|
692
|
+
else return t.type;
|
|
653
693
|
node.id = ownId ?? target.id;
|
|
654
694
|
const resolved = baseProvided ? cloneTypeDef(target.type) : target.type;
|
|
655
695
|
Object.defineProperty(node, "type", {
|
|
@@ -661,7 +701,7 @@ else return t.type;
|
|
|
661
701
|
},
|
|
662
702
|
configurable: true
|
|
663
703
|
});
|
|
664
|
-
} else throw new TypeError(`${type
|
|
704
|
+
} else throw new TypeError(`${type} is not annotated type`);
|
|
665
705
|
return this;
|
|
666
706
|
},
|
|
667
707
|
annotate(key, value, asArray) {
|
|
@@ -673,12 +713,21 @@ else return t.type;
|
|
|
673
713
|
return this;
|
|
674
714
|
}
|
|
675
715
|
};
|
|
676
|
-
return handle;
|
|
677
716
|
}
|
|
678
|
-
|
|
717
|
+
/**
|
|
718
|
+
* Checks whether an annotated type is a phantom type.
|
|
719
|
+
*
|
|
720
|
+
* Phantom types do not affect the data type, validation, or schema,
|
|
721
|
+
* but are discoverable via runtime type traversal.
|
|
722
|
+
*/ function isPhantomType(def) {
|
|
679
723
|
return def.type.kind === "" && def.type.designType === "phantom";
|
|
680
724
|
}
|
|
681
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Checks whether an annotated type resolves to a primitive (non-object, non-array) shape.
|
|
727
|
+
*
|
|
728
|
+
* Returns `true` for final types and for unions/intersections/tuples
|
|
729
|
+
* whose members are all primitives.
|
|
730
|
+
*/ function isAnnotatedTypeOfPrimitive(t) {
|
|
682
731
|
if (NON_PRIMITIVE_KINDS.has(t.type.kind)) return false;
|
|
683
732
|
if (!t.type.kind) return true;
|
|
684
733
|
if (COMPLEX_KINDS.has(t.type.kind)) {
|
|
@@ -690,7 +739,17 @@ function isAnnotatedTypeOfPrimitive(t) {
|
|
|
690
739
|
|
|
691
740
|
//#endregion
|
|
692
741
|
//#region packages/typescript/src/runtime/traverse.ts
|
|
693
|
-
|
|
742
|
+
/**
|
|
743
|
+
* Type-safe dispatch over `TAtscriptAnnotatedType` by its `type.kind`.
|
|
744
|
+
*
|
|
745
|
+
* Provides the common `switch (def.type.kind)` pattern used by
|
|
746
|
+
* the validator, JSON-schema builder, and serializer.
|
|
747
|
+
* Each caller supplies its own handlers that control recursion.
|
|
748
|
+
*
|
|
749
|
+
* When a `phantom` handler is provided, phantom types (`designType === 'phantom'`)
|
|
750
|
+
* are dispatched to it instead of `final`. This allows consumers to skip or
|
|
751
|
+
* handle phantom props without polluting their `final` handler.
|
|
752
|
+
*/ function forAnnotatedType(def, handlers) {
|
|
694
753
|
switch (def.type.kind) {
|
|
695
754
|
case "": {
|
|
696
755
|
const typed = def;
|
|
@@ -708,21 +767,27 @@ function forAnnotatedType(def, handlers) {
|
|
|
708
767
|
|
|
709
768
|
//#endregion
|
|
710
769
|
//#region packages/typescript/src/runtime/json-schema.ts
|
|
711
|
-
|
|
770
|
+
/**
|
|
771
|
+
* Detects a discriminator property across union items.
|
|
772
|
+
*
|
|
773
|
+
* Scans all items for object-typed members that share a common property
|
|
774
|
+
* with distinct const/literal values. If exactly one such property exists,
|
|
775
|
+
* it is returned as the discriminator. Returns `null` when no qualifying
|
|
776
|
+
* property exists, or when more than one does (ambiguous).
|
|
777
|
+
*/ function detectDiscriminator(items) {
|
|
712
778
|
if (items.length < 2) return null;
|
|
713
779
|
for (const item of items) if (item.type.kind !== "object") return null;
|
|
714
780
|
const firstObj = items[0].type;
|
|
715
781
|
const candidates = [];
|
|
716
|
-
for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !==
|
|
782
|
+
for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== void 0) candidates.push(propName);
|
|
717
783
|
let result = null;
|
|
718
784
|
for (const candidate of candidates) {
|
|
719
|
-
const values = new Set();
|
|
785
|
+
const values = /* @__PURE__ */ new Set();
|
|
720
786
|
const indexMapping = {};
|
|
721
787
|
let valid = true;
|
|
722
788
|
for (let i = 0; i < items.length; i++) {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
|
|
789
|
+
const prop = items[i].type.props.get(candidate);
|
|
790
|
+
if (!prop || prop.type.kind !== "" || prop.type.value === void 0) {
|
|
726
791
|
valid = false;
|
|
727
792
|
break;
|
|
728
793
|
}
|
|
@@ -744,7 +809,23 @@ function detectDiscriminator(items) {
|
|
|
744
809
|
}
|
|
745
810
|
return result;
|
|
746
811
|
}
|
|
747
|
-
|
|
812
|
+
/**
|
|
813
|
+
* Builds a JSON Schema from an {@link TAtscriptAnnotatedType}.
|
|
814
|
+
*
|
|
815
|
+
* Translates the atscript type structure and validation metadata
|
|
816
|
+
* (min/max, patterns, integer constraints, etc.) into a standard JSON Schema.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```ts
|
|
820
|
+
* import { buildJsonSchema } from '@atscript/typescript'
|
|
821
|
+
*
|
|
822
|
+
* const schema = buildJsonSchema(MyInterface)
|
|
823
|
+
* // { type: 'object', properties: { ... }, required: [...] }
|
|
824
|
+
* ```
|
|
825
|
+
*
|
|
826
|
+
* @param type - The annotated type to convert.
|
|
827
|
+
* @returns A JSON Schema object.
|
|
828
|
+
*/ function buildJsonSchema(type) {
|
|
748
829
|
const defs = {};
|
|
749
830
|
let hasDefs = false;
|
|
750
831
|
const buildObject = (d) => {
|
|
@@ -755,12 +836,12 @@ function buildJsonSchema(type) {
|
|
|
755
836
|
properties[key] = build(val);
|
|
756
837
|
if (!val.optional) required.push(key);
|
|
757
838
|
}
|
|
758
|
-
const schema
|
|
839
|
+
const schema = {
|
|
759
840
|
type: "object",
|
|
760
841
|
properties
|
|
761
842
|
};
|
|
762
|
-
if (required.length > 0) schema
|
|
763
|
-
return schema
|
|
843
|
+
if (required.length > 0) schema.required = required;
|
|
844
|
+
return schema;
|
|
764
845
|
};
|
|
765
846
|
const build = (def) => {
|
|
766
847
|
if (def.id && def.type.kind === "object" && def !== type) {
|
|
@@ -781,15 +862,15 @@ function buildJsonSchema(type) {
|
|
|
781
862
|
return buildObject(d);
|
|
782
863
|
},
|
|
783
864
|
array(d) {
|
|
784
|
-
const schema
|
|
865
|
+
const schema = {
|
|
785
866
|
type: "array",
|
|
786
867
|
items: build(d.type.of)
|
|
787
868
|
};
|
|
788
869
|
const minLength = meta.get("expect.minLength");
|
|
789
|
-
if (minLength) schema
|
|
870
|
+
if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
790
871
|
const maxLength = meta.get("expect.maxLength");
|
|
791
|
-
if (maxLength) schema
|
|
792
|
-
return schema
|
|
872
|
+
if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
873
|
+
return schema;
|
|
793
874
|
},
|
|
794
875
|
union(d) {
|
|
795
876
|
const disc = detectDiscriminator(d.type.items);
|
|
@@ -821,30 +902,30 @@ function buildJsonSchema(type) {
|
|
|
821
902
|
};
|
|
822
903
|
},
|
|
823
904
|
final(d) {
|
|
824
|
-
const schema
|
|
825
|
-
if (d.type.value !==
|
|
905
|
+
const schema = {};
|
|
906
|
+
if (d.type.value !== void 0) schema.const = d.type.value;
|
|
826
907
|
if (d.type.designType && d.type.designType !== "any") {
|
|
827
908
|
const dt = d.type.designType;
|
|
828
|
-
schema
|
|
829
|
-
if (schema
|
|
909
|
+
schema.type = dt === "undefined" ? "null" : dt === "decimal" ? "string" : dt;
|
|
910
|
+
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
830
911
|
}
|
|
831
|
-
if (schema
|
|
832
|
-
if (meta.get("meta.required")) schema
|
|
912
|
+
if (schema.type === "string") {
|
|
913
|
+
if (meta.get("meta.required")) schema.minLength = 1;
|
|
833
914
|
const minLength = meta.get("expect.minLength");
|
|
834
|
-
if (minLength) schema
|
|
915
|
+
if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
835
916
|
const maxLength = meta.get("expect.maxLength");
|
|
836
|
-
if (maxLength) schema
|
|
917
|
+
if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
837
918
|
const patterns = meta.get("expect.pattern");
|
|
838
|
-
if (patterns?.length) if (patterns.length === 1) schema
|
|
839
|
-
else schema
|
|
919
|
+
if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
|
|
920
|
+
else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
840
921
|
}
|
|
841
|
-
if (schema
|
|
922
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
842
923
|
const min = meta.get("expect.min");
|
|
843
|
-
if (min) schema
|
|
924
|
+
if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
|
|
844
925
|
const max = meta.get("expect.max");
|
|
845
|
-
if (max) schema
|
|
926
|
+
if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
|
|
846
927
|
}
|
|
847
|
-
return schema
|
|
928
|
+
return schema;
|
|
848
929
|
}
|
|
849
930
|
});
|
|
850
931
|
};
|
|
@@ -855,9 +936,28 @@ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ patte
|
|
|
855
936
|
};
|
|
856
937
|
return schema;
|
|
857
938
|
}
|
|
858
|
-
|
|
939
|
+
/**
|
|
940
|
+
* Converts a JSON Schema object into a {@link TAtscriptAnnotatedType}.
|
|
941
|
+
*
|
|
942
|
+
* This is the inverse of {@link buildJsonSchema}. A round-trip
|
|
943
|
+
* `buildJsonSchema(fromJsonSchema(schema))` preserves structure and constraints.
|
|
944
|
+
*
|
|
945
|
+
* Supports the JSON Schema subset produced by `buildJsonSchema` plus
|
|
946
|
+
* common extensions like `oneOf` (treated as union) and `enum` (union of literals).
|
|
947
|
+
*
|
|
948
|
+
* @example
|
|
949
|
+
* ```ts
|
|
950
|
+
* import { fromJsonSchema } from '@atscript/typescript'
|
|
951
|
+
*
|
|
952
|
+
* const type = fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] })
|
|
953
|
+
* type.validator().validate({ name: 'Alice' }) // passes
|
|
954
|
+
* ```
|
|
955
|
+
*
|
|
956
|
+
* @param schema - A JSON Schema object.
|
|
957
|
+
* @returns An annotated type with full validator support.
|
|
958
|
+
*/ function fromJsonSchema(schema) {
|
|
859
959
|
const defsSource = schema.$defs || schema.definitions || {};
|
|
860
|
-
const resolved = new Map();
|
|
960
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
861
961
|
const convert = (s) => {
|
|
862
962
|
if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
|
|
863
963
|
if (s.$ref) {
|
|
@@ -920,9 +1020,9 @@ function fromJsonSchema(schema) {
|
|
|
920
1020
|
}
|
|
921
1021
|
if (s.type === "array") {
|
|
922
1022
|
if (Array.isArray(s.items)) {
|
|
923
|
-
const handle
|
|
924
|
-
for (const item of s.items) handle
|
|
925
|
-
return handle
|
|
1023
|
+
const handle = defineAnnotatedType("tuple");
|
|
1024
|
+
for (const item of s.items) handle.item(convert(item));
|
|
1025
|
+
return handle.$type;
|
|
926
1026
|
}
|
|
927
1027
|
const itemType = s.items ? convert(s.items) : defineAnnotatedType().designType("any").$type;
|
|
928
1028
|
const handle = defineAnnotatedType("array").of(itemType);
|
|
@@ -959,7 +1059,16 @@ function fromJsonSchema(schema) {
|
|
|
959
1059
|
};
|
|
960
1060
|
return convert(schema);
|
|
961
1061
|
}
|
|
962
|
-
|
|
1062
|
+
/**
|
|
1063
|
+
* Merges multiple annotated types into a combined schema map with shared `$defs`.
|
|
1064
|
+
*
|
|
1065
|
+
* Each type must have an `id`. The returned `schemas` object contains individual
|
|
1066
|
+
* schemas keyed by type id, and `$defs` contains all shared type definitions
|
|
1067
|
+
* deduplicated across schemas.
|
|
1068
|
+
*
|
|
1069
|
+
* @param types - Array of annotated types, each with an `id`.
|
|
1070
|
+
* @returns An object with `schemas` (keyed by id) and shared `$defs`.
|
|
1071
|
+
*/ function mergeJsonSchemas(types) {
|
|
963
1072
|
const mergedDefs = {};
|
|
964
1073
|
const schemas = {};
|
|
965
1074
|
for (const type of types) {
|
|
@@ -968,7 +1077,7 @@ function mergeJsonSchemas(types) {
|
|
|
968
1077
|
const schema = buildJsonSchema(type);
|
|
969
1078
|
if (schema.$defs) {
|
|
970
1079
|
for (const [defName, defSchema] of Object.entries(schema.$defs)) if (!mergedDefs[defName]) mergedDefs[defName] = defSchema;
|
|
971
|
-
const { $defs: _
|
|
1080
|
+
const { $defs: _, ...rest } = schema;
|
|
972
1081
|
schemas[name] = rest;
|
|
973
1082
|
} else schemas[name] = schema;
|
|
974
1083
|
}
|