pocketbase-zod-schema 0.3.1 → 0.3.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 (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/index.cjs +64 -38
  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 +64 -38
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +64 -38
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +64 -38
  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-RVj26U-O.d.cts → fields-DBBm06VU.d.cts} +34 -7
  15. package/dist/{fields-RVj26U-O.d.ts → fields-DBBm06VU.d.ts} +34 -7
  16. package/dist/index.cjs +167 -75
  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 -75
  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 +10 -1
  27. package/dist/migration/diff.cjs.map +1 -1
  28. package/dist/migration/diff.d.cts +2 -2
  29. package/dist/migration/diff.d.ts +2 -2
  30. package/dist/migration/diff.js +10 -1
  31. package/dist/migration/diff.js.map +1 -1
  32. package/dist/migration/generator.cjs +102 -68
  33. package/dist/migration/generator.cjs.map +1 -1
  34. package/dist/migration/generator.d.cts +3 -3
  35. package/dist/migration/generator.d.ts +3 -3
  36. package/dist/migration/generator.js +102 -68
  37. package/dist/migration/generator.js.map +1 -1
  38. package/dist/migration/index.cjs +112 -69
  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 +112 -69
  43. package/dist/migration/index.js.map +1 -1
  44. package/dist/migration/snapshot.d.cts +2 -2
  45. package/dist/migration/snapshot.d.ts +2 -2
  46. package/dist/migration/utils/index.cjs.map +1 -1
  47. package/dist/migration/utils/index.d.cts +2 -2
  48. package/dist/migration/utils/index.d.ts +2 -2
  49. package/dist/migration/utils/index.js.map +1 -1
  50. package/dist/schema.cjs +55 -6
  51. package/dist/schema.cjs.map +1 -1
  52. package/dist/schema.d.cts +1 -1
  53. package/dist/schema.d.ts +1 -1
  54. package/dist/schema.js +55 -6
  55. package/dist/schema.js.map +1 -1
  56. package/dist/{type-mapper-DaBe-1ph.d.cts → type-mapper-DsGgZwUo.d.cts} +1 -1
  57. package/dist/{type-mapper-CZzVeDj7.d.ts → type-mapper-Dvh4QTM-.d.ts} +1 -1
  58. package/dist/{types-CUVzgZ9k.d.ts → types-BWhwQxG-.d.ts} +6 -1
  59. package/dist/{types-D-Fsdn_O.d.cts → types-d0yBwHoN.d.cts} +6 -1
  60. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { Ora } from 'ora';
2
- import { e as SchemaDiff } from '../../types-D-Fsdn_O.cjs';
3
- import '../../fields-RVj26U-O.cjs';
2
+ import { e as SchemaDiff } from '../../types-d0yBwHoN.cjs';
3
+ import '../../fields-DBBm06VU.cjs';
4
4
  import 'zod';
5
5
  import '../../permissions-ZHafVSIx.cjs';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { Ora } from 'ora';
2
- import { e as SchemaDiff } from '../../types-CUVzgZ9k.js';
3
- import '../../fields-RVj26U-O.js';
2
+ import { e as SchemaDiff } from '../../types-BWhwQxG-.js';
3
+ import '../../fields-DBBm06VU.js';
4
4
  import 'zod';
5
5
  import '../../permissions-ZHafVSIx.js';
6
6
 
@@ -116,6 +116,15 @@ interface SelectFieldOptions {
116
116
  */
117
117
  maxSelect?: number;
118
118
  }
119
+ /**
120
+ * Human-friendly byte size input.
121
+ *
122
+ * - Use a number for raw bytes (e.g. `5242880`)
123
+ * - Use a string with unit suffix for kibibytes/mebibytes/gibibytes (e.g. `"5M"`, `"1G"`)
124
+ *
125
+ * Supported suffixes: `K`, `M`, `G` (case-insensitive).
126
+ */
127
+ type ByteSize = number | `${number}${"K" | "M" | "G" | "k" | "m" | "g"}`;
119
128
  /**
120
129
  * File field configuration options
121
130
  */
@@ -126,9 +135,19 @@ interface FileFieldOptions {
126
135
  */
127
136
  mimeTypes?: string[];
128
137
  /**
129
- * Maximum file size in bytes
138
+ * Maximum file size.
139
+ *
140
+ * - Provide a number for raw bytes
141
+ * - Or use a string with `K`, `M`, `G` suffix (case-insensitive)
142
+ *
143
+ * Max allowed is `8G`.
144
+ *
145
+ * @example
146
+ * maxSize: 5242880
147
+ * maxSize: "5M"
148
+ * maxSize: "1G"
130
149
  */
131
- maxSize?: number;
150
+ maxSize?: ByteSize;
132
151
  /**
133
152
  * Thumbnail sizes to generate
134
153
  * Example: ["100x100", "200x200"]
@@ -299,29 +318,37 @@ declare function SelectField<T extends [string, ...string[]]>(values: T, options
299
318
  * Maps to PocketBase 'file' field type with maxSelect=1
300
319
  *
301
320
  * @param options - Optional file constraints
302
- * @returns Zod File schema with PocketBase metadata
321
+ * @returns Zod schema that accepts File on input and returns string when reading from database
303
322
  *
304
323
  * @example
305
324
  * const ProductSchema = z.object({
306
325
  * thumbnail: FileField({ mimeTypes: ["image/*"], maxSize: 5242880 }),
307
326
  * document: FileField({ mimeTypes: ["application/pdf"] }),
308
327
  * });
328
+ *
329
+ * @remarks
330
+ * - When creating/updating records: accepts File objects
331
+ * - When reading from PocketBase: returns string (filename)
309
332
  */
310
- declare function FileField(options?: FileFieldOptions): z.ZodType<File>;
333
+ declare function FileField(options?: FileFieldOptions): z.ZodType<string, z.ZodTypeDef, File | string>;
311
334
  /**
312
335
  * Creates a multiple files field schema
313
336
  * Maps to PocketBase 'file' field type with maxSelect>1
314
337
  *
315
338
  * @param options - Optional file constraints
316
- * @returns Zod array of File schema with PocketBase metadata
339
+ * @returns Zod array schema that accepts File[] on input and returns string[] when reading from database
317
340
  *
318
341
  * @example
319
342
  * const ProductSchema = z.object({
320
343
  * images: FilesField({ mimeTypes: ["image/*"], maxSelect: 5 }),
321
344
  * attachments: FilesField({ minSelect: 1, maxSelect: 10 }),
322
345
  * });
346
+ *
347
+ * @remarks
348
+ * - When creating/updating records: accepts File[]
349
+ * - When reading from PocketBase: returns string[] (filenames)
323
350
  */
324
- declare function FilesField(options?: FilesFieldOptions): z.ZodArray<z.ZodType<File>>;
351
+ declare function FilesField(options?: FilesFieldOptions): z.ZodType<string[], z.ZodTypeDef, (File | string)[]>;
325
352
  /**
326
353
  * Creates a JSON field schema with optional inner schema validation
327
354
  * Maps to PocketBase 'json' field type
@@ -362,4 +389,4 @@ declare function GeoPointField(): z.ZodObject<{
362
389
  lat: z.ZodNumber;
363
390
  }>;
364
391
 
365
- export { type AutodateFieldOptions as A, BoolField as B, type DateFieldOptions as D, EmailField as E, FIELD_METADATA_KEY as F, GeoPointField as G, JSONField as J, type NumberFieldOptions as N, type PocketBaseFieldType as P, type SelectFieldOptions as S, type TextFieldOptions as T, URLField as U, type FieldMetadata as a, type FileFieldOptions as b, type FilesFieldOptions as c, NumberField as d, extractFieldMetadata as e, TextField as f, EditorField as g, DateField as h, AutodateField as i, SelectField as j, FileField as k, FilesField as l };
392
+ export { type AutodateFieldOptions as A, type ByteSize as B, type DateFieldOptions as D, EmailField as E, FIELD_METADATA_KEY as F, GeoPointField as G, JSONField as J, type NumberFieldOptions as N, type PocketBaseFieldType as P, type SelectFieldOptions as S, type TextFieldOptions as T, URLField as U, type FieldMetadata as a, type FileFieldOptions as b, type FilesFieldOptions as c, BoolField as d, extractFieldMetadata as e, NumberField as f, TextField as g, EditorField as h, DateField as i, AutodateField as j, SelectField as k, FileField as l, FilesField as m };
@@ -116,6 +116,15 @@ interface SelectFieldOptions {
116
116
  */
117
117
  maxSelect?: number;
118
118
  }
119
+ /**
120
+ * Human-friendly byte size input.
121
+ *
122
+ * - Use a number for raw bytes (e.g. `5242880`)
123
+ * - Use a string with unit suffix for kibibytes/mebibytes/gibibytes (e.g. `"5M"`, `"1G"`)
124
+ *
125
+ * Supported suffixes: `K`, `M`, `G` (case-insensitive).
126
+ */
127
+ type ByteSize = number | `${number}${"K" | "M" | "G" | "k" | "m" | "g"}`;
119
128
  /**
120
129
  * File field configuration options
121
130
  */
@@ -126,9 +135,19 @@ interface FileFieldOptions {
126
135
  */
127
136
  mimeTypes?: string[];
128
137
  /**
129
- * Maximum file size in bytes
138
+ * Maximum file size.
139
+ *
140
+ * - Provide a number for raw bytes
141
+ * - Or use a string with `K`, `M`, `G` suffix (case-insensitive)
142
+ *
143
+ * Max allowed is `8G`.
144
+ *
145
+ * @example
146
+ * maxSize: 5242880
147
+ * maxSize: "5M"
148
+ * maxSize: "1G"
130
149
  */
131
- maxSize?: number;
150
+ maxSize?: ByteSize;
132
151
  /**
133
152
  * Thumbnail sizes to generate
134
153
  * Example: ["100x100", "200x200"]
@@ -299,29 +318,37 @@ declare function SelectField<T extends [string, ...string[]]>(values: T, options
299
318
  * Maps to PocketBase 'file' field type with maxSelect=1
300
319
  *
301
320
  * @param options - Optional file constraints
302
- * @returns Zod File schema with PocketBase metadata
321
+ * @returns Zod schema that accepts File on input and returns string when reading from database
303
322
  *
304
323
  * @example
305
324
  * const ProductSchema = z.object({
306
325
  * thumbnail: FileField({ mimeTypes: ["image/*"], maxSize: 5242880 }),
307
326
  * document: FileField({ mimeTypes: ["application/pdf"] }),
308
327
  * });
328
+ *
329
+ * @remarks
330
+ * - When creating/updating records: accepts File objects
331
+ * - When reading from PocketBase: returns string (filename)
309
332
  */
310
- declare function FileField(options?: FileFieldOptions): z.ZodType<File>;
333
+ declare function FileField(options?: FileFieldOptions): z.ZodType<string, z.ZodTypeDef, File | string>;
311
334
  /**
312
335
  * Creates a multiple files field schema
313
336
  * Maps to PocketBase 'file' field type with maxSelect>1
314
337
  *
315
338
  * @param options - Optional file constraints
316
- * @returns Zod array of File schema with PocketBase metadata
339
+ * @returns Zod array schema that accepts File[] on input and returns string[] when reading from database
317
340
  *
318
341
  * @example
319
342
  * const ProductSchema = z.object({
320
343
  * images: FilesField({ mimeTypes: ["image/*"], maxSelect: 5 }),
321
344
  * attachments: FilesField({ minSelect: 1, maxSelect: 10 }),
322
345
  * });
346
+ *
347
+ * @remarks
348
+ * - When creating/updating records: accepts File[]
349
+ * - When reading from PocketBase: returns string[] (filenames)
323
350
  */
324
- declare function FilesField(options?: FilesFieldOptions): z.ZodArray<z.ZodType<File>>;
351
+ declare function FilesField(options?: FilesFieldOptions): z.ZodType<string[], z.ZodTypeDef, (File | string)[]>;
325
352
  /**
326
353
  * Creates a JSON field schema with optional inner schema validation
327
354
  * Maps to PocketBase 'json' field type
@@ -362,4 +389,4 @@ declare function GeoPointField(): z.ZodObject<{
362
389
  lat: z.ZodNumber;
363
390
  }>;
364
391
 
365
- export { type AutodateFieldOptions as A, BoolField as B, type DateFieldOptions as D, EmailField as E, FIELD_METADATA_KEY as F, GeoPointField as G, JSONField as J, type NumberFieldOptions as N, type PocketBaseFieldType as P, type SelectFieldOptions as S, type TextFieldOptions as T, URLField as U, type FieldMetadata as a, type FileFieldOptions as b, type FilesFieldOptions as c, NumberField as d, extractFieldMetadata as e, TextField as f, EditorField as g, DateField as h, AutodateField as i, SelectField as j, FileField as k, FilesField as l };
392
+ export { type AutodateFieldOptions as A, type ByteSize as B, type DateFieldOptions as D, EmailField as E, FIELD_METADATA_KEY as F, GeoPointField as G, JSONField as J, type NumberFieldOptions as N, type PocketBaseFieldType as P, type SelectFieldOptions as S, type TextFieldOptions as T, URLField as U, type FieldMetadata as a, type FileFieldOptions as b, type FilesFieldOptions as c, BoolField as d, extractFieldMetadata as e, NumberField as f, TextField as g, EditorField as h, DateField as i, AutodateField as j, SelectField as k, FileField as l, FilesField as m };
package/dist/index.cjs CHANGED
@@ -488,6 +488,45 @@ function extractFieldMetadata(description) {
488
488
  }
489
489
  return null;
490
490
  }
491
+ var MAX_FILE_SIZE_BYTES = 8 * 1024 * 1024 * 1024;
492
+ function parseByteSizeToBytes(value, context) {
493
+ let bytes;
494
+ if (typeof value === "number") {
495
+ if (!Number.isFinite(value)) {
496
+ throw new Error(`${context}: maxSize must be a finite number of bytes`);
497
+ }
498
+ bytes = Math.round(value);
499
+ } else {
500
+ const trimmed = value.trim();
501
+ const match = /^(\d+(?:\.\d+)?)\s*([KMG])$/i.exec(trimmed);
502
+ if (!match) {
503
+ throw new Error(`${context}: maxSize string must be like "10K", "5M", or "1G" (case-insensitive)`);
504
+ }
505
+ const amount = Number(match[1]);
506
+ const unit = match[2].toUpperCase();
507
+ if (!Number.isFinite(amount)) {
508
+ throw new Error(`${context}: maxSize must be a valid number`);
509
+ }
510
+ const multiplier = unit === "K" ? 1024 : unit === "M" ? 1024 * 1024 : 1024 * 1024 * 1024;
511
+ bytes = Math.round(amount * multiplier);
512
+ }
513
+ if (bytes < 0) {
514
+ throw new Error(`${context}: maxSize must be >= 0`);
515
+ }
516
+ if (bytes > MAX_FILE_SIZE_BYTES) {
517
+ throw new Error(`${context}: maxSize cannot exceed 8G (${MAX_FILE_SIZE_BYTES} bytes)`);
518
+ }
519
+ return bytes;
520
+ }
521
+ function normalizeFileFieldOptions(options, context) {
522
+ if (!options) return options;
523
+ if (options.maxSize === void 0) return options;
524
+ return {
525
+ ...options,
526
+ // PocketBase expects bytes; normalize any human-friendly inputs to bytes here.
527
+ maxSize: parseByteSizeToBytes(options.maxSize, context)
528
+ };
529
+ }
491
530
  function BoolField() {
492
531
  const metadata = {
493
532
  [FIELD_METADATA_KEY]: {
@@ -610,11 +649,14 @@ function SelectField(values, options) {
610
649
  return enumSchema.describe(JSON.stringify(metadata));
611
650
  }
612
651
  function FileField(options) {
613
- const schema = zod.z.instanceof(File);
652
+ const schema = zod.z.preprocess((val) => {
653
+ return val instanceof File ? val.name || "" : val;
654
+ }, zod.z.string());
655
+ const normalizedOptions = normalizeFileFieldOptions(options, "FileField");
614
656
  const metadata = {
615
657
  [FIELD_METADATA_KEY]: {
616
658
  type: "file",
617
- options: options || {}
659
+ options: normalizedOptions || {}
618
660
  }
619
661
  };
620
662
  return schema.describe(JSON.stringify(metadata));
@@ -625,17 +667,24 @@ function FilesField(options) {
625
667
  throw new Error("FilesField: minSelect cannot be greater than maxSelect");
626
668
  }
627
669
  }
628
- let schema = zod.z.array(zod.z.instanceof(File));
670
+ let baseArraySchema = zod.z.array(zod.z.string());
629
671
  if (options?.minSelect !== void 0) {
630
- schema = schema.min(options.minSelect);
672
+ baseArraySchema = baseArraySchema.min(options.minSelect);
631
673
  }
632
674
  if (options?.maxSelect !== void 0) {
633
- schema = schema.max(options.maxSelect);
675
+ baseArraySchema = baseArraySchema.max(options.maxSelect);
634
676
  }
677
+ const schema = zod.z.preprocess((val) => {
678
+ if (Array.isArray(val)) {
679
+ return val.map((item) => item instanceof File ? item.name || "" : item);
680
+ }
681
+ return val;
682
+ }, baseArraySchema);
683
+ const normalizedOptions = normalizeFileFieldOptions(options, "FilesField");
635
684
  const metadata = {
636
685
  [FIELD_METADATA_KEY]: {
637
686
  type: "file",
638
- options: options || {}
687
+ options: normalizedOptions || {}
639
688
  }
640
689
  };
641
690
  return schema.describe(JSON.stringify(metadata));
@@ -3799,10 +3848,19 @@ function aggregateChanges(currentSchema, previousSnapshot, config) {
3799
3848
  collectionsToModify.push(modification);
3800
3849
  }
3801
3850
  }
3851
+ const existingCollectionIds = /* @__PURE__ */ new Map();
3852
+ if (previousSnapshot) {
3853
+ for (const [name, collection] of previousSnapshot.collections) {
3854
+ if (collection.id) {
3855
+ existingCollectionIds.set(name, collection.id);
3856
+ }
3857
+ }
3858
+ }
3802
3859
  return {
3803
3860
  collectionsToCreate: collectionsWithIds,
3804
3861
  collectionsToDelete: filteredCollectionsToDelete,
3805
- collectionsToModify
3862
+ collectionsToModify,
3863
+ existingCollectionIds
3806
3864
  };
3807
3865
  }
3808
3866
  function detectDestructiveChanges(diff, config) {
@@ -4195,7 +4253,7 @@ function formatValue(value) {
4195
4253
  return JSON.stringify(value).replace(/","/g, '", "');
4196
4254
  }
4197
4255
  if (typeof value === "object") {
4198
- const entries = Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
4256
+ const entries = Object.entries(value).filter(([_k, v]) => v !== void 0).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
4199
4257
  return `{ ${entries} }`;
4200
4258
  }
4201
4259
  return String(value);
@@ -4458,7 +4516,7 @@ function generateFieldAddition(collectionName, field, varName, isLast = false, c
4458
4516
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4459
4517
  return lines.join("\n");
4460
4518
  }
4461
- function generateFieldModification(collectionName, modification, varName, isLast = false) {
4519
+ function generateFieldModification(collectionName, modification, varName, isLast = false, collectionIdMap) {
4462
4520
  const lines = [];
4463
4521
  const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
4464
4522
  const fieldVar = `${collectionVar}_field`;
@@ -4473,7 +4531,14 @@ function generateFieldModification(collectionName, modification, varName, isLast
4473
4531
  const relationKey = change.property.replace("relation.", "");
4474
4532
  if (relationKey === "collection") {
4475
4533
  const isUsersCollection = String(change.newValue).toLowerCase() === "users";
4476
- const collectionIdValue = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
4534
+ let collectionIdValue;
4535
+ if (isUsersCollection) {
4536
+ collectionIdValue = '"_pb_users_auth_"';
4537
+ } else if (collectionIdMap && collectionIdMap.has(String(change.newValue))) {
4538
+ collectionIdValue = `"${collectionIdMap.get(String(change.newValue))}"`;
4539
+ } else {
4540
+ collectionIdValue = `app.findCollectionByNameOrId("${change.newValue}").id`;
4541
+ }
4477
4542
  lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
4478
4543
  } else {
4479
4544
  lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
@@ -4489,11 +4554,9 @@ function generateFieldModification(collectionName, modification, varName, isLast
4489
4554
  function generateFieldDeletion(collectionName, fieldName, varName, isLast = false) {
4490
4555
  const lines = [];
4491
4556
  const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
4492
- const fieldVar = `${collectionVar}_field`;
4493
4557
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
4494
- lines.push(` const ${fieldVar} = ${collectionVar}.fields.getByName("${fieldName}");`);
4495
4558
  lines.push(``);
4496
- lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
4559
+ lines.push(` ${collectionVar}.fields.removeByName("${fieldName}");`);
4497
4560
  lines.push(``);
4498
4561
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4499
4562
  return lines.join("\n");
@@ -4551,9 +4614,10 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4551
4614
  const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4552
4615
  let operationCount = 0;
4553
4616
  const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
4554
- for (const field of modification.fieldsToAdd) {
4617
+ for (let i = 0; i < modification.fieldsToAdd.length; i++) {
4618
+ const field = modification.fieldsToAdd[i];
4555
4619
  operationCount++;
4556
- const varName = `collection_${collectionName}_add_${field.name}`;
4620
+ const varName = `collection_${collectionName}_add_${field.name}_${i}`;
4557
4621
  const isLast = operationCount === totalOperations;
4558
4622
  lines.push(generateFieldAddition(collectionName, field, varName, isLast, collectionIdMap));
4559
4623
  if (!isLast) lines.push("");
@@ -4562,7 +4626,7 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4562
4626
  operationCount++;
4563
4627
  const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
4564
4628
  const isLast = operationCount === totalOperations;
4565
- lines.push(generateFieldModification(collectionName, fieldMod, varName, isLast));
4629
+ lines.push(generateFieldModification(collectionName, fieldMod, varName, isLast, collectionIdMap));
4566
4630
  if (!isLast) lines.push("");
4567
4631
  }
4568
4632
  for (const field of modification.fieldsToRemove) {
@@ -4611,20 +4675,23 @@ function generateOperationUpMigration(operation, collectionIdMap) {
4611
4675
  lines.push(generateCollectionDeletion(collectionName, varName, true));
4612
4676
  }
4613
4677
  let code = lines.join("\n");
4614
- const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4615
- const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4616
- const saveMatches = [...code.matchAll(savePattern)];
4617
- const deleteMatches = [...code.matchAll(deletePattern)];
4618
- const allMatches = [
4619
- ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4620
- ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4621
- ].sort((a, b) => b.index - a.index);
4622
- if (allMatches.length > 0) {
4623
- const lastMatch = allMatches[0];
4624
- if (lastMatch.type === "save") {
4625
- 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);
4626
- } else {
4627
- 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);
4678
+ const hasReturnStatement = /return\s+app\.(save|delete)\(/m.test(code);
4679
+ if (!hasReturnStatement) {
4680
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4681
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4682
+ const saveMatches = [...code.matchAll(savePattern)];
4683
+ const deleteMatches = [...code.matchAll(deletePattern)];
4684
+ const allMatches = [
4685
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4686
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4687
+ ].sort((a, b) => b.index - a.index);
4688
+ if (allMatches.length > 0) {
4689
+ const lastMatch = allMatches[0];
4690
+ if (lastMatch.type === "save") {
4691
+ 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);
4692
+ } else {
4693
+ 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);
4694
+ }
4628
4695
  }
4629
4696
  }
4630
4697
  return code;
@@ -4713,20 +4780,23 @@ function generateOperationDownMigration(operation, collectionIdMap) {
4713
4780
  }
4714
4781
  }
4715
4782
  let code = lines.join("\n");
4716
- const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4717
- const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4718
- const saveMatches = [...code.matchAll(savePattern)];
4719
- const deleteMatches = [...code.matchAll(deletePattern)];
4720
- const allMatches = [
4721
- ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4722
- ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4723
- ].sort((a, b) => b.index - a.index);
4724
- if (allMatches.length > 0) {
4725
- const lastMatch = allMatches[0];
4726
- if (lastMatch.type === "save") {
4727
- 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);
4728
- } else {
4729
- 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);
4783
+ const hasReturnStatement = /return\s+app\.(save|delete)\(/m.test(code);
4784
+ if (!hasReturnStatement) {
4785
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4786
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4787
+ const saveMatches = [...code.matchAll(savePattern)];
4788
+ const deleteMatches = [...code.matchAll(deletePattern)];
4789
+ const allMatches = [
4790
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4791
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4792
+ ].sort((a, b) => b.index - a.index);
4793
+ if (allMatches.length > 0) {
4794
+ const lastMatch = allMatches[0];
4795
+ if (lastMatch.type === "save") {
4796
+ 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);
4797
+ } else {
4798
+ 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);
4799
+ }
4730
4800
  }
4731
4801
  }
4732
4802
  return code;
@@ -4741,6 +4811,11 @@ function generateUpMigration(diff) {
4741
4811
  collectionIdMap.set(collection.name, collection.id);
4742
4812
  }
4743
4813
  }
4814
+ if (diff.existingCollectionIds) {
4815
+ for (const [name, id] of diff.existingCollectionIds) {
4816
+ collectionIdMap.set(name, id);
4817
+ }
4818
+ }
4744
4819
  if (diff.collectionsToCreate.length > 0) {
4745
4820
  lines.push(` // Create new collections`);
4746
4821
  for (let i = 0; i < diff.collectionsToCreate.length; i++) {
@@ -4756,8 +4831,9 @@ function generateUpMigration(diff) {
4756
4831
  const collectionName = modification.collection;
4757
4832
  if (modification.fieldsToAdd.length > 0) {
4758
4833
  lines.push(` // Add fields to ${collectionName}`);
4759
- for (const field of modification.fieldsToAdd) {
4760
- const varName = `collection_${collectionName}_add_${field.name}`;
4834
+ for (let i = 0; i < modification.fieldsToAdd.length; i++) {
4835
+ const field = modification.fieldsToAdd[i];
4836
+ const varName = `collection_${collectionName}_add_${field.name}_${i}`;
4761
4837
  lines.push(generateFieldAddition(collectionName, field, varName, false, collectionIdMap));
4762
4838
  lines.push(``);
4763
4839
  }
@@ -4766,7 +4842,7 @@ function generateUpMigration(diff) {
4766
4842
  lines.push(` // Modify fields in ${collectionName}`);
4767
4843
  for (const fieldMod of modification.fieldsToModify) {
4768
4844
  const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
4769
- lines.push(generateFieldModification(collectionName, fieldMod, varName));
4845
+ lines.push(generateFieldModification(collectionName, fieldMod, varName, false, collectionIdMap));
4770
4846
  lines.push(``);
4771
4847
  }
4772
4848
  }
@@ -4827,20 +4903,23 @@ function generateUpMigration(diff) {
4827
4903
  lines.push(``);
4828
4904
  }
4829
4905
  let code = lines.join("\n");
4830
- const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4831
- const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4832
- const saveMatches = [...code.matchAll(savePattern)];
4833
- const deleteMatches = [...code.matchAll(deletePattern)];
4834
- const allMatches = [
4835
- ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4836
- ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4837
- ].sort((a, b) => b.index - a.index);
4838
- if (allMatches.length > 0) {
4839
- const lastMatch = allMatches[0];
4840
- if (lastMatch.type === "save") {
4841
- 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);
4842
- } else {
4843
- 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);
4906
+ const hasReturnStatement = /return\s+app\.(save|delete)\(/m.test(code);
4907
+ if (!hasReturnStatement) {
4908
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4909
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4910
+ const saveMatches = [...code.matchAll(savePattern)];
4911
+ const deleteMatches = [...code.matchAll(deletePattern)];
4912
+ const allMatches = [
4913
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4914
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4915
+ ].sort((a, b) => b.index - a.index);
4916
+ if (allMatches.length > 0) {
4917
+ const lastMatch = allMatches[0];
4918
+ if (lastMatch.type === "save") {
4919
+ 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);
4920
+ } else {
4921
+ 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);
4922
+ }
4844
4923
  }
4845
4924
  }
4846
4925
  return code;
@@ -4860,6 +4939,11 @@ function generateDownMigration(diff) {
4860
4939
  collectionIdMap.set(collection.name, collection.id);
4861
4940
  }
4862
4941
  }
4942
+ if (diff.existingCollectionIds) {
4943
+ for (const [name, id] of diff.existingCollectionIds) {
4944
+ collectionIdMap.set(name, id);
4945
+ }
4946
+ }
4863
4947
  if (diff.collectionsToDelete.length > 0) {
4864
4948
  lines.push(` // Recreate deleted collections`);
4865
4949
  for (let i = 0; i < diff.collectionsToDelete.length; i++) {
@@ -4957,20 +5041,23 @@ function generateDownMigration(diff) {
4957
5041
  lines.push(``);
4958
5042
  }
4959
5043
  let code = lines.join("\n");
4960
- const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4961
- const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4962
- const saveMatches = [...code.matchAll(savePattern)];
4963
- const deleteMatches = [...code.matchAll(deletePattern)];
4964
- const allMatches = [
4965
- ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4966
- ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4967
- ].sort((a, b) => b.index - a.index);
4968
- if (allMatches.length > 0) {
4969
- const lastMatch = allMatches[0];
4970
- if (lastMatch.type === "save") {
4971
- 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);
4972
- } else {
4973
- 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);
5044
+ const hasReturnStatement = /return\s+app\.(save|delete)\(/m.test(code);
5045
+ if (!hasReturnStatement) {
5046
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
5047
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
5048
+ const saveMatches = [...code.matchAll(savePattern)];
5049
+ const deleteMatches = [...code.matchAll(deletePattern)];
5050
+ const allMatches = [
5051
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
5052
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
5053
+ ].sort((a, b) => b.index - a.index);
5054
+ if (allMatches.length > 0) {
5055
+ const lastMatch = allMatches[0];
5056
+ if (lastMatch.type === "save") {
5057
+ 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);
5058
+ } else {
5059
+ 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);
5060
+ }
4974
5061
  }
4975
5062
  }
4976
5063
  return code;
@@ -4994,6 +5081,11 @@ function generate(diff, config) {
4994
5081
  collectionIdMap.set(collection.name, collection.id);
4995
5082
  }
4996
5083
  }
5084
+ if (diff.existingCollectionIds) {
5085
+ for (const [name, id] of diff.existingCollectionIds) {
5086
+ collectionIdMap.set(name, id);
5087
+ }
5088
+ }
4997
5089
  const baseTimestamp = generateTimestamp(normalizedConfig);
4998
5090
  const operations = splitDiffByCollection(diff, baseTimestamp);
4999
5091
  const filePaths = [];