@atscript/db 0.1.135 → 0.1.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{agg-DKuf_v2L.d.cts → agg-CV7y8nC6.d.cts} +13 -3
  2. package/dist/{agg-BtlGeRfj.d.mts → agg-D5DHsAby.d.mts} +13 -3
  3. package/dist/agg.cjs +35 -3
  4. package/dist/agg.d.cts +2 -2
  5. package/dist/agg.d.mts +2 -2
  6. package/dist/agg.mjs +34 -1
  7. package/dist/aggregate-fns-CGBv3E8S.cjs +87 -0
  8. package/dist/aggregate-fns-CfsveE1w.mjs +58 -0
  9. package/dist/{buckets-GruoVxH5.d.mts → buckets-BFG2RYRW.d.mts} +662 -31
  10. package/dist/{buckets-D6PBXKRJ.d.cts → buckets-C-27xmtq.d.cts} +662 -31
  11. package/dist/column-diff-BmqvgBWw.d.cts +24 -0
  12. package/dist/{db-view-Doh8vPg3.mjs → column-diff-BwOA5101.mjs} +795 -125
  13. package/dist/{db-view-Dm7MpUGB.cjs → column-diff-CgxgFKzx.cjs} +873 -125
  14. package/dist/column-diff-DPkbZIVE.d.mts +24 -0
  15. package/dist/index.cjs +79 -55
  16. package/dist/index.d.cts +12 -10
  17. package/dist/index.d.mts +12 -10
  18. package/dist/index.mjs +30 -11
  19. package/dist/{nested-writer-wk1EFUNY.cjs → nested-writer-BZNCuqI6.cjs} +16 -2
  20. package/dist/{nested-writer-CqL24ojl.mjs → nested-writer-FWD5oOYh.mjs} +11 -3
  21. package/dist/plugin.cjs +217 -77
  22. package/dist/plugin.mjs +217 -78
  23. package/dist/rel.cjs +2 -2
  24. package/dist/rel.d.cts +2 -20
  25. package/dist/rel.d.mts +2 -20
  26. package/dist/rel.mjs +2 -2
  27. package/dist/relation-helpers-D3Zu0Mta.d.mts +30 -0
  28. package/dist/relation-helpers-DxrvS6ar.d.cts +30 -0
  29. package/dist/{relation-loader-BD4xANQJ.cjs → relation-loader-6ZB_5KFq.cjs} +1 -1
  30. package/dist/{relation-loader-B68R1LET.mjs → relation-loader-CTFaZpVa.mjs} +1 -1
  31. package/dist/shared.cjs +5 -1
  32. package/dist/shared.d.cts +16 -3
  33. package/dist/shared.d.mts +16 -3
  34. package/dist/shared.mjs +2 -2
  35. package/dist/sync.cjs +38 -321
  36. package/dist/sync.d.cts +29 -17
  37. package/dist/sync.d.mts +29 -17
  38. package/dist/sync.mjs +6 -289
  39. package/dist/{validation-utils-MWOP1Ts4.mjs → validation-utils-B4h-GW4d.mjs} +29 -7
  40. package/dist/{validation-utils-B7SXPkm7.cjs → validation-utils-Dg0hW6dn.cjs} +52 -6
  41. package/dist/{validator-CewfnGZj.d.mts → validator-Drb2N-YL.d.cts} +1 -1
  42. package/dist/{validator-CewfnGZj.d.cts → validator-Drb2N-YL.d.mts} +1 -1
  43. package/dist/validator.d.cts +1 -1
  44. package/dist/validator.d.mts +1 -1
  45. package/package.json +1 -1
  46. package/dist/agg-CvXDGnKi.mjs +0 -61
  47. package/dist/agg-EcIbAFnJ.cjs +0 -78
  48. package/dist/db-space-9CH5neN7.d.mts +0 -439
  49. package/dist/db-space-BdtBNTeH.d.cts +0 -439
package/dist/plugin.mjs CHANGED
@@ -1,8 +1,9 @@
1
+ import { i as SUPPORTED_AGGREGATE_FNS, r as NULL_WHEN_EMPTY_AGGREGATE_FNS } from "./aggregate-fns-CfsveE1w.mjs";
1
2
  import "./consts-C_-5_pFq.mjs";
2
- import { a as getAnnotationAlias, c as getParentStruct, d as validateExclusiveWith, f as validateFieldBaseType, i as validateRefArgument, l as getParentTypeName, n as hasAnyViewAnnotation, o as getDbTableOwner, p as validateSiblingStringField, r as validateQueryScope, s as getNavTargetTypeName, t as findFKFieldsPointingTo, u as refActionAnnotation } from "./validation-utils-MWOP1Ts4.mjs";
3
+ import { _ as validateSiblingStringField, a as validateRefArgument, c as getAnnotationAlias, d as getParentStruct, f as getParentTypeName, g as validateFieldBaseType, h as validateExclusiveWith, i as validateQueryScope, l as getDbTableOwner, m as refActionAnnotation, n as hasAnyViewAnnotation, o as viewJoins, p as primitiveBaseType, r as joinTargets, s as viewScopeTypes, t as findFKFieldsPointingTo, u as getNavTargetTypeName } from "./validation-utils-B4h-GW4d.mjs";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
- import { AnnotationSpec, DEFAULT_FORMAT, isArray, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
6
+ import { AnnotationSpec, DEFAULT_FORMAT, isArray, isInterface, isPrimitive, isProp, isRef, isStructure } from "@atscript/core";
6
7
  //#region src/plugin/manifest.ts
7
8
  const DB_ENTITY_ANNOTATIONS = [
8
9
  "db.table",
@@ -95,67 +96,98 @@ async function generateModelManifest(options, output, format, repo) {
95
96
  }
96
97
  //#endregion
97
98
  //#region src/plugin/annotations/agg.ts
98
- const dbAggAnnotations = { agg: {
99
- sum: new AnnotationSpec({
100
- description: "Declares a view field as SUM of a source column.",
101
- nodeType: ["prop"],
102
- passedWhenReferred: false,
103
- argument: {
104
- name: "field",
105
- type: "string",
106
- description: "Source column name to sum."
107
- },
108
- validate(token, _args, doc) {
109
- return validateFieldBaseType(token, doc, "@db.agg.sum", ["number", "decimal"]);
110
- }
111
- }),
112
- avg: new AnnotationSpec({
113
- description: "Declares a view field as AVG of a source column.",
114
- nodeType: ["prop"],
115
- passedWhenReferred: false,
116
- argument: {
117
- name: "field",
118
- type: "string",
119
- description: "Source column name to average."
120
- },
121
- validate(token, _args, doc) {
122
- return validateFieldBaseType(token, doc, "@db.agg.avg", ["number", "decimal"]);
123
- }
124
- }),
125
- count: new AnnotationSpec({
126
- description: "Declares a view field as COUNT. Without argument: COUNT(*). With field name argument: COUNT(field) (non-null count).",
127
- nodeType: ["prop"],
128
- passedWhenReferred: false,
129
- argument: {
130
- name: "field",
131
- type: "string",
132
- optional: true,
133
- description: "Source column name to count non-null values. Omit for COUNT(*)."
134
- },
135
- validate(token, _args, doc) {
136
- return validateFieldBaseType(token, doc, "@db.agg.count", ["number"]);
137
- }
138
- }),
139
- min: new AnnotationSpec({
140
- description: "Declares a view field as MIN of a source column.",
141
- nodeType: ["prop"],
142
- passedWhenReferred: false,
143
- argument: {
144
- name: "field",
145
- type: "string",
146
- description: "Source column name."
147
- }
148
- }),
149
- max: new AnnotationSpec({
150
- description: "Declares a view field as MAX of a source column.",
99
+ /**
100
+ * A conditional aggregate that is NULL when no row matches (so its field
101
+ * must be optional): every NULL-when-empty function but `sum`, whose
102
+ * conditional form is `COALESCE(…, 0)`.
103
+ */
104
+ function conditionalIsNullable(name) {
105
+ return name !== "sum" && NULL_WHEN_EMPTY_AGGREGATE_FNS.has(name);
106
+ }
107
+ /** The optional 2nd argument every `@db.agg.*` takes. */
108
+ const CONDITION_ARG = {
109
+ name: "condition",
110
+ type: "query",
111
+ optional: true,
112
+ description: "Row predicate of a conditional aggregate: only rows where it holds are aggregated (SQL `FN(CASE WHEN … THEN field END)`). May reference the entry table and every join; unqualified fields resolve to the entry table."
113
+ };
114
+ /**
115
+ * The rules every `@db.agg.*` shares: `'*'` is `count`'s only, and a
116
+ * condition (2nd argument) must stay within the view's tables (entry + every
117
+ * join, like `@db.view.filter`) and — for the aggregates that are NULL when
118
+ * no row matches (avg / min / max) — sit on an optional field.
119
+ */
120
+ function validateAggArgs(name, token, args, doc) {
121
+ const errors = [];
122
+ const annotation = `@db.agg.${name}`;
123
+ if (args[0]?.text === "*" && name !== "count") errors.push({
124
+ message: `${annotation} needs a field — only @db.agg.count accepts '*'`,
125
+ severity: 1,
126
+ range: args[0].range
127
+ });
128
+ const condition = args[1];
129
+ if (!condition?.queryNode) return errors;
130
+ const owner = getDbTableOwner(token);
131
+ const entryTypeName = owner ? getAnnotationAlias(owner, "db.view.for") : void 0;
132
+ if (!owner || !entryTypeName) {
133
+ errors.push({
134
+ message: `A conditional ${annotation} requires @db.view.for on the view`,
135
+ severity: 1,
136
+ range: condition.range
137
+ });
138
+ return errors;
139
+ }
140
+ errors.push(...validateQueryScope(condition, viewScopeTypes(owner), entryTypeName, doc));
141
+ const prop = token.parentNode;
142
+ if (conditionalIsNullable(name) && prop && !prop.has("optional")) {
143
+ const field = prop.id ?? "field";
144
+ errors.push({
145
+ message: `Field "${field}" has a conditional ${annotation} and must be optional (${field}?: …) — it is NULL when no row matches`,
146
+ severity: 1,
147
+ range: token.range
148
+ });
149
+ }
150
+ return errors;
151
+ }
152
+ const CONDITIONAL = " An optional 2nd argument (a query) makes it conditional: only rows where it holds are aggregated.";
153
+ /** What a conditional `name` yields when no row matches (from `NULL_WHEN_EMPTY_AGGREGATE_FNS`). */
154
+ function conditionalNote(name) {
155
+ if (!NULL_WHEN_EMPTY_AGGREGATE_FNS.has(name)) return "";
156
+ const fn = name.toUpperCase();
157
+ return conditionalIsNullable(name) ? ` A conditional ${fn} is NULL when no row matches, so its field must be optional.` : ` A conditional ${fn} is 0 (not NULL) when no row matches.`;
158
+ }
159
+ /**
160
+ * One `@db.agg.<name>` spec: `[field, condition?]` (field optional for count
161
+ * only). The description gets the conditional-form sentences appended.
162
+ */
163
+ function aggSpec(name, description, field, types, extra = "") {
164
+ return new AnnotationSpec({
165
+ description: description + CONDITIONAL + conditionalNote(name) + extra,
151
166
  nodeType: ["prop"],
152
167
  passedWhenReferred: false,
153
- argument: {
168
+ argument: [{
154
169
  name: "field",
155
170
  type: "string",
156
- description: "Source column name."
171
+ optional: field.optional,
172
+ description: field.description
173
+ }, CONDITION_ARG],
174
+ validate(token, args, doc) {
175
+ const errors = validateAggArgs(name, token, args, doc);
176
+ if (types) errors.push(...validateFieldBaseType(token, doc, `@db.agg.${name}`, types));
177
+ return errors;
157
178
  }
158
- })
179
+ });
180
+ }
181
+ const dbAggAnnotations = { agg: {
182
+ sum: aggSpec("sum", "Declares a view field as SUM of a source column.", { description: "Source column name to sum." }, ["number", "decimal"]),
183
+ avg: aggSpec("avg", "Declares a view field as AVG of a source column.", { description: "Source column name to average." }, ["number", "decimal"]),
184
+ count: aggSpec("count", "Declares a view field as COUNT. Without argument (or with `'*'`): COUNT(*). With field name argument: COUNT(field) (non-null count).", {
185
+ optional: true,
186
+ description: "Source column name to count non-null values. Omit (or `'*'`) for COUNT(*)."
187
+ }, ["number"], " A conditional COUNT(*) is spelled `@db.agg.count '*', <query>`."),
188
+ countDistinct: aggSpec("countDistinct", "Declares a view field as COUNT(DISTINCT field): the number of distinct non-null values of a source column.", { description: "Source column name whose distinct non-null values are counted." }, ["number"]),
189
+ min: aggSpec("min", "Declares a view field as MIN of a source column.", { description: "Source column name." }),
190
+ max: aggSpec("max", "Declares a view field as MAX of a source column.", { description: "Source column name." })
159
191
  } };
160
192
  //#endregion
161
193
  //#region src/plugin/annotations/amount.ts
@@ -245,7 +277,7 @@ const dbColumnAnnotations = {
245
277
  }) },
246
278
  column: {
247
279
  $self: new AnnotationSpec({
248
- description: "Overrides the physical column name in the database. For nested (flattened) fields, the parent prefix is still prepended automatically.\n\n**Example:**\n```atscript\n@db.column \"first_name\"\nfirstName: string\n// → physical column: first_name\n\n// Nested:\naddress: {\n @db.column \"zip_code\"\n zip: string\n}\n// → physical column: address__zip_code\n```\n",
280
+ description: "Overrides the physical column name in the database. For nested (flattened) fields, the parent prefix is still prepended automatically. Document storage (MongoDB) renames top-level fields only — a nested field keeps its name there.\n\n**Example:**\n```atscript\n@db.column \"first_name\"\nfirstName: string\n// → physical column: first_name\n\n// Nested:\naddress: {\n @db.column \"zip_code\"\n zip: string\n}\n// → physical column: address__zip_code\n```\n",
249
281
  nodeType: ["prop"],
250
282
  passedWhenReferred: false,
251
283
  argument: {
@@ -255,7 +287,7 @@ const dbColumnAnnotations = {
255
287
  }
256
288
  }),
257
289
  renamed: new AnnotationSpec({
258
- description: "Specifies the previous local field name for column rename migration. The sync engine generates ALTER TABLE RENAME COLUMN instead of drop+add.\n\n**Example:**\n```atscript\n@db.column.renamed \"zip\"\npostalCode: string\n// Renames address__zip → address__postalCode\n```\n",
290
+ description: "Specifies the previous local field name for column rename migration. The sync engine generates ALTER TABLE RENAME COLUMN instead of drop+add (on document storage, top-level fields only).\n\n**Example:**\n```atscript\n@db.column.renamed \"zip\"\npostalCode: string\n// Renames address__zip → address__postalCode\n```\n",
259
291
  nodeType: ["prop"],
260
292
  passedWhenReferred: false,
261
293
  argument: {
@@ -1261,6 +1293,86 @@ const dbUnitAnnotations = { unit: {
1261
1293
  })
1262
1294
  } };
1263
1295
  //#endregion
1296
+ //#region src/shared/view-validation.ts
1297
+ /** `@db.agg.*` annotations that are never NULL (the counts: 0 over no value). */
1298
+ const NULL_SAFE_AGG_ANNOTATIONS = SUPPORTED_AGGREGATE_FNS.filter((fn) => !NULL_WHEN_EMPTY_AGGREGATE_FNS.has(fn)).map((fn) => `db.agg.${fn}`);
1299
+ /** Primitive leaf types a JSON-stored path may end at. */
1300
+ const JSON_LEAF_TYPES = new Set([
1301
+ "string",
1302
+ "number",
1303
+ "boolean"
1304
+ ]);
1305
+ /** Props of a view interface (its own structure; views don't use `extends`). */
1306
+ function viewProps(owner) {
1307
+ if (isInterface(owner)) return owner.props;
1308
+ const def = owner.getDefinition();
1309
+ return def && isStructure(def) ? def.props : void 0;
1310
+ }
1311
+ /**
1312
+ * VW8: a chain ref that passes through a `@db.json` or array node reads a
1313
+ * JSON leaf, which views can only extract as a string / number / boolean.
1314
+ * Intermediate nodes that `unwindType` can't reach are skipped (sync-time
1315
+ * resolution reports them).
1316
+ */
1317
+ function validateJsonChain(fieldName, ref, doc, range) {
1318
+ const typeName = ref.id;
1319
+ const chain = ref.chain.map((t) => t.text);
1320
+ if (!typeName || chain.length < 2) return [];
1321
+ let throughJson = false;
1322
+ for (let i = 1; i < chain.length; i++) {
1323
+ const step = doc.unwindType(typeName, chain.slice(0, i));
1324
+ if (!step) return [];
1325
+ const stepNode = step.node;
1326
+ if (stepNode && isProp(stepNode) && stepNode.countAnnotations("db.json") > 0 || isArray(step.def)) {
1327
+ throughJson = true;
1328
+ break;
1329
+ }
1330
+ }
1331
+ if (!throughJson) return [];
1332
+ const leaf = doc.unwindType(typeName, chain)?.def;
1333
+ const leafType = primitiveBaseType(leaf);
1334
+ if (leafType !== void 0 && JSON_LEAF_TYPES.has(leafType)) return [];
1335
+ return [{
1336
+ message: `Field "${fieldName}" reads "${typeName}.${chain.join(".")}" inside a JSON-stored field — it must end at a string, number or boolean leaf`,
1337
+ severity: 1,
1338
+ range
1339
+ }];
1340
+ }
1341
+ /**
1342
+ * Whole-view checks, run once per `@db.view.for` interface:
1343
+ *
1344
+ * - VW7 — a field reading from a left-joined table must be optional (the
1345
+ * join yields NULL for unmatched rows); `@db.agg.count` / `countDistinct`
1346
+ * fields are exempt (they count 0, never NULL).
1347
+ * - VW8 — a chain ref through a `@db.json` or array node must end at a
1348
+ * primitive string / number / boolean leaf.
1349
+ * @since 0.1.136
1350
+ */
1351
+ function validateViewInterface(owner, doc) {
1352
+ const errors = [];
1353
+ const props = viewProps(owner);
1354
+ if (!props) return errors;
1355
+ const leftJoined = /* @__PURE__ */ new Set();
1356
+ for (const join of viewJoins(owner)) {
1357
+ const target = join.args[0]?.text;
1358
+ if (target && join.args[2]?.text === "left") leftJoined.add(target);
1359
+ }
1360
+ for (const [fieldName, prop] of props) {
1361
+ const def = prop.getDefinition();
1362
+ if (!def || !isRef(def)) continue;
1363
+ const ref = def;
1364
+ const range = (prop.token("identifier") ?? ref.token("identifier"))?.range;
1365
+ if (!range) continue;
1366
+ if (ref.id && leftJoined.has(ref.id) && !prop.has("optional") && !NULL_SAFE_AGG_ANNOTATIONS.some((name) => prop.countAnnotations(name) > 0)) errors.push({
1367
+ message: `Field "${fieldName}" reads from left-joined "${ref.id}" and must be optional (${fieldName}?: …)`,
1368
+ severity: 1,
1369
+ range
1370
+ });
1371
+ errors.push(...validateJsonChain(fieldName, ref, doc, range));
1372
+ }
1373
+ return errors;
1374
+ }
1375
+ //#endregion
1264
1376
  //#region src/plugin/annotations/view.ts
1265
1377
  const dbViewAnnotations = { view: {
1266
1378
  $self: new AnnotationSpec({
@@ -1292,29 +1404,41 @@ const dbViewAnnotations = { view: {
1292
1404
  },
1293
1405
  validate(token, args, doc) {
1294
1406
  const errors = [];
1295
- if (token.parentNode.countAnnotations("db.table") > 0) errors.push({
1407
+ const owner = token.parentNode;
1408
+ if (owner.countAnnotations("db.table") > 0) errors.push({
1296
1409
  message: "An interface cannot be both a @db.table and a @db.view",
1297
1410
  severity: 1,
1298
1411
  range: token.range
1299
1412
  });
1300
1413
  if (args[0]) errors.push(...validateRefArgument(args[0], doc, { requireDbTable: true }));
1414
+ errors.push(...validateViewInterface(owner, doc));
1301
1415
  return errors;
1302
1416
  }
1303
1417
  }),
1304
1418
  joins: new AnnotationSpec({
1305
- description: "Declares an explicit join for a view. Use when no `@db.rel.*` path exists between the entry table and the target.\n\n**Example:**\n```atscript\n@db.view.for Order\n@db.view.joins Warehouse, `Warehouse.regionId = Order.regionId`\nexport interface OrderWarehouse { ... }\n```\n",
1419
+ description: "Declares an explicit join for a view. Joins are INNER by default — pass `'left'` as the third argument to keep entry rows without a match (fields read from a left-joined table must be optional). A join condition may reference the entry table and joins declared before it (chained joins); a table can be joined once (no aliases / self-joins).\n\n**Example:**\n```atscript\n@db.view.for Order\n@db.view.joins Customer, `Customer.id = Order.customerId`\n@db.view.joins Region, `Region.id = Customer.regionId`, 'left'\nexport interface OrderRegion { ... }\n```\n",
1306
1420
  nodeType: ["interface"],
1307
1421
  multiple: true,
1308
1422
  mergeStrategy: "append",
1309
- argument: [{
1310
- name: "target",
1311
- type: "ref",
1312
- description: "The table type to join (must have @db.table)."
1313
- }, {
1314
- name: "condition",
1315
- type: "query",
1316
- description: "Join condition expression."
1317
- }],
1423
+ argument: [
1424
+ {
1425
+ name: "target",
1426
+ type: "ref",
1427
+ description: "The table type to join (must have @db.table)."
1428
+ },
1429
+ {
1430
+ name: "condition",
1431
+ type: "query",
1432
+ description: "Join condition expression."
1433
+ },
1434
+ {
1435
+ name: "kind",
1436
+ type: "string",
1437
+ optional: true,
1438
+ description: "`\"inner\"` (default) drops entry rows without a match; `\"left\"` keeps them with NULLs.",
1439
+ values: ["inner", "left"]
1440
+ }
1441
+ ],
1318
1442
  validate(token, args, doc) {
1319
1443
  const errors = [];
1320
1444
  const owner = token.parentNode;
@@ -1336,9 +1460,29 @@ const dbViewAnnotations = { view: {
1336
1460
  });
1337
1461
  return errors;
1338
1462
  }
1463
+ const allJoins = viewJoins(owner);
1464
+ const position = allJoins.findIndex((a) => a.token === token);
1465
+ const earlier = joinTargets(position === -1 ? [] : allJoins.slice(0, position));
1466
+ if (args[0]) {
1467
+ const target = args[0].text;
1468
+ if (target === entryTypeName) errors.push({
1469
+ message: `@db.view.joins cannot join the entry table '${target}' — no join aliases / self-joins yet`,
1470
+ severity: 1,
1471
+ range: args[0].range
1472
+ });
1473
+ else if (earlier.includes(target)) errors.push({
1474
+ message: `'${target}' is joined more than once — no join aliases / self-joins yet`,
1475
+ severity: 1,
1476
+ range: args[0].range
1477
+ });
1478
+ }
1339
1479
  if (args[1]?.queryNode && args[0]) {
1340
1480
  const joinTargetName = args[0].text;
1341
- errors.push(...validateQueryScope(args[1], [joinTargetName, entryTypeName], entryTypeName, doc));
1481
+ errors.push(...validateQueryScope(args[1], [
1482
+ joinTargetName,
1483
+ entryTypeName,
1484
+ ...earlier
1485
+ ], entryTypeName, doc, "a join may reference the entry table and joins declared before it"));
1342
1486
  }
1343
1487
  return errors;
1344
1488
  }
@@ -1372,12 +1516,7 @@ const dbViewAnnotations = { view: {
1372
1516
  });
1373
1517
  return errors;
1374
1518
  }
1375
- const allowedTypes = [entryTypeName];
1376
- const joinsAnnotations = owner.annotations?.filter((a) => a.name === "db.view.joins");
1377
- if (joinsAnnotations) {
1378
- for (const join of joinsAnnotations) if (join.args[0]) allowedTypes.push(join.args[0].text);
1379
- }
1380
- errors.push(...validateQueryScope(args[0], allowedTypes, entryTypeName, doc));
1519
+ errors.push(...validateQueryScope(args[0], viewScopeTypes(owner), entryTypeName, doc));
1381
1520
  return errors;
1382
1521
  }
1383
1522
  }),
package/dist/rel.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_nested_writer = require("./nested-writer-wk1EFUNY.cjs");
3
- const require_relation_loader = require("./relation-loader-BD4xANQJ.cjs");
2
+ const require_nested_writer = require("./nested-writer-BZNCuqI6.cjs");
3
+ const require_relation_loader = require("./relation-loader-6ZB_5KFq.cjs");
4
4
  exports.batchInsertNestedFrom = require_nested_writer.batchInsertNestedFrom;
5
5
  exports.batchInsertNestedTo = require_nested_writer.batchInsertNestedTo;
6
6
  exports.batchInsertNestedVia = require_nested_writer.batchInsertNestedVia;
package/dist/rel.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { Ct as TWriteTableResolver, Ht as TGenericLogger, K as TDbRelation, Nt as BaseDbAdapter, Ot as TableMetadata, R as TDbForeignKey, yt as TTableResolver } from "./buckets-D6PBXKRJ.cjs";
1
+ import { Pt as BaseDbAdapter, bt as TTableResolver, kt as TableMetadata, q as TDbRelation, sn as TGenericLogger, wt as TWriteTableResolver, z as TDbForeignKey } from "./buckets-C-27xmtq.cjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.cjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-DxrvS6ar.cjs";
3
4
  import { FilterExpr, WithRelation } from "@uniqu/core";
4
5
  import { Validator } from "@atscript/typescript/utils";
5
6
 
@@ -20,25 +21,6 @@ interface TRelationLoaderHost {
20
21
  */
21
22
  declare function loadRelationsImpl(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], host: TRelationLoaderHost): Promise<void>;
22
23
  //#endregion
23
- //#region src/rel/relation-helpers.d.ts
24
- /**
25
- * Finds the FK entry that connects a `@db.rel.to` relation to its target.
26
- */
27
- declare function findFKForRelation(relation: TDbRelation, foreignKeys: ReadonlyMap<string, TDbForeignKey>): {
28
- localFields: string[];
29
- targetFields: string[];
30
- } | undefined;
31
- /**
32
- * Finds a FK on a remote table that points back to a given table name.
33
- */
34
- declare function findRemoteFK(targetTable: {
35
- foreignKeys: ReadonlyMap<string, TDbForeignKey>;
36
- }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
37
- /**
38
- * Resolves the target table name from a relation's target type metadata.
39
- */
40
- declare function resolveRelationTargetTable(relation: TDbRelation): string;
41
- //#endregion
42
24
  //#region src/rel/nested-writer.d.ts
43
25
  /**
44
26
  * Properties the nested writer functions need from the table instance.
package/dist/rel.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { Ct as TWriteTableResolver, Ht as TGenericLogger, K as TDbRelation, Nt as BaseDbAdapter, Ot as TableMetadata, R as TDbForeignKey, yt as TTableResolver } from "./buckets-GruoVxH5.mjs";
1
+ import { Pt as BaseDbAdapter, bt as TTableResolver, kt as TableMetadata, q as TDbRelation, sn as TGenericLogger, wt as TWriteTableResolver, z as TDbForeignKey } from "./buckets-BFG2RYRW.mjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.mjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-D3Zu0Mta.mjs";
3
4
  import { Validator } from "@atscript/typescript/utils";
4
5
  import { FilterExpr, WithRelation } from "@uniqu/core";
5
6
 
@@ -20,25 +21,6 @@ interface TRelationLoaderHost {
20
21
  */
21
22
  declare function loadRelationsImpl(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], host: TRelationLoaderHost): Promise<void>;
22
23
  //#endregion
23
- //#region src/rel/relation-helpers.d.ts
24
- /**
25
- * Finds the FK entry that connects a `@db.rel.to` relation to its target.
26
- */
27
- declare function findFKForRelation(relation: TDbRelation, foreignKeys: ReadonlyMap<string, TDbForeignKey>): {
28
- localFields: string[];
29
- targetFields: string[];
30
- } | undefined;
31
- /**
32
- * Finds a FK on a remote table that points back to a given table name.
33
- */
34
- declare function findRemoteFK(targetTable: {
35
- foreignKeys: ReadonlyMap<string, TDbForeignKey>;
36
- }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
37
- /**
38
- * Resolves the target table name from a relation's target type metadata.
39
- */
40
- declare function resolveRelationTargetTable(relation: TDbRelation): string;
41
- //#endregion
42
24
  //#region src/rel/nested-writer.d.ts
43
25
  /**
44
26
  * Properties the nested writer functions need from the table instance.
package/dist/rel.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as loadRelationsImpl } from "./relation-loader-B68R1LET.mjs";
2
- import { _ as resolveRelationTargetTable, a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, i as batchPatchNestedFrom, l as batchReplaceNestedVia, n as batchInsertNestedTo, o as batchPatchNestedVia, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-CqL24ojl.mjs";
1
+ import { t as loadRelationsImpl } from "./relation-loader-CTFaZpVa.mjs";
2
+ import { _ as resolveRelationTargetTable, a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, i as batchPatchNestedFrom, l as batchReplaceNestedVia, n as batchInsertNestedTo, o as batchPatchNestedVia, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-FWD5oOYh.mjs";
3
3
  export { batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
@@ -0,0 +1,30 @@
1
+ import { q as TDbRelation, z as TDbForeignKey } from "./buckets-BFG2RYRW.mjs";
2
+ import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
+
4
+ //#region src/rel/relation-helpers.d.ts
5
+ /**
6
+ * Finds the FK entry that connects a `@db.rel.to` relation to its target.
7
+ */
8
+ declare function findFKForRelation(relation: TDbRelation, foreignKeys: ReadonlyMap<string, TDbForeignKey>): {
9
+ localFields: string[];
10
+ targetFields: string[];
11
+ } | undefined;
12
+ /**
13
+ * Finds a FK on a remote table that points back to a given table name.
14
+ */
15
+ declare function findRemoteFK(targetTable: {
16
+ foreignKeys: ReadonlyMap<string, TDbForeignKey>;
17
+ }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
18
+ /**
19
+ * Table name of an annotated type — its `@db.table`, else its type id (`""`
20
+ * when neither is set). The rule relations, foreign keys and view plans use
21
+ * to name a referenced table.
22
+ * @since 0.1.136
23
+ */
24
+ declare function tableNameOf(type: TAtscriptAnnotatedType | undefined): string;
25
+ /**
26
+ * Resolves the target table name from a relation's target type metadata.
27
+ */
28
+ declare function resolveRelationTargetTable(relation: TDbRelation): string;
29
+ //#endregion
30
+ export { tableNameOf as i, findRemoteFK as n, resolveRelationTargetTable as r, findFKForRelation as t };
@@ -0,0 +1,30 @@
1
+ import { q as TDbRelation, z as TDbForeignKey } from "./buckets-C-27xmtq.cjs";
2
+ import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
+
4
+ //#region src/rel/relation-helpers.d.ts
5
+ /**
6
+ * Finds the FK entry that connects a `@db.rel.to` relation to its target.
7
+ */
8
+ declare function findFKForRelation(relation: TDbRelation, foreignKeys: ReadonlyMap<string, TDbForeignKey>): {
9
+ localFields: string[];
10
+ targetFields: string[];
11
+ } | undefined;
12
+ /**
13
+ * Finds a FK on a remote table that points back to a given table name.
14
+ */
15
+ declare function findRemoteFK(targetTable: {
16
+ foreignKeys: ReadonlyMap<string, TDbForeignKey>;
17
+ }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
18
+ /**
19
+ * Table name of an annotated type — its `@db.table`, else its type id (`""`
20
+ * when neither is set). The rule relations, foreign keys and view plans use
21
+ * to name a referenced table.
22
+ * @since 0.1.136
23
+ */
24
+ declare function tableNameOf(type: TAtscriptAnnotatedType | undefined): string;
25
+ /**
26
+ * Resolves the target table name from a relation's target type metadata.
27
+ */
28
+ declare function resolveRelationTargetTable(relation: TDbRelation): string;
29
+ //#endregion
30
+ export { tableNameOf as i, findRemoteFK as n, resolveRelationTargetTable as r, findFKForRelation as t };
@@ -1,4 +1,4 @@
1
- const require_nested_writer = require("./nested-writer-wk1EFUNY.cjs");
1
+ const require_nested_writer = require("./nested-writer-BZNCuqI6.cjs");
2
2
  //#region src/rel/relation-loader.ts
3
3
  var relation_loader_exports = /* @__PURE__ */ require("./plugin.cjs").__exportAll({
4
4
  ensureSelectIncludesFields: () => ensureSelectIncludesFields,
@@ -1,4 +1,4 @@
1
- import { _ as resolveRelationTargetTable, g as findRemoteFK, h as findFKForRelation } from "./nested-writer-CqL24ojl.mjs";
1
+ import { _ as resolveRelationTargetTable, g as findRemoteFK, h as findFKForRelation } from "./nested-writer-FWD5oOYh.mjs";
2
2
  //#region \0rolldown/runtime.js
3
3
  var __defProp = Object.defineProperty;
4
4
  var __exportAll = (all, no_symbols) => {
package/dist/shared.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_validation_utils = require("./validation-utils-B7SXPkm7.cjs");
2
+ const require_validation_utils = require("./validation-utils-Dg0hW6dn.cjs");
3
3
  exports.findFKFieldsPointingTo = require_validation_utils.findFKFieldsPointingTo;
4
4
  exports.getAnnotationAlias = require_validation_utils.getAnnotationAlias;
5
5
  exports.getDbTableOwner = require_validation_utils.getDbTableOwner;
@@ -7,9 +7,13 @@ exports.getNavTargetTypeName = require_validation_utils.getNavTargetTypeName;
7
7
  exports.getParentStruct = require_validation_utils.getParentStruct;
8
8
  exports.getParentTypeName = require_validation_utils.getParentTypeName;
9
9
  exports.hasAnyViewAnnotation = require_validation_utils.hasAnyViewAnnotation;
10
+ exports.joinTargets = require_validation_utils.joinTargets;
11
+ exports.primitiveBaseType = require_validation_utils.primitiveBaseType;
10
12
  exports.refActionAnnotation = require_validation_utils.refActionAnnotation;
11
13
  exports.validateExclusiveWith = require_validation_utils.validateExclusiveWith;
12
14
  exports.validateFieldBaseType = require_validation_utils.validateFieldBaseType;
13
15
  exports.validateQueryScope = require_validation_utils.validateQueryScope;
14
16
  exports.validateRefArgument = require_validation_utils.validateRefArgument;
15
17
  exports.validateSiblingStringField = require_validation_utils.validateSiblingStringField;
18
+ exports.viewJoins = require_validation_utils.viewJoins;
19
+ exports.viewScopeTypes = require_validation_utils.viewScopeTypes;
package/dist/shared.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnnotationSpec, AtscriptDoc, SemanticInterfaceNode, SemanticNode, SemanticPropNode, SemanticStructureNode, TMessages, Token } from "@atscript/core";
1
+ import { AnnotationSpec, AtscriptDoc, SemanticInterfaceNode, SemanticNode, SemanticPropNode, SemanticStructureNode, TAnnotationTokens, TMessages, Token } from "@atscript/core";
2
2
 
3
3
  //#region src/shared/annotation-utils.d.ts
4
4
  /** Asserts the field carrying this annotation does not also carry any of `others`. */
@@ -6,6 +6,8 @@ declare function validateExclusiveWith(token: Token, selfName: string, others: A
6
6
  key: string;
7
7
  displayName?: string;
8
8
  }>): TMessages;
9
+ /** Primitive base type (`string`, `number`, `decimal`, …) of a resolved definition, if it is a primitive. */
10
+ declare function primitiveBaseType(def: SemanticNode | undefined): string | undefined;
9
11
  /** Asserts `args[0]` names a sibling property whose primitive base type is `string`. */
10
12
  declare function validateSiblingStringField(token: Token, args: Token[], doc: AtscriptDoc, selfName: string): TMessages;
11
13
  /**
@@ -61,6 +63,16 @@ interface TFKFieldMatch {
61
63
  * Resolves `extends` to include inherited fields.
62
64
  */
63
65
  declare function findFKFieldsPointingTo(doc: AtscriptDoc, iface: SemanticInterfaceNode | SemanticStructureNode, targetTypeName: string, alias?: string): TFKFieldMatch[];
66
+ /** The `@db.view.joins` annotations of a view interface, in declaration order. */
67
+ declare function viewJoins(owner: SemanticNode): TAnnotationTokens[];
68
+ /** The target type names of `@db.view.joins` annotations. */
69
+ declare function joinTargets(joins: readonly TAnnotationTokens[]): string[];
70
+ /**
71
+ * The type names a view predicate (`@db.view.filter`, a conditional
72
+ * `@db.agg.*`) may reference: the `@db.view.for` entry table, then every
73
+ * `@db.view.joins` target.
74
+ */
75
+ declare function viewScopeTypes(owner: SemanticNode): string[];
64
76
  /**
65
77
  * Check if a node has any @db.view.* annotation.
66
78
  */
@@ -72,7 +84,8 @@ declare function hasAnyViewAnnotation(node: SemanticNode): boolean;
72
84
  * @param allowedTypes - Type names allowed as qualified refs
73
85
  * @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
74
86
  * @param doc - The document for type lookups
87
+ * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
75
88
  */
76
- declare function validateQueryScope(queryToken: Token, allowedTypes: string[], unqualifiedTarget: string | null, doc: AtscriptDoc): TMessages;
89
+ declare function validateQueryScope(queryToken: Token, allowedTypes: string[], unqualifiedTarget: string | null, doc: AtscriptDoc, scopeHint?: string): TMessages;
77
90
  //#endregion
78
- export { TFKFieldMatch, findFKFieldsPointingTo, getAnnotationAlias, getDbTableOwner, getNavTargetTypeName, getParentStruct, getParentTypeName, hasAnyViewAnnotation, refActionAnnotation, validateExclusiveWith, validateFieldBaseType, validateQueryScope, validateRefArgument, validateSiblingStringField };
91
+ export { TFKFieldMatch, findFKFieldsPointingTo, getAnnotationAlias, getDbTableOwner, getNavTargetTypeName, getParentStruct, getParentTypeName, hasAnyViewAnnotation, joinTargets, primitiveBaseType, refActionAnnotation, validateExclusiveWith, validateFieldBaseType, validateQueryScope, validateRefArgument, validateSiblingStringField, viewJoins, viewScopeTypes };
package/dist/shared.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnnotationSpec, AtscriptDoc, SemanticInterfaceNode, SemanticNode, SemanticPropNode, SemanticStructureNode, TMessages, Token } from "@atscript/core";
1
+ import { AnnotationSpec, AtscriptDoc, SemanticInterfaceNode, SemanticNode, SemanticPropNode, SemanticStructureNode, TAnnotationTokens, TMessages, Token } from "@atscript/core";
2
2
 
3
3
  //#region src/shared/annotation-utils.d.ts
4
4
  /** Asserts the field carrying this annotation does not also carry any of `others`. */
@@ -6,6 +6,8 @@ declare function validateExclusiveWith(token: Token, selfName: string, others: A
6
6
  key: string;
7
7
  displayName?: string;
8
8
  }>): TMessages;
9
+ /** Primitive base type (`string`, `number`, `decimal`, …) of a resolved definition, if it is a primitive. */
10
+ declare function primitiveBaseType(def: SemanticNode | undefined): string | undefined;
9
11
  /** Asserts `args[0]` names a sibling property whose primitive base type is `string`. */
10
12
  declare function validateSiblingStringField(token: Token, args: Token[], doc: AtscriptDoc, selfName: string): TMessages;
11
13
  /**
@@ -61,6 +63,16 @@ interface TFKFieldMatch {
61
63
  * Resolves `extends` to include inherited fields.
62
64
  */
63
65
  declare function findFKFieldsPointingTo(doc: AtscriptDoc, iface: SemanticInterfaceNode | SemanticStructureNode, targetTypeName: string, alias?: string): TFKFieldMatch[];
66
+ /** The `@db.view.joins` annotations of a view interface, in declaration order. */
67
+ declare function viewJoins(owner: SemanticNode): TAnnotationTokens[];
68
+ /** The target type names of `@db.view.joins` annotations. */
69
+ declare function joinTargets(joins: readonly TAnnotationTokens[]): string[];
70
+ /**
71
+ * The type names a view predicate (`@db.view.filter`, a conditional
72
+ * `@db.agg.*`) may reference: the `@db.view.for` entry table, then every
73
+ * `@db.view.joins` target.
74
+ */
75
+ declare function viewScopeTypes(owner: SemanticNode): string[];
64
76
  /**
65
77
  * Check if a node has any @db.view.* annotation.
66
78
  */
@@ -72,7 +84,8 @@ declare function hasAnyViewAnnotation(node: SemanticNode): boolean;
72
84
  * @param allowedTypes - Type names allowed as qualified refs
73
85
  * @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
74
86
  * @param doc - The document for type lookups
87
+ * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
75
88
  */
76
- declare function validateQueryScope(queryToken: Token, allowedTypes: string[], unqualifiedTarget: string | null, doc: AtscriptDoc): TMessages;
89
+ declare function validateQueryScope(queryToken: Token, allowedTypes: string[], unqualifiedTarget: string | null, doc: AtscriptDoc, scopeHint?: string): TMessages;
77
90
  //#endregion
78
- export { TFKFieldMatch, findFKFieldsPointingTo, getAnnotationAlias, getDbTableOwner, getNavTargetTypeName, getParentStruct, getParentTypeName, hasAnyViewAnnotation, refActionAnnotation, validateExclusiveWith, validateFieldBaseType, validateQueryScope, validateRefArgument, validateSiblingStringField };
91
+ export { TFKFieldMatch, findFKFieldsPointingTo, getAnnotationAlias, getDbTableOwner, getNavTargetTypeName, getParentStruct, getParentTypeName, hasAnyViewAnnotation, joinTargets, primitiveBaseType, refActionAnnotation, validateExclusiveWith, validateFieldBaseType, validateQueryScope, validateRefArgument, validateSiblingStringField, viewJoins, viewScopeTypes };