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
package/dist/server.cjs CHANGED
@@ -481,13 +481,16 @@ function withIndexes(schema, indexes) {
481
481
  return schema.describe(JSON.stringify(metadata));
482
482
  }
483
483
  function defineCollection(config) {
484
- const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
484
+ const { collectionName, schema, permissions, indexes, type, viewQuery, ...futureOptions } = config;
485
485
  const metadata = {
486
486
  collectionName
487
487
  };
488
488
  if (type) {
489
489
  metadata.type = type;
490
490
  }
491
+ if (viewQuery !== void 0) {
492
+ metadata.viewQuery = viewQuery;
493
+ }
491
494
  if (permissions) {
492
495
  metadata.permissions = permissions;
493
496
  }
@@ -753,6 +756,88 @@ function GeoPointField() {
753
756
  };
754
757
  return schema.describe(JSON.stringify(metadata));
755
758
  }
759
+ function dedentSql(value) {
760
+ const lines = value.replace(/\r\n/g, "\n").split("\n");
761
+ while (lines.length > 0 && lines[0].trim() === "") {
762
+ lines.shift();
763
+ }
764
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
765
+ lines.pop();
766
+ }
767
+ if (lines.length === 0) {
768
+ return "";
769
+ }
770
+ let minIndent = Infinity;
771
+ for (const line of lines) {
772
+ if (line.trim() === "") continue;
773
+ const indent = line.length - line.trimStart().length;
774
+ if (indent < minIndent) {
775
+ minIndent = indent;
776
+ }
777
+ }
778
+ if (!Number.isFinite(minIndent) || minIndent === 0) {
779
+ return lines.map((line) => line.trimEnd()).join("\n");
780
+ }
781
+ return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
782
+ }
783
+ function sql(strings, ...values) {
784
+ let result = "";
785
+ for (let i = 0; i < strings.length; i++) {
786
+ result += strings[i];
787
+ if (i < values.length) {
788
+ result += String(values[i]);
789
+ }
790
+ }
791
+ return dedentSql(result);
792
+ }
793
+ function stripLeadingComments(query) {
794
+ let remaining = query.trim();
795
+ while (remaining.length > 0) {
796
+ if (remaining.startsWith("--")) {
797
+ const newline = remaining.indexOf("\n");
798
+ remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
799
+ continue;
800
+ }
801
+ if (remaining.startsWith("/*")) {
802
+ const end = remaining.indexOf("*/");
803
+ remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
804
+ continue;
805
+ }
806
+ break;
807
+ }
808
+ return remaining;
809
+ }
810
+ function validateViewQuery(collectionName, viewQuery) {
811
+ if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
812
+ throw new Error(
813
+ `View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
814
+ );
815
+ }
816
+ const body = stripLeadingComments(viewQuery);
817
+ if (!/^(select|with)\b/i.test(body)) {
818
+ throw new Error(
819
+ `View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
820
+ );
821
+ }
822
+ }
823
+ function defineView(config) {
824
+ const { collectionName, schema, viewQuery, permissions } = config;
825
+ validateViewQuery(collectionName, viewQuery);
826
+ return defineCollection({
827
+ collectionName,
828
+ schema,
829
+ type: "view",
830
+ viewQuery,
831
+ // Write rules are always locked for views - PocketBase rejects anything else
832
+ permissions: {
833
+ listRule: permissions?.listRule ?? null,
834
+ viewRule: permissions?.viewRule ?? null,
835
+ createRule: null,
836
+ updateRule: null,
837
+ deleteRule: null
838
+ }
839
+ });
840
+ }
756
841
 
757
842
  // src/utils/permission-templates.ts
758
843
  var PermissionTemplates = {
@@ -2299,13 +2384,26 @@ function extractCollectionTypeFromSchema(zodSchema) {
2299
2384
  }
2300
2385
  try {
2301
2386
  const metadata = JSON.parse(zodSchema.description);
2302
- if (metadata.type === "base" || metadata.type === "auth") {
2387
+ if (metadata.type === "base" || metadata.type === "auth" || metadata.type === "view") {
2303
2388
  return metadata.type;
2304
2389
  }
2305
2390
  } catch {
2306
2391
  }
2307
2392
  return null;
2308
2393
  }
2394
+ function extractViewQueryFromSchema(zodSchema) {
2395
+ if (!zodSchema.description) {
2396
+ return null;
2397
+ }
2398
+ try {
2399
+ const metadata = JSON.parse(zodSchema.description);
2400
+ if (typeof metadata.viewQuery === "string") {
2401
+ return metadata.viewQuery;
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ return null;
2406
+ }
2309
2407
  function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema", "Collection"]) {
2310
2408
  const result = {};
2311
2409
  if (module.default instanceof zod.z.ZodObject) {
@@ -2464,6 +2562,15 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
2464
2562
  const rawFields = extractFieldDefinitions(zodSchema);
2465
2563
  const explicitType = extractCollectionTypeFromSchema(zodSchema);
2466
2564
  const collectionType = explicitType ?? (isAuthCollection(rawFields) ? "auth" : "base");
2565
+ const isView = collectionType === "view";
2566
+ const viewQuery = extractViewQueryFromSchema(zodSchema);
2567
+ if (isView) {
2568
+ validateViewQuery(collectionName, viewQuery);
2569
+ } else if (viewQuery !== null) {
2570
+ console.warn(
2571
+ `[${collectionName}] viewQuery is only used by view collections and will be ignored. Use defineView() or set type: "view" to define a view collection.`
2572
+ );
2573
+ }
2467
2574
  const fields = rawFields.filter((f) => !["created", "updated"].includes(f.name)).map(({ name, zodType }) => buildFieldDefinition(name, zodType));
2468
2575
  if (collectionType === "auth") {
2469
2576
  const authSystemFields = [
@@ -2523,26 +2630,43 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
2523
2630
  }
2524
2631
  }
2525
2632
  const indexes = extractIndexes(zodSchema) || [];
2633
+ if (isView && indexes.length > 0) {
2634
+ throw new Error(
2635
+ `View collection "${collectionName}" cannot declare indexes. PocketBase view collections are backed by a SQL query and do not support indexes.`
2636
+ );
2637
+ }
2526
2638
  const permissionAnalyzer = new PermissionAnalyzer();
2527
2639
  let permissions = void 0;
2528
2640
  const schemaDescription = zodSchema.description;
2529
2641
  const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
2530
2642
  if (extractedPermissions) {
2531
2643
  const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
2532
- const validationResults = permissionAnalyzer.validatePermissions(
2533
- collectionName,
2534
- resolvedPermissions,
2535
- fields,
2536
- collectionType === "auth"
2537
- );
2538
- for (const [ruleType, result] of validationResults) {
2539
- if (!result.valid) {
2540
- console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
2541
- result.errors.forEach((error) => console.error(` - ${error}`));
2644
+ if (isView) {
2645
+ for (const ruleType of ["createRule", "updateRule", "deleteRule", "manageRule"]) {
2646
+ if (resolvedPermissions[ruleType]) {
2647
+ console.warn(
2648
+ `[${collectionName}] ${ruleType} is not supported on view collections and will be set to null.`
2649
+ );
2650
+ }
2651
+ resolvedPermissions[ruleType] = null;
2542
2652
  }
2543
- if (result.warnings.length > 0) {
2544
- console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
2545
- result.warnings.forEach((warning) => console.warn(` - ${warning}`));
2653
+ }
2654
+ if (!isView) {
2655
+ const validationResults = permissionAnalyzer.validatePermissions(
2656
+ collectionName,
2657
+ resolvedPermissions,
2658
+ fields,
2659
+ collectionType === "auth"
2660
+ );
2661
+ for (const [ruleType, result] of validationResults) {
2662
+ if (!result.valid) {
2663
+ console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
2664
+ result.errors.forEach((error) => console.error(` - ${error}`));
2665
+ }
2666
+ if (result.warnings.length > 0) {
2667
+ console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
2668
+ result.warnings.forEach((warning) => console.warn(` - ${warning}`));
2669
+ }
2546
2670
  }
2547
2671
  }
2548
2672
  permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
@@ -2563,6 +2687,9 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
2563
2687
  },
2564
2688
  permissions
2565
2689
  };
2690
+ if (isView) {
2691
+ collectionSchema.viewQuery = viewQuery;
2692
+ }
2566
2693
  return collectionSchema;
2567
2694
  }
2568
2695
  var tsxLoaderRegistered = false;
@@ -2844,8 +2971,9 @@ function convertPocketBaseField(pbField) {
2844
2971
  }
2845
2972
  function convertPocketBaseCollection(pbCollection) {
2846
2973
  const fields = [];
2974
+ const isView = pbCollection.type === "view";
2847
2975
  const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
2848
- if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
2976
+ if (!isView && pbCollection.fields && Array.isArray(pbCollection.fields)) {
2849
2977
  for (const pbField of pbCollection.fields) {
2850
2978
  if (pbField.system || systemFieldNames.includes(pbField.name)) {
2851
2979
  const isAuthSystemField = pbCollection.type === "auth" && ["email", "emailVisibility", "verified", "password", "tokenKey"].includes(pbField.name);
@@ -2865,6 +2993,9 @@ function convertPocketBaseCollection(pbCollection) {
2865
2993
  if (pbCollection.id) {
2866
2994
  schema.id = pbCollection.id;
2867
2995
  }
2996
+ if (typeof pbCollection.viewQuery === "string") {
2997
+ schema.viewQuery = dedentSql(pbCollection.viewQuery);
2998
+ }
2868
2999
  if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
2869
3000
  schema.indexes = pbCollection.indexes;
2870
3001
  }
@@ -2966,6 +3097,24 @@ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
2966
3097
  return [];
2967
3098
  }
2968
3099
  }
3100
+ function skipTemplateLiteral(content, start) {
3101
+ let i = start + 1;
3102
+ while (i < content.length) {
3103
+ const char = content[i];
3104
+ if (char === "\\") {
3105
+ i += 2;
3106
+ continue;
3107
+ }
3108
+ if (char === "`") {
3109
+ return i + 1;
3110
+ }
3111
+ i++;
3112
+ }
3113
+ return i;
3114
+ }
3115
+ function unescapeTemplateLiteral(raw) {
3116
+ return raw.replace(/\\(`|\$\{|\\)/g, "$1");
3117
+ }
2969
3118
  function parseMigrationOperationsFromContent(content) {
2970
3119
  const collectionsToCreate = [];
2971
3120
  const collectionsToDelete = [];
@@ -3054,6 +3203,10 @@ function parseMigrationOperationsFromContent(content) {
3054
3203
  while (i < content.length && bCount > 0) {
3055
3204
  const char = content[i];
3056
3205
  const prev = i > 0 ? content[i - 1] : "";
3206
+ if (!inStr && char === "`") {
3207
+ i = skipTemplateLiteral(content, i);
3208
+ continue;
3209
+ }
3057
3210
  if (!inStr && (char === '"' || char === "'")) {
3058
3211
  inStr = true;
3059
3212
  strChar = char;
@@ -3243,6 +3396,95 @@ function parseMigrationOperationsFromContent(content) {
3243
3396
  }
3244
3397
  }
3245
3398
  }
3399
+ const viewQueryRegex = /(\w+)\.viewQuery\s*=\s*/g;
3400
+ let viewQueryMatch;
3401
+ while ((viewQueryMatch = viewQueryRegex.exec(content)) !== null) {
3402
+ const varInfo = variables.get(viewQueryMatch[1]);
3403
+ if (!varInfo || varInfo.type !== "collection") continue;
3404
+ const valueStart = viewQueryMatch.index + viewQueryMatch[0].length;
3405
+ if (content[valueStart] === "`") {
3406
+ const end = skipTemplateLiteral(content, valueStart);
3407
+ const raw = content.substring(valueStart + 1, end - 1);
3408
+ getUpdate(varInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
3409
+ viewQueryRegex.lastIndex = end;
3410
+ } else {
3411
+ const terminator = content.indexOf(";", valueStart);
3412
+ const valueStr = content.substring(valueStart, terminator === -1 ? content.length : terminator);
3413
+ try {
3414
+ getUpdate(varInfo.name).viewQuery = dedentSql(new Function("app", `return ${valueStr}`)(mockApp));
3415
+ } catch {
3416
+ getUpdate(varInfo.name).viewQuery = dedentSql(valueStr.trim());
3417
+ }
3418
+ }
3419
+ }
3420
+ const unmarshalRegex = /unmarshal\s*\(/g;
3421
+ let unmarshalMatch;
3422
+ while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
3423
+ let i = unmarshalMatch.index + unmarshalMatch[0].length;
3424
+ while (i < content.length && /\s/.test(content[i])) i++;
3425
+ if (content[i] !== "{") continue;
3426
+ const objStart = i;
3427
+ let braceCount = 1;
3428
+ let inStr = false;
3429
+ let strChar = null;
3430
+ i++;
3431
+ while (i < content.length && braceCount > 0) {
3432
+ const ch = content[i];
3433
+ const prev = i > 0 ? content[i - 1] : "";
3434
+ if (!inStr && ch === "`") {
3435
+ i = skipTemplateLiteral(content, i);
3436
+ continue;
3437
+ }
3438
+ if (!inStr && (ch === '"' || ch === "'")) {
3439
+ inStr = true;
3440
+ strChar = ch;
3441
+ } else if (inStr && ch === strChar && prev !== "\\") {
3442
+ inStr = false;
3443
+ strChar = null;
3444
+ }
3445
+ if (!inStr) {
3446
+ if (ch === "{") braceCount++;
3447
+ if (ch === "}") braceCount--;
3448
+ }
3449
+ i++;
3450
+ }
3451
+ if (braceCount !== 0) continue;
3452
+ const objStr = content.substring(objStart, i);
3453
+ while (i < content.length && /[\s,]/.test(content[i])) i++;
3454
+ const varStart = i;
3455
+ while (i < content.length && /\w/.test(content[i])) i++;
3456
+ const colVarName = content.substring(varStart, i);
3457
+ const colInfo = variables.get(colVarName);
3458
+ if (!colInfo || colInfo.type !== "collection") continue;
3459
+ const keyValRegex = /"(\w+Rule)"\s*:\s*([^,}\n]+)/g;
3460
+ let kvMatch;
3461
+ while ((kvMatch = keyValRegex.exec(objStr)) !== null) {
3462
+ const ruleKey = kvMatch[1];
3463
+ const valStr = kvMatch[2].trim();
3464
+ try {
3465
+ const value = new Function("app", `return ${valStr}`)(mockApp);
3466
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = value;
3467
+ } catch {
3468
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
3469
+ }
3470
+ }
3471
+ const viewQueryKey = objStr.match(/"viewQuery"\s*:\s*/);
3472
+ if (viewQueryKey) {
3473
+ const valueStart = viewQueryKey.index + viewQueryKey[0].length;
3474
+ if (objStr[valueStart] === "`") {
3475
+ const end = skipTemplateLiteral(objStr, valueStart);
3476
+ const raw = objStr.substring(valueStart + 1, end - 1);
3477
+ getUpdate(colInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
3478
+ } else {
3479
+ const valStr = objStr.substring(valueStart).replace(/[,}\s]+$/, "");
3480
+ try {
3481
+ getUpdate(colInfo.name).viewQuery = dedentSql(new Function("app", `return ${valStr}`)(mockApp));
3482
+ } catch {
3483
+ getUpdate(colInfo.name).viewQuery = dedentSql(valStr);
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3246
3488
  const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
3247
3489
  let match;
3248
3490
  while ((match = idxPushRegex.exec(content)) !== null) {
@@ -3372,6 +3614,10 @@ function parseMigrationOperations(migrationContent) {
3372
3614
  while (i < migrationContent.length && braceCount > 0) {
3373
3615
  const char = migrationContent[i];
3374
3616
  const prevChar = i > 0 ? migrationContent[i - 1] : "";
3617
+ if (!inString && char === "`") {
3618
+ i = skipTemplateLiteral(migrationContent, i);
3619
+ continue;
3620
+ }
3375
3621
  if (!inString && (char === '"' || char === "'")) {
3376
3622
  inString = true;
3377
3623
  stringChar = char;
@@ -3707,6 +3953,9 @@ function applyMigrationOperations(snapshot, operations) {
3707
3953
  collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
3708
3954
  }
3709
3955
  }
3956
+ if (update.viewQuery !== void 0) {
3957
+ collection.viewQuery = update.viewQuery;
3958
+ }
3710
3959
  if (Object.keys(update.rulesToUpdate).length > 0) {
3711
3960
  if (!collection.rules) collection.rules = {};
3712
3961
  if (!collection.permissions) collection.permissions = {};
@@ -4326,7 +4575,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
4326
4575
  fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
4327
4576
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
4328
4577
  }
4578
+ function normalizeSql(query) {
4579
+ if (!query) {
4580
+ return "";
4581
+ }
4582
+ return query.split("\n").map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim();
4583
+ }
4584
+ function compareViewQuery(currentCollection, previousCollection) {
4585
+ const newValue = currentCollection.viewQuery;
4586
+ if (typeof newValue !== "string") {
4587
+ return void 0;
4588
+ }
4589
+ if (normalizeSql(newValue) === normalizeSql(previousCollection.viewQuery)) {
4590
+ return void 0;
4591
+ }
4592
+ return {
4593
+ oldValue: previousCollection.viewQuery ?? null,
4594
+ newValue
4595
+ };
4596
+ }
4329
4597
  function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
4598
+ const isView = currentCollection.type === "view" || previousCollection.type === "view";
4599
+ if (isView) {
4600
+ return {
4601
+ collection: currentCollection.name,
4602
+ fieldsToAdd: [],
4603
+ fieldsToRemove: [],
4604
+ fieldsToModify: [],
4605
+ indexesToAdd: [],
4606
+ indexesToRemove: [],
4607
+ rulesToUpdate: compareRules(
4608
+ currentCollection.rules,
4609
+ previousCollection.rules,
4610
+ currentCollection.permissions,
4611
+ previousCollection.permissions
4612
+ ),
4613
+ permissionsToUpdate: comparePermissions(currentCollection.permissions, previousCollection.permissions),
4614
+ viewQueryUpdate: compareViewQuery(currentCollection, previousCollection)
4615
+ };
4616
+ }
4330
4617
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
4331
4618
  currentCollection,
4332
4619
  previousCollection,
@@ -4358,6 +4645,9 @@ function detectDestructiveChanges(diff, config) {
4358
4645
  const destructiveChanges = [];
4359
4646
  const mergedConfig = mergeConfig3(config);
4360
4647
  for (const collection of diff.collectionsToDelete) {
4648
+ if (collection.type === "view") {
4649
+ continue;
4650
+ }
4361
4651
  destructiveChanges.push({
4362
4652
  type: "collection_delete",
4363
4653
  severity: "high",
@@ -4445,7 +4735,11 @@ function categorizeChangesBySeverity(diff, _config) {
4445
4735
  const destructive = [];
4446
4736
  const nonDestructive = [];
4447
4737
  for (const collection of diff.collectionsToDelete) {
4448
- destructive.push(`Delete collection: ${collection.name}`);
4738
+ if (collection.type === "view") {
4739
+ nonDestructive.push(`Delete view collection: ${collection.name}`);
4740
+ } else {
4741
+ destructive.push(`Delete collection: ${collection.name}`);
4742
+ }
4449
4743
  }
4450
4744
  for (const collection of diff.collectionsToCreate) {
4451
4745
  nonDestructive.push(`Create collection: ${collection.name}`);
@@ -4480,6 +4774,9 @@ function categorizeChangesBySeverity(diff, _config) {
4480
4774
  for (const rule of modification.rulesToUpdate) {
4481
4775
  nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
4482
4776
  }
4777
+ if (modification.viewQueryUpdate) {
4778
+ nonDestructive.push(`Update view query: ${collectionName}`);
4779
+ }
4483
4780
  }
4484
4781
  return { destructive, nonDestructive };
4485
4782
  }
@@ -4492,6 +4789,7 @@ function generateChangeSummary(diff, config) {
4492
4789
  let indexChanges = 0;
4493
4790
  let ruleChanges = 0;
4494
4791
  let permissionChanges = 0;
4792
+ let viewQueryChanges = 0;
4495
4793
  for (const modification of diff.collectionsToModify) {
4496
4794
  fieldsToAdd += modification.fieldsToAdd.length;
4497
4795
  fieldsToRemove += modification.fieldsToRemove.length;
@@ -4499,6 +4797,9 @@ function generateChangeSummary(diff, config) {
4499
4797
  indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
4500
4798
  ruleChanges += modification.rulesToUpdate.length;
4501
4799
  permissionChanges += modification.permissionsToUpdate.length;
4800
+ if (modification.viewQueryUpdate) {
4801
+ viewQueryChanges += 1;
4802
+ }
4502
4803
  }
4503
4804
  return {
4504
4805
  totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
@@ -4511,6 +4812,7 @@ function generateChangeSummary(diff, config) {
4511
4812
  indexChanges,
4512
4813
  ruleChanges,
4513
4814
  permissionChanges,
4815
+ viewQueryChanges,
4514
4816
  destructiveChanges,
4515
4817
  nonDestructiveChanges: nonDestructive
4516
4818
  };
@@ -4540,12 +4842,11 @@ function filterDiff(diff, options) {
4540
4842
  });
4541
4843
  let collectionsToDelete = diff.collectionsToDelete;
4542
4844
  if (skipDestructive) {
4543
- collectionsToDelete = [];
4544
- } else {
4545
- collectionsToDelete = collectionsToDelete.filter((col) => {
4546
- return matchesPattern(col.name, patterns);
4547
- });
4845
+ collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
4548
4846
  }
4847
+ collectionsToDelete = collectionsToDelete.filter((col) => {
4848
+ return matchesPattern(col.name, patterns);
4849
+ });
4549
4850
  const collectionsToModify = diff.collectionsToModify.map((mod) => {
4550
4851
  const collectionMatches = matchesPattern(mod.collection, patterns);
4551
4852
  const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
@@ -4570,6 +4871,7 @@ function filterDiff(diff, options) {
4570
4871
  const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
4571
4872
  const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
4572
4873
  const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
4874
+ const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
4573
4875
  return {
4574
4876
  ...mod,
4575
4877
  fieldsToAdd,
@@ -4578,10 +4880,11 @@ function filterDiff(diff, options) {
4578
4880
  indexesToAdd,
4579
4881
  indexesToRemove,
4580
4882
  rulesToUpdate,
4581
- permissionsToUpdate
4883
+ permissionsToUpdate,
4884
+ viewQueryUpdate
4582
4885
  };
4583
4886
  }).filter((mod) => {
4584
- 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;
4887
+ 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;
4585
4888
  });
4586
4889
  return {
4587
4890
  ...diff,
@@ -4593,7 +4896,7 @@ function filterDiff(diff, options) {
4593
4896
 
4594
4897
  // src/migration/diff/index.ts
4595
4898
  function hasChanges(modification) {
4596
- 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;
4899
+ 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;
4597
4900
  }
4598
4901
  function aggregateChanges(currentSchema, previousSnapshot, config) {
4599
4902
  const collectionIdToName = /* @__PURE__ */ new Map();
@@ -4808,6 +5111,13 @@ function formatValue(value) {
4808
5111
  }
4809
5112
  return String(value);
4810
5113
  }
5114
+ function formatSqlTemplate(query, indent = " ") {
5115
+ const escaped = query.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
5116
+ const body = escaped.split("\n").map((line) => line.trim() === "" ? "" : `${indent}${line}`).join("\n");
5117
+ return `\`
5118
+ ${body}
5119
+ ${indent.slice(0, -2)}\``;
5120
+ }
4811
5121
  function getFieldConstructorName(fieldType) {
4812
5122
  const constructorMap = {
4813
5123
  text: "TextField",
@@ -4846,6 +5156,36 @@ function getSystemFields() {
4846
5156
  }
4847
5157
  ];
4848
5158
  }
5159
+ function getSystemTimestampFields() {
5160
+ return [
5161
+ {
5162
+ name: "created",
5163
+ id: "autodate2990389176",
5164
+ type: "autodate",
5165
+ required: false,
5166
+ options: {
5167
+ hidden: false,
5168
+ onCreate: true,
5169
+ onUpdate: false,
5170
+ presentable: false,
5171
+ system: true
5172
+ }
5173
+ },
5174
+ {
5175
+ name: "updated",
5176
+ id: "autodate3332085495",
5177
+ type: "autodate",
5178
+ required: false,
5179
+ options: {
5180
+ hidden: false,
5181
+ onCreate: true,
5182
+ onUpdate: true,
5183
+ presentable: false,
5184
+ system: true
5185
+ }
5186
+ }
5187
+ ];
5188
+ }
4849
5189
  function getAuthSystemFields() {
4850
5190
  return [
4851
5191
  {
@@ -5138,6 +5478,15 @@ function generateCollectionRules(rules, collectionType = "base") {
5138
5478
  if (!rules) {
5139
5479
  return "";
5140
5480
  }
5481
+ if (collectionType === "view") {
5482
+ return [
5483
+ `"listRule": ${formatValue(rules.listRule ?? null)}`,
5484
+ `"viewRule": ${formatValue(rules.viewRule ?? null)}`,
5485
+ `"createRule": null`,
5486
+ `"updateRule": null`,
5487
+ `"deleteRule": null`
5488
+ ].join(",\n ");
5489
+ }
5141
5490
  const parts = [];
5142
5491
  if (rules.listRule !== void 0) {
5143
5492
  parts.push(`"listRule": ${formatValue(rules.listRule)}`);
@@ -5163,6 +5512,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
5163
5512
  if (!permissions) {
5164
5513
  return "";
5165
5514
  }
5515
+ if (collectionType === "view") {
5516
+ return [
5517
+ `"listRule": ${formatValue(permissions.listRule ?? null)}`,
5518
+ `"viewRule": ${formatValue(permissions.viewRule ?? null)}`,
5519
+ `"createRule": null`,
5520
+ `"updateRule": null`,
5521
+ `"deleteRule": null`
5522
+ ].join(",\n ");
5523
+ }
5166
5524
  const parts = [];
5167
5525
  if (permissions.listRule !== void 0) {
5168
5526
  parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
@@ -5200,6 +5558,28 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
5200
5558
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
5201
5559
  return lines.join("\n");
5202
5560
  }
5561
+ function generateViewQueryUpdate(collectionName, viewQuery, varName, isLast = false, collectionIdMap) {
5562
+ const lines = [];
5563
+ const collectionVar = varName || `collection_${collectionName}_viewQuery`;
5564
+ lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
5565
+ lines.push(` unmarshal({`);
5566
+ lines.push(` "viewQuery": ${formatSqlTemplate(viewQuery, " ")},`);
5567
+ lines.push(` }, ${collectionVar})`);
5568
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
5569
+ return lines.join("\n");
5570
+ }
5571
+ function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
5572
+ const collectionVar = `collection_${collectionName}_${varSuffix}`;
5573
+ const lines = [];
5574
+ lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
5575
+ lines.push(` unmarshal({`);
5576
+ for (const entry of entries) {
5577
+ lines.push(` "${entry.ruleType}": ${formatValue(entry.value)},`);
5578
+ }
5579
+ lines.push(` }, ${collectionVar})`);
5580
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
5581
+ return lines.join("\n");
5582
+ }
5203
5583
 
5204
5584
  // src/migration/generator/collections.ts
5205
5585
  function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
@@ -5218,16 +5598,24 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
5218
5598
  } else if (rulesCode) {
5219
5599
  lines.push(` ${rulesCode},`);
5220
5600
  }
5601
+ if (collection.type === "view") {
5602
+ lines.push(` "viewQuery": ${formatSqlTemplate(collection.viewQuery ?? "")},`);
5603
+ lines.push(` });`);
5604
+ lines.push(``);
5605
+ lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
5606
+ return lines.join("\n");
5607
+ }
5221
5608
  const systemFieldNames = ["created", "updated", "id"];
5222
5609
  if (collection.type === "auth") {
5223
5610
  systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
5224
5611
  }
5225
5612
  const userFields = collection.fields.filter((f) => !systemFieldNames.includes(f.name));
5226
- const allFields = [...getSystemFields().filter((f) => f.name === "id")];
5613
+ const allFields = [...getSystemFields()];
5227
5614
  if (collection.type === "auth") {
5228
5615
  allFields.push(...getAuthSystemFields());
5229
5616
  }
5230
5617
  allFields.push(...userFields);
5618
+ allFields.push(...getSystemTimestampFields());
5231
5619
  lines.push(` "fields": ${generateFieldsArray(allFields, collectionIdMap)},`);
5232
5620
  let allIndexes = [...collection.indexes || []];
5233
5621
  if (collection.type === "auth") {
@@ -5257,7 +5645,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
5257
5645
  const modification = operation.modifications;
5258
5646
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
5259
5647
  let operationCount = 0;
5260
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
5648
+ 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);
5649
+ if (modification.viewQueryUpdate) {
5650
+ operationCount++;
5651
+ const isLast = operationCount === totalOperations;
5652
+ lines.push(
5653
+ generateViewQueryUpdate(
5654
+ collectionName,
5655
+ modification.viewQueryUpdate.newValue,
5656
+ void 0,
5657
+ isLast,
5658
+ collectionIdMap
5659
+ )
5660
+ );
5661
+ if (!isLast) lines.push("");
5662
+ }
5261
5663
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
5262
5664
  const field = modification.fieldsToAdd[i];
5263
5665
  operationCount++;
@@ -5297,23 +5699,29 @@ function generateOperationUpMigration(operation, collectionIdMap) {
5297
5699
  if (!isLast) lines.push("");
5298
5700
  }
5299
5701
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5300
- for (const permission of modification.permissionsToUpdate) {
5301
- operationCount++;
5702
+ operationCount++;
5703
+ const isLast = operationCount === totalOperations;
5704
+ if (modification.permissionsToUpdate.length >= 2) {
5705
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.newValue }));
5706
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
5707
+ } else {
5708
+ const permission = modification.permissionsToUpdate[0];
5302
5709
  const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
5303
- const isLast = operationCount === totalOperations;
5304
- lines.push(
5305
- generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap)
5306
- );
5307
- if (!isLast) lines.push("");
5710
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap));
5308
5711
  }
5712
+ if (!isLast) lines.push("");
5309
5713
  } else if (modification.rulesToUpdate.length > 0) {
5310
- for (const rule of modification.rulesToUpdate) {
5311
- operationCount++;
5714
+ operationCount++;
5715
+ const isLast = operationCount === totalOperations;
5716
+ if (modification.rulesToUpdate.length >= 2) {
5717
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.newValue }));
5718
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
5719
+ } else {
5720
+ const rule = modification.rulesToUpdate[0];
5312
5721
  const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
5313
- const isLast = operationCount === totalOperations;
5314
5722
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, isLast, collectionIdMap));
5315
- if (!isLast) lines.push("");
5316
5723
  }
5724
+ if (!isLast) lines.push("");
5317
5725
  }
5318
5726
  } else if (operation.type === "delete") {
5319
5727
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection.name;
@@ -5352,25 +5760,45 @@ function generateOperationDownMigration(operation, collectionIdMap) {
5352
5760
  const modification = operation.modifications;
5353
5761
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
5354
5762
  let operationCount = 0;
5355
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
5763
+ 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);
5764
+ if (modification.viewQueryUpdate) {
5765
+ operationCount++;
5766
+ const isLast = operationCount === totalOperations;
5767
+ lines.push(
5768
+ generateViewQueryUpdate(
5769
+ collectionName,
5770
+ modification.viewQueryUpdate.oldValue ?? "",
5771
+ `collection_${collectionName}_revert_viewQuery`,
5772
+ isLast,
5773
+ collectionIdMap
5774
+ )
5775
+ );
5776
+ if (!isLast) lines.push("");
5777
+ }
5356
5778
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5357
- for (const permission of modification.permissionsToUpdate) {
5358
- operationCount++;
5779
+ operationCount++;
5780
+ const isLast = operationCount === totalOperations;
5781
+ if (modification.permissionsToUpdate.length >= 2) {
5782
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.oldValue }));
5783
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
5784
+ } else {
5785
+ const permission = modification.permissionsToUpdate[0];
5359
5786
  const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
5360
- const isLast = operationCount === totalOperations;
5361
- lines.push(
5362
- generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap)
5363
- );
5364
- if (!isLast) lines.push("");
5787
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap));
5365
5788
  }
5789
+ if (!isLast) lines.push("");
5366
5790
  } else if (modification.rulesToUpdate.length > 0) {
5367
- for (const rule of modification.rulesToUpdate) {
5368
- operationCount++;
5791
+ operationCount++;
5792
+ const isLast = operationCount === totalOperations;
5793
+ if (modification.rulesToUpdate.length >= 2) {
5794
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.oldValue }));
5795
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
5796
+ } else {
5797
+ const rule = modification.rulesToUpdate[0];
5369
5798
  const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
5370
- const isLast = operationCount === totalOperations;
5371
5799
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, isLast, collectionIdMap));
5372
- if (!isLast) lines.push("");
5373
5800
  }
5801
+ if (!isLast) lines.push("");
5374
5802
  }
5375
5803
  for (let i = 0; i < modification.indexesToRemove.length; i++) {
5376
5804
  operationCount++;
@@ -5477,6 +5905,19 @@ function generateUpMigration(diff) {
5477
5905
  lines.push(` // Modify existing collections`);
5478
5906
  for (const modification of diff.collectionsToModify) {
5479
5907
  const collectionName = modification.collection;
5908
+ if (modification.viewQueryUpdate) {
5909
+ lines.push(` // Update the view query of ${collectionName}`);
5910
+ lines.push(
5911
+ generateViewQueryUpdate(
5912
+ collectionName,
5913
+ modification.viewQueryUpdate.newValue,
5914
+ void 0,
5915
+ false,
5916
+ collectionIdMap
5917
+ )
5918
+ );
5919
+ lines.push(``);
5920
+ }
5480
5921
  if (modification.fieldsToAdd.length > 0) {
5481
5922
  lines.push(` // Add fields to ${collectionName}`);
5482
5923
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
@@ -5522,20 +5963,26 @@ function generateUpMigration(diff) {
5522
5963
  }
5523
5964
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5524
5965
  lines.push(` // Update permissions for ${collectionName}`);
5525
- for (const permission of modification.permissionsToUpdate) {
5966
+ if (modification.permissionsToUpdate.length >= 2) {
5967
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.newValue }));
5968
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", false, collectionIdMap));
5969
+ } else {
5970
+ const permission = modification.permissionsToUpdate[0];
5526
5971
  const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
5527
- lines.push(
5528
- generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, false, collectionIdMap)
5529
- );
5530
- lines.push(``);
5972
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, false, collectionIdMap));
5531
5973
  }
5974
+ lines.push(``);
5532
5975
  } else if (modification.rulesToUpdate.length > 0) {
5533
5976
  lines.push(` // Update rules for ${collectionName}`);
5534
- for (const rule of modification.rulesToUpdate) {
5977
+ if (modification.rulesToUpdate.length >= 2) {
5978
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.newValue }));
5979
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", false, collectionIdMap));
5980
+ } else {
5981
+ const rule = modification.rulesToUpdate[0];
5535
5982
  const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
5536
5983
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, false, collectionIdMap));
5537
- lines.push(``);
5538
5984
  }
5985
+ lines.push(``);
5539
5986
  }
5540
5987
  }
5541
5988
  }
@@ -5607,22 +6054,41 @@ function generateDownMigration(diff) {
5607
6054
  lines.push(` // Revert modifications`);
5608
6055
  for (const modification of diff.collectionsToModify) {
5609
6056
  const collectionName = modification.collection;
6057
+ if (modification.viewQueryUpdate) {
6058
+ lines.push(` // Restore the view query of ${collectionName}`);
6059
+ lines.push(
6060
+ generateViewQueryUpdate(
6061
+ collectionName,
6062
+ modification.viewQueryUpdate.oldValue ?? "",
6063
+ `collection_${collectionName}_revert_viewQuery`,
6064
+ false,
6065
+ collectionIdMap
6066
+ )
6067
+ );
6068
+ lines.push(``);
6069
+ }
5610
6070
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5611
6071
  lines.push(` // Revert permissions for ${collectionName}`);
5612
- for (const permission of modification.permissionsToUpdate) {
6072
+ if (modification.permissionsToUpdate.length >= 2) {
6073
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.oldValue }));
6074
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", false, collectionIdMap));
6075
+ } else {
6076
+ const permission = modification.permissionsToUpdate[0];
5613
6077
  const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
5614
- lines.push(
5615
- generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, false, collectionIdMap)
5616
- );
5617
- lines.push(``);
6078
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, false, collectionIdMap));
5618
6079
  }
6080
+ lines.push(``);
5619
6081
  } else if (modification.rulesToUpdate.length > 0) {
5620
6082
  lines.push(` // Revert rules for ${collectionName}`);
5621
- for (const rule of modification.rulesToUpdate) {
6083
+ if (modification.rulesToUpdate.length >= 2) {
6084
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.oldValue }));
6085
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", false, collectionIdMap));
6086
+ } else {
6087
+ const rule = modification.rulesToUpdate[0];
5622
6088
  const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
5623
6089
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, false, collectionIdMap));
5624
- lines.push(``);
5625
6090
  }
6091
+ lines.push(``);
5626
6092
  }
5627
6093
  if (modification.indexesToRemove.length > 0) {
5628
6094
  lines.push(` // Restore indexes to ${collectionName}`);
@@ -5952,6 +6418,9 @@ var MigrationGenerator = class {
5952
6418
  function detectCollectionDeletions(diff) {
5953
6419
  const changes = [];
5954
6420
  for (const collection of diff.collectionsToDelete) {
6421
+ if (collection.type === "view") {
6422
+ continue;
6423
+ }
5955
6424
  changes.push({
5956
6425
  type: "collection_deletion" /* COLLECTION_DELETION */,
5957
6426
  description: `Delete collection: ${collection.name}`,
@@ -6366,7 +6835,11 @@ function formatChangeSummary(diff) {
6366
6835
  lines.push(chalk__default.default.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
6367
6836
  for (const collection of diff.collectionsToCreate) {
6368
6837
  lines.push(chalk__default.default.green(` + ${collection.name} (${collection.type})`));
6369
- lines.push(chalk__default.default.gray(` ${collection.fields.length} field(s)`));
6838
+ if (collection.type === "view") {
6839
+ lines.push(chalk__default.default.gray(` fields derived from the view query`));
6840
+ } else {
6841
+ lines.push(chalk__default.default.gray(` ${collection.fields.length} field(s)`));
6842
+ }
6370
6843
  }
6371
6844
  lines.push("");
6372
6845
  }
@@ -6411,6 +6884,9 @@ function formatChangeSummary(diff) {
6411
6884
  if (modification.rulesToUpdate.length > 0) {
6412
6885
  lines.push(chalk__default.default.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
6413
6886
  }
6887
+ if (modification.viewQueryUpdate) {
6888
+ lines.push(chalk__default.default.yellow(` ~ view query to update`));
6889
+ }
6414
6890
  lines.push("");
6415
6891
  }
6416
6892
  }
@@ -6884,7 +7360,9 @@ exports.convertPocketBaseMigration = convertPocketBaseMigration;
6884
7360
  exports.convertZodSchemaToCollectionSchema = convertZodSchemaToCollectionSchema;
6885
7361
  exports.createMigrationFileStructure = createMigrationFileStructure;
6886
7362
  exports.createPermissions = createPermissions;
7363
+ exports.dedentSql = dedentSql;
6887
7364
  exports.defineCollection = defineCollection;
7365
+ exports.defineView = defineView;
6888
7366
  exports.detectDestructiveChanges = detectDestructiveChanges;
6889
7367
  exports.detectDestructiveChangesValidation = detectDestructiveChanges2;
6890
7368
  exports.detectFieldChanges = detectFieldChanges;
@@ -6979,12 +7457,14 @@ exports.saveSnapshot = saveSnapshot;
6979
7457
  exports.selectSchemaForCollection = selectSchemaForCollection;
6980
7458
  exports.singularize = singularize;
6981
7459
  exports.snapshotExists = snapshotExists;
7460
+ exports.sql = sql;
6982
7461
  exports.summarizeDestructiveChanges = summarizeDestructiveChanges;
6983
7462
  exports.toCollectionName = toCollectionName;
6984
7463
  exports.unwrapZodType = unwrapZodType;
6985
7464
  exports.validatePermissionConfig = validatePermissionConfig;
6986
7465
  exports.validateRuleExpression = validateRuleExpression;
6987
7466
  exports.validateSnapshot = validateSnapshot;
7467
+ exports.validateViewQuery = validateViewQuery;
6988
7468
  exports.withIndexes = withIndexes;
6989
7469
  exports.withPermissions = withPermissions;
6990
7470
  exports.withProgress = withProgress;