pocketbase-zod-schema 0.1.3 → 0.2.0

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 (57) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +233 -98
  3. package/dist/cli/index.cjs +449 -108
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +447 -106
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +452 -111
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +447 -106
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/index.cjs +593 -175
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +4 -4
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +583 -172
  16. package/dist/index.js.map +1 -1
  17. package/dist/migration/analyzer.cjs +44 -6
  18. package/dist/migration/analyzer.cjs.map +1 -1
  19. package/dist/migration/analyzer.d.cts +11 -1
  20. package/dist/migration/analyzer.d.ts +11 -1
  21. package/dist/migration/analyzer.js +44 -7
  22. package/dist/migration/analyzer.js.map +1 -1
  23. package/dist/migration/diff.cjs +21 -3
  24. package/dist/migration/diff.cjs.map +1 -1
  25. package/dist/migration/diff.js +21 -3
  26. package/dist/migration/diff.js.map +1 -1
  27. package/dist/migration/index.cjs +500 -129
  28. package/dist/migration/index.cjs.map +1 -1
  29. package/dist/migration/index.d.cts +1 -1
  30. package/dist/migration/index.d.ts +1 -1
  31. package/dist/migration/index.js +499 -129
  32. package/dist/migration/index.js.map +1 -1
  33. package/dist/migration/snapshot.cjs +432 -118
  34. package/dist/migration/snapshot.cjs.map +1 -1
  35. package/dist/migration/snapshot.d.cts +34 -12
  36. package/dist/migration/snapshot.d.ts +34 -12
  37. package/dist/migration/snapshot.js +430 -117
  38. package/dist/migration/snapshot.js.map +1 -1
  39. package/dist/mutator.cjs +20 -21
  40. package/dist/mutator.cjs.map +1 -1
  41. package/dist/mutator.d.cts +4 -4
  42. package/dist/mutator.d.ts +4 -4
  43. package/dist/mutator.js +20 -21
  44. package/dist/mutator.js.map +1 -1
  45. package/dist/schema.cjs +69 -10
  46. package/dist/schema.cjs.map +1 -1
  47. package/dist/schema.d.cts +98 -8
  48. package/dist/schema.d.ts +98 -8
  49. package/dist/schema.js +62 -9
  50. package/dist/schema.js.map +1 -1
  51. package/dist/types.d.cts +5 -2
  52. package/dist/types.d.ts +5 -2
  53. package/dist/user-DTJQIj4K.d.cts +149 -0
  54. package/dist/user-DTJQIj4K.d.ts +149 -0
  55. package/package.json +3 -3
  56. package/dist/user-C39DQ40N.d.cts +0 -53
  57. package/dist/user-C39DQ40N.d.ts +0 -53
@@ -1,4 +1,4 @@
1
- import * as fs2 from 'fs';
1
+ import * as fs3 from 'fs';
2
2
  import * as path from 'path';
3
3
  import { z } from 'zod';
4
4
 
@@ -130,10 +130,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
130
130
  operation;
131
131
  code;
132
132
  originalError;
133
- constructor(message, path4, operation, code, originalError) {
133
+ constructor(message, path5, operation, code, originalError) {
134
134
  super(message);
135
135
  this.name = "FileSystemError";
136
- this.path = path4;
136
+ this.path = path5;
137
137
  this.operation = operation;
138
138
  this.code = code;
139
139
  this.originalError = originalError;
@@ -1319,6 +1319,16 @@ function getFieldTypeInfo(zodType, fieldName) {
1319
1319
  }
1320
1320
 
1321
1321
  // src/migration/analyzer.ts
1322
+ var tsxLoaderRegistered = false;
1323
+ async function ensureTsxLoader() {
1324
+ if (tsxLoaderRegistered) return;
1325
+ try {
1326
+ await import('tsx/esm');
1327
+ tsxLoaderRegistered = true;
1328
+ } catch {
1329
+ tsxLoaderRegistered = false;
1330
+ }
1331
+ }
1322
1332
  var DEFAULT_CONFIG = {
1323
1333
  workspaceRoot: process.cwd(),
1324
1334
  excludePatterns: [
@@ -1356,10 +1366,10 @@ function discoverSchemaFiles(config) {
1356
1366
  const mergedConfig = mergeConfig(normalizedConfig);
1357
1367
  const schemaDir = resolveSchemaDir(normalizedConfig);
1358
1368
  try {
1359
- if (!fs2.existsSync(schemaDir)) {
1369
+ if (!fs3.existsSync(schemaDir)) {
1360
1370
  throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1361
1371
  }
1362
- const files = fs2.readdirSync(schemaDir);
1372
+ const files = fs3.readdirSync(schemaDir);
1363
1373
  const schemaFiles = files.filter((file) => {
1364
1374
  const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
1365
1375
  if (!hasValidExtension) return false;
@@ -1409,31 +1419,44 @@ async function importSchemaModule(filePath, config) {
1409
1419
  let resolvedPath = null;
1410
1420
  const jsPath = `${importPath}.js`;
1411
1421
  const tsPath = `${importPath}.ts`;
1412
- if (fs2.existsSync(jsPath)) {
1422
+ if (fs3.existsSync(jsPath)) {
1413
1423
  resolvedPath = jsPath;
1414
- } else if (fs2.existsSync(tsPath)) {
1424
+ } else if (fs3.existsSync(tsPath)) {
1415
1425
  resolvedPath = tsPath;
1416
1426
  } else {
1417
1427
  resolvedPath = jsPath;
1418
1428
  }
1429
+ if (resolvedPath.endsWith(".ts")) {
1430
+ await ensureTsxLoader();
1431
+ if (!tsxLoaderRegistered) {
1432
+ throw new SchemaParsingError(
1433
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1434
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1435
+ Alternatively, compile your schema files to JavaScript first.`,
1436
+ filePath
1437
+ );
1438
+ }
1439
+ }
1419
1440
  const fileUrl = new URL(`file://${path.resolve(resolvedPath)}`);
1420
1441
  const module = await import(fileUrl.href);
1421
1442
  return module;
1422
1443
  } catch (error) {
1423
1444
  const tsPath = `${filePath}.ts`;
1424
- const isTypeScriptFile = fs2.existsSync(tsPath);
1445
+ const isTypeScriptFile = fs3.existsSync(tsPath);
1446
+ if (isTypeScriptFile && error instanceof SchemaParsingError) {
1447
+ throw error;
1448
+ }
1425
1449
  if (isTypeScriptFile) {
1426
1450
  throw new SchemaParsingError(
1427
- `Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
1428
- Please either:
1429
- 1. Compile your schema files to JavaScript first, or
1430
- 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")`,
1451
+ `Failed to import TypeScript schema file. The 'tsx' package is required to load TypeScript files.
1452
+ Please install tsx: npm install tsx (or yarn add tsx, or pnpm add tsx)
1453
+ Alternatively, compile your schema files to JavaScript first.`,
1431
1454
  filePath,
1432
1455
  error
1433
1456
  );
1434
1457
  }
1435
1458
  throw new SchemaParsingError(
1436
- `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1459
+ `Failed to import schema module. Make sure the schema files exist and are valid.`,
1437
1460
  filePath,
1438
1461
  error
1439
1462
  );
@@ -1443,6 +1466,19 @@ function getCollectionNameFromFile(filePath) {
1443
1466
  const filename = path.basename(filePath).replace(/\.(ts|js)$/, "");
1444
1467
  return toCollectionName(filename);
1445
1468
  }
1469
+ function extractCollectionNameFromSchema(zodSchema) {
1470
+ if (!zodSchema.description) {
1471
+ return null;
1472
+ }
1473
+ try {
1474
+ const metadata = JSON.parse(zodSchema.description);
1475
+ if (metadata.collectionName && typeof metadata.collectionName === "string") {
1476
+ return metadata.collectionName;
1477
+ }
1478
+ } catch {
1479
+ }
1480
+ return null;
1481
+ }
1446
1482
  function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
1447
1483
  const result = {};
1448
1484
  for (const [key, value] of Object.entries(module)) {
@@ -1605,7 +1641,7 @@ async function buildSchemaDefinition(config) {
1605
1641
  importPath = normalizedConfig.pathTransformer(filePath);
1606
1642
  } else if (mergedConfig.useCompiledFiles) {
1607
1643
  const distPath = filePath.replace(/\/src\//, "/dist/");
1608
- if (fs2.existsSync(`${distPath}.js`) || fs2.existsSync(`${distPath}.mjs`)) {
1644
+ if (fs3.existsSync(`${distPath}.js`) || fs3.existsSync(`${distPath}.mjs`)) {
1609
1645
  importPath = distPath;
1610
1646
  } else {
1611
1647
  importPath = filePath;
@@ -1618,7 +1654,8 @@ async function buildSchemaDefinition(config) {
1618
1654
  console.warn(`No valid schema found in ${filePath}, skipping...`);
1619
1655
  continue;
1620
1656
  }
1621
- const collectionName = getCollectionNameFromFile(filePath);
1657
+ const collectionNameFromSchema = extractCollectionNameFromSchema(zodSchema);
1658
+ const collectionName = collectionNameFromSchema ?? getCollectionNameFromFile(filePath);
1622
1659
  const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
1623
1660
  collections.set(collectionName, collectionSchema);
1624
1661
  } catch (error) {
@@ -1661,7 +1698,359 @@ var SchemaAnalyzer = class {
1661
1698
  return convertZodSchemaToCollectionSchema(name, schema);
1662
1699
  }
1663
1700
  };
1701
+
1702
+ // src/migration/pocketbase-converter.ts
1664
1703
  var SNAPSHOT_VERSION = "1.0.0";
1704
+ function resolveCollectionIdToName(collectionId) {
1705
+ if (collectionId === "_pb_users_auth_") {
1706
+ return "Users";
1707
+ }
1708
+ const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1709
+ if (nameMatch) {
1710
+ return nameMatch[1];
1711
+ }
1712
+ return collectionId;
1713
+ }
1714
+ function convertPocketBaseCollection(pbCollection) {
1715
+ const fields = [];
1716
+ const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
1717
+ const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
1718
+ if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
1719
+ for (const pbField of pbCollection.fields) {
1720
+ if (pbField.system || systemFieldNames.includes(pbField.name)) {
1721
+ continue;
1722
+ }
1723
+ if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
1724
+ continue;
1725
+ }
1726
+ const field = {
1727
+ name: pbField.name,
1728
+ type: pbField.type,
1729
+ required: pbField.required || false
1730
+ };
1731
+ field.options = pbField.options ? { ...pbField.options } : {};
1732
+ if (pbField.type === "select") {
1733
+ if (pbField.values && Array.isArray(pbField.values)) {
1734
+ field.options.values = pbField.values;
1735
+ } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
1736
+ field.options.values = pbField.options.values;
1737
+ }
1738
+ }
1739
+ if (pbField.type === "relation") {
1740
+ const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
1741
+ const collectionName = resolveCollectionIdToName(collectionId);
1742
+ field.relation = {
1743
+ collection: collectionName,
1744
+ cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
1745
+ maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
1746
+ minSelect: pbField.minSelect ?? pbField.options?.minSelect
1747
+ };
1748
+ }
1749
+ const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
1750
+ if (Object.keys(field.options).length === 0) {
1751
+ delete field.options;
1752
+ } else if (pbField.type === "select" && hasOnlyValues) ;
1753
+ fields.push(field);
1754
+ }
1755
+ }
1756
+ const schema = {
1757
+ name: pbCollection.name,
1758
+ type: pbCollection.type || "base",
1759
+ fields
1760
+ };
1761
+ if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
1762
+ schema.indexes = pbCollection.indexes;
1763
+ }
1764
+ const rules = {};
1765
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1766
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1767
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1768
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1769
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1770
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1771
+ if (Object.keys(rules).length > 0) {
1772
+ schema.rules = rules;
1773
+ schema.permissions = { ...rules };
1774
+ }
1775
+ return schema;
1776
+ }
1777
+ function convertPocketBaseMigration(migrationContent) {
1778
+ try {
1779
+ const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
1780
+ if (!snapshotMatch) {
1781
+ throw new Error("Could not find snapshot array in migration file");
1782
+ }
1783
+ const snapshotArrayStr = snapshotMatch[1];
1784
+ let snapshotArray;
1785
+ try {
1786
+ snapshotArray = new Function(`return ${snapshotArrayStr}`)();
1787
+ } catch (parseError) {
1788
+ throw new Error(`Failed to parse snapshot array: ${parseError}`);
1789
+ }
1790
+ if (!Array.isArray(snapshotArray)) {
1791
+ throw new Error("Snapshot is not an array");
1792
+ }
1793
+ const collections = /* @__PURE__ */ new Map();
1794
+ for (const pbCollection of snapshotArray) {
1795
+ if (!pbCollection.name) {
1796
+ console.warn("Skipping collection without name");
1797
+ continue;
1798
+ }
1799
+ const schema = convertPocketBaseCollection(pbCollection);
1800
+ collections.set(pbCollection.name, schema);
1801
+ }
1802
+ return {
1803
+ version: SNAPSHOT_VERSION,
1804
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1805
+ collections
1806
+ };
1807
+ } catch (error) {
1808
+ throw new SnapshotError(
1809
+ `Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
1810
+ void 0,
1811
+ "parse",
1812
+ error instanceof Error ? error : void 0
1813
+ );
1814
+ }
1815
+ }
1816
+
1817
+ // src/migration/migration-parser.ts
1818
+ function extractTimestampFromFilename(filename) {
1819
+ const match = filename.match(/^(\d+)_/);
1820
+ if (match) {
1821
+ return parseInt(match[1], 10);
1822
+ }
1823
+ return null;
1824
+ }
1825
+ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
1826
+ try {
1827
+ if (!fs3.existsSync(migrationsPath)) {
1828
+ return [];
1829
+ }
1830
+ const files = fs3.readdirSync(migrationsPath);
1831
+ const migrationFiles = [];
1832
+ for (const file of files) {
1833
+ if (file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")) {
1834
+ continue;
1835
+ }
1836
+ if (!file.endsWith(".js")) {
1837
+ continue;
1838
+ }
1839
+ const timestamp = extractTimestampFromFilename(file);
1840
+ if (timestamp && timestamp > snapshotTimestamp) {
1841
+ migrationFiles.push({
1842
+ path: path.join(migrationsPath, file),
1843
+ timestamp
1844
+ });
1845
+ }
1846
+ }
1847
+ migrationFiles.sort((a, b) => a.timestamp - b.timestamp);
1848
+ return migrationFiles.map((f) => f.path);
1849
+ } catch (error) {
1850
+ console.warn(`Error finding migrations after snapshot: ${error}`);
1851
+ return [];
1852
+ }
1853
+ }
1854
+ function parseMigrationOperationsFromContent(content) {
1855
+ const collectionsToCreate = [];
1856
+ const collectionsToDelete = [];
1857
+ try {
1858
+ let searchIndex = 0;
1859
+ while (true) {
1860
+ const collectionStart = content.indexOf("new Collection(", searchIndex);
1861
+ if (collectionStart === -1) {
1862
+ break;
1863
+ }
1864
+ const openParen = collectionStart + "new Collection(".length;
1865
+ let braceCount = 0;
1866
+ let parenCount = 1;
1867
+ let inString = false;
1868
+ let stringChar = null;
1869
+ let i = openParen;
1870
+ while (i < content.length && /\s/.test(content[i])) {
1871
+ i++;
1872
+ }
1873
+ if (content[i] !== "{") {
1874
+ searchIndex = i + 1;
1875
+ continue;
1876
+ }
1877
+ const objectStart = i;
1878
+ braceCount = 1;
1879
+ i++;
1880
+ while (i < content.length && (braceCount > 0 || parenCount > 0)) {
1881
+ const char = content[i];
1882
+ const prevChar = i > 0 ? content[i - 1] : "";
1883
+ if (!inString && (char === '"' || char === "'")) {
1884
+ inString = true;
1885
+ stringChar = char;
1886
+ } else if (inString && char === stringChar && prevChar !== "\\") {
1887
+ inString = false;
1888
+ stringChar = null;
1889
+ }
1890
+ if (!inString) {
1891
+ if (char === "{") braceCount++;
1892
+ if (char === "}") braceCount--;
1893
+ if (char === "(") parenCount++;
1894
+ if (char === ")") parenCount--;
1895
+ }
1896
+ i++;
1897
+ }
1898
+ if (braceCount === 0 && parenCount === 0) {
1899
+ const objectContent = content.substring(objectStart, i - 1);
1900
+ try {
1901
+ const collectionObj = new Function(`return ${objectContent}`)();
1902
+ if (collectionObj && collectionObj.name) {
1903
+ const schema = convertPocketBaseCollection(collectionObj);
1904
+ collectionsToCreate.push(schema);
1905
+ }
1906
+ } catch (error) {
1907
+ console.warn(`Failed to parse collection definition: ${error}`);
1908
+ }
1909
+ }
1910
+ searchIndex = i;
1911
+ }
1912
+ const deleteMatches = content.matchAll(
1913
+ /app\.delete\s*\(\s*(?:collection_\w+|app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\))\s*\)/g
1914
+ );
1915
+ for (const match of deleteMatches) {
1916
+ if (match[1]) {
1917
+ collectionsToDelete.push(match[1]);
1918
+ } else {
1919
+ const varNameMatch = match[0].match(/collection_(\w+)/);
1920
+ if (varNameMatch) {
1921
+ const varName = `collection_${varNameMatch[1]}`;
1922
+ const deleteIndex = content.indexOf(match[0]);
1923
+ const beforeDelete = content.substring(0, deleteIndex);
1924
+ const varDefMatch = beforeDelete.match(
1925
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`, "g")
1926
+ );
1927
+ if (varDefMatch && varDefMatch.length > 0) {
1928
+ const collectionDefMatch = beforeDelete.match(
1929
+ new RegExp(`const\\s+${varName}\\s*=\\s*new\\s+Collection\\(\\s*(\\{[\\s\\S]*?\\})\\s*\\)`)
1930
+ );
1931
+ if (collectionDefMatch) {
1932
+ try {
1933
+ const collectionDefStr = collectionDefMatch[1];
1934
+ const collectionObj = new Function(`return ${collectionDefStr}`)();
1935
+ if (collectionObj && collectionObj.name) {
1936
+ collectionsToDelete.push(collectionObj.name);
1937
+ }
1938
+ } catch {
1939
+ }
1940
+ }
1941
+ }
1942
+ }
1943
+ }
1944
+ }
1945
+ const findAndDeleteMatches = content.matchAll(
1946
+ /app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)[\s\S]*?app\.delete/g
1947
+ );
1948
+ for (const match of findAndDeleteMatches) {
1949
+ collectionsToDelete.push(match[1]);
1950
+ }
1951
+ } catch (error) {
1952
+ console.warn(`Failed to parse migration operations from content: ${error}`);
1953
+ }
1954
+ return { collectionsToCreate, collectionsToDelete };
1955
+ }
1956
+ function parseMigrationOperations(migrationContent) {
1957
+ try {
1958
+ const migrateMatch = migrationContent.match(/migrate\s*\(\s*/);
1959
+ if (!migrateMatch) {
1960
+ return parseMigrationOperationsFromContent(migrationContent);
1961
+ }
1962
+ const startIndex = migrateMatch.index + migrateMatch[0].length;
1963
+ let i = startIndex;
1964
+ let parenCount = 0;
1965
+ let foundFirstParen = false;
1966
+ while (i < migrationContent.length) {
1967
+ const char = migrationContent[i];
1968
+ if (char === "(") {
1969
+ parenCount++;
1970
+ foundFirstParen = true;
1971
+ i++;
1972
+ break;
1973
+ }
1974
+ i++;
1975
+ }
1976
+ if (!foundFirstParen) {
1977
+ return parseMigrationOperationsFromContent(migrationContent);
1978
+ }
1979
+ let inString = false;
1980
+ let stringChar = null;
1981
+ let foundBrace = false;
1982
+ let braceStart = -1;
1983
+ while (i < migrationContent.length && !foundBrace) {
1984
+ const char = migrationContent[i];
1985
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
1986
+ if (!inString && (char === '"' || char === "'")) {
1987
+ inString = true;
1988
+ stringChar = char;
1989
+ } else if (inString && char === stringChar && prevChar !== "\\") {
1990
+ inString = false;
1991
+ stringChar = null;
1992
+ }
1993
+ if (!inString) {
1994
+ if (char === "(") parenCount++;
1995
+ if (char === ")") {
1996
+ parenCount--;
1997
+ if (parenCount === 0) {
1998
+ i++;
1999
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2000
+ i++;
2001
+ }
2002
+ if (i < migrationContent.length - 1 && migrationContent[i] === "=" && migrationContent[i + 1] === ">") {
2003
+ i += 2;
2004
+ while (i < migrationContent.length && /\s/.test(migrationContent[i])) {
2005
+ i++;
2006
+ }
2007
+ if (i < migrationContent.length && migrationContent[i] === "{") {
2008
+ foundBrace = true;
2009
+ braceStart = i + 1;
2010
+ break;
2011
+ }
2012
+ }
2013
+ }
2014
+ }
2015
+ }
2016
+ i++;
2017
+ }
2018
+ if (!foundBrace || braceStart === -1) {
2019
+ return parseMigrationOperationsFromContent(migrationContent);
2020
+ }
2021
+ let braceCount = 1;
2022
+ i = braceStart;
2023
+ inString = false;
2024
+ stringChar = null;
2025
+ while (i < migrationContent.length && braceCount > 0) {
2026
+ const char = migrationContent[i];
2027
+ const prevChar = i > 0 ? migrationContent[i - 1] : "";
2028
+ if (!inString && (char === '"' || char === "'")) {
2029
+ inString = true;
2030
+ stringChar = char;
2031
+ } else if (inString && char === stringChar && prevChar !== "\\") {
2032
+ inString = false;
2033
+ stringChar = null;
2034
+ }
2035
+ if (!inString) {
2036
+ if (char === "{") braceCount++;
2037
+ if (char === "}") braceCount--;
2038
+ }
2039
+ i++;
2040
+ }
2041
+ if (braceCount === 0) {
2042
+ const upMigrationContent = migrationContent.substring(braceStart, i - 1);
2043
+ return parseMigrationOperationsFromContent(upMigrationContent);
2044
+ }
2045
+ return parseMigrationOperationsFromContent(migrationContent);
2046
+ } catch (error) {
2047
+ console.warn(`Failed to parse migration operations: ${error}`);
2048
+ return { collectionsToCreate: [], collectionsToDelete: [] };
2049
+ }
2050
+ }
2051
+
2052
+ // src/migration/snapshot.ts
2053
+ var SNAPSHOT_VERSION2 = "1.0.0";
1665
2054
  var DEFAULT_SNAPSHOT_FILENAME = ".migration-snapshot.json";
1666
2055
  var SNAPSHOT_MIGRATIONS = [
1667
2056
  // Add migrations here as the format evolves
@@ -1676,7 +2065,7 @@ var DEFAULT_CONFIG2 = {
1676
2065
  snapshotPath: DEFAULT_SNAPSHOT_FILENAME,
1677
2066
  workspaceRoot: process.cwd(),
1678
2067
  autoMigrate: true,
1679
- version: SNAPSHOT_VERSION
2068
+ version: SNAPSHOT_VERSION2
1680
2069
  };
1681
2070
  function mergeConfig2(config = {}) {
1682
2071
  return {
@@ -1696,7 +2085,7 @@ function getSnapshotPath(config = {}) {
1696
2085
  function snapshotExists(config = {}) {
1697
2086
  try {
1698
2087
  const snapshotPath = getSnapshotPath(config);
1699
- return fs2.existsSync(snapshotPath);
2088
+ return fs3.existsSync(snapshotPath);
1700
2089
  } catch {
1701
2090
  return false;
1702
2091
  }
@@ -1756,12 +2145,12 @@ function saveSnapshot(schema, config = {}) {
1756
2145
  const snapshotPath = getSnapshotPath(config);
1757
2146
  try {
1758
2147
  const snapshotDir = path.dirname(snapshotPath);
1759
- if (!fs2.existsSync(snapshotDir)) {
1760
- fs2.mkdirSync(snapshotDir, { recursive: true });
2148
+ if (!fs3.existsSync(snapshotDir)) {
2149
+ fs3.mkdirSync(snapshotDir, { recursive: true });
1761
2150
  }
1762
2151
  const snapshotData = addSnapshotMetadata(schema, config);
1763
2152
  const jsonContent = JSON.stringify(snapshotData, null, 2);
1764
- fs2.writeFileSync(snapshotPath, jsonContent, "utf-8");
2153
+ fs3.writeFileSync(snapshotPath, jsonContent, "utf-8");
1765
2154
  } catch (error) {
1766
2155
  handleFileSystemError(error, "write", snapshotPath);
1767
2156
  }
@@ -1856,7 +2245,7 @@ function deserializeSnapshot(data) {
1856
2245
  function loadSnapshot(config = {}) {
1857
2246
  const snapshotPath = getSnapshotPath(config);
1858
2247
  try {
1859
- const jsonContent = fs2.readFileSync(snapshotPath, "utf-8");
2248
+ const jsonContent = fs3.readFileSync(snapshotPath, "utf-8");
1860
2249
  const data = parseAndValidateSnapshot(jsonContent, snapshotPath);
1861
2250
  const migratedData = migrateSnapshotFormat(data, config);
1862
2251
  return deserializeSnapshot(migratedData);
@@ -1891,10 +2280,10 @@ function mergeSnapshots(baseSnapshot, customSnapshot) {
1891
2280
  }
1892
2281
  function findLatestSnapshot(migrationsPath) {
1893
2282
  try {
1894
- if (!fs2.existsSync(migrationsPath)) {
2283
+ if (!fs3.existsSync(migrationsPath)) {
1895
2284
  return null;
1896
2285
  }
1897
- const files = fs2.readdirSync(migrationsPath);
2286
+ const files = fs3.readdirSync(migrationsPath);
1898
2287
  const snapshotFiles = files.filter(
1899
2288
  (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
1900
2289
  );
@@ -1912,14 +2301,68 @@ function findLatestSnapshot(migrationsPath) {
1912
2301
  return null;
1913
2302
  }
1914
2303
  }
2304
+ function applyMigrationOperations(snapshot, operations) {
2305
+ const updatedCollections = new Map(snapshot.collections);
2306
+ for (const collectionName of operations.collectionsToDelete) {
2307
+ updatedCollections.delete(collectionName);
2308
+ }
2309
+ for (const collection of operations.collectionsToCreate) {
2310
+ updatedCollections.set(collection.name, collection);
2311
+ }
2312
+ return {
2313
+ ...snapshot,
2314
+ collections: updatedCollections
2315
+ };
2316
+ }
2317
+ function loadSnapshotWithMigrations(config = {}) {
2318
+ const migrationsPath = config.migrationsPath;
2319
+ if (!migrationsPath) {
2320
+ return null;
2321
+ }
2322
+ if (fs3.existsSync(migrationsPath) && fs3.statSync(migrationsPath).isFile()) {
2323
+ try {
2324
+ const migrationContent = fs3.readFileSync(migrationsPath, "utf-8");
2325
+ return convertPocketBaseMigration(migrationContent);
2326
+ } catch (error) {
2327
+ console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2328
+ return null;
2329
+ }
2330
+ }
2331
+ const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2332
+ if (!latestSnapshotPath) {
2333
+ return null;
2334
+ }
2335
+ try {
2336
+ const migrationContent = fs3.readFileSync(latestSnapshotPath, "utf-8");
2337
+ let snapshot = convertPocketBaseMigration(migrationContent);
2338
+ const snapshotFilename = path.basename(latestSnapshotPath);
2339
+ const snapshotTimestamp = extractTimestampFromFilename(snapshotFilename);
2340
+ if (snapshotTimestamp) {
2341
+ const migrationFiles = findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp);
2342
+ for (const migrationFile of migrationFiles) {
2343
+ try {
2344
+ const migrationContent2 = fs3.readFileSync(migrationFile, "utf-8");
2345
+ const operations = parseMigrationOperations(migrationContent2);
2346
+ snapshot = applyMigrationOperations(snapshot, operations);
2347
+ } catch (error) {
2348
+ console.warn(`Failed to apply migration ${migrationFile}: ${error}`);
2349
+ }
2350
+ }
2351
+ }
2352
+ return snapshot;
2353
+ } catch (error) {
2354
+ console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2355
+ return null;
2356
+ }
2357
+ }
1915
2358
  function loadSnapshotIfExists(config = {}) {
1916
2359
  const migrationsPath = config.migrationsPath;
1917
2360
  if (!migrationsPath) {
1918
2361
  return null;
1919
2362
  }
1920
- if (fs2.existsSync(migrationsPath) && fs2.statSync(migrationsPath).isFile()) {
2363
+ if (fs3.existsSync(migrationsPath) && fs3.statSync(migrationsPath).isFile()) {
1921
2364
  try {
1922
- const migrationContent = fs2.readFileSync(migrationsPath, "utf-8");
2365
+ const migrationContent = fs3.readFileSync(migrationsPath, "utf-8");
1923
2366
  return convertPocketBaseMigration(migrationContent);
1924
2367
  } catch (error) {
1925
2368
  console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
@@ -1929,7 +2372,7 @@ function loadSnapshotIfExists(config = {}) {
1929
2372
  const latestSnapshotPath = findLatestSnapshot(migrationsPath);
1930
2373
  if (latestSnapshotPath) {
1931
2374
  try {
1932
- const migrationContent = fs2.readFileSync(latestSnapshotPath, "utf-8");
2375
+ const migrationContent = fs3.readFileSync(latestSnapshotPath, "utf-8");
1933
2376
  return convertPocketBaseMigration(migrationContent);
1934
2377
  } catch (error) {
1935
2378
  console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
@@ -1938,100 +2381,9 @@ function loadSnapshotIfExists(config = {}) {
1938
2381
  }
1939
2382
  return null;
1940
2383
  }
1941
- function convertPocketBaseCollection(pbCollection) {
1942
- const fields = [];
1943
- const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
1944
- const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
1945
- if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
1946
- for (const pbField of pbCollection.fields) {
1947
- if (pbField.system || systemFieldNames.includes(pbField.name)) {
1948
- continue;
1949
- }
1950
- if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
1951
- continue;
1952
- }
1953
- const field = {
1954
- name: pbField.name,
1955
- type: pbField.type,
1956
- required: pbField.required || false
1957
- };
1958
- if (pbField.options) {
1959
- field.options = pbField.options;
1960
- }
1961
- if (pbField.type === "relation") {
1962
- field.relation = {
1963
- collection: pbField.options?.collectionId || "",
1964
- cascadeDelete: pbField.options?.cascadeDelete || false,
1965
- maxSelect: pbField.options?.maxSelect,
1966
- minSelect: pbField.options?.minSelect
1967
- };
1968
- }
1969
- fields.push(field);
1970
- }
1971
- }
1972
- const schema = {
1973
- name: pbCollection.name,
1974
- type: pbCollection.type || "base",
1975
- fields
1976
- };
1977
- if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
1978
- schema.indexes = pbCollection.indexes;
1979
- }
1980
- const rules = {};
1981
- if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1982
- if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1983
- if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1984
- if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1985
- if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1986
- if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1987
- if (Object.keys(rules).length > 0) {
1988
- schema.rules = rules;
1989
- schema.permissions = { ...rules };
1990
- }
1991
- return schema;
1992
- }
1993
- function convertPocketBaseMigration(migrationContent) {
1994
- try {
1995
- const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
1996
- if (!snapshotMatch) {
1997
- throw new Error("Could not find snapshot array in migration file");
1998
- }
1999
- const snapshotArrayStr = snapshotMatch[1];
2000
- let snapshotArray;
2001
- try {
2002
- snapshotArray = new Function(`return ${snapshotArrayStr}`)();
2003
- } catch (parseError) {
2004
- throw new Error(`Failed to parse snapshot array: ${parseError}`);
2005
- }
2006
- if (!Array.isArray(snapshotArray)) {
2007
- throw new Error("Snapshot is not an array");
2008
- }
2009
- const collections = /* @__PURE__ */ new Map();
2010
- for (const pbCollection of snapshotArray) {
2011
- if (!pbCollection.name) {
2012
- console.warn("Skipping collection without name");
2013
- continue;
2014
- }
2015
- const schema = convertPocketBaseCollection(pbCollection);
2016
- collections.set(pbCollection.name, schema);
2017
- }
2018
- return {
2019
- version: SNAPSHOT_VERSION,
2020
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2021
- collections
2022
- };
2023
- } catch (error) {
2024
- throw new SnapshotError(
2025
- `Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
2026
- void 0,
2027
- "parse",
2028
- error instanceof Error ? error : void 0
2029
- );
2030
- }
2031
- }
2032
2384
  function loadBaseMigration(migrationPath) {
2033
2385
  try {
2034
- if (!fs2.existsSync(migrationPath)) {
2386
+ if (!fs3.existsSync(migrationPath)) {
2035
2387
  throw new SnapshotError(
2036
2388
  `Base migration file not found: ${migrationPath}
2037
2389
 
@@ -2042,7 +2394,7 @@ If the file exists in a different location, update the configuration.`,
2042
2394
  "read"
2043
2395
  );
2044
2396
  }
2045
- const migrationContent = fs2.readFileSync(migrationPath, "utf-8");
2397
+ const migrationContent = fs3.readFileSync(migrationPath, "utf-8");
2046
2398
  const snapshot = convertPocketBaseMigration(migrationContent);
2047
2399
  return snapshot;
2048
2400
  } catch (error) {
@@ -2078,14 +2430,14 @@ Please ensure PocketBase is properly set up by running 'yarn setup'.`,
2078
2430
  }
2079
2431
  }
2080
2432
  function getSnapshotVersion() {
2081
- return SNAPSHOT_VERSION;
2433
+ return SNAPSHOT_VERSION2;
2082
2434
  }
2083
2435
  function validateSnapshot(snapshot) {
2084
2436
  const issues = [];
2085
2437
  if (!snapshot.version) {
2086
2438
  issues.push("Missing version field");
2087
- } else if (compareVersions(snapshot.version, SNAPSHOT_VERSION) > 0) {
2088
- issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION}`);
2439
+ } else if (compareVersions(snapshot.version, SNAPSHOT_VERSION2) > 0) {
2440
+ issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION2}`);
2089
2441
  }
2090
2442
  if (!snapshot.timestamp) {
2091
2443
  issues.push("Missing timestamp field");
@@ -2304,6 +2656,9 @@ function compareFieldOptions(currentField, previousField) {
2304
2656
  for (const key of allKeys) {
2305
2657
  const currentValue = currentOptions[key];
2306
2658
  const previousValue = previousOptions[key];
2659
+ if (currentValue === void 0 && previousValue === void 0) {
2660
+ continue;
2661
+ }
2307
2662
  if (!areValuesEqual(currentValue, previousValue)) {
2308
2663
  changes.push({
2309
2664
  property: `options.${key}`,
@@ -2324,11 +2679,26 @@ function compareRelationConfigurations(currentField, previousField) {
2324
2679
  if (!currentRelation || !previousRelation) {
2325
2680
  return changes;
2326
2681
  }
2327
- if (currentRelation.collection !== previousRelation.collection) {
2682
+ const normalizeCollection = (collection) => {
2683
+ if (!collection) return collection;
2684
+ if (collection === "_pb_users_auth_") {
2685
+ return "Users";
2686
+ }
2687
+ const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2688
+ if (nameMatch) {
2689
+ return nameMatch[1];
2690
+ }
2691
+ return collection;
2692
+ };
2693
+ const normalizedCurrent = normalizeCollection(currentRelation.collection);
2694
+ const normalizedPrevious = normalizeCollection(previousRelation.collection);
2695
+ if (normalizedCurrent !== normalizedPrevious) {
2328
2696
  changes.push({
2329
2697
  property: "relation.collection",
2330
- oldValue: previousRelation.collection,
2331
- newValue: currentRelation.collection
2698
+ oldValue: normalizedPrevious,
2699
+ // Use normalized value for clarity
2700
+ newValue: normalizedCurrent
2701
+ // Use normalized value for clarity
2332
2702
  });
2333
2703
  }
2334
2704
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -2762,9 +3132,9 @@ function createMigrationFileStructure(upCode, downCode, config) {
2762
3132
  }
2763
3133
  function writeMigrationFile(migrationDir, filename, content) {
2764
3134
  try {
2765
- if (!fs2.existsSync(migrationDir)) {
3135
+ if (!fs3.existsSync(migrationDir)) {
2766
3136
  try {
2767
- fs2.mkdirSync(migrationDir, { recursive: true });
3137
+ fs3.mkdirSync(migrationDir, { recursive: true });
2768
3138
  } catch (error) {
2769
3139
  const fsError = error;
2770
3140
  if (fsError.code === "EACCES" || fsError.code === "EPERM") {
@@ -2786,7 +3156,7 @@ function writeMigrationFile(migrationDir, filename, content) {
2786
3156
  }
2787
3157
  }
2788
3158
  const filePath = path.join(migrationDir, filename);
2789
- fs2.writeFileSync(filePath, content, "utf-8");
3159
+ fs3.writeFileSync(filePath, content, "utf-8");
2790
3160
  return filePath;
2791
3161
  } catch (error) {
2792
3162
  if (error instanceof FileSystemError) {
@@ -3369,6 +3739,6 @@ var MigrationGenerator = class {
3369
3739
  }
3370
3740
  };
3371
3741
 
3372
- export { CLIUsageError, ConfigurationError, DiffEngine, FIELD_TYPE_INFO, FileSystemError, MigrationError, MigrationGenerationError, MigrationGenerator, POCKETBASE_FIELD_TYPES, SchemaAnalyzer, SchemaParsingError, SnapshotError, SnapshotManager, aggregateChanges, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldOptions, extractIndexes, extractSchemaDefinitions, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isRelationField, isSingleRelationField, isSystemCollection, loadBaseMigration, loadSnapshot, loadSnapshotIfExists, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergeSnapshots, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, saveSnapshot, selectSchemaForCollection, singularize, snapshotExists, toCollectionName, unwrapZodType, validateSnapshot, writeMigrationFile };
3742
+ export { CLIUsageError, ConfigurationError, DiffEngine, FIELD_TYPE_INFO, FileSystemError, MigrationError, MigrationGenerationError, MigrationGenerator, POCKETBASE_FIELD_TYPES, SchemaAnalyzer, SchemaParsingError, SnapshotError, SnapshotManager, aggregateChanges, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldOptions, extractIndexes, extractSchemaDefinitions, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isRelationField, isSingleRelationField, isSystemCollection, loadBaseMigration, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergeSnapshots, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, saveSnapshot, selectSchemaForCollection, singularize, snapshotExists, toCollectionName, unwrapZodType, validateSnapshot, writeMigrationFile };
3373
3743
  //# sourceMappingURL=index.js.map
3374
3744
  //# sourceMappingURL=index.js.map