pocketbase-zod-schema 0.3.0 → 0.3.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 (64) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli/index.cjs +167 -46
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.d.cts +2 -2
  5. package/dist/cli/index.d.ts +2 -2
  6. package/dist/cli/index.js +167 -46
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +167 -46
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +167 -46
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/cli/utils/index.d.cts +2 -2
  13. package/dist/cli/utils/index.d.ts +2 -2
  14. package/dist/{fields-UcOPu1OQ.d.cts → fields-RVj26U-O.d.cts} +1 -0
  15. package/dist/{fields-UcOPu1OQ.d.ts → fields-RVj26U-O.d.ts} +1 -0
  16. package/dist/index.cjs +167 -46
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +167 -46
  21. package/dist/index.js.map +1 -1
  22. package/dist/migration/analyzer.cjs.map +1 -1
  23. package/dist/migration/analyzer.d.cts +2 -2
  24. package/dist/migration/analyzer.d.ts +2 -2
  25. package/dist/migration/analyzer.js.map +1 -1
  26. package/dist/migration/diff.cjs +77 -26
  27. package/dist/migration/diff.cjs.map +1 -1
  28. package/dist/migration/diff.d.cts +4 -4
  29. package/dist/migration/diff.d.ts +4 -4
  30. package/dist/migration/diff.js +77 -26
  31. package/dist/migration/diff.js.map +1 -1
  32. package/dist/migration/generator.cjs +39 -2
  33. package/dist/migration/generator.cjs.map +1 -1
  34. package/dist/migration/generator.d.cts +2 -2
  35. package/dist/migration/generator.d.ts +2 -2
  36. package/dist/migration/generator.js +39 -2
  37. package/dist/migration/generator.js.map +1 -1
  38. package/dist/migration/index.cjs +167 -46
  39. package/dist/migration/index.cjs.map +1 -1
  40. package/dist/migration/index.d.cts +3 -3
  41. package/dist/migration/index.d.ts +3 -3
  42. package/dist/migration/index.js +167 -46
  43. package/dist/migration/index.js.map +1 -1
  44. package/dist/migration/snapshot.cjs +51 -18
  45. package/dist/migration/snapshot.cjs.map +1 -1
  46. package/dist/migration/snapshot.d.cts +2 -2
  47. package/dist/migration/snapshot.d.ts +2 -2
  48. package/dist/migration/snapshot.js +51 -18
  49. package/dist/migration/snapshot.js.map +1 -1
  50. package/dist/migration/utils/index.cjs +5 -3
  51. package/dist/migration/utils/index.cjs.map +1 -1
  52. package/dist/migration/utils/index.d.cts +4 -4
  53. package/dist/migration/utils/index.d.ts +4 -4
  54. package/dist/migration/utils/index.js +5 -3
  55. package/dist/migration/utils/index.js.map +1 -1
  56. package/dist/schema.cjs.map +1 -1
  57. package/dist/schema.d.cts +1 -1
  58. package/dist/schema.d.ts +1 -1
  59. package/dist/schema.js.map +1 -1
  60. package/dist/{type-mapper-n231Fspm.d.ts → type-mapper-CZzVeDj7.d.ts} +1 -1
  61. package/dist/{type-mapper-DrQmtznD.d.cts → type-mapper-DaBe-1ph.d.cts} +1 -1
  62. package/dist/{types-Ds3NQvny.d.ts → types-CUVzgZ9k.d.ts} +1 -1
  63. package/dist/{types-YoBjsa-A.d.cts → types-D-Fsdn_O.d.cts} +1 -1
  64. package/package.json +1 -1
@@ -1796,7 +1796,7 @@ var SchemaAnalyzer = class {
1796
1796
  var SNAPSHOT_VERSION = "1.0.0";
1797
1797
  function resolveCollectionIdToName(collectionId) {
1798
1798
  if (collectionId === "_pb_users_auth_") {
1799
- return "Users";
1799
+ return "users";
1800
1800
  }
1801
1801
  const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
1802
1802
  if (nameMatch) {
@@ -1804,6 +1804,39 @@ function resolveCollectionIdToName(collectionId) {
1804
1804
  }
1805
1805
  return collectionId;
1806
1806
  }
1807
+ function extractFieldOptions2(pbField) {
1808
+ const options = {};
1809
+ if (pbField.options && typeof pbField.options === "object") {
1810
+ Object.assign(options, pbField.options);
1811
+ }
1812
+ const directOptionKeys = [
1813
+ "min",
1814
+ "max",
1815
+ "pattern",
1816
+ "noDecimal",
1817
+ // text/number fields
1818
+ "values",
1819
+ "maxSelect",
1820
+ // select fields
1821
+ "mimeTypes",
1822
+ "maxSize",
1823
+ "thumbs",
1824
+ "protected",
1825
+ // file fields
1826
+ "onCreate",
1827
+ "onUpdate",
1828
+ // autodate fields
1829
+ "exceptDomains",
1830
+ "onlyDomains"
1831
+ // email/url fields
1832
+ ];
1833
+ for (const key of directOptionKeys) {
1834
+ if (pbField[key] !== void 0) {
1835
+ options[key] = pbField[key];
1836
+ }
1837
+ }
1838
+ return options;
1839
+ }
1807
1840
  function convertPocketBaseCollection(pbCollection) {
1808
1841
  const fields = [];
1809
1842
  const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
@@ -1821,23 +1854,19 @@ function convertPocketBaseCollection(pbCollection) {
1821
1854
  type: pbField.type,
1822
1855
  required: pbField.required || false
1823
1856
  };
1824
- field.options = pbField.options ? { ...pbField.options } : {};
1825
- if (pbField.type === "select") {
1826
- if (pbField.values && Array.isArray(pbField.values)) {
1827
- field.options.values = pbField.values;
1828
- } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
1829
- field.options.values = pbField.options.values;
1830
- }
1831
- }
1857
+ field.options = extractFieldOptions2(pbField);
1832
1858
  if (pbField.type === "relation") {
1833
1859
  const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
1834
- const collectionName = resolveCollectionIdToName(collectionId);
1860
+ const collectionName = resolveCollectionIdToName(collectionId || "");
1835
1861
  field.relation = {
1836
1862
  collection: collectionName,
1837
1863
  cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
1838
1864
  maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
1839
1865
  minSelect: pbField.minSelect ?? pbField.options?.minSelect
1840
1866
  };
1867
+ delete field.options.maxSelect;
1868
+ delete field.options.minSelect;
1869
+ delete field.options.cascadeDelete;
1841
1870
  }
1842
1871
  const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
1843
1872
  if (Object.keys(field.options).length === 0) {
@@ -1851,17 +1880,21 @@ function convertPocketBaseCollection(pbCollection) {
1851
1880
  type: pbCollection.type || "base",
1852
1881
  fields
1853
1882
  };
1883
+ if (pbCollection.id) {
1884
+ schema.id = pbCollection.id;
1885
+ }
1854
1886
  if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
1855
1887
  schema.indexes = pbCollection.indexes;
1856
1888
  }
1857
- const rules = {};
1858
- if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1859
- if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1860
- if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1861
- if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1862
- if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1863
- if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1864
- if (Object.keys(rules).length > 0) {
1889
+ const hasAnyRule = pbCollection.listRule !== void 0 || pbCollection.viewRule !== void 0 || pbCollection.createRule !== void 0 || pbCollection.updateRule !== void 0 || pbCollection.deleteRule !== void 0 || pbCollection.manageRule !== void 0;
1890
+ if (hasAnyRule) {
1891
+ const rules = {};
1892
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
1893
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
1894
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
1895
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
1896
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
1897
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1865
1898
  schema.rules = rules;
1866
1899
  schema.permissions = { ...rules };
1867
1900
  }
@@ -2611,17 +2644,19 @@ var CollectionIdRegistry = class {
2611
2644
  }
2612
2645
  /**
2613
2646
  * Generates a unique collection ID for a given collection name
2614
- * Special case: Returns constant "_pb_users_auth_" for users collection
2615
2647
  * Retries up to 10 times if collision occurs (extremely rare)
2648
+ * Special case: returns "_pb_users_auth_" for users collection
2616
2649
  *
2617
- * @param collectionName - The name of the collection
2650
+ * @param collectionName - The name of the collection (optional)
2618
2651
  * @returns A unique collection ID
2619
2652
  * @throws Error if unable to generate unique ID after max attempts
2620
2653
  */
2621
2654
  generate(collectionName) {
2622
2655
  if (collectionName && collectionName.toLowerCase() === "users") {
2623
2656
  const usersId = "_pb_users_auth_";
2624
- this.register(usersId);
2657
+ if (!this.has(usersId)) {
2658
+ this.register(usersId);
2659
+ }
2625
2660
  return usersId;
2626
2661
  }
2627
2662
  const maxAttempts = 10;
@@ -2805,18 +2840,49 @@ function compareFieldConstraints(currentField, previousField) {
2805
2840
  }
2806
2841
  return changes;
2807
2842
  }
2843
+ function normalizeOptionValue(key, value, fieldType) {
2844
+ if (key === "maxSelect" && value === 1 && (fieldType === "select" || fieldType === "file")) {
2845
+ return void 0;
2846
+ }
2847
+ if (key === "maxSize" && value === 0 && fieldType === "file") {
2848
+ return void 0;
2849
+ }
2850
+ if (fieldType === "file") {
2851
+ if (key === "mimeTypes" && Array.isArray(value) && value.length === 0) {
2852
+ return void 0;
2853
+ }
2854
+ if (key === "thumbs" && Array.isArray(value) && value.length === 0) {
2855
+ return void 0;
2856
+ }
2857
+ if (key === "protected" && value === false) {
2858
+ return void 0;
2859
+ }
2860
+ }
2861
+ if (fieldType === "autodate") {
2862
+ if (key === "onCreate" && value === true) {
2863
+ return void 0;
2864
+ }
2865
+ if (key === "onUpdate" && value === false) {
2866
+ return void 0;
2867
+ }
2868
+ }
2869
+ return value;
2870
+ }
2808
2871
  function compareFieldOptions(currentField, previousField) {
2809
2872
  const changes = [];
2810
2873
  const currentOptions = currentField.options || {};
2811
2874
  const previousOptions = previousField.options || {};
2812
2875
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
2876
+ const fieldType = currentField.type;
2813
2877
  for (const key of allKeys) {
2814
2878
  const currentValue = currentOptions[key];
2815
2879
  const previousValue = previousOptions[key];
2816
- if (currentValue === void 0 && previousValue === void 0) {
2880
+ const normalizedCurrent = normalizeOptionValue(key, currentValue, fieldType);
2881
+ const normalizedPrevious = normalizeOptionValue(key, previousValue, fieldType);
2882
+ if (normalizedCurrent === void 0 && normalizedPrevious === void 0) {
2817
2883
  continue;
2818
2884
  }
2819
- if (!areValuesEqual(currentValue, previousValue)) {
2885
+ if (!areValuesEqual(normalizedCurrent, normalizedPrevious)) {
2820
2886
  changes.push({
2821
2887
  property: `options.${key}`,
2822
2888
  oldValue: previousValue,
@@ -2826,7 +2892,7 @@ function compareFieldOptions(currentField, previousField) {
2826
2892
  }
2827
2893
  return changes;
2828
2894
  }
2829
- function compareRelationConfigurations(currentField, previousField) {
2895
+ function compareRelationConfigurations(currentField, previousField, collectionIdToName) {
2830
2896
  const changes = [];
2831
2897
  const currentRelation = currentField.relation;
2832
2898
  const previousRelation = previousField.relation;
@@ -2838,8 +2904,8 @@ function compareRelationConfigurations(currentField, previousField) {
2838
2904
  }
2839
2905
  const normalizeCollection = (collection) => {
2840
2906
  if (!collection) return collection;
2841
- if (collection === "_pb_users_auth_") {
2842
- return "Users";
2907
+ if (collectionIdToName && collectionIdToName.has(collection)) {
2908
+ return collectionIdToName.get(collection);
2843
2909
  }
2844
2910
  const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2845
2911
  if (nameMatch) {
@@ -2849,13 +2915,11 @@ function compareRelationConfigurations(currentField, previousField) {
2849
2915
  };
2850
2916
  const normalizedCurrent = normalizeCollection(currentRelation.collection);
2851
2917
  const normalizedPrevious = normalizeCollection(previousRelation.collection);
2852
- if (normalizedCurrent !== normalizedPrevious) {
2918
+ if (normalizedCurrent.toLowerCase() !== normalizedPrevious.toLowerCase()) {
2853
2919
  changes.push({
2854
2920
  property: "relation.collection",
2855
- oldValue: normalizedPrevious,
2856
- // Use normalized value for clarity
2857
- newValue: normalizedCurrent
2858
- // Use normalized value for clarity
2921
+ oldValue: previousRelation.collection,
2922
+ newValue: currentRelation.collection
2859
2923
  });
2860
2924
  }
2861
2925
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -2865,14 +2929,20 @@ function compareRelationConfigurations(currentField, previousField) {
2865
2929
  newValue: currentRelation.cascadeDelete
2866
2930
  });
2867
2931
  }
2868
- if (currentRelation.maxSelect !== previousRelation.maxSelect) {
2932
+ const normalizeMax = (val) => val === 1 ? null : val;
2933
+ const currentMax = normalizeMax(currentRelation.maxSelect);
2934
+ const previousMax = normalizeMax(previousRelation.maxSelect);
2935
+ if (currentMax != previousMax) {
2869
2936
  changes.push({
2870
2937
  property: "relation.maxSelect",
2871
2938
  oldValue: previousRelation.maxSelect,
2872
2939
  newValue: currentRelation.maxSelect
2873
2940
  });
2874
2941
  }
2875
- if (currentRelation.minSelect !== previousRelation.minSelect) {
2942
+ const normalizeMin = (val) => val === 0 ? null : val;
2943
+ const currentMin = normalizeMin(currentRelation.minSelect);
2944
+ const previousMin = normalizeMin(previousRelation.minSelect);
2945
+ if (currentMin != previousMin) {
2876
2946
  changes.push({
2877
2947
  property: "relation.minSelect",
2878
2948
  oldValue: previousRelation.minSelect,
@@ -2881,7 +2951,7 @@ function compareRelationConfigurations(currentField, previousField) {
2881
2951
  }
2882
2952
  return changes;
2883
2953
  }
2884
- function detectFieldChanges(currentField, previousField) {
2954
+ function detectFieldChanges(currentField, previousField, collectionIdToName) {
2885
2955
  const changes = [];
2886
2956
  const typeChange = compareFieldTypes(currentField, previousField);
2887
2957
  if (typeChange) {
@@ -2890,7 +2960,7 @@ function detectFieldChanges(currentField, previousField) {
2890
2960
  changes.push(...compareFieldConstraints(currentField, previousField));
2891
2961
  changes.push(...compareFieldOptions(currentField, previousField));
2892
2962
  if (currentField.type === "relation" && previousField.type === "relation") {
2893
- changes.push(...compareRelationConfigurations(currentField, previousField));
2963
+ changes.push(...compareRelationConfigurations(currentField, previousField, collectionIdToName));
2894
2964
  }
2895
2965
  return changes;
2896
2966
  }
@@ -2901,7 +2971,7 @@ function compareIndexes(currentIndexes = [], previousIndexes = []) {
2901
2971
  const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
2902
2972
  return { indexesToAdd, indexesToRemove };
2903
2973
  }
2904
- function compareRules(currentRules, previousRules) {
2974
+ function compareRules(currentRules, previousRules, currentPermissions, previousPermissions) {
2905
2975
  const updates = [];
2906
2976
  const ruleTypes = [
2907
2977
  "listRule",
@@ -2912,8 +2982,8 @@ function compareRules(currentRules, previousRules) {
2912
2982
  "manageRule"
2913
2983
  ];
2914
2984
  for (const ruleType of ruleTypes) {
2915
- const currentValue = currentRules?.[ruleType] ?? null;
2916
- const previousValue = previousRules?.[ruleType] ?? null;
2985
+ const currentValue = currentRules?.[ruleType] ?? currentPermissions?.[ruleType] ?? null;
2986
+ const previousValue = previousRules?.[ruleType] ?? previousPermissions?.[ruleType] ?? null;
2917
2987
  if (currentValue !== previousValue) {
2918
2988
  updates.push({
2919
2989
  ruleType,
@@ -2940,7 +3010,7 @@ function comparePermissions(currentPermissions, previousPermissions) {
2940
3010
  }
2941
3011
  return changes;
2942
3012
  }
2943
- function compareCollectionFields(currentCollection, previousCollection, config) {
3013
+ function compareCollectionFields(currentCollection, previousCollection, config, collectionIdToName) {
2944
3014
  let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
2945
3015
  const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
2946
3016
  const fieldsToModify = [];
@@ -2950,7 +3020,7 @@ function compareCollectionFields(currentCollection, previousCollection, config)
2950
3020
  }
2951
3021
  const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
2952
3022
  for (const [currentField, previousField] of matchedFields) {
2953
- const changes = detectFieldChanges(currentField, previousField);
3023
+ const changes = detectFieldChanges(currentField, previousField, collectionIdToName);
2954
3024
  if (changes.length > 0) {
2955
3025
  fieldsToModify.push({
2956
3026
  fieldName: currentField.name,
@@ -2962,14 +3032,20 @@ function compareCollectionFields(currentCollection, previousCollection, config)
2962
3032
  }
2963
3033
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
2964
3034
  }
2965
- function buildCollectionModification(currentCollection, previousCollection, config) {
3035
+ function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
2966
3036
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
2967
3037
  currentCollection,
2968
3038
  previousCollection,
2969
- config
3039
+ config,
3040
+ collectionIdToName
2970
3041
  );
2971
3042
  const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
2972
- const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
3043
+ const rulesToUpdate = compareRules(
3044
+ currentCollection.rules,
3045
+ previousCollection.rules,
3046
+ currentCollection.permissions,
3047
+ previousCollection.permissions
3048
+ );
2973
3049
  const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
2974
3050
  return {
2975
3051
  collection: currentCollection.name,
@@ -2986,6 +3062,14 @@ function hasChanges(modification) {
2986
3062
  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;
2987
3063
  }
2988
3064
  function aggregateChanges(currentSchema, previousSnapshot, config) {
3065
+ const collectionIdToName = /* @__PURE__ */ new Map();
3066
+ if (previousSnapshot) {
3067
+ for (const [name, collection] of previousSnapshot.collections) {
3068
+ if (collection.id) {
3069
+ collectionIdToName.set(collection.id, name);
3070
+ }
3071
+ }
3072
+ }
2989
3073
  const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
2990
3074
  const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
2991
3075
  const filteredCollectionsToCreate = collectionsToCreate.filter(
@@ -3009,7 +3093,7 @@ function aggregateChanges(currentSchema, previousSnapshot, config) {
3009
3093
  const collectionsToModify = [];
3010
3094
  const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
3011
3095
  for (const [currentCollection, previousCollection] of matchedCollections) {
3012
- const modification = buildCollectionModification(currentCollection, previousCollection, config);
3096
+ const modification = buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName);
3013
3097
  if (hasChanges(modification)) {
3014
3098
  collectionsToModify.push(modification);
3015
3099
  }
@@ -3579,6 +3663,9 @@ function getSystemFields() {
3579
3663
  function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
3580
3664
  const lines = [];
3581
3665
  lines.push(` const ${varName} = new Collection({`);
3666
+ if (collection.id) {
3667
+ lines.push(` id: ${formatValue(collection.id)},`);
3668
+ }
3582
3669
  lines.push(` name: "${collection.name}",`);
3583
3670
  lines.push(` type: "${collection.type}",`);
3584
3671
  const permissionsCode = generateCollectionPermissions(collection.permissions);
@@ -3822,7 +3909,24 @@ function generateOperationUpMigration(operation, collectionIdMap) {
3822
3909
  const varName = `collection_${collectionName}`;
3823
3910
  lines.push(generateCollectionDeletion(collectionName, varName, true));
3824
3911
  }
3825
- return lines.join("\n");
3912
+ let code = lines.join("\n");
3913
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3914
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3915
+ const saveMatches = [...code.matchAll(savePattern)];
3916
+ const deleteMatches = [...code.matchAll(deletePattern)];
3917
+ const allMatches = [
3918
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3919
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3920
+ ].sort((a, b) => b.index - a.index);
3921
+ if (allMatches.length > 0) {
3922
+ const lastMatch = allMatches[0];
3923
+ if (lastMatch.type === "save") {
3924
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3925
+ } else {
3926
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
3927
+ }
3928
+ }
3929
+ return code;
3826
3930
  }
3827
3931
  function generateOperationDownMigration(operation, collectionIdMap) {
3828
3932
  const lines = [];
@@ -3907,7 +4011,24 @@ function generateOperationDownMigration(operation, collectionIdMap) {
3907
4011
  lines.push(generateCollectionCreation(collection, varName, true, collectionIdMap));
3908
4012
  }
3909
4013
  }
3910
- return lines.join("\n");
4014
+ let code = lines.join("\n");
4015
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4016
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4017
+ const saveMatches = [...code.matchAll(savePattern)];
4018
+ const deleteMatches = [...code.matchAll(deletePattern)];
4019
+ const allMatches = [
4020
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4021
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4022
+ ].sort((a, b) => b.index - a.index);
4023
+ if (allMatches.length > 0) {
4024
+ const lastMatch = allMatches[0];
4025
+ if (lastMatch.type === "save") {
4026
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.save(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
4027
+ } else {
4028
+ code = code.substring(0, lastMatch.match.index) + lastMatch.match[1] + "return app.delete(" + lastMatch.match[2] + ");" + code.substring(lastMatch.match.index + lastMatch.match[0].length);
4029
+ }
4030
+ }
4031
+ return code;
3911
4032
  }
3912
4033
  function generateUpMigration(diff) {
3913
4034
  const lines = [];