pocketbase-zod-schema 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +209 -24
- package/dist/cli/index.cjs +34 -0
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -1
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.js +34 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrate.cjs +34 -0
- package/dist/cli/migrate.cjs.map +1 -1
- package/dist/cli/migrate.js +34 -0
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/utils/index.d.cts +3 -1
- package/dist/cli/utils/index.d.ts +3 -1
- package/dist/fields-YjcpBXVp.d.cts +348 -0
- package/dist/fields-YjcpBXVp.d.ts +348 -0
- package/dist/index.cjs +833 -694
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +818 -687
- package/dist/index.js.map +1 -1
- package/dist/migration/analyzer.cjs +34 -0
- package/dist/migration/analyzer.cjs.map +1 -1
- package/dist/migration/analyzer.d.cts +2 -1
- package/dist/migration/analyzer.d.ts +2 -1
- package/dist/migration/analyzer.js +34 -0
- package/dist/migration/analyzer.js.map +1 -1
- package/dist/migration/diff.d.cts +3 -1
- package/dist/migration/diff.d.ts +3 -1
- package/dist/migration/generator.d.cts +3 -1
- package/dist/migration/generator.d.ts +3 -1
- package/dist/migration/index.cjs +36 -0
- package/dist/migration/index.cjs.map +1 -1
- package/dist/migration/index.d.cts +2 -1
- package/dist/migration/index.d.ts +2 -1
- package/dist/migration/index.js +35 -1
- package/dist/migration/index.js.map +1 -1
- package/dist/migration/snapshot.cjs.map +1 -1
- package/dist/migration/snapshot.d.cts +3 -1
- package/dist/migration/snapshot.d.ts +3 -1
- package/dist/migration/snapshot.js.map +1 -1
- package/dist/migration/utils/index.cjs +16 -0
- package/dist/migration/utils/index.cjs.map +1 -1
- package/dist/migration/utils/index.d.cts +2 -2
- package/dist/migration/utils/index.d.ts +2 -2
- package/dist/migration/utils/index.js +15 -1
- package/dist/migration/utils/index.js.map +1 -1
- package/dist/mutator.cjs +2 -98
- package/dist/mutator.cjs.map +1 -1
- package/dist/mutator.d.cts +4 -31
- package/dist/mutator.d.ts +4 -31
- package/dist/mutator.js +2 -98
- package/dist/mutator.js.map +1 -1
- package/dist/schema.cjs +200 -78
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +186 -72
- package/dist/schema.js.map +1 -1
- package/dist/{types-z1Dkjg8m.d.ts → types-LFBGHl9Y.d.ts} +2 -2
- package/dist/{types-BbTgmg6H.d.cts → types-mhQXWNi3.d.cts} +2 -2
- package/package.json +1 -6
- package/dist/types.cjs +0 -4
- package/dist/types.cjs.map +0 -1
- package/dist/types.d.cts +0 -14
- package/dist/types.d.ts +0 -14
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/user-BnFWg5tw.d.cts +0 -161
- package/dist/user-BnFWg5tw.d.ts +0 -161
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,333 @@ var StatusEnum = zod.z.enum([
|
|
|
42
42
|
"fail"
|
|
43
43
|
// Failed project at any stage
|
|
44
44
|
]);
|
|
45
|
+
var BaseMutator = class {
|
|
46
|
+
pb;
|
|
47
|
+
// Define a default property that subclasses will override
|
|
48
|
+
options = {
|
|
49
|
+
expand: [],
|
|
50
|
+
filter: [],
|
|
51
|
+
sort: []
|
|
52
|
+
};
|
|
53
|
+
constructor(pb, options) {
|
|
54
|
+
this.pb = pb;
|
|
55
|
+
this.initializeOptions();
|
|
56
|
+
if (options) {
|
|
57
|
+
this.overrideOptions(options);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
initializeOptions() {
|
|
61
|
+
this.options = this.setDefaults();
|
|
62
|
+
}
|
|
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
|
+
};
|
|
73
|
+
}
|
|
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;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
toSnakeCase(str) {
|
|
89
|
+
return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
90
|
+
}
|
|
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
|
+
}
|
|
102
|
+
}
|
|
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
|
+
}
|
|
113
|
+
}
|
|
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);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get entity by ID
|
|
125
|
+
*/
|
|
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
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get first entity by filter
|
|
136
|
+
*/
|
|
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
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get list of entities
|
|
147
|
+
*/
|
|
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
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Delete entity by ID
|
|
158
|
+
*/
|
|
159
|
+
async delete(id) {
|
|
160
|
+
try {
|
|
161
|
+
return await this.entityDelete(id);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return this.handleError(error, { returnValue: false });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Process a single record before returning it
|
|
168
|
+
* Can be overridden to handle special cases like mapped entities
|
|
169
|
+
*/
|
|
170
|
+
async processRecord(record) {
|
|
171
|
+
return record;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Process a list result before returning it
|
|
175
|
+
* Can be overridden to handle special cases like mapped entities
|
|
176
|
+
*/
|
|
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
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Prepare expand parameter
|
|
186
|
+
* Combines default expands with provided expands
|
|
187
|
+
*/
|
|
188
|
+
prepareExpand(expand) {
|
|
189
|
+
if (!this.options.expand.length && !expand) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
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
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
|
|
201
|
+
if (!uniqueExpands.length) {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
return uniqueExpands.join(",");
|
|
205
|
+
}
|
|
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);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
|
|
223
|
+
if (!validFilters.length) {
|
|
224
|
+
return void 0;
|
|
225
|
+
}
|
|
226
|
+
return validFilters.join("&&");
|
|
227
|
+
}
|
|
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
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Perform the actual create operation
|
|
246
|
+
*/
|
|
247
|
+
async entityCreate(data) {
|
|
248
|
+
return await this.getCollection().create(data);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Perform the actual update operation
|
|
252
|
+
*/
|
|
253
|
+
async entityUpdate(id, data) {
|
|
254
|
+
return await this.getCollection().update(id, data);
|
|
255
|
+
}
|
|
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);
|
|
263
|
+
}
|
|
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);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Perform the actual getList operation
|
|
278
|
+
* Returns a list result with items of type T
|
|
279
|
+
*/
|
|
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);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Perform the actual delete operation
|
|
292
|
+
*/
|
|
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);
|
|
307
|
+
}
|
|
308
|
+
if (allowNotFound && this.isNotFoundError(error)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
if (returnValue !== void 0) {
|
|
312
|
+
return returnValue;
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
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"));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Standard error handling wrapper (legacy method, consider using handleError instead)
|
|
324
|
+
*/
|
|
325
|
+
errorWrapper(error) {
|
|
326
|
+
console.error(`Error in ${this.constructor.name}:`, error);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
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
|
|
335
|
+
*/
|
|
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);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
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
|
|
346
|
+
*/
|
|
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
|
+
};
|
|
45
372
|
var baseSchema = {
|
|
46
373
|
id: zod.z.string().describe("unique id"),
|
|
47
374
|
collectionId: zod.z.string().describe("collection id"),
|
|
@@ -64,762 +391,544 @@ var inputImageFileSchema = {
|
|
|
64
391
|
imageFiles: zod.z.array(zod.z.instanceof(File))
|
|
65
392
|
};
|
|
66
393
|
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
|
-
}
|
|
119
|
-
};
|
|
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
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
let schema = zod.z.array(zod.z.string());
|
|
133
|
-
if (config.minSelect !== void 0) {
|
|
134
|
-
schema = schema.min(config.minSelect);
|
|
135
|
-
}
|
|
136
|
-
if (config.maxSelect !== void 0) {
|
|
137
|
-
schema = schema.max(config.maxSelect);
|
|
138
|
-
}
|
|
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 {
|
|
149
|
-
}
|
|
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 {
|
|
173
|
-
}
|
|
174
|
-
}
|
|
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;
|
|
188
|
-
}
|
|
189
|
-
if (permissions) {
|
|
190
|
-
metadata.permissions = permissions;
|
|
191
|
-
}
|
|
192
|
-
if (indexes) {
|
|
193
|
-
metadata.indexes = indexes;
|
|
194
|
-
}
|
|
195
|
-
if (Object.keys(futureOptions).length > 0) {
|
|
196
|
-
Object.assign(metadata, futureOptions);
|
|
197
|
-
}
|
|
198
|
-
return schema.describe(JSON.stringify(metadata));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/utils/permission-templates.ts
|
|
202
|
-
var PermissionTemplates = {
|
|
203
|
-
/**
|
|
204
|
-
* Public access - anyone can perform all operations
|
|
205
|
-
*/
|
|
206
|
-
public: () => ({
|
|
207
|
-
listRule: "",
|
|
208
|
-
viewRule: "",
|
|
209
|
-
createRule: "",
|
|
210
|
-
updateRule: "",
|
|
211
|
-
deleteRule: ""
|
|
212
|
-
}),
|
|
213
|
-
/**
|
|
214
|
-
* Authenticated users only - requires valid authentication for all operations
|
|
215
|
-
*/
|
|
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
|
-
}),
|
|
223
|
-
/**
|
|
224
|
-
* Owner-only access - users can only manage their own records
|
|
225
|
-
* @param ownerField - Name of the relation field pointing to user (default: 'User')
|
|
226
|
-
*/
|
|
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
|
-
}),
|
|
234
|
-
/**
|
|
235
|
-
* Admin/superuser only access
|
|
236
|
-
* Assumes a 'role' field exists with 'admin' value
|
|
237
|
-
* @param roleField - Name of the role field (default: 'role')
|
|
238
|
-
*/
|
|
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
|
-
}),
|
|
246
|
-
/**
|
|
247
|
-
* Public read, authenticated write
|
|
248
|
-
* Anyone can list/view, but only authenticated users can create/update/delete
|
|
249
|
-
*/
|
|
250
|
-
readPublic: () => ({
|
|
251
|
-
listRule: "",
|
|
252
|
-
viewRule: "",
|
|
253
|
-
createRule: '@request.auth.id != ""',
|
|
254
|
-
updateRule: '@request.auth.id != ""',
|
|
255
|
-
deleteRule: '@request.auth.id != ""'
|
|
256
|
-
}),
|
|
257
|
-
/**
|
|
258
|
-
* Locked access - only superusers can perform operations
|
|
259
|
-
* All rules are set to null (locked)
|
|
260
|
-
*/
|
|
261
|
-
locked: () => ({
|
|
262
|
-
listRule: null,
|
|
263
|
-
viewRule: null,
|
|
264
|
-
createRule: null,
|
|
265
|
-
updateRule: null,
|
|
266
|
-
deleteRule: null
|
|
267
|
-
}),
|
|
268
|
-
/**
|
|
269
|
-
* Read-only authenticated - authenticated users can read, no write access
|
|
270
|
-
*/
|
|
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}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
...baseRules,
|
|
307
|
-
...config.customRules
|
|
308
|
-
};
|
|
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;
|
|
309
402
|
}
|
|
310
|
-
function
|
|
311
|
-
return
|
|
403
|
+
function emailField() {
|
|
404
|
+
return zod.z.string().email();
|
|
312
405
|
}
|
|
313
|
-
function
|
|
314
|
-
return
|
|
406
|
+
function urlField() {
|
|
407
|
+
return zod.z.string().url();
|
|
315
408
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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'");
|
|
326
|
-
}
|
|
327
|
-
if (config.template === "admin-only" && !config.roleField) {
|
|
328
|
-
result.warnings.push("admin-only template without roleField specified - using default 'role'");
|
|
329
|
-
}
|
|
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");
|
|
341
|
-
}
|
|
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;
|
|
349
|
-
}
|
|
350
|
-
result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return result;
|
|
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;
|
|
354
414
|
}
|
|
355
|
-
function
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
445
|
+
}
|
|
360
446
|
};
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return result;
|
|
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
|
|
372
457
|
}
|
|
458
|
+
};
|
|
459
|
+
let schema = zod.z.array(zod.z.string());
|
|
460
|
+
if (config.minSelect !== void 0) {
|
|
461
|
+
schema = schema.min(config.minSelect);
|
|
373
462
|
}
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
result.valid = false;
|
|
377
|
-
}
|
|
378
|
-
if (expression.includes("==")) {
|
|
379
|
-
result.warnings.push("Use '=' instead of '==' for equality comparison");
|
|
463
|
+
if (config.maxSelect !== void 0) {
|
|
464
|
+
schema = schema.max(config.maxSelect);
|
|
380
465
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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];
|
|
387
474
|
}
|
|
475
|
+
} catch {
|
|
388
476
|
}
|
|
389
|
-
return
|
|
477
|
+
return null;
|
|
390
478
|
}
|
|
391
|
-
function
|
|
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
|
-
};
|
|
479
|
+
function editorField() {
|
|
480
|
+
return zod.z.string();
|
|
400
481
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
409
491
|
};
|
|
410
|
-
|
|
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;
|
|
417
|
-
}
|
|
418
|
-
return merged;
|
|
492
|
+
return schema.describe(JSON.stringify(metadata));
|
|
419
493
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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)'
|
|
494
|
+
function withIndexes(schema, indexes) {
|
|
495
|
+
let existingMetadata = {};
|
|
496
|
+
if (schema.description) {
|
|
497
|
+
try {
|
|
498
|
+
existingMetadata = JSON.parse(schema.description);
|
|
499
|
+
} catch {
|
|
439
500
|
}
|
|
440
501
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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: []
|
|
502
|
+
const metadata = {
|
|
503
|
+
...existingMetadata,
|
|
504
|
+
indexes
|
|
486
505
|
};
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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;
|
|
493
515
|
}
|
|
494
|
-
|
|
495
|
-
|
|
516
|
+
if (permissions) {
|
|
517
|
+
metadata.permissions = permissions;
|
|
496
518
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
* Subclasses should override this instead of directly setting options
|
|
500
|
-
*/
|
|
501
|
-
setDefaults() {
|
|
502
|
-
return {
|
|
503
|
-
expand: [],
|
|
504
|
-
filter: [],
|
|
505
|
-
sort: []
|
|
506
|
-
};
|
|
519
|
+
if (indexes) {
|
|
520
|
+
metadata.indexes = indexes;
|
|
507
521
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
522
|
+
if (Object.keys(futureOptions).length > 0) {
|
|
523
|
+
Object.assign(metadata, futureOptions);
|
|
524
|
+
}
|
|
525
|
+
return schema.describe(JSON.stringify(metadata));
|
|
526
|
+
}
|
|
527
|
+
var FIELD_METADATA_KEY = "__pocketbase_field__";
|
|
528
|
+
function extractFieldMetadata(description) {
|
|
529
|
+
if (!description) return null;
|
|
530
|
+
try {
|
|
531
|
+
const parsed = JSON.parse(description);
|
|
532
|
+
if (parsed[FIELD_METADATA_KEY]) {
|
|
533
|
+
return parsed[FIELD_METADATA_KEY];
|
|
520
534
|
}
|
|
535
|
+
} catch {
|
|
521
536
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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);
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
function BoolField() {
|
|
540
|
+
const metadata = {
|
|
541
|
+
[FIELD_METADATA_KEY]: {
|
|
542
|
+
type: "bool"
|
|
535
543
|
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
return await this.processRecord(record);
|
|
544
|
-
} catch (error) {
|
|
545
|
-
return this.errorWrapper(error);
|
|
544
|
+
};
|
|
545
|
+
return zod.z.boolean().describe(JSON.stringify(metadata));
|
|
546
|
+
}
|
|
547
|
+
function NumberField(options) {
|
|
548
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
549
|
+
if (options.min > options.max) {
|
|
550
|
+
throw new Error("NumberField: min cannot be greater than max");
|
|
546
551
|
}
|
|
547
552
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
async upsert(input) {
|
|
552
|
-
if (input?.id) {
|
|
553
|
-
return await this.update(input.id, input);
|
|
554
|
-
}
|
|
555
|
-
return await this.create(input);
|
|
553
|
+
let schema = zod.z.number();
|
|
554
|
+
if (options?.min !== void 0) {
|
|
555
|
+
schema = schema.min(options.min);
|
|
556
556
|
}
|
|
557
|
-
|
|
558
|
-
|
|
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 });
|
|
566
|
-
}
|
|
557
|
+
if (options?.max !== void 0) {
|
|
558
|
+
schema = schema.max(options.max);
|
|
567
559
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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 });
|
|
560
|
+
const metadata = {
|
|
561
|
+
[FIELD_METADATA_KEY]: {
|
|
562
|
+
type: "number",
|
|
563
|
+
options: options || {}
|
|
577
564
|
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return await this.processListResult(result);
|
|
586
|
-
} catch (error) {
|
|
587
|
-
return this.errorWrapper(error);
|
|
565
|
+
};
|
|
566
|
+
return schema.describe(JSON.stringify(metadata));
|
|
567
|
+
}
|
|
568
|
+
function TextField(options) {
|
|
569
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
570
|
+
if (options.min > options.max) {
|
|
571
|
+
throw new Error("TextField: min cannot be greater than max");
|
|
588
572
|
}
|
|
589
573
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
async delete(id) {
|
|
594
|
-
try {
|
|
595
|
-
return await this.entityDelete(id);
|
|
596
|
-
} catch (error) {
|
|
597
|
-
return this.handleError(error, { returnValue: false });
|
|
598
|
-
}
|
|
574
|
+
let schema = zod.z.string();
|
|
575
|
+
if (options?.min !== void 0) {
|
|
576
|
+
schema = schema.min(options.min);
|
|
599
577
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
* Can be overridden to handle special cases like mapped entities
|
|
603
|
-
*/
|
|
604
|
-
async processRecord(record) {
|
|
605
|
-
return record;
|
|
578
|
+
if (options?.max !== void 0) {
|
|
579
|
+
schema = schema.max(options.max);
|
|
606
580
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
};
|
|
581
|
+
if (options?.pattern !== void 0) {
|
|
582
|
+
const pattern = options.pattern instanceof RegExp ? options.pattern : new RegExp(options.pattern);
|
|
583
|
+
schema = schema.regex(pattern);
|
|
617
584
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
prepareExpand(expand) {
|
|
623
|
-
if (!this.options.expand.length && !expand) {
|
|
624
|
-
return void 0;
|
|
585
|
+
const metadata = {
|
|
586
|
+
[FIELD_METADATA_KEY]: {
|
|
587
|
+
type: "text",
|
|
588
|
+
options: options || {}
|
|
625
589
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
590
|
+
};
|
|
591
|
+
return schema.describe(JSON.stringify(metadata));
|
|
592
|
+
}
|
|
593
|
+
function EmailField() {
|
|
594
|
+
const metadata = {
|
|
595
|
+
[FIELD_METADATA_KEY]: {
|
|
596
|
+
type: "email"
|
|
633
597
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
598
|
+
};
|
|
599
|
+
return zod.z.string().email().describe(JSON.stringify(metadata));
|
|
600
|
+
}
|
|
601
|
+
function URLField() {
|
|
602
|
+
const metadata = {
|
|
603
|
+
[FIELD_METADATA_KEY]: {
|
|
604
|
+
type: "url"
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
return zod.z.string().url().describe(JSON.stringify(metadata));
|
|
608
|
+
}
|
|
609
|
+
function EditorField() {
|
|
610
|
+
const metadata = {
|
|
611
|
+
[FIELD_METADATA_KEY]: {
|
|
612
|
+
type: "editor"
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
return zod.z.string().describe(JSON.stringify(metadata));
|
|
616
|
+
}
|
|
617
|
+
function DateField(options) {
|
|
618
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
619
|
+
const minDate = typeof options.min === "string" ? new Date(options.min) : options.min;
|
|
620
|
+
const maxDate = typeof options.max === "string" ? new Date(options.max) : options.max;
|
|
621
|
+
if (minDate > maxDate) {
|
|
622
|
+
throw new Error("DateField: min cannot be greater than max");
|
|
637
623
|
}
|
|
638
|
-
return uniqueExpands.join(",");
|
|
639
624
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (!this.options.filter.length && !filter) {
|
|
646
|
-
return void 0;
|
|
625
|
+
const schema = zod.z.string();
|
|
626
|
+
const metadata = {
|
|
627
|
+
[FIELD_METADATA_KEY]: {
|
|
628
|
+
type: "date",
|
|
629
|
+
options: options || {}
|
|
647
630
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
631
|
+
};
|
|
632
|
+
return schema.describe(JSON.stringify(metadata));
|
|
633
|
+
}
|
|
634
|
+
function AutodateField(options) {
|
|
635
|
+
const schema = zod.z.string();
|
|
636
|
+
const metadata = {
|
|
637
|
+
[FIELD_METADATA_KEY]: {
|
|
638
|
+
type: "autodate",
|
|
639
|
+
options: options || {}
|
|
655
640
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
641
|
+
};
|
|
642
|
+
return schema.describe(JSON.stringify(metadata));
|
|
643
|
+
}
|
|
644
|
+
function SelectField(values, options) {
|
|
645
|
+
const enumSchema = zod.z.enum(values);
|
|
646
|
+
const metadata = {
|
|
647
|
+
[FIELD_METADATA_KEY]: {
|
|
648
|
+
type: "select",
|
|
649
|
+
options: {
|
|
650
|
+
values,
|
|
651
|
+
maxSelect: options?.maxSelect ?? 1
|
|
652
|
+
}
|
|
659
653
|
}
|
|
660
|
-
|
|
654
|
+
};
|
|
655
|
+
if (options?.maxSelect && options.maxSelect > 1) {
|
|
656
|
+
return zod.z.array(enumSchema).describe(JSON.stringify(metadata));
|
|
661
657
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
658
|
+
return enumSchema.describe(JSON.stringify(metadata));
|
|
659
|
+
}
|
|
660
|
+
function FileField(options) {
|
|
661
|
+
const schema = zod.z.instanceof(File);
|
|
662
|
+
const metadata = {
|
|
663
|
+
[FIELD_METADATA_KEY]: {
|
|
664
|
+
type: "file",
|
|
665
|
+
options: options || {}
|
|
669
666
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
667
|
+
};
|
|
668
|
+
return schema.describe(JSON.stringify(metadata));
|
|
669
|
+
}
|
|
670
|
+
function FilesField(options) {
|
|
671
|
+
if (options?.minSelect !== void 0 && options?.maxSelect !== void 0) {
|
|
672
|
+
if (options.minSelect > options.maxSelect) {
|
|
673
|
+
throw new Error("FilesField: minSelect cannot be greater than maxSelect");
|
|
675
674
|
}
|
|
676
|
-
return void 0;
|
|
677
675
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
676
|
+
let schema = zod.z.array(zod.z.instanceof(File));
|
|
677
|
+
if (options?.minSelect !== void 0) {
|
|
678
|
+
schema = schema.min(options.minSelect);
|
|
679
|
+
}
|
|
680
|
+
if (options?.maxSelect !== void 0) {
|
|
681
|
+
schema = schema.max(options.maxSelect);
|
|
683
682
|
}
|
|
683
|
+
const metadata = {
|
|
684
|
+
[FIELD_METADATA_KEY]: {
|
|
685
|
+
type: "file",
|
|
686
|
+
options: options || {}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
return schema.describe(JSON.stringify(metadata));
|
|
690
|
+
}
|
|
691
|
+
function JSONField(schema) {
|
|
692
|
+
const baseSchema2 = schema ?? zod.z.record(zod.z.string(), zod.z.any());
|
|
693
|
+
const metadata = {
|
|
694
|
+
[FIELD_METADATA_KEY]: {
|
|
695
|
+
type: "json"
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
return baseSchema2.describe(JSON.stringify(metadata));
|
|
699
|
+
}
|
|
700
|
+
function GeoPointField() {
|
|
701
|
+
const schema = zod.z.object({
|
|
702
|
+
lon: zod.z.number(),
|
|
703
|
+
lat: zod.z.number()
|
|
704
|
+
});
|
|
705
|
+
const metadata = {
|
|
706
|
+
[FIELD_METADATA_KEY]: {
|
|
707
|
+
type: "geoPoint"
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
return schema.describe(JSON.stringify(metadata));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/utils/permission-templates.ts
|
|
714
|
+
var PermissionTemplates = {
|
|
684
715
|
/**
|
|
685
|
-
*
|
|
716
|
+
* Public access - anyone can perform all operations
|
|
686
717
|
*/
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
718
|
+
public: () => ({
|
|
719
|
+
listRule: "",
|
|
720
|
+
viewRule: "",
|
|
721
|
+
createRule: "",
|
|
722
|
+
updateRule: "",
|
|
723
|
+
deleteRule: ""
|
|
724
|
+
}),
|
|
690
725
|
/**
|
|
691
|
-
*
|
|
726
|
+
* Authenticated users only - requires valid authentication for all operations
|
|
692
727
|
*/
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
728
|
+
authenticated: () => ({
|
|
729
|
+
listRule: '@request.auth.id != ""',
|
|
730
|
+
viewRule: '@request.auth.id != ""',
|
|
731
|
+
createRule: '@request.auth.id != ""',
|
|
732
|
+
updateRule: '@request.auth.id != ""',
|
|
733
|
+
deleteRule: '@request.auth.id != ""'
|
|
734
|
+
}),
|
|
698
735
|
/**
|
|
699
|
-
*
|
|
736
|
+
* Owner-only access - users can only manage their own records
|
|
737
|
+
* @param ownerField - Name of the relation field pointing to user (default: 'User')
|
|
700
738
|
*/
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
739
|
+
ownerOnly: (ownerField = "User") => ({
|
|
740
|
+
listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
741
|
+
viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
742
|
+
createRule: '@request.auth.id != ""',
|
|
743
|
+
updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
744
|
+
deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
|
|
745
|
+
}),
|
|
746
|
+
/**
|
|
747
|
+
* Admin/superuser only access
|
|
748
|
+
* Assumes a 'role' field exists with 'admin' value
|
|
749
|
+
* @param roleField - Name of the role field (default: 'role')
|
|
750
|
+
*/
|
|
751
|
+
adminOnly: (roleField = "role") => ({
|
|
752
|
+
listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
753
|
+
viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
754
|
+
createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
755
|
+
updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
756
|
+
deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
|
|
757
|
+
}),
|
|
710
758
|
/**
|
|
711
|
-
*
|
|
712
|
-
*
|
|
759
|
+
* Public read, authenticated write
|
|
760
|
+
* Anyone can list/view, but only authenticated users can create/update/delete
|
|
713
761
|
*/
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (finalSort) options.sort = finalSort;
|
|
722
|
-
return await this.getCollection().getList(page, perPage, options);
|
|
723
|
-
}
|
|
762
|
+
readPublic: () => ({
|
|
763
|
+
listRule: "",
|
|
764
|
+
viewRule: "",
|
|
765
|
+
createRule: '@request.auth.id != ""',
|
|
766
|
+
updateRule: '@request.auth.id != ""',
|
|
767
|
+
deleteRule: '@request.auth.id != ""'
|
|
768
|
+
}),
|
|
724
769
|
/**
|
|
725
|
-
*
|
|
770
|
+
* Locked access - only superusers can perform operations
|
|
771
|
+
* All rules are set to null (locked)
|
|
726
772
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
773
|
+
locked: () => ({
|
|
774
|
+
listRule: null,
|
|
775
|
+
viewRule: null,
|
|
776
|
+
createRule: null,
|
|
777
|
+
updateRule: null,
|
|
778
|
+
deleteRule: null
|
|
779
|
+
}),
|
|
731
780
|
/**
|
|
732
|
-
*
|
|
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
|
|
781
|
+
* Read-only authenticated - authenticated users can read, no write access
|
|
736
782
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
783
|
+
readOnlyAuthenticated: () => ({
|
|
784
|
+
listRule: '@request.auth.id != ""',
|
|
785
|
+
viewRule: '@request.auth.id != ""',
|
|
786
|
+
createRule: null,
|
|
787
|
+
updateRule: null,
|
|
788
|
+
deleteRule: null
|
|
789
|
+
})
|
|
790
|
+
};
|
|
791
|
+
function resolveTemplate(config) {
|
|
792
|
+
let baseRules;
|
|
793
|
+
switch (config.template) {
|
|
794
|
+
case "public":
|
|
795
|
+
baseRules = PermissionTemplates.public();
|
|
796
|
+
break;
|
|
797
|
+
case "authenticated":
|
|
798
|
+
baseRules = PermissionTemplates.authenticated();
|
|
799
|
+
break;
|
|
800
|
+
case "owner-only":
|
|
801
|
+
baseRules = PermissionTemplates.ownerOnly(config.ownerField);
|
|
802
|
+
break;
|
|
803
|
+
case "admin-only":
|
|
804
|
+
baseRules = PermissionTemplates.adminOnly(config.roleField);
|
|
805
|
+
break;
|
|
806
|
+
case "read-public":
|
|
807
|
+
baseRules = PermissionTemplates.readPublic();
|
|
808
|
+
break;
|
|
809
|
+
case "custom":
|
|
810
|
+
baseRules = {};
|
|
811
|
+
break;
|
|
812
|
+
default: {
|
|
813
|
+
const _exhaustive = config.template;
|
|
814
|
+
throw new Error(`Unknown template type: ${_exhaustive}`);
|
|
741
815
|
}
|
|
742
|
-
|
|
743
|
-
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
...baseRules,
|
|
819
|
+
...config.customRules
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function isTemplateConfig(config) {
|
|
823
|
+
return "template" in config;
|
|
824
|
+
}
|
|
825
|
+
function isPermissionSchema(config) {
|
|
826
|
+
return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
|
|
827
|
+
}
|
|
828
|
+
function validatePermissionConfig(config, isAuthCollection2 = false) {
|
|
829
|
+
const result = {
|
|
830
|
+
valid: true,
|
|
831
|
+
errors: [],
|
|
832
|
+
warnings: []
|
|
833
|
+
};
|
|
834
|
+
let permissions;
|
|
835
|
+
if (isTemplateConfig(config)) {
|
|
836
|
+
if (config.template === "owner-only" && !config.ownerField) {
|
|
837
|
+
result.warnings.push("owner-only template without ownerField specified - using default 'User'");
|
|
744
838
|
}
|
|
745
|
-
if (
|
|
746
|
-
|
|
839
|
+
if (config.template === "admin-only" && !config.roleField) {
|
|
840
|
+
result.warnings.push("admin-only template without roleField specified - using default 'role'");
|
|
747
841
|
}
|
|
748
|
-
|
|
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"));
|
|
842
|
+
permissions = resolveTemplate(config);
|
|
843
|
+
} else {
|
|
844
|
+
permissions = config;
|
|
755
845
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
errorWrapper(error) {
|
|
760
|
-
console.error(`Error in ${this.constructor.name}:`, error);
|
|
761
|
-
throw error;
|
|
846
|
+
if (permissions.manageRule !== void 0 && !isAuthCollection2) {
|
|
847
|
+
result.errors.push("manageRule is only valid for auth collections");
|
|
848
|
+
result.valid = false;
|
|
762
849
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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);
|
|
850
|
+
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
|
|
851
|
+
if (isAuthCollection2) {
|
|
852
|
+
ruleTypes.push("manageRule");
|
|
774
853
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
854
|
+
for (const ruleType of ruleTypes) {
|
|
855
|
+
const rule = permissions[ruleType];
|
|
856
|
+
if (rule !== void 0 && rule !== null && rule !== "") {
|
|
857
|
+
const ruleValidation = validateRuleExpression(rule);
|
|
858
|
+
if (!ruleValidation.valid) {
|
|
859
|
+
result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
|
|
860
|
+
result.valid = false;
|
|
861
|
+
}
|
|
862
|
+
result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
|
|
863
|
+
}
|
|
785
864
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
function validateRuleExpression(expression) {
|
|
868
|
+
const result = {
|
|
869
|
+
valid: true,
|
|
870
|
+
errors: [],
|
|
871
|
+
warnings: []
|
|
872
|
+
};
|
|
873
|
+
if (expression === null || expression === "") {
|
|
874
|
+
return result;
|
|
792
875
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
876
|
+
let parenCount = 0;
|
|
877
|
+
for (const char of expression) {
|
|
878
|
+
if (char === "(") parenCount++;
|
|
879
|
+
if (char === ")") parenCount--;
|
|
880
|
+
if (parenCount < 0) {
|
|
881
|
+
result.errors.push("Unbalanced parentheses");
|
|
882
|
+
result.valid = false;
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
798
885
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
unsubscribeAll() {
|
|
803
|
-
this.getCollection().unsubscribe();
|
|
886
|
+
if (parenCount !== 0) {
|
|
887
|
+
result.errors.push("Unbalanced parentheses");
|
|
888
|
+
result.valid = false;
|
|
804
889
|
}
|
|
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
|
-
};
|
|
890
|
+
if (expression.includes("==")) {
|
|
891
|
+
result.warnings.push("Use '=' instead of '==' for equality comparison");
|
|
815
892
|
}
|
|
816
|
-
|
|
817
|
-
|
|
893
|
+
const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
|
|
894
|
+
for (const ref of requestRefs) {
|
|
895
|
+
const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
|
|
896
|
+
if (!isValid) {
|
|
897
|
+
result.errors.push(`Invalid @request reference: '${ref}'`);
|
|
898
|
+
result.valid = false;
|
|
899
|
+
}
|
|
818
900
|
}
|
|
819
|
-
|
|
820
|
-
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
903
|
+
function createPermissions(permissions) {
|
|
904
|
+
return {
|
|
905
|
+
listRule: permissions.listRule ?? null,
|
|
906
|
+
viewRule: permissions.viewRule ?? null,
|
|
907
|
+
createRule: permissions.createRule ?? null,
|
|
908
|
+
updateRule: permissions.updateRule ?? null,
|
|
909
|
+
deleteRule: permissions.deleteRule ?? null,
|
|
910
|
+
manageRule: permissions.manageRule ?? null
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
function mergePermissions(...schemas) {
|
|
914
|
+
const merged = {
|
|
915
|
+
listRule: null,
|
|
916
|
+
viewRule: null,
|
|
917
|
+
createRule: null,
|
|
918
|
+
updateRule: null,
|
|
919
|
+
deleteRule: null,
|
|
920
|
+
manageRule: null
|
|
921
|
+
};
|
|
922
|
+
for (const schema of schemas) {
|
|
923
|
+
if (schema.listRule !== void 0) merged.listRule = schema.listRule;
|
|
924
|
+
if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
|
|
925
|
+
if (schema.createRule !== void 0) merged.createRule = schema.createRule;
|
|
926
|
+
if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
|
|
927
|
+
if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
|
|
928
|
+
if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
|
|
821
929
|
}
|
|
822
|
-
|
|
930
|
+
return merged;
|
|
931
|
+
}
|
|
823
932
|
|
|
824
933
|
// src/migration/errors.ts
|
|
825
934
|
var MigrationError = class _MigrationError extends Error {
|
|
@@ -2220,6 +2329,28 @@ function isAuthCollection(fields) {
|
|
|
2220
2329
|
return hasEmail && hasPassword;
|
|
2221
2330
|
}
|
|
2222
2331
|
function buildFieldDefinition(fieldName, zodType) {
|
|
2332
|
+
const fieldMetadata = extractFieldMetadata(zodType.description);
|
|
2333
|
+
if (fieldMetadata) {
|
|
2334
|
+
const required2 = isFieldRequired(zodType);
|
|
2335
|
+
const fieldDef2 = {
|
|
2336
|
+
name: fieldName,
|
|
2337
|
+
type: fieldMetadata.type,
|
|
2338
|
+
required: required2,
|
|
2339
|
+
options: fieldMetadata.options
|
|
2340
|
+
};
|
|
2341
|
+
if (fieldMetadata.type === "relation") {
|
|
2342
|
+
const relationMetadata2 = extractRelationMetadata(zodType.description);
|
|
2343
|
+
if (relationMetadata2) {
|
|
2344
|
+
fieldDef2.relation = {
|
|
2345
|
+
collection: relationMetadata2.collection,
|
|
2346
|
+
maxSelect: relationMetadata2.maxSelect,
|
|
2347
|
+
minSelect: relationMetadata2.minSelect,
|
|
2348
|
+
cascadeDelete: relationMetadata2.cascadeDelete
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return fieldDef2;
|
|
2353
|
+
}
|
|
2223
2354
|
const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
|
|
2224
2355
|
const required = isFieldRequired(zodType);
|
|
2225
2356
|
const options = extractFieldOptions(zodType);
|
|
@@ -5346,31 +5477,38 @@ async function executeStatus(options) {
|
|
|
5346
5477
|
}
|
|
5347
5478
|
}
|
|
5348
5479
|
|
|
5480
|
+
exports.AutodateField = AutodateField;
|
|
5481
|
+
exports.BaseMutator = BaseMutator;
|
|
5482
|
+
exports.BoolField = BoolField;
|
|
5349
5483
|
exports.CLIUsageError = CLIUsageError;
|
|
5350
5484
|
exports.ConfigurationError = ConfigurationError;
|
|
5485
|
+
exports.DateField = DateField;
|
|
5351
5486
|
exports.DiffEngine = DiffEngine;
|
|
5487
|
+
exports.EditorField = EditorField;
|
|
5488
|
+
exports.EmailField = EmailField;
|
|
5489
|
+
exports.FIELD_METADATA_KEY = FIELD_METADATA_KEY;
|
|
5352
5490
|
exports.FIELD_TYPE_INFO = FIELD_TYPE_INFO;
|
|
5491
|
+
exports.FileField = FileField;
|
|
5353
5492
|
exports.FileSystemError = FileSystemError;
|
|
5493
|
+
exports.FilesField = FilesField;
|
|
5494
|
+
exports.GeoPointField = GeoPointField;
|
|
5495
|
+
exports.JSONField = JSONField;
|
|
5354
5496
|
exports.MigrationError = MigrationError;
|
|
5355
5497
|
exports.MigrationGenerationError = MigrationGenerationError;
|
|
5356
5498
|
exports.MigrationGenerator = MigrationGenerator;
|
|
5499
|
+
exports.NumberField = NumberField;
|
|
5357
5500
|
exports.POCKETBASE_FIELD_TYPES = POCKETBASE_FIELD_TYPES;
|
|
5358
5501
|
exports.PermissionTemplates = PermissionTemplates;
|
|
5359
|
-
exports.ProjectCollection = ProjectCollection;
|
|
5360
|
-
exports.ProjectInputSchema = ProjectInputSchema;
|
|
5361
|
-
exports.ProjectSchema = ProjectSchema;
|
|
5362
5502
|
exports.RelationField = RelationField;
|
|
5363
5503
|
exports.RelationsField = RelationsField;
|
|
5364
5504
|
exports.SchemaAnalyzer = SchemaAnalyzer;
|
|
5365
5505
|
exports.SchemaParsingError = SchemaParsingError;
|
|
5506
|
+
exports.SelectField = SelectField;
|
|
5366
5507
|
exports.SnapshotError = SnapshotError;
|
|
5367
5508
|
exports.SnapshotManager = SnapshotManager;
|
|
5368
5509
|
exports.StatusEnum = StatusEnum;
|
|
5369
|
-
exports.
|
|
5370
|
-
exports.
|
|
5371
|
-
exports.UserInputSchema = UserInputSchema;
|
|
5372
|
-
exports.UserMutator = UserMutator;
|
|
5373
|
-
exports.UserSchema = UserSchema;
|
|
5510
|
+
exports.TextField = TextField;
|
|
5511
|
+
exports.URLField = URLField;
|
|
5374
5512
|
exports.aggregateChanges = aggregateChanges;
|
|
5375
5513
|
exports.baseImageFileSchema = baseImageFileSchema;
|
|
5376
5514
|
exports.baseSchema = baseSchema;
|
|
@@ -5398,6 +5536,7 @@ exports.editorField = editorField;
|
|
|
5398
5536
|
exports.emailField = emailField;
|
|
5399
5537
|
exports.extractComprehensiveFieldOptions = extractComprehensiveFieldOptions;
|
|
5400
5538
|
exports.extractFieldDefinitions = extractFieldDefinitions;
|
|
5539
|
+
exports.extractFieldMetadata = extractFieldMetadata;
|
|
5401
5540
|
exports.extractFieldOptions = extractFieldOptions;
|
|
5402
5541
|
exports.extractIndexes = extractIndexes;
|
|
5403
5542
|
exports.extractRelationMetadata = extractRelationMetadata;
|