pocketbase-zod-schema 0.2.2 → 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.
Files changed (48) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/index.cjs +52 -1
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.js +52 -1
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/migrate.cjs +52 -1
  7. package/dist/cli/migrate.cjs.map +1 -1
  8. package/dist/cli/migrate.js +52 -1
  9. package/dist/cli/migrate.js.map +1 -1
  10. package/dist/index.cjs +658 -690
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +1 -3
  13. package/dist/index.d.ts +1 -3
  14. package/dist/index.js +658 -683
  15. package/dist/index.js.map +1 -1
  16. package/dist/migration/generator.cjs +52 -1
  17. package/dist/migration/generator.cjs.map +1 -1
  18. package/dist/migration/generator.d.cts +0 -7
  19. package/dist/migration/generator.d.ts +0 -7
  20. package/dist/migration/generator.js +52 -1
  21. package/dist/migration/generator.js.map +1 -1
  22. package/dist/migration/index.cjs +52 -1
  23. package/dist/migration/index.cjs.map +1 -1
  24. package/dist/migration/index.js +52 -1
  25. package/dist/migration/index.js.map +1 -1
  26. package/dist/migration/snapshot.cjs.map +1 -1
  27. package/dist/migration/snapshot.js.map +1 -1
  28. package/dist/mutator.cjs +2 -98
  29. package/dist/mutator.cjs.map +1 -1
  30. package/dist/mutator.d.cts +4 -31
  31. package/dist/mutator.d.ts +4 -31
  32. package/dist/mutator.js +2 -98
  33. package/dist/mutator.js.map +1 -1
  34. package/dist/schema.cjs +0 -78
  35. package/dist/schema.cjs.map +1 -1
  36. package/dist/schema.d.cts +0 -1
  37. package/dist/schema.d.ts +0 -1
  38. package/dist/schema.js +1 -72
  39. package/dist/schema.js.map +1 -1
  40. package/package.json +1 -6
  41. package/dist/types.cjs +0 -4
  42. package/dist/types.cjs.map +0 -1
  43. package/dist/types.d.cts +0 -14
  44. package/dist/types.d.ts +0 -14
  45. package/dist/types.js +0 -3
  46. package/dist/types.js.map +0 -1
  47. package/dist/user-BnFWg5tw.d.cts +0 -161
  48. package/dist/user-BnFWg5tw.d.ts +0 -161
package/dist/index.cjs CHANGED
@@ -42,784 +42,708 @@ var StatusEnum = zod.z.enum([
42
42
  "fail"
43
43
  // Failed project at any stage
44
44
  ]);
45
- var baseSchema = {
46
- id: zod.z.string().describe("unique id"),
47
- collectionId: zod.z.string().describe("collection id"),
48
- collectionName: zod.z.string().describe("collection name"),
49
- expand: zod.z.record(zod.z.any()).describe("expandable fields"),
50
- created: zod.z.string().describe("creation timestamp"),
51
- updated: zod.z.string().describe("last update timestamp")
52
- };
53
- var baseSchemaWithTimestamps = {
54
- ...baseSchema,
55
- created: zod.z.string().describe("creation timestamp"),
56
- updated: zod.z.string().describe("last update timestamp")
57
- };
58
- var baseImageFileSchema = {
59
- ...baseSchema,
60
- thumbnailURL: zod.z.string().optional(),
61
- imageFiles: zod.z.array(zod.z.string())
62
- };
63
- var inputImageFileSchema = {
64
- imageFiles: zod.z.array(zod.z.instanceof(File))
65
- };
66
- var omitImageFilesSchema = {
67
- imageFiles: true
68
- };
69
- function textField(options) {
70
- let schema = zod.z.string();
71
- if (options?.min !== void 0) schema = schema.min(options.min);
72
- if (options?.max !== void 0) schema = schema.max(options.max);
73
- if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
74
- return schema;
75
- }
76
- function emailField() {
77
- return zod.z.string().email();
78
- }
79
- function urlField() {
80
- return zod.z.string().url();
81
- }
82
- function numberField(options) {
83
- let schema = zod.z.number();
84
- if (options?.min !== void 0) schema = schema.min(options.min);
85
- if (options?.max !== void 0) schema = schema.max(options.max);
86
- return schema;
87
- }
88
- function boolField() {
89
- return zod.z.boolean();
90
- }
91
- function dateField() {
92
- return zod.z.date();
93
- }
94
- function selectField(values) {
95
- return zod.z.enum(values);
96
- }
97
- function jsonField(schema) {
98
- return schema ?? zod.z.record(zod.z.any());
99
- }
100
- function fileField() {
101
- return zod.z.instanceof(File);
102
- }
103
- function filesField(options) {
104
- let schema = zod.z.array(zod.z.instanceof(File));
105
- if (options?.min !== void 0) schema = schema.min(options.min);
106
- if (options?.max !== void 0) schema = schema.max(options.max);
107
- return schema;
108
- }
109
- var RELATION_METADATA_KEY = "__pocketbase_relation__";
110
- function RelationField(config) {
111
- const metadata = {
112
- [RELATION_METADATA_KEY]: {
113
- type: "single",
114
- collection: config.collection,
115
- cascadeDelete: config.cascadeDelete ?? false,
116
- maxSelect: 1,
117
- minSelect: 0
118
- }
45
+ var BaseMutator = class {
46
+ pb;
47
+ // Define a default property that subclasses will override
48
+ options = {
49
+ expand: [],
50
+ filter: [],
51
+ sort: []
119
52
  };
120
- return zod.z.string().describe(JSON.stringify(metadata));
121
- }
122
- function RelationsField(config) {
123
- const metadata = {
124
- [RELATION_METADATA_KEY]: {
125
- type: "multiple",
126
- collection: config.collection,
127
- cascadeDelete: config.cascadeDelete ?? false,
128
- maxSelect: config.maxSelect ?? 999,
129
- minSelect: config.minSelect ?? 0
53
+ constructor(pb, options) {
54
+ this.pb = pb;
55
+ this.initializeOptions();
56
+ if (options) {
57
+ this.overrideOptions(options);
130
58
  }
131
- };
132
- let schema = zod.z.array(zod.z.string());
133
- if (config.minSelect !== void 0) {
134
- schema = schema.min(config.minSelect);
135
59
  }
136
- if (config.maxSelect !== void 0) {
137
- schema = schema.max(config.maxSelect);
60
+ initializeOptions() {
61
+ this.options = this.setDefaults();
138
62
  }
139
- return schema.describe(JSON.stringify(metadata));
140
- }
141
- function extractRelationMetadata(description) {
142
- if (!description) return null;
143
- try {
144
- const parsed = JSON.parse(description);
145
- if (parsed[RELATION_METADATA_KEY]) {
146
- return parsed[RELATION_METADATA_KEY];
147
- }
148
- } catch {
63
+ /**
64
+ * Initialize options with class-specific defaults
65
+ * Subclasses should override this instead of directly setting options
66
+ */
67
+ setDefaults() {
68
+ return {
69
+ expand: [],
70
+ filter: [],
71
+ sort: []
72
+ };
149
73
  }
150
- return null;
151
- }
152
- function editorField() {
153
- return zod.z.string();
154
- }
155
- function geoPointField() {
156
- return zod.z.object({
157
- lon: zod.z.number(),
158
- lat: zod.z.number()
159
- });
160
- }
161
- function withPermissions(schema, config) {
162
- const metadata = {
163
- permissions: config
164
- };
165
- return schema.describe(JSON.stringify(metadata));
166
- }
167
- function withIndexes(schema, indexes) {
168
- let existingMetadata = {};
169
- if (schema.description) {
170
- try {
171
- existingMetadata = JSON.parse(schema.description);
172
- } catch {
74
+ /**
75
+ * Merge provided options with current options
76
+ */
77
+ overrideOptions(newOptions) {
78
+ if (newOptions.expand !== void 0) {
79
+ this.options.expand = newOptions.expand;
80
+ }
81
+ if (newOptions.filter !== void 0) {
82
+ this.options.filter = newOptions.filter;
83
+ }
84
+ if (newOptions.sort !== void 0) {
85
+ this.options.sort = newOptions.sort;
173
86
  }
174
87
  }
175
- const metadata = {
176
- ...existingMetadata,
177
- indexes
178
- };
179
- return schema.describe(JSON.stringify(metadata));
180
- }
181
- function defineCollection(config) {
182
- const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
183
- const metadata = {
184
- collectionName
185
- };
186
- if (type) {
187
- metadata.type = type;
88
+ toSnakeCase(str) {
89
+ return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
188
90
  }
189
- if (permissions) {
190
- metadata.permissions = permissions;
91
+ /**
92
+ * Create a new entity
93
+ */
94
+ async create(input) {
95
+ try {
96
+ const data = await this.validateInput(input);
97
+ const record = await this.entityCreate(data);
98
+ return await this.processRecord(record);
99
+ } catch (error) {
100
+ return this.errorWrapper(error);
101
+ }
191
102
  }
192
- if (indexes) {
193
- metadata.indexes = indexes;
103
+ /**
104
+ * Update an existing entity
105
+ */
106
+ async update(id, input) {
107
+ try {
108
+ const record = await this.entityUpdate(id, input);
109
+ return await this.processRecord(record);
110
+ } catch (error) {
111
+ return this.errorWrapper(error);
112
+ }
194
113
  }
195
- if (Object.keys(futureOptions).length > 0) {
196
- Object.assign(metadata, futureOptions);
114
+ /**
115
+ * Create or update entity (upsert)
116
+ */
117
+ async upsert(input) {
118
+ if (input?.id) {
119
+ return await this.update(input.id, input);
120
+ }
121
+ return await this.create(input);
197
122
  }
198
- return schema.describe(JSON.stringify(metadata));
199
- }
200
-
201
- // src/utils/permission-templates.ts
202
- var PermissionTemplates = {
203
123
  /**
204
- * Public access - anyone can perform all operations
124
+ * Get entity by ID
205
125
  */
206
- public: () => ({
207
- listRule: "",
208
- viewRule: "",
209
- createRule: "",
210
- updateRule: "",
211
- deleteRule: ""
212
- }),
126
+ async getById(id, expand) {
127
+ try {
128
+ const record = await this.entityGetById(id, expand);
129
+ return await this.processRecord(record);
130
+ } catch (error) {
131
+ return this.handleError(error, { allowNotFound: true });
132
+ }
133
+ }
213
134
  /**
214
- * Authenticated users only - requires valid authentication for all operations
135
+ * Get first entity by filter
215
136
  */
216
- authenticated: () => ({
217
- listRule: '@request.auth.id != ""',
218
- viewRule: '@request.auth.id != ""',
219
- createRule: '@request.auth.id != ""',
220
- updateRule: '@request.auth.id != ""',
221
- deleteRule: '@request.auth.id != ""'
222
- }),
137
+ async getFirstByFilter(filter, expand, sort) {
138
+ try {
139
+ const record = await this.entityGetFirstByFilter(filter, expand, sort);
140
+ return await this.processRecord(record);
141
+ } catch (error) {
142
+ return this.handleError(error, { allowNotFound: true });
143
+ }
144
+ }
223
145
  /**
224
- * Owner-only access - users can only manage their own records
225
- * @param ownerField - Name of the relation field pointing to user (default: 'User')
146
+ * Get list of entities
226
147
  */
227
- ownerOnly: (ownerField = "User") => ({
228
- listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
229
- viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
230
- createRule: '@request.auth.id != ""',
231
- updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
232
- deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
233
- }),
148
+ async getList(page = 1, perPage = 100, filter, sort, expand) {
149
+ try {
150
+ const result = await this.entityGetList(page, perPage, filter, sort, expand);
151
+ return await this.processListResult(result);
152
+ } catch (error) {
153
+ return this.errorWrapper(error);
154
+ }
155
+ }
234
156
  /**
235
- * Admin/superuser only access
236
- * Assumes a 'role' field exists with 'admin' value
237
- * @param roleField - Name of the role field (default: 'role')
157
+ * Delete entity by ID
238
158
  */
239
- adminOnly: (roleField = "role") => ({
240
- listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
241
- viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
242
- createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
243
- updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
244
- deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
245
- }),
159
+ async delete(id) {
160
+ try {
161
+ return await this.entityDelete(id);
162
+ } catch (error) {
163
+ return this.handleError(error, { returnValue: false });
164
+ }
165
+ }
246
166
  /**
247
- * Public read, authenticated write
248
- * Anyone can list/view, but only authenticated users can create/update/delete
167
+ * Process a single record before returning it
168
+ * Can be overridden to handle special cases like mapped entities
249
169
  */
250
- readPublic: () => ({
251
- listRule: "",
252
- viewRule: "",
253
- createRule: '@request.auth.id != ""',
254
- updateRule: '@request.auth.id != ""',
255
- deleteRule: '@request.auth.id != ""'
256
- }),
170
+ async processRecord(record) {
171
+ return record;
172
+ }
257
173
  /**
258
- * Locked access - only superusers can perform operations
259
- * All rules are set to null (locked)
174
+ * Process a list result before returning it
175
+ * Can be overridden to handle special cases like mapped entities
260
176
  */
261
- locked: () => ({
262
- listRule: null,
263
- viewRule: null,
264
- createRule: null,
265
- updateRule: null,
266
- deleteRule: null
267
- }),
177
+ async processListResult(result) {
178
+ const processedItems = await Promise.all(result.items.map((item) => this.processRecord(item)));
179
+ return {
180
+ ...result,
181
+ items: processedItems
182
+ };
183
+ }
268
184
  /**
269
- * Read-only authenticated - authenticated users can read, no write access
185
+ * Prepare expand parameter
186
+ * Combines default expands with provided expands
270
187
  */
271
- readOnlyAuthenticated: () => ({
272
- listRule: '@request.auth.id != ""',
273
- viewRule: '@request.auth.id != ""',
274
- createRule: null,
275
- updateRule: null,
276
- deleteRule: null
277
- })
278
- };
279
- function resolveTemplate(config) {
280
- let baseRules;
281
- switch (config.template) {
282
- case "public":
283
- baseRules = PermissionTemplates.public();
284
- break;
285
- case "authenticated":
286
- baseRules = PermissionTemplates.authenticated();
287
- break;
288
- case "owner-only":
289
- baseRules = PermissionTemplates.ownerOnly(config.ownerField);
290
- break;
291
- case "admin-only":
292
- baseRules = PermissionTemplates.adminOnly(config.roleField);
293
- break;
294
- case "read-public":
295
- baseRules = PermissionTemplates.readPublic();
296
- break;
297
- case "custom":
298
- baseRules = {};
299
- break;
300
- default: {
301
- const _exhaustive = config.template;
302
- throw new Error(`Unknown template type: ${_exhaustive}`);
188
+ prepareExpand(expand) {
189
+ if (!this.options.expand.length && !expand) {
190
+ return void 0;
303
191
  }
304
- }
305
- return {
306
- ...baseRules,
307
- ...config.customRules
308
- };
309
- }
310
- function isTemplateConfig(config) {
311
- return "template" in config;
312
- }
313
- function isPermissionSchema(config) {
314
- return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
315
- }
316
- function validatePermissionConfig(config, isAuthCollection2 = false) {
317
- const result = {
318
- valid: true,
319
- errors: [],
320
- warnings: []
321
- };
322
- let permissions;
323
- if (isTemplateConfig(config)) {
324
- if (config.template === "owner-only" && !config.ownerField) {
325
- result.warnings.push("owner-only template without ownerField specified - using default 'User'");
192
+ let expandArray = [...this.options.expand];
193
+ if (expand) {
194
+ if (typeof expand === "string") {
195
+ expandArray = expandArray.concat(expand.split(",").map((e) => e.trim()));
196
+ } else {
197
+ expandArray = expandArray.concat(expand);
198
+ }
326
199
  }
327
- if (config.template === "admin-only" && !config.roleField) {
328
- result.warnings.push("admin-only template without roleField specified - using default 'role'");
200
+ const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
201
+ if (!uniqueExpands.length) {
202
+ return void 0;
329
203
  }
330
- permissions = resolveTemplate(config);
331
- } else {
332
- permissions = config;
333
- }
334
- if (permissions.manageRule !== void 0 && !isAuthCollection2) {
335
- result.errors.push("manageRule is only valid for auth collections");
336
- result.valid = false;
337
- }
338
- const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
339
- if (isAuthCollection2) {
340
- ruleTypes.push("manageRule");
204
+ return uniqueExpands.join(",");
341
205
  }
342
- for (const ruleType of ruleTypes) {
343
- const rule = permissions[ruleType];
344
- if (rule !== void 0 && rule !== null && rule !== "") {
345
- const ruleValidation = validateRuleExpression(rule);
346
- if (!ruleValidation.valid) {
347
- result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
348
- result.valid = false;
206
+ /**
207
+ * Prepare filter parameter
208
+ * Combines default filters with provided filters
209
+ */
210
+ prepareFilter(filter) {
211
+ if (!this.options.filter.length && !filter) {
212
+ return void 0;
213
+ }
214
+ let filterArray = [...this.options.filter];
215
+ if (filter) {
216
+ if (typeof filter === "string") {
217
+ if (filter) filterArray.push(filter);
218
+ } else {
219
+ filterArray = filterArray.concat(filter);
349
220
  }
350
- result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
351
221
  }
352
- }
353
- return result;
354
- }
355
- function validateRuleExpression(expression) {
356
- const result = {
357
- valid: true,
358
- errors: [],
359
- warnings: []
360
- };
361
- if (expression === null || expression === "") {
362
- return result;
363
- }
364
- let parenCount = 0;
365
- for (const char of expression) {
366
- if (char === "(") parenCount++;
367
- if (char === ")") parenCount--;
368
- if (parenCount < 0) {
369
- result.errors.push("Unbalanced parentheses");
370
- result.valid = false;
371
- return result;
222
+ const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
223
+ if (!validFilters.length) {
224
+ return void 0;
372
225
  }
226
+ return validFilters.join("&&");
373
227
  }
374
- if (parenCount !== 0) {
375
- result.errors.push("Unbalanced parentheses");
376
- result.valid = false;
377
- }
378
- if (expression.includes("==")) {
379
- result.warnings.push("Use '=' instead of '==' for equality comparison");
380
- }
381
- const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
382
- for (const ref of requestRefs) {
383
- const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
384
- if (!isValid) {
385
- result.errors.push(`Invalid @request reference: '${ref}'`);
386
- result.valid = false;
228
+ /**
229
+ * Prepare sort parameter
230
+ * Uses provided sort or falls back to default sort
231
+ */
232
+ prepareSort(sort) {
233
+ if (sort && sort !== "") {
234
+ return sort;
235
+ }
236
+ if (this.options.sort.length) {
237
+ const validSorts = this.options.sort.filter((s) => s !== "" && s !== void 0);
238
+ if (validSorts.length) {
239
+ return validSorts.join(",");
240
+ }
387
241
  }
242
+ return void 0;
388
243
  }
389
- return result;
390
- }
391
- function createPermissions(permissions) {
392
- return {
393
- listRule: permissions.listRule ?? null,
394
- viewRule: permissions.viewRule ?? null,
395
- createRule: permissions.createRule ?? null,
396
- updateRule: permissions.updateRule ?? null,
397
- deleteRule: permissions.deleteRule ?? null,
398
- manageRule: permissions.manageRule ?? null
399
- };
400
- }
401
- function mergePermissions(...schemas) {
402
- const merged = {
403
- listRule: null,
404
- viewRule: null,
405
- createRule: null,
406
- updateRule: null,
407
- deleteRule: null,
408
- manageRule: null
409
- };
410
- for (const schema of schemas) {
411
- if (schema.listRule !== void 0) merged.listRule = schema.listRule;
412
- if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
413
- if (schema.createRule !== void 0) merged.createRule = schema.createRule;
414
- if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
415
- if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
416
- if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
244
+ /**
245
+ * Perform the actual create operation
246
+ */
247
+ async entityCreate(data) {
248
+ return await this.getCollection().create(data);
417
249
  }
418
- return merged;
419
- }
420
- var ProjectInputSchema = zod.z.object({
421
- // Required fields
422
- title: zod.z.string(),
423
- content: zod.z.string(),
424
- status: StatusEnum,
425
- summary: zod.z.string().optional(),
426
- OwnerUser: RelationField({ collection: "Users" }),
427
- SubscriberUsers: RelationsField({ collection: "Users" })
428
- }).extend(inputImageFileSchema);
429
- var ProjectSchema = ProjectInputSchema.omit(omitImageFilesSchema).extend(baseImageFileSchema);
430
- var ProjectCollection = defineCollection({
431
- collectionName: "Projects",
432
- schema: ProjectSchema,
433
- permissions: {
434
- template: "owner-only",
435
- ownerField: "OwnerUser",
436
- customRules: {
437
- listRule: '@request.auth.id != ""',
438
- viewRule: '@request.auth.id != "" && (OwnerUser = @request.auth.id || SubscriberUsers ?= @request.auth.id)'
439
- }
250
+ /**
251
+ * Perform the actual update operation
252
+ */
253
+ async entityUpdate(id, data) {
254
+ return await this.getCollection().update(id, data);
440
255
  }
441
- });
442
- var UserInputSchema = zod.z.object({
443
- name: zod.z.string().optional(),
444
- email: zod.z.string().email(),
445
- password: zod.z.string().min(8, "Password must be at least 8 characters"),
446
- passwordConfirm: zod.z.string(),
447
- avatar: zod.z.instanceof(File).optional()
448
- });
449
- var UserCollectionSchema = zod.z.object({
450
- name: zod.z.string().optional(),
451
- email: zod.z.string().email(),
452
- password: zod.z.string().min(8, "Password must be at least 8 characters"),
453
- avatar: zod.z.instanceof(File).optional()
454
- });
455
- var UserSchema = UserCollectionSchema.extend(baseSchema);
456
- var UserCollection = defineCollection({
457
- collectionName: "users",
458
- type: "auth",
459
- schema: UserSchema,
460
- permissions: {
461
- // Users can list their own profile
462
- listRule: "id = @request.auth.id",
463
- // Users can view their own profile
464
- viewRule: "id = @request.auth.id",
465
- // Anyone can create an account (sign up)
466
- createRule: "",
467
- // Users can only update their own profile
468
- updateRule: "id = @request.auth.id",
469
- // Users can only delete their own account
470
- deleteRule: "id = @request.auth.id"
471
- // manageRule is null in PocketBase default (not set)
472
- },
473
- indexes: [
474
- // PocketBase's default indexes for auth collections
475
- "CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
476
- "CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
477
- ]
478
- });
479
- var BaseMutator = class {
480
- pb;
481
- // Define a default property that subclasses will override
482
- options = {
483
- expand: [],
484
- filter: [],
485
- sort: []
486
- };
487
- constructor(pb, options) {
488
- this.pb = pb;
489
- this.initializeOptions();
490
- if (options) {
491
- this.overrideOptions(options);
492
- }
256
+ /**
257
+ * Perform the actual getById operation
258
+ */
259
+ async entityGetById(id, expand) {
260
+ const finalExpand = this.prepareExpand(expand);
261
+ const options = finalExpand ? { expand: finalExpand } : {};
262
+ return await this.getCollection().getOne(id, options);
493
263
  }
494
- initializeOptions() {
495
- this.options = this.setDefaults();
264
+ /**
265
+ * Perform the actual getFirstByFilter operation
266
+ */
267
+ async entityGetFirstByFilter(filter, expand, sort) {
268
+ const finalFilter = this.prepareFilter(filter);
269
+ const finalExpand = this.prepareExpand(expand);
270
+ const finalSort = this.prepareSort(sort);
271
+ const options = {};
272
+ if (finalExpand) options.expand = finalExpand;
273
+ if (finalSort) options.sort = finalSort;
274
+ return await this.getCollection().getFirstListItem(finalFilter || "", options);
496
275
  }
497
276
  /**
498
- * Initialize options with class-specific defaults
499
- * Subclasses should override this instead of directly setting options
277
+ * Perform the actual getList operation
278
+ * Returns a list result with items of type T
500
279
  */
501
- setDefaults() {
502
- return {
503
- expand: [],
504
- filter: [],
505
- sort: []
506
- };
280
+ async entityGetList(page, perPage, filter, sort, expand) {
281
+ const finalFilter = this.prepareFilter(filter);
282
+ const finalExpand = this.prepareExpand(expand);
283
+ const finalSort = this.prepareSort(sort);
284
+ const options = {};
285
+ if (finalFilter) options.filter = finalFilter;
286
+ if (finalExpand) options.expand = finalExpand;
287
+ if (finalSort) options.sort = finalSort;
288
+ return await this.getCollection().getList(page, perPage, options);
507
289
  }
508
290
  /**
509
- * Merge provided options with current options
291
+ * Perform the actual delete operation
510
292
  */
511
- overrideOptions(newOptions) {
512
- if (newOptions.expand !== void 0) {
513
- this.options.expand = newOptions.expand;
293
+ async entityDelete(id) {
294
+ await this.getCollection().delete(id);
295
+ return true;
296
+ }
297
+ /**
298
+ * Error handler for common errors
299
+ * @param error The error to handle
300
+ * @param options Handler options
301
+ * @returns The value to return if the error is handled, or throws if not handled
302
+ */
303
+ handleError(error, options = { logError: true }) {
304
+ const { allowNotFound = false, returnValue, logError: logError2 = true } = options;
305
+ if (logError2) {
306
+ console.error(`Error in ${this.constructor.name}:`, error);
514
307
  }
515
- if (newOptions.filter !== void 0) {
516
- this.options.filter = newOptions.filter;
308
+ if (allowNotFound && this.isNotFoundError(error)) {
309
+ return null;
517
310
  }
518
- if (newOptions.sort !== void 0) {
519
- this.options.sort = newOptions.sort;
311
+ if (returnValue !== void 0) {
312
+ return returnValue;
520
313
  }
314
+ throw error;
521
315
  }
522
- toSnakeCase(str) {
523
- return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
316
+ /**
317
+ * Check if an error is a "not found" error
318
+ */
319
+ isNotFoundError(error) {
320
+ return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
524
321
  }
525
322
  /**
526
- * Create a new entity
323
+ * Standard error handling wrapper (legacy method, consider using handleError instead)
527
324
  */
528
- async create(input) {
529
- try {
530
- const data = await this.validateInput(input);
531
- const record = await this.entityCreate(data);
532
- return await this.processRecord(record);
533
- } catch (error) {
534
- return this.errorWrapper(error);
535
- }
325
+ errorWrapper(error) {
326
+ console.error(`Error in ${this.constructor.name}:`, error);
327
+ throw error;
536
328
  }
537
329
  /**
538
- * Update an existing entity
330
+ * Subscribe to changes on a specific record
331
+ * @param id The ID of the record to subscribe to
332
+ * @param callback Function to call when changes occur
333
+ * @param expand Optional expand parameters
334
+ * @returns Promise that resolves to an unsubscribe function
539
335
  */
540
- async update(id, input) {
541
- try {
542
- const record = await this.entityUpdate(id, input);
543
- return await this.processRecord(record);
544
- } catch (error) {
545
- return this.errorWrapper(error);
546
- }
336
+ async subscribeToRecord(id, callback, expand) {
337
+ const finalExpand = this.prepareExpand(expand);
338
+ const options = finalExpand ? { expand: finalExpand } : {};
339
+ return this.getCollection().subscribe(id, callback, options);
547
340
  }
548
341
  /**
549
- * Create or update entity (upsert)
342
+ * Subscribe to changes on the entire collection
343
+ * @param callback Function to call when changes occur
344
+ * @param expand Optional expand parameters
345
+ * @returns Promise that resolves to an unsubscribe function
550
346
  */
551
- async upsert(input) {
552
- if (input?.id) {
553
- return await this.update(input.id, input);
347
+ async subscribeToCollection(callback, expand) {
348
+ const finalExpand = this.prepareExpand(expand);
349
+ const options = finalExpand ? { expand: finalExpand } : {};
350
+ return this.getCollection().subscribe("*", callback, options);
351
+ }
352
+ /**
353
+ * Unsubscribe from a specific record's changes
354
+ * @param id The ID of the record to unsubscribe from
355
+ */
356
+ unsubscribeFromRecord(id) {
357
+ this.getCollection().unsubscribe(id);
358
+ }
359
+ /**
360
+ * Unsubscribe from collection-wide changes
361
+ */
362
+ unsubscribeFromCollection() {
363
+ this.getCollection().unsubscribe("*");
364
+ }
365
+ /**
366
+ * Unsubscribe from all subscriptions in this collection
367
+ */
368
+ unsubscribeAll() {
369
+ this.getCollection().unsubscribe();
370
+ }
371
+ };
372
+ var baseSchema = {
373
+ id: zod.z.string().describe("unique id"),
374
+ collectionId: zod.z.string().describe("collection id"),
375
+ collectionName: zod.z.string().describe("collection name"),
376
+ expand: zod.z.record(zod.z.any()).describe("expandable fields"),
377
+ created: zod.z.string().describe("creation timestamp"),
378
+ updated: zod.z.string().describe("last update timestamp")
379
+ };
380
+ var baseSchemaWithTimestamps = {
381
+ ...baseSchema,
382
+ created: zod.z.string().describe("creation timestamp"),
383
+ updated: zod.z.string().describe("last update timestamp")
384
+ };
385
+ var baseImageFileSchema = {
386
+ ...baseSchema,
387
+ thumbnailURL: zod.z.string().optional(),
388
+ imageFiles: zod.z.array(zod.z.string())
389
+ };
390
+ var inputImageFileSchema = {
391
+ imageFiles: zod.z.array(zod.z.instanceof(File))
392
+ };
393
+ var omitImageFilesSchema = {
394
+ imageFiles: true
395
+ };
396
+ function textField(options) {
397
+ let schema = zod.z.string();
398
+ if (options?.min !== void 0) schema = schema.min(options.min);
399
+ if (options?.max !== void 0) schema = schema.max(options.max);
400
+ if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
401
+ return schema;
402
+ }
403
+ function emailField() {
404
+ return zod.z.string().email();
405
+ }
406
+ function urlField() {
407
+ return zod.z.string().url();
408
+ }
409
+ function numberField(options) {
410
+ let schema = zod.z.number();
411
+ if (options?.min !== void 0) schema = schema.min(options.min);
412
+ if (options?.max !== void 0) schema = schema.max(options.max);
413
+ return schema;
414
+ }
415
+ function boolField() {
416
+ return zod.z.boolean();
417
+ }
418
+ function dateField() {
419
+ return zod.z.date();
420
+ }
421
+ function selectField(values) {
422
+ return zod.z.enum(values);
423
+ }
424
+ function jsonField(schema) {
425
+ return schema ?? zod.z.record(zod.z.any());
426
+ }
427
+ function fileField() {
428
+ return zod.z.instanceof(File);
429
+ }
430
+ function filesField(options) {
431
+ let schema = zod.z.array(zod.z.instanceof(File));
432
+ if (options?.min !== void 0) schema = schema.min(options.min);
433
+ if (options?.max !== void 0) schema = schema.max(options.max);
434
+ return schema;
435
+ }
436
+ var RELATION_METADATA_KEY = "__pocketbase_relation__";
437
+ function RelationField(config) {
438
+ const metadata = {
439
+ [RELATION_METADATA_KEY]: {
440
+ type: "single",
441
+ collection: config.collection,
442
+ cascadeDelete: config.cascadeDelete ?? false,
443
+ maxSelect: 1,
444
+ minSelect: 0
554
445
  }
555
- return await this.create(input);
556
- }
557
- /**
558
- * Get entity by ID
559
- */
560
- async getById(id, expand) {
561
- try {
562
- const record = await this.entityGetById(id, expand);
563
- return await this.processRecord(record);
564
- } catch (error) {
565
- return this.handleError(error, { allowNotFound: true });
446
+ };
447
+ return zod.z.string().describe(JSON.stringify(metadata));
448
+ }
449
+ function RelationsField(config) {
450
+ const metadata = {
451
+ [RELATION_METADATA_KEY]: {
452
+ type: "multiple",
453
+ collection: config.collection,
454
+ cascadeDelete: config.cascadeDelete ?? false,
455
+ maxSelect: config.maxSelect ?? 999,
456
+ minSelect: config.minSelect ?? 0
566
457
  }
458
+ };
459
+ let schema = zod.z.array(zod.z.string());
460
+ if (config.minSelect !== void 0) {
461
+ schema = schema.min(config.minSelect);
567
462
  }
568
- /**
569
- * Get first entity by filter
570
- */
571
- async getFirstByFilter(filter, expand, sort) {
572
- try {
573
- const record = await this.entityGetFirstByFilter(filter, expand, sort);
574
- return await this.processRecord(record);
575
- } catch (error) {
576
- return this.handleError(error, { allowNotFound: true });
577
- }
463
+ if (config.maxSelect !== void 0) {
464
+ schema = schema.max(config.maxSelect);
578
465
  }
579
- /**
580
- * Get list of entities
581
- */
582
- async getList(page = 1, perPage = 100, filter, sort, expand) {
583
- try {
584
- const result = await this.entityGetList(page, perPage, filter, sort, expand);
585
- return await this.processListResult(result);
586
- } catch (error) {
587
- return this.errorWrapper(error);
466
+ return schema.describe(JSON.stringify(metadata));
467
+ }
468
+ function extractRelationMetadata(description) {
469
+ if (!description) return null;
470
+ try {
471
+ const parsed = JSON.parse(description);
472
+ if (parsed[RELATION_METADATA_KEY]) {
473
+ return parsed[RELATION_METADATA_KEY];
588
474
  }
475
+ } catch {
589
476
  }
590
- /**
591
- * Delete entity by ID
592
- */
593
- async delete(id) {
477
+ return null;
478
+ }
479
+ function editorField() {
480
+ return zod.z.string();
481
+ }
482
+ function geoPointField() {
483
+ return zod.z.object({
484
+ lon: zod.z.number(),
485
+ lat: zod.z.number()
486
+ });
487
+ }
488
+ function withPermissions(schema, config) {
489
+ const metadata = {
490
+ permissions: config
491
+ };
492
+ return schema.describe(JSON.stringify(metadata));
493
+ }
494
+ function withIndexes(schema, indexes) {
495
+ let existingMetadata = {};
496
+ if (schema.description) {
594
497
  try {
595
- return await this.entityDelete(id);
596
- } catch (error) {
597
- return this.handleError(error, { returnValue: false });
498
+ existingMetadata = JSON.parse(schema.description);
499
+ } catch {
598
500
  }
599
501
  }
600
- /**
601
- * Process a single record before returning it
602
- * Can be overridden to handle special cases like mapped entities
603
- */
604
- async processRecord(record) {
605
- return record;
606
- }
607
- /**
608
- * Process a list result before returning it
609
- * Can be overridden to handle special cases like mapped entities
610
- */
611
- async processListResult(result) {
612
- const processedItems = await Promise.all(result.items.map((item) => this.processRecord(item)));
613
- return {
614
- ...result,
615
- items: processedItems
616
- };
502
+ const metadata = {
503
+ ...existingMetadata,
504
+ indexes
505
+ };
506
+ return schema.describe(JSON.stringify(metadata));
507
+ }
508
+ function defineCollection(config) {
509
+ const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
510
+ const metadata = {
511
+ collectionName
512
+ };
513
+ if (type) {
514
+ metadata.type = type;
617
515
  }
618
- /**
619
- * Prepare expand parameter
620
- * Combines default expands with provided expands
621
- */
622
- prepareExpand(expand) {
623
- if (!this.options.expand.length && !expand) {
624
- return void 0;
625
- }
626
- let expandArray = [...this.options.expand];
627
- if (expand) {
628
- if (typeof expand === "string") {
629
- expandArray = expandArray.concat(expand.split(",").map((e) => e.trim()));
630
- } else {
631
- expandArray = expandArray.concat(expand);
632
- }
633
- }
634
- const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
635
- if (!uniqueExpands.length) {
636
- return void 0;
637
- }
638
- return uniqueExpands.join(",");
516
+ if (permissions) {
517
+ metadata.permissions = permissions;
639
518
  }
640
- /**
641
- * Prepare filter parameter
642
- * Combines default filters with provided filters
643
- */
644
- prepareFilter(filter) {
645
- if (!this.options.filter.length && !filter) {
646
- return void 0;
647
- }
648
- let filterArray = [...this.options.filter];
649
- if (filter) {
650
- if (typeof filter === "string") {
651
- if (filter) filterArray.push(filter);
652
- } else {
653
- filterArray = filterArray.concat(filter);
654
- }
655
- }
656
- const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
657
- if (!validFilters.length) {
658
- return void 0;
659
- }
660
- return validFilters.join("&&");
519
+ if (indexes) {
520
+ metadata.indexes = indexes;
661
521
  }
662
- /**
663
- * Prepare sort parameter
664
- * Uses provided sort or falls back to default sort
665
- */
666
- prepareSort(sort) {
667
- if (sort && sort !== "") {
668
- return sort;
669
- }
670
- if (this.options.sort.length) {
671
- const validSorts = this.options.sort.filter((s) => s !== "" && s !== void 0);
672
- if (validSorts.length) {
673
- return validSorts.join(",");
674
- }
675
- }
676
- return void 0;
522
+ if (Object.keys(futureOptions).length > 0) {
523
+ Object.assign(metadata, futureOptions);
677
524
  }
525
+ return schema.describe(JSON.stringify(metadata));
526
+ }
527
+
528
+ // src/utils/permission-templates.ts
529
+ var PermissionTemplates = {
678
530
  /**
679
- * Perform the actual create operation
531
+ * Public access - anyone can perform all operations
680
532
  */
681
- async entityCreate(data) {
682
- return await this.getCollection().create(data);
683
- }
533
+ public: () => ({
534
+ listRule: "",
535
+ viewRule: "",
536
+ createRule: "",
537
+ updateRule: "",
538
+ deleteRule: ""
539
+ }),
684
540
  /**
685
- * Perform the actual update operation
541
+ * Authenticated users only - requires valid authentication for all operations
686
542
  */
687
- async entityUpdate(id, data) {
688
- return await this.getCollection().update(id, data);
689
- }
543
+ authenticated: () => ({
544
+ listRule: '@request.auth.id != ""',
545
+ viewRule: '@request.auth.id != ""',
546
+ createRule: '@request.auth.id != ""',
547
+ updateRule: '@request.auth.id != ""',
548
+ deleteRule: '@request.auth.id != ""'
549
+ }),
690
550
  /**
691
- * Perform the actual getById operation
551
+ * Owner-only access - users can only manage their own records
552
+ * @param ownerField - Name of the relation field pointing to user (default: 'User')
692
553
  */
693
- async entityGetById(id, expand) {
694
- const finalExpand = this.prepareExpand(expand);
695
- const options = finalExpand ? { expand: finalExpand } : {};
696
- return await this.getCollection().getOne(id, options);
697
- }
554
+ ownerOnly: (ownerField = "User") => ({
555
+ listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
556
+ viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
557
+ createRule: '@request.auth.id != ""',
558
+ updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
559
+ deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
560
+ }),
698
561
  /**
699
- * Perform the actual getFirstByFilter operation
562
+ * Admin/superuser only access
563
+ * Assumes a 'role' field exists with 'admin' value
564
+ * @param roleField - Name of the role field (default: 'role')
700
565
  */
701
- async entityGetFirstByFilter(filter, expand, sort) {
702
- const finalFilter = this.prepareFilter(filter);
703
- const finalExpand = this.prepareExpand(expand);
704
- const finalSort = this.prepareSort(sort);
705
- const options = {};
706
- if (finalExpand) options.expand = finalExpand;
707
- if (finalSort) options.sort = finalSort;
708
- return await this.getCollection().getFirstListItem(finalFilter || "", options);
709
- }
566
+ adminOnly: (roleField = "role") => ({
567
+ listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
568
+ viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
569
+ createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
570
+ updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
571
+ deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
572
+ }),
710
573
  /**
711
- * Perform the actual getList operation
712
- * Returns a list result with items of type T
574
+ * Public read, authenticated write
575
+ * Anyone can list/view, but only authenticated users can create/update/delete
713
576
  */
714
- async entityGetList(page, perPage, filter, sort, expand) {
715
- const finalFilter = this.prepareFilter(filter);
716
- const finalExpand = this.prepareExpand(expand);
717
- const finalSort = this.prepareSort(sort);
718
- const options = {};
719
- if (finalFilter) options.filter = finalFilter;
720
- if (finalExpand) options.expand = finalExpand;
721
- if (finalSort) options.sort = finalSort;
722
- return await this.getCollection().getList(page, perPage, options);
723
- }
577
+ readPublic: () => ({
578
+ listRule: "",
579
+ viewRule: "",
580
+ createRule: '@request.auth.id != ""',
581
+ updateRule: '@request.auth.id != ""',
582
+ deleteRule: '@request.auth.id != ""'
583
+ }),
724
584
  /**
725
- * Perform the actual delete operation
585
+ * Locked access - only superusers can perform operations
586
+ * All rules are set to null (locked)
726
587
  */
727
- async entityDelete(id) {
728
- await this.getCollection().delete(id);
729
- return true;
730
- }
588
+ locked: () => ({
589
+ listRule: null,
590
+ viewRule: null,
591
+ createRule: null,
592
+ updateRule: null,
593
+ deleteRule: null
594
+ }),
731
595
  /**
732
- * Error handler for common errors
733
- * @param error The error to handle
734
- * @param options Handler options
735
- * @returns The value to return if the error is handled, or throws if not handled
596
+ * Read-only authenticated - authenticated users can read, no write access
736
597
  */
737
- handleError(error, options = { logError: true }) {
738
- const { allowNotFound = false, returnValue, logError: logError2 = true } = options;
739
- if (logError2) {
740
- console.error(`Error in ${this.constructor.name}:`, error);
598
+ readOnlyAuthenticated: () => ({
599
+ listRule: '@request.auth.id != ""',
600
+ viewRule: '@request.auth.id != ""',
601
+ createRule: null,
602
+ updateRule: null,
603
+ deleteRule: null
604
+ })
605
+ };
606
+ function resolveTemplate(config) {
607
+ let baseRules;
608
+ switch (config.template) {
609
+ case "public":
610
+ baseRules = PermissionTemplates.public();
611
+ break;
612
+ case "authenticated":
613
+ baseRules = PermissionTemplates.authenticated();
614
+ break;
615
+ case "owner-only":
616
+ baseRules = PermissionTemplates.ownerOnly(config.ownerField);
617
+ break;
618
+ case "admin-only":
619
+ baseRules = PermissionTemplates.adminOnly(config.roleField);
620
+ break;
621
+ case "read-public":
622
+ baseRules = PermissionTemplates.readPublic();
623
+ break;
624
+ case "custom":
625
+ baseRules = {};
626
+ break;
627
+ default: {
628
+ const _exhaustive = config.template;
629
+ throw new Error(`Unknown template type: ${_exhaustive}`);
741
630
  }
742
- if (allowNotFound && this.isNotFoundError(error)) {
743
- return null;
631
+ }
632
+ return {
633
+ ...baseRules,
634
+ ...config.customRules
635
+ };
636
+ }
637
+ function isTemplateConfig(config) {
638
+ return "template" in config;
639
+ }
640
+ function isPermissionSchema(config) {
641
+ return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
642
+ }
643
+ function validatePermissionConfig(config, isAuthCollection2 = false) {
644
+ const result = {
645
+ valid: true,
646
+ errors: [],
647
+ warnings: []
648
+ };
649
+ let permissions;
650
+ if (isTemplateConfig(config)) {
651
+ if (config.template === "owner-only" && !config.ownerField) {
652
+ result.warnings.push("owner-only template without ownerField specified - using default 'User'");
744
653
  }
745
- if (returnValue !== void 0) {
746
- return returnValue;
654
+ if (config.template === "admin-only" && !config.roleField) {
655
+ result.warnings.push("admin-only template without roleField specified - using default 'role'");
747
656
  }
748
- throw error;
749
- }
750
- /**
751
- * Check if an error is a "not found" error
752
- */
753
- isNotFoundError(error) {
754
- return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
657
+ permissions = resolveTemplate(config);
658
+ } else {
659
+ permissions = config;
755
660
  }
756
- /**
757
- * Standard error handling wrapper (legacy method, consider using handleError instead)
758
- */
759
- errorWrapper(error) {
760
- console.error(`Error in ${this.constructor.name}:`, error);
761
- throw error;
661
+ if (permissions.manageRule !== void 0 && !isAuthCollection2) {
662
+ result.errors.push("manageRule is only valid for auth collections");
663
+ result.valid = false;
762
664
  }
763
- /**
764
- * Subscribe to changes on a specific record
765
- * @param id The ID of the record to subscribe to
766
- * @param callback Function to call when changes occur
767
- * @param expand Optional expand parameters
768
- * @returns Promise that resolves to an unsubscribe function
769
- */
770
- async subscribeToRecord(id, callback, expand) {
771
- const finalExpand = this.prepareExpand(expand);
772
- const options = finalExpand ? { expand: finalExpand } : {};
773
- return this.getCollection().subscribe(id, callback, options);
665
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
666
+ if (isAuthCollection2) {
667
+ ruleTypes.push("manageRule");
774
668
  }
775
- /**
776
- * Subscribe to changes on the entire collection
777
- * @param callback Function to call when changes occur
778
- * @param expand Optional expand parameters
779
- * @returns Promise that resolves to an unsubscribe function
780
- */
781
- async subscribeToCollection(callback, expand) {
782
- const finalExpand = this.prepareExpand(expand);
783
- const options = finalExpand ? { expand: finalExpand } : {};
784
- return this.getCollection().subscribe("*", callback, options);
669
+ for (const ruleType of ruleTypes) {
670
+ const rule = permissions[ruleType];
671
+ if (rule !== void 0 && rule !== null && rule !== "") {
672
+ const ruleValidation = validateRuleExpression(rule);
673
+ if (!ruleValidation.valid) {
674
+ result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
675
+ result.valid = false;
676
+ }
677
+ result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
678
+ }
785
679
  }
786
- /**
787
- * Unsubscribe from a specific record's changes
788
- * @param id The ID of the record to unsubscribe from
789
- */
790
- unsubscribeFromRecord(id) {
791
- this.getCollection().unsubscribe(id);
680
+ return result;
681
+ }
682
+ function validateRuleExpression(expression) {
683
+ const result = {
684
+ valid: true,
685
+ errors: [],
686
+ warnings: []
687
+ };
688
+ if (expression === null || expression === "") {
689
+ return result;
792
690
  }
793
- /**
794
- * Unsubscribe from collection-wide changes
795
- */
796
- unsubscribeFromCollection() {
797
- this.getCollection().unsubscribe("*");
691
+ let parenCount = 0;
692
+ for (const char of expression) {
693
+ if (char === "(") parenCount++;
694
+ if (char === ")") parenCount--;
695
+ if (parenCount < 0) {
696
+ result.errors.push("Unbalanced parentheses");
697
+ result.valid = false;
698
+ return result;
699
+ }
798
700
  }
799
- /**
800
- * Unsubscribe from all subscriptions in this collection
801
- */
802
- unsubscribeAll() {
803
- this.getCollection().unsubscribe();
701
+ if (parenCount !== 0) {
702
+ result.errors.push("Unbalanced parentheses");
703
+ result.valid = false;
804
704
  }
805
- };
806
-
807
- // src/mutator/userMutator.ts
808
- var UserMutator = class extends BaseMutator {
809
- setDefaults() {
810
- return {
811
- expand: [],
812
- filter: [],
813
- sort: ["-updated"]
814
- };
705
+ if (expression.includes("==")) {
706
+ result.warnings.push("Use '=' instead of '==' for equality comparison");
815
707
  }
816
- getCollection() {
817
- return this.pb.collection("Users");
708
+ const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
709
+ for (const ref of requestRefs) {
710
+ const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
711
+ if (!isValid) {
712
+ result.errors.push(`Invalid @request reference: '${ref}'`);
713
+ result.valid = false;
714
+ }
818
715
  }
819
- async validateInput(input) {
820
- return UserInputSchema.parse(input);
716
+ return result;
717
+ }
718
+ function createPermissions(permissions) {
719
+ return {
720
+ listRule: permissions.listRule ?? null,
721
+ viewRule: permissions.viewRule ?? null,
722
+ createRule: permissions.createRule ?? null,
723
+ updateRule: permissions.updateRule ?? null,
724
+ deleteRule: permissions.deleteRule ?? null,
725
+ manageRule: permissions.manageRule ?? null
726
+ };
727
+ }
728
+ function mergePermissions(...schemas) {
729
+ const merged = {
730
+ listRule: null,
731
+ viewRule: null,
732
+ createRule: null,
733
+ updateRule: null,
734
+ deleteRule: null,
735
+ manageRule: null
736
+ };
737
+ for (const schema of schemas) {
738
+ if (schema.listRule !== void 0) merged.listRule = schema.listRule;
739
+ if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
740
+ if (schema.createRule !== void 0) merged.createRule = schema.createRule;
741
+ if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
742
+ if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
743
+ if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
821
744
  }
822
- };
745
+ return merged;
746
+ }
823
747
 
824
748
  // src/migration/errors.ts
825
749
  var MigrationError = class _MigrationError extends Error {
@@ -4009,6 +3933,55 @@ function generateIndexesArray(indexes) {
4009
3933
  ${indexStrings.join(",\n ")},
4010
3934
  ]`;
4011
3935
  }
3936
+ function getSystemFields() {
3937
+ return [
3938
+ // id field - primary key, auto-generated
3939
+ {
3940
+ name: "id",
3941
+ type: "text",
3942
+ required: true,
3943
+ options: {
3944
+ autogeneratePattern: "[a-z0-9]{15}",
3945
+ hidden: false,
3946
+ id: "text3208210256",
3947
+ max: 15,
3948
+ min: 15,
3949
+ pattern: "^[a-z0-9]+$",
3950
+ presentable: false,
3951
+ primaryKey: true,
3952
+ system: true
3953
+ }
3954
+ },
3955
+ // created field - autodate, set on creation
3956
+ {
3957
+ name: "created",
3958
+ type: "autodate",
3959
+ required: true,
3960
+ options: {
3961
+ hidden: false,
3962
+ id: "autodate2990389176",
3963
+ onCreate: true,
3964
+ onUpdate: false,
3965
+ presentable: false,
3966
+ system: false
3967
+ }
3968
+ },
3969
+ // updated field - autodate, set on creation and update
3970
+ {
3971
+ name: "updated",
3972
+ type: "autodate",
3973
+ required: true,
3974
+ options: {
3975
+ hidden: false,
3976
+ id: "autodate3332085495",
3977
+ onCreate: true,
3978
+ onUpdate: true,
3979
+ presentable: false,
3980
+ system: false
3981
+ }
3982
+ }
3983
+ ];
3984
+ }
4012
3985
  function generateCollectionCreation(collection, varName = "collection", isLast = false) {
4013
3986
  const lines = [];
4014
3987
  lines.push(` const ${varName} = new Collection({`);
@@ -4021,7 +3994,9 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
4021
3994
  } else if (rulesCode) {
4022
3995
  lines.push(` ${rulesCode},`);
4023
3996
  }
4024
- lines.push(` fields: ${generateFieldsArray(collection.fields)},`);
3997
+ const systemFields = getSystemFields();
3998
+ const allFields = [...systemFields, ...collection.fields];
3999
+ lines.push(` fields: ${generateFieldsArray(allFields)},`);
4025
4000
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
4026
4001
  lines.push(` });`);
4027
4002
  lines.push(``);
@@ -5295,6 +5270,7 @@ async function executeStatus(options) {
5295
5270
  }
5296
5271
  }
5297
5272
 
5273
+ exports.BaseMutator = BaseMutator;
5298
5274
  exports.CLIUsageError = CLIUsageError;
5299
5275
  exports.ConfigurationError = ConfigurationError;
5300
5276
  exports.DiffEngine = DiffEngine;
@@ -5305,9 +5281,6 @@ exports.MigrationGenerationError = MigrationGenerationError;
5305
5281
  exports.MigrationGenerator = MigrationGenerator;
5306
5282
  exports.POCKETBASE_FIELD_TYPES = POCKETBASE_FIELD_TYPES;
5307
5283
  exports.PermissionTemplates = PermissionTemplates;
5308
- exports.ProjectCollection = ProjectCollection;
5309
- exports.ProjectInputSchema = ProjectInputSchema;
5310
- exports.ProjectSchema = ProjectSchema;
5311
5284
  exports.RelationField = RelationField;
5312
5285
  exports.RelationsField = RelationsField;
5313
5286
  exports.SchemaAnalyzer = SchemaAnalyzer;
@@ -5315,11 +5288,6 @@ exports.SchemaParsingError = SchemaParsingError;
5315
5288
  exports.SnapshotError = SnapshotError;
5316
5289
  exports.SnapshotManager = SnapshotManager;
5317
5290
  exports.StatusEnum = StatusEnum;
5318
- exports.UserCollection = UserCollection;
5319
- exports.UserCollectionSchema = UserCollectionSchema;
5320
- exports.UserInputSchema = UserInputSchema;
5321
- exports.UserMutator = UserMutator;
5322
- exports.UserSchema = UserSchema;
5323
5291
  exports.aggregateChanges = aggregateChanges;
5324
5292
  exports.baseImageFileSchema = baseImageFileSchema;
5325
5293
  exports.baseSchema = baseSchema;