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.js
CHANGED
|
@@ -15,6 +15,333 @@ var StatusEnum = z.enum([
|
|
|
15
15
|
"fail"
|
|
16
16
|
// Failed project at any stage
|
|
17
17
|
]);
|
|
18
|
+
var BaseMutator = class {
|
|
19
|
+
pb;
|
|
20
|
+
// Define a default property that subclasses will override
|
|
21
|
+
options = {
|
|
22
|
+
expand: [],
|
|
23
|
+
filter: [],
|
|
24
|
+
sort: []
|
|
25
|
+
};
|
|
26
|
+
constructor(pb, options) {
|
|
27
|
+
this.pb = pb;
|
|
28
|
+
this.initializeOptions();
|
|
29
|
+
if (options) {
|
|
30
|
+
this.overrideOptions(options);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
initializeOptions() {
|
|
34
|
+
this.options = this.setDefaults();
|
|
35
|
+
}
|
|
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
|
+
};
|
|
46
|
+
}
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
toSnakeCase(str) {
|
|
62
|
+
return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
63
|
+
}
|
|
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
|
+
}
|
|
75
|
+
}
|
|
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
|
+
}
|
|
86
|
+
}
|
|
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);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get entity by ID
|
|
98
|
+
*/
|
|
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
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get first entity by filter
|
|
109
|
+
*/
|
|
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
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get list of entities
|
|
120
|
+
*/
|
|
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
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Delete entity by ID
|
|
131
|
+
*/
|
|
132
|
+
async delete(id) {
|
|
133
|
+
try {
|
|
134
|
+
return await this.entityDelete(id);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return this.handleError(error, { returnValue: false });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Process a single record before returning it
|
|
141
|
+
* Can be overridden to handle special cases like mapped entities
|
|
142
|
+
*/
|
|
143
|
+
async processRecord(record) {
|
|
144
|
+
return record;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Process a list result before returning it
|
|
148
|
+
* Can be overridden to handle special cases like mapped entities
|
|
149
|
+
*/
|
|
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
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Prepare expand parameter
|
|
159
|
+
* Combines default expands with provided expands
|
|
160
|
+
*/
|
|
161
|
+
prepareExpand(expand) {
|
|
162
|
+
if (!this.options.expand.length && !expand) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
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
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const uniqueExpands = [...new Set(expandArray)].filter((e) => e !== "" && e !== void 0);
|
|
174
|
+
if (!uniqueExpands.length) {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
return uniqueExpands.join(",");
|
|
178
|
+
}
|
|
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);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const validFilters = filterArray.filter((f) => f !== "" && f !== void 0);
|
|
196
|
+
if (!validFilters.length) {
|
|
197
|
+
return void 0;
|
|
198
|
+
}
|
|
199
|
+
return validFilters.join("&&");
|
|
200
|
+
}
|
|
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
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Perform the actual create operation
|
|
219
|
+
*/
|
|
220
|
+
async entityCreate(data) {
|
|
221
|
+
return await this.getCollection().create(data);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Perform the actual update operation
|
|
225
|
+
*/
|
|
226
|
+
async entityUpdate(id, data) {
|
|
227
|
+
return await this.getCollection().update(id, data);
|
|
228
|
+
}
|
|
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);
|
|
236
|
+
}
|
|
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);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Perform the actual getList operation
|
|
251
|
+
* Returns a list result with items of type T
|
|
252
|
+
*/
|
|
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);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Perform the actual delete operation
|
|
265
|
+
*/
|
|
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);
|
|
280
|
+
}
|
|
281
|
+
if (allowNotFound && this.isNotFoundError(error)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
if (returnValue !== void 0) {
|
|
285
|
+
return returnValue;
|
|
286
|
+
}
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
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"));
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Standard error handling wrapper (legacy method, consider using handleError instead)
|
|
297
|
+
*/
|
|
298
|
+
errorWrapper(error) {
|
|
299
|
+
console.error(`Error in ${this.constructor.name}:`, error);
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Subscribe to changes on a specific record
|
|
304
|
+
* @param id The ID of the record to subscribe to
|
|
305
|
+
* @param callback Function to call when changes occur
|
|
306
|
+
* @param expand Optional expand parameters
|
|
307
|
+
* @returns Promise that resolves to an unsubscribe function
|
|
308
|
+
*/
|
|
309
|
+
async subscribeToRecord(id, callback, expand) {
|
|
310
|
+
const finalExpand = this.prepareExpand(expand);
|
|
311
|
+
const options = finalExpand ? { expand: finalExpand } : {};
|
|
312
|
+
return this.getCollection().subscribe(id, callback, options);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Subscribe to changes on the entire collection
|
|
316
|
+
* @param callback Function to call when changes occur
|
|
317
|
+
* @param expand Optional expand parameters
|
|
318
|
+
* @returns Promise that resolves to an unsubscribe function
|
|
319
|
+
*/
|
|
320
|
+
async subscribeToCollection(callback, expand) {
|
|
321
|
+
const finalExpand = this.prepareExpand(expand);
|
|
322
|
+
const options = finalExpand ? { expand: finalExpand } : {};
|
|
323
|
+
return this.getCollection().subscribe("*", callback, options);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Unsubscribe from a specific record's changes
|
|
327
|
+
* @param id The ID of the record to unsubscribe from
|
|
328
|
+
*/
|
|
329
|
+
unsubscribeFromRecord(id) {
|
|
330
|
+
this.getCollection().unsubscribe(id);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Unsubscribe from collection-wide changes
|
|
334
|
+
*/
|
|
335
|
+
unsubscribeFromCollection() {
|
|
336
|
+
this.getCollection().unsubscribe("*");
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Unsubscribe from all subscriptions in this collection
|
|
340
|
+
*/
|
|
341
|
+
unsubscribeAll() {
|
|
342
|
+
this.getCollection().unsubscribe();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
18
345
|
var baseSchema = {
|
|
19
346
|
id: z.string().describe("unique id"),
|
|
20
347
|
collectionId: z.string().describe("collection id"),
|
|
@@ -37,762 +364,544 @@ var inputImageFileSchema = {
|
|
|
37
364
|
imageFiles: z.array(z.instanceof(File))
|
|
38
365
|
};
|
|
39
366
|
var omitImageFilesSchema = {
|
|
40
|
-
imageFiles: true
|
|
41
|
-
};
|
|
42
|
-
function textField(options) {
|
|
43
|
-
let schema = z.string();
|
|
44
|
-
if (options?.min !== void 0) schema = schema.min(options.min);
|
|
45
|
-
if (options?.max !== void 0) schema = schema.max(options.max);
|
|
46
|
-
if (options?.pattern !== void 0) schema = schema.regex(options.pattern);
|
|
47
|
-
return schema;
|
|
48
|
-
}
|
|
49
|
-
function emailField() {
|
|
50
|
-
return z.string().email();
|
|
51
|
-
}
|
|
52
|
-
function urlField() {
|
|
53
|
-
return z.string().url();
|
|
54
|
-
}
|
|
55
|
-
function numberField(options) {
|
|
56
|
-
let schema = z.number();
|
|
57
|
-
if (options?.min !== void 0) schema = schema.min(options.min);
|
|
58
|
-
if (options?.max !== void 0) schema = schema.max(options.max);
|
|
59
|
-
return schema;
|
|
60
|
-
}
|
|
61
|
-
function boolField() {
|
|
62
|
-
return z.boolean();
|
|
63
|
-
}
|
|
64
|
-
function dateField() {
|
|
65
|
-
return z.date();
|
|
66
|
-
}
|
|
67
|
-
function selectField(values) {
|
|
68
|
-
return z.enum(values);
|
|
69
|
-
}
|
|
70
|
-
function jsonField(schema) {
|
|
71
|
-
return schema ?? z.record(z.any());
|
|
72
|
-
}
|
|
73
|
-
function fileField() {
|
|
74
|
-
return z.instanceof(File);
|
|
75
|
-
}
|
|
76
|
-
function filesField(options) {
|
|
77
|
-
let schema = z.array(z.instanceof(File));
|
|
78
|
-
if (options?.min !== void 0) schema = schema.min(options.min);
|
|
79
|
-
if (options?.max !== void 0) schema = schema.max(options.max);
|
|
80
|
-
return schema;
|
|
81
|
-
}
|
|
82
|
-
var RELATION_METADATA_KEY = "__pocketbase_relation__";
|
|
83
|
-
function RelationField(config) {
|
|
84
|
-
const metadata = {
|
|
85
|
-
[RELATION_METADATA_KEY]: {
|
|
86
|
-
type: "single",
|
|
87
|
-
collection: config.collection,
|
|
88
|
-
cascadeDelete: config.cascadeDelete ?? false,
|
|
89
|
-
maxSelect: 1,
|
|
90
|
-
minSelect: 0
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
return z.string().describe(JSON.stringify(metadata));
|
|
94
|
-
}
|
|
95
|
-
function RelationsField(config) {
|
|
96
|
-
const metadata = {
|
|
97
|
-
[RELATION_METADATA_KEY]: {
|
|
98
|
-
type: "multiple",
|
|
99
|
-
collection: config.collection,
|
|
100
|
-
cascadeDelete: config.cascadeDelete ?? false,
|
|
101
|
-
maxSelect: config.maxSelect ?? 999,
|
|
102
|
-
minSelect: config.minSelect ?? 0
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
let schema = z.array(z.string());
|
|
106
|
-
if (config.minSelect !== void 0) {
|
|
107
|
-
schema = schema.min(config.minSelect);
|
|
108
|
-
}
|
|
109
|
-
if (config.maxSelect !== void 0) {
|
|
110
|
-
schema = schema.max(config.maxSelect);
|
|
111
|
-
}
|
|
112
|
-
return schema.describe(JSON.stringify(metadata));
|
|
113
|
-
}
|
|
114
|
-
function extractRelationMetadata(description) {
|
|
115
|
-
if (!description) return null;
|
|
116
|
-
try {
|
|
117
|
-
const parsed = JSON.parse(description);
|
|
118
|
-
if (parsed[RELATION_METADATA_KEY]) {
|
|
119
|
-
return parsed[RELATION_METADATA_KEY];
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
function editorField() {
|
|
126
|
-
return z.string();
|
|
127
|
-
}
|
|
128
|
-
function geoPointField() {
|
|
129
|
-
return z.object({
|
|
130
|
-
lon: z.number(),
|
|
131
|
-
lat: z.number()
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
function withPermissions(schema, config) {
|
|
135
|
-
const metadata = {
|
|
136
|
-
permissions: config
|
|
137
|
-
};
|
|
138
|
-
return schema.describe(JSON.stringify(metadata));
|
|
139
|
-
}
|
|
140
|
-
function withIndexes(schema, indexes) {
|
|
141
|
-
let existingMetadata = {};
|
|
142
|
-
if (schema.description) {
|
|
143
|
-
try {
|
|
144
|
-
existingMetadata = JSON.parse(schema.description);
|
|
145
|
-
} catch {
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const metadata = {
|
|
149
|
-
...existingMetadata,
|
|
150
|
-
indexes
|
|
151
|
-
};
|
|
152
|
-
return schema.describe(JSON.stringify(metadata));
|
|
153
|
-
}
|
|
154
|
-
function defineCollection(config) {
|
|
155
|
-
const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
|
|
156
|
-
const metadata = {
|
|
157
|
-
collectionName
|
|
158
|
-
};
|
|
159
|
-
if (type) {
|
|
160
|
-
metadata.type = type;
|
|
161
|
-
}
|
|
162
|
-
if (permissions) {
|
|
163
|
-
metadata.permissions = permissions;
|
|
164
|
-
}
|
|
165
|
-
if (indexes) {
|
|
166
|
-
metadata.indexes = indexes;
|
|
167
|
-
}
|
|
168
|
-
if (Object.keys(futureOptions).length > 0) {
|
|
169
|
-
Object.assign(metadata, futureOptions);
|
|
170
|
-
}
|
|
171
|
-
return schema.describe(JSON.stringify(metadata));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// src/utils/permission-templates.ts
|
|
175
|
-
var PermissionTemplates = {
|
|
176
|
-
/**
|
|
177
|
-
* Public access - anyone can perform all operations
|
|
178
|
-
*/
|
|
179
|
-
public: () => ({
|
|
180
|
-
listRule: "",
|
|
181
|
-
viewRule: "",
|
|
182
|
-
createRule: "",
|
|
183
|
-
updateRule: "",
|
|
184
|
-
deleteRule: ""
|
|
185
|
-
}),
|
|
186
|
-
/**
|
|
187
|
-
* Authenticated users only - requires valid authentication for all operations
|
|
188
|
-
*/
|
|
189
|
-
authenticated: () => ({
|
|
190
|
-
listRule: '@request.auth.id != ""',
|
|
191
|
-
viewRule: '@request.auth.id != ""',
|
|
192
|
-
createRule: '@request.auth.id != ""',
|
|
193
|
-
updateRule: '@request.auth.id != ""',
|
|
194
|
-
deleteRule: '@request.auth.id != ""'
|
|
195
|
-
}),
|
|
196
|
-
/**
|
|
197
|
-
* Owner-only access - users can only manage their own records
|
|
198
|
-
* @param ownerField - Name of the relation field pointing to user (default: 'User')
|
|
199
|
-
*/
|
|
200
|
-
ownerOnly: (ownerField = "User") => ({
|
|
201
|
-
listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
202
|
-
viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
203
|
-
createRule: '@request.auth.id != ""',
|
|
204
|
-
updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
205
|
-
deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
|
|
206
|
-
}),
|
|
207
|
-
/**
|
|
208
|
-
* Admin/superuser only access
|
|
209
|
-
* Assumes a 'role' field exists with 'admin' value
|
|
210
|
-
* @param roleField - Name of the role field (default: 'role')
|
|
211
|
-
*/
|
|
212
|
-
adminOnly: (roleField = "role") => ({
|
|
213
|
-
listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
214
|
-
viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
215
|
-
createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
216
|
-
updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
217
|
-
deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
|
|
218
|
-
}),
|
|
219
|
-
/**
|
|
220
|
-
* Public read, authenticated write
|
|
221
|
-
* Anyone can list/view, but only authenticated users can create/update/delete
|
|
222
|
-
*/
|
|
223
|
-
readPublic: () => ({
|
|
224
|
-
listRule: "",
|
|
225
|
-
viewRule: "",
|
|
226
|
-
createRule: '@request.auth.id != ""',
|
|
227
|
-
updateRule: '@request.auth.id != ""',
|
|
228
|
-
deleteRule: '@request.auth.id != ""'
|
|
229
|
-
}),
|
|
230
|
-
/**
|
|
231
|
-
* Locked access - only superusers can perform operations
|
|
232
|
-
* All rules are set to null (locked)
|
|
233
|
-
*/
|
|
234
|
-
locked: () => ({
|
|
235
|
-
listRule: null,
|
|
236
|
-
viewRule: null,
|
|
237
|
-
createRule: null,
|
|
238
|
-
updateRule: null,
|
|
239
|
-
deleteRule: null
|
|
240
|
-
}),
|
|
241
|
-
/**
|
|
242
|
-
* Read-only authenticated - authenticated users can read, no write access
|
|
243
|
-
*/
|
|
244
|
-
readOnlyAuthenticated: () => ({
|
|
245
|
-
listRule: '@request.auth.id != ""',
|
|
246
|
-
viewRule: '@request.auth.id != ""',
|
|
247
|
-
createRule: null,
|
|
248
|
-
updateRule: null,
|
|
249
|
-
deleteRule: null
|
|
250
|
-
})
|
|
251
|
-
};
|
|
252
|
-
function resolveTemplate(config) {
|
|
253
|
-
let baseRules;
|
|
254
|
-
switch (config.template) {
|
|
255
|
-
case "public":
|
|
256
|
-
baseRules = PermissionTemplates.public();
|
|
257
|
-
break;
|
|
258
|
-
case "authenticated":
|
|
259
|
-
baseRules = PermissionTemplates.authenticated();
|
|
260
|
-
break;
|
|
261
|
-
case "owner-only":
|
|
262
|
-
baseRules = PermissionTemplates.ownerOnly(config.ownerField);
|
|
263
|
-
break;
|
|
264
|
-
case "admin-only":
|
|
265
|
-
baseRules = PermissionTemplates.adminOnly(config.roleField);
|
|
266
|
-
break;
|
|
267
|
-
case "read-public":
|
|
268
|
-
baseRules = PermissionTemplates.readPublic();
|
|
269
|
-
break;
|
|
270
|
-
case "custom":
|
|
271
|
-
baseRules = {};
|
|
272
|
-
break;
|
|
273
|
-
default: {
|
|
274
|
-
const _exhaustive = config.template;
|
|
275
|
-
throw new Error(`Unknown template type: ${_exhaustive}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
...baseRules,
|
|
280
|
-
...config.customRules
|
|
281
|
-
};
|
|
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;
|
|
282
375
|
}
|
|
283
|
-
function
|
|
284
|
-
return
|
|
376
|
+
function emailField() {
|
|
377
|
+
return z.string().email();
|
|
285
378
|
}
|
|
286
|
-
function
|
|
287
|
-
return
|
|
379
|
+
function urlField() {
|
|
380
|
+
return z.string().url();
|
|
288
381
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
};
|
|
295
|
-
let permissions;
|
|
296
|
-
if (isTemplateConfig(config)) {
|
|
297
|
-
if (config.template === "owner-only" && !config.ownerField) {
|
|
298
|
-
result.warnings.push("owner-only template without ownerField specified - using default 'User'");
|
|
299
|
-
}
|
|
300
|
-
if (config.template === "admin-only" && !config.roleField) {
|
|
301
|
-
result.warnings.push("admin-only template without roleField specified - using default 'role'");
|
|
302
|
-
}
|
|
303
|
-
permissions = resolveTemplate(config);
|
|
304
|
-
} else {
|
|
305
|
-
permissions = config;
|
|
306
|
-
}
|
|
307
|
-
if (permissions.manageRule !== void 0 && !isAuthCollection2) {
|
|
308
|
-
result.errors.push("manageRule is only valid for auth collections");
|
|
309
|
-
result.valid = false;
|
|
310
|
-
}
|
|
311
|
-
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
|
|
312
|
-
if (isAuthCollection2) {
|
|
313
|
-
ruleTypes.push("manageRule");
|
|
314
|
-
}
|
|
315
|
-
for (const ruleType of ruleTypes) {
|
|
316
|
-
const rule = permissions[ruleType];
|
|
317
|
-
if (rule !== void 0 && rule !== null && rule !== "") {
|
|
318
|
-
const ruleValidation = validateRuleExpression(rule);
|
|
319
|
-
if (!ruleValidation.valid) {
|
|
320
|
-
result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
|
|
321
|
-
result.valid = false;
|
|
322
|
-
}
|
|
323
|
-
result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return result;
|
|
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;
|
|
327
387
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
388
|
+
function boolField() {
|
|
389
|
+
return z.boolean();
|
|
390
|
+
}
|
|
391
|
+
function dateField() {
|
|
392
|
+
return z.date();
|
|
393
|
+
}
|
|
394
|
+
function selectField(values) {
|
|
395
|
+
return z.enum(values);
|
|
396
|
+
}
|
|
397
|
+
function jsonField(schema) {
|
|
398
|
+
return schema ?? z.record(z.any());
|
|
399
|
+
}
|
|
400
|
+
function fileField() {
|
|
401
|
+
return z.instanceof(File);
|
|
402
|
+
}
|
|
403
|
+
function filesField(options) {
|
|
404
|
+
let schema = z.array(z.instanceof(File));
|
|
405
|
+
if (options?.min !== void 0) schema = schema.min(options.min);
|
|
406
|
+
if (options?.max !== void 0) schema = schema.max(options.max);
|
|
407
|
+
return schema;
|
|
408
|
+
}
|
|
409
|
+
var RELATION_METADATA_KEY = "__pocketbase_relation__";
|
|
410
|
+
function RelationField(config) {
|
|
411
|
+
const metadata = {
|
|
412
|
+
[RELATION_METADATA_KEY]: {
|
|
413
|
+
type: "single",
|
|
414
|
+
collection: config.collection,
|
|
415
|
+
cascadeDelete: config.cascadeDelete ?? false,
|
|
416
|
+
maxSelect: 1,
|
|
417
|
+
minSelect: 0
|
|
418
|
+
}
|
|
333
419
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return result;
|
|
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
|
|
345
430
|
}
|
|
431
|
+
};
|
|
432
|
+
let schema = z.array(z.string());
|
|
433
|
+
if (config.minSelect !== void 0) {
|
|
434
|
+
schema = schema.min(config.minSelect);
|
|
346
435
|
}
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
result.valid = false;
|
|
350
|
-
}
|
|
351
|
-
if (expression.includes("==")) {
|
|
352
|
-
result.warnings.push("Use '=' instead of '==' for equality comparison");
|
|
436
|
+
if (config.maxSelect !== void 0) {
|
|
437
|
+
schema = schema.max(config.maxSelect);
|
|
353
438
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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];
|
|
360
447
|
}
|
|
448
|
+
} catch {
|
|
361
449
|
}
|
|
362
|
-
return
|
|
450
|
+
return null;
|
|
363
451
|
}
|
|
364
|
-
function
|
|
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
|
-
};
|
|
452
|
+
function editorField() {
|
|
453
|
+
return z.string();
|
|
373
454
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
|
382
464
|
};
|
|
383
|
-
|
|
384
|
-
if (schema.listRule !== void 0) merged.listRule = schema.listRule;
|
|
385
|
-
if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
|
|
386
|
-
if (schema.createRule !== void 0) merged.createRule = schema.createRule;
|
|
387
|
-
if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
|
|
388
|
-
if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
|
|
389
|
-
if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
|
|
390
|
-
}
|
|
391
|
-
return merged;
|
|
465
|
+
return schema.describe(JSON.stringify(metadata));
|
|
392
466
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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)'
|
|
467
|
+
function withIndexes(schema, indexes) {
|
|
468
|
+
let existingMetadata = {};
|
|
469
|
+
if (schema.description) {
|
|
470
|
+
try {
|
|
471
|
+
existingMetadata = JSON.parse(schema.description);
|
|
472
|
+
} catch {
|
|
412
473
|
}
|
|
413
474
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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: []
|
|
475
|
+
const metadata = {
|
|
476
|
+
...existingMetadata,
|
|
477
|
+
indexes
|
|
459
478
|
};
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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;
|
|
466
488
|
}
|
|
467
|
-
|
|
468
|
-
|
|
489
|
+
if (permissions) {
|
|
490
|
+
metadata.permissions = permissions;
|
|
469
491
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
* Subclasses should override this instead of directly setting options
|
|
473
|
-
*/
|
|
474
|
-
setDefaults() {
|
|
475
|
-
return {
|
|
476
|
-
expand: [],
|
|
477
|
-
filter: [],
|
|
478
|
-
sort: []
|
|
479
|
-
};
|
|
492
|
+
if (indexes) {
|
|
493
|
+
metadata.indexes = indexes;
|
|
480
494
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
495
|
+
if (Object.keys(futureOptions).length > 0) {
|
|
496
|
+
Object.assign(metadata, futureOptions);
|
|
497
|
+
}
|
|
498
|
+
return schema.describe(JSON.stringify(metadata));
|
|
499
|
+
}
|
|
500
|
+
var FIELD_METADATA_KEY = "__pocketbase_field__";
|
|
501
|
+
function extractFieldMetadata(description) {
|
|
502
|
+
if (!description) return null;
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(description);
|
|
505
|
+
if (parsed[FIELD_METADATA_KEY]) {
|
|
506
|
+
return parsed[FIELD_METADATA_KEY];
|
|
493
507
|
}
|
|
508
|
+
} catch {
|
|
494
509
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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);
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
function BoolField() {
|
|
513
|
+
const metadata = {
|
|
514
|
+
[FIELD_METADATA_KEY]: {
|
|
515
|
+
type: "bool"
|
|
508
516
|
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return await this.processRecord(record);
|
|
517
|
-
} catch (error) {
|
|
518
|
-
return this.errorWrapper(error);
|
|
517
|
+
};
|
|
518
|
+
return z.boolean().describe(JSON.stringify(metadata));
|
|
519
|
+
}
|
|
520
|
+
function NumberField(options) {
|
|
521
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
522
|
+
if (options.min > options.max) {
|
|
523
|
+
throw new Error("NumberField: min cannot be greater than max");
|
|
519
524
|
}
|
|
520
525
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
async upsert(input) {
|
|
525
|
-
if (input?.id) {
|
|
526
|
-
return await this.update(input.id, input);
|
|
527
|
-
}
|
|
528
|
-
return await this.create(input);
|
|
526
|
+
let schema = z.number();
|
|
527
|
+
if (options?.min !== void 0) {
|
|
528
|
+
schema = schema.min(options.min);
|
|
529
529
|
}
|
|
530
|
-
|
|
531
|
-
|
|
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 });
|
|
539
|
-
}
|
|
530
|
+
if (options?.max !== void 0) {
|
|
531
|
+
schema = schema.max(options.max);
|
|
540
532
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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 });
|
|
533
|
+
const metadata = {
|
|
534
|
+
[FIELD_METADATA_KEY]: {
|
|
535
|
+
type: "number",
|
|
536
|
+
options: options || {}
|
|
550
537
|
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return await this.processListResult(result);
|
|
559
|
-
} catch (error) {
|
|
560
|
-
return this.errorWrapper(error);
|
|
538
|
+
};
|
|
539
|
+
return schema.describe(JSON.stringify(metadata));
|
|
540
|
+
}
|
|
541
|
+
function TextField(options) {
|
|
542
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
543
|
+
if (options.min > options.max) {
|
|
544
|
+
throw new Error("TextField: min cannot be greater than max");
|
|
561
545
|
}
|
|
562
546
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
async delete(id) {
|
|
567
|
-
try {
|
|
568
|
-
return await this.entityDelete(id);
|
|
569
|
-
} catch (error) {
|
|
570
|
-
return this.handleError(error, { returnValue: false });
|
|
571
|
-
}
|
|
547
|
+
let schema = z.string();
|
|
548
|
+
if (options?.min !== void 0) {
|
|
549
|
+
schema = schema.min(options.min);
|
|
572
550
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
* Can be overridden to handle special cases like mapped entities
|
|
576
|
-
*/
|
|
577
|
-
async processRecord(record) {
|
|
578
|
-
return record;
|
|
551
|
+
if (options?.max !== void 0) {
|
|
552
|
+
schema = schema.max(options.max);
|
|
579
553
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
};
|
|
554
|
+
if (options?.pattern !== void 0) {
|
|
555
|
+
const pattern = options.pattern instanceof RegExp ? options.pattern : new RegExp(options.pattern);
|
|
556
|
+
schema = schema.regex(pattern);
|
|
590
557
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
prepareExpand(expand) {
|
|
596
|
-
if (!this.options.expand.length && !expand) {
|
|
597
|
-
return void 0;
|
|
558
|
+
const metadata = {
|
|
559
|
+
[FIELD_METADATA_KEY]: {
|
|
560
|
+
type: "text",
|
|
561
|
+
options: options || {}
|
|
598
562
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
563
|
+
};
|
|
564
|
+
return schema.describe(JSON.stringify(metadata));
|
|
565
|
+
}
|
|
566
|
+
function EmailField() {
|
|
567
|
+
const metadata = {
|
|
568
|
+
[FIELD_METADATA_KEY]: {
|
|
569
|
+
type: "email"
|
|
606
570
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
571
|
+
};
|
|
572
|
+
return z.string().email().describe(JSON.stringify(metadata));
|
|
573
|
+
}
|
|
574
|
+
function URLField() {
|
|
575
|
+
const metadata = {
|
|
576
|
+
[FIELD_METADATA_KEY]: {
|
|
577
|
+
type: "url"
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
return z.string().url().describe(JSON.stringify(metadata));
|
|
581
|
+
}
|
|
582
|
+
function EditorField() {
|
|
583
|
+
const metadata = {
|
|
584
|
+
[FIELD_METADATA_KEY]: {
|
|
585
|
+
type: "editor"
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
return z.string().describe(JSON.stringify(metadata));
|
|
589
|
+
}
|
|
590
|
+
function DateField(options) {
|
|
591
|
+
if (options?.min !== void 0 && options?.max !== void 0) {
|
|
592
|
+
const minDate = typeof options.min === "string" ? new Date(options.min) : options.min;
|
|
593
|
+
const maxDate = typeof options.max === "string" ? new Date(options.max) : options.max;
|
|
594
|
+
if (minDate > maxDate) {
|
|
595
|
+
throw new Error("DateField: min cannot be greater than max");
|
|
610
596
|
}
|
|
611
|
-
return uniqueExpands.join(",");
|
|
612
597
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (!this.options.filter.length && !filter) {
|
|
619
|
-
return void 0;
|
|
598
|
+
const schema = z.string();
|
|
599
|
+
const metadata = {
|
|
600
|
+
[FIELD_METADATA_KEY]: {
|
|
601
|
+
type: "date",
|
|
602
|
+
options: options || {}
|
|
620
603
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
604
|
+
};
|
|
605
|
+
return schema.describe(JSON.stringify(metadata));
|
|
606
|
+
}
|
|
607
|
+
function AutodateField(options) {
|
|
608
|
+
const schema = z.string();
|
|
609
|
+
const metadata = {
|
|
610
|
+
[FIELD_METADATA_KEY]: {
|
|
611
|
+
type: "autodate",
|
|
612
|
+
options: options || {}
|
|
628
613
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
614
|
+
};
|
|
615
|
+
return schema.describe(JSON.stringify(metadata));
|
|
616
|
+
}
|
|
617
|
+
function SelectField(values, options) {
|
|
618
|
+
const enumSchema = z.enum(values);
|
|
619
|
+
const metadata = {
|
|
620
|
+
[FIELD_METADATA_KEY]: {
|
|
621
|
+
type: "select",
|
|
622
|
+
options: {
|
|
623
|
+
values,
|
|
624
|
+
maxSelect: options?.maxSelect ?? 1
|
|
625
|
+
}
|
|
632
626
|
}
|
|
633
|
-
|
|
627
|
+
};
|
|
628
|
+
if (options?.maxSelect && options.maxSelect > 1) {
|
|
629
|
+
return z.array(enumSchema).describe(JSON.stringify(metadata));
|
|
634
630
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
631
|
+
return enumSchema.describe(JSON.stringify(metadata));
|
|
632
|
+
}
|
|
633
|
+
function FileField(options) {
|
|
634
|
+
const schema = z.instanceof(File);
|
|
635
|
+
const metadata = {
|
|
636
|
+
[FIELD_METADATA_KEY]: {
|
|
637
|
+
type: "file",
|
|
638
|
+
options: options || {}
|
|
642
639
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
640
|
+
};
|
|
641
|
+
return schema.describe(JSON.stringify(metadata));
|
|
642
|
+
}
|
|
643
|
+
function FilesField(options) {
|
|
644
|
+
if (options?.minSelect !== void 0 && options?.maxSelect !== void 0) {
|
|
645
|
+
if (options.minSelect > options.maxSelect) {
|
|
646
|
+
throw new Error("FilesField: minSelect cannot be greater than maxSelect");
|
|
648
647
|
}
|
|
649
|
-
return void 0;
|
|
650
648
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
649
|
+
let schema = z.array(z.instanceof(File));
|
|
650
|
+
if (options?.minSelect !== void 0) {
|
|
651
|
+
schema = schema.min(options.minSelect);
|
|
652
|
+
}
|
|
653
|
+
if (options?.maxSelect !== void 0) {
|
|
654
|
+
schema = schema.max(options.maxSelect);
|
|
656
655
|
}
|
|
656
|
+
const metadata = {
|
|
657
|
+
[FIELD_METADATA_KEY]: {
|
|
658
|
+
type: "file",
|
|
659
|
+
options: options || {}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
return schema.describe(JSON.stringify(metadata));
|
|
663
|
+
}
|
|
664
|
+
function JSONField(schema) {
|
|
665
|
+
const baseSchema2 = schema ?? z.record(z.string(), z.any());
|
|
666
|
+
const metadata = {
|
|
667
|
+
[FIELD_METADATA_KEY]: {
|
|
668
|
+
type: "json"
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
return baseSchema2.describe(JSON.stringify(metadata));
|
|
672
|
+
}
|
|
673
|
+
function GeoPointField() {
|
|
674
|
+
const schema = z.object({
|
|
675
|
+
lon: z.number(),
|
|
676
|
+
lat: z.number()
|
|
677
|
+
});
|
|
678
|
+
const metadata = {
|
|
679
|
+
[FIELD_METADATA_KEY]: {
|
|
680
|
+
type: "geoPoint"
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
return schema.describe(JSON.stringify(metadata));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/utils/permission-templates.ts
|
|
687
|
+
var PermissionTemplates = {
|
|
657
688
|
/**
|
|
658
|
-
*
|
|
689
|
+
* Public access - anyone can perform all operations
|
|
659
690
|
*/
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
691
|
+
public: () => ({
|
|
692
|
+
listRule: "",
|
|
693
|
+
viewRule: "",
|
|
694
|
+
createRule: "",
|
|
695
|
+
updateRule: "",
|
|
696
|
+
deleteRule: ""
|
|
697
|
+
}),
|
|
663
698
|
/**
|
|
664
|
-
*
|
|
699
|
+
* Authenticated users only - requires valid authentication for all operations
|
|
665
700
|
*/
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
701
|
+
authenticated: () => ({
|
|
702
|
+
listRule: '@request.auth.id != ""',
|
|
703
|
+
viewRule: '@request.auth.id != ""',
|
|
704
|
+
createRule: '@request.auth.id != ""',
|
|
705
|
+
updateRule: '@request.auth.id != ""',
|
|
706
|
+
deleteRule: '@request.auth.id != ""'
|
|
707
|
+
}),
|
|
671
708
|
/**
|
|
672
|
-
*
|
|
709
|
+
* Owner-only access - users can only manage their own records
|
|
710
|
+
* @param ownerField - Name of the relation field pointing to user (default: 'User')
|
|
673
711
|
*/
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
712
|
+
ownerOnly: (ownerField = "User") => ({
|
|
713
|
+
listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
714
|
+
viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
715
|
+
createRule: '@request.auth.id != ""',
|
|
716
|
+
updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
717
|
+
deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
|
|
718
|
+
}),
|
|
719
|
+
/**
|
|
720
|
+
* Admin/superuser only access
|
|
721
|
+
* Assumes a 'role' field exists with 'admin' value
|
|
722
|
+
* @param roleField - Name of the role field (default: 'role')
|
|
723
|
+
*/
|
|
724
|
+
adminOnly: (roleField = "role") => ({
|
|
725
|
+
listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
726
|
+
viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
727
|
+
createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
728
|
+
updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
729
|
+
deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
|
|
730
|
+
}),
|
|
683
731
|
/**
|
|
684
|
-
*
|
|
685
|
-
*
|
|
732
|
+
* Public read, authenticated write
|
|
733
|
+
* Anyone can list/view, but only authenticated users can create/update/delete
|
|
686
734
|
*/
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (finalSort) options.sort = finalSort;
|
|
695
|
-
return await this.getCollection().getList(page, perPage, options);
|
|
696
|
-
}
|
|
735
|
+
readPublic: () => ({
|
|
736
|
+
listRule: "",
|
|
737
|
+
viewRule: "",
|
|
738
|
+
createRule: '@request.auth.id != ""',
|
|
739
|
+
updateRule: '@request.auth.id != ""',
|
|
740
|
+
deleteRule: '@request.auth.id != ""'
|
|
741
|
+
}),
|
|
697
742
|
/**
|
|
698
|
-
*
|
|
743
|
+
* Locked access - only superusers can perform operations
|
|
744
|
+
* All rules are set to null (locked)
|
|
699
745
|
*/
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
746
|
+
locked: () => ({
|
|
747
|
+
listRule: null,
|
|
748
|
+
viewRule: null,
|
|
749
|
+
createRule: null,
|
|
750
|
+
updateRule: null,
|
|
751
|
+
deleteRule: null
|
|
752
|
+
}),
|
|
704
753
|
/**
|
|
705
|
-
*
|
|
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
|
|
754
|
+
* Read-only authenticated - authenticated users can read, no write access
|
|
709
755
|
*/
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
756
|
+
readOnlyAuthenticated: () => ({
|
|
757
|
+
listRule: '@request.auth.id != ""',
|
|
758
|
+
viewRule: '@request.auth.id != ""',
|
|
759
|
+
createRule: null,
|
|
760
|
+
updateRule: null,
|
|
761
|
+
deleteRule: null
|
|
762
|
+
})
|
|
763
|
+
};
|
|
764
|
+
function resolveTemplate(config) {
|
|
765
|
+
let baseRules;
|
|
766
|
+
switch (config.template) {
|
|
767
|
+
case "public":
|
|
768
|
+
baseRules = PermissionTemplates.public();
|
|
769
|
+
break;
|
|
770
|
+
case "authenticated":
|
|
771
|
+
baseRules = PermissionTemplates.authenticated();
|
|
772
|
+
break;
|
|
773
|
+
case "owner-only":
|
|
774
|
+
baseRules = PermissionTemplates.ownerOnly(config.ownerField);
|
|
775
|
+
break;
|
|
776
|
+
case "admin-only":
|
|
777
|
+
baseRules = PermissionTemplates.adminOnly(config.roleField);
|
|
778
|
+
break;
|
|
779
|
+
case "read-public":
|
|
780
|
+
baseRules = PermissionTemplates.readPublic();
|
|
781
|
+
break;
|
|
782
|
+
case "custom":
|
|
783
|
+
baseRules = {};
|
|
784
|
+
break;
|
|
785
|
+
default: {
|
|
786
|
+
const _exhaustive = config.template;
|
|
787
|
+
throw new Error(`Unknown template type: ${_exhaustive}`);
|
|
714
788
|
}
|
|
715
|
-
|
|
716
|
-
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
...baseRules,
|
|
792
|
+
...config.customRules
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function isTemplateConfig(config) {
|
|
796
|
+
return "template" in config;
|
|
797
|
+
}
|
|
798
|
+
function isPermissionSchema(config) {
|
|
799
|
+
return "listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config;
|
|
800
|
+
}
|
|
801
|
+
function validatePermissionConfig(config, isAuthCollection2 = false) {
|
|
802
|
+
const result = {
|
|
803
|
+
valid: true,
|
|
804
|
+
errors: [],
|
|
805
|
+
warnings: []
|
|
806
|
+
};
|
|
807
|
+
let permissions;
|
|
808
|
+
if (isTemplateConfig(config)) {
|
|
809
|
+
if (config.template === "owner-only" && !config.ownerField) {
|
|
810
|
+
result.warnings.push("owner-only template without ownerField specified - using default 'User'");
|
|
717
811
|
}
|
|
718
|
-
if (
|
|
719
|
-
|
|
812
|
+
if (config.template === "admin-only" && !config.roleField) {
|
|
813
|
+
result.warnings.push("admin-only template without roleField specified - using default 'role'");
|
|
720
814
|
}
|
|
721
|
-
|
|
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"));
|
|
815
|
+
permissions = resolveTemplate(config);
|
|
816
|
+
} else {
|
|
817
|
+
permissions = config;
|
|
728
818
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
errorWrapper(error) {
|
|
733
|
-
console.error(`Error in ${this.constructor.name}:`, error);
|
|
734
|
-
throw error;
|
|
819
|
+
if (permissions.manageRule !== void 0 && !isAuthCollection2) {
|
|
820
|
+
result.errors.push("manageRule is only valid for auth collections");
|
|
821
|
+
result.valid = false;
|
|
735
822
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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);
|
|
823
|
+
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
|
|
824
|
+
if (isAuthCollection2) {
|
|
825
|
+
ruleTypes.push("manageRule");
|
|
747
826
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
827
|
+
for (const ruleType of ruleTypes) {
|
|
828
|
+
const rule = permissions[ruleType];
|
|
829
|
+
if (rule !== void 0 && rule !== null && rule !== "") {
|
|
830
|
+
const ruleValidation = validateRuleExpression(rule);
|
|
831
|
+
if (!ruleValidation.valid) {
|
|
832
|
+
result.errors.push(`${ruleType}: ${ruleValidation.errors.join(", ")}`);
|
|
833
|
+
result.valid = false;
|
|
834
|
+
}
|
|
835
|
+
result.warnings.push(...ruleValidation.warnings.map((w) => `${ruleType}: ${w}`));
|
|
836
|
+
}
|
|
758
837
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
function validateRuleExpression(expression) {
|
|
841
|
+
const result = {
|
|
842
|
+
valid: true,
|
|
843
|
+
errors: [],
|
|
844
|
+
warnings: []
|
|
845
|
+
};
|
|
846
|
+
if (expression === null || expression === "") {
|
|
847
|
+
return result;
|
|
765
848
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
849
|
+
let parenCount = 0;
|
|
850
|
+
for (const char of expression) {
|
|
851
|
+
if (char === "(") parenCount++;
|
|
852
|
+
if (char === ")") parenCount--;
|
|
853
|
+
if (parenCount < 0) {
|
|
854
|
+
result.errors.push("Unbalanced parentheses");
|
|
855
|
+
result.valid = false;
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
771
858
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
unsubscribeAll() {
|
|
776
|
-
this.getCollection().unsubscribe();
|
|
859
|
+
if (parenCount !== 0) {
|
|
860
|
+
result.errors.push("Unbalanced parentheses");
|
|
861
|
+
result.valid = false;
|
|
777
862
|
}
|
|
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
|
-
};
|
|
863
|
+
if (expression.includes("==")) {
|
|
864
|
+
result.warnings.push("Use '=' instead of '==' for equality comparison");
|
|
788
865
|
}
|
|
789
|
-
|
|
790
|
-
|
|
866
|
+
const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
|
|
867
|
+
for (const ref of requestRefs) {
|
|
868
|
+
const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
|
|
869
|
+
if (!isValid) {
|
|
870
|
+
result.errors.push(`Invalid @request reference: '${ref}'`);
|
|
871
|
+
result.valid = false;
|
|
872
|
+
}
|
|
791
873
|
}
|
|
792
|
-
|
|
793
|
-
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
function createPermissions(permissions) {
|
|
877
|
+
return {
|
|
878
|
+
listRule: permissions.listRule ?? null,
|
|
879
|
+
viewRule: permissions.viewRule ?? null,
|
|
880
|
+
createRule: permissions.createRule ?? null,
|
|
881
|
+
updateRule: permissions.updateRule ?? null,
|
|
882
|
+
deleteRule: permissions.deleteRule ?? null,
|
|
883
|
+
manageRule: permissions.manageRule ?? null
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function mergePermissions(...schemas) {
|
|
887
|
+
const merged = {
|
|
888
|
+
listRule: null,
|
|
889
|
+
viewRule: null,
|
|
890
|
+
createRule: null,
|
|
891
|
+
updateRule: null,
|
|
892
|
+
deleteRule: null,
|
|
893
|
+
manageRule: null
|
|
894
|
+
};
|
|
895
|
+
for (const schema of schemas) {
|
|
896
|
+
if (schema.listRule !== void 0) merged.listRule = schema.listRule;
|
|
897
|
+
if (schema.viewRule !== void 0) merged.viewRule = schema.viewRule;
|
|
898
|
+
if (schema.createRule !== void 0) merged.createRule = schema.createRule;
|
|
899
|
+
if (schema.updateRule !== void 0) merged.updateRule = schema.updateRule;
|
|
900
|
+
if (schema.deleteRule !== void 0) merged.deleteRule = schema.deleteRule;
|
|
901
|
+
if (schema.manageRule !== void 0) merged.manageRule = schema.manageRule;
|
|
794
902
|
}
|
|
795
|
-
|
|
903
|
+
return merged;
|
|
904
|
+
}
|
|
796
905
|
|
|
797
906
|
// src/migration/errors.ts
|
|
798
907
|
var MigrationError = class _MigrationError extends Error {
|
|
@@ -2193,6 +2302,28 @@ function isAuthCollection(fields) {
|
|
|
2193
2302
|
return hasEmail && hasPassword;
|
|
2194
2303
|
}
|
|
2195
2304
|
function buildFieldDefinition(fieldName, zodType) {
|
|
2305
|
+
const fieldMetadata = extractFieldMetadata(zodType.description);
|
|
2306
|
+
if (fieldMetadata) {
|
|
2307
|
+
const required2 = isFieldRequired(zodType);
|
|
2308
|
+
const fieldDef2 = {
|
|
2309
|
+
name: fieldName,
|
|
2310
|
+
type: fieldMetadata.type,
|
|
2311
|
+
required: required2,
|
|
2312
|
+
options: fieldMetadata.options
|
|
2313
|
+
};
|
|
2314
|
+
if (fieldMetadata.type === "relation") {
|
|
2315
|
+
const relationMetadata2 = extractRelationMetadata(zodType.description);
|
|
2316
|
+
if (relationMetadata2) {
|
|
2317
|
+
fieldDef2.relation = {
|
|
2318
|
+
collection: relationMetadata2.collection,
|
|
2319
|
+
maxSelect: relationMetadata2.maxSelect,
|
|
2320
|
+
minSelect: relationMetadata2.minSelect,
|
|
2321
|
+
cascadeDelete: relationMetadata2.cascadeDelete
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
return fieldDef2;
|
|
2326
|
+
}
|
|
2196
2327
|
const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
|
|
2197
2328
|
const required = isFieldRequired(zodType);
|
|
2198
2329
|
const options = extractFieldOptions(zodType);
|
|
@@ -5319,6 +5450,6 @@ async function executeStatus(options) {
|
|
|
5319
5450
|
}
|
|
5320
5451
|
}
|
|
5321
5452
|
|
|
5322
|
-
export { CLIUsageError, ConfigurationError, DiffEngine, FIELD_TYPE_INFO, FileSystemError,
|
|
5453
|
+
export { AutodateField, BaseMutator, BoolField, CLIUsageError, ConfigurationError, DateField, DiffEngine, EditorField, EmailField, FIELD_METADATA_KEY, FIELD_TYPE_INFO, FileField, FileSystemError, FilesField, GeoPointField, JSONField, MigrationError, MigrationGenerationError, MigrationGenerator, NumberField, POCKETBASE_FIELD_TYPES, PermissionTemplates, RelationField, RelationsField, SchemaAnalyzer, SchemaParsingError, SelectField, SnapshotError, SnapshotManager, StatusEnum, TextField, URLField, 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, extractFieldMetadata, extractFieldOptions, extractIndexes, extractRelationMetadata, extractSchemaDefinitions, fileField, filesField, filterSystemCollections, findLatestSnapshot, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, formatChangeSummary, generate, generateChangeSummary, generateCollectionCreation, generateCollectionPermissions, generateCollectionRules, generateDownMigration, generateFieldAddition, generateFieldDefinitionObject, generateFieldDeletion, generateFieldModification, generateFieldsArray, generateIndexesArray, executeGenerate as generateMigration, generateMigrationDescription, generateMigrationFilename, generatePermissionUpdate, generateTimestamp, generateUpMigration, geoPointField, getArrayElementType, getCollectionNameFromFile, getDefaultValue, getFieldTypeInfo, getMaxSelect, executeStatus as getMigrationStatus, getMinSelect, getSnapshotPath, getSnapshotVersion, getUsersSystemFields, importSchemaModule, inputImageFileSchema, isArrayType, isAuthCollection, isEditorField, isFieldRequired, isFileFieldByName, isGeoPointType, isMultipleRelationField, isPermissionSchema, isRelationField, isSingleRelationField, isSystemCollection, isTemplateConfig, jsonField, loadBaseMigration, loadConfig, loadSnapshot, loadSnapshotIfExists, loadSnapshotWithMigrations, logError, logInfo, logSection, logSuccess, logWarning, mapZodArrayType, mapZodBooleanType, mapZodDateType, mapZodEnumType, mapZodNumberType, mapZodRecordType, mapZodStringType, mapZodTypeToPocketBase, matchCollectionsByName, matchFieldsByName, mergePermissions, mergeSnapshots, numberField, omitImageFilesSchema, parseSchemaFiles, pluralize, requiresForceFlag, resolveTargetCollection, resolveTemplate, saveSnapshot, selectField, selectSchemaForCollection, singularize, snapshotExists, textField, toCollectionName, unwrapZodType, urlField, validatePermissionConfig, validateRuleExpression, validateSnapshot, withIndexes, withPermissions, withProgress, writeMigrationFile };
|
|
5323
5454
|
//# sourceMappingURL=index.js.map
|
|
5324
5455
|
//# sourceMappingURL=index.js.map
|