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
@@ -26,6 +26,35 @@ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
26
26
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
27
 
28
28
  // src/migration/analyzer.ts
29
+ ({
30
+ id: zod.z.string().describe("unique id"),
31
+ collectionId: zod.z.string().describe("collection id"),
32
+ collectionName: zod.z.string().describe("collection name"),
33
+ expand: zod.z.record(zod.z.any()).describe("expandable fields")
34
+ });
35
+ ({
36
+ created: zod.z.string().describe("creation timestamp"),
37
+ updated: zod.z.string().describe("last update timestamp")
38
+ });
39
+ ({
40
+ thumbnailURL: zod.z.string().optional(),
41
+ imageFiles: zod.z.array(zod.z.string())
42
+ });
43
+ ({
44
+ imageFiles: zod.z.array(zod.z.instanceof(File))
45
+ });
46
+ var RELATION_METADATA_KEY = "__pocketbase_relation__";
47
+ function extractRelationMetadata(description) {
48
+ if (!description) return null;
49
+ try {
50
+ const parsed = JSON.parse(description);
51
+ if (parsed[RELATION_METADATA_KEY]) {
52
+ return parsed[RELATION_METADATA_KEY];
53
+ }
54
+ } catch {
55
+ }
56
+ return null;
57
+ }
29
58
 
30
59
  // src/migration/errors.ts
31
60
  var MigrationError = class _MigrationError extends Error {
@@ -216,7 +245,7 @@ Suggestion: ${this.suggestion}`);
216
245
  }
217
246
  };
218
247
 
219
- // src/schema/permission-templates.ts
248
+ // src/utils/permission-templates.ts
220
249
  var PermissionTemplates = {
221
250
  /**
222
251
  * Public access - anyone can perform all operations
@@ -896,26 +925,28 @@ function getMaxSelect(fieldName, zodType) {
896
925
  return 1;
897
926
  }
898
927
  function getMinSelect(fieldName, zodType) {
899
- if (!isMultipleRelationField(fieldName, zodType)) {
900
- return void 0;
901
- }
902
- let unwrappedType = zodType;
903
- if (zodType instanceof zod.z.ZodOptional) {
904
- unwrappedType = zodType._def.innerType;
905
- }
906
- if (unwrappedType instanceof zod.z.ZodNullable) {
907
- unwrappedType = unwrappedType._def.innerType;
908
- }
909
- if (unwrappedType instanceof zod.z.ZodDefault) {
910
- unwrappedType = unwrappedType._def.innerType;
928
+ if (isSingleRelationField(fieldName, zodType)) {
929
+ return 0;
911
930
  }
912
- if (unwrappedType instanceof zod.z.ZodArray) {
913
- const arrayDef = unwrappedType._def;
914
- if (arrayDef.minLength) {
915
- return arrayDef.minLength.value;
931
+ if (isMultipleRelationField(fieldName, zodType)) {
932
+ let unwrappedType = zodType;
933
+ if (zodType instanceof zod.z.ZodOptional) {
934
+ unwrappedType = zodType._def.innerType;
935
+ }
936
+ if (unwrappedType instanceof zod.z.ZodNullable) {
937
+ unwrappedType = unwrappedType._def.innerType;
938
+ }
939
+ if (unwrappedType instanceof zod.z.ZodDefault) {
940
+ unwrappedType = unwrappedType._def.innerType;
941
+ }
942
+ if (unwrappedType instanceof zod.z.ZodArray) {
943
+ const arrayDef = unwrappedType._def;
944
+ if (arrayDef.minLength) {
945
+ return arrayDef.minLength.value;
946
+ }
916
947
  }
917
948
  }
918
- return void 0;
949
+ return 0;
919
950
  }
920
951
  var POCKETBASE_FIELD_TYPES = [
921
952
  "text",
@@ -1398,13 +1429,32 @@ async function importSchemaModule(filePath, config) {
1398
1429
  if (config?.pathTransformer) {
1399
1430
  importPath = config.pathTransformer(filePath);
1400
1431
  }
1401
- if (!importPath.endsWith(".js")) {
1402
- importPath = `${importPath}.js`;
1432
+ let resolvedPath = null;
1433
+ const jsPath = `${importPath}.js`;
1434
+ const tsPath = `${importPath}.ts`;
1435
+ if (fs2__namespace.existsSync(jsPath)) {
1436
+ resolvedPath = jsPath;
1437
+ } else if (fs2__namespace.existsSync(tsPath)) {
1438
+ resolvedPath = tsPath;
1439
+ } else {
1440
+ resolvedPath = jsPath;
1403
1441
  }
1404
- const fileUrl = new URL(`file://${path__namespace.resolve(importPath)}`);
1442
+ const fileUrl = new URL(`file://${path__namespace.resolve(resolvedPath)}`);
1405
1443
  const module = await import(fileUrl.href);
1406
1444
  return module;
1407
1445
  } catch (error) {
1446
+ const tsPath = `${filePath}.ts`;
1447
+ const isTypeScriptFile = fs2__namespace.existsSync(tsPath);
1448
+ if (isTypeScriptFile) {
1449
+ throw new SchemaParsingError(
1450
+ `Failed to import TypeScript schema file. Node.js cannot import TypeScript files directly.
1451
+ Please either:
1452
+ 1. Compile your schema files to JavaScript first, or
1453
+ 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")`,
1454
+ filePath,
1455
+ error
1456
+ );
1457
+ }
1408
1458
  throw new SchemaParsingError(
1409
1459
  `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1410
1460
  filePath,
@@ -1467,7 +1517,17 @@ function buildFieldDefinition(fieldName, zodType) {
1467
1517
  required,
1468
1518
  options
1469
1519
  };
1470
- if (isRelationField(fieldName, zodType)) {
1520
+ const relationMetadata = extractRelationMetadata(zodType.description);
1521
+ if (relationMetadata) {
1522
+ fieldDef.type = "relation";
1523
+ fieldDef.relation = {
1524
+ collection: relationMetadata.collection,
1525
+ maxSelect: relationMetadata.maxSelect,
1526
+ minSelect: relationMetadata.minSelect,
1527
+ cascadeDelete: relationMetadata.cascadeDelete
1528
+ };
1529
+ fieldDef.options = void 0;
1530
+ } else if (isRelationField(fieldName, zodType)) {
1471
1531
  fieldDef.type = "relation";
1472
1532
  const targetCollection = resolveTargetCollection(fieldName);
1473
1533
  const maxSelect = getMaxSelect(fieldName, zodType);
@@ -1479,6 +1539,13 @@ function buildFieldDefinition(fieldName, zodType) {
1479
1539
  cascadeDelete: false
1480
1540
  // Default to false, can be configured later
1481
1541
  };
1542
+ if (fieldDef.options) {
1543
+ const { min, max, pattern, ...relationSafeOptions } = fieldDef.options;
1544
+ console.log("min", min);
1545
+ console.log("max", max);
1546
+ console.log("pattern", pattern);
1547
+ fieldDef.options = Object.keys(relationSafeOptions).length > 0 ? relationSafeOptions : void 0;
1548
+ }
1482
1549
  }
1483
1550
  return fieldDef;
1484
1551
  }
@@ -1531,11 +1598,12 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1531
1598
  fields,
1532
1599
  indexes,
1533
1600
  rules: {
1534
- listRule: null,
1535
- viewRule: null,
1536
- createRule: null,
1537
- updateRule: null,
1538
- deleteRule: null
1601
+ listRule: permissions?.listRule ?? null,
1602
+ viewRule: permissions?.viewRule ?? null,
1603
+ createRule: permissions?.createRule ?? null,
1604
+ updateRule: permissions?.updateRule ?? null,
1605
+ deleteRule: permissions?.deleteRule ?? null,
1606
+ manageRule: permissions?.manageRule ?? null
1539
1607
  },
1540
1608
  permissions
1541
1609
  };
@@ -1559,7 +1627,12 @@ async function buildSchemaDefinition(config) {
1559
1627
  if (normalizedConfig.pathTransformer) {
1560
1628
  importPath = normalizedConfig.pathTransformer(filePath);
1561
1629
  } else if (mergedConfig.useCompiledFiles) {
1562
- importPath = filePath.replace(/\/src\//, "/dist/");
1630
+ const distPath = filePath.replace(/\/src\//, "/dist/");
1631
+ if (fs2__namespace.existsSync(`${distPath}.js`) || fs2__namespace.existsSync(`${distPath}.mjs`)) {
1632
+ importPath = distPath;
1633
+ } else {
1634
+ importPath = filePath;
1635
+ }
1563
1636
  }
1564
1637
  const module = await importSchemaModule(importPath, normalizedConfig);
1565
1638
  const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
@@ -1936,6 +2009,7 @@ function convertPocketBaseCollection(pbCollection) {
1936
2009
  if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
1937
2010
  if (Object.keys(rules).length > 0) {
1938
2011
  schema.rules = rules;
2012
+ schema.permissions = { ...rules };
1939
2013
  }
1940
2014
  return schema;
1941
2015
  }
@@ -2631,10 +2705,8 @@ var DiffEngine = class {
2631
2705
  var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
2632
2706
  migrate((app) => {
2633
2707
  {{UP_CODE}}
2634
- return true;
2635
2708
  }, (app) => {
2636
2709
  {{DOWN_CODE}}
2637
- return true;
2638
2710
  });
2639
2711
  `;
2640
2712
  var DEFAULT_CONFIG4 = {
@@ -2799,7 +2871,8 @@ function generateFieldDefinitionObject(field) {
2799
2871
  }
2800
2872
  }
2801
2873
  if (field.relation) {
2802
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2874
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
2875
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2803
2876
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2804
2877
  if (field.relation.maxSelect !== void 0) {
2805
2878
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2883,7 +2956,7 @@ function generateIndexesArray(indexes) {
2883
2956
  ${indexStrings.join(",\n ")},
2884
2957
  ]`;
2885
2958
  }
2886
- function generateCollectionCreation(collection, varName = "collection") {
2959
+ function generateCollectionCreation(collection, varName = "collection", isLast = false) {
2887
2960
  const lines = [];
2888
2961
  lines.push(` const ${varName} = new Collection({`);
2889
2962
  lines.push(` name: "${collection.name}",`);
@@ -2899,7 +2972,7 @@ function generateCollectionCreation(collection, varName = "collection") {
2899
2972
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
2900
2973
  lines.push(` });`);
2901
2974
  lines.push(``);
2902
- lines.push(` app.save(${varName});`);
2975
+ lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
2903
2976
  return lines.join("\n");
2904
2977
  }
2905
2978
  function getFieldConstructorName(fieldType) {
@@ -2930,7 +3003,8 @@ function generateFieldConstructorOptions(field) {
2930
3003
  }
2931
3004
  }
2932
3005
  if (field.relation && field.type === "relation") {
2933
- const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
3006
+ const isUsersCollection = field.relation.collection.toLowerCase() === "users";
3007
+ const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
2934
3008
  parts.push(` collectionId: ${collectionIdPlaceholder}`);
2935
3009
  if (field.relation.maxSelect !== void 0) {
2936
3010
  parts.push(` maxSelect: ${field.relation.maxSelect}`);
@@ -2944,7 +3018,7 @@ function generateFieldConstructorOptions(field) {
2944
3018
  }
2945
3019
  return parts.join(",\n");
2946
3020
  }
2947
- function generateFieldAddition(collectionName, field, varName) {
3021
+ function generateFieldAddition(collectionName, field, varName, isLast = false) {
2948
3022
  const lines = [];
2949
3023
  const constructorName = getFieldConstructorName(field.type);
2950
3024
  const collectionVar = varName || `collection_${collectionName}_${field.name}`;
@@ -2954,10 +3028,10 @@ function generateFieldAddition(collectionName, field, varName) {
2954
3028
  lines.push(generateFieldConstructorOptions(field));
2955
3029
  lines.push(` }));`);
2956
3030
  lines.push(``);
2957
- lines.push(` app.save(${collectionVar});`);
3031
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2958
3032
  return lines.join("\n");
2959
3033
  }
2960
- function generateFieldModification(collectionName, modification, varName) {
3034
+ function generateFieldModification(collectionName, modification, varName, isLast = false) {
2961
3035
  const lines = [];
2962
3036
  const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
2963
3037
  const fieldVar = `${collectionVar}_field`;
@@ -2971,7 +3045,8 @@ function generateFieldModification(collectionName, modification, varName) {
2971
3045
  } else if (change.property.startsWith("relation.")) {
2972
3046
  const relationKey = change.property.replace("relation.", "");
2973
3047
  if (relationKey === "collection") {
2974
- const collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
3048
+ const isUsersCollection = String(change.newValue).toLowerCase() === "users";
3049
+ const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
2975
3050
  lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
2976
3051
  } else {
2977
3052
  lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
@@ -2981,10 +3056,10 @@ function generateFieldModification(collectionName, modification, varName) {
2981
3056
  }
2982
3057
  }
2983
3058
  lines.push(``);
2984
- lines.push(` app.save(${collectionVar});`);
3059
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2985
3060
  return lines.join("\n");
2986
3061
  }
2987
- function generateFieldDeletion(collectionName, fieldName, varName) {
3062
+ function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
2988
3063
  const lines = [];
2989
3064
  const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
2990
3065
  const fieldVar = `${collectionVar}_field`;
@@ -2993,18 +3068,18 @@ function generateFieldDeletion(collectionName, fieldName, varName) {
2993
3068
  lines.push(``);
2994
3069
  lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
2995
3070
  lines.push(``);
2996
- lines.push(` app.save(${collectionVar});`);
3071
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
2997
3072
  return lines.join("\n");
2998
3073
  }
2999
- function generateIndexAddition(collectionName, index, varName) {
3074
+ function generateIndexAddition(collectionName, index, varName, isLast = false) {
3000
3075
  const lines = [];
3001
3076
  const collectionVar = varName || `collection_${collectionName}_idx`;
3002
3077
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3003
3078
  lines.push(` ${collectionVar}.indexes.push("${index}");`);
3004
- lines.push(` app.save(${collectionVar});`);
3079
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3005
3080
  return lines.join("\n");
3006
3081
  }
3007
- function generateIndexRemoval(collectionName, index, varName) {
3082
+ function generateIndexRemoval(collectionName, index, varName, isLast = false) {
3008
3083
  const lines = [];
3009
3084
  const collectionVar = varName || `collection_${collectionName}_idx`;
3010
3085
  const indexVar = `${collectionVar}_indexToRemove`;
@@ -3013,29 +3088,29 @@ function generateIndexRemoval(collectionName, index, varName) {
3013
3088
  lines.push(` if (${indexVar} !== -1) {`);
3014
3089
  lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
3015
3090
  lines.push(` }`);
3016
- lines.push(` app.save(${collectionVar});`);
3091
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3017
3092
  return lines.join("\n");
3018
3093
  }
3019
- function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
3094
+ function generateRuleUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
3020
3095
  const lines = [];
3021
3096
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3022
3097
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3023
3098
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3024
- lines.push(` app.save(${collectionVar});`);
3099
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3025
3100
  return lines.join("\n");
3026
3101
  }
3027
- function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
3102
+ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, isLast = false) {
3028
3103
  const lines = [];
3029
3104
  const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3030
3105
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3031
3106
  lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3032
- lines.push(` app.save(${collectionVar});`);
3107
+ lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
3033
3108
  return lines.join("\n");
3034
3109
  }
3035
- function generateCollectionDeletion(collectionName, varName = "collection") {
3110
+ function generateCollectionDeletion(collectionName, varName = "collection", isLast = false) {
3036
3111
  const lines = [];
3037
3112
  lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
3038
- lines.push(` app.delete(${varName});`);
3113
+ lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
3039
3114
  return lines.join("\n");
3040
3115
  }
3041
3116
  function generateUpMigration(diff) {
@@ -3127,7 +3202,24 @@ function generateUpMigration(diff) {
3127
3202
  lines.push(` // No changes detected`);
3128
3203
  lines.push(``);
3129
3204
  }
3130
- return lines.join("\n");
3205
+ let code = lines.join("\n");
3206
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3207
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3208
+ const saveMatches = [...code.matchAll(savePattern)];
3209
+ const deleteMatches = [...code.matchAll(deletePattern)];
3210
+ const allMatches = [
3211
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3212
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3213
+ ].sort((a, b) => b.index - a.index);
3214
+ if (allMatches.length > 0) {
3215
+ const lastMatch = allMatches[0];
3216
+ if (lastMatch.type === "save") {
3217
+ 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);
3218
+ } else {
3219
+ 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);
3220
+ }
3221
+ }
3222
+ return code;
3131
3223
  }
3132
3224
  function generateDownMigration(diff) {
3133
3225
  const lines = [];
@@ -3229,7 +3321,24 @@ function generateDownMigration(diff) {
3229
3321
  lines.push(` // No changes to revert`);
3230
3322
  lines.push(``);
3231
3323
  }
3232
- return lines.join("\n");
3324
+ let code = lines.join("\n");
3325
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
3326
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
3327
+ const saveMatches = [...code.matchAll(savePattern)];
3328
+ const deleteMatches = [...code.matchAll(deletePattern)];
3329
+ const allMatches = [
3330
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
3331
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
3332
+ ].sort((a, b) => b.index - a.index);
3333
+ if (allMatches.length > 0) {
3334
+ const lastMatch = allMatches[0];
3335
+ if (lastMatch.type === "save") {
3336
+ 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);
3337
+ } else {
3338
+ 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);
3339
+ }
3340
+ }
3341
+ return code;
3233
3342
  }
3234
3343
  function generate(diff, config) {
3235
3344
  const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;