@atscript/db 0.1.145 → 0.1.147

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 (45) hide show
  1. package/dist/agg.cjs +1 -1
  2. package/dist/agg.d.cts +1 -1
  3. package/dist/agg.d.mts +1 -1
  4. package/dist/agg.mjs +1 -1
  5. package/dist/{aggregate-fns-CfsveE1w.mjs → aggregate-fns-C0BymEqa.mjs} +3 -1
  6. package/dist/{aggregate-fns-CGBv3E8S.cjs → aggregate-fns-CtsRbZn9.cjs} +8 -0
  7. package/dist/{buckets-DYFu0eZ8.d.cts → buckets-CjdPipCC.d.cts} +522 -17
  8. package/dist/{buckets-DRycmhOW.d.mts → buckets-DYopfd2Y.d.mts} +522 -17
  9. package/dist/{column-diff-e2oHc71_.mjs → column-diff-CO06iQvI.mjs} +862 -115
  10. package/dist/{column-diff-D_Kyuh0S.cjs → column-diff-Dt7EDJsA.cjs} +872 -113
  11. package/dist/{column-diff-Q9UmWn5x.d.mts → fk-diff-BtOmL4OW.d.mts} +48 -2
  12. package/dist/{column-diff-w-Mym_3w.d.cts → fk-diff-DLqwVkKG.d.cts} +48 -2
  13. package/dist/index.cjs +28 -33
  14. package/dist/index.d.cts +40 -15
  15. package/dist/index.d.mts +40 -15
  16. package/dist/index.mjs +5 -33
  17. package/dist/{nested-writer-CnOOAehr.mjs → nested-writer-D35UsDBT.mjs} +542 -2
  18. package/dist/{nested-writer-xfQwxplL.cjs → nested-writer-SqtYJHF0.cjs} +666 -0
  19. package/dist/plugin.cjs +266 -5
  20. package/dist/plugin.mjs +267 -6
  21. package/dist/rel.cjs +2 -2
  22. package/dist/rel.d.cts +2 -2
  23. package/dist/rel.d.mts +2 -2
  24. package/dist/rel.mjs +2 -2
  25. package/dist/{relation-helpers-B-0NRKat.d.mts → relation-helpers-C2bLtE-t.d.mts} +1 -1
  26. package/dist/{relation-helpers-BOMm_HUI.d.cts → relation-helpers-DH3LvwLV.d.cts} +1 -1
  27. package/dist/relation-loader-BFhVuJ-B.cjs +369 -0
  28. package/dist/relation-loader-C0qE8xgR.mjs +370 -0
  29. package/dist/shared.cjs +1 -1
  30. package/dist/shared.d.cts +4 -3
  31. package/dist/shared.d.mts +4 -3
  32. package/dist/shared.mjs +1 -1
  33. package/dist/sync.cjs +10 -6
  34. package/dist/sync.d.cts +2 -25
  35. package/dist/sync.d.mts +2 -25
  36. package/dist/sync.mjs +10 -6
  37. package/dist/{validation-utils-DOsB4e6G.cjs → validation-utils-Da2GjobR.cjs} +35 -24
  38. package/dist/{validation-utils-CMR4fe2M.mjs → validation-utils-Dq0uZ7ef.mjs} +35 -24
  39. package/dist/{validator-Bw6ks9Hy.d.mts → validator-CMnTI6r7.d.cts} +9 -6
  40. package/dist/{validator-Bw6ks9Hy.d.cts → validator-CMnTI6r7.d.mts} +9 -6
  41. package/dist/validator.d.cts +1 -1
  42. package/dist/validator.d.mts +1 -1
  43. package/package.json +8 -8
  44. package/dist/relation-loader-CBPY6kM7.cjs +0 -461
  45. package/dist/relation-loader-D9XuXaMv.mjs +0 -462
@@ -0,0 +1,370 @@
1
+ import { I as findFKForRelation, L as findRemoteFK, M as relationStaticFilter, P as compositeKey, R as resolveRelationTargetTable, w as andFilters, z as slicePerGroup } from "./nested-writer-D35UsDBT.mjs";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ //#region src/rel/relation-loader.ts
15
+ var relation_loader_exports = /* @__PURE__ */ __exportAll({
16
+ ensureSelectIncludesFields: () => ensureSelectIncludesFields,
17
+ loadRelationsImpl: () => loadRelationsImpl
18
+ });
19
+ /**
20
+ * Loads related data for `$with` relations and attaches them to result rows.
21
+ */
22
+ async function loadRelationsImpl(rows, withRelations, host) {
23
+ if (rows.length === 0 || withRelations.length === 0) return;
24
+ if (host.adapter.supportsNativeRelations()) return host.adapter.loadRelations(rows, withRelations, host._meta.relations, host._meta.foreignKeys, host._tableResolver);
25
+ if (!host._tableResolver) return;
26
+ const tasks = [];
27
+ for (const withRel of withRelations) {
28
+ const relName = withRel.name;
29
+ if (relName.includes(".")) continue;
30
+ const relation = host._meta.relations.get(relName);
31
+ if (!relation) throw new Error(`Unknown relation "${relName}" in $with. Available relations: ${[...host._meta.relations.keys()].join(", ") || "(none)"}`);
32
+ const targetType = relation.targetType();
33
+ if (!targetType) continue;
34
+ const targetTable = host._tableResolver(targetType);
35
+ if (!targetTable) {
36
+ host.logger.warn(`Could not resolve table for relation "${relName}" — skipping`);
37
+ continue;
38
+ }
39
+ const statics = relationStaticFilter(relation, relName);
40
+ const merged = andFilters(statics.target, withRel.filter);
41
+ const filter = Object.keys(merged).length > 0 ? merged : void 0;
42
+ const flatRel = withRel;
43
+ const controls = { ...withRel.controls || {} };
44
+ if (flatRel.$sort && !controls.$sort) controls.$sort = flatRel.$sort;
45
+ if (flatRel.$limit !== null && flatRel.$limit !== void 0 && (controls.$limit === null || controls.$limit === void 0)) controls.$limit = flatRel.$limit;
46
+ if (flatRel.$skip !== null && flatRel.$skip !== void 0 && (controls.$skip === null || controls.$skip === void 0)) controls.$skip = flatRel.$skip;
47
+ if (flatRel.$select && !controls.$select) controls.$select = flatRel.$select;
48
+ if (flatRel.$with && !controls.$with) controls.$with = flatRel.$with;
49
+ const { $skip, $limit, ...pageless } = controls;
50
+ const skip = asCount($skip) || void 0;
51
+ const limit = asCount($limit);
52
+ const relQuery = {
53
+ filter,
54
+ controls: pageless,
55
+ page: skip === void 0 && limit === void 0 ? void 0 : {
56
+ skip,
57
+ limit
58
+ },
59
+ junctionFilter: statics.junction
60
+ };
61
+ if (relation.direction === "to") tasks.push(loadToRelation(rows, {
62
+ relName,
63
+ relation,
64
+ targetTable,
65
+ relQuery
66
+ }, host));
67
+ else if (relation.direction === "via") tasks.push(loadViaRelation(rows, {
68
+ relName,
69
+ relation,
70
+ targetTable,
71
+ relQuery
72
+ }, host));
73
+ else tasks.push(loadFromRelation(rows, {
74
+ relName,
75
+ relation,
76
+ targetTable,
77
+ relQuery
78
+ }, host));
79
+ }
80
+ await Promise.all(tasks);
81
+ }
82
+ /**
83
+ * Loads a `@db.rel.to` relation (FK is on this table). Single-valued: the
84
+ * relation's `$skip` / `$limit` page each row's (at most one) target row.
85
+ */
86
+ async function loadToRelation(rows, opts, host) {
87
+ const { relName, relation, targetTable, relQuery } = opts;
88
+ const fkEntry = findFKForRelation(relation, host._meta.foreignKeys);
89
+ if (!fkEntry) return;
90
+ const { localFields, targetFields } = fkEntry;
91
+ const index = indexFirst(await readRelatedByKeys(rows, {
92
+ localFields,
93
+ targetFields,
94
+ targetTable,
95
+ relQuery
96
+ }), targetFields);
97
+ for (const row of rows) row[relName] = index.get(compositeKey(localFields, row)) ?? null;
98
+ }
99
+ /**
100
+ * Loads a `@db.rel.from` relation (FK is on the target table). `$sort`,
101
+ * `$skip` and `$limit` apply to the related rows of each row.
102
+ */
103
+ async function loadFromRelation(rows, opts, host) {
104
+ const { relName, relation, targetTable, relQuery } = opts;
105
+ const remoteFK = findRemoteFK(targetTable, host.tableName, relation.alias);
106
+ if (!remoteFK) {
107
+ host.logger.warn(`Could not find FK on target table for relation "${relName}"`);
108
+ return;
109
+ }
110
+ const localFields = remoteFK.targetFields;
111
+ const remoteFields = remoteFK.fields;
112
+ const related = await readRelatedByKeys(rows, {
113
+ localFields,
114
+ targetFields: remoteFields,
115
+ targetTable,
116
+ relQuery
117
+ });
118
+ if (relation.isArray) {
119
+ const groups = groupBy(related, (item) => compositeKey(remoteFields, item));
120
+ for (const row of rows) row[relName] = groups.get(compositeKey(localFields, row)) ?? [];
121
+ } else {
122
+ const index = indexFirst(related, remoteFields);
123
+ for (const row of rows) row[relName] = index.get(compositeKey(localFields, row)) ?? null;
124
+ }
125
+ }
126
+ /**
127
+ * Loads a `@db.rel.via` relation (M:N through a junction table). The target
128
+ * rows are read once for all rows; `$sort` orders, and `$skip` / `$limit`
129
+ * page, the target rows of each row.
130
+ */
131
+ async function loadViaRelation(rows, opts, host) {
132
+ const { relName, relation, targetTable, relQuery } = opts;
133
+ if (!relation.viaType || !host._tableResolver) return;
134
+ const junctionType = relation.viaType();
135
+ if (!junctionType) return;
136
+ const junctionTable = host._tableResolver(junctionType);
137
+ if (!junctionTable) {
138
+ host.logger.warn(`Could not resolve junction table for via relation "${relName}"`);
139
+ return;
140
+ }
141
+ const fkToThis = findRemoteFK(junctionTable, host.tableName);
142
+ if (!fkToThis) {
143
+ host.logger.warn(`Could not find FK on junction table pointing to "${host.tableName}" for via relation "${relName}"`);
144
+ return;
145
+ }
146
+ const targetTableName = resolveRelationTargetTable(relation);
147
+ const fkToTarget = findRemoteFK(junctionTable, targetTableName);
148
+ if (!fkToTarget) {
149
+ host.logger.warn(`Could not find FK on junction table pointing to target "${targetTableName}" for via relation "${relName}"`);
150
+ return;
151
+ }
152
+ const localPKFields = fkToThis.targetFields;
153
+ const fields = {
154
+ junctionLocalFields: fkToThis.fields,
155
+ junctionTargetFields: fkToTarget.fields,
156
+ targetPKFields: fkToTarget.targetFields
157
+ };
158
+ const parentFilter = matchAnyFilter(rows, localPKFields, fields.junctionLocalFields);
159
+ const junctionRows = parentFilter ? await junctionTable.findMany({
160
+ filter: andFilters(parentFilter, relQuery.junctionFilter),
161
+ controls: { $select: [...fields.junctionLocalFields, ...fields.junctionTargetFields] }
162
+ }) : [];
163
+ const targetFilter = matchAnyFilter(junctionRows, fields.junctionTargetFields, fields.targetPKFields);
164
+ if (!targetFilter) {
165
+ for (const row of rows) row[relName] = relation.isArray ? [] : null;
166
+ return;
167
+ }
168
+ const sorted = hasSort(relQuery.controls);
169
+ let groups = /* @__PURE__ */ new Map();
170
+ const pick = (targets) => {
171
+ groups = groupViaTargets(targets, junctionRows, fields, sorted);
172
+ const page = relQuery.page;
173
+ if (!page) return targets;
174
+ const start = page.skip ?? 0;
175
+ const end = page.limit === void 0 ? void 0 : start + page.limit;
176
+ const kept = /* @__PURE__ */ new Set();
177
+ for (const [key, group] of groups) {
178
+ const slice = group.slice(start, end);
179
+ groups.set(key, slice);
180
+ for (const target of slice) kept.add(target);
181
+ }
182
+ return targets.filter((target) => kept.has(target));
183
+ };
184
+ await readRelated(targetTable, {
185
+ filter: relQuery.filter ? { $and: [targetFilter, relQuery.filter] } : targetFilter,
186
+ controls: ensureSelectIncludesFields(relQuery.controls, fields.targetPKFields)
187
+ }, { pick });
188
+ for (const row of rows) {
189
+ const group = groups.get(compositeKey(localPKFields, row));
190
+ row[relName] = relation.isArray ? group ?? [] : group?.[0] ?? null;
191
+ }
192
+ }
193
+ /**
194
+ * Each row's targets (keyed by the junction's local key), once per junction
195
+ * row. The junction rows only map rows to targets: with a `$sort` the targets
196
+ * keep the order the target read returned them in, else junction order.
197
+ */
198
+ function groupViaTargets(targets, junctionRows, fields, sorted) {
199
+ const groups = /* @__PURE__ */ new Map();
200
+ if (sorted) {
201
+ const linksByTarget = groupBy(junctionRows, (jRow) => compositeKey(fields.junctionTargetFields, jRow));
202
+ for (const target of targets) for (const jRow of linksByTarget.get(compositeKey(fields.targetPKFields, target)) ?? []) appendTo(groups, compositeKey(fields.junctionLocalFields, jRow), target);
203
+ } else {
204
+ const index = indexFirst(targets, fields.targetPKFields);
205
+ for (const jRow of junctionRows) {
206
+ const target = index.get(compositeKey(fields.junctionTargetFields, jRow));
207
+ if (target) appendTo(groups, compositeKey(fields.junctionLocalFields, jRow), target);
208
+ }
209
+ }
210
+ return groups;
211
+ }
212
+ /**
213
+ * Reads the target rows whose `targetFields` match `localFields` of any of
214
+ * `rows`, the relation's `$skip` / `$limit` applied per such row.
215
+ */
216
+ async function readRelatedByKeys(rows, opts) {
217
+ const { localFields, targetFields, targetTable, relQuery } = opts;
218
+ const keyFilter = matchAnyFilter(rows, localFields, targetFields);
219
+ if (!keyFilter) return [];
220
+ return readRelated(targetTable, {
221
+ filter: relQuery.filter ? { $and: [keyFilter, relQuery.filter] } : keyFilter,
222
+ controls: ensureSelectIncludesFields(relQuery.controls, targetFields)
223
+ }, {
224
+ partitionBy: targetFields,
225
+ page: relQuery.page
226
+ });
227
+ }
228
+ /**
229
+ * Reads related rows. With `partitionBy` and a `page`, the page applies per
230
+ * group of rows sharing the `partitionBy` values; `pick` keeps a subset of
231
+ * the rows before their own (nested) `$with` relations load.
232
+ */
233
+ async function readRelated(table, query, opts) {
234
+ const { partitionBy, page, pick } = opts;
235
+ const partitioned = partitionBy && page;
236
+ if (table._findManyForRelation) {
237
+ const controls = partitioned ? {
238
+ ...query.controls,
239
+ ...pageControls(page)
240
+ } : query.controls;
241
+ return table._findManyForRelation({
242
+ filter: query.filter,
243
+ controls
244
+ }, {
245
+ partitionBy: partitioned ? partitionBy : void 0,
246
+ pick
247
+ });
248
+ }
249
+ let rows = await table.findMany(query);
250
+ if (partitioned) rows = slicePerGroup(rows, (row) => compositeKey(partitionBy, row), page);
251
+ return pick ? pick(rows) : rows;
252
+ }
253
+ /** A `$skip` / `$limit` value as a number (`undefined` when unset or not a number). */
254
+ function asCount(value) {
255
+ if (value === null || value === void 0 || value === "") return;
256
+ const n = Number(value);
257
+ return Number.isFinite(n) ? n : void 0;
258
+ }
259
+ /** `$skip` / `$limit` controls of a page (only the ones set). */
260
+ function pageControls(page) {
261
+ const controls = {};
262
+ if (page.skip !== void 0) controls.$skip = page.skip;
263
+ if (page.limit !== void 0) controls.$limit = page.limit;
264
+ return controls;
265
+ }
266
+ /** Whether `controls` carry a non-empty `$sort`. */
267
+ function hasSort(controls) {
268
+ const sort = controls.$sort;
269
+ return !!sort && typeof sort === "object" && Object.keys(sort).length > 0;
270
+ }
271
+ /**
272
+ * A filter matching the rows whose `toFields` equal `fromFields` of any of
273
+ * `rows` — `$in` for a single field, `$or` of conditions for a composite key.
274
+ * `undefined` when no row has every `fromFields` value.
275
+ */
276
+ function matchAnyFilter(rows, fromFields, toFields) {
277
+ if (fromFields.length === 1) {
278
+ const values = /* @__PURE__ */ new Set();
279
+ for (const row of rows) {
280
+ const v = row[fromFields[0]];
281
+ if (v !== null && v !== void 0) values.add(v);
282
+ }
283
+ return values.size > 0 ? { [toFields[0]]: { $in: [...values] } } : void 0;
284
+ }
285
+ const seen = /* @__PURE__ */ new Set();
286
+ const conditions = [];
287
+ for (const row of rows) {
288
+ const key = compositeKey(fromFields, row);
289
+ if (seen.has(key)) continue;
290
+ seen.add(key);
291
+ const condition = {};
292
+ let valid = true;
293
+ for (let i = 0; i < fromFields.length; i++) {
294
+ const v = row[fromFields[i]];
295
+ if (v === null || v === void 0) {
296
+ valid = false;
297
+ break;
298
+ }
299
+ condition[toFields[i]] = v;
300
+ }
301
+ if (valid) conditions.push(condition);
302
+ }
303
+ if (conditions.length === 0) return;
304
+ return conditions.length === 1 ? conditions[0] : { $or: conditions };
305
+ }
306
+ /** The first of `items` per `fields` key. */
307
+ function indexFirst(items, fields) {
308
+ const index = /* @__PURE__ */ new Map();
309
+ for (const item of items) {
310
+ const key = compositeKey(fields, item);
311
+ if (!index.has(key)) index.set(key, item);
312
+ }
313
+ return index;
314
+ }
315
+ /** `items` grouped by `keyOf`, each group in the order of `items`. */
316
+ function groupBy(items, keyOf) {
317
+ const groups = /* @__PURE__ */ new Map();
318
+ for (const item of items) appendTo(groups, keyOf(item), item);
319
+ return groups;
320
+ }
321
+ function appendTo(groups, key, item) {
322
+ const group = groups.get(key);
323
+ if (group) group.push(item);
324
+ else groups.set(key, [item]);
325
+ }
326
+ /**
327
+ * Ensure the given join fields survive the user's $select on a $with relation
328
+ * read. Without this, the JS-side join would key off undefined and silently
329
+ * return empty relations.
330
+ *
331
+ * - Array $select: append missing FK fields.
332
+ * - Include-mode object $select (any value is 1/true, or empty object): set
333
+ * missing FK fields to 1.
334
+ * - Exclude-mode object $select: leave alone — FK survives by default. If the
335
+ * FK is explicitly excluded, throw rather than silently override user intent.
336
+ */
337
+ function ensureSelectIncludesFields(controls, fields) {
338
+ if (!controls) return controls;
339
+ const sel = controls.$select;
340
+ if (sel === void 0 || sel === null) return controls;
341
+ if (Array.isArray(sel)) {
342
+ const augmented = [...sel];
343
+ for (const f of fields) if (!augmented.includes(f)) augmented.push(f);
344
+ return {
345
+ ...controls,
346
+ $select: augmented
347
+ };
348
+ }
349
+ if (typeof sel === "object") {
350
+ const selObj = sel;
351
+ const isInclude = Object.keys(selObj).length === 0 || Object.values(selObj).some((v) => v === 1 || v === true);
352
+ const augmented = { ...selObj };
353
+ let mutated = false;
354
+ for (const f of fields) {
355
+ const v = augmented[f];
356
+ if (v === 0 || v === false) throw new Error(`Cannot exclude join column "${f}" from $select on $with relation`);
357
+ if (v === void 0 && isInclude) {
358
+ augmented[f] = 1;
359
+ mutated = true;
360
+ }
361
+ }
362
+ return mutated ? {
363
+ ...controls,
364
+ $select: augmented
365
+ } : controls;
366
+ }
367
+ return controls;
368
+ }
369
+ //#endregion
370
+ export { relation_loader_exports as n, loadRelationsImpl as t };
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-DOsB4e6G.cjs");
2
+ const require_validation_utils = require("./validation-utils-Da2GjobR.cjs");
3
3
  exports.earlierJoinTargets = require_validation_utils.earlierJoinTargets;
4
4
  exports.findFKFieldsPointingTo = require_validation_utils.findFKFieldsPointingTo;
5
5
  exports.findViewCycle = require_validation_utils.findViewCycle;
package/dist/shared.d.cts CHANGED
@@ -108,13 +108,14 @@ declare function viewScopeTypes(owner: SemanticNode): string[];
108
108
  */
109
109
  declare function hasAnyViewAnnotation(node: SemanticNode): boolean;
110
110
  /**
111
- * Validate that all field refs in a query expression are within `scope` —
112
- * the same {@link TQueryScope} the editor completes (`lsp-scopes`):
111
+ * Validate that all field refs in a backtick argument (a query, an `expr` or
112
+ * an `order`) are within `scope` — the same {@link TQueryScope} the editor
113
+ * completes (`lsp-scopes`):
113
114
  * qualified refs must name one of `scope.allowedTypes`; an unqualified ref
114
115
  * (a dotted path included) must be a field of `scope.unqualifiedTarget`, or
115
116
  * is rejected outright when that is `null`.
116
117
  *
117
- * @param queryToken - The query arg token (must have .queryNode)
118
+ * @param queryToken - The backtick arg token (with `.queryNode`, `.exprNode` or `.orderNode`)
118
119
  * @param scope - The scope of the argument
119
120
  * @param doc - The document for type lookups
120
121
  * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
package/dist/shared.d.mts CHANGED
@@ -108,13 +108,14 @@ declare function viewScopeTypes(owner: SemanticNode): string[];
108
108
  */
109
109
  declare function hasAnyViewAnnotation(node: SemanticNode): boolean;
110
110
  /**
111
- * Validate that all field refs in a query expression are within `scope` —
112
- * the same {@link TQueryScope} the editor completes (`lsp-scopes`):
111
+ * Validate that all field refs in a backtick argument (a query, an `expr` or
112
+ * an `order`) are within `scope` — the same {@link TQueryScope} the editor
113
+ * completes (`lsp-scopes`):
113
114
  * qualified refs must name one of `scope.allowedTypes`; an unqualified ref
114
115
  * (a dotted path included) must be a field of `scope.unqualifiedTarget`, or
115
116
  * is rejected outright when that is `null`.
116
117
  *
117
- * @param queryToken - The query arg token (must have .queryNode)
118
+ * @param queryToken - The backtick arg token (with `.queryNode`, `.exprNode` or `.orderNode`)
118
119
  * @param scope - The scope of the argument
119
120
  * @param doc - The document for type lookups
120
121
  * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
package/dist/shared.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { S as validateSiblingStringField, _ as getParentTypeName, a as hasAnyViewAnnotation, b as validateExclusiveWith, c as joinTargets, d as viewJoins, f as viewScopeTypes, g as getParentStruct, h as getNavTargetTypeName, i as forEachFieldRef, l as validateQueryScope, m as getDbTableOwner, n as findFKFieldsPointingTo, o as isAliasDecl, p as getAnnotationAlias, r as findViewCycle, s as isDbSourceDecl, t as earlierJoinTargets, u as validateRefArgument, v as primitiveBaseType, x as validateFieldBaseType, y as refActionAnnotation } from "./validation-utils-CMR4fe2M.mjs";
1
+ import { S as validateSiblingStringField, _ as getParentTypeName, a as hasAnyViewAnnotation, b as validateExclusiveWith, c as joinTargets, d as viewJoins, f as viewScopeTypes, g as getParentStruct, h as getNavTargetTypeName, i as forEachFieldRef, l as validateQueryScope, m as getDbTableOwner, n as findFKFieldsPointingTo, o as isAliasDecl, p as getAnnotationAlias, r as findViewCycle, s as isDbSourceDecl, t as earlierJoinTargets, u as validateRefArgument, v as primitiveBaseType, x as validateFieldBaseType, y as refActionAnnotation } from "./validation-utils-Dq0uZ7ef.mjs";
2
2
  export { earlierJoinTargets, findFKFieldsPointingTo, findViewCycle, forEachFieldRef, getAnnotationAlias, getDbTableOwner, getNavTargetTypeName, getParentStruct, getParentTypeName, hasAnyViewAnnotation, isAliasDecl, isDbSourceDecl, joinTargets, primitiveBaseType, refActionAnnotation, validateExclusiveWith, validateFieldBaseType, validateQueryScope, validateRefArgument, validateSiblingStringField, viewJoins, viewScopeTypes };
package/dist/sync.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_db_error = require("./db-error-DTkkeu5b.cjs");
3
- const require_column_diff = require("./column-diff-D_Kyuh0S.cjs");
3
+ const require_column_diff = require("./column-diff-Dt7EDJsA.cjs");
4
4
  //#region src/schema/table-option-diff.ts
5
5
  /**
6
6
  * Computes the difference between desired and existing table options.
@@ -745,7 +745,7 @@ async function executeSyncTable(facts, safe, deps, exec) {
745
745
  };
746
746
  } else {
747
747
  if (hasFkChanges && fkDiff && adapter.dropForeignKeys) {
748
- const keysToDrop = [...fkDiff.removed.map((fk) => require_column_diff.fkKey(fk.fields)), ...fkDiff.changed.map((fk) => require_column_diff.fkKey(fk.desired.fields))];
748
+ const keysToDrop = [...fkDiff.removed.map((fk) => require_column_diff.fkKey(fk.fields)), ...fkDiff.changed.map((fk) => require_column_diff.fkKey(require_column_diff.fkColumns(fk.desired).fields))];
749
749
  if (keysToDrop.length > 0) {
750
750
  await adapter.dropForeignKeys(keysToDrop);
751
751
  droppedFks.push(...keysToDrop);
@@ -1611,7 +1611,7 @@ var SchemaSync = class {
1611
1611
  const fkDiff = facts.fkDiff;
1612
1612
  init.status = "alter";
1613
1613
  init.fkAdded = fkDiff.added.map((fk) => ({
1614
- fields: fk.fields,
1614
+ fields: require_column_diff.fkColumns(fk).fields,
1615
1615
  targetTable: fk.targetTable
1616
1616
  }));
1617
1617
  init.fkRemoved = fkDiff.removed.map((fk) => ({
@@ -1619,9 +1619,12 @@ var SchemaSync = class {
1619
1619
  targetTable: fk.targetTable
1620
1620
  }));
1621
1621
  init.fkChanged = fkDiff.changed.map((fk) => ({
1622
- fields: fk.desired.fields,
1622
+ fields: require_column_diff.fkColumns(fk.desired).fields,
1623
1623
  targetTable: fk.desired.targetTable,
1624
- details: buildFkChangeDetails(fk.desired, fk.existing)
1624
+ details: buildFkChangeDetails({
1625
+ ...fk.desired,
1626
+ targetFields: require_column_diff.fkColumns(fk.desired).targetFields
1627
+ }, fk.existing)
1625
1628
  }));
1626
1629
  }
1627
1630
  if (facts.diff?.primaryKeyChanged) facts.populated = await adapter.hasRows(pendingRename ? dbName : void 0) ?? "unknown";
@@ -1659,7 +1662,7 @@ var SchemaSync = class {
1659
1662
  addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but the referencing table "${child.name}" is renamed in the same run (from "${child.pendingRename}") — rename it in a separate run first, then retarget the foreign key and re-run.`);
1660
1663
  continue;
1661
1664
  }
1662
- if (!(child !== void 0 && [...child.readable.foreignKeys.values()].some((desired) => desired.targetTable === t.name && require_column_diff.fkKey(desired.fields) === require_column_diff.fkKey(fk.fields) && require_column_diff.fkKey(desired.targetFields) === require_column_diff.fkKey(pk.to)))) addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but "${fk.table}.${fk.fields.join(",")}" still references the old key — retarget the foreign key (or migrate manually) and re-run.`);
1665
+ if (!(child !== void 0 && [...child.readable.foreignKeys.values()].some((desired) => desired.targetTable === t.name && require_column_diff.fkKey(require_column_diff.fkColumns(desired).fields) === require_column_diff.fkKey(fk.fields) && require_column_diff.fkKey(require_column_diff.fkColumns(desired).targetFields) === require_column_diff.fkKey(pk.to)))) addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but "${fk.table}.${fk.fields.join(",")}" still references the old key — retarget the foreign key (or migrate manually) and re-run.`);
1663
1666
  }
1664
1667
  for (const f of t.readable.fieldDescriptors) if (!f.ignored && !f.isPrimaryKey && f.defaultValue?.kind === "fn" && f.defaultValue.fn === "increment") addRefusal(refusals, t.name, `"${t.name}.${f.physicalName}" is auto-increment but no longer part of the primary key; auto-increment columns must be primary-key columns.`);
1665
1668
  }
@@ -1674,6 +1677,7 @@ var SchemaSync = class {
1674
1677
  if (d.viewObjectKinds.get(view.tableName) === "table") addRefusal(refusals, view.tableName, `A physical table "${view.tableName}" exists where managed view "${view.tableName}" is declared — drop or rename it`);
1675
1678
  const fns = view.dbAdapter.aggregateFns();
1676
1679
  for (const col of view.getViewColumnMappings()) if (col.aggFn && !fns.has(col.aggFn)) addRefusal(refusals, view.tableName, `View "${view.tableName}" field "${col.viewPath}": aggregate "${col.aggFn}" is not supported by this adapter (aggregateFns())`);
1680
+ for (const problem of view.viewCapabilityProblems()) addRefusal(refusals, view.tableName, problem);
1677
1681
  for (const source of d.viewSources.get(view.tableName)) if (d.externalTargets.get(source) === false) addRefusal(refusals, view.tableName, `View "${view.tableName}" reads "${source}" which is neither in the sync inventory nor present in the database`);
1678
1682
  }
1679
1683
  if (!safe) for (const r of d.removed) {
package/dist/sync.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as TDbFieldMeta, Et as TTableOptionDiff, Ht as DbSpace, V as TDbForeignKey, bt as TPrimaryKeyChange, ct as TEnsureTableOptions, dt as TExistingTableOption, gn as TGenericLogger, ot as TDerivedChangeReason, q as TDbObjectKind, ut as TExistingForeignKey, xt as TReferencingForeignKey } from "./buckets-DYFu0eZ8.cjs";
2
- import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-w-Mym_3w.cjs";
1
+ import { Dt as TTableOptionDiff, Fn as TGenericLogger, Gt as DbSpace, J as TDbObjectKind, St as TReferencingForeignKey, V as TDbFieldMeta, dt as TExistingForeignKey, ft as TExistingTableOption, lt as TEnsureTableOptions, st as TDerivedChangeReason, xt as TPrimaryKeyChange } from "./buckets-CjdPipCC.cjs";
2
+ import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-DLqwVkKG.cjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
@@ -472,29 +472,6 @@ declare class SchemaSync {
472
472
  */
473
473
  declare function computeTableOptionDiff(desired: readonly TExistingTableOption[], existing: readonly TExistingTableOption[], destructiveKeys?: ReadonlySet<string>): TTableOptionDiff;
474
474
  //#endregion
475
- //#region src/schema/fk-diff.d.ts
476
- interface TForeignKeyDiff {
477
- /** FKs present in desired but not in stored snapshot (new). */
478
- added: TDbForeignKey[];
479
- /** FKs present in stored snapshot but not in desired (removed). */
480
- removed: TForeignKeySnapshot[];
481
- /** FKs where fields match but target, onDelete, or onUpdate differ. */
482
- changed: Array<{
483
- desired: TDbForeignKey;
484
- existing: TForeignKeySnapshot;
485
- }>;
486
- }
487
- /** Canonical key for an FK: sorted local field names, comma-joined. */
488
- declare function fkKey(fields: readonly string[]): string;
489
- /**
490
- * Compares desired FK constraints against stored snapshot to detect
491
- * additions, removals, and property changes (target table, target fields,
492
- * onDelete, onUpdate).
493
- */
494
- declare function computeForeignKeyDiff(desired: ReadonlyMap<string, TDbForeignKey>, existingSnapshot: readonly TForeignKeySnapshot[]): TForeignKeyDiff;
495
- /** Whether the FK diff contains any changes. */
496
- declare function hasForeignKeyChanges(diff: TForeignKeyDiff): boolean;
497
- //#endregion
498
475
  //#region src/schema/dependency-order.d.ts
499
476
  /** A directed edge `from → to`: `from` depends on `to` (child → parent). */
500
477
  type TDependencyEdge = readonly [from: string, to: string];
package/dist/sync.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as TDbFieldMeta, Et as TTableOptionDiff, Ht as DbSpace, V as TDbForeignKey, bt as TPrimaryKeyChange, ct as TEnsureTableOptions, dt as TExistingTableOption, gn as TGenericLogger, ot as TDerivedChangeReason, q as TDbObjectKind, ut as TExistingForeignKey, xt as TReferencingForeignKey } from "./buckets-DRycmhOW.mjs";
2
- import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-Q9UmWn5x.mjs";
1
+ import { Dt as TTableOptionDiff, Fn as TGenericLogger, Gt as DbSpace, J as TDbObjectKind, St as TReferencingForeignKey, V as TDbFieldMeta, dt as TExistingForeignKey, ft as TExistingTableOption, lt as TEnsureTableOptions, st as TDerivedChangeReason, xt as TPrimaryKeyChange } from "./buckets-DYopfd2Y.mjs";
2
+ import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-BtOmL4OW.mjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
@@ -472,29 +472,6 @@ declare class SchemaSync {
472
472
  */
473
473
  declare function computeTableOptionDiff(desired: readonly TExistingTableOption[], existing: readonly TExistingTableOption[], destructiveKeys?: ReadonlySet<string>): TTableOptionDiff;
474
474
  //#endregion
475
- //#region src/schema/fk-diff.d.ts
476
- interface TForeignKeyDiff {
477
- /** FKs present in desired but not in stored snapshot (new). */
478
- added: TDbForeignKey[];
479
- /** FKs present in stored snapshot but not in desired (removed). */
480
- removed: TForeignKeySnapshot[];
481
- /** FKs where fields match but target, onDelete, or onUpdate differ. */
482
- changed: Array<{
483
- desired: TDbForeignKey;
484
- existing: TForeignKeySnapshot;
485
- }>;
486
- }
487
- /** Canonical key for an FK: sorted local field names, comma-joined. */
488
- declare function fkKey(fields: readonly string[]): string;
489
- /**
490
- * Compares desired FK constraints against stored snapshot to detect
491
- * additions, removals, and property changes (target table, target fields,
492
- * onDelete, onUpdate).
493
- */
494
- declare function computeForeignKeyDiff(desired: ReadonlyMap<string, TDbForeignKey>, existingSnapshot: readonly TForeignKeySnapshot[]): TForeignKeyDiff;
495
- /** Whether the FK diff contains any changes. */
496
- declare function hasForeignKeyChanges(diff: TForeignKeyDiff): boolean;
497
- //#endregion
498
475
  //#region src/schema/dependency-order.d.ts
499
476
  /** A directed edge `from → to`: `from` depends on `to` (child → parent). */
500
477
  type TDependencyEdge = readonly [from: string, to: string];
package/dist/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as DbError } from "./db-error-D5uilS_A.mjs";
2
- import { a as hasForeignKeyChanges, c as computeTableHash, d as snapshotToExistingColumns, f as snapshotToExistingTableOptions, i as fkKey, l as computeTableSnapshot, o as canonicalizeQueryNode, p as viewSnapshotSources, r as computeForeignKeyDiff, s as computeSchemaHash, st as NoopLogger, t as computeColumnDiff, tt as aliasTargetOf, u as computeViewSnapshot } from "./column-diff-e2oHc71_.mjs";
2
+ import { a as computeTableHash, c as snapshotToExistingColumns, d as computeForeignKeyDiff, f as fkColumns, i as computeSchemaHash, l as snapshotToExistingTableOptions, lt as NoopLogger, m as hasForeignKeyChanges, o as computeTableSnapshot, p as fkKey, r as canonicalizeQueryNode, rt as aliasTargetOf, s as computeViewSnapshot, t as computeColumnDiff, u as viewSnapshotSources } from "./column-diff-CO06iQvI.mjs";
3
3
  //#region src/schema/table-option-diff.ts
4
4
  /**
5
5
  * Computes the difference between desired and existing table options.
@@ -744,7 +744,7 @@ async function executeSyncTable(facts, safe, deps, exec) {
744
744
  };
745
745
  } else {
746
746
  if (hasFkChanges && fkDiff && adapter.dropForeignKeys) {
747
- const keysToDrop = [...fkDiff.removed.map((fk) => fkKey(fk.fields)), ...fkDiff.changed.map((fk) => fkKey(fk.desired.fields))];
747
+ const keysToDrop = [...fkDiff.removed.map((fk) => fkKey(fk.fields)), ...fkDiff.changed.map((fk) => fkKey(fkColumns(fk.desired).fields))];
748
748
  if (keysToDrop.length > 0) {
749
749
  await adapter.dropForeignKeys(keysToDrop);
750
750
  droppedFks.push(...keysToDrop);
@@ -1610,7 +1610,7 @@ var SchemaSync = class {
1610
1610
  const fkDiff = facts.fkDiff;
1611
1611
  init.status = "alter";
1612
1612
  init.fkAdded = fkDiff.added.map((fk) => ({
1613
- fields: fk.fields,
1613
+ fields: fkColumns(fk).fields,
1614
1614
  targetTable: fk.targetTable
1615
1615
  }));
1616
1616
  init.fkRemoved = fkDiff.removed.map((fk) => ({
@@ -1618,9 +1618,12 @@ var SchemaSync = class {
1618
1618
  targetTable: fk.targetTable
1619
1619
  }));
1620
1620
  init.fkChanged = fkDiff.changed.map((fk) => ({
1621
- fields: fk.desired.fields,
1621
+ fields: fkColumns(fk.desired).fields,
1622
1622
  targetTable: fk.desired.targetTable,
1623
- details: buildFkChangeDetails(fk.desired, fk.existing)
1623
+ details: buildFkChangeDetails({
1624
+ ...fk.desired,
1625
+ targetFields: fkColumns(fk.desired).targetFields
1626
+ }, fk.existing)
1624
1627
  }));
1625
1628
  }
1626
1629
  if (facts.diff?.primaryKeyChanged) facts.populated = await adapter.hasRows(pendingRename ? dbName : void 0) ?? "unknown";
@@ -1658,7 +1661,7 @@ var SchemaSync = class {
1658
1661
  addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but the referencing table "${child.name}" is renamed in the same run (from "${child.pendingRename}") — rename it in a separate run first, then retarget the foreign key and re-run.`);
1659
1662
  continue;
1660
1663
  }
1661
- if (!(child !== void 0 && [...child.readable.foreignKeys.values()].some((desired) => desired.targetTable === t.name && fkKey(desired.fields) === fkKey(fk.fields) && fkKey(desired.targetFields) === fkKey(pk.to)))) addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but "${fk.table}.${fk.fields.join(",")}" still references the old key — retarget the foreign key (or migrate manually) and re-run.`);
1664
+ if (!(child !== void 0 && [...child.readable.foreignKeys.values()].some((desired) => desired.targetTable === t.name && fkKey(fkColumns(desired).fields) === fkKey(fk.fields) && fkKey(fkColumns(desired).targetFields) === fkKey(pk.to)))) addRefusal(refusals, t.name, `Primary key of "${t.name}" changed ${label} but "${fk.table}.${fk.fields.join(",")}" still references the old key — retarget the foreign key (or migrate manually) and re-run.`);
1662
1665
  }
1663
1666
  for (const f of t.readable.fieldDescriptors) if (!f.ignored && !f.isPrimaryKey && f.defaultValue?.kind === "fn" && f.defaultValue.fn === "increment") addRefusal(refusals, t.name, `"${t.name}.${f.physicalName}" is auto-increment but no longer part of the primary key; auto-increment columns must be primary-key columns.`);
1664
1667
  }
@@ -1673,6 +1676,7 @@ var SchemaSync = class {
1673
1676
  if (d.viewObjectKinds.get(view.tableName) === "table") addRefusal(refusals, view.tableName, `A physical table "${view.tableName}" exists where managed view "${view.tableName}" is declared — drop or rename it`);
1674
1677
  const fns = view.dbAdapter.aggregateFns();
1675
1678
  for (const col of view.getViewColumnMappings()) if (col.aggFn && !fns.has(col.aggFn)) addRefusal(refusals, view.tableName, `View "${view.tableName}" field "${col.viewPath}": aggregate "${col.aggFn}" is not supported by this adapter (aggregateFns())`);
1679
+ for (const problem of view.viewCapabilityProblems()) addRefusal(refusals, view.tableName, problem);
1676
1680
  for (const source of d.viewSources.get(view.tableName)) if (d.externalTargets.get(source) === false) addRefusal(refusals, view.tableName, `View "${view.tableName}" reads "${source}" which is neither in the sync inventory nor present in the database`);
1677
1681
  }
1678
1682
  if (!safe) for (const r of d.removed) {