@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/sync.mjs CHANGED
@@ -1,314 +1,4 @@
1
- import { W as NoopLogger } from "./db-view-BrAtbaOx.mjs";
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db",
3
- "version": "0.1.136",
3
+ "version": "0.1.137",
4
4
  "description": "Database adapter utilities for atscript.",
5
5
  "keywords": [
6
6
  "atscript",