pocketbase-zod-schema 0.1.2 → 0.1.3

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 (63) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +329 -99
  3. package/dist/cli/index.cjs +176 -55
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +176 -55
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +196 -58
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +194 -57
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/cli/utils/index.cjs +1 -1
  12. package/dist/cli/utils/index.cjs.map +1 -1
  13. package/dist/cli/utils/index.js +1 -1
  14. package/dist/cli/utils/index.js.map +1 -1
  15. package/dist/index.cjs +197 -96
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +197 -95
  20. package/dist/index.js.map +1 -1
  21. package/dist/migration/analyzer.cjs +101 -28
  22. package/dist/migration/analyzer.cjs.map +1 -1
  23. package/dist/migration/analyzer.js +101 -28
  24. package/dist/migration/analyzer.js.map +1 -1
  25. package/dist/migration/generator.cjs +60 -25
  26. package/dist/migration/generator.cjs.map +1 -1
  27. package/dist/migration/generator.d.cts +9 -5
  28. package/dist/migration/generator.d.ts +9 -5
  29. package/dist/migration/generator.js +60 -25
  30. package/dist/migration/generator.js.map +1 -1
  31. package/dist/migration/index.cjs +162 -53
  32. package/dist/migration/index.cjs.map +1 -1
  33. package/dist/migration/index.js +162 -53
  34. package/dist/migration/index.js.map +1 -1
  35. package/dist/migration/snapshot.cjs +1 -0
  36. package/dist/migration/snapshot.cjs.map +1 -1
  37. package/dist/migration/snapshot.js +1 -0
  38. package/dist/migration/snapshot.js.map +1 -1
  39. package/dist/migration/utils/index.cjs +19 -17
  40. package/dist/migration/utils/index.cjs.map +1 -1
  41. package/dist/migration/utils/index.d.cts +3 -1
  42. package/dist/migration/utils/index.d.ts +3 -1
  43. package/dist/migration/utils/index.js +19 -17
  44. package/dist/migration/utils/index.js.map +1 -1
  45. package/dist/mutator.cjs +9 -11
  46. package/dist/mutator.cjs.map +1 -1
  47. package/dist/mutator.d.cts +5 -9
  48. package/dist/mutator.d.ts +5 -9
  49. package/dist/mutator.js +9 -11
  50. package/dist/mutator.js.map +1 -1
  51. package/dist/schema.cjs +50 -53
  52. package/dist/schema.cjs.map +1 -1
  53. package/dist/schema.d.cts +94 -12
  54. package/dist/schema.d.ts +94 -12
  55. package/dist/schema.js +50 -52
  56. package/dist/schema.js.map +1 -1
  57. package/dist/types.d.cts +2 -5
  58. package/dist/types.d.ts +2 -5
  59. package/dist/user-C39DQ40N.d.cts +53 -0
  60. package/dist/user-C39DQ40N.d.ts +53 -0
  61. package/package.json +2 -3
  62. package/dist/user-jS1aYoeD.d.cts +0 -123
  63. package/dist/user-jS1aYoeD.d.ts +0 -123
@@ -3,6 +3,35 @@ import * as path from 'path';
3
3
  import { z } from 'zod';
4
4
 
5
5
  // src/migration/analyzer.ts
6
+ ({
7
+ id: z.string().describe("unique id"),
8
+ collectionId: z.string().describe("collection id"),
9
+ collectionName: z.string().describe("collection name"),
10
+ expand: z.record(z.any()).describe("expandable fields")
11
+ });
12
+ ({
13
+ created: z.string().describe("creation timestamp"),
14
+ updated: z.string().describe("last update timestamp")
15
+ });
16
+ ({
17
+ thumbnailURL: z.string().optional(),
18
+ imageFiles: z.array(z.string())
19
+ });
20
+ ({
21
+ imageFiles: z.array(z.instanceof(File))
22
+ });
23
+ var RELATION_METADATA_KEY = "__pocketbase_relation__";
24
+ function extractRelationMetadata(description) {
25
+ if (!description) return null;
26
+ try {
27
+ const parsed = JSON.parse(description);
28
+ if (parsed[RELATION_METADATA_KEY]) {
29
+ return parsed[RELATION_METADATA_KEY];
30
+ }
31
+ } catch {
32
+ }
33
+ return null;
34
+ }
6
35
 
7
36
  // src/migration/errors.ts
8
37
  var MigrationError = class _MigrationError extends Error {
@@ -193,7 +222,7 @@ Suggestion: ${this.suggestion}`);
193
222
  }
194
223
  };
195
224
 
196
- // src/schema/permission-templates.ts
225
+ // src/utils/permission-templates.ts
197
226
  var PermissionTemplates = {
198
227
  /**
199
228
  * Public access - anyone can perform all operations
@@ -873,26 +902,28 @@ function getMaxSelect(fieldName, zodType) {
873
902
  return 1;
874
903
  }
875
904
  function getMinSelect(fieldName, zodType) {
876
- if (!isMultipleRelationField(fieldName, zodType)) {
877
- return void 0;
878
- }
879
- let unwrappedType = zodType;
880
- if (zodType instanceof z.ZodOptional) {
881
- unwrappedType = zodType._def.innerType;
882
- }
883
- if (unwrappedType instanceof z.ZodNullable) {
884
- unwrappedType = unwrappedType._def.innerType;
885
- }
886
- if (unwrappedType instanceof z.ZodDefault) {
887
- unwrappedType = unwrappedType._def.innerType;
905
+ if (isSingleRelationField(fieldName, zodType)) {
906
+ return 0;
888
907
  }
889
- if (unwrappedType instanceof z.ZodArray) {
890
- const arrayDef = unwrappedType._def;
891
- if (arrayDef.minLength) {
892
- return arrayDef.minLength.value;
908
+ if (isMultipleRelationField(fieldName, zodType)) {
909
+ let unwrappedType = zodType;
910
+ if (zodType instanceof z.ZodOptional) {
911
+ unwrappedType = zodType._def.innerType;
912
+ }
913
+ if (unwrappedType instanceof z.ZodNullable) {
914
+ unwrappedType = unwrappedType._def.innerType;
915
+ }
916
+ if (unwrappedType instanceof z.ZodDefault) {
917
+ unwrappedType = unwrappedType._def.innerType;
918
+ }
919
+ if (unwrappedType instanceof z.ZodArray) {
920
+ const arrayDef = unwrappedType._def;
921
+ if (arrayDef.minLength) {
922
+ return arrayDef.minLength.value;
923
+ }
893
924
  }
894
925
  }
895
- return void 0;
926
+ return 0;
896
927
  }
897
928
  var POCKETBASE_FIELD_TYPES = [
898
929
  "text",
@@ -1375,13 +1406,32 @@ async function importSchemaModule(filePath, config) {
1375
1406
  if (config?.pathTransformer) {
1376
1407
  importPath = config.pathTransformer(filePath);
1377
1408
  }
1378
- if (!importPath.endsWith(".js")) {
1379
- importPath = `${importPath}.js`;
1409
+ let resolvedPath = null;
1410
+ const jsPath = `${importPath}.js`;
1411
+ const tsPath = `${importPath}.ts`;
1412
+ if (fs2.existsSync(jsPath)) {
1413
+ resolvedPath = jsPath;
1414
+ } else if (fs2.existsSync(tsPath)) {
1415
+ resolvedPath = tsPath;
1416
+ } else {
1417
+ resolvedPath = jsPath;
1380
1418
  }
1381
- const fileUrl = new URL(`file://${path.resolve(importPath)}`);
1419
+ const fileUrl = new URL(`file://${path.resolve(resolvedPath)}`);
1382
1420
  const module = await import(fileUrl.href);
1383
1421
  return module;
1384
1422
  } catch (error) {
1423
+ const tsPath = `${filePath}.ts`;
1424
+ const isTypeScriptFile = fs2.existsSync(tsPath);
1425
+ if (isTypeScriptFile) {
1426
+ throw new SchemaParsingError(
1427
+ `Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
1428
+ Please either:
1429
+ 1. Compile your schema files to JavaScript first, or
1430
+ 2. Use tsx to run the migration tool (e.g., "npx tsx package/dist/cli/migrate.js status" or "tsx package/dist/cli/migrate.js status")`,
1431
+ filePath,
1432
+ error
1433
+ );
1434
+ }
1385
1435
  throw new SchemaParsingError(
1386
1436
  `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1387
1437
  filePath,
@@ -1444,7 +1494,17 @@ function buildFieldDefinition(fieldName, zodType) {
1444
1494
  required,
1445
1495
  options
1446
1496
  };
1447
- if (isRelationField(fieldName, zodType)) {
1497
+ const relationMetadata = extractRelationMetadata(zodType.description);
1498
+ if (relationMetadata) {
1499
+ fieldDef.type = "relation";
1500
+ fieldDef.relation = {
1501
+ collection: relationMetadata.collection,
1502
+ maxSelect: relationMetadata.maxSelect,
1503
+ minSelect: relationMetadata.minSelect,
1504
+ cascadeDelete: relationMetadata.cascadeDelete
1505
+ };
1506
+ fieldDef.options = void 0;
1507
+ } else if (isRelationField(fieldName, zodType)) {
1448
1508
  fieldDef.type = "relation";
1449
1509
  const targetCollection = resolveTargetCollection(fieldName);
1450
1510
  const maxSelect = getMaxSelect(fieldName, zodType);
@@ -1456,6 +1516,13 @@ function buildFieldDefinition(fieldName, zodType) {
1456
1516
  cascadeDelete: false
1457
1517
  // Default to false, can be configured later
1458
1518
  };
1519
+ if (fieldDef.options) {
1520
+ const { min, max, pattern, ...relationSafeOptions } = fieldDef.options;
1521
+ console.log("min", min);
1522
+ console.log("max", max);
1523
+ console.log("pattern", pattern);
1524
+ fieldDef.options = Object.keys(relationSafeOptions).length > 0 ? relationSafeOptions : void 0;
1525
+ }
1459
1526
  }
1460
1527
  return fieldDef;
1461
1528
  }
@@ -1508,11 +1575,12 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1508
1575
  fields,
1509
1576
  indexes,
1510
1577
  rules: {
1511
- listRule: null,
1512
- viewRule: null,
1513
- createRule: null,
1514
- updateRule: null,
1515
- deleteRule: null
1578
+ listRule: permissions?.listRule ?? null,
1579
+ viewRule: permissions?.viewRule ?? null,
1580
+ createRule: permissions?.createRule ?? null,
1581
+ updateRule: permissions?.updateRule ?? null,
1582
+ deleteRule: permissions?.deleteRule ?? null,
1583
+ manageRule: permissions?.manageRule ?? null
1516
1584
  },
1517
1585
  permissions
1518
1586
  };
@@ -1536,7 +1604,12 @@ async function buildSchemaDefinition(config) {
1536
1604
  if (normalizedConfig.pathTransformer) {
1537
1605
  importPath = normalizedConfig.pathTransformer(filePath);
1538
1606
  } else if (mergedConfig.useCompiledFiles) {
1539
- importPath = filePath.replace(/\/src\//, "/dist/");
1607
+ const distPath = filePath.replace(/\/src\//, "/dist/");
1608
+ if (fs2.existsSync(`${distPath}.js`) || fs2.existsSync(`${distPath}.mjs`)) {
1609
+ importPath = distPath;
1610
+ } else {
1611
+ importPath = filePath;
1612
+ }
1540
1613
  }
1541
1614
  const module = await importSchemaModule(importPath, normalizedConfig);
1542
1615
  const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
@@ -1913,6 +1986,7 @@ function convertPocketBaseCollection(pbCollection) {
1913
1986
  if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1914
1987
  if (Object.keys(rules).length > 0) {
1915
1988
  schema.rules = rules;
1989
+ schema.permissions = { ...rules };
1916
1990
  }
1917
1991
  return schema;
1918
1992
  }
@@ -2608,10 +2682,8 @@ var DiffEngine = class {
2608
2682
  var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
2609
2683
  migrate((app) => {
2610
2684
  {{UP_CODE}}
2611
- return true;
2612
2685
  }, (app) => {
2613
2686
  {{DOWN_CODE}}
2614
- return true;
2615
2687
  });
2616
2688
  `;
2617
2689
  var DEFAULT_CONFIG4 = {
@@ -2776,7 +2848,8 @@ function generateFieldDefinitionObject(field) {
2776
2848
  }
2777
2849
  }
2778
2850
  if (field.relation) {
2779
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2851
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
2852
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2780
2853
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2781
2854
  if (field.relation.maxSelect !== void 0) {
2782
2855
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2860,7 +2933,7 @@ function generateIndexesArray(indexes) {
2860
2933
  ${indexStrings.join(",\n ")},
2861
2934
  ]`;
2862
2935
  }
2863
- function generateCollectionCreation(collection, varName = "collection") {
2936
+ function generateCollectionCreation(collection, varName = "collection", isLast = false) {
2864
2937
  const lines = [];
2865
2938
  lines.push(` const ${varName} = new Collection({`);
2866
2939
  lines.push(` name: "${collection.name}",`);
@@ -2876,7 +2949,7 @@ function generateCollectionCreation(collection, varName = "collection") {
2876
2949
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
2877
2950
  lines.push(` });`);
2878
2951
  lines.push(``);
2879
- lines.push(` app.save(${varName});`);
2952
+ lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
2880
2953
  return lines.join("\n");
2881
2954
  }
2882
2955
  function getFieldConstructorName(fieldType) {
@@ -2907,7 +2980,8 @@ function generateFieldConstructorOptions(field) {
2907
2980
  }
2908
2981
  }
2909
2982
  if (field.relation && field.type === "relation") {
2910
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2983
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
2984
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2911
2985
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2912
2986
  if (field.relation.maxSelect !== void 0) {
2913
2987
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2921,7 +2995,7 @@ function generateFieldConstructorOptions(field) {
2921
2995
  }
2922
2996
  return parts.join(",\n");
2923
2997
  }
2924
- function generateFieldAddition(collectionName, field, varName) {
2998
+ function generateFieldAddition(collectionName, field, varName, isLast = false) {
2925
2999
  const lines = [];
2926
3000
  const constructorName = getFieldConstructorName(field.type);
2927
3001
  const collectionVar = varName || `collection_${collectionName}_${field.name}`;
@@ -2931,10 +3005,10 @@ function generateFieldAddition(collectionName, field, varName) {
2931
3005
  lines.push(generateFieldConstructorOptions(field));
2932
3006
  lines.push(` }));`);
2933
3007
  lines.push(``);
2934
- lines.push(` app.save(${collectionVar});`);
3008
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2935
3009
  return lines.join("\n");
2936
3010
  }
2937
- function generateFieldModification(collectionName, modification, varName) {
3011
+ function generateFieldModification(collectionName, modification, varName, isLast = false) {
2938
3012
  const lines = [];
2939
3013
  const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
2940
3014
  const fieldVar = `${collectionVar}_field`;
@@ -2948,7 +3022,8 @@ function generateFieldModification(collectionName, modification, varName) {
2948
3022
  } else if (change.property.startsWith("relation.")) {
2949
3023
  const relationKey = change.property.replace("relation.", "");
2950
3024
  if (relationKey === "collection") {
2951
- const collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
3025
+ const isUsersCollection = String(change.newValue).toLowerCase() === "users";
3026
+ const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
2952
3027
  lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
2953
3028
  } else {
2954
3029
  lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
@@ -2958,10 +3033,10 @@ function generateFieldModification(collectionName, modification, varName) {
2958
3033
  }
2959
3034
  }
2960
3035
  lines.push(``);
2961
- lines.push(` app.save(${collectionVar});`);
3036
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2962
3037
  return lines.join("\n");
2963
3038
  }
2964
- function generateFieldDeletion(collectionName, fieldName, varName) {
3039
+ function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
2965
3040
  const lines = [];
2966
3041
  const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
2967
3042
  const fieldVar = `${collectionVar}_field`;
@@ -2970,18 +3045,18 @@ function generateFieldDeletion(collectionName, fieldName, varName) {
2970
3045
  lines.push(``);
2971
3046
  lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
2972
3047
  lines.push(``);
2973
- lines.push(` app.save(${collectionVar});`);
3048
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2974
3049
  return lines.join("\n");
2975
3050
  }
2976
- function generateIndexAddition(collectionName, index, varName) {
3051
+ function generateIndexAddition(collectionName, index, varName, isLast = false) {
2977
3052
  const lines = [];
2978
3053
  const collectionVar = varName || `collection_${collectionName}_idx`;
2979
3054
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
2980
3055
  lines.push(` ${collectionVar}.indexes.push("${index}");`);
2981
- lines.push(` app.save(${collectionVar});`);
3056
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2982
3057
  return lines.join("\n");
2983
3058
  }
2984
- function generateIndexRemoval(collectionName, index, varName) {
3059
+ function generateIndexRemoval(collectionName, index, varName, isLast = false) {
2985
3060
  const lines = [];
2986
3061
  const collectionVar = varName || `collection_${collectionName}_idx`;
2987
3062
  const indexVar = `${collectionVar}_indexToRemove`;
@@ -2990,29 +3065,29 @@ function generateIndexRemoval(collectionName, index, varName) {
2990
3065
  lines.push(` if (${indexVar} !== -1) {`);
2991
3066
  lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
2992
3067
  lines.push(` }`);
2993
- lines.push(` app.save(${collectionVar});`);
3068
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2994
3069
  return lines.join("\n");
2995
3070
  }
2996
- function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
3071
+ function generateRuleUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
2997
3072
  const lines = [];
2998
3073
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
2999
3074
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3000
3075
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3001
- lines.push(` app.save(${collectionVar});`);
3076
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3002
3077
  return lines.join("\n");
3003
3078
  }
3004
- function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
3079
+ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
3005
3080
  const lines = [];
3006
3081
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3007
3082
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3008
3083
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3009
- lines.push(` app.save(${collectionVar});`);
3084
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3010
3085
  return lines.join("\n");
3011
3086
  }
3012
- function generateCollectionDeletion(collectionName, varName = "collection") {
3087
+ function generateCollectionDeletion(collectionName, varName = "collection", isLast = false) {
3013
3088
  const lines = [];
3014
3089
  lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
3015
- lines.push(` app.delete(${varName});`);
3090
+ lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
3016
3091
  return lines.join("\n");
3017
3092
  }
3018
3093
  function generateUpMigration(diff) {
@@ -3104,7 +3179,24 @@ function generateUpMigration(diff) {
3104
3179
  lines.push(` // No changes detected`);
3105
3180
  lines.push(``);
3106
3181
  }
3107
- return lines.join("\n");
3182
+ let code = lines.join("\n");
3183
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3184
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3185
+ const saveMatches = [...code.matchAll(savePattern)];
3186
+ const deleteMatches = [...code.matchAll(deletePattern)];
3187
+ const allMatches = [
3188
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3189
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3190
+ ].sort((a, b) => b.index - a.index);
3191
+ if (allMatches.length > 0) {
3192
+ const lastMatch = allMatches[0];
3193
+ if (lastMatch.type === "save") {
3194
+ 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);
3195
+ } else {
3196
+ 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);
3197
+ }
3198
+ }
3199
+ return code;
3108
3200
  }
3109
3201
  function generateDownMigration(diff) {
3110
3202
  const lines = [];
@@ -3206,7 +3298,24 @@ function generateDownMigration(diff) {
3206
3298
  lines.push(` // No changes to revert`);
3207
3299
  lines.push(``);
3208
3300
  }
3209
- return lines.join("\n");
3301
+ let code = lines.join("\n");
3302
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3303
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3304
+ const saveMatches = [...code.matchAll(savePattern)];
3305
+ const deleteMatches = [...code.matchAll(deletePattern)];
3306
+ const allMatches = [
3307
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3308
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3309
+ ].sort((a, b) => b.index - a.index);
3310
+ if (allMatches.length > 0) {
3311
+ const lastMatch = allMatches[0];
3312
+ if (lastMatch.type === "save") {
3313
+ 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);
3314
+ } else {
3315
+ 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);
3316
+ }
3317
+ }
3318
+ return code;
3210
3319
  }
3211
3320
  function generate(diff, config) {
3212
3321
  const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;