pocketbase-zod-schema 0.7.1 → 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 +7 -0
  2. package/dist/cli/index.cjs +323 -27
  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 +323 -27
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +329 -30
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +329 -30
  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 +100 -2
  37. package/dist/migration/generator.cjs.map +1 -1
  38. package/dist/migration/generator.d.cts +32 -2
  39. package/dist/migration/generator.d.ts +32 -2
  40. package/dist/migration/generator.js +99 -3
  41. package/dist/migration/generator.js.map +1 -1
  42. package/dist/migration/index.cjs +349 -26
  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 +349 -26
  47. package/dist/migration/index.js.map +1 -1
  48. package/dist/migration/snapshot.cjs +99 -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 +99 -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 +393 -28
  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 +390 -29
  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,27 @@ 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
+ }
2453
2596
  const unmarshalRegex = /unmarshal\s*\(/g;
2454
2597
  let unmarshalMatch;
2455
2598
  while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
@@ -2464,6 +2607,10 @@ function parseMigrationOperationsFromContent(content) {
2464
2607
  while (i < content.length && braceCount > 0) {
2465
2608
  const ch = content[i];
2466
2609
  const prev = i > 0 ? content[i - 1] : "";
2610
+ if (!inStr && ch === "`") {
2611
+ i = skipTemplateLiteral(content, i);
2612
+ continue;
2613
+ }
2467
2614
  if (!inStr && (ch === '"' || ch === "'")) {
2468
2615
  inStr = true;
2469
2616
  strChar = ch;
@@ -2497,6 +2644,22 @@ function parseMigrationOperationsFromContent(content) {
2497
2644
  getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
2498
2645
  }
2499
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
+ }
2500
2663
  }
2501
2664
  const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
2502
2665
  let match;
@@ -2627,6 +2790,10 @@ function parseMigrationOperations(migrationContent) {
2627
2790
  while (i < migrationContent.length && braceCount > 0) {
2628
2791
  const char = migrationContent[i];
2629
2792
  const prevChar = i > 0 ? migrationContent[i - 1] : "";
2793
+ if (!inString && char === "`") {
2794
+ i = skipTemplateLiteral(migrationContent, i);
2795
+ continue;
2796
+ }
2630
2797
  if (!inString && (char === '"' || char === "'")) {
2631
2798
  inString = true;
2632
2799
  stringChar = char;
@@ -2962,6 +3129,9 @@ function applyMigrationOperations(snapshot, operations) {
2962
3129
  collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
2963
3130
  }
2964
3131
  }
3132
+ if (update.viewQuery !== void 0) {
3133
+ collection.viewQuery = update.viewQuery;
3134
+ }
2965
3135
  if (Object.keys(update.rulesToUpdate).length > 0) {
2966
3136
  if (!collection.rules) collection.rules = {};
2967
3137
  if (!collection.permissions) collection.permissions = {};
@@ -3581,7 +3751,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
3581
3751
  fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
3582
3752
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
3583
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
+ }
3584
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
+ }
3585
3793
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
3586
3794
  currentCollection,
3587
3795
  previousCollection,
@@ -3613,6 +3821,9 @@ function detectDestructiveChanges(diff, config) {
3613
3821
  const destructiveChanges = [];
3614
3822
  const mergedConfig = mergeConfig3(config);
3615
3823
  for (const collection of diff.collectionsToDelete) {
3824
+ if (collection.type === "view") {
3825
+ continue;
3826
+ }
3616
3827
  destructiveChanges.push({
3617
3828
  type: "collection_delete",
3618
3829
  severity: "high",
@@ -3700,7 +3911,11 @@ function categorizeChangesBySeverity(diff, _config) {
3700
3911
  const destructive = [];
3701
3912
  const nonDestructive = [];
3702
3913
  for (const collection of diff.collectionsToDelete) {
3703
- 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
+ }
3704
3919
  }
3705
3920
  for (const collection of diff.collectionsToCreate) {
3706
3921
  nonDestructive.push(`Create collection: ${collection.name}`);
@@ -3735,6 +3950,9 @@ function categorizeChangesBySeverity(diff, _config) {
3735
3950
  for (const rule of modification.rulesToUpdate) {
3736
3951
  nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
3737
3952
  }
3953
+ if (modification.viewQueryUpdate) {
3954
+ nonDestructive.push(`Update view query: ${collectionName}`);
3955
+ }
3738
3956
  }
3739
3957
  return { destructive, nonDestructive };
3740
3958
  }
@@ -3747,6 +3965,7 @@ function generateChangeSummary(diff, config) {
3747
3965
  let indexChanges = 0;
3748
3966
  let ruleChanges = 0;
3749
3967
  let permissionChanges = 0;
3968
+ let viewQueryChanges = 0;
3750
3969
  for (const modification of diff.collectionsToModify) {
3751
3970
  fieldsToAdd += modification.fieldsToAdd.length;
3752
3971
  fieldsToRemove += modification.fieldsToRemove.length;
@@ -3754,6 +3973,9 @@ function generateChangeSummary(diff, config) {
3754
3973
  indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
3755
3974
  ruleChanges += modification.rulesToUpdate.length;
3756
3975
  permissionChanges += modification.permissionsToUpdate.length;
3976
+ if (modification.viewQueryUpdate) {
3977
+ viewQueryChanges += 1;
3978
+ }
3757
3979
  }
3758
3980
  return {
3759
3981
  totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
@@ -3766,6 +3988,7 @@ function generateChangeSummary(diff, config) {
3766
3988
  indexChanges,
3767
3989
  ruleChanges,
3768
3990
  permissionChanges,
3991
+ viewQueryChanges,
3769
3992
  destructiveChanges,
3770
3993
  nonDestructiveChanges: nonDestructive
3771
3994
  };
@@ -3795,12 +4018,11 @@ function filterDiff(diff, options) {
3795
4018
  });
3796
4019
  let collectionsToDelete = diff.collectionsToDelete;
3797
4020
  if (skipDestructive) {
3798
- collectionsToDelete = [];
3799
- } else {
3800
- collectionsToDelete = collectionsToDelete.filter((col) => {
3801
- return matchesPattern(col.name, patterns);
3802
- });
4021
+ collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
3803
4022
  }
4023
+ collectionsToDelete = collectionsToDelete.filter((col) => {
4024
+ return matchesPattern(col.name, patterns);
4025
+ });
3804
4026
  const collectionsToModify = diff.collectionsToModify.map((mod) => {
3805
4027
  const collectionMatches = matchesPattern(mod.collection, patterns);
3806
4028
  const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
@@ -3825,6 +4047,7 @@ function filterDiff(diff, options) {
3825
4047
  const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
3826
4048
  const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
3827
4049
  const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
4050
+ const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
3828
4051
  return {
3829
4052
  ...mod,
3830
4053
  fieldsToAdd,
@@ -3833,10 +4056,11 @@ function filterDiff(diff, options) {
3833
4056
  indexesToAdd,
3834
4057
  indexesToRemove,
3835
4058
  rulesToUpdate,
3836
- permissionsToUpdate
4059
+ permissionsToUpdate,
4060
+ viewQueryUpdate
3837
4061
  };
3838
4062
  }).filter((mod) => {
3839
- 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;
3840
4064
  });
3841
4065
  return {
3842
4066
  ...diff,
@@ -3848,7 +4072,7 @@ function filterDiff(diff, options) {
3848
4072
 
3849
4073
  // src/migration/diff/index.ts
3850
4074
  function hasChanges(modification) {
3851
- 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;
3852
4076
  }
3853
4077
  function aggregateChanges(currentSchema, previousSnapshot, config) {
3854
4078
  const collectionIdToName = /* @__PURE__ */ new Map();
@@ -4063,6 +4287,13 @@ function formatValue(value) {
4063
4287
  }
4064
4288
  return String(value);
4065
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
+ }
4066
4297
  function getFieldConstructorName(fieldType) {
4067
4298
  const constructorMap = {
4068
4299
  text: "TextField",
@@ -4423,6 +4654,15 @@ function generateCollectionRules(rules, collectionType = "base") {
4423
4654
  if (!rules) {
4424
4655
  return "";
4425
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
+ }
4426
4666
  const parts = [];
4427
4667
  if (rules.listRule !== void 0) {
4428
4668
  parts.push(`"listRule": ${formatValue(rules.listRule)}`);
@@ -4448,6 +4688,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
4448
4688
  if (!permissions) {
4449
4689
  return "";
4450
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
+ }
4451
4700
  const parts = [];
4452
4701
  if (permissions.listRule !== void 0) {
4453
4702
  parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
@@ -4485,6 +4734,16 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
4485
4734
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4486
4735
  return lines.join("\n");
4487
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
+ }
4488
4747
  function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
4489
4748
  const collectionVar = `collection_${collectionName}_${varSuffix}`;
4490
4749
  const lines = [];
@@ -4515,6 +4774,13 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
4515
4774
  } else if (rulesCode) {
4516
4775
  lines.push(` ${rulesCode},`);
4517
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
+ }
4518
4784
  const systemFieldNames = ["created", "updated", "id"];
4519
4785
  if (collection.type === "auth") {
4520
4786
  systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
@@ -4555,7 +4821,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4555
4821
  const modification = operation.modifications;
4556
4822
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4557
4823
  let operationCount = 0;
4558
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
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
+ }
4559
4839
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
4560
4840
  const field = modification.fieldsToAdd[i];
4561
4841
  operationCount++;
@@ -4656,7 +4936,21 @@ function generateOperationDownMigration(operation, collectionIdMap) {
4656
4936
  const modification = operation.modifications;
4657
4937
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4658
4938
  let operationCount = 0;
4659
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
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
+ }
4660
4954
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4661
4955
  operationCount++;
4662
4956
  const isLast = operationCount === totalOperations;
@@ -4787,6 +5081,19 @@ function generateUpMigration(diff) {
4787
5081
  lines.push(` // Modify existing collections`);
4788
5082
  for (const modification of diff.collectionsToModify) {
4789
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
+ }
4790
5097
  if (modification.fieldsToAdd.length > 0) {
4791
5098
  lines.push(` // Add fields to ${collectionName}`);
4792
5099
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
@@ -4923,6 +5230,19 @@ function generateDownMigration(diff) {
4923
5230
  lines.push(` // Revert modifications`);
4924
5231
  for (const modification of diff.collectionsToModify) {
4925
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
+ }
4926
5246
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4927
5247
  lines.push(` // Revert permissions for ${collectionName}`);
4928
5248
  if (modification.permissionsToUpdate.length >= 2) {
@@ -5274,6 +5594,9 @@ var MigrationGenerator = class {
5274
5594
  function detectCollectionDeletions(diff) {
5275
5595
  const changes = [];
5276
5596
  for (const collection of diff.collectionsToDelete) {
5597
+ if (collection.type === "view") {
5598
+ continue;
5599
+ }
5277
5600
  changes.push({
5278
5601
  type: "collection_deletion" /* COLLECTION_DELETION */,
5279
5602
  description: `Delete collection: ${collection.name}`,