pocketbase-zod-schema 0.2.5 → 0.3.1

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 (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli/index.cjs +497 -298
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.d.cts +2 -2
  5. package/dist/cli/index.d.ts +2 -2
  6. package/dist/cli/index.js +497 -298
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +497 -298
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +497 -298
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/cli/utils/index.d.cts +2 -2
  13. package/dist/cli/utils/index.d.ts +2 -2
  14. package/dist/{fields-YjcpBXVp.d.cts → fields-RVj26U-O.d.cts} +17 -0
  15. package/dist/{fields-YjcpBXVp.d.ts → fields-RVj26U-O.d.ts} +17 -0
  16. package/dist/index.cjs +575 -155
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +4 -4
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +576 -144
  21. package/dist/index.js.map +1 -1
  22. package/dist/migration/analyzer.cjs +12 -2
  23. package/dist/migration/analyzer.cjs.map +1 -1
  24. package/dist/migration/analyzer.d.cts +2 -2
  25. package/dist/migration/analyzer.d.ts +2 -2
  26. package/dist/migration/analyzer.js +12 -2
  27. package/dist/migration/analyzer.js.map +1 -1
  28. package/dist/migration/diff.cjs +150 -24
  29. package/dist/migration/diff.cjs.map +1 -1
  30. package/dist/migration/diff.d.cts +4 -4
  31. package/dist/migration/diff.d.ts +4 -4
  32. package/dist/migration/diff.js +150 -24
  33. package/dist/migration/diff.js.map +1 -1
  34. package/dist/migration/generator.cjs +360 -46
  35. package/dist/migration/generator.cjs.map +1 -1
  36. package/dist/migration/generator.d.cts +59 -12
  37. package/dist/migration/generator.d.ts +59 -12
  38. package/dist/migration/generator.js +356 -47
  39. package/dist/migration/generator.js.map +1 -1
  40. package/dist/migration/index.cjs +561 -90
  41. package/dist/migration/index.cjs.map +1 -1
  42. package/dist/migration/index.d.cts +3 -3
  43. package/dist/migration/index.d.ts +3 -3
  44. package/dist/migration/index.js +561 -90
  45. package/dist/migration/index.js.map +1 -1
  46. package/dist/migration/snapshot.cjs +51 -18
  47. package/dist/migration/snapshot.cjs.map +1 -1
  48. package/dist/migration/snapshot.d.cts +2 -2
  49. package/dist/migration/snapshot.d.ts +2 -2
  50. package/dist/migration/snapshot.js +51 -18
  51. package/dist/migration/snapshot.js.map +1 -1
  52. package/dist/migration/utils/index.cjs +66 -0
  53. package/dist/migration/utils/index.cjs.map +1 -1
  54. package/dist/migration/utils/index.d.cts +39 -202
  55. package/dist/migration/utils/index.d.ts +39 -202
  56. package/dist/migration/utils/index.js +65 -1
  57. package/dist/migration/utils/index.js.map +1 -1
  58. package/dist/schema.cjs +0 -61
  59. package/dist/schema.cjs.map +1 -1
  60. package/dist/schema.d.cts +2 -86
  61. package/dist/schema.d.ts +2 -86
  62. package/dist/schema.js +1 -50
  63. package/dist/schema.js.map +1 -1
  64. package/dist/type-mapper-CZzVeDj7.d.ts +208 -0
  65. package/dist/type-mapper-DaBe-1ph.d.cts +208 -0
  66. package/dist/{types-LFBGHl9Y.d.ts → types-CUVzgZ9k.d.ts} +33 -2
  67. package/dist/{types-mhQXWNi3.d.cts → types-D-Fsdn_O.d.cts} +33 -2
  68. package/package.json +1 -1
@@ -7,6 +7,7 @@ var fs5 = require('fs');
7
7
  var path5 = require('path');
8
8
  var url = require('url');
9
9
  var zod = require('zod');
10
+ var crypto = require('crypto');
10
11
  var ora = require('ora');
11
12
 
12
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -1320,12 +1321,22 @@ function isAuthCollection(fields) {
1320
1321
  function buildFieldDefinition(fieldName, zodType) {
1321
1322
  const fieldMetadata = extractFieldMetadata(zodType.description);
1322
1323
  if (fieldMetadata) {
1323
- const required2 = isFieldRequired(zodType);
1324
+ let required2;
1325
+ if (fieldMetadata.type === "number") {
1326
+ if (fieldMetadata.options?.required !== void 0) {
1327
+ required2 = fieldMetadata.options.required;
1328
+ } else {
1329
+ required2 = false;
1330
+ }
1331
+ } else {
1332
+ required2 = isFieldRequired(zodType);
1333
+ }
1334
+ const { required: _required, ...options2 } = fieldMetadata.options || {};
1324
1335
  const fieldDef2 = {
1325
1336
  name: fieldName,
1326
1337
  type: fieldMetadata.type,
1327
1338
  required: required2,
1328
- options: fieldMetadata.options
1339
+ options: Object.keys(options2).length > 0 ? options2 : void 0
1329
1340
  };
1330
1341
  if (fieldMetadata.type === "relation") {
1331
1342
  const relationMetadata2 = extractRelationMetadata(zodType.description);
@@ -1495,6 +1506,67 @@ async function buildSchemaDefinition(config) {
1495
1506
  async function parseSchemaFiles(config) {
1496
1507
  return buildSchemaDefinition(config);
1497
1508
  }
1509
+ function generateCollectionId() {
1510
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
1511
+ const idLength = 15;
1512
+ const bytes = crypto.randomBytes(idLength);
1513
+ let id = "pb_";
1514
+ for (let i = 0; i < idLength; i++) {
1515
+ const index = bytes[i] % chars.length;
1516
+ id += chars[index];
1517
+ }
1518
+ return id;
1519
+ }
1520
+ var CollectionIdRegistry = class {
1521
+ ids;
1522
+ constructor() {
1523
+ this.ids = /* @__PURE__ */ new Set();
1524
+ }
1525
+ /**
1526
+ * Generates a unique collection ID for a given collection name
1527
+ * Retries up to 10 times if collision occurs (extremely rare)
1528
+ * Special case: returns "_pb_users_auth_" for users collection
1529
+ *
1530
+ * @param collectionName - The name of the collection (optional)
1531
+ * @returns A unique collection ID
1532
+ * @throws Error if unable to generate unique ID after max attempts
1533
+ */
1534
+ generate(collectionName) {
1535
+ if (collectionName && collectionName.toLowerCase() === "users") {
1536
+ const usersId = "_pb_users_auth_";
1537
+ if (!this.has(usersId)) {
1538
+ this.register(usersId);
1539
+ }
1540
+ return usersId;
1541
+ }
1542
+ const maxAttempts = 10;
1543
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1544
+ const id = generateCollectionId();
1545
+ if (!this.has(id)) {
1546
+ this.register(id);
1547
+ return id;
1548
+ }
1549
+ }
1550
+ throw new Error("Failed to generate unique collection ID after maximum attempts");
1551
+ }
1552
+ /**
1553
+ * Checks if an ID has already been registered
1554
+ *
1555
+ * @param id - The collection ID to check
1556
+ * @returns True if the ID exists in the registry
1557
+ */
1558
+ has(id) {
1559
+ return this.ids.has(id);
1560
+ }
1561
+ /**
1562
+ * Registers a collection ID in the registry
1563
+ *
1564
+ * @param id - The collection ID to register
1565
+ */
1566
+ register(id) {
1567
+ this.ids.add(id);
1568
+ }
1569
+ };
1498
1570
 
1499
1571
  // src/migration/diff.ts
1500
1572
  var DEFAULT_CONFIG2 = {
@@ -1637,18 +1709,49 @@ function compareFieldConstraints(currentField, previousField) {
1637
1709
  }
1638
1710
  return changes;
1639
1711
  }
1712
+ function normalizeOptionValue(key, value, fieldType) {
1713
+ if (key === "maxSelect" && value === 1 && (fieldType === "select" || fieldType === "file")) {
1714
+ return void 0;
1715
+ }
1716
+ if (key === "maxSize" && value === 0 && fieldType === "file") {
1717
+ return void 0;
1718
+ }
1719
+ if (fieldType === "file") {
1720
+ if (key === "mimeTypes" && Array.isArray(value) && value.length === 0) {
1721
+ return void 0;
1722
+ }
1723
+ if (key === "thumbs" && Array.isArray(value) && value.length === 0) {
1724
+ return void 0;
1725
+ }
1726
+ if (key === "protected" && value === false) {
1727
+ return void 0;
1728
+ }
1729
+ }
1730
+ if (fieldType === "autodate") {
1731
+ if (key === "onCreate" && value === true) {
1732
+ return void 0;
1733
+ }
1734
+ if (key === "onUpdate" && value === false) {
1735
+ return void 0;
1736
+ }
1737
+ }
1738
+ return value;
1739
+ }
1640
1740
  function compareFieldOptions(currentField, previousField) {
1641
1741
  const changes = [];
1642
1742
  const currentOptions = currentField.options || {};
1643
1743
  const previousOptions = previousField.options || {};
1644
1744
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
1745
+ const fieldType = currentField.type;
1645
1746
  for (const key of allKeys) {
1646
1747
  const currentValue = currentOptions[key];
1647
1748
  const previousValue = previousOptions[key];
1648
- if (currentValue === void 0 && previousValue === void 0) {
1749
+ const normalizedCurrent = normalizeOptionValue(key, currentValue, fieldType);
1750
+ const normalizedPrevious = normalizeOptionValue(key, previousValue, fieldType);
1751
+ if (normalizedCurrent === void 0 && normalizedPrevious === void 0) {
1649
1752
  continue;
1650
1753
  }
1651
- if (!areValuesEqual(currentValue, previousValue)) {
1754
+ if (!areValuesEqual(normalizedCurrent, normalizedPrevious)) {
1652
1755
  changes.push({
1653
1756
  property: `options.${key}`,
1654
1757
  oldValue: previousValue,
@@ -1658,7 +1761,7 @@ function compareFieldOptions(currentField, previousField) {
1658
1761
  }
1659
1762
  return changes;
1660
1763
  }
1661
- function compareRelationConfigurations(currentField, previousField) {
1764
+ function compareRelationConfigurations(currentField, previousField, collectionIdToName) {
1662
1765
  const changes = [];
1663
1766
  const currentRelation = currentField.relation;
1664
1767
  const previousRelation = previousField.relation;
@@ -1670,8 +1773,8 @@ function compareRelationConfigurations(currentField, previousField) {
1670
1773
  }
1671
1774
  const normalizeCollection = (collection) => {
1672
1775
  if (!collection) return collection;
1673
- if (collection === "_pb_users_auth_") {
1674
- return "Users";
1776
+ if (collectionIdToName && collectionIdToName.has(collection)) {
1777
+ return collectionIdToName.get(collection);
1675
1778
  }
1676
1779
  const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1677
1780
  if (nameMatch) {
@@ -1681,13 +1784,11 @@ function compareRelationConfigurations(currentField, previousField) {
1681
1784
  };
1682
1785
  const normalizedCurrent = normalizeCollection(currentRelation.collection);
1683
1786
  const normalizedPrevious = normalizeCollection(previousRelation.collection);
1684
- if (normalizedCurrent !== normalizedPrevious) {
1787
+ if (normalizedCurrent.toLowerCase() !== normalizedPrevious.toLowerCase()) {
1685
1788
  changes.push({
1686
1789
  property: "relation.collection",
1687
- oldValue: normalizedPrevious,
1688
- // Use normalized value for clarity
1689
- newValue: normalizedCurrent
1690
- // Use normalized value for clarity
1790
+ oldValue: previousRelation.collection,
1791
+ newValue: currentRelation.collection
1691
1792
  });
1692
1793
  }
1693
1794
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -1697,14 +1798,20 @@ function compareRelationConfigurations(currentField, previousField) {
1697
1798
  newValue: currentRelation.cascadeDelete
1698
1799
  });
1699
1800
  }
1700
- if (currentRelation.maxSelect !== previousRelation.maxSelect) {
1801
+ const normalizeMax = (val) => val === 1 ? null : val;
1802
+ const currentMax = normalizeMax(currentRelation.maxSelect);
1803
+ const previousMax = normalizeMax(previousRelation.maxSelect);
1804
+ if (currentMax != previousMax) {
1701
1805
  changes.push({
1702
1806
  property: "relation.maxSelect",
1703
1807
  oldValue: previousRelation.maxSelect,
1704
1808
  newValue: currentRelation.maxSelect
1705
1809
  });
1706
1810
  }
1707
- if (currentRelation.minSelect !== previousRelation.minSelect) {
1811
+ const normalizeMin = (val) => val === 0 ? null : val;
1812
+ const currentMin = normalizeMin(currentRelation.minSelect);
1813
+ const previousMin = normalizeMin(previousRelation.minSelect);
1814
+ if (currentMin != previousMin) {
1708
1815
  changes.push({
1709
1816
  property: "relation.minSelect",
1710
1817
  oldValue: previousRelation.minSelect,
@@ -1713,7 +1820,7 @@ function compareRelationConfigurations(currentField, previousField) {
1713
1820
  }
1714
1821
  return changes;
1715
1822
  }
1716
- function detectFieldChanges(currentField, previousField) {
1823
+ function detectFieldChanges(currentField, previousField, collectionIdToName) {
1717
1824
  const changes = [];
1718
1825
  const typeChange = compareFieldTypes(currentField, previousField);
1719
1826
  if (typeChange) {
@@ -1722,7 +1829,7 @@ function detectFieldChanges(currentField, previousField) {
1722
1829
  changes.push(...compareFieldConstraints(currentField, previousField));
1723
1830
  changes.push(...compareFieldOptions(currentField, previousField));
1724
1831
  if (currentField.type === "relation" && previousField.type === "relation") {
1725
- changes.push(...compareRelationConfigurations(currentField, previousField));
1832
+ changes.push(...compareRelationConfigurations(currentField, previousField, collectionIdToName));
1726
1833
  }
1727
1834
  return changes;
1728
1835
  }
@@ -1733,7 +1840,7 @@ function compareIndexes(currentIndexes = [], previousIndexes = []) {
1733
1840
  const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
1734
1841
  return { indexesToAdd, indexesToRemove };
1735
1842
  }
1736
- function compareRules(currentRules, previousRules) {
1843
+ function compareRules(currentRules, previousRules, currentPermissions, previousPermissions) {
1737
1844
  const updates = [];
1738
1845
  const ruleTypes = [
1739
1846
  "listRule",
@@ -1744,8 +1851,8 @@ function compareRules(currentRules, previousRules) {
1744
1851
  "manageRule"
1745
1852
  ];
1746
1853
  for (const ruleType of ruleTypes) {
1747
- const currentValue = currentRules?.[ruleType] ?? null;
1748
- const previousValue = previousRules?.[ruleType] ?? null;
1854
+ const currentValue = currentRules?.[ruleType] ?? currentPermissions?.[ruleType] ?? null;
1855
+ const previousValue = previousRules?.[ruleType] ?? previousPermissions?.[ruleType] ?? null;
1749
1856
  if (currentValue !== previousValue) {
1750
1857
  updates.push({
1751
1858
  ruleType,
@@ -1772,7 +1879,7 @@ function comparePermissions(currentPermissions, previousPermissions) {
1772
1879
  }
1773
1880
  return changes;
1774
1881
  }
1775
- function compareCollectionFields(currentCollection, previousCollection, config) {
1882
+ function compareCollectionFields(currentCollection, previousCollection, config, collectionIdToName) {
1776
1883
  let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
1777
1884
  const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
1778
1885
  const fieldsToModify = [];
@@ -1782,7 +1889,7 @@ function compareCollectionFields(currentCollection, previousCollection, config)
1782
1889
  }
1783
1890
  const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
1784
1891
  for (const [currentField, previousField] of matchedFields) {
1785
- const changes = detectFieldChanges(currentField, previousField);
1892
+ const changes = detectFieldChanges(currentField, previousField, collectionIdToName);
1786
1893
  if (changes.length > 0) {
1787
1894
  fieldsToModify.push({
1788
1895
  fieldName: currentField.name,
@@ -1794,14 +1901,20 @@ function compareCollectionFields(currentCollection, previousCollection, config)
1794
1901
  }
1795
1902
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
1796
1903
  }
1797
- function buildCollectionModification(currentCollection, previousCollection, config) {
1904
+ function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
1798
1905
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
1799
1906
  currentCollection,
1800
1907
  previousCollection,
1801
- config
1908
+ config,
1909
+ collectionIdToName
1802
1910
  );
1803
1911
  const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
1804
- const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
1912
+ const rulesToUpdate = compareRules(
1913
+ currentCollection.rules,
1914
+ previousCollection.rules,
1915
+ currentCollection.permissions,
1916
+ previousCollection.permissions
1917
+ );
1805
1918
  const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
1806
1919
  return {
1807
1920
  collection: currentCollection.name,
@@ -1818,6 +1931,14 @@ function hasChanges(modification) {
1818
1931
  return modification.fieldsToAdd.length > 0 || modification.fieldsToRemove.length > 0 || modification.fieldsToModify.length > 0 || modification.indexesToAdd.length > 0 || modification.indexesToRemove.length > 0 || modification.rulesToUpdate.length > 0 || modification.permissionsToUpdate.length > 0;
1819
1932
  }
1820
1933
  function aggregateChanges(currentSchema, previousSnapshot, config) {
1934
+ const collectionIdToName = /* @__PURE__ */ new Map();
1935
+ if (previousSnapshot) {
1936
+ for (const [name, collection] of previousSnapshot.collections) {
1937
+ if (collection.id) {
1938
+ collectionIdToName.set(collection.id, name);
1939
+ }
1940
+ }
1941
+ }
1821
1942
  const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
1822
1943
  const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
1823
1944
  const filteredCollectionsToCreate = collectionsToCreate.filter(
@@ -1826,16 +1947,28 @@ function aggregateChanges(currentSchema, previousSnapshot, config) {
1826
1947
  const filteredCollectionsToDelete = collectionsToDelete.filter(
1827
1948
  (collection) => !isSystemCollection(collection.name, config)
1828
1949
  );
1950
+ const registry = new CollectionIdRegistry();
1951
+ const collectionsWithIds = filteredCollectionsToCreate.map((collection) => {
1952
+ if (collection.id) {
1953
+ registry.register(collection.id);
1954
+ return collection;
1955
+ }
1956
+ const id = registry.generate(collection.name);
1957
+ return {
1958
+ ...collection,
1959
+ id
1960
+ };
1961
+ });
1829
1962
  const collectionsToModify = [];
1830
1963
  const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
1831
1964
  for (const [currentCollection, previousCollection] of matchedCollections) {
1832
- const modification = buildCollectionModification(currentCollection, previousCollection, config);
1965
+ const modification = buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName);
1833
1966
  if (hasChanges(modification)) {
1834
1967
  collectionsToModify.push(modification);
1835
1968
  }
1836
1969
  }
1837
1970
  return {
1838
- collectionsToCreate: filteredCollectionsToCreate,
1971
+ collectionsToCreate: collectionsWithIds,
1839
1972
  collectionsToDelete: filteredCollectionsToDelete,
1840
1973
  collectionsToModify
1841
1974
  };
@@ -1918,42 +2051,48 @@ function generateTimestamp(config) {
1918
2051
  }
1919
2052
  return Math.floor(Date.now() / 1e3).toString();
1920
2053
  }
1921
- function generateMigrationDescription(diff) {
1922
- const parts = [];
1923
- if (diff.collectionsToCreate.length > 0) {
1924
- if (diff.collectionsToCreate.length === 1) {
1925
- parts.push(`created_${diff.collectionsToCreate[0].name}`);
1926
- } else {
1927
- parts.push(`created_${diff.collectionsToCreate.length}_collections`);
1928
- }
1929
- }
1930
- if (diff.collectionsToDelete.length > 0) {
1931
- if (diff.collectionsToDelete.length === 1) {
1932
- parts.push(`deleted_${diff.collectionsToDelete[0].name}`);
1933
- } else {
1934
- parts.push(`deleted_${diff.collectionsToDelete.length}_collections`);
1935
- }
1936
- }
1937
- if (diff.collectionsToModify.length > 0) {
1938
- if (diff.collectionsToModify.length === 1) {
1939
- parts.push(`updated_${diff.collectionsToModify[0].collection}`);
1940
- } else {
1941
- parts.push(`updated_${diff.collectionsToModify.length}_collections`);
1942
- }
2054
+ function splitDiffByCollection(diff, baseTimestamp) {
2055
+ const operations = [];
2056
+ let currentTimestamp = parseInt(baseTimestamp, 10);
2057
+ for (const collection of diff.collectionsToCreate) {
2058
+ operations.push({
2059
+ type: "create",
2060
+ collection,
2061
+ timestamp: currentTimestamp.toString()
2062
+ });
2063
+ currentTimestamp += 1;
1943
2064
  }
1944
- if (parts.length === 0) {
1945
- return "no_changes";
2065
+ for (const modification of diff.collectionsToModify) {
2066
+ operations.push({
2067
+ type: "modify",
2068
+ collection: modification.collection,
2069
+ modifications: modification,
2070
+ timestamp: currentTimestamp.toString()
2071
+ });
2072
+ currentTimestamp += 1;
1946
2073
  }
1947
- let description = parts.join("_");
1948
- if (description.length > 80) {
1949
- description = description.substring(0, 77) + "...";
2074
+ for (const collection of diff.collectionsToDelete) {
2075
+ operations.push({
2076
+ type: "delete",
2077
+ collection: collection.name || collection,
2078
+ // Handle both object and string
2079
+ timestamp: currentTimestamp.toString()
2080
+ });
2081
+ currentTimestamp += 1;
1950
2082
  }
1951
- return description;
2083
+ return operations;
1952
2084
  }
1953
- function generateMigrationFilename(diff, config) {
1954
- const timestamp = generateTimestamp(config);
1955
- const description = generateMigrationDescription(diff);
1956
- return `${timestamp}_${description}.js`;
2085
+ function generateCollectionMigrationFilename(operation) {
2086
+ const timestamp = operation.timestamp;
2087
+ const operationType = operation.type === "modify" ? "updated" : operation.type === "create" ? "created" : "deleted";
2088
+ let collectionName;
2089
+ if (typeof operation.collection === "string") {
2090
+ collectionName = operation.collection;
2091
+ } else {
2092
+ collectionName = operation.collection.name;
2093
+ }
2094
+ const sanitizedName = collectionName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
2095
+ return `${timestamp}_${operationType}_${sanitizedName}.js`;
1957
2096
  }
1958
2097
  function createMigrationFileStructure(upCode, downCode, config) {
1959
2098
  const mergedConfig = config ? mergeConfig3(config) : DEFAULT_CONFIG3;
@@ -2025,14 +2164,13 @@ function formatValue(value) {
2025
2164
  return "null";
2026
2165
  }
2027
2166
  if (typeof value === "string") {
2028
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
2167
+ return JSON.stringify(value);
2029
2168
  }
2030
2169
  if (typeof value === "number" || typeof value === "boolean") {
2031
2170
  return String(value);
2032
2171
  }
2033
2172
  if (Array.isArray(value)) {
2034
- const items = value.map((v) => formatValue(v)).join(", ");
2035
- return `[${items}]`;
2173
+ return JSON.stringify(value).replace(/","/g, '", "');
2036
2174
  }
2037
2175
  if (typeof value === "object") {
2038
2176
  const entries = Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
@@ -2040,7 +2178,7 @@ function formatValue(value) {
2040
2178
  }
2041
2179
  return String(value);
2042
2180
  }
2043
- function generateFieldDefinitionObject(field) {
2181
+ function generateFieldDefinitionObject(field, collectionIdMap) {
2044
2182
  const parts = [];
2045
2183
  parts.push(` name: "${field.name}"`);
2046
2184
  parts.push(` type: "${field.type}"`);
@@ -2048,34 +2186,47 @@ function generateFieldDefinitionObject(field) {
2048
2186
  if (field.unique !== void 0) {
2049
2187
  parts.push(` unique: ${field.unique}`);
2050
2188
  }
2189
+ if (field.type === "select") {
2190
+ const maxSelect = field.options?.maxSelect ?? 1;
2191
+ parts.push(` maxSelect: ${maxSelect}`);
2192
+ const values = field.options?.values ?? [];
2193
+ parts.push(` values: ${formatValue(values)}`);
2194
+ }
2051
2195
  if (field.options && Object.keys(field.options).length > 0) {
2052
2196
  for (const [key, value] of Object.entries(field.options)) {
2197
+ if (field.type === "select" && (key === "maxSelect" || key === "values")) {
2198
+ continue;
2199
+ }
2053
2200
  parts.push(` ${key}: ${formatValue(value)}`);
2054
2201
  }
2055
2202
  }
2056
2203
  if (field.relation) {
2057
2204
  const isUsersCollection = field.relation.collection.toLowerCase() === "users";
2058
- const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2059
- parts.push(` collectionId: ${collectionIdPlaceholder}`);
2060
- if (field.relation.maxSelect !== void 0) {
2061
- parts.push(` maxSelect: ${field.relation.maxSelect}`);
2062
- }
2063
- if (field.relation.minSelect !== void 0) {
2064
- parts.push(` minSelect: ${field.relation.minSelect}`);
2065
- }
2066
- if (field.relation.cascadeDelete !== void 0) {
2067
- parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
2205
+ let collectionIdValue;
2206
+ if (isUsersCollection) {
2207
+ collectionIdValue = '"_pb_users_auth_"';
2208
+ } else if (collectionIdMap && collectionIdMap.has(field.relation.collection)) {
2209
+ collectionIdValue = `"${collectionIdMap.get(field.relation.collection)}"`;
2210
+ } else {
2211
+ collectionIdValue = `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2068
2212
  }
2213
+ parts.push(` collectionId: ${collectionIdValue}`);
2214
+ const maxSelect = field.relation.maxSelect ?? 1;
2215
+ parts.push(` maxSelect: ${maxSelect}`);
2216
+ const minSelect = field.relation.minSelect ?? null;
2217
+ parts.push(` minSelect: ${minSelect}`);
2218
+ const cascadeDelete = field.relation.cascadeDelete ?? false;
2219
+ parts.push(` cascadeDelete: ${cascadeDelete}`);
2069
2220
  }
2070
2221
  return ` {
2071
2222
  ${parts.join(",\n")},
2072
2223
  }`;
2073
2224
  }
2074
- function generateFieldsArray(fields) {
2225
+ function generateFieldsArray(fields, collectionIdMap) {
2075
2226
  if (fields.length === 0) {
2076
2227
  return "[]";
2077
2228
  }
2078
- const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field));
2229
+ const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field, collectionIdMap));
2079
2230
  return `[
2080
2231
  ${fieldObjects.join(",\n")},
2081
2232
  ]`;
@@ -2134,7 +2285,7 @@ function generateIndexesArray(indexes) {
2134
2285
  if (!indexes || indexes.length === 0) {
2135
2286
  return "[]";
2136
2287
  }
2137
- const indexStrings = indexes.map((idx) => `"${idx}"`);
2288
+ const indexStrings = indexes.map((idx) => JSON.stringify(idx));
2138
2289
  return `[
2139
2290
  ${indexStrings.join(",\n ")},
2140
2291
  ]`;
@@ -2188,9 +2339,12 @@ function getSystemFields() {
2188
2339
  }
2189
2340
  ];
2190
2341
  }
2191
- function generateCollectionCreation(collection, varName = "collection", isLast = false) {
2342
+ function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
2192
2343
  const lines = [];
2193
2344
  lines.push(` const ${varName} = new Collection({`);
2345
+ if (collection.id) {
2346
+ lines.push(` id: ${formatValue(collection.id)},`);
2347
+ }
2194
2348
  lines.push(` name: "${collection.name}",`);
2195
2349
  lines.push(` type: "${collection.type}",`);
2196
2350
  const permissionsCode = generateCollectionPermissions(collection.permissions);
@@ -2202,7 +2356,7 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
2202
2356
  }
2203
2357
  const systemFields = getSystemFields();
2204
2358
  const allFields = [...systemFields, ...collection.fields];
2205
- lines.push(` fields: ${generateFieldsArray(allFields)},`);
2359
+ lines.push(` fields: ${generateFieldsArray(allFields, collectionIdMap)},`);
2206
2360
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
2207
2361
  lines.push(` });`);
2208
2362
  lines.push(``);
@@ -2224,42 +2378,59 @@ function getFieldConstructorName(fieldType) {
2224
2378
  };
2225
2379
  return constructorMap[fieldType] || "TextField";
2226
2380
  }
2227
- function generateFieldConstructorOptions(field) {
2381
+ function generateFieldConstructorOptions(field, collectionIdMap) {
2228
2382
  const parts = [];
2229
2383
  parts.push(` name: "${field.name}"`);
2230
2384
  parts.push(` required: ${field.required}`);
2231
2385
  if (field.unique !== void 0) {
2232
2386
  parts.push(` unique: ${field.unique}`);
2233
2387
  }
2388
+ if (field.type === "select") {
2389
+ const maxSelect = field.options?.maxSelect ?? 1;
2390
+ parts.push(` maxSelect: ${maxSelect}`);
2391
+ const values = field.options?.values ?? [];
2392
+ parts.push(` values: ${formatValue(values)}`);
2393
+ }
2234
2394
  if (field.options && Object.keys(field.options).length > 0) {
2235
2395
  for (const [key, value] of Object.entries(field.options)) {
2236
- parts.push(` ${key}: ${formatValue(value)}`);
2396
+ if (field.type === "select" && (key === "maxSelect" || key === "values")) {
2397
+ continue;
2398
+ }
2399
+ if (field.type === "number" && key === "noDecimal") {
2400
+ parts.push(` onlyInt: ${formatValue(value)}`);
2401
+ } else {
2402
+ parts.push(` ${key}: ${formatValue(value)}`);
2403
+ }
2237
2404
  }
2238
2405
  }
2239
2406
  if (field.relation && field.type === "relation") {
2240
2407
  const isUsersCollection = field.relation.collection.toLowerCase() === "users";
2241
- const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2242
- parts.push(` collectionId: ${collectionIdPlaceholder}`);
2243
- if (field.relation.maxSelect !== void 0) {
2244
- parts.push(` maxSelect: ${field.relation.maxSelect}`);
2245
- }
2246
- if (field.relation.minSelect !== void 0) {
2247
- parts.push(` minSelect: ${field.relation.minSelect}`);
2248
- }
2249
- if (field.relation.cascadeDelete !== void 0) {
2250
- parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
2408
+ let collectionIdValue;
2409
+ if (isUsersCollection) {
2410
+ collectionIdValue = '"_pb_users_auth_"';
2411
+ } else if (collectionIdMap && collectionIdMap.has(field.relation.collection)) {
2412
+ collectionIdValue = `"${collectionIdMap.get(field.relation.collection)}"`;
2413
+ } else {
2414
+ collectionIdValue = `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2251
2415
  }
2416
+ parts.push(` collectionId: ${collectionIdValue}`);
2417
+ const maxSelect = field.relation.maxSelect ?? 1;
2418
+ parts.push(` maxSelect: ${maxSelect}`);
2419
+ const minSelect = field.relation.minSelect ?? null;
2420
+ parts.push(` minSelect: ${minSelect}`);
2421
+ const cascadeDelete = field.relation.cascadeDelete ?? false;
2422
+ parts.push(` cascadeDelete: ${cascadeDelete}`);
2252
2423
  }
2253
2424
  return parts.join(",\n");
2254
2425
  }
2255
- function generateFieldAddition(collectionName, field, varName, isLast = false) {
2426
+ function generateFieldAddition(collectionName, field, varName, isLast = false, collectionIdMap) {
2256
2427
  const lines = [];
2257
2428
  const constructorName = getFieldConstructorName(field.type);
2258
2429
  const collectionVar = varName || `collection_${collectionName}_${field.name}`;
2259
2430
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
2260
2431
  lines.push(``);
2261
2432
  lines.push(` ${collectionVar}.fields.add(new ${constructorName}({`);
2262
- lines.push(generateFieldConstructorOptions(field));
2433
+ lines.push(generateFieldConstructorOptions(field, collectionIdMap));
2263
2434
  lines.push(` }));`);
2264
2435
  lines.push(``);
2265
2436
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
@@ -2309,7 +2480,7 @@ function generateIndexAddition(collectionName, index, varName, isLast = false) {
2309
2480
  const lines = [];
2310
2481
  const collectionVar = varName || `collection_${collectionName}_idx`;
2311
2482
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
2312
- lines.push(` ${collectionVar}.indexes.push("${index}");`);
2483
+ lines.push(` ${collectionVar}.indexes.push(${JSON.stringify(index)});`);
2313
2484
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2314
2485
  return lines.join("\n");
2315
2486
  }
@@ -2318,7 +2489,7 @@ function generateIndexRemoval(collectionName, index, varName, isLast = false) {
2318
2489
  const collectionVar = varName || `collection_${collectionName}_idx`;
2319
2490
  const indexVar = `${collectionVar}_indexToRemove`;
2320
2491
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
2321
- lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === "${index}");`);
2492
+ lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === ${JSON.stringify(index)});`);
2322
2493
  lines.push(` if (${indexVar} !== -1) {`);
2323
2494
  lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
2324
2495
  lines.push(` }`);
@@ -2347,94 +2518,75 @@ function generateCollectionDeletion(collectionName, varName = "collection", isLa
2347
2518
  lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
2348
2519
  return lines.join("\n");
2349
2520
  }
2350
- function generateUpMigration(diff) {
2521
+ function generateOperationUpMigration(operation, collectionIdMap) {
2351
2522
  const lines = [];
2352
- lines.push(` // UP MIGRATION`);
2353
- lines.push(``);
2354
- if (diff.collectionsToCreate.length > 0) {
2355
- lines.push(` // Create new collections`);
2356
- for (let i = 0; i < diff.collectionsToCreate.length; i++) {
2357
- const collection = diff.collectionsToCreate[i];
2358
- const varName = `collection_${collection.name}_create`;
2359
- lines.push(generateCollectionCreation(collection, varName));
2360
- lines.push(``);
2523
+ if (operation.type === "create") {
2524
+ const collection = operation.collection;
2525
+ const varName = `collection_${collection.name}`;
2526
+ lines.push(generateCollectionCreation(collection, varName, true, collectionIdMap));
2527
+ } else if (operation.type === "modify") {
2528
+ const modification = operation.modifications;
2529
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
2530
+ let operationCount = 0;
2531
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
2532
+ for (const field of modification.fieldsToAdd) {
2533
+ operationCount++;
2534
+ const varName = `collection_${collectionName}_add_${field.name}`;
2535
+ const isLast = operationCount === totalOperations;
2536
+ lines.push(generateFieldAddition(collectionName, field, varName, isLast, collectionIdMap));
2537
+ if (!isLast) lines.push("");
2361
2538
  }
2362
- }
2363
- if (diff.collectionsToModify.length > 0) {
2364
- lines.push(` // Modify existing collections`);
2365
- for (const modification of diff.collectionsToModify) {
2366
- const collectionName = modification.collection;
2367
- if (modification.fieldsToAdd.length > 0) {
2368
- lines.push(` // Add fields to ${collectionName}`);
2369
- for (const field of modification.fieldsToAdd) {
2370
- const varName = `collection_${collectionName}_add_${field.name}`;
2371
- lines.push(generateFieldAddition(collectionName, field, varName));
2372
- lines.push(``);
2373
- }
2374
- }
2375
- if (modification.fieldsToModify.length > 0) {
2376
- lines.push(` // Modify fields in ${collectionName}`);
2377
- for (const fieldMod of modification.fieldsToModify) {
2378
- const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
2379
- lines.push(generateFieldModification(collectionName, fieldMod, varName));
2380
- lines.push(``);
2381
- }
2382
- }
2383
- if (modification.fieldsToRemove.length > 0) {
2384
- lines.push(` // Remove fields from ${collectionName}`);
2385
- for (const field of modification.fieldsToRemove) {
2386
- const varName = `collection_${collectionName}_remove_${field.name}`;
2387
- lines.push(generateFieldDeletion(collectionName, field.name, varName));
2388
- lines.push(``);
2389
- }
2390
- }
2391
- if (modification.indexesToAdd.length > 0) {
2392
- lines.push(` // Add indexes to ${collectionName}`);
2393
- for (let i = 0; i < modification.indexesToAdd.length; i++) {
2394
- const index = modification.indexesToAdd[i];
2395
- const varName = `collection_${collectionName}_addidx_${i}`;
2396
- lines.push(generateIndexAddition(collectionName, index, varName));
2397
- lines.push(``);
2398
- }
2399
- }
2400
- if (modification.indexesToRemove.length > 0) {
2401
- lines.push(` // Remove indexes from ${collectionName}`);
2402
- for (let i = 0; i < modification.indexesToRemove.length; i++) {
2403
- const index = modification.indexesToRemove[i];
2404
- const varName = `collection_${collectionName}_rmidx_${i}`;
2405
- lines.push(generateIndexRemoval(collectionName, index, varName));
2406
- lines.push(``);
2407
- }
2539
+ for (const fieldMod of modification.fieldsToModify) {
2540
+ operationCount++;
2541
+ const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
2542
+ const isLast = operationCount === totalOperations;
2543
+ lines.push(generateFieldModification(collectionName, fieldMod, varName, isLast));
2544
+ if (!isLast) lines.push("");
2545
+ }
2546
+ for (const field of modification.fieldsToRemove) {
2547
+ operationCount++;
2548
+ const varName = `collection_${collectionName}_remove_${field.name}`;
2549
+ const isLast = operationCount === totalOperations;
2550
+ lines.push(generateFieldDeletion(collectionName, field.name, varName, isLast));
2551
+ if (!isLast) lines.push("");
2552
+ }
2553
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
2554
+ operationCount++;
2555
+ const index = modification.indexesToAdd[i];
2556
+ const varName = `collection_${collectionName}_addidx_${i}`;
2557
+ const isLast = operationCount === totalOperations;
2558
+ lines.push(generateIndexAddition(collectionName, index, varName, isLast));
2559
+ if (!isLast) lines.push("");
2560
+ }
2561
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
2562
+ operationCount++;
2563
+ const index = modification.indexesToRemove[i];
2564
+ const varName = `collection_${collectionName}_rmidx_${i}`;
2565
+ const isLast = operationCount === totalOperations;
2566
+ lines.push(generateIndexRemoval(collectionName, index, varName, isLast));
2567
+ if (!isLast) lines.push("");
2568
+ }
2569
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
2570
+ for (const permission of modification.permissionsToUpdate) {
2571
+ operationCount++;
2572
+ const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
2573
+ const isLast = operationCount === totalOperations;
2574
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast));
2575
+ if (!isLast) lines.push("");
2408
2576
  }
2409
- if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
2410
- lines.push(` // Update permissions for ${collectionName}`);
2411
- for (const permission of modification.permissionsToUpdate) {
2412
- const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
2413
- lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName));
2414
- lines.push(``);
2415
- }
2416
- } else if (modification.rulesToUpdate.length > 0) {
2417
- lines.push(` // Update rules for ${collectionName}`);
2418
- for (const rule of modification.rulesToUpdate) {
2419
- const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
2420
- lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName));
2421
- lines.push(``);
2422
- }
2577
+ } else if (modification.rulesToUpdate.length > 0) {
2578
+ for (const rule of modification.rulesToUpdate) {
2579
+ operationCount++;
2580
+ const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
2581
+ const isLast = operationCount === totalOperations;
2582
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, isLast));
2583
+ if (!isLast) lines.push("");
2423
2584
  }
2424
2585
  }
2425
- }
2426
- if (diff.collectionsToDelete.length > 0) {
2427
- lines.push(` // Delete collections`);
2428
- for (let i = 0; i < diff.collectionsToDelete.length; i++) {
2429
- const collection = diff.collectionsToDelete[i];
2430
- const varName = `collection_${collection.name}_delete`;
2431
- lines.push(generateCollectionDeletion(collection.name, varName));
2432
- lines.push(``);
2433
- }
2434
- }
2435
- if (lines.length === 2) {
2436
- lines.push(` // No changes detected`);
2437
- lines.push(``);
2586
+ } else if (operation.type === "delete") {
2587
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection.name;
2588
+ const varName = `collection_${collectionName}`;
2589
+ lines.push(generateCollectionDeletion(collectionName, varName, true));
2438
2590
  }
2439
2591
  let code = lines.join("\n");
2440
2592
  const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
@@ -2455,105 +2607,88 @@ function generateUpMigration(diff) {
2455
2607
  }
2456
2608
  return code;
2457
2609
  }
2458
- function generateDownMigration(diff) {
2610
+ function generateOperationDownMigration(operation, collectionIdMap) {
2459
2611
  const lines = [];
2460
- lines.push(` // DOWN MIGRATION (ROLLBACK)`);
2461
- lines.push(``);
2462
- if (diff.collectionsToDelete.length > 0) {
2463
- lines.push(` // Recreate deleted collections`);
2464
- for (let i = 0; i < diff.collectionsToDelete.length; i++) {
2465
- const collection = diff.collectionsToDelete[i];
2466
- const varName = `collection_${collection.name}_recreate`;
2467
- lines.push(generateCollectionCreation(collection, varName));
2468
- lines.push(``);
2469
- }
2470
- }
2471
- if (diff.collectionsToModify.length > 0) {
2472
- lines.push(` // Revert modifications`);
2473
- for (const modification of diff.collectionsToModify) {
2474
- const collectionName = modification.collection;
2475
- if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
2476
- lines.push(` // Revert permissions for ${collectionName}`);
2477
- for (const permission of modification.permissionsToUpdate) {
2478
- const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
2479
- lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName));
2480
- lines.push(``);
2481
- }
2482
- } else if (modification.rulesToUpdate.length > 0) {
2483
- lines.push(` // Revert rules for ${collectionName}`);
2484
- for (const rule of modification.rulesToUpdate) {
2485
- const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
2486
- lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName));
2487
- lines.push(``);
2488
- }
2489
- }
2490
- if (modification.indexesToRemove.length > 0) {
2491
- lines.push(` // Restore indexes to ${collectionName}`);
2492
- for (let i = 0; i < modification.indexesToRemove.length; i++) {
2493
- const index = modification.indexesToRemove[i];
2494
- const varName = `collection_${collectionName}_restore_idx_${i}`;
2495
- lines.push(generateIndexAddition(collectionName, index, varName));
2496
- lines.push(``);
2497
- }
2498
- }
2499
- if (modification.indexesToAdd.length > 0) {
2500
- lines.push(` // Remove indexes from ${collectionName}`);
2501
- for (let i = 0; i < modification.indexesToAdd.length; i++) {
2502
- const index = modification.indexesToAdd[i];
2503
- const varName = `collection_${collectionName}_revert_idx_${i}`;
2504
- lines.push(generateIndexRemoval(collectionName, index, varName));
2505
- lines.push(``);
2506
- }
2507
- }
2508
- if (modification.fieldsToRemove.length > 0) {
2509
- lines.push(` // Restore fields to ${collectionName}`);
2510
- for (const field of modification.fieldsToRemove) {
2511
- const varName = `collection_${collectionName}_restore_${field.name}`;
2512
- lines.push(generateFieldAddition(collectionName, field, varName));
2513
- lines.push(``);
2514
- }
2612
+ if (operation.type === "create") {
2613
+ const collection = operation.collection;
2614
+ const varName = `collection_${collection.name}`;
2615
+ lines.push(generateCollectionDeletion(collection.name, varName, true));
2616
+ } else if (operation.type === "modify") {
2617
+ const modification = operation.modifications;
2618
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
2619
+ let operationCount = 0;
2620
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
2621
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
2622
+ for (const permission of modification.permissionsToUpdate) {
2623
+ operationCount++;
2624
+ const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
2625
+ const isLast = operationCount === totalOperations;
2626
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast));
2627
+ if (!isLast) lines.push("");
2515
2628
  }
2516
- if (modification.fieldsToModify.length > 0) {
2517
- lines.push(` // Revert field modifications in ${collectionName}`);
2518
- for (const fieldMod of modification.fieldsToModify) {
2519
- const reverseChanges = fieldMod.changes.map((change) => ({
2520
- property: change.property,
2521
- oldValue: change.newValue,
2522
- newValue: change.oldValue
2523
- }));
2524
- const reverseMod = {
2525
- fieldName: fieldMod.fieldName,
2526
- currentDefinition: fieldMod.newDefinition,
2527
- newDefinition: fieldMod.currentDefinition,
2528
- changes: reverseChanges
2529
- };
2530
- const varName = `collection_${collectionName}_revert_${fieldMod.fieldName}`;
2531
- lines.push(generateFieldModification(collectionName, reverseMod, varName));
2532
- lines.push(``);
2533
- }
2534
- }
2535
- if (modification.fieldsToAdd.length > 0) {
2536
- lines.push(` // Remove added fields from ${collectionName}`);
2537
- for (const field of modification.fieldsToAdd) {
2538
- const varName = `collection_${collectionName}_revert_add_${field.name}`;
2539
- lines.push(generateFieldDeletion(collectionName, field.name, varName));
2540
- lines.push(``);
2541
- }
2629
+ } else if (modification.rulesToUpdate.length > 0) {
2630
+ for (const rule of modification.rulesToUpdate) {
2631
+ operationCount++;
2632
+ const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
2633
+ const isLast = operationCount === totalOperations;
2634
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, isLast));
2635
+ if (!isLast) lines.push("");
2542
2636
  }
2543
2637
  }
2544
- }
2545
- if (diff.collectionsToCreate.length > 0) {
2546
- lines.push(` // Delete created collections`);
2547
- for (let i = 0; i < diff.collectionsToCreate.length; i++) {
2548
- const collection = diff.collectionsToCreate[i];
2549
- const varName = `collection_${collection.name}_rollback`;
2550
- lines.push(generateCollectionDeletion(collection.name, varName));
2551
- lines.push(``);
2638
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
2639
+ operationCount++;
2640
+ const index = modification.indexesToRemove[i];
2641
+ const varName = `collection_${collectionName}_restore_idx_${i}`;
2642
+ const isLast = operationCount === totalOperations;
2643
+ lines.push(generateIndexAddition(collectionName, index, varName, isLast));
2644
+ if (!isLast) lines.push("");
2645
+ }
2646
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
2647
+ operationCount++;
2648
+ const index = modification.indexesToAdd[i];
2649
+ const varName = `collection_${collectionName}_revert_idx_${i}`;
2650
+ const isLast = operationCount === totalOperations;
2651
+ lines.push(generateIndexRemoval(collectionName, index, varName, isLast));
2652
+ if (!isLast) lines.push("");
2653
+ }
2654
+ for (const field of modification.fieldsToRemove) {
2655
+ operationCount++;
2656
+ const varName = `collection_${collectionName}_restore_${field.name}`;
2657
+ const isLast = operationCount === totalOperations;
2658
+ lines.push(generateFieldAddition(collectionName, field, varName, isLast, collectionIdMap));
2659
+ if (!isLast) lines.push("");
2660
+ }
2661
+ for (const fieldMod of modification.fieldsToModify) {
2662
+ operationCount++;
2663
+ const reverseChanges = fieldMod.changes.map((change) => ({
2664
+ property: change.property,
2665
+ oldValue: change.newValue,
2666
+ newValue: change.oldValue
2667
+ }));
2668
+ const reverseMod = {
2669
+ fieldName: fieldMod.fieldName,
2670
+ currentDefinition: fieldMod.newDefinition,
2671
+ newDefinition: fieldMod.currentDefinition,
2672
+ changes: reverseChanges
2673
+ };
2674
+ const varName = `collection_${collectionName}_revert_${fieldMod.fieldName}`;
2675
+ const isLast = operationCount === totalOperations;
2676
+ lines.push(generateFieldModification(collectionName, reverseMod, varName, isLast));
2677
+ if (!isLast) lines.push("");
2678
+ }
2679
+ for (const field of modification.fieldsToAdd) {
2680
+ operationCount++;
2681
+ const varName = `collection_${collectionName}_revert_add_${field.name}`;
2682
+ const isLast = operationCount === totalOperations;
2683
+ lines.push(generateFieldDeletion(collectionName, field.name, varName, isLast));
2684
+ if (!isLast) lines.push("");
2685
+ }
2686
+ } else if (operation.type === "delete") {
2687
+ const collection = operation.collection;
2688
+ if (typeof collection !== "string") {
2689
+ const varName = `collection_${collection.name}`;
2690
+ lines.push(generateCollectionCreation(collection, varName, true, collectionIdMap));
2552
2691
  }
2553
- }
2554
- if (lines.length === 2) {
2555
- lines.push(` // No changes to revert`);
2556
- lines.push(``);
2557
2692
  }
2558
2693
  let code = lines.join("\n");
2559
2694
  const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
@@ -2578,12 +2713,33 @@ function generate(diff, config) {
2578
2713
  const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
2579
2714
  try {
2580
2715
  const migrationDir = resolveMigrationDir(normalizedConfig);
2581
- const upCode = generateUpMigration(diff);
2582
- const downCode = generateDownMigration(diff);
2583
- const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
2584
- const filename = generateMigrationFilename(diff, normalizedConfig);
2585
- const filePath = writeMigrationFile(migrationDir, filename, content);
2586
- return filePath;
2716
+ const hasChanges4 = diff.collectionsToCreate.length > 0 || diff.collectionsToModify.length > 0 || diff.collectionsToDelete.length > 0;
2717
+ if (!hasChanges4) {
2718
+ return [];
2719
+ }
2720
+ const collectionIdMap = /* @__PURE__ */ new Map();
2721
+ for (const collection of diff.collectionsToCreate) {
2722
+ if (collection.id) {
2723
+ collectionIdMap.set(collection.name, collection.id);
2724
+ }
2725
+ }
2726
+ for (const collection of diff.collectionsToDelete) {
2727
+ if (collection.id) {
2728
+ collectionIdMap.set(collection.name, collection.id);
2729
+ }
2730
+ }
2731
+ const baseTimestamp = generateTimestamp(normalizedConfig);
2732
+ const operations = splitDiffByCollection(diff, baseTimestamp);
2733
+ const filePaths = [];
2734
+ for (const operation of operations) {
2735
+ const upCode = generateOperationUpMigration(operation, collectionIdMap);
2736
+ const downCode = generateOperationDownMigration(operation, collectionIdMap);
2737
+ const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
2738
+ const filename = generateCollectionMigrationFilename(operation);
2739
+ const filePath = writeMigrationFile(migrationDir, filename, content);
2740
+ filePaths.push(filePath);
2741
+ }
2742
+ return filePaths;
2587
2743
  } catch (error) {
2588
2744
  if (error instanceof MigrationGenerationError || error instanceof FileSystemError) {
2589
2745
  throw error;
@@ -2600,7 +2756,7 @@ function generate(diff, config) {
2600
2756
  var SNAPSHOT_VERSION = "1.0.0";
2601
2757
  function resolveCollectionIdToName(collectionId) {
2602
2758
  if (collectionId === "_pb_users_auth_") {
2603
- return "Users";
2759
+ return "users";
2604
2760
  }
2605
2761
  const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2606
2762
  if (nameMatch) {
@@ -2608,6 +2764,39 @@ function resolveCollectionIdToName(collectionId) {
2608
2764
  }
2609
2765
  return collectionId;
2610
2766
  }
2767
+ function extractFieldOptions2(pbField) {
2768
+ const options = {};
2769
+ if (pbField.options && typeof pbField.options === "object") {
2770
+ Object.assign(options, pbField.options);
2771
+ }
2772
+ const directOptionKeys = [
2773
+ "min",
2774
+ "max",
2775
+ "pattern",
2776
+ "noDecimal",
2777
+ // text/number fields
2778
+ "values",
2779
+ "maxSelect",
2780
+ // select fields
2781
+ "mimeTypes",
2782
+ "maxSize",
2783
+ "thumbs",
2784
+ "protected",
2785
+ // file fields
2786
+ "onCreate",
2787
+ "onUpdate",
2788
+ // autodate fields
2789
+ "exceptDomains",
2790
+ "onlyDomains"
2791
+ // email/url fields
2792
+ ];
2793
+ for (const key of directOptionKeys) {
2794
+ if (pbField[key] !== void 0) {
2795
+ options[key] = pbField[key];
2796
+ }
2797
+ }
2798
+ return options;
2799
+ }
2611
2800
  function convertPocketBaseCollection(pbCollection) {
2612
2801
  const fields = [];
2613
2802
  const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
@@ -2625,23 +2814,19 @@ function convertPocketBaseCollection(pbCollection) {
2625
2814
  type: pbField.type,
2626
2815
  required: pbField.required || false
2627
2816
  };
2628
- field.options = pbField.options ? { ...pbField.options } : {};
2629
- if (pbField.type === "select") {
2630
- if (pbField.values && Array.isArray(pbField.values)) {
2631
- field.options.values = pbField.values;
2632
- } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
2633
- field.options.values = pbField.options.values;
2634
- }
2635
- }
2817
+ field.options = extractFieldOptions2(pbField);
2636
2818
  if (pbField.type === "relation") {
2637
2819
  const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
2638
- const collectionName = resolveCollectionIdToName(collectionId);
2820
+ const collectionName = resolveCollectionIdToName(collectionId || "");
2639
2821
  field.relation = {
2640
2822
  collection: collectionName,
2641
2823
  cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
2642
2824
  maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
2643
2825
  minSelect: pbField.minSelect ?? pbField.options?.minSelect
2644
2826
  };
2827
+ delete field.options.maxSelect;
2828
+ delete field.options.minSelect;
2829
+ delete field.options.cascadeDelete;
2645
2830
  }
2646
2831
  const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
2647
2832
  if (Object.keys(field.options).length === 0) {
@@ -2655,17 +2840,21 @@ function convertPocketBaseCollection(pbCollection) {
2655
2840
  type: pbCollection.type || "base",
2656
2841
  fields
2657
2842
  };
2843
+ if (pbCollection.id) {
2844
+ schema.id = pbCollection.id;
2845
+ }
2658
2846
  if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
2659
2847
  schema.indexes = pbCollection.indexes;
2660
2848
  }
2661
- const rules = {};
2662
- if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
2663
- if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
2664
- if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
2665
- if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
2666
- if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
2667
- if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
2668
- if (Object.keys(rules).length > 0) {
2849
+ const hasAnyRule = pbCollection.listRule !== void 0 || pbCollection.viewRule !== void 0 || pbCollection.createRule !== void 0 || pbCollection.updateRule !== void 0 || pbCollection.deleteRule !== void 0 || pbCollection.manageRule !== void 0;
2850
+ if (hasAnyRule) {
2851
+ const rules = {};
2852
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
2853
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
2854
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
2855
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
2856
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
2857
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
2669
2858
  schema.rules = rules;
2670
2859
  schema.permissions = { ...rules };
2671
2860
  }
@@ -3604,15 +3793,25 @@ async function executeGenerate(options) {
3604
3793
  process.exit(1);
3605
3794
  }
3606
3795
  logSection("\u{1F4DD} Generating Migration");
3607
- const migrationPath = await withProgress(
3796
+ const migrationPaths = await withProgress(
3608
3797
  "Creating migration file...",
3609
3798
  () => Promise.resolve(generate(diff, migrationsDir))
3610
3799
  );
3611
- logSuccess(`Migration file created: ${path5__namespace.basename(migrationPath)}`);
3800
+ if (migrationPaths.length === 0) {
3801
+ logWarning("No migration files were generated (no changes detected).");
3802
+ return;
3803
+ }
3804
+ if (migrationPaths.length === 1) {
3805
+ logSuccess(`Migration file created: ${path5__namespace.basename(migrationPaths[0])}`);
3806
+ } else {
3807
+ logSuccess(`Created ${migrationPaths.length} migration files`);
3808
+ }
3612
3809
  logSection("\u2705 Next Steps");
3613
3810
  console.log();
3614
- console.log(" 1. Review the generated migration file:");
3615
- console.log(` ${migrationPath}`);
3811
+ console.log(" 1. Review the generated migration file(s):");
3812
+ migrationPaths.forEach((migrationPath) => {
3813
+ console.log(` ${migrationPath}`);
3814
+ });
3616
3815
  console.log();
3617
3816
  console.log(" 2. Apply the migration by running PocketBase:");
3618
3817
  console.log(" yarn pb");