pocketbase-zod-schema 0.1.2 → 0.1.4

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 (69) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +329 -99
  3. package/dist/cli/index.cjs +577 -152
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +575 -150
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +595 -153
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +592 -151
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/cli/utils/index.cjs +1 -1
  12. package/dist/cli/utils/index.cjs.map +1 -1
  13. package/dist/cli/utils/index.js +1 -1
  14. package/dist/cli/utils/index.js.map +1 -1
  15. package/dist/index.cjs +688 -231
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +685 -230
  20. package/dist/index.js.map +1 -1
  21. package/dist/migration/analyzer.cjs +101 -28
  22. package/dist/migration/analyzer.cjs.map +1 -1
  23. package/dist/migration/analyzer.js +101 -28
  24. package/dist/migration/analyzer.js.map +1 -1
  25. package/dist/migration/diff.cjs +21 -3
  26. package/dist/migration/diff.cjs.map +1 -1
  27. package/dist/migration/diff.js +21 -3
  28. package/dist/migration/diff.js.map +1 -1
  29. package/dist/migration/generator.cjs +60 -25
  30. package/dist/migration/generator.cjs.map +1 -1
  31. package/dist/migration/generator.d.cts +9 -5
  32. package/dist/migration/generator.d.ts +9 -5
  33. package/dist/migration/generator.js +60 -25
  34. package/dist/migration/generator.js.map +1 -1
  35. package/dist/migration/index.cjs +614 -171
  36. package/dist/migration/index.cjs.map +1 -1
  37. package/dist/migration/index.d.cts +1 -1
  38. package/dist/migration/index.d.ts +1 -1
  39. package/dist/migration/index.js +613 -171
  40. package/dist/migration/index.js.map +1 -1
  41. package/dist/migration/snapshot.cjs +432 -117
  42. package/dist/migration/snapshot.cjs.map +1 -1
  43. package/dist/migration/snapshot.d.cts +34 -12
  44. package/dist/migration/snapshot.d.ts +34 -12
  45. package/dist/migration/snapshot.js +430 -116
  46. package/dist/migration/snapshot.js.map +1 -1
  47. package/dist/migration/utils/index.cjs +19 -17
  48. package/dist/migration/utils/index.cjs.map +1 -1
  49. package/dist/migration/utils/index.d.cts +3 -1
  50. package/dist/migration/utils/index.d.ts +3 -1
  51. package/dist/migration/utils/index.js +19 -17
  52. package/dist/migration/utils/index.js.map +1 -1
  53. package/dist/mutator.cjs +9 -11
  54. package/dist/mutator.cjs.map +1 -1
  55. package/dist/mutator.d.cts +5 -9
  56. package/dist/mutator.d.ts +5 -9
  57. package/dist/mutator.js +9 -11
  58. package/dist/mutator.js.map +1 -1
  59. package/dist/schema.cjs +54 -23
  60. package/dist/schema.cjs.map +1 -1
  61. package/dist/schema.d.cts +94 -12
  62. package/dist/schema.d.ts +94 -12
  63. package/dist/schema.js +54 -24
  64. package/dist/schema.js.map +1 -1
  65. package/dist/types.d.cts +1 -1
  66. package/dist/types.d.ts +1 -1
  67. package/dist/{user-jS1aYoeD.d.cts → user-_AM523hb.d.cts} +6 -6
  68. package/dist/{user-jS1aYoeD.d.ts → user-_AM523hb.d.ts} +6 -6
  69. package/package.json +2 -4
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var fs2 = require('fs');
3
+ var fs3 = require('fs');
4
4
  var path = require('path');
5
5
  var zod = require('zod');
6
6
 
@@ -22,10 +22,39 @@ function _interopNamespace(e) {
22
22
  return Object.freeze(n);
23
23
  }
24
24
 
25
- var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
25
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
26
26
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
27
 
28
28
  // src/migration/analyzer.ts
29
+ ({
30
+ id: zod.z.string().describe("unique id"),
31
+ collectionId: zod.z.string().describe("collection id"),
32
+ collectionName: zod.z.string().describe("collection name"),
33
+ expand: zod.z.record(zod.z.any()).describe("expandable fields")
34
+ });
35
+ ({
36
+ created: zod.z.string().describe("creation timestamp"),
37
+ updated: zod.z.string().describe("last update timestamp")
38
+ });
39
+ ({
40
+ thumbnailURL: zod.z.string().optional(),
41
+ imageFiles: zod.z.array(zod.z.string())
42
+ });
43
+ ({
44
+ imageFiles: zod.z.array(zod.z.instanceof(File))
45
+ });
46
+ var RELATION_METADATA_KEY = "__pocketbase_relation__";
47
+ function extractRelationMetadata(description) {
48
+ if (!description) return null;
49
+ try {
50
+ const parsed = JSON.parse(description);
51
+ if (parsed[RELATION_METADATA_KEY]) {
52
+ return parsed[RELATION_METADATA_KEY];
53
+ }
54
+ } catch {
55
+ }
56
+ return null;
57
+ }
29
58
 
30
59
  // src/migration/errors.ts
31
60
  var MigrationError = class _MigrationError extends Error {
@@ -124,10 +153,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
124
153
  operation;
125
154
  code;
126
155
  originalError;
127
- constructor(message, path4, operation, code, originalError) {
156
+ constructor(message, path5, operation, code, originalError) {
128
157
  super(message);
129
158
  this.name = "FileSystemError";
130
- this.path = path4;
159
+ this.path = path5;
131
160
  this.operation = operation;
132
161
  this.code = code;
133
162
  this.originalError = originalError;
@@ -216,7 +245,7 @@ Suggestion: ${this.suggestion}`);
216
245
  }
217
246
  };
218
247
 
219
- // src/schema/permission-templates.ts
248
+ // src/utils/permission-templates.ts
220
249
  var PermissionTemplates = {
221
250
  /**
222
251
  * Public access - anyone can perform all operations
@@ -896,26 +925,28 @@ function getMaxSelect(fieldName, zodType) {
896
925
  return 1;
897
926
  }
898
927
  function getMinSelect(fieldName, zodType) {
899
- if (!isMultipleRelationField(fieldName, zodType)) {
900
- return void 0;
901
- }
902
- let unwrappedType = zodType;
903
- if (zodType instanceof zod.z.ZodOptional) {
904
- unwrappedType = zodType._def.innerType;
905
- }
906
- if (unwrappedType instanceof zod.z.ZodNullable) {
907
- unwrappedType = unwrappedType._def.innerType;
908
- }
909
- if (unwrappedType instanceof zod.z.ZodDefault) {
910
- unwrappedType = unwrappedType._def.innerType;
928
+ if (isSingleRelationField(fieldName, zodType)) {
929
+ return 0;
911
930
  }
912
- if (unwrappedType instanceof zod.z.ZodArray) {
913
- const arrayDef = unwrappedType._def;
914
- if (arrayDef.minLength) {
915
- return arrayDef.minLength.value;
931
+ if (isMultipleRelationField(fieldName, zodType)) {
932
+ let unwrappedType = zodType;
933
+ if (zodType instanceof zod.z.ZodOptional) {
934
+ unwrappedType = zodType._def.innerType;
935
+ }
936
+ if (unwrappedType instanceof zod.z.ZodNullable) {
937
+ unwrappedType = unwrappedType._def.innerType;
938
+ }
939
+ if (unwrappedType instanceof zod.z.ZodDefault) {
940
+ unwrappedType = unwrappedType._def.innerType;
941
+ }
942
+ if (unwrappedType instanceof zod.z.ZodArray) {
943
+ const arrayDef = unwrappedType._def;
944
+ if (arrayDef.minLength) {
945
+ return arrayDef.minLength.value;
946
+ }
916
947
  }
917
948
  }
918
- return void 0;
949
+ return 0;
919
950
  }
920
951
  var POCKETBASE_FIELD_TYPES = [
921
952
  "text",
@@ -1348,10 +1379,10 @@ function discoverSchemaFiles(config) {
1348
1379
  const mergedConfig = mergeConfig(normalizedConfig);
1349
1380
  const schemaDir = resolveSchemaDir(normalizedConfig);
1350
1381
  try {
1351
- if (!fs2__namespace.existsSync(schemaDir)) {
1382
+ if (!fs3__namespace.existsSync(schemaDir)) {
1352
1383
  throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1353
1384
  }
1354
- const files = fs2__namespace.readdirSync(schemaDir);
1385
+ const files = fs3__namespace.readdirSync(schemaDir);
1355
1386
  const schemaFiles = files.filter((file) => {
1356
1387
  const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
1357
1388
  if (!hasValidExtension) return false;
@@ -1398,13 +1429,32 @@ async function importSchemaModule(filePath, config) {
1398
1429
  if (config?.pathTransformer) {
1399
1430
  importPath = config.pathTransformer(filePath);
1400
1431
  }
1401
- if (!importPath.endsWith(".js")) {
1402
- importPath = `${importPath}.js`;
1432
+ let resolvedPath = null;
1433
+ const jsPath = `${importPath}.js`;
1434
+ const tsPath = `${importPath}.ts`;
1435
+ if (fs3__namespace.existsSync(jsPath)) {
1436
+ resolvedPath = jsPath;
1437
+ } else if (fs3__namespace.existsSync(tsPath)) {
1438
+ resolvedPath = tsPath;
1439
+ } else {
1440
+ resolvedPath = jsPath;
1403
1441
  }
1404
- const fileUrl = new URL(`file://${path__namespace.resolve(importPath)}`);
1442
+ const fileUrl = new URL(`file://${path__namespace.resolve(resolvedPath)}`);
1405
1443
  const module = await import(fileUrl.href);
1406
1444
  return module;
1407
1445
  } catch (error) {
1446
+ const tsPath = `${filePath}.ts`;
1447
+ const isTypeScriptFile = fs3__namespace.existsSync(tsPath);
1448
+ if (isTypeScriptFile) {
1449
+ throw new SchemaParsingError(
1450
+ `Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
1451
+ Please either:
1452
+ 1. Compile your schema files to JavaScript first, or
1453
+ 2. Use tsx to run the migration tool (e.g., "npx tsx package/dist/cli/migrate.js status" or "tsx package/dist/cli/migrate.js status")`,
1454
+ filePath,
1455
+ error
1456
+ );
1457
+ }
1408
1458
  throw new SchemaParsingError(
1409
1459
  `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1410
1460
  filePath,
@@ -1467,7 +1517,17 @@ function buildFieldDefinition(fieldName, zodType) {
1467
1517
  required,
1468
1518
  options
1469
1519
  };
1470
- if (isRelationField(fieldName, zodType)) {
1520
+ const relationMetadata = extractRelationMetadata(zodType.description);
1521
+ if (relationMetadata) {
1522
+ fieldDef.type = "relation";
1523
+ fieldDef.relation = {
1524
+ collection: relationMetadata.collection,
1525
+ maxSelect: relationMetadata.maxSelect,
1526
+ minSelect: relationMetadata.minSelect,
1527
+ cascadeDelete: relationMetadata.cascadeDelete
1528
+ };
1529
+ fieldDef.options = void 0;
1530
+ } else if (isRelationField(fieldName, zodType)) {
1471
1531
  fieldDef.type = "relation";
1472
1532
  const targetCollection = resolveTargetCollection(fieldName);
1473
1533
  const maxSelect = getMaxSelect(fieldName, zodType);
@@ -1479,6 +1539,13 @@ function buildFieldDefinition(fieldName, zodType) {
1479
1539
  cascadeDelete: false
1480
1540
  // Default to false, can be configured later
1481
1541
  };
1542
+ if (fieldDef.options) {
1543
+ const { min, max, pattern, ...relationSafeOptions } = fieldDef.options;
1544
+ console.log("min", min);
1545
+ console.log("max", max);
1546
+ console.log("pattern", pattern);
1547
+ fieldDef.options = Object.keys(relationSafeOptions).length > 0 ? relationSafeOptions : void 0;
1548
+ }
1482
1549
  }
1483
1550
  return fieldDef;
1484
1551
  }
@@ -1531,11 +1598,12 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1531
1598
  fields,
1532
1599
  indexes,
1533
1600
  rules: {
1534
- listRule: null,
1535
- viewRule: null,
1536
- createRule: null,
1537
- updateRule: null,
1538
- deleteRule: null
1601
+ listRule: permissions?.listRule ?? null,
1602
+ viewRule: permissions?.viewRule ?? null,
1603
+ createRule: permissions?.createRule ?? null,
1604
+ updateRule: permissions?.updateRule ?? null,
1605
+ deleteRule: permissions?.deleteRule ?? null,
1606
+ manageRule: permissions?.manageRule ?? null
1539
1607
  },
1540
1608
  permissions
1541
1609
  };
@@ -1559,7 +1627,12 @@ async function buildSchemaDefinition(config) {
1559
1627
  if (normalizedConfig.pathTransformer) {
1560
1628
  importPath = normalizedConfig.pathTransformer(filePath);
1561
1629
  } else if (mergedConfig.useCompiledFiles) {
1562
- importPath = filePath.replace(/\/src\//, "/dist/");
1630
+ const distPath = filePath.replace(/\/src\//, "/dist/");
1631
+ if (fs3__namespace.existsSync(`${distPath}.js`) || fs3__namespace.existsSync(`${distPath}.mjs`)) {
1632
+ importPath = distPath;
1633
+ } else {
1634
+ importPath = filePath;
1635
+ }
1563
1636
  }
1564
1637
  const module = await importSchemaModule(importPath, normalizedConfig);
1565
1638
  const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
@@ -1611,7 +1684,359 @@ var SchemaAnalyzer = class {
1611
1684
  return convertZodSchemaToCollectionSchema(name, schema);
1612
1685
  }
1613
1686
  };
1687
+
1688
+ // src/migration/pocketbase-converter.ts
1614
1689
  var SNAPSHOT_VERSION = "1.0.0";
1690
+ function resolveCollectionIdToName(collectionId) {
1691
+ if (collectionId === "_pb_users_auth_") {
1692
+ return "Users";
1693
+ }
1694
+ const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1695
+ if (nameMatch) {
1696
+ return nameMatch[1];
1697
+ }
1698
+ return collectionId;
1699
+ }
1700
+ function convertPocketBaseCollection(pbCollection) {
1701
+ const fields = [];
1702
+ const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
1703
+ const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
1704
+ if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
1705
+ for (const pbField of pbCollection.fields) {
1706
+ if (pbField.system || systemFieldNames.includes(pbField.name)) {
1707
+ continue;
1708
+ }
1709
+ if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
1710
+ continue;
1711
+ }
1712
+ const field = {
1713
+ name: pbField.name,
1714
+ type: pbField.type,
1715
+ required: pbField.required || false
1716
+ };
1717
+ field.options = pbField.options ? { ...pbField.options } : {};
1718
+ if (pbField.type === "select") {
1719
+ if (pbField.values && Array.isArray(pbField.values)) {
1720
+ field.options.values = pbField.values;
1721
+ } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
1722
+ field.options.values = pbField.options.values;
1723
+ }
1724
+ }
1725
+ if (pbField.type === "relation") {
1726
+ const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
1727
+ const collectionName = resolveCollectionIdToName(collectionId);
1728
+ field.relation = {
1729
+ collection: collectionName,
1730
+ cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
1731
+ maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
1732
+ minSelect: pbField.minSelect ?? pbField.options?.minSelect
1733
+ };
1734
+ }
1735
+ const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
1736
+ if (Object.keys(field.options).length === 0) {
1737
+ delete field.options;
1738
+ } else if (pbField.type === "select" && hasOnlyValues) ;
1739
+ fields.push(field);
1740
+ }
1741
+ }
1742
+ const schema = {
1743
+ name: pbCollection.name,
1744
+ type: pbCollection.type || "base",
1745
+ fields
1746
+ };
1747
+ if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
1748
+ schema.indexes = pbCollection.indexes;
1749
+ }
1750
+ const rules = {};
1751
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1752
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1753
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1754
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1755
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1756
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1757
+ if (Object.keys(rules).length > 0) {
1758
+ schema.rules = rules;
1759
+ schema.permissions = { ...rules };
1760
+ }
1761
+ return schema;
1762
+ }
1763
+ function convertPocketBaseMigration(migrationContent) {
1764
+ try {
1765
+ const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
1766
+ if (!snapshotMatch) {
1767
+ throw new Error("Could not find snapshot array in migration file");
1768
+ }
1769
+ const snapshotArrayStr = snapshotMatch[1];
1770
+ let snapshotArray;
1771
+ try {
1772
+ snapshotArray = new Function(`return ${snapshotArrayStr}`)();
1773
+ } catch (parseError) {
1774
+ throw new Error(`Failed to parse snapshot array: ${parseError}`);
1775
+ }
1776
+ if (!Array.isArray(snapshotArray)) {
1777
+ throw new Error("Snapshot is not an array");
1778
+ }
1779
+ const collections = /* @__PURE__ */ new Map();
1780
+ for (const pbCollection of snapshotArray) {
1781
+ if (!pbCollection.name) {
1782
+ console.warn("Skipping collection without name");
1783
+ continue;
1784
+ }
1785
+ const schema = convertPocketBaseCollection(pbCollection);
1786
+ collections.set(pbCollection.name, schema);
1787
+ }
1788
+ return {
1789
+ version: SNAPSHOT_VERSION,
1790
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1791
+ collections
1792
+ };
1793
+ } catch (error) {
1794
+ throw new SnapshotError(
1795
+ `Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
1796
+ void 0,
1797
+ "parse",
1798
+ error instanceof Error ? error : void 0
1799
+ );
1800
+ }
1801
+ }
1802
+
1803
+ // src/migration/migration-parser.ts
1804
+ function extractTimestampFromFilename(filename) {
1805
+ const match = filename.match(/^(\d+)_/);
1806
+ if (match) {
1807
+ return parseInt(match[1], 10);
1808
+ }
1809
+ return null;
1810
+ }
1811
+ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
1812
+ try {
1813
+ if (!fs3__namespace.existsSync(migrationsPath)) {
1814
+ return [];
1815
+ }
1816
+ const files = fs3__namespace.readdirSync(migrationsPath);
1817
+ const migrationFiles = [];
1818
+ for (const file of files) {
1819
+ if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
1820
+ continue;
1821
+ }
1822
+ if (!file.endsWith(".js")) {
1823
+ continue;
1824
+ }
1825
+ const timestamp = extractTimestampFromFilename(file);
1826
+ if (timestamp && timestamp > snapshotTimestamp) {
1827
+ migrationFiles.push({
1828
+ path: path__namespace.join(migrationsPath, file),
1829
+ timestamp
1830
+ });
1831
+ }
1832
+ }
1833
+ migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
1834
+ return migrationFiles.map((f) => f.path);
1835
+ } catch (error) {
1836
+ console.warn(`Error finding migrations after snapshot: ${error}`);
1837
+ return [];
1838
+ }
1839
+ }
1840
+ function parseMigrationOperationsFromContent(content) {
1841
+ const collectionsToCreate = [];
1842
+ const collectionsToDelete = [];
1843
+ try {
1844
+ let searchIndex = 0;
1845
+ while (true) {
1846
+ const collectionStart = content.indexOf("new Collection(", searchIndex);
1847
+ if (collectionStart === -1) {
1848
+ break;
1849
+ }
1850
+ const openParen = collectionStart + "new Collection(".length;
1851
+ let braceCount = 0;
1852
+ let parenCount = 1;
1853
+ let inString = false;
1854
+ let stringChar = null;
1855
+ let i = openParen;
1856
+ while (i < content.length && /\s/.test(content[i])) {
1857
+ i++;
1858
+ }
1859
+ if (content[i] !== "{") {
1860
+ searchIndex = i + 1;
1861
+ continue;
1862
+ }
1863
+ const objectStart = i;
1864
+ braceCount = 1;
1865
+ i++;
1866
+ while (i < content.length && (braceCount > 0 || parenCount > 0)) {
1867
+ const char = content[i];
1868
+ const prevChar = i > 0 ? content[i - 1] : "";
1869
+ if (!inString && (char === '"' || char === "'")) {
1870
+ inString = true;
1871
+ stringChar = char;
1872
+ } else if (inString && char === stringChar && prevChar !== "\\") {
1873
+ inString = false;
1874
+ stringChar = null;
1875
+ }
1876
+ if (!inString) {
1877
+ if (char === "{") braceCount++;
1878
+ if (char === "}") braceCount--;
1879
+ if (char === "(") parenCount++;
1880
+ if (char === ")") parenCount--;
1881
+ }
1882
+ i++;
1883
+ }
1884
+ if (braceCount === 0 && parenCount === 0) {
1885
+ const objectContent = content.substring(objectStart, i - 1);
1886
+ try {
1887
+ const collectionObj = new Function(`return ${objectContent}`)();
1888
+ if (collectionObj && collectionObj.name) {
1889
+ const schema = convertPocketBaseCollection(collectionObj);
1890
+ collectionsToCreate.push(schema);
1891
+ }
1892
+ } catch (error) {
1893
+ console.warn(`Failed to parse collection definition: ${error}`);
1894
+ }
1895
+ }
1896
+ searchIndex = i;
1897
+ }
1898
+ const deleteMatches = content.matchAll(
1899
+ /app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
1900
+ );
1901
+ for (const match of deleteMatches) {
1902
+ if (match[1]) {
1903
+ collectionsToDelete.push(match[1]);
1904
+ } else {
1905
+ const varNameMatch = match[0].match(/collection_(\w+)/);
1906
+ if (varNameMatch) {
1907
+ const varName = `collection_${varNameMatch[1]}`;
1908
+ const deleteIndex = content.indexOf(match[0]);
1909
+ const beforeDelete = content.substring(0, deleteIndex);
1910
+ const varDefMatch = beforeDelete.match(
1911
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
1912
+ );
1913
+ if (varDefMatch && varDefMatch.length > 0) {
1914
+ const collectionDefMatch = beforeDelete.match(
1915
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
1916
+ );
1917
+ if (collectionDefMatch) {
1918
+ try {
1919
+ const collectionDefStr = collectionDefMatch[1];
1920
+ const collectionObj = new Function(`return ${collectionDefStr}`)();
1921
+ if (collectionObj && collectionObj.name) {
1922
+ collectionsToDelete.push(collectionObj.name);
1923
+ }
1924
+ } catch {
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ }
1930
+ }
1931
+ const findAndDeleteMatches = content.matchAll(
1932
+ /app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
1933
+ );
1934
+ for (const match of findAndDeleteMatches) {
1935
+ collectionsToDelete.push(match[1]);
1936
+ }
1937
+ } catch (error) {
1938
+ console.warn(`Failed to parse migration operations from content: ${error}`);
1939
+ }
1940
+ return { collectionsToCreate, collectionsToDelete };
1941
+ }
1942
+ function parseMigrationOperations(migrationContent) {
1943
+ try {
1944
+ const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
1945
+ if (!migrateMatch) {
1946
+ return parseMigrationOperationsFromContent(migrationContent);
1947
+ }
1948
+ const startIndex = migrateMatch.index + migrateMatch[0].length;
1949
+ let i = startIndex;
1950
+ let parenCount = 0;
1951
+ let foundFirstParen = false;
1952
+ while (i < migrationContent.length) {
1953
+ const char = migrationContent[i];
1954
+ if (char === "(") {
1955
+ parenCount++;
1956
+ foundFirstParen = true;
1957
+ i++;
1958
+ break;
1959
+ }
1960
+ i++;
1961
+ }
1962
+ if (!foundFirstParen) {
1963
+ return parseMigrationOperationsFromContent(migrationContent);
1964
+ }
1965
+ let inString = false;
1966
+ let stringChar = null;
1967
+ let foundBrace = false;
1968
+ let braceStart = -1;
1969
+ while (i < migrationContent.length && !foundBrace) {
1970
+ const char = migrationContent[i];
1971
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
1972
+ if (!inString && (char === '"' || char === "'")) {
1973
+ inString = true;
1974
+ stringChar = char;
1975
+ } else if (inString && char === stringChar && prevChar !== "\\") {
1976
+ inString = false;
1977
+ stringChar = null;
1978
+ }
1979
+ if (!inString) {
1980
+ if (char === "(") parenCount++;
1981
+ if (char === ")") {
1982
+ parenCount--;
1983
+ if (parenCount === 0) {
1984
+ i++;
1985
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
1986
+ i++;
1987
+ }
1988
+ if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
1989
+ i += 2;
1990
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
1991
+ i++;
1992
+ }
1993
+ if (i < migrationContent.length && migrationContent[i] === "{") {
1994
+ foundBrace = true;
1995
+ braceStart = i + 1;
1996
+ break;
1997
+ }
1998
+ }
1999
+ }
2000
+ }
2001
+ }
2002
+ i++;
2003
+ }
2004
+ if (!foundBrace || braceStart === -1) {
2005
+ return parseMigrationOperationsFromContent(migrationContent);
2006
+ }
2007
+ let braceCount = 1;
2008
+ i = braceStart;
2009
+ inString = false;
2010
+ stringChar = null;
2011
+ while (i < migrationContent.length && braceCount > 0) {
2012
+ const char = migrationContent[i];
2013
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2014
+ if (!inString && (char === '"' || char === "'")) {
2015
+ inString = true;
2016
+ stringChar = char;
2017
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2018
+ inString = false;
2019
+ stringChar = null;
2020
+ }
2021
+ if (!inString) {
2022
+ if (char === "{") braceCount++;
2023
+ if (char === "}") braceCount--;
2024
+ }
2025
+ i++;
2026
+ }
2027
+ if (braceCount === 0) {
2028
+ const upMigrationContent = migrationContent.substring(braceStart, i - 1);
2029
+ return parseMigrationOperationsFromContent(upMigrationContent);
2030
+ }
2031
+ return parseMigrationOperationsFromContent(migrationContent);
2032
+ } catch (error) {
2033
+ console.warn(`Failed to parse migration operations: ${error}`);
2034
+ return { collectionsToCreate: [], collectionsToDelete: [] };
2035
+ }
2036
+ }
2037
+
2038
+ // src/migration/snapshot.ts
2039
+ var SNAPSHOT_VERSION2 = "1.0.0";
1615
2040
  var DEFAULT_SNAPSHOT_FILENAME = ".migration-snapshot.json";
1616
2041
  var SNAPSHOT_MIGRATIONS = [
1617
2042
  // Add migrations here as the format evolves
@@ -1626,7 +2051,7 @@ var DEFAULT_CONFIG2 = {
1626
2051
  snapshotPath: DEFAULT_SNAPSHOT_FILENAME,
1627
2052
  workspaceRoot: process.cwd(),
1628
2053
  autoMigrate: true,
1629
- version: SNAPSHOT_VERSION
2054
+ version: SNAPSHOT_VERSION2
1630
2055
  };
1631
2056
  function mergeConfig2(config = {}) {
1632
2057
  return {
@@ -1646,7 +2071,7 @@ function getSnapshotPath(config = {}) {
1646
2071
  function snapshotExists(config = {}) {
1647
2072
  try {
1648
2073
  const snapshotPath = getSnapshotPath(config);
1649
- return fs2__namespace.existsSync(snapshotPath);
2074
+ return fs3__namespace.existsSync(snapshotPath);
1650
2075
  } catch {
1651
2076
  return false;
1652
2077
  }
@@ -1706,12 +2131,12 @@ function saveSnapshot(schema, config = {}) {
1706
2131
  const snapshotPath = getSnapshotPath(config);
1707
2132
  try {
1708
2133
  const snapshotDir = path__namespace.dirname(snapshotPath);
1709
- if (!fs2__namespace.existsSync(snapshotDir)) {
1710
- fs2__namespace.mkdirSync(snapshotDir, { recursive: true });
2134
+ if (!fs3__namespace.existsSync(snapshotDir)) {
2135
+ fs3__namespace.mkdirSync(snapshotDir, { recursive: true });
1711
2136
  }
1712
2137
  const snapshotData = addSnapshotMetadata(schema, config);
1713
2138
  const jsonContent = JSON.stringify(snapshotData, null, 2);
1714
- fs2__namespace.writeFileSync(snapshotPath, jsonContent, "utf-8");
2139
+ fs3__namespace.writeFileSync(snapshotPath, jsonContent, "utf-8");
1715
2140
  } catch (error) {
1716
2141
  handleFileSystemError(error, "write", snapshotPath);
1717
2142
  }
@@ -1806,7 +2231,7 @@ function deserializeSnapshot(data) {
1806
2231
  function loadSnapshot(config = {}) {
1807
2232
  const snapshotPath = getSnapshotPath(config);
1808
2233
  try {
1809
- const jsonContent = fs2__namespace.readFileSync(snapshotPath, "utf-8");
2234
+ const jsonContent = fs3__namespace.readFileSync(snapshotPath, "utf-8");
1810
2235
  const data = parseAndValidateSnapshot(jsonContent, snapshotPath);
1811
2236
  const migratedData = migrateSnapshotFormat(data, config);
1812
2237
  return deserializeSnapshot(migratedData);
@@ -1841,10 +2266,10 @@ function mergeSnapshots(baseSnapshot, customSnapshot) {
1841
2266
  }
1842
2267
  function findLatestSnapshot(migrationsPath) {
1843
2268
  try {
1844
- if (!fs2__namespace.existsSync(migrationsPath)) {
2269
+ if (!fs3__namespace.existsSync(migrationsPath)) {
1845
2270
  return null;
1846
2271
  }
1847
- const files = fs2__namespace.readdirSync(migrationsPath);
2272
+ const files = fs3__namespace.readdirSync(migrationsPath);
1848
2273
  const snapshotFiles = files.filter(
1849
2274
  (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
1850
2275
  );
@@ -1862,14 +2287,68 @@ function findLatestSnapshot(migrationsPath) {
1862
2287
  return null;
1863
2288
  }
1864
2289
  }
2290
+ function applyMigrationOperations(snapshot, operations) {
2291
+ const updatedCollections = new Map(snapshot.collections);
2292
+ for (const collectionName of operations.collectionsToDelete) {
2293
+ updatedCollections.delete(collectionName);
2294
+ }
2295
+ for (const collection of operations.collectionsToCreate) {
2296
+ updatedCollections.set(collection.name, collection);
2297
+ }
2298
+ return {
2299
+ ...snapshot,
2300
+ collections: updatedCollections
2301
+ };
2302
+ }
2303
+ function loadSnapshotWithMigrations(config = {}) {
2304
+ const migrationsPath = config.migrationsPath;
2305
+ if (!migrationsPath) {
2306
+ return null;
2307
+ }
2308
+ if (fs3__namespace.existsSync(migrationsPath) && fs3__namespace.statSync(migrationsPath).isFile()) {
2309
+ try {
2310
+ const migrationContent = fs3__namespace.readFileSync(migrationsPath, "utf-8");
2311
+ return convertPocketBaseMigration(migrationContent);
2312
+ } catch (error) {
2313
+ console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2314
+ return null;
2315
+ }
2316
+ }
2317
+ const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2318
+ if (!latestSnapshotPath) {
2319
+ return null;
2320
+ }
2321
+ try {
2322
+ const migrationContent = fs3__namespace.readFileSync(latestSnapshotPath, "utf-8");
2323
+ let snapshot = convertPocketBaseMigration(migrationContent);
2324
+ const snapshotFilename = path__namespace.basename(latestSnapshotPath);
2325
+ const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
2326
+ if (snapshotTimestamp) {
2327
+ const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
2328
+ for (const migrationFile of migrationFiles) {
2329
+ try {
2330
+ const migrationContent2 = fs3__namespace.readFileSync(migrationFile, "utf-8");
2331
+ const operations = parseMigrationOperations(migrationContent2);
2332
+ snapshot = applyMigrationOperations(snapshot, operations);
2333
+ } catch (error) {
2334
+ console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
2335
+ }
2336
+ }
2337
+ }
2338
+ return snapshot;
2339
+ } catch (error) {
2340
+ console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2341
+ return null;
2342
+ }
2343
+ }
1865
2344
  function loadSnapshotIfExists(config = {}) {
1866
2345
  const migrationsPath = config.migrationsPath;
1867
2346
  if (!migrationsPath) {
1868
2347
  return null;
1869
2348
  }
1870
- if (fs2__namespace.existsSync(migrationsPath) && fs2__namespace.statSync(migrationsPath).isFile()) {
2349
+ if (fs3__namespace.existsSync(migrationsPath) && fs3__namespace.statSync(migrationsPath).isFile()) {
1871
2350
  try {
1872
- const migrationContent = fs2__namespace.readFileSync(migrationsPath, "utf-8");
2351
+ const migrationContent = fs3__namespace.readFileSync(migrationsPath, "utf-8");
1873
2352
  return convertPocketBaseMigration(migrationContent);
1874
2353
  } catch (error) {
1875
2354
  console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
@@ -1879,7 +2358,7 @@ function loadSnapshotIfExists(config = {}) {
1879
2358
  const latestSnapshotPath = findLatestSnapshot(migrationsPath);
1880
2359
  if (latestSnapshotPath) {
1881
2360
  try {
1882
- const migrationContent = fs2__namespace.readFileSync(latestSnapshotPath, "utf-8");
2361
+ const migrationContent = fs3__namespace.readFileSync(latestSnapshotPath, "utf-8");
1883
2362
  return convertPocketBaseMigration(migrationContent);
1884
2363
  } catch (error) {
1885
2364
  console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
@@ -1888,99 +2367,9 @@ function loadSnapshotIfExists(config = {}) {
1888
2367
  }
1889
2368
  return null;
1890
2369
  }
1891
- function convertPocketBaseCollection(pbCollection) {
1892
- const fields = [];
1893
- const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
1894
- const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
1895
- if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
1896
- for (const pbField of pbCollection.fields) {
1897
- if (pbField.system || systemFieldNames.includes(pbField.name)) {
1898
- continue;
1899
- }
1900
- if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
1901
- continue;
1902
- }
1903
- const field = {
1904
- name: pbField.name,
1905
- type: pbField.type,
1906
- required: pbField.required || false
1907
- };
1908
- if (pbField.options) {
1909
- field.options = pbField.options;
1910
- }
1911
- if (pbField.type === "relation") {
1912
- field.relation = {
1913
- collection: pbField.options?.collectionId || "",
1914
- cascadeDelete: pbField.options?.cascadeDelete || false,
1915
- maxSelect: pbField.options?.maxSelect,
1916
- minSelect: pbField.options?.minSelect
1917
- };
1918
- }
1919
- fields.push(field);
1920
- }
1921
- }
1922
- const schema = {
1923
- name: pbCollection.name,
1924
- type: pbCollection.type || "base",
1925
- fields
1926
- };
1927
- if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
1928
- schema.indexes = pbCollection.indexes;
1929
- }
1930
- const rules = {};
1931
- if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1932
- if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1933
- if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1934
- if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1935
- if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1936
- if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1937
- if (Object.keys(rules).length > 0) {
1938
- schema.rules = rules;
1939
- }
1940
- return schema;
1941
- }
1942
- function convertPocketBaseMigration(migrationContent) {
1943
- try {
1944
- const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
1945
- if (!snapshotMatch) {
1946
- throw new Error("Could not find snapshot array in migration file");
1947
- }
1948
- const snapshotArrayStr = snapshotMatch[1];
1949
- let snapshotArray;
1950
- try {
1951
- snapshotArray = new Function(`return ${snapshotArrayStr}`)();
1952
- } catch (parseError) {
1953
- throw new Error(`Failed to parse snapshot array: ${parseError}`);
1954
- }
1955
- if (!Array.isArray(snapshotArray)) {
1956
- throw new Error("Snapshot is not an array");
1957
- }
1958
- const collections = /* @__PURE__ */ new Map();
1959
- for (const pbCollection of snapshotArray) {
1960
- if (!pbCollection.name) {
1961
- console.warn("Skipping collection without name");
1962
- continue;
1963
- }
1964
- const schema = convertPocketBaseCollection(pbCollection);
1965
- collections.set(pbCollection.name, schema);
1966
- }
1967
- return {
1968
- version: SNAPSHOT_VERSION,
1969
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1970
- collections
1971
- };
1972
- } catch (error) {
1973
- throw new SnapshotError(
1974
- `Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
1975
- void 0,
1976
- "parse",
1977
- error instanceof Error ? error : void 0
1978
- );
1979
- }
1980
- }
1981
2370
  function loadBaseMigration(migrationPath) {
1982
2371
  try {
1983
- if (!fs2__namespace.existsSync(migrationPath)) {
2372
+ if (!fs3__namespace.existsSync(migrationPath)) {
1984
2373
  throw new SnapshotError(
1985
2374
  `Base migration file not found: ${migrationPath}
1986
2375
 
@@ -1991,7 +2380,7 @@ If the file exists in a different location, update the configuration.`,
1991
2380
  "read"
1992
2381
  );
1993
2382
  }
1994
- const migrationContent = fs2__namespace.readFileSync(migrationPath, "utf-8");
2383
+ const migrationContent = fs3__namespace.readFileSync(migrationPath, "utf-8");
1995
2384
  const snapshot = convertPocketBaseMigration(migrationContent);
1996
2385
  return snapshot;
1997
2386
  } catch (error) {
@@ -2027,14 +2416,14 @@ Please ensure PocketBase is properly set up by running 'yarn setup'.`,
2027
2416
  }
2028
2417
  }
2029
2418
  function getSnapshotVersion() {
2030
- return SNAPSHOT_VERSION;
2419
+ return SNAPSHOT_VERSION2;
2031
2420
  }
2032
2421
  function validateSnapshot(snapshot) {
2033
2422
  const issues = [];
2034
2423
  if (!snapshot.version) {
2035
2424
  issues.push("Missing version field");
2036
- } else if (compareVersions(snapshot.version, SNAPSHOT_VERSION) > 0) {
2037
- issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION}`);
2425
+ } else if (compareVersions(snapshot.version, SNAPSHOT_VERSION2) > 0) {
2426
+ issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION2}`);
2038
2427
  }
2039
2428
  if (!snapshot.timestamp) {
2040
2429
  issues.push("Missing timestamp field");
@@ -2253,6 +2642,9 @@ function compareFieldOptions(currentField, previousField) {
2253
2642
  for (const key of allKeys) {
2254
2643
  const currentValue = currentOptions[key];
2255
2644
  const previousValue = previousOptions[key];
2645
+ if (currentValue === void 0 && previousValue === void 0) {
2646
+ continue;
2647
+ }
2256
2648
  if (!areValuesEqual(currentValue, previousValue)) {
2257
2649
  changes.push({
2258
2650
  property: `options.${key}`,
@@ -2273,11 +2665,26 @@ function compareRelationConfigurations(currentField, previousField) {
2273
2665
  if (!currentRelation || !previousRelation) {
2274
2666
  return changes;
2275
2667
  }
2276
- if (currentRelation.collection !== previousRelation.collection) {
2668
+ const normalizeCollection = (collection) => {
2669
+ if (!collection) return collection;
2670
+ if (collection === "_pb_users_auth_") {
2671
+ return "Users";
2672
+ }
2673
+ const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2674
+ if (nameMatch) {
2675
+ return nameMatch[1];
2676
+ }
2677
+ return collection;
2678
+ };
2679
+ const normalizedCurrent = normalizeCollection(currentRelation.collection);
2680
+ const normalizedPrevious = normalizeCollection(previousRelation.collection);
2681
+ if (normalizedCurrent !== normalizedPrevious) {
2277
2682
  changes.push({
2278
2683
  property: "relation.collection",
2279
- oldValue: previousRelation.collection,
2280
- newValue: currentRelation.collection
2684
+ oldValue: normalizedPrevious,
2685
+ // Use normalized value for clarity
2686
+ newValue: normalizedCurrent
2687
+ // Use normalized value for clarity
2281
2688
  });
2282
2689
  }
2283
2690
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -2631,10 +3038,8 @@ var DiffEngine = class {
2631
3038
  var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
2632
3039
  migrate((app) => {
2633
3040
  {{UP_CODE}}
2634
- return true;
2635
3041
  }, (app) => {
2636
3042
  {{DOWN_CODE}}
2637
- return true;
2638
3043
  });
2639
3044
  `;
2640
3045
  var DEFAULT_CONFIG4 = {
@@ -2713,9 +3118,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
2713
3118
  }
2714
3119
  function writeMigrationFile(migrationDir, filename, content) {
2715
3120
  try {
2716
- if (!fs2__namespace.existsSync(migrationDir)) {
3121
+ if (!fs3__namespace.existsSync(migrationDir)) {
2717
3122
  try {
2718
- fs2__namespace.mkdirSync(migrationDir, { recursive: true });
3123
+ fs3__namespace.mkdirSync(migrationDir, { recursive: true });
2719
3124
  } catch (error) {
2720
3125
  const fsError = error;
2721
3126
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
@@ -2737,7 +3142,7 @@ function writeMigrationFile(migrationDir, filename, content) {
2737
3142
  }
2738
3143
  }
2739
3144
  const filePath = path__namespace.join(migrationDir, filename);
2740
- fs2__namespace.writeFileSync(filePath, content, "utf-8");
3145
+ fs3__namespace.writeFileSync(filePath, content, "utf-8");
2741
3146
  return filePath;
2742
3147
  } catch (error) {
2743
3148
  if (error instanceof FileSystemError) {
@@ -2799,7 +3204,8 @@ function generateFieldDefinitionObject(field) {
2799
3204
  }
2800
3205
  }
2801
3206
  if (field.relation) {
2802
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
3207
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
3208
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2803
3209
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2804
3210
  if (field.relation.maxSelect !== void 0) {
2805
3211
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2883,7 +3289,7 @@ function generateIndexesArray(indexes) {
2883
3289
  ${indexStrings.join(",\n ")},
2884
3290
  ]`;
2885
3291
  }
2886
- function generateCollectionCreation(collection, varName = "collection") {
3292
+ function generateCollectionCreation(collection, varName = "collection", isLast = false) {
2887
3293
  const lines = [];
2888
3294
  lines.push(` const ${varName} = new Collection({`);
2889
3295
  lines.push(` name: "${collection.name}",`);
@@ -2899,7 +3305,7 @@ function generateCollectionCreation(collection, varName = "collection") {
2899
3305
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
2900
3306
  lines.push(` });`);
2901
3307
  lines.push(``);
2902
- lines.push(` app.save(${varName});`);
3308
+ lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
2903
3309
  return lines.join("\n");
2904
3310
  }
2905
3311
  function getFieldConstructorName(fieldType) {
@@ -2930,7 +3336,8 @@ function generateFieldConstructorOptions(field) {
2930
3336
  }
2931
3337
  }
2932
3338
  if (field.relation && field.type === "relation") {
2933
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
3339
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
3340
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2934
3341
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2935
3342
  if (field.relation.maxSelect !== void 0) {
2936
3343
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2944,7 +3351,7 @@ function generateFieldConstructorOptions(field) {
2944
3351
  }
2945
3352
  return parts.join(",\n");
2946
3353
  }
2947
- function generateFieldAddition(collectionName, field, varName) {
3354
+ function generateFieldAddition(collectionName, field, varName, isLast = false) {
2948
3355
  const lines = [];
2949
3356
  const constructorName = getFieldConstructorName(field.type);
2950
3357
  const collectionVar = varName || `collection_${collectionName}_${field.name}`;
@@ -2954,10 +3361,10 @@ function generateFieldAddition(collectionName, field, varName) {
2954
3361
  lines.push(generateFieldConstructorOptions(field));
2955
3362
  lines.push(` }));`);
2956
3363
  lines.push(``);
2957
- lines.push(` app.save(${collectionVar});`);
3364
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2958
3365
  return lines.join("\n");
2959
3366
  }
2960
- function generateFieldModification(collectionName, modification, varName) {
3367
+ function generateFieldModification(collectionName, modification, varName, isLast = false) {
2961
3368
  const lines = [];
2962
3369
  const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
2963
3370
  const fieldVar = `${collectionVar}_field`;
@@ -2971,7 +3378,8 @@ function generateFieldModification(collectionName, modification, varName) {
2971
3378
  } else if (change.property.startsWith("relation.")) {
2972
3379
  const relationKey = change.property.replace("relation.", "");
2973
3380
  if (relationKey === "collection") {
2974
- const collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
3381
+ const isUsersCollection = String(change.newValue).toLowerCase() === "users";
3382
+ const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
2975
3383
  lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
2976
3384
  } else {
2977
3385
  lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
@@ -2981,10 +3389,10 @@ function generateFieldModification(collectionName, modification, varName) {
2981
3389
  }
2982
3390
  }
2983
3391
  lines.push(``);
2984
- lines.push(` app.save(${collectionVar});`);
3392
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2985
3393
  return lines.join("\n");
2986
3394
  }
2987
- function generateFieldDeletion(collectionName, fieldName, varName) {
3395
+ function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
2988
3396
  const lines = [];
2989
3397
  const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
2990
3398
  const fieldVar = `${collectionVar}_field`;
@@ -2993,18 +3401,18 @@ function generateFieldDeletion(collectionName, fieldName, varName) {
2993
3401
  lines.push(``);
2994
3402
  lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
2995
3403
  lines.push(``);
2996
- lines.push(` app.save(${collectionVar});`);
3404
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2997
3405
  return lines.join("\n");
2998
3406
  }
2999
- function generateIndexAddition(collectionName, index, varName) {
3407
+ function generateIndexAddition(collectionName, index, varName, isLast = false) {
3000
3408
  const lines = [];
3001
3409
  const collectionVar = varName || `collection_${collectionName}_idx`;
3002
3410
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3003
3411
  lines.push(` ${collectionVar}.indexes.push("${index}");`);
3004
- lines.push(` app.save(${collectionVar});`);
3412
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3005
3413
  return lines.join("\n");
3006
3414
  }
3007
- function generateIndexRemoval(collectionName, index, varName) {
3415
+ function generateIndexRemoval(collectionName, index, varName, isLast = false) {
3008
3416
  const lines = [];
3009
3417
  const collectionVar = varName || `collection_${collectionName}_idx`;
3010
3418
  const indexVar = `${collectionVar}_indexToRemove`;
@@ -3013,29 +3421,29 @@ function generateIndexRemoval(collectionName, index, varName) {
3013
3421
  lines.push(` if (${indexVar} !== -1) {`);
3014
3422
  lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
3015
3423
  lines.push(` }`);
3016
- lines.push(` app.save(${collectionVar});`);
3424
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3017
3425
  return lines.join("\n");
3018
3426
  }
3019
- function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
3427
+ function generateRuleUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
3020
3428
  const lines = [];
3021
3429
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3022
3430
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3023
3431
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3024
- lines.push(` app.save(${collectionVar});`);
3432
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3025
3433
  return lines.join("\n");
3026
3434
  }
3027
- function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
3435
+ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
3028
3436
  const lines = [];
3029
3437
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3030
3438
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3031
3439
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3032
- lines.push(` app.save(${collectionVar});`);
3440
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3033
3441
  return lines.join("\n");
3034
3442
  }
3035
- function generateCollectionDeletion(collectionName, varName = "collection") {
3443
+ function generateCollectionDeletion(collectionName, varName = "collection", isLast = false) {
3036
3444
  const lines = [];
3037
3445
  lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
3038
- lines.push(` app.delete(${varName});`);
3446
+ lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
3039
3447
  return lines.join("\n");
3040
3448
  }
3041
3449
  function generateUpMigration(diff) {
@@ -3127,7 +3535,24 @@ function generateUpMigration(diff) {
3127
3535
  lines.push(` // No changes detected`);
3128
3536
  lines.push(``);
3129
3537
  }
3130
- return lines.join("\n");
3538
+ let code = lines.join("\n");
3539
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3540
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3541
+ const saveMatches = [...code.matchAll(savePattern)];
3542
+ const deleteMatches = [...code.matchAll(deletePattern)];
3543
+ const allMatches = [
3544
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3545
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3546
+ ].sort((a, b) => b.index - a.index);
3547
+ if (allMatches.length > 0) {
3548
+ const lastMatch = allMatches[0];
3549
+ if (lastMatch.type === "save") {
3550
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3551
+ } else {
3552
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3553
+ }
3554
+ }
3555
+ return code;
3131
3556
  }
3132
3557
  function generateDownMigration(diff) {
3133
3558
  const lines = [];
@@ -3229,7 +3654,24 @@ function generateDownMigration(diff) {
3229
3654
  lines.push(` // No changes to revert`);
3230
3655
  lines.push(``);
3231
3656
  }
3232
- return lines.join("\n");
3657
+ let code = lines.join("\n");
3658
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3659
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3660
+ const saveMatches = [...code.matchAll(savePattern)];
3661
+ const deleteMatches = [...code.matchAll(deletePattern)];
3662
+ const allMatches = [
3663
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3664
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3665
+ ].sort((a, b) => b.index - a.index);
3666
+ if (allMatches.length > 0) {
3667
+ const lastMatch = allMatches[0];
3668
+ if (lastMatch.type === "save") {
3669
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3670
+ } else {
3671
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3672
+ }
3673
+ }
3674
+ return code;
3233
3675
  }
3234
3676
  function generate(diff, config) {
3235
3677
  const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
@@ -3363,6 +3805,7 @@ exports.isSystemCollection = isSystemCollection;
3363
3805
  exports.loadBaseMigration = loadBaseMigration;
3364
3806
  exports.loadSnapshot = loadSnapshot;
3365
3807
  exports.loadSnapshotIfExists = loadSnapshotIfExists;
3808
+ exports.loadSnapshotWithMigrations = loadSnapshotWithMigrations;
3366
3809
  exports.mapZodArrayType = mapZodArrayType;
3367
3810
  exports.mapZodBooleanType = mapZodBooleanType;
3368
3811
  exports.mapZodDateType = mapZodDateType;