@famgia/omnify-atlas 0.0.5 → 0.0.6

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