pocketbase-zod-schema 0.6.1 → 0.7.1

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 (46) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cli/index.cjs +219 -27
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.d.cts +2 -1
  5. package/dist/cli/index.d.ts +2 -1
  6. package/dist/cli/index.js +219 -27
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +258 -36
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +259 -37
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/migration/diff.cjs +76 -0
  15. package/dist/migration/diff.cjs.map +1 -1
  16. package/dist/migration/diff.d.cts +7 -1
  17. package/dist/migration/diff.d.ts +7 -1
  18. package/dist/migration/diff.js +76 -1
  19. package/dist/migration/diff.js.map +1 -1
  20. package/dist/migration/generator.cjs +108 -39
  21. package/dist/migration/generator.cjs.map +1 -1
  22. package/dist/migration/generator.d.cts +23 -1
  23. package/dist/migration/generator.d.ts +23 -1
  24. package/dist/migration/generator.js +107 -40
  25. package/dist/migration/generator.js.map +1 -1
  26. package/dist/migration/index.cjs +230 -39
  27. package/dist/migration/index.cjs.map +1 -1
  28. package/dist/migration/index.d.cts +1 -1
  29. package/dist/migration/index.d.ts +1 -1
  30. package/dist/migration/index.js +230 -40
  31. package/dist/migration/index.js.map +1 -1
  32. package/dist/migration/snapshot.cjs +48 -0
  33. package/dist/migration/snapshot.cjs.map +1 -1
  34. package/dist/migration/snapshot.js +48 -0
  35. package/dist/migration/snapshot.js.map +1 -1
  36. package/dist/mutator.cjs.map +1 -1
  37. package/dist/mutator.d.cts +10 -10
  38. package/dist/mutator.d.ts +10 -10
  39. package/dist/mutator.js.map +1 -1
  40. package/dist/server.cjs +246 -41
  41. package/dist/server.cjs.map +1 -1
  42. package/dist/server.d.cts +1 -1
  43. package/dist/server.d.ts +1 -1
  44. package/dist/server.js +246 -42
  45. package/dist/server.js.map +1 -1
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.7.0...pocketbase-zod-schema-v0.7.1) (2026-05-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * missing autodate fields ([e3d6dc6](https://github.com/dastron/pocketbase-zod-schema/commit/e3d6dc60d2e6b7d0fa9a52b6bace3c2d3addfc84))
9
+
10
+ ## [0.7.0](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.6.1...pocketbase-zod-schema-v0.7.0) (2026-02-13)
11
+
12
+
13
+ ### Features
14
+
15
+ * Add migration filtering and skip destructive changes without force ([8b6f4f1](https://github.com/dastron/pocketbase-zod-schema/commit/8b6f4f1e918c40052ec15390b7c5c4df87e8a871))
16
+ * Add migration filtering and skip destructive changes without force ([1a8ea15](https://github.com/dastron/pocketbase-zod-schema/commit/1a8ea15489957ec2407af8b57316f703eb40db57))
17
+ * enhance codegen with complex types and strict mutators ([4086561](https://github.com/dastron/pocketbase-zod-schema/commit/4086561aa4ae8f132e965ff7672a581644eaa364))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * remove idOrName:string from typegen ([423dcbb](https://github.com/dastron/pocketbase-zod-schema/commit/423dcbb1a584f9bf3722c797297cc9450ba45325))
23
+
3
24
  ## [0.6.1](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.6.0...pocketbase-zod-schema-v0.6.1) (2026-01-26)
4
25
 
5
26
 
@@ -2163,6 +2163,54 @@ function parseMigrationOperationsFromContent(content) {
2163
2163
  }
2164
2164
  }
2165
2165
  }
2166
+ const unmarshalRegex = /unmarshal\s*\(/g;
2167
+ let unmarshalMatch;
2168
+ while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
2169
+ let i = unmarshalMatch.index + unmarshalMatch[0].length;
2170
+ while (i < content.length && /\s/.test(content[i])) i++;
2171
+ if (content[i] !== "{") continue;
2172
+ const objStart = i;
2173
+ let braceCount = 1;
2174
+ let inStr = false;
2175
+ let strChar = null;
2176
+ i++;
2177
+ while (i < content.length && braceCount > 0) {
2178
+ const ch = content[i];
2179
+ const prev = i > 0 ? content[i - 1] : "";
2180
+ if (!inStr && (ch === '"' || ch === "'")) {
2181
+ inStr = true;
2182
+ strChar = ch;
2183
+ } else if (inStr && ch === strChar && prev !== "\\") {
2184
+ inStr = false;
2185
+ strChar = null;
2186
+ }
2187
+ if (!inStr) {
2188
+ if (ch === "{") braceCount++;
2189
+ if (ch === "}") braceCount--;
2190
+ }
2191
+ i++;
2192
+ }
2193
+ if (braceCount !== 0) continue;
2194
+ const objStr = content.substring(objStart, i);
2195
+ while (i < content.length && /[\s,]/.test(content[i])) i++;
2196
+ const varStart = i;
2197
+ while (i < content.length && /\w/.test(content[i])) i++;
2198
+ const colVarName = content.substring(varStart, i);
2199
+ const colInfo = variables.get(colVarName);
2200
+ if (!colInfo || colInfo.type !== "collection") continue;
2201
+ const keyValRegex = /"(\w+Rule)"\s*:\s*([^,}\n]+)/g;
2202
+ let kvMatch;
2203
+ while ((kvMatch = keyValRegex.exec(objStr)) !== null) {
2204
+ const ruleKey = kvMatch[1];
2205
+ const valStr = kvMatch[2].trim();
2206
+ try {
2207
+ const value = new Function("app", `return ${valStr}`)(mockApp);
2208
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = value;
2209
+ } catch {
2210
+ getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
2211
+ }
2212
+ }
2213
+ }
2166
2214
  const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
2167
2215
  let match;
2168
2216
  while ((match = idxPushRegex.exec(content)) !== null) {
@@ -2933,6 +2981,81 @@ function categorizeChangesBySeverity(diff, _config) {
2933
2981
  return { destructive, nonDestructive };
2934
2982
  }
2935
2983
 
2984
+ // src/migration/diff/filter.ts
2985
+ function matchesPattern(text, patterns) {
2986
+ if (!patterns || patterns.length === 0) return true;
2987
+ return patterns.some((pattern) => {
2988
+ try {
2989
+ const regex = new RegExp(pattern);
2990
+ return regex.test(text);
2991
+ } catch {
2992
+ return text.includes(pattern);
2993
+ }
2994
+ });
2995
+ }
2996
+ function isDestructiveFieldModification(mod) {
2997
+ const typeChange = mod.changes.find((c) => c.property === "type");
2998
+ const requiredChange = mod.changes.find((c) => c.property === "required" && c.newValue === true);
2999
+ return !!(typeChange || requiredChange);
3000
+ }
3001
+ function filterDiff(diff, options) {
3002
+ const { patterns = [], skipDestructive = false } = options;
3003
+ const collectionsToCreate = diff.collectionsToCreate.filter((col) => {
3004
+ return matchesPattern(col.name, patterns);
3005
+ });
3006
+ let collectionsToDelete = diff.collectionsToDelete;
3007
+ if (skipDestructive) {
3008
+ collectionsToDelete = [];
3009
+ } else {
3010
+ collectionsToDelete = collectionsToDelete.filter((col) => {
3011
+ return matchesPattern(col.name, patterns);
3012
+ });
3013
+ }
3014
+ const collectionsToModify = diff.collectionsToModify.map((mod) => {
3015
+ const collectionMatches = matchesPattern(mod.collection, patterns);
3016
+ const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
3017
+ return collectionMatches || matchesPattern(`${mod.collection}.${field.name}`, patterns);
3018
+ });
3019
+ let fieldsToRemove = mod.fieldsToRemove;
3020
+ if (skipDestructive) {
3021
+ fieldsToRemove = [];
3022
+ } else {
3023
+ fieldsToRemove = fieldsToRemove.filter((field) => {
3024
+ return collectionMatches || matchesPattern(`${mod.collection}.${field.name}`, patterns);
3025
+ });
3026
+ }
3027
+ let fieldsToModify = mod.fieldsToModify;
3028
+ if (skipDestructive) {
3029
+ fieldsToModify = fieldsToModify.filter((f) => !isDestructiveFieldModification(f));
3030
+ }
3031
+ fieldsToModify = fieldsToModify.filter((f) => {
3032
+ return collectionMatches || matchesPattern(`${mod.collection}.${f.fieldName}`, patterns);
3033
+ });
3034
+ const indexesToAdd = collectionMatches ? mod.indexesToAdd : [];
3035
+ const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
3036
+ const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
3037
+ const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
3038
+ return {
3039
+ ...mod,
3040
+ fieldsToAdd,
3041
+ fieldsToRemove,
3042
+ fieldsToModify,
3043
+ indexesToAdd,
3044
+ indexesToRemove,
3045
+ rulesToUpdate,
3046
+ permissionsToUpdate
3047
+ };
3048
+ }).filter((mod) => {
3049
+ 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;
3050
+ });
3051
+ return {
3052
+ ...diff,
3053
+ collectionsToCreate,
3054
+ collectionsToDelete,
3055
+ collectionsToModify
3056
+ };
3057
+ }
3058
+
2936
3059
  // src/migration/diff/index.ts
2937
3060
  function hasChanges(modification) {
2938
3061
  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;
@@ -3152,6 +3275,36 @@ function getSystemFields() {
3152
3275
  }
3153
3276
  ];
3154
3277
  }
3278
+ function getSystemTimestampFields() {
3279
+ return [
3280
+ {
3281
+ name: "created",
3282
+ id: "autodate2990389176",
3283
+ type: "autodate",
3284
+ required: false,
3285
+ options: {
3286
+ hidden: false,
3287
+ onCreate: true,
3288
+ onUpdate: false,
3289
+ presentable: false,
3290
+ system: true
3291
+ }
3292
+ },
3293
+ {
3294
+ name: "updated",
3295
+ id: "autodate3332085495",
3296
+ type: "autodate",
3297
+ required: false,
3298
+ options: {
3299
+ hidden: false,
3300
+ onCreate: true,
3301
+ onUpdate: true,
3302
+ presentable: false,
3303
+ system: true
3304
+ }
3305
+ }
3306
+ ];
3307
+ }
3155
3308
  function getAuthSystemFields() {
3156
3309
  return [
3157
3310
  {
@@ -3506,6 +3659,18 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
3506
3659
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3507
3660
  return lines.join("\n");
3508
3661
  }
3662
+ function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
3663
+ const collectionVar = `collection_${collectionName}_${varSuffix}`;
3664
+ const lines = [];
3665
+ lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
3666
+ lines.push(` unmarshal({`);
3667
+ for (const entry of entries) {
3668
+ lines.push(` "${entry.ruleType}": ${formatValue(entry.value)},`);
3669
+ }
3670
+ lines.push(` }, ${collectionVar})`);
3671
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3672
+ return lines.join("\n");
3673
+ }
3509
3674
 
3510
3675
  // src/migration/generator/collections.ts
3511
3676
  function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
@@ -3529,11 +3694,12 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
3529
3694
  systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
3530
3695
  }
3531
3696
  const userFields = collection.fields.filter((f) => !systemFieldNames.includes(f.name));
3532
- const allFields = [...getSystemFields().filter((f) => f.name === "id")];
3697
+ const allFields = [...getSystemFields()];
3533
3698
  if (collection.type === "auth") {
3534
3699
  allFields.push(...getAuthSystemFields());
3535
3700
  }
3536
3701
  allFields.push(...userFields);
3702
+ allFields.push(...getSystemTimestampFields());
3537
3703
  lines.push(` "fields": ${generateFieldsArray(allFields, collectionIdMap)},`);
3538
3704
  let allIndexes = [...collection.indexes || []];
3539
3705
  if (collection.type === "auth") {
@@ -3563,7 +3729,7 @@ function generateOperationUpMigration(operation, collectionIdMap) {
3563
3729
  const modification = operation.modifications;
3564
3730
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
3565
3731
  let operationCount = 0;
3566
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
3732
+ 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);
3567
3733
  for (let i = 0; i < modification.fieldsToAdd.length; i++) {
3568
3734
  const field = modification.fieldsToAdd[i];
3569
3735
  operationCount++;
@@ -3603,23 +3769,29 @@ function generateOperationUpMigration(operation, collectionIdMap) {
3603
3769
  if (!isLast) lines.push("");
3604
3770
  }
3605
3771
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
3606
- for (const permission of modification.permissionsToUpdate) {
3607
- operationCount++;
3772
+ operationCount++;
3773
+ const isLast = operationCount === totalOperations;
3774
+ if (modification.permissionsToUpdate.length >= 2) {
3775
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.newValue }));
3776
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
3777
+ } else {
3778
+ const permission = modification.permissionsToUpdate[0];
3608
3779
  const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
3609
- const isLast = operationCount === totalOperations;
3610
- lines.push(
3611
- generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap)
3612
- );
3613
- if (!isLast) lines.push("");
3780
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast, collectionIdMap));
3614
3781
  }
3782
+ if (!isLast) lines.push("");
3615
3783
  } else if (modification.rulesToUpdate.length > 0) {
3616
- for (const rule of modification.rulesToUpdate) {
3617
- operationCount++;
3784
+ operationCount++;
3785
+ const isLast = operationCount === totalOperations;
3786
+ if (modification.rulesToUpdate.length >= 2) {
3787
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.newValue }));
3788
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "rules", isLast, collectionIdMap));
3789
+ } else {
3790
+ const rule = modification.rulesToUpdate[0];
3618
3791
  const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
3619
- const isLast = operationCount === totalOperations;
3620
3792
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, isLast, collectionIdMap));
3621
- if (!isLast) lines.push("");
3622
3793
  }
3794
+ if (!isLast) lines.push("");
3623
3795
  }
3624
3796
  } else if (operation.type === "delete") {
3625
3797
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection.name;
@@ -3658,25 +3830,31 @@ function generateOperationDownMigration(operation, collectionIdMap) {
3658
3830
  const modification = operation.modifications;
3659
3831
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
3660
3832
  let operationCount = 0;
3661
- const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
3833
+ 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);
3662
3834
  if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
3663
- for (const permission of modification.permissionsToUpdate) {
3664
- operationCount++;
3835
+ operationCount++;
3836
+ const isLast = operationCount === totalOperations;
3837
+ if (modification.permissionsToUpdate.length >= 2) {
3838
+ const entries = modification.permissionsToUpdate.map((p) => ({ ruleType: p.ruleType, value: p.oldValue }));
3839
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
3840
+ } else {
3841
+ const permission = modification.permissionsToUpdate[0];
3665
3842
  const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
3666
- const isLast = operationCount === totalOperations;
3667
- lines.push(
3668
- generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap)
3669
- );
3670
- if (!isLast) lines.push("");
3843
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast, collectionIdMap));
3671
3844
  }
3845
+ if (!isLast) lines.push("");
3672
3846
  } else if (modification.rulesToUpdate.length > 0) {
3673
- for (const rule of modification.rulesToUpdate) {
3674
- operationCount++;
3847
+ operationCount++;
3848
+ const isLast = operationCount === totalOperations;
3849
+ if (modification.rulesToUpdate.length >= 2) {
3850
+ const entries = modification.rulesToUpdate.map((r) => ({ ruleType: r.ruleType, value: r.oldValue }));
3851
+ lines.push(generateGroupedRuleUpdates(collectionName, entries, "revert_rules", isLast, collectionIdMap));
3852
+ } else {
3853
+ const rule = modification.rulesToUpdate[0];
3675
3854
  const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
3676
- const isLast = operationCount === totalOperations;
3677
3855
  lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, isLast, collectionIdMap));
3678
- if (!isLast) lines.push("");
3679
3856
  }
3857
+ if (!isLast) lines.push("");
3680
3858
  }
3681
3859
  for (let i = 0; i < modification.indexesToRemove.length; i++) {
3682
3860
  operationCount++;
@@ -4521,7 +4699,7 @@ function handleDestructiveChanges(diff, config, force) {
4521
4699
  }
4522
4700
  return true;
4523
4701
  }
4524
- async function executeGenerate(options) {
4702
+ async function executeGenerate(filters, options) {
4525
4703
  try {
4526
4704
  const parentOpts = options.parent?.opts?.() || {};
4527
4705
  if (parentOpts.verbose) {
@@ -4531,6 +4709,9 @@ async function executeGenerate(options) {
4531
4709
  }
4532
4710
  logDebug("Starting migration generation...");
4533
4711
  logDebug(`Options: ${JSON.stringify(options, null, 2)}`);
4712
+ if (filters && filters.length > 0) {
4713
+ logDebug(`Filters: ${JSON.stringify(filters)}`);
4714
+ }
4534
4715
  const config = await loadConfig(options);
4535
4716
  const schemaDir = getSchemaDirectory(config);
4536
4717
  const migrationsDir = getMigrationsDirectory(config);
@@ -4554,7 +4735,18 @@ async function executeGenerate(options) {
4554
4735
  logSuccess("Loaded previous snapshot as base reference");
4555
4736
  }
4556
4737
  logSection("\u{1F4CA} Comparing Schemas");
4557
- const diff = compare(currentSchema, previousSnapshot);
4738
+ let diff = compare(currentSchema, previousSnapshot);
4739
+ const skipDestructive = !options.force;
4740
+ if (skipDestructive) {
4741
+ const destructive = detectDestructiveChanges(diff);
4742
+ if (destructive.length > 0) {
4743
+ logInfo(`\u2139\uFE0F Omitting ${destructive.length} destructive change(s) because --force is not set.`);
4744
+ }
4745
+ }
4746
+ diff = filterDiff(diff, {
4747
+ patterns: filters,
4748
+ skipDestructive
4749
+ });
4558
4750
  if (!hasChanges2(diff)) {
4559
4751
  logInfo("No changes detected");
4560
4752
  console.log();