pocketbase-zod-schema 0.1.2
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 +18 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/cli/index.cjs +3383 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +30 -0
- package/dist/cli/index.d.ts +30 -0
- package/dist/cli/index.js +3331 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/migrate.cjs +3380 -0
- package/dist/cli/migrate.cjs.map +1 -0
- package/dist/cli/migrate.d.cts +1 -0
- package/dist/cli/migrate.d.ts +1 -0
- package/dist/cli/migrate.js +3353 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/utils/index.cjs +540 -0
- package/dist/cli/utils/index.cjs.map +1 -0
- package/dist/cli/utils/index.d.cts +232 -0
- package/dist/cli/utils/index.d.ts +232 -0
- package/dist/cli/utils/index.js +487 -0
- package/dist/cli/utils/index.js.map +1 -0
- package/dist/enums.cjs +19 -0
- package/dist/enums.cjs.map +1 -0
- package/dist/enums.d.cts +6 -0
- package/dist/enums.d.ts +6 -0
- package/dist/enums.js +17 -0
- package/dist/enums.js.map +1 -0
- package/dist/index.cjs +4900 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +4726 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/analyzer.cjs +1267 -0
- package/dist/migration/analyzer.cjs.map +1 -0
- package/dist/migration/analyzer.d.cts +186 -0
- package/dist/migration/analyzer.d.ts +186 -0
- package/dist/migration/analyzer.js +1232 -0
- package/dist/migration/analyzer.js.map +1 -0
- package/dist/migration/diff.cjs +557 -0
- package/dist/migration/diff.cjs.map +1 -0
- package/dist/migration/diff.d.cts +291 -0
- package/dist/migration/diff.d.ts +291 -0
- package/dist/migration/diff.js +534 -0
- package/dist/migration/diff.js.map +1 -0
- package/dist/migration/generator.cjs +778 -0
- package/dist/migration/generator.cjs.map +1 -0
- package/dist/migration/generator.d.cts +225 -0
- package/dist/migration/generator.d.ts +225 -0
- package/dist/migration/generator.js +737 -0
- package/dist/migration/generator.js.map +1 -0
- package/dist/migration/index.cjs +3390 -0
- package/dist/migration/index.cjs.map +1 -0
- package/dist/migration/index.d.cts +103 -0
- package/dist/migration/index.d.ts +103 -0
- package/dist/migration/index.js +3265 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/migration/snapshot.cjs +609 -0
- package/dist/migration/snapshot.cjs.map +1 -0
- package/dist/migration/snapshot.d.cts +167 -0
- package/dist/migration/snapshot.d.ts +167 -0
- package/dist/migration/snapshot.js +575 -0
- package/dist/migration/snapshot.js.map +1 -0
- package/dist/migration/utils/index.cjs +672 -0
- package/dist/migration/utils/index.cjs.map +1 -0
- package/dist/migration/utils/index.d.cts +207 -0
- package/dist/migration/utils/index.d.ts +207 -0
- package/dist/migration/utils/index.js +641 -0
- package/dist/migration/utils/index.js.map +1 -0
- package/dist/mutator.cjs +427 -0
- package/dist/mutator.cjs.map +1 -0
- package/dist/mutator.d.cts +190 -0
- package/dist/mutator.d.ts +190 -0
- package/dist/mutator.js +425 -0
- package/dist/mutator.js.map +1 -0
- package/dist/permissions-ZHafVSIx.d.cts +71 -0
- package/dist/permissions-ZHafVSIx.d.ts +71 -0
- package/dist/schema.cjs +430 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +316 -0
- package/dist/schema.d.ts +316 -0
- package/dist/schema.js +396 -0
- package/dist/schema.js.map +1 -0
- package/dist/types-BbTgmg6H.d.cts +91 -0
- package/dist/types-z1Dkjg8m.d.ts +91 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +14 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/user-jS1aYoeD.d.cts +123 -0
- package/dist/user-jS1aYoeD.d.ts +123 -0
- package/package.json +165 -0
|
@@ -0,0 +1,3390 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs2 = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var zod = require('zod');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
26
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
27
|
+
|
|
28
|
+
// src/migration/analyzer.ts
|
|
29
|
+
|
|
30
|
+
// src/migration/errors.ts
|
|
31
|
+
var MigrationError = class _MigrationError extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "MigrationError";
|
|
35
|
+
Object.setPrototypeOf(this, _MigrationError.prototype);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var SchemaParsingError = class _SchemaParsingError extends MigrationError {
|
|
39
|
+
filePath;
|
|
40
|
+
originalError;
|
|
41
|
+
constructor(message, filePath, originalError) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "SchemaParsingError";
|
|
44
|
+
this.filePath = filePath;
|
|
45
|
+
this.originalError = originalError;
|
|
46
|
+
Object.setPrototypeOf(this, _SchemaParsingError.prototype);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates a formatted error message with file path and original error details
|
|
50
|
+
*/
|
|
51
|
+
getDetailedMessage() {
|
|
52
|
+
const parts = [this.message];
|
|
53
|
+
if (this.filePath) {
|
|
54
|
+
parts.push(`
|
|
55
|
+
File: ${this.filePath}`);
|
|
56
|
+
}
|
|
57
|
+
if (this.originalError) {
|
|
58
|
+
parts.push(`
|
|
59
|
+
Cause: ${this.originalError.message}`);
|
|
60
|
+
}
|
|
61
|
+
return parts.join("");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var SnapshotError = class _SnapshotError extends MigrationError {
|
|
65
|
+
snapshotPath;
|
|
66
|
+
operation;
|
|
67
|
+
originalError;
|
|
68
|
+
constructor(message, snapshotPath, operation, originalError) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "SnapshotError";
|
|
71
|
+
this.snapshotPath = snapshotPath;
|
|
72
|
+
this.operation = operation;
|
|
73
|
+
this.originalError = originalError;
|
|
74
|
+
Object.setPrototypeOf(this, _SnapshotError.prototype);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a formatted error message with snapshot path and operation details
|
|
78
|
+
*/
|
|
79
|
+
getDetailedMessage() {
|
|
80
|
+
const parts = [this.message];
|
|
81
|
+
if (this.operation) {
|
|
82
|
+
parts.push(`
|
|
83
|
+
Operation: ${this.operation}`);
|
|
84
|
+
}
|
|
85
|
+
if (this.snapshotPath) {
|
|
86
|
+
parts.push(`
|
|
87
|
+
Snapshot: ${this.snapshotPath}`);
|
|
88
|
+
}
|
|
89
|
+
if (this.originalError) {
|
|
90
|
+
parts.push(`
|
|
91
|
+
Cause: ${this.originalError.message}`);
|
|
92
|
+
}
|
|
93
|
+
return parts.join("");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var MigrationGenerationError = class _MigrationGenerationError extends MigrationError {
|
|
97
|
+
migrationPath;
|
|
98
|
+
originalError;
|
|
99
|
+
constructor(message, migrationPath, originalError) {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = "MigrationGenerationError";
|
|
102
|
+
this.migrationPath = migrationPath;
|
|
103
|
+
this.originalError = originalError;
|
|
104
|
+
Object.setPrototypeOf(this, _MigrationGenerationError.prototype);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates a formatted error message with migration path and original error details
|
|
108
|
+
*/
|
|
109
|
+
getDetailedMessage() {
|
|
110
|
+
const parts = [this.message];
|
|
111
|
+
if (this.migrationPath) {
|
|
112
|
+
parts.push(`
|
|
113
|
+
Migration: ${this.migrationPath}`);
|
|
114
|
+
}
|
|
115
|
+
if (this.originalError) {
|
|
116
|
+
parts.push(`
|
|
117
|
+
Cause: ${this.originalError.message}`);
|
|
118
|
+
}
|
|
119
|
+
return parts.join("");
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var FileSystemError = class _FileSystemError extends MigrationError {
|
|
123
|
+
path;
|
|
124
|
+
operation;
|
|
125
|
+
code;
|
|
126
|
+
originalError;
|
|
127
|
+
constructor(message, path4, operation, code, originalError) {
|
|
128
|
+
super(message);
|
|
129
|
+
this.name = "FileSystemError";
|
|
130
|
+
this.path = path4;
|
|
131
|
+
this.operation = operation;
|
|
132
|
+
this.code = code;
|
|
133
|
+
this.originalError = originalError;
|
|
134
|
+
Object.setPrototypeOf(this, _FileSystemError.prototype);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Creates a formatted error message with path, operation, and error code details
|
|
138
|
+
*/
|
|
139
|
+
getDetailedMessage() {
|
|
140
|
+
const parts = [this.message];
|
|
141
|
+
if (this.operation) {
|
|
142
|
+
parts.push(`
|
|
143
|
+
Operation: ${this.operation}`);
|
|
144
|
+
}
|
|
145
|
+
if (this.path) {
|
|
146
|
+
parts.push(`
|
|
147
|
+
Path: ${this.path}`);
|
|
148
|
+
}
|
|
149
|
+
if (this.code) {
|
|
150
|
+
parts.push(`
|
|
151
|
+
Error Code: ${this.code}`);
|
|
152
|
+
}
|
|
153
|
+
if (this.originalError) {
|
|
154
|
+
parts.push(`
|
|
155
|
+
Cause: ${this.originalError.message}`);
|
|
156
|
+
}
|
|
157
|
+
return parts.join("");
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var ConfigurationError = class _ConfigurationError extends MigrationError {
|
|
161
|
+
configPath;
|
|
162
|
+
invalidFields;
|
|
163
|
+
originalError;
|
|
164
|
+
constructor(message, configPath, invalidFields, originalError) {
|
|
165
|
+
super(message);
|
|
166
|
+
this.name = "ConfigurationError";
|
|
167
|
+
this.configPath = configPath;
|
|
168
|
+
this.invalidFields = invalidFields;
|
|
169
|
+
this.originalError = originalError;
|
|
170
|
+
Object.setPrototypeOf(this, _ConfigurationError.prototype);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Creates a formatted error message with configuration details
|
|
174
|
+
*/
|
|
175
|
+
getDetailedMessage() {
|
|
176
|
+
const parts = [this.message];
|
|
177
|
+
if (this.configPath) {
|
|
178
|
+
parts.push(`
|
|
179
|
+
Configuration File: ${this.configPath}`);
|
|
180
|
+
}
|
|
181
|
+
if (this.invalidFields && this.invalidFields.length > 0) {
|
|
182
|
+
parts.push(`
|
|
183
|
+
Invalid Fields: ${this.invalidFields.join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
if (this.originalError) {
|
|
186
|
+
parts.push(`
|
|
187
|
+
Cause: ${this.originalError.message}`);
|
|
188
|
+
}
|
|
189
|
+
return parts.join("");
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var CLIUsageError = class _CLIUsageError extends MigrationError {
|
|
193
|
+
command;
|
|
194
|
+
suggestion;
|
|
195
|
+
constructor(message, command, suggestion) {
|
|
196
|
+
super(message);
|
|
197
|
+
this.name = "CLIUsageError";
|
|
198
|
+
this.command = command;
|
|
199
|
+
this.suggestion = suggestion;
|
|
200
|
+
Object.setPrototypeOf(this, _CLIUsageError.prototype);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Creates a formatted error message with usage suggestions
|
|
204
|
+
*/
|
|
205
|
+
getDetailedMessage() {
|
|
206
|
+
const parts = [this.message];
|
|
207
|
+
if (this.command) {
|
|
208
|
+
parts.push(`
|
|
209
|
+
Command: ${this.command}`);
|
|
210
|
+
}
|
|
211
|
+
if (this.suggestion) {
|
|
212
|
+
parts.push(`
|
|
213
|
+
Suggestion: ${this.suggestion}`);
|
|
214
|
+
}
|
|
215
|
+
return parts.join("");
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/schema/permission-templates.ts
|
|
220
|
+
var PermissionTemplates = {
|
|
221
|
+
/**
|
|
222
|
+
* Public access - anyone can perform all operations
|
|
223
|
+
*/
|
|
224
|
+
public: () => ({
|
|
225
|
+
listRule: "",
|
|
226
|
+
viewRule: "",
|
|
227
|
+
createRule: "",
|
|
228
|
+
updateRule: "",
|
|
229
|
+
deleteRule: ""
|
|
230
|
+
}),
|
|
231
|
+
/**
|
|
232
|
+
* Authenticated users only - requires valid authentication for all operations
|
|
233
|
+
*/
|
|
234
|
+
authenticated: () => ({
|
|
235
|
+
listRule: '@request.auth.id != ""',
|
|
236
|
+
viewRule: '@request.auth.id != ""',
|
|
237
|
+
createRule: '@request.auth.id != ""',
|
|
238
|
+
updateRule: '@request.auth.id != ""',
|
|
239
|
+
deleteRule: '@request.auth.id != ""'
|
|
240
|
+
}),
|
|
241
|
+
/**
|
|
242
|
+
* Owner-only access - users can only manage their own records
|
|
243
|
+
* @param ownerField - Name of the relation field pointing to user (default: 'User')
|
|
244
|
+
*/
|
|
245
|
+
ownerOnly: (ownerField = "User") => ({
|
|
246
|
+
listRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
247
|
+
viewRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
248
|
+
createRule: '@request.auth.id != ""',
|
|
249
|
+
updateRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`,
|
|
250
|
+
deleteRule: `@request.auth.id != "" && ${ownerField} = @request.auth.id`
|
|
251
|
+
}),
|
|
252
|
+
/**
|
|
253
|
+
* Admin/superuser only access
|
|
254
|
+
* Assumes a 'role' field exists with 'admin' value
|
|
255
|
+
* @param roleField - Name of the role field (default: 'role')
|
|
256
|
+
*/
|
|
257
|
+
adminOnly: (roleField = "role") => ({
|
|
258
|
+
listRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
259
|
+
viewRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
260
|
+
createRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
261
|
+
updateRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`,
|
|
262
|
+
deleteRule: `@request.auth.id != "" && @request.auth.${roleField} = "admin"`
|
|
263
|
+
}),
|
|
264
|
+
/**
|
|
265
|
+
* Public read, authenticated write
|
|
266
|
+
* Anyone can list/view, but only authenticated users can create/update/delete
|
|
267
|
+
*/
|
|
268
|
+
readPublic: () => ({
|
|
269
|
+
listRule: "",
|
|
270
|
+
viewRule: "",
|
|
271
|
+
createRule: '@request.auth.id != ""',
|
|
272
|
+
updateRule: '@request.auth.id != ""',
|
|
273
|
+
deleteRule: '@request.auth.id != ""'
|
|
274
|
+
}),
|
|
275
|
+
/**
|
|
276
|
+
* Locked access - only superusers can perform operations
|
|
277
|
+
* All rules are set to null (locked)
|
|
278
|
+
*/
|
|
279
|
+
locked: () => ({
|
|
280
|
+
listRule: null,
|
|
281
|
+
viewRule: null,
|
|
282
|
+
createRule: null,
|
|
283
|
+
updateRule: null,
|
|
284
|
+
deleteRule: null
|
|
285
|
+
}),
|
|
286
|
+
/**
|
|
287
|
+
* Read-only authenticated - authenticated users can read, no write access
|
|
288
|
+
*/
|
|
289
|
+
readOnlyAuthenticated: () => ({
|
|
290
|
+
listRule: '@request.auth.id != ""',
|
|
291
|
+
viewRule: '@request.auth.id != ""',
|
|
292
|
+
createRule: null,
|
|
293
|
+
updateRule: null,
|
|
294
|
+
deleteRule: null
|
|
295
|
+
})
|
|
296
|
+
};
|
|
297
|
+
function resolveTemplate(config) {
|
|
298
|
+
let baseRules;
|
|
299
|
+
switch (config.template) {
|
|
300
|
+
case "public":
|
|
301
|
+
baseRules = PermissionTemplates.public();
|
|
302
|
+
break;
|
|
303
|
+
case "authenticated":
|
|
304
|
+
baseRules = PermissionTemplates.authenticated();
|
|
305
|
+
break;
|
|
306
|
+
case "owner-only":
|
|
307
|
+
baseRules = PermissionTemplates.ownerOnly(config.ownerField);
|
|
308
|
+
break;
|
|
309
|
+
case "admin-only":
|
|
310
|
+
baseRules = PermissionTemplates.adminOnly(config.roleField);
|
|
311
|
+
break;
|
|
312
|
+
case "read-public":
|
|
313
|
+
baseRules = PermissionTemplates.readPublic();
|
|
314
|
+
break;
|
|
315
|
+
case "custom":
|
|
316
|
+
baseRules = {};
|
|
317
|
+
break;
|
|
318
|
+
default: {
|
|
319
|
+
const _exhaustive = config.template;
|
|
320
|
+
throw new Error(`Unknown template type: ${_exhaustive}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
...baseRules,
|
|
325
|
+
...config.customRules
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/migration/rule-validator.ts
|
|
330
|
+
var RuleValidator = class {
|
|
331
|
+
fields;
|
|
332
|
+
collectionName;
|
|
333
|
+
isAuthCollection;
|
|
334
|
+
constructor(collectionName, fields, isAuthCollection2 = false) {
|
|
335
|
+
this.collectionName = collectionName;
|
|
336
|
+
this.fields = new Map(fields.map((f) => [f.name, f]));
|
|
337
|
+
this.isAuthCollection = isAuthCollection2;
|
|
338
|
+
this.addSystemFields();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Add system fields that are always available in PocketBase collections
|
|
342
|
+
* These fields are automatically added by PocketBase and can be referenced in rules
|
|
343
|
+
*/
|
|
344
|
+
addSystemFields() {
|
|
345
|
+
const systemFields = [
|
|
346
|
+
{ name: "id", type: "text", required: true, options: {} },
|
|
347
|
+
{ name: "created", type: "date", required: true, options: {} },
|
|
348
|
+
{ name: "updated", type: "date", required: true, options: {} },
|
|
349
|
+
{ name: "collectionId", type: "text", required: true, options: {} },
|
|
350
|
+
{ name: "collectionName", type: "text", required: true, options: {} }
|
|
351
|
+
];
|
|
352
|
+
if (this.isAuthCollection) {
|
|
353
|
+
systemFields.push(
|
|
354
|
+
{ name: "email", type: "email", required: true, options: {} },
|
|
355
|
+
{ name: "emailVisibility", type: "bool", required: false, options: {} },
|
|
356
|
+
{ name: "verified", type: "bool", required: false, options: {} },
|
|
357
|
+
{ name: "tokenKey", type: "text", required: true, options: {} },
|
|
358
|
+
{ name: "password", type: "text", required: true, options: {} }
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
for (const field of systemFields) {
|
|
362
|
+
if (!this.fields.has(field.name)) {
|
|
363
|
+
this.fields.set(field.name, field);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Validate a rule expression
|
|
369
|
+
*
|
|
370
|
+
* @param ruleType - The type of rule being validated
|
|
371
|
+
* @param expression - The rule expression to validate
|
|
372
|
+
* @returns Validation result with errors, warnings, and field references
|
|
373
|
+
*/
|
|
374
|
+
validate(ruleType, expression) {
|
|
375
|
+
const result = {
|
|
376
|
+
valid: true,
|
|
377
|
+
errors: [],
|
|
378
|
+
warnings: [],
|
|
379
|
+
fieldReferences: []
|
|
380
|
+
};
|
|
381
|
+
if (expression === null) {
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
if (expression === "") {
|
|
385
|
+
result.warnings.push(`${ruleType} is public - anyone can perform this operation`);
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
if (ruleType === "manageRule" && !this.isAuthCollection) {
|
|
389
|
+
result.valid = false;
|
|
390
|
+
result.errors.push("manageRule is only valid for auth collections");
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
const fieldRefs = this.extractFieldReferences(expression);
|
|
394
|
+
result.fieldReferences = fieldRefs;
|
|
395
|
+
for (const fieldRef of fieldRefs) {
|
|
396
|
+
this.validateFieldReference(fieldRef, result);
|
|
397
|
+
}
|
|
398
|
+
this.validateRequestReferences(expression, result);
|
|
399
|
+
this.validateSyntax(expression, result);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Extract field references from expression
|
|
404
|
+
*
|
|
405
|
+
* Matches field names that are not @request references.
|
|
406
|
+
* Handles dot notation for relations: user.email, post.author.name
|
|
407
|
+
*
|
|
408
|
+
* @param expression - The rule expression
|
|
409
|
+
* @returns Array of unique field references
|
|
410
|
+
*/
|
|
411
|
+
extractFieldReferences(expression) {
|
|
412
|
+
const refs = [];
|
|
413
|
+
let cleaned = expression.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''");
|
|
414
|
+
cleaned = cleaned.replace(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g, "");
|
|
415
|
+
const fieldPattern = /(?:^|[^@\w])([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)(?=[^a-zA-Z0-9_.]|$)/g;
|
|
416
|
+
let match;
|
|
417
|
+
while ((match = fieldPattern.exec(cleaned)) !== null) {
|
|
418
|
+
const ref = match[1];
|
|
419
|
+
if (!this.isKeyword(ref)) {
|
|
420
|
+
refs.push(ref);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return [...new Set(refs)];
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Check if a word is a PocketBase keyword
|
|
427
|
+
*
|
|
428
|
+
* @param word - The word to check
|
|
429
|
+
* @returns True if the word is a keyword
|
|
430
|
+
*/
|
|
431
|
+
isKeyword(word) {
|
|
432
|
+
const keywords = ["true", "false", "null", "AND", "OR", "NOT", "LIKE", "IN"];
|
|
433
|
+
return keywords.includes(word.toUpperCase());
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Validate a field reference exists in schema
|
|
437
|
+
*
|
|
438
|
+
* Checks if the root field exists and validates relation chains.
|
|
439
|
+
* For nested references, warns about potential issues since we can't
|
|
440
|
+
* validate across collections without loading related schemas.
|
|
441
|
+
*
|
|
442
|
+
* @param fieldRef - The field reference to validate (e.g., "user" or "user.email")
|
|
443
|
+
* @param result - The validation result to update
|
|
444
|
+
*/
|
|
445
|
+
validateFieldReference(fieldRef, result) {
|
|
446
|
+
const parts = fieldRef.split(".");
|
|
447
|
+
const rootField = parts[0];
|
|
448
|
+
if (!this.fields.has(rootField)) {
|
|
449
|
+
result.errors.push(`Field '${rootField}' does not exist in collection '${this.collectionName}'`);
|
|
450
|
+
result.valid = false;
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (parts.length > 1) {
|
|
454
|
+
const field = this.fields.get(rootField);
|
|
455
|
+
if (field.type !== "relation") {
|
|
456
|
+
result.errors.push(`Field '${rootField}' is not a relation field, cannot access nested property '${parts[1]}'`);
|
|
457
|
+
result.valid = false;
|
|
458
|
+
} else {
|
|
459
|
+
result.warnings.push(
|
|
460
|
+
`Nested field reference '${fieldRef}' - ensure target collection has field '${parts.slice(1).join(".")}'`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Validate @request references
|
|
467
|
+
*
|
|
468
|
+
* Checks that @request references follow valid PocketBase patterns:
|
|
469
|
+
* - @request.auth.* - authenticated user data
|
|
470
|
+
* - @request.body.* - request body fields
|
|
471
|
+
* - @request.query.* - query parameters
|
|
472
|
+
* - @request.headers.* - request headers
|
|
473
|
+
* - @request.method - HTTP method
|
|
474
|
+
* - @request.context - execution context
|
|
475
|
+
*
|
|
476
|
+
* @param expression - The rule expression
|
|
477
|
+
* @param result - The validation result to update
|
|
478
|
+
*/
|
|
479
|
+
validateRequestReferences(expression, result) {
|
|
480
|
+
const requestRefs = expression.match(/@request\.[a-zA-Z_][a-zA-Z0-9_.]*/g) || [];
|
|
481
|
+
for (const ref of requestRefs) {
|
|
482
|
+
const isValid = ref.startsWith("@request.auth.") || ref === "@request.method" || ref === "@request.context" || ref.startsWith("@request.body.") || ref.startsWith("@request.query.") || ref.startsWith("@request.headers.");
|
|
483
|
+
if (!isValid) {
|
|
484
|
+
result.errors.push(`Invalid @request reference: '${ref}'`);
|
|
485
|
+
result.valid = false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Validate basic syntax patterns
|
|
491
|
+
*
|
|
492
|
+
* Checks for:
|
|
493
|
+
* - Balanced parentheses
|
|
494
|
+
* - Common operator mistakes (== instead of =)
|
|
495
|
+
*
|
|
496
|
+
* @param expression - The rule expression
|
|
497
|
+
* @param result - The validation result to update
|
|
498
|
+
*/
|
|
499
|
+
validateSyntax(expression, result) {
|
|
500
|
+
let parenCount = 0;
|
|
501
|
+
for (const char of expression) {
|
|
502
|
+
if (char === "(") parenCount++;
|
|
503
|
+
if (char === ")") parenCount--;
|
|
504
|
+
if (parenCount < 0) {
|
|
505
|
+
result.errors.push("Unbalanced parentheses in expression");
|
|
506
|
+
result.valid = false;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (parenCount !== 0) {
|
|
511
|
+
result.errors.push("Unbalanced parentheses in expression");
|
|
512
|
+
result.valid = false;
|
|
513
|
+
}
|
|
514
|
+
if (expression.includes("==")) {
|
|
515
|
+
result.warnings.push("Use '=' instead of '==' for equality comparison in PocketBase rules");
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// src/migration/permission-analyzer.ts
|
|
521
|
+
var PermissionAnalyzer = class {
|
|
522
|
+
/**
|
|
523
|
+
* Extract permission metadata from Zod schema description
|
|
524
|
+
*
|
|
525
|
+
* Zod schemas can have permission metadata attached via the describe() method.
|
|
526
|
+
* This method parses the description and extracts the permission configuration.
|
|
527
|
+
*
|
|
528
|
+
* @param schemaDescription - The Zod schema description string
|
|
529
|
+
* @returns Permission schema if found, null otherwise
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```typescript
|
|
533
|
+
* const analyzer = new PermissionAnalyzer();
|
|
534
|
+
* const permissions = analyzer.extractPermissions(schema.description);
|
|
535
|
+
* ```
|
|
536
|
+
*/
|
|
537
|
+
extractPermissions(schemaDescription) {
|
|
538
|
+
if (!schemaDescription) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const metadata = JSON.parse(schemaDescription);
|
|
543
|
+
if (metadata.permissions) {
|
|
544
|
+
return metadata.permissions;
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Resolve template configuration to concrete rules
|
|
553
|
+
*
|
|
554
|
+
* Takes either a template configuration or a direct permission schema
|
|
555
|
+
* and returns a fully resolved permission schema with all rules defined.
|
|
556
|
+
*
|
|
557
|
+
* If the input is already a permission schema (has rule properties),
|
|
558
|
+
* it's returned as-is. Otherwise, the template is resolved using the
|
|
559
|
+
* template resolver.
|
|
560
|
+
*
|
|
561
|
+
* @param config - Template configuration or direct permission schema
|
|
562
|
+
* @returns Resolved permission schema
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```typescript
|
|
566
|
+
* const analyzer = new PermissionAnalyzer();
|
|
567
|
+
*
|
|
568
|
+
* // Resolve from template
|
|
569
|
+
* const permissions = analyzer.resolvePermissions({
|
|
570
|
+
* template: 'owner-only',
|
|
571
|
+
* ownerField: 'User'
|
|
572
|
+
* });
|
|
573
|
+
*
|
|
574
|
+
* // Or pass direct schema
|
|
575
|
+
* const permissions = analyzer.resolvePermissions({
|
|
576
|
+
* listRule: '@request.auth.id != ""',
|
|
577
|
+
* viewRule: '@request.auth.id != ""'
|
|
578
|
+
* });
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
resolvePermissions(config) {
|
|
582
|
+
if ("listRule" in config || "viewRule" in config || "createRule" in config || "updateRule" in config || "deleteRule" in config || "manageRule" in config) {
|
|
583
|
+
return config;
|
|
584
|
+
}
|
|
585
|
+
return resolveTemplate(config);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Validate all rules in a permission schema
|
|
589
|
+
*
|
|
590
|
+
* Validates each rule in the permission schema against the collection's
|
|
591
|
+
* field definitions. Returns a map of validation results keyed by rule type.
|
|
592
|
+
*
|
|
593
|
+
* Only validates rules that are defined (not undefined). Undefined rules
|
|
594
|
+
* are treated as null (locked) by default.
|
|
595
|
+
*
|
|
596
|
+
* @param collectionName - Name of the collection being validated
|
|
597
|
+
* @param permissions - Permission schema to validate
|
|
598
|
+
* @param fields - Collection field definitions
|
|
599
|
+
* @param isAuthCollection - Whether this is an auth collection (allows manageRule)
|
|
600
|
+
* @returns Map of validation results by rule type
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```typescript
|
|
604
|
+
* const analyzer = new PermissionAnalyzer();
|
|
605
|
+
* const results = analyzer.validatePermissions(
|
|
606
|
+
* 'posts',
|
|
607
|
+
* { listRule: '@request.auth.id != ""', viewRule: 'author = @request.auth.id' },
|
|
608
|
+
* fields,
|
|
609
|
+
* false
|
|
610
|
+
* );
|
|
611
|
+
*
|
|
612
|
+
* for (const [ruleType, result] of results) {
|
|
613
|
+
* if (!result.valid) {
|
|
614
|
+
* console.error(`${ruleType} validation failed:`, result.errors);
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
validatePermissions(collectionName, permissions, fields, isAuthCollection2 = false) {
|
|
620
|
+
const validator = new RuleValidator(collectionName, fields, isAuthCollection2);
|
|
621
|
+
const results = /* @__PURE__ */ new Map();
|
|
622
|
+
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"];
|
|
623
|
+
if (isAuthCollection2) {
|
|
624
|
+
ruleTypes.push("manageRule");
|
|
625
|
+
}
|
|
626
|
+
for (const ruleType of ruleTypes) {
|
|
627
|
+
const expression = permissions[ruleType];
|
|
628
|
+
if (expression !== void 0) {
|
|
629
|
+
results.set(ruleType, validator.validate(ruleType, expression));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return results;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Merge permissions with defaults
|
|
636
|
+
*
|
|
637
|
+
* Ensures all rule types have a defined value. Undefined rules are set
|
|
638
|
+
* to null (locked to superusers only), which is the PocketBase default.
|
|
639
|
+
*
|
|
640
|
+
* This is useful when generating migrations to ensure all rules are
|
|
641
|
+
* explicitly set in the collection configuration.
|
|
642
|
+
*
|
|
643
|
+
* @param permissions - Permission schema (may have undefined rules)
|
|
644
|
+
* @returns Permission schema with all rules defined (null if not specified)
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* ```typescript
|
|
648
|
+
* const analyzer = new PermissionAnalyzer();
|
|
649
|
+
* const merged = analyzer.mergeWithDefaults({
|
|
650
|
+
* listRule: '@request.auth.id != ""'
|
|
651
|
+
* // other rules undefined
|
|
652
|
+
* });
|
|
653
|
+
*
|
|
654
|
+
* // Result:
|
|
655
|
+
* // {
|
|
656
|
+
* // listRule: '@request.auth.id != ""',
|
|
657
|
+
* // viewRule: null,
|
|
658
|
+
* // createRule: null,
|
|
659
|
+
* // updateRule: null,
|
|
660
|
+
* // deleteRule: null,
|
|
661
|
+
* // manageRule: null
|
|
662
|
+
* // }
|
|
663
|
+
* ```
|
|
664
|
+
*/
|
|
665
|
+
mergeWithDefaults(permissions) {
|
|
666
|
+
return {
|
|
667
|
+
listRule: permissions.listRule ?? null,
|
|
668
|
+
viewRule: permissions.viewRule ?? null,
|
|
669
|
+
createRule: permissions.createRule ?? null,
|
|
670
|
+
updateRule: permissions.updateRule ?? null,
|
|
671
|
+
deleteRule: permissions.deleteRule ?? null,
|
|
672
|
+
manageRule: permissions.manageRule ?? null
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/migration/utils/pluralize.ts
|
|
678
|
+
var SPECIAL_CASES = {
|
|
679
|
+
// Common irregular plurals
|
|
680
|
+
person: "people",
|
|
681
|
+
Person: "People",
|
|
682
|
+
child: "children",
|
|
683
|
+
Child: "Children",
|
|
684
|
+
man: "men",
|
|
685
|
+
Man: "Men",
|
|
686
|
+
woman: "women",
|
|
687
|
+
Woman: "Women",
|
|
688
|
+
tooth: "teeth",
|
|
689
|
+
Tooth: "Teeth",
|
|
690
|
+
foot: "feet",
|
|
691
|
+
Foot: "Feet",
|
|
692
|
+
mouse: "mice",
|
|
693
|
+
Mouse: "Mice",
|
|
694
|
+
goose: "geese",
|
|
695
|
+
Goose: "Geese",
|
|
696
|
+
// Words ending in -y
|
|
697
|
+
category: "categories",
|
|
698
|
+
Category: "Categories",
|
|
699
|
+
company: "companies",
|
|
700
|
+
Company: "Companies",
|
|
701
|
+
city: "cities",
|
|
702
|
+
City: "Cities",
|
|
703
|
+
country: "countries",
|
|
704
|
+
Country: "Countries",
|
|
705
|
+
story: "stories",
|
|
706
|
+
Story: "Stories",
|
|
707
|
+
party: "parties",
|
|
708
|
+
Party: "Parties",
|
|
709
|
+
family: "families",
|
|
710
|
+
Family: "Families",
|
|
711
|
+
activity: "activities",
|
|
712
|
+
Activity: "Activities",
|
|
713
|
+
priority: "priorities",
|
|
714
|
+
Priority: "Priorities",
|
|
715
|
+
// Words ending in -f or -fe
|
|
716
|
+
life: "lives",
|
|
717
|
+
Life: "Lives",
|
|
718
|
+
wife: "wives",
|
|
719
|
+
Wife: "Wives",
|
|
720
|
+
knife: "knives",
|
|
721
|
+
Knife: "Knives",
|
|
722
|
+
leaf: "leaves",
|
|
723
|
+
Leaf: "Leaves",
|
|
724
|
+
shelf: "shelves",
|
|
725
|
+
Shelf: "Shelves",
|
|
726
|
+
half: "halves",
|
|
727
|
+
Half: "Halves",
|
|
728
|
+
// Words ending in -is
|
|
729
|
+
analysis: "analyses",
|
|
730
|
+
Analysis: "Analyses",
|
|
731
|
+
basis: "bases",
|
|
732
|
+
Basis: "Bases",
|
|
733
|
+
crisis: "crises",
|
|
734
|
+
Crisis: "Crises",
|
|
735
|
+
thesis: "theses",
|
|
736
|
+
Thesis: "Theses",
|
|
737
|
+
// Words ending in -us
|
|
738
|
+
cactus: "cacti",
|
|
739
|
+
Cactus: "Cacti",
|
|
740
|
+
focus: "foci",
|
|
741
|
+
Focus: "Foci",
|
|
742
|
+
fungus: "fungi",
|
|
743
|
+
Fungus: "Fungi",
|
|
744
|
+
nucleus: "nuclei",
|
|
745
|
+
Nucleus: "Nuclei",
|
|
746
|
+
radius: "radii",
|
|
747
|
+
Radius: "Radii",
|
|
748
|
+
// Words ending in -on
|
|
749
|
+
phenomenon: "phenomena",
|
|
750
|
+
Phenomenon: "Phenomena",
|
|
751
|
+
criterion: "criteria",
|
|
752
|
+
Criterion: "Criteria",
|
|
753
|
+
// Words ending in -um
|
|
754
|
+
datum: "data",
|
|
755
|
+
Datum: "Data",
|
|
756
|
+
medium: "media",
|
|
757
|
+
Medium: "Media",
|
|
758
|
+
curriculum: "curricula",
|
|
759
|
+
Curriculum: "Curricula",
|
|
760
|
+
// Unchanged plurals
|
|
761
|
+
sheep: "sheep",
|
|
762
|
+
Sheep: "Sheep",
|
|
763
|
+
deer: "deer",
|
|
764
|
+
Deer: "Deer",
|
|
765
|
+
fish: "fish",
|
|
766
|
+
Fish: "Fish",
|
|
767
|
+
species: "species",
|
|
768
|
+
Species: "Species",
|
|
769
|
+
series: "series",
|
|
770
|
+
Series: "Series"
|
|
771
|
+
};
|
|
772
|
+
function pluralize(singular) {
|
|
773
|
+
if (SPECIAL_CASES[singular]) {
|
|
774
|
+
return SPECIAL_CASES[singular];
|
|
775
|
+
}
|
|
776
|
+
if (singular.length > 3 && singular.endsWith("s") && !singular.endsWith("ss")) {
|
|
777
|
+
return singular;
|
|
778
|
+
}
|
|
779
|
+
const lowerSingular = singular.toLowerCase();
|
|
780
|
+
let plural;
|
|
781
|
+
if (/(?:s|ss|sh|ch|x|z)$/.test(lowerSingular)) {
|
|
782
|
+
plural = singular + "es";
|
|
783
|
+
} else if (/[^aeiou]y$/.test(lowerSingular)) {
|
|
784
|
+
plural = singular.slice(0, -1) + "ies";
|
|
785
|
+
} else if (/[^aeiou]o$/.test(lowerSingular)) {
|
|
786
|
+
plural = singular + "es";
|
|
787
|
+
} else if (/fe?$/.test(lowerSingular)) {
|
|
788
|
+
if (lowerSingular.endsWith("fe")) {
|
|
789
|
+
plural = singular.slice(0, -2) + "ves";
|
|
790
|
+
} else {
|
|
791
|
+
plural = singular.slice(0, -1) + "ves";
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
plural = singular + "s";
|
|
795
|
+
}
|
|
796
|
+
return plural;
|
|
797
|
+
}
|
|
798
|
+
function toCollectionName(entityName) {
|
|
799
|
+
return pluralize(entityName);
|
|
800
|
+
}
|
|
801
|
+
function singularize(plural) {
|
|
802
|
+
for (const [singular, pluralForm] of Object.entries(SPECIAL_CASES)) {
|
|
803
|
+
if (pluralForm === plural) {
|
|
804
|
+
return singular;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const lower = plural.toLowerCase();
|
|
808
|
+
if (lower.endsWith("ies") && plural.length > 3) {
|
|
809
|
+
return plural.slice(0, -3) + "y";
|
|
810
|
+
}
|
|
811
|
+
if (lower.endsWith("ves")) {
|
|
812
|
+
return plural.slice(0, -3) + "fe";
|
|
813
|
+
}
|
|
814
|
+
if (/(?:ses|shes|ches|xes|zes)$/.test(lower)) {
|
|
815
|
+
return plural.slice(0, -2);
|
|
816
|
+
}
|
|
817
|
+
if (lower.endsWith("s") && plural.length > 1) {
|
|
818
|
+
return plural.slice(0, -1);
|
|
819
|
+
}
|
|
820
|
+
return plural;
|
|
821
|
+
}
|
|
822
|
+
function isSingleRelationField(fieldName, zodType) {
|
|
823
|
+
let unwrappedType = zodType;
|
|
824
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
825
|
+
unwrappedType = zodType._def.innerType;
|
|
826
|
+
}
|
|
827
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
828
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
829
|
+
}
|
|
830
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
831
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
832
|
+
}
|
|
833
|
+
if (!(unwrappedType instanceof zod.z.ZodString)) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
const startsWithUppercase = /^[A-Z]/.test(fieldName);
|
|
837
|
+
const commonStringFields = ["Title", "Name", "Description", "Content", "Summary", "Status", "Type"];
|
|
838
|
+
const isCommonField = commonStringFields.includes(fieldName);
|
|
839
|
+
return startsWithUppercase && !isCommonField;
|
|
840
|
+
}
|
|
841
|
+
function isMultipleRelationField(fieldName, zodType) {
|
|
842
|
+
let unwrappedType = zodType;
|
|
843
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
844
|
+
unwrappedType = zodType._def.innerType;
|
|
845
|
+
}
|
|
846
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
847
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
848
|
+
}
|
|
849
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
850
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
851
|
+
}
|
|
852
|
+
if (!(unwrappedType instanceof zod.z.ZodArray)) {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
const elementType = unwrappedType._def.type;
|
|
856
|
+
if (!(elementType instanceof zod.z.ZodString)) {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
const hasUppercase = /[A-Z]/.test(fieldName);
|
|
860
|
+
return hasUppercase;
|
|
861
|
+
}
|
|
862
|
+
function resolveTargetCollection(fieldName) {
|
|
863
|
+
const matches = fieldName.match(/[A-Z][a-z]+/g);
|
|
864
|
+
if (!matches || matches.length === 0) {
|
|
865
|
+
return pluralize(fieldName);
|
|
866
|
+
}
|
|
867
|
+
const entityName = matches[matches.length - 1];
|
|
868
|
+
return pluralize(entityName);
|
|
869
|
+
}
|
|
870
|
+
function isRelationField(fieldName, zodType) {
|
|
871
|
+
return isSingleRelationField(fieldName, zodType) || isMultipleRelationField(fieldName, zodType);
|
|
872
|
+
}
|
|
873
|
+
function getMaxSelect(fieldName, zodType) {
|
|
874
|
+
if (isSingleRelationField(fieldName, zodType)) {
|
|
875
|
+
return 1;
|
|
876
|
+
}
|
|
877
|
+
if (isMultipleRelationField(fieldName, zodType)) {
|
|
878
|
+
let unwrappedType = zodType;
|
|
879
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
880
|
+
unwrappedType = zodType._def.innerType;
|
|
881
|
+
}
|
|
882
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
883
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
884
|
+
}
|
|
885
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
886
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
887
|
+
}
|
|
888
|
+
if (unwrappedType instanceof zod.z.ZodArray) {
|
|
889
|
+
const arrayDef = unwrappedType._def;
|
|
890
|
+
if (arrayDef.maxLength) {
|
|
891
|
+
return arrayDef.maxLength.value;
|
|
892
|
+
}
|
|
893
|
+
return 999;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return 1;
|
|
897
|
+
}
|
|
898
|
+
function getMinSelect(fieldName, zodType) {
|
|
899
|
+
if (!isMultipleRelationField(fieldName, zodType)) {
|
|
900
|
+
return void 0;
|
|
901
|
+
}
|
|
902
|
+
let unwrappedType = zodType;
|
|
903
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
904
|
+
unwrappedType = zodType._def.innerType;
|
|
905
|
+
}
|
|
906
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
907
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
908
|
+
}
|
|
909
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
910
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
911
|
+
}
|
|
912
|
+
if (unwrappedType instanceof zod.z.ZodArray) {
|
|
913
|
+
const arrayDef = unwrappedType._def;
|
|
914
|
+
if (arrayDef.minLength) {
|
|
915
|
+
return arrayDef.minLength.value;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return void 0;
|
|
919
|
+
}
|
|
920
|
+
var POCKETBASE_FIELD_TYPES = [
|
|
921
|
+
"text",
|
|
922
|
+
"email",
|
|
923
|
+
"url",
|
|
924
|
+
"number",
|
|
925
|
+
"bool",
|
|
926
|
+
"date",
|
|
927
|
+
"select",
|
|
928
|
+
"relation",
|
|
929
|
+
"file",
|
|
930
|
+
"json",
|
|
931
|
+
"editor",
|
|
932
|
+
"geoPoint",
|
|
933
|
+
"autodate"
|
|
934
|
+
];
|
|
935
|
+
var FIELD_TYPE_INFO = {
|
|
936
|
+
text: {
|
|
937
|
+
type: "text",
|
|
938
|
+
description: "Plain text field",
|
|
939
|
+
zodTypes: ["ZodString"],
|
|
940
|
+
supportsMultiple: false
|
|
941
|
+
},
|
|
942
|
+
email: {
|
|
943
|
+
type: "email",
|
|
944
|
+
description: "Email address field with validation",
|
|
945
|
+
zodTypes: ["ZodString with email()"],
|
|
946
|
+
supportsMultiple: false
|
|
947
|
+
},
|
|
948
|
+
url: {
|
|
949
|
+
type: "url",
|
|
950
|
+
description: "URL field with validation",
|
|
951
|
+
zodTypes: ["ZodString with url()"],
|
|
952
|
+
supportsMultiple: false
|
|
953
|
+
},
|
|
954
|
+
editor: {
|
|
955
|
+
type: "editor",
|
|
956
|
+
description: "Rich text editor field",
|
|
957
|
+
zodTypes: ["ZodString"],
|
|
958
|
+
supportsMultiple: false
|
|
959
|
+
},
|
|
960
|
+
number: {
|
|
961
|
+
type: "number",
|
|
962
|
+
description: "Numeric field (integer or float)",
|
|
963
|
+
zodTypes: ["ZodNumber"],
|
|
964
|
+
supportsMultiple: false
|
|
965
|
+
},
|
|
966
|
+
bool: {
|
|
967
|
+
type: "bool",
|
|
968
|
+
description: "Boolean field",
|
|
969
|
+
zodTypes: ["ZodBoolean"],
|
|
970
|
+
supportsMultiple: false
|
|
971
|
+
},
|
|
972
|
+
date: {
|
|
973
|
+
type: "date",
|
|
974
|
+
description: "Date/datetime field",
|
|
975
|
+
zodTypes: ["ZodDate", "ZodString with datetime format"],
|
|
976
|
+
supportsMultiple: false
|
|
977
|
+
},
|
|
978
|
+
autodate: {
|
|
979
|
+
type: "autodate",
|
|
980
|
+
description: "Auto-managed date field (created/updated)",
|
|
981
|
+
zodTypes: ["ZodString"],
|
|
982
|
+
supportsMultiple: false
|
|
983
|
+
},
|
|
984
|
+
select: {
|
|
985
|
+
type: "select",
|
|
986
|
+
description: "Single or multiple select from predefined values",
|
|
987
|
+
zodTypes: ["ZodEnum", "ZodArray<ZodEnum>"],
|
|
988
|
+
supportsMultiple: true
|
|
989
|
+
},
|
|
990
|
+
relation: {
|
|
991
|
+
type: "relation",
|
|
992
|
+
description: "Reference to another collection",
|
|
993
|
+
zodTypes: ["ZodString", "ZodArray<ZodString>"],
|
|
994
|
+
supportsMultiple: true
|
|
995
|
+
},
|
|
996
|
+
file: {
|
|
997
|
+
type: "file",
|
|
998
|
+
description: "File upload field",
|
|
999
|
+
zodTypes: ["File", "ZodArray<File>"],
|
|
1000
|
+
supportsMultiple: true
|
|
1001
|
+
},
|
|
1002
|
+
json: {
|
|
1003
|
+
type: "json",
|
|
1004
|
+
description: "JSON data field",
|
|
1005
|
+
zodTypes: ["ZodRecord", "ZodObject", "ZodArray"],
|
|
1006
|
+
supportsMultiple: false
|
|
1007
|
+
},
|
|
1008
|
+
geoPoint: {
|
|
1009
|
+
type: "geoPoint",
|
|
1010
|
+
description: "Geographic coordinates (lon, lat)",
|
|
1011
|
+
zodTypes: ["ZodObject with lon/lat"],
|
|
1012
|
+
supportsMultiple: false
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
function mapZodStringType(zodType) {
|
|
1016
|
+
const checks = zodType._def.checks || [];
|
|
1017
|
+
const hasEmail = checks.some((check) => check.kind === "email");
|
|
1018
|
+
if (hasEmail) {
|
|
1019
|
+
return "email";
|
|
1020
|
+
}
|
|
1021
|
+
const hasUrl = checks.some((check) => check.kind === "url");
|
|
1022
|
+
if (hasUrl) {
|
|
1023
|
+
return "url";
|
|
1024
|
+
}
|
|
1025
|
+
const hasDatetime = checks.some((check) => check.kind === "datetime");
|
|
1026
|
+
if (hasDatetime) {
|
|
1027
|
+
return "date";
|
|
1028
|
+
}
|
|
1029
|
+
return "text";
|
|
1030
|
+
}
|
|
1031
|
+
function mapZodNumberType(_zodType) {
|
|
1032
|
+
return "number";
|
|
1033
|
+
}
|
|
1034
|
+
function mapZodBooleanType(_zodType) {
|
|
1035
|
+
return "bool";
|
|
1036
|
+
}
|
|
1037
|
+
function mapZodEnumType(_zodType) {
|
|
1038
|
+
return "select";
|
|
1039
|
+
}
|
|
1040
|
+
function mapZodArrayType(zodType, _fieldName) {
|
|
1041
|
+
const elementType = zodType._def.type;
|
|
1042
|
+
if (elementType instanceof zod.z.ZodType) {
|
|
1043
|
+
const typeName = elementType._def.typeName;
|
|
1044
|
+
if (typeName === "ZodType" && elementType._def?.innerType?.name === "File") {
|
|
1045
|
+
return "file";
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (elementType._def?.typeName === "ZodType") {
|
|
1049
|
+
const checks = elementType._def?.checks || [];
|
|
1050
|
+
const isFileInstance = checks.some(
|
|
1051
|
+
(check) => check.kind === "instanceof" || elementType._def?.innerType?.name === "File"
|
|
1052
|
+
);
|
|
1053
|
+
if (isFileInstance) {
|
|
1054
|
+
return "file";
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (elementType instanceof zod.z.ZodString) {
|
|
1058
|
+
return "relation";
|
|
1059
|
+
}
|
|
1060
|
+
return "json";
|
|
1061
|
+
}
|
|
1062
|
+
function mapZodDateType(_zodType) {
|
|
1063
|
+
return "date";
|
|
1064
|
+
}
|
|
1065
|
+
function mapZodRecordType(_zodType) {
|
|
1066
|
+
return "json";
|
|
1067
|
+
}
|
|
1068
|
+
function mapZodTypeToPocketBase(zodType, fieldName) {
|
|
1069
|
+
let unwrappedType = zodType;
|
|
1070
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
1071
|
+
unwrappedType = zodType._def.innerType;
|
|
1072
|
+
}
|
|
1073
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
1074
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
1075
|
+
}
|
|
1076
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
1077
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
1078
|
+
}
|
|
1079
|
+
if (unwrappedType._def?.typeName === "ZodEffects") {
|
|
1080
|
+
const effect = unwrappedType._def?.effect;
|
|
1081
|
+
if (effect?.type === "refinement") {
|
|
1082
|
+
const fileFieldNames = ["avatar", "image", "file", "attachment", "photo", "picture", "document", "upload"];
|
|
1083
|
+
if (fileFieldNames.some((name) => fieldName.toLowerCase().includes(name))) {
|
|
1084
|
+
return "file";
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (unwrappedType._def?.typeName === "ZodType") {
|
|
1089
|
+
const checks = unwrappedType._def?.checks || [];
|
|
1090
|
+
const innerType = unwrappedType._def?.innerType;
|
|
1091
|
+
if (innerType?.name === "File" || checks.some((check) => check.kind === "instanceof")) {
|
|
1092
|
+
return "file";
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (unwrappedType instanceof zod.z.ZodString) {
|
|
1096
|
+
return mapZodStringType(unwrappedType);
|
|
1097
|
+
}
|
|
1098
|
+
if (unwrappedType instanceof zod.z.ZodNumber) {
|
|
1099
|
+
return mapZodNumberType();
|
|
1100
|
+
}
|
|
1101
|
+
if (unwrappedType instanceof zod.z.ZodBoolean) {
|
|
1102
|
+
return mapZodBooleanType();
|
|
1103
|
+
}
|
|
1104
|
+
if (unwrappedType instanceof zod.z.ZodEnum) {
|
|
1105
|
+
return mapZodEnumType();
|
|
1106
|
+
}
|
|
1107
|
+
if (unwrappedType instanceof zod.z.ZodArray) {
|
|
1108
|
+
return mapZodArrayType(unwrappedType);
|
|
1109
|
+
}
|
|
1110
|
+
if (unwrappedType instanceof zod.z.ZodDate) {
|
|
1111
|
+
return mapZodDateType();
|
|
1112
|
+
}
|
|
1113
|
+
if (unwrappedType instanceof zod.z.ZodRecord || unwrappedType instanceof zod.z.ZodObject) {
|
|
1114
|
+
return mapZodRecordType();
|
|
1115
|
+
}
|
|
1116
|
+
return "text";
|
|
1117
|
+
}
|
|
1118
|
+
function extractFieldOptions(zodType) {
|
|
1119
|
+
const options = {};
|
|
1120
|
+
let unwrappedType = zodType;
|
|
1121
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
1122
|
+
unwrappedType = zodType._def.innerType;
|
|
1123
|
+
}
|
|
1124
|
+
if (unwrappedType instanceof zod.z.ZodNullable) {
|
|
1125
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
1126
|
+
}
|
|
1127
|
+
if (unwrappedType instanceof zod.z.ZodDefault) {
|
|
1128
|
+
unwrappedType = unwrappedType._def.innerType;
|
|
1129
|
+
}
|
|
1130
|
+
const checks = unwrappedType._def?.checks || [];
|
|
1131
|
+
if (unwrappedType instanceof zod.z.ZodString) {
|
|
1132
|
+
for (const check of checks) {
|
|
1133
|
+
if (check.kind === "min") {
|
|
1134
|
+
options.min = check.value;
|
|
1135
|
+
}
|
|
1136
|
+
if (check.kind === "max") {
|
|
1137
|
+
options.max = check.value;
|
|
1138
|
+
}
|
|
1139
|
+
if (check.kind === "regex") {
|
|
1140
|
+
options.pattern = check.regex.source;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (unwrappedType instanceof zod.z.ZodNumber) {
|
|
1145
|
+
for (const check of checks) {
|
|
1146
|
+
if (check.kind === "min") {
|
|
1147
|
+
options.min = check.value;
|
|
1148
|
+
}
|
|
1149
|
+
if (check.kind === "max") {
|
|
1150
|
+
options.max = check.value;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (unwrappedType instanceof zod.z.ZodEnum) {
|
|
1155
|
+
options.values = unwrappedType._def.values;
|
|
1156
|
+
}
|
|
1157
|
+
if (unwrappedType instanceof zod.z.ZodArray) {
|
|
1158
|
+
const arrayChecks = unwrappedType._def?.checks || [];
|
|
1159
|
+
for (const check of arrayChecks) {
|
|
1160
|
+
if (check.kind === "min") {
|
|
1161
|
+
options.minSelect = check.value;
|
|
1162
|
+
}
|
|
1163
|
+
if (check.kind === "max") {
|
|
1164
|
+
options.maxSelect = check.value;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return options;
|
|
1169
|
+
}
|
|
1170
|
+
function isFieldRequired(zodType) {
|
|
1171
|
+
if (zodType instanceof zod.z.ZodOptional) {
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
if (zodType instanceof zod.z.ZodDefault) {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
if (zodType instanceof zod.z.ZodNullable) {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
function unwrapZodType(zodType) {
|
|
1183
|
+
let unwrapped = zodType;
|
|
1184
|
+
if (unwrapped instanceof zod.z.ZodOptional) {
|
|
1185
|
+
unwrapped = unwrapped._def.innerType;
|
|
1186
|
+
}
|
|
1187
|
+
if (unwrapped instanceof zod.z.ZodNullable) {
|
|
1188
|
+
unwrapped = unwrapped._def.innerType;
|
|
1189
|
+
}
|
|
1190
|
+
if (unwrapped instanceof zod.z.ZodDefault) {
|
|
1191
|
+
unwrapped = unwrapped._def.innerType;
|
|
1192
|
+
}
|
|
1193
|
+
return unwrapped;
|
|
1194
|
+
}
|
|
1195
|
+
function getDefaultValue(zodType) {
|
|
1196
|
+
if (zodType instanceof zod.z.ZodDefault) {
|
|
1197
|
+
return zodType._def.defaultValue();
|
|
1198
|
+
}
|
|
1199
|
+
return void 0;
|
|
1200
|
+
}
|
|
1201
|
+
function isArrayType(zodType) {
|
|
1202
|
+
const unwrapped = unwrapZodType(zodType);
|
|
1203
|
+
return unwrapped instanceof zod.z.ZodArray;
|
|
1204
|
+
}
|
|
1205
|
+
function getArrayElementType(zodType) {
|
|
1206
|
+
const unwrapped = unwrapZodType(zodType);
|
|
1207
|
+
if (unwrapped instanceof zod.z.ZodArray) {
|
|
1208
|
+
return unwrapped._def.type;
|
|
1209
|
+
}
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
function isGeoPointType(zodType) {
|
|
1213
|
+
const unwrapped = unwrapZodType(zodType);
|
|
1214
|
+
if (!(unwrapped instanceof zod.z.ZodObject)) {
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
const shape = unwrapped._def.shape();
|
|
1218
|
+
const hasLon = "lon" in shape && shape.lon instanceof zod.z.ZodNumber;
|
|
1219
|
+
const hasLat = "lat" in shape && shape.lat instanceof zod.z.ZodNumber;
|
|
1220
|
+
return hasLon && hasLat;
|
|
1221
|
+
}
|
|
1222
|
+
function extractComprehensiveFieldOptions(zodType) {
|
|
1223
|
+
const options = {};
|
|
1224
|
+
const unwrapped = unwrapZodType(zodType);
|
|
1225
|
+
const checks = unwrapped._def?.checks || [];
|
|
1226
|
+
if (unwrapped instanceof zod.z.ZodString) {
|
|
1227
|
+
for (const check of checks) {
|
|
1228
|
+
if (check.kind === "min") {
|
|
1229
|
+
options.min = check.value;
|
|
1230
|
+
}
|
|
1231
|
+
if (check.kind === "max") {
|
|
1232
|
+
options.max = check.value;
|
|
1233
|
+
}
|
|
1234
|
+
if (check.kind === "regex") {
|
|
1235
|
+
options.pattern = check.regex.source;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (unwrapped instanceof zod.z.ZodNumber) {
|
|
1240
|
+
for (const check of checks) {
|
|
1241
|
+
if (check.kind === "min") {
|
|
1242
|
+
options.min = check.value;
|
|
1243
|
+
}
|
|
1244
|
+
if (check.kind === "max") {
|
|
1245
|
+
options.max = check.value;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (unwrapped instanceof zod.z.ZodEnum) {
|
|
1250
|
+
options.values = unwrapped._def.values;
|
|
1251
|
+
}
|
|
1252
|
+
if (unwrapped instanceof zod.z.ZodArray) {
|
|
1253
|
+
const arrayDef = unwrapped._def;
|
|
1254
|
+
if (arrayDef.minLength) {
|
|
1255
|
+
options.minSelect = arrayDef.minLength.value;
|
|
1256
|
+
}
|
|
1257
|
+
if (arrayDef.maxLength) {
|
|
1258
|
+
options.maxSelect = arrayDef.maxLength.value;
|
|
1259
|
+
}
|
|
1260
|
+
const elementType = arrayDef.type;
|
|
1261
|
+
if (elementType instanceof zod.z.ZodEnum) {
|
|
1262
|
+
options.values = elementType._def.values;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return options;
|
|
1266
|
+
}
|
|
1267
|
+
function isEditorField(fieldName) {
|
|
1268
|
+
const editorFieldNames = [
|
|
1269
|
+
"content",
|
|
1270
|
+
"body",
|
|
1271
|
+
"description",
|
|
1272
|
+
"bio",
|
|
1273
|
+
"about",
|
|
1274
|
+
"summary",
|
|
1275
|
+
"notes",
|
|
1276
|
+
"details",
|
|
1277
|
+
"html",
|
|
1278
|
+
"richtext",
|
|
1279
|
+
"editor"
|
|
1280
|
+
];
|
|
1281
|
+
return editorFieldNames.some((name) => fieldName.toLowerCase().includes(name));
|
|
1282
|
+
}
|
|
1283
|
+
function isFileFieldByName(fieldName) {
|
|
1284
|
+
const fileFieldNames = [
|
|
1285
|
+
"avatar",
|
|
1286
|
+
"image",
|
|
1287
|
+
"file",
|
|
1288
|
+
"attachment",
|
|
1289
|
+
"photo",
|
|
1290
|
+
"picture",
|
|
1291
|
+
"document",
|
|
1292
|
+
"upload",
|
|
1293
|
+
"thumbnail",
|
|
1294
|
+
"cover",
|
|
1295
|
+
"banner",
|
|
1296
|
+
"logo",
|
|
1297
|
+
"icon",
|
|
1298
|
+
"media"
|
|
1299
|
+
];
|
|
1300
|
+
return fileFieldNames.some((name) => fieldName.toLowerCase().includes(name));
|
|
1301
|
+
}
|
|
1302
|
+
function getFieldTypeInfo(zodType, fieldName) {
|
|
1303
|
+
const type = mapZodTypeToPocketBase(zodType, fieldName);
|
|
1304
|
+
const isMultiple = isArrayType(zodType);
|
|
1305
|
+
const options = extractComprehensiveFieldOptions(zodType);
|
|
1306
|
+
return {
|
|
1307
|
+
type,
|
|
1308
|
+
isMultiple,
|
|
1309
|
+
options
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// src/migration/analyzer.ts
|
|
1314
|
+
var DEFAULT_CONFIG = {
|
|
1315
|
+
workspaceRoot: process.cwd(),
|
|
1316
|
+
excludePatterns: [
|
|
1317
|
+
"base.ts",
|
|
1318
|
+
"index.ts",
|
|
1319
|
+
"permissions.ts",
|
|
1320
|
+
"permission-templates.ts",
|
|
1321
|
+
"base.js",
|
|
1322
|
+
"index.js",
|
|
1323
|
+
"permissions.js",
|
|
1324
|
+
"permission-templates.js"
|
|
1325
|
+
],
|
|
1326
|
+
includeExtensions: [".ts", ".js"],
|
|
1327
|
+
schemaPatterns: ["Schema", "InputSchema"],
|
|
1328
|
+
useCompiledFiles: true
|
|
1329
|
+
};
|
|
1330
|
+
function mergeConfig(config) {
|
|
1331
|
+
return {
|
|
1332
|
+
...DEFAULT_CONFIG,
|
|
1333
|
+
...config,
|
|
1334
|
+
excludePatterns: config.excludePatterns || DEFAULT_CONFIG.excludePatterns,
|
|
1335
|
+
includeExtensions: config.includeExtensions || DEFAULT_CONFIG.includeExtensions,
|
|
1336
|
+
schemaPatterns: config.schemaPatterns || DEFAULT_CONFIG.schemaPatterns
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function resolveSchemaDir(config) {
|
|
1340
|
+
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
1341
|
+
if (path__namespace.isAbsolute(config.schemaDir)) {
|
|
1342
|
+
return config.schemaDir;
|
|
1343
|
+
}
|
|
1344
|
+
return path__namespace.join(workspaceRoot, config.schemaDir);
|
|
1345
|
+
}
|
|
1346
|
+
function discoverSchemaFiles(config) {
|
|
1347
|
+
const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
|
|
1348
|
+
const mergedConfig = mergeConfig(normalizedConfig);
|
|
1349
|
+
const schemaDir = resolveSchemaDir(normalizedConfig);
|
|
1350
|
+
try {
|
|
1351
|
+
if (!fs2__namespace.existsSync(schemaDir)) {
|
|
1352
|
+
throw new FileSystemError(`Schema directory not found: ${schemaDir}`, schemaDir, "access", "ENOENT");
|
|
1353
|
+
}
|
|
1354
|
+
const files = fs2__namespace.readdirSync(schemaDir);
|
|
1355
|
+
const schemaFiles = files.filter((file) => {
|
|
1356
|
+
const hasValidExtension = mergedConfig.includeExtensions.some((ext) => file.endsWith(ext));
|
|
1357
|
+
if (!hasValidExtension) return false;
|
|
1358
|
+
const isExcluded = mergedConfig.excludePatterns.some((pattern) => {
|
|
1359
|
+
if (pattern.includes("*")) {
|
|
1360
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
1361
|
+
return regex.test(file);
|
|
1362
|
+
}
|
|
1363
|
+
return file === pattern;
|
|
1364
|
+
});
|
|
1365
|
+
if (isExcluded) return false;
|
|
1366
|
+
return true;
|
|
1367
|
+
});
|
|
1368
|
+
return schemaFiles.map((file) => {
|
|
1369
|
+
const ext = mergedConfig.includeExtensions.find((ext2) => file.endsWith(ext2)) || ".ts";
|
|
1370
|
+
return path__namespace.join(schemaDir, file.replace(new RegExp(`\\${ext}$`), ""));
|
|
1371
|
+
});
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
if (error instanceof FileSystemError) {
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1376
|
+
const fsError = error;
|
|
1377
|
+
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
1378
|
+
throw new FileSystemError(
|
|
1379
|
+
`Permission denied reading schema directory: ${schemaDir}`,
|
|
1380
|
+
schemaDir,
|
|
1381
|
+
"read",
|
|
1382
|
+
fsError.code,
|
|
1383
|
+
error
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
throw new FileSystemError(
|
|
1387
|
+
`Failed to read schema directory: ${schemaDir}`,
|
|
1388
|
+
schemaDir,
|
|
1389
|
+
"read",
|
|
1390
|
+
fsError.code,
|
|
1391
|
+
error
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async function importSchemaModule(filePath, config) {
|
|
1396
|
+
try {
|
|
1397
|
+
let importPath = filePath;
|
|
1398
|
+
if (config?.pathTransformer) {
|
|
1399
|
+
importPath = config.pathTransformer(filePath);
|
|
1400
|
+
}
|
|
1401
|
+
if (!importPath.endsWith(".js")) {
|
|
1402
|
+
importPath = `${importPath}.js`;
|
|
1403
|
+
}
|
|
1404
|
+
const fileUrl = new URL(`file://${path__namespace.resolve(importPath)}`);
|
|
1405
|
+
const module = await import(fileUrl.href);
|
|
1406
|
+
return module;
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
throw new SchemaParsingError(
|
|
1409
|
+
`Failed to import schema module. Make sure the schema files are compiled to JavaScript.`,
|
|
1410
|
+
filePath,
|
|
1411
|
+
error
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function getCollectionNameFromFile(filePath) {
|
|
1416
|
+
const filename = path__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1417
|
+
return toCollectionName(filename);
|
|
1418
|
+
}
|
|
1419
|
+
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema"]) {
|
|
1420
|
+
const result = {};
|
|
1421
|
+
for (const [key, value] of Object.entries(module)) {
|
|
1422
|
+
if (value instanceof zod.z.ZodObject) {
|
|
1423
|
+
if (patterns.includes("InputSchema") && key.endsWith("InputSchema")) {
|
|
1424
|
+
result.inputSchema = value;
|
|
1425
|
+
} else if (patterns.includes("Schema") && key.endsWith("Schema") && !key.endsWith("InputSchema")) {
|
|
1426
|
+
result.schema = value;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return result;
|
|
1431
|
+
}
|
|
1432
|
+
function selectSchemaForCollection(schemas) {
|
|
1433
|
+
if (schemas.schema) {
|
|
1434
|
+
return schemas.schema;
|
|
1435
|
+
}
|
|
1436
|
+
if (schemas.inputSchema) {
|
|
1437
|
+
return schemas.inputSchema;
|
|
1438
|
+
}
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
function extractFieldDefinitions(zodSchema, excludeFields) {
|
|
1442
|
+
const shape = zodSchema.shape;
|
|
1443
|
+
const fields = [];
|
|
1444
|
+
const baseFields = ["id", "collectionId", "collectionName", "created", "updated", "expand"];
|
|
1445
|
+
const defaultExcludeFields = ["thumbnailURL", "imageFiles"];
|
|
1446
|
+
const allExclusions = /* @__PURE__ */ new Set([...baseFields, ...defaultExcludeFields, ...excludeFields || []]);
|
|
1447
|
+
for (const [fieldName, zodType] of Object.entries(shape)) {
|
|
1448
|
+
if (!allExclusions.has(fieldName)) {
|
|
1449
|
+
fields.push({ name: fieldName, zodType });
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return fields;
|
|
1453
|
+
}
|
|
1454
|
+
function isAuthCollection(fields) {
|
|
1455
|
+
const fieldNames = fields.map((f) => f.name.toLowerCase());
|
|
1456
|
+
const hasEmail = fieldNames.includes("email");
|
|
1457
|
+
const hasPassword = fieldNames.includes("password");
|
|
1458
|
+
return hasEmail && hasPassword;
|
|
1459
|
+
}
|
|
1460
|
+
function buildFieldDefinition(fieldName, zodType) {
|
|
1461
|
+
const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
|
|
1462
|
+
const required = isFieldRequired(zodType);
|
|
1463
|
+
const options = extractFieldOptions(zodType);
|
|
1464
|
+
const fieldDef = {
|
|
1465
|
+
name: fieldName,
|
|
1466
|
+
type: fieldType,
|
|
1467
|
+
required,
|
|
1468
|
+
options
|
|
1469
|
+
};
|
|
1470
|
+
if (isRelationField(fieldName, zodType)) {
|
|
1471
|
+
fieldDef.type = "relation";
|
|
1472
|
+
const targetCollection = resolveTargetCollection(fieldName);
|
|
1473
|
+
const maxSelect = getMaxSelect(fieldName, zodType);
|
|
1474
|
+
const minSelect = getMinSelect(fieldName, zodType);
|
|
1475
|
+
fieldDef.relation = {
|
|
1476
|
+
collection: targetCollection,
|
|
1477
|
+
maxSelect,
|
|
1478
|
+
minSelect,
|
|
1479
|
+
cascadeDelete: false
|
|
1480
|
+
// Default to false, can be configured later
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
return fieldDef;
|
|
1484
|
+
}
|
|
1485
|
+
function extractIndexes(schema) {
|
|
1486
|
+
const schemaDescription = schema.description;
|
|
1487
|
+
if (!schemaDescription) {
|
|
1488
|
+
return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const metadata = JSON.parse(schemaDescription);
|
|
1492
|
+
if (metadata.indexes && Array.isArray(metadata.indexes)) {
|
|
1493
|
+
return metadata.indexes;
|
|
1494
|
+
}
|
|
1495
|
+
} catch {
|
|
1496
|
+
}
|
|
1497
|
+
return void 0;
|
|
1498
|
+
}
|
|
1499
|
+
function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
1500
|
+
const rawFields = extractFieldDefinitions(zodSchema);
|
|
1501
|
+
const collectionType = isAuthCollection(rawFields) ? "auth" : "base";
|
|
1502
|
+
const fields = rawFields.map(({ name, zodType }) => buildFieldDefinition(name, zodType));
|
|
1503
|
+
const indexes = extractIndexes(zodSchema) || [];
|
|
1504
|
+
const permissionAnalyzer = new PermissionAnalyzer();
|
|
1505
|
+
let permissions = void 0;
|
|
1506
|
+
const schemaDescription = zodSchema.description;
|
|
1507
|
+
const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
|
|
1508
|
+
if (extractedPermissions) {
|
|
1509
|
+
const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
|
|
1510
|
+
const validationResults = permissionAnalyzer.validatePermissions(
|
|
1511
|
+
collectionName,
|
|
1512
|
+
resolvedPermissions,
|
|
1513
|
+
fields,
|
|
1514
|
+
collectionType === "auth"
|
|
1515
|
+
);
|
|
1516
|
+
for (const [ruleType, result] of validationResults) {
|
|
1517
|
+
if (!result.valid) {
|
|
1518
|
+
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1519
|
+
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1520
|
+
}
|
|
1521
|
+
if (result.warnings.length > 0) {
|
|
1522
|
+
console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
|
|
1523
|
+
result.warnings.forEach((warning) => console.warn(` - ${warning}`));
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
|
|
1527
|
+
}
|
|
1528
|
+
const collectionSchema = {
|
|
1529
|
+
name: collectionName,
|
|
1530
|
+
type: collectionType,
|
|
1531
|
+
fields,
|
|
1532
|
+
indexes,
|
|
1533
|
+
rules: {
|
|
1534
|
+
listRule: null,
|
|
1535
|
+
viewRule: null,
|
|
1536
|
+
createRule: null,
|
|
1537
|
+
updateRule: null,
|
|
1538
|
+
deleteRule: null
|
|
1539
|
+
},
|
|
1540
|
+
permissions
|
|
1541
|
+
};
|
|
1542
|
+
return collectionSchema;
|
|
1543
|
+
}
|
|
1544
|
+
async function buildSchemaDefinition(config) {
|
|
1545
|
+
const normalizedConfig = typeof config === "string" ? { schemaDir: config } : config;
|
|
1546
|
+
const mergedConfig = mergeConfig(normalizedConfig);
|
|
1547
|
+
const collections = /* @__PURE__ */ new Map();
|
|
1548
|
+
const schemaFiles = discoverSchemaFiles(normalizedConfig);
|
|
1549
|
+
if (schemaFiles.length === 0) {
|
|
1550
|
+
const schemaDir = resolveSchemaDir(normalizedConfig);
|
|
1551
|
+
throw new SchemaParsingError(
|
|
1552
|
+
`No schema files found in ${schemaDir}. Make sure you have schema files in the directory.`,
|
|
1553
|
+
schemaDir
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
for (const filePath of schemaFiles) {
|
|
1557
|
+
try {
|
|
1558
|
+
let importPath = filePath;
|
|
1559
|
+
if (normalizedConfig.pathTransformer) {
|
|
1560
|
+
importPath = normalizedConfig.pathTransformer(filePath);
|
|
1561
|
+
} else if (mergedConfig.useCompiledFiles) {
|
|
1562
|
+
importPath = filePath.replace(/\/src\//, "/dist/");
|
|
1563
|
+
}
|
|
1564
|
+
const module = await importSchemaModule(importPath, normalizedConfig);
|
|
1565
|
+
const schemas = extractSchemaDefinitions(module, mergedConfig.schemaPatterns);
|
|
1566
|
+
const zodSchema = selectSchemaForCollection(schemas);
|
|
1567
|
+
if (!zodSchema) {
|
|
1568
|
+
console.warn(`No valid schema found in ${filePath}, skipping...`);
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
const collectionName = getCollectionNameFromFile(filePath);
|
|
1572
|
+
const collectionSchema = convertZodSchemaToCollectionSchema(collectionName, zodSchema);
|
|
1573
|
+
collections.set(collectionName, collectionSchema);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (error instanceof SchemaParsingError) {
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1578
|
+
throw new SchemaParsingError(
|
|
1579
|
+
`Error processing schema file: ${error instanceof Error ? error.message : String(error)}`,
|
|
1580
|
+
filePath,
|
|
1581
|
+
error
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return { collections };
|
|
1586
|
+
}
|
|
1587
|
+
async function parseSchemaFiles(config) {
|
|
1588
|
+
return buildSchemaDefinition(config);
|
|
1589
|
+
}
|
|
1590
|
+
var SchemaAnalyzer = class {
|
|
1591
|
+
config;
|
|
1592
|
+
constructor(config) {
|
|
1593
|
+
this.config = mergeConfig(config);
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Discovers schema files in the configured directory
|
|
1597
|
+
*/
|
|
1598
|
+
discoverSchemaFiles() {
|
|
1599
|
+
return discoverSchemaFiles(this.config);
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Parses all schema files and returns a SchemaDefinition
|
|
1603
|
+
*/
|
|
1604
|
+
async parseSchemaFiles() {
|
|
1605
|
+
return buildSchemaDefinition(this.config);
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Converts a single Zod schema to a CollectionSchema
|
|
1609
|
+
*/
|
|
1610
|
+
convertZodSchemaToCollectionSchema(name, schema) {
|
|
1611
|
+
return convertZodSchemaToCollectionSchema(name, schema);
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
var SNAPSHOT_VERSION = "1.0.0";
|
|
1615
|
+
var DEFAULT_SNAPSHOT_FILENAME = ".migration-snapshot.json";
|
|
1616
|
+
var SNAPSHOT_MIGRATIONS = [
|
|
1617
|
+
// Add migrations here as the format evolves
|
|
1618
|
+
// Example:
|
|
1619
|
+
// {
|
|
1620
|
+
// fromVersion: '0.9.0',
|
|
1621
|
+
// toVersion: '1.0.0',
|
|
1622
|
+
// migrate: (data) => ({ ...data, newField: 'default' })
|
|
1623
|
+
// }
|
|
1624
|
+
];
|
|
1625
|
+
var DEFAULT_CONFIG2 = {
|
|
1626
|
+
snapshotPath: DEFAULT_SNAPSHOT_FILENAME,
|
|
1627
|
+
workspaceRoot: process.cwd(),
|
|
1628
|
+
autoMigrate: true,
|
|
1629
|
+
version: SNAPSHOT_VERSION
|
|
1630
|
+
};
|
|
1631
|
+
function mergeConfig2(config = {}) {
|
|
1632
|
+
return {
|
|
1633
|
+
...DEFAULT_CONFIG2,
|
|
1634
|
+
...config
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function getSnapshotPath(config = {}) {
|
|
1638
|
+
const mergedConfig = mergeConfig2(config);
|
|
1639
|
+
const workspaceRoot = mergedConfig.workspaceRoot;
|
|
1640
|
+
const snapshotFilename = mergedConfig.snapshotPath;
|
|
1641
|
+
if (path__namespace.isAbsolute(snapshotFilename)) {
|
|
1642
|
+
return snapshotFilename;
|
|
1643
|
+
}
|
|
1644
|
+
return path__namespace.join(workspaceRoot, snapshotFilename);
|
|
1645
|
+
}
|
|
1646
|
+
function snapshotExists(config = {}) {
|
|
1647
|
+
try {
|
|
1648
|
+
const snapshotPath = getSnapshotPath(config);
|
|
1649
|
+
return fs2__namespace.existsSync(snapshotPath);
|
|
1650
|
+
} catch {
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
function handleFileSystemError(error, operation, filePath) {
|
|
1655
|
+
const fsError = error;
|
|
1656
|
+
if (fsError.code === "ENOENT") {
|
|
1657
|
+
throw new SnapshotError(`Snapshot file not found: ${filePath}`, filePath, operation, error);
|
|
1658
|
+
} else if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
1659
|
+
throw new FileSystemError(
|
|
1660
|
+
`Permission denied ${operation === "read" ? "reading" : "writing"} snapshot file. Check file permissions.`,
|
|
1661
|
+
filePath,
|
|
1662
|
+
operation,
|
|
1663
|
+
fsError.code,
|
|
1664
|
+
error
|
|
1665
|
+
);
|
|
1666
|
+
} else if (fsError.code === "ENOSPC") {
|
|
1667
|
+
throw new FileSystemError(
|
|
1668
|
+
`No space left on device when ${operation === "read" ? "reading" : "writing"} snapshot file.`,
|
|
1669
|
+
filePath,
|
|
1670
|
+
operation,
|
|
1671
|
+
fsError.code,
|
|
1672
|
+
error
|
|
1673
|
+
);
|
|
1674
|
+
} else {
|
|
1675
|
+
throw new SnapshotError(`Failed to ${operation} snapshot file: ${error.message}`, filePath, operation, error);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function serializeMap(map) {
|
|
1679
|
+
const obj = {};
|
|
1680
|
+
for (const [key, value] of map.entries()) {
|
|
1681
|
+
obj[key] = value;
|
|
1682
|
+
}
|
|
1683
|
+
return obj;
|
|
1684
|
+
}
|
|
1685
|
+
function deserializeMap(obj) {
|
|
1686
|
+
const map = /* @__PURE__ */ new Map();
|
|
1687
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1688
|
+
map.set(key, value);
|
|
1689
|
+
}
|
|
1690
|
+
return map;
|
|
1691
|
+
}
|
|
1692
|
+
function serializeSchemaDefinition(schema) {
|
|
1693
|
+
return {
|
|
1694
|
+
collections: serializeMap(schema.collections)
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
function addSnapshotMetadata(schema, config) {
|
|
1698
|
+
const mergedConfig = mergeConfig2(config);
|
|
1699
|
+
return {
|
|
1700
|
+
version: mergedConfig.version,
|
|
1701
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1702
|
+
...serializeSchemaDefinition(schema)
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
function saveSnapshot(schema, config = {}) {
|
|
1706
|
+
const snapshotPath = getSnapshotPath(config);
|
|
1707
|
+
try {
|
|
1708
|
+
const snapshotDir = path__namespace.dirname(snapshotPath);
|
|
1709
|
+
if (!fs2__namespace.existsSync(snapshotDir)) {
|
|
1710
|
+
fs2__namespace.mkdirSync(snapshotDir, { recursive: true });
|
|
1711
|
+
}
|
|
1712
|
+
const snapshotData = addSnapshotMetadata(schema, config);
|
|
1713
|
+
const jsonContent = JSON.stringify(snapshotData, null, 2);
|
|
1714
|
+
fs2__namespace.writeFileSync(snapshotPath, jsonContent, "utf-8");
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
handleFileSystemError(error, "write", snapshotPath);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
function parseAndValidateSnapshot(jsonContent, snapshotPath) {
|
|
1720
|
+
try {
|
|
1721
|
+
const data = JSON.parse(jsonContent);
|
|
1722
|
+
if (!data.version) {
|
|
1723
|
+
throw new SnapshotError(
|
|
1724
|
+
"Snapshot file is missing version field. The snapshot may be corrupted.",
|
|
1725
|
+
snapshotPath,
|
|
1726
|
+
"validate"
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
if (!data.timestamp) {
|
|
1730
|
+
throw new SnapshotError(
|
|
1731
|
+
"Snapshot file is missing timestamp field. The snapshot may be corrupted.",
|
|
1732
|
+
snapshotPath,
|
|
1733
|
+
"validate"
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
if (!data.collections) {
|
|
1737
|
+
throw new SnapshotError(
|
|
1738
|
+
"Snapshot file is missing collections field. The snapshot may be corrupted.",
|
|
1739
|
+
snapshotPath,
|
|
1740
|
+
"validate"
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
return data;
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
if (error instanceof SnapshotError) {
|
|
1746
|
+
throw error;
|
|
1747
|
+
}
|
|
1748
|
+
if (error instanceof SyntaxError) {
|
|
1749
|
+
throw new SnapshotError(
|
|
1750
|
+
`Invalid JSON in snapshot file. The file may be corrupted or manually edited incorrectly.`,
|
|
1751
|
+
snapshotPath,
|
|
1752
|
+
"parse",
|
|
1753
|
+
error
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
throw error;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function compareVersions(a, b) {
|
|
1760
|
+
const partsA = a.split(".").map(Number);
|
|
1761
|
+
const partsB = b.split(".").map(Number);
|
|
1762
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
1763
|
+
const numA = partsA[i] || 0;
|
|
1764
|
+
const numB = partsB[i] || 0;
|
|
1765
|
+
if (numA < numB) return -1;
|
|
1766
|
+
if (numA > numB) return 1;
|
|
1767
|
+
}
|
|
1768
|
+
return 0;
|
|
1769
|
+
}
|
|
1770
|
+
function migrateSnapshotFormat(data, config) {
|
|
1771
|
+
const mergedConfig = mergeConfig2(config);
|
|
1772
|
+
const currentVersion = data.version;
|
|
1773
|
+
const targetVersion = mergedConfig.version;
|
|
1774
|
+
if (currentVersion === targetVersion) {
|
|
1775
|
+
return data;
|
|
1776
|
+
}
|
|
1777
|
+
if (!mergedConfig.autoMigrate) {
|
|
1778
|
+
console.warn(
|
|
1779
|
+
`Snapshot version ${currentVersion} differs from current ${targetVersion}, but auto-migrate is disabled.`
|
|
1780
|
+
);
|
|
1781
|
+
return data;
|
|
1782
|
+
}
|
|
1783
|
+
let migratedData = { ...data };
|
|
1784
|
+
let currentMigrationVersion = currentVersion;
|
|
1785
|
+
const sortedMigrations = [...SNAPSHOT_MIGRATIONS].sort((a, b) => compareVersions(a.fromVersion, b.fromVersion));
|
|
1786
|
+
for (const migration of sortedMigrations) {
|
|
1787
|
+
if (compareVersions(currentMigrationVersion, migration.fromVersion) === 0) {
|
|
1788
|
+
console.log(`Migrating snapshot from ${migration.fromVersion} to ${migration.toVersion}...`);
|
|
1789
|
+
migratedData = migration.migrate(migratedData);
|
|
1790
|
+
migratedData.version = migration.toVersion;
|
|
1791
|
+
currentMigrationVersion = migration.toVersion;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (compareVersions(currentMigrationVersion, targetVersion) !== 0) {
|
|
1795
|
+
console.warn(`Unknown snapshot version ${currentVersion}, attempting to load anyway...`);
|
|
1796
|
+
}
|
|
1797
|
+
return migratedData;
|
|
1798
|
+
}
|
|
1799
|
+
function deserializeSnapshot(data) {
|
|
1800
|
+
return {
|
|
1801
|
+
version: data.version,
|
|
1802
|
+
timestamp: data.timestamp,
|
|
1803
|
+
collections: deserializeMap(data.collections)
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
function loadSnapshot(config = {}) {
|
|
1807
|
+
const snapshotPath = getSnapshotPath(config);
|
|
1808
|
+
try {
|
|
1809
|
+
const jsonContent = fs2__namespace.readFileSync(snapshotPath, "utf-8");
|
|
1810
|
+
const data = parseAndValidateSnapshot(jsonContent, snapshotPath);
|
|
1811
|
+
const migratedData = migrateSnapshotFormat(data, config);
|
|
1812
|
+
return deserializeSnapshot(migratedData);
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
if (error instanceof SnapshotError || error instanceof FileSystemError) {
|
|
1815
|
+
throw error;
|
|
1816
|
+
}
|
|
1817
|
+
if (error.code === "ENOENT") {
|
|
1818
|
+
throw new SnapshotError(
|
|
1819
|
+
`Snapshot file not found. This may be the first migration run.`,
|
|
1820
|
+
snapshotPath,
|
|
1821
|
+
"read",
|
|
1822
|
+
error
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
handleFileSystemError(error, "read", snapshotPath);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
function mergeSnapshots(baseSnapshot, customSnapshot) {
|
|
1829
|
+
if (!customSnapshot) {
|
|
1830
|
+
return baseSnapshot;
|
|
1831
|
+
}
|
|
1832
|
+
const mergedCollections = new Map(baseSnapshot.collections);
|
|
1833
|
+
for (const [name, schema] of customSnapshot.collections.entries()) {
|
|
1834
|
+
mergedCollections.set(name, schema);
|
|
1835
|
+
}
|
|
1836
|
+
return {
|
|
1837
|
+
version: customSnapshot.version || baseSnapshot.version,
|
|
1838
|
+
timestamp: customSnapshot.timestamp || baseSnapshot.timestamp,
|
|
1839
|
+
collections: mergedCollections
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
function findLatestSnapshot(migrationsPath) {
|
|
1843
|
+
try {
|
|
1844
|
+
if (!fs2__namespace.existsSync(migrationsPath)) {
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
const files = fs2__namespace.readdirSync(migrationsPath);
|
|
1848
|
+
const snapshotFiles = files.filter(
|
|
1849
|
+
(file) => file.endsWith("_collections_snapshot.js") || file.endsWith("_snapshot.js")
|
|
1850
|
+
);
|
|
1851
|
+
if (snapshotFiles.length === 0) {
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
snapshotFiles.sort().reverse();
|
|
1855
|
+
const latestSnapshot = snapshotFiles[0];
|
|
1856
|
+
if (!latestSnapshot) {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
return path__namespace.join(migrationsPath, latestSnapshot);
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
console.warn(`Error finding latest snapshot: ${error}`);
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
function loadSnapshotIfExists(config = {}) {
|
|
1866
|
+
const migrationsPath = config.migrationsPath;
|
|
1867
|
+
if (!migrationsPath) {
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
if (fs2__namespace.existsSync(migrationsPath) && fs2__namespace.statSync(migrationsPath).isFile()) {
|
|
1871
|
+
try {
|
|
1872
|
+
const migrationContent = fs2__namespace.readFileSync(migrationsPath, "utf-8");
|
|
1873
|
+
return convertPocketBaseMigration(migrationContent);
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
console.warn(`Failed to load snapshot from ${migrationsPath}: ${error}`);
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const latestSnapshotPath = findLatestSnapshot(migrationsPath);
|
|
1880
|
+
if (latestSnapshotPath) {
|
|
1881
|
+
try {
|
|
1882
|
+
const migrationContent = fs2__namespace.readFileSync(latestSnapshotPath, "utf-8");
|
|
1883
|
+
return convertPocketBaseMigration(migrationContent);
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
console.warn(`Failed to load snapshot from ${latestSnapshotPath}: ${error}`);
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
function convertPocketBaseCollection(pbCollection) {
|
|
1892
|
+
const fields = [];
|
|
1893
|
+
const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
|
|
1894
|
+
const authSystemFieldNames = ["email", "emailVisibility", "verified", "password", "tokenKey"];
|
|
1895
|
+
if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1896
|
+
for (const pbField of pbCollection.fields) {
|
|
1897
|
+
if (pbField.system || systemFieldNames.includes(pbField.name)) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (pbCollection.type === "auth" && authSystemFieldNames.includes(pbField.name)) {
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
const field = {
|
|
1904
|
+
name: pbField.name,
|
|
1905
|
+
type: pbField.type,
|
|
1906
|
+
required: pbField.required || false
|
|
1907
|
+
};
|
|
1908
|
+
if (pbField.options) {
|
|
1909
|
+
field.options = pbField.options;
|
|
1910
|
+
}
|
|
1911
|
+
if (pbField.type === "relation") {
|
|
1912
|
+
field.relation = {
|
|
1913
|
+
collection: pbField.options?.collectionId || "",
|
|
1914
|
+
cascadeDelete: pbField.options?.cascadeDelete || false,
|
|
1915
|
+
maxSelect: pbField.options?.maxSelect,
|
|
1916
|
+
minSelect: pbField.options?.minSelect
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
fields.push(field);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
const schema = {
|
|
1923
|
+
name: pbCollection.name,
|
|
1924
|
+
type: pbCollection.type || "base",
|
|
1925
|
+
fields
|
|
1926
|
+
};
|
|
1927
|
+
if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
|
|
1928
|
+
schema.indexes = pbCollection.indexes;
|
|
1929
|
+
}
|
|
1930
|
+
const rules = {};
|
|
1931
|
+
if (pbCollection.listRule !== void 0) rules.listRule = pbCollection.listRule;
|
|
1932
|
+
if (pbCollection.viewRule !== void 0) rules.viewRule = pbCollection.viewRule;
|
|
1933
|
+
if (pbCollection.createRule !== void 0) rules.createRule = pbCollection.createRule;
|
|
1934
|
+
if (pbCollection.updateRule !== void 0) rules.updateRule = pbCollection.updateRule;
|
|
1935
|
+
if (pbCollection.deleteRule !== void 0) rules.deleteRule = pbCollection.deleteRule;
|
|
1936
|
+
if (pbCollection.manageRule !== void 0) rules.manageRule = pbCollection.manageRule;
|
|
1937
|
+
if (Object.keys(rules).length > 0) {
|
|
1938
|
+
schema.rules = rules;
|
|
1939
|
+
}
|
|
1940
|
+
return schema;
|
|
1941
|
+
}
|
|
1942
|
+
function convertPocketBaseMigration(migrationContent) {
|
|
1943
|
+
try {
|
|
1944
|
+
const snapshotMatch = migrationContent.match(/const\s+snapshot\s*=\s*(\[[\s\S]*?\]);/);
|
|
1945
|
+
if (!snapshotMatch) {
|
|
1946
|
+
throw new Error("Could not find snapshot array in migration file");
|
|
1947
|
+
}
|
|
1948
|
+
const snapshotArrayStr = snapshotMatch[1];
|
|
1949
|
+
let snapshotArray;
|
|
1950
|
+
try {
|
|
1951
|
+
snapshotArray = new Function(`return ${snapshotArrayStr}`)();
|
|
1952
|
+
} catch (parseError) {
|
|
1953
|
+
throw new Error(`Failed to parse snapshot array: ${parseError}`);
|
|
1954
|
+
}
|
|
1955
|
+
if (!Array.isArray(snapshotArray)) {
|
|
1956
|
+
throw new Error("Snapshot is not an array");
|
|
1957
|
+
}
|
|
1958
|
+
const collections = /* @__PURE__ */ new Map();
|
|
1959
|
+
for (const pbCollection of snapshotArray) {
|
|
1960
|
+
if (!pbCollection.name) {
|
|
1961
|
+
console.warn("Skipping collection without name");
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
const schema = convertPocketBaseCollection(pbCollection);
|
|
1965
|
+
collections.set(pbCollection.name, schema);
|
|
1966
|
+
}
|
|
1967
|
+
return {
|
|
1968
|
+
version: SNAPSHOT_VERSION,
|
|
1969
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1970
|
+
collections
|
|
1971
|
+
};
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
throw new SnapshotError(
|
|
1974
|
+
`Failed to convert PocketBase migration: ${error instanceof Error ? error.message : String(error)}`,
|
|
1975
|
+
void 0,
|
|
1976
|
+
"parse",
|
|
1977
|
+
error instanceof Error ? error : void 0
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function loadBaseMigration(migrationPath) {
|
|
1982
|
+
try {
|
|
1983
|
+
if (!fs2__namespace.existsSync(migrationPath)) {
|
|
1984
|
+
throw new SnapshotError(
|
|
1985
|
+
`Base migration file not found: ${migrationPath}
|
|
1986
|
+
|
|
1987
|
+
This file should contain PocketBase's initial schema.
|
|
1988
|
+
Please ensure PocketBase is properly set up by running 'yarn setup'.
|
|
1989
|
+
If the file exists in a different location, update the configuration.`,
|
|
1990
|
+
migrationPath,
|
|
1991
|
+
"read"
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
const migrationContent = fs2__namespace.readFileSync(migrationPath, "utf-8");
|
|
1995
|
+
const snapshot = convertPocketBaseMigration(migrationContent);
|
|
1996
|
+
return snapshot;
|
|
1997
|
+
} catch (error) {
|
|
1998
|
+
if (error instanceof SnapshotError) {
|
|
1999
|
+
throw error;
|
|
2000
|
+
}
|
|
2001
|
+
if (error.code === "ENOENT") {
|
|
2002
|
+
throw new SnapshotError(
|
|
2003
|
+
`Base migration file not found: ${migrationPath}
|
|
2004
|
+
|
|
2005
|
+
This file should contain PocketBase's initial schema.
|
|
2006
|
+
Please ensure PocketBase is properly set up by running 'yarn setup'.`,
|
|
2007
|
+
migrationPath,
|
|
2008
|
+
"read",
|
|
2009
|
+
error
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
2013
|
+
throw new FileSystemError(
|
|
2014
|
+
`Permission denied reading base migration file. Check file permissions.`,
|
|
2015
|
+
migrationPath,
|
|
2016
|
+
"read",
|
|
2017
|
+
error.code,
|
|
2018
|
+
error
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
throw new SnapshotError(
|
|
2022
|
+
`Failed to load base migration: ${error instanceof Error ? error.message : String(error)}`,
|
|
2023
|
+
migrationPath,
|
|
2024
|
+
"read",
|
|
2025
|
+
error instanceof Error ? error : void 0
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
function getSnapshotVersion() {
|
|
2030
|
+
return SNAPSHOT_VERSION;
|
|
2031
|
+
}
|
|
2032
|
+
function validateSnapshot(snapshot) {
|
|
2033
|
+
const issues = [];
|
|
2034
|
+
if (!snapshot.version) {
|
|
2035
|
+
issues.push("Missing version field");
|
|
2036
|
+
} else if (compareVersions(snapshot.version, SNAPSHOT_VERSION) > 0) {
|
|
2037
|
+
issues.push(`Snapshot version ${snapshot.version} is newer than supported version ${SNAPSHOT_VERSION}`);
|
|
2038
|
+
}
|
|
2039
|
+
if (!snapshot.timestamp) {
|
|
2040
|
+
issues.push("Missing timestamp field");
|
|
2041
|
+
}
|
|
2042
|
+
if (!snapshot.collections) {
|
|
2043
|
+
issues.push("Missing collections field");
|
|
2044
|
+
} else if (!(snapshot.collections instanceof Map)) {
|
|
2045
|
+
issues.push("Collections field is not a Map");
|
|
2046
|
+
}
|
|
2047
|
+
return {
|
|
2048
|
+
valid: issues.length === 0,
|
|
2049
|
+
issues
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
var SnapshotManager = class {
|
|
2053
|
+
config;
|
|
2054
|
+
constructor(config = {}) {
|
|
2055
|
+
this.config = mergeConfig2(config);
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Loads the current snapshot
|
|
2059
|
+
*/
|
|
2060
|
+
loadSnapshot() {
|
|
2061
|
+
return loadSnapshot(this.config);
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Saves a schema as a snapshot
|
|
2065
|
+
*/
|
|
2066
|
+
saveSnapshot(schema) {
|
|
2067
|
+
saveSnapshot(schema, this.config);
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Loads snapshot if it exists, returns null otherwise
|
|
2071
|
+
*/
|
|
2072
|
+
loadSnapshotIfExists() {
|
|
2073
|
+
return loadSnapshotIfExists(this.config);
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Checks if a snapshot exists
|
|
2077
|
+
*/
|
|
2078
|
+
snapshotExists() {
|
|
2079
|
+
return snapshotExists(this.config);
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Converts a PocketBase migration to a snapshot
|
|
2083
|
+
*/
|
|
2084
|
+
convertPocketBaseMigration(content) {
|
|
2085
|
+
return convertPocketBaseMigration(content);
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Gets the snapshot file path
|
|
2089
|
+
*/
|
|
2090
|
+
getSnapshotPath() {
|
|
2091
|
+
return getSnapshotPath(this.config);
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Validates a snapshot
|
|
2095
|
+
*/
|
|
2096
|
+
validateSnapshot(snapshot) {
|
|
2097
|
+
return validateSnapshot(snapshot);
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
// src/migration/diff.ts
|
|
2102
|
+
var DEFAULT_CONFIG3 = {
|
|
2103
|
+
warnOnDelete: true,
|
|
2104
|
+
requireForceForDestructive: true,
|
|
2105
|
+
severityThreshold: "high",
|
|
2106
|
+
systemCollections: ["_mfas", "_otps", "_externalAuths", "_authOrigins", "_superusers"],
|
|
2107
|
+
usersSystemFields: ["id", "password", "tokenKey", "email", "emailVisibility", "verified", "created", "updated"]
|
|
2108
|
+
};
|
|
2109
|
+
function mergeConfig3(config) {
|
|
2110
|
+
return {
|
|
2111
|
+
...DEFAULT_CONFIG3,
|
|
2112
|
+
...config
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
function isSystemCollection(collectionName, config) {
|
|
2116
|
+
const mergedConfig = mergeConfig3(config);
|
|
2117
|
+
return mergedConfig.systemCollections.includes(collectionName);
|
|
2118
|
+
}
|
|
2119
|
+
function getUsersSystemFields(config) {
|
|
2120
|
+
const mergedConfig = mergeConfig3(config);
|
|
2121
|
+
return new Set(mergedConfig.usersSystemFields);
|
|
2122
|
+
}
|
|
2123
|
+
function filterSystemCollections(schema, config) {
|
|
2124
|
+
const filteredCollections = /* @__PURE__ */ new Map();
|
|
2125
|
+
for (const [collectionName, collectionSchema] of schema.collections) {
|
|
2126
|
+
if (!isSystemCollection(collectionName, config)) {
|
|
2127
|
+
filteredCollections.set(collectionName, collectionSchema);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return {
|
|
2131
|
+
collections: filteredCollections
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
function findNewCollections(currentSchema, previousSnapshot) {
|
|
2135
|
+
const newCollections = [];
|
|
2136
|
+
if (!previousSnapshot) {
|
|
2137
|
+
return Array.from(currentSchema.collections.values());
|
|
2138
|
+
}
|
|
2139
|
+
for (const [collectionName, collectionSchema] of currentSchema.collections) {
|
|
2140
|
+
if (!previousSnapshot.collections.has(collectionName)) {
|
|
2141
|
+
newCollections.push(collectionSchema);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
return newCollections;
|
|
2145
|
+
}
|
|
2146
|
+
function findRemovedCollections(currentSchema, previousSnapshot) {
|
|
2147
|
+
const removedCollections = [];
|
|
2148
|
+
if (!previousSnapshot) {
|
|
2149
|
+
return removedCollections;
|
|
2150
|
+
}
|
|
2151
|
+
for (const [collectionName, collectionSchema] of previousSnapshot.collections) {
|
|
2152
|
+
if (!currentSchema.collections.has(collectionName)) {
|
|
2153
|
+
removedCollections.push(collectionSchema);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return removedCollections;
|
|
2157
|
+
}
|
|
2158
|
+
function matchCollectionsByName(currentSchema, previousSnapshot) {
|
|
2159
|
+
const matches = [];
|
|
2160
|
+
if (!previousSnapshot) {
|
|
2161
|
+
return matches;
|
|
2162
|
+
}
|
|
2163
|
+
for (const [collectionName, currentCollection] of currentSchema.collections) {
|
|
2164
|
+
const previousCollection = previousSnapshot.collections.get(collectionName);
|
|
2165
|
+
if (previousCollection) {
|
|
2166
|
+
matches.push([currentCollection, previousCollection]);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return matches;
|
|
2170
|
+
}
|
|
2171
|
+
function findNewFields(currentFields, previousFields) {
|
|
2172
|
+
const newFields = [];
|
|
2173
|
+
const previousFieldNames = new Set(previousFields.map((f) => f.name));
|
|
2174
|
+
for (const currentField of currentFields) {
|
|
2175
|
+
if (!previousFieldNames.has(currentField.name)) {
|
|
2176
|
+
newFields.push(currentField);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
return newFields;
|
|
2180
|
+
}
|
|
2181
|
+
function findRemovedFields(currentFields, previousFields) {
|
|
2182
|
+
const removedFields = [];
|
|
2183
|
+
const currentFieldNames = new Set(currentFields.map((f) => f.name));
|
|
2184
|
+
for (const previousField of previousFields) {
|
|
2185
|
+
if (!currentFieldNames.has(previousField.name)) {
|
|
2186
|
+
removedFields.push(previousField);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
return removedFields;
|
|
2190
|
+
}
|
|
2191
|
+
function matchFieldsByName(currentFields, previousFields) {
|
|
2192
|
+
const matches = [];
|
|
2193
|
+
const previousFieldMap = /* @__PURE__ */ new Map();
|
|
2194
|
+
for (const previousField of previousFields) {
|
|
2195
|
+
previousFieldMap.set(previousField.name, previousField);
|
|
2196
|
+
}
|
|
2197
|
+
for (const currentField of currentFields) {
|
|
2198
|
+
const previousField = previousFieldMap.get(currentField.name);
|
|
2199
|
+
if (previousField) {
|
|
2200
|
+
matches.push([currentField, previousField]);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
return matches;
|
|
2204
|
+
}
|
|
2205
|
+
function areValuesEqual(a, b) {
|
|
2206
|
+
if (a === b) return true;
|
|
2207
|
+
if (a == null || b == null) return false;
|
|
2208
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2209
|
+
if (a.length !== b.length) return false;
|
|
2210
|
+
return a.every((val, idx) => areValuesEqual(val, b[idx]));
|
|
2211
|
+
}
|
|
2212
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
2213
|
+
const keysA = Object.keys(a);
|
|
2214
|
+
const keysB = Object.keys(b);
|
|
2215
|
+
if (keysA.length !== keysB.length) return false;
|
|
2216
|
+
return keysA.every((key) => areValuesEqual(a[key], b[key]));
|
|
2217
|
+
}
|
|
2218
|
+
return a === b;
|
|
2219
|
+
}
|
|
2220
|
+
function compareFieldTypes(currentField, previousField) {
|
|
2221
|
+
if (currentField.type !== previousField.type) {
|
|
2222
|
+
return {
|
|
2223
|
+
property: "type",
|
|
2224
|
+
oldValue: previousField.type,
|
|
2225
|
+
newValue: currentField.type
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
return null;
|
|
2229
|
+
}
|
|
2230
|
+
function compareFieldConstraints(currentField, previousField) {
|
|
2231
|
+
const changes = [];
|
|
2232
|
+
if (currentField.required !== previousField.required) {
|
|
2233
|
+
changes.push({
|
|
2234
|
+
property: "required",
|
|
2235
|
+
oldValue: previousField.required,
|
|
2236
|
+
newValue: currentField.required
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
if (currentField.unique !== previousField.unique) {
|
|
2240
|
+
changes.push({
|
|
2241
|
+
property: "unique",
|
|
2242
|
+
oldValue: previousField.unique,
|
|
2243
|
+
newValue: currentField.unique
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
return changes;
|
|
2247
|
+
}
|
|
2248
|
+
function compareFieldOptions(currentField, previousField) {
|
|
2249
|
+
const changes = [];
|
|
2250
|
+
const currentOptions = currentField.options || {};
|
|
2251
|
+
const previousOptions = previousField.options || {};
|
|
2252
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
|
|
2253
|
+
for (const key of allKeys) {
|
|
2254
|
+
const currentValue = currentOptions[key];
|
|
2255
|
+
const previousValue = previousOptions[key];
|
|
2256
|
+
if (!areValuesEqual(currentValue, previousValue)) {
|
|
2257
|
+
changes.push({
|
|
2258
|
+
property: `options.${key}`,
|
|
2259
|
+
oldValue: previousValue,
|
|
2260
|
+
newValue: currentValue
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
return changes;
|
|
2265
|
+
}
|
|
2266
|
+
function compareRelationConfigurations(currentField, previousField) {
|
|
2267
|
+
const changes = [];
|
|
2268
|
+
const currentRelation = currentField.relation;
|
|
2269
|
+
const previousRelation = previousField.relation;
|
|
2270
|
+
if (!currentRelation && !previousRelation) {
|
|
2271
|
+
return changes;
|
|
2272
|
+
}
|
|
2273
|
+
if (!currentRelation || !previousRelation) {
|
|
2274
|
+
return changes;
|
|
2275
|
+
}
|
|
2276
|
+
if (currentRelation.collection !== previousRelation.collection) {
|
|
2277
|
+
changes.push({
|
|
2278
|
+
property: "relation.collection",
|
|
2279
|
+
oldValue: previousRelation.collection,
|
|
2280
|
+
newValue: currentRelation.collection
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
|
|
2284
|
+
changes.push({
|
|
2285
|
+
property: "relation.cascadeDelete",
|
|
2286
|
+
oldValue: previousRelation.cascadeDelete,
|
|
2287
|
+
newValue: currentRelation.cascadeDelete
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
if (currentRelation.maxSelect !== previousRelation.maxSelect) {
|
|
2291
|
+
changes.push({
|
|
2292
|
+
property: "relation.maxSelect",
|
|
2293
|
+
oldValue: previousRelation.maxSelect,
|
|
2294
|
+
newValue: currentRelation.maxSelect
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
if (currentRelation.minSelect !== previousRelation.minSelect) {
|
|
2298
|
+
changes.push({
|
|
2299
|
+
property: "relation.minSelect",
|
|
2300
|
+
oldValue: previousRelation.minSelect,
|
|
2301
|
+
newValue: currentRelation.minSelect
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
return changes;
|
|
2305
|
+
}
|
|
2306
|
+
function detectFieldChanges(currentField, previousField) {
|
|
2307
|
+
const changes = [];
|
|
2308
|
+
const typeChange = compareFieldTypes(currentField, previousField);
|
|
2309
|
+
if (typeChange) {
|
|
2310
|
+
changes.push(typeChange);
|
|
2311
|
+
}
|
|
2312
|
+
changes.push(...compareFieldConstraints(currentField, previousField));
|
|
2313
|
+
changes.push(...compareFieldOptions(currentField, previousField));
|
|
2314
|
+
if (currentField.type === "relation" && previousField.type === "relation") {
|
|
2315
|
+
changes.push(...compareRelationConfigurations(currentField, previousField));
|
|
2316
|
+
}
|
|
2317
|
+
return changes;
|
|
2318
|
+
}
|
|
2319
|
+
function compareIndexes(currentIndexes = [], previousIndexes = []) {
|
|
2320
|
+
const currentSet = new Set(currentIndexes);
|
|
2321
|
+
const previousSet = new Set(previousIndexes);
|
|
2322
|
+
const indexesToAdd = currentIndexes.filter((idx) => !previousSet.has(idx));
|
|
2323
|
+
const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
|
|
2324
|
+
return { indexesToAdd, indexesToRemove };
|
|
2325
|
+
}
|
|
2326
|
+
function compareRules(currentRules, previousRules) {
|
|
2327
|
+
const updates = [];
|
|
2328
|
+
const ruleTypes = [
|
|
2329
|
+
"listRule",
|
|
2330
|
+
"viewRule",
|
|
2331
|
+
"createRule",
|
|
2332
|
+
"updateRule",
|
|
2333
|
+
"deleteRule",
|
|
2334
|
+
"manageRule"
|
|
2335
|
+
];
|
|
2336
|
+
for (const ruleType of ruleTypes) {
|
|
2337
|
+
const currentValue = currentRules?.[ruleType] ?? null;
|
|
2338
|
+
const previousValue = previousRules?.[ruleType] ?? null;
|
|
2339
|
+
if (currentValue !== previousValue) {
|
|
2340
|
+
updates.push({
|
|
2341
|
+
ruleType,
|
|
2342
|
+
oldValue: previousValue,
|
|
2343
|
+
newValue: currentValue
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return updates;
|
|
2348
|
+
}
|
|
2349
|
+
function comparePermissions(currentPermissions, previousPermissions) {
|
|
2350
|
+
const changes = [];
|
|
2351
|
+
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule", "manageRule"];
|
|
2352
|
+
for (const ruleType of ruleTypes) {
|
|
2353
|
+
const currentValue = currentPermissions?.[ruleType] ?? null;
|
|
2354
|
+
const previousValue = previousPermissions?.[ruleType] ?? null;
|
|
2355
|
+
if (currentValue !== previousValue) {
|
|
2356
|
+
changes.push({
|
|
2357
|
+
ruleType,
|
|
2358
|
+
oldValue: previousValue,
|
|
2359
|
+
newValue: currentValue
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
return changes;
|
|
2364
|
+
}
|
|
2365
|
+
function compareCollectionFields(currentCollection, previousCollection, config) {
|
|
2366
|
+
let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
|
|
2367
|
+
const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
|
|
2368
|
+
const fieldsToModify = [];
|
|
2369
|
+
if (currentCollection.name === "users") {
|
|
2370
|
+
const systemFields = getUsersSystemFields(config);
|
|
2371
|
+
fieldsToAdd = fieldsToAdd.filter((field) => !systemFields.has(field.name));
|
|
2372
|
+
}
|
|
2373
|
+
const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
|
|
2374
|
+
for (const [currentField, previousField] of matchedFields) {
|
|
2375
|
+
const changes = detectFieldChanges(currentField, previousField);
|
|
2376
|
+
if (changes.length > 0) {
|
|
2377
|
+
fieldsToModify.push({
|
|
2378
|
+
fieldName: currentField.name,
|
|
2379
|
+
currentDefinition: previousField,
|
|
2380
|
+
newDefinition: currentField,
|
|
2381
|
+
changes
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return { fieldsToAdd, fieldsToRemove, fieldsToModify };
|
|
2386
|
+
}
|
|
2387
|
+
function buildCollectionModification(currentCollection, previousCollection, config) {
|
|
2388
|
+
const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
|
|
2389
|
+
currentCollection,
|
|
2390
|
+
previousCollection,
|
|
2391
|
+
config
|
|
2392
|
+
);
|
|
2393
|
+
const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
|
|
2394
|
+
const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
|
|
2395
|
+
const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
|
|
2396
|
+
return {
|
|
2397
|
+
collection: currentCollection.name,
|
|
2398
|
+
fieldsToAdd,
|
|
2399
|
+
fieldsToRemove,
|
|
2400
|
+
fieldsToModify,
|
|
2401
|
+
indexesToAdd,
|
|
2402
|
+
indexesToRemove,
|
|
2403
|
+
rulesToUpdate,
|
|
2404
|
+
permissionsToUpdate
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
function hasChanges(modification) {
|
|
2408
|
+
return modification.fieldsToAdd.length > 0 || modification.fieldsToRemove.length > 0 || modification.fieldsToModify.length > 0 || modification.indexesToAdd.length > 0 || modification.indexesToRemove.length > 0 || modification.rulesToUpdate.length > 0 || modification.permissionsToUpdate.length > 0;
|
|
2409
|
+
}
|
|
2410
|
+
function aggregateChanges(currentSchema, previousSnapshot, config) {
|
|
2411
|
+
const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
|
|
2412
|
+
const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
|
|
2413
|
+
const filteredCollectionsToCreate = collectionsToCreate.filter(
|
|
2414
|
+
(collection) => !isSystemCollection(collection.name, config)
|
|
2415
|
+
);
|
|
2416
|
+
const filteredCollectionsToDelete = collectionsToDelete.filter(
|
|
2417
|
+
(collection) => !isSystemCollection(collection.name, config)
|
|
2418
|
+
);
|
|
2419
|
+
const collectionsToModify = [];
|
|
2420
|
+
const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
|
|
2421
|
+
for (const [currentCollection, previousCollection] of matchedCollections) {
|
|
2422
|
+
const modification = buildCollectionModification(currentCollection, previousCollection, config);
|
|
2423
|
+
if (hasChanges(modification)) {
|
|
2424
|
+
collectionsToModify.push(modification);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return {
|
|
2428
|
+
collectionsToCreate: filteredCollectionsToCreate,
|
|
2429
|
+
collectionsToDelete: filteredCollectionsToDelete,
|
|
2430
|
+
collectionsToModify
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function detectDestructiveChanges(diff, config) {
|
|
2434
|
+
const destructiveChanges = [];
|
|
2435
|
+
const mergedConfig = mergeConfig3(config);
|
|
2436
|
+
for (const collection of diff.collectionsToDelete) {
|
|
2437
|
+
destructiveChanges.push({
|
|
2438
|
+
type: "collection_delete",
|
|
2439
|
+
severity: "high",
|
|
2440
|
+
collection: collection.name,
|
|
2441
|
+
description: `Delete collection: ${collection.name}`
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
for (const modification of diff.collectionsToModify) {
|
|
2445
|
+
const collectionName = modification.collection;
|
|
2446
|
+
for (const field of modification.fieldsToRemove) {
|
|
2447
|
+
destructiveChanges.push({
|
|
2448
|
+
type: "field_delete",
|
|
2449
|
+
severity: "high",
|
|
2450
|
+
collection: collectionName,
|
|
2451
|
+
field: field.name,
|
|
2452
|
+
description: `Delete field: ${collectionName}.${field.name}`
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
2456
|
+
const typeChange = fieldMod.changes.find((c) => c.property === "type");
|
|
2457
|
+
const requiredChange = fieldMod.changes.find((c) => c.property === "required" && c.newValue === true);
|
|
2458
|
+
if (typeChange) {
|
|
2459
|
+
destructiveChanges.push({
|
|
2460
|
+
type: "type_change",
|
|
2461
|
+
severity: "high",
|
|
2462
|
+
collection: collectionName,
|
|
2463
|
+
field: fieldMod.fieldName,
|
|
2464
|
+
description: `Change field type: ${collectionName}.${fieldMod.fieldName} (${typeChange.oldValue} \u2192 ${typeChange.newValue})`,
|
|
2465
|
+
oldValue: typeChange.oldValue,
|
|
2466
|
+
newValue: typeChange.newValue
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
if (requiredChange && mergedConfig.severityThreshold !== "high") {
|
|
2470
|
+
destructiveChanges.push({
|
|
2471
|
+
type: "required_change",
|
|
2472
|
+
severity: "medium",
|
|
2473
|
+
collection: collectionName,
|
|
2474
|
+
field: fieldMod.fieldName,
|
|
2475
|
+
description: `Make field required: ${collectionName}.${fieldMod.fieldName}`,
|
|
2476
|
+
oldValue: false,
|
|
2477
|
+
newValue: true
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
if (mergedConfig.severityThreshold === "low") {
|
|
2481
|
+
const otherChanges = fieldMod.changes.filter((c) => c.property !== "type" && c.property !== "required");
|
|
2482
|
+
for (const change of otherChanges) {
|
|
2483
|
+
destructiveChanges.push({
|
|
2484
|
+
type: "constraint_change",
|
|
2485
|
+
severity: "low",
|
|
2486
|
+
collection: collectionName,
|
|
2487
|
+
field: fieldMod.fieldName,
|
|
2488
|
+
description: `Change constraint: ${collectionName}.${fieldMod.fieldName}.${change.property}`,
|
|
2489
|
+
oldValue: change.oldValue,
|
|
2490
|
+
newValue: change.newValue
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return destructiveChanges;
|
|
2497
|
+
}
|
|
2498
|
+
function categorizeChangesBySeverity(diff, _config) {
|
|
2499
|
+
const destructive = [];
|
|
2500
|
+
const nonDestructive = [];
|
|
2501
|
+
for (const collection of diff.collectionsToDelete) {
|
|
2502
|
+
destructive.push(`Delete collection: ${collection.name}`);
|
|
2503
|
+
}
|
|
2504
|
+
for (const collection of diff.collectionsToCreate) {
|
|
2505
|
+
nonDestructive.push(`Create collection: ${collection.name}`);
|
|
2506
|
+
}
|
|
2507
|
+
for (const modification of diff.collectionsToModify) {
|
|
2508
|
+
const collectionName = modification.collection;
|
|
2509
|
+
for (const field of modification.fieldsToRemove) {
|
|
2510
|
+
destructive.push(`Delete field: ${collectionName}.${field.name}`);
|
|
2511
|
+
}
|
|
2512
|
+
for (const field of modification.fieldsToAdd) {
|
|
2513
|
+
nonDestructive.push(`Add field: ${collectionName}.${field.name}`);
|
|
2514
|
+
}
|
|
2515
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
2516
|
+
const hasTypeChange = fieldMod.changes.some((c) => c.property === "type");
|
|
2517
|
+
const hasRequiredChange = fieldMod.changes.some((c) => c.property === "required" && c.newValue === true);
|
|
2518
|
+
if (hasTypeChange) {
|
|
2519
|
+
destructive.push(
|
|
2520
|
+
`Change field type: ${collectionName}.${fieldMod.fieldName} (${fieldMod.changes.find((c) => c.property === "type")?.oldValue} \u2192 ${fieldMod.changes.find((c) => c.property === "type")?.newValue})`
|
|
2521
|
+
);
|
|
2522
|
+
} else if (hasRequiredChange) {
|
|
2523
|
+
destructive.push(`Make field required: ${collectionName}.${fieldMod.fieldName}`);
|
|
2524
|
+
} else {
|
|
2525
|
+
nonDestructive.push(`Modify field: ${collectionName}.${fieldMod.fieldName}`);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
for (const _index of modification.indexesToAdd) {
|
|
2529
|
+
nonDestructive.push(`Add index: ${collectionName}`);
|
|
2530
|
+
}
|
|
2531
|
+
for (const _index of modification.indexesToRemove) {
|
|
2532
|
+
nonDestructive.push(`Remove index: ${collectionName}`);
|
|
2533
|
+
}
|
|
2534
|
+
for (const rule of modification.rulesToUpdate) {
|
|
2535
|
+
nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return { destructive, nonDestructive };
|
|
2539
|
+
}
|
|
2540
|
+
function generateChangeSummary(diff, config) {
|
|
2541
|
+
const destructiveChanges = detectDestructiveChanges(diff, config);
|
|
2542
|
+
const { nonDestructive } = categorizeChangesBySeverity(diff);
|
|
2543
|
+
let fieldsToAdd = 0;
|
|
2544
|
+
let fieldsToRemove = 0;
|
|
2545
|
+
let fieldsToModify = 0;
|
|
2546
|
+
let indexChanges = 0;
|
|
2547
|
+
let ruleChanges = 0;
|
|
2548
|
+
let permissionChanges = 0;
|
|
2549
|
+
for (const modification of diff.collectionsToModify) {
|
|
2550
|
+
fieldsToAdd += modification.fieldsToAdd.length;
|
|
2551
|
+
fieldsToRemove += modification.fieldsToRemove.length;
|
|
2552
|
+
fieldsToModify += modification.fieldsToModify.length;
|
|
2553
|
+
indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
|
|
2554
|
+
ruleChanges += modification.rulesToUpdate.length;
|
|
2555
|
+
permissionChanges += modification.permissionsToUpdate.length;
|
|
2556
|
+
}
|
|
2557
|
+
return {
|
|
2558
|
+
totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
|
|
2559
|
+
collectionsToCreate: diff.collectionsToCreate.length,
|
|
2560
|
+
collectionsToDelete: diff.collectionsToDelete.length,
|
|
2561
|
+
collectionsToModify: diff.collectionsToModify.length,
|
|
2562
|
+
fieldsToAdd,
|
|
2563
|
+
fieldsToRemove,
|
|
2564
|
+
fieldsToModify,
|
|
2565
|
+
indexChanges,
|
|
2566
|
+
ruleChanges,
|
|
2567
|
+
permissionChanges,
|
|
2568
|
+
destructiveChanges,
|
|
2569
|
+
nonDestructiveChanges: nonDestructive
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function requiresForceFlag(diff, config) {
|
|
2573
|
+
const mergedConfig = mergeConfig3(config);
|
|
2574
|
+
if (!mergedConfig.requireForceForDestructive) {
|
|
2575
|
+
return false;
|
|
2576
|
+
}
|
|
2577
|
+
const destructiveChanges = detectDestructiveChanges(diff, config);
|
|
2578
|
+
const relevantChanges = destructiveChanges.filter((change) => {
|
|
2579
|
+
switch (mergedConfig.severityThreshold) {
|
|
2580
|
+
case "high":
|
|
2581
|
+
return change.severity === "high";
|
|
2582
|
+
case "medium":
|
|
2583
|
+
return change.severity === "high" || change.severity === "medium";
|
|
2584
|
+
case "low":
|
|
2585
|
+
return true;
|
|
2586
|
+
default:
|
|
2587
|
+
return change.severity === "high";
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
return relevantChanges.length > 0;
|
|
2591
|
+
}
|
|
2592
|
+
function compare(currentSchema, previousSnapshot, config) {
|
|
2593
|
+
return aggregateChanges(currentSchema, previousSnapshot, config);
|
|
2594
|
+
}
|
|
2595
|
+
var DiffEngine = class {
|
|
2596
|
+
config;
|
|
2597
|
+
constructor(config) {
|
|
2598
|
+
this.config = mergeConfig3(config);
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Compares current schema with previous snapshot
|
|
2602
|
+
*/
|
|
2603
|
+
compare(currentSchema, previousSnapshot) {
|
|
2604
|
+
return compare(currentSchema, previousSnapshot, this.config);
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Detects destructive changes in a diff
|
|
2608
|
+
*/
|
|
2609
|
+
detectDestructiveChanges(diff) {
|
|
2610
|
+
return detectDestructiveChanges(diff, this.config);
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Categorizes changes by severity
|
|
2614
|
+
*/
|
|
2615
|
+
categorizeChangesBySeverity(diff) {
|
|
2616
|
+
return categorizeChangesBySeverity(diff, this.config);
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Generates a summary of changes
|
|
2620
|
+
*/
|
|
2621
|
+
generateChangeSummary(diff) {
|
|
2622
|
+
return generateChangeSummary(diff, this.config);
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Checks if force flag is required
|
|
2626
|
+
*/
|
|
2627
|
+
requiresForceFlag(diff) {
|
|
2628
|
+
return requiresForceFlag(diff, this.config);
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
var DEFAULT_TEMPLATE = `/// <reference path="{{TYPES_PATH}}" />
|
|
2632
|
+
migrate((app) => {
|
|
2633
|
+
{{UP_CODE}}
|
|
2634
|
+
return true;
|
|
2635
|
+
}, (app) => {
|
|
2636
|
+
{{DOWN_CODE}}
|
|
2637
|
+
return true;
|
|
2638
|
+
});
|
|
2639
|
+
`;
|
|
2640
|
+
var DEFAULT_CONFIG4 = {
|
|
2641
|
+
workspaceRoot: process.cwd(),
|
|
2642
|
+
timestampGenerator: () => Math.floor(Date.now() / 1e3).toString(),
|
|
2643
|
+
template: DEFAULT_TEMPLATE,
|
|
2644
|
+
includeTypeReference: true,
|
|
2645
|
+
typesPath: "../pb_data/types.d.ts"
|
|
2646
|
+
};
|
|
2647
|
+
function mergeConfig4(config) {
|
|
2648
|
+
return {
|
|
2649
|
+
...DEFAULT_CONFIG4,
|
|
2650
|
+
...config
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
function resolveMigrationDir(config) {
|
|
2654
|
+
const workspaceRoot = config.workspaceRoot || process.cwd();
|
|
2655
|
+
if (path__namespace.isAbsolute(config.migrationDir)) {
|
|
2656
|
+
return config.migrationDir;
|
|
2657
|
+
}
|
|
2658
|
+
return path__namespace.join(workspaceRoot, config.migrationDir);
|
|
2659
|
+
}
|
|
2660
|
+
function generateTimestamp(config) {
|
|
2661
|
+
if (config?.timestampGenerator) {
|
|
2662
|
+
return config.timestampGenerator();
|
|
2663
|
+
}
|
|
2664
|
+
return Math.floor(Date.now() / 1e3).toString();
|
|
2665
|
+
}
|
|
2666
|
+
function generateMigrationDescription(diff) {
|
|
2667
|
+
const parts = [];
|
|
2668
|
+
if (diff.collectionsToCreate.length > 0) {
|
|
2669
|
+
if (diff.collectionsToCreate.length === 1) {
|
|
2670
|
+
parts.push(`created_${diff.collectionsToCreate[0].name}`);
|
|
2671
|
+
} else {
|
|
2672
|
+
parts.push(`created_${diff.collectionsToCreate.length}_collections`);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (diff.collectionsToDelete.length > 0) {
|
|
2676
|
+
if (diff.collectionsToDelete.length === 1) {
|
|
2677
|
+
parts.push(`deleted_${diff.collectionsToDelete[0].name}`);
|
|
2678
|
+
} else {
|
|
2679
|
+
parts.push(`deleted_${diff.collectionsToDelete.length}_collections`);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
if (diff.collectionsToModify.length > 0) {
|
|
2683
|
+
if (diff.collectionsToModify.length === 1) {
|
|
2684
|
+
parts.push(`updated_${diff.collectionsToModify[0].collection}`);
|
|
2685
|
+
} else {
|
|
2686
|
+
parts.push(`updated_${diff.collectionsToModify.length}_collections`);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
if (parts.length === 0) {
|
|
2690
|
+
return "no_changes";
|
|
2691
|
+
}
|
|
2692
|
+
let description = parts.join("_");
|
|
2693
|
+
if (description.length > 80) {
|
|
2694
|
+
description = description.substring(0, 77) + "...";
|
|
2695
|
+
}
|
|
2696
|
+
return description;
|
|
2697
|
+
}
|
|
2698
|
+
function generateMigrationFilename(diff, config) {
|
|
2699
|
+
const timestamp = generateTimestamp(config);
|
|
2700
|
+
const description = generateMigrationDescription(diff);
|
|
2701
|
+
return `${timestamp}_${description}.js`;
|
|
2702
|
+
}
|
|
2703
|
+
function createMigrationFileStructure(upCode, downCode, config) {
|
|
2704
|
+
const mergedConfig = config ? mergeConfig4(config) : DEFAULT_CONFIG4;
|
|
2705
|
+
let template = mergedConfig.template;
|
|
2706
|
+
template = template.replace("{{TYPES_PATH}}", mergedConfig.typesPath);
|
|
2707
|
+
template = template.replace("{{UP_CODE}}", upCode);
|
|
2708
|
+
template = template.replace("{{DOWN_CODE}}", downCode);
|
|
2709
|
+
if (!mergedConfig.includeTypeReference) {
|
|
2710
|
+
template = template.replace(/\/\/\/ <reference path="[^"]*" \/>\n?/, "");
|
|
2711
|
+
}
|
|
2712
|
+
return template;
|
|
2713
|
+
}
|
|
2714
|
+
function writeMigrationFile(migrationDir, filename, content) {
|
|
2715
|
+
try {
|
|
2716
|
+
if (!fs2__namespace.existsSync(migrationDir)) {
|
|
2717
|
+
try {
|
|
2718
|
+
fs2__namespace.mkdirSync(migrationDir, { recursive: true });
|
|
2719
|
+
} catch (error) {
|
|
2720
|
+
const fsError = error;
|
|
2721
|
+
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
2722
|
+
throw new FileSystemError(
|
|
2723
|
+
`Permission denied creating migration directory. Check directory permissions.`,
|
|
2724
|
+
migrationDir,
|
|
2725
|
+
"create",
|
|
2726
|
+
fsError.code,
|
|
2727
|
+
error
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
throw new FileSystemError(
|
|
2731
|
+
`Failed to create migration directory: ${fsError.message}`,
|
|
2732
|
+
migrationDir,
|
|
2733
|
+
"create",
|
|
2734
|
+
fsError.code,
|
|
2735
|
+
error
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
const filePath = path__namespace.join(migrationDir, filename);
|
|
2740
|
+
fs2__namespace.writeFileSync(filePath, content, "utf-8");
|
|
2741
|
+
return filePath;
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
if (error instanceof FileSystemError) {
|
|
2744
|
+
throw error;
|
|
2745
|
+
}
|
|
2746
|
+
const fsError = error;
|
|
2747
|
+
const filePath = path__namespace.join(migrationDir, filename);
|
|
2748
|
+
if (fsError.code === "EACCES" || fsError.code === "EPERM") {
|
|
2749
|
+
throw new FileSystemError(
|
|
2750
|
+
`Permission denied writing migration file. Check file and directory permissions.`,
|
|
2751
|
+
filePath,
|
|
2752
|
+
"write",
|
|
2753
|
+
fsError.code,
|
|
2754
|
+
error
|
|
2755
|
+
);
|
|
2756
|
+
} else if (fsError.code === "ENOSPC") {
|
|
2757
|
+
throw new FileSystemError(
|
|
2758
|
+
`No space left on device when writing migration file.`,
|
|
2759
|
+
filePath,
|
|
2760
|
+
"write",
|
|
2761
|
+
fsError.code,
|
|
2762
|
+
error
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
throw new MigrationGenerationError(`Failed to write migration file: ${fsError.message}`, filePath, error);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
function formatValue(value) {
|
|
2769
|
+
if (value === null || value === void 0) {
|
|
2770
|
+
return "null";
|
|
2771
|
+
}
|
|
2772
|
+
if (typeof value === "string") {
|
|
2773
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
|
|
2774
|
+
}
|
|
2775
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
2776
|
+
return String(value);
|
|
2777
|
+
}
|
|
2778
|
+
if (Array.isArray(value)) {
|
|
2779
|
+
const items = value.map((v) => formatValue(v)).join(", ");
|
|
2780
|
+
return `[${items}]`;
|
|
2781
|
+
}
|
|
2782
|
+
if (typeof value === "object") {
|
|
2783
|
+
const entries = Object.entries(value).map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
|
|
2784
|
+
return `{ ${entries} }`;
|
|
2785
|
+
}
|
|
2786
|
+
return String(value);
|
|
2787
|
+
}
|
|
2788
|
+
function generateFieldDefinitionObject(field) {
|
|
2789
|
+
const parts = [];
|
|
2790
|
+
parts.push(` name: "${field.name}"`);
|
|
2791
|
+
parts.push(` type: "${field.type}"`);
|
|
2792
|
+
parts.push(` required: ${field.required}`);
|
|
2793
|
+
if (field.unique !== void 0) {
|
|
2794
|
+
parts.push(` unique: ${field.unique}`);
|
|
2795
|
+
}
|
|
2796
|
+
if (field.options && Object.keys(field.options).length > 0) {
|
|
2797
|
+
for (const [key, value] of Object.entries(field.options)) {
|
|
2798
|
+
parts.push(` ${key}: ${formatValue(value)}`);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
if (field.relation) {
|
|
2802
|
+
const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
2803
|
+
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
2804
|
+
if (field.relation.maxSelect !== void 0) {
|
|
2805
|
+
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
2806
|
+
}
|
|
2807
|
+
if (field.relation.minSelect !== void 0) {
|
|
2808
|
+
parts.push(` minSelect: ${field.relation.minSelect}`);
|
|
2809
|
+
}
|
|
2810
|
+
if (field.relation.cascadeDelete !== void 0) {
|
|
2811
|
+
parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
return ` {
|
|
2815
|
+
${parts.join(",\n")},
|
|
2816
|
+
}`;
|
|
2817
|
+
}
|
|
2818
|
+
function generateFieldsArray(fields) {
|
|
2819
|
+
if (fields.length === 0) {
|
|
2820
|
+
return "[]";
|
|
2821
|
+
}
|
|
2822
|
+
const fieldObjects = fields.map((field) => generateFieldDefinitionObject(field));
|
|
2823
|
+
return `[
|
|
2824
|
+
${fieldObjects.join(",\n")},
|
|
2825
|
+
]`;
|
|
2826
|
+
}
|
|
2827
|
+
function generateCollectionRules(rules) {
|
|
2828
|
+
if (!rules) {
|
|
2829
|
+
return "";
|
|
2830
|
+
}
|
|
2831
|
+
const parts = [];
|
|
2832
|
+
if (rules.listRule !== void 0) {
|
|
2833
|
+
parts.push(`listRule: ${formatValue(rules.listRule)}`);
|
|
2834
|
+
}
|
|
2835
|
+
if (rules.viewRule !== void 0) {
|
|
2836
|
+
parts.push(`viewRule: ${formatValue(rules.viewRule)}`);
|
|
2837
|
+
}
|
|
2838
|
+
if (rules.createRule !== void 0) {
|
|
2839
|
+
parts.push(`createRule: ${formatValue(rules.createRule)}`);
|
|
2840
|
+
}
|
|
2841
|
+
if (rules.updateRule !== void 0) {
|
|
2842
|
+
parts.push(`updateRule: ${formatValue(rules.updateRule)}`);
|
|
2843
|
+
}
|
|
2844
|
+
if (rules.deleteRule !== void 0) {
|
|
2845
|
+
parts.push(`deleteRule: ${formatValue(rules.deleteRule)}`);
|
|
2846
|
+
}
|
|
2847
|
+
if (rules.manageRule !== void 0) {
|
|
2848
|
+
parts.push(`manageRule: ${formatValue(rules.manageRule)}`);
|
|
2849
|
+
}
|
|
2850
|
+
return parts.join(",\n ");
|
|
2851
|
+
}
|
|
2852
|
+
function generateCollectionPermissions(permissions) {
|
|
2853
|
+
if (!permissions) {
|
|
2854
|
+
return "";
|
|
2855
|
+
}
|
|
2856
|
+
const parts = [];
|
|
2857
|
+
if (permissions.listRule !== void 0) {
|
|
2858
|
+
parts.push(`listRule: ${formatValue(permissions.listRule)}`);
|
|
2859
|
+
}
|
|
2860
|
+
if (permissions.viewRule !== void 0) {
|
|
2861
|
+
parts.push(`viewRule: ${formatValue(permissions.viewRule)}`);
|
|
2862
|
+
}
|
|
2863
|
+
if (permissions.createRule !== void 0) {
|
|
2864
|
+
parts.push(`createRule: ${formatValue(permissions.createRule)}`);
|
|
2865
|
+
}
|
|
2866
|
+
if (permissions.updateRule !== void 0) {
|
|
2867
|
+
parts.push(`updateRule: ${formatValue(permissions.updateRule)}`);
|
|
2868
|
+
}
|
|
2869
|
+
if (permissions.deleteRule !== void 0) {
|
|
2870
|
+
parts.push(`deleteRule: ${formatValue(permissions.deleteRule)}`);
|
|
2871
|
+
}
|
|
2872
|
+
if (permissions.manageRule !== void 0) {
|
|
2873
|
+
parts.push(`manageRule: ${formatValue(permissions.manageRule)}`);
|
|
2874
|
+
}
|
|
2875
|
+
return parts.join(",\n ");
|
|
2876
|
+
}
|
|
2877
|
+
function generateIndexesArray(indexes) {
|
|
2878
|
+
if (!indexes || indexes.length === 0) {
|
|
2879
|
+
return "[]";
|
|
2880
|
+
}
|
|
2881
|
+
const indexStrings = indexes.map((idx) => `"${idx}"`);
|
|
2882
|
+
return `[
|
|
2883
|
+
${indexStrings.join(",\n ")},
|
|
2884
|
+
]`;
|
|
2885
|
+
}
|
|
2886
|
+
function generateCollectionCreation(collection, varName = "collection") {
|
|
2887
|
+
const lines = [];
|
|
2888
|
+
lines.push(` const ${varName} = new Collection({`);
|
|
2889
|
+
lines.push(` name: "${collection.name}",`);
|
|
2890
|
+
lines.push(` type: "${collection.type}",`);
|
|
2891
|
+
const permissionsCode = generateCollectionPermissions(collection.permissions);
|
|
2892
|
+
const rulesCode = generateCollectionRules(collection.rules);
|
|
2893
|
+
if (permissionsCode) {
|
|
2894
|
+
lines.push(` ${permissionsCode},`);
|
|
2895
|
+
} else if (rulesCode) {
|
|
2896
|
+
lines.push(` ${rulesCode},`);
|
|
2897
|
+
}
|
|
2898
|
+
lines.push(` fields: ${generateFieldsArray(collection.fields)},`);
|
|
2899
|
+
lines.push(` indexes: ${generateIndexesArray(collection.indexes)},`);
|
|
2900
|
+
lines.push(` });`);
|
|
2901
|
+
lines.push(``);
|
|
2902
|
+
lines.push(` app.save(${varName});`);
|
|
2903
|
+
return lines.join("\n");
|
|
2904
|
+
}
|
|
2905
|
+
function getFieldConstructorName(fieldType) {
|
|
2906
|
+
const constructorMap = {
|
|
2907
|
+
text: "TextField",
|
|
2908
|
+
email: "EmailField",
|
|
2909
|
+
url: "URLField",
|
|
2910
|
+
number: "NumberField",
|
|
2911
|
+
bool: "BoolField",
|
|
2912
|
+
date: "DateField",
|
|
2913
|
+
select: "SelectField",
|
|
2914
|
+
relation: "RelationField",
|
|
2915
|
+
file: "FileField",
|
|
2916
|
+
json: "JSONField"
|
|
2917
|
+
};
|
|
2918
|
+
return constructorMap[fieldType] || "TextField";
|
|
2919
|
+
}
|
|
2920
|
+
function generateFieldConstructorOptions(field) {
|
|
2921
|
+
const parts = [];
|
|
2922
|
+
parts.push(` name: "${field.name}"`);
|
|
2923
|
+
parts.push(` required: ${field.required}`);
|
|
2924
|
+
if (field.unique !== void 0) {
|
|
2925
|
+
parts.push(` unique: ${field.unique}`);
|
|
2926
|
+
}
|
|
2927
|
+
if (field.options && Object.keys(field.options).length > 0) {
|
|
2928
|
+
for (const [key, value] of Object.entries(field.options)) {
|
|
2929
|
+
parts.push(` ${key}: ${formatValue(value)}`);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
if (field.relation && field.type === "relation") {
|
|
2933
|
+
const collectionIdPlaceholder = field.relation.collection === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${field.relation.collection}").id`;
|
|
2934
|
+
parts.push(` collectionId: ${collectionIdPlaceholder}`);
|
|
2935
|
+
if (field.relation.maxSelect !== void 0) {
|
|
2936
|
+
parts.push(` maxSelect: ${field.relation.maxSelect}`);
|
|
2937
|
+
}
|
|
2938
|
+
if (field.relation.minSelect !== void 0) {
|
|
2939
|
+
parts.push(` minSelect: ${field.relation.minSelect}`);
|
|
2940
|
+
}
|
|
2941
|
+
if (field.relation.cascadeDelete !== void 0) {
|
|
2942
|
+
parts.push(` cascadeDelete: ${field.relation.cascadeDelete}`);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
return parts.join(",\n");
|
|
2946
|
+
}
|
|
2947
|
+
function generateFieldAddition(collectionName, field, varName) {
|
|
2948
|
+
const lines = [];
|
|
2949
|
+
const constructorName = getFieldConstructorName(field.type);
|
|
2950
|
+
const collectionVar = varName || `collection_${collectionName}_${field.name}`;
|
|
2951
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2952
|
+
lines.push(``);
|
|
2953
|
+
lines.push(` ${collectionVar}.fields.add(new ${constructorName}({`);
|
|
2954
|
+
lines.push(generateFieldConstructorOptions(field));
|
|
2955
|
+
lines.push(` }));`);
|
|
2956
|
+
lines.push(``);
|
|
2957
|
+
lines.push(` app.save(${collectionVar});`);
|
|
2958
|
+
return lines.join("\n");
|
|
2959
|
+
}
|
|
2960
|
+
function generateFieldModification(collectionName, modification, varName) {
|
|
2961
|
+
const lines = [];
|
|
2962
|
+
const collectionVar = varName || `collection_${collectionName}_${modification.fieldName}`;
|
|
2963
|
+
const fieldVar = `${collectionVar}_field`;
|
|
2964
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2965
|
+
lines.push(` const ${fieldVar} = ${collectionVar}.fields.getByName("${modification.fieldName}");`);
|
|
2966
|
+
lines.push(``);
|
|
2967
|
+
for (const change of modification.changes) {
|
|
2968
|
+
if (change.property.startsWith("options.")) {
|
|
2969
|
+
const optionKey = change.property.replace("options.", "");
|
|
2970
|
+
lines.push(` ${fieldVar}.${optionKey} = ${formatValue(change.newValue)};`);
|
|
2971
|
+
} else if (change.property.startsWith("relation.")) {
|
|
2972
|
+
const relationKey = change.property.replace("relation.", "");
|
|
2973
|
+
if (relationKey === "collection") {
|
|
2974
|
+
const collectionIdValue = change.newValue === "Users" ? '"_pb_users_auth_"' : `app.findCollectionByNameOrId("${change.newValue}").id`;
|
|
2975
|
+
lines.push(` ${fieldVar}.collectionId = ${collectionIdValue};`);
|
|
2976
|
+
} else {
|
|
2977
|
+
lines.push(` ${fieldVar}.${relationKey} = ${formatValue(change.newValue)};`);
|
|
2978
|
+
}
|
|
2979
|
+
} else {
|
|
2980
|
+
lines.push(` ${fieldVar}.${change.property} = ${formatValue(change.newValue)};`);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
lines.push(``);
|
|
2984
|
+
lines.push(` app.save(${collectionVar});`);
|
|
2985
|
+
return lines.join("\n");
|
|
2986
|
+
}
|
|
2987
|
+
function generateFieldDeletion(collectionName, fieldName, varName) {
|
|
2988
|
+
const lines = [];
|
|
2989
|
+
const collectionVar = varName || `collection_${collectionName}_${fieldName}`;
|
|
2990
|
+
const fieldVar = `${collectionVar}_field`;
|
|
2991
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
2992
|
+
lines.push(` const ${fieldVar} = ${collectionVar}.fields.getByName("${fieldName}");`);
|
|
2993
|
+
lines.push(``);
|
|
2994
|
+
lines.push(` ${collectionVar}.fields.remove(${fieldVar}.id);`);
|
|
2995
|
+
lines.push(``);
|
|
2996
|
+
lines.push(` app.save(${collectionVar});`);
|
|
2997
|
+
return lines.join("\n");
|
|
2998
|
+
}
|
|
2999
|
+
function generateIndexAddition(collectionName, index, varName) {
|
|
3000
|
+
const lines = [];
|
|
3001
|
+
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
3002
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
3003
|
+
lines.push(` ${collectionVar}.indexes.push("${index}");`);
|
|
3004
|
+
lines.push(` app.save(${collectionVar});`);
|
|
3005
|
+
return lines.join("\n");
|
|
3006
|
+
}
|
|
3007
|
+
function generateIndexRemoval(collectionName, index, varName) {
|
|
3008
|
+
const lines = [];
|
|
3009
|
+
const collectionVar = varName || `collection_${collectionName}_idx`;
|
|
3010
|
+
const indexVar = `${collectionVar}_indexToRemove`;
|
|
3011
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
3012
|
+
lines.push(` const ${indexVar} = ${collectionVar}.indexes.findIndex(idx => idx === "${index}");`);
|
|
3013
|
+
lines.push(` if (${indexVar} !== -1) {`);
|
|
3014
|
+
lines.push(` ${collectionVar}.indexes.splice(${indexVar}, 1);`);
|
|
3015
|
+
lines.push(` }`);
|
|
3016
|
+
lines.push(` app.save(${collectionVar});`);
|
|
3017
|
+
return lines.join("\n");
|
|
3018
|
+
}
|
|
3019
|
+
function generateRuleUpdate(collectionName, ruleType, newValue, varName) {
|
|
3020
|
+
const lines = [];
|
|
3021
|
+
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
3022
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
3023
|
+
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
3024
|
+
lines.push(` app.save(${collectionVar});`);
|
|
3025
|
+
return lines.join("\n");
|
|
3026
|
+
}
|
|
3027
|
+
function generatePermissionUpdate(collectionName, ruleType, newValue, varName) {
|
|
3028
|
+
const lines = [];
|
|
3029
|
+
const collectionVar = varName || `collection_${collectionName}_${ruleType}`;
|
|
3030
|
+
lines.push(` const ${collectionVar} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
3031
|
+
lines.push(` ${collectionVar}.${ruleType} = ${formatValue(newValue)};`);
|
|
3032
|
+
lines.push(` app.save(${collectionVar});`);
|
|
3033
|
+
return lines.join("\n");
|
|
3034
|
+
}
|
|
3035
|
+
function generateCollectionDeletion(collectionName, varName = "collection") {
|
|
3036
|
+
const lines = [];
|
|
3037
|
+
lines.push(` const ${varName} = app.findCollectionByNameOrId("${collectionName}");`);
|
|
3038
|
+
lines.push(` app.delete(${varName});`);
|
|
3039
|
+
return lines.join("\n");
|
|
3040
|
+
}
|
|
3041
|
+
function generateUpMigration(diff) {
|
|
3042
|
+
const lines = [];
|
|
3043
|
+
lines.push(` // UP MIGRATION`);
|
|
3044
|
+
lines.push(``);
|
|
3045
|
+
if (diff.collectionsToCreate.length > 0) {
|
|
3046
|
+
lines.push(` // Create new collections`);
|
|
3047
|
+
for (let i = 0; i < diff.collectionsToCreate.length; i++) {
|
|
3048
|
+
const collection = diff.collectionsToCreate[i];
|
|
3049
|
+
const varName = `collection_${collection.name}_create`;
|
|
3050
|
+
lines.push(generateCollectionCreation(collection, varName));
|
|
3051
|
+
lines.push(``);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
if (diff.collectionsToModify.length > 0) {
|
|
3055
|
+
lines.push(` // Modify existing collections`);
|
|
3056
|
+
for (const modification of diff.collectionsToModify) {
|
|
3057
|
+
const collectionName = modification.collection;
|
|
3058
|
+
if (modification.fieldsToAdd.length > 0) {
|
|
3059
|
+
lines.push(` // Add fields to ${collectionName}`);
|
|
3060
|
+
for (const field of modification.fieldsToAdd) {
|
|
3061
|
+
const varName = `collection_${collectionName}_add_${field.name}`;
|
|
3062
|
+
lines.push(generateFieldAddition(collectionName, field, varName));
|
|
3063
|
+
lines.push(``);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
if (modification.fieldsToModify.length > 0) {
|
|
3067
|
+
lines.push(` // Modify fields in ${collectionName}`);
|
|
3068
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
3069
|
+
const varName = `collection_${collectionName}_modify_${fieldMod.fieldName}`;
|
|
3070
|
+
lines.push(generateFieldModification(collectionName, fieldMod, varName));
|
|
3071
|
+
lines.push(``);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
if (modification.fieldsToRemove.length > 0) {
|
|
3075
|
+
lines.push(` // Remove fields from ${collectionName}`);
|
|
3076
|
+
for (const field of modification.fieldsToRemove) {
|
|
3077
|
+
const varName = `collection_${collectionName}_remove_${field.name}`;
|
|
3078
|
+
lines.push(generateFieldDeletion(collectionName, field.name, varName));
|
|
3079
|
+
lines.push(``);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
if (modification.indexesToAdd.length > 0) {
|
|
3083
|
+
lines.push(` // Add indexes to ${collectionName}`);
|
|
3084
|
+
for (let i = 0; i < modification.indexesToAdd.length; i++) {
|
|
3085
|
+
const index = modification.indexesToAdd[i];
|
|
3086
|
+
const varName = `collection_${collectionName}_addidx_${i}`;
|
|
3087
|
+
lines.push(generateIndexAddition(collectionName, index, varName));
|
|
3088
|
+
lines.push(``);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
if (modification.indexesToRemove.length > 0) {
|
|
3092
|
+
lines.push(` // Remove indexes from ${collectionName}`);
|
|
3093
|
+
for (let i = 0; i < modification.indexesToRemove.length; i++) {
|
|
3094
|
+
const index = modification.indexesToRemove[i];
|
|
3095
|
+
const varName = `collection_${collectionName}_rmidx_${i}`;
|
|
3096
|
+
lines.push(generateIndexRemoval(collectionName, index, varName));
|
|
3097
|
+
lines.push(``);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
|
|
3101
|
+
lines.push(` // Update permissions for ${collectionName}`);
|
|
3102
|
+
for (const permission of modification.permissionsToUpdate) {
|
|
3103
|
+
const varName = `collection_${collectionName}_perm_${permission.ruleType}`;
|
|
3104
|
+
lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.newValue, varName));
|
|
3105
|
+
lines.push(``);
|
|
3106
|
+
}
|
|
3107
|
+
} else if (modification.rulesToUpdate.length > 0) {
|
|
3108
|
+
lines.push(` // Update rules for ${collectionName}`);
|
|
3109
|
+
for (const rule of modification.rulesToUpdate) {
|
|
3110
|
+
const varName = `collection_${collectionName}_rule_${rule.ruleType}`;
|
|
3111
|
+
lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.newValue, varName));
|
|
3112
|
+
lines.push(``);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
if (diff.collectionsToDelete.length > 0) {
|
|
3118
|
+
lines.push(` // Delete collections`);
|
|
3119
|
+
for (let i = 0; i < diff.collectionsToDelete.length; i++) {
|
|
3120
|
+
const collection = diff.collectionsToDelete[i];
|
|
3121
|
+
const varName = `collection_${collection.name}_delete`;
|
|
3122
|
+
lines.push(generateCollectionDeletion(collection.name, varName));
|
|
3123
|
+
lines.push(``);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
if (lines.length === 2) {
|
|
3127
|
+
lines.push(` // No changes detected`);
|
|
3128
|
+
lines.push(``);
|
|
3129
|
+
}
|
|
3130
|
+
return lines.join("\n");
|
|
3131
|
+
}
|
|
3132
|
+
function generateDownMigration(diff) {
|
|
3133
|
+
const lines = [];
|
|
3134
|
+
lines.push(` // DOWN MIGRATION (ROLLBACK)`);
|
|
3135
|
+
lines.push(``);
|
|
3136
|
+
if (diff.collectionsToDelete.length > 0) {
|
|
3137
|
+
lines.push(` // Recreate deleted collections`);
|
|
3138
|
+
for (let i = 0; i < diff.collectionsToDelete.length; i++) {
|
|
3139
|
+
const collection = diff.collectionsToDelete[i];
|
|
3140
|
+
const varName = `collection_${collection.name}_recreate`;
|
|
3141
|
+
lines.push(generateCollectionCreation(collection, varName));
|
|
3142
|
+
lines.push(``);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (diff.collectionsToModify.length > 0) {
|
|
3146
|
+
lines.push(` // Revert modifications`);
|
|
3147
|
+
for (const modification of diff.collectionsToModify) {
|
|
3148
|
+
const collectionName = modification.collection;
|
|
3149
|
+
if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
|
|
3150
|
+
lines.push(` // Revert permissions for ${collectionName}`);
|
|
3151
|
+
for (const permission of modification.permissionsToUpdate) {
|
|
3152
|
+
const varName = `collection_${collectionName}_revert_perm_${permission.ruleType}`;
|
|
3153
|
+
lines.push(generatePermissionUpdate(collectionName, permission.ruleType, permission.oldValue, varName));
|
|
3154
|
+
lines.push(``);
|
|
3155
|
+
}
|
|
3156
|
+
} else if (modification.rulesToUpdate.length > 0) {
|
|
3157
|
+
lines.push(` // Revert rules for ${collectionName}`);
|
|
3158
|
+
for (const rule of modification.rulesToUpdate) {
|
|
3159
|
+
const varName = `collection_${collectionName}_revert_rule_${rule.ruleType}`;
|
|
3160
|
+
lines.push(generateRuleUpdate(collectionName, rule.ruleType, rule.oldValue, varName));
|
|
3161
|
+
lines.push(``);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
if (modification.indexesToRemove.length > 0) {
|
|
3165
|
+
lines.push(` // Restore indexes to ${collectionName}`);
|
|
3166
|
+
for (let i = 0; i < modification.indexesToRemove.length; i++) {
|
|
3167
|
+
const index = modification.indexesToRemove[i];
|
|
3168
|
+
const varName = `collection_${collectionName}_restore_idx_${i}`;
|
|
3169
|
+
lines.push(generateIndexAddition(collectionName, index, varName));
|
|
3170
|
+
lines.push(``);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if (modification.indexesToAdd.length > 0) {
|
|
3174
|
+
lines.push(` // Remove indexes from ${collectionName}`);
|
|
3175
|
+
for (let i = 0; i < modification.indexesToAdd.length; i++) {
|
|
3176
|
+
const index = modification.indexesToAdd[i];
|
|
3177
|
+
const varName = `collection_${collectionName}_revert_idx_${i}`;
|
|
3178
|
+
lines.push(generateIndexRemoval(collectionName, index, varName));
|
|
3179
|
+
lines.push(``);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
if (modification.fieldsToRemove.length > 0) {
|
|
3183
|
+
lines.push(` // Restore fields to ${collectionName}`);
|
|
3184
|
+
for (const field of modification.fieldsToRemove) {
|
|
3185
|
+
const varName = `collection_${collectionName}_restore_${field.name}`;
|
|
3186
|
+
lines.push(generateFieldAddition(collectionName, field, varName));
|
|
3187
|
+
lines.push(``);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
if (modification.fieldsToModify.length > 0) {
|
|
3191
|
+
lines.push(` // Revert field modifications in ${collectionName}`);
|
|
3192
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
3193
|
+
const reverseChanges = fieldMod.changes.map((change) => ({
|
|
3194
|
+
property: change.property,
|
|
3195
|
+
oldValue: change.newValue,
|
|
3196
|
+
newValue: change.oldValue
|
|
3197
|
+
}));
|
|
3198
|
+
const reverseMod = {
|
|
3199
|
+
fieldName: fieldMod.fieldName,
|
|
3200
|
+
currentDefinition: fieldMod.newDefinition,
|
|
3201
|
+
newDefinition: fieldMod.currentDefinition,
|
|
3202
|
+
changes: reverseChanges
|
|
3203
|
+
};
|
|
3204
|
+
const varName = `collection_${collectionName}_revert_${fieldMod.fieldName}`;
|
|
3205
|
+
lines.push(generateFieldModification(collectionName, reverseMod, varName));
|
|
3206
|
+
lines.push(``);
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
if (modification.fieldsToAdd.length > 0) {
|
|
3210
|
+
lines.push(` // Remove added fields from ${collectionName}`);
|
|
3211
|
+
for (const field of modification.fieldsToAdd) {
|
|
3212
|
+
const varName = `collection_${collectionName}_revert_add_${field.name}`;
|
|
3213
|
+
lines.push(generateFieldDeletion(collectionName, field.name, varName));
|
|
3214
|
+
lines.push(``);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
if (diff.collectionsToCreate.length > 0) {
|
|
3220
|
+
lines.push(` // Delete created collections`);
|
|
3221
|
+
for (let i = 0; i < diff.collectionsToCreate.length; i++) {
|
|
3222
|
+
const collection = diff.collectionsToCreate[i];
|
|
3223
|
+
const varName = `collection_${collection.name}_rollback`;
|
|
3224
|
+
lines.push(generateCollectionDeletion(collection.name, varName));
|
|
3225
|
+
lines.push(``);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
if (lines.length === 2) {
|
|
3229
|
+
lines.push(` // No changes to revert`);
|
|
3230
|
+
lines.push(``);
|
|
3231
|
+
}
|
|
3232
|
+
return lines.join("\n");
|
|
3233
|
+
}
|
|
3234
|
+
function generate(diff, config) {
|
|
3235
|
+
const normalizedConfig = typeof config === "string" ? { migrationDir: config } : config;
|
|
3236
|
+
try {
|
|
3237
|
+
const migrationDir = resolveMigrationDir(normalizedConfig);
|
|
3238
|
+
const upCode = generateUpMigration(diff);
|
|
3239
|
+
const downCode = generateDownMigration(diff);
|
|
3240
|
+
const content = createMigrationFileStructure(upCode, downCode, normalizedConfig);
|
|
3241
|
+
const filename = generateMigrationFilename(diff, normalizedConfig);
|
|
3242
|
+
const filePath = writeMigrationFile(migrationDir, filename, content);
|
|
3243
|
+
return filePath;
|
|
3244
|
+
} catch (error) {
|
|
3245
|
+
if (error instanceof MigrationGenerationError || error instanceof FileSystemError) {
|
|
3246
|
+
throw error;
|
|
3247
|
+
}
|
|
3248
|
+
throw new MigrationGenerationError(
|
|
3249
|
+
`Failed to generate migration: ${error instanceof Error ? error.message : String(error)}`,
|
|
3250
|
+
normalizedConfig.migrationDir,
|
|
3251
|
+
error
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
var MigrationGenerator = class {
|
|
3256
|
+
config;
|
|
3257
|
+
constructor(config) {
|
|
3258
|
+
this.config = mergeConfig4(config);
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Generates a migration file from a schema diff
|
|
3262
|
+
*/
|
|
3263
|
+
generate(diff) {
|
|
3264
|
+
return generate(diff, this.config);
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Generates the up migration code without writing to file
|
|
3268
|
+
*/
|
|
3269
|
+
generateUpMigration(diff) {
|
|
3270
|
+
return generateUpMigration(diff);
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Generates the down migration code without writing to file
|
|
3274
|
+
*/
|
|
3275
|
+
generateDownMigration(diff) {
|
|
3276
|
+
return generateDownMigration(diff);
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Generates a migration filename
|
|
3280
|
+
*/
|
|
3281
|
+
generateMigrationFilename(diff) {
|
|
3282
|
+
return generateMigrationFilename(diff, this.config);
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
|
|
3286
|
+
exports.CLIUsageError = CLIUsageError;
|
|
3287
|
+
exports.ConfigurationError = ConfigurationError;
|
|
3288
|
+
exports.DiffEngine = DiffEngine;
|
|
3289
|
+
exports.FIELD_TYPE_INFO = FIELD_TYPE_INFO;
|
|
3290
|
+
exports.FileSystemError = FileSystemError;
|
|
3291
|
+
exports.MigrationError = MigrationError;
|
|
3292
|
+
exports.MigrationGenerationError = MigrationGenerationError;
|
|
3293
|
+
exports.MigrationGenerator = MigrationGenerator;
|
|
3294
|
+
exports.POCKETBASE_FIELD_TYPES = POCKETBASE_FIELD_TYPES;
|
|
3295
|
+
exports.SchemaAnalyzer = SchemaAnalyzer;
|
|
3296
|
+
exports.SchemaParsingError = SchemaParsingError;
|
|
3297
|
+
exports.SnapshotError = SnapshotError;
|
|
3298
|
+
exports.SnapshotManager = SnapshotManager;
|
|
3299
|
+
exports.aggregateChanges = aggregateChanges;
|
|
3300
|
+
exports.buildFieldDefinition = buildFieldDefinition;
|
|
3301
|
+
exports.buildSchemaDefinition = buildSchemaDefinition;
|
|
3302
|
+
exports.categorizeChangesBySeverity = categorizeChangesBySeverity;
|
|
3303
|
+
exports.compare = compare;
|
|
3304
|
+
exports.compareFieldConstraints = compareFieldConstraints;
|
|
3305
|
+
exports.compareFieldOptions = compareFieldOptions;
|
|
3306
|
+
exports.compareFieldTypes = compareFieldTypes;
|
|
3307
|
+
exports.comparePermissions = comparePermissions;
|
|
3308
|
+
exports.compareRelationConfigurations = compareRelationConfigurations;
|
|
3309
|
+
exports.convertPocketBaseMigration = convertPocketBaseMigration;
|
|
3310
|
+
exports.convertZodSchemaToCollectionSchema = convertZodSchemaToCollectionSchema;
|
|
3311
|
+
exports.createMigrationFileStructure = createMigrationFileStructure;
|
|
3312
|
+
exports.detectDestructiveChanges = detectDestructiveChanges;
|
|
3313
|
+
exports.detectFieldChanges = detectFieldChanges;
|
|
3314
|
+
exports.discoverSchemaFiles = discoverSchemaFiles;
|
|
3315
|
+
exports.extractComprehensiveFieldOptions = extractComprehensiveFieldOptions;
|
|
3316
|
+
exports.extractFieldDefinitions = extractFieldDefinitions;
|
|
3317
|
+
exports.extractFieldOptions = extractFieldOptions;
|
|
3318
|
+
exports.extractIndexes = extractIndexes;
|
|
3319
|
+
exports.extractSchemaDefinitions = extractSchemaDefinitions;
|
|
3320
|
+
exports.filterSystemCollections = filterSystemCollections;
|
|
3321
|
+
exports.findLatestSnapshot = findLatestSnapshot;
|
|
3322
|
+
exports.findNewCollections = findNewCollections;
|
|
3323
|
+
exports.findNewFields = findNewFields;
|
|
3324
|
+
exports.findRemovedCollections = findRemovedCollections;
|
|
3325
|
+
exports.findRemovedFields = findRemovedFields;
|
|
3326
|
+
exports.generate = generate;
|
|
3327
|
+
exports.generateChangeSummary = generateChangeSummary;
|
|
3328
|
+
exports.generateCollectionCreation = generateCollectionCreation;
|
|
3329
|
+
exports.generateCollectionPermissions = generateCollectionPermissions;
|
|
3330
|
+
exports.generateCollectionRules = generateCollectionRules;
|
|
3331
|
+
exports.generateDownMigration = generateDownMigration;
|
|
3332
|
+
exports.generateFieldAddition = generateFieldAddition;
|
|
3333
|
+
exports.generateFieldDefinitionObject = generateFieldDefinitionObject;
|
|
3334
|
+
exports.generateFieldDeletion = generateFieldDeletion;
|
|
3335
|
+
exports.generateFieldModification = generateFieldModification;
|
|
3336
|
+
exports.generateFieldsArray = generateFieldsArray;
|
|
3337
|
+
exports.generateIndexesArray = generateIndexesArray;
|
|
3338
|
+
exports.generateMigrationDescription = generateMigrationDescription;
|
|
3339
|
+
exports.generateMigrationFilename = generateMigrationFilename;
|
|
3340
|
+
exports.generatePermissionUpdate = generatePermissionUpdate;
|
|
3341
|
+
exports.generateTimestamp = generateTimestamp;
|
|
3342
|
+
exports.generateUpMigration = generateUpMigration;
|
|
3343
|
+
exports.getArrayElementType = getArrayElementType;
|
|
3344
|
+
exports.getCollectionNameFromFile = getCollectionNameFromFile;
|
|
3345
|
+
exports.getDefaultValue = getDefaultValue;
|
|
3346
|
+
exports.getFieldTypeInfo = getFieldTypeInfo;
|
|
3347
|
+
exports.getMaxSelect = getMaxSelect;
|
|
3348
|
+
exports.getMinSelect = getMinSelect;
|
|
3349
|
+
exports.getSnapshotPath = getSnapshotPath;
|
|
3350
|
+
exports.getSnapshotVersion = getSnapshotVersion;
|
|
3351
|
+
exports.getUsersSystemFields = getUsersSystemFields;
|
|
3352
|
+
exports.importSchemaModule = importSchemaModule;
|
|
3353
|
+
exports.isArrayType = isArrayType;
|
|
3354
|
+
exports.isAuthCollection = isAuthCollection;
|
|
3355
|
+
exports.isEditorField = isEditorField;
|
|
3356
|
+
exports.isFieldRequired = isFieldRequired;
|
|
3357
|
+
exports.isFileFieldByName = isFileFieldByName;
|
|
3358
|
+
exports.isGeoPointType = isGeoPointType;
|
|
3359
|
+
exports.isMultipleRelationField = isMultipleRelationField;
|
|
3360
|
+
exports.isRelationField = isRelationField;
|
|
3361
|
+
exports.isSingleRelationField = isSingleRelationField;
|
|
3362
|
+
exports.isSystemCollection = isSystemCollection;
|
|
3363
|
+
exports.loadBaseMigration = loadBaseMigration;
|
|
3364
|
+
exports.loadSnapshot = loadSnapshot;
|
|
3365
|
+
exports.loadSnapshotIfExists = loadSnapshotIfExists;
|
|
3366
|
+
exports.mapZodArrayType = mapZodArrayType;
|
|
3367
|
+
exports.mapZodBooleanType = mapZodBooleanType;
|
|
3368
|
+
exports.mapZodDateType = mapZodDateType;
|
|
3369
|
+
exports.mapZodEnumType = mapZodEnumType;
|
|
3370
|
+
exports.mapZodNumberType = mapZodNumberType;
|
|
3371
|
+
exports.mapZodRecordType = mapZodRecordType;
|
|
3372
|
+
exports.mapZodStringType = mapZodStringType;
|
|
3373
|
+
exports.mapZodTypeToPocketBase = mapZodTypeToPocketBase;
|
|
3374
|
+
exports.matchCollectionsByName = matchCollectionsByName;
|
|
3375
|
+
exports.matchFieldsByName = matchFieldsByName;
|
|
3376
|
+
exports.mergeSnapshots = mergeSnapshots;
|
|
3377
|
+
exports.parseSchemaFiles = parseSchemaFiles;
|
|
3378
|
+
exports.pluralize = pluralize;
|
|
3379
|
+
exports.requiresForceFlag = requiresForceFlag;
|
|
3380
|
+
exports.resolveTargetCollection = resolveTargetCollection;
|
|
3381
|
+
exports.saveSnapshot = saveSnapshot;
|
|
3382
|
+
exports.selectSchemaForCollection = selectSchemaForCollection;
|
|
3383
|
+
exports.singularize = singularize;
|
|
3384
|
+
exports.snapshotExists = snapshotExists;
|
|
3385
|
+
exports.toCollectionName = toCollectionName;
|
|
3386
|
+
exports.unwrapZodType = unwrapZodType;
|
|
3387
|
+
exports.validateSnapshot = validateSnapshot;
|
|
3388
|
+
exports.writeMigrationFile = writeMigrationFile;
|
|
3389
|
+
//# sourceMappingURL=index.cjs.map
|
|
3390
|
+
//# sourceMappingURL=index.cjs.map
|