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
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,27 @@ 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
+ }
3246
3420
  const unmarshalRegex = /unmarshal\s*\(/g;
3247
3421
  let unmarshalMatch;
3248
3422
  while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
@@ -3257,6 +3431,10 @@ function parseMigrationOperationsFromContent(content) {
3257
3431
  while (i < content.length && braceCount > 0) {
3258
3432
  const ch = content[i];
3259
3433
  const prev = i > 0 ? content[i - 1] : "";
3434
+ if (!inStr && ch === "`") {
3435
+ i = skipTemplateLiteral(content, i);
3436
+ continue;
3437
+ }
3260
3438
  if (!inStr && (ch === '"' || ch === "'")) {
3261
3439
  inStr = true;
3262
3440
  strChar = ch;
@@ -3290,6 +3468,22 @@ function parseMigrationOperationsFromContent(content) {
3290
3468
  getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
3291
3469
  }
3292
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
+ }
3293
3487
  }
3294
3488
  const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
3295
3489
  let match;
@@ -3420,6 +3614,10 @@ function parseMigrationOperations(migrationContent) {
3420
3614
  while (i < migrationContent.length && braceCount > 0) {
3421
3615
  const char = migrationContent[i];
3422
3616
  const prevChar = i > 0 ? migrationContent[i - 1] : "";
3617
+ if (!inString && char === "`") {
3618
+ i = skipTemplateLiteral(migrationContent, i);
3619
+ continue;
3620
+ }
3423
3621
  if (!inString && (char === '"' || char === "'")) {
3424
3622
  inString = true;
3425
3623
  stringChar = char;
@@ -3755,6 +3953,9 @@ function applyMigrationOperations(snapshot, operations) {
3755
3953
  collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
3756
3954
  }
3757
3955
  }
3956
+ if (update.viewQuery !== void 0) {
3957
+ collection.viewQuery = update.viewQuery;
3958
+ }
3758
3959
  if (Object.keys(update.rulesToUpdate).length > 0) {
3759
3960
  if (!collection.rules) collection.rules = {};
3760
3961
  if (!collection.permissions) collection.permissions = {};
@@ -4374,7 +4575,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
4374
4575
  fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
4375
4576
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
4376
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
+ }
4377
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
+ }
4378
4617
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
4379
4618
  currentCollection,
4380
4619
  previousCollection,
@@ -4406,6 +4645,9 @@ function detectDestructiveChanges(diff, config) {
4406
4645
  const destructiveChanges = [];
4407
4646
  const mergedConfig = mergeConfig3(config);
4408
4647
  for (const collection of diff.collectionsToDelete) {
4648
+ if (collection.type === "view") {
4649
+ continue;
4650
+ }
4409
4651
  destructiveChanges.push({
4410
4652
  type: "collection_delete",
4411
4653
  severity: "high",
@@ -4493,7 +4735,11 @@ function categorizeChangesBySeverity(diff, _config) {
4493
4735
  const destructive = [];
4494
4736
  const nonDestructive = [];
4495
4737
  for (const collection of diff.collectionsToDelete) {
4496
- 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
+ }
4497
4743
  }
4498
4744
  for (const collection of diff.collectionsToCreate) {
4499
4745
  nonDestructive.push(`Create collection: ${collection.name}`);
@@ -4528,6 +4774,9 @@ function categorizeChangesBySeverity(diff, _config) {
4528
4774
  for (const rule of modification.rulesToUpdate) {
4529
4775
  nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
4530
4776
  }
4777
+ if (modification.viewQueryUpdate) {
4778
+ nonDestructive.push(`Update view query: ${collectionName}`);
4779
+ }
4531
4780
  }
4532
4781
  return { destructive, nonDestructive };
4533
4782
  }
@@ -4540,6 +4789,7 @@ function generateChangeSummary(diff, config) {
4540
4789
  let indexChanges = 0;
4541
4790
  let ruleChanges = 0;
4542
4791
  let permissionChanges = 0;
4792
+ let viewQueryChanges = 0;
4543
4793
  for (const modification of diff.collectionsToModify) {
4544
4794
  fieldsToAdd += modification.fieldsToAdd.length;
4545
4795
  fieldsToRemove += modification.fieldsToRemove.length;
@@ -4547,6 +4797,9 @@ function generateChangeSummary(diff, config) {
4547
4797
  indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
4548
4798
  ruleChanges += modification.rulesToUpdate.length;
4549
4799
  permissionChanges += modification.permissionsToUpdate.length;
4800
+ if (modification.viewQueryUpdate) {
4801
+ viewQueryChanges += 1;
4802
+ }
4550
4803
  }
4551
4804
  return {
4552
4805
  totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
@@ -4559,6 +4812,7 @@ function generateChangeSummary(diff, config) {
4559
4812
  indexChanges,
4560
4813
  ruleChanges,
4561
4814
  permissionChanges,
4815
+ viewQueryChanges,
4562
4816
  destructiveChanges,
4563
4817
  nonDestructiveChanges: nonDestructive
4564
4818
  };
@@ -4588,12 +4842,11 @@ function filterDiff(diff, options) {
4588
4842
  });
4589
4843
  let collectionsToDelete = diff.collectionsToDelete;
4590
4844
  if (skipDestructive) {
4591
- collectionsToDelete = [];
4592
- } else {
4593
- collectionsToDelete = collectionsToDelete.filter((col) => {
4594
- return matchesPattern(col.name, patterns);
4595
- });
4845
+ collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
4596
4846
  }
4847
+ collectionsToDelete = collectionsToDelete.filter((col) => {
4848
+ return matchesPattern(col.name, patterns);
4849
+ });
4597
4850
  const collectionsToModify = diff.collectionsToModify.map((mod) => {
4598
4851
  const collectionMatches = matchesPattern(mod.collection, patterns);
4599
4852
  const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
@@ -4618,6 +4871,7 @@ function filterDiff(diff, options) {
4618
4871
  const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
4619
4872
  const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
4620
4873
  const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
4874
+ const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
4621
4875
  return {
4622
4876
  ...mod,
4623
4877
  fieldsToAdd,
@@ -4626,10 +4880,11 @@ function filterDiff(diff, options) {
4626
4880
  indexesToAdd,
4627
4881
  indexesToRemove,
4628
4882
  rulesToUpdate,
4629
- permissionsToUpdate
4883
+ permissionsToUpdate,
4884
+ viewQueryUpdate
4630
4885
  };
4631
4886
  }).filter((mod) => {
4632
- 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;
4633
4888
  });
4634
4889
  return {
4635
4890
  ...diff,
@@ -4641,7 +4896,7 @@ function filterDiff(diff, options) {
4641
4896
 
4642
4897
  // src/migration/diff/index.ts
4643
4898
  function hasChanges(modification) {
4644
- 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;
4645
4900
  }
4646
4901
  function aggregateChanges(currentSchema, previousSnapshot, config) {
4647
4902
  const collectionIdToName = /* @__PURE__ */ new Map();
@@ -4856,6 +5111,13 @@ function formatValue(value) {
4856
5111
  }
4857
5112
  return String(value);
4858
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
+ }
4859
5121
  function getFieldConstructorName(fieldType) {
4860
5122
  const constructorMap = {
4861
5123
  text: "TextField",
@@ -5216,6 +5478,15 @@ function generateCollectionRules(rules, collectionType = "base") {
5216
5478
  if (!rules) {
5217
5479
  return "";
5218
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
+ }
5219
5490
  const parts = [];
5220
5491
  if (rules.listRule !== void 0) {
5221
5492
  parts.push(`"listRule": ${formatValue(rules.listRule)}`);
@@ -5241,6 +5512,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
5241
5512
  if (!permissions) {
5242
5513
  return "";
5243
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
+ }
5244
5524
  const parts = [];
5245
5525
  if (permissions.listRule !== void 0) {
5246
5526
  parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
@@ -5278,6 +5558,16 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
5278
5558
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
5279
5559
  return lines.join("\n");
5280
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
+ }
5281
5571
  function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
5282
5572
  const collectionVar = `collection_${collectionName}_${varSuffix}`;
5283
5573
  const lines = [];
@@ -5308,6 +5598,13 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
5308
5598
  } else if (rulesCode) {
5309
5599
  lines.push(` ${rulesCode},`);
5310
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
+ }
5311
5608
  const systemFieldNames = ["created", "updated", "id"];
5312
5609
  if (collection.type === "auth") {
5313
5610
  systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
@@ -5348,7 +5645,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
5348
5645
  const modification = operation.modifications;
5349
5646
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
5350
5647
  let operationCount = 0;
5351
- 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);
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
+ }
5352
5663
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
5353
5664
  const field = modification.fieldsToAdd[i];
5354
5665
  operationCount++;
@@ -5449,7 +5760,21 @@ function generateOperationDownMigration(operation, collectionIdMap) {
5449
5760
  const modification = operation.modifications;
5450
5761
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
5451
5762
  let operationCount = 0;
5452
- 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);
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
+ }
5453
5778
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5454
5779
  operationCount++;
5455
5780
  const isLast = operationCount === totalOperations;
@@ -5580,6 +5905,19 @@ function generateUpMigration(diff) {
5580
5905
  lines.push(` // Modify existing collections`);
5581
5906
  for (const modification of diff.collectionsToModify) {
5582
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
+ }
5583
5921
  if (modification.fieldsToAdd.length > 0) {
5584
5922
  lines.push(` // Add fields to ${collectionName}`);
5585
5923
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
@@ -5716,6 +6054,19 @@ function generateDownMigration(diff) {
5716
6054
  lines.push(` // Revert modifications`);
5717
6055
  for (const modification of diff.collectionsToModify) {
5718
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
+ }
5719
6070
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
5720
6071
  lines.push(` // Revert permissions for ${collectionName}`);
5721
6072
  if (modification.permissionsToUpdate.length >= 2) {
@@ -6067,6 +6418,9 @@ var MigrationGenerator = class {
6067
6418
  function detectCollectionDeletions(diff) {
6068
6419
  const changes = [];
6069
6420
  for (const collection of diff.collectionsToDelete) {
6421
+ if (collection.type === "view") {
6422
+ continue;
6423
+ }
6070
6424
  changes.push({
6071
6425
  type: "collection_deletion" /* COLLECTION_DELETION */,
6072
6426
  description: `Delete collection: ${collection.name}`,
@@ -6481,7 +6835,11 @@ function formatChangeSummary(diff) {
6481
6835
  lines.push(chalk__default.default.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
6482
6836
  for (const collection of diff.collectionsToCreate) {
6483
6837
  lines.push(chalk__default.default.green(` + ${collection.name} (${collection.type})`));
6484
- 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
+ }
6485
6843
  }
6486
6844
  lines.push("");
6487
6845
  }
@@ -6526,6 +6884,9 @@ function formatChangeSummary(diff) {
6526
6884
  if (modification.rulesToUpdate.length > 0) {
6527
6885
  lines.push(chalk__default.default.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
6528
6886
  }
6887
+ if (modification.viewQueryUpdate) {
6888
+ lines.push(chalk__default.default.yellow(` ~ view query to update`));
6889
+ }
6529
6890
  lines.push("");
6530
6891
  }
6531
6892
  }
@@ -6999,7 +7360,9 @@ exports.convertPocketBaseMigration = convertPocketBaseMigration;
6999
7360
  exports.convertZodSchemaToCollectionSchema = convertZodSchemaToCollectionSchema;
7000
7361
  exports.createMigrationFileStructure = createMigrationFileStructure;
7001
7362
  exports.createPermissions = createPermissions;
7363
+ exports.dedentSql = dedentSql;
7002
7364
  exports.defineCollection = defineCollection;
7365
+ exports.defineView = defineView;
7003
7366
  exports.detectDestructiveChanges = detectDestructiveChanges;
7004
7367
  exports.detectDestructiveChangesValidation = detectDestructiveChanges2;
7005
7368
  exports.detectFieldChanges = detectFieldChanges;
@@ -7094,12 +7457,14 @@ exports.saveSnapshot = saveSnapshot;
7094
7457
  exports.selectSchemaForCollection = selectSchemaForCollection;
7095
7458
  exports.singularize = singularize;
7096
7459
  exports.snapshotExists = snapshotExists;
7460
+ exports.sql = sql;
7097
7461
  exports.summarizeDestructiveChanges = summarizeDestructiveChanges;
7098
7462
  exports.toCollectionName = toCollectionName;
7099
7463
  exports.unwrapZodType = unwrapZodType;
7100
7464
  exports.validatePermissionConfig = validatePermissionConfig;
7101
7465
  exports.validateRuleExpression = validateRuleExpression;
7102
7466
  exports.validateSnapshot = validateSnapshot;
7467
+ exports.validateViewQuery = validateViewQuery;
7103
7468
  exports.withIndexes = withIndexes;
7104
7469
  exports.withPermissions = withPermissions;
7105
7470
  exports.withProgress = withProgress;