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