@atscript/typescript 0.1.32 → 0.1.34

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/index.mjs CHANGED
@@ -455,28 +455,72 @@ else {
455
455
  *
456
456
  * - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
457
457
  * - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
458
- * - **No PK** → no `__pk` emitted
458
+ * - **No PK** → no `__pk` emitted (unless unique indexes exist)
459
+ * - **Unique indexes** (`@db.index.unique`) → appended as union members
460
+ * - **Mongo collection** → always includes `string` (ObjectId) in the union;
461
+ * if no `@meta.id` fields, `__pk` is just `string`
459
462
  */ renderPk(node) {
463
+ const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
460
464
  let struct;
461
465
  if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
462
466
  if (!struct) struct = node.getDefinition();
463
467
  if (!struct || !isStructure(struct)) return;
468
+ const structNode = struct;
464
469
  const pkProps = [];
465
- for (const [name, prop] of struct.props) {
470
+ const uniqueByIndex = new Map();
471
+ for (const [name, prop] of structNode.props) {
466
472
  if (prop.token("identifier")?.pattern) continue;
473
+ if (isMongoCollection && name === "_id") continue;
467
474
  if (prop.countAnnotations("meta.id") > 0) pkProps.push({
468
475
  name,
469
476
  prop
470
477
  });
478
+ if (prop.annotations) {
479
+ for (const ann of prop.annotations) if (ann.name === "db.index.unique") {
480
+ const indexName = ann.args[0]?.text ?? name;
481
+ let group = uniqueByIndex.get(indexName);
482
+ if (!group) {
483
+ group = [];
484
+ uniqueByIndex.set(indexName, group);
485
+ }
486
+ group.push({
487
+ name,
488
+ prop
489
+ });
490
+ }
491
+ }
492
+ }
493
+ const uniqueProps = [];
494
+ const pkNames = new Set(pkProps.map((p) => p.name));
495
+ for (const fields of uniqueByIndex.values()) if (fields.length === 1 && !pkNames.has(fields[0].name)) uniqueProps.push(fields[0]);
496
+ if (pkProps.length === 0 && uniqueProps.length === 0 && !isMongoCollection) return;
497
+ let mongoIdType;
498
+ if (isMongoCollection) {
499
+ const idProp = structNode.props.get("_id");
500
+ if (idProp) mongoIdType = this.renderTypeDefString(idProp.getDefinition()).trim();
501
+ mongoIdType ?? (mongoIdType = "string");
502
+ }
503
+ const uniqueTypes = [];
504
+ const seenTypes = new Set();
505
+ for (const { prop } of uniqueProps) {
506
+ const rendered = this.renderTypeDefString(prop.getDefinition()).trim();
507
+ if (!seenTypes.has(rendered)) {
508
+ seenTypes.add(rendered);
509
+ uniqueTypes.push(rendered);
510
+ }
471
511
  }
472
- if (pkProps.length === 0) return;
473
512
  this.writeln();
474
- if (pkProps.length === 1) {
513
+ const uniqueSuffix = uniqueTypes.length > 0 ? ` | ${uniqueTypes.join(" | ")}` : "";
514
+ if (pkProps.length === 0 && !isMongoCollection) this.writeln(`static __pk: ${uniqueTypes.join(" | ")}`);
515
+ else if (pkProps.length === 0) this.writeln(`static __pk: ${mongoIdType}${uniqueSuffix}`);
516
+ else if (pkProps.length === 1) {
475
517
  this.write("static __pk: ");
476
- const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition());
477
- renderedDef.split("\n").forEach((l) => this.writeln(l));
518
+ if (isMongoCollection) this.write(`${mongoIdType} | `);
519
+ const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition()).trim();
520
+ this.writeln(`${renderedDef}${uniqueSuffix}`);
478
521
  } else {
479
522
  this.write("static __pk: ");
523
+ if (isMongoCollection) this.write(`${mongoIdType} | `);
480
524
  this.blockln("{}");
481
525
  for (const { name, prop } of pkProps) {
482
526
  this.write(wrapProp(name), ": ");
@@ -484,6 +528,7 @@ else {
484
528
  renderedDef.split("\n").forEach((l) => this.writeln(l));
485
529
  }
486
530
  this.pop();
531
+ this.writeln(uniqueSuffix);
487
532
  }
488
533
  }
489
534
  phantomPropType(def) {
@@ -971,6 +1016,25 @@ var ValidatorError = class extends Error {
971
1016
 
972
1017
  //#endregion
973
1018
  //#region packages/typescript/src/annotated-type.ts
1019
+ const COMPLEX_KINDS = new Set([
1020
+ "union",
1021
+ "intersection",
1022
+ "tuple"
1023
+ ]);
1024
+ /** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
1025
+ return new Validator(this, opts);
1026
+ }
1027
+ function createAnnotatedTypeNode(type, metadata, opts) {
1028
+ return {
1029
+ __is_atscript_annotated_type: true,
1030
+ type,
1031
+ metadata,
1032
+ validator: validatorMethod,
1033
+ id: opts?.id,
1034
+ optional: opts?.optional,
1035
+ ref: opts?.ref
1036
+ };
1037
+ }
974
1038
  function isAnnotatedType(type) {
975
1039
  return type && type.__is_atscript_annotated_type;
976
1040
  }
@@ -987,38 +1051,24 @@ function defineAnnotatedType(_kind, base) {
987
1051
  const kind = _kind || "";
988
1052
  const type = base?.type || {};
989
1053
  type.kind = kind;
990
- if ([
991
- "union",
992
- "intersection",
993
- "tuple"
994
- ].includes(kind)) type.items = [];
1054
+ if (COMPLEX_KINDS.has(kind)) type.items = [];
995
1055
  if (kind === "object") {
996
1056
  type.props = new Map();
997
1057
  type.propsPatterns = [];
998
1058
  }
999
1059
  type.tags = new Set();
1000
1060
  const metadata = base?.metadata || new Map();
1001
- if (base) Object.assign(base, {
1061
+ const payload = {
1002
1062
  __is_atscript_annotated_type: true,
1003
1063
  metadata,
1004
1064
  type,
1005
- validator(opts) {
1006
- return new Validator(this, opts);
1007
- }
1008
- });
1009
- else base = {
1010
- __is_atscript_annotated_type: true,
1011
- metadata,
1012
- type,
1013
- validator(opts) {
1014
- return new Validator(this, opts);
1015
- }
1065
+ validator: validatorMethod
1016
1066
  };
1067
+ base = base ? Object.assign(base, payload) : payload;
1017
1068
  const handle = {
1018
1069
  $type: base,
1019
1070
  $def: type,
1020
1071
  $metadata: metadata,
1021
- _existingObject: undefined,
1022
1072
  tags(...tags) {
1023
1073
  for (const tag of tags) this.$def.tags.add(tag);
1024
1074
  return this;
@@ -1059,26 +1109,56 @@ else base = {
1059
1109
  return this;
1060
1110
  },
1061
1111
  refTo(type$1, chain) {
1062
- let newBase = type$1;
1063
- const typeName = type$1.name || "Unknown";
1064
- if (isAnnotatedType(newBase)) {
1065
- let keys = "";
1066
- for (const c of chain || []) {
1067
- keys += `["${c}"]`;
1112
+ if (isAnnotatedType(type$1)) {
1113
+ let newBase = type$1;
1114
+ const typeName = type$1.name || "Unknown";
1115
+ if (chain) for (let i = 0; i < chain.length; i++) {
1116
+ const c = chain[i];
1068
1117
  if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
1069
- else throw new Error(`Can't find prop ${typeName}${keys}`);
1118
+ else {
1119
+ const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
1120
+ throw new Error(`Can't find prop ${typeName}${keys}`);
1121
+ }
1070
1122
  }
1071
- if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
1072
- else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
1073
- this.$type = {
1074
- __is_atscript_annotated_type: true,
1075
- type: newBase.type,
1076
- metadata,
1123
+ this.$type = createAnnotatedTypeNode(newBase.type, metadata, {
1077
1124
  id: newBase.id,
1078
- validator(opts) {
1079
- return new Validator(this, opts);
1080
- }
1081
- };
1125
+ ref: chain && chain.length > 0 ? {
1126
+ type: () => type$1,
1127
+ field: chain.join(".")
1128
+ } : undefined
1129
+ });
1130
+ } else if (typeof type$1 === "function") {
1131
+ const lazyType = type$1;
1132
+ this.$type = createAnnotatedTypeNode({ kind: "" }, metadata, { ref: {
1133
+ type: lazyType,
1134
+ field: chain ? chain.join(".") : ""
1135
+ } });
1136
+ const node = this.$type;
1137
+ const placeholder = node.type;
1138
+ Object.defineProperty(node, "type", {
1139
+ get() {
1140
+ const t = lazyType();
1141
+ if (!isAnnotatedType(t)) {
1142
+ Object.defineProperty(node, "type", {
1143
+ value: placeholder,
1144
+ writable: false,
1145
+ configurable: true
1146
+ });
1147
+ return placeholder;
1148
+ }
1149
+ let target = t;
1150
+ if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
1151
+ else return t.type;
1152
+ node.id = target.id || t.id;
1153
+ Object.defineProperty(node, "type", {
1154
+ value: target.type,
1155
+ writable: false,
1156
+ configurable: true
1157
+ });
1158
+ return target.type;
1159
+ },
1160
+ configurable: true
1161
+ });
1082
1162
  } else throw new Error(`${type$1} is not annotated type`);
1083
1163
  return this;
1084
1164
  },
@@ -1129,10 +1209,10 @@ function forAnnotatedType(def, handlers) {
1129
1209
  const firstObj = items[0].type;
1130
1210
  const candidates = [];
1131
1211
  for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
1132
- const validCandidates = [];
1212
+ let result = null;
1133
1213
  for (const candidate of candidates) {
1134
1214
  const values = new Set();
1135
- const mapping = {};
1215
+ const indexMapping = {};
1136
1216
  let valid = true;
1137
1217
  for (let i = 0; i < items.length; i++) {
1138
1218
  const obj = items[i].type;
@@ -1147,19 +1227,21 @@ function forAnnotatedType(def, handlers) {
1147
1227
  break;
1148
1228
  }
1149
1229
  values.add(val);
1150
- mapping[String(val)] = `#/oneOf/${i}`;
1230
+ indexMapping[String(val)] = i;
1231
+ }
1232
+ if (valid) {
1233
+ if (result) return null;
1234
+ result = {
1235
+ propertyName: candidate,
1236
+ indexMapping
1237
+ };
1151
1238
  }
1152
- if (valid) validCandidates.push({
1153
- propertyName: candidate,
1154
- mapping
1155
- });
1156
1239
  }
1157
- if (validCandidates.length === 1) return validCandidates[0];
1158
- return null;
1240
+ return result;
1159
1241
  }
1160
1242
  function buildJsonSchema(type) {
1161
1243
  const defs = {};
1162
- let isRoot = true;
1244
+ let hasDefs = false;
1163
1245
  const buildObject = (d) => {
1164
1246
  const properties = {};
1165
1247
  const required = [];
@@ -1176,15 +1258,15 @@ function buildJsonSchema(type) {
1176
1258
  return schema$1;
1177
1259
  };
1178
1260
  const build = (def) => {
1179
- if (def.id && def.type.kind === "object" && !isRoot) {
1261
+ if (def.id && def.type.kind === "object" && def !== type) {
1180
1262
  const name = def.id;
1181
1263
  if (!defs[name]) {
1264
+ hasDefs = true;
1182
1265
  defs[name] = {};
1183
1266
  defs[name] = buildObject(def);
1184
1267
  }
1185
1268
  return { $ref: `#/$defs/${name}` };
1186
1269
  }
1187
- isRoot = false;
1188
1270
  const meta = def.metadata;
1189
1271
  return forAnnotatedType(def, {
1190
1272
  phantom() {
@@ -1209,11 +1291,9 @@ function buildJsonSchema(type) {
1209
1291
  if (disc) {
1210
1292
  const oneOf = d.type.items.map(build);
1211
1293
  const mapping = {};
1212
- for (const [val, origPath] of Object.entries(disc.mapping)) {
1213
- const idx = Number.parseInt(origPath.split("/").pop(), 10);
1294
+ for (const [val, idx] of Object.entries(disc.indexMapping)) {
1214
1295
  const item = d.type.items[idx];
1215
- if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1216
- else mapping[val] = origPath;
1296
+ mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
1217
1297
  }
1218
1298
  return {
1219
1299
  oneOf,
@@ -1263,7 +1343,7 @@ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ patte
1263
1343
  });
1264
1344
  };
1265
1345
  const schema = build(type);
1266
- if (Object.keys(defs).length > 0) return {
1346
+ if (hasDefs) return {
1267
1347
  ...schema,
1268
1348
  $defs: defs
1269
1349
  };
@@ -1564,8 +1644,8 @@ else handle.prop(prop.id, propHandle.$type);
1564
1644
  const ref = node;
1565
1645
  const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
1566
1646
  if (isPrimitive(decl)) {
1567
- const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
1568
- if (!ownerDecl?.node || ownerDecl.node.entity !== "type" && ownerDecl.node.entity !== "interface") {
1647
+ const ownerDecl$1 = this.doc.getDeclarationOwnerNode(ref.id);
1648
+ if (!ownerDecl$1?.node || ownerDecl$1.node.entity !== "type" && ownerDecl$1.node.entity !== "interface") {
1569
1649
  this.annotateType(decl, name);
1570
1650
  return this;
1571
1651
  }
@@ -1579,9 +1659,11 @@ else handle.prop(prop.id, propHandle.$type);
1579
1659
  }
1580
1660
  }
1581
1661
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1582
- this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1662
+ const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
1663
+ const isImported = ownerDecl ? ownerDecl.doc !== this.doc : false;
1664
+ const refExpr = isImported ? `() => ${ref.id}` : ref.id;
1665
+ this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${refExpr}${chain})`);
1583
1666
  if (!ref.hasChain) {
1584
- const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
1585
1667
  if (ownerDecl?.node) {
1586
1668
  const typeAnnotations = ownerDecl.doc.evalAnnotationsForNode(ownerDecl.node);
1587
1669
  typeAnnotations?.forEach((an) => {