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.js CHANGED
@@ -15,784 +15,708 @@ 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
- }
18
+ var BaseMutator = class {
19
+ pb;
20
+ // Define a default property that subclasses will override
21
+ options = {
22
+ expand: [],
23
+ filter: [],
24
+ sort: []
92
25
  };
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
26
+ constructor(pb, options) {
27
+ this.pb = pb;
28
+ this.initializeOptions();
29
+ if (options) {
30
+ this.overrideOptions(options);
103
31
  }
104
- };
105
- let schema = z.array(z.string());
106
- if (config.minSelect !== void 0) {
107
- schema = schema.min(config.minSelect);
108
32
  }
109
- if (config.maxSelect !== void 0) {
110
- schema = schema.max(config.maxSelect);
33
+ initializeOptions() {
34
+ this.options = this.setDefaults();
111
35
  }
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 {
36
+ /**
37
+ * Initialize options with class-specific defaults
38
+ * Subclasses should override this instead of directly setting options
39
+ */
40
+ setDefaults() {
41
+ return {
42
+ expand: [],
43
+ filter: [],
44
+ sort: []
45
+ };
122
46
  }
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 {
47
+ /**
48
+ * Merge provided options with current options
49
+ */
50
+ overrideOptions(newOptions) {
51
+ if (newOptions.expand !== void 0) {
52
+ this.options.expand = newOptions.expand;
53
+ }
54
+ if (newOptions.filter !== void 0) {
55
+ this.options.filter = newOptions.filter;
56
+ }
57
+ if (newOptions.sort !== void 0) {
58
+ this.options.sort = newOptions.sort;
146
59
  }
147
60
  }
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;
61
+ toSnakeCase(str) {
62
+ return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
161
63
  }
162
- if (permissions) {
163
- metadata.permissions = permissions;
64
+ /**
65
+ * Create a new entity
66
+ */
67
+ async create(input) {
68
+ try {
69
+ const data = await this.validateInput(input);
70
+ const record = await this.entityCreate(data);
71
+ return await this.processRecord(record);
72
+ } catch (error) {
73
+ return this.errorWrapper(error);
74
+ }
164
75
  }
165
- if (indexes) {
166
- metadata.indexes = indexes;
76
+ /**
77
+ * Update an existing entity
78
+ */
79
+ async update(id, input) {
80
+ try {
81
+ const record = await this.entityUpdate(id, input);
82
+ return await this.processRecord(record);
83
+ } catch (error) {
84
+ return this.errorWrapper(error);
85
+ }
167
86
  }
168
- if (Object.keys(futureOptions).length > 0) {
169
- Object.assign(metadata, futureOptions);
87
+ /**
88
+ * Create or update entity (upsert)
89
+ */
90
+ async upsert(input) {
91
+ if (input?.id) {
92
+ return await this.update(input.id, input);
93
+ }
94
+ return await this.create(input);
170
95
  }
171
- return schema.describe(JSON.stringify(metadata));
172
- }
173
-
174
- // src/utils/permission-templates.ts
175
- var PermissionTemplates = {
176
96
  /**
177
- * Public access - anyone can perform all operations
97
+ * Get entity by ID
178
98
  */
179
- public: () => ({
180
- listRule: "",
181
- viewRule: "",
182
- createRule: "",
183
- updateRule: "",
184
- deleteRule: ""
185
- }),
99
+ async getById(id, expand) {
100
+ try {
101
+ const record = await this.entityGetById(id, expand);
102
+ return await this.processRecord(record);
103
+ } catch (error) {
104
+ return this.handleError(error, { allowNotFound: true });
105
+ }
106
+ }
186
107
  /**
187
- * Authenticated users only - requires valid authentication for all operations
108
+ * Get first entity by filter
188
109
  */
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
- }),
110
+ async getFirstByFilter(filter, expand, sort) {
111
+ try {
112
+ const record = await this.entityGetFirstByFilter(filter, expand, sort);
113
+ return await this.processRecord(record);
114
+ } catch (error) {
115
+ return this.handleError(error, { allowNotFound: true });
116
+ }
117
+ }
196
118
  /**
197
- * Owner-only access - users can only manage their own records
198
- * @param ownerField - Name of the relation field pointing to user (default: 'User')
119
+ * Get list of entities
199
120
  */
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
- }),
121
+ async getList(page = 1, perPage = 100, filter, sort, expand) {
122
+ try {
123
+ const result = await this.entityGetList(page, perPage, filter, sort, expand);
124
+ return await this.processListResult(result);
125
+ } catch (error) {
126
+ return this.errorWrapper(error);
127
+ }
128
+ }
207
129
  /**
208
- * Admin/superuser only access
209
- * Assumes a 'role' field exists with 'admin' value
210
- * @param roleField - Name of the role field (default: 'role')
130
+ * Delete entity by ID
211
131
  */
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
- }),
132
+ async delete(id) {
133
+ try {
134
+ return await this.entityDelete(id);
135
+ } catch (error) {
136
+ return this.handleError(error, { returnValue: false });
137
+ }
138
+ }
219
139
  /**
220
- * Public read, authenticated write
221
- * Anyone can list/view, but only authenticated users can create/update/delete
140
+ * Process a single record before returning it
141
+ * Can be overridden to handle special cases like mapped entities
222
142
  */
223
- readPublic: () => ({
224
- listRule: "",
225
- viewRule: "",
226
- createRule: '@request.auth.id != ""',
227
- updateRule: '@request.auth.id != ""',
228
- deleteRule: '@request.auth.id != ""'
229
- }),
143
+ async processRecord(record) {
144
+ return record;
145
+ }
230
146
  /**
231
- * Locked access - only superusers can perform operations
232
- * All rules are set to null (locked)
147
+ * Process a list result before returning it
148
+ * Can be overridden to handle special cases like mapped entities
233
149
  */
234
- locked: () => ({
235
- listRule: null,
236
- viewRule: null,
237
- createRule: null,
238
- updateRule: null,
239
- deleteRule: null
240
- }),
150
+ async processListResult(result) {
151
+ const processedItems = await Promise.all(result.items.map((item) => this.processRecord(item)));
152
+ return {
153
+ ...result,
154
+ items: processedItems
155
+ };
156
+ }
241
157
  /**
242
- * Read-only authenticated - authenticated users can read, no write access
158
+ * Prepare expand parameter
159
+ * Combines default expands with provided expands
243
160
  */
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}`);
161
+ prepareExpand(expand) {
162
+ if (!this.options.expand.length && !expand) {
163
+ return void 0;
276
164
  }
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'");
165
+ let expandArray = [...this.options.expand];
166
+ if (expand) {
167
+ if (typeof expand === "string") {
168
+ expandArray = expandArray.concat(expand.split(",").map((e) => e.trim()));
169
+ } else {
170
+ expandArray = expandArray.concat(expand);
171
+ }
299
172
  }
300
- if (config.template === "admin-only" && !config.roleField) {
301
- result.warnings.push("admin-only template without roleField specified - using default 'role'");
173
+ const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
174
+ if (!uniqueExpands.length) {
175
+ return void 0;
302
176
  }
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");
177
+ return uniqueExpands.join(",");
314
178
  }
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;
179
+ /**
180
+ * Prepare filter parameter
181
+ * Combines default filters with provided filters
182
+ */
183
+ prepareFilter(filter) {
184
+ if (!this.options.filter.length && !filter) {
185
+ return void 0;
186
+ }
187
+ let filterArray = [...this.options.filter];
188
+ if (filter) {
189
+ if (typeof filter === "string") {
190
+ if (filter) filterArray.push(filter);
191
+ } else {
192
+ filterArray = filterArray.concat(filter);
322
193
  }
323
- result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
324
194
  }
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;
195
+ const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
196
+ if (!validFilters.length) {
197
+ return void 0;
345
198
  }
199
+ return validFilters.join("&&");
346
200
  }
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;
201
+ /**
202
+ * Prepare sort parameter
203
+ * Uses provided sort or falls back to default sort
204
+ */
205
+ prepareSort(sort) {
206
+ if (sort && sort !== "") {
207
+ return sort;
208
+ }
209
+ if (this.options.sort.length) {
210
+ const validSorts = this.options.sort.filter((s) => s !== "" && s !== void 0);
211
+ if (validSorts.length) {
212
+ return validSorts.join(",");
213
+ }
360
214
  }
215
+ return void 0;
361
216
  }
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;
217
+ /**
218
+ * Perform the actual create operation
219
+ */
220
+ async entityCreate(data) {
221
+ return await this.getCollection().create(data);
390
222
  }
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
- }
223
+ /**
224
+ * Perform the actual update operation
225
+ */
226
+ async entityUpdate(id, data) {
227
+ return await this.getCollection().update(id, data);
413
228
  }
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
- var BaseMutator = class {
453
- pb;
454
- // Define a default property that subclasses will override
455
- options = {
456
- expand: [],
457
- filter: [],
458
- sort: []
459
- };
460
- constructor(pb, options) {
461
- this.pb = pb;
462
- this.initializeOptions();
463
- if (options) {
464
- this.overrideOptions(options);
465
- }
229
+ /**
230
+ * Perform the actual getById operation
231
+ */
232
+ async entityGetById(id, expand) {
233
+ const finalExpand = this.prepareExpand(expand);
234
+ const options = finalExpand ? { expand: finalExpand } : {};
235
+ return await this.getCollection().getOne(id, options);
466
236
  }
467
- initializeOptions() {
468
- this.options = this.setDefaults();
237
+ /**
238
+ * Perform the actual getFirstByFilter operation
239
+ */
240
+ async entityGetFirstByFilter(filter, expand, sort) {
241
+ const finalFilter = this.prepareFilter(filter);
242
+ const finalExpand = this.prepareExpand(expand);
243
+ const finalSort = this.prepareSort(sort);
244
+ const options = {};
245
+ if (finalExpand) options.expand = finalExpand;
246
+ if (finalSort) options.sort = finalSort;
247
+ return await this.getCollection().getFirstListItem(finalFilter || "", options);
469
248
  }
470
249
  /**
471
- * Initialize options with class-specific defaults
472
- * Subclasses should override this instead of directly setting options
250
+ * Perform the actual getList operation
251
+ * Returns a list result with items of type T
473
252
  */
474
- setDefaults() {
475
- return {
476
- expand: [],
477
- filter: [],
478
- sort: []
479
- };
253
+ async entityGetList(page, perPage, filter, sort, expand) {
254
+ const finalFilter = this.prepareFilter(filter);
255
+ const finalExpand = this.prepareExpand(expand);
256
+ const finalSort = this.prepareSort(sort);
257
+ const options = {};
258
+ if (finalFilter) options.filter = finalFilter;
259
+ if (finalExpand) options.expand = finalExpand;
260
+ if (finalSort) options.sort = finalSort;
261
+ return await this.getCollection().getList(page, perPage, options);
480
262
  }
481
263
  /**
482
- * Merge provided options with current options
264
+ * Perform the actual delete operation
483
265
  */
484
- overrideOptions(newOptions) {
485
- if (newOptions.expand !== void 0) {
486
- this.options.expand = newOptions.expand;
266
+ async entityDelete(id) {
267
+ await this.getCollection().delete(id);
268
+ return true;
269
+ }
270
+ /**
271
+ * Error handler for common errors
272
+ * @param error The error to handle
273
+ * @param options Handler options
274
+ * @returns The value to return if the error is handled, or throws if not handled
275
+ */
276
+ handleError(error, options = { logError: true }) {
277
+ const { allowNotFound = false, returnValue, logError: logError2 = true } = options;
278
+ if (logError2) {
279
+ console.error(`Error in ${this.constructor.name}:`, error);
487
280
  }
488
- if (newOptions.filter !== void 0) {
489
- this.options.filter = newOptions.filter;
281
+ if (allowNotFound && this.isNotFoundError(error)) {
282
+ return null;
490
283
  }
491
- if (newOptions.sort !== void 0) {
492
- this.options.sort = newOptions.sort;
284
+ if (returnValue !== void 0) {
285
+ return returnValue;
493
286
  }
287
+ throw error;
494
288
  }
495
- toSnakeCase(str) {
496
- return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
289
+ /**
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"));
497
294
  }
498
295
  /**
499
- * Create a new entity
296
+ * Standard error handling wrapper (legacy method, consider using handleError instead)
500
297
  */
501
- async create(input) {
502
- try {
503
- const data = await this.validateInput(input);
504
- const record = await this.entityCreate(data);
505
- return await this.processRecord(record);
506
- } catch (error) {
507
- return this.errorWrapper(error);
508
- }
298
+ errorWrapper(error) {
299
+ console.error(`Error in ${this.constructor.name}:`, error);
300
+ throw error;
509
301
  }
510
302
  /**
511
- * Update an existing entity
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
512
308
  */
513
- async update(id, input) {
514
- try {
515
- const record = await this.entityUpdate(id, input);
516
- return await this.processRecord(record);
517
- } catch (error) {
518
- return this.errorWrapper(error);
519
- }
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);
520
313
  }
521
314
  /**
522
- * Create or update entity (upsert)
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
523
319
  */
524
- async upsert(input) {
525
- if (input?.id) {
526
- return await this.update(input.id, input);
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
527
418
  }
528
- return await this.create(input);
529
- }
530
- /**
531
- * Get entity by ID
532
- */
533
- async getById(id, expand) {
534
- try {
535
- const record = await this.entityGetById(id, expand);
536
- return await this.processRecord(record);
537
- } catch (error) {
538
- return this.handleError(error, { allowNotFound: true });
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
539
430
  }
431
+ };
432
+ let schema = z.array(z.string());
433
+ if (config.minSelect !== void 0) {
434
+ schema = schema.min(config.minSelect);
540
435
  }
541
- /**
542
- * Get first entity by filter
543
- */
544
- async getFirstByFilter(filter, expand, sort) {
545
- try {
546
- const record = await this.entityGetFirstByFilter(filter, expand, sort);
547
- return await this.processRecord(record);
548
- } catch (error) {
549
- return this.handleError(error, { allowNotFound: true });
550
- }
436
+ if (config.maxSelect !== void 0) {
437
+ schema = schema.max(config.maxSelect);
551
438
  }
552
- /**
553
- * Get list of entities
554
- */
555
- async getList(page = 1, perPage = 100, filter, sort, expand) {
556
- try {
557
- const result = await this.entityGetList(page, perPage, filter, sort, expand);
558
- return await this.processListResult(result);
559
- } catch (error) {
560
- return this.errorWrapper(error);
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];
561
447
  }
448
+ } catch {
562
449
  }
563
- /**
564
- * Delete entity by ID
565
- */
566
- async delete(id) {
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) {
567
470
  try {
568
- return await this.entityDelete(id);
569
- } catch (error) {
570
- return this.handleError(error, { returnValue: false });
471
+ existingMetadata = JSON.parse(schema.description);
472
+ } catch {
571
473
  }
572
474
  }
573
- /**
574
- * Process a single record before returning it
575
- * Can be overridden to handle special cases like mapped entities
576
- */
577
- async processRecord(record) {
578
- return record;
579
- }
580
- /**
581
- * Process a list result before returning it
582
- * Can be overridden to handle special cases like mapped entities
583
- */
584
- async processListResult(result) {
585
- const processedItems = await Promise.all(result.items.map((item) => this.processRecord(item)));
586
- return {
587
- ...result,
588
- items: processedItems
589
- };
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;
590
488
  }
591
- /**
592
- * Prepare expand parameter
593
- * Combines default expands with provided expands
594
- */
595
- prepareExpand(expand) {
596
- if (!this.options.expand.length && !expand) {
597
- return void 0;
598
- }
599
- let expandArray = [...this.options.expand];
600
- if (expand) {
601
- if (typeof expand === "string") {
602
- expandArray = expandArray.concat(expand.split(",").map((e) => e.trim()));
603
- } else {
604
- expandArray = expandArray.concat(expand);
605
- }
606
- }
607
- const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
608
- if (!uniqueExpands.length) {
609
- return void 0;
610
- }
611
- return uniqueExpands.join(",");
489
+ if (permissions) {
490
+ metadata.permissions = permissions;
612
491
  }
613
- /**
614
- * Prepare filter parameter
615
- * Combines default filters with provided filters
616
- */
617
- prepareFilter(filter) {
618
- if (!this.options.filter.length && !filter) {
619
- return void 0;
620
- }
621
- let filterArray = [...this.options.filter];
622
- if (filter) {
623
- if (typeof filter === "string") {
624
- if (filter) filterArray.push(filter);
625
- } else {
626
- filterArray = filterArray.concat(filter);
627
- }
628
- }
629
- const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
630
- if (!validFilters.length) {
631
- return void 0;
632
- }
633
- return validFilters.join("&&");
492
+ if (indexes) {
493
+ metadata.indexes = indexes;
634
494
  }
635
- /**
636
- * Prepare sort parameter
637
- * Uses provided sort or falls back to default sort
638
- */
639
- prepareSort(sort) {
640
- if (sort && sort !== "") {
641
- return sort;
642
- }
643
- if (this.options.sort.length) {
644
- const validSorts = this.options.sort.filter((s) => s !== "" && s !== void 0);
645
- if (validSorts.length) {
646
- return validSorts.join(",");
647
- }
648
- }
649
- return void 0;
495
+ if (Object.keys(futureOptions).length > 0) {
496
+ Object.assign(metadata, futureOptions);
650
497
  }
498
+ return schema.describe(JSON.stringify(metadata));
499
+ }
500
+
501
+ // src/utils/permission-templates.ts
502
+ var PermissionTemplates = {
651
503
  /**
652
- * Perform the actual create operation
504
+ * Public access - anyone can perform all operations
653
505
  */
654
- async entityCreate(data) {
655
- return await this.getCollection().create(data);
656
- }
506
+ public: () => ({
507
+ listRule: "",
508
+ viewRule: "",
509
+ createRule: "",
510
+ updateRule: "",
511
+ deleteRule: ""
512
+ }),
657
513
  /**
658
- * Perform the actual update operation
514
+ * Authenticated users only - requires valid authentication for all operations
659
515
  */
660
- async entityUpdate(id, data) {
661
- return await this.getCollection().update(id, data);
662
- }
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
+ }),
663
523
  /**
664
- * Perform the actual getById operation
524
+ * Owner-only access - users can only manage their own records
525
+ * @param ownerField - Name of the relation field pointing to user (default: 'User')
665
526
  */
666
- async entityGetById(id, expand) {
667
- const finalExpand = this.prepareExpand(expand);
668
- const options = finalExpand ? { expand: finalExpand } : {};
669
- return await this.getCollection().getOne(id, options);
670
- }
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
+ }),
671
534
  /**
672
- * Perform the actual getFirstByFilter operation
535
+ * Admin/superuser only access
536
+ * Assumes a 'role' field exists with 'admin' value
537
+ * @param roleField - Name of the role field (default: 'role')
673
538
  */
674
- async entityGetFirstByFilter(filter, expand, sort) {
675
- const finalFilter = this.prepareFilter(filter);
676
- const finalExpand = this.prepareExpand(expand);
677
- const finalSort = this.prepareSort(sort);
678
- const options = {};
679
- if (finalExpand) options.expand = finalExpand;
680
- if (finalSort) options.sort = finalSort;
681
- return await this.getCollection().getFirstListItem(finalFilter || "", options);
682
- }
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
+ }),
683
546
  /**
684
- * Perform the actual getList operation
685
- * Returns a list result with items of type T
547
+ * Public read, authenticated write
548
+ * Anyone can list/view, but only authenticated users can create/update/delete
686
549
  */
687
- async entityGetList(page, perPage, filter, sort, expand) {
688
- const finalFilter = this.prepareFilter(filter);
689
- const finalExpand = this.prepareExpand(expand);
690
- const finalSort = this.prepareSort(sort);
691
- const options = {};
692
- if (finalFilter) options.filter = finalFilter;
693
- if (finalExpand) options.expand = finalExpand;
694
- if (finalSort) options.sort = finalSort;
695
- return await this.getCollection().getList(page, perPage, options);
696
- }
550
+ readPublic: () => ({
551
+ listRule: "",
552
+ viewRule: "",
553
+ createRule: '@request.auth.id != ""',
554
+ updateRule: '@request.auth.id != ""',
555
+ deleteRule: '@request.auth.id != ""'
556
+ }),
697
557
  /**
698
- * Perform the actual delete operation
558
+ * Locked access - only superusers can perform operations
559
+ * All rules are set to null (locked)
699
560
  */
700
- async entityDelete(id) {
701
- await this.getCollection().delete(id);
702
- return true;
703
- }
561
+ locked: () => ({
562
+ listRule: null,
563
+ viewRule: null,
564
+ createRule: null,
565
+ updateRule: null,
566
+ deleteRule: null
567
+ }),
704
568
  /**
705
- * Error handler for common errors
706
- * @param error The error to handle
707
- * @param options Handler options
708
- * @returns The value to return if the error is handled, or throws if not handled
569
+ * Read-only authenticated - authenticated users can read, no write access
709
570
  */
710
- handleError(error, options = { logError: true }) {
711
- const { allowNotFound = false, returnValue, logError: logError2 = true } = options;
712
- if (logError2) {
713
- console.error(`Error in ${this.constructor.name}:`, error);
571
+ readOnlyAuthenticated: () => ({
572
+ listRule: '@request.auth.id != ""',
573
+ viewRule: '@request.auth.id != ""',
574
+ createRule: null,
575
+ updateRule: null,
576
+ deleteRule: null
577
+ })
578
+ };
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}`);
714
603
  }
715
- if (allowNotFound && this.isNotFoundError(error)) {
716
- return null;
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'");
717
626
  }
718
- if (returnValue !== void 0) {
719
- return returnValue;
627
+ if (config.template === "admin-only" && !config.roleField) {
628
+ result.warnings.push("admin-only template without roleField specified - using default 'role'");
720
629
  }
721
- throw error;
722
- }
723
- /**
724
- * Check if an error is a "not found" error
725
- */
726
- isNotFoundError(error) {
727
- return error instanceof Error && (error.message.includes("404") || error.message.toLowerCase().includes("not found"));
630
+ permissions = resolveTemplate(config);
631
+ } else {
632
+ permissions = config;
728
633
  }
729
- /**
730
- * Standard error handling wrapper (legacy method, consider using handleError instead)
731
- */
732
- errorWrapper(error) {
733
- console.error(`Error in ${this.constructor.name}:`, error);
734
- throw error;
634
+ if (permissions.manageRule !== void 0 && !isAuthCollection2) {
635
+ result.errors.push("manageRule is only valid for auth collections");
636
+ result.valid = false;
735
637
  }
736
- /**
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
742
- */
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);
638
+ const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
639
+ if (isAuthCollection2) {
640
+ ruleTypes.push("manageRule");
747
641
  }
748
- /**
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
753
- */
754
- async subscribeToCollection(callback, expand) {
755
- const finalExpand = this.prepareExpand(expand);
756
- const options = finalExpand ? { expand: finalExpand } : {};
757
- return this.getCollection().subscribe("*", callback, options);
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
+ }
758
652
  }
759
- /**
760
- * Unsubscribe from a specific record's changes
761
- * @param id The ID of the record to unsubscribe from
762
- */
763
- unsubscribeFromRecord(id) {
764
- this.getCollection().unsubscribe(id);
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;
765
663
  }
766
- /**
767
- * Unsubscribe from collection-wide changes
768
- */
769
- unsubscribeFromCollection() {
770
- this.getCollection().unsubscribe("*");
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
+ }
771
673
  }
772
- /**
773
- * Unsubscribe from all subscriptions in this collection
774
- */
775
- unsubscribeAll() {
776
- this.getCollection().unsubscribe();
674
+ if (parenCount !== 0) {
675
+ result.errors.push("Unbalanced parentheses");
676
+ result.valid = false;
777
677
  }
778
- };
779
-
780
- // src/mutator/userMutator.ts
781
- var UserMutator = class extends BaseMutator {
782
- setDefaults() {
783
- return {
784
- expand: [],
785
- filter: [],
786
- sort: ["-updated"]
787
- };
678
+ if (expression.includes("==")) {
679
+ result.warnings.push("Use '=' instead of '==' for equality comparison");
788
680
  }
789
- getCollection() {
790
- return this.pb.collection("Users");
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
+ }
791
688
  }
792
- async validateInput(input) {
793
- return UserInputSchema.parse(input);
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;
794
717
  }
795
- };
718
+ return merged;
719
+ }
796
720
 
797
721
  // src/migration/errors.ts
798
722
  var MigrationError = class _MigrationError extends Error {
@@ -3982,6 +3906,55 @@ function generateIndexesArray(indexes) {
3982
3906
  ${indexStrings.join(",\n ")},
3983
3907
  ]`;
3984
3908
  }
3909
+ function getSystemFields() {
3910
+ return [
3911
+ // id field - primary key, auto-generated
3912
+ {
3913
+ name: "id",
3914
+ type: "text",
3915
+ required: true,
3916
+ options: {
3917
+ autogeneratePattern: "[a-z0-9]{15}",
3918
+ hidden: false,
3919
+ id: "text3208210256",
3920
+ max: 15,
3921
+ min: 15,
3922
+ pattern: "^[a-z0-9]+$",
3923
+ presentable: false,
3924
+ primaryKey: true,
3925
+ system: true
3926
+ }
3927
+ },
3928
+ // created field - autodate, set on creation
3929
+ {
3930
+ name: "created",
3931
+ type: "autodate",
3932
+ required: true,
3933
+ options: {
3934
+ hidden: false,
3935
+ id: "autodate2990389176",
3936
+ onCreate: true,
3937
+ onUpdate: false,
3938
+ presentable: false,
3939
+ system: false
3940
+ }
3941
+ },
3942
+ // updated field - autodate, set on creation and update
3943
+ {
3944
+ name: "updated",
3945
+ type: "autodate",
3946
+ required: true,
3947
+ options: {
3948
+ hidden: false,
3949
+ id: "autodate3332085495",
3950
+ onCreate: true,
3951
+ onUpdate: true,
3952
+ presentable: false,
3953
+ system: false
3954
+ }
3955
+ }
3956
+ ];
3957
+ }
3985
3958
  function generateCollectionCreation(collection, varName = "collection", isLast = false) {
3986
3959
  const lines = [];
3987
3960
  lines.push(` const ${varName} = new Collection({`);
@@ -3994,7 +3967,9 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
3994
3967
  } else if (rulesCode) {
3995
3968
  lines.push(` ${rulesCode},`);
3996
3969
  }
3997
- lines.push(` fields: ${generateFieldsArray(collection.fields)},`);
3970
+ const systemFields = getSystemFields();
3971
+ const allFields = [...systemFields, ...collection.fields];
3972
+ lines.push(` fields: ${generateFieldsArray(allFields)},`);
3998
3973
  lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
3999
3974
  lines.push(` });`);
4000
3975
  lines.push(``);
@@ -5268,6 +5243,6 @@ async function executeStatus(options) {
5268
5243
  }
5269
5244
  }
5270
5245
 
5271
- 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 };
5272
5247
  //# sourceMappingURL=index.js.map
5273
5248
  //# sourceMappingURL=index.js.map