@famgia/omnify-atlas 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -25,8 +25,10 @@ __export(index_exports, {
25
25
  addMigrationRecord: () => addMigrationRecord,
26
26
  applySchema: () => applySchema,
27
27
  buildSchemaHashes: () => buildSchemaHashes,
28
+ buildSchemaSnapshots: () => buildSchemaSnapshots,
28
29
  checkAtlasVersion: () => checkAtlasVersion,
29
30
  compareSchemas: () => compareSchemas,
31
+ compareSchemasDeep: () => compareSchemasDeep,
30
32
  computeHash: () => computeHash,
31
33
  computeSchemaHash: () => computeSchemaHash,
32
34
  createEmptyLockFile: () => createEmptyLockFile,
@@ -39,15 +41,19 @@ __export(index_exports, {
39
41
  getPrimaryKeyType: () => getPrimaryKeyType,
40
42
  getTimestampType: () => getTimestampType,
41
43
  hasBlockingIssues: () => hasBlockingIssues,
44
+ isLockFileV2: () => isLockFileV2,
42
45
  mapPropertyToSql: () => mapPropertyToSql,
43
46
  parseDiffOutput: () => parseDiffOutput,
44
47
  previewSchemaChanges: () => previewSchemaChanges,
45
48
  propertyNameToColumnName: () => propertyNameToColumnName,
49
+ propertyToSnapshot: () => propertyToSnapshot,
46
50
  readLockFile: () => readLockFile,
47
51
  renderHcl: () => renderHcl,
48
52
  runAtlasDiff: () => runAtlasDiff,
49
53
  schemaNameToTableName: () => schemaNameToTableName,
54
+ schemaToSnapshot: () => schemaToSnapshot,
50
55
  updateLockFile: () => updateLockFile,
56
+ updateLockFileV1: () => updateLockFileV1,
51
57
  validateHcl: () => validateHcl,
52
58
  writeLockFile: () => writeLockFile
53
59
  });
@@ -57,7 +63,7 @@ module.exports = __toCommonJS(index_exports);
57
63
  var import_node_crypto = require("crypto");
58
64
  var import_promises = require("fs/promises");
59
65
  var LOCK_FILE_NAME = ".omnify.lock";
60
- var LOCK_FILE_VERSION = 1;
66
+ var LOCK_FILE_VERSION = 2;
61
67
  function computeHash(content) {
62
68
  return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
63
69
  }
@@ -80,13 +86,75 @@ function createEmptyLockFile(driver) {
80
86
  migrations: []
81
87
  };
82
88
  }
89
+ function propertyToSnapshot(property) {
90
+ const prop = property;
91
+ return {
92
+ type: prop.type,
93
+ nullable: prop.nullable,
94
+ unique: prop.unique,
95
+ default: prop.default,
96
+ length: prop.length,
97
+ unsigned: prop.unsigned,
98
+ precision: prop.precision,
99
+ scale: prop.scale,
100
+ enum: prop.enum,
101
+ relation: prop.relation,
102
+ target: prop.target,
103
+ onDelete: prop.onDelete,
104
+ onUpdate: prop.onUpdate,
105
+ mappedBy: prop.mappedBy,
106
+ joinTable: prop.joinTable,
107
+ // renamedFrom is kept in snapshot for comparison (rename detection),
108
+ // but will be stripped when writing to lock file.
109
+ renamedFrom: prop.renamedFrom
110
+ };
111
+ }
112
+ function schemaToSnapshot(schema, hash, modifiedAt) {
113
+ const properties = {};
114
+ if (schema.properties) {
115
+ for (const [name, prop] of Object.entries(schema.properties)) {
116
+ properties[name] = propertyToSnapshot(prop);
117
+ }
118
+ }
119
+ const opts = schema.options;
120
+ let indexes;
121
+ if (opts?.indexes && opts.indexes.length > 0) {
122
+ indexes = opts.indexes.map((idx) => ({
123
+ columns: idx.columns,
124
+ unique: idx.unique ?? false,
125
+ name: idx.name
126
+ }));
127
+ }
128
+ let uniqueConstraints;
129
+ if (opts?.unique) {
130
+ uniqueConstraints = Array.isArray(opts.unique[0]) ? opts.unique : [opts.unique];
131
+ }
132
+ return {
133
+ name: schema.name,
134
+ kind: schema.kind ?? "object",
135
+ hash,
136
+ relativePath: schema.relativePath,
137
+ modifiedAt,
138
+ properties,
139
+ primaryKeyType: opts?.primaryKeyType,
140
+ timestamps: opts?.timestamps,
141
+ softDelete: opts?.softDelete,
142
+ indexes,
143
+ uniqueConstraints,
144
+ values: schema.values
145
+ };
146
+ }
147
+ function isLockFileV2(lockFile) {
148
+ return lockFile.version === 2;
149
+ }
83
150
  async function readLockFile(lockFilePath) {
84
151
  try {
85
152
  const content = await (0, import_promises.readFile)(lockFilePath, "utf8");
86
153
  const parsed = JSON.parse(content);
87
- if (parsed.version !== LOCK_FILE_VERSION) {
154
+ const lockFile = parsed;
155
+ if (lockFile.version !== 1 && lockFile.version !== 2) {
88
156
  throw new Error(
89
- `Lock file version mismatch: expected ${LOCK_FILE_VERSION}, got ${parsed.version}`
157
+ `Lock file version mismatch: expected 1 or 2, got ${lockFile.version}`
90
158
  );
91
159
  }
92
160
  return parsed;
@@ -121,6 +189,138 @@ async function buildSchemaHashes(schemas) {
121
189
  }
122
190
  return hashes;
123
191
  }
192
+ async function buildSchemaSnapshots(schemas) {
193
+ const snapshots = {};
194
+ for (const [name, schema] of Object.entries(schemas)) {
195
+ const hash = computeSchemaHash(schema);
196
+ let modifiedAt;
197
+ try {
198
+ const stats = await (0, import_promises.stat)(schema.filePath);
199
+ modifiedAt = stats.mtime.toISOString();
200
+ } catch {
201
+ modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
202
+ }
203
+ snapshots[name] = schemaToSnapshot(schema, hash, modifiedAt);
204
+ }
205
+ return snapshots;
206
+ }
207
+ function diffPropertySnapshots(prev, curr) {
208
+ const modifications = [];
209
+ if (prev.type !== curr.type) modifications.push("type");
210
+ if (prev.nullable !== curr.nullable) modifications.push("nullable");
211
+ if (prev.unique !== curr.unique) modifications.push("unique");
212
+ if (JSON.stringify(prev.default) !== JSON.stringify(curr.default)) modifications.push("default");
213
+ if (prev.length !== curr.length) modifications.push("length");
214
+ if (prev.unsigned !== curr.unsigned) modifications.push("unsigned");
215
+ if (prev.precision !== curr.precision) modifications.push("precision");
216
+ if (prev.scale !== curr.scale) modifications.push("scale");
217
+ if (JSON.stringify(prev.enum) !== JSON.stringify(curr.enum)) modifications.push("enum");
218
+ if (prev.relation !== curr.relation) modifications.push("relation");
219
+ if (prev.target !== curr.target) modifications.push("target");
220
+ if (prev.onDelete !== curr.onDelete) modifications.push("onDelete");
221
+ if (prev.onUpdate !== curr.onUpdate) modifications.push("onUpdate");
222
+ if (prev.mappedBy !== curr.mappedBy) modifications.push("mappedBy");
223
+ return modifications;
224
+ }
225
+ function diffIndexes(prev, curr) {
226
+ const changes = [];
227
+ const prevIndexes = prev ?? [];
228
+ const currIndexes = curr ?? [];
229
+ const indexKey = (idx) => `${idx.columns.join(",")}:${idx.unique}`;
230
+ const prevKeys = new Map(prevIndexes.map((idx) => [indexKey(idx), idx]));
231
+ const currKeys = new Map(currIndexes.map((idx) => [indexKey(idx), idx]));
232
+ for (const [key, idx] of currKeys) {
233
+ if (!prevKeys.has(key)) {
234
+ changes.push({ changeType: "added", index: idx });
235
+ }
236
+ }
237
+ for (const [key, idx] of prevKeys) {
238
+ if (!currKeys.has(key)) {
239
+ changes.push({ changeType: "removed", index: idx });
240
+ }
241
+ }
242
+ return changes;
243
+ }
244
+ function diffSchemaSnapshots(prev, curr) {
245
+ const columnChanges = [];
246
+ const prevProps = prev.properties;
247
+ const currProps = curr.properties;
248
+ const prevNames = new Set(Object.keys(prevProps));
249
+ const currNames = new Set(Object.keys(currProps));
250
+ const renamedNewNames = /* @__PURE__ */ new Set();
251
+ const renamedOldNames = /* @__PURE__ */ new Set();
252
+ for (const [name, currProp] of Object.entries(currProps)) {
253
+ if (currProp.renamedFrom && prevProps[currProp.renamedFrom]) {
254
+ const prevProp = prevProps[currProp.renamedFrom];
255
+ const mods = diffPropertySnapshots(prevProp, currProp);
256
+ const filteredMods = mods.filter((m) => m !== "renamedFrom");
257
+ columnChanges.push({
258
+ column: name,
259
+ changeType: "renamed",
260
+ previousColumn: currProp.renamedFrom,
261
+ previousDef: prevProp,
262
+ currentDef: currProp,
263
+ modifications: filteredMods.length > 0 ? filteredMods : void 0
264
+ });
265
+ renamedNewNames.add(name);
266
+ renamedOldNames.add(currProp.renamedFrom);
267
+ }
268
+ }
269
+ for (const name of currNames) {
270
+ if (!prevNames.has(name) && !renamedNewNames.has(name)) {
271
+ columnChanges.push({
272
+ column: name,
273
+ changeType: "added",
274
+ currentDef: currProps[name]
275
+ });
276
+ }
277
+ }
278
+ for (const name of prevNames) {
279
+ if (!currNames.has(name) && !renamedOldNames.has(name)) {
280
+ columnChanges.push({
281
+ column: name,
282
+ changeType: "removed",
283
+ previousDef: prevProps[name]
284
+ });
285
+ }
286
+ }
287
+ for (const name of currNames) {
288
+ if (prevNames.has(name) && !renamedNewNames.has(name)) {
289
+ const prevProp = prevProps[name];
290
+ const currProp = currProps[name];
291
+ const mods = diffPropertySnapshots(prevProp, currProp);
292
+ if (mods.length > 0) {
293
+ columnChanges.push({
294
+ column: name,
295
+ changeType: "modified",
296
+ previousDef: prevProp,
297
+ currentDef: currProp,
298
+ modifications: mods
299
+ });
300
+ }
301
+ }
302
+ }
303
+ const indexChanges = diffIndexes(prev.indexes, curr.indexes);
304
+ const optionChanges = {};
305
+ let hasOptionChanges = false;
306
+ if (prev.timestamps !== curr.timestamps) {
307
+ optionChanges.timestamps = { from: prev.timestamps, to: curr.timestamps };
308
+ hasOptionChanges = true;
309
+ }
310
+ if (prev.softDelete !== curr.softDelete) {
311
+ optionChanges.softDelete = { from: prev.softDelete, to: curr.softDelete };
312
+ hasOptionChanges = true;
313
+ }
314
+ if (prev.primaryKeyType !== curr.primaryKeyType) {
315
+ optionChanges.primaryKeyType = { from: prev.primaryKeyType, to: curr.primaryKeyType };
316
+ hasOptionChanges = true;
317
+ }
318
+ return {
319
+ columnChanges: columnChanges.length > 0 ? columnChanges : void 0,
320
+ indexChanges: indexChanges.length > 0 ? indexChanges : void 0,
321
+ optionChanges: hasOptionChanges ? optionChanges : void 0
322
+ };
323
+ }
124
324
  function compareSchemas(currentHashes, lockFile) {
125
325
  const changes = [];
126
326
  const unchanged = [];
@@ -175,11 +375,95 @@ function compareSchemas(currentHashes, lockFile) {
175
375
  unchanged
176
376
  };
177
377
  }
178
- function updateLockFile(existingLockFile, currentHashes, driver) {
378
+ function compareSchemasDeep(currentSnapshots, lockFile) {
379
+ const changes = [];
380
+ const unchanged = [];
381
+ const previousSnapshots = lockFile?.schemas ?? {};
382
+ const previousNames = new Set(Object.keys(previousSnapshots));
383
+ const currentNames = new Set(Object.keys(currentSnapshots));
384
+ for (const name of currentNames) {
385
+ if (!previousNames.has(name)) {
386
+ const current = currentSnapshots[name];
387
+ if (current) {
388
+ changes.push({
389
+ schemaName: name,
390
+ changeType: "added",
391
+ currentHash: current.hash
392
+ });
393
+ }
394
+ }
395
+ }
396
+ for (const name of previousNames) {
397
+ if (!currentNames.has(name)) {
398
+ const previous = previousSnapshots[name];
399
+ if (previous) {
400
+ changes.push({
401
+ schemaName: name,
402
+ changeType: "removed",
403
+ previousHash: previous.hash
404
+ });
405
+ }
406
+ }
407
+ }
408
+ for (const name of currentNames) {
409
+ if (previousNames.has(name)) {
410
+ const current = currentSnapshots[name];
411
+ const previous = previousSnapshots[name];
412
+ if (current.hash !== previous.hash) {
413
+ const diff = diffSchemaSnapshots(previous, current);
414
+ changes.push({
415
+ schemaName: name,
416
+ changeType: "modified",
417
+ previousHash: previous.hash,
418
+ currentHash: current.hash,
419
+ ...diff
420
+ });
421
+ } else {
422
+ unchanged.push(name);
423
+ }
424
+ }
425
+ }
426
+ return {
427
+ hasChanges: changes.length > 0,
428
+ changes,
429
+ unchanged
430
+ };
431
+ }
432
+ function stripTransientInfo(snapshots) {
433
+ const result = {};
434
+ for (const [schemaName, snapshot] of Object.entries(snapshots)) {
435
+ if (!snapshot.properties) {
436
+ result[schemaName] = snapshot;
437
+ continue;
438
+ }
439
+ const cleanProperties = {};
440
+ for (const [propName, prop] of Object.entries(snapshot.properties)) {
441
+ const { renamedFrom, ...rest } = prop;
442
+ cleanProperties[propName] = rest;
443
+ }
444
+ result[schemaName] = {
445
+ ...snapshot,
446
+ properties: cleanProperties
447
+ };
448
+ }
449
+ return result;
450
+ }
451
+ function updateLockFile(existingLockFile, currentSnapshots, driver) {
452
+ const cleanSnapshots = stripTransientInfo(currentSnapshots);
179
453
  return {
180
454
  version: LOCK_FILE_VERSION,
181
455
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
182
456
  driver,
457
+ schemas: cleanSnapshots,
458
+ migrations: existingLockFile?.migrations ?? [],
459
+ hclChecksum: existingLockFile?.hclChecksum
460
+ };
461
+ }
462
+ function updateLockFileV1(existingLockFile, currentHashes, driver) {
463
+ return {
464
+ version: 1,
465
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
466
+ driver,
183
467
  schemas: currentHashes,
184
468
  migrations: existingLockFile?.migrations ?? [],
185
469
  hclChecksum: existingLockFile?.hclChecksum
@@ -1360,8 +1644,10 @@ function hasBlockingIssues(_preview) {
1360
1644
  addMigrationRecord,
1361
1645
  applySchema,
1362
1646
  buildSchemaHashes,
1647
+ buildSchemaSnapshots,
1363
1648
  checkAtlasVersion,
1364
1649
  compareSchemas,
1650
+ compareSchemasDeep,
1365
1651
  computeHash,
1366
1652
  computeSchemaHash,
1367
1653
  createEmptyLockFile,
@@ -1374,15 +1660,19 @@ function hasBlockingIssues(_preview) {
1374
1660
  getPrimaryKeyType,
1375
1661
  getTimestampType,
1376
1662
  hasBlockingIssues,
1663
+ isLockFileV2,
1377
1664
  mapPropertyToSql,
1378
1665
  parseDiffOutput,
1379
1666
  previewSchemaChanges,
1380
1667
  propertyNameToColumnName,
1668
+ propertyToSnapshot,
1381
1669
  readLockFile,
1382
1670
  renderHcl,
1383
1671
  runAtlasDiff,
1384
1672
  schemaNameToTableName,
1673
+ schemaToSnapshot,
1385
1674
  updateLockFile,
1675
+ updateLockFileV1,
1386
1676
  validateHcl,
1387
1677
  writeLockFile
1388
1678
  });