pocketbase-zod-schema 0.2.3 → 0.2.4

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.
package/dist/index.js CHANGED
@@ -15,440 +15,6 @@ var StatusEnum = z.enum([
15
15
  "fail"
16
16
  // Failed project at any stage
17
17
  ]);
18
- var baseSchema = {
19
- id: z.string().describe("unique id"),
20
- collectionId: z.string().describe("collection id"),
21
- collectionName: z.string().describe("collection name"),
22
- expand: z.record(z.any()).describe("expandable fields"),
23
- created: z.string().describe("creation timestamp"),
24
- updated: z.string().describe("last update timestamp")
25
- };
26
- var baseSchemaWithTimestamps = {
27
- ...baseSchema,
28
- created: z.string().describe("creation timestamp"),
29
- updated: z.string().describe("last update timestamp")
30
- };
31
- var baseImageFileSchema = {
32
- ...baseSchema,
33
- thumbnailURL: z.string().optional(),
34
- imageFiles: z.array(z.string())
35
- };
36
- var inputImageFileSchema = {
37
- imageFiles: z.array(z.instanceof(File))
38
- };
39
- var omitImageFilesSchema = {
40
- imageFiles: true
41
- };
42
- function textField(options) {
43
- let schema = z.string();
44
- if (options?.min !== void 0) schema = schema.min(options.min);
45
- if (options?.max !== void 0) schema = schema.max(options.max);
46
- if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
47
- return schema;
48
- }
49
- function emailField() {
50
- return z.string().email();
51
- }
52
- function urlField() {
53
- return z.string().url();
54
- }
55
- function numberField(options) {
56
- let schema = z.number();
57
- if (options?.min !== void 0) schema = schema.min(options.min);
58
- if (options?.max !== void 0) schema = schema.max(options.max);
59
- return schema;
60
- }
61
- function boolField() {
62
- return z.boolean();
63
- }
64
- function dateField() {
65
- return z.date();
66
- }
67
- function selectField(values) {
68
- return z.enum(values);
69
- }
70
- function jsonField(schema) {
71
- return schema ?? z.record(z.any());
72
- }
73
- function fileField() {
74
- return z.instanceof(File);
75
- }
76
- function filesField(options) {
77
- let schema = z.array(z.instanceof(File));
78
- if (options?.min !== void 0) schema = schema.min(options.min);
79
- if (options?.max !== void 0) schema = schema.max(options.max);
80
- return schema;
81
- }
82
- var RELATION_METADATA_KEY = "__pocketbase_relation__";
83
- function RelationField(config) {
84
- const metadata = {
85
- [RELATION_METADATA_KEY]: {
86
- type: "single",
87
- collection: config.collection,
88
- cascadeDelete: config.cascadeDelete ?? false,
89
- maxSelect: 1,
90
- minSelect: 0
91
- }
92
- };
93
- return z.string().describe(JSON.stringify(metadata));
94
- }
95
- function RelationsField(config) {
96
- const metadata = {
97
- [RELATION_METADATA_KEY]: {
98
- type: "multiple",
99
- collection: config.collection,
100
- cascadeDelete: config.cascadeDelete ?? false,
101
- maxSelect: config.maxSelect ?? 999,
102
- minSelect: config.minSelect ?? 0
103
- }
104
- };
105
- let schema = z.array(z.string());
106
- if (config.minSelect !== void 0) {
107
- schema = schema.min(config.minSelect);
108
- }
109
- if (config.maxSelect !== void 0) {
110
- schema = schema.max(config.maxSelect);
111
- }
112
- return schema.describe(JSON.stringify(metadata));
113
- }
114
- function extractRelationMetadata(description) {
115
- if (!description) return null;
116
- try {
117
- const parsed = JSON.parse(description);
118
- if (parsed[RELATION_METADATA_KEY]) {
119
- return parsed[RELATION_METADATA_KEY];
120
- }
121
- } catch {
122
- }
123
- return null;
124
- }
125
- function editorField() {
126
- return z.string();
127
- }
128
- function geoPointField() {
129
- return z.object({
130
- lon: z.number(),
131
- lat: z.number()
132
- });
133
- }
134
- function withPermissions(schema, config) {
135
- const metadata = {
136
- permissions: config
137
- };
138
- return schema.describe(JSON.stringify(metadata));
139
- }
140
- function withIndexes(schema, indexes) {
141
- let existingMetadata = {};
142
- if (schema.description) {
143
- try {
144
- existingMetadata = JSON.parse(schema.description);
145
- } catch {
146
- }
147
- }
148
- const metadata = {
149
- ...existingMetadata,
150
- indexes
151
- };
152
- return schema.describe(JSON.stringify(metadata));
153
- }
154
- function defineCollection(config) {
155
- const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
156
- const metadata = {
157
- collectionName
158
- };
159
- if (type) {
160
- metadata.type = type;
161
- }
162
- if (permissions) {
163
- metadata.permissions = permissions;
164
- }
165
- if (indexes) {
166
- metadata.indexes = indexes;
167
- }
168
- if (Object.keys(futureOptions).length > 0) {
169
- Object.assign(metadata, futureOptions);
170
- }
171
- return schema.describe(JSON.stringify(metadata));
172
- }
173
-
174
- // src/utils/permission-templates.ts
175
- var PermissionTemplates = {
176
- /**
177
- * Public access - anyone can perform all operations
178
- */
179
- public: () => ({
180
- listRule: "",
181
- viewRule: "",
182
- createRule: "",
183
- updateRule: "",
184
- deleteRule: ""
185
- }),
186
- /**
187
- * Authenticated users only - requires valid authentication for all operations
188
- */
189
- authenticated: () => ({
190
- listRule: '@request.auth.id != ""',
191
- viewRule: '@request.auth.id != ""',
192
- createRule: '@request.auth.id != ""',
193
- updateRule: '@request.auth.id != ""',
194
- deleteRule: '@request.auth.id != ""'
195
- }),
196
- /**
197
- * Owner-only access - users can only manage their own records
198
- * @param ownerField - Name of the relation field pointing to user (default: 'User')
199
- */
200
- ownerOnly: (ownerField = "User") => ({
201
- listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
202
- viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
203
- createRule: '@request.auth.id != ""',
204
- updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
205
- deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
206
- }),
207
- /**
208
- * Admin/superuser only access
209
- * Assumes a 'role' field exists with 'admin' value
210
- * @param roleField - Name of the role field (default: 'role')
211
- */
212
- adminOnly: (roleField = "role") => ({
213
- listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
214
- viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
215
- createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
216
- updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
217
- deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
218
- }),
219
- /**
220
- * Public read, authenticated write
221
- * Anyone can list/view, but only authenticated users can create/update/delete
222
- */
223
- readPublic: () => ({
224
- listRule: "",
225
- viewRule: "",
226
- createRule: '@request.auth.id != ""',
227
- updateRule: '@request.auth.id != ""',
228
- deleteRule: '@request.auth.id != ""'
229
- }),
230
- /**
231
- * Locked access - only superusers can perform operations
232
- * All rules are set to null (locked)
233
- */
234
- locked: () => ({
235
- listRule: null,
236
- viewRule: null,
237
- createRule: null,
238
- updateRule: null,
239
- deleteRule: null
240
- }),
241
- /**
242
- * Read-only authenticated - authenticated users can read, no write access
243
- */
244
- readOnlyAuthenticated: () => ({
245
- listRule: '@request.auth.id != ""',
246
- viewRule: '@request.auth.id != ""',
247
- createRule: null,
248
- updateRule: null,
249
- deleteRule: null
250
- })
251
- };
252
- function resolveTemplate(config) {
253
- let baseRules;
254
- switch (config.template) {
255
- case "public":
256
- baseRules = PermissionTemplates.public();
257
- break;
258
- case "authenticated":
259
- baseRules = PermissionTemplates.authenticated();
260
- break;
261
- case "owner-only":
262
- baseRules = PermissionTemplates.ownerOnly(config.ownerField);
263
- break;
264
- case "admin-only":
265
- baseRules = PermissionTemplates.adminOnly(config.roleField);
266
- break;
267
- case "read-public":
268
- baseRules = PermissionTemplates.readPublic();
269
- break;
270
- case "custom":
271
- baseRules = {};
272
- break;
273
- default: {
274
- const _exhaustive = config.template;
275
- throw new Error(`Unknown template type: ${_exhaustive}`);
276
- }
277
- }
278
- return {
279
- ...baseRules,
280
- ...config.customRules
281
- };
282
- }
283
- function isTemplateConfig(config) {
284
- return "template" in config;
285
- }
286
- function isPermissionSchema(config) {
287
- return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
288
- }
289
- function validatePermissionConfig(config, isAuthCollection2 = false) {
290
- const result = {
291
- valid: true,
292
- errors: [],
293
- warnings: []
294
- };
295
- let permissions;
296
- if (isTemplateConfig(config)) {
297
- if (config.template === "owner-only" && !config.ownerField) {
298
- result.warnings.push("owner-only template without ownerField specified - using default 'User'");
299
- }
300
- if (config.template === "admin-only" && !config.roleField) {
301
- result.warnings.push("admin-only template without roleField specified - using default 'role'");
302
- }
303
- permissions = resolveTemplate(config);
304
- } else {
305
- permissions = config;
306
- }
307
- if (permissions.manageRule !== void 0 && !isAuthCollection2) {
308
- result.errors.push("manageRule is only valid for auth collections");
309
- result.valid = false;
310
- }
311
- const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
312
- if (isAuthCollection2) {
313
- ruleTypes.push("manageRule");
314
- }
315
- for (const ruleType of ruleTypes) {
316
- const rule = permissions[ruleType];
317
- if (rule !== void 0 && rule !== null && rule !== "") {
318
- const ruleValidation = validateRuleExpression(rule);
319
- if (!ruleValidation.valid) {
320
- result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
321
- result.valid = false;
322
- }
323
- result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
324
- }
325
- }
326
- return result;
327
- }
328
- function validateRuleExpression(expression) {
329
- const result = {
330
- valid: true,
331
- errors: [],
332
- warnings: []
333
- };
334
- if (expression === null || expression === "") {
335
- return result;
336
- }
337
- let parenCount = 0;
338
- for (const char of expression) {
339
- if (char === "(") parenCount++;
340
- if (char === ")") parenCount--;
341
- if (parenCount < 0) {
342
- result.errors.push("Unbalanced parentheses");
343
- result.valid = false;
344
- return result;
345
- }
346
- }
347
- if (parenCount !== 0) {
348
- result.errors.push("Unbalanced parentheses");
349
- result.valid = false;
350
- }
351
- if (expression.includes("==")) {
352
- result.warnings.push("Use '=' instead of '==' for equality comparison");
353
- }
354
- const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
355
- for (const ref of requestRefs) {
356
- const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
357
- if (!isValid) {
358
- result.errors.push(`Invalid @request reference: '${ref}'`);
359
- result.valid = false;
360
- }
361
- }
362
- return result;
363
- }
364
- function createPermissions(permissions) {
365
- return {
366
- listRule: permissions.listRule ?? null,
367
- viewRule: permissions.viewRule ?? null,
368
- createRule: permissions.createRule ?? null,
369
- updateRule: permissions.updateRule ?? null,
370
- deleteRule: permissions.deleteRule ?? null,
371
- manageRule: permissions.manageRule ?? null
372
- };
373
- }
374
- function mergePermissions(...schemas) {
375
- const merged = {
376
- listRule: null,
377
- viewRule: null,
378
- createRule: null,
379
- updateRule: null,
380
- deleteRule: null,
381
- manageRule: null
382
- };
383
- for (const schema of schemas) {
384
- if (schema.listRule !== void 0) merged.listRule = schema.listRule;
385
- if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
386
- if (schema.createRule !== void 0) merged.createRule = schema.createRule;
387
- if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
388
- if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
389
- if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
390
- }
391
- return merged;
392
- }
393
- var ProjectInputSchema = z.object({
394
- // Required fields
395
- title: z.string(),
396
- content: z.string(),
397
- status: StatusEnum,
398
- summary: z.string().optional(),
399
- OwnerUser: RelationField({ collection: "Users" }),
400
- SubscriberUsers: RelationsField({ collection: "Users" })
401
- }).extend(inputImageFileSchema);
402
- var ProjectSchema = ProjectInputSchema.omit(omitImageFilesSchema).extend(baseImageFileSchema);
403
- var ProjectCollection = defineCollection({
404
- collectionName: "Projects",
405
- schema: ProjectSchema,
406
- permissions: {
407
- template: "owner-only",
408
- ownerField: "OwnerUser",
409
- customRules: {
410
- listRule: '@request.auth.id != ""',
411
- viewRule: '@request.auth.id != "" && (OwnerUser = @request.auth.id || SubscriberUsers ?= @request.auth.id)'
412
- }
413
- }
414
- });
415
- var UserInputSchema = z.object({
416
- name: z.string().optional(),
417
- email: z.string().email(),
418
- password: z.string().min(8, "Password must be at least 8 characters"),
419
- passwordConfirm: z.string(),
420
- avatar: z.instanceof(File).optional()
421
- });
422
- var UserCollectionSchema = z.object({
423
- name: z.string().optional(),
424
- email: z.string().email(),
425
- password: z.string().min(8, "Password must be at least 8 characters"),
426
- avatar: z.instanceof(File).optional()
427
- });
428
- var UserSchema = UserCollectionSchema.extend(baseSchema);
429
- var UserCollection = defineCollection({
430
- collectionName: "users",
431
- type: "auth",
432
- schema: UserSchema,
433
- permissions: {
434
- // Users can list their own profile
435
- listRule: "id = @request.auth.id",
436
- // Users can view their own profile
437
- viewRule: "id = @request.auth.id",
438
- // Anyone can create an account (sign up)
439
- createRule: "",
440
- // Users can only update their own profile
441
- updateRule: "id = @request.auth.id",
442
- // Users can only delete their own account
443
- deleteRule: "id = @request.auth.id"
444
- // manageRule is null in PocketBase default (not set)
445
- },
446
- indexes: [
447
- // PocketBase's default indexes for auth collections
448
- "CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
449
- "CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
450
- ]
451
- });
452
18
  var BaseMutator = class {
453
19
  pb;
454
20
  // Define a default property that subclasses will override
@@ -721,78 +287,436 @@ var BaseMutator = class {
721
287
  throw error;
722
288
  }
723
289
  /**
724
- * Check if an error is a "not found" error
290
+ * Check if an error is a "not found" error
291
+ */
292
+ isNotFoundError(error) {
293
+ return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
294
+ }
295
+ /**
296
+ * Standard error handling wrapper (legacy method, consider using handleError instead)
297
+ */
298
+ errorWrapper(error) {
299
+ console.error(`Error in ${this.constructor.name}:`, error);
300
+ throw error;
301
+ }
302
+ /**
303
+ * Subscribe to changes on a specific record
304
+ * @param id The ID of the record to subscribe to
305
+ * @param callback Function to call when changes occur
306
+ * @param expand Optional expand parameters
307
+ * @returns Promise that resolves to an unsubscribe function
308
+ */
309
+ async subscribeToRecord(id, callback, expand) {
310
+ const finalExpand = this.prepareExpand(expand);
311
+ const options = finalExpand ? { expand: finalExpand } : {};
312
+ return this.getCollection().subscribe(id, callback, options);
313
+ }
314
+ /**
315
+ * Subscribe to changes on the entire collection
316
+ * @param callback Function to call when changes occur
317
+ * @param expand Optional expand parameters
318
+ * @returns Promise that resolves to an unsubscribe function
319
+ */
320
+ async subscribeToCollection(callback, expand) {
321
+ const finalExpand = this.prepareExpand(expand);
322
+ const options = finalExpand ? { expand: finalExpand } : {};
323
+ return this.getCollection().subscribe("*", callback, options);
324
+ }
325
+ /**
326
+ * Unsubscribe from a specific record's changes
327
+ * @param id The ID of the record to unsubscribe from
328
+ */
329
+ unsubscribeFromRecord(id) {
330
+ this.getCollection().unsubscribe(id);
331
+ }
332
+ /**
333
+ * Unsubscribe from collection-wide changes
334
+ */
335
+ unsubscribeFromCollection() {
336
+ this.getCollection().unsubscribe("*");
337
+ }
338
+ /**
339
+ * Unsubscribe from all subscriptions in this collection
340
+ */
341
+ unsubscribeAll() {
342
+ this.getCollection().unsubscribe();
343
+ }
344
+ };
345
+ var baseSchema = {
346
+ id: z.string().describe("unique id"),
347
+ collectionId: z.string().describe("collection id"),
348
+ collectionName: z.string().describe("collection name"),
349
+ expand: z.record(z.any()).describe("expandable fields"),
350
+ created: z.string().describe("creation timestamp"),
351
+ updated: z.string().describe("last update timestamp")
352
+ };
353
+ var baseSchemaWithTimestamps = {
354
+ ...baseSchema,
355
+ created: z.string().describe("creation timestamp"),
356
+ updated: z.string().describe("last update timestamp")
357
+ };
358
+ var baseImageFileSchema = {
359
+ ...baseSchema,
360
+ thumbnailURL: z.string().optional(),
361
+ imageFiles: z.array(z.string())
362
+ };
363
+ var inputImageFileSchema = {
364
+ imageFiles: z.array(z.instanceof(File))
365
+ };
366
+ var omitImageFilesSchema = {
367
+ imageFiles: true
368
+ };
369
+ function textField(options) {
370
+ let schema = z.string();
371
+ if (options?.min !== void 0) schema = schema.min(options.min);
372
+ if (options?.max !== void 0) schema = schema.max(options.max);
373
+ if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
374
+ return schema;
375
+ }
376
+ function emailField() {
377
+ return z.string().email();
378
+ }
379
+ function urlField() {
380
+ return z.string().url();
381
+ }
382
+ function numberField(options) {
383
+ let schema = z.number();
384
+ if (options?.min !== void 0) schema = schema.min(options.min);
385
+ if (options?.max !== void 0) schema = schema.max(options.max);
386
+ return schema;
387
+ }
388
+ function boolField() {
389
+ return z.boolean();
390
+ }
391
+ function dateField() {
392
+ return z.date();
393
+ }
394
+ function selectField(values) {
395
+ return z.enum(values);
396
+ }
397
+ function jsonField(schema) {
398
+ return schema ?? z.record(z.any());
399
+ }
400
+ function fileField() {
401
+ return z.instanceof(File);
402
+ }
403
+ function filesField(options) {
404
+ let schema = z.array(z.instanceof(File));
405
+ if (options?.min !== void 0) schema = schema.min(options.min);
406
+ if (options?.max !== void 0) schema = schema.max(options.max);
407
+ return schema;
408
+ }
409
+ var RELATION_METADATA_KEY = "__pocketbase_relation__";
410
+ function RelationField(config) {
411
+ const metadata = {
412
+ [RELATION_METADATA_KEY]: {
413
+ type: "single",
414
+ collection: config.collection,
415
+ cascadeDelete: config.cascadeDelete ?? false,
416
+ maxSelect: 1,
417
+ minSelect: 0
418
+ }
419
+ };
420
+ return z.string().describe(JSON.stringify(metadata));
421
+ }
422
+ function RelationsField(config) {
423
+ const metadata = {
424
+ [RELATION_METADATA_KEY]: {
425
+ type: "multiple",
426
+ collection: config.collection,
427
+ cascadeDelete: config.cascadeDelete ?? false,
428
+ maxSelect: config.maxSelect ?? 999,
429
+ minSelect: config.minSelect ?? 0
430
+ }
431
+ };
432
+ let schema = z.array(z.string());
433
+ if (config.minSelect !== void 0) {
434
+ schema = schema.min(config.minSelect);
435
+ }
436
+ if (config.maxSelect !== void 0) {
437
+ schema = schema.max(config.maxSelect);
438
+ }
439
+ return schema.describe(JSON.stringify(metadata));
440
+ }
441
+ function extractRelationMetadata(description) {
442
+ if (!description) return null;
443
+ try {
444
+ const parsed = JSON.parse(description);
445
+ if (parsed[RELATION_METADATA_KEY]) {
446
+ return parsed[RELATION_METADATA_KEY];
447
+ }
448
+ } catch {
449
+ }
450
+ return null;
451
+ }
452
+ function editorField() {
453
+ return z.string();
454
+ }
455
+ function geoPointField() {
456
+ return z.object({
457
+ lon: z.number(),
458
+ lat: z.number()
459
+ });
460
+ }
461
+ function withPermissions(schema, config) {
462
+ const metadata = {
463
+ permissions: config
464
+ };
465
+ return schema.describe(JSON.stringify(metadata));
466
+ }
467
+ function withIndexes(schema, indexes) {
468
+ let existingMetadata = {};
469
+ if (schema.description) {
470
+ try {
471
+ existingMetadata = JSON.parse(schema.description);
472
+ } catch {
473
+ }
474
+ }
475
+ const metadata = {
476
+ ...existingMetadata,
477
+ indexes
478
+ };
479
+ return schema.describe(JSON.stringify(metadata));
480
+ }
481
+ function defineCollection(config) {
482
+ const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
483
+ const metadata = {
484
+ collectionName
485
+ };
486
+ if (type) {
487
+ metadata.type = type;
488
+ }
489
+ if (permissions) {
490
+ metadata.permissions = permissions;
491
+ }
492
+ if (indexes) {
493
+ metadata.indexes = indexes;
494
+ }
495
+ if (Object.keys(futureOptions).length > 0) {
496
+ Object.assign(metadata, futureOptions);
497
+ }
498
+ return schema.describe(JSON.stringify(metadata));
499
+ }
500
+
501
+ // src/utils/permission-templates.ts
502
+ var PermissionTemplates = {
503
+ /**
504
+ * Public access - anyone can perform all operations
725
505
  */
726
- isNotFoundError(error) {
727
- return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
728
- }
506
+ public: () => ({
507
+ listRule: "",
508
+ viewRule: "",
509
+ createRule: "",
510
+ updateRule: "",
511
+ deleteRule: ""
512
+ }),
729
513
  /**
730
- * Standard error handling wrapper (legacy method, consider using handleError instead)
514
+ * Authenticated users only - requires valid authentication for all operations
731
515
  */
732
- errorWrapper(error) {
733
- console.error(`Error in ${this.constructor.name}:`, error);
734
- throw error;
735
- }
516
+ authenticated: () => ({
517
+ listRule: '@request.auth.id != ""',
518
+ viewRule: '@request.auth.id != ""',
519
+ createRule: '@request.auth.id != ""',
520
+ updateRule: '@request.auth.id != ""',
521
+ deleteRule: '@request.auth.id != ""'
522
+ }),
736
523
  /**
737
- * Subscribe to changes on a specific record
738
- * @param id The ID of the record to subscribe to
739
- * @param callback Function to call when changes occur
740
- * @param expand Optional expand parameters
741
- * @returns Promise that resolves to an unsubscribe function
524
+ * Owner-only access - users can only manage their own records
525
+ * @param ownerField - Name of the relation field pointing to user (default: 'User')
742
526
  */
743
- async subscribeToRecord(id, callback, expand) {
744
- const finalExpand = this.prepareExpand(expand);
745
- const options = finalExpand ? { expand: finalExpand } : {};
746
- return this.getCollection().subscribe(id, callback, options);
747
- }
527
+ ownerOnly: (ownerField = "User") => ({
528
+ listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
529
+ viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
530
+ createRule: '@request.auth.id != ""',
531
+ updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
532
+ deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
533
+ }),
748
534
  /**
749
- * Subscribe to changes on the entire collection
750
- * @param callback Function to call when changes occur
751
- * @param expand Optional expand parameters
752
- * @returns Promise that resolves to an unsubscribe function
535
+ * Admin/superuser only access
536
+ * Assumes a 'role' field exists with 'admin' value
537
+ * @param roleField - Name of the role field (default: 'role')
753
538
  */
754
- async subscribeToCollection(callback, expand) {
755
- const finalExpand = this.prepareExpand(expand);
756
- const options = finalExpand ? { expand: finalExpand } : {};
757
- return this.getCollection().subscribe("*", callback, options);
758
- }
539
+ adminOnly: (roleField = "role") => ({
540
+ listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
541
+ viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
542
+ createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
543
+ updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
544
+ deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
545
+ }),
759
546
  /**
760
- * Unsubscribe from a specific record's changes
761
- * @param id The ID of the record to unsubscribe from
547
+ * Public read, authenticated write
548
+ * Anyone can list/view, but only authenticated users can create/update/delete
762
549
  */
763
- unsubscribeFromRecord(id) {
764
- this.getCollection().unsubscribe(id);
765
- }
550
+ readPublic: () => ({
551
+ listRule: "",
552
+ viewRule: "",
553
+ createRule: '@request.auth.id != ""',
554
+ updateRule: '@request.auth.id != ""',
555
+ deleteRule: '@request.auth.id != ""'
556
+ }),
766
557
  /**
767
- * Unsubscribe from collection-wide changes
558
+ * Locked access - only superusers can perform operations
559
+ * All rules are set to null (locked)
768
560
  */
769
- unsubscribeFromCollection() {
770
- this.getCollection().unsubscribe("*");
771
- }
561
+ locked: () => ({
562
+ listRule: null,
563
+ viewRule: null,
564
+ createRule: null,
565
+ updateRule: null,
566
+ deleteRule: null
567
+ }),
772
568
  /**
773
- * Unsubscribe from all subscriptions in this collection
569
+ * Read-only authenticated - authenticated users can read, no write access
774
570
  */
775
- unsubscribeAll() {
776
- this.getCollection().unsubscribe();
777
- }
571
+ readOnlyAuthenticated: () => ({
572
+ listRule: '@request.auth.id != ""',
573
+ viewRule: '@request.auth.id != ""',
574
+ createRule: null,
575
+ updateRule: null,
576
+ deleteRule: null
577
+ })
778
578
  };
779
-
780
- // src/mutator/userMutator.ts
781
- var UserMutator = class extends BaseMutator {
782
- setDefaults() {
783
- return {
784
- expand: [],
785
- filter: [],
786
- sort: ["-updated"]
787
- };
579
+ function resolveTemplate(config) {
580
+ let baseRules;
581
+ switch (config.template) {
582
+ case "public":
583
+ baseRules = PermissionTemplates.public();
584
+ break;
585
+ case "authenticated":
586
+ baseRules = PermissionTemplates.authenticated();
587
+ break;
588
+ case "owner-only":
589
+ baseRules = PermissionTemplates.ownerOnly(config.ownerField);
590
+ break;
591
+ case "admin-only":
592
+ baseRules = PermissionTemplates.adminOnly(config.roleField);
593
+ break;
594
+ case "read-public":
595
+ baseRules = PermissionTemplates.readPublic();
596
+ break;
597
+ case "custom":
598
+ baseRules = {};
599
+ break;
600
+ default: {
601
+ const _exhaustive = config.template;
602
+ throw new Error(`Unknown template type: ${_exhaustive}`);
603
+ }
604
+ }
605
+ return {
606
+ ...baseRules,
607
+ ...config.customRules
608
+ };
609
+ }
610
+ function isTemplateConfig(config) {
611
+ return "template" in config;
612
+ }
613
+ function isPermissionSchema(config) {
614
+ return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
615
+ }
616
+ function validatePermissionConfig(config, isAuthCollection2 = false) {
617
+ const result = {
618
+ valid: true,
619
+ errors: [],
620
+ warnings: []
621
+ };
622
+ let permissions;
623
+ if (isTemplateConfig(config)) {
624
+ if (config.template === "owner-only" && !config.ownerField) {
625
+ result.warnings.push("owner-only template without ownerField specified - using default 'User'");
626
+ }
627
+ if (config.template === "admin-only" && !config.roleField) {
628
+ result.warnings.push("admin-only template without roleField specified - using default 'role'");
629
+ }
630
+ permissions = resolveTemplate(config);
631
+ } else {
632
+ permissions = config;
633
+ }
634
+ if (permissions.manageRule !== void 0 && !isAuthCollection2) {
635
+ result.errors.push("manageRule is only valid for auth collections");
636
+ result.valid = false;
788
637
  }
789
- getCollection() {
790
- return this.pb.collection("Users");
638
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
639
+ if (isAuthCollection2) {
640
+ ruleTypes.push("manageRule");
791
641
  }
792
- async validateInput(input) {
793
- return UserInputSchema.parse(input);
642
+ for (const ruleType of ruleTypes) {
643
+ const rule = permissions[ruleType];
644
+ if (rule !== void 0 && rule !== null && rule !== "") {
645
+ const ruleValidation = validateRuleExpression(rule);
646
+ if (!ruleValidation.valid) {
647
+ result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
648
+ result.valid = false;
649
+ }
650
+ result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
651
+ }
794
652
  }
795
- };
653
+ return result;
654
+ }
655
+ function validateRuleExpression(expression) {
656
+ const result = {
657
+ valid: true,
658
+ errors: [],
659
+ warnings: []
660
+ };
661
+ if (expression === null || expression === "") {
662
+ return result;
663
+ }
664
+ let parenCount = 0;
665
+ for (const char of expression) {
666
+ if (char === "(") parenCount++;
667
+ if (char === ")") parenCount--;
668
+ if (parenCount < 0) {
669
+ result.errors.push("Unbalanced parentheses");
670
+ result.valid = false;
671
+ return result;
672
+ }
673
+ }
674
+ if (parenCount !== 0) {
675
+ result.errors.push("Unbalanced parentheses");
676
+ result.valid = false;
677
+ }
678
+ if (expression.includes("==")) {
679
+ result.warnings.push("Use '=' instead of '==' for equality comparison");
680
+ }
681
+ const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
682
+ for (const ref of requestRefs) {
683
+ const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
684
+ if (!isValid) {
685
+ result.errors.push(`Invalid @request reference: '${ref}'`);
686
+ result.valid = false;
687
+ }
688
+ }
689
+ return result;
690
+ }
691
+ function createPermissions(permissions) {
692
+ return {
693
+ listRule: permissions.listRule ?? null,
694
+ viewRule: permissions.viewRule ?? null,
695
+ createRule: permissions.createRule ?? null,
696
+ updateRule: permissions.updateRule ?? null,
697
+ deleteRule: permissions.deleteRule ?? null,
698
+ manageRule: permissions.manageRule ?? null
699
+ };
700
+ }
701
+ function mergePermissions(...schemas) {
702
+ const merged = {
703
+ listRule: null,
704
+ viewRule: null,
705
+ createRule: null,
706
+ updateRule: null,
707
+ deleteRule: null,
708
+ manageRule: null
709
+ };
710
+ for (const schema of schemas) {
711
+ if (schema.listRule !== void 0) merged.listRule = schema.listRule;
712
+ if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
713
+ if (schema.createRule !== void 0) merged.createRule = schema.createRule;
714
+ if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
715
+ if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
716
+ if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
717
+ }
718
+ return merged;
719
+ }
796
720
 
797
721
  // src/migration/errors.ts
798
722
  var MigrationError = class _MigrationError extends Error {
@@ -5319,6 +5243,6 @@ async function executeStatus(options) {
5319
5243
  }
5320
5244
  }
5321
5245
 
5322
- export { CLIUsageError, ConfigurationError, DiffEngine, FIELD_TYPE_INFO, FileSystemError, MigrationError, MigrationGenerationError, MigrationGenerator, POCKETBASE_FIELD_TYPES, PermissionTemplates, ProjectCollection, ProjectInputSchema, ProjectSchema, RelationField, RelationsField, SchemaAnalyzer, SchemaParsingError, SnapshotError, SnapshotManager, StatusEnum, UserCollection, UserCollectionSchema, UserInputSchema, UserMutator, UserSchema, aggregateChanges, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, boolField, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, createPermissions, dateField, defineCollection, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, editorField, emailField, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldOptions, extractIndexes, extractRelationMetadata, extractSchemaDefinitions, fileField, filesField, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, formatChangeSummary, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, executeGenerate as generateMigration, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, geoPointField, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, executeStatus as getMigrationStatus, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, inputImageFileSchema, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isPermissionSchema, isRelationField, isSingleRelationField, isSystemCollection, isTemplateConfig, jsonField, loadBaseMigration, loadConfig, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, logError, logInfo, logSection, logSuccess, logWarning, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergePermissions, mergeSnapshots, numberField, omitImageFilesSchema, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, resolveTemplate, saveSnapshot, selectField, selectSchemaForCollection, singularize, snapshotExists, textField, toCollectionName, unwrapZodType, urlField, validatePermissionConfig, validateRuleExpression, validateSnapshot, withIndexes, withPermissions, withProgress, writeMigrationFile };
5246
+ export { BaseMutator, CLIUsageError, ConfigurationError, DiffEngine, FIELD_TYPE_INFO, FileSystemError, MigrationError, MigrationGenerationError, MigrationGenerator, POCKETBASE_FIELD_TYPES, PermissionTemplates, RelationField, RelationsField, SchemaAnalyzer, SchemaParsingError, SnapshotError, SnapshotManager, StatusEnum, aggregateChanges, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, boolField, buildFieldDefinition, buildSchemaDefinition, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, convertPocketBaseMigration, convertZodSchemaToCollectionSchema, createMigrationFileStructure, createPermissions, dateField, defineCollection, detectDestructiveChanges, detectFieldChanges, discoverSchemaFiles, editorField, emailField, extractComprehensiveFieldOptions, extractFieldDefinitions, extractFieldOptions, extractIndexes, extractRelationMetadata, extractSchemaDefinitions, fileField, filesField, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, formatChangeSummary, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, executeGenerate as generateMigration, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, geoPointField, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, executeStatus as getMigrationStatus, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, inputImageFileSchema, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isPermissionSchema, isRelationField, isSingleRelationField, isSystemCollection, isTemplateConfig, jsonField, loadBaseMigration, loadConfig, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, logError, logInfo, logSection, logSuccess, logWarning, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergePermissions, mergeSnapshots, numberField, omitImageFilesSchema, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, resolveTemplate, saveSnapshot, selectField, selectSchemaForCollection, singularize, snapshotExists, textField, toCollectionName, unwrapZodType, urlField, validatePermissionConfig, validateRuleExpression, validateSnapshot, withIndexes, withPermissions, withProgress, writeMigrationFile };
5323
5247
  //# sourceMappingURL=index.js.map
5324
5248
  //# sourceMappingURL=index.js.map