@atscript/typescript 0.1.51 → 0.1.53

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 CHANGED
@@ -1320,13 +1320,7 @@ function forAnnotatedType(def, handlers) {
1320
1320
 
1321
1321
  //#endregion
1322
1322
  //#region packages/typescript/src/runtime/json-schema.ts
1323
- /**
1324
- * Detects a discriminator property across union items.
1325
- *
1326
- * Scans all items for object-typed members that share a common property
1327
- * with distinct const/literal values. If exactly one such property exists,
1328
- * it is returned as the discriminator.
1329
- */ function detectDiscriminator(items) {
1323
+ function detectDiscriminator(items) {
1330
1324
  if (items.length < 2) return null;
1331
1325
  for (const item of items) if (item.type.kind !== "object") return null;
1332
1326
  const firstObj = items[0].type;
@@ -1500,6 +1494,132 @@ const QUERY_OP_MAP = {
1500
1494
  "not exists": "$exists"
1501
1495
  };
1502
1496
  var JsRenderer = class extends BaseRenderer {
1497
+ /**
1498
+ * Override base `unused` to subtract symbols that the JS output references
1499
+ * transitively through `extends`-resolved parent prop trees. Without this,
1500
+ * cross-file `extends` strips the imports needed by inherited `.refTo()` calls.
1501
+ */ get unused() {
1502
+ if (this._unusedJs) return this._unusedJs;
1503
+ const base = new Set(this.doc.getUnusedTokens().map((t) => t.text));
1504
+ for (const ref of this.getExtendsRefs().keys()) if (ref.id) base.delete(ref.id);
1505
+ return this._unusedJs = base;
1506
+ }
1507
+ getExtendsRefs() {
1508
+ if (this._extendsRefs) return this._extendsRefs;
1509
+ const out = new Map();
1510
+ const visitedInterfaces = new Set();
1511
+ for (const node of this.doc.nodes) if ((0, __atscript_core.isInterface)(node) && node.hasExtends) this.walkExtendsParents(node, this.doc, out, visitedInterfaces);
1512
+ return this._extendsRefs = out;
1513
+ }
1514
+ walkRefs(node, visited, visit) {
1515
+ if (!node || visited.has(node)) return;
1516
+ visited.add(node);
1517
+ if ((0, __atscript_core.isRef)(node)) {
1518
+ visit(node);
1519
+ return;
1520
+ }
1521
+ if ((0, __atscript_core.isGroup)(node)) {
1522
+ for (const item of node.unwrap()) this.walkRefs(item, visited, visit);
1523
+ return;
1524
+ }
1525
+ if ((0, __atscript_core.isArray)(node) || (0, __atscript_core.isProp)(node)) this.walkRefs(node.getDefinition(), visited, visit);
1526
+ }
1527
+ walkExtendsParents(iface, doc, out, visited) {
1528
+ if (visited.has(iface)) return;
1529
+ visited.add(iface);
1530
+ const visitedNodes = new Set();
1531
+ for (const token of iface.extendsTokens) {
1532
+ const unwound = doc.unwindType(token.text);
1533
+ if (!unwound?.def || !(0, __atscript_core.isInterface)(unwound.def)) continue;
1534
+ const parentInterface = unwound.def;
1535
+ const parentDoc = unwound.doc;
1536
+ this.walkRefs(parentInterface.getDefinition(), visitedNodes, (ref) => {
1537
+ if (!out.has(ref)) out.set(ref, parentDoc);
1538
+ });
1539
+ if (parentInterface.hasExtends) this.walkExtendsParents(parentInterface, parentDoc, out, visited);
1540
+ }
1541
+ }
1542
+ computeSynthesizedImports() {
1543
+ if (this._synthComputed) return;
1544
+ this._synthComputed = true;
1545
+ const refToOriginDoc = this.getExtendsRefs();
1546
+ if (refToOriginDoc.size === 0) return;
1547
+ const taken = new Set(this.doc.registry.definitions.keys());
1548
+ const allocated = new Map();
1549
+ const synthPathCache = new Map();
1550
+ for (const [refNode, originDoc] of refToOriginDoc) {
1551
+ const refId = refNode.id;
1552
+ if (!refId) continue;
1553
+ const ownerInfo = this.resolveOwner(originDoc, refId);
1554
+ if (!ownerInfo?.doc || ownerInfo.doc === this.doc) continue;
1555
+ if (ownerInfo.node && (0, __atscript_core.isPrimitive)(ownerInfo.node)) continue;
1556
+ const ownerDoc = ownerInfo.doc;
1557
+ let synthPath;
1558
+ if (synthPathCache.has(ownerDoc)) synthPath = synthPathCache.get(ownerDoc);
1559
+ else {
1560
+ synthPath = this.computeSynthPath(ownerDoc);
1561
+ synthPathCache.set(ownerDoc, synthPath);
1562
+ }
1563
+ if (!synthPath) continue;
1564
+ const allocKey = `${synthPath}::${refId}`;
1565
+ let info = allocated.get(allocKey);
1566
+ if (!info) {
1567
+ info = this.allocateSynthInfo(refId, synthPath, ownerInfo, taken);
1568
+ allocated.set(allocKey, info);
1569
+ }
1570
+ this._refSynthInfo.set(refNode, info);
1571
+ }
1572
+ }
1573
+ resolveOwner(fromDoc, refId) {
1574
+ const key = `${fromDoc.id}::${refId}`;
1575
+ let v = this._ownerCache.get(key);
1576
+ if (v === undefined && !this._ownerCache.has(key)) {
1577
+ v = fromDoc.getDeclarationOwnerNode(refId);
1578
+ this._ownerCache.set(key, v);
1579
+ }
1580
+ return v;
1581
+ }
1582
+ allocateSynthInfo(refId, synthPath, ownerInfo, taken) {
1583
+ const ownerDoc = ownerInfo.doc;
1584
+ const ownerNode = ownerInfo.node;
1585
+ const existing = this.doc.importedDefs.get(refId);
1586
+ if (existing?.text === synthPath) return {
1587
+ alias: refId,
1588
+ ownerDoc,
1589
+ ownerNode
1590
+ };
1591
+ const alias = this.allocAlias(refId, taken);
1592
+ let group = this._synthImports.get(synthPath);
1593
+ if (!group) {
1594
+ group = [];
1595
+ this._synthImports.set(synthPath, group);
1596
+ }
1597
+ group.push({
1598
+ name: refId,
1599
+ alias
1600
+ });
1601
+ return {
1602
+ alias,
1603
+ ownerDoc,
1604
+ ownerNode
1605
+ };
1606
+ }
1607
+ allocAlias(name, taken) {
1608
+ if (!taken.has(name)) {
1609
+ taken.add(name);
1610
+ return name;
1611
+ }
1612
+ let n = 1;
1613
+ while (taken.has(`${name}_${n}`)) n++;
1614
+ const alias = `${name}_${n}`;
1615
+ taken.add(alias);
1616
+ return alias;
1617
+ }
1618
+ computeSynthPath(ownerDoc) {
1619
+ if ((0, __atscript_core.isBareId)(ownerDoc.id)) return ownerDoc.id.slice("bare:".length, -".as".length);
1620
+ if (!ownerDoc.id.startsWith("file://")) return undefined;
1621
+ return (0, __atscript_core.getRelPath)(this.doc.id, ownerDoc.id);
1622
+ }
1503
1623
  pre() {
1504
1624
  this.writeln("// prettier-ignore-start");
1505
1625
  this.writeln("/* eslint-disable */");
@@ -1523,6 +1643,11 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
1523
1643
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1524
1644
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1525
1645
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1646
+ this.computeSynthesizedImports();
1647
+ for (const [synthPath, names] of this._synthImports) {
1648
+ const list = names.map((n) => n.alias === n.name ? n.name : `${n.name} as ${n.alias}`).join(", ");
1649
+ this.writeln(`import { ${list} } from "${this.transformFromPath(synthPath)}"`);
1650
+ }
1526
1651
  }
1527
1652
  buildAdHocMap(annotateNodes) {
1528
1653
  const map = new Map();
@@ -1801,7 +1926,7 @@ else handle.prop(prop.id, propHandle.$type);
1801
1926
  const ref = node;
1802
1927
  const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
1803
1928
  if ((0, __atscript_core.isPrimitive)(decl)) {
1804
- const ownerDecl$1 = this.doc.getDeclarationOwnerNode(ref.id);
1929
+ const ownerDecl$1 = this.resolveOwner(this.doc, ref.id);
1805
1930
  if (!ownerDecl$1?.node || ownerDecl$1.node.entity !== "type" && ownerDecl$1.node.entity !== "interface") {
1806
1931
  this.annotateType(decl, name);
1807
1932
  return this;
@@ -1816,9 +1941,14 @@ else handle.prop(prop.id, propHandle.$type);
1816
1941
  }
1817
1942
  }
1818
1943
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1819
- const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
1820
- const isImported = ownerDecl ? ownerDecl.doc !== this.doc : false;
1821
- const refExpr = isImported ? `() => ${ref.id}` : ref.id;
1944
+ const synth = this._refSynthInfo.get(ref);
1945
+ const ownerDecl = synth ? {
1946
+ doc: synth.ownerDoc,
1947
+ node: synth.ownerNode
1948
+ } : this.resolveOwner(this.doc, ref.id);
1949
+ const refName = synth?.alias ?? ref.id;
1950
+ const isImported = !!synth || (ownerDecl ? ownerDecl.doc !== this.doc : false);
1951
+ const refExpr = isImported ? `() => ${refName}` : refName;
1822
1952
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${refExpr}${chain})`);
1823
1953
  if (!ref.hasChain) {
1824
1954
  if (ownerDecl?.node) {
@@ -2233,7 +2363,7 @@ else {
2233
2363
  return [];
2234
2364
  }
2235
2365
  constructor(doc, opts) {
2236
- super(doc), _define_property$3(this, "opts", void 0), _define_property$3(this, "postAnnotate", void 0), _define_property$3(this, "_adHocAnnotations", void 0), _define_property$3(this, "_propPath", void 0), _define_property$3(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
2366
+ super(doc), _define_property$3(this, "opts", void 0), _define_property$3(this, "postAnnotate", void 0), _define_property$3(this, "_adHocAnnotations", void 0), _define_property$3(this, "_propPath", void 0), _define_property$3(this, "typeIds", void 0), _define_property$3(this, "_unusedJs", void 0), _define_property$3(this, "_extendsRefs", void 0), _define_property$3(this, "_refSynthInfo", void 0), _define_property$3(this, "_synthImports", void 0), _define_property$3(this, "_synthComputed", void 0), _define_property$3(this, "_ownerCache", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map(), this._refSynthInfo = new Map(), this._synthImports = new Map(), this._synthComputed = false, this._ownerCache = new Map();
2237
2367
  }
2238
2368
  };
2239
2369
 
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
- const require_plugin = require('./plugin-cY7VLpmN.cjs');
3
- require('./json-schema-BgW_S2sP.cjs');
2
+ const require_plugin = require('./plugin-C90RBCnx.cjs');
3
+ require('./json-schema-Zi4TJzJJ.cjs');
4
4
 
5
5
  exports.default = require_plugin.tsPlugin
6
6
  exports.tsPlugin = require_plugin.tsPlugin
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { tsPlugin } from "./plugin-D96fH7Va.mjs";
2
- import "./json-schema-DrJMwvm1.mjs";
1
+ import { tsPlugin } from "./plugin-PADgal82.mjs";
2
+ import "./json-schema-D_wrIxzk.mjs";
3
3
 
4
4
  export { tsPlugin as default, tsPlugin };
@@ -701,13 +701,7 @@ function forAnnotatedType(def, handlers) {
701
701
 
702
702
  //#endregion
703
703
  //#region packages/typescript/src/runtime/json-schema.ts
704
- /**
705
- * Detects a discriminator property across union items.
706
- *
707
- * Scans all items for object-typed members that share a common property
708
- * with distinct const/literal values. If exactly one such property exists,
709
- * it is returned as the discriminator.
710
- */ function detectDiscriminator(items) {
704
+ function detectDiscriminator(items) {
711
705
  if (items.length < 2) return null;
712
706
  for (const item of items) if (item.type.kind !== "object") return null;
713
707
  const firstObj = items[0].type;
@@ -978,4 +972,4 @@ function mergeJsonSchemas(types) {
978
972
  }
979
973
 
980
974
  //#endregion
981
- export { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas };
975
+ export { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, detectDiscriminator, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas };
@@ -702,13 +702,7 @@ function forAnnotatedType(def, handlers) {
702
702
 
703
703
  //#endregion
704
704
  //#region packages/typescript/src/runtime/json-schema.ts
705
- /**
706
- * Detects a discriminator property across union items.
707
- *
708
- * Scans all items for object-typed members that share a common property
709
- * with distinct const/literal values. If exactly one such property exists,
710
- * it is returned as the discriminator.
711
- */ function detectDiscriminator(items) {
705
+ function detectDiscriminator(items) {
712
706
  if (items.length < 2) return null;
713
707
  for (const item of items) if (item.type.kind !== "object") return null;
714
708
  const firstObj = items[0].type;
@@ -1021,6 +1015,12 @@ Object.defineProperty(exports, 'defineAnnotatedType', {
1021
1015
  return defineAnnotatedType;
1022
1016
  }
1023
1017
  });
1018
+ Object.defineProperty(exports, 'detectDiscriminator', {
1019
+ enumerable: true,
1020
+ get: function () {
1021
+ return detectDiscriminator;
1022
+ }
1023
+ });
1024
1024
  Object.defineProperty(exports, 'forAnnotatedType', {
1025
1025
  enumerable: true,
1026
1026
  get: function () {
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
 
24
24
  //#endregion
25
- const require_json_schema = require('./json-schema-BgW_S2sP.cjs');
25
+ const require_json_schema = require('./json-schema-Zi4TJzJJ.cjs');
26
26
  const path = __toESM(require("path"));
27
27
  const __atscript_core = __toESM(require("@atscript/core"));
28
28
 
@@ -695,6 +695,132 @@ const QUERY_OP_MAP = {
695
695
  "not exists": "$exists"
696
696
  };
697
697
  var JsRenderer = class extends BaseRenderer {
698
+ /**
699
+ * Override base `unused` to subtract symbols that the JS output references
700
+ * transitively through `extends`-resolved parent prop trees. Without this,
701
+ * cross-file `extends` strips the imports needed by inherited `.refTo()` calls.
702
+ */ get unused() {
703
+ if (this._unusedJs) return this._unusedJs;
704
+ const base = new Set(this.doc.getUnusedTokens().map((t) => t.text));
705
+ for (const ref of this.getExtendsRefs().keys()) if (ref.id) base.delete(ref.id);
706
+ return this._unusedJs = base;
707
+ }
708
+ getExtendsRefs() {
709
+ if (this._extendsRefs) return this._extendsRefs;
710
+ const out = new Map();
711
+ const visitedInterfaces = new Set();
712
+ for (const node of this.doc.nodes) if ((0, __atscript_core.isInterface)(node) && node.hasExtends) this.walkExtendsParents(node, this.doc, out, visitedInterfaces);
713
+ return this._extendsRefs = out;
714
+ }
715
+ walkRefs(node, visited, visit) {
716
+ if (!node || visited.has(node)) return;
717
+ visited.add(node);
718
+ if ((0, __atscript_core.isRef)(node)) {
719
+ visit(node);
720
+ return;
721
+ }
722
+ if ((0, __atscript_core.isGroup)(node)) {
723
+ for (const item of node.unwrap()) this.walkRefs(item, visited, visit);
724
+ return;
725
+ }
726
+ if ((0, __atscript_core.isArray)(node) || (0, __atscript_core.isProp)(node)) this.walkRefs(node.getDefinition(), visited, visit);
727
+ }
728
+ walkExtendsParents(iface, doc, out, visited) {
729
+ if (visited.has(iface)) return;
730
+ visited.add(iface);
731
+ const visitedNodes = new Set();
732
+ for (const token of iface.extendsTokens) {
733
+ const unwound = doc.unwindType(token.text);
734
+ if (!unwound?.def || !(0, __atscript_core.isInterface)(unwound.def)) continue;
735
+ const parentInterface = unwound.def;
736
+ const parentDoc = unwound.doc;
737
+ this.walkRefs(parentInterface.getDefinition(), visitedNodes, (ref) => {
738
+ if (!out.has(ref)) out.set(ref, parentDoc);
739
+ });
740
+ if (parentInterface.hasExtends) this.walkExtendsParents(parentInterface, parentDoc, out, visited);
741
+ }
742
+ }
743
+ computeSynthesizedImports() {
744
+ if (this._synthComputed) return;
745
+ this._synthComputed = true;
746
+ const refToOriginDoc = this.getExtendsRefs();
747
+ if (refToOriginDoc.size === 0) return;
748
+ const taken = new Set(this.doc.registry.definitions.keys());
749
+ const allocated = new Map();
750
+ const synthPathCache = new Map();
751
+ for (const [refNode, originDoc] of refToOriginDoc) {
752
+ const refId = refNode.id;
753
+ if (!refId) continue;
754
+ const ownerInfo = this.resolveOwner(originDoc, refId);
755
+ if (!ownerInfo?.doc || ownerInfo.doc === this.doc) continue;
756
+ if (ownerInfo.node && (0, __atscript_core.isPrimitive)(ownerInfo.node)) continue;
757
+ const ownerDoc = ownerInfo.doc;
758
+ let synthPath;
759
+ if (synthPathCache.has(ownerDoc)) synthPath = synthPathCache.get(ownerDoc);
760
+ else {
761
+ synthPath = this.computeSynthPath(ownerDoc);
762
+ synthPathCache.set(ownerDoc, synthPath);
763
+ }
764
+ if (!synthPath) continue;
765
+ const allocKey = `${synthPath}::${refId}`;
766
+ let info = allocated.get(allocKey);
767
+ if (!info) {
768
+ info = this.allocateSynthInfo(refId, synthPath, ownerInfo, taken);
769
+ allocated.set(allocKey, info);
770
+ }
771
+ this._refSynthInfo.set(refNode, info);
772
+ }
773
+ }
774
+ resolveOwner(fromDoc, refId) {
775
+ const key = `${fromDoc.id}::${refId}`;
776
+ let v = this._ownerCache.get(key);
777
+ if (v === undefined && !this._ownerCache.has(key)) {
778
+ v = fromDoc.getDeclarationOwnerNode(refId);
779
+ this._ownerCache.set(key, v);
780
+ }
781
+ return v;
782
+ }
783
+ allocateSynthInfo(refId, synthPath, ownerInfo, taken) {
784
+ const ownerDoc = ownerInfo.doc;
785
+ const ownerNode = ownerInfo.node;
786
+ const existing = this.doc.importedDefs.get(refId);
787
+ if (existing?.text === synthPath) return {
788
+ alias: refId,
789
+ ownerDoc,
790
+ ownerNode
791
+ };
792
+ const alias = this.allocAlias(refId, taken);
793
+ let group = this._synthImports.get(synthPath);
794
+ if (!group) {
795
+ group = [];
796
+ this._synthImports.set(synthPath, group);
797
+ }
798
+ group.push({
799
+ name: refId,
800
+ alias
801
+ });
802
+ return {
803
+ alias,
804
+ ownerDoc,
805
+ ownerNode
806
+ };
807
+ }
808
+ allocAlias(name, taken) {
809
+ if (!taken.has(name)) {
810
+ taken.add(name);
811
+ return name;
812
+ }
813
+ let n = 1;
814
+ while (taken.has(`${name}_${n}`)) n++;
815
+ const alias = `${name}_${n}`;
816
+ taken.add(alias);
817
+ return alias;
818
+ }
819
+ computeSynthPath(ownerDoc) {
820
+ if ((0, __atscript_core.isBareId)(ownerDoc.id)) return ownerDoc.id.slice("bare:".length, -".as".length);
821
+ if (!ownerDoc.id.startsWith("file://")) return undefined;
822
+ return (0, __atscript_core.getRelPath)(this.doc.id, ownerDoc.id);
823
+ }
698
824
  pre() {
699
825
  this.writeln("// prettier-ignore-start");
700
826
  this.writeln("/* eslint-disable */");
@@ -718,6 +844,11 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
718
844
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
719
845
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
720
846
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
847
+ this.computeSynthesizedImports();
848
+ for (const [synthPath, names] of this._synthImports) {
849
+ const list = names.map((n) => n.alias === n.name ? n.name : `${n.name} as ${n.alias}`).join(", ");
850
+ this.writeln(`import { ${list} } from "${this.transformFromPath(synthPath)}"`);
851
+ }
721
852
  }
722
853
  buildAdHocMap(annotateNodes) {
723
854
  const map = new Map();
@@ -996,7 +1127,7 @@ else handle.prop(prop.id, propHandle.$type);
996
1127
  const ref = node;
997
1128
  const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
998
1129
  if ((0, __atscript_core.isPrimitive)(decl)) {
999
- const ownerDecl$1 = this.doc.getDeclarationOwnerNode(ref.id);
1130
+ const ownerDecl$1 = this.resolveOwner(this.doc, ref.id);
1000
1131
  if (!ownerDecl$1?.node || ownerDecl$1.node.entity !== "type" && ownerDecl$1.node.entity !== "interface") {
1001
1132
  this.annotateType(decl, name);
1002
1133
  return this;
@@ -1011,9 +1142,14 @@ else handle.prop(prop.id, propHandle.$type);
1011
1142
  }
1012
1143
  }
1013
1144
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1014
- const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
1015
- const isImported = ownerDecl ? ownerDecl.doc !== this.doc : false;
1016
- const refExpr = isImported ? `() => ${ref.id}` : ref.id;
1145
+ const synth = this._refSynthInfo.get(ref);
1146
+ const ownerDecl = synth ? {
1147
+ doc: synth.ownerDoc,
1148
+ node: synth.ownerNode
1149
+ } : this.resolveOwner(this.doc, ref.id);
1150
+ const refName = synth?.alias ?? ref.id;
1151
+ const isImported = !!synth || (ownerDecl ? ownerDecl.doc !== this.doc : false);
1152
+ const refExpr = isImported ? `() => ${refName}` : refName;
1017
1153
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${refExpr}${chain})`);
1018
1154
  if (!ref.hasChain) {
1019
1155
  if (ownerDecl?.node) {
@@ -1428,7 +1564,7 @@ else {
1428
1564
  return [];
1429
1565
  }
1430
1566
  constructor(doc, opts) {
1431
- super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1567
+ super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), _define_property(this, "_unusedJs", void 0), _define_property(this, "_extendsRefs", void 0), _define_property(this, "_refSynthInfo", void 0), _define_property(this, "_synthImports", void 0), _define_property(this, "_synthComputed", void 0), _define_property(this, "_ownerCache", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map(), this._refSynthInfo = new Map(), this._synthImports = new Map(), this._synthComputed = false, this._ownerCache = new Map();
1432
1568
  }
1433
1569
  };
1434
1570
 
@@ -1,6 +1,6 @@
1
- import { buildJsonSchema, defineAnnotatedType } from "./json-schema-DrJMwvm1.mjs";
1
+ import { buildJsonSchema, defineAnnotatedType } from "./json-schema-D_wrIxzk.mjs";
2
2
  import path from "path";
3
- import { DEFAULT_FORMAT, flattenInterfaceNode, hasNavPropAnnotation, isArray, isConst, isGroup, isInterface, isPrimitive, isQueryLogical, isRef, isStructure } from "@atscript/core";
3
+ import { DEFAULT_FORMAT, flattenInterfaceNode, getRelPath, hasNavPropAnnotation, isArray, isBareId, isConst, isGroup, isInterface, isPrimitive, isProp, isQueryLogical, isRef, isStructure } from "@atscript/core";
4
4
 
5
5
  //#region packages/typescript/src/codegen/code-printer.ts
6
6
  function _define_property$3(obj, key, value) {
@@ -671,6 +671,132 @@ const QUERY_OP_MAP = {
671
671
  "not exists": "$exists"
672
672
  };
673
673
  var JsRenderer = class extends BaseRenderer {
674
+ /**
675
+ * Override base `unused` to subtract symbols that the JS output references
676
+ * transitively through `extends`-resolved parent prop trees. Without this,
677
+ * cross-file `extends` strips the imports needed by inherited `.refTo()` calls.
678
+ */ get unused() {
679
+ if (this._unusedJs) return this._unusedJs;
680
+ const base = new Set(this.doc.getUnusedTokens().map((t) => t.text));
681
+ for (const ref of this.getExtendsRefs().keys()) if (ref.id) base.delete(ref.id);
682
+ return this._unusedJs = base;
683
+ }
684
+ getExtendsRefs() {
685
+ if (this._extendsRefs) return this._extendsRefs;
686
+ const out = new Map();
687
+ const visitedInterfaces = new Set();
688
+ for (const node of this.doc.nodes) if (isInterface(node) && node.hasExtends) this.walkExtendsParents(node, this.doc, out, visitedInterfaces);
689
+ return this._extendsRefs = out;
690
+ }
691
+ walkRefs(node, visited, visit) {
692
+ if (!node || visited.has(node)) return;
693
+ visited.add(node);
694
+ if (isRef(node)) {
695
+ visit(node);
696
+ return;
697
+ }
698
+ if (isGroup(node)) {
699
+ for (const item of node.unwrap()) this.walkRefs(item, visited, visit);
700
+ return;
701
+ }
702
+ if (isArray(node) || isProp(node)) this.walkRefs(node.getDefinition(), visited, visit);
703
+ }
704
+ walkExtendsParents(iface, doc, out, visited) {
705
+ if (visited.has(iface)) return;
706
+ visited.add(iface);
707
+ const visitedNodes = new Set();
708
+ for (const token of iface.extendsTokens) {
709
+ const unwound = doc.unwindType(token.text);
710
+ if (!unwound?.def || !isInterface(unwound.def)) continue;
711
+ const parentInterface = unwound.def;
712
+ const parentDoc = unwound.doc;
713
+ this.walkRefs(parentInterface.getDefinition(), visitedNodes, (ref) => {
714
+ if (!out.has(ref)) out.set(ref, parentDoc);
715
+ });
716
+ if (parentInterface.hasExtends) this.walkExtendsParents(parentInterface, parentDoc, out, visited);
717
+ }
718
+ }
719
+ computeSynthesizedImports() {
720
+ if (this._synthComputed) return;
721
+ this._synthComputed = true;
722
+ const refToOriginDoc = this.getExtendsRefs();
723
+ if (refToOriginDoc.size === 0) return;
724
+ const taken = new Set(this.doc.registry.definitions.keys());
725
+ const allocated = new Map();
726
+ const synthPathCache = new Map();
727
+ for (const [refNode, originDoc] of refToOriginDoc) {
728
+ const refId = refNode.id;
729
+ if (!refId) continue;
730
+ const ownerInfo = this.resolveOwner(originDoc, refId);
731
+ if (!ownerInfo?.doc || ownerInfo.doc === this.doc) continue;
732
+ if (ownerInfo.node && isPrimitive(ownerInfo.node)) continue;
733
+ const ownerDoc = ownerInfo.doc;
734
+ let synthPath;
735
+ if (synthPathCache.has(ownerDoc)) synthPath = synthPathCache.get(ownerDoc);
736
+ else {
737
+ synthPath = this.computeSynthPath(ownerDoc);
738
+ synthPathCache.set(ownerDoc, synthPath);
739
+ }
740
+ if (!synthPath) continue;
741
+ const allocKey = `${synthPath}::${refId}`;
742
+ let info = allocated.get(allocKey);
743
+ if (!info) {
744
+ info = this.allocateSynthInfo(refId, synthPath, ownerInfo, taken);
745
+ allocated.set(allocKey, info);
746
+ }
747
+ this._refSynthInfo.set(refNode, info);
748
+ }
749
+ }
750
+ resolveOwner(fromDoc, refId) {
751
+ const key = `${fromDoc.id}::${refId}`;
752
+ let v = this._ownerCache.get(key);
753
+ if (v === undefined && !this._ownerCache.has(key)) {
754
+ v = fromDoc.getDeclarationOwnerNode(refId);
755
+ this._ownerCache.set(key, v);
756
+ }
757
+ return v;
758
+ }
759
+ allocateSynthInfo(refId, synthPath, ownerInfo, taken) {
760
+ const ownerDoc = ownerInfo.doc;
761
+ const ownerNode = ownerInfo.node;
762
+ const existing = this.doc.importedDefs.get(refId);
763
+ if (existing?.text === synthPath) return {
764
+ alias: refId,
765
+ ownerDoc,
766
+ ownerNode
767
+ };
768
+ const alias = this.allocAlias(refId, taken);
769
+ let group = this._synthImports.get(synthPath);
770
+ if (!group) {
771
+ group = [];
772
+ this._synthImports.set(synthPath, group);
773
+ }
774
+ group.push({
775
+ name: refId,
776
+ alias
777
+ });
778
+ return {
779
+ alias,
780
+ ownerDoc,
781
+ ownerNode
782
+ };
783
+ }
784
+ allocAlias(name, taken) {
785
+ if (!taken.has(name)) {
786
+ taken.add(name);
787
+ return name;
788
+ }
789
+ let n = 1;
790
+ while (taken.has(`${name}_${n}`)) n++;
791
+ const alias = `${name}_${n}`;
792
+ taken.add(alias);
793
+ return alias;
794
+ }
795
+ computeSynthPath(ownerDoc) {
796
+ if (isBareId(ownerDoc.id)) return ownerDoc.id.slice("bare:".length, -".as".length);
797
+ if (!ownerDoc.id.startsWith("file://")) return undefined;
798
+ return getRelPath(this.doc.id, ownerDoc.id);
799
+ }
674
800
  pre() {
675
801
  this.writeln("// prettier-ignore-start");
676
802
  this.writeln("/* eslint-disable */");
@@ -694,6 +820,11 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
694
820
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
695
821
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
696
822
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
823
+ this.computeSynthesizedImports();
824
+ for (const [synthPath, names] of this._synthImports) {
825
+ const list = names.map((n) => n.alias === n.name ? n.name : `${n.name} as ${n.alias}`).join(", ");
826
+ this.writeln(`import { ${list} } from "${this.transformFromPath(synthPath)}"`);
827
+ }
697
828
  }
698
829
  buildAdHocMap(annotateNodes) {
699
830
  const map = new Map();
@@ -972,7 +1103,7 @@ else handle.prop(prop.id, propHandle.$type);
972
1103
  const ref = node;
973
1104
  const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
974
1105
  if (isPrimitive(decl)) {
975
- const ownerDecl$1 = this.doc.getDeclarationOwnerNode(ref.id);
1106
+ const ownerDecl$1 = this.resolveOwner(this.doc, ref.id);
976
1107
  if (!ownerDecl$1?.node || ownerDecl$1.node.entity !== "type" && ownerDecl$1.node.entity !== "interface") {
977
1108
  this.annotateType(decl, name);
978
1109
  return this;
@@ -987,9 +1118,14 @@ else handle.prop(prop.id, propHandle.$type);
987
1118
  }
988
1119
  }
989
1120
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
990
- const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
991
- const isImported = ownerDecl ? ownerDecl.doc !== this.doc : false;
992
- const refExpr = isImported ? `() => ${ref.id}` : ref.id;
1121
+ const synth = this._refSynthInfo.get(ref);
1122
+ const ownerDecl = synth ? {
1123
+ doc: synth.ownerDoc,
1124
+ node: synth.ownerNode
1125
+ } : this.resolveOwner(this.doc, ref.id);
1126
+ const refName = synth?.alias ?? ref.id;
1127
+ const isImported = !!synth || (ownerDecl ? ownerDecl.doc !== this.doc : false);
1128
+ const refExpr = isImported ? `() => ${refName}` : refName;
993
1129
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${refExpr}${chain})`);
994
1130
  if (!ref.hasChain) {
995
1131
  if (ownerDecl?.node) {
@@ -1404,7 +1540,7 @@ else {
1404
1540
  return [];
1405
1541
  }
1406
1542
  constructor(doc, opts) {
1407
- super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1543
+ super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), _define_property(this, "_unusedJs", void 0), _define_property(this, "_extendsRefs", void 0), _define_property(this, "_refSynthInfo", void 0), _define_property(this, "_synthImports", void 0), _define_property(this, "_synthComputed", void 0), _define_property(this, "_ownerCache", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map(), this._refSynthInfo = new Map(), this._synthImports = new Map(), this._synthComputed = false, this._ownerCache = new Map();
1408
1544
  }
1409
1545
  };
1410
1546
 
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
- const require_plugin = require('./plugin-cY7VLpmN.cjs');
3
- require('./json-schema-BgW_S2sP.cjs');
2
+ const require_plugin = require('./plugin-C90RBCnx.cjs');
3
+ require('./json-schema-Zi4TJzJJ.cjs');
4
4
  const path = require_plugin.__toESM(require("path"));
5
5
  const __atscript_core = require_plugin.__toESM(require("@atscript/core"));
6
6
  const fs = require_plugin.__toESM(require("fs"));
@@ -1,5 +1,5 @@
1
- import { tsPlugin } from "./plugin-D96fH7Va.mjs";
2
- import "./json-schema-DrJMwvm1.mjs";
1
+ import { tsPlugin } from "./plugin-PADgal82.mjs";
2
+ import "./json-schema-D_wrIxzk.mjs";
3
3
  import path from "path";
4
4
  import { build } from "@atscript/core";
5
5
  import { writeFileSync } from "fs";
package/dist/utils.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const require_json_schema = require('./json-schema-BgW_S2sP.cjs');
2
+ const require_json_schema = require('./json-schema-Zi4TJzJJ.cjs');
3
3
 
4
4
  //#region packages/typescript/src/runtime/default-value.ts
5
5
  /**
@@ -471,6 +471,7 @@ exports.createAnnotatedTypeNode = require_json_schema.createAnnotatedTypeNode
471
471
  exports.createDataFromAnnotatedType = createDataFromAnnotatedType
472
472
  exports.defineAnnotatedType = require_json_schema.defineAnnotatedType
473
473
  exports.deserializeAnnotatedType = deserializeAnnotatedType
474
+ exports.detectDiscriminator = require_json_schema.detectDiscriminator
474
475
  exports.flattenAnnotatedType = flattenAnnotatedType
475
476
  exports.forAnnotatedType = require_json_schema.forAnnotatedType
476
477
  exports.fromJsonSchema = require_json_schema.fromJsonSchema
package/dist/utils.d.ts CHANGED
@@ -292,6 +292,28 @@ declare function isAnnotatedTypeOfPrimitive(t: TAtscriptAnnotatedType): boolean;
292
292
 
293
293
  /** A JSON Schema object (draft-compatible). */
294
294
  type TJsonSchema = Record<string, any>;
295
+ /**
296
+ * Describes a discriminator property shared by all variants of a union.
297
+ *
298
+ * Returned by {@link detectDiscriminator} when a union qualifies as a
299
+ * discriminated (a.k.a. tagged) union — every variant is an object that
300
+ * carries the same literal-valued property with a distinct value.
301
+ */
302
+ type TUnionDiscriminator = {
303
+ /** Name of the discriminator property shared by all variants. */
304
+ propertyName: string;
305
+ /** Maps the literal value of `propertyName` → variant index in `items`. */
306
+ indexMapping: Record<string, number>;
307
+ };
308
+ /**
309
+ * Detects a discriminator property across union items.
310
+ *
311
+ * Scans all items for object-typed members that share a common property
312
+ * with distinct const/literal values. If exactly one such property exists,
313
+ * it is returned as the discriminator. Returns `null` when no qualifying
314
+ * property exists, or when more than one does (ambiguous).
315
+ */
316
+ declare function detectDiscriminator(items: TAtscriptAnnotatedType[]): TUnionDiscriminator | null;
295
317
  /**
296
318
  * Builds a JSON Schema from an {@link TAtscriptAnnotatedType}.
297
319
  *
@@ -644,5 +666,5 @@ type NavPropsOf<T> = T extends {
644
666
  __navProps: infer N extends Record<string, unknown>;
645
667
  } ? N : Record<string, never>;
646
668
 
647
- export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
648
- export type { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryNode, AtscriptRef, FlatOf, InferDataType, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
669
+ export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
670
+ export type { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryNode, AtscriptRef, FlatOf, InferDataType, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TUnionDiscriminator, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
package/dist/utils.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas } from "./json-schema-DrJMwvm1.mjs";
1
+ import { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, detectDiscriminator, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas } from "./json-schema-D_wrIxzk.mjs";
2
2
 
3
3
  //#region packages/typescript/src/runtime/default-value.ts
4
4
  /**
@@ -460,4 +460,4 @@ function deserializeTypeDef(t, resolved) {
460
460
  }
461
461
 
462
462
  //#endregion
463
- export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
463
+ export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/typescript",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "Atscript: typescript-gen support.",
5
5
  "keywords": [
6
6
  "annotations",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@atscript/db": "^0.1.48",
76
- "@atscript/core": "^0.1.51"
76
+ "@atscript/core": "^0.1.53"
77
77
  },
78
78
  "peerDependenciesMeta": {
79
79
  "@atscript/db": {