pocketbase-zod-schema 0.2.5 → 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 (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli/index.cjs +497 -298
  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 +497 -298
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +497 -298
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +497 -298
  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-YjcpBXVp.d.cts → fields-RVj26U-O.d.cts} +17 -0
  15. package/dist/{fields-YjcpBXVp.d.ts → fields-RVj26U-O.d.ts} +17 -0
  16. package/dist/index.cjs +575 -155
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +4 -4
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +576 -144
  21. package/dist/index.js.map +1 -1
  22. package/dist/migration/analyzer.cjs +12 -2
  23. package/dist/migration/analyzer.cjs.map +1 -1
  24. package/dist/migration/analyzer.d.cts +2 -2
  25. package/dist/migration/analyzer.d.ts +2 -2
  26. package/dist/migration/analyzer.js +12 -2
  27. package/dist/migration/analyzer.js.map +1 -1
  28. package/dist/migration/diff.cjs +150 -24
  29. package/dist/migration/diff.cjs.map +1 -1
  30. package/dist/migration/diff.d.cts +4 -4
  31. package/dist/migration/diff.d.ts +4 -4
  32. package/dist/migration/diff.js +150 -24
  33. package/dist/migration/diff.js.map +1 -1
  34. package/dist/migration/generator.cjs +360 -46
  35. package/dist/migration/generator.cjs.map +1 -1
  36. package/dist/migration/generator.d.cts +59 -12
  37. package/dist/migration/generator.d.ts +59 -12
  38. package/dist/migration/generator.js +356 -47
  39. package/dist/migration/generator.js.map +1 -1
  40. package/dist/migration/index.cjs +561 -90
  41. package/dist/migration/index.cjs.map +1 -1
  42. package/dist/migration/index.d.cts +3 -3
  43. package/dist/migration/index.d.ts +3 -3
  44. package/dist/migration/index.js +561 -90
  45. package/dist/migration/index.js.map +1 -1
  46. package/dist/migration/snapshot.cjs +51 -18
  47. package/dist/migration/snapshot.cjs.map +1 -1
  48. package/dist/migration/snapshot.d.cts +2 -2
  49. package/dist/migration/snapshot.d.ts +2 -2
  50. package/dist/migration/snapshot.js +51 -18
  51. package/dist/migration/snapshot.js.map +1 -1
  52. package/dist/migration/utils/index.cjs +66 -0
  53. package/dist/migration/utils/index.cjs.map +1 -1
  54. package/dist/migration/utils/index.d.cts +39 -202
  55. package/dist/migration/utils/index.d.ts +39 -202
  56. package/dist/migration/utils/index.js +65 -1
  57. package/dist/migration/utils/index.js.map +1 -1
  58. package/dist/schema.cjs +0 -61
  59. package/dist/schema.cjs.map +1 -1
  60. package/dist/schema.d.cts +2 -86
  61. package/dist/schema.d.ts +2 -86
  62. package/dist/schema.js +1 -50
  63. package/dist/schema.js.map +1 -1
  64. package/dist/type-mapper-CZzVeDj7.d.ts +208 -0
  65. package/dist/type-mapper-DaBe-1ph.d.cts +208 -0
  66. package/dist/{types-LFBGHl9Y.d.ts → types-CUVzgZ9k.d.ts} +33 -2
  67. package/dist/{types-mhQXWNi3.d.cts → types-D-Fsdn_O.d.cts} +33 -2
  68. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import * as fs3 from 'fs';
3
3
  import * as path5 from 'path';
4
+ import { randomBytes } from 'crypto';
4
5
  import chalk from 'chalk';
5
6
  import ora from 'ora';
6
7
 
@@ -366,46 +367,6 @@ var inputImageFileSchema = {
366
367
  var omitImageFilesSchema = {
367
368
  imageFiles: true
368
369
  };
369
- function textField(options) {
370
- let schema = z.string();
371
- if (options?.min !== void 0) schema = schema.min(options.min);
372
- if (options?.max !== void 0) schema = schema.max(options.max);
373
- if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
374
- return schema;
375
- }
376
- function emailField() {
377
- return z.string().email();
378
- }
379
- function urlField() {
380
- return z.string().url();
381
- }
382
- function numberField(options) {
383
- let schema = z.number();
384
- if (options?.min !== void 0) schema = schema.min(options.min);
385
- if (options?.max !== void 0) schema = schema.max(options.max);
386
- return schema;
387
- }
388
- function boolField() {
389
- return z.boolean();
390
- }
391
- function dateField() {
392
- return z.date();
393
- }
394
- function selectField(values) {
395
- return z.enum(values);
396
- }
397
- function jsonField(schema) {
398
- return schema ?? z.record(z.any());
399
- }
400
- function fileField() {
401
- return z.instanceof(File);
402
- }
403
- function filesField(options) {
404
- let schema = z.array(z.instanceof(File));
405
- if (options?.min !== void 0) schema = schema.min(options.min);
406
- if (options?.max !== void 0) schema = schema.max(options.max);
407
- return schema;
408
- }
409
370
  var RELATION_METADATA_KEY = "__pocketbase_relation__";
410
371
  function RelationField(config) {
411
372
  const metadata = {
@@ -449,15 +410,6 @@ function extractRelationMetadata(description) {
449
410
  }
450
411
  return null;
451
412
  }
452
- function editorField() {
453
- return z.string();
454
- }
455
- function geoPointField() {
456
- return z.object({
457
- lon: z.number(),
458
- lat: z.number()
459
- });
460
- }
461
413
  function withPermissions(schema, config) {
462
414
  const metadata = {
463
415
  permissions: config
@@ -2304,12 +2256,22 @@ function isAuthCollection(fields) {
2304
2256
  function buildFieldDefinition(fieldName, zodType) {
2305
2257
  const fieldMetadata = extractFieldMetadata(zodType.description);
2306
2258
  if (fieldMetadata) {
2307
- const required2 = isFieldRequired(zodType);
2259
+ let required2;
2260
+ if (fieldMetadata.type === "number") {
2261
+ if (fieldMetadata.options?.required !== void 0) {
2262
+ required2 = fieldMetadata.options.required;
2263
+ } else {
2264
+ required2 = false;
2265
+ }
2266
+ } else {
2267
+ required2 = isFieldRequired(zodType);
2268
+ }
2269
+ const { required: _required, ...options2 } = fieldMetadata.options || {};
2308
2270
  const fieldDef2 = {
2309
2271
  name: fieldName,
2310
2272
  type: fieldMetadata.type,
2311
2273
  required: required2,
2312
- options: fieldMetadata.options
2274
+ options: Object.keys(options2).length > 0 ? options2 : void 0
2313
2275
  };
2314
2276
  if (fieldMetadata.type === "relation") {
2315
2277
  const relationMetadata2 = extractRelationMetadata(zodType.description);
@@ -2508,7 +2470,7 @@ var SchemaAnalyzer = class {
2508
2470
  var SNAPSHOT_VERSION = "1.0.0";
2509
2471
  function resolveCollectionIdToName(collectionId) {
2510
2472
  if (collectionId === "_pb_users_auth_") {
2511
- return "Users";
2473
+ return "users";
2512
2474
  }
2513
2475
  const nameMatch = collectionId.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
2514
2476
  if (nameMatch) {
@@ -2516,6 +2478,39 @@ function resolveCollectionIdToName(collectionId) {
2516
2478
  }
2517
2479
  return collectionId;
2518
2480
  }
2481
+ function extractFieldOptions2(pbField) {
2482
+ const options = {};
2483
+ if (pbField.options && typeof pbField.options === "object") {
2484
+ Object.assign(options, pbField.options);
2485
+ }
2486
+ const directOptionKeys = [
2487
+ "min",
2488
+ "max",
2489
+ "pattern",
2490
+ "noDecimal",
2491
+ // text/number fields
2492
+ "values",
2493
+ "maxSelect",
2494
+ // select fields
2495
+ "mimeTypes",
2496
+ "maxSize",
2497
+ "thumbs",
2498
+ "protected",
2499
+ // file fields
2500
+ "onCreate",
2501
+ "onUpdate",
2502
+ // autodate fields
2503
+ "exceptDomains",
2504
+ "onlyDomains"
2505
+ // email/url fields
2506
+ ];
2507
+ for (const key of directOptionKeys) {
2508
+ if (pbField[key] !== void 0) {
2509
+ options[key] = pbField[key];
2510
+ }
2511
+ }
2512
+ return options;
2513
+ }
2519
2514
  function convertPocketBaseCollection(pbCollection) {
2520
2515
  const fields = [];
2521
2516
  const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
@@ -2533,23 +2528,19 @@ function convertPocketBaseCollection(pbCollection) {
2533
2528
  type: pbField.type,
2534
2529
  required: pbField.required || false
2535
2530
  };
2536
- field.options = pbField.options ? { ...pbField.options } : {};
2537
- if (pbField.type === "select") {
2538
- if (pbField.values && Array.isArray(pbField.values)) {
2539
- field.options.values = pbField.values;
2540
- } else if (pbField.options?.values && Array.isArray(pbField.options.values)) {
2541
- field.options.values = pbField.options.values;
2542
- }
2543
- }
2531
+ field.options = extractFieldOptions2(pbField);
2544
2532
  if (pbField.type === "relation") {
2545
2533
  const collectionId = pbField.collectionId || pbField.options?.collectionId || "";
2546
- const collectionName = resolveCollectionIdToName(collectionId);
2534
+ const collectionName = resolveCollectionIdToName(collectionId || "");
2547
2535
  field.relation = {
2548
2536
  collection: collectionName,
2549
2537
  cascadeDelete: pbField.cascadeDelete ?? pbField.options?.cascadeDelete ?? false,
2550
2538
  maxSelect: pbField.maxSelect ?? pbField.options?.maxSelect,
2551
2539
  minSelect: pbField.minSelect ?? pbField.options?.minSelect
2552
2540
  };
2541
+ delete field.options.maxSelect;
2542
+ delete field.options.minSelect;
2543
+ delete field.options.cascadeDelete;
2553
2544
  }
2554
2545
  const hasOnlyValues = Object.keys(field.options).length === 1 && field.options.values !== void 0;
2555
2546
  if (Object.keys(field.options).length === 0) {
@@ -2563,17 +2554,21 @@ function convertPocketBaseCollection(pbCollection) {
2563
2554
  type: pbCollection.type || "base",
2564
2555
  fields
2565
2556
  };
2557
+ if (pbCollection.id) {
2558
+ schema.id = pbCollection.id;
2559
+ }
2566
2560
  if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
2567
2561
  schema.indexes = pbCollection.indexes;
2568
2562
  }
2569
- const rules = {};
2570
- if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
2571
- if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
2572
- if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
2573
- if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
2574
- if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
2575
- if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
2576
- if (Object.keys(rules).length > 0) {
2563
+ 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;
2564
+ if (hasAnyRule) {
2565
+ const rules = {};
2566
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
2567
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
2568
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
2569
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
2570
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
2571
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
2577
2572
  schema.rules = rules;
2578
2573
  schema.permissions = { ...rules };
2579
2574
  }
@@ -3305,6 +3300,67 @@ var SnapshotManager = class {
3305
3300
  return validateSnapshot(snapshot);
3306
3301
  }
3307
3302
  };
3303
+ function generateCollectionId() {
3304
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
3305
+ const idLength = 15;
3306
+ const bytes = randomBytes(idLength);
3307
+ let id = "pb_";
3308
+ for (let i = 0; i < idLength; i++) {
3309
+ const index = bytes[i] % chars.length;
3310
+ id += chars[index];
3311
+ }
3312
+ return id;
3313
+ }
3314
+ var CollectionIdRegistry = class {
3315
+ ids;
3316
+ constructor() {
3317
+ this.ids = /* @__PURE__ */ new Set();
3318
+ }
3319
+ /**
3320
+ * Generates a unique collection ID for a given collection name
3321
+ * Retries up to 10 times if collision occurs (extremely rare)
3322
+ * Special case: returns "_pb_users_auth_" for users collection
3323
+ *
3324
+ * @param collectionName - The name of the collection (optional)
3325
+ * @returns A unique collection ID
3326
+ * @throws Error if unable to generate unique ID after max attempts
3327
+ */
3328
+ generate(collectionName) {
3329
+ if (collectionName && collectionName.toLowerCase() === "users") {
3330
+ const usersId = "_pb_users_auth_";
3331
+ if (!this.has(usersId)) {
3332
+ this.register(usersId);
3333
+ }
3334
+ return usersId;
3335
+ }
3336
+ const maxAttempts = 10;
3337
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3338
+ const id = generateCollectionId();
3339
+ if (!this.has(id)) {
3340
+ this.register(id);
3341
+ return id;
3342
+ }
3343
+ }
3344
+ throw new Error("Failed to generate unique collection ID after maximum attempts");
3345
+ }
3346
+ /**
3347
+ * Checks if an ID has already been registered
3348
+ *
3349
+ * @param id - The collection ID to check
3350
+ * @returns True if the ID exists in the registry
3351
+ */
3352
+ has(id) {
3353
+ return this.ids.has(id);
3354
+ }
3355
+ /**
3356
+ * Registers a collection ID in the registry
3357
+ *
3358
+ * @param id - The collection ID to register
3359
+ */
3360
+ register(id) {
3361
+ this.ids.add(id);
3362
+ }
3363
+ };
3308
3364
 
3309
3365
  // src/migration/diff.ts
3310
3366
  var DEFAULT_CONFIG3 = {
@@ -3458,18 +3514,49 @@ function compareFieldConstraints(currentField, previousField) {
3458
3514
  }
3459
3515
  return changes;
3460
3516
  }
3517
+ function normalizeOptionValue(key, value, fieldType) {
3518
+ if (key === "maxSelect" && value === 1 && (fieldType === "select" || fieldType === "file")) {
3519
+ return void 0;
3520
+ }
3521
+ if (key === "maxSize" && value === 0 && fieldType === "file") {
3522
+ return void 0;
3523
+ }
3524
+ if (fieldType === "file") {
3525
+ if (key === "mimeTypes" && Array.isArray(value) && value.length === 0) {
3526
+ return void 0;
3527
+ }
3528
+ if (key === "thumbs" && Array.isArray(value) && value.length === 0) {
3529
+ return void 0;
3530
+ }
3531
+ if (key === "protected" && value === false) {
3532
+ return void 0;
3533
+ }
3534
+ }
3535
+ if (fieldType === "autodate") {
3536
+ if (key === "onCreate" && value === true) {
3537
+ return void 0;
3538
+ }
3539
+ if (key === "onUpdate" && value === false) {
3540
+ return void 0;
3541
+ }
3542
+ }
3543
+ return value;
3544
+ }
3461
3545
  function compareFieldOptions(currentField, previousField) {
3462
3546
  const changes = [];
3463
3547
  const currentOptions = currentField.options || {};
3464
3548
  const previousOptions = previousField.options || {};
3465
3549
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
3550
+ const fieldType = currentField.type;
3466
3551
  for (const key of allKeys) {
3467
3552
  const currentValue = currentOptions[key];
3468
3553
  const previousValue = previousOptions[key];
3469
- if (currentValue === void 0 && previousValue === void 0) {
3554
+ const normalizedCurrent = normalizeOptionValue(key, currentValue, fieldType);
3555
+ const normalizedPrevious = normalizeOptionValue(key, previousValue, fieldType);
3556
+ if (normalizedCurrent === void 0 && normalizedPrevious === void 0) {
3470
3557
  continue;
3471
3558
  }
3472
- if (!areValuesEqual(currentValue, previousValue)) {
3559
+ if (!areValuesEqual(normalizedCurrent, normalizedPrevious)) {
3473
3560
  changes.push({
3474
3561
  property: `options.${key}`,
3475
3562
  oldValue: previousValue,
@@ -3479,7 +3566,7 @@ function compareFieldOptions(currentField, previousField) {
3479
3566
  }
3480
3567
  return changes;
3481
3568
  }
3482
- function compareRelationConfigurations(currentField, previousField) {
3569
+ function compareRelationConfigurations(currentField, previousField, collectionIdToName) {
3483
3570
  const changes = [];
3484
3571
  const currentRelation = currentField.relation;
3485
3572
  const previousRelation = previousField.relation;
@@ -3491,8 +3578,8 @@ function compareRelationConfigurations(currentField, previousField) {
3491
3578
  }
3492
3579
  const normalizeCollection = (collection) => {
3493
3580
  if (!collection) return collection;
3494
- if (collection === "_pb_users_auth_") {
3495
- return "Users";
3581
+ if (collectionIdToName && collectionIdToName.has(collection)) {
3582
+ return collectionIdToName.get(collection);
3496
3583
  }
3497
3584
  const nameMatch = collection.match(/app\.findCollectionByNameOrId\s*\(\s*["']([^"']+)["']\s*\)/);
3498
3585
  if (nameMatch) {
@@ -3502,13 +3589,11 @@ function compareRelationConfigurations(currentField, previousField) {
3502
3589
  };
3503
3590
  const normalizedCurrent = normalizeCollection(currentRelation.collection);
3504
3591
  const normalizedPrevious = normalizeCollection(previousRelation.collection);
3505
- if (normalizedCurrent !== normalizedPrevious) {
3592
+ if (normalizedCurrent.toLowerCase() !== normalizedPrevious.toLowerCase()) {
3506
3593
  changes.push({
3507
3594
  property: "relation.collection",
3508
- oldValue: normalizedPrevious,
3509
- // Use normalized value for clarity
3510
- newValue: normalizedCurrent
3511
- // Use normalized value for clarity
3595
+ oldValue: previousRelation.collection,
3596
+ newValue: currentRelation.collection
3512
3597
  });
3513
3598
  }
3514
3599
  if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
@@ -3518,14 +3603,20 @@ function compareRelationConfigurations(currentField, previousField) {
3518
3603
  newValue: currentRelation.cascadeDelete
3519
3604
  });
3520
3605
  }
3521
- if (currentRelation.maxSelect !== previousRelation.maxSelect) {
3606
+ const normalizeMax = (val) => val === 1 ? null : val;
3607
+ const currentMax = normalizeMax(currentRelation.maxSelect);
3608
+ const previousMax = normalizeMax(previousRelation.maxSelect);
3609
+ if (currentMax != previousMax) {
3522
3610
  changes.push({
3523
3611
  property: "relation.maxSelect",
3524
3612
  oldValue: previousRelation.maxSelect,
3525
3613
  newValue: currentRelation.maxSelect
3526
3614
  });
3527
3615
  }
3528
- if (currentRelation.minSelect !== previousRelation.minSelect) {
3616
+ const normalizeMin = (val) => val === 0 ? null : val;
3617
+ const currentMin = normalizeMin(currentRelation.minSelect);
3618
+ const previousMin = normalizeMin(previousRelation.minSelect);
3619
+ if (currentMin != previousMin) {
3529
3620
  changes.push({
3530
3621
  property: "relation.minSelect",
3531
3622
  oldValue: previousRelation.minSelect,
@@ -3534,7 +3625,7 @@ function compareRelationConfigurations(currentField, previousField) {
3534
3625
  }
3535
3626
  return changes;
3536
3627
  }
3537
- function detectFieldChanges(currentField, previousField) {
3628
+ function detectFieldChanges(currentField, previousField, collectionIdToName) {
3538
3629
  const changes = [];
3539
3630
  const typeChange = compareFieldTypes(currentField, previousField);
3540
3631
  if (typeChange) {
@@ -3543,7 +3634,7 @@ function detectFieldChanges(currentField, previousField) {
3543
3634
  changes.push(...compareFieldConstraints(currentField, previousField));
3544
3635
  changes.push(...compareFieldOptions(currentField, previousField));
3545
3636
  if (currentField.type === "relation" && previousField.type === "relation") {
3546
- changes.push(...compareRelationConfigurations(currentField, previousField));
3637
+ changes.push(...compareRelationConfigurations(currentField, previousField, collectionIdToName));
3547
3638
  }
3548
3639
  return changes;
3549
3640
  }
@@ -3554,7 +3645,7 @@ function compareIndexes(currentIndexes = [], previousIndexes = []) {
3554
3645
  const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
3555
3646
  return { indexesToAdd, indexesToRemove };
3556
3647
  }
3557
- function compareRules(currentRules, previousRules) {
3648
+ function compareRules(currentRules, previousRules, currentPermissions, previousPermissions) {
3558
3649
  const updates = [];
3559
3650
  const ruleTypes = [
3560
3651
  "listRule",
@@ -3565,8 +3656,8 @@ function compareRules(currentRules, previousRules) {
3565
3656
  "manageRule"
3566
3657
  ];
3567
3658
  for (const ruleType of ruleTypes) {
3568
- const currentValue = currentRules?.[ruleType] ?? null;
3569
- const previousValue = previousRules?.[ruleType] ?? null;
3659
+ const currentValue = currentRules?.[ruleType] ?? currentPermissions?.[ruleType] ?? null;
3660
+ const previousValue = previousRules?.[ruleType] ?? previousPermissions?.[ruleType] ?? null;
3570
3661
  if (currentValue !== previousValue) {
3571
3662
  updates.push({
3572
3663
  ruleType,
@@ -3593,7 +3684,7 @@ function comparePermissions(currentPermissions, previousPermissions) {
3593
3684
  }
3594
3685
  return changes;
3595
3686
  }
3596
- function compareCollectionFields(currentCollection, previousCollection, config) {
3687
+ function compareCollectionFields(currentCollection, previousCollection, config, collectionIdToName) {
3597
3688
  let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
3598
3689
  const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
3599
3690
  const fieldsToModify = [];
@@ -3603,7 +3694,7 @@ function compareCollectionFields(currentCollection, previousCollection, config)
3603
3694
  }
3604
3695
  const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
3605
3696
  for (const [currentField, previousField] of matchedFields) {
3606
- const changes = detectFieldChanges(currentField, previousField);
3697
+ const changes = detectFieldChanges(currentField, previousField, collectionIdToName);
3607
3698
  if (changes.length > 0) {
3608
3699
  fieldsToModify.push({
3609
3700
  fieldName: currentField.name,
@@ -3615,14 +3706,20 @@ function compareCollectionFields(currentCollection, previousCollection, config)
3615
3706
  }
3616
3707
  return { fieldsToAdd, fieldsToRemove, fieldsToModify };
3617
3708
  }
3618
- function buildCollectionModification(currentCollection, previousCollection, config) {
3709
+ function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
3619
3710
  const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
3620
3711
  currentCollection,
3621
3712
  previousCollection,
3622
- config
3713
+ config,
3714
+ collectionIdToName
3623
3715
  );
3624
3716
  const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
3625
- const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
3717
+ const rulesToUpdate = compareRules(
3718
+ currentCollection.rules,
3719
+ previousCollection.rules,
3720
+ currentCollection.permissions,
3721
+ previousCollection.permissions
3722
+ );
3626
3723
  const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
3627
3724
  return {
3628
3725
  collection: currentCollection.name,
@@ -3639,6 +3736,14 @@ function hasChanges(modification) {
3639
3736
  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;
3640
3737
  }
3641
3738
  function aggregateChanges(currentSchema, previousSnapshot, config) {
3739
+ const collectionIdToName = /* @__PURE__ */ new Map();
3740
+ if (previousSnapshot) {
3741
+ for (const [name, collection] of previousSnapshot.collections) {
3742
+ if (collection.id) {
3743
+ collectionIdToName.set(collection.id, name);
3744
+ }
3745
+ }
3746
+ }
3642
3747
  const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
3643
3748
  const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
3644
3749
  const filteredCollectionsToCreate = collectionsToCreate.filter(
@@ -3647,16 +3752,28 @@ function aggregateChanges(currentSchema, previousSnapshot, config) {
3647
3752
  const filteredCollectionsToDelete = collectionsToDelete.filter(
3648
3753
  (collection) => !isSystemCollection(collection.name, config)
3649
3754
  );
3755
+ const registry = new CollectionIdRegistry();
3756
+ const collectionsWithIds = filteredCollectionsToCreate.map((collection) => {
3757
+ if (collection.id) {
3758
+ registry.register(collection.id);
3759
+ return collection;
3760
+ }
3761
+ const id = registry.generate(collection.name);
3762
+ return {
3763
+ ...collection,
3764
+ id
3765
+ };
3766
+ });
3650
3767
  const collectionsToModify = [];
3651
3768
  const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
3652
3769
  for (const [currentCollection, previousCollection] of matchedCollections) {
3653
- const modification = buildCollectionModification(currentCollection, previousCollection, config);
3770
+ const modification = buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName);
3654
3771
  if (hasChanges(modification)) {
3655
3772
  collectionsToModify.push(modification);
3656
3773
  }
3657
3774
  }
3658
3775
  return {
3659
- collectionsToCreate: filteredCollectionsToCreate,
3776
+ collectionsToCreate: collectionsWithIds,
3660
3777
  collectionsToDelete: filteredCollectionsToDelete,
3661
3778
  collectionsToModify
3662
3779
  };
@@ -3892,6 +4009,49 @@ function generateTimestamp(config) {
3892
4009
  }
3893
4010
  return Math.floor(Date.now() / 1e3).toString();
3894
4011
  }
4012
+ function splitDiffByCollection(diff, baseTimestamp) {
4013
+ const operations = [];
4014
+ let currentTimestamp = parseInt(baseTimestamp, 10);
4015
+ for (const collection of diff.collectionsToCreate) {
4016
+ operations.push({
4017
+ type: "create",
4018
+ collection,
4019
+ timestamp: currentTimestamp.toString()
4020
+ });
4021
+ currentTimestamp += 1;
4022
+ }
4023
+ for (const modification of diff.collectionsToModify) {
4024
+ operations.push({
4025
+ type: "modify",
4026
+ collection: modification.collection,
4027
+ modifications: modification,
4028
+ timestamp: currentTimestamp.toString()
4029
+ });
4030
+ currentTimestamp += 1;
4031
+ }
4032
+ for (const collection of diff.collectionsToDelete) {
4033
+ operations.push({
4034
+ type: "delete",
4035
+ collection: collection.name || collection,
4036
+ // Handle both object and string
4037
+ timestamp: currentTimestamp.toString()
4038
+ });
4039
+ currentTimestamp += 1;
4040
+ }
4041
+ return operations;
4042
+ }
4043
+ function generateCollectionMigrationFilename(operation) {
4044
+ const timestamp = operation.timestamp;
4045
+ const operationType = operation.type === "modify" ? "updated" : operation.type === "create" ? "created" : "deleted";
4046
+ let collectionName;
4047
+ if (typeof operation.collection === "string") {
4048
+ collectionName = operation.collection;
4049
+ } else {
4050
+ collectionName = operation.collection.name;
4051
+ }
4052
+ const sanitizedName = collectionName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
4053
+ return `${timestamp}_${operationType}_${sanitizedName}.js`;
4054
+ }
3895
4055
  function generateMigrationDescription(diff) {
3896
4056
  const parts = [];
3897
4057
  if (diff.collectionsToCreate.length > 0) {
@@ -3999,14 +4159,13 @@ function formatValue(value) {
3999
4159
  return "null";
4000
4160
  }
4001
4161
  if (typeof value === "string") {
4002
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
4162
+ return JSON.stringify(value);
4003
4163
  }
4004
4164
  if (typeof value === "number" || typeof value === "boolean") {
4005
4165
  return String(value);
4006
4166
  }
4007
4167
  if (Array.isArray(value)) {
4008
- const items = value.map((v) => formatValue(v)).join(", ");
4009
- return `[${items}]`;
4168
+ return JSON.stringify(value).replace(/","/g, '", "');
4010
4169
  }
4011
4170
  if (typeof value === "object") {
4012
4171
  const entries = Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
@@ -4014,7 +4173,7 @@ function formatValue(value) {
4014
4173
  }
4015
4174
  return String(value);
4016
4175
  }
4017
- function generateFieldDefinitionObject(field) {
4176
+ function generateFieldDefinitionObject(field, collectionIdMap) {
4018
4177
  const parts = [];
4019
4178
  parts.push(` name: "${field.name}"`);
4020
4179
  parts.push(` type: "${field.type}"`);
@@ -4022,34 +4181,47 @@ function generateFieldDefinitionObject(field) {
4022
4181
  if (field.unique !== void 0) {
4023
4182
  parts.push(` unique: ${field.unique}`);
4024
4183
  }
4184
+ if (field.type === "select") {
4185
+ const maxSelect = field.options?.maxSelect ?? 1;
4186
+ parts.push(` maxSelect: ${maxSelect}`);
4187
+ const values = field.options?.values ?? [];
4188
+ parts.push(` values: ${formatValue(values)}`);
4189
+ }
4025
4190
  if (field.options && Object.keys(field.options).length > 0) {
4026
4191
  for (const [key, value] of Object.entries(field.options)) {
4192
+ if (field.type === "select" && (key === "maxSelect" || key === "values")) {
4193
+ continue;
4194
+ }
4027
4195
  parts.push(` ${key}: ${formatValue(value)}`);
4028
4196
  }
4029
4197
  }
4030
4198
  if (field.relation) {
4031
4199
  const isUsersCollection = field.relation.collection.toLowerCase() === "users";
4032
- const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
4033
- parts.push(` collectionId: ${collectionIdPlaceholder}`);
4034
- if (field.relation.maxSelect !== void 0) {
4035
- parts.push(` maxSelect: ${field.relation.maxSelect}`);
4036
- }
4037
- if (field.relation.minSelect !== void 0) {
4038
- parts.push(` minSelect: ${field.relation.minSelect}`);
4039
- }
4040
- if (field.relation.cascadeDelete !== void 0) {
4041
- parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
4200
+ let collectionIdValue;
4201
+ if (isUsersCollection) {
4202
+ collectionIdValue = '"_pb_users_auth_"';
4203
+ } else if (collectionIdMap && collectionIdMap.has(field.relation.collection)) {
4204
+ collectionIdValue = `"${collectionIdMap.get(field.relation.collection)}"`;
4205
+ } else {
4206
+ collectionIdValue = `app.findCollectionByNameOrId("${field.relation.collection}").id`;
4042
4207
  }
4208
+ parts.push(` collectionId: ${collectionIdValue}`);
4209
+ const maxSelect = field.relation.maxSelect ?? 1;
4210
+ parts.push(` maxSelect: ${maxSelect}`);
4211
+ const minSelect = field.relation.minSelect ?? null;
4212
+ parts.push(` minSelect: ${minSelect}`);
4213
+ const cascadeDelete = field.relation.cascadeDelete ?? false;
4214
+ parts.push(` cascadeDelete: ${cascadeDelete}`);
4043
4215
  }
4044
4216
  return ` {
4045
4217
  ${parts.join(",\n")},
4046
4218
  }`;
4047
4219
  }
4048
- function generateFieldsArray(fields) {
4220
+ function generateFieldsArray(fields, collectionIdMap) {
4049
4221
  if (fields.length === 0) {
4050
4222
  return "[]";
4051
4223
  }
4052
- const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field));
4224
+ const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field, collectionIdMap));
4053
4225
  return `[
4054
4226
  ${fieldObjects.join(",\n")},
4055
4227
  ]`;
@@ -4108,7 +4280,7 @@ function generateIndexesArray(indexes) {
4108
4280
  if (!indexes || indexes.length === 0) {
4109
4281
  return "[]";
4110
4282
  }
4111
- const indexStrings = indexes.map((idx) => `"${idx}"`);
4283
+ const indexStrings = indexes.map((idx) => JSON.stringify(idx));
4112
4284
  return `[
4113
4285
  ${indexStrings.join(",\n ")},
4114
4286
  ]`;
@@ -4162,9 +4334,12 @@ function getSystemFields() {
4162
4334
  }
4163
4335
  ];
4164
4336
  }
4165
- function generateCollectionCreation(collection, varName = "collection", isLast = false) {
4337
+ function generateCollectionCreation(collection, varName = "collection", isLast = false, collectionIdMap) {
4166
4338
  const lines = [];
4167
4339
  lines.push(` const ${varName} = new Collection({`);
4340
+ if (collection.id) {
4341
+ lines.push(` id: ${formatValue(collection.id)},`);
4342
+ }
4168
4343
  lines.push(` name: "${collection.name}",`);
4169
4344
  lines.push(` type: "${collection.type}",`);
4170
4345
  const permissionsCode = generateCollectionPermissions(collection.permissions);
@@ -4176,7 +4351,7 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
4176
4351
  }
4177
4352
  const systemFields = getSystemFields();
4178
4353
  const allFields = [...systemFields, ...collection.fields];
4179
- lines.push(` fields: ${generateFieldsArray(allFields)},`);
4354
+ lines.push(` fields: ${generateFieldsArray(allFields, collectionIdMap)},`);
4180
4355
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
4181
4356
  lines.push(` });`);
4182
4357
  lines.push(``);
@@ -4198,42 +4373,59 @@ function getFieldConstructorName(fieldType) {
4198
4373
  };
4199
4374
  return constructorMap[fieldType] || "TextField";
4200
4375
  }
4201
- function generateFieldConstructorOptions(field) {
4376
+ function generateFieldConstructorOptions(field, collectionIdMap) {
4202
4377
  const parts = [];
4203
4378
  parts.push(` name: "${field.name}"`);
4204
4379
  parts.push(` required: ${field.required}`);
4205
4380
  if (field.unique !== void 0) {
4206
4381
  parts.push(` unique: ${field.unique}`);
4207
4382
  }
4383
+ if (field.type === "select") {
4384
+ const maxSelect = field.options?.maxSelect ?? 1;
4385
+ parts.push(` maxSelect: ${maxSelect}`);
4386
+ const values = field.options?.values ?? [];
4387
+ parts.push(` values: ${formatValue(values)}`);
4388
+ }
4208
4389
  if (field.options && Object.keys(field.options).length > 0) {
4209
4390
  for (const [key, value] of Object.entries(field.options)) {
4210
- parts.push(` ${key}: ${formatValue(value)}`);
4391
+ if (field.type === "select" && (key === "maxSelect" || key === "values")) {
4392
+ continue;
4393
+ }
4394
+ if (field.type === "number" && key === "noDecimal") {
4395
+ parts.push(` onlyInt: ${formatValue(value)}`);
4396
+ } else {
4397
+ parts.push(` ${key}: ${formatValue(value)}`);
4398
+ }
4211
4399
  }
4212
4400
  }
4213
4401
  if (field.relation && field.type === "relation") {
4214
4402
  const isUsersCollection = field.relation.collection.toLowerCase() === "users";
4215
- const collectionIdPlaceholder = isUsersCollection ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
4216
- parts.push(` collectionId: ${collectionIdPlaceholder}`);
4217
- if (field.relation.maxSelect !== void 0) {
4218
- parts.push(` maxSelect: ${field.relation.maxSelect}`);
4219
- }
4220
- if (field.relation.minSelect !== void 0) {
4221
- parts.push(` minSelect: ${field.relation.minSelect}`);
4222
- }
4223
- if (field.relation.cascadeDelete !== void 0) {
4224
- parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
4403
+ let collectionIdValue;
4404
+ if (isUsersCollection) {
4405
+ collectionIdValue = '"_pb_users_auth_"';
4406
+ } else if (collectionIdMap && collectionIdMap.has(field.relation.collection)) {
4407
+ collectionIdValue = `"${collectionIdMap.get(field.relation.collection)}"`;
4408
+ } else {
4409
+ collectionIdValue = `app.findCollectionByNameOrId("${field.relation.collection}").id`;
4225
4410
  }
4411
+ parts.push(` collectionId: ${collectionIdValue}`);
4412
+ const maxSelect = field.relation.maxSelect ?? 1;
4413
+ parts.push(` maxSelect: ${maxSelect}`);
4414
+ const minSelect = field.relation.minSelect ?? null;
4415
+ parts.push(` minSelect: ${minSelect}`);
4416
+ const cascadeDelete = field.relation.cascadeDelete ?? false;
4417
+ parts.push(` cascadeDelete: ${cascadeDelete}`);
4226
4418
  }
4227
4419
  return parts.join(",\n");
4228
4420
  }
4229
- function generateFieldAddition(collectionName, field, varName, isLast = false) {
4421
+ function generateFieldAddition(collectionName, field, varName, isLast = false, collectionIdMap) {
4230
4422
  const lines = [];
4231
4423
  const constructorName = getFieldConstructorName(field.type);
4232
4424
  const collectionVar = varName || `collection_${collectionName}_${field.name}`;
4233
4425
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
4234
4426
  lines.push(``);
4235
4427
  lines.push(` ${collectionVar}.fields.add(new ${constructorName}({`);
4236
- lines.push(generateFieldConstructorOptions(field));
4428
+ lines.push(generateFieldConstructorOptions(field, collectionIdMap));
4237
4429
  lines.push(` }));`);
4238
4430
  lines.push(``);
4239
4431
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
@@ -4283,7 +4475,7 @@ function generateIndexAddition(collectionName, index, varName, isLast = false) {
4283
4475
  const lines = [];
4284
4476
  const collectionVar = varName || `collection_${collectionName}_idx`;
4285
4477
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
4286
- lines.push(` ${collectionVar}.indexes.push("${index}");`);
4478
+ lines.push(` ${collectionVar}.indexes.push(${JSON.stringify(index)});`);
4287
4479
  lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
4288
4480
  return lines.join("\n");
4289
4481
  }
@@ -4292,7 +4484,7 @@ function generateIndexRemoval(collectionName, index, varName, isLast = false) {
4292
4484
  const collectionVar = varName || `collection_${collectionName}_idx`;
4293
4485
  const indexVar = `${collectionVar}_indexToRemove`;
4294
4486
  lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
4295
- lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === "${index}");`);
4487
+ lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === ${JSON.stringify(index)});`);
4296
4488
  lines.push(` if (${indexVar} !== -1) {`);
4297
4489
  lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
4298
4490
  lines.push(` }`);
@@ -4321,16 +4513,213 @@ function generateCollectionDeletion(collectionName, varName = "collection", isLa
4321
4513
  lines.push(isLast ? ` return app.delete(${varName});` : ` app.delete(${varName});`);
4322
4514
  return lines.join("\n");
4323
4515
  }
4516
+ function generateOperationUpMigration(operation, collectionIdMap) {
4517
+ const lines = [];
4518
+ if (operation.type === "create") {
4519
+ const collection = operation.collection;
4520
+ const varName = `collection_${collection.name}`;
4521
+ lines.push(generateCollectionCreation(collection, varName, true, collectionIdMap));
4522
+ } else if (operation.type === "modify") {
4523
+ const modification = operation.modifications;
4524
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4525
+ let operationCount = 0;
4526
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
4527
+ for (const field of modification.fieldsToAdd) {
4528
+ operationCount++;
4529
+ const varName = `collection_${collectionName}_add_${field.name}`;
4530
+ const isLast = operationCount === totalOperations;
4531
+ lines.push(generateFieldAddition(collectionName, field, varName, isLast, collectionIdMap));
4532
+ if (!isLast) lines.push("");
4533
+ }
4534
+ for (const fieldMod of modification.fieldsToModify) {
4535
+ operationCount++;
4536
+ const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
4537
+ const isLast = operationCount === totalOperations;
4538
+ lines.push(generateFieldModification(collectionName, fieldMod, varName, isLast));
4539
+ if (!isLast) lines.push("");
4540
+ }
4541
+ for (const field of modification.fieldsToRemove) {
4542
+ operationCount++;
4543
+ const varName = `collection_${collectionName}_remove_${field.name}`;
4544
+ const isLast = operationCount === totalOperations;
4545
+ lines.push(generateFieldDeletion(collectionName, field.name, varName, isLast));
4546
+ if (!isLast) lines.push("");
4547
+ }
4548
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
4549
+ operationCount++;
4550
+ const index = modification.indexesToAdd[i];
4551
+ const varName = `collection_${collectionName}_addidx_${i}`;
4552
+ const isLast = operationCount === totalOperations;
4553
+ lines.push(generateIndexAddition(collectionName, index, varName, isLast));
4554
+ if (!isLast) lines.push("");
4555
+ }
4556
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
4557
+ operationCount++;
4558
+ const index = modification.indexesToRemove[i];
4559
+ const varName = `collection_${collectionName}_rmidx_${i}`;
4560
+ const isLast = operationCount === totalOperations;
4561
+ lines.push(generateIndexRemoval(collectionName, index, varName, isLast));
4562
+ if (!isLast) lines.push("");
4563
+ }
4564
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4565
+ for (const permission of modification.permissionsToUpdate) {
4566
+ operationCount++;
4567
+ const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
4568
+ const isLast = operationCount === totalOperations;
4569
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName, isLast));
4570
+ if (!isLast) lines.push("");
4571
+ }
4572
+ } else if (modification.rulesToUpdate.length > 0) {
4573
+ for (const rule of modification.rulesToUpdate) {
4574
+ operationCount++;
4575
+ const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
4576
+ const isLast = operationCount === totalOperations;
4577
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName, isLast));
4578
+ if (!isLast) lines.push("");
4579
+ }
4580
+ }
4581
+ } else if (operation.type === "delete") {
4582
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection.name;
4583
+ const varName = `collection_${collectionName}`;
4584
+ lines.push(generateCollectionDeletion(collectionName, varName, true));
4585
+ }
4586
+ let code = lines.join("\n");
4587
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4588
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4589
+ const saveMatches = [...code.matchAll(savePattern)];
4590
+ const deleteMatches = [...code.matchAll(deletePattern)];
4591
+ const allMatches = [
4592
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4593
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4594
+ ].sort((a, b) => b.index - a.index);
4595
+ if (allMatches.length > 0) {
4596
+ const lastMatch = allMatches[0];
4597
+ if (lastMatch.type === "save") {
4598
+ 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);
4599
+ } else {
4600
+ 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);
4601
+ }
4602
+ }
4603
+ return code;
4604
+ }
4605
+ function generateOperationDownMigration(operation, collectionIdMap) {
4606
+ const lines = [];
4607
+ if (operation.type === "create") {
4608
+ const collection = operation.collection;
4609
+ const varName = `collection_${collection.name}`;
4610
+ lines.push(generateCollectionDeletion(collection.name, varName, true));
4611
+ } else if (operation.type === "modify") {
4612
+ const modification = operation.modifications;
4613
+ const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
4614
+ let operationCount = 0;
4615
+ const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + modification.rulesToUpdate.length + modification.permissionsToUpdate.length;
4616
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
4617
+ for (const permission of modification.permissionsToUpdate) {
4618
+ operationCount++;
4619
+ const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
4620
+ const isLast = operationCount === totalOperations;
4621
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName, isLast));
4622
+ if (!isLast) lines.push("");
4623
+ }
4624
+ } else if (modification.rulesToUpdate.length > 0) {
4625
+ for (const rule of modification.rulesToUpdate) {
4626
+ operationCount++;
4627
+ const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
4628
+ const isLast = operationCount === totalOperations;
4629
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName, isLast));
4630
+ if (!isLast) lines.push("");
4631
+ }
4632
+ }
4633
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
4634
+ operationCount++;
4635
+ const index = modification.indexesToRemove[i];
4636
+ const varName = `collection_${collectionName}_restore_idx_${i}`;
4637
+ const isLast = operationCount === totalOperations;
4638
+ lines.push(generateIndexAddition(collectionName, index, varName, isLast));
4639
+ if (!isLast) lines.push("");
4640
+ }
4641
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
4642
+ operationCount++;
4643
+ const index = modification.indexesToAdd[i];
4644
+ const varName = `collection_${collectionName}_revert_idx_${i}`;
4645
+ const isLast = operationCount === totalOperations;
4646
+ lines.push(generateIndexRemoval(collectionName, index, varName, isLast));
4647
+ if (!isLast) lines.push("");
4648
+ }
4649
+ for (const field of modification.fieldsToRemove) {
4650
+ operationCount++;
4651
+ const varName = `collection_${collectionName}_restore_${field.name}`;
4652
+ const isLast = operationCount === totalOperations;
4653
+ lines.push(generateFieldAddition(collectionName, field, varName, isLast, collectionIdMap));
4654
+ if (!isLast) lines.push("");
4655
+ }
4656
+ for (const fieldMod of modification.fieldsToModify) {
4657
+ operationCount++;
4658
+ const reverseChanges = fieldMod.changes.map((change) => ({
4659
+ property: change.property,
4660
+ oldValue: change.newValue,
4661
+ newValue: change.oldValue
4662
+ }));
4663
+ const reverseMod = {
4664
+ fieldName: fieldMod.fieldName,
4665
+ currentDefinition: fieldMod.newDefinition,
4666
+ newDefinition: fieldMod.currentDefinition,
4667
+ changes: reverseChanges
4668
+ };
4669
+ const varName = `collection_${collectionName}_revert_${fieldMod.fieldName}`;
4670
+ const isLast = operationCount === totalOperations;
4671
+ lines.push(generateFieldModification(collectionName, reverseMod, varName, isLast));
4672
+ if (!isLast) lines.push("");
4673
+ }
4674
+ for (const field of modification.fieldsToAdd) {
4675
+ operationCount++;
4676
+ const varName = `collection_${collectionName}_revert_add_${field.name}`;
4677
+ const isLast = operationCount === totalOperations;
4678
+ lines.push(generateFieldDeletion(collectionName, field.name, varName, isLast));
4679
+ if (!isLast) lines.push("");
4680
+ }
4681
+ } else if (operation.type === "delete") {
4682
+ const collection = operation.collection;
4683
+ if (typeof collection !== "string") {
4684
+ const varName = `collection_${collection.name}`;
4685
+ lines.push(generateCollectionCreation(collection, varName, true, collectionIdMap));
4686
+ }
4687
+ }
4688
+ let code = lines.join("\n");
4689
+ const savePattern = /^(\s*)app\.save\((\w+)\);$/gm;
4690
+ const deletePattern = /^(\s*)app\.delete\((\w+)\);$/gm;
4691
+ const saveMatches = [...code.matchAll(savePattern)];
4692
+ const deleteMatches = [...code.matchAll(deletePattern)];
4693
+ const allMatches = [
4694
+ ...saveMatches.map((m) => ({ match: m, type: "save", index: m.index })),
4695
+ ...deleteMatches.map((m) => ({ match: m, type: "delete", index: m.index }))
4696
+ ].sort((a, b) => b.index - a.index);
4697
+ if (allMatches.length > 0) {
4698
+ const lastMatch = allMatches[0];
4699
+ if (lastMatch.type === "save") {
4700
+ 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);
4701
+ } else {
4702
+ 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);
4703
+ }
4704
+ }
4705
+ return code;
4706
+ }
4324
4707
  function generateUpMigration(diff) {
4325
4708
  const lines = [];
4326
4709
  lines.push(` // UP MIGRATION`);
4327
4710
  lines.push(``);
4711
+ const collectionIdMap = /* @__PURE__ */ new Map();
4712
+ for (const collection of diff.collectionsToCreate) {
4713
+ if (collection.id) {
4714
+ collectionIdMap.set(collection.name, collection.id);
4715
+ }
4716
+ }
4328
4717
  if (diff.collectionsToCreate.length > 0) {
4329
4718
  lines.push(` // Create new collections`);
4330
4719
  for (let i = 0; i < diff.collectionsToCreate.length; i++) {
4331
4720
  const collection = diff.collectionsToCreate[i];
4332
4721
  const varName = `collection_${collection.name}_create`;
4333
- lines.push(generateCollectionCreation(collection, varName));
4722
+ lines.push(generateCollectionCreation(collection, varName, false, collectionIdMap));
4334
4723
  lines.push(``);
4335
4724
  }
4336
4725
  }
@@ -4342,7 +4731,7 @@ function generateUpMigration(diff) {
4342
4731
  lines.push(` // Add fields to ${collectionName}`);
4343
4732
  for (const field of modification.fieldsToAdd) {
4344
4733
  const varName = `collection_${collectionName}_add_${field.name}`;
4345
- lines.push(generateFieldAddition(collectionName, field, varName));
4734
+ lines.push(generateFieldAddition(collectionName, field, varName, false, collectionIdMap));
4346
4735
  lines.push(``);
4347
4736
  }
4348
4737
  }
@@ -4433,12 +4822,23 @@ function generateDownMigration(diff) {
4433
4822
  const lines = [];
4434
4823
  lines.push(` // DOWN MIGRATION (ROLLBACK)`);
4435
4824
  lines.push(``);
4825
+ const collectionIdMap = /* @__PURE__ */ new Map();
4826
+ for (const collection of diff.collectionsToCreate) {
4827
+ if (collection.id) {
4828
+ collectionIdMap.set(collection.name, collection.id);
4829
+ }
4830
+ }
4831
+ for (const collection of diff.collectionsToDelete) {
4832
+ if (collection.id) {
4833
+ collectionIdMap.set(collection.name, collection.id);
4834
+ }
4835
+ }
4436
4836
  if (diff.collectionsToDelete.length > 0) {
4437
4837
  lines.push(` // Recreate deleted collections`);
4438
4838
  for (let i = 0; i < diff.collectionsToDelete.length; i++) {
4439
4839
  const collection = diff.collectionsToDelete[i];
4440
4840
  const varName = `collection_${collection.name}_recreate`;
4441
- lines.push(generateCollectionCreation(collection, varName));
4841
+ lines.push(generateCollectionCreation(collection, varName, false, collectionIdMap));
4442
4842
  lines.push(``);
4443
4843
  }
4444
4844
  }
@@ -4483,7 +4883,7 @@ function generateDownMigration(diff) {
4483
4883
  lines.push(` // Restore fields to ${collectionName}`);
4484
4884
  for (const field of modification.fieldsToRemove) {
4485
4885
  const varName = `collection_${collectionName}_restore_${field.name}`;
4486
- lines.push(generateFieldAddition(collectionName, field, varName));
4886
+ lines.push(generateFieldAddition(collectionName, field, varName, false, collectionIdMap));
4487
4887
  lines.push(``);
4488
4888
  }
4489
4889
  }
@@ -4552,12 +4952,33 @@ function generate(diff, config) {
4552
4952
  const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
4553
4953
  try {
4554
4954
  const migrationDir = resolveMigrationDir(normalizedConfig);
4555
- const upCode = generateUpMigration(diff);
4556
- const downCode = generateDownMigration(diff);
4557
- const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
4558
- const filename = generateMigrationFilename(diff, normalizedConfig);
4559
- const filePath = writeMigrationFile(migrationDir, filename, content);
4560
- return filePath;
4955
+ const hasChanges4 = diff.collectionsToCreate.length > 0 || diff.collectionsToModify.length > 0 || diff.collectionsToDelete.length > 0;
4956
+ if (!hasChanges4) {
4957
+ return [];
4958
+ }
4959
+ const collectionIdMap = /* @__PURE__ */ new Map();
4960
+ for (const collection of diff.collectionsToCreate) {
4961
+ if (collection.id) {
4962
+ collectionIdMap.set(collection.name, collection.id);
4963
+ }
4964
+ }
4965
+ for (const collection of diff.collectionsToDelete) {
4966
+ if (collection.id) {
4967
+ collectionIdMap.set(collection.name, collection.id);
4968
+ }
4969
+ }
4970
+ const baseTimestamp = generateTimestamp(normalizedConfig);
4971
+ const operations = splitDiffByCollection(diff, baseTimestamp);
4972
+ const filePaths = [];
4973
+ for (const operation of operations) {
4974
+ const upCode = generateOperationUpMigration(operation, collectionIdMap);
4975
+ const downCode = generateOperationDownMigration(operation, collectionIdMap);
4976
+ const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
4977
+ const filename = generateCollectionMigrationFilename(operation);
4978
+ const filePath = writeMigrationFile(migrationDir, filename, content);
4979
+ filePaths.push(filePath);
4980
+ }
4981
+ return filePaths;
4561
4982
  } catch (error) {
4562
4983
  if (error instanceof MigrationGenerationError || error instanceof FileSystemError) {
4563
4984
  throw error;
@@ -4575,7 +4996,8 @@ var MigrationGenerator = class {
4575
4996
  this.config = mergeConfig4(config);
4576
4997
  }
4577
4998
  /**
4578
- * Generates a migration file from a schema diff
4999
+ * Generates migration files from a schema diff
5000
+ * Returns array of file paths (one per collection operation)
4579
5001
  */
4580
5002
  generate(diff) {
4581
5003
  return generate(diff, this.config);
@@ -5179,15 +5601,25 @@ async function executeGenerate(options) {
5179
5601
  process.exit(1);
5180
5602
  }
5181
5603
  logSection("\u{1F4DD} Generating Migration");
5182
- const migrationPath = await withProgress(
5604
+ const migrationPaths = await withProgress(
5183
5605
  "Creating migration file...",
5184
5606
  () => Promise.resolve(generate(diff, migrationsDir))
5185
5607
  );
5186
- logSuccess(`Migration file created: ${path5.basename(migrationPath)}`);
5608
+ if (migrationPaths.length === 0) {
5609
+ logWarning("No migration files were generated (no changes detected).");
5610
+ return;
5611
+ }
5612
+ if (migrationPaths.length === 1) {
5613
+ logSuccess(`Migration file created: ${path5.basename(migrationPaths[0])}`);
5614
+ } else {
5615
+ logSuccess(`Created ${migrationPaths.length} migration files`);
5616
+ }
5187
5617
  logSection("\u2705 Next Steps");
5188
5618
  console.log();
5189
- console.log(" 1. Review the generated migration file:");
5190
- console.log(` ${migrationPath}`);
5619
+ console.log(" 1. Review the generated migration file(s):");
5620
+ migrationPaths.forEach((migrationPath) => {
5621
+ console.log(` ${migrationPath}`);
5622
+ });
5191
5623
  console.log();
5192
5624
  console.log(" 2. Apply the migration by running PocketBase:");
5193
5625
  console.log(" yarn pb");
@@ -5450,6 +5882,6 @@ async function executeStatus(options) {
5450
5882
  }
5451
5883
  }
5452
5884
 
5453
- export { AutodateField, BaseMutator, BoolField, CLIUsageError, ConfigurationError, DateField, DiffEngine, EditorField, EmailField, FIELD_METADATA_KEY, FIELD_TYPE_INFO, FileField, FileSystemError, FilesField, GeoPointField, JSONField, MigrationError, MigrationGenerationError, MigrationGenerator, NumberField, POCKETBASE_FIELD_TYPES, PermissionTemplates, RelationField, RelationsField, SchemaAnalyzer, SchemaParsingError, SelectField, SnapshotError, SnapshotManager, StatusEnum, TextField, URLField, aggregateChanges, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, boolField, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, createPermissions, dateField, defineCollection, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, editorField, emailField, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldMetadata, extractFieldOptions, extractIndexes, extractRelationMetadata, extractSchemaDefinitions, fileField, filesField, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, formatChangeSummary, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, executeGenerate as generateMigration, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, geoPointField, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, executeStatus as getMigrationStatus, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, inputImageFileSchema, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isPermissionSchema, isRelationField, isSingleRelationField, isSystemCollection, isTemplateConfig, jsonField, loadBaseMigration, loadConfig, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, logError, logInfo, logSection, logSuccess, logWarning, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergePermissions, mergeSnapshots, numberField, omitImageFilesSchema, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, resolveTemplate, saveSnapshot, selectField, selectSchemaForCollection, singularize, snapshotExists, textField, toCollectionName, unwrapZodType, urlField, validatePermissionConfig, validateRuleExpression, validateSnapshot, withIndexes, withPermissions, withProgress, writeMigrationFile };
5885
+ export { AutodateField, BaseMutator, BoolField, CLIUsageError, ConfigurationError, DateField, DiffEngine, EditorField, EmailField, FIELD_METADATA_KEY, FIELD_TYPE_INFO, FileField, FileSystemError, FilesField, GeoPointField, JSONField, MigrationError, MigrationGenerationError, MigrationGenerator, NumberField, POCKETBASE_FIELD_TYPES, PermissionTemplates, RelationField, RelationsField, SchemaAnalyzer, SchemaParsingError, SelectField, SnapshotError, SnapshotManager, StatusEnum, TextField, URLField, aggregateChanges, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, createPermissions, defineCollection, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldMetadata, extractFieldOptions, extractIndexes, extractRelationMetadata, extractSchemaDefinitions, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, formatChangeSummary, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, executeGenerate as generateMigration, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, executeStatus as getMigrationStatus, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, inputImageFileSchema, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isPermissionSchema, isRelationField, isSingleRelationField, isSystemCollection, isTemplateConfig, loadBaseMigration, loadConfig, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, logError, logInfo, logSection, logSuccess, logWarning, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergePermissions, mergeSnapshots, omitImageFilesSchema, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, resolveTemplate, saveSnapshot, selectSchemaForCollection, singularize, snapshotExists, toCollectionName, unwrapZodType, validatePermissionConfig, validateRuleExpression, validateSnapshot, withIndexes, withPermissions, withProgress, writeMigrationFile };
5454
5886
  //# sourceMappingURL=index.js.map
5455
5887
  //# sourceMappingURL=index.js.map