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