pocketbase-zod-schema 0.7.0 → 0.7.2

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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/index.cjs +449 -50
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.d.cts +1 -1
  5. package/dist/cli/index.d.ts +1 -1
  6. package/dist/cli/index.js +449 -50
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +455 -53
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +455 -53
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/cli/utils/index.cjs +8 -1
  13. package/dist/cli/utils/index.cjs.map +1 -1
  14. package/dist/cli/utils/index.d.cts +1 -1
  15. package/dist/cli/utils/index.d.ts +1 -1
  16. package/dist/cli/utils/index.js +8 -1
  17. package/dist/cli/utils/index.js.map +1 -1
  18. package/dist/index.cjs +90 -1
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +87 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/migration/analyzer.cjs +87 -14
  25. package/dist/migration/analyzer.cjs.map +1 -1
  26. package/dist/migration/analyzer.d.cts +12 -4
  27. package/dist/migration/analyzer.d.ts +12 -4
  28. package/dist/migration/analyzer.js +87 -15
  29. package/dist/migration/analyzer.js.map +1 -1
  30. package/dist/migration/diff.cjs +64 -9
  31. package/dist/migration/diff.cjs.map +1 -1
  32. package/dist/migration/diff.d.cts +12 -2
  33. package/dist/migration/diff.d.ts +12 -2
  34. package/dist/migration/diff.js +64 -10
  35. package/dist/migration/diff.js.map +1 -1
  36. package/dist/migration/generator.cjs +206 -39
  37. package/dist/migration/generator.cjs.map +1 -1
  38. package/dist/migration/generator.d.cts +54 -2
  39. package/dist/migration/generator.d.ts +54 -2
  40. package/dist/migration/generator.js +203 -40
  41. package/dist/migration/generator.js.map +1 -1
  42. package/dist/migration/index.cjs +501 -63
  43. package/dist/migration/index.cjs.map +1 -1
  44. package/dist/migration/index.d.cts +2 -2
  45. package/dist/migration/index.d.ts +2 -2
  46. package/dist/migration/index.js +501 -63
  47. package/dist/migration/index.js.map +1 -1
  48. package/dist/migration/snapshot.cjs +147 -1
  49. package/dist/migration/snapshot.cjs.map +1 -1
  50. package/dist/migration/snapshot.d.cts +3 -1
  51. package/dist/migration/snapshot.d.ts +3 -1
  52. package/dist/migration/snapshot.js +147 -1
  53. package/dist/migration/snapshot.js.map +1 -1
  54. package/dist/migration/utils/index.d.cts +1 -1
  55. package/dist/migration/utils/index.d.ts +1 -1
  56. package/dist/schema.cjs +90 -1
  57. package/dist/schema.cjs.map +1 -1
  58. package/dist/schema.d.cts +128 -2
  59. package/dist/schema.d.ts +128 -2
  60. package/dist/schema.js +87 -2
  61. package/dist/schema.js.map +1 -1
  62. package/dist/server.cjs +545 -65
  63. package/dist/server.cjs.map +1 -1
  64. package/dist/server.d.cts +2 -2
  65. package/dist/server.d.ts +2 -2
  66. package/dist/server.js +542 -66
  67. package/dist/server.js.map +1 -1
  68. package/dist/{types-CWHV6ATd.d.cts → types-CnzfX6JH.d.cts} +20 -2
  69. package/dist/{types-C2nGWHLV.d.ts → types-Do3jyFBm.d.ts} +20 -2
  70. package/package.json +1 -1
@@ -1483,6 +1483,60 @@ function getFieldTypeInfo(zodType, fieldName) {
1483
1483
  options
1484
1484
  };
1485
1485
  }
1486
+ function dedentSql(value) {
1487
+ const lines = value.replace(/\r\n/g, "\n").split("\n");
1488
+ while (lines.length > 0 && lines[0].trim() === "") {
1489
+ lines.shift();
1490
+ }
1491
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
1492
+ lines.pop();
1493
+ }
1494
+ if (lines.length === 0) {
1495
+ return "";
1496
+ }
1497
+ let minIndent = Infinity;
1498
+ for (const line of lines) {
1499
+ if (line.trim() === "") continue;
1500
+ const indent = line.length - line.trimStart().length;
1501
+ if (indent < minIndent) {
1502
+ minIndent = indent;
1503
+ }
1504
+ }
1505
+ if (!Number.isFinite(minIndent) || minIndent === 0) {
1506
+ return lines.map((line) => line.trimEnd()).join("\n");
1507
+ }
1508
+ return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
1509
+ }
1510
+ function stripLeadingComments(query) {
1511
+ let remaining = query.trim();
1512
+ while (remaining.length > 0) {
1513
+ if (remaining.startsWith("--")) {
1514
+ const newline = remaining.indexOf("\n");
1515
+ remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
1516
+ continue;
1517
+ }
1518
+ if (remaining.startsWith("/*")) {
1519
+ const end = remaining.indexOf("*/");
1520
+ remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
1521
+ continue;
1522
+ }
1523
+ break;
1524
+ }
1525
+ return remaining;
1526
+ }
1527
+ function validateViewQuery(collectionName, viewQuery) {
1528
+ if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
1529
+ throw new Error(
1530
+ `View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
1531
+ );
1532
+ }
1533
+ const body = stripLeadingComments(viewQuery);
1534
+ if (!/^(select|with)\b/i.test(body)) {
1535
+ throw new Error(
1536
+ `View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
1537
+ );
1538
+ }
1539
+ }
1486
1540
  function getCollectionNameFromFile(filePath) {
1487
1541
  const filename = path5__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
1488
1542
  return toCollectionName(filename);
@@ -1506,13 +1560,26 @@ function extractCollectionTypeFromSchema(zodSchema) {
1506
1560
  }
1507
1561
  try {
1508
1562
  const metadata = JSON.parse(zodSchema.description);
1509
- if (metadata.type === "base" || metadata.type === "auth") {
1563
+ if (metadata.type === "base" || metadata.type === "auth" || metadata.type === "view") {
1510
1564
  return metadata.type;
1511
1565
  }
1512
1566
  } catch {
1513
1567
  }
1514
1568
  return null;
1515
1569
  }
1570
+ function extractViewQueryFromSchema(zodSchema) {
1571
+ if (!zodSchema.description) {
1572
+ return null;
1573
+ }
1574
+ try {
1575
+ const metadata = JSON.parse(zodSchema.description);
1576
+ if (typeof metadata.viewQuery === "string") {
1577
+ return metadata.viewQuery;
1578
+ }
1579
+ } catch {
1580
+ }
1581
+ return null;
1582
+ }
1516
1583
  function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema", "Collection"]) {
1517
1584
  const result = {};
1518
1585
  if (module.default instanceof zod.z.ZodObject) {
@@ -1671,6 +1738,15 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1671
1738
  const rawFields = extractFieldDefinitions(zodSchema);
1672
1739
  const explicitType = extractCollectionTypeFromSchema(zodSchema);
1673
1740
  const collectionType = explicitType ?? (isAuthCollection(rawFields) ? "auth" : "base");
1741
+ const isView = collectionType === "view";
1742
+ const viewQuery = extractViewQueryFromSchema(zodSchema);
1743
+ if (isView) {
1744
+ validateViewQuery(collectionName, viewQuery);
1745
+ } else if (viewQuery !== null) {
1746
+ console.warn(
1747
+ `[${collectionName}] viewQuery is only used by view collections and will be ignored. Use defineView() or set type: "view" to define a view collection.`
1748
+ );
1749
+ }
1674
1750
  const fields = rawFields.filter((f) => !["created", "updated"].includes(f.name)).map(({ name, zodType }) => buildFieldDefinition(name, zodType));
1675
1751
  if (collectionType === "auth") {
1676
1752
  const authSystemFields = [
@@ -1730,26 +1806,43 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1730
1806
  }
1731
1807
  }
1732
1808
  const indexes = extractIndexes(zodSchema) || [];
1809
+ if (isView && indexes.length > 0) {
1810
+ throw new Error(
1811
+ `View collection "${collectionName}" cannot declare indexes. PocketBase view collections are backed by a SQL query and do not support indexes.`
1812
+ );
1813
+ }
1733
1814
  const permissionAnalyzer = new PermissionAnalyzer();
1734
1815
  let permissions = void 0;
1735
1816
  const schemaDescription = zodSchema.description;
1736
1817
  const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
1737
1818
  if (extractedPermissions) {
1738
1819
  const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
1739
- const validationResults = permissionAnalyzer.validatePermissions(
1740
- collectionName,
1741
- resolvedPermissions,
1742
- fields,
1743
- collectionType === "auth"
1744
- );
1745
- for (const [ruleType, result] of validationResults) {
1746
- if (!result.valid) {
1747
- console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
1748
- result.errors.forEach((error) => console.error(` - ${error}`));
1820
+ if (isView) {
1821
+ for (const ruleType of ["createRule", "updateRule", "deleteRule", "manageRule"]) {
1822
+ if (resolvedPermissions[ruleType]) {
1823
+ console.warn(
1824
+ `[${collectionName}] ${ruleType} is not supported on view collections and will be set to null.`
1825
+ );
1826
+ }
1827
+ resolvedPermissions[ruleType] = null;
1749
1828
  }
1750
- if (result.warnings.length > 0) {
1751
- console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
1752
- result.warnings.forEach((warning) => console.warn(` - ${warning}`));
1829
+ }
1830
+ if (!isView) {
1831
+ const validationResults = permissionAnalyzer.validatePermissions(
1832
+ collectionName,
1833
+ resolvedPermissions,
1834
+ fields,
1835
+ collectionType === "auth"
1836
+ );
1837
+ for (const [ruleType, result] of validationResults) {
1838
+ if (!result.valid) {
1839
+ console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
1840
+ result.errors.forEach((error) => console.error(` - ${error}`));
1841
+ }
1842
+ if (result.warnings.length > 0) {
1843
+ console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
1844
+ result.warnings.forEach((warning) => console.warn(` - ${warning}`));
1845
+ }
1753
1846
  }
1754
1847
  }
1755
1848
  permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
@@ -1770,6 +1863,9 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1770
1863
  },
1771
1864
  permissions
1772
1865
  };
1866
+ if (isView) {
1867
+ collectionSchema.viewQuery = viewQuery;
1868
+ }
1773
1869
  return collectionSchema;
1774
1870
  }
1775
1871
  var tsxLoaderRegistered = false;
@@ -2051,8 +2147,9 @@ function convertPocketBaseField(pbField) {
2051
2147
  }
2052
2148
  function convertPocketBaseCollection(pbCollection) {
2053
2149
  const fields = [];
2150
+ const isView = pbCollection.type === "view";
2054
2151
  const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
2055
- if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
2152
+ if (!isView && pbCollection.fields && Array.isArray(pbCollection.fields)) {
2056
2153
  for (const pbField of pbCollection.fields) {
2057
2154
  if (pbField.system || systemFieldNames.includes(pbField.name)) {
2058
2155
  const isAuthSystemField = pbCollection.type === "auth" && ["email", "emailVisibility", "verified", "password", "tokenKey"].includes(pbField.name);
@@ -2072,6 +2169,9 @@ function convertPocketBaseCollection(pbCollection) {
2072
2169
  if (pbCollection.id) {
2073
2170
  schema.id = pbCollection.id;
2074
2171
  }
2172
+ if (typeof pbCollection.viewQuery === "string") {
2173
+ schema.viewQuery = dedentSql(pbCollection.viewQuery);
2174
+ }
2075
2175
  if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
2076
2176
  schema.indexes = pbCollection.indexes;
2077
2177
  }
@@ -2173,6 +2273,24 @@ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
2173
2273
  return [];
2174
2274
  }
2175
2275
  }
2276
+ function skipTemplateLiteral(content, start) {
2277
+ let i = start + 1;
2278
+ while (i < content.length) {
2279
+ const char = content[i];
2280
+ if (char === "\\") {
2281
+ i += 2;
2282
+ continue;
2283
+ }
2284
+ if (char === "`") {
2285
+ return i + 1;
2286
+ }
2287
+ i++;
2288
+ }
2289
+ return i;
2290
+ }
2291
+ function unescapeTemplateLiteral(raw) {
2292
+ return raw.replace(/\\(`|\$\{|\\)/g, "$1");
2293
+ }
2176
2294
  function parseMigrationOperationsFromContent(content) {
2177
2295
  const collectionsToCreate = [];
2178
2296
  const collectionsToDelete = [];
@@ -2261,6 +2379,10 @@ function parseMigrationOperationsFromContent(content) {
2261
2379
  while (i < content.length && bCount > 0) {
2262
2380
  const char = content[i];
2263
2381
  const prev = i > 0 ? content[i - 1] : "";
2382
+ if (!inStr && char === "`") {
2383
+ i = skipTemplateLiteral(content, i);
2384
+ continue;
2385
+ }
2264
2386
  if (!inStr && (char === '"' || char === "'")) {
2265
2387
  inStr = true;
2266
2388
  strChar = char;
@@ -2450,6 +2572,95 @@ function parseMigrationOperationsFromContent(content) {
2450
2572
  }
2451
2573
  }
2452
2574
  }
2575
+ const viewQueryRegex = /(\w+)\.viewQuery\s*=\s*/g;
2576
+ let viewQueryMatch;
2577
+ while ((viewQueryMatch = viewQueryRegex.exec(content)) !== null) {
2578
+ const varInfo = variables.get(viewQueryMatch[1]);
2579
+ if (!varInfo || varInfo.type !== "collection") continue;
2580
+ const valueStart = viewQueryMatch.index + viewQueryMatch[0].length;
2581
+ if (content[valueStart] === "`") {
2582
+ const end = skipTemplateLiteral(content, valueStart);
2583
+ const raw = content.substring(valueStart + 1, end - 1);
2584
+ getUpdate(varInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
2585
+ viewQueryRegex.lastIndex = end;
2586
+ } else {
2587
+ const terminator = content.indexOf(";", valueStart);
2588
+ const valueStr = content.substring(valueStart, terminator === -1 ? content.length : terminator);
2589
+ try {
2590
+ getUpdate(varInfo.name).viewQuery = dedentSql(new Function("app", `return ${valueStr}`)(mockApp));
2591
+ } catch {
2592
+ getUpdate(varInfo.name).viewQuery = dedentSql(valueStr.trim());
2593
+ }
2594
+ }
2595
+ }
2596
+ const unmarshalRegex = /unmarshal\s*\(/g;
2597
+ let unmarshalMatch;
2598
+ while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
2599
+ let i = unmarshalMatch.index + unmarshalMatch[0].length;
2600
+ while (i < content.length && /\s/.test(content[i])) i++;
2601
+ if (content[i] !== "{") continue;
2602
+ const objStart = i;
2603
+ let braceCount = 1;
2604
+ let inStr = false;
2605
+ let strChar = null;
2606
+ i++;
2607
+ while (i < content.length && braceCount > 0) {
2608
+ const ch = content[i];
2609
+ const prev = i > 0 ? content[i - 1] : "";
2610
+ if (!inStr && ch === "`") {
2611
+ i = skipTemplateLiteral(content, i);
2612
+ continue;
2613
+ }
2614
+ if (!inStr && (ch === '"' || ch === "'")) {
2615
+ inStr = true;
2616
+ strChar = ch;
2617
+ } else if (inStr && ch === strChar && prev !== "\\") {
2618
+ inStr = false;
2619
+ strChar = null;
2620
+ }
2621
+ if (!inStr) {
2622
+ if (ch === "{") braceCount++;
2623
+ if (ch === "}") braceCount--;
2624
+ }
2625
+ i++;
2626
+ }
2627
+ if (braceCount !== 0) continue;
2628
+ const objStr = content.substring(objStart, i);
2629
+ while (i < content.length && /[\s,]/.test(content[i])) i++;
2630
+ const varStart = i;
2631
+ while (i < content.length && /\w/.test(content[i])) i++;
2632
+ const colVarName = content.substring(varStart, i);
2633
+ const colInfo = variables.get(colVarName);
2634
+ if (!colInfo || colInfo.type !== "collection") continue;
2635
+ const keyValRegex = /"(\w+Rule)"\s*:\s*([^,}\n]+)/g;
2636
+ let kvMatch;
2637
+ while ((kvMatch = keyValRegex.exec(objStr)) !== null) {
2638
+ const ruleKey = kvMatch[1];
2639
+ const valStr = kvMatch[2].trim();
2640
+ try {
2641
+ const value = new Function("app", `return ${valStr}`)(mockApp);
2642
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = value;
2643
+ } catch {
2644
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
2645
+ }
2646
+ }
2647
+ const viewQueryKey = objStr.match(/"viewQuery"\s*:\s*/);
2648
+ if (viewQueryKey) {
2649
+ const valueStart = viewQueryKey.index + viewQueryKey[0].length;
2650
+ if (objStr[valueStart] === "`") {
2651
+ const end = skipTemplateLiteral(objStr, valueStart);
2652
+ const raw = objStr.substring(valueStart + 1, end - 1);
2653
+ getUpdate(colInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
2654
+ } else {
2655
+ const valStr = objStr.substring(valueStart).replace(/[,}\s]+$/, "");
2656
+ try {
2657
+ getUpdate(colInfo.name).viewQuery = dedentSql(new Function("app", `return ${valStr}`)(mockApp));
2658
+ } catch {
2659
+ getUpdate(colInfo.name).viewQuery = dedentSql(valStr);
2660
+ }
2661
+ }
2662
+ }
2663
+ }
2453
2664
  const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
2454
2665
  let match;
2455
2666
  while ((match = idxPushRegex.exec(content)) !== null) {
@@ -2579,6 +2790,10 @@ function parseMigrationOperations(migrationContent) {
2579
2790
  while (i < migrationContent.length && braceCount > 0) {
2580
2791
  const char = migrationContent[i];
2581
2792
  const prevChar = i > 0 ? migrationContent[i - 1] : "";
2793
+ if (!inString && char === "`") {
2794
+ i = skipTemplateLiteral(migrationContent, i);
2795
+ continue;
2796
+ }
2582
2797
  if (!inString && (char === '"' || char === "'")) {
2583
2798
  inString = true;
2584
2799
  stringChar = char;
@@ -2914,6 +3129,9 @@ function applyMigrationOperations(snapshot, operations) {
2914
3129
  collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
2915
3130
  }
2916
3131
  }
3132
+ if (update.viewQuery !== void 0) {
3133
+ collection.viewQuery = update.viewQuery;
3134
+ }
2917
3135
  if (Object.keys(update.rulesToUpdate).length > 0) {
2918
3136
  if (!collection.rules) collection.rules = {};
2919
3137
  if (!collection.permissions) collection.permissions = {};
@@ -3533,7 +3751,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
3533
3751
  fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
3534
3752
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
3535
3753
  }
3754
+ function normalizeSql(query) {
3755
+ if (!query) {
3756
+ return "";
3757
+ }
3758
+ return query.split("\n").map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim();
3759
+ }
3760
+ function compareViewQuery(currentCollection, previousCollection) {
3761
+ const newValue = currentCollection.viewQuery;
3762
+ if (typeof newValue !== "string") {
3763
+ return void 0;
3764
+ }
3765
+ if (normalizeSql(newValue) === normalizeSql(previousCollection.viewQuery)) {
3766
+ return void 0;
3767
+ }
3768
+ return {
3769
+ oldValue: previousCollection.viewQuery ?? null,
3770
+ newValue
3771
+ };
3772
+ }
3536
3773
  function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
3774
+ const isView = currentCollection.type === "view" || previousCollection.type === "view";
3775
+ if (isView) {
3776
+ return {
3777
+ collection: currentCollection.name,
3778
+ fieldsToAdd: [],
3779
+ fieldsToRemove: [],
3780
+ fieldsToModify: [],
3781
+ indexesToAdd: [],
3782
+ indexesToRemove: [],
3783
+ rulesToUpdate: compareRules(
3784
+ currentCollection.rules,
3785
+ previousCollection.rules,
3786
+ currentCollection.permissions,
3787
+ previousCollection.permissions
3788
+ ),
3789
+ permissionsToUpdate: comparePermissions(currentCollection.permissions, previousCollection.permissions),
3790
+ viewQueryUpdate: compareViewQuery(currentCollection, previousCollection)
3791
+ };
3792
+ }
3537
3793
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
3538
3794
  currentCollection,
3539
3795
  previousCollection,
@@ -3565,6 +3821,9 @@ function detectDestructiveChanges(diff, config) {
3565
3821
  const destructiveChanges = [];
3566
3822
  const mergedConfig = mergeConfig3(config);
3567
3823
  for (const collection of diff.collectionsToDelete) {
3824
+ if (collection.type === "view") {
3825
+ continue;
3826
+ }
3568
3827
  destructiveChanges.push({
3569
3828
  type: "collection_delete",
3570
3829
  severity: "high",
@@ -3652,7 +3911,11 @@ function categorizeChangesBySeverity(diff, _config) {
3652
3911
  const destructive = [];
3653
3912
  const nonDestructive = [];
3654
3913
  for (const collection of diff.collectionsToDelete) {
3655
- destructive.push(`Delete collection: ${collection.name}`);
3914
+ if (collection.type === "view") {
3915
+ nonDestructive.push(`Delete view collection: ${collection.name}`);
3916
+ } else {
3917
+ destructive.push(`Delete collection: ${collection.name}`);
3918
+ }
3656
3919
  }
3657
3920
  for (const collection of diff.collectionsToCreate) {
3658
3921
  nonDestructive.push(`Create collection: ${collection.name}`);
@@ -3687,6 +3950,9 @@ function categorizeChangesBySeverity(diff, _config) {
3687
3950
  for (const rule of modification.rulesToUpdate) {
3688
3951
  nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
3689
3952
  }
3953
+ if (modification.viewQueryUpdate) {
3954
+ nonDestructive.push(`Update view query: ${collectionName}`);
3955
+ }
3690
3956
  }
3691
3957
  return { destructive, nonDestructive };
3692
3958
  }
@@ -3699,6 +3965,7 @@ function generateChangeSummary(diff, config) {
3699
3965
  let indexChanges = 0;
3700
3966
  let ruleChanges = 0;
3701
3967
  let permissionChanges = 0;
3968
+ let viewQueryChanges = 0;
3702
3969
  for (const modification of diff.collectionsToModify) {
3703
3970
  fieldsToAdd += modification.fieldsToAdd.length;
3704
3971
  fieldsToRemove += modification.fieldsToRemove.length;
@@ -3706,6 +3973,9 @@ function generateChangeSummary(diff, config) {
3706
3973
  indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
3707
3974
  ruleChanges += modification.rulesToUpdate.length;
3708
3975
  permissionChanges += modification.permissionsToUpdate.length;
3976
+ if (modification.viewQueryUpdate) {
3977
+ viewQueryChanges += 1;
3978
+ }
3709
3979
  }
3710
3980
  return {
3711
3981
  totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
@@ -3718,6 +3988,7 @@ function generateChangeSummary(diff, config) {
3718
3988
  indexChanges,
3719
3989
  ruleChanges,
3720
3990
  permissionChanges,
3991
+ viewQueryChanges,
3721
3992
  destructiveChanges,
3722
3993
  nonDestructiveChanges: nonDestructive
3723
3994
  };
@@ -3747,12 +4018,11 @@ function filterDiff(diff, options) {
3747
4018
  });
3748
4019
  let collectionsToDelete = diff.collectionsToDelete;
3749
4020
  if (skipDestructive) {
3750
- collectionsToDelete = [];
3751
- } else {
3752
- collectionsToDelete = collectionsToDelete.filter((col) => {
3753
- return matchesPattern(col.name, patterns);
3754
- });
4021
+ collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
3755
4022
  }
4023
+ collectionsToDelete = collectionsToDelete.filter((col) => {
4024
+ return matchesPattern(col.name, patterns);
4025
+ });
3756
4026
  const collectionsToModify = diff.collectionsToModify.map((mod) => {
3757
4027
  const collectionMatches = matchesPattern(mod.collection, patterns);
3758
4028
  const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
@@ -3777,6 +4047,7 @@ function filterDiff(diff, options) {
3777
4047
  const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
3778
4048
  const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
3779
4049
  const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
4050
+ const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
3780
4051
  return {
3781
4052
  ...mod,
3782
4053
  fieldsToAdd,
@@ -3785,10 +4056,11 @@ function filterDiff(diff, options) {
3785
4056
  indexesToAdd,
3786
4057
  indexesToRemove,
3787
4058
  rulesToUpdate,
3788
- permissionsToUpdate
4059
+ permissionsToUpdate,
4060
+ viewQueryUpdate
3789
4061
  };
3790
4062
  }).filter((mod) => {
3791
- return mod.fieldsToAdd.length > 0 || mod.fieldsToRemove.length > 0 || mod.fieldsToModify.length > 0 || mod.indexesToAdd.length > 0 || mod.indexesToRemove.length > 0 || mod.rulesToUpdate.length > 0 || mod.permissionsToUpdate.length > 0;
4063
+ return mod.fieldsToAdd.length > 0 || mod.fieldsToRemove.length > 0 || mod.fieldsToModify.length > 0 || mod.indexesToAdd.length > 0 || mod.indexesToRemove.length > 0 || mod.rulesToUpdate.length > 0 || mod.permissionsToUpdate.length > 0 || mod.viewQueryUpdate !== void 0;
3792
4064
  });
3793
4065
  return {
3794
4066
  ...diff,
@@ -3800,7 +4072,7 @@ function filterDiff(diff, options) {
3800
4072
 
3801
4073
  // src/migration/diff/index.ts
3802
4074
  function hasChanges(modification) {
3803
- 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;
4075
+ 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 || modification.viewQueryUpdate !== void 0;
3804
4076
  }
3805
4077
  function aggregateChanges(currentSchema, previousSnapshot, config) {
3806
4078
  const collectionIdToName = /* @__PURE__ */ new Map();
@@ -4015,6 +4287,13 @@ function formatValue(value) {
4015
4287
  }
4016
4288
  return String(value);
4017
4289
  }
4290
+ function formatSqlTemplate(query, indent = " ") {
4291
+ const escaped = query.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
4292
+ const body = escaped.split("\n").map((line) => line.trim() === "" ? "" : `${indent}${line}`).join("\n");
4293
+ return `\`
4294
+ ${body}
4295
+ ${indent.slice(0, -2)}\``;
4296
+ }
4018
4297
  function getFieldConstructorName(fieldType) {
4019
4298
  const constructorMap = {
4020
4299
  text: "TextField",
@@ -4053,6 +4332,36 @@ function getSystemFields() {
4053
4332
  }
4054
4333
  ];
4055
4334
  }
4335
+ function getSystemTimestampFields() {
4336
+ return [
4337
+ {
4338
+ name: "created",
4339
+ id: "autodate2990389176",
4340
+ type: "autodate",
4341
+ required: false,
4342
+ options: {
4343
+ hidden: false,
4344
+ onCreate: true,
4345
+ onUpdate: false,
4346
+ presentable: false,
4347
+ system: true
4348
+ }
4349
+ },
4350
+ {
4351
+ name: "updated",
4352
+ id: "autodate3332085495",
4353
+ type: "autodate",
4354
+ required: false,
4355
+ options: {
4356
+ hidden: false,
4357
+ onCreate: true,
4358
+ onUpdate: true,
4359
+ presentable: false,
4360
+ system: true
4361
+ }
4362
+ }
4363
+ ];
4364
+ }
4056
4365
  function getAuthSystemFields() {
4057
4366
  return [
4058
4367
  {
@@ -4345,6 +4654,15 @@ function generateCollectionRules(rules, collectionType = "base") {
4345
4654
  if (!rules) {
4346
4655
  return "";
4347
4656
  }
4657
+ if (collectionType === "view") {
4658
+ return [
4659
+ `"listRule": ${formatValue(rules.listRule ?? null)}`,
4660
+ `"viewRule": ${formatValue(rules.viewRule ?? null)}`,
4661
+ `"createRule": null`,
4662
+ `"updateRule": null`,
4663
+ `"deleteRule": null`
4664
+ ].join(",\n ");
4665
+ }
4348
4666
  const parts = [];
4349
4667
  if (rules.listRule !== void 0) {
4350
4668
  parts.push(`"listRule": ${formatValue(rules.listRule)}`);
@@ -4370,6 +4688,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
4370
4688
  if (!permissions) {
4371
4689
  return "";
4372
4690
  }
4691
+ if (collectionType === "view") {
4692
+ return [
4693
+ `"listRule": ${formatValue(permissions.listRule ?? null)}`,
4694
+ `"viewRule": ${formatValue(permissions.viewRule ?? null)}`,
4695
+ `"createRule": null`,
4696
+ `"updateRule": null`,
4697
+ `"deleteRule": null`
4698
+ ].join(",\n ");
4699
+ }
4373
4700
  const parts = [];
4374
4701
  if (permissions.listRule !== void 0) {
4375
4702
  parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
@@ -4407,6 +4734,28 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
4407
4734
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4408
4735
  return lines.join("\n");
4409
4736
  }
4737
+ function generateViewQueryUpdate(collectionName, viewQuery, varName, isLast = false, collectionIdMap) {
4738
+ const lines = [];
4739
+ const collectionVar = varName || `collection_${collectionName}_viewQuery`;
4740
+ lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
4741
+ lines.push(` unmarshal({`);
4742
+ lines.push(` "viewQuery": ${formatSqlTemplate(viewQuery, " ")},`);
4743
+ lines.push(` }, ${collectionVar})`);
4744
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4745
+ return lines.join("\n");
4746
+ }
4747
+ function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
4748
+ const collectionVar = `collection_${collectionName}_${varSuffix}`;
4749
+ const lines = [];
4750
+ lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
4751
+ lines.push(` unmarshal({`);
4752
+ for (const entry of entries) {
4753
+ lines.push(` "${entry.ruleType}": ${formatValue(entry.value)},`);
4754
+ }
4755
+ lines.push(` }, ${collectionVar})`);
4756
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4757
+ return lines.join("\n");
4758
+ }
4410
4759
 
4411
4760
  // src/migration/generator/collections.ts
4412
4761
  function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
@@ -4425,16 +4774,24 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
4425
4774
  } else if (rulesCode) {
4426
4775
  lines.push(` ${rulesCode},`);
4427
4776
  }
4777
+ if (collection.type === "view") {
4778
+ lines.push(` "viewQuery": ${formatSqlTemplate(collection.viewQuery ?? "")},`);
4779
+ lines.push(` });`);
4780
+ lines.push(``);
4781
+ lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
4782
+ return lines.join("\n");
4783
+ }
4428
4784
  const systemFieldNames = ["created", "updated", "id"];
4429
4785
  if (collection.type === "auth") {
4430
4786
  systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
4431
4787
  }
4432
4788
  const userFields = collection.fields.filter((f) => !systemFieldNames.includes(f.name));
4433
- const allFields = [...getSystemFields().filter((f) => f.name === "id")];
4789
+ const allFields = [...getSystemFields()];
4434
4790
  if (collection.type === "auth") {
4435
4791
  allFields.push(...getAuthSystemFields());
4436
4792
  }
4437
4793
  allFields.push(...userFields);
4794
+ allFields.push(...getSystemTimestampFields());
4438
4795
  lines.push(` "fields": ${generateFieldsArray(allFields, collectionIdMap)},`);
4439
4796
  let allIndexes = [...collection.indexes || []];
4440
4797
  if (collection.type === "auth") {
@@ -4464,7 +4821,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4464
4821
  const modification = operation.modifications;
4465
4822
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4466
4823
  let operationCount = 0;
4467
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
4824
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.viewQueryUpdate ? 1 : 0) + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
4825
+ if (modification.viewQueryUpdate) {
4826
+ operationCount++;
4827
+ const isLast = operationCount === totalOperations;
4828
+ lines.push(
4829
+ generateViewQueryUpdate(
4830
+ collectionName,
4831
+ modification.viewQueryUpdate.newValue,
4832
+ void 0,
4833
+ isLast,
4834
+ collectionIdMap
4835
+ )
4836
+ );
4837
+ if (!isLast) lines.push("");
4838
+ }
4468
4839
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
4469
4840
  const field = modification.fieldsToAdd[i];
4470
4841
  operationCount++;
@@ -4504,23 +4875,29 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4504
4875
  if (!isLast) lines.push("");
4505
4876
  }
4506
4877
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4507
- for (const permission of modification.permissionsToUpdate) {
4508
- operationCount++;
4878
+ operationCount++;
4879
+ const isLast = operationCount === totalOperations;
4880
+ if (modification.permissionsToUpdate.length >= 2) {
4881
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.newValue }));
4882
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
4883
+ } else {
4884
+ const permission = modification.permissionsToUpdate[0];
4509
4885
  const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
4510
- const isLast = operationCount === totalOperations;
4511
- lines.push(
4512
- generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap)
4513
- );
4514
- if (!isLast) lines.push("");
4886
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap));
4515
4887
  }
4888
+ if (!isLast) lines.push("");
4516
4889
  } else if (modification.rulesToUpdate.length > 0) {
4517
- for (const rule of modification.rulesToUpdate) {
4518
- operationCount++;
4890
+ operationCount++;
4891
+ const isLast = operationCount === totalOperations;
4892
+ if (modification.rulesToUpdate.length >= 2) {
4893
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.newValue }));
4894
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
4895
+ } else {
4896
+ const rule = modification.rulesToUpdate[0];
4519
4897
  const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
4520
- const isLast = operationCount === totalOperations;
4521
4898
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, isLast, collectionIdMap));
4522
- if (!isLast) lines.push("");
4523
4899
  }
4900
+ if (!isLast) lines.push("");
4524
4901
  }
4525
4902
  } else if (operation.type === "delete") {
4526
4903
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection.name;
@@ -4559,25 +4936,45 @@ function generateOperationDownMigration(operation, collectionIdMap) {
4559
4936
  const modification = operation.modifications;
4560
4937
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4561
4938
  let operationCount = 0;
4562
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
4939
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.viewQueryUpdate ? 1 : 0) + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
4940
+ if (modification.viewQueryUpdate) {
4941
+ operationCount++;
4942
+ const isLast = operationCount === totalOperations;
4943
+ lines.push(
4944
+ generateViewQueryUpdate(
4945
+ collectionName,
4946
+ modification.viewQueryUpdate.oldValue ?? "",
4947
+ `collection_${collectionName}_revert_viewQuery`,
4948
+ isLast,
4949
+ collectionIdMap
4950
+ )
4951
+ );
4952
+ if (!isLast) lines.push("");
4953
+ }
4563
4954
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4564
- for (const permission of modification.permissionsToUpdate) {
4565
- operationCount++;
4955
+ operationCount++;
4956
+ const isLast = operationCount === totalOperations;
4957
+ if (modification.permissionsToUpdate.length >= 2) {
4958
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.oldValue }));
4959
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
4960
+ } else {
4961
+ const permission = modification.permissionsToUpdate[0];
4566
4962
  const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
4567
- const isLast = operationCount === totalOperations;
4568
- lines.push(
4569
- generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap)
4570
- );
4571
- if (!isLast) lines.push("");
4963
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap));
4572
4964
  }
4965
+ if (!isLast) lines.push("");
4573
4966
  } else if (modification.rulesToUpdate.length > 0) {
4574
- for (const rule of modification.rulesToUpdate) {
4575
- operationCount++;
4967
+ operationCount++;
4968
+ const isLast = operationCount === totalOperations;
4969
+ if (modification.rulesToUpdate.length >= 2) {
4970
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.oldValue }));
4971
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
4972
+ } else {
4973
+ const rule = modification.rulesToUpdate[0];
4576
4974
  const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
4577
- const isLast = operationCount === totalOperations;
4578
4975
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, isLast, collectionIdMap));
4579
- if (!isLast) lines.push("");
4580
4976
  }
4977
+ if (!isLast) lines.push("");
4581
4978
  }
4582
4979
  for (let i = 0; i < modification.indexesToRemove.length; i++) {
4583
4980
  operationCount++;
@@ -4684,6 +5081,19 @@ function generateUpMigration(diff) {
4684
5081
  lines.push(` // Modify existing collections`);
4685
5082
  for (const modification of diff.collectionsToModify) {
4686
5083
  const collectionName = modification.collection;
5084
+ if (modification.viewQueryUpdate) {
5085
+ lines.push(` // Update the view query of ${collectionName}`);
5086
+ lines.push(
5087
+ generateViewQueryUpdate(
5088
+ collectionName,
5089
+ modification.viewQueryUpdate.newValue,
5090
+ void 0,
5091
+ false,
5092
+ collectionIdMap
5093
+ )
5094
+ );
5095
+ lines.push(``);
5096
+ }
4687
5097
  if (modification.fieldsToAdd.length > 0) {
4688
5098
  lines.push(` // Add fields to ${collectionName}`);
4689
5099
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
@@ -4729,20 +5139,26 @@ function generateUpMigration(diff) {
4729
5139
  }
4730
5140
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4731
5141
  lines.push(` // Update permissions for ${collectionName}`);
4732
- for (const permission of modification.permissionsToUpdate) {
5142
+ if (modification.permissionsToUpdate.length >= 2) {
5143
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.newValue }));
5144
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", false, collectionIdMap));
5145
+ } else {
5146
+ const permission = modification.permissionsToUpdate[0];
4733
5147
  const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
4734
- lines.push(
4735
- generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, false, collectionIdMap)
4736
- );
4737
- lines.push(``);
5148
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, false, collectionIdMap));
4738
5149
  }
5150
+ lines.push(``);
4739
5151
  } else if (modification.rulesToUpdate.length > 0) {
4740
5152
  lines.push(` // Update rules for ${collectionName}`);
4741
- for (const rule of modification.rulesToUpdate) {
5153
+ if (modification.rulesToUpdate.length >= 2) {
5154
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.newValue }));
5155
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", false, collectionIdMap));
5156
+ } else {
5157
+ const rule = modification.rulesToUpdate[0];
4742
5158
  const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
4743
5159
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, false, collectionIdMap));
4744
- lines.push(``);
4745
5160
  }
5161
+ lines.push(``);
4746
5162
  }
4747
5163
  }
4748
5164
  }
@@ -4814,22 +5230,41 @@ function generateDownMigration(diff) {
4814
5230
  lines.push(` // Revert modifications`);
4815
5231
  for (const modification of diff.collectionsToModify) {
4816
5232
  const collectionName = modification.collection;
5233
+ if (modification.viewQueryUpdate) {
5234
+ lines.push(` // Restore the view query of ${collectionName}`);
5235
+ lines.push(
5236
+ generateViewQueryUpdate(
5237
+ collectionName,
5238
+ modification.viewQueryUpdate.oldValue ?? "",
5239
+ `collection_${collectionName}_revert_viewQuery`,
5240
+ false,
5241
+ collectionIdMap
5242
+ )
5243
+ );
5244
+ lines.push(``);
5245
+ }
4817
5246
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4818
5247
  lines.push(` // Revert permissions for ${collectionName}`);
4819
- for (const permission of modification.permissionsToUpdate) {
5248
+ if (modification.permissionsToUpdate.length >= 2) {
5249
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.oldValue }));
5250
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", false, collectionIdMap));
5251
+ } else {
5252
+ const permission = modification.permissionsToUpdate[0];
4820
5253
  const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
4821
- lines.push(
4822
- generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, false, collectionIdMap)
4823
- );
4824
- lines.push(``);
5254
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, false, collectionIdMap));
4825
5255
  }
5256
+ lines.push(``);
4826
5257
  } else if (modification.rulesToUpdate.length > 0) {
4827
5258
  lines.push(` // Revert rules for ${collectionName}`);
4828
- for (const rule of modification.rulesToUpdate) {
5259
+ if (modification.rulesToUpdate.length >= 2) {
5260
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.oldValue }));
5261
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", false, collectionIdMap));
5262
+ } else {
5263
+ const rule = modification.rulesToUpdate[0];
4829
5264
  const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
4830
5265
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, false, collectionIdMap));
4831
- lines.push(``);
4832
5266
  }
5267
+ lines.push(``);
4833
5268
  }
4834
5269
  if (modification.indexesToRemove.length > 0) {
4835
5270
  lines.push(` // Restore indexes to ${collectionName}`);
@@ -5159,6 +5594,9 @@ var MigrationGenerator = class {
5159
5594
  function detectCollectionDeletions(diff) {
5160
5595
  const changes = [];
5161
5596
  for (const collection of diff.collectionsToDelete) {
5597
+ if (collection.type === "view") {
5598
+ continue;
5599
+ }
5162
5600
  changes.push({
5163
5601
  type: "collection_deletion" /* COLLECTION_DELETION */,
5164
5602
  description: `Delete collection: ${collection.name}`,