pocketbase-zod-schema 0.1.2

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 (94) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/dist/cli/index.cjs +3383 -0
  5. package/dist/cli/index.cjs.map +1 -0
  6. package/dist/cli/index.d.cts +30 -0
  7. package/dist/cli/index.d.ts +30 -0
  8. package/dist/cli/index.js +3331 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/migrate.cjs +3380 -0
  11. package/dist/cli/migrate.cjs.map +1 -0
  12. package/dist/cli/migrate.d.cts +1 -0
  13. package/dist/cli/migrate.d.ts +1 -0
  14. package/dist/cli/migrate.js +3353 -0
  15. package/dist/cli/migrate.js.map +1 -0
  16. package/dist/cli/utils/index.cjs +540 -0
  17. package/dist/cli/utils/index.cjs.map +1 -0
  18. package/dist/cli/utils/index.d.cts +232 -0
  19. package/dist/cli/utils/index.d.ts +232 -0
  20. package/dist/cli/utils/index.js +487 -0
  21. package/dist/cli/utils/index.js.map +1 -0
  22. package/dist/enums.cjs +19 -0
  23. package/dist/enums.cjs.map +1 -0
  24. package/dist/enums.d.cts +6 -0
  25. package/dist/enums.d.ts +6 -0
  26. package/dist/enums.js +17 -0
  27. package/dist/enums.js.map +1 -0
  28. package/dist/index.cjs +4900 -0
  29. package/dist/index.cjs.map +1 -0
  30. package/dist/index.d.cts +18 -0
  31. package/dist/index.d.ts +18 -0
  32. package/dist/index.js +4726 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/migration/analyzer.cjs +1267 -0
  35. package/dist/migration/analyzer.cjs.map +1 -0
  36. package/dist/migration/analyzer.d.cts +186 -0
  37. package/dist/migration/analyzer.d.ts +186 -0
  38. package/dist/migration/analyzer.js +1232 -0
  39. package/dist/migration/analyzer.js.map +1 -0
  40. package/dist/migration/diff.cjs +557 -0
  41. package/dist/migration/diff.cjs.map +1 -0
  42. package/dist/migration/diff.d.cts +291 -0
  43. package/dist/migration/diff.d.ts +291 -0
  44. package/dist/migration/diff.js +534 -0
  45. package/dist/migration/diff.js.map +1 -0
  46. package/dist/migration/generator.cjs +778 -0
  47. package/dist/migration/generator.cjs.map +1 -0
  48. package/dist/migration/generator.d.cts +225 -0
  49. package/dist/migration/generator.d.ts +225 -0
  50. package/dist/migration/generator.js +737 -0
  51. package/dist/migration/generator.js.map +1 -0
  52. package/dist/migration/index.cjs +3390 -0
  53. package/dist/migration/index.cjs.map +1 -0
  54. package/dist/migration/index.d.cts +103 -0
  55. package/dist/migration/index.d.ts +103 -0
  56. package/dist/migration/index.js +3265 -0
  57. package/dist/migration/index.js.map +1 -0
  58. package/dist/migration/snapshot.cjs +609 -0
  59. package/dist/migration/snapshot.cjs.map +1 -0
  60. package/dist/migration/snapshot.d.cts +167 -0
  61. package/dist/migration/snapshot.d.ts +167 -0
  62. package/dist/migration/snapshot.js +575 -0
  63. package/dist/migration/snapshot.js.map +1 -0
  64. package/dist/migration/utils/index.cjs +672 -0
  65. package/dist/migration/utils/index.cjs.map +1 -0
  66. package/dist/migration/utils/index.d.cts +207 -0
  67. package/dist/migration/utils/index.d.ts +207 -0
  68. package/dist/migration/utils/index.js +641 -0
  69. package/dist/migration/utils/index.js.map +1 -0
  70. package/dist/mutator.cjs +427 -0
  71. package/dist/mutator.cjs.map +1 -0
  72. package/dist/mutator.d.cts +190 -0
  73. package/dist/mutator.d.ts +190 -0
  74. package/dist/mutator.js +425 -0
  75. package/dist/mutator.js.map +1 -0
  76. package/dist/permissions-ZHafVSIx.d.cts +71 -0
  77. package/dist/permissions-ZHafVSIx.d.ts +71 -0
  78. package/dist/schema.cjs +430 -0
  79. package/dist/schema.cjs.map +1 -0
  80. package/dist/schema.d.cts +316 -0
  81. package/dist/schema.d.ts +316 -0
  82. package/dist/schema.js +396 -0
  83. package/dist/schema.js.map +1 -0
  84. package/dist/types-BbTgmg6H.d.cts +91 -0
  85. package/dist/types-z1Dkjg8m.d.ts +91 -0
  86. package/dist/types.cjs +4 -0
  87. package/dist/types.cjs.map +1 -0
  88. package/dist/types.d.cts +14 -0
  89. package/dist/types.d.ts +14 -0
  90. package/dist/types.js +3 -0
  91. package/dist/types.js.map +1 -0
  92. package/dist/user-jS1aYoeD.d.cts +123 -0
  93. package/dist/user-jS1aYoeD.d.ts +123 -0
  94. package/package.json +165 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,4900 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var fs2 = require('fs');
5
+ var path4 = require('path');
6
+ var chalk = require('chalk');
7
+ var ora = require('ora');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
30
+ var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
31
+ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
32
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
33
+
34
+ // src/enums.ts
35
+ var StatusEnum = zod.z.enum([
36
+ "draft",
37
+ // Initial proposal stage (RequestDraft, ProjectDraft)
38
+ "active",
39
+ // Work in progress
40
+ "complete",
41
+ // Fully completed project
42
+ "fail"
43
+ // Failed project at any stage
44
+ ]);
45
+ var baseSchema = {
46
+ id: zod.z.string().describe("unique id"),
47
+ collectionId: zod.z.string().describe("collection id"),
48
+ collectionName: zod.z.string().describe("collection name"),
49
+ expand: zod.z.record(zod.z.any()).describe("expandable fields")
50
+ };
51
+ var baseSchemaWithTimestamps = {
52
+ ...baseSchema,
53
+ created: zod.z.string().describe("creation timestamp"),
54
+ updated: zod.z.string().describe("last update timestamp")
55
+ };
56
+ var baseImageFileSchema = {
57
+ ...baseSchema,
58
+ thumbnailURL: zod.z.string().optional(),
59
+ imageFiles: zod.z.array(zod.z.string())
60
+ };
61
+ var inputImageFileSchema = {
62
+ imageFiles: zod.z.array(zod.z.instanceof(File))
63
+ };
64
+ var omitImageFilesSchema = {
65
+ imageFiles: true
66
+ };
67
+ function textField(options) {
68
+ let schema = zod.z.string();
69
+ if (options?.min !== void 0) schema = schema.min(options.min);
70
+ if (options?.max !== void 0) schema = schema.max(options.max);
71
+ if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
72
+ return schema;
73
+ }
74
+ function emailField() {
75
+ return zod.z.string().email();
76
+ }
77
+ function urlField() {
78
+ return zod.z.string().url();
79
+ }
80
+ function numberField(options) {
81
+ let schema = zod.z.number();
82
+ if (options?.min !== void 0) schema = schema.min(options.min);
83
+ if (options?.max !== void 0) schema = schema.max(options.max);
84
+ return schema;
85
+ }
86
+ function boolField() {
87
+ return zod.z.boolean();
88
+ }
89
+ function dateField() {
90
+ return zod.z.date();
91
+ }
92
+ function selectField(values) {
93
+ return zod.z.enum(values);
94
+ }
95
+ function jsonField(schema) {
96
+ return schema ?? zod.z.record(zod.z.any());
97
+ }
98
+ function fileField() {
99
+ return zod.z.instanceof(File);
100
+ }
101
+ function filesField(options) {
102
+ let schema = zod.z.array(zod.z.instanceof(File));
103
+ if (options?.min !== void 0) schema = schema.min(options.min);
104
+ if (options?.max !== void 0) schema = schema.max(options.max);
105
+ return schema;
106
+ }
107
+ function relationField() {
108
+ return zod.z.string();
109
+ }
110
+ function relationsField(options) {
111
+ let schema = zod.z.array(zod.z.string());
112
+ if (options?.min !== void 0) schema = schema.min(options.min);
113
+ if (options?.max !== void 0) schema = schema.max(options.max);
114
+ return schema;
115
+ }
116
+ function editorField() {
117
+ return zod.z.string();
118
+ }
119
+ function geoPointField() {
120
+ return zod.z.object({
121
+ lon: zod.z.number(),
122
+ lat: zod.z.number()
123
+ });
124
+ }
125
+ function withPermissions(schema, config) {
126
+ const metadata = {
127
+ permissions: config
128
+ };
129
+ return schema.describe(JSON.stringify(metadata));
130
+ }
131
+ function withIndexes(schema, indexes) {
132
+ let existingMetadata = {};
133
+ if (schema.description) {
134
+ try {
135
+ existingMetadata = JSON.parse(schema.description);
136
+ } catch {
137
+ }
138
+ }
139
+ const metadata = {
140
+ ...existingMetadata,
141
+ indexes
142
+ };
143
+ return schema.describe(JSON.stringify(metadata));
144
+ }
145
+
146
+ // src/schema/permission-templates.ts
147
+ var PermissionTemplates = {
148
+ /**
149
+ * Public access - anyone can perform all operations
150
+ */
151
+ public: () => ({
152
+ listRule: "",
153
+ viewRule: "",
154
+ createRule: "",
155
+ updateRule: "",
156
+ deleteRule: ""
157
+ }),
158
+ /**
159
+ * Authenticated users only - requires valid authentication for all operations
160
+ */
161
+ authenticated: () => ({
162
+ listRule: '@request.auth.id != ""',
163
+ viewRule: '@request.auth.id != ""',
164
+ createRule: '@request.auth.id != ""',
165
+ updateRule: '@request.auth.id != ""',
166
+ deleteRule: '@request.auth.id != ""'
167
+ }),
168
+ /**
169
+ * Owner-only access - users can only manage their own records
170
+ * @param ownerField - Name of the relation field pointing to user (default: 'User')
171
+ */
172
+ ownerOnly: (ownerField = "User") => ({
173
+ listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
174
+ viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
175
+ createRule: '@request.auth.id != ""',
176
+ updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
177
+ deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
178
+ }),
179
+ /**
180
+ * Admin/superuser only access
181
+ * Assumes a 'role' field exists with 'admin' value
182
+ * @param roleField - Name of the role field (default: 'role')
183
+ */
184
+ adminOnly: (roleField = "role") => ({
185
+ listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
186
+ viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
187
+ createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
188
+ updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
189
+ deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
190
+ }),
191
+ /**
192
+ * Public read, authenticated write
193
+ * Anyone can list/view, but only authenticated users can create/update/delete
194
+ */
195
+ readPublic: () => ({
196
+ listRule: "",
197
+ viewRule: "",
198
+ createRule: '@request.auth.id != ""',
199
+ updateRule: '@request.auth.id != ""',
200
+ deleteRule: '@request.auth.id != ""'
201
+ }),
202
+ /**
203
+ * Locked access - only superusers can perform operations
204
+ * All rules are set to null (locked)
205
+ */
206
+ locked: () => ({
207
+ listRule: null,
208
+ viewRule: null,
209
+ createRule: null,
210
+ updateRule: null,
211
+ deleteRule: null
212
+ }),
213
+ /**
214
+ * Read-only authenticated - authenticated users can read, no write access
215
+ */
216
+ readOnlyAuthenticated: () => ({
217
+ listRule: '@request.auth.id != ""',
218
+ viewRule: '@request.auth.id != ""',
219
+ createRule: null,
220
+ updateRule: null,
221
+ deleteRule: null
222
+ })
223
+ };
224
+ function resolveTemplate(config) {
225
+ let baseRules;
226
+ switch (config.template) {
227
+ case "public":
228
+ baseRules = PermissionTemplates.public();
229
+ break;
230
+ case "authenticated":
231
+ baseRules = PermissionTemplates.authenticated();
232
+ break;
233
+ case "owner-only":
234
+ baseRules = PermissionTemplates.ownerOnly(config.ownerField);
235
+ break;
236
+ case "admin-only":
237
+ baseRules = PermissionTemplates.adminOnly(config.roleField);
238
+ break;
239
+ case "read-public":
240
+ baseRules = PermissionTemplates.readPublic();
241
+ break;
242
+ case "custom":
243
+ baseRules = {};
244
+ break;
245
+ default: {
246
+ const _exhaustive = config.template;
247
+ throw new Error(`Unknown template type: ${_exhaustive}`);
248
+ }
249
+ }
250
+ return {
251
+ ...baseRules,
252
+ ...config.customRules
253
+ };
254
+ }
255
+ function isTemplateConfig(config) {
256
+ return "template" in config;
257
+ }
258
+ function isPermissionSchema(config) {
259
+ return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
260
+ }
261
+ function validatePermissionConfig(config, isAuthCollection2 = false) {
262
+ const result = {
263
+ valid: true,
264
+ errors: [],
265
+ warnings: []
266
+ };
267
+ let permissions;
268
+ if (isTemplateConfig(config)) {
269
+ if (config.template === "owner-only" && !config.ownerField) {
270
+ result.warnings.push("owner-only template without ownerField specified - using default 'User'");
271
+ }
272
+ if (config.template === "admin-only" && !config.roleField) {
273
+ result.warnings.push("admin-only template without roleField specified - using default 'role'");
274
+ }
275
+ permissions = resolveTemplate(config);
276
+ } else {
277
+ permissions = config;
278
+ }
279
+ if (permissions.manageRule !== void 0 && !isAuthCollection2) {
280
+ result.errors.push("manageRule is only valid for auth collections");
281
+ result.valid = false;
282
+ }
283
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
284
+ if (isAuthCollection2) {
285
+ ruleTypes.push("manageRule");
286
+ }
287
+ for (const ruleType of ruleTypes) {
288
+ const rule = permissions[ruleType];
289
+ if (rule !== void 0 && rule !== null && rule !== "") {
290
+ const ruleValidation = validateRuleExpression(rule);
291
+ if (!ruleValidation.valid) {
292
+ result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
293
+ result.valid = false;
294
+ }
295
+ result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
296
+ }
297
+ }
298
+ return result;
299
+ }
300
+ function validateRuleExpression(expression) {
301
+ const result = {
302
+ valid: true,
303
+ errors: [],
304
+ warnings: []
305
+ };
306
+ if (expression === null || expression === "") {
307
+ return result;
308
+ }
309
+ let parenCount = 0;
310
+ for (const char of expression) {
311
+ if (char === "(") parenCount++;
312
+ if (char === ")") parenCount--;
313
+ if (parenCount < 0) {
314
+ result.errors.push("Unbalanced parentheses");
315
+ result.valid = false;
316
+ return result;
317
+ }
318
+ }
319
+ if (parenCount !== 0) {
320
+ result.errors.push("Unbalanced parentheses");
321
+ result.valid = false;
322
+ }
323
+ if (expression.includes("==")) {
324
+ result.warnings.push("Use '=' instead of '==' for equality comparison");
325
+ }
326
+ const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
327
+ for (const ref of requestRefs) {
328
+ const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
329
+ if (!isValid) {
330
+ result.errors.push(`Invalid @request reference: '${ref}'`);
331
+ result.valid = false;
332
+ }
333
+ }
334
+ return result;
335
+ }
336
+ function createPermissions(permissions) {
337
+ return {
338
+ listRule: permissions.listRule ?? null,
339
+ viewRule: permissions.viewRule ?? null,
340
+ createRule: permissions.createRule ?? null,
341
+ updateRule: permissions.updateRule ?? null,
342
+ deleteRule: permissions.deleteRule ?? null,
343
+ manageRule: permissions.manageRule ?? null
344
+ };
345
+ }
346
+ function mergePermissions(...schemas) {
347
+ const merged = {
348
+ listRule: null,
349
+ viewRule: null,
350
+ createRule: null,
351
+ updateRule: null,
352
+ deleteRule: null,
353
+ manageRule: null
354
+ };
355
+ for (const schema of schemas) {
356
+ if (schema.listRule !== void 0) merged.listRule = schema.listRule;
357
+ if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
358
+ if (schema.createRule !== void 0) merged.createRule = schema.createRule;
359
+ if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
360
+ if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
361
+ if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
362
+ }
363
+ return merged;
364
+ }
365
+ var ProjectInputSchema = zod.z.object({
366
+ // Required fields
367
+ title: zod.z.string(),
368
+ content: zod.z.string(),
369
+ status: StatusEnum,
370
+ summary: zod.z.string().optional(),
371
+ User: zod.z.string().nonempty("User ID is missing"),
372
+ SubscriberUsers: zod.z.array(zod.z.string())
373
+ }).extend(inputImageFileSchema);
374
+ var ProjectSchema = withPermissions(
375
+ ProjectInputSchema.omit(omitImageFilesSchema).extend(baseImageFileSchema),
376
+ {
377
+ template: "owner-only",
378
+ ownerField: "User",
379
+ customRules: {
380
+ // Override list rule to allow authenticated users to see all projects
381
+ listRule: '@request.auth.id != ""',
382
+ // Allow viewing if user is owner OR a subscriber
383
+ viewRule: '@request.auth.id != "" && (User = @request.auth.id || SubscriberUsers ?= @request.auth.id)'
384
+ }
385
+ }
386
+ );
387
+ var UserInputSchema = zod.z.object({
388
+ name: zod.z.string().min(2, "Name must be longer").optional(),
389
+ email: zod.z.string().email(),
390
+ password: zod.z.string().min(8, "Password must be at least 8 characters"),
391
+ passwordConfirm: zod.z.string(),
392
+ avatar: zod.z.instanceof(File).optional()
393
+ });
394
+ var UserDatabaseSchema = zod.z.object({
395
+ name: zod.z.string().min(2, "Name must be longer").optional(),
396
+ email: zod.z.string().email(),
397
+ password: zod.z.string().min(8, "Password must be at least 8 characters"),
398
+ avatar: zod.z.instanceof(File).optional()
399
+ });
400
+ var UserSchema = withIndexes(
401
+ withPermissions(UserDatabaseSchema.extend(baseSchema), {
402
+ // Users can list other users (for mentions, user search, etc.)
403
+ listRule: "id = @request.auth.id",
404
+ // Users can view their own profile
405
+ viewRule: "id = @request.auth.id",
406
+ // Anyone can create an account (sign up)
407
+ createRule: "",
408
+ // Users can only update their own profile
409
+ updateRule: "id = @request.auth.id",
410
+ // Users can only delete their own account
411
+ deleteRule: "id = @request.auth.id",
412
+ // Users can only manage their own account (change email, password, etc.)
413
+ manageRule: "id = @request.auth.id"
414
+ }),
415
+ [
416
+ // Email should be unique for authentication
417
+ "CREATE UNIQUE INDEX idx_users_email ON users (email)",
418
+ // Index on name for user search and sorting
419
+ "CREATE INDEX idx_users_name ON users (name)"
420
+ ]
421
+ );
422
+ var BaseMutator = class {
423
+ pb;
424
+ // Define a default property that subclasses will override
425
+ options = {
426
+ expand: [],
427
+ filter: [],
428
+ sort: []
429
+ };
430
+ constructor(pb, options) {
431
+ this.pb = pb;
432
+ this.initializeOptions();
433
+ if (options) {
434
+ this.overrideOptions(options);
435
+ }
436
+ }
437
+ initializeOptions() {
438
+ this.options = this.setDefaults();
439
+ }
440
+ /**
441
+ * Initialize options with class-specific defaults
442
+ * Subclasses should override this instead of directly setting options
443
+ */
444
+ setDefaults() {
445
+ return {
446
+ expand: [],
447
+ filter: [],
448
+ sort: []
449
+ };
450
+ }
451
+ /**
452
+ * Merge provided options with current options
453
+ */
454
+ overrideOptions(newOptions) {
455
+ if (newOptions.expand !== void 0) {
456
+ this.options.expand = newOptions.expand;
457
+ }
458
+ if (newOptions.filter !== void 0) {
459
+ this.options.filter = newOptions.filter;
460
+ }
461
+ if (newOptions.sort !== void 0) {
462
+ this.options.sort = newOptions.sort;
463
+ }
464
+ }
465
+ toSnakeCase(str) {
466
+ return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
467
+ }
468
+ /**
469
+ * Create a new entity
470
+ */
471
+ async create(input) {
472
+ try {
473
+ const data = await this.validateInput(input);
474
+ const record = await this.entityCreate(data);
475
+ return await this.processRecord(record);
476
+ } catch (error) {
477
+ return this.errorWrapper(error);
478
+ }
479
+ }
480
+ /**
481
+ * Update an existing entity
482
+ */
483
+ async update(id, input) {
484
+ try {
485
+ const record = await this.entityUpdate(id, input);
486
+ return await this.processRecord(record);
487
+ } catch (error) {
488
+ return this.errorWrapper(error);
489
+ }
490
+ }
491
+ /**
492
+ * Create or update entity (upsert)
493
+ */
494
+ async upsert(input) {
495
+ if (input?.id) {
496
+ return await this.update(input.id, input);
497
+ }
498
+ return await this.create(input);
499
+ }
500
+ /**
501
+ * Get entity by ID
502
+ */
503
+ async getById(id, expand) {
504
+ try {
505
+ const record = await this.entityGetById(id, expand);
506
+ return await this.processRecord(record);
507
+ } catch (error) {
508
+ return this.handleError(error, { allowNotFound: true });
509
+ }
510
+ }
511
+ /**
512
+ * Get first entity by filter
513
+ */
514
+ async getFirstByFilter(filter, expand, sort) {
515
+ try {
516
+ const record = await this.entityGetFirstByFilter(filter, expand, sort);
517
+ return await this.processRecord(record);
518
+ } catch (error) {
519
+ return this.handleError(error, { allowNotFound: true });
520
+ }
521
+ }
522
+ /**
523
+ * Get list of entities
524
+ */
525
+ async getList(page = 1, perPage = 100, filter, sort, expand) {
526
+ try {
527
+ const result = await this.entityGetList(page, perPage, filter, sort, expand);
528
+ return await this.processListResult(result);
529
+ } catch (error) {
530
+ return this.errorWrapper(error);
531
+ }
532
+ }
533
+ /**
534
+ * Delete entity by ID
535
+ */
536
+ async delete(id) {
537
+ try {
538
+ return await this.entityDelete(id);
539
+ } catch (error) {
540
+ return this.handleError(error, { returnValue: false });
541
+ }
542
+ }
543
+ /**
544
+ * Process a single record before returning it
545
+ * Can be overridden to handle special cases like mapped entities
546
+ */
547
+ async processRecord(record) {
548
+ return record;
549
+ }
550
+ /**
551
+ * Process a list result before returning it
552
+ * Can be overridden to handle special cases like mapped entities
553
+ */
554
+ async processListResult(result) {
555
+ const processedItems = await Promise.all(result.items.map((item) => this.processRecord(item)));
556
+ return {
557
+ ...result,
558
+ items: processedItems
559
+ };
560
+ }
561
+ /**
562
+ * Prepare expand parameter
563
+ * Combines default expands with provided expands
564
+ */
565
+ prepareExpand(expand) {
566
+ if (!this.options.expand.length && !expand) {
567
+ return void 0;
568
+ }
569
+ let expandArray = [...this.options.expand];
570
+ if (expand) {
571
+ if (typeof expand === "string") {
572
+ expandArray = expandArray.concat(expand.split(",").map((e) => e.trim()));
573
+ } else {
574
+ expandArray = expandArray.concat(expand);
575
+ }
576
+ }
577
+ const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
578
+ if (!uniqueExpands.length) {
579
+ return void 0;
580
+ }
581
+ return uniqueExpands.join(",");
582
+ }
583
+ /**
584
+ * Prepare filter parameter
585
+ * Combines default filters with provided filters
586
+ */
587
+ prepareFilter(filter) {
588
+ if (!this.options.filter.length && !filter) {
589
+ return void 0;
590
+ }
591
+ let filterArray = [...this.options.filter];
592
+ if (filter) {
593
+ if (typeof filter === "string") {
594
+ if (filter) filterArray.push(filter);
595
+ } else {
596
+ filterArray = filterArray.concat(filter);
597
+ }
598
+ }
599
+ const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
600
+ if (!validFilters.length) {
601
+ return void 0;
602
+ }
603
+ return validFilters.join("&&");
604
+ }
605
+ /**
606
+ * Prepare sort parameter
607
+ * Uses provided sort or falls back to default sort
608
+ */
609
+ prepareSort(sort) {
610
+ if (sort && sort !== "") {
611
+ return sort;
612
+ }
613
+ if (this.options.sort.length) {
614
+ const validSorts = this.options.sort.filter((s) => s !== "" && s !== void 0);
615
+ if (validSorts.length) {
616
+ return validSorts.join(",");
617
+ }
618
+ }
619
+ return void 0;
620
+ }
621
+ /**
622
+ * Perform the actual create operation
623
+ */
624
+ async entityCreate(data) {
625
+ return await this.getCollection().create(data);
626
+ }
627
+ /**
628
+ * Perform the actual update operation
629
+ */
630
+ async entityUpdate(id, data) {
631
+ return await this.getCollection().update(id, data);
632
+ }
633
+ /**
634
+ * Perform the actual getById operation
635
+ */
636
+ async entityGetById(id, expand) {
637
+ const finalExpand = this.prepareExpand(expand);
638
+ const options = finalExpand ? { expand: finalExpand } : {};
639
+ return await this.getCollection().getOne(id, options);
640
+ }
641
+ /**
642
+ * Perform the actual getFirstByFilter operation
643
+ */
644
+ async entityGetFirstByFilter(filter, expand, sort) {
645
+ const finalFilter = this.prepareFilter(filter);
646
+ const finalExpand = this.prepareExpand(expand);
647
+ const finalSort = this.prepareSort(sort);
648
+ const options = {};
649
+ if (finalExpand) options.expand = finalExpand;
650
+ if (finalSort) options.sort = finalSort;
651
+ return await this.getCollection().getFirstListItem(finalFilter || "", options);
652
+ }
653
+ /**
654
+ * Perform the actual getList operation
655
+ * Returns a list result with items of type T
656
+ */
657
+ async entityGetList(page, perPage, filter, sort, expand) {
658
+ const finalFilter = this.prepareFilter(filter);
659
+ const finalExpand = this.prepareExpand(expand);
660
+ const finalSort = this.prepareSort(sort);
661
+ const options = {};
662
+ if (finalFilter) options.filter = finalFilter;
663
+ if (finalExpand) options.expand = finalExpand;
664
+ if (finalSort) options.sort = finalSort;
665
+ return await this.getCollection().getList(page, perPage, options);
666
+ }
667
+ /**
668
+ * Perform the actual delete operation
669
+ */
670
+ async entityDelete(id) {
671
+ await this.getCollection().delete(id);
672
+ return true;
673
+ }
674
+ /**
675
+ * Error handler for common errors
676
+ * @param error The error to handle
677
+ * @param options Handler options
678
+ * @returns The value to return if the error is handled, or throws if not handled
679
+ */
680
+ handleError(error, options = { logError: true }) {
681
+ const { allowNotFound = false, returnValue, logError: logError2 = true } = options;
682
+ if (logError2) {
683
+ console.error(`Error in ${this.constructor.name}:`, error);
684
+ }
685
+ if (allowNotFound && this.isNotFoundError(error)) {
686
+ return null;
687
+ }
688
+ if (returnValue !== void 0) {
689
+ return returnValue;
690
+ }
691
+ throw error;
692
+ }
693
+ /**
694
+ * Check if an error is a "not found" error
695
+ */
696
+ isNotFoundError(error) {
697
+ return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
698
+ }
699
+ /**
700
+ * Standard error handling wrapper (legacy method, consider using handleError instead)
701
+ */
702
+ errorWrapper(error) {
703
+ console.error(`Error in ${this.constructor.name}:`, error);
704
+ throw error;
705
+ }
706
+ /**
707
+ * Subscribe to changes on a specific record
708
+ * @param id The ID of the record to subscribe to
709
+ * @param callback Function to call when changes occur
710
+ * @param expand Optional expand parameters
711
+ * @returns Promise that resolves to an unsubscribe function
712
+ */
713
+ async subscribeToRecord(id, callback, expand) {
714
+ const finalExpand = this.prepareExpand(expand);
715
+ const options = finalExpand ? { expand: finalExpand } : {};
716
+ return this.getCollection().subscribe(id, callback, options);
717
+ }
718
+ /**
719
+ * Subscribe to changes on the entire collection
720
+ * @param callback Function to call when changes occur
721
+ * @param expand Optional expand parameters
722
+ * @returns Promise that resolves to an unsubscribe function
723
+ */
724
+ async subscribeToCollection(callback, expand) {
725
+ const finalExpand = this.prepareExpand(expand);
726
+ const options = finalExpand ? { expand: finalExpand } : {};
727
+ return this.getCollection().subscribe("*", callback, options);
728
+ }
729
+ /**
730
+ * Unsubscribe from a specific record's changes
731
+ * @param id The ID of the record to unsubscribe from
732
+ */
733
+ unsubscribeFromRecord(id) {
734
+ this.getCollection().unsubscribe(id);
735
+ }
736
+ /**
737
+ * Unsubscribe from collection-wide changes
738
+ */
739
+ unsubscribeFromCollection() {
740
+ this.getCollection().unsubscribe("*");
741
+ }
742
+ /**
743
+ * Unsubscribe from all subscriptions in this collection
744
+ */
745
+ unsubscribeAll() {
746
+ this.getCollection().unsubscribe();
747
+ }
748
+ };
749
+
750
+ // src/mutator/userMutator.ts
751
+ var UserMutator = class extends BaseMutator {
752
+ setDefaults() {
753
+ return {
754
+ expand: [],
755
+ filter: [],
756
+ sort: ["-updated"]
757
+ };
758
+ }
759
+ getCollection() {
760
+ return this.pb.collection("Projects");
761
+ }
762
+ async validateInput(input) {
763
+ return UserInputSchema.parse(input);
764
+ }
765
+ };
766
+
767
+ // src/migration/errors.ts
768
+ var MigrationError = class _MigrationError extends Error {
769
+ constructor(message) {
770
+ super(message);
771
+ this.name = "MigrationError";
772
+ Object.setPrototypeOf(this, _MigrationError.prototype);
773
+ }
774
+ };
775
+ var SchemaParsingError = class _SchemaParsingError extends MigrationError {
776
+ filePath;
777
+ originalError;
778
+ constructor(message, filePath, originalError) {
779
+ super(message);
780
+ this.name = "SchemaParsingError";
781
+ this.filePath = filePath;
782
+ this.originalError = originalError;
783
+ Object.setPrototypeOf(this, _SchemaParsingError.prototype);
784
+ }
785
+ /**
786
+ * Creates a formatted error message with file path and original error details
787
+ */
788
+ getDetailedMessage() {
789
+ const parts = [this.message];
790
+ if (this.filePath) {
791
+ parts.push(`
792
+ File: ${this.filePath}`);
793
+ }
794
+ if (this.originalError) {
795
+ parts.push(`
796
+ Cause: ${this.originalError.message}`);
797
+ }
798
+ return parts.join("");
799
+ }
800
+ };
801
+ var SnapshotError = class _SnapshotError extends MigrationError {
802
+ snapshotPath;
803
+ operation;
804
+ originalError;
805
+ constructor(message, snapshotPath, operation, originalError) {
806
+ super(message);
807
+ this.name = "SnapshotError";
808
+ this.snapshotPath = snapshotPath;
809
+ this.operation = operation;
810
+ this.originalError = originalError;
811
+ Object.setPrototypeOf(this, _SnapshotError.prototype);
812
+ }
813
+ /**
814
+ * Creates a formatted error message with snapshot path and operation details
815
+ */
816
+ getDetailedMessage() {
817
+ const parts = [this.message];
818
+ if (this.operation) {
819
+ parts.push(`
820
+ Operation: ${this.operation}`);
821
+ }
822
+ if (this.snapshotPath) {
823
+ parts.push(`
824
+ Snapshot: ${this.snapshotPath}`);
825
+ }
826
+ if (this.originalError) {
827
+ parts.push(`
828
+ Cause: ${this.originalError.message}`);
829
+ }
830
+ return parts.join("");
831
+ }
832
+ };
833
+ var MigrationGenerationError = class _MigrationGenerationError extends MigrationError {
834
+ migrationPath;
835
+ originalError;
836
+ constructor(message, migrationPath, originalError) {
837
+ super(message);
838
+ this.name = "MigrationGenerationError";
839
+ this.migrationPath = migrationPath;
840
+ this.originalError = originalError;
841
+ Object.setPrototypeOf(this, _MigrationGenerationError.prototype);
842
+ }
843
+ /**
844
+ * Creates a formatted error message with migration path and original error details
845
+ */
846
+ getDetailedMessage() {
847
+ const parts = [this.message];
848
+ if (this.migrationPath) {
849
+ parts.push(`
850
+ Migration: ${this.migrationPath}`);
851
+ }
852
+ if (this.originalError) {
853
+ parts.push(`
854
+ Cause: ${this.originalError.message}`);
855
+ }
856
+ return parts.join("");
857
+ }
858
+ };
859
+ var FileSystemError = class _FileSystemError extends MigrationError {
860
+ path;
861
+ operation;
862
+ code;
863
+ originalError;
864
+ constructor(message, path6, operation, code, originalError) {
865
+ super(message);
866
+ this.name = "FileSystemError";
867
+ this.path = path6;
868
+ this.operation = operation;
869
+ this.code = code;
870
+ this.originalError = originalError;
871
+ Object.setPrototypeOf(this, _FileSystemError.prototype);
872
+ }
873
+ /**
874
+ * Creates a formatted error message with path, operation, and error code details
875
+ */
876
+ getDetailedMessage() {
877
+ const parts = [this.message];
878
+ if (this.operation) {
879
+ parts.push(`
880
+ Operation: ${this.operation}`);
881
+ }
882
+ if (this.path) {
883
+ parts.push(`
884
+ Path: ${this.path}`);
885
+ }
886
+ if (this.code) {
887
+ parts.push(`
888
+ Error Code: ${this.code}`);
889
+ }
890
+ if (this.originalError) {
891
+ parts.push(`
892
+ Cause: ${this.originalError.message}`);
893
+ }
894
+ return parts.join("");
895
+ }
896
+ };
897
+ var ConfigurationError = class _ConfigurationError extends MigrationError {
898
+ configPath;
899
+ invalidFields;
900
+ originalError;
901
+ constructor(message, configPath, invalidFields, originalError) {
902
+ super(message);
903
+ this.name = "ConfigurationError";
904
+ this.configPath = configPath;
905
+ this.invalidFields = invalidFields;
906
+ this.originalError = originalError;
907
+ Object.setPrototypeOf(this, _ConfigurationError.prototype);
908
+ }
909
+ /**
910
+ * Creates a formatted error message with configuration details
911
+ */
912
+ getDetailedMessage() {
913
+ const parts = [this.message];
914
+ if (this.configPath) {
915
+ parts.push(`
916
+ Configuration File: ${this.configPath}`);
917
+ }
918
+ if (this.invalidFields && this.invalidFields.length > 0) {
919
+ parts.push(`
920
+ Invalid Fields: ${this.invalidFields.join(", ")}`);
921
+ }
922
+ if (this.originalError) {
923
+ parts.push(`
924
+ Cause: ${this.originalError.message}`);
925
+ }
926
+ return parts.join("");
927
+ }
928
+ };
929
+ var CLIUsageError = class _CLIUsageError extends MigrationError {
930
+ command;
931
+ suggestion;
932
+ constructor(message, command, suggestion) {
933
+ super(message);
934
+ this.name = "CLIUsageError";
935
+ this.command = command;
936
+ this.suggestion = suggestion;
937
+ Object.setPrototypeOf(this, _CLIUsageError.prototype);
938
+ }
939
+ /**
940
+ * Creates a formatted error message with usage suggestions
941
+ */
942
+ getDetailedMessage() {
943
+ const parts = [this.message];
944
+ if (this.command) {
945
+ parts.push(`
946
+ Command: ${this.command}`);
947
+ }
948
+ if (this.suggestion) {
949
+ parts.push(`
950
+ Suggestion: ${this.suggestion}`);
951
+ }
952
+ return parts.join("");
953
+ }
954
+ };
955
+
956
+ // src/migration/rule-validator.ts
957
+ var RuleValidator = class {
958
+ fields;
959
+ collectionName;
960
+ isAuthCollection;
961
+ constructor(collectionName, fields, isAuthCollection2 = false) {
962
+ this.collectionName = collectionName;
963
+ this.fields = new Map(fields.map((f) => [f.name, f]));
964
+ this.isAuthCollection = isAuthCollection2;
965
+ this.addSystemFields();
966
+ }
967
+ /**
968
+ * Add system fields that are always available in PocketBase collections
969
+ * These fields are automatically added by PocketBase and can be referenced in rules
970
+ */
971
+ addSystemFields() {
972
+ const systemFields = [
973
+ { name: "id", type: "text", required: true, options: {} },
974
+ { name: "created", type: "date", required: true, options: {} },
975
+ { name: "updated", type: "date", required: true, options: {} },
976
+ { name: "collectionId", type: "text", required: true, options: {} },
977
+ { name: "collectionName", type: "text", required: true, options: {} }
978
+ ];
979
+ if (this.isAuthCollection) {
980
+ systemFields.push(
981
+ { name: "email", type: "email", required: true, options: {} },
982
+ { name: "emailVisibility", type: "bool", required: false, options: {} },
983
+ { name: "verified", type: "bool", required: false, options: {} },
984
+ { name: "tokenKey", type: "text", required: true, options: {} },
985
+ { name: "password", type: "text", required: true, options: {} }
986
+ );
987
+ }
988
+ for (const field of systemFields) {
989
+ if (!this.fields.has(field.name)) {
990
+ this.fields.set(field.name, field);
991
+ }
992
+ }
993
+ }
994
+ /**
995
+ * Validate a rule expression
996
+ *
997
+ * @param ruleType - The type of rule being validated
998
+ * @param expression - The rule expression to validate
999
+ * @returns Validation result with errors, warnings, and field references
1000
+ */
1001
+ validate(ruleType, expression) {
1002
+ const result = {
1003
+ valid: true,
1004
+ errors: [],
1005
+ warnings: [],
1006
+ fieldReferences: []
1007
+ };
1008
+ if (expression === null) {
1009
+ return result;
1010
+ }
1011
+ if (expression === "") {
1012
+ result.warnings.push(`${ruleType} is public - anyone can perform this operation`);
1013
+ return result;
1014
+ }
1015
+ if (ruleType === "manageRule" && !this.isAuthCollection) {
1016
+ result.valid = false;
1017
+ result.errors.push("manageRule is only valid for auth collections");
1018
+ return result;
1019
+ }
1020
+ const fieldRefs = this.extractFieldReferences(expression);
1021
+ result.fieldReferences = fieldRefs;
1022
+ for (const fieldRef of fieldRefs) {
1023
+ this.validateFieldReference(fieldRef, result);
1024
+ }
1025
+ this.validateRequestReferences(expression, result);
1026
+ this.validateSyntax(expression, result);
1027
+ return result;
1028
+ }
1029
+ /**
1030
+ * Extract field references from expression
1031
+ *
1032
+ * Matches field names that are not @request references.
1033
+ * Handles dot notation for relations: user.email, post.author.name
1034
+ *
1035
+ * @param expression - The rule expression
1036
+ * @returns Array of unique field references
1037
+ */
1038
+ extractFieldReferences(expression) {
1039
+ const refs = [];
1040
+ let cleaned = expression.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''");
1041
+ cleaned = cleaned.replace(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g, "");
1042
+ const fieldPattern = /(?:^|[^@\w])([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)(?=[^a-zA-Z0-9_.]|$)/g;
1043
+ let match;
1044
+ while ((match = fieldPattern.exec(cleaned)) !== null) {
1045
+ const ref = match[1];
1046
+ if (!this.isKeyword(ref)) {
1047
+ refs.push(ref);
1048
+ }
1049
+ }
1050
+ return [...new Set(refs)];
1051
+ }
1052
+ /**
1053
+ * Check if a word is a PocketBase keyword
1054
+ *
1055
+ * @param word - The word to check
1056
+ * @returns True if the word is a keyword
1057
+ */
1058
+ isKeyword(word) {
1059
+ const keywords = ["true", "false", "null", "AND", "OR", "NOT", "LIKE", "IN"];
1060
+ return keywords.includes(word.toUpperCase());
1061
+ }
1062
+ /**
1063
+ * Validate a field reference exists in schema
1064
+ *
1065
+ * Checks if the root field exists and validates relation chains.
1066
+ * For nested references, warns about potential issues since we can't
1067
+ * validate across collections without loading related schemas.
1068
+ *
1069
+ * @param fieldRef - The field reference to validate (e.g., "user" or "user.email")
1070
+ * @param result - The validation result to update
1071
+ */
1072
+ validateFieldReference(fieldRef, result) {
1073
+ const parts = fieldRef.split(".");
1074
+ const rootField = parts[0];
1075
+ if (!this.fields.has(rootField)) {
1076
+ result.errors.push(`Field '${rootField}' does not exist in collection '${this.collectionName}'`);
1077
+ result.valid = false;
1078
+ return;
1079
+ }
1080
+ if (parts.length > 1) {
1081
+ const field = this.fields.get(rootField);
1082
+ if (field.type !== "relation") {
1083
+ result.errors.push(`Field '${rootField}' is not a relation field, cannot access nested property '${parts[1]}'`);
1084
+ result.valid = false;
1085
+ } else {
1086
+ result.warnings.push(
1087
+ `Nested field reference '${fieldRef}' - ensure target collection has field '${parts.slice(1).join(".")}'`
1088
+ );
1089
+ }
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Validate @request references
1094
+ *
1095
+ * Checks that @request references follow valid PocketBase patterns:
1096
+ * - @request.auth.* - authenticated user data
1097
+ * - @request.body.* - request body fields
1098
+ * - @request.query.* - query parameters
1099
+ * - @request.headers.* - request headers
1100
+ * - @request.method - HTTP method
1101
+ * - @request.context - execution context
1102
+ *
1103
+ * @param expression - The rule expression
1104
+ * @param result - The validation result to update
1105
+ */
1106
+ validateRequestReferences(expression, result) {
1107
+ const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
1108
+ for (const ref of requestRefs) {
1109
+ const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
1110
+ if (!isValid) {
1111
+ result.errors.push(`Invalid @request reference: '${ref}'`);
1112
+ result.valid = false;
1113
+ }
1114
+ }
1115
+ }
1116
+ /**
1117
+ * Validate basic syntax patterns
1118
+ *
1119
+ * Checks for:
1120
+ * - Balanced parentheses
1121
+ * - Common operator mistakes (== instead of =)
1122
+ *
1123
+ * @param expression - The rule expression
1124
+ * @param result - The validation result to update
1125
+ */
1126
+ validateSyntax(expression, result) {
1127
+ let parenCount = 0;
1128
+ for (const char of expression) {
1129
+ if (char === "(") parenCount++;
1130
+ if (char === ")") parenCount--;
1131
+ if (parenCount < 0) {
1132
+ result.errors.push("Unbalanced parentheses in expression");
1133
+ result.valid = false;
1134
+ return;
1135
+ }
1136
+ }
1137
+ if (parenCount !== 0) {
1138
+ result.errors.push("Unbalanced parentheses in expression");
1139
+ result.valid = false;
1140
+ }
1141
+ if (expression.includes("==")) {
1142
+ result.warnings.push("Use '=' instead of '==' for equality comparison in PocketBase rules");
1143
+ }
1144
+ }
1145
+ };
1146
+
1147
+ // src/migration/permission-analyzer.ts
1148
+ var PermissionAnalyzer = class {
1149
+ /**
1150
+ * Extract permission metadata from Zod schema description
1151
+ *
1152
+ * Zod schemas can have permission metadata attached via the describe() method.
1153
+ * This method parses the description and extracts the permission configuration.
1154
+ *
1155
+ * @param schemaDescription - The Zod schema description string
1156
+ * @returns Permission schema if found, null otherwise
1157
+ *
1158
+ * @example
1159
+ * ```typescript
1160
+ * const analyzer = new PermissionAnalyzer();
1161
+ * const permissions = analyzer.extractPermissions(schema.description);
1162
+ * ```
1163
+ */
1164
+ extractPermissions(schemaDescription) {
1165
+ if (!schemaDescription) {
1166
+ return null;
1167
+ }
1168
+ try {
1169
+ const metadata = JSON.parse(schemaDescription);
1170
+ if (metadata.permissions) {
1171
+ return metadata.permissions;
1172
+ }
1173
+ } catch {
1174
+ return null;
1175
+ }
1176
+ return null;
1177
+ }
1178
+ /**
1179
+ * Resolve template configuration to concrete rules
1180
+ *
1181
+ * Takes either a template configuration or a direct permission schema
1182
+ * and returns a fully resolved permission schema with all rules defined.
1183
+ *
1184
+ * If the input is already a permission schema (has rule properties),
1185
+ * it's returned as-is. Otherwise, the template is resolved using the
1186
+ * template resolver.
1187
+ *
1188
+ * @param config - Template configuration or direct permission schema
1189
+ * @returns Resolved permission schema
1190
+ *
1191
+ * @example
1192
+ * ```typescript
1193
+ * const analyzer = new PermissionAnalyzer();
1194
+ *
1195
+ * // Resolve from template
1196
+ * const permissions = analyzer.resolvePermissions({
1197
+ * template: 'owner-only',
1198
+ * ownerField: 'User'
1199
+ * });
1200
+ *
1201
+ * // Or pass direct schema
1202
+ * const permissions = analyzer.resolvePermissions({
1203
+ * listRule: '@request.auth.id != ""',
1204
+ * viewRule: '@request.auth.id != ""'
1205
+ * });
1206
+ * ```
1207
+ */
1208
+ resolvePermissions(config) {
1209
+ if ("listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config) {
1210
+ return config;
1211
+ }
1212
+ return resolveTemplate(config);
1213
+ }
1214
+ /**
1215
+ * Validate all rules in a permission schema
1216
+ *
1217
+ * Validates each rule in the permission schema against the collection's
1218
+ * field definitions. Returns a map of validation results keyed by rule type.
1219
+ *
1220
+ * Only validates rules that are defined (not undefined). Undefined rules
1221
+ * are treated as null (locked) by default.
1222
+ *
1223
+ * @param collectionName - Name of the collection being validated
1224
+ * @param permissions - Permission schema to validate
1225
+ * @param fields - Collection field definitions
1226
+ * @param isAuthCollection - Whether this is an auth collection (allows manageRule)
1227
+ * @returns Map of validation results by rule type
1228
+ *
1229
+ * @example
1230
+ * ```typescript
1231
+ * const analyzer = new PermissionAnalyzer();
1232
+ * const results = analyzer.validatePermissions(
1233
+ * 'posts',
1234
+ * { listRule: '@request.auth.id != ""', viewRule: 'author = @request.auth.id' },
1235
+ * fields,
1236
+ * false
1237
+ * );
1238
+ *
1239
+ * for (const [ruleType, result] of results) {
1240
+ * if (!result.valid) {
1241
+ * console.error(`${ruleType} validation failed:`, result.errors);
1242
+ * }
1243
+ * }
1244
+ * ```
1245
+ */
1246
+ validatePermissions(collectionName, permissions, fields, isAuthCollection2 = false) {
1247
+ const validator = new RuleValidator(collectionName, fields, isAuthCollection2);
1248
+ const results = /* @__PURE__ */ new Map();
1249
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
1250
+ if (isAuthCollection2) {
1251
+ ruleTypes.push("manageRule");
1252
+ }
1253
+ for (const ruleType of ruleTypes) {
1254
+ const expression = permissions[ruleType];
1255
+ if (expression !== void 0) {
1256
+ results.set(ruleType, validator.validate(ruleType, expression));
1257
+ }
1258
+ }
1259
+ return results;
1260
+ }
1261
+ /**
1262
+ * Merge permissions with defaults
1263
+ *
1264
+ * Ensures all rule types have a defined value. Undefined rules are set
1265
+ * to null (locked to superusers only), which is the PocketBase default.
1266
+ *
1267
+ * This is useful when generating migrations to ensure all rules are
1268
+ * explicitly set in the collection configuration.
1269
+ *
1270
+ * @param permissions - Permission schema (may have undefined rules)
1271
+ * @returns Permission schema with all rules defined (null if not specified)
1272
+ *
1273
+ * @example
1274
+ * ```typescript
1275
+ * const analyzer = new PermissionAnalyzer();
1276
+ * const merged = analyzer.mergeWithDefaults({
1277
+ * listRule: '@request.auth.id != ""'
1278
+ * // other rules undefined
1279
+ * });
1280
+ *
1281
+ * // Result:
1282
+ * // {
1283
+ * // listRule: '@request.auth.id != ""',
1284
+ * // viewRule: null,
1285
+ * // createRule: null,
1286
+ * // updateRule: null,
1287
+ * // deleteRule: null,
1288
+ * // manageRule: null
1289
+ * // }
1290
+ * ```
1291
+ */
1292
+ mergeWithDefaults(permissions) {
1293
+ return {
1294
+ listRule: permissions.listRule ?? null,
1295
+ viewRule: permissions.viewRule ?? null,
1296
+ createRule: permissions.createRule ?? null,
1297
+ updateRule: permissions.updateRule ?? null,
1298
+ deleteRule: permissions.deleteRule ?? null,
1299
+ manageRule: permissions.manageRule ?? null
1300
+ };
1301
+ }
1302
+ };
1303
+
1304
+ // src/migration/utils/pluralize.ts
1305
+ var SPECIAL_CASES = {
1306
+ // Common irregular plurals
1307
+ person: "people",
1308
+ Person: "People",
1309
+ child: "children",
1310
+ Child: "Children",
1311
+ man: "men",
1312
+ Man: "Men",
1313
+ woman: "women",
1314
+ Woman: "Women",
1315
+ tooth: "teeth",
1316
+ Tooth: "Teeth",
1317
+ foot: "feet",
1318
+ Foot: "Feet",
1319
+ mouse: "mice",
1320
+ Mouse: "Mice",
1321
+ goose: "geese",
1322
+ Goose: "Geese",
1323
+ // Words ending in -y
1324
+ category: "categories",
1325
+ Category: "Categories",
1326
+ company: "companies",
1327
+ Company: "Companies",
1328
+ city: "cities",
1329
+ City: "Cities",
1330
+ country: "countries",
1331
+ Country: "Countries",
1332
+ story: "stories",
1333
+ Story: "Stories",
1334
+ party: "parties",
1335
+ Party: "Parties",
1336
+ family: "families",
1337
+ Family: "Families",
1338
+ activity: "activities",
1339
+ Activity: "Activities",
1340
+ priority: "priorities",
1341
+ Priority: "Priorities",
1342
+ // Words ending in -f or -fe
1343
+ life: "lives",
1344
+ Life: "Lives",
1345
+ wife: "wives",
1346
+ Wife: "Wives",
1347
+ knife: "knives",
1348
+ Knife: "Knives",
1349
+ leaf: "leaves",
1350
+ Leaf: "Leaves",
1351
+ shelf: "shelves",
1352
+ Shelf: "Shelves",
1353
+ half: "halves",
1354
+ Half: "Halves",
1355
+ // Words ending in -is
1356
+ analysis: "analyses",
1357
+ Analysis: "Analyses",
1358
+ basis: "bases",
1359
+ Basis: "Bases",
1360
+ crisis: "crises",
1361
+ Crisis: "Crises",
1362
+ thesis: "theses",
1363
+ Thesis: "Theses",
1364
+ // Words ending in -us
1365
+ cactus: "cacti",
1366
+ Cactus: "Cacti",
1367
+ focus: "foci",
1368
+ Focus: "Foci",
1369
+ fungus: "fungi",
1370
+ Fungus: "Fungi",
1371
+ nucleus: "nuclei",
1372
+ Nucleus: "Nuclei",
1373
+ radius: "radii",
1374
+ Radius: "Radii",
1375
+ // Words ending in -on
1376
+ phenomenon: "phenomena",
1377
+ Phenomenon: "Phenomena",
1378
+ criterion: "criteria",
1379
+ Criterion: "Criteria",
1380
+ // Words ending in -um
1381
+ datum: "data",
1382
+ Datum: "Data",
1383
+ medium: "media",
1384
+ Medium: "Media",
1385
+ curriculum: "curricula",
1386
+ Curriculum: "Curricula",
1387
+ // Unchanged plurals
1388
+ sheep: "sheep",
1389
+ Sheep: "Sheep",
1390
+ deer: "deer",
1391
+ Deer: "Deer",
1392
+ fish: "fish",
1393
+ Fish: "Fish",
1394
+ species: "species",
1395
+ Species: "Species",
1396
+ series: "series",
1397
+ Series: "Series"
1398
+ };
1399
+ function pluralize(singular) {
1400
+ if (SPECIAL_CASES[singular]) {
1401
+ return SPECIAL_CASES[singular];
1402
+ }
1403
+ if (singular.length > 3 && singular.endsWith("s") && !singular.endsWith("ss")) {
1404
+ return singular;
1405
+ }
1406
+ const lowerSingular = singular.toLowerCase();
1407
+ let plural;
1408
+ if (/(?:s|ss|sh|ch|x|z)$/.test(lowerSingular)) {
1409
+ plural = singular + "es";
1410
+ } else if (/[^aeiou]y$/.test(lowerSingular)) {
1411
+ plural = singular.slice(0, -1) + "ies";
1412
+ } else if (/[^aeiou]o$/.test(lowerSingular)) {
1413
+ plural = singular + "es";
1414
+ } else if (/fe?$/.test(lowerSingular)) {
1415
+ if (lowerSingular.endsWith("fe")) {
1416
+ plural = singular.slice(0, -2) + "ves";
1417
+ } else {
1418
+ plural = singular.slice(0, -1) + "ves";
1419
+ }
1420
+ } else {
1421
+ plural = singular + "s";
1422
+ }
1423
+ return plural;
1424
+ }
1425
+ function toCollectionName(entityName) {
1426
+ return pluralize(entityName);
1427
+ }
1428
+ function singularize(plural) {
1429
+ for (const [singular, pluralForm] of Object.entries(SPECIAL_CASES)) {
1430
+ if (pluralForm === plural) {
1431
+ return singular;
1432
+ }
1433
+ }
1434
+ const lower = plural.toLowerCase();
1435
+ if (lower.endsWith("ies") && plural.length > 3) {
1436
+ return plural.slice(0, -3) + "y";
1437
+ }
1438
+ if (lower.endsWith("ves")) {
1439
+ return plural.slice(0, -3) + "fe";
1440
+ }
1441
+ if (/(?:ses|shes|ches|xes|zes)$/.test(lower)) {
1442
+ return plural.slice(0, -2);
1443
+ }
1444
+ if (lower.endsWith("s") && plural.length > 1) {
1445
+ return plural.slice(0, -1);
1446
+ }
1447
+ return plural;
1448
+ }
1449
+ function isSingleRelationField(fieldName, zodType) {
1450
+ let unwrappedType = zodType;
1451
+ if (zodType instanceof zod.z.ZodOptional) {
1452
+ unwrappedType = zodType._def.innerType;
1453
+ }
1454
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1455
+ unwrappedType = unwrappedType._def.innerType;
1456
+ }
1457
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1458
+ unwrappedType = unwrappedType._def.innerType;
1459
+ }
1460
+ if (!(unwrappedType instanceof zod.z.ZodString)) {
1461
+ return false;
1462
+ }
1463
+ const startsWithUppercase = /^[A-Z]/.test(fieldName);
1464
+ const commonStringFields = ["Title", "Name", "Description", "Content", "Summary", "Status", "Type"];
1465
+ const isCommonField = commonStringFields.includes(fieldName);
1466
+ return startsWithUppercase && !isCommonField;
1467
+ }
1468
+ function isMultipleRelationField(fieldName, zodType) {
1469
+ let unwrappedType = zodType;
1470
+ if (zodType instanceof zod.z.ZodOptional) {
1471
+ unwrappedType = zodType._def.innerType;
1472
+ }
1473
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1474
+ unwrappedType = unwrappedType._def.innerType;
1475
+ }
1476
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1477
+ unwrappedType = unwrappedType._def.innerType;
1478
+ }
1479
+ if (!(unwrappedType instanceof zod.z.ZodArray)) {
1480
+ return false;
1481
+ }
1482
+ const elementType = unwrappedType._def.type;
1483
+ if (!(elementType instanceof zod.z.ZodString)) {
1484
+ return false;
1485
+ }
1486
+ const hasUppercase = /[A-Z]/.test(fieldName);
1487
+ return hasUppercase;
1488
+ }
1489
+ function resolveTargetCollection(fieldName) {
1490
+ const matches = fieldName.match(/[A-Z][a-z]+/g);
1491
+ if (!matches || matches.length === 0) {
1492
+ return pluralize(fieldName);
1493
+ }
1494
+ const entityName = matches[matches.length - 1];
1495
+ return pluralize(entityName);
1496
+ }
1497
+ function isRelationField(fieldName, zodType) {
1498
+ return isSingleRelationField(fieldName, zodType) || isMultipleRelationField(fieldName, zodType);
1499
+ }
1500
+ function getMaxSelect(fieldName, zodType) {
1501
+ if (isSingleRelationField(fieldName, zodType)) {
1502
+ return 1;
1503
+ }
1504
+ if (isMultipleRelationField(fieldName, zodType)) {
1505
+ let unwrappedType = zodType;
1506
+ if (zodType instanceof zod.z.ZodOptional) {
1507
+ unwrappedType = zodType._def.innerType;
1508
+ }
1509
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1510
+ unwrappedType = unwrappedType._def.innerType;
1511
+ }
1512
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1513
+ unwrappedType = unwrappedType._def.innerType;
1514
+ }
1515
+ if (unwrappedType instanceof zod.z.ZodArray) {
1516
+ const arrayDef = unwrappedType._def;
1517
+ if (arrayDef.maxLength) {
1518
+ return arrayDef.maxLength.value;
1519
+ }
1520
+ return 999;
1521
+ }
1522
+ }
1523
+ return 1;
1524
+ }
1525
+ function getMinSelect(fieldName, zodType) {
1526
+ if (!isMultipleRelationField(fieldName, zodType)) {
1527
+ return void 0;
1528
+ }
1529
+ let unwrappedType = zodType;
1530
+ if (zodType instanceof zod.z.ZodOptional) {
1531
+ unwrappedType = zodType._def.innerType;
1532
+ }
1533
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1534
+ unwrappedType = unwrappedType._def.innerType;
1535
+ }
1536
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1537
+ unwrappedType = unwrappedType._def.innerType;
1538
+ }
1539
+ if (unwrappedType instanceof zod.z.ZodArray) {
1540
+ const arrayDef = unwrappedType._def;
1541
+ if (arrayDef.minLength) {
1542
+ return arrayDef.minLength.value;
1543
+ }
1544
+ }
1545
+ return void 0;
1546
+ }
1547
+ var POCKETBASE_FIELD_TYPES = [
1548
+ "text",
1549
+ "email",
1550
+ "url",
1551
+ "number",
1552
+ "bool",
1553
+ "date",
1554
+ "select",
1555
+ "relation",
1556
+ "file",
1557
+ "json",
1558
+ "editor",
1559
+ "geoPoint",
1560
+ "autodate"
1561
+ ];
1562
+ var FIELD_TYPE_INFO = {
1563
+ text: {
1564
+ type: "text",
1565
+ description: "Plain text field",
1566
+ zodTypes: ["ZodString"],
1567
+ supportsMultiple: false
1568
+ },
1569
+ email: {
1570
+ type: "email",
1571
+ description: "Email address field with validation",
1572
+ zodTypes: ["ZodString with email()"],
1573
+ supportsMultiple: false
1574
+ },
1575
+ url: {
1576
+ type: "url",
1577
+ description: "URL field with validation",
1578
+ zodTypes: ["ZodString with url()"],
1579
+ supportsMultiple: false
1580
+ },
1581
+ editor: {
1582
+ type: "editor",
1583
+ description: "Rich text editor field",
1584
+ zodTypes: ["ZodString"],
1585
+ supportsMultiple: false
1586
+ },
1587
+ number: {
1588
+ type: "number",
1589
+ description: "Numeric field (integer or float)",
1590
+ zodTypes: ["ZodNumber"],
1591
+ supportsMultiple: false
1592
+ },
1593
+ bool: {
1594
+ type: "bool",
1595
+ description: "Boolean field",
1596
+ zodTypes: ["ZodBoolean"],
1597
+ supportsMultiple: false
1598
+ },
1599
+ date: {
1600
+ type: "date",
1601
+ description: "Date/datetime field",
1602
+ zodTypes: ["ZodDate", "ZodString with datetime format"],
1603
+ supportsMultiple: false
1604
+ },
1605
+ autodate: {
1606
+ type: "autodate",
1607
+ description: "Auto-managed date field (created/updated)",
1608
+ zodTypes: ["ZodString"],
1609
+ supportsMultiple: false
1610
+ },
1611
+ select: {
1612
+ type: "select",
1613
+ description: "Single or multiple select from predefined values",
1614
+ zodTypes: ["ZodEnum", "ZodArray<ZodEnum>"],
1615
+ supportsMultiple: true
1616
+ },
1617
+ relation: {
1618
+ type: "relation",
1619
+ description: "Reference to another collection",
1620
+ zodTypes: ["ZodString", "ZodArray<ZodString>"],
1621
+ supportsMultiple: true
1622
+ },
1623
+ file: {
1624
+ type: "file",
1625
+ description: "File upload field",
1626
+ zodTypes: ["File", "ZodArray<File>"],
1627
+ supportsMultiple: true
1628
+ },
1629
+ json: {
1630
+ type: "json",
1631
+ description: "JSON data field",
1632
+ zodTypes: ["ZodRecord", "ZodObject", "ZodArray"],
1633
+ supportsMultiple: false
1634
+ },
1635
+ geoPoint: {
1636
+ type: "geoPoint",
1637
+ description: "Geographic coordinates (lon, lat)",
1638
+ zodTypes: ["ZodObject with lon/lat"],
1639
+ supportsMultiple: false
1640
+ }
1641
+ };
1642
+ function mapZodStringType(zodType) {
1643
+ const checks = zodType._def.checks || [];
1644
+ const hasEmail = checks.some((check) => check.kind === "email");
1645
+ if (hasEmail) {
1646
+ return "email";
1647
+ }
1648
+ const hasUrl = checks.some((check) => check.kind === "url");
1649
+ if (hasUrl) {
1650
+ return "url";
1651
+ }
1652
+ const hasDatetime = checks.some((check) => check.kind === "datetime");
1653
+ if (hasDatetime) {
1654
+ return "date";
1655
+ }
1656
+ return "text";
1657
+ }
1658
+ function mapZodNumberType(_zodType) {
1659
+ return "number";
1660
+ }
1661
+ function mapZodBooleanType(_zodType) {
1662
+ return "bool";
1663
+ }
1664
+ function mapZodEnumType(_zodType) {
1665
+ return "select";
1666
+ }
1667
+ function mapZodArrayType(zodType, _fieldName) {
1668
+ const elementType = zodType._def.type;
1669
+ if (elementType instanceof zod.z.ZodType) {
1670
+ const typeName = elementType._def.typeName;
1671
+ if (typeName === "ZodType" && elementType._def?.innerType?.name === "File") {
1672
+ return "file";
1673
+ }
1674
+ }
1675
+ if (elementType._def?.typeName === "ZodType") {
1676
+ const checks = elementType._def?.checks || [];
1677
+ const isFileInstance = checks.some(
1678
+ (check) => check.kind === "instanceof" || elementType._def?.innerType?.name === "File"
1679
+ );
1680
+ if (isFileInstance) {
1681
+ return "file";
1682
+ }
1683
+ }
1684
+ if (elementType instanceof zod.z.ZodString) {
1685
+ return "relation";
1686
+ }
1687
+ return "json";
1688
+ }
1689
+ function mapZodDateType(_zodType) {
1690
+ return "date";
1691
+ }
1692
+ function mapZodRecordType(_zodType) {
1693
+ return "json";
1694
+ }
1695
+ function mapZodTypeToPocketBase(zodType, fieldName) {
1696
+ let unwrappedType = zodType;
1697
+ if (zodType instanceof zod.z.ZodOptional) {
1698
+ unwrappedType = zodType._def.innerType;
1699
+ }
1700
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1701
+ unwrappedType = unwrappedType._def.innerType;
1702
+ }
1703
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1704
+ unwrappedType = unwrappedType._def.innerType;
1705
+ }
1706
+ if (unwrappedType._def?.typeName === "ZodEffects") {
1707
+ const effect = unwrappedType._def?.effect;
1708
+ if (effect?.type === "refinement") {
1709
+ const fileFieldNames = ["avatar", "image", "file", "attachment", "photo", "picture", "document", "upload"];
1710
+ if (fileFieldNames.some((name) => fieldName.toLowerCase().includes(name))) {
1711
+ return "file";
1712
+ }
1713
+ }
1714
+ }
1715
+ if (unwrappedType._def?.typeName === "ZodType") {
1716
+ const checks = unwrappedType._def?.checks || [];
1717
+ const innerType = unwrappedType._def?.innerType;
1718
+ if (innerType?.name === "File" || checks.some((check) => check.kind === "instanceof")) {
1719
+ return "file";
1720
+ }
1721
+ }
1722
+ if (unwrappedType instanceof zod.z.ZodString) {
1723
+ return mapZodStringType(unwrappedType);
1724
+ }
1725
+ if (unwrappedType instanceof zod.z.ZodNumber) {
1726
+ return mapZodNumberType();
1727
+ }
1728
+ if (unwrappedType instanceof zod.z.ZodBoolean) {
1729
+ return mapZodBooleanType();
1730
+ }
1731
+ if (unwrappedType instanceof zod.z.ZodEnum) {
1732
+ return mapZodEnumType();
1733
+ }
1734
+ if (unwrappedType instanceof zod.z.ZodArray) {
1735
+ return mapZodArrayType(unwrappedType);
1736
+ }
1737
+ if (unwrappedType instanceof zod.z.ZodDate) {
1738
+ return mapZodDateType();
1739
+ }
1740
+ if (unwrappedType instanceof zod.z.ZodRecord || unwrappedType instanceof zod.z.ZodObject) {
1741
+ return mapZodRecordType();
1742
+ }
1743
+ return "text";
1744
+ }
1745
+ function extractFieldOptions(zodType) {
1746
+ const options = {};
1747
+ let unwrappedType = zodType;
1748
+ if (zodType instanceof zod.z.ZodOptional) {
1749
+ unwrappedType = zodType._def.innerType;
1750
+ }
1751
+ if (unwrappedType instanceof zod.z.ZodNullable) {
1752
+ unwrappedType = unwrappedType._def.innerType;
1753
+ }
1754
+ if (unwrappedType instanceof zod.z.ZodDefault) {
1755
+ unwrappedType = unwrappedType._def.innerType;
1756
+ }
1757
+ const checks = unwrappedType._def?.checks || [];
1758
+ if (unwrappedType instanceof zod.z.ZodString) {
1759
+ for (const check of checks) {
1760
+ if (check.kind === "min") {
1761
+ options.min = check.value;
1762
+ }
1763
+ if (check.kind === "max") {
1764
+ options.max = check.value;
1765
+ }
1766
+ if (check.kind === "regex") {
1767
+ options.pattern = check.regex.source;
1768
+ }
1769
+ }
1770
+ }
1771
+ if (unwrappedType instanceof zod.z.ZodNumber) {
1772
+ for (const check of checks) {
1773
+ if (check.kind === "min") {
1774
+ options.min = check.value;
1775
+ }
1776
+ if (check.kind === "max") {
1777
+ options.max = check.value;
1778
+ }
1779
+ }
1780
+ }
1781
+ if (unwrappedType instanceof zod.z.ZodEnum) {
1782
+ options.values = unwrappedType._def.values;
1783
+ }
1784
+ if (unwrappedType instanceof zod.z.ZodArray) {
1785
+ const arrayChecks = unwrappedType._def?.checks || [];
1786
+ for (const check of arrayChecks) {
1787
+ if (check.kind === "min") {
1788
+ options.minSelect = check.value;
1789
+ }
1790
+ if (check.kind === "max") {
1791
+ options.maxSelect = check.value;
1792
+ }
1793
+ }
1794
+ }
1795
+ return options;
1796
+ }
1797
+ function isFieldRequired(zodType) {
1798
+ if (zodType instanceof zod.z.ZodOptional) {
1799
+ return false;
1800
+ }
1801
+ if (zodType instanceof zod.z.ZodDefault) {
1802
+ return false;
1803
+ }
1804
+ if (zodType instanceof zod.z.ZodNullable) {
1805
+ return false;
1806
+ }
1807
+ return true;
1808
+ }
1809
+ function unwrapZodType(zodType) {
1810
+ let unwrapped = zodType;
1811
+ if (unwrapped instanceof zod.z.ZodOptional) {
1812
+ unwrapped = unwrapped._def.innerType;
1813
+ }
1814
+ if (unwrapped instanceof zod.z.ZodNullable) {
1815
+ unwrapped = unwrapped._def.innerType;
1816
+ }
1817
+ if (unwrapped instanceof zod.z.ZodDefault) {
1818
+ unwrapped = unwrapped._def.innerType;
1819
+ }
1820
+ return unwrapped;
1821
+ }
1822
+ function getDefaultValue(zodType) {
1823
+ if (zodType instanceof zod.z.ZodDefault) {
1824
+ return zodType._def.defaultValue();
1825
+ }
1826
+ return void 0;
1827
+ }
1828
+ function isArrayType(zodType) {
1829
+ const unwrapped = unwrapZodType(zodType);
1830
+ return unwrapped instanceof zod.z.ZodArray;
1831
+ }
1832
+ function getArrayElementType(zodType) {
1833
+ const unwrapped = unwrapZodType(zodType);
1834
+ if (unwrapped instanceof zod.z.ZodArray) {
1835
+ return unwrapped._def.type;
1836
+ }
1837
+ return null;
1838
+ }
1839
+ function isGeoPointType(zodType) {
1840
+ const unwrapped = unwrapZodType(zodType);
1841
+ if (!(unwrapped instanceof zod.z.ZodObject)) {
1842
+ return false;
1843
+ }
1844
+ const shape = unwrapped._def.shape();
1845
+ const hasLon = "lon" in shape && shape.lon instanceof zod.z.ZodNumber;
1846
+ const hasLat = "lat" in shape && shape.lat instanceof zod.z.ZodNumber;
1847
+ return hasLon && hasLat;
1848
+ }
1849
+ function extractComprehensiveFieldOptions(zodType) {
1850
+ const options = {};
1851
+ const unwrapped = unwrapZodType(zodType);
1852
+ const checks = unwrapped._def?.checks || [];
1853
+ if (unwrapped instanceof zod.z.ZodString) {
1854
+ for (const check of checks) {
1855
+ if (check.kind === "min") {
1856
+ options.min = check.value;
1857
+ }
1858
+ if (check.kind === "max") {
1859
+ options.max = check.value;
1860
+ }
1861
+ if (check.kind === "regex") {
1862
+ options.pattern = check.regex.source;
1863
+ }
1864
+ }
1865
+ }
1866
+ if (unwrapped instanceof zod.z.ZodNumber) {
1867
+ for (const check of checks) {
1868
+ if (check.kind === "min") {
1869
+ options.min = check.value;
1870
+ }
1871
+ if (check.kind === "max") {
1872
+ options.max = check.value;
1873
+ }
1874
+ }
1875
+ }
1876
+ if (unwrapped instanceof zod.z.ZodEnum) {
1877
+ options.values = unwrapped._def.values;
1878
+ }
1879
+ if (unwrapped instanceof zod.z.ZodArray) {
1880
+ const arrayDef = unwrapped._def;
1881
+ if (arrayDef.minLength) {
1882
+ options.minSelect = arrayDef.minLength.value;
1883
+ }
1884
+ if (arrayDef.maxLength) {
1885
+ options.maxSelect = arrayDef.maxLength.value;
1886
+ }
1887
+ const elementType = arrayDef.type;
1888
+ if (elementType instanceof zod.z.ZodEnum) {
1889
+ options.values = elementType._def.values;
1890
+ }
1891
+ }
1892
+ return options;
1893
+ }
1894
+ function isEditorField(fieldName) {
1895
+ const editorFieldNames = [
1896
+ "content",
1897
+ "body",
1898
+ "description",
1899
+ "bio",
1900
+ "about",
1901
+ "summary",
1902
+ "notes",
1903
+ "details",
1904
+ "html",
1905
+ "richtext",
1906
+ "editor"
1907
+ ];
1908
+ return editorFieldNames.some((name) => fieldName.toLowerCase().includes(name));
1909
+ }
1910
+ function isFileFieldByName(fieldName) {
1911
+ const fileFieldNames = [
1912
+ "avatar",
1913
+ "image",
1914
+ "file",
1915
+ "attachment",
1916
+ "photo",
1917
+ "picture",
1918
+ "document",
1919
+ "upload",
1920
+ "thumbnail",
1921
+ "cover",
1922
+ "banner",
1923
+ "logo",
1924
+ "icon",
1925
+ "media"
1926
+ ];
1927
+ return fileFieldNames.some((name) => fieldName.toLowerCase().includes(name));
1928
+ }
1929
+ function getFieldTypeInfo(zodType, fieldName) {
1930
+ const type = mapZodTypeToPocketBase(zodType, fieldName);
1931
+ const isMultiple = isArrayType(zodType);
1932
+ const options = extractComprehensiveFieldOptions(zodType);
1933
+ return {
1934
+ type,
1935
+ isMultiple,
1936
+ options
1937
+ };
1938
+ }
1939
+
1940
+ // src/migration/analyzer.ts
1941
+ var DEFAULT_CONFIG = {
1942
+ workspaceRoot: process.cwd(),
1943
+ excludePatterns: [
1944
+ "base.ts",
1945
+ "index.ts",
1946
+ "permissions.ts",
1947
+ "permission-templates.ts",
1948
+ "base.js",
1949
+ "index.js",
1950
+ "permissions.js",
1951
+ "permission-templates.js"
1952
+ ],
1953
+ includeExtensions: [".ts", ".js"],
1954
+ schemaPatterns: ["Schema", "InputSchema"],
1955
+ useCompiledFiles: true
1956
+ };
1957
+ function mergeConfig(config) {
1958
+ return {
1959
+ ...DEFAULT_CONFIG,
1960
+ ...config,
1961
+ excludePatterns: config.excludePatterns || DEFAULT_CONFIG.excludePatterns,
1962
+ includeExtensions: config.includeExtensions || DEFAULT_CONFIG.includeExtensions,
1963
+ schemaPatterns: config.schemaPatterns || DEFAULT_CONFIG.schemaPatterns
1964
+ };
1965
+ }
1966
+ function resolveSchemaDir(config) {
1967
+ const workspaceRoot = config.workspaceRoot || process.cwd();
1968
+ if (path4__namespace.isAbsolute(config.schemaDir)) {
1969
+ return config.schemaDir;
1970
+ }
1971
+ return path4__namespace.join(workspaceRoot, config.schemaDir);
1972
+ }
1973
+ function discoverSchemaFiles(config) {
1974
+ const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
1975
+ const mergedConfig = mergeConfig(normalizedConfig);
1976
+ const schemaDir = resolveSchemaDir(normalizedConfig);
1977
+ try {
1978
+ if (!fs2__namespace.existsSync(schemaDir)) {
1979
+ throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
1980
+ }
1981
+ const files = fs2__namespace.readdirSync(schemaDir);
1982
+ const schemaFiles = files.filter((file) => {
1983
+ const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
1984
+ if (!hasValidExtension) return false;
1985
+ const isExcluded = mergedConfig.excludePatterns.some((pattern) => {
1986
+ if (pattern.includes("*")) {
1987
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
1988
+ return regex.test(file);
1989
+ }
1990
+ return file === pattern;
1991
+ });
1992
+ if (isExcluded) return false;
1993
+ return true;
1994
+ });
1995
+ return schemaFiles.map((file) => {
1996
+ const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
1997
+ return path4__namespace.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1998
+ });
1999
+ } catch (error) {
2000
+ if (error instanceof FileSystemError) {
2001
+ throw error;
2002
+ }
2003
+ const fsError = error;
2004
+ if (fsError.code === "EACCES" || fsError.code === "EPERM") {
2005
+ throw new FileSystemError(
2006
+ `Permission denied reading schema directory: ${schemaDir}`,
2007
+ schemaDir,
2008
+ "read",
2009
+ fsError.code,
2010
+ error
2011
+ );
2012
+ }
2013
+ throw new FileSystemError(
2014
+ `Failed to read schema directory: ${schemaDir}`,
2015
+ schemaDir,
2016
+ "read",
2017
+ fsError.code,
2018
+ error
2019
+ );
2020
+ }
2021
+ }
2022
+ async function importSchemaModule(filePath, config) {
2023
+ try {
2024
+ let importPath = filePath;
2025
+ if (config?.pathTransformer) {
2026
+ importPath = config.pathTransformer(filePath);
2027
+ }
2028
+ if (!importPath.endsWith(".js")) {
2029
+ importPath = `${importPath}.js`;
2030
+ }
2031
+ const fileUrl = new URL(`file://${path4__namespace.resolve(importPath)}`);
2032
+ const module = await import(fileUrl.href);
2033
+ return module;
2034
+ } catch (error) {
2035
+ throw new SchemaParsingError(
2036
+ `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
2037
+ filePath,
2038
+ error
2039
+ );
2040
+ }
2041
+ }
2042
+ function getCollectionNameFromFile(filePath) {
2043
+ const filename = path4__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
2044
+ return toCollectionName(filename);
2045
+ }
2046
+ function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
2047
+ const result = {};
2048
+ for (const [key, value] of Object.entries(module)) {
2049
+ if (value instanceof zod.z.ZodObject) {
2050
+ if (patterns.includes("InputSchema") && key.endsWith("InputSchema")) {
2051
+ result.inputSchema = value;
2052
+ } else if (patterns.includes("Schema") && key.endsWith("Schema") && !key.endsWith("InputSchema")) {
2053
+ result.schema = value;
2054
+ }
2055
+ }
2056
+ }
2057
+ return result;
2058
+ }
2059
+ function selectSchemaForCollection(schemas) {
2060
+ if (schemas.schema) {
2061
+ return schemas.schema;
2062
+ }
2063
+ if (schemas.inputSchema) {
2064
+ return schemas.inputSchema;
2065
+ }
2066
+ return null;
2067
+ }
2068
+ function extractFieldDefinitions(zodSchema, excludeFields) {
2069
+ const shape = zodSchema.shape;
2070
+ const fields = [];
2071
+ const baseFields = ["id", "collectionId", "collectionName", "created", "updated", "expand"];
2072
+ const defaultExcludeFields = ["thumbnailURL", "imageFiles"];
2073
+ const allExclusions = /* @__PURE__ */ new Set([...baseFields, ...defaultExcludeFields, ...excludeFields || []]);
2074
+ for (const [fieldName, zodType] of Object.entries(shape)) {
2075
+ if (!allExclusions.has(fieldName)) {
2076
+ fields.push({ name: fieldName, zodType });
2077
+ }
2078
+ }
2079
+ return fields;
2080
+ }
2081
+ function isAuthCollection(fields) {
2082
+ const fieldNames = fields.map((f) => f.name.toLowerCase());
2083
+ const hasEmail = fieldNames.includes("email");
2084
+ const hasPassword = fieldNames.includes("password");
2085
+ return hasEmail && hasPassword;
2086
+ }
2087
+ function buildFieldDefinition(fieldName, zodType) {
2088
+ const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
2089
+ const required = isFieldRequired(zodType);
2090
+ const options = extractFieldOptions(zodType);
2091
+ const fieldDef = {
2092
+ name: fieldName,
2093
+ type: fieldType,
2094
+ required,
2095
+ options
2096
+ };
2097
+ if (isRelationField(fieldName, zodType)) {
2098
+ fieldDef.type = "relation";
2099
+ const targetCollection = resolveTargetCollection(fieldName);
2100
+ const maxSelect = getMaxSelect(fieldName, zodType);
2101
+ const minSelect = getMinSelect(fieldName, zodType);
2102
+ fieldDef.relation = {
2103
+ collection: targetCollection,
2104
+ maxSelect,
2105
+ minSelect,
2106
+ cascadeDelete: false
2107
+ // Default to false, can be configured later
2108
+ };
2109
+ }
2110
+ return fieldDef;
2111
+ }
2112
+ function extractIndexes(schema) {
2113
+ const schemaDescription = schema.description;
2114
+ if (!schemaDescription) {
2115
+ return void 0;
2116
+ }
2117
+ try {
2118
+ const metadata = JSON.parse(schemaDescription);
2119
+ if (metadata.indexes && Array.isArray(metadata.indexes)) {
2120
+ return metadata.indexes;
2121
+ }
2122
+ } catch {
2123
+ }
2124
+ return void 0;
2125
+ }
2126
+ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
2127
+ const rawFields = extractFieldDefinitions(zodSchema);
2128
+ const collectionType = isAuthCollection(rawFields) ? "auth" : "base";
2129
+ const fields = rawFields.map(({ name, zodType }) => buildFieldDefinition(name, zodType));
2130
+ const indexes = extractIndexes(zodSchema) || [];
2131
+ const permissionAnalyzer = new PermissionAnalyzer();
2132
+ let permissions = void 0;
2133
+ const schemaDescription = zodSchema.description;
2134
+ const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
2135
+ if (extractedPermissions) {
2136
+ const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
2137
+ const validationResults = permissionAnalyzer.validatePermissions(
2138
+ collectionName,
2139
+ resolvedPermissions,
2140
+ fields,
2141
+ collectionType === "auth"
2142
+ );
2143
+ for (const [ruleType, result] of validationResults) {
2144
+ if (!result.valid) {
2145
+ console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
2146
+ result.errors.forEach((error) => console.error(` - ${error}`));
2147
+ }
2148
+ if (result.warnings.length > 0) {
2149
+ console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
2150
+ result.warnings.forEach((warning) => console.warn(` - ${warning}`));
2151
+ }
2152
+ }
2153
+ permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
2154
+ }
2155
+ const collectionSchema = {
2156
+ name: collectionName,
2157
+ type: collectionType,
2158
+ fields,
2159
+ indexes,
2160
+ rules: {
2161
+ listRule: null,
2162
+ viewRule: null,
2163
+ createRule: null,
2164
+ updateRule: null,
2165
+ deleteRule: null
2166
+ },
2167
+ permissions
2168
+ };
2169
+ return collectionSchema;
2170
+ }
2171
+ async function buildSchemaDefinition(config) {
2172
+ const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
2173
+ const mergedConfig = mergeConfig(normalizedConfig);
2174
+ const collections = /* @__PURE__ */ new Map();
2175
+ const schemaFiles = discoverSchemaFiles(normalizedConfig);
2176
+ if (schemaFiles.length === 0) {
2177
+ const schemaDir = resolveSchemaDir(normalizedConfig);
2178
+ throw new SchemaParsingError(
2179
+ `No schema files found in ${schemaDir}. Make sure you have schema files in the directory.`,
2180
+ schemaDir
2181
+ );
2182
+ }
2183
+ for (const filePath of schemaFiles) {
2184
+ try {
2185
+ let importPath = filePath;
2186
+ if (normalizedConfig.pathTransformer) {
2187
+ importPath = normalizedConfig.pathTransformer(filePath);
2188
+ } else if (mergedConfig.useCompiledFiles) {
2189
+ importPath = filePath.replace(/\/src\//, "/dist/");
2190
+ }
2191
+ const module = await importSchemaModule(importPath, normalizedConfig);
2192
+ const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
2193
+ const zodSchema = selectSchemaForCollection(schemas);
2194
+ if (!zodSchema) {
2195
+ console.warn(`No valid schema found in ${filePath}, skipping...`);
2196
+ continue;
2197
+ }
2198
+ const collectionName = getCollectionNameFromFile(filePath);
2199
+ const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
2200
+ collections.set(collectionName, collectionSchema);
2201
+ } catch (error) {
2202
+ if (error instanceof SchemaParsingError) {
2203
+ throw error;
2204
+ }
2205
+ throw new SchemaParsingError(
2206
+ `Error processing schema file: ${error instanceof Error ? error.message : String(error)}`,
2207
+ filePath,
2208
+ error
2209
+ );
2210
+ }
2211
+ }
2212
+ return { collections };
2213
+ }
2214
+ async function parseSchemaFiles(config) {
2215
+ return buildSchemaDefinition(config);
2216
+ }
2217
+ var SchemaAnalyzer = class {
2218
+ config;
2219
+ constructor(config) {
2220
+ this.config = mergeConfig(config);
2221
+ }
2222
+ /**
2223
+ * Discovers schema files in the configured directory
2224
+ */
2225
+ discoverSchemaFiles() {
2226
+ return discoverSchemaFiles(this.config);
2227
+ }
2228
+ /**
2229
+ * Parses all schema files and returns a SchemaDefinition
2230
+ */
2231
+ async parseSchemaFiles() {
2232
+ return buildSchemaDefinition(this.config);
2233
+ }
2234
+ /**
2235
+ * Converts a single Zod schema to a CollectionSchema
2236
+ */
2237
+ convertZodSchemaToCollectionSchema(name, schema) {
2238
+ return convertZodSchemaToCollectionSchema(name, schema);
2239
+ }
2240
+ };
2241
+ var SNAPSHOT_VERSION = "1.0.0";
2242
+ var DEFAULT_SNAPSHOT_FILENAME = ".migration-snapshot.json";
2243
+ var SNAPSHOT_MIGRATIONS = [
2244
+ // Add migrations here as the format evolves
2245
+ // Example:
2246
+ // {
2247
+ // fromVersion: '0.9.0',
2248
+ // toVersion: '1.0.0',
2249
+ // migrate: (data) => ({ ...data, newField: 'default' })
2250
+ // }
2251
+ ];
2252
+ var DEFAULT_CONFIG2 = {
2253
+ snapshotPath: DEFAULT_SNAPSHOT_FILENAME,
2254
+ workspaceRoot: process.cwd(),
2255
+ autoMigrate: true,
2256
+ version: SNAPSHOT_VERSION
2257
+ };
2258
+ function mergeConfig2(config = {}) {
2259
+ return {
2260
+ ...DEFAULT_CONFIG2,
2261
+ ...config
2262
+ };
2263
+ }
2264
+ function getSnapshotPath(config = {}) {
2265
+ const mergedConfig = mergeConfig2(config);
2266
+ const workspaceRoot = mergedConfig.workspaceRoot;
2267
+ const snapshotFilename = mergedConfig.snapshotPath;
2268
+ if (path4__namespace.isAbsolute(snapshotFilename)) {
2269
+ return snapshotFilename;
2270
+ }
2271
+ return path4__namespace.join(workspaceRoot, snapshotFilename);
2272
+ }
2273
+ function snapshotExists(config = {}) {
2274
+ try {
2275
+ const snapshotPath = getSnapshotPath(config);
2276
+ return fs2__namespace.existsSync(snapshotPath);
2277
+ } catch {
2278
+ return false;
2279
+ }
2280
+ }
2281
+ function handleFileSystemError(error, operation, filePath) {
2282
+ const fsError = error;
2283
+ if (fsError.code === "ENOENT") {
2284
+ throw new SnapshotError(`Snapshot file not found: ${filePath}`, filePath, operation, error);
2285
+ } else if (fsError.code === "EACCES" || fsError.code === "EPERM") {
2286
+ throw new FileSystemError(
2287
+ `Permission denied ${operation === "read" ? "reading" : "writing"} snapshot file. Check file permissions.`,
2288
+ filePath,
2289
+ operation,
2290
+ fsError.code,
2291
+ error
2292
+ );
2293
+ } else if (fsError.code === "ENOSPC") {
2294
+ throw new FileSystemError(
2295
+ `No space left on device when ${operation === "read" ? "reading" : "writing"} snapshot file.`,
2296
+ filePath,
2297
+ operation,
2298
+ fsError.code,
2299
+ error
2300
+ );
2301
+ } else {
2302
+ throw new SnapshotError(`Failed to ${operation} snapshot file: ${error.message}`, filePath, operation, error);
2303
+ }
2304
+ }
2305
+ function serializeMap(map) {
2306
+ const obj = {};
2307
+ for (const [key, value] of map.entries()) {
2308
+ obj[key] = value;
2309
+ }
2310
+ return obj;
2311
+ }
2312
+ function deserializeMap(obj) {
2313
+ const map = /* @__PURE__ */ new Map();
2314
+ for (const [key, value] of Object.entries(obj)) {
2315
+ map.set(key, value);
2316
+ }
2317
+ return map;
2318
+ }
2319
+ function serializeSchemaDefinition(schema) {
2320
+ return {
2321
+ collections: serializeMap(schema.collections)
2322
+ };
2323
+ }
2324
+ function addSnapshotMetadata(schema, config) {
2325
+ const mergedConfig = mergeConfig2(config);
2326
+ return {
2327
+ version: mergedConfig.version,
2328
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2329
+ ...serializeSchemaDefinition(schema)
2330
+ };
2331
+ }
2332
+ function saveSnapshot(schema, config = {}) {
2333
+ const snapshotPath = getSnapshotPath(config);
2334
+ try {
2335
+ const snapshotDir = path4__namespace.dirname(snapshotPath);
2336
+ if (!fs2__namespace.existsSync(snapshotDir)) {
2337
+ fs2__namespace.mkdirSync(snapshotDir, { recursive: true });
2338
+ }
2339
+ const snapshotData = addSnapshotMetadata(schema, config);
2340
+ const jsonContent = JSON.stringify(snapshotData, null, 2);
2341
+ fs2__namespace.writeFileSync(snapshotPath, jsonContent, "utf-8");
2342
+ } catch (error) {
2343
+ handleFileSystemError(error, "write", snapshotPath);
2344
+ }
2345
+ }
2346
+ function parseAndValidateSnapshot(jsonContent, snapshotPath) {
2347
+ try {
2348
+ const data = JSON.parse(jsonContent);
2349
+ if (!data.version) {
2350
+ throw new SnapshotError(
2351
+ "Snapshot file is missing version field. The snapshot may be corrupted.",
2352
+ snapshotPath,
2353
+ "validate"
2354
+ );
2355
+ }
2356
+ if (!data.timestamp) {
2357
+ throw new SnapshotError(
2358
+ "Snapshot file is missing timestamp field. The snapshot may be corrupted.",
2359
+ snapshotPath,
2360
+ "validate"
2361
+ );
2362
+ }
2363
+ if (!data.collections) {
2364
+ throw new SnapshotError(
2365
+ "Snapshot file is missing collections field. The snapshot may be corrupted.",
2366
+ snapshotPath,
2367
+ "validate"
2368
+ );
2369
+ }
2370
+ return data;
2371
+ } catch (error) {
2372
+ if (error instanceof SnapshotError) {
2373
+ throw error;
2374
+ }
2375
+ if (error instanceof SyntaxError) {
2376
+ throw new SnapshotError(
2377
+ `Invalid JSON in snapshot file. The file may be corrupted or manually edited incorrectly.`,
2378
+ snapshotPath,
2379
+ "parse",
2380
+ error
2381
+ );
2382
+ }
2383
+ throw error;
2384
+ }
2385
+ }
2386
+ function compareVersions(a, b) {
2387
+ const partsA = a.split(".").map(Number);
2388
+ const partsB = b.split(".").map(Number);
2389
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
2390
+ const numA = partsA[i] || 0;
2391
+ const numB = partsB[i] || 0;
2392
+ if (numA < numB) return -1;
2393
+ if (numA > numB) return 1;
2394
+ }
2395
+ return 0;
2396
+ }
2397
+ function migrateSnapshotFormat(data, config) {
2398
+ const mergedConfig = mergeConfig2(config);
2399
+ const currentVersion = data.version;
2400
+ const targetVersion = mergedConfig.version;
2401
+ if (currentVersion === targetVersion) {
2402
+ return data;
2403
+ }
2404
+ if (!mergedConfig.autoMigrate) {
2405
+ console.warn(
2406
+ `Snapshot version ${currentVersion} differs from current ${targetVersion}, but auto-migrate is disabled.`
2407
+ );
2408
+ return data;
2409
+ }
2410
+ let migratedData = { ...data };
2411
+ let currentMigrationVersion = currentVersion;
2412
+ const sortedMigrations = [...SNAPSHOT_MIGRATIONS].sort((a, b) => compareVersions(a.fromVersion, b.fromVersion));
2413
+ for (const migration of sortedMigrations) {
2414
+ if (compareVersions(currentMigrationVersion, migration.fromVersion) === 0) {
2415
+ console.log(`Migrating snapshot from ${migration.fromVersion} to ${migration.toVersion}...`);
2416
+ migratedData = migration.migrate(migratedData);
2417
+ migratedData.version = migration.toVersion;
2418
+ currentMigrationVersion = migration.toVersion;
2419
+ }
2420
+ }
2421
+ if (compareVersions(currentMigrationVersion, targetVersion) !== 0) {
2422
+ console.warn(`Unknown snapshot version ${currentVersion}, attempting to load anyway...`);
2423
+ }
2424
+ return migratedData;
2425
+ }
2426
+ function deserializeSnapshot(data) {
2427
+ return {
2428
+ version: data.version,
2429
+ timestamp: data.timestamp,
2430
+ collections: deserializeMap(data.collections)
2431
+ };
2432
+ }
2433
+ function loadSnapshot(config = {}) {
2434
+ const snapshotPath = getSnapshotPath(config);
2435
+ try {
2436
+ const jsonContent = fs2__namespace.readFileSync(snapshotPath, "utf-8");
2437
+ const data = parseAndValidateSnapshot(jsonContent, snapshotPath);
2438
+ const migratedData = migrateSnapshotFormat(data, config);
2439
+ return deserializeSnapshot(migratedData);
2440
+ } catch (error) {
2441
+ if (error instanceof SnapshotError || error instanceof FileSystemError) {
2442
+ throw error;
2443
+ }
2444
+ if (error.code === "ENOENT") {
2445
+ throw new SnapshotError(
2446
+ `Snapshot file not found. This may be the first migration run.`,
2447
+ snapshotPath,
2448
+ "read",
2449
+ error
2450
+ );
2451
+ }
2452
+ handleFileSystemError(error, "read", snapshotPath);
2453
+ }
2454
+ }
2455
+ function mergeSnapshots(baseSnapshot, customSnapshot) {
2456
+ if (!customSnapshot) {
2457
+ return baseSnapshot;
2458
+ }
2459
+ const mergedCollections = new Map(baseSnapshot.collections);
2460
+ for (const [name, schema] of customSnapshot.collections.entries()) {
2461
+ mergedCollections.set(name, schema);
2462
+ }
2463
+ return {
2464
+ version: customSnapshot.version || baseSnapshot.version,
2465
+ timestamp: customSnapshot.timestamp || baseSnapshot.timestamp,
2466
+ collections: mergedCollections
2467
+ };
2468
+ }
2469
+ function findLatestSnapshot(migrationsPath) {
2470
+ try {
2471
+ if (!fs2__namespace.existsSync(migrationsPath)) {
2472
+ return null;
2473
+ }
2474
+ const files = fs2__namespace.readdirSync(migrationsPath);
2475
+ const snapshotFiles = files.filter(
2476
+ (file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
2477
+ );
2478
+ if (snapshotFiles.length === 0) {
2479
+ return null;
2480
+ }
2481
+ snapshotFiles.sort().reverse();
2482
+ const latestSnapshot = snapshotFiles[0];
2483
+ if (!latestSnapshot) {
2484
+ return null;
2485
+ }
2486
+ return path4__namespace.join(migrationsPath, latestSnapshot);
2487
+ } catch (error) {
2488
+ console.warn(`Error finding latest snapshot: ${error}`);
2489
+ return null;
2490
+ }
2491
+ }
2492
+ function loadSnapshotIfExists(config = {}) {
2493
+ const migrationsPath = config.migrationsPath;
2494
+ if (!migrationsPath) {
2495
+ return null;
2496
+ }
2497
+ if (fs2__namespace.existsSync(migrationsPath) && fs2__namespace.statSync(migrationsPath).isFile()) {
2498
+ try {
2499
+ const migrationContent = fs2__namespace.readFileSync(migrationsPath, "utf-8");
2500
+ return convertPocketBaseMigration(migrationContent);
2501
+ } catch (error) {
2502
+ console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
2503
+ return null;
2504
+ }
2505
+ }
2506
+ const latestSnapshotPath = findLatestSnapshot(migrationsPath);
2507
+ if (latestSnapshotPath) {
2508
+ try {
2509
+ const migrationContent = fs2__namespace.readFileSync(latestSnapshotPath, "utf-8");
2510
+ return convertPocketBaseMigration(migrationContent);
2511
+ } catch (error) {
2512
+ console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
2513
+ return null;
2514
+ }
2515
+ }
2516
+ return null;
2517
+ }
2518
+ function convertPocketBaseCollection(pbCollection) {
2519
+ const fields = [];
2520
+ const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
2521
+ const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
2522
+ if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
2523
+ for (const pbField of pbCollection.fields) {
2524
+ if (pbField.system || systemFieldNames.includes(pbField.name)) {
2525
+ continue;
2526
+ }
2527
+ if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
2528
+ continue;
2529
+ }
2530
+ const field = {
2531
+ name: pbField.name,
2532
+ type: pbField.type,
2533
+ required: pbField.required || false
2534
+ };
2535
+ if (pbField.options) {
2536
+ field.options = pbField.options;
2537
+ }
2538
+ if (pbField.type === "relation") {
2539
+ field.relation = {
2540
+ collection: pbField.options?.collectionId || "",
2541
+ cascadeDelete: pbField.options?.cascadeDelete || false,
2542
+ maxSelect: pbField.options?.maxSelect,
2543
+ minSelect: pbField.options?.minSelect
2544
+ };
2545
+ }
2546
+ fields.push(field);
2547
+ }
2548
+ }
2549
+ const schema = {
2550
+ name: pbCollection.name,
2551
+ type: pbCollection.type || "base",
2552
+ fields
2553
+ };
2554
+ if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
2555
+ schema.indexes = pbCollection.indexes;
2556
+ }
2557
+ const rules = {};
2558
+ if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
2559
+ if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
2560
+ if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
2561
+ if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
2562
+ if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
2563
+ if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
2564
+ if (Object.keys(rules).length > 0) {
2565
+ schema.rules = rules;
2566
+ }
2567
+ return schema;
2568
+ }
2569
+ function convertPocketBaseMigration(migrationContent) {
2570
+ try {
2571
+ const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
2572
+ if (!snapshotMatch) {
2573
+ throw new Error("Could not find snapshot array in migration file");
2574
+ }
2575
+ const snapshotArrayStr = snapshotMatch[1];
2576
+ let snapshotArray;
2577
+ try {
2578
+ snapshotArray = new Function(`return ${snapshotArrayStr}`)();
2579
+ } catch (parseError) {
2580
+ throw new Error(`Failed to parse snapshot array: ${parseError}`);
2581
+ }
2582
+ if (!Array.isArray(snapshotArray)) {
2583
+ throw new Error("Snapshot is not an array");
2584
+ }
2585
+ const collections = /* @__PURE__ */ new Map();
2586
+ for (const pbCollection of snapshotArray) {
2587
+ if (!pbCollection.name) {
2588
+ console.warn("Skipping collection without name");
2589
+ continue;
2590
+ }
2591
+ const schema = convertPocketBaseCollection(pbCollection);
2592
+ collections.set(pbCollection.name, schema);
2593
+ }
2594
+ return {
2595
+ version: SNAPSHOT_VERSION,
2596
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2597
+ collections
2598
+ };
2599
+ } catch (error) {
2600
+ throw new SnapshotError(
2601
+ `Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
2602
+ void 0,
2603
+ "parse",
2604
+ error instanceof Error ? error : void 0
2605
+ );
2606
+ }
2607
+ }
2608
+ function loadBaseMigration(migrationPath) {
2609
+ try {
2610
+ if (!fs2__namespace.existsSync(migrationPath)) {
2611
+ throw new SnapshotError(
2612
+ `Base migration file not found: ${migrationPath}
2613
+
2614
+ This file should contain PocketBase's initial schema.
2615
+ Please ensure PocketBase is properly set up by running 'yarn setup'.
2616
+ If the file exists in a different location, update the configuration.`,
2617
+ migrationPath,
2618
+ "read"
2619
+ );
2620
+ }
2621
+ const migrationContent = fs2__namespace.readFileSync(migrationPath, "utf-8");
2622
+ const snapshot = convertPocketBaseMigration(migrationContent);
2623
+ return snapshot;
2624
+ } catch (error) {
2625
+ if (error instanceof SnapshotError) {
2626
+ throw error;
2627
+ }
2628
+ if (error.code === "ENOENT") {
2629
+ throw new SnapshotError(
2630
+ `Base migration file not found: ${migrationPath}
2631
+
2632
+ This file should contain PocketBase's initial schema.
2633
+ Please ensure PocketBase is properly set up by running 'yarn setup'.`,
2634
+ migrationPath,
2635
+ "read",
2636
+ error
2637
+ );
2638
+ }
2639
+ if (error.code === "EACCES" || error.code === "EPERM") {
2640
+ throw new FileSystemError(
2641
+ `Permission denied reading base migration file. Check file permissions.`,
2642
+ migrationPath,
2643
+ "read",
2644
+ error.code,
2645
+ error
2646
+ );
2647
+ }
2648
+ throw new SnapshotError(
2649
+ `Failed to load base migration: ${error instanceof Error ? error.message : String(error)}`,
2650
+ migrationPath,
2651
+ "read",
2652
+ error instanceof Error ? error : void 0
2653
+ );
2654
+ }
2655
+ }
2656
+ function getSnapshotVersion() {
2657
+ return SNAPSHOT_VERSION;
2658
+ }
2659
+ function validateSnapshot(snapshot) {
2660
+ const issues = [];
2661
+ if (!snapshot.version) {
2662
+ issues.push("Missing version field");
2663
+ } else if (compareVersions(snapshot.version, SNAPSHOT_VERSION) > 0) {
2664
+ issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION}`);
2665
+ }
2666
+ if (!snapshot.timestamp) {
2667
+ issues.push("Missing timestamp field");
2668
+ }
2669
+ if (!snapshot.collections) {
2670
+ issues.push("Missing collections field");
2671
+ } else if (!(snapshot.collections instanceof Map)) {
2672
+ issues.push("Collections field is not a Map");
2673
+ }
2674
+ return {
2675
+ valid: issues.length === 0,
2676
+ issues
2677
+ };
2678
+ }
2679
+ var SnapshotManager = class {
2680
+ config;
2681
+ constructor(config = {}) {
2682
+ this.config = mergeConfig2(config);
2683
+ }
2684
+ /**
2685
+ * Loads the current snapshot
2686
+ */
2687
+ loadSnapshot() {
2688
+ return loadSnapshot(this.config);
2689
+ }
2690
+ /**
2691
+ * Saves a schema as a snapshot
2692
+ */
2693
+ saveSnapshot(schema) {
2694
+ saveSnapshot(schema, this.config);
2695
+ }
2696
+ /**
2697
+ * Loads snapshot if it exists, returns null otherwise
2698
+ */
2699
+ loadSnapshotIfExists() {
2700
+ return loadSnapshotIfExists(this.config);
2701
+ }
2702
+ /**
2703
+ * Checks if a snapshot exists
2704
+ */
2705
+ snapshotExists() {
2706
+ return snapshotExists(this.config);
2707
+ }
2708
+ /**
2709
+ * Converts a PocketBase migration to a snapshot
2710
+ */
2711
+ convertPocketBaseMigration(content) {
2712
+ return convertPocketBaseMigration(content);
2713
+ }
2714
+ /**
2715
+ * Gets the snapshot file path
2716
+ */
2717
+ getSnapshotPath() {
2718
+ return getSnapshotPath(this.config);
2719
+ }
2720
+ /**
2721
+ * Validates a snapshot
2722
+ */
2723
+ validateSnapshot(snapshot) {
2724
+ return validateSnapshot(snapshot);
2725
+ }
2726
+ };
2727
+
2728
+ // src/migration/diff.ts
2729
+ var DEFAULT_CONFIG3 = {
2730
+ warnOnDelete: true,
2731
+ requireForceForDestructive: true,
2732
+ severityThreshold: "high",
2733
+ systemCollections: ["_mfas", "_otps", "_externalAuths", "_authOrigins", "_superusers"],
2734
+ usersSystemFields: ["id", "password", "tokenKey", "email", "emailVisibility", "verified", "created", "updated"]
2735
+ };
2736
+ function mergeConfig3(config) {
2737
+ return {
2738
+ ...DEFAULT_CONFIG3,
2739
+ ...config
2740
+ };
2741
+ }
2742
+ function isSystemCollection(collectionName, config) {
2743
+ const mergedConfig = mergeConfig3(config);
2744
+ return mergedConfig.systemCollections.includes(collectionName);
2745
+ }
2746
+ function getUsersSystemFields(config) {
2747
+ const mergedConfig = mergeConfig3(config);
2748
+ return new Set(mergedConfig.usersSystemFields);
2749
+ }
2750
+ function filterSystemCollections(schema, config) {
2751
+ const filteredCollections = /* @__PURE__ */ new Map();
2752
+ for (const [collectionName, collectionSchema] of schema.collections) {
2753
+ if (!isSystemCollection(collectionName, config)) {
2754
+ filteredCollections.set(collectionName, collectionSchema);
2755
+ }
2756
+ }
2757
+ return {
2758
+ collections: filteredCollections
2759
+ };
2760
+ }
2761
+ function findNewCollections(currentSchema, previousSnapshot) {
2762
+ const newCollections = [];
2763
+ if (!previousSnapshot) {
2764
+ return Array.from(currentSchema.collections.values());
2765
+ }
2766
+ for (const [collectionName, collectionSchema] of currentSchema.collections) {
2767
+ if (!previousSnapshot.collections.has(collectionName)) {
2768
+ newCollections.push(collectionSchema);
2769
+ }
2770
+ }
2771
+ return newCollections;
2772
+ }
2773
+ function findRemovedCollections(currentSchema, previousSnapshot) {
2774
+ const removedCollections = [];
2775
+ if (!previousSnapshot) {
2776
+ return removedCollections;
2777
+ }
2778
+ for (const [collectionName, collectionSchema] of previousSnapshot.collections) {
2779
+ if (!currentSchema.collections.has(collectionName)) {
2780
+ removedCollections.push(collectionSchema);
2781
+ }
2782
+ }
2783
+ return removedCollections;
2784
+ }
2785
+ function matchCollectionsByName(currentSchema, previousSnapshot) {
2786
+ const matches = [];
2787
+ if (!previousSnapshot) {
2788
+ return matches;
2789
+ }
2790
+ for (const [collectionName, currentCollection] of currentSchema.collections) {
2791
+ const previousCollection = previousSnapshot.collections.get(collectionName);
2792
+ if (previousCollection) {
2793
+ matches.push([currentCollection, previousCollection]);
2794
+ }
2795
+ }
2796
+ return matches;
2797
+ }
2798
+ function findNewFields(currentFields, previousFields) {
2799
+ const newFields = [];
2800
+ const previousFieldNames = new Set(previousFields.map((f) => f.name));
2801
+ for (const currentField of currentFields) {
2802
+ if (!previousFieldNames.has(currentField.name)) {
2803
+ newFields.push(currentField);
2804
+ }
2805
+ }
2806
+ return newFields;
2807
+ }
2808
+ function findRemovedFields(currentFields, previousFields) {
2809
+ const removedFields = [];
2810
+ const currentFieldNames = new Set(currentFields.map((f) => f.name));
2811
+ for (const previousField of previousFields) {
2812
+ if (!currentFieldNames.has(previousField.name)) {
2813
+ removedFields.push(previousField);
2814
+ }
2815
+ }
2816
+ return removedFields;
2817
+ }
2818
+ function matchFieldsByName(currentFields, previousFields) {
2819
+ const matches = [];
2820
+ const previousFieldMap = /* @__PURE__ */ new Map();
2821
+ for (const previousField of previousFields) {
2822
+ previousFieldMap.set(previousField.name, previousField);
2823
+ }
2824
+ for (const currentField of currentFields) {
2825
+ const previousField = previousFieldMap.get(currentField.name);
2826
+ if (previousField) {
2827
+ matches.push([currentField, previousField]);
2828
+ }
2829
+ }
2830
+ return matches;
2831
+ }
2832
+ function areValuesEqual(a, b) {
2833
+ if (a === b) return true;
2834
+ if (a == null || b == null) return false;
2835
+ if (Array.isArray(a) && Array.isArray(b)) {
2836
+ if (a.length !== b.length) return false;
2837
+ return a.every((val, idx) => areValuesEqual(val, b[idx]));
2838
+ }
2839
+ if (typeof a === "object" && typeof b === "object") {
2840
+ const keysA = Object.keys(a);
2841
+ const keysB = Object.keys(b);
2842
+ if (keysA.length !== keysB.length) return false;
2843
+ return keysA.every((key) => areValuesEqual(a[key], b[key]));
2844
+ }
2845
+ return a === b;
2846
+ }
2847
+ function compareFieldTypes(currentField, previousField) {
2848
+ if (currentField.type !== previousField.type) {
2849
+ return {
2850
+ property: "type",
2851
+ oldValue: previousField.type,
2852
+ newValue: currentField.type
2853
+ };
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function compareFieldConstraints(currentField, previousField) {
2858
+ const changes = [];
2859
+ if (currentField.required !== previousField.required) {
2860
+ changes.push({
2861
+ property: "required",
2862
+ oldValue: previousField.required,
2863
+ newValue: currentField.required
2864
+ });
2865
+ }
2866
+ if (currentField.unique !== previousField.unique) {
2867
+ changes.push({
2868
+ property: "unique",
2869
+ oldValue: previousField.unique,
2870
+ newValue: currentField.unique
2871
+ });
2872
+ }
2873
+ return changes;
2874
+ }
2875
+ function compareFieldOptions(currentField, previousField) {
2876
+ const changes = [];
2877
+ const currentOptions = currentField.options || {};
2878
+ const previousOptions = previousField.options || {};
2879
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
2880
+ for (const key of allKeys) {
2881
+ const currentValue = currentOptions[key];
2882
+ const previousValue = previousOptions[key];
2883
+ if (!areValuesEqual(currentValue, previousValue)) {
2884
+ changes.push({
2885
+ property: `options.${key}`,
2886
+ oldValue: previousValue,
2887
+ newValue: currentValue
2888
+ });
2889
+ }
2890
+ }
2891
+ return changes;
2892
+ }
2893
+ function compareRelationConfigurations(currentField, previousField) {
2894
+ const changes = [];
2895
+ const currentRelation = currentField.relation;
2896
+ const previousRelation = previousField.relation;
2897
+ if (!currentRelation && !previousRelation) {
2898
+ return changes;
2899
+ }
2900
+ if (!currentRelation || !previousRelation) {
2901
+ return changes;
2902
+ }
2903
+ if (currentRelation.collection !== previousRelation.collection) {
2904
+ changes.push({
2905
+ property: "relation.collection",
2906
+ oldValue: previousRelation.collection,
2907
+ newValue: currentRelation.collection
2908
+ });
2909
+ }
2910
+ if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
2911
+ changes.push({
2912
+ property: "relation.cascadeDelete",
2913
+ oldValue: previousRelation.cascadeDelete,
2914
+ newValue: currentRelation.cascadeDelete
2915
+ });
2916
+ }
2917
+ if (currentRelation.maxSelect !== previousRelation.maxSelect) {
2918
+ changes.push({
2919
+ property: "relation.maxSelect",
2920
+ oldValue: previousRelation.maxSelect,
2921
+ newValue: currentRelation.maxSelect
2922
+ });
2923
+ }
2924
+ if (currentRelation.minSelect !== previousRelation.minSelect) {
2925
+ changes.push({
2926
+ property: "relation.minSelect",
2927
+ oldValue: previousRelation.minSelect,
2928
+ newValue: currentRelation.minSelect
2929
+ });
2930
+ }
2931
+ return changes;
2932
+ }
2933
+ function detectFieldChanges(currentField, previousField) {
2934
+ const changes = [];
2935
+ const typeChange = compareFieldTypes(currentField, previousField);
2936
+ if (typeChange) {
2937
+ changes.push(typeChange);
2938
+ }
2939
+ changes.push(...compareFieldConstraints(currentField, previousField));
2940
+ changes.push(...compareFieldOptions(currentField, previousField));
2941
+ if (currentField.type === "relation" && previousField.type === "relation") {
2942
+ changes.push(...compareRelationConfigurations(currentField, previousField));
2943
+ }
2944
+ return changes;
2945
+ }
2946
+ function compareIndexes(currentIndexes = [], previousIndexes = []) {
2947
+ const currentSet = new Set(currentIndexes);
2948
+ const previousSet = new Set(previousIndexes);
2949
+ const indexesToAdd = currentIndexes.filter((idx) => !previousSet.has(idx));
2950
+ const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
2951
+ return { indexesToAdd, indexesToRemove };
2952
+ }
2953
+ function compareRules(currentRules, previousRules) {
2954
+ const updates = [];
2955
+ const ruleTypes = [
2956
+ "listRule",
2957
+ "viewRule",
2958
+ "createRule",
2959
+ "updateRule",
2960
+ "deleteRule",
2961
+ "manageRule"
2962
+ ];
2963
+ for (const ruleType of ruleTypes) {
2964
+ const currentValue = currentRules?.[ruleType] ?? null;
2965
+ const previousValue = previousRules?.[ruleType] ?? null;
2966
+ if (currentValue !== previousValue) {
2967
+ updates.push({
2968
+ ruleType,
2969
+ oldValue: previousValue,
2970
+ newValue: currentValue
2971
+ });
2972
+ }
2973
+ }
2974
+ return updates;
2975
+ }
2976
+ function comparePermissions(currentPermissions, previousPermissions) {
2977
+ const changes = [];
2978
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule", "manageRule"];
2979
+ for (const ruleType of ruleTypes) {
2980
+ const currentValue = currentPermissions?.[ruleType] ?? null;
2981
+ const previousValue = previousPermissions?.[ruleType] ?? null;
2982
+ if (currentValue !== previousValue) {
2983
+ changes.push({
2984
+ ruleType,
2985
+ oldValue: previousValue,
2986
+ newValue: currentValue
2987
+ });
2988
+ }
2989
+ }
2990
+ return changes;
2991
+ }
2992
+ function compareCollectionFields(currentCollection, previousCollection, config) {
2993
+ let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
2994
+ const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
2995
+ const fieldsToModify = [];
2996
+ if (currentCollection.name === "users") {
2997
+ const systemFields = getUsersSystemFields(config);
2998
+ fieldsToAdd = fieldsToAdd.filter((field) => !systemFields.has(field.name));
2999
+ }
3000
+ const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
3001
+ for (const [currentField, previousField] of matchedFields) {
3002
+ const changes = detectFieldChanges(currentField, previousField);
3003
+ if (changes.length > 0) {
3004
+ fieldsToModify.push({
3005
+ fieldName: currentField.name,
3006
+ currentDefinition: previousField,
3007
+ newDefinition: currentField,
3008
+ changes
3009
+ });
3010
+ }
3011
+ }
3012
+ return { fieldsToAdd, fieldsToRemove, fieldsToModify };
3013
+ }
3014
+ function buildCollectionModification(currentCollection, previousCollection, config) {
3015
+ const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
3016
+ currentCollection,
3017
+ previousCollection,
3018
+ config
3019
+ );
3020
+ const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
3021
+ const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
3022
+ const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
3023
+ return {
3024
+ collection: currentCollection.name,
3025
+ fieldsToAdd,
3026
+ fieldsToRemove,
3027
+ fieldsToModify,
3028
+ indexesToAdd,
3029
+ indexesToRemove,
3030
+ rulesToUpdate,
3031
+ permissionsToUpdate
3032
+ };
3033
+ }
3034
+ function hasChanges(modification) {
3035
+ 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;
3036
+ }
3037
+ function aggregateChanges(currentSchema, previousSnapshot, config) {
3038
+ const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
3039
+ const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
3040
+ const filteredCollectionsToCreate = collectionsToCreate.filter(
3041
+ (collection) => !isSystemCollection(collection.name, config)
3042
+ );
3043
+ const filteredCollectionsToDelete = collectionsToDelete.filter(
3044
+ (collection) => !isSystemCollection(collection.name, config)
3045
+ );
3046
+ const collectionsToModify = [];
3047
+ const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
3048
+ for (const [currentCollection, previousCollection] of matchedCollections) {
3049
+ const modification = buildCollectionModification(currentCollection, previousCollection, config);
3050
+ if (hasChanges(modification)) {
3051
+ collectionsToModify.push(modification);
3052
+ }
3053
+ }
3054
+ return {
3055
+ collectionsToCreate: filteredCollectionsToCreate,
3056
+ collectionsToDelete: filteredCollectionsToDelete,
3057
+ collectionsToModify
3058
+ };
3059
+ }
3060
+ function detectDestructiveChanges(diff, config) {
3061
+ const destructiveChanges = [];
3062
+ const mergedConfig = mergeConfig3(config);
3063
+ for (const collection of diff.collectionsToDelete) {
3064
+ destructiveChanges.push({
3065
+ type: "collection_delete",
3066
+ severity: "high",
3067
+ collection: collection.name,
3068
+ description: `Delete collection: ${collection.name}`
3069
+ });
3070
+ }
3071
+ for (const modification of diff.collectionsToModify) {
3072
+ const collectionName = modification.collection;
3073
+ for (const field of modification.fieldsToRemove) {
3074
+ destructiveChanges.push({
3075
+ type: "field_delete",
3076
+ severity: "high",
3077
+ collection: collectionName,
3078
+ field: field.name,
3079
+ description: `Delete field: ${collectionName}.${field.name}`
3080
+ });
3081
+ }
3082
+ for (const fieldMod of modification.fieldsToModify) {
3083
+ const typeChange = fieldMod.changes.find((c) => c.property === "type");
3084
+ const requiredChange = fieldMod.changes.find((c) => c.property === "required" && c.newValue === true);
3085
+ if (typeChange) {
3086
+ destructiveChanges.push({
3087
+ type: "type_change",
3088
+ severity: "high",
3089
+ collection: collectionName,
3090
+ field: fieldMod.fieldName,
3091
+ description: `Change field type: ${collectionName}.${fieldMod.fieldName} (${typeChange.oldValue} \u2192 ${typeChange.newValue})`,
3092
+ oldValue: typeChange.oldValue,
3093
+ newValue: typeChange.newValue
3094
+ });
3095
+ }
3096
+ if (requiredChange && mergedConfig.severityThreshold !== "high") {
3097
+ destructiveChanges.push({
3098
+ type: "required_change",
3099
+ severity: "medium",
3100
+ collection: collectionName,
3101
+ field: fieldMod.fieldName,
3102
+ description: `Make field required: ${collectionName}.${fieldMod.fieldName}`,
3103
+ oldValue: false,
3104
+ newValue: true
3105
+ });
3106
+ }
3107
+ if (mergedConfig.severityThreshold === "low") {
3108
+ const otherChanges = fieldMod.changes.filter((c) => c.property !== "type" && c.property !== "required");
3109
+ for (const change of otherChanges) {
3110
+ destructiveChanges.push({
3111
+ type: "constraint_change",
3112
+ severity: "low",
3113
+ collection: collectionName,
3114
+ field: fieldMod.fieldName,
3115
+ description: `Change constraint: ${collectionName}.${fieldMod.fieldName}.${change.property}`,
3116
+ oldValue: change.oldValue,
3117
+ newValue: change.newValue
3118
+ });
3119
+ }
3120
+ }
3121
+ }
3122
+ }
3123
+ return destructiveChanges;
3124
+ }
3125
+ function categorizeChangesBySeverity(diff, _config) {
3126
+ const destructive = [];
3127
+ const nonDestructive = [];
3128
+ for (const collection of diff.collectionsToDelete) {
3129
+ destructive.push(`Delete collection: ${collection.name}`);
3130
+ }
3131
+ for (const collection of diff.collectionsToCreate) {
3132
+ nonDestructive.push(`Create collection: ${collection.name}`);
3133
+ }
3134
+ for (const modification of diff.collectionsToModify) {
3135
+ const collectionName = modification.collection;
3136
+ for (const field of modification.fieldsToRemove) {
3137
+ destructive.push(`Delete field: ${collectionName}.${field.name}`);
3138
+ }
3139
+ for (const field of modification.fieldsToAdd) {
3140
+ nonDestructive.push(`Add field: ${collectionName}.${field.name}`);
3141
+ }
3142
+ for (const fieldMod of modification.fieldsToModify) {
3143
+ const hasTypeChange = fieldMod.changes.some((c) => c.property === "type");
3144
+ const hasRequiredChange = fieldMod.changes.some((c) => c.property === "required" && c.newValue === true);
3145
+ if (hasTypeChange) {
3146
+ destructive.push(
3147
+ `Change field type: ${collectionName}.${fieldMod.fieldName} (${fieldMod.changes.find((c) => c.property === "type")?.oldValue} \u2192 ${fieldMod.changes.find((c) => c.property === "type")?.newValue})`
3148
+ );
3149
+ } else if (hasRequiredChange) {
3150
+ destructive.push(`Make field required: ${collectionName}.${fieldMod.fieldName}`);
3151
+ } else {
3152
+ nonDestructive.push(`Modify field: ${collectionName}.${fieldMod.fieldName}`);
3153
+ }
3154
+ }
3155
+ for (const _index of modification.indexesToAdd) {
3156
+ nonDestructive.push(`Add index: ${collectionName}`);
3157
+ }
3158
+ for (const _index of modification.indexesToRemove) {
3159
+ nonDestructive.push(`Remove index: ${collectionName}`);
3160
+ }
3161
+ for (const rule of modification.rulesToUpdate) {
3162
+ nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
3163
+ }
3164
+ }
3165
+ return { destructive, nonDestructive };
3166
+ }
3167
+ function generateChangeSummary(diff, config) {
3168
+ const destructiveChanges = detectDestructiveChanges(diff, config);
3169
+ const { nonDestructive } = categorizeChangesBySeverity(diff);
3170
+ let fieldsToAdd = 0;
3171
+ let fieldsToRemove = 0;
3172
+ let fieldsToModify = 0;
3173
+ let indexChanges = 0;
3174
+ let ruleChanges = 0;
3175
+ let permissionChanges = 0;
3176
+ for (const modification of diff.collectionsToModify) {
3177
+ fieldsToAdd += modification.fieldsToAdd.length;
3178
+ fieldsToRemove += modification.fieldsToRemove.length;
3179
+ fieldsToModify += modification.fieldsToModify.length;
3180
+ indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
3181
+ ruleChanges += modification.rulesToUpdate.length;
3182
+ permissionChanges += modification.permissionsToUpdate.length;
3183
+ }
3184
+ return {
3185
+ totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
3186
+ collectionsToCreate: diff.collectionsToCreate.length,
3187
+ collectionsToDelete: diff.collectionsToDelete.length,
3188
+ collectionsToModify: diff.collectionsToModify.length,
3189
+ fieldsToAdd,
3190
+ fieldsToRemove,
3191
+ fieldsToModify,
3192
+ indexChanges,
3193
+ ruleChanges,
3194
+ permissionChanges,
3195
+ destructiveChanges,
3196
+ nonDestructiveChanges: nonDestructive
3197
+ };
3198
+ }
3199
+ function requiresForceFlag(diff, config) {
3200
+ const mergedConfig = mergeConfig3(config);
3201
+ if (!mergedConfig.requireForceForDestructive) {
3202
+ return false;
3203
+ }
3204
+ const destructiveChanges = detectDestructiveChanges(diff, config);
3205
+ const relevantChanges = destructiveChanges.filter((change) => {
3206
+ switch (mergedConfig.severityThreshold) {
3207
+ case "high":
3208
+ return change.severity === "high";
3209
+ case "medium":
3210
+ return change.severity === "high" || change.severity === "medium";
3211
+ case "low":
3212
+ return true;
3213
+ default:
3214
+ return change.severity === "high";
3215
+ }
3216
+ });
3217
+ return relevantChanges.length > 0;
3218
+ }
3219
+ function compare(currentSchema, previousSnapshot, config) {
3220
+ return aggregateChanges(currentSchema, previousSnapshot, config);
3221
+ }
3222
+ var DiffEngine = class {
3223
+ config;
3224
+ constructor(config) {
3225
+ this.config = mergeConfig3(config);
3226
+ }
3227
+ /**
3228
+ * Compares current schema with previous snapshot
3229
+ */
3230
+ compare(currentSchema, previousSnapshot) {
3231
+ return compare(currentSchema, previousSnapshot, this.config);
3232
+ }
3233
+ /**
3234
+ * Detects destructive changes in a diff
3235
+ */
3236
+ detectDestructiveChanges(diff) {
3237
+ return detectDestructiveChanges(diff, this.config);
3238
+ }
3239
+ /**
3240
+ * Categorizes changes by severity
3241
+ */
3242
+ categorizeChangesBySeverity(diff) {
3243
+ return categorizeChangesBySeverity(diff, this.config);
3244
+ }
3245
+ /**
3246
+ * Generates a summary of changes
3247
+ */
3248
+ generateChangeSummary(diff) {
3249
+ return generateChangeSummary(diff, this.config);
3250
+ }
3251
+ /**
3252
+ * Checks if force flag is required
3253
+ */
3254
+ requiresForceFlag(diff) {
3255
+ return requiresForceFlag(diff, this.config);
3256
+ }
3257
+ };
3258
+ var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
3259
+ migrate((app) => {
3260
+ {{UP_CODE}}
3261
+ return true;
3262
+ }, (app) => {
3263
+ {{DOWN_CODE}}
3264
+ return true;
3265
+ });
3266
+ `;
3267
+ var DEFAULT_CONFIG4 = {
3268
+ workspaceRoot: process.cwd(),
3269
+ timestampGenerator: () => Math.floor(Date.now() / 1e3).toString(),
3270
+ template: DEFAULT_TEMPLATE,
3271
+ includeTypeReference: true,
3272
+ typesPath: "../pb_data/types.d.ts"
3273
+ };
3274
+ function mergeConfig4(config) {
3275
+ return {
3276
+ ...DEFAULT_CONFIG4,
3277
+ ...config
3278
+ };
3279
+ }
3280
+ function resolveMigrationDir(config) {
3281
+ const workspaceRoot = config.workspaceRoot || process.cwd();
3282
+ if (path4__namespace.isAbsolute(config.migrationDir)) {
3283
+ return config.migrationDir;
3284
+ }
3285
+ return path4__namespace.join(workspaceRoot, config.migrationDir);
3286
+ }
3287
+ function generateTimestamp(config) {
3288
+ if (config?.timestampGenerator) {
3289
+ return config.timestampGenerator();
3290
+ }
3291
+ return Math.floor(Date.now() / 1e3).toString();
3292
+ }
3293
+ function generateMigrationDescription(diff) {
3294
+ const parts = [];
3295
+ if (diff.collectionsToCreate.length > 0) {
3296
+ if (diff.collectionsToCreate.length === 1) {
3297
+ parts.push(`created_${diff.collectionsToCreate[0].name}`);
3298
+ } else {
3299
+ parts.push(`created_${diff.collectionsToCreate.length}_collections`);
3300
+ }
3301
+ }
3302
+ if (diff.collectionsToDelete.length > 0) {
3303
+ if (diff.collectionsToDelete.length === 1) {
3304
+ parts.push(`deleted_${diff.collectionsToDelete[0].name}`);
3305
+ } else {
3306
+ parts.push(`deleted_${diff.collectionsToDelete.length}_collections`);
3307
+ }
3308
+ }
3309
+ if (diff.collectionsToModify.length > 0) {
3310
+ if (diff.collectionsToModify.length === 1) {
3311
+ parts.push(`updated_${diff.collectionsToModify[0].collection}`);
3312
+ } else {
3313
+ parts.push(`updated_${diff.collectionsToModify.length}_collections`);
3314
+ }
3315
+ }
3316
+ if (parts.length === 0) {
3317
+ return "no_changes";
3318
+ }
3319
+ let description = parts.join("_");
3320
+ if (description.length > 80) {
3321
+ description = description.substring(0, 77) + "...";
3322
+ }
3323
+ return description;
3324
+ }
3325
+ function generateMigrationFilename(diff, config) {
3326
+ const timestamp = generateTimestamp(config);
3327
+ const description = generateMigrationDescription(diff);
3328
+ return `${timestamp}_${description}.js`;
3329
+ }
3330
+ function createMigrationFileStructure(upCode, downCode, config) {
3331
+ const mergedConfig = config ? mergeConfig4(config) : DEFAULT_CONFIG4;
3332
+ let template = mergedConfig.template;
3333
+ template = template.replace("{{TYPES_PATH}}", mergedConfig.typesPath);
3334
+ template = template.replace("{{UP_CODE}}", upCode);
3335
+ template = template.replace("{{DOWN_CODE}}", downCode);
3336
+ if (!mergedConfig.includeTypeReference) {
3337
+ template = template.replace(/\/\/\/ <reference path="[^"]*" \/>\n?/, "");
3338
+ }
3339
+ return template;
3340
+ }
3341
+ function writeMigrationFile(migrationDir, filename, content) {
3342
+ try {
3343
+ if (!fs2__namespace.existsSync(migrationDir)) {
3344
+ try {
3345
+ fs2__namespace.mkdirSync(migrationDir, { recursive: true });
3346
+ } catch (error) {
3347
+ const fsError = error;
3348
+ if (fsError.code === "EACCES" || fsError.code === "EPERM") {
3349
+ throw new FileSystemError(
3350
+ `Permission denied creating migration directory. Check directory permissions.`,
3351
+ migrationDir,
3352
+ "create",
3353
+ fsError.code,
3354
+ error
3355
+ );
3356
+ }
3357
+ throw new FileSystemError(
3358
+ `Failed to create migration directory: ${fsError.message}`,
3359
+ migrationDir,
3360
+ "create",
3361
+ fsError.code,
3362
+ error
3363
+ );
3364
+ }
3365
+ }
3366
+ const filePath = path4__namespace.join(migrationDir, filename);
3367
+ fs2__namespace.writeFileSync(filePath, content, "utf-8");
3368
+ return filePath;
3369
+ } catch (error) {
3370
+ if (error instanceof FileSystemError) {
3371
+ throw error;
3372
+ }
3373
+ const fsError = error;
3374
+ const filePath = path4__namespace.join(migrationDir, filename);
3375
+ if (fsError.code === "EACCES" || fsError.code === "EPERM") {
3376
+ throw new FileSystemError(
3377
+ `Permission denied writing migration file. Check file and directory permissions.`,
3378
+ filePath,
3379
+ "write",
3380
+ fsError.code,
3381
+ error
3382
+ );
3383
+ } else if (fsError.code === "ENOSPC") {
3384
+ throw new FileSystemError(
3385
+ `No space left on device when writing migration file.`,
3386
+ filePath,
3387
+ "write",
3388
+ fsError.code,
3389
+ error
3390
+ );
3391
+ }
3392
+ throw new MigrationGenerationError(`Failed to write migration file: ${fsError.message}`, filePath, error);
3393
+ }
3394
+ }
3395
+ function formatValue(value) {
3396
+ if (value === null || value === void 0) {
3397
+ return "null";
3398
+ }
3399
+ if (typeof value === "string") {
3400
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
3401
+ }
3402
+ if (typeof value === "number" || typeof value === "boolean") {
3403
+ return String(value);
3404
+ }
3405
+ if (Array.isArray(value)) {
3406
+ const items = value.map((v) => formatValue(v)).join(", ");
3407
+ return `[${items}]`;
3408
+ }
3409
+ if (typeof value === "object") {
3410
+ const entries = Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
3411
+ return `{ ${entries} }`;
3412
+ }
3413
+ return String(value);
3414
+ }
3415
+ function generateFieldDefinitionObject(field) {
3416
+ const parts = [];
3417
+ parts.push(` name: "${field.name}"`);
3418
+ parts.push(` type: "${field.type}"`);
3419
+ parts.push(` required: ${field.required}`);
3420
+ if (field.unique !== void 0) {
3421
+ parts.push(` unique: ${field.unique}`);
3422
+ }
3423
+ if (field.options && Object.keys(field.options).length > 0) {
3424
+ for (const [key, value] of Object.entries(field.options)) {
3425
+ parts.push(` ${key}: ${formatValue(value)}`);
3426
+ }
3427
+ }
3428
+ if (field.relation) {
3429
+ const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
3430
+ parts.push(` collectionId: ${collectionIdPlaceholder}`);
3431
+ if (field.relation.maxSelect !== void 0) {
3432
+ parts.push(` maxSelect: ${field.relation.maxSelect}`);
3433
+ }
3434
+ if (field.relation.minSelect !== void 0) {
3435
+ parts.push(` minSelect: ${field.relation.minSelect}`);
3436
+ }
3437
+ if (field.relation.cascadeDelete !== void 0) {
3438
+ parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
3439
+ }
3440
+ }
3441
+ return ` {
3442
+ ${parts.join(",\n")},
3443
+ }`;
3444
+ }
3445
+ function generateFieldsArray(fields) {
3446
+ if (fields.length === 0) {
3447
+ return "[]";
3448
+ }
3449
+ const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field));
3450
+ return `[
3451
+ ${fieldObjects.join(",\n")},
3452
+ ]`;
3453
+ }
3454
+ function generateCollectionRules(rules) {
3455
+ if (!rules) {
3456
+ return "";
3457
+ }
3458
+ const parts = [];
3459
+ if (rules.listRule !== void 0) {
3460
+ parts.push(`listRule: ${formatValue(rules.listRule)}`);
3461
+ }
3462
+ if (rules.viewRule !== void 0) {
3463
+ parts.push(`viewRule: ${formatValue(rules.viewRule)}`);
3464
+ }
3465
+ if (rules.createRule !== void 0) {
3466
+ parts.push(`createRule: ${formatValue(rules.createRule)}`);
3467
+ }
3468
+ if (rules.updateRule !== void 0) {
3469
+ parts.push(`updateRule: ${formatValue(rules.updateRule)}`);
3470
+ }
3471
+ if (rules.deleteRule !== void 0) {
3472
+ parts.push(`deleteRule: ${formatValue(rules.deleteRule)}`);
3473
+ }
3474
+ if (rules.manageRule !== void 0) {
3475
+ parts.push(`manageRule: ${formatValue(rules.manageRule)}`);
3476
+ }
3477
+ return parts.join(",\n ");
3478
+ }
3479
+ function generateCollectionPermissions(permissions) {
3480
+ if (!permissions) {
3481
+ return "";
3482
+ }
3483
+ const parts = [];
3484
+ if (permissions.listRule !== void 0) {
3485
+ parts.push(`listRule: ${formatValue(permissions.listRule)}`);
3486
+ }
3487
+ if (permissions.viewRule !== void 0) {
3488
+ parts.push(`viewRule: ${formatValue(permissions.viewRule)}`);
3489
+ }
3490
+ if (permissions.createRule !== void 0) {
3491
+ parts.push(`createRule: ${formatValue(permissions.createRule)}`);
3492
+ }
3493
+ if (permissions.updateRule !== void 0) {
3494
+ parts.push(`updateRule: ${formatValue(permissions.updateRule)}`);
3495
+ }
3496
+ if (permissions.deleteRule !== void 0) {
3497
+ parts.push(`deleteRule: ${formatValue(permissions.deleteRule)}`);
3498
+ }
3499
+ if (permissions.manageRule !== void 0) {
3500
+ parts.push(`manageRule: ${formatValue(permissions.manageRule)}`);
3501
+ }
3502
+ return parts.join(",\n ");
3503
+ }
3504
+ function generateIndexesArray(indexes) {
3505
+ if (!indexes || indexes.length === 0) {
3506
+ return "[]";
3507
+ }
3508
+ const indexStrings = indexes.map((idx) => `"${idx}"`);
3509
+ return `[
3510
+ ${indexStrings.join(",\n ")},
3511
+ ]`;
3512
+ }
3513
+ function generateCollectionCreation(collection, varName = "collection") {
3514
+ const lines = [];
3515
+ lines.push(` const ${varName} = new Collection({`);
3516
+ lines.push(` name: "${collection.name}",`);
3517
+ lines.push(` type: "${collection.type}",`);
3518
+ const permissionsCode = generateCollectionPermissions(collection.permissions);
3519
+ const rulesCode = generateCollectionRules(collection.rules);
3520
+ if (permissionsCode) {
3521
+ lines.push(` ${permissionsCode},`);
3522
+ } else if (rulesCode) {
3523
+ lines.push(` ${rulesCode},`);
3524
+ }
3525
+ lines.push(` fields: ${generateFieldsArray(collection.fields)},`);
3526
+ lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
3527
+ lines.push(` });`);
3528
+ lines.push(``);
3529
+ lines.push(` app.save(${varName});`);
3530
+ return lines.join("\n");
3531
+ }
3532
+ function getFieldConstructorName(fieldType) {
3533
+ const constructorMap = {
3534
+ text: "TextField",
3535
+ email: "EmailField",
3536
+ url: "URLField",
3537
+ number: "NumberField",
3538
+ bool: "BoolField",
3539
+ date: "DateField",
3540
+ select: "SelectField",
3541
+ relation: "RelationField",
3542
+ file: "FileField",
3543
+ json: "JSONField"
3544
+ };
3545
+ return constructorMap[fieldType] || "TextField";
3546
+ }
3547
+ function generateFieldConstructorOptions(field) {
3548
+ const parts = [];
3549
+ parts.push(` name: "${field.name}"`);
3550
+ parts.push(` required: ${field.required}`);
3551
+ if (field.unique !== void 0) {
3552
+ parts.push(` unique: ${field.unique}`);
3553
+ }
3554
+ if (field.options && Object.keys(field.options).length > 0) {
3555
+ for (const [key, value] of Object.entries(field.options)) {
3556
+ parts.push(` ${key}: ${formatValue(value)}`);
3557
+ }
3558
+ }
3559
+ if (field.relation && field.type === "relation") {
3560
+ const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
3561
+ parts.push(` collectionId: ${collectionIdPlaceholder}`);
3562
+ if (field.relation.maxSelect !== void 0) {
3563
+ parts.push(` maxSelect: ${field.relation.maxSelect}`);
3564
+ }
3565
+ if (field.relation.minSelect !== void 0) {
3566
+ parts.push(` minSelect: ${field.relation.minSelect}`);
3567
+ }
3568
+ if (field.relation.cascadeDelete !== void 0) {
3569
+ parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
3570
+ }
3571
+ }
3572
+ return parts.join(",\n");
3573
+ }
3574
+ function generateFieldAddition(collectionName, field, varName) {
3575
+ const lines = [];
3576
+ const constructorName = getFieldConstructorName(field.type);
3577
+ const collectionVar = varName || `collection_${collectionName}_${field.name}`;
3578
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3579
+ lines.push(``);
3580
+ lines.push(` ${collectionVar}.fields.add(new ${constructorName}({`);
3581
+ lines.push(generateFieldConstructorOptions(field));
3582
+ lines.push(` }));`);
3583
+ lines.push(``);
3584
+ lines.push(` app.save(${collectionVar});`);
3585
+ return lines.join("\n");
3586
+ }
3587
+ function generateFieldModification(collectionName, modification, varName) {
3588
+ const lines = [];
3589
+ const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
3590
+ const fieldVar = `${collectionVar}_field`;
3591
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3592
+ lines.push(` const ${fieldVar} = ${collectionVar}.fields.getByName("${modification.fieldName}");`);
3593
+ lines.push(``);
3594
+ for (const change of modification.changes) {
3595
+ if (change.property.startsWith("options.")) {
3596
+ const optionKey = change.property.replace("options.", "");
3597
+ lines.push(` ${fieldVar}.${optionKey} = ${formatValue(change.newValue)};`);
3598
+ } else if (change.property.startsWith("relation.")) {
3599
+ const relationKey = change.property.replace("relation.", "");
3600
+ if (relationKey === "collection") {
3601
+ const collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
3602
+ lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
3603
+ } else {
3604
+ lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
3605
+ }
3606
+ } else {
3607
+ lines.push(` ${fieldVar}.${change.property} = ${formatValue(change.newValue)};`);
3608
+ }
3609
+ }
3610
+ lines.push(``);
3611
+ lines.push(` app.save(${collectionVar});`);
3612
+ return lines.join("\n");
3613
+ }
3614
+ function generateFieldDeletion(collectionName, fieldName, varName) {
3615
+ const lines = [];
3616
+ const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
3617
+ const fieldVar = `${collectionVar}_field`;
3618
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3619
+ lines.push(` const ${fieldVar} = ${collectionVar}.fields.getByName("${fieldName}");`);
3620
+ lines.push(``);
3621
+ lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
3622
+ lines.push(``);
3623
+ lines.push(` app.save(${collectionVar});`);
3624
+ return lines.join("\n");
3625
+ }
3626
+ function generateIndexAddition(collectionName, index, varName) {
3627
+ const lines = [];
3628
+ const collectionVar = varName || `collection_${collectionName}_idx`;
3629
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3630
+ lines.push(` ${collectionVar}.indexes.push("${index}");`);
3631
+ lines.push(` app.save(${collectionVar});`);
3632
+ return lines.join("\n");
3633
+ }
3634
+ function generateIndexRemoval(collectionName, index, varName) {
3635
+ const lines = [];
3636
+ const collectionVar = varName || `collection_${collectionName}_idx`;
3637
+ const indexVar = `${collectionVar}_indexToRemove`;
3638
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3639
+ lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === "${index}");`);
3640
+ lines.push(` if (${indexVar} !== -1) {`);
3641
+ lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
3642
+ lines.push(` }`);
3643
+ lines.push(` app.save(${collectionVar});`);
3644
+ return lines.join("\n");
3645
+ }
3646
+ function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
3647
+ const lines = [];
3648
+ const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3649
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3650
+ lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3651
+ lines.push(` app.save(${collectionVar});`);
3652
+ return lines.join("\n");
3653
+ }
3654
+ function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
3655
+ const lines = [];
3656
+ const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
3657
+ lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
3658
+ lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
3659
+ lines.push(` app.save(${collectionVar});`);
3660
+ return lines.join("\n");
3661
+ }
3662
+ function generateCollectionDeletion(collectionName, varName = "collection") {
3663
+ const lines = [];
3664
+ lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
3665
+ lines.push(` app.delete(${varName});`);
3666
+ return lines.join("\n");
3667
+ }
3668
+ function generateUpMigration(diff) {
3669
+ const lines = [];
3670
+ lines.push(` // UP MIGRATION`);
3671
+ lines.push(``);
3672
+ if (diff.collectionsToCreate.length > 0) {
3673
+ lines.push(` // Create new collections`);
3674
+ for (let i = 0; i < diff.collectionsToCreate.length; i++) {
3675
+ const collection = diff.collectionsToCreate[i];
3676
+ const varName = `collection_${collection.name}_create`;
3677
+ lines.push(generateCollectionCreation(collection, varName));
3678
+ lines.push(``);
3679
+ }
3680
+ }
3681
+ if (diff.collectionsToModify.length > 0) {
3682
+ lines.push(` // Modify existing collections`);
3683
+ for (const modification of diff.collectionsToModify) {
3684
+ const collectionName = modification.collection;
3685
+ if (modification.fieldsToAdd.length > 0) {
3686
+ lines.push(` // Add fields to ${collectionName}`);
3687
+ for (const field of modification.fieldsToAdd) {
3688
+ const varName = `collection_${collectionName}_add_${field.name}`;
3689
+ lines.push(generateFieldAddition(collectionName, field, varName));
3690
+ lines.push(``);
3691
+ }
3692
+ }
3693
+ if (modification.fieldsToModify.length > 0) {
3694
+ lines.push(` // Modify fields in ${collectionName}`);
3695
+ for (const fieldMod of modification.fieldsToModify) {
3696
+ const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
3697
+ lines.push(generateFieldModification(collectionName, fieldMod, varName));
3698
+ lines.push(``);
3699
+ }
3700
+ }
3701
+ if (modification.fieldsToRemove.length > 0) {
3702
+ lines.push(` // Remove fields from ${collectionName}`);
3703
+ for (const field of modification.fieldsToRemove) {
3704
+ const varName = `collection_${collectionName}_remove_${field.name}`;
3705
+ lines.push(generateFieldDeletion(collectionName, field.name, varName));
3706
+ lines.push(``);
3707
+ }
3708
+ }
3709
+ if (modification.indexesToAdd.length > 0) {
3710
+ lines.push(` // Add indexes to ${collectionName}`);
3711
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
3712
+ const index = modification.indexesToAdd[i];
3713
+ const varName = `collection_${collectionName}_addidx_${i}`;
3714
+ lines.push(generateIndexAddition(collectionName, index, varName));
3715
+ lines.push(``);
3716
+ }
3717
+ }
3718
+ if (modification.indexesToRemove.length > 0) {
3719
+ lines.push(` // Remove indexes from ${collectionName}`);
3720
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
3721
+ const index = modification.indexesToRemove[i];
3722
+ const varName = `collection_${collectionName}_rmidx_${i}`;
3723
+ lines.push(generateIndexRemoval(collectionName, index, varName));
3724
+ lines.push(``);
3725
+ }
3726
+ }
3727
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
3728
+ lines.push(` // Update permissions for ${collectionName}`);
3729
+ for (const permission of modification.permissionsToUpdate) {
3730
+ const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
3731
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName));
3732
+ lines.push(``);
3733
+ }
3734
+ } else if (modification.rulesToUpdate.length > 0) {
3735
+ lines.push(` // Update rules for ${collectionName}`);
3736
+ for (const rule of modification.rulesToUpdate) {
3737
+ const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
3738
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName));
3739
+ lines.push(``);
3740
+ }
3741
+ }
3742
+ }
3743
+ }
3744
+ if (diff.collectionsToDelete.length > 0) {
3745
+ lines.push(` // Delete collections`);
3746
+ for (let i = 0; i < diff.collectionsToDelete.length; i++) {
3747
+ const collection = diff.collectionsToDelete[i];
3748
+ const varName = `collection_${collection.name}_delete`;
3749
+ lines.push(generateCollectionDeletion(collection.name, varName));
3750
+ lines.push(``);
3751
+ }
3752
+ }
3753
+ if (lines.length === 2) {
3754
+ lines.push(` // No changes detected`);
3755
+ lines.push(``);
3756
+ }
3757
+ return lines.join("\n");
3758
+ }
3759
+ function generateDownMigration(diff) {
3760
+ const lines = [];
3761
+ lines.push(` // DOWN MIGRATION (ROLLBACK)`);
3762
+ lines.push(``);
3763
+ if (diff.collectionsToDelete.length > 0) {
3764
+ lines.push(` // Recreate deleted collections`);
3765
+ for (let i = 0; i < diff.collectionsToDelete.length; i++) {
3766
+ const collection = diff.collectionsToDelete[i];
3767
+ const varName = `collection_${collection.name}_recreate`;
3768
+ lines.push(generateCollectionCreation(collection, varName));
3769
+ lines.push(``);
3770
+ }
3771
+ }
3772
+ if (diff.collectionsToModify.length > 0) {
3773
+ lines.push(` // Revert modifications`);
3774
+ for (const modification of diff.collectionsToModify) {
3775
+ const collectionName = modification.collection;
3776
+ if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
3777
+ lines.push(` // Revert permissions for ${collectionName}`);
3778
+ for (const permission of modification.permissionsToUpdate) {
3779
+ const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
3780
+ lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName));
3781
+ lines.push(``);
3782
+ }
3783
+ } else if (modification.rulesToUpdate.length > 0) {
3784
+ lines.push(` // Revert rules for ${collectionName}`);
3785
+ for (const rule of modification.rulesToUpdate) {
3786
+ const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
3787
+ lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName));
3788
+ lines.push(``);
3789
+ }
3790
+ }
3791
+ if (modification.indexesToRemove.length > 0) {
3792
+ lines.push(` // Restore indexes to ${collectionName}`);
3793
+ for (let i = 0; i < modification.indexesToRemove.length; i++) {
3794
+ const index = modification.indexesToRemove[i];
3795
+ const varName = `collection_${collectionName}_restore_idx_${i}`;
3796
+ lines.push(generateIndexAddition(collectionName, index, varName));
3797
+ lines.push(``);
3798
+ }
3799
+ }
3800
+ if (modification.indexesToAdd.length > 0) {
3801
+ lines.push(` // Remove indexes from ${collectionName}`);
3802
+ for (let i = 0; i < modification.indexesToAdd.length; i++) {
3803
+ const index = modification.indexesToAdd[i];
3804
+ const varName = `collection_${collectionName}_revert_idx_${i}`;
3805
+ lines.push(generateIndexRemoval(collectionName, index, varName));
3806
+ lines.push(``);
3807
+ }
3808
+ }
3809
+ if (modification.fieldsToRemove.length > 0) {
3810
+ lines.push(` // Restore fields to ${collectionName}`);
3811
+ for (const field of modification.fieldsToRemove) {
3812
+ const varName = `collection_${collectionName}_restore_${field.name}`;
3813
+ lines.push(generateFieldAddition(collectionName, field, varName));
3814
+ lines.push(``);
3815
+ }
3816
+ }
3817
+ if (modification.fieldsToModify.length > 0) {
3818
+ lines.push(` // Revert field modifications in ${collectionName}`);
3819
+ for (const fieldMod of modification.fieldsToModify) {
3820
+ const reverseChanges = fieldMod.changes.map((change) => ({
3821
+ property: change.property,
3822
+ oldValue: change.newValue,
3823
+ newValue: change.oldValue
3824
+ }));
3825
+ const reverseMod = {
3826
+ fieldName: fieldMod.fieldName,
3827
+ currentDefinition: fieldMod.newDefinition,
3828
+ newDefinition: fieldMod.currentDefinition,
3829
+ changes: reverseChanges
3830
+ };
3831
+ const varName = `collection_${collectionName}_revert_${fieldMod.fieldName}`;
3832
+ lines.push(generateFieldModification(collectionName, reverseMod, varName));
3833
+ lines.push(``);
3834
+ }
3835
+ }
3836
+ if (modification.fieldsToAdd.length > 0) {
3837
+ lines.push(` // Remove added fields from ${collectionName}`);
3838
+ for (const field of modification.fieldsToAdd) {
3839
+ const varName = `collection_${collectionName}_revert_add_${field.name}`;
3840
+ lines.push(generateFieldDeletion(collectionName, field.name, varName));
3841
+ lines.push(``);
3842
+ }
3843
+ }
3844
+ }
3845
+ }
3846
+ if (diff.collectionsToCreate.length > 0) {
3847
+ lines.push(` // Delete created collections`);
3848
+ for (let i = 0; i < diff.collectionsToCreate.length; i++) {
3849
+ const collection = diff.collectionsToCreate[i];
3850
+ const varName = `collection_${collection.name}_rollback`;
3851
+ lines.push(generateCollectionDeletion(collection.name, varName));
3852
+ lines.push(``);
3853
+ }
3854
+ }
3855
+ if (lines.length === 2) {
3856
+ lines.push(` // No changes to revert`);
3857
+ lines.push(``);
3858
+ }
3859
+ return lines.join("\n");
3860
+ }
3861
+ function generate(diff, config) {
3862
+ const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
3863
+ try {
3864
+ const migrationDir = resolveMigrationDir(normalizedConfig);
3865
+ const upCode = generateUpMigration(diff);
3866
+ const downCode = generateDownMigration(diff);
3867
+ const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
3868
+ const filename = generateMigrationFilename(diff, normalizedConfig);
3869
+ const filePath = writeMigrationFile(migrationDir, filename, content);
3870
+ return filePath;
3871
+ } catch (error) {
3872
+ if (error instanceof MigrationGenerationError || error instanceof FileSystemError) {
3873
+ throw error;
3874
+ }
3875
+ throw new MigrationGenerationError(
3876
+ `Failed to generate migration: ${error instanceof Error ? error.message : String(error)}`,
3877
+ normalizedConfig.migrationDir,
3878
+ error
3879
+ );
3880
+ }
3881
+ }
3882
+ var MigrationGenerator = class {
3883
+ config;
3884
+ constructor(config) {
3885
+ this.config = mergeConfig4(config);
3886
+ }
3887
+ /**
3888
+ * Generates a migration file from a schema diff
3889
+ */
3890
+ generate(diff) {
3891
+ return generate(diff, this.config);
3892
+ }
3893
+ /**
3894
+ * Generates the up migration code without writing to file
3895
+ */
3896
+ generateUpMigration(diff) {
3897
+ return generateUpMigration(diff);
3898
+ }
3899
+ /**
3900
+ * Generates the down migration code without writing to file
3901
+ */
3902
+ generateDownMigration(diff) {
3903
+ return generateDownMigration(diff);
3904
+ }
3905
+ /**
3906
+ * Generates a migration filename
3907
+ */
3908
+ generateMigrationFilename(diff) {
3909
+ return generateMigrationFilename(diff, this.config);
3910
+ }
3911
+ };
3912
+
3913
+ // src/migration/validation.ts
3914
+ function detectCollectionDeletions(diff) {
3915
+ const changes = [];
3916
+ for (const collection of diff.collectionsToDelete) {
3917
+ changes.push({
3918
+ type: "collection_deletion" /* COLLECTION_DELETION */,
3919
+ description: `Delete collection: ${collection.name}`,
3920
+ collection: collection.name,
3921
+ severity: "high",
3922
+ warning: `All data in the "${collection.name}" collection will be permanently deleted.`
3923
+ });
3924
+ }
3925
+ return changes;
3926
+ }
3927
+ function detectFieldDeletions(diff) {
3928
+ const changes = [];
3929
+ for (const modification of diff.collectionsToModify) {
3930
+ for (const field of modification.fieldsToRemove) {
3931
+ changes.push({
3932
+ type: "field_deletion" /* FIELD_DELETION */,
3933
+ description: `Delete field: ${modification.collection}.${field.name}`,
3934
+ collection: modification.collection,
3935
+ field: field.name,
3936
+ severity: "high",
3937
+ warning: `All data in the "${field.name}" field of "${modification.collection}" will be permanently deleted.`
3938
+ });
3939
+ }
3940
+ }
3941
+ return changes;
3942
+ }
3943
+ function detectFieldTypeChanges(diff) {
3944
+ const changes = [];
3945
+ for (const modification of diff.collectionsToModify) {
3946
+ for (const fieldMod of modification.fieldsToModify) {
3947
+ const typeChange = fieldMod.changes.find((c) => c.property === "type");
3948
+ if (typeChange) {
3949
+ changes.push({
3950
+ type: "field_type_change" /* FIELD_TYPE_CHANGE */,
3951
+ description: `Change field type: ${modification.collection}.${fieldMod.fieldName}`,
3952
+ collection: modification.collection,
3953
+ field: fieldMod.fieldName,
3954
+ details: {
3955
+ oldValue: typeChange.oldValue,
3956
+ newValue: typeChange.newValue
3957
+ },
3958
+ severity: "high",
3959
+ warning: `Changing field type from "${typeChange.oldValue}" to "${typeChange.newValue}" may cause data loss or conversion errors.`
3960
+ });
3961
+ }
3962
+ }
3963
+ }
3964
+ return changes;
3965
+ }
3966
+ function detectFieldRequiredChanges(diff) {
3967
+ const changes = [];
3968
+ for (const modification of diff.collectionsToModify) {
3969
+ for (const fieldMod of modification.fieldsToModify) {
3970
+ const requiredChange = fieldMod.changes.find(
3971
+ (c) => c.property === "required" && c.newValue === true && c.oldValue === false
3972
+ );
3973
+ if (requiredChange) {
3974
+ changes.push({
3975
+ type: "field_required_change" /* FIELD_REQUIRED_CHANGE */,
3976
+ description: `Make field required: ${modification.collection}.${fieldMod.fieldName}`,
3977
+ collection: modification.collection,
3978
+ field: fieldMod.fieldName,
3979
+ details: {
3980
+ oldValue: false,
3981
+ newValue: true
3982
+ },
3983
+ severity: "medium",
3984
+ warning: `Making "${fieldMod.fieldName}" required may cause issues with existing records that have null/empty values.`
3985
+ });
3986
+ }
3987
+ }
3988
+ }
3989
+ return changes;
3990
+ }
3991
+ function detectDestructiveChanges2(diff) {
3992
+ const changes = [];
3993
+ changes.push(...detectCollectionDeletions(diff));
3994
+ changes.push(...detectFieldDeletions(diff));
3995
+ changes.push(...detectFieldTypeChanges(diff));
3996
+ changes.push(...detectFieldRequiredChanges(diff));
3997
+ return changes;
3998
+ }
3999
+ function formatDestructiveChanges(changes) {
4000
+ if (changes.length === 0) {
4001
+ return "No destructive changes detected.";
4002
+ }
4003
+ const lines = [];
4004
+ const highSeverity = changes.filter((c) => c.severity === "high");
4005
+ const mediumSeverity = changes.filter((c) => c.severity === "medium");
4006
+ const lowSeverity = changes.filter((c) => c.severity === "low");
4007
+ if (highSeverity.length > 0) {
4008
+ lines.push("\u{1F534} HIGH SEVERITY CHANGES (Data Loss Risk):");
4009
+ lines.push("");
4010
+ for (const change of highSeverity) {
4011
+ lines.push(` \u2022 ${change.description}`);
4012
+ lines.push(` \u26A0\uFE0F ${change.warning}`);
4013
+ if (change.details) {
4014
+ if (change.details.oldValue !== void 0 && change.details.newValue !== void 0) {
4015
+ lines.push(` Old: ${change.details.oldValue} \u2192 New: ${change.details.newValue}`);
4016
+ }
4017
+ }
4018
+ lines.push("");
4019
+ }
4020
+ }
4021
+ if (mediumSeverity.length > 0) {
4022
+ lines.push("\u{1F7E1} MEDIUM SEVERITY CHANGES (Potential Issues):");
4023
+ lines.push("");
4024
+ for (const change of mediumSeverity) {
4025
+ lines.push(` \u2022 ${change.description}`);
4026
+ lines.push(` \u26A0\uFE0F ${change.warning}`);
4027
+ if (change.details) {
4028
+ if (change.details.oldValue !== void 0 && change.details.newValue !== void 0) {
4029
+ lines.push(` Old: ${change.details.oldValue} \u2192 New: ${change.details.newValue}`);
4030
+ }
4031
+ }
4032
+ lines.push("");
4033
+ }
4034
+ }
4035
+ if (lowSeverity.length > 0) {
4036
+ lines.push("\u{1F7E2} LOW SEVERITY CHANGES:");
4037
+ lines.push("");
4038
+ for (const change of lowSeverity) {
4039
+ lines.push(` \u2022 ${change.description}`);
4040
+ lines.push(` \u2139\uFE0F ${change.warning}`);
4041
+ lines.push("");
4042
+ }
4043
+ }
4044
+ return lines.join("\n");
4045
+ }
4046
+ function summarizeDestructiveChanges(changes) {
4047
+ return {
4048
+ total: changes.length,
4049
+ high: changes.filter((c) => c.severity === "high").length,
4050
+ medium: changes.filter((c) => c.severity === "medium").length,
4051
+ low: changes.filter((c) => c.severity === "low").length
4052
+ };
4053
+ }
4054
+ function requiresForceFlag2(changes) {
4055
+ return changes.some((c) => c.severity === "high" || c.severity === "medium");
4056
+ }
4057
+ var CONFIG_FILE_NAMES = [
4058
+ "pocketbase-migrate.config.js",
4059
+ "pocketbase-migrate.config.mjs",
4060
+ "pocketbase-migrate.config.json",
4061
+ "migrate.config.js",
4062
+ "migrate.config.mjs",
4063
+ "migrate.config.json"
4064
+ ];
4065
+ var DEFAULT_CONFIG5 = {
4066
+ schema: {
4067
+ directory: "src/schema",
4068
+ exclude: ["base.ts", "index.ts", "permissions.ts", "permission-templates.ts"]
4069
+ },
4070
+ migrations: {
4071
+ directory: "pocketbase/pb_migrations",
4072
+ format: "timestamp_description"
4073
+ },
4074
+ diff: {
4075
+ warnOnDelete: true,
4076
+ requireForceForDestructive: true
4077
+ }
4078
+ };
4079
+ function findConfigFile(directory) {
4080
+ for (const fileName of CONFIG_FILE_NAMES) {
4081
+ const filePath = path4__namespace.join(directory, fileName);
4082
+ if (fs2__namespace.existsSync(filePath)) {
4083
+ return filePath;
4084
+ }
4085
+ }
4086
+ return null;
4087
+ }
4088
+ function loadJsonConfig(configPath) {
4089
+ try {
4090
+ const content = fs2__namespace.readFileSync(configPath, "utf-8");
4091
+ return JSON.parse(content);
4092
+ } catch (error) {
4093
+ if (error instanceof SyntaxError) {
4094
+ throw new ConfigurationError("Invalid JSON syntax in configuration file", configPath, void 0, error);
4095
+ }
4096
+ throw new ConfigurationError(
4097
+ "Failed to read configuration file",
4098
+ configPath,
4099
+ void 0,
4100
+ error instanceof Error ? error : void 0
4101
+ );
4102
+ }
4103
+ }
4104
+ async function loadJsConfig(configPath) {
4105
+ try {
4106
+ const fileUrl = `file://${configPath}`;
4107
+ const module = await import(fileUrl);
4108
+ return module.default || module;
4109
+ } catch (error) {
4110
+ throw new ConfigurationError(
4111
+ "Failed to load JavaScript configuration file",
4112
+ configPath,
4113
+ void 0,
4114
+ error instanceof Error ? error : void 0
4115
+ );
4116
+ }
4117
+ }
4118
+ async function loadConfigFile(configPath) {
4119
+ if (!fs2__namespace.existsSync(configPath)) {
4120
+ return null;
4121
+ }
4122
+ const ext = path4__namespace.extname(configPath).toLowerCase();
4123
+ if (ext === ".json") {
4124
+ return loadJsonConfig(configPath);
4125
+ } else if (ext === ".js" || ext === ".mjs") {
4126
+ return loadJsConfig(configPath);
4127
+ } else {
4128
+ throw new ConfigurationError(`Unsupported configuration file format: ${ext}`, configPath, void 0);
4129
+ }
4130
+ }
4131
+ function mergeConfig5(base, override) {
4132
+ return {
4133
+ schema: { ...base.schema, ...override.schema },
4134
+ migrations: { ...base.migrations, ...override.migrations },
4135
+ diff: { ...base.diff, ...override.diff }
4136
+ };
4137
+ }
4138
+ function loadConfigFromEnv() {
4139
+ const config = {};
4140
+ if (process.env.MIGRATION_SCHEMA_DIR) {
4141
+ config.schema = { directory: process.env.MIGRATION_SCHEMA_DIR };
4142
+ }
4143
+ if (process.env.MIGRATION_SCHEMA_EXCLUDE) {
4144
+ config.schema = {
4145
+ ...config.schema,
4146
+ exclude: process.env.MIGRATION_SCHEMA_EXCLUDE.split(",").map((s) => s.trim())
4147
+ };
4148
+ }
4149
+ if (process.env.MIGRATION_OUTPUT_DIR) {
4150
+ config.migrations = { directory: process.env.MIGRATION_OUTPUT_DIR };
4151
+ }
4152
+ if (process.env.MIGRATION_REQUIRE_FORCE !== void 0) {
4153
+ config.diff = { requireForceForDestructive: process.env.MIGRATION_REQUIRE_FORCE === "true" };
4154
+ }
4155
+ return config;
4156
+ }
4157
+ function loadConfigFromArgs(options) {
4158
+ const config = {};
4159
+ if (options.output) {
4160
+ config.migrations = { directory: options.output };
4161
+ }
4162
+ if (options.schemaDir) {
4163
+ config.schema = { directory: options.schemaDir };
4164
+ }
4165
+ return config;
4166
+ }
4167
+ function validateConfig(config, configPath) {
4168
+ const invalidFields = [];
4169
+ if (typeof config.schema.directory !== "string" || config.schema.directory.trim() === "") {
4170
+ invalidFields.push("schema.directory (must be a non-empty string)");
4171
+ }
4172
+ if (!Array.isArray(config.schema.exclude)) {
4173
+ invalidFields.push("schema.exclude (must be an array of strings)");
4174
+ }
4175
+ if (typeof config.migrations.directory !== "string" || config.migrations.directory.trim() === "") {
4176
+ invalidFields.push("migrations.directory (must be a non-empty string)");
4177
+ }
4178
+ if (typeof config.diff.warnOnDelete !== "boolean") {
4179
+ invalidFields.push("diff.warnOnDelete (must be a boolean)");
4180
+ }
4181
+ if (typeof config.diff.requireForceForDestructive !== "boolean") {
4182
+ invalidFields.push("diff.requireForceForDestructive (must be a boolean)");
4183
+ }
4184
+ if (invalidFields.length > 0) {
4185
+ throw new ConfigurationError("Invalid configuration values", configPath, invalidFields);
4186
+ }
4187
+ const cwd = process.cwd();
4188
+ const possiblePaths = [
4189
+ path4__namespace.resolve(cwd, config.schema.directory),
4190
+ path4__namespace.resolve(cwd, "shared", config.schema.directory)
4191
+ ];
4192
+ const schemaDir = possiblePaths.find((p) => fs2__namespace.existsSync(p));
4193
+ if (!schemaDir) {
4194
+ throw new ConfigurationError(`Schema directory not found. Tried: ${possiblePaths.join(", ")}`, configPath, [
4195
+ "schema.directory"
4196
+ ]);
4197
+ }
4198
+ }
4199
+ async function loadConfig(options = {}) {
4200
+ let config = { ...DEFAULT_CONFIG5 };
4201
+ let configFilePath;
4202
+ const cwd = process.cwd();
4203
+ if (options.config) {
4204
+ const explicitPath = path4__namespace.resolve(cwd, options.config);
4205
+ if (!fs2__namespace.existsSync(explicitPath)) {
4206
+ throw new ConfigurationError(`Configuration file not found: ${explicitPath}`, explicitPath);
4207
+ }
4208
+ configFilePath = explicitPath;
4209
+ } else {
4210
+ const searchDirs = [cwd, path4__namespace.join(cwd, "shared")];
4211
+ for (const dir of searchDirs) {
4212
+ if (fs2__namespace.existsSync(dir)) {
4213
+ const found = findConfigFile(dir);
4214
+ if (found) {
4215
+ configFilePath = found;
4216
+ break;
4217
+ }
4218
+ }
4219
+ }
4220
+ }
4221
+ if (configFilePath) {
4222
+ const fileConfig = await loadConfigFile(configFilePath);
4223
+ if (fileConfig) {
4224
+ config = mergeConfig5(config, fileConfig);
4225
+ }
4226
+ }
4227
+ const envConfig = loadConfigFromEnv();
4228
+ if (Object.keys(envConfig).length > 0) {
4229
+ config = mergeConfig5(config, envConfig);
4230
+ }
4231
+ const argsConfig = loadConfigFromArgs(options);
4232
+ if (Object.keys(argsConfig).length > 0) {
4233
+ config = mergeConfig5(config, argsConfig);
4234
+ }
4235
+ validateConfig(config, configFilePath);
4236
+ return config;
4237
+ }
4238
+ function getSchemaDirectory(config) {
4239
+ const cwd = process.cwd();
4240
+ const possiblePaths = [
4241
+ path4__namespace.resolve(cwd, config.schema.directory),
4242
+ path4__namespace.resolve(cwd, "shared", config.schema.directory)
4243
+ ];
4244
+ return possiblePaths.find((p) => fs2__namespace.existsSync(p)) || possiblePaths[0];
4245
+ }
4246
+ function getMigrationsDirectory(config) {
4247
+ const cwd = process.cwd();
4248
+ const possiblePaths = [
4249
+ path4__namespace.resolve(cwd, config.migrations.directory),
4250
+ path4__namespace.resolve(cwd, "shared", config.migrations.directory)
4251
+ ];
4252
+ return possiblePaths.find((p) => fs2__namespace.existsSync(p)) || possiblePaths[0];
4253
+ }
4254
+ var currentVerbosity = "normal";
4255
+ function setVerbosity(level) {
4256
+ currentVerbosity = level;
4257
+ }
4258
+ function shouldLog(requiredLevel) {
4259
+ const levels = ["quiet", "normal", "verbose"];
4260
+ const currentIndex = levels.indexOf(currentVerbosity);
4261
+ const requiredIndex = levels.indexOf(requiredLevel);
4262
+ return currentIndex >= requiredIndex;
4263
+ }
4264
+ function createSpinner(text) {
4265
+ if (currentVerbosity === "quiet") {
4266
+ return ora__default.default({ text, isSilent: true });
4267
+ }
4268
+ return ora__default.default(text);
4269
+ }
4270
+ function logSuccess(message) {
4271
+ if (shouldLog("normal")) {
4272
+ console.log(chalk__default.default.green("\u2713"), message);
4273
+ }
4274
+ }
4275
+ function logError(message) {
4276
+ console.error(chalk__default.default.red("\u2717"), message);
4277
+ }
4278
+ function logWarning(message) {
4279
+ if (shouldLog("normal")) {
4280
+ console.warn(chalk__default.default.yellow("\u26A0"), message);
4281
+ }
4282
+ }
4283
+ function logInfo(message) {
4284
+ if (shouldLog("normal")) {
4285
+ console.log(chalk__default.default.blue("\u2139"), message);
4286
+ }
4287
+ }
4288
+ function logDebug(message) {
4289
+ if (shouldLog("verbose")) {
4290
+ console.log(chalk__default.default.gray("\u2699"), chalk__default.default.gray(message));
4291
+ }
4292
+ }
4293
+ function logSection(title) {
4294
+ if (shouldLog("normal")) {
4295
+ console.log();
4296
+ console.log(chalk__default.default.bold.cyan(title));
4297
+ console.log(chalk__default.default.cyan("\u2500".repeat(title.length)));
4298
+ }
4299
+ }
4300
+ function formatFieldChange(change) {
4301
+ const oldValue = change.oldValue === null ? "null" : JSON.stringify(change.oldValue);
4302
+ const newValue = change.newValue === null ? "null" : JSON.stringify(change.newValue);
4303
+ return `${change.property}: ${chalk__default.default.red(oldValue)} \u2192 ${chalk__default.default.green(newValue)}`;
4304
+ }
4305
+ function formatChangeSummary(diff) {
4306
+ const lines = [];
4307
+ const totalCollectionsToCreate = diff.collectionsToCreate.length;
4308
+ const totalCollectionsToDelete = diff.collectionsToDelete.length;
4309
+ const totalCollectionsToModify = diff.collectionsToModify.length;
4310
+ const totalChanges = totalCollectionsToCreate + totalCollectionsToDelete + totalCollectionsToModify;
4311
+ if (totalChanges === 0) {
4312
+ return chalk__default.default.gray("No changes detected");
4313
+ }
4314
+ lines.push(chalk__default.default.bold(`Found ${totalChanges} collection change(s):`));
4315
+ lines.push("");
4316
+ if (totalCollectionsToCreate > 0) {
4317
+ lines.push(chalk__default.default.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
4318
+ for (const collection of diff.collectionsToCreate) {
4319
+ lines.push(chalk__default.default.green(` + ${collection.name} (${collection.type})`));
4320
+ lines.push(chalk__default.default.gray(` ${collection.fields.length} field(s)`));
4321
+ }
4322
+ lines.push("");
4323
+ }
4324
+ if (totalCollectionsToDelete > 0) {
4325
+ lines.push(chalk__default.default.red.bold(`\u2717 ${totalCollectionsToDelete} collection(s) to delete:`));
4326
+ for (const collection of diff.collectionsToDelete) {
4327
+ lines.push(chalk__default.default.red(` - ${collection.name}`));
4328
+ }
4329
+ lines.push("");
4330
+ }
4331
+ if (totalCollectionsToModify > 0) {
4332
+ lines.push(chalk__default.default.yellow.bold(`\u26A1 ${totalCollectionsToModify} collection(s) to modify:`));
4333
+ for (const modification of diff.collectionsToModify) {
4334
+ lines.push(chalk__default.default.yellow(` ~ ${modification.collection}`));
4335
+ if (modification.fieldsToAdd.length > 0) {
4336
+ lines.push(chalk__default.default.green(` + ${modification.fieldsToAdd.length} field(s) to add:`));
4337
+ for (const field of modification.fieldsToAdd) {
4338
+ lines.push(chalk__default.default.green(` + ${field.name} (${field.type})`));
4339
+ }
4340
+ }
4341
+ if (modification.fieldsToRemove.length > 0) {
4342
+ lines.push(chalk__default.default.red(` - ${modification.fieldsToRemove.length} field(s) to remove:`));
4343
+ for (const field of modification.fieldsToRemove) {
4344
+ lines.push(chalk__default.default.red(` - ${field.name}`));
4345
+ }
4346
+ }
4347
+ if (modification.fieldsToModify.length > 0) {
4348
+ lines.push(chalk__default.default.yellow(` ~ ${modification.fieldsToModify.length} field(s) to modify:`));
4349
+ for (const fieldMod of modification.fieldsToModify) {
4350
+ lines.push(chalk__default.default.yellow(` ~ ${fieldMod.fieldName}`));
4351
+ for (const change of fieldMod.changes) {
4352
+ lines.push(chalk__default.default.gray(` ${formatFieldChange(change)}`));
4353
+ }
4354
+ }
4355
+ }
4356
+ if (modification.indexesToAdd.length > 0) {
4357
+ lines.push(chalk__default.default.green(` + ${modification.indexesToAdd.length} index(es) to add`));
4358
+ }
4359
+ if (modification.indexesToRemove.length > 0) {
4360
+ lines.push(chalk__default.default.red(` - ${modification.indexesToRemove.length} index(es) to remove`));
4361
+ }
4362
+ if (modification.rulesToUpdate.length > 0) {
4363
+ lines.push(chalk__default.default.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
4364
+ }
4365
+ lines.push("");
4366
+ }
4367
+ }
4368
+ return lines.join("\n");
4369
+ }
4370
+ async function withProgress(message, operation) {
4371
+ const spinner = createSpinner(message).start();
4372
+ try {
4373
+ const result = await operation();
4374
+ spinner.succeed();
4375
+ return result;
4376
+ } catch (error) {
4377
+ spinner.fail();
4378
+ throw error;
4379
+ }
4380
+ }
4381
+ function logKeyValue(key, value, indent = 2) {
4382
+ if (shouldLog("normal")) {
4383
+ const padding = " ".repeat(indent);
4384
+ console.log(`${padding}${chalk__default.default.gray(key + ":")} ${value}`);
4385
+ }
4386
+ }
4387
+ function logTable(headers, rows) {
4388
+ if (!shouldLog("normal")) return;
4389
+ const widths = headers.map((h, i) => {
4390
+ const maxRowWidth = Math.max(...rows.map((r) => (r[i] || "").length));
4391
+ return Math.max(h.length, maxRowWidth);
4392
+ });
4393
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
4394
+ console.log(chalk__default.default.bold(headerLine));
4395
+ console.log(chalk__default.default.gray("\u2500".repeat(headerLine.length)));
4396
+ for (const row of rows) {
4397
+ const rowLine = row.map((cell, i) => (cell || "").padEnd(widths[i])).join(" ");
4398
+ console.log(rowLine);
4399
+ }
4400
+ }
4401
+ function formatStatusJson(output) {
4402
+ return JSON.stringify(output, null, 2);
4403
+ }
4404
+
4405
+ // src/cli/commands/generate.ts
4406
+ function hasChanges2(diff) {
4407
+ return diff.collectionsToCreate.length > 0 || diff.collectionsToDelete.length > 0 || diff.collectionsToModify.length > 0;
4408
+ }
4409
+ function handleDestructiveChanges(diff, config, force) {
4410
+ const destructiveChanges = detectDestructiveChanges2(diff);
4411
+ if (destructiveChanges.length === 0) {
4412
+ return true;
4413
+ }
4414
+ logSection("\u26A0\uFE0F Destructive Changes Detected");
4415
+ console.log();
4416
+ console.log(formatDestructiveChanges(destructiveChanges));
4417
+ const summary = summarizeDestructiveChanges(destructiveChanges);
4418
+ console.log("Summary:");
4419
+ console.log(` Total: ${summary.total} destructive change(s)`);
4420
+ if (summary.high > 0) {
4421
+ console.log(` High Severity: ${summary.high}`);
4422
+ }
4423
+ if (summary.medium > 0) {
4424
+ console.log(` Medium Severity: ${summary.medium}`);
4425
+ }
4426
+ if (summary.low > 0) {
4427
+ console.log(` Low Severity: ${summary.low}`);
4428
+ }
4429
+ console.log();
4430
+ const forceRequired = config.diff.requireForceForDestructive && requiresForceFlag2(destructiveChanges);
4431
+ if (forceRequired && !force) {
4432
+ logError("Destructive changes require the --force flag to proceed.");
4433
+ console.log();
4434
+ logInfo("To proceed with these changes, run the command again with --force:");
4435
+ console.log(" yarn migrate:generate --force");
4436
+ console.log();
4437
+ logWarning("\u26A0\uFE0F WARNING: Using --force will apply these changes and may result in data loss!");
4438
+ return false;
4439
+ }
4440
+ if (force) {
4441
+ logWarning("Proceeding with destructive changes (--force flag provided)");
4442
+ console.log();
4443
+ }
4444
+ return true;
4445
+ }
4446
+ async function executeGenerate(options) {
4447
+ try {
4448
+ const parentOpts = options.parent?.opts?.() || {};
4449
+ if (parentOpts.verbose) {
4450
+ setVerbosity("verbose");
4451
+ } else if (parentOpts.quiet) {
4452
+ setVerbosity("quiet");
4453
+ }
4454
+ logDebug("Starting migration generation...");
4455
+ logDebug(`Options: ${JSON.stringify(options, null, 2)}`);
4456
+ const config = await loadConfig(options);
4457
+ const schemaDir = getSchemaDirectory(config);
4458
+ const migrationsDir = getMigrationsDirectory(config);
4459
+ logSection("\u{1F50D} Analyzing Schema");
4460
+ const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(schemaDir));
4461
+ logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
4462
+ logInfo("Loading previous snapshot...");
4463
+ const previousSnapshot = loadSnapshotIfExists({
4464
+ migrationsPath: migrationsDir,
4465
+ workspaceRoot: process.cwd()
4466
+ });
4467
+ if (!previousSnapshot) {
4468
+ logInfo("No previous snapshot found - treating as empty database (first-time generation)");
4469
+ } else {
4470
+ logSuccess("Loaded previous snapshot as base reference");
4471
+ }
4472
+ logSection("\u{1F4CA} Comparing Schemas");
4473
+ const diff = compare(currentSchema, previousSnapshot);
4474
+ if (!hasChanges2(diff)) {
4475
+ logInfo("No changes detected");
4476
+ console.log();
4477
+ logSuccess("Schema is up to date!");
4478
+ return;
4479
+ }
4480
+ console.log();
4481
+ console.log(formatChangeSummary(diff));
4482
+ if (!handleDestructiveChanges(diff, config, options.force)) {
4483
+ process.exit(1);
4484
+ }
4485
+ logSection("\u{1F4DD} Generating Migration");
4486
+ const migrationPath = await withProgress(
4487
+ "Creating migration file...",
4488
+ () => Promise.resolve(generate(diff, migrationsDir))
4489
+ );
4490
+ logSuccess(`Migration file created: ${path4__namespace.basename(migrationPath)}`);
4491
+ logSection("\u2705 Next Steps");
4492
+ console.log();
4493
+ console.log(" 1. Review the generated migration file:");
4494
+ console.log(` ${migrationPath}`);
4495
+ console.log();
4496
+ console.log(" 2. Apply the migration by running PocketBase:");
4497
+ console.log(" yarn pb");
4498
+ console.log();
4499
+ console.log(" Or apply migrations manually:");
4500
+ console.log(" cd pb && ./pocketbase migrate up");
4501
+ console.log();
4502
+ } catch (error) {
4503
+ if (error instanceof SchemaParsingError) {
4504
+ logError("Schema Parsing Error");
4505
+ console.error();
4506
+ console.error(error.getDetailedMessage());
4507
+ console.error();
4508
+ logInfo("Suggestions:");
4509
+ console.log(" \u2022 Make sure your schema files are valid Zod schemas");
4510
+ console.log(' \u2022 Run "yarn build" in the shared workspace to compile TypeScript files');
4511
+ console.log(' \u2022 Check that schema files export schemas ending with "Schema" or "InputSchema"');
4512
+ } else if (error instanceof SnapshotError) {
4513
+ logError("Snapshot Error");
4514
+ console.error();
4515
+ console.error(error.getDetailedMessage());
4516
+ console.error();
4517
+ logInfo("Suggestions:");
4518
+ console.log(" \u2022 Check that the snapshot file is not corrupted");
4519
+ console.log(" \u2022 Verify file permissions for the snapshot file");
4520
+ console.log(" \u2022 If this is the first run, this error should not occur");
4521
+ } else if (error instanceof MigrationGenerationError) {
4522
+ logError("Migration Generation Error");
4523
+ console.error();
4524
+ console.error(error.getDetailedMessage());
4525
+ console.error();
4526
+ logInfo("Suggestions:");
4527
+ console.log(" \u2022 Check that the migration directory exists and is writable");
4528
+ console.log(" \u2022 Verify you have sufficient disk space");
4529
+ console.log(" \u2022 Check file permissions for the migration directory");
4530
+ } else if (error instanceof FileSystemError) {
4531
+ logError("File System Error");
4532
+ console.error();
4533
+ console.error(error.getDetailedMessage());
4534
+ console.error();
4535
+ logInfo("Suggestions:");
4536
+ console.log(" \u2022 Check file and directory permissions");
4537
+ console.log(" \u2022 Verify you have sufficient disk space");
4538
+ console.log(" \u2022 Ensure the paths are correct and accessible");
4539
+ } else if (error instanceof ConfigurationError) {
4540
+ logError("Configuration Error");
4541
+ console.error();
4542
+ console.error(error.getDetailedMessage());
4543
+ console.error();
4544
+ logInfo("Suggestions:");
4545
+ console.log(" \u2022 Check your configuration file syntax");
4546
+ console.log(" \u2022 Verify all paths are correct and accessible");
4547
+ console.log(" \u2022 Run with --verbose flag for more details");
4548
+ } else {
4549
+ logError(`Failed to generate migration: ${error}`);
4550
+ if (error instanceof Error && error.stack) {
4551
+ console.error();
4552
+ console.error(error.stack);
4553
+ }
4554
+ }
4555
+ console.error();
4556
+ process.exit(1);
4557
+ }
4558
+ }
4559
+ function hasChanges3(diff) {
4560
+ return diff.collectionsToCreate.length > 0 || diff.collectionsToDelete.length > 0 || diff.collectionsToModify.length > 0;
4561
+ }
4562
+ function hasDestructiveChanges(diff) {
4563
+ const { destructive } = categorizeChangesBySeverity(diff);
4564
+ return destructive.length > 0;
4565
+ }
4566
+ function createStatusOutput(status, currentCount, snapshotCount, diff) {
4567
+ return {
4568
+ status,
4569
+ collections: {
4570
+ current: currentCount,
4571
+ snapshot: snapshotCount
4572
+ },
4573
+ changes: {
4574
+ create: diff?.collectionsToCreate.length ?? 0,
4575
+ delete: diff?.collectionsToDelete.length ?? 0,
4576
+ modify: diff?.collectionsToModify.length ?? 0
4577
+ },
4578
+ destructive: diff ? hasDestructiveChanges(diff) : false
4579
+ };
4580
+ }
4581
+ function displayDestructiveChangesSummary(diff) {
4582
+ const { destructive, nonDestructive } = categorizeChangesBySeverity(diff);
4583
+ if (destructive.length > 0) {
4584
+ logSection("\u26A0\uFE0F Destructive Changes");
4585
+ console.log();
4586
+ for (const change of destructive) {
4587
+ console.log(chalk__default.default.red(` ${change}`));
4588
+ }
4589
+ console.log();
4590
+ }
4591
+ if (nonDestructive.length > 0) {
4592
+ logSection("\u2713 Non-Destructive Changes");
4593
+ console.log();
4594
+ for (const change of nonDestructive) {
4595
+ console.log(chalk__default.default.green(` ${change}`));
4596
+ }
4597
+ console.log();
4598
+ }
4599
+ }
4600
+ function displayChangeTable(diff) {
4601
+ const rows = [];
4602
+ for (const collection of diff.collectionsToCreate) {
4603
+ rows.push([
4604
+ chalk__default.default.green("+"),
4605
+ collection.name,
4606
+ collection.type,
4607
+ `${collection.fields.length} fields`,
4608
+ chalk__default.default.green("Create")
4609
+ ]);
4610
+ }
4611
+ for (const collection of diff.collectionsToDelete) {
4612
+ rows.push([chalk__default.default.red("-"), collection.name, collection.type || "base", "-", chalk__default.default.red("Delete")]);
4613
+ }
4614
+ for (const mod of diff.collectionsToModify) {
4615
+ const changes = [];
4616
+ if (mod.fieldsToAdd.length > 0) changes.push(`+${mod.fieldsToAdd.length} fields`);
4617
+ if (mod.fieldsToRemove.length > 0) changes.push(`-${mod.fieldsToRemove.length} fields`);
4618
+ if (mod.fieldsToModify.length > 0) changes.push(`~${mod.fieldsToModify.length} fields`);
4619
+ if (mod.indexesToAdd.length > 0) changes.push(`+${mod.indexesToAdd.length} indexes`);
4620
+ if (mod.indexesToRemove.length > 0) changes.push(`-${mod.indexesToRemove.length} indexes`);
4621
+ if (mod.rulesToUpdate.length > 0) changes.push(`~${mod.rulesToUpdate.length} rules`);
4622
+ rows.push([chalk__default.default.yellow("~"), mod.collection, "-", changes.join(", ") || "No changes", chalk__default.default.yellow("Modify")]);
4623
+ }
4624
+ if (rows.length > 0) {
4625
+ logTable(["", "Collection", "Type", "Changes", "Action"], rows);
4626
+ }
4627
+ }
4628
+ async function executeStatus(options) {
4629
+ const isJsonMode = options.json === true;
4630
+ try {
4631
+ if (isJsonMode) {
4632
+ setVerbosity("quiet");
4633
+ } else {
4634
+ const parentOpts = options.parent?.opts?.() || {};
4635
+ if (parentOpts.verbose) {
4636
+ setVerbosity("verbose");
4637
+ } else if (parentOpts.quiet) {
4638
+ setVerbosity("quiet");
4639
+ }
4640
+ }
4641
+ logDebug("Checking migration status...");
4642
+ logDebug(`Options: ${JSON.stringify(options, null, 2)}`);
4643
+ const config = await loadConfig(options);
4644
+ const schemaDir = getSchemaDirectory(config);
4645
+ const migrationsDir = getMigrationsDirectory(config);
4646
+ logSection("\u{1F50D} Checking Migration Status");
4647
+ const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(schemaDir));
4648
+ logSuccess(`Found ${currentSchema.collections.size} collection(s) in schema`);
4649
+ logInfo("Loading previous snapshot...");
4650
+ const previousSnapshot = loadSnapshotIfExists({
4651
+ migrationsPath: migrationsDir,
4652
+ workspaceRoot: process.cwd()
4653
+ });
4654
+ if (!previousSnapshot) {
4655
+ if (isJsonMode) {
4656
+ const output = createStatusOutput("first-time-setup", currentSchema.collections.size, 0);
4657
+ console.log(formatStatusJson(output));
4658
+ return;
4659
+ }
4660
+ logSection("\u{1F195} First-Time Setup Detected");
4661
+ console.log();
4662
+ logInfo("No previous snapshot found. This appears to be a first-time setup.");
4663
+ console.log();
4664
+ logKeyValue("Collections in schema", String(currentSchema.collections.size));
4665
+ console.log();
4666
+ logInfo('Run "pocketbase-migrate generate" to create the initial migration.');
4667
+ return;
4668
+ }
4669
+ logSuccess(`Loaded snapshot with ${previousSnapshot.collections.size} collection(s)`);
4670
+ logSection("\u{1F4CA} Schema Comparison");
4671
+ const diff = compare(currentSchema, previousSnapshot);
4672
+ if (!hasChanges3(diff)) {
4673
+ if (isJsonMode) {
4674
+ const output = createStatusOutput(
4675
+ "up-to-date",
4676
+ currentSchema.collections.size,
4677
+ previousSnapshot.collections.size,
4678
+ diff
4679
+ );
4680
+ console.log(formatStatusJson(output));
4681
+ return;
4682
+ }
4683
+ console.log();
4684
+ logSuccess("\u2713 Schema is in sync with snapshot");
4685
+ logInfo("No pending changes detected");
4686
+ console.log();
4687
+ logKeyValue("Collections", String(currentSchema.collections.size));
4688
+ return;
4689
+ }
4690
+ if (isJsonMode) {
4691
+ const output = createStatusOutput(
4692
+ "changes-pending",
4693
+ currentSchema.collections.size,
4694
+ previousSnapshot.collections.size,
4695
+ diff
4696
+ );
4697
+ console.log(formatStatusJson(output));
4698
+ return;
4699
+ }
4700
+ console.log();
4701
+ console.log(formatChangeSummary(diff));
4702
+ logDebug("Detailed change table:");
4703
+ displayChangeTable(diff);
4704
+ displayDestructiveChangesSummary(diff);
4705
+ logSection("\u{1F4DD} Next Steps");
4706
+ console.log();
4707
+ console.log(" To generate a migration for these changes, run:");
4708
+ console.log(chalk__default.default.cyan(" pocketbase-migrate generate"));
4709
+ console.log();
4710
+ const { destructive } = categorizeChangesBySeverity(diff);
4711
+ if (destructive.length > 0) {
4712
+ console.log(chalk__default.default.yellow(" \u26A0\uFE0F Destructive changes detected. Use --force flag when generating:"));
4713
+ console.log(chalk__default.default.cyan(" pocketbase-migrate generate --force"));
4714
+ console.log();
4715
+ }
4716
+ } catch (error) {
4717
+ if (error instanceof SchemaParsingError) {
4718
+ logError("Schema Parsing Error");
4719
+ console.error();
4720
+ console.error(error.getDetailedMessage());
4721
+ console.error();
4722
+ logInfo("Suggestions:");
4723
+ console.log(" \u2022 Make sure your schema files are valid Zod schemas");
4724
+ console.log(' \u2022 Check that schema files export schemas ending with "Schema" or "InputSchema"');
4725
+ } else if (error instanceof SnapshotError) {
4726
+ logError("Snapshot Error");
4727
+ console.error();
4728
+ console.error(error.getDetailedMessage());
4729
+ console.error();
4730
+ logInfo("Suggestions:");
4731
+ console.log(" \u2022 Check that the snapshot file is not corrupted");
4732
+ console.log(" \u2022 Verify file permissions for the snapshot file");
4733
+ } else if (error instanceof ConfigurationError) {
4734
+ logError("Configuration Error");
4735
+ console.error();
4736
+ console.error(error.getDetailedMessage());
4737
+ console.error();
4738
+ logInfo("Suggestions:");
4739
+ console.log(" \u2022 Check your configuration file syntax");
4740
+ console.log(" \u2022 Verify all paths are correct and accessible");
4741
+ } else {
4742
+ logError(`Failed to check status: ${error}`);
4743
+ if (error instanceof Error && error.stack) {
4744
+ console.error(error.stack);
4745
+ }
4746
+ }
4747
+ process.exit(1);
4748
+ }
4749
+ }
4750
+
4751
+ exports.CLIUsageError = CLIUsageError;
4752
+ exports.ConfigurationError = ConfigurationError;
4753
+ exports.DiffEngine = DiffEngine;
4754
+ exports.FIELD_TYPE_INFO = FIELD_TYPE_INFO;
4755
+ exports.FileSystemError = FileSystemError;
4756
+ exports.MigrationError = MigrationError;
4757
+ exports.MigrationGenerationError = MigrationGenerationError;
4758
+ exports.MigrationGenerator = MigrationGenerator;
4759
+ exports.POCKETBASE_FIELD_TYPES = POCKETBASE_FIELD_TYPES;
4760
+ exports.PermissionTemplates = PermissionTemplates;
4761
+ exports.ProjectInputSchema = ProjectInputSchema;
4762
+ exports.ProjectSchema = ProjectSchema;
4763
+ exports.SchemaAnalyzer = SchemaAnalyzer;
4764
+ exports.SchemaParsingError = SchemaParsingError;
4765
+ exports.SnapshotError = SnapshotError;
4766
+ exports.SnapshotManager = SnapshotManager;
4767
+ exports.StatusEnum = StatusEnum;
4768
+ exports.UserInputSchema = UserInputSchema;
4769
+ exports.UserMutator = UserMutator;
4770
+ exports.UserSchema = UserSchema;
4771
+ exports.aggregateChanges = aggregateChanges;
4772
+ exports.baseImageFileSchema = baseImageFileSchema;
4773
+ exports.baseSchema = baseSchema;
4774
+ exports.baseSchemaWithTimestamps = baseSchemaWithTimestamps;
4775
+ exports.boolField = boolField;
4776
+ exports.buildFieldDefinition = buildFieldDefinition;
4777
+ exports.buildSchemaDefinition = buildSchemaDefinition;
4778
+ exports.categorizeChangesBySeverity = categorizeChangesBySeverity;
4779
+ exports.compare = compare;
4780
+ exports.compareFieldConstraints = compareFieldConstraints;
4781
+ exports.compareFieldOptions = compareFieldOptions;
4782
+ exports.compareFieldTypes = compareFieldTypes;
4783
+ exports.comparePermissions = comparePermissions;
4784
+ exports.compareRelationConfigurations = compareRelationConfigurations;
4785
+ exports.convertPocketBaseMigration = convertPocketBaseMigration;
4786
+ exports.convertZodSchemaToCollectionSchema = convertZodSchemaToCollectionSchema;
4787
+ exports.createMigrationFileStructure = createMigrationFileStructure;
4788
+ exports.createPermissions = createPermissions;
4789
+ exports.dateField = dateField;
4790
+ exports.detectDestructiveChanges = detectDestructiveChanges;
4791
+ exports.detectFieldChanges = detectFieldChanges;
4792
+ exports.discoverSchemaFiles = discoverSchemaFiles;
4793
+ exports.editorField = editorField;
4794
+ exports.emailField = emailField;
4795
+ exports.extractComprehensiveFieldOptions = extractComprehensiveFieldOptions;
4796
+ exports.extractFieldDefinitions = extractFieldDefinitions;
4797
+ exports.extractFieldOptions = extractFieldOptions;
4798
+ exports.extractIndexes = extractIndexes;
4799
+ exports.extractSchemaDefinitions = extractSchemaDefinitions;
4800
+ exports.fileField = fileField;
4801
+ exports.filesField = filesField;
4802
+ exports.filterSystemCollections = filterSystemCollections;
4803
+ exports.findLatestSnapshot = findLatestSnapshot;
4804
+ exports.findNewCollections = findNewCollections;
4805
+ exports.findNewFields = findNewFields;
4806
+ exports.findRemovedCollections = findRemovedCollections;
4807
+ exports.findRemovedFields = findRemovedFields;
4808
+ exports.formatChangeSummary = formatChangeSummary;
4809
+ exports.generate = generate;
4810
+ exports.generateChangeSummary = generateChangeSummary;
4811
+ exports.generateCollectionCreation = generateCollectionCreation;
4812
+ exports.generateCollectionPermissions = generateCollectionPermissions;
4813
+ exports.generateCollectionRules = generateCollectionRules;
4814
+ exports.generateDownMigration = generateDownMigration;
4815
+ exports.generateFieldAddition = generateFieldAddition;
4816
+ exports.generateFieldDefinitionObject = generateFieldDefinitionObject;
4817
+ exports.generateFieldDeletion = generateFieldDeletion;
4818
+ exports.generateFieldModification = generateFieldModification;
4819
+ exports.generateFieldsArray = generateFieldsArray;
4820
+ exports.generateIndexesArray = generateIndexesArray;
4821
+ exports.generateMigration = executeGenerate;
4822
+ exports.generateMigrationDescription = generateMigrationDescription;
4823
+ exports.generateMigrationFilename = generateMigrationFilename;
4824
+ exports.generatePermissionUpdate = generatePermissionUpdate;
4825
+ exports.generateTimestamp = generateTimestamp;
4826
+ exports.generateUpMigration = generateUpMigration;
4827
+ exports.geoPointField = geoPointField;
4828
+ exports.getArrayElementType = getArrayElementType;
4829
+ exports.getCollectionNameFromFile = getCollectionNameFromFile;
4830
+ exports.getDefaultValue = getDefaultValue;
4831
+ exports.getFieldTypeInfo = getFieldTypeInfo;
4832
+ exports.getMaxSelect = getMaxSelect;
4833
+ exports.getMigrationStatus = executeStatus;
4834
+ exports.getMinSelect = getMinSelect;
4835
+ exports.getSnapshotPath = getSnapshotPath;
4836
+ exports.getSnapshotVersion = getSnapshotVersion;
4837
+ exports.getUsersSystemFields = getUsersSystemFields;
4838
+ exports.importSchemaModule = importSchemaModule;
4839
+ exports.inputImageFileSchema = inputImageFileSchema;
4840
+ exports.isArrayType = isArrayType;
4841
+ exports.isAuthCollection = isAuthCollection;
4842
+ exports.isEditorField = isEditorField;
4843
+ exports.isFieldRequired = isFieldRequired;
4844
+ exports.isFileFieldByName = isFileFieldByName;
4845
+ exports.isGeoPointType = isGeoPointType;
4846
+ exports.isMultipleRelationField = isMultipleRelationField;
4847
+ exports.isPermissionSchema = isPermissionSchema;
4848
+ exports.isRelationField = isRelationField;
4849
+ exports.isSingleRelationField = isSingleRelationField;
4850
+ exports.isSystemCollection = isSystemCollection;
4851
+ exports.isTemplateConfig = isTemplateConfig;
4852
+ exports.jsonField = jsonField;
4853
+ exports.loadBaseMigration = loadBaseMigration;
4854
+ exports.loadConfig = loadConfig;
4855
+ exports.loadSnapshot = loadSnapshot;
4856
+ exports.loadSnapshotIfExists = loadSnapshotIfExists;
4857
+ exports.logError = logError;
4858
+ exports.logInfo = logInfo;
4859
+ exports.logSection = logSection;
4860
+ exports.logSuccess = logSuccess;
4861
+ exports.logWarning = logWarning;
4862
+ exports.mapZodArrayType = mapZodArrayType;
4863
+ exports.mapZodBooleanType = mapZodBooleanType;
4864
+ exports.mapZodDateType = mapZodDateType;
4865
+ exports.mapZodEnumType = mapZodEnumType;
4866
+ exports.mapZodNumberType = mapZodNumberType;
4867
+ exports.mapZodRecordType = mapZodRecordType;
4868
+ exports.mapZodStringType = mapZodStringType;
4869
+ exports.mapZodTypeToPocketBase = mapZodTypeToPocketBase;
4870
+ exports.matchCollectionsByName = matchCollectionsByName;
4871
+ exports.matchFieldsByName = matchFieldsByName;
4872
+ exports.mergePermissions = mergePermissions;
4873
+ exports.mergeSnapshots = mergeSnapshots;
4874
+ exports.numberField = numberField;
4875
+ exports.omitImageFilesSchema = omitImageFilesSchema;
4876
+ exports.parseSchemaFiles = parseSchemaFiles;
4877
+ exports.pluralize = pluralize;
4878
+ exports.relationField = relationField;
4879
+ exports.relationsField = relationsField;
4880
+ exports.requiresForceFlag = requiresForceFlag;
4881
+ exports.resolveTargetCollection = resolveTargetCollection;
4882
+ exports.resolveTemplate = resolveTemplate;
4883
+ exports.saveSnapshot = saveSnapshot;
4884
+ exports.selectField = selectField;
4885
+ exports.selectSchemaForCollection = selectSchemaForCollection;
4886
+ exports.singularize = singularize;
4887
+ exports.snapshotExists = snapshotExists;
4888
+ exports.textField = textField;
4889
+ exports.toCollectionName = toCollectionName;
4890
+ exports.unwrapZodType = unwrapZodType;
4891
+ exports.urlField = urlField;
4892
+ exports.validatePermissionConfig = validatePermissionConfig;
4893
+ exports.validateRuleExpression = validateRuleExpression;
4894
+ exports.validateSnapshot = validateSnapshot;
4895
+ exports.withIndexes = withIndexes;
4896
+ exports.withPermissions = withPermissions;
4897
+ exports.withProgress = withProgress;
4898
+ exports.writeMigrationFile = writeMigrationFile;
4899
+ //# sourceMappingURL=index.cjs.map
4900
+ //# sourceMappingURL=index.cjs.map