@atscript/db 0.1.136 → 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.
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-y4EzoX7D.d.mts → buckets-BFG2RYRW.d.mts} +603 -15
- package/dist/{buckets-kKY0RKYN.d.cts → buckets-C-27xmtq.d.cts} +603 -15
- package/dist/column-diff-BmqvgBWw.d.cts +24 -0
- package/dist/{db-view-BrAtbaOx.mjs → column-diff-BwOA5101.mjs} +423 -46
- package/dist/{db-view-BAzhEUxB.cjs → column-diff-CgxgFKzx.cjs} +494 -45
- package/dist/column-diff-DPkbZIVE.d.mts +24 -0
- package/dist/index.cjs +64 -54
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +18 -9
- package/dist/plugin.cjs +2 -2
- package/dist/plugin.mjs +2 -2
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/{relation-helpers-BVjQflL_.d.cts → relation-helpers-D3Zu0Mta.d.mts} +1 -1
- package/dist/{relation-helpers-D4haLo1n.d.mts → relation-helpers-DxrvS6ar.d.cts} +1 -1
- package/dist/sync.cjs +33 -343
- package/dist/sync.d.cts +7 -15
- package/dist/sync.d.mts +7 -15
- package/dist/sync.mjs +1 -311
- package/package.json +1 -1
- package/dist/db-space-BX4K3vb6.d.mts +0 -558
- package/dist/db-space-ClG41vJh.d.cts +0 -558
package/dist/sync.mjs
CHANGED
|
@@ -1,314 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/schema/schema-hash.ts
|
|
3
|
-
/** Extracts sorted field snapshots from a readable's field descriptors. */
|
|
4
|
-
function extractFieldSnapshots(fields, typeMapper) {
|
|
5
|
-
return fields.filter((f) => !f.ignored).map((f) => {
|
|
6
|
-
const snap = {
|
|
7
|
-
physicalName: f.physicalName,
|
|
8
|
-
designType: f.designType,
|
|
9
|
-
optional: f.optional,
|
|
10
|
-
isPrimaryKey: f.isPrimaryKey,
|
|
11
|
-
storage: f.storage
|
|
12
|
-
};
|
|
13
|
-
if (f.defaultValue) snap.defaultValue = f.defaultValue;
|
|
14
|
-
if (typeMapper) snap.mappedType = typeMapper(f);
|
|
15
|
-
if (f.encrypted) snap.encrypted = true;
|
|
16
|
-
return snap;
|
|
17
|
-
}).toSorted((a, b) => a.physicalName.localeCompare(b.physicalName));
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Extracts a canonical, serializable snapshot from a readable's metadata.
|
|
21
|
-
* Sorted deterministically so the hash is stable across runs.
|
|
22
|
-
*
|
|
23
|
-
* @param readable - The table/view readable.
|
|
24
|
-
* @param typeMapper - Optional adapter-specific type mapper. When provided,
|
|
25
|
-
* each field's mapped type (e.g., "VARCHAR(255)") is stored in the snapshot
|
|
26
|
-
* for precise type change detection.
|
|
27
|
-
*/
|
|
28
|
-
function computeTableSnapshot(readable, typeMapper, tableOptions) {
|
|
29
|
-
const fields = extractFieldSnapshots(readable.fieldDescriptors, typeMapper);
|
|
30
|
-
const indexes = [...readable.indexes.values()].map((idx) => ({
|
|
31
|
-
key: idx.key,
|
|
32
|
-
type: idx.type,
|
|
33
|
-
fields: idx.fields.map((f) => ({
|
|
34
|
-
name: f.name,
|
|
35
|
-
sort: f.sort
|
|
36
|
-
}))
|
|
37
|
-
})).toSorted((a, b) => a.key.localeCompare(b.key));
|
|
38
|
-
const foreignKeys = [...readable.foreignKeys.values()].map((fk) => ({
|
|
39
|
-
fields: [...fk.fields].toSorted(),
|
|
40
|
-
targetTable: fk.targetTable,
|
|
41
|
-
targetFields: [...fk.targetFields].toSorted(),
|
|
42
|
-
onDelete: fk.onDelete,
|
|
43
|
-
onUpdate: fk.onUpdate
|
|
44
|
-
})).toSorted((a, b) => a.fields.join(",").localeCompare(b.fields.join(",")));
|
|
45
|
-
const snapshot = {
|
|
46
|
-
tableName: readable.tableName,
|
|
47
|
-
fields,
|
|
48
|
-
indexes,
|
|
49
|
-
foreignKeys
|
|
50
|
-
};
|
|
51
|
-
if (tableOptions?.length) snapshot.tableOptions = [...tableOptions].toSorted((a, b) => a.key.localeCompare(b.key));
|
|
52
|
-
return snapshot;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Extracts a canonical, serializable snapshot from a view's metadata.
|
|
56
|
-
* Captures view plan (entry table, joins, filter, materialization) for
|
|
57
|
-
* detecting view definition changes.
|
|
58
|
-
*/
|
|
59
|
-
function computeViewSnapshot(view) {
|
|
60
|
-
const fields = extractFieldSnapshots(view.fieldDescriptors);
|
|
61
|
-
if (view.isExternal) return {
|
|
62
|
-
tableName: view.tableName,
|
|
63
|
-
viewType: "E",
|
|
64
|
-
fields
|
|
65
|
-
};
|
|
66
|
-
const plan = view.viewPlan;
|
|
67
|
-
const qualify = (ref) => view.resolveFieldRef(ref, (n) => n);
|
|
68
|
-
const canonical = (node) => JSON.stringify(canonicalizeQueryNode(node, qualify));
|
|
69
|
-
const columns = view.getViewColumnMappings().map((m) => {
|
|
70
|
-
const col = {
|
|
71
|
-
column: m.viewColumn,
|
|
72
|
-
sourceTable: m.sourceTable,
|
|
73
|
-
sourceColumn: m.sourceColumn
|
|
74
|
-
};
|
|
75
|
-
if (m.json) {
|
|
76
|
-
col.jsonPath = JSON.stringify(m.json.path);
|
|
77
|
-
col.jsonType = m.json.type;
|
|
78
|
-
}
|
|
79
|
-
if (m.aggFn) col.aggFn = m.aggFn;
|
|
80
|
-
if (m.aggField) col.aggField = m.aggField;
|
|
81
|
-
if (m.aggFilter) col.aggFilter = canonical(m.aggFilter);
|
|
82
|
-
return col;
|
|
83
|
-
}).toSorted((a, b) => a.column < b.column ? -1 : a.column > b.column ? 1 : 0);
|
|
84
|
-
const result = {
|
|
85
|
-
tableName: view.tableName,
|
|
86
|
-
viewType: plan.materialized ? "M" : "V",
|
|
87
|
-
entryTable: plan.entryTable,
|
|
88
|
-
joinTables: plan.joins.map((j) => {
|
|
89
|
-
const join = {
|
|
90
|
-
targetTable: j.targetTable,
|
|
91
|
-
condition: canonical(j.condition)
|
|
92
|
-
};
|
|
93
|
-
if (j.kind === "left") join.kind = "left";
|
|
94
|
-
return join;
|
|
95
|
-
}),
|
|
96
|
-
columns
|
|
97
|
-
};
|
|
98
|
-
if (plan.filter) result.filterHash = fnv1a(canonical(plan.filter));
|
|
99
|
-
if (plan.having) result.havingHash = fnv1a(canonical(plan.having));
|
|
100
|
-
if (plan.materialized) result.materialized = true;
|
|
101
|
-
return {
|
|
102
|
-
...result,
|
|
103
|
-
fields
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Converts a view predicate (join condition, `@db.view.filter`,
|
|
108
|
-
* `@db.view.having`) into a serializable structure whose JSON is a stable
|
|
109
|
-
* function of its MEANING: field refs become `qualify(ref)` — the view's
|
|
110
|
-
* `resolveFieldRef(ref, (n) => n)`, i.e. `"<table>.<field>"`, so a predicate
|
|
111
|
-
* retargeted to another table with the same field name changes — operators
|
|
112
|
-
* and literal values are kept as-is, `$and`/`$or` keep declaration order, and
|
|
113
|
-
* no function references survive. Two identical models produce byte-identical
|
|
114
|
-
* JSON.
|
|
115
|
-
* @since 0.1.128
|
|
116
|
-
*/
|
|
117
|
-
function canonicalizeQueryNode(node, qualify) {
|
|
118
|
-
if ("$and" in node) return { and: node.$and.map((n) => canonicalizeQueryNode(n, qualify)) };
|
|
119
|
-
if ("$or" in node) return { or: node.$or.map((n) => canonicalizeQueryNode(n, qualify)) };
|
|
120
|
-
if ("$not" in node) return { not: canonicalizeQueryNode(node.$not, qualify) };
|
|
121
|
-
const comp = node;
|
|
122
|
-
const out = {
|
|
123
|
-
l: qualify(comp.left),
|
|
124
|
-
op: comp.op
|
|
125
|
-
};
|
|
126
|
-
if (comp.right !== void 0) out.r = comp.right !== null && typeof comp.right === "object" && "field" in comp.right ? { f: qualify(comp.right) } : comp.right;
|
|
127
|
-
return out;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Computes a deterministic hash string from multiple table snapshots.
|
|
131
|
-
* Uses FNV-1a for speed — not cryptographic, just needs stability + collision resistance.
|
|
132
|
-
*/
|
|
133
|
-
function computeSchemaHash(snapshots) {
|
|
134
|
-
const sorted = [...snapshots].toSorted((a, b) => a.tableName.localeCompare(b.tableName));
|
|
135
|
-
return fnv1a(JSON.stringify(sorted));
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Computes a hash for a single table/view snapshot.
|
|
139
|
-
* Used for per-table change detection via stored snapshots.
|
|
140
|
-
*/
|
|
141
|
-
function computeTableHash(snapshot) {
|
|
142
|
-
return fnv1a(JSON.stringify(snapshot));
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Converts stored snapshot fields to `TExistingColumn[]` format
|
|
146
|
-
* for use with `computeColumnDiff`. Used by adapters that lack
|
|
147
|
-
* native column introspection (e.g., MongoDB).
|
|
148
|
-
*
|
|
149
|
-
* The `type` field uses `mappedType` when available (adapter-specific),
|
|
150
|
-
* falling back to `designType`.
|
|
151
|
-
*/
|
|
152
|
-
function snapshotToExistingColumns(snapshot) {
|
|
153
|
-
return snapshot.fields.map((f) => ({
|
|
154
|
-
name: f.physicalName,
|
|
155
|
-
type: f.mappedType ?? f.designType,
|
|
156
|
-
notnull: !f.optional,
|
|
157
|
-
pk: f.isPrimaryKey,
|
|
158
|
-
dflt_value: serializeDefaultValue(f.defaultValue)
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Extracts table options from a stored snapshot for diff comparison.
|
|
163
|
-
* Used as fallback when an adapter lacks native table option introspection.
|
|
164
|
-
*/
|
|
165
|
-
function snapshotToExistingTableOptions(snapshot) {
|
|
166
|
-
return snapshot.tableOptions ?? [];
|
|
167
|
-
}
|
|
168
|
-
/** Serializes a TDbDefaultValue to a comparable string. */
|
|
169
|
-
function serializeDefaultValue(dv) {
|
|
170
|
-
if (!dv) return;
|
|
171
|
-
if (dv.kind === "value") return dv.value;
|
|
172
|
-
return `fn:${dv.fn}`;
|
|
173
|
-
}
|
|
174
|
-
/** FNV-1a 32-bit hash → hex string */
|
|
175
|
-
function fnv1a(str) {
|
|
176
|
-
let hash = 2166136261;
|
|
177
|
-
for (let i = 0; i < str.length; i++) {
|
|
178
|
-
hash ^= str.codePointAt(i);
|
|
179
|
-
hash = Math.imul(hash, 16777619);
|
|
180
|
-
}
|
|
181
|
-
return Math.trunc(hash).toString(16).padStart(8, "0");
|
|
182
|
-
}
|
|
183
|
-
//#endregion
|
|
184
|
-
//#region src/schema/fk-diff.ts
|
|
185
|
-
/** Canonical key for an FK: sorted local field names, comma-joined. */
|
|
186
|
-
function fkKey(fields) {
|
|
187
|
-
return [...fields].toSorted().join(",");
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Compares desired FK constraints against stored snapshot to detect
|
|
191
|
-
* additions, removals, and property changes (target table, target fields,
|
|
192
|
-
* onDelete, onUpdate).
|
|
193
|
-
*/
|
|
194
|
-
function computeForeignKeyDiff(desired, existingSnapshot) {
|
|
195
|
-
const added = [];
|
|
196
|
-
const removed = [];
|
|
197
|
-
const changed = [];
|
|
198
|
-
const existingByKey = /* @__PURE__ */ new Map();
|
|
199
|
-
for (const fk of existingSnapshot) existingByKey.set(fkKey(fk.fields), fk);
|
|
200
|
-
const desiredKeys = /* @__PURE__ */ new Set();
|
|
201
|
-
for (const fk of desired.values()) {
|
|
202
|
-
const key = fkKey(fk.fields);
|
|
203
|
-
desiredKeys.add(key);
|
|
204
|
-
const existing = existingByKey.get(key);
|
|
205
|
-
if (!existing) added.push(fk);
|
|
206
|
-
else if (fkPropertiesDiffer(fk, existing)) changed.push({
|
|
207
|
-
desired: fk,
|
|
208
|
-
existing
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
for (const [key, fk] of existingByKey) if (!desiredKeys.has(key)) removed.push(fk);
|
|
212
|
-
return {
|
|
213
|
-
added,
|
|
214
|
-
removed,
|
|
215
|
-
changed
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
/** Whether the FK diff contains any changes. */
|
|
219
|
-
function hasForeignKeyChanges(diff) {
|
|
220
|
-
return diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
|
|
221
|
-
}
|
|
222
|
-
function fkPropertiesDiffer(desired, existing) {
|
|
223
|
-
if (desired.targetTable !== existing.targetTable) return true;
|
|
224
|
-
if (fkKey(desired.targetFields) !== fkKey(existing.targetFields)) return true;
|
|
225
|
-
if ((desired.onDelete ?? void 0) !== (existing.onDelete ?? void 0)) return true;
|
|
226
|
-
if ((desired.onUpdate ?? void 0) !== (existing.onUpdate ?? void 0)) return true;
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
//#endregion
|
|
230
|
-
//#region src/schema/column-diff.ts
|
|
231
|
-
/**
|
|
232
|
-
* Computes the difference between desired schema fields and existing database columns.
|
|
233
|
-
*
|
|
234
|
-
* @param desired - Field descriptors from the Atscript type (after flattening).
|
|
235
|
-
* @param existing - Columns currently in the database (from introspection).
|
|
236
|
-
* @param typeMapper - Optional function to map field metadata to DB-native type strings.
|
|
237
|
-
* Receives the full field meta (design type, annotations, PK status, etc.)
|
|
238
|
-
* so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
|
|
239
|
-
* Required for type change detection.
|
|
240
|
-
*/
|
|
241
|
-
function computeColumnDiff(desired, existing, typeMapper) {
|
|
242
|
-
const existingByName = new Map(existing.map((c) => [c.name, c]));
|
|
243
|
-
const desiredByName = /* @__PURE__ */ new Map();
|
|
244
|
-
const renamedOldNames = /* @__PURE__ */ new Set();
|
|
245
|
-
const added = [];
|
|
246
|
-
const renamed = [];
|
|
247
|
-
const typeChanged = [];
|
|
248
|
-
const nullableChanged = [];
|
|
249
|
-
const defaultChanged = [];
|
|
250
|
-
const conflicts = [];
|
|
251
|
-
for (const field of desired) {
|
|
252
|
-
if (field.ignored) continue;
|
|
253
|
-
desiredByName.set(field.physicalName, field);
|
|
254
|
-
const existingCol = existingByName.get(field.physicalName);
|
|
255
|
-
if (existingCol) if (field.renamedFrom && existingByName.has(field.renamedFrom)) {
|
|
256
|
-
conflicts.push({
|
|
257
|
-
field,
|
|
258
|
-
oldName: field.renamedFrom,
|
|
259
|
-
conflictsWith: field.physicalName
|
|
260
|
-
});
|
|
261
|
-
renamedOldNames.add(field.renamedFrom);
|
|
262
|
-
} else {
|
|
263
|
-
if (typeMapper) {
|
|
264
|
-
if (typeMapper(field).toUpperCase() !== existingCol.type.toUpperCase()) typeChanged.push({
|
|
265
|
-
field,
|
|
266
|
-
existingType: existingCol.type
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
if (!field.isPrimaryKey && !existingCol.pk) {
|
|
270
|
-
const desiredNotNull = !field.optional;
|
|
271
|
-
if (existingCol.notnull !== desiredNotNull) nullableChanged.push({
|
|
272
|
-
field,
|
|
273
|
-
wasNullable: !existingCol.notnull
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
const desiredDefault = serializeDefaultValue(field.defaultValue);
|
|
277
|
-
if (existingCol.dflt_value !== void 0 && existingCol.dflt_value !== desiredDefault) defaultChanged.push({
|
|
278
|
-
field,
|
|
279
|
-
oldDefault: existingCol.dflt_value,
|
|
280
|
-
newDefault: desiredDefault
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
else if (field.renamedFrom && existingByName.has(field.renamedFrom)) {
|
|
284
|
-
renamed.push({
|
|
285
|
-
field,
|
|
286
|
-
oldName: field.renamedFrom
|
|
287
|
-
});
|
|
288
|
-
renamedOldNames.add(field.renamedFrom);
|
|
289
|
-
} else added.push(field);
|
|
290
|
-
}
|
|
291
|
-
const diff = {
|
|
292
|
-
added,
|
|
293
|
-
removed: existing.filter((c) => !desiredByName.has(c.name) && !renamedOldNames.has(c.name)),
|
|
294
|
-
renamed,
|
|
295
|
-
typeChanged,
|
|
296
|
-
nullableChanged,
|
|
297
|
-
defaultChanged,
|
|
298
|
-
conflicts
|
|
299
|
-
};
|
|
300
|
-
if (existing.length > 0) {
|
|
301
|
-
const newNameByOld = new Map(renamed.map((r) => [r.oldName, r.field.physicalName]));
|
|
302
|
-
const from = existing.filter((c) => c.pk).map((c) => newNameByOld.get(c.name) ?? c.name);
|
|
303
|
-
const to = desired.filter((f) => !f.ignored && f.isPrimaryKey).map((f) => f.physicalName);
|
|
304
|
-
if (fkKey(from) !== fkKey(to)) diff.primaryKeyChanged = {
|
|
305
|
-
from,
|
|
306
|
-
to
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
return diff;
|
|
310
|
-
}
|
|
311
|
-
//#endregion
|
|
1
|
+
import { a as hasForeignKeyChanges, c as computeTableHash, d as snapshotToExistingColumns, f as snapshotToExistingTableOptions, i as fkKey, l as computeTableSnapshot, nt as NoopLogger, o as canonicalizeQueryNode, r as computeForeignKeyDiff, s as computeSchemaHash, t as computeColumnDiff, u as computeViewSnapshot } from "./column-diff-BwOA5101.mjs";
|
|
312
2
|
//#region src/schema/table-option-diff.ts
|
|
313
3
|
/**
|
|
314
4
|
* Computes the difference between desired and existing table options.
|