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