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
@@ -0,0 +1,1267 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var zod = require('zod');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
26
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
+
28
+ // src/migration/analyzer.ts
29
+
30
+ // src/migration/errors.ts
31
+ var MigrationError = class _MigrationError extends Error {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = "MigrationError";
35
+ Object.setPrototypeOf(this, _MigrationError.prototype);
36
+ }
37
+ };
38
+ var SchemaParsingError = class _SchemaParsingError extends MigrationError {
39
+ filePath;
40
+ originalError;
41
+ constructor(message, filePath, originalError) {
42
+ super(message);
43
+ this.name = "SchemaParsingError";
44
+ this.filePath = filePath;
45
+ this.originalError = originalError;
46
+ Object.setPrototypeOf(this, _SchemaParsingError.prototype);
47
+ }
48
+ /**
49
+ * Creates a formatted error message with file path and original error details
50
+ */
51
+ getDetailedMessage() {
52
+ const parts = [this.message];
53
+ if (this.filePath) {
54
+ parts.push(`
55
+ File: ${this.filePath}`);
56
+ }
57
+ if (this.originalError) {
58
+ parts.push(`
59
+ Cause: ${this.originalError.message}`);
60
+ }
61
+ return parts.join("");
62
+ }
63
+ };
64
+ var FileSystemError = class _FileSystemError extends MigrationError {
65
+ path;
66
+ operation;
67
+ code;
68
+ originalError;
69
+ constructor(message, path2, operation, code, originalError) {
70
+ super(message);
71
+ this.name = "FileSystemError";
72
+ this.path = path2;
73
+ this.operation = operation;
74
+ this.code = code;
75
+ this.originalError = originalError;
76
+ Object.setPrototypeOf(this, _FileSystemError.prototype);
77
+ }
78
+ /**
79
+ * Creates a formatted error message with path, operation, and error code details
80
+ */
81
+ getDetailedMessage() {
82
+ const parts = [this.message];
83
+ if (this.operation) {
84
+ parts.push(`
85
+ Operation: ${this.operation}`);
86
+ }
87
+ if (this.path) {
88
+ parts.push(`
89
+ Path: ${this.path}`);
90
+ }
91
+ if (this.code) {
92
+ parts.push(`
93
+ Error Code: ${this.code}`);
94
+ }
95
+ if (this.originalError) {
96
+ parts.push(`
97
+ Cause: ${this.originalError.message}`);
98
+ }
99
+ return parts.join("");
100
+ }
101
+ };
102
+
103
+ // src/schema/permission-templates.ts
104
+ var PermissionTemplates = {
105
+ /**
106
+ * Public access - anyone can perform all operations
107
+ */
108
+ public: () => ({
109
+ listRule: "",
110
+ viewRule: "",
111
+ createRule: "",
112
+ updateRule: "",
113
+ deleteRule: ""
114
+ }),
115
+ /**
116
+ * Authenticated users only - requires valid authentication for all operations
117
+ */
118
+ authenticated: () => ({
119
+ listRule: '@request.auth.id != ""',
120
+ viewRule: '@request.auth.id != ""',
121
+ createRule: '@request.auth.id != ""',
122
+ updateRule: '@request.auth.id != ""',
123
+ deleteRule: '@request.auth.id != ""'
124
+ }),
125
+ /**
126
+ * Owner-only access - users can only manage their own records
127
+ * @param ownerField - Name of the relation field pointing to user (default: 'User')
128
+ */
129
+ ownerOnly: (ownerField = "User") => ({
130
+ listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
131
+ viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
132
+ createRule: '@request.auth.id != ""',
133
+ updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
134
+ deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
135
+ }),
136
+ /**
137
+ * Admin/superuser only access
138
+ * Assumes a 'role' field exists with 'admin' value
139
+ * @param roleField - Name of the role field (default: 'role')
140
+ */
141
+ adminOnly: (roleField = "role") => ({
142
+ listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
143
+ viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
144
+ createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
145
+ updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
146
+ deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
147
+ }),
148
+ /**
149
+ * Public read, authenticated write
150
+ * Anyone can list/view, but only authenticated users can create/update/delete
151
+ */
152
+ readPublic: () => ({
153
+ listRule: "",
154
+ viewRule: "",
155
+ createRule: '@request.auth.id != ""',
156
+ updateRule: '@request.auth.id != ""',
157
+ deleteRule: '@request.auth.id != ""'
158
+ }),
159
+ /**
160
+ * Locked access - only superusers can perform operations
161
+ * All rules are set to null (locked)
162
+ */
163
+ locked: () => ({
164
+ listRule: null,
165
+ viewRule: null,
166
+ createRule: null,
167
+ updateRule: null,
168
+ deleteRule: null
169
+ }),
170
+ /**
171
+ * Read-only authenticated - authenticated users can read, no write access
172
+ */
173
+ readOnlyAuthenticated: () => ({
174
+ listRule: '@request.auth.id != ""',
175
+ viewRule: '@request.auth.id != ""',
176
+ createRule: null,
177
+ updateRule: null,
178
+ deleteRule: null
179
+ })
180
+ };
181
+ function resolveTemplate(config) {
182
+ let baseRules;
183
+ switch (config.template) {
184
+ case "public":
185
+ baseRules = PermissionTemplates.public();
186
+ break;
187
+ case "authenticated":
188
+ baseRules = PermissionTemplates.authenticated();
189
+ break;
190
+ case "owner-only":
191
+ baseRules = PermissionTemplates.ownerOnly(config.ownerField);
192
+ break;
193
+ case "admin-only":
194
+ baseRules = PermissionTemplates.adminOnly(config.roleField);
195
+ break;
196
+ case "read-public":
197
+ baseRules = PermissionTemplates.readPublic();
198
+ break;
199
+ case "custom":
200
+ baseRules = {};
201
+ break;
202
+ default: {
203
+ const _exhaustive = config.template;
204
+ throw new Error(`Unknown template type: ${_exhaustive}`);
205
+ }
206
+ }
207
+ return {
208
+ ...baseRules,
209
+ ...config.customRules
210
+ };
211
+ }
212
+
213
+ // src/migration/rule-validator.ts
214
+ var RuleValidator = class {
215
+ fields;
216
+ collectionName;
217
+ isAuthCollection;
218
+ constructor(collectionName, fields, isAuthCollection2 = false) {
219
+ this.collectionName = collectionName;
220
+ this.fields = new Map(fields.map((f) => [f.name, f]));
221
+ this.isAuthCollection = isAuthCollection2;
222
+ this.addSystemFields();
223
+ }
224
+ /**
225
+ * Add system fields that are always available in PocketBase collections
226
+ * These fields are automatically added by PocketBase and can be referenced in rules
227
+ */
228
+ addSystemFields() {
229
+ const systemFields = [
230
+ { name: "id", type: "text", required: true, options: {} },
231
+ { name: "created", type: "date", required: true, options: {} },
232
+ { name: "updated", type: "date", required: true, options: {} },
233
+ { name: "collectionId", type: "text", required: true, options: {} },
234
+ { name: "collectionName", type: "text", required: true, options: {} }
235
+ ];
236
+ if (this.isAuthCollection) {
237
+ systemFields.push(
238
+ { name: "email", type: "email", required: true, options: {} },
239
+ { name: "emailVisibility", type: "bool", required: false, options: {} },
240
+ { name: "verified", type: "bool", required: false, options: {} },
241
+ { name: "tokenKey", type: "text", required: true, options: {} },
242
+ { name: "password", type: "text", required: true, options: {} }
243
+ );
244
+ }
245
+ for (const field of systemFields) {
246
+ if (!this.fields.has(field.name)) {
247
+ this.fields.set(field.name, field);
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Validate a rule expression
253
+ *
254
+ * @param ruleType - The type of rule being validated
255
+ * @param expression - The rule expression to validate
256
+ * @returns Validation result with errors, warnings, and field references
257
+ */
258
+ validate(ruleType, expression) {
259
+ const result = {
260
+ valid: true,
261
+ errors: [],
262
+ warnings: [],
263
+ fieldReferences: []
264
+ };
265
+ if (expression === null) {
266
+ return result;
267
+ }
268
+ if (expression === "") {
269
+ result.warnings.push(`${ruleType} is public - anyone can perform this operation`);
270
+ return result;
271
+ }
272
+ if (ruleType === "manageRule" && !this.isAuthCollection) {
273
+ result.valid = false;
274
+ result.errors.push("manageRule is only valid for auth collections");
275
+ return result;
276
+ }
277
+ const fieldRefs = this.extractFieldReferences(expression);
278
+ result.fieldReferences = fieldRefs;
279
+ for (const fieldRef of fieldRefs) {
280
+ this.validateFieldReference(fieldRef, result);
281
+ }
282
+ this.validateRequestReferences(expression, result);
283
+ this.validateSyntax(expression, result);
284
+ return result;
285
+ }
286
+ /**
287
+ * Extract field references from expression
288
+ *
289
+ * Matches field names that are not @request references.
290
+ * Handles dot notation for relations: user.email, post.author.name
291
+ *
292
+ * @param expression - The rule expression
293
+ * @returns Array of unique field references
294
+ */
295
+ extractFieldReferences(expression) {
296
+ const refs = [];
297
+ let cleaned = expression.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''");
298
+ cleaned = cleaned.replace(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g, "");
299
+ const fieldPattern = /(?:^|[^@\w])([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)(?=[^a-zA-Z0-9_.]|$)/g;
300
+ let match;
301
+ while ((match = fieldPattern.exec(cleaned)) !== null) {
302
+ const ref = match[1];
303
+ if (!this.isKeyword(ref)) {
304
+ refs.push(ref);
305
+ }
306
+ }
307
+ return [...new Set(refs)];
308
+ }
309
+ /**
310
+ * Check if a word is a PocketBase keyword
311
+ *
312
+ * @param word - The word to check
313
+ * @returns True if the word is a keyword
314
+ */
315
+ isKeyword(word) {
316
+ const keywords = ["true", "false", "null", "AND", "OR", "NOT", "LIKE", "IN"];
317
+ return keywords.includes(word.toUpperCase());
318
+ }
319
+ /**
320
+ * Validate a field reference exists in schema
321
+ *
322
+ * Checks if the root field exists and validates relation chains.
323
+ * For nested references, warns about potential issues since we can't
324
+ * validate across collections without loading related schemas.
325
+ *
326
+ * @param fieldRef - The field reference to validate (e.g., "user" or "user.email")
327
+ * @param result - The validation result to update
328
+ */
329
+ validateFieldReference(fieldRef, result) {
330
+ const parts = fieldRef.split(".");
331
+ const rootField = parts[0];
332
+ if (!this.fields.has(rootField)) {
333
+ result.errors.push(`Field '${rootField}' does not exist in collection '${this.collectionName}'`);
334
+ result.valid = false;
335
+ return;
336
+ }
337
+ if (parts.length > 1) {
338
+ const field = this.fields.get(rootField);
339
+ if (field.type !== "relation") {
340
+ result.errors.push(`Field '${rootField}' is not a relation field, cannot access nested property '${parts[1]}'`);
341
+ result.valid = false;
342
+ } else {
343
+ result.warnings.push(
344
+ `Nested field reference '${fieldRef}' - ensure target collection has field '${parts.slice(1).join(".")}'`
345
+ );
346
+ }
347
+ }
348
+ }
349
+ /**
350
+ * Validate @request references
351
+ *
352
+ * Checks that @request references follow valid PocketBase patterns:
353
+ * - @request.auth.* - authenticated user data
354
+ * - @request.body.* - request body fields
355
+ * - @request.query.* - query parameters
356
+ * - @request.headers.* - request headers
357
+ * - @request.method - HTTP method
358
+ * - @request.context - execution context
359
+ *
360
+ * @param expression - The rule expression
361
+ * @param result - The validation result to update
362
+ */
363
+ validateRequestReferences(expression, result) {
364
+ const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
365
+ for (const ref of requestRefs) {
366
+ const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
367
+ if (!isValid) {
368
+ result.errors.push(`Invalid @request reference: '${ref}'`);
369
+ result.valid = false;
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Validate basic syntax patterns
375
+ *
376
+ * Checks for:
377
+ * - Balanced parentheses
378
+ * - Common operator mistakes (== instead of =)
379
+ *
380
+ * @param expression - The rule expression
381
+ * @param result - The validation result to update
382
+ */
383
+ validateSyntax(expression, result) {
384
+ let parenCount = 0;
385
+ for (const char of expression) {
386
+ if (char === "(") parenCount++;
387
+ if (char === ")") parenCount--;
388
+ if (parenCount < 0) {
389
+ result.errors.push("Unbalanced parentheses in expression");
390
+ result.valid = false;
391
+ return;
392
+ }
393
+ }
394
+ if (parenCount !== 0) {
395
+ result.errors.push("Unbalanced parentheses in expression");
396
+ result.valid = false;
397
+ }
398
+ if (expression.includes("==")) {
399
+ result.warnings.push("Use '=' instead of '==' for equality comparison in PocketBase rules");
400
+ }
401
+ }
402
+ };
403
+
404
+ // src/migration/permission-analyzer.ts
405
+ var PermissionAnalyzer = class {
406
+ /**
407
+ * Extract permission metadata from Zod schema description
408
+ *
409
+ * Zod schemas can have permission metadata attached via the describe() method.
410
+ * This method parses the description and extracts the permission configuration.
411
+ *
412
+ * @param schemaDescription - The Zod schema description string
413
+ * @returns Permission schema if found, null otherwise
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const analyzer = new PermissionAnalyzer();
418
+ * const permissions = analyzer.extractPermissions(schema.description);
419
+ * ```
420
+ */
421
+ extractPermissions(schemaDescription) {
422
+ if (!schemaDescription) {
423
+ return null;
424
+ }
425
+ try {
426
+ const metadata = JSON.parse(schemaDescription);
427
+ if (metadata.permissions) {
428
+ return metadata.permissions;
429
+ }
430
+ } catch {
431
+ return null;
432
+ }
433
+ return null;
434
+ }
435
+ /**
436
+ * Resolve template configuration to concrete rules
437
+ *
438
+ * Takes either a template configuration or a direct permission schema
439
+ * and returns a fully resolved permission schema with all rules defined.
440
+ *
441
+ * If the input is already a permission schema (has rule properties),
442
+ * it's returned as-is. Otherwise, the template is resolved using the
443
+ * template resolver.
444
+ *
445
+ * @param config - Template configuration or direct permission schema
446
+ * @returns Resolved permission schema
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * const analyzer = new PermissionAnalyzer();
451
+ *
452
+ * // Resolve from template
453
+ * const permissions = analyzer.resolvePermissions({
454
+ * template: 'owner-only',
455
+ * ownerField: 'User'
456
+ * });
457
+ *
458
+ * // Or pass direct schema
459
+ * const permissions = analyzer.resolvePermissions({
460
+ * listRule: '@request.auth.id != ""',
461
+ * viewRule: '@request.auth.id != ""'
462
+ * });
463
+ * ```
464
+ */
465
+ resolvePermissions(config) {
466
+ if ("listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config) {
467
+ return config;
468
+ }
469
+ return resolveTemplate(config);
470
+ }
471
+ /**
472
+ * Validate all rules in a permission schema
473
+ *
474
+ * Validates each rule in the permission schema against the collection's
475
+ * field definitions. Returns a map of validation results keyed by rule type.
476
+ *
477
+ * Only validates rules that are defined (not undefined). Undefined rules
478
+ * are treated as null (locked) by default.
479
+ *
480
+ * @param collectionName - Name of the collection being validated
481
+ * @param permissions - Permission schema to validate
482
+ * @param fields - Collection field definitions
483
+ * @param isAuthCollection - Whether this is an auth collection (allows manageRule)
484
+ * @returns Map of validation results by rule type
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * const analyzer = new PermissionAnalyzer();
489
+ * const results = analyzer.validatePermissions(
490
+ * 'posts',
491
+ * { listRule: '@request.auth.id != ""', viewRule: 'author = @request.auth.id' },
492
+ * fields,
493
+ * false
494
+ * );
495
+ *
496
+ * for (const [ruleType, result] of results) {
497
+ * if (!result.valid) {
498
+ * console.error(`${ruleType} validation failed:`, result.errors);
499
+ * }
500
+ * }
501
+ * ```
502
+ */
503
+ validatePermissions(collectionName, permissions, fields, isAuthCollection2 = false) {
504
+ const validator = new RuleValidator(collectionName, fields, isAuthCollection2);
505
+ const results = /* @__PURE__ */ new Map();
506
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
507
+ if (isAuthCollection2) {
508
+ ruleTypes.push("manageRule");
509
+ }
510
+ for (const ruleType of ruleTypes) {
511
+ const expression = permissions[ruleType];
512
+ if (expression !== void 0) {
513
+ results.set(ruleType, validator.validate(ruleType, expression));
514
+ }
515
+ }
516
+ return results;
517
+ }
518
+ /**
519
+ * Merge permissions with defaults
520
+ *
521
+ * Ensures all rule types have a defined value. Undefined rules are set
522
+ * to null (locked to superusers only), which is the PocketBase default.
523
+ *
524
+ * This is useful when generating migrations to ensure all rules are
525
+ * explicitly set in the collection configuration.
526
+ *
527
+ * @param permissions - Permission schema (may have undefined rules)
528
+ * @returns Permission schema with all rules defined (null if not specified)
529
+ *
530
+ * @example
531
+ * ```typescript
532
+ * const analyzer = new PermissionAnalyzer();
533
+ * const merged = analyzer.mergeWithDefaults({
534
+ * listRule: '@request.auth.id != ""'
535
+ * // other rules undefined
536
+ * });
537
+ *
538
+ * // Result:
539
+ * // {
540
+ * // listRule: '@request.auth.id != ""',
541
+ * // viewRule: null,
542
+ * // createRule: null,
543
+ * // updateRule: null,
544
+ * // deleteRule: null,
545
+ * // manageRule: null
546
+ * // }
547
+ * ```
548
+ */
549
+ mergeWithDefaults(permissions) {
550
+ return {
551
+ listRule: permissions.listRule ?? null,
552
+ viewRule: permissions.viewRule ?? null,
553
+ createRule: permissions.createRule ?? null,
554
+ updateRule: permissions.updateRule ?? null,
555
+ deleteRule: permissions.deleteRule ?? null,
556
+ manageRule: permissions.manageRule ?? null
557
+ };
558
+ }
559
+ };
560
+
561
+ // src/migration/utils/pluralize.ts
562
+ var SPECIAL_CASES = {
563
+ // Common irregular plurals
564
+ person: "people",
565
+ Person: "People",
566
+ child: "children",
567
+ Child: "Children",
568
+ man: "men",
569
+ Man: "Men",
570
+ woman: "women",
571
+ Woman: "Women",
572
+ tooth: "teeth",
573
+ Tooth: "Teeth",
574
+ foot: "feet",
575
+ Foot: "Feet",
576
+ mouse: "mice",
577
+ Mouse: "Mice",
578
+ goose: "geese",
579
+ Goose: "Geese",
580
+ // Words ending in -y
581
+ category: "categories",
582
+ Category: "Categories",
583
+ company: "companies",
584
+ Company: "Companies",
585
+ city: "cities",
586
+ City: "Cities",
587
+ country: "countries",
588
+ Country: "Countries",
589
+ story: "stories",
590
+ Story: "Stories",
591
+ party: "parties",
592
+ Party: "Parties",
593
+ family: "families",
594
+ Family: "Families",
595
+ activity: "activities",
596
+ Activity: "Activities",
597
+ priority: "priorities",
598
+ Priority: "Priorities",
599
+ // Words ending in -f or -fe
600
+ life: "lives",
601
+ Life: "Lives",
602
+ wife: "wives",
603
+ Wife: "Wives",
604
+ knife: "knives",
605
+ Knife: "Knives",
606
+ leaf: "leaves",
607
+ Leaf: "Leaves",
608
+ shelf: "shelves",
609
+ Shelf: "Shelves",
610
+ half: "halves",
611
+ Half: "Halves",
612
+ // Words ending in -is
613
+ analysis: "analyses",
614
+ Analysis: "Analyses",
615
+ basis: "bases",
616
+ Basis: "Bases",
617
+ crisis: "crises",
618
+ Crisis: "Crises",
619
+ thesis: "theses",
620
+ Thesis: "Theses",
621
+ // Words ending in -us
622
+ cactus: "cacti",
623
+ Cactus: "Cacti",
624
+ focus: "foci",
625
+ Focus: "Foci",
626
+ fungus: "fungi",
627
+ Fungus: "Fungi",
628
+ nucleus: "nuclei",
629
+ Nucleus: "Nuclei",
630
+ radius: "radii",
631
+ Radius: "Radii",
632
+ // Words ending in -on
633
+ phenomenon: "phenomena",
634
+ Phenomenon: "Phenomena",
635
+ criterion: "criteria",
636
+ Criterion: "Criteria",
637
+ // Words ending in -um
638
+ datum: "data",
639
+ Datum: "Data",
640
+ medium: "media",
641
+ Medium: "Media",
642
+ curriculum: "curricula",
643
+ Curriculum: "Curricula",
644
+ // Unchanged plurals
645
+ sheep: "sheep",
646
+ Sheep: "Sheep",
647
+ deer: "deer",
648
+ Deer: "Deer",
649
+ fish: "fish",
650
+ Fish: "Fish",
651
+ species: "species",
652
+ Species: "Species",
653
+ series: "series",
654
+ Series: "Series"
655
+ };
656
+ function pluralize(singular) {
657
+ if (SPECIAL_CASES[singular]) {
658
+ return SPECIAL_CASES[singular];
659
+ }
660
+ if (singular.length > 3 && singular.endsWith("s") && !singular.endsWith("ss")) {
661
+ return singular;
662
+ }
663
+ const lowerSingular = singular.toLowerCase();
664
+ let plural;
665
+ if (/(?:s|ss|sh|ch|x|z)$/.test(lowerSingular)) {
666
+ plural = singular + "es";
667
+ } else if (/[^aeiou]y$/.test(lowerSingular)) {
668
+ plural = singular.slice(0, -1) + "ies";
669
+ } else if (/[^aeiou]o$/.test(lowerSingular)) {
670
+ plural = singular + "es";
671
+ } else if (/fe?$/.test(lowerSingular)) {
672
+ if (lowerSingular.endsWith("fe")) {
673
+ plural = singular.slice(0, -2) + "ves";
674
+ } else {
675
+ plural = singular.slice(0, -1) + "ves";
676
+ }
677
+ } else {
678
+ plural = singular + "s";
679
+ }
680
+ return plural;
681
+ }
682
+ function toCollectionName(entityName) {
683
+ return pluralize(entityName);
684
+ }
685
+ function isSingleRelationField(fieldName, zodType) {
686
+ let unwrappedType = zodType;
687
+ if (zodType instanceof zod.z.ZodOptional) {
688
+ unwrappedType = zodType._def.innerType;
689
+ }
690
+ if (unwrappedType instanceof zod.z.ZodNullable) {
691
+ unwrappedType = unwrappedType._def.innerType;
692
+ }
693
+ if (unwrappedType instanceof zod.z.ZodDefault) {
694
+ unwrappedType = unwrappedType._def.innerType;
695
+ }
696
+ if (!(unwrappedType instanceof zod.z.ZodString)) {
697
+ return false;
698
+ }
699
+ const startsWithUppercase = /^[A-Z]/.test(fieldName);
700
+ const commonStringFields = ["Title", "Name", "Description", "Content", "Summary", "Status", "Type"];
701
+ const isCommonField = commonStringFields.includes(fieldName);
702
+ return startsWithUppercase && !isCommonField;
703
+ }
704
+ function isMultipleRelationField(fieldName, zodType) {
705
+ let unwrappedType = zodType;
706
+ if (zodType instanceof zod.z.ZodOptional) {
707
+ unwrappedType = zodType._def.innerType;
708
+ }
709
+ if (unwrappedType instanceof zod.z.ZodNullable) {
710
+ unwrappedType = unwrappedType._def.innerType;
711
+ }
712
+ if (unwrappedType instanceof zod.z.ZodDefault) {
713
+ unwrappedType = unwrappedType._def.innerType;
714
+ }
715
+ if (!(unwrappedType instanceof zod.z.ZodArray)) {
716
+ return false;
717
+ }
718
+ const elementType = unwrappedType._def.type;
719
+ if (!(elementType instanceof zod.z.ZodString)) {
720
+ return false;
721
+ }
722
+ const hasUppercase = /[A-Z]/.test(fieldName);
723
+ return hasUppercase;
724
+ }
725
+ function resolveTargetCollection(fieldName) {
726
+ const matches = fieldName.match(/[A-Z][a-z]+/g);
727
+ if (!matches || matches.length === 0) {
728
+ return pluralize(fieldName);
729
+ }
730
+ const entityName = matches[matches.length - 1];
731
+ return pluralize(entityName);
732
+ }
733
+ function isRelationField(fieldName, zodType) {
734
+ return isSingleRelationField(fieldName, zodType) || isMultipleRelationField(fieldName, zodType);
735
+ }
736
+ function getMaxSelect(fieldName, zodType) {
737
+ if (isSingleRelationField(fieldName, zodType)) {
738
+ return 1;
739
+ }
740
+ if (isMultipleRelationField(fieldName, zodType)) {
741
+ let unwrappedType = zodType;
742
+ if (zodType instanceof zod.z.ZodOptional) {
743
+ unwrappedType = zodType._def.innerType;
744
+ }
745
+ if (unwrappedType instanceof zod.z.ZodNullable) {
746
+ unwrappedType = unwrappedType._def.innerType;
747
+ }
748
+ if (unwrappedType instanceof zod.z.ZodDefault) {
749
+ unwrappedType = unwrappedType._def.innerType;
750
+ }
751
+ if (unwrappedType instanceof zod.z.ZodArray) {
752
+ const arrayDef = unwrappedType._def;
753
+ if (arrayDef.maxLength) {
754
+ return arrayDef.maxLength.value;
755
+ }
756
+ return 999;
757
+ }
758
+ }
759
+ return 1;
760
+ }
761
+ function getMinSelect(fieldName, zodType) {
762
+ if (!isMultipleRelationField(fieldName, zodType)) {
763
+ return void 0;
764
+ }
765
+ let unwrappedType = zodType;
766
+ if (zodType instanceof zod.z.ZodOptional) {
767
+ unwrappedType = zodType._def.innerType;
768
+ }
769
+ if (unwrappedType instanceof zod.z.ZodNullable) {
770
+ unwrappedType = unwrappedType._def.innerType;
771
+ }
772
+ if (unwrappedType instanceof zod.z.ZodDefault) {
773
+ unwrappedType = unwrappedType._def.innerType;
774
+ }
775
+ if (unwrappedType instanceof zod.z.ZodArray) {
776
+ const arrayDef = unwrappedType._def;
777
+ if (arrayDef.minLength) {
778
+ return arrayDef.minLength.value;
779
+ }
780
+ }
781
+ return void 0;
782
+ }
783
+ function mapZodStringType(zodType) {
784
+ const checks = zodType._def.checks || [];
785
+ const hasEmail = checks.some((check) => check.kind === "email");
786
+ if (hasEmail) {
787
+ return "email";
788
+ }
789
+ const hasUrl = checks.some((check) => check.kind === "url");
790
+ if (hasUrl) {
791
+ return "url";
792
+ }
793
+ const hasDatetime = checks.some((check) => check.kind === "datetime");
794
+ if (hasDatetime) {
795
+ return "date";
796
+ }
797
+ return "text";
798
+ }
799
+ function mapZodNumberType(_zodType) {
800
+ return "number";
801
+ }
802
+ function mapZodBooleanType(_zodType) {
803
+ return "bool";
804
+ }
805
+ function mapZodEnumType(_zodType) {
806
+ return "select";
807
+ }
808
+ function mapZodArrayType(zodType, _fieldName) {
809
+ const elementType = zodType._def.type;
810
+ if (elementType instanceof zod.z.ZodType) {
811
+ const typeName = elementType._def.typeName;
812
+ if (typeName === "ZodType" && elementType._def?.innerType?.name === "File") {
813
+ return "file";
814
+ }
815
+ }
816
+ if (elementType._def?.typeName === "ZodType") {
817
+ const checks = elementType._def?.checks || [];
818
+ const isFileInstance = checks.some(
819
+ (check) => check.kind === "instanceof" || elementType._def?.innerType?.name === "File"
820
+ );
821
+ if (isFileInstance) {
822
+ return "file";
823
+ }
824
+ }
825
+ if (elementType instanceof zod.z.ZodString) {
826
+ return "relation";
827
+ }
828
+ return "json";
829
+ }
830
+ function mapZodDateType(_zodType) {
831
+ return "date";
832
+ }
833
+ function mapZodRecordType(_zodType) {
834
+ return "json";
835
+ }
836
+ function mapZodTypeToPocketBase(zodType, fieldName) {
837
+ let unwrappedType = zodType;
838
+ if (zodType instanceof zod.z.ZodOptional) {
839
+ unwrappedType = zodType._def.innerType;
840
+ }
841
+ if (unwrappedType instanceof zod.z.ZodNullable) {
842
+ unwrappedType = unwrappedType._def.innerType;
843
+ }
844
+ if (unwrappedType instanceof zod.z.ZodDefault) {
845
+ unwrappedType = unwrappedType._def.innerType;
846
+ }
847
+ if (unwrappedType._def?.typeName === "ZodEffects") {
848
+ const effect = unwrappedType._def?.effect;
849
+ if (effect?.type === "refinement") {
850
+ const fileFieldNames = ["avatar", "image", "file", "attachment", "photo", "picture", "document", "upload"];
851
+ if (fileFieldNames.some((name) => fieldName.toLowerCase().includes(name))) {
852
+ return "file";
853
+ }
854
+ }
855
+ }
856
+ if (unwrappedType._def?.typeName === "ZodType") {
857
+ const checks = unwrappedType._def?.checks || [];
858
+ const innerType = unwrappedType._def?.innerType;
859
+ if (innerType?.name === "File" || checks.some((check) => check.kind === "instanceof")) {
860
+ return "file";
861
+ }
862
+ }
863
+ if (unwrappedType instanceof zod.z.ZodString) {
864
+ return mapZodStringType(unwrappedType);
865
+ }
866
+ if (unwrappedType instanceof zod.z.ZodNumber) {
867
+ return mapZodNumberType();
868
+ }
869
+ if (unwrappedType instanceof zod.z.ZodBoolean) {
870
+ return mapZodBooleanType();
871
+ }
872
+ if (unwrappedType instanceof zod.z.ZodEnum) {
873
+ return mapZodEnumType();
874
+ }
875
+ if (unwrappedType instanceof zod.z.ZodArray) {
876
+ return mapZodArrayType(unwrappedType);
877
+ }
878
+ if (unwrappedType instanceof zod.z.ZodDate) {
879
+ return mapZodDateType();
880
+ }
881
+ if (unwrappedType instanceof zod.z.ZodRecord || unwrappedType instanceof zod.z.ZodObject) {
882
+ return mapZodRecordType();
883
+ }
884
+ return "text";
885
+ }
886
+ function extractFieldOptions(zodType) {
887
+ const options = {};
888
+ let unwrappedType = zodType;
889
+ if (zodType instanceof zod.z.ZodOptional) {
890
+ unwrappedType = zodType._def.innerType;
891
+ }
892
+ if (unwrappedType instanceof zod.z.ZodNullable) {
893
+ unwrappedType = unwrappedType._def.innerType;
894
+ }
895
+ if (unwrappedType instanceof zod.z.ZodDefault) {
896
+ unwrappedType = unwrappedType._def.innerType;
897
+ }
898
+ const checks = unwrappedType._def?.checks || [];
899
+ if (unwrappedType instanceof zod.z.ZodString) {
900
+ for (const check of checks) {
901
+ if (check.kind === "min") {
902
+ options.min = check.value;
903
+ }
904
+ if (check.kind === "max") {
905
+ options.max = check.value;
906
+ }
907
+ if (check.kind === "regex") {
908
+ options.pattern = check.regex.source;
909
+ }
910
+ }
911
+ }
912
+ if (unwrappedType instanceof zod.z.ZodNumber) {
913
+ for (const check of checks) {
914
+ if (check.kind === "min") {
915
+ options.min = check.value;
916
+ }
917
+ if (check.kind === "max") {
918
+ options.max = check.value;
919
+ }
920
+ }
921
+ }
922
+ if (unwrappedType instanceof zod.z.ZodEnum) {
923
+ options.values = unwrappedType._def.values;
924
+ }
925
+ if (unwrappedType instanceof zod.z.ZodArray) {
926
+ const arrayChecks = unwrappedType._def?.checks || [];
927
+ for (const check of arrayChecks) {
928
+ if (check.kind === "min") {
929
+ options.minSelect = check.value;
930
+ }
931
+ if (check.kind === "max") {
932
+ options.maxSelect = check.value;
933
+ }
934
+ }
935
+ }
936
+ return options;
937
+ }
938
+ function isFieldRequired(zodType) {
939
+ if (zodType instanceof zod.z.ZodOptional) {
940
+ return false;
941
+ }
942
+ if (zodType instanceof zod.z.ZodDefault) {
943
+ return false;
944
+ }
945
+ if (zodType instanceof zod.z.ZodNullable) {
946
+ return false;
947
+ }
948
+ return true;
949
+ }
950
+
951
+ // src/migration/analyzer.ts
952
+ var DEFAULT_CONFIG = {
953
+ workspaceRoot: process.cwd(),
954
+ excludePatterns: [
955
+ "base.ts",
956
+ "index.ts",
957
+ "permissions.ts",
958
+ "permission-templates.ts",
959
+ "base.js",
960
+ "index.js",
961
+ "permissions.js",
962
+ "permission-templates.js"
963
+ ],
964
+ includeExtensions: [".ts", ".js"],
965
+ schemaPatterns: ["Schema", "InputSchema"],
966
+ useCompiledFiles: true
967
+ };
968
+ function mergeConfig(config) {
969
+ return {
970
+ ...DEFAULT_CONFIG,
971
+ ...config,
972
+ excludePatterns: config.excludePatterns || DEFAULT_CONFIG.excludePatterns,
973
+ includeExtensions: config.includeExtensions || DEFAULT_CONFIG.includeExtensions,
974
+ schemaPatterns: config.schemaPatterns || DEFAULT_CONFIG.schemaPatterns
975
+ };
976
+ }
977
+ function resolveSchemaDir(config) {
978
+ const workspaceRoot = config.workspaceRoot || process.cwd();
979
+ if (path__namespace.isAbsolute(config.schemaDir)) {
980
+ return config.schemaDir;
981
+ }
982
+ return path__namespace.join(workspaceRoot, config.schemaDir);
983
+ }
984
+ function discoverSchemaFiles(config) {
985
+ const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
986
+ const mergedConfig = mergeConfig(normalizedConfig);
987
+ const schemaDir = resolveSchemaDir(normalizedConfig);
988
+ try {
989
+ if (!fs__namespace.existsSync(schemaDir)) {
990
+ throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
991
+ }
992
+ const files = fs__namespace.readdirSync(schemaDir);
993
+ const schemaFiles = files.filter((file) => {
994
+ const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
995
+ if (!hasValidExtension) return false;
996
+ const isExcluded = mergedConfig.excludePatterns.some((pattern) => {
997
+ if (pattern.includes("*")) {
998
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
999
+ return regex.test(file);
1000
+ }
1001
+ return file === pattern;
1002
+ });
1003
+ if (isExcluded) return false;
1004
+ return true;
1005
+ });
1006
+ return schemaFiles.map((file) => {
1007
+ const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
1008
+ return path__namespace.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
1009
+ });
1010
+ } catch (error) {
1011
+ if (error instanceof FileSystemError) {
1012
+ throw error;
1013
+ }
1014
+ const fsError = error;
1015
+ if (fsError.code === "EACCES" || fsError.code === "EPERM") {
1016
+ throw new FileSystemError(
1017
+ `Permission denied reading schema directory: ${schemaDir}`,
1018
+ schemaDir,
1019
+ "read",
1020
+ fsError.code,
1021
+ error
1022
+ );
1023
+ }
1024
+ throw new FileSystemError(
1025
+ `Failed to read schema directory: ${schemaDir}`,
1026
+ schemaDir,
1027
+ "read",
1028
+ fsError.code,
1029
+ error
1030
+ );
1031
+ }
1032
+ }
1033
+ async function importSchemaModule(filePath, config) {
1034
+ try {
1035
+ let importPath = filePath;
1036
+ if (config?.pathTransformer) {
1037
+ importPath = config.pathTransformer(filePath);
1038
+ }
1039
+ if (!importPath.endsWith(".js")) {
1040
+ importPath = `${importPath}.js`;
1041
+ }
1042
+ const fileUrl = new URL(`file://${path__namespace.resolve(importPath)}`);
1043
+ const module = await import(fileUrl.href);
1044
+ return module;
1045
+ } catch (error) {
1046
+ throw new SchemaParsingError(
1047
+ `Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
1048
+ filePath,
1049
+ error
1050
+ );
1051
+ }
1052
+ }
1053
+ function getCollectionNameFromFile(filePath) {
1054
+ const filename = path__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
1055
+ return toCollectionName(filename);
1056
+ }
1057
+ function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
1058
+ const result = {};
1059
+ for (const [key, value] of Object.entries(module)) {
1060
+ if (value instanceof zod.z.ZodObject) {
1061
+ if (patterns.includes("InputSchema") && key.endsWith("InputSchema")) {
1062
+ result.inputSchema = value;
1063
+ } else if (patterns.includes("Schema") && key.endsWith("Schema") && !key.endsWith("InputSchema")) {
1064
+ result.schema = value;
1065
+ }
1066
+ }
1067
+ }
1068
+ return result;
1069
+ }
1070
+ function selectSchemaForCollection(schemas) {
1071
+ if (schemas.schema) {
1072
+ return schemas.schema;
1073
+ }
1074
+ if (schemas.inputSchema) {
1075
+ return schemas.inputSchema;
1076
+ }
1077
+ return null;
1078
+ }
1079
+ function extractFieldDefinitions(zodSchema, excludeFields) {
1080
+ const shape = zodSchema.shape;
1081
+ const fields = [];
1082
+ const baseFields = ["id", "collectionId", "collectionName", "created", "updated", "expand"];
1083
+ const defaultExcludeFields = ["thumbnailURL", "imageFiles"];
1084
+ const allExclusions = /* @__PURE__ */ new Set([...baseFields, ...defaultExcludeFields, ...excludeFields || []]);
1085
+ for (const [fieldName, zodType] of Object.entries(shape)) {
1086
+ if (!allExclusions.has(fieldName)) {
1087
+ fields.push({ name: fieldName, zodType });
1088
+ }
1089
+ }
1090
+ return fields;
1091
+ }
1092
+ function isAuthCollection(fields) {
1093
+ const fieldNames = fields.map((f) => f.name.toLowerCase());
1094
+ const hasEmail = fieldNames.includes("email");
1095
+ const hasPassword = fieldNames.includes("password");
1096
+ return hasEmail && hasPassword;
1097
+ }
1098
+ function buildFieldDefinition(fieldName, zodType) {
1099
+ const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
1100
+ const required = isFieldRequired(zodType);
1101
+ const options = extractFieldOptions(zodType);
1102
+ const fieldDef = {
1103
+ name: fieldName,
1104
+ type: fieldType,
1105
+ required,
1106
+ options
1107
+ };
1108
+ if (isRelationField(fieldName, zodType)) {
1109
+ fieldDef.type = "relation";
1110
+ const targetCollection = resolveTargetCollection(fieldName);
1111
+ const maxSelect = getMaxSelect(fieldName, zodType);
1112
+ const minSelect = getMinSelect(fieldName, zodType);
1113
+ fieldDef.relation = {
1114
+ collection: targetCollection,
1115
+ maxSelect,
1116
+ minSelect,
1117
+ cascadeDelete: false
1118
+ // Default to false, can be configured later
1119
+ };
1120
+ }
1121
+ return fieldDef;
1122
+ }
1123
+ function extractIndexes(schema) {
1124
+ const schemaDescription = schema.description;
1125
+ if (!schemaDescription) {
1126
+ return void 0;
1127
+ }
1128
+ try {
1129
+ const metadata = JSON.parse(schemaDescription);
1130
+ if (metadata.indexes && Array.isArray(metadata.indexes)) {
1131
+ return metadata.indexes;
1132
+ }
1133
+ } catch {
1134
+ }
1135
+ return void 0;
1136
+ }
1137
+ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
1138
+ const rawFields = extractFieldDefinitions(zodSchema);
1139
+ const collectionType = isAuthCollection(rawFields) ? "auth" : "base";
1140
+ const fields = rawFields.map(({ name, zodType }) => buildFieldDefinition(name, zodType));
1141
+ const indexes = extractIndexes(zodSchema) || [];
1142
+ const permissionAnalyzer = new PermissionAnalyzer();
1143
+ let permissions = void 0;
1144
+ const schemaDescription = zodSchema.description;
1145
+ const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
1146
+ if (extractedPermissions) {
1147
+ const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
1148
+ const validationResults = permissionAnalyzer.validatePermissions(
1149
+ collectionName,
1150
+ resolvedPermissions,
1151
+ fields,
1152
+ collectionType === "auth"
1153
+ );
1154
+ for (const [ruleType, result] of validationResults) {
1155
+ if (!result.valid) {
1156
+ console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
1157
+ result.errors.forEach((error) => console.error(` - ${error}`));
1158
+ }
1159
+ if (result.warnings.length > 0) {
1160
+ console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
1161
+ result.warnings.forEach((warning) => console.warn(` - ${warning}`));
1162
+ }
1163
+ }
1164
+ permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
1165
+ }
1166
+ const collectionSchema = {
1167
+ name: collectionName,
1168
+ type: collectionType,
1169
+ fields,
1170
+ indexes,
1171
+ rules: {
1172
+ listRule: null,
1173
+ viewRule: null,
1174
+ createRule: null,
1175
+ updateRule: null,
1176
+ deleteRule: null
1177
+ },
1178
+ permissions
1179
+ };
1180
+ return collectionSchema;
1181
+ }
1182
+ async function buildSchemaDefinition(config) {
1183
+ const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
1184
+ const mergedConfig = mergeConfig(normalizedConfig);
1185
+ const collections = /* @__PURE__ */ new Map();
1186
+ const schemaFiles = discoverSchemaFiles(normalizedConfig);
1187
+ if (schemaFiles.length === 0) {
1188
+ const schemaDir = resolveSchemaDir(normalizedConfig);
1189
+ throw new SchemaParsingError(
1190
+ `No schema files found in ${schemaDir}. Make sure you have schema files in the directory.`,
1191
+ schemaDir
1192
+ );
1193
+ }
1194
+ for (const filePath of schemaFiles) {
1195
+ try {
1196
+ let importPath = filePath;
1197
+ if (normalizedConfig.pathTransformer) {
1198
+ importPath = normalizedConfig.pathTransformer(filePath);
1199
+ } else if (mergedConfig.useCompiledFiles) {
1200
+ importPath = filePath.replace(/\/src\//, "/dist/");
1201
+ }
1202
+ const module = await importSchemaModule(importPath, normalizedConfig);
1203
+ const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
1204
+ const zodSchema = selectSchemaForCollection(schemas);
1205
+ if (!zodSchema) {
1206
+ console.warn(`No valid schema found in ${filePath}, skipping...`);
1207
+ continue;
1208
+ }
1209
+ const collectionName = getCollectionNameFromFile(filePath);
1210
+ const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
1211
+ collections.set(collectionName, collectionSchema);
1212
+ } catch (error) {
1213
+ if (error instanceof SchemaParsingError) {
1214
+ throw error;
1215
+ }
1216
+ throw new SchemaParsingError(
1217
+ `Error processing schema file: ${error instanceof Error ? error.message : String(error)}`,
1218
+ filePath,
1219
+ error
1220
+ );
1221
+ }
1222
+ }
1223
+ return { collections };
1224
+ }
1225
+ async function parseSchemaFiles(config) {
1226
+ return buildSchemaDefinition(config);
1227
+ }
1228
+ var SchemaAnalyzer = class {
1229
+ config;
1230
+ constructor(config) {
1231
+ this.config = mergeConfig(config);
1232
+ }
1233
+ /**
1234
+ * Discovers schema files in the configured directory
1235
+ */
1236
+ discoverSchemaFiles() {
1237
+ return discoverSchemaFiles(this.config);
1238
+ }
1239
+ /**
1240
+ * Parses all schema files and returns a SchemaDefinition
1241
+ */
1242
+ async parseSchemaFiles() {
1243
+ return buildSchemaDefinition(this.config);
1244
+ }
1245
+ /**
1246
+ * Converts a single Zod schema to a CollectionSchema
1247
+ */
1248
+ convertZodSchemaToCollectionSchema(name, schema) {
1249
+ return convertZodSchemaToCollectionSchema(name, schema);
1250
+ }
1251
+ };
1252
+
1253
+ exports.SchemaAnalyzer = SchemaAnalyzer;
1254
+ exports.buildFieldDefinition = buildFieldDefinition;
1255
+ exports.buildSchemaDefinition = buildSchemaDefinition;
1256
+ exports.convertZodSchemaToCollectionSchema = convertZodSchemaToCollectionSchema;
1257
+ exports.discoverSchemaFiles = discoverSchemaFiles;
1258
+ exports.extractFieldDefinitions = extractFieldDefinitions;
1259
+ exports.extractIndexes = extractIndexes;
1260
+ exports.extractSchemaDefinitions = extractSchemaDefinitions;
1261
+ exports.getCollectionNameFromFile = getCollectionNameFromFile;
1262
+ exports.importSchemaModule = importSchemaModule;
1263
+ exports.isAuthCollection = isAuthCollection;
1264
+ exports.parseSchemaFiles = parseSchemaFiles;
1265
+ exports.selectSchemaForCollection = selectSchemaForCollection;
1266
+ //# sourceMappingURL=analyzer.cjs.map
1267
+ //# sourceMappingURL=analyzer.cjs.map