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,534 @@
|
|
|
1
|
+
// src/migration/diff.ts
|
|
2
|
+
var DEFAULT_CONFIG = {
|
|
3
|
+
warnOnDelete: true,
|
|
4
|
+
requireForceForDestructive: true,
|
|
5
|
+
severityThreshold: "high",
|
|
6
|
+
systemCollections: ["_mfas", "_otps", "_externalAuths", "_authOrigins", "_superusers"],
|
|
7
|
+
usersSystemFields: ["id", "password", "tokenKey", "email", "emailVisibility", "verified", "created", "updated"]
|
|
8
|
+
};
|
|
9
|
+
function mergeConfig(config) {
|
|
10
|
+
return {
|
|
11
|
+
...DEFAULT_CONFIG,
|
|
12
|
+
...config
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function isSystemCollection(collectionName, config) {
|
|
16
|
+
const mergedConfig = mergeConfig(config);
|
|
17
|
+
return mergedConfig.systemCollections.includes(collectionName);
|
|
18
|
+
}
|
|
19
|
+
function getUsersSystemFields(config) {
|
|
20
|
+
const mergedConfig = mergeConfig(config);
|
|
21
|
+
return new Set(mergedConfig.usersSystemFields);
|
|
22
|
+
}
|
|
23
|
+
function filterSystemCollections(schema, config) {
|
|
24
|
+
const filteredCollections = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const [collectionName, collectionSchema] of schema.collections) {
|
|
26
|
+
if (!isSystemCollection(collectionName, config)) {
|
|
27
|
+
filteredCollections.set(collectionName, collectionSchema);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
collections: filteredCollections
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function findNewCollections(currentSchema, previousSnapshot) {
|
|
35
|
+
const newCollections = [];
|
|
36
|
+
if (!previousSnapshot) {
|
|
37
|
+
return Array.from(currentSchema.collections.values());
|
|
38
|
+
}
|
|
39
|
+
for (const [collectionName, collectionSchema] of currentSchema.collections) {
|
|
40
|
+
if (!previousSnapshot.collections.has(collectionName)) {
|
|
41
|
+
newCollections.push(collectionSchema);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return newCollections;
|
|
45
|
+
}
|
|
46
|
+
function findRemovedCollections(currentSchema, previousSnapshot) {
|
|
47
|
+
const removedCollections = [];
|
|
48
|
+
if (!previousSnapshot) {
|
|
49
|
+
return removedCollections;
|
|
50
|
+
}
|
|
51
|
+
for (const [collectionName, collectionSchema] of previousSnapshot.collections) {
|
|
52
|
+
if (!currentSchema.collections.has(collectionName)) {
|
|
53
|
+
removedCollections.push(collectionSchema);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return removedCollections;
|
|
57
|
+
}
|
|
58
|
+
function matchCollectionsByName(currentSchema, previousSnapshot) {
|
|
59
|
+
const matches = [];
|
|
60
|
+
if (!previousSnapshot) {
|
|
61
|
+
return matches;
|
|
62
|
+
}
|
|
63
|
+
for (const [collectionName, currentCollection] of currentSchema.collections) {
|
|
64
|
+
const previousCollection = previousSnapshot.collections.get(collectionName);
|
|
65
|
+
if (previousCollection) {
|
|
66
|
+
matches.push([currentCollection, previousCollection]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return matches;
|
|
70
|
+
}
|
|
71
|
+
function findNewFields(currentFields, previousFields) {
|
|
72
|
+
const newFields = [];
|
|
73
|
+
const previousFieldNames = new Set(previousFields.map((f) => f.name));
|
|
74
|
+
for (const currentField of currentFields) {
|
|
75
|
+
if (!previousFieldNames.has(currentField.name)) {
|
|
76
|
+
newFields.push(currentField);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return newFields;
|
|
80
|
+
}
|
|
81
|
+
function findRemovedFields(currentFields, previousFields) {
|
|
82
|
+
const removedFields = [];
|
|
83
|
+
const currentFieldNames = new Set(currentFields.map((f) => f.name));
|
|
84
|
+
for (const previousField of previousFields) {
|
|
85
|
+
if (!currentFieldNames.has(previousField.name)) {
|
|
86
|
+
removedFields.push(previousField);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return removedFields;
|
|
90
|
+
}
|
|
91
|
+
function matchFieldsByName(currentFields, previousFields) {
|
|
92
|
+
const matches = [];
|
|
93
|
+
const previousFieldMap = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const previousField of previousFields) {
|
|
95
|
+
previousFieldMap.set(previousField.name, previousField);
|
|
96
|
+
}
|
|
97
|
+
for (const currentField of currentFields) {
|
|
98
|
+
const previousField = previousFieldMap.get(currentField.name);
|
|
99
|
+
if (previousField) {
|
|
100
|
+
matches.push([currentField, previousField]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return matches;
|
|
104
|
+
}
|
|
105
|
+
function areValuesEqual(a, b) {
|
|
106
|
+
if (a === b) return true;
|
|
107
|
+
if (a == null || b == null) return false;
|
|
108
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
109
|
+
if (a.length !== b.length) return false;
|
|
110
|
+
return a.every((val, idx) => areValuesEqual(val, b[idx]));
|
|
111
|
+
}
|
|
112
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
113
|
+
const keysA = Object.keys(a);
|
|
114
|
+
const keysB = Object.keys(b);
|
|
115
|
+
if (keysA.length !== keysB.length) return false;
|
|
116
|
+
return keysA.every((key) => areValuesEqual(a[key], b[key]));
|
|
117
|
+
}
|
|
118
|
+
return a === b;
|
|
119
|
+
}
|
|
120
|
+
function compareFieldTypes(currentField, previousField) {
|
|
121
|
+
if (currentField.type !== previousField.type) {
|
|
122
|
+
return {
|
|
123
|
+
property: "type",
|
|
124
|
+
oldValue: previousField.type,
|
|
125
|
+
newValue: currentField.type
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function compareFieldConstraints(currentField, previousField) {
|
|
131
|
+
const changes = [];
|
|
132
|
+
if (currentField.required !== previousField.required) {
|
|
133
|
+
changes.push({
|
|
134
|
+
property: "required",
|
|
135
|
+
oldValue: previousField.required,
|
|
136
|
+
newValue: currentField.required
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (currentField.unique !== previousField.unique) {
|
|
140
|
+
changes.push({
|
|
141
|
+
property: "unique",
|
|
142
|
+
oldValue: previousField.unique,
|
|
143
|
+
newValue: currentField.unique
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return changes;
|
|
147
|
+
}
|
|
148
|
+
function compareFieldOptions(currentField, previousField) {
|
|
149
|
+
const changes = [];
|
|
150
|
+
const currentOptions = currentField.options || {};
|
|
151
|
+
const previousOptions = previousField.options || {};
|
|
152
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);
|
|
153
|
+
for (const key of allKeys) {
|
|
154
|
+
const currentValue = currentOptions[key];
|
|
155
|
+
const previousValue = previousOptions[key];
|
|
156
|
+
if (!areValuesEqual(currentValue, previousValue)) {
|
|
157
|
+
changes.push({
|
|
158
|
+
property: `options.${key}`,
|
|
159
|
+
oldValue: previousValue,
|
|
160
|
+
newValue: currentValue
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return changes;
|
|
165
|
+
}
|
|
166
|
+
function compareRelationConfigurations(currentField, previousField) {
|
|
167
|
+
const changes = [];
|
|
168
|
+
const currentRelation = currentField.relation;
|
|
169
|
+
const previousRelation = previousField.relation;
|
|
170
|
+
if (!currentRelation && !previousRelation) {
|
|
171
|
+
return changes;
|
|
172
|
+
}
|
|
173
|
+
if (!currentRelation || !previousRelation) {
|
|
174
|
+
return changes;
|
|
175
|
+
}
|
|
176
|
+
if (currentRelation.collection !== previousRelation.collection) {
|
|
177
|
+
changes.push({
|
|
178
|
+
property: "relation.collection",
|
|
179
|
+
oldValue: previousRelation.collection,
|
|
180
|
+
newValue: currentRelation.collection
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {
|
|
184
|
+
changes.push({
|
|
185
|
+
property: "relation.cascadeDelete",
|
|
186
|
+
oldValue: previousRelation.cascadeDelete,
|
|
187
|
+
newValue: currentRelation.cascadeDelete
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (currentRelation.maxSelect !== previousRelation.maxSelect) {
|
|
191
|
+
changes.push({
|
|
192
|
+
property: "relation.maxSelect",
|
|
193
|
+
oldValue: previousRelation.maxSelect,
|
|
194
|
+
newValue: currentRelation.maxSelect
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (currentRelation.minSelect !== previousRelation.minSelect) {
|
|
198
|
+
changes.push({
|
|
199
|
+
property: "relation.minSelect",
|
|
200
|
+
oldValue: previousRelation.minSelect,
|
|
201
|
+
newValue: currentRelation.minSelect
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return changes;
|
|
205
|
+
}
|
|
206
|
+
function detectFieldChanges(currentField, previousField) {
|
|
207
|
+
const changes = [];
|
|
208
|
+
const typeChange = compareFieldTypes(currentField, previousField);
|
|
209
|
+
if (typeChange) {
|
|
210
|
+
changes.push(typeChange);
|
|
211
|
+
}
|
|
212
|
+
changes.push(...compareFieldConstraints(currentField, previousField));
|
|
213
|
+
changes.push(...compareFieldOptions(currentField, previousField));
|
|
214
|
+
if (currentField.type === "relation" && previousField.type === "relation") {
|
|
215
|
+
changes.push(...compareRelationConfigurations(currentField, previousField));
|
|
216
|
+
}
|
|
217
|
+
return changes;
|
|
218
|
+
}
|
|
219
|
+
function compareIndexes(currentIndexes = [], previousIndexes = []) {
|
|
220
|
+
const currentSet = new Set(currentIndexes);
|
|
221
|
+
const previousSet = new Set(previousIndexes);
|
|
222
|
+
const indexesToAdd = currentIndexes.filter((idx) => !previousSet.has(idx));
|
|
223
|
+
const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));
|
|
224
|
+
return { indexesToAdd, indexesToRemove };
|
|
225
|
+
}
|
|
226
|
+
function compareRules(currentRules, previousRules) {
|
|
227
|
+
const updates = [];
|
|
228
|
+
const ruleTypes = [
|
|
229
|
+
"listRule",
|
|
230
|
+
"viewRule",
|
|
231
|
+
"createRule",
|
|
232
|
+
"updateRule",
|
|
233
|
+
"deleteRule",
|
|
234
|
+
"manageRule"
|
|
235
|
+
];
|
|
236
|
+
for (const ruleType of ruleTypes) {
|
|
237
|
+
const currentValue = currentRules?.[ruleType] ?? null;
|
|
238
|
+
const previousValue = previousRules?.[ruleType] ?? null;
|
|
239
|
+
if (currentValue !== previousValue) {
|
|
240
|
+
updates.push({
|
|
241
|
+
ruleType,
|
|
242
|
+
oldValue: previousValue,
|
|
243
|
+
newValue: currentValue
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return updates;
|
|
248
|
+
}
|
|
249
|
+
function comparePermissions(currentPermissions, previousPermissions) {
|
|
250
|
+
const changes = [];
|
|
251
|
+
const ruleTypes = ["listRule", "viewRule", "createRule", "updateRule", "deleteRule", "manageRule"];
|
|
252
|
+
for (const ruleType of ruleTypes) {
|
|
253
|
+
const currentValue = currentPermissions?.[ruleType] ?? null;
|
|
254
|
+
const previousValue = previousPermissions?.[ruleType] ?? null;
|
|
255
|
+
if (currentValue !== previousValue) {
|
|
256
|
+
changes.push({
|
|
257
|
+
ruleType,
|
|
258
|
+
oldValue: previousValue,
|
|
259
|
+
newValue: currentValue
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return changes;
|
|
264
|
+
}
|
|
265
|
+
function compareCollectionFields(currentCollection, previousCollection, config) {
|
|
266
|
+
let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);
|
|
267
|
+
const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);
|
|
268
|
+
const fieldsToModify = [];
|
|
269
|
+
if (currentCollection.name === "users") {
|
|
270
|
+
const systemFields = getUsersSystemFields(config);
|
|
271
|
+
fieldsToAdd = fieldsToAdd.filter((field) => !systemFields.has(field.name));
|
|
272
|
+
}
|
|
273
|
+
const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);
|
|
274
|
+
for (const [currentField, previousField] of matchedFields) {
|
|
275
|
+
const changes = detectFieldChanges(currentField, previousField);
|
|
276
|
+
if (changes.length > 0) {
|
|
277
|
+
fieldsToModify.push({
|
|
278
|
+
fieldName: currentField.name,
|
|
279
|
+
currentDefinition: previousField,
|
|
280
|
+
newDefinition: currentField,
|
|
281
|
+
changes
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { fieldsToAdd, fieldsToRemove, fieldsToModify };
|
|
286
|
+
}
|
|
287
|
+
function buildCollectionModification(currentCollection, previousCollection, config) {
|
|
288
|
+
const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
|
|
289
|
+
currentCollection,
|
|
290
|
+
previousCollection,
|
|
291
|
+
config
|
|
292
|
+
);
|
|
293
|
+
const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);
|
|
294
|
+
const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);
|
|
295
|
+
const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);
|
|
296
|
+
return {
|
|
297
|
+
collection: currentCollection.name,
|
|
298
|
+
fieldsToAdd,
|
|
299
|
+
fieldsToRemove,
|
|
300
|
+
fieldsToModify,
|
|
301
|
+
indexesToAdd,
|
|
302
|
+
indexesToRemove,
|
|
303
|
+
rulesToUpdate,
|
|
304
|
+
permissionsToUpdate
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function hasChanges(modification) {
|
|
308
|
+
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;
|
|
309
|
+
}
|
|
310
|
+
function aggregateChanges(currentSchema, previousSnapshot, config) {
|
|
311
|
+
const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);
|
|
312
|
+
const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);
|
|
313
|
+
const filteredCollectionsToCreate = collectionsToCreate.filter(
|
|
314
|
+
(collection) => !isSystemCollection(collection.name, config)
|
|
315
|
+
);
|
|
316
|
+
const filteredCollectionsToDelete = collectionsToDelete.filter(
|
|
317
|
+
(collection) => !isSystemCollection(collection.name, config)
|
|
318
|
+
);
|
|
319
|
+
const collectionsToModify = [];
|
|
320
|
+
const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);
|
|
321
|
+
for (const [currentCollection, previousCollection] of matchedCollections) {
|
|
322
|
+
const modification = buildCollectionModification(currentCollection, previousCollection, config);
|
|
323
|
+
if (hasChanges(modification)) {
|
|
324
|
+
collectionsToModify.push(modification);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
collectionsToCreate: filteredCollectionsToCreate,
|
|
329
|
+
collectionsToDelete: filteredCollectionsToDelete,
|
|
330
|
+
collectionsToModify
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function detectDestructiveChanges(diff, config) {
|
|
334
|
+
const destructiveChanges = [];
|
|
335
|
+
const mergedConfig = mergeConfig(config);
|
|
336
|
+
for (const collection of diff.collectionsToDelete) {
|
|
337
|
+
destructiveChanges.push({
|
|
338
|
+
type: "collection_delete",
|
|
339
|
+
severity: "high",
|
|
340
|
+
collection: collection.name,
|
|
341
|
+
description: `Delete collection: ${collection.name}`
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
for (const modification of diff.collectionsToModify) {
|
|
345
|
+
const collectionName = modification.collection;
|
|
346
|
+
for (const field of modification.fieldsToRemove) {
|
|
347
|
+
destructiveChanges.push({
|
|
348
|
+
type: "field_delete",
|
|
349
|
+
severity: "high",
|
|
350
|
+
collection: collectionName,
|
|
351
|
+
field: field.name,
|
|
352
|
+
description: `Delete field: ${collectionName}.${field.name}`
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
356
|
+
const typeChange = fieldMod.changes.find((c) => c.property === "type");
|
|
357
|
+
const requiredChange = fieldMod.changes.find((c) => c.property === "required" && c.newValue === true);
|
|
358
|
+
if (typeChange) {
|
|
359
|
+
destructiveChanges.push({
|
|
360
|
+
type: "type_change",
|
|
361
|
+
severity: "high",
|
|
362
|
+
collection: collectionName,
|
|
363
|
+
field: fieldMod.fieldName,
|
|
364
|
+
description: `Change field type: ${collectionName}.${fieldMod.fieldName} (${typeChange.oldValue} \u2192 ${typeChange.newValue})`,
|
|
365
|
+
oldValue: typeChange.oldValue,
|
|
366
|
+
newValue: typeChange.newValue
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
if (requiredChange && mergedConfig.severityThreshold !== "high") {
|
|
370
|
+
destructiveChanges.push({
|
|
371
|
+
type: "required_change",
|
|
372
|
+
severity: "medium",
|
|
373
|
+
collection: collectionName,
|
|
374
|
+
field: fieldMod.fieldName,
|
|
375
|
+
description: `Make field required: ${collectionName}.${fieldMod.fieldName}`,
|
|
376
|
+
oldValue: false,
|
|
377
|
+
newValue: true
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (mergedConfig.severityThreshold === "low") {
|
|
381
|
+
const otherChanges = fieldMod.changes.filter((c) => c.property !== "type" && c.property !== "required");
|
|
382
|
+
for (const change of otherChanges) {
|
|
383
|
+
destructiveChanges.push({
|
|
384
|
+
type: "constraint_change",
|
|
385
|
+
severity: "low",
|
|
386
|
+
collection: collectionName,
|
|
387
|
+
field: fieldMod.fieldName,
|
|
388
|
+
description: `Change constraint: ${collectionName}.${fieldMod.fieldName}.${change.property}`,
|
|
389
|
+
oldValue: change.oldValue,
|
|
390
|
+
newValue: change.newValue
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return destructiveChanges;
|
|
397
|
+
}
|
|
398
|
+
function categorizeChangesBySeverity(diff, _config) {
|
|
399
|
+
const destructive = [];
|
|
400
|
+
const nonDestructive = [];
|
|
401
|
+
for (const collection of diff.collectionsToDelete) {
|
|
402
|
+
destructive.push(`Delete collection: ${collection.name}`);
|
|
403
|
+
}
|
|
404
|
+
for (const collection of diff.collectionsToCreate) {
|
|
405
|
+
nonDestructive.push(`Create collection: ${collection.name}`);
|
|
406
|
+
}
|
|
407
|
+
for (const modification of diff.collectionsToModify) {
|
|
408
|
+
const collectionName = modification.collection;
|
|
409
|
+
for (const field of modification.fieldsToRemove) {
|
|
410
|
+
destructive.push(`Delete field: ${collectionName}.${field.name}`);
|
|
411
|
+
}
|
|
412
|
+
for (const field of modification.fieldsToAdd) {
|
|
413
|
+
nonDestructive.push(`Add field: ${collectionName}.${field.name}`);
|
|
414
|
+
}
|
|
415
|
+
for (const fieldMod of modification.fieldsToModify) {
|
|
416
|
+
const hasTypeChange = fieldMod.changes.some((c) => c.property === "type");
|
|
417
|
+
const hasRequiredChange = fieldMod.changes.some((c) => c.property === "required" && c.newValue === true);
|
|
418
|
+
if (hasTypeChange) {
|
|
419
|
+
destructive.push(
|
|
420
|
+
`Change field type: ${collectionName}.${fieldMod.fieldName} (${fieldMod.changes.find((c) => c.property === "type")?.oldValue} \u2192 ${fieldMod.changes.find((c) => c.property === "type")?.newValue})`
|
|
421
|
+
);
|
|
422
|
+
} else if (hasRequiredChange) {
|
|
423
|
+
destructive.push(`Make field required: ${collectionName}.${fieldMod.fieldName}`);
|
|
424
|
+
} else {
|
|
425
|
+
nonDestructive.push(`Modify field: ${collectionName}.${fieldMod.fieldName}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const _index of modification.indexesToAdd) {
|
|
429
|
+
nonDestructive.push(`Add index: ${collectionName}`);
|
|
430
|
+
}
|
|
431
|
+
for (const _index of modification.indexesToRemove) {
|
|
432
|
+
nonDestructive.push(`Remove index: ${collectionName}`);
|
|
433
|
+
}
|
|
434
|
+
for (const rule of modification.rulesToUpdate) {
|
|
435
|
+
nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return { destructive, nonDestructive };
|
|
439
|
+
}
|
|
440
|
+
function generateChangeSummary(diff, config) {
|
|
441
|
+
const destructiveChanges = detectDestructiveChanges(diff, config);
|
|
442
|
+
const { nonDestructive } = categorizeChangesBySeverity(diff);
|
|
443
|
+
let fieldsToAdd = 0;
|
|
444
|
+
let fieldsToRemove = 0;
|
|
445
|
+
let fieldsToModify = 0;
|
|
446
|
+
let indexChanges = 0;
|
|
447
|
+
let ruleChanges = 0;
|
|
448
|
+
let permissionChanges = 0;
|
|
449
|
+
for (const modification of diff.collectionsToModify) {
|
|
450
|
+
fieldsToAdd += modification.fieldsToAdd.length;
|
|
451
|
+
fieldsToRemove += modification.fieldsToRemove.length;
|
|
452
|
+
fieldsToModify += modification.fieldsToModify.length;
|
|
453
|
+
indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;
|
|
454
|
+
ruleChanges += modification.rulesToUpdate.length;
|
|
455
|
+
permissionChanges += modification.permissionsToUpdate.length;
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,
|
|
459
|
+
collectionsToCreate: diff.collectionsToCreate.length,
|
|
460
|
+
collectionsToDelete: diff.collectionsToDelete.length,
|
|
461
|
+
collectionsToModify: diff.collectionsToModify.length,
|
|
462
|
+
fieldsToAdd,
|
|
463
|
+
fieldsToRemove,
|
|
464
|
+
fieldsToModify,
|
|
465
|
+
indexChanges,
|
|
466
|
+
ruleChanges,
|
|
467
|
+
permissionChanges,
|
|
468
|
+
destructiveChanges,
|
|
469
|
+
nonDestructiveChanges: nonDestructive
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function requiresForceFlag(diff, config) {
|
|
473
|
+
const mergedConfig = mergeConfig(config);
|
|
474
|
+
if (!mergedConfig.requireForceForDestructive) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const destructiveChanges = detectDestructiveChanges(diff, config);
|
|
478
|
+
const relevantChanges = destructiveChanges.filter((change) => {
|
|
479
|
+
switch (mergedConfig.severityThreshold) {
|
|
480
|
+
case "high":
|
|
481
|
+
return change.severity === "high";
|
|
482
|
+
case "medium":
|
|
483
|
+
return change.severity === "high" || change.severity === "medium";
|
|
484
|
+
case "low":
|
|
485
|
+
return true;
|
|
486
|
+
default:
|
|
487
|
+
return change.severity === "high";
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
return relevantChanges.length > 0;
|
|
491
|
+
}
|
|
492
|
+
function compare(currentSchema, previousSnapshot, config) {
|
|
493
|
+
return aggregateChanges(currentSchema, previousSnapshot, config);
|
|
494
|
+
}
|
|
495
|
+
var DiffEngine = class {
|
|
496
|
+
config;
|
|
497
|
+
constructor(config) {
|
|
498
|
+
this.config = mergeConfig(config);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Compares current schema with previous snapshot
|
|
502
|
+
*/
|
|
503
|
+
compare(currentSchema, previousSnapshot) {
|
|
504
|
+
return compare(currentSchema, previousSnapshot, this.config);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Detects destructive changes in a diff
|
|
508
|
+
*/
|
|
509
|
+
detectDestructiveChanges(diff) {
|
|
510
|
+
return detectDestructiveChanges(diff, this.config);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Categorizes changes by severity
|
|
514
|
+
*/
|
|
515
|
+
categorizeChangesBySeverity(diff) {
|
|
516
|
+
return categorizeChangesBySeverity(diff, this.config);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Generates a summary of changes
|
|
520
|
+
*/
|
|
521
|
+
generateChangeSummary(diff) {
|
|
522
|
+
return generateChangeSummary(diff, this.config);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Checks if force flag is required
|
|
526
|
+
*/
|
|
527
|
+
requiresForceFlag(diff) {
|
|
528
|
+
return requiresForceFlag(diff, this.config);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
export { DiffEngine, aggregateChanges, categorizeChangesBySeverity, compare, compareFieldConstraints, compareFieldOptions, compareFieldTypes, comparePermissions, compareRelationConfigurations, detectDestructiveChanges, detectFieldChanges, filterSystemCollections, findNewCollections, findNewFields, findRemovedCollections, findRemovedFields, generateChangeSummary, getUsersSystemFields, isSystemCollection, matchCollectionsByName, matchFieldsByName, requiresForceFlag };
|
|
533
|
+
//# sourceMappingURL=diff.js.map
|
|
534
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/migration/diff.ts"],"names":[],"mappings":"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","file":"diff.js","sourcesContent":["/**\n * Diff Engine component\n * Compares current schema with previous snapshot and identifies changes\n *\n * This module provides a standalone, configurable diff engine that can be used\n * by consumer projects to compare schema definitions and detect changes.\n */\n\nimport type {\n APIRuleType,\n CollectionModification,\n CollectionSchema,\n FieldChange,\n FieldDefinition,\n FieldModification,\n PermissionChange,\n RuleUpdate,\n SchemaDefinition,\n SchemaDiff,\n SchemaSnapshot,\n} from \"./types\";\n\n/**\n * Configuration options for the diff engine\n */\nexport interface DiffEngineConfig {\n /**\n * Whether to warn on collection deletions\n * Defaults to true\n */\n warnOnDelete?: boolean;\n\n /**\n * Whether to require --force flag for destructive changes\n * Defaults to true\n */\n requireForceForDestructive?: boolean;\n\n /**\n * Severity threshold for requiring force flag\n * 'high' = only collection/field deletions and type changes\n * 'medium' = includes making fields required\n * 'low' = includes any constraint changes\n * Defaults to 'high'\n */\n severityThreshold?: \"high\" | \"medium\" | \"low\";\n\n /**\n * Custom system collections to exclude from diff\n * These collections will not be created or deleted\n */\n systemCollections?: string[];\n\n /**\n * Custom system fields to exclude from user collection diffs\n * These fields will not be included in fieldsToAdd for the users collection\n */\n usersSystemFields?: string[];\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<DiffEngineConfig> = {\n warnOnDelete: true,\n requireForceForDestructive: true,\n severityThreshold: \"high\",\n systemCollections: [\"_mfas\", \"_otps\", \"_externalAuths\", \"_authOrigins\", \"_superusers\"],\n usersSystemFields: [\"id\", \"password\", \"tokenKey\", \"email\", \"emailVisibility\", \"verified\", \"created\", \"updated\"],\n};\n\n/**\n * Merges user config with defaults\n */\nfunction mergeConfig(config?: DiffEngineConfig): Required<DiffEngineConfig> {\n return {\n ...DEFAULT_CONFIG,\n ...config,\n };\n}\n\n/**\n * Destructive change information\n */\nexport interface DestructiveChange {\n type: \"collection_delete\" | \"field_delete\" | \"type_change\" | \"required_change\" | \"constraint_change\";\n severity: \"high\" | \"medium\" | \"low\";\n collection: string;\n field?: string;\n description: string;\n oldValue?: any;\n newValue?: any;\n}\n\n/**\n * Change summary for status reporting\n */\nexport interface ChangeSummary {\n totalChanges: number;\n collectionsToCreate: number;\n collectionsToDelete: number;\n collectionsToModify: number;\n fieldsToAdd: number;\n fieldsToRemove: number;\n fieldsToModify: number;\n indexChanges: number;\n ruleChanges: number;\n permissionChanges: number;\n destructiveChanges: DestructiveChange[];\n nonDestructiveChanges: string[];\n}\n\n/**\n * Checks if a collection is a PocketBase system collection\n * System collections are internal to PocketBase and should not be created or deleted\n *\n * @param collectionName - Name of the collection to check\n * @param config - Optional configuration with custom system collections\n * @returns True if the collection is a system collection\n */\nexport function isSystemCollection(collectionName: string, config?: DiffEngineConfig): boolean {\n const mergedConfig = mergeConfig(config);\n return mergedConfig.systemCollections.includes(collectionName);\n}\n\n/**\n * Returns the list of system field names for the users collection\n * These fields are automatically provided by PocketBase for auth collections\n * and should not be included when generating migrations for users collection extensions\n *\n * @param config - Optional configuration with custom system fields\n * @returns Set of system field names\n */\nexport function getUsersSystemFields(config?: DiffEngineConfig): Set<string> {\n const mergedConfig = mergeConfig(config);\n return new Set(mergedConfig.usersSystemFields);\n}\n\n/**\n * Filters system collections from a schema definition\n * Returns a new SchemaDefinition with only custom (non-system) collections\n *\n * @param schema - Schema definition to filter\n * @param config - Optional configuration\n * @returns Filtered SchemaDefinition without system collections\n */\nexport function filterSystemCollections(schema: SchemaDefinition, config?: DiffEngineConfig): SchemaDefinition {\n const filteredCollections = new Map<string, CollectionSchema>();\n\n for (const [collectionName, collectionSchema] of schema.collections) {\n if (!isSystemCollection(collectionName, config)) {\n filteredCollections.set(collectionName, collectionSchema);\n }\n }\n\n return {\n collections: filteredCollections,\n };\n}\n\n/**\n * Identifies new collections in schema that don't exist in snapshot\n *\n * @param currentSchema - Current schema definition\n * @param previousSnapshot - Previous schema snapshot\n * @returns Array of new collections\n */\nexport function findNewCollections(\n currentSchema: SchemaDefinition,\n previousSnapshot: SchemaSnapshot | null\n): CollectionSchema[] {\n const newCollections: CollectionSchema[] = [];\n\n // If no previous snapshot, all collections are new\n if (!previousSnapshot) {\n return Array.from(currentSchema.collections.values());\n }\n\n // Find collections in current schema that don't exist in snapshot\n for (const [collectionName, collectionSchema] of currentSchema.collections) {\n if (!previousSnapshot.collections.has(collectionName)) {\n newCollections.push(collectionSchema);\n }\n }\n\n return newCollections;\n}\n\n/**\n * Identifies collections removed from schema (exist in snapshot but not in current schema)\n *\n * @param currentSchema - Current schema definition\n * @param previousSnapshot - Previous schema snapshot\n * @returns Array of removed collections\n */\nexport function findRemovedCollections(\n currentSchema: SchemaDefinition,\n previousSnapshot: SchemaSnapshot | null\n): CollectionSchema[] {\n const removedCollections: CollectionSchema[] = [];\n\n // If no previous snapshot, nothing can be removed\n if (!previousSnapshot) {\n return removedCollections;\n }\n\n // Find collections in snapshot that don't exist in current schema\n for (const [collectionName, collectionSchema] of previousSnapshot.collections) {\n if (!currentSchema.collections.has(collectionName)) {\n removedCollections.push(collectionSchema);\n }\n }\n\n return removedCollections;\n}\n\n/**\n * Matches collections by name between current schema and snapshot\n * Returns pairs of [current, previous] for collections that exist in both\n *\n * @param currentSchema - Current schema definition\n * @param previousSnapshot - Previous schema snapshot\n * @returns Array of matched collection pairs\n */\nexport function matchCollectionsByName(\n currentSchema: SchemaDefinition,\n previousSnapshot: SchemaSnapshot | null\n): Array<[CollectionSchema, CollectionSchema]> {\n const matches: Array<[CollectionSchema, CollectionSchema]> = [];\n\n // If no previous snapshot, no matches possible\n if (!previousSnapshot) {\n return matches;\n }\n\n // Find collections that exist in both current and previous\n for (const [collectionName, currentCollection] of currentSchema.collections) {\n const previousCollection = previousSnapshot.collections.get(collectionName);\n\n if (previousCollection) {\n matches.push([currentCollection, previousCollection]);\n }\n }\n\n return matches;\n}\n\n/**\n * Identifies new fields in current collection that don't exist in previous\n *\n * @param currentFields - Current collection fields\n * @param previousFields - Previous collection fields\n * @returns Array of new fields\n */\nexport function findNewFields(currentFields: FieldDefinition[], previousFields: FieldDefinition[]): FieldDefinition[] {\n const newFields: FieldDefinition[] = [];\n const previousFieldNames = new Set(previousFields.map((f) => f.name));\n\n for (const currentField of currentFields) {\n if (!previousFieldNames.has(currentField.name)) {\n newFields.push(currentField);\n }\n }\n\n return newFields;\n}\n\n/**\n * Identifies fields removed from current collection (exist in previous but not in current)\n *\n * @param currentFields - Current collection fields\n * @param previousFields - Previous collection fields\n * @returns Array of removed fields\n */\nexport function findRemovedFields(\n currentFields: FieldDefinition[],\n previousFields: FieldDefinition[]\n): FieldDefinition[] {\n const removedFields: FieldDefinition[] = [];\n const currentFieldNames = new Set(currentFields.map((f) => f.name));\n\n for (const previousField of previousFields) {\n if (!currentFieldNames.has(previousField.name)) {\n removedFields.push(previousField);\n }\n }\n\n return removedFields;\n}\n\n/**\n * Matches fields by name between current and previous collections\n * Returns pairs of [current, previous] for fields that exist in both\n *\n * @param currentFields - Current collection fields\n * @param previousFields - Previous collection fields\n * @returns Array of matched field pairs\n */\nexport function matchFieldsByName(\n currentFields: FieldDefinition[],\n previousFields: FieldDefinition[]\n): Array<[FieldDefinition, FieldDefinition]> {\n const matches: Array<[FieldDefinition, FieldDefinition]> = [];\n\n // Create a map of previous fields by name for efficient lookup\n const previousFieldMap = new Map<string, FieldDefinition>();\n for (const previousField of previousFields) {\n previousFieldMap.set(previousField.name, previousField);\n }\n\n // Find matching fields\n for (const currentField of currentFields) {\n const previousField = previousFieldMap.get(currentField.name);\n\n if (previousField) {\n matches.push([currentField, previousField]);\n }\n }\n\n return matches;\n}\n\n/**\n * Compares two values for equality, handling deep object comparison\n *\n * @param a - First value\n * @param b - Second value\n * @returns True if values are equal\n */\nfunction areValuesEqual(a: any, b: any): boolean {\n // Handle null/undefined\n if (a === b) return true;\n if (a == null || b == null) return false;\n\n // Handle arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((val, idx) => areValuesEqual(val, b[idx]));\n }\n\n // Handle objects\n if (typeof a === \"object\" && typeof b === \"object\") {\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) return false;\n\n return keysA.every((key) => areValuesEqual(a[key], b[key]));\n }\n\n // Primitive comparison\n return a === b;\n}\n\n/**\n * Compares field types between current and previous\n *\n * @param currentField - Current field definition\n * @param previousField - Previous field definition\n * @returns FieldChange if types differ, null otherwise\n */\nexport function compareFieldTypes(currentField: FieldDefinition, previousField: FieldDefinition): FieldChange | null {\n if (currentField.type !== previousField.type) {\n return {\n property: \"type\",\n oldValue: previousField.type,\n newValue: currentField.type,\n };\n }\n\n return null;\n}\n\n/**\n * Compares field constraints (required, unique) between current and previous\n *\n * @param currentField - Current field definition\n * @param previousField - Previous field definition\n * @returns Array of FieldChange for constraint differences\n */\nexport function compareFieldConstraints(currentField: FieldDefinition, previousField: FieldDefinition): FieldChange[] {\n const changes: FieldChange[] = [];\n\n // Compare required constraint\n if (currentField.required !== previousField.required) {\n changes.push({\n property: \"required\",\n oldValue: previousField.required,\n newValue: currentField.required,\n });\n }\n\n // Compare unique constraint\n if (currentField.unique !== previousField.unique) {\n changes.push({\n property: \"unique\",\n oldValue: previousField.unique,\n newValue: currentField.unique,\n });\n }\n\n return changes;\n}\n\n/**\n * Compares field options (min, max, pattern, etc.) between current and previous\n *\n * @param currentField - Current field definition\n * @param previousField - Previous field definition\n * @returns Array of FieldChange for option differences\n */\nexport function compareFieldOptions(currentField: FieldDefinition, previousField: FieldDefinition): FieldChange[] {\n const changes: FieldChange[] = [];\n\n const currentOptions = currentField.options || {};\n const previousOptions = previousField.options || {};\n\n // Get all unique option keys\n const allKeys = new Set([...Object.keys(currentOptions), ...Object.keys(previousOptions)]);\n\n // Compare each option\n for (const key of allKeys) {\n const currentValue = currentOptions[key];\n const previousValue = previousOptions[key];\n\n if (!areValuesEqual(currentValue, previousValue)) {\n changes.push({\n property: `options.${key}`,\n oldValue: previousValue,\n newValue: currentValue,\n });\n }\n }\n\n return changes;\n}\n\n/**\n * Compares relation configurations between current and previous\n *\n * @param currentField - Current field definition\n * @param previousField - Previous field definition\n * @returns Array of FieldChange for relation differences\n */\nexport function compareRelationConfigurations(\n currentField: FieldDefinition,\n previousField: FieldDefinition\n): FieldChange[] {\n const changes: FieldChange[] = [];\n\n const currentRelation = currentField.relation;\n const previousRelation = previousField.relation;\n\n // If one has relation and other doesn't, that's a type change (handled elsewhere)\n if (!currentRelation && !previousRelation) {\n return changes;\n }\n\n if (!currentRelation || !previousRelation) {\n // This shouldn't happen if types match, but handle gracefully\n return changes;\n }\n\n // Compare relation properties\n if (currentRelation.collection !== previousRelation.collection) {\n changes.push({\n property: \"relation.collection\",\n oldValue: previousRelation.collection,\n newValue: currentRelation.collection,\n });\n }\n\n if (currentRelation.cascadeDelete !== previousRelation.cascadeDelete) {\n changes.push({\n property: \"relation.cascadeDelete\",\n oldValue: previousRelation.cascadeDelete,\n newValue: currentRelation.cascadeDelete,\n });\n }\n\n if (currentRelation.maxSelect !== previousRelation.maxSelect) {\n changes.push({\n property: \"relation.maxSelect\",\n oldValue: previousRelation.maxSelect,\n newValue: currentRelation.maxSelect,\n });\n }\n\n if (currentRelation.minSelect !== previousRelation.minSelect) {\n changes.push({\n property: \"relation.minSelect\",\n oldValue: previousRelation.minSelect,\n newValue: currentRelation.minSelect,\n });\n }\n\n return changes;\n}\n\n/**\n * Detects all changes between two field definitions\n * Combines type, constraint, option, and relation changes\n *\n * @param currentField - Current field definition\n * @param previousField - Previous field definition\n * @returns Array of all detected changes\n */\nexport function detectFieldChanges(currentField: FieldDefinition, previousField: FieldDefinition): FieldChange[] {\n const changes: FieldChange[] = [];\n\n // Compare types\n const typeChange = compareFieldTypes(currentField, previousField);\n if (typeChange) {\n changes.push(typeChange);\n }\n\n // Compare constraints\n changes.push(...compareFieldConstraints(currentField, previousField));\n\n // Compare options\n changes.push(...compareFieldOptions(currentField, previousField));\n\n // Compare relation configurations (if applicable)\n if (currentField.type === \"relation\" && previousField.type === \"relation\") {\n changes.push(...compareRelationConfigurations(currentField, previousField));\n }\n\n return changes;\n}\n\n/**\n * Compares indexes between current and previous collections\n *\n * @param currentIndexes - Current collection indexes\n * @param previousIndexes - Previous collection indexes\n * @returns Object with indexes to add and remove\n */\nfunction compareIndexes(\n currentIndexes: string[] = [],\n previousIndexes: string[] = []\n): { indexesToAdd: string[]; indexesToRemove: string[] } {\n const currentSet = new Set(currentIndexes);\n const previousSet = new Set(previousIndexes);\n\n const indexesToAdd = currentIndexes.filter((idx) => !previousSet.has(idx));\n const indexesToRemove = previousIndexes.filter((idx) => !currentSet.has(idx));\n\n return { indexesToAdd, indexesToRemove };\n}\n\n/**\n * Compares API rules between current and previous collections\n *\n * @param currentRules - Current collection rules\n * @param previousRules - Previous collection rules\n * @returns Array of rule updates\n */\nfunction compareRules(currentRules: CollectionSchema[\"rules\"], previousRules: CollectionSchema[\"rules\"]): RuleUpdate[] {\n const updates: RuleUpdate[] = [];\n\n const ruleTypes: Array<keyof NonNullable<CollectionSchema[\"rules\"]>> = [\n \"listRule\",\n \"viewRule\",\n \"createRule\",\n \"updateRule\",\n \"deleteRule\",\n \"manageRule\",\n ];\n\n for (const ruleType of ruleTypes) {\n const currentValue = currentRules?.[ruleType] ?? null;\n const previousValue = previousRules?.[ruleType] ?? null;\n\n if (currentValue !== previousValue) {\n updates.push({\n ruleType: ruleType as RuleUpdate[\"ruleType\"],\n oldValue: previousValue,\n newValue: currentValue,\n });\n }\n }\n\n return updates;\n}\n\n/**\n * Compares permissions between current and previous collections\n * Detects changes in permission rules defined in schema\n *\n * @param currentPermissions - Current collection permissions\n * @param previousPermissions - Previous collection permissions\n * @returns Array of permission changes\n */\nexport function comparePermissions(\n currentPermissions: CollectionSchema[\"permissions\"],\n previousPermissions: CollectionSchema[\"permissions\"]\n): PermissionChange[] {\n const changes: PermissionChange[] = [];\n\n const ruleTypes: APIRuleType[] = [\"listRule\", \"viewRule\", \"createRule\", \"updateRule\", \"deleteRule\", \"manageRule\"];\n\n for (const ruleType of ruleTypes) {\n const currentValue = currentPermissions?.[ruleType] ?? null;\n const previousValue = previousPermissions?.[ruleType] ?? null;\n\n // Compare permission values\n if (currentValue !== previousValue) {\n changes.push({\n ruleType,\n oldValue: previousValue,\n newValue: currentValue,\n });\n }\n }\n\n return changes;\n}\n\n/**\n * Compares fields between current and previous collections\n * Identifies new, removed, and modified fields\n * For the users collection, filters out system fields from fieldsToAdd\n *\n * @param currentCollection - Current collection schema\n * @param previousCollection - Previous collection schema\n * @param config - Optional configuration\n * @returns Object with field changes\n */\nfunction compareCollectionFields(\n currentCollection: CollectionSchema,\n previousCollection: CollectionSchema,\n config?: DiffEngineConfig\n): {\n fieldsToAdd: FieldDefinition[];\n fieldsToRemove: FieldDefinition[];\n fieldsToModify: FieldModification[];\n} {\n let fieldsToAdd = findNewFields(currentCollection.fields, previousCollection.fields);\n const fieldsToRemove = findRemovedFields(currentCollection.fields, previousCollection.fields);\n const fieldsToModify: FieldModification[] = [];\n\n // For users collection, filter out system fields from fieldsToAdd\n // System fields are automatically provided by PocketBase and should not be in migrations\n if (currentCollection.name === \"users\") {\n const systemFields = getUsersSystemFields(config);\n fieldsToAdd = fieldsToAdd.filter((field) => !systemFields.has(field.name));\n }\n\n // Check for modified fields\n const matchedFields = matchFieldsByName(currentCollection.fields, previousCollection.fields);\n\n for (const [currentField, previousField] of matchedFields) {\n const changes = detectFieldChanges(currentField, previousField);\n\n if (changes.length > 0) {\n fieldsToModify.push({\n fieldName: currentField.name,\n currentDefinition: previousField,\n newDefinition: currentField,\n changes,\n });\n }\n }\n\n return { fieldsToAdd, fieldsToRemove, fieldsToModify };\n}\n\n/**\n * Builds a CollectionModification for a matched collection pair\n *\n * @param currentCollection - Current collection schema\n * @param previousCollection - Previous collection schema\n * @param config - Optional configuration\n * @returns CollectionModification object\n */\nfunction buildCollectionModification(\n currentCollection: CollectionSchema,\n previousCollection: CollectionSchema,\n config?: DiffEngineConfig\n): CollectionModification {\n // Compare fields\n const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(\n currentCollection,\n previousCollection,\n config\n );\n\n // Compare indexes\n const { indexesToAdd, indexesToRemove } = compareIndexes(currentCollection.indexes, previousCollection.indexes);\n\n // Compare rules\n const rulesToUpdate = compareRules(currentCollection.rules, previousCollection.rules);\n\n // Compare permissions\n const permissionsToUpdate = comparePermissions(currentCollection.permissions, previousCollection.permissions);\n\n return {\n collection: currentCollection.name,\n fieldsToAdd,\n fieldsToRemove,\n fieldsToModify,\n indexesToAdd,\n indexesToRemove,\n rulesToUpdate,\n permissionsToUpdate,\n };\n}\n\n/**\n * Checks if a collection modification has any actual changes\n *\n * @param modification - Collection modification to check\n * @returns True if there are any changes\n */\nfunction hasChanges(modification: CollectionModification): boolean {\n return (\n modification.fieldsToAdd.length > 0 ||\n modification.fieldsToRemove.length > 0 ||\n modification.fieldsToModify.length > 0 ||\n modification.indexesToAdd.length > 0 ||\n modification.indexesToRemove.length > 0 ||\n modification.rulesToUpdate.length > 0 ||\n modification.permissionsToUpdate.length > 0\n );\n}\n\n/**\n * Aggregates all detected changes into a SchemaDiff\n * Main entry point for diff comparison\n *\n * @param currentSchema - Current schema definition\n * @param previousSnapshot - Previous schema snapshot\n * @param config - Optional configuration\n * @returns Complete SchemaDiff with all changes\n */\nexport function aggregateChanges(\n currentSchema: SchemaDefinition,\n previousSnapshot: SchemaSnapshot | null,\n config?: DiffEngineConfig\n): SchemaDiff {\n // Find new and removed collections\n const collectionsToCreate = findNewCollections(currentSchema, previousSnapshot);\n const collectionsToDelete = findRemovedCollections(currentSchema, previousSnapshot);\n\n // Filter out system collections from create and delete operations\n const filteredCollectionsToCreate = collectionsToCreate.filter(\n (collection) => !isSystemCollection(collection.name, config)\n );\n const filteredCollectionsToDelete = collectionsToDelete.filter(\n (collection) => !isSystemCollection(collection.name, config)\n );\n\n // Find modified collections\n const collectionsToModify: CollectionModification[] = [];\n const matchedCollections = matchCollectionsByName(currentSchema, previousSnapshot);\n\n for (const [currentCollection, previousCollection] of matchedCollections) {\n const modification = buildCollectionModification(currentCollection, previousCollection, config);\n\n // Only include if there are actual changes\n // Note: We allow modifications to the users collection (non-system)\n if (hasChanges(modification)) {\n collectionsToModify.push(modification);\n }\n }\n\n return {\n collectionsToCreate: filteredCollectionsToCreate,\n collectionsToDelete: filteredCollectionsToDelete,\n collectionsToModify,\n };\n}\n\n/**\n * Detects destructive changes in a schema diff\n * Returns detailed information about each destructive change\n *\n * @param diff - Schema diff to analyze\n * @param config - Optional configuration for severity thresholds\n * @returns Array of destructive changes with severity information\n */\nexport function detectDestructiveChanges(diff: SchemaDiff, config?: DiffEngineConfig): DestructiveChange[] {\n const destructiveChanges: DestructiveChange[] = [];\n const mergedConfig = mergeConfig(config);\n\n // Collection deletions are always high severity\n for (const collection of diff.collectionsToDelete) {\n destructiveChanges.push({\n type: \"collection_delete\",\n severity: \"high\",\n collection: collection.name,\n description: `Delete collection: ${collection.name}`,\n });\n }\n\n // Analyze modifications\n for (const modification of diff.collectionsToModify) {\n const collectionName = modification.collection;\n\n // Field deletions are high severity\n for (const field of modification.fieldsToRemove) {\n destructiveChanges.push({\n type: \"field_delete\",\n severity: \"high\",\n collection: collectionName,\n field: field.name,\n description: `Delete field: ${collectionName}.${field.name}`,\n });\n }\n\n // Field modifications can be various severities\n for (const fieldMod of modification.fieldsToModify) {\n const typeChange = fieldMod.changes.find((c) => c.property === \"type\");\n const requiredChange = fieldMod.changes.find((c) => c.property === \"required\" && c.newValue === true);\n\n if (typeChange) {\n destructiveChanges.push({\n type: \"type_change\",\n severity: \"high\",\n collection: collectionName,\n field: fieldMod.fieldName,\n description: `Change field type: ${collectionName}.${fieldMod.fieldName} (${typeChange.oldValue} → ${typeChange.newValue})`,\n oldValue: typeChange.oldValue,\n newValue: typeChange.newValue,\n });\n }\n\n if (requiredChange && mergedConfig.severityThreshold !== \"high\") {\n destructiveChanges.push({\n type: \"required_change\",\n severity: \"medium\",\n collection: collectionName,\n field: fieldMod.fieldName,\n description: `Make field required: ${collectionName}.${fieldMod.fieldName}`,\n oldValue: false,\n newValue: true,\n });\n }\n\n // Other constraint changes at low severity\n if (mergedConfig.severityThreshold === \"low\") {\n const otherChanges = fieldMod.changes.filter((c) => c.property !== \"type\" && c.property !== \"required\");\n for (const change of otherChanges) {\n destructiveChanges.push({\n type: \"constraint_change\",\n severity: \"low\",\n collection: collectionName,\n field: fieldMod.fieldName,\n description: `Change constraint: ${collectionName}.${fieldMod.fieldName}.${change.property}`,\n oldValue: change.oldValue,\n newValue: change.newValue,\n });\n }\n }\n }\n }\n\n return destructiveChanges;\n}\n\n/**\n * Categorizes changes by severity\n * Returns object with destructive and non-destructive changes\n *\n * @param diff - Schema diff to categorize\n * @param config - Optional configuration\n * @returns Object with categorized changes\n */\nexport function categorizeChangesBySeverity(\n diff: SchemaDiff,\n _config?: DiffEngineConfig\n): {\n destructive: string[];\n nonDestructive: string[];\n} {\n const destructive: string[] = [];\n const nonDestructive: string[] = [];\n\n // Collection deletions are destructive\n for (const collection of diff.collectionsToDelete) {\n destructive.push(`Delete collection: ${collection.name}`);\n }\n\n // Collection creations are non-destructive\n for (const collection of diff.collectionsToCreate) {\n nonDestructive.push(`Create collection: ${collection.name}`);\n }\n\n // Analyze modifications\n for (const modification of diff.collectionsToModify) {\n const collectionName = modification.collection;\n\n // Field deletions are destructive\n for (const field of modification.fieldsToRemove) {\n destructive.push(`Delete field: ${collectionName}.${field.name}`);\n }\n\n // Field additions are non-destructive\n for (const field of modification.fieldsToAdd) {\n nonDestructive.push(`Add field: ${collectionName}.${field.name}`);\n }\n\n // Field modifications can be destructive or non-destructive\n for (const fieldMod of modification.fieldsToModify) {\n const hasTypeChange = fieldMod.changes.some((c) => c.property === \"type\");\n const hasRequiredChange = fieldMod.changes.some((c) => c.property === \"required\" && c.newValue === true);\n\n if (hasTypeChange) {\n destructive.push(\n `Change field type: ${collectionName}.${fieldMod.fieldName} (${fieldMod.changes.find((c) => c.property === \"type\")?.oldValue} → ${fieldMod.changes.find((c) => c.property === \"type\")?.newValue})`\n );\n } else if (hasRequiredChange) {\n destructive.push(`Make field required: ${collectionName}.${fieldMod.fieldName}`);\n } else {\n nonDestructive.push(`Modify field: ${collectionName}.${fieldMod.fieldName}`);\n }\n }\n\n // Index changes are generally non-destructive\n for (const _index of modification.indexesToAdd) {\n nonDestructive.push(`Add index: ${collectionName}`);\n }\n\n for (const _index of modification.indexesToRemove) {\n nonDestructive.push(`Remove index: ${collectionName}`);\n }\n\n // Rule changes are non-destructive\n for (const rule of modification.rulesToUpdate) {\n nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);\n }\n }\n\n return { destructive, nonDestructive };\n}\n\n/**\n * Generates a summary of all changes in a diff\n * Useful for status reporting and user feedback\n *\n * @param diff - Schema diff to summarize\n * @param config - Optional configuration\n * @returns Change summary with counts and details\n */\nexport function generateChangeSummary(diff: SchemaDiff, config?: DiffEngineConfig): ChangeSummary {\n const destructiveChanges = detectDestructiveChanges(diff, config);\n const { nonDestructive } = categorizeChangesBySeverity(diff, config);\n\n let fieldsToAdd = 0;\n let fieldsToRemove = 0;\n let fieldsToModify = 0;\n let indexChanges = 0;\n let ruleChanges = 0;\n let permissionChanges = 0;\n\n for (const modification of diff.collectionsToModify) {\n fieldsToAdd += modification.fieldsToAdd.length;\n fieldsToRemove += modification.fieldsToRemove.length;\n fieldsToModify += modification.fieldsToModify.length;\n indexChanges += modification.indexesToAdd.length + modification.indexesToRemove.length;\n ruleChanges += modification.rulesToUpdate.length;\n permissionChanges += modification.permissionsToUpdate.length;\n }\n\n return {\n totalChanges: diff.collectionsToCreate.length + diff.collectionsToDelete.length + diff.collectionsToModify.length,\n collectionsToCreate: diff.collectionsToCreate.length,\n collectionsToDelete: diff.collectionsToDelete.length,\n collectionsToModify: diff.collectionsToModify.length,\n fieldsToAdd,\n fieldsToRemove,\n fieldsToModify,\n indexChanges,\n ruleChanges,\n permissionChanges,\n destructiveChanges,\n nonDestructiveChanges: nonDestructive,\n };\n}\n\n/**\n * Checks if a diff requires the --force flag based on configuration\n *\n * @param diff - Schema diff to check\n * @param config - Configuration with severity threshold\n * @returns True if force flag is required\n */\nexport function requiresForceFlag(diff: SchemaDiff, config?: DiffEngineConfig): boolean {\n const mergedConfig = mergeConfig(config);\n\n if (!mergedConfig.requireForceForDestructive) {\n return false;\n }\n\n const destructiveChanges = detectDestructiveChanges(diff, config);\n\n // Filter by severity threshold\n const relevantChanges = destructiveChanges.filter((change) => {\n switch (mergedConfig.severityThreshold) {\n case \"high\":\n return change.severity === \"high\";\n case \"medium\":\n return change.severity === \"high\" || change.severity === \"medium\";\n case \"low\":\n return true;\n default:\n return change.severity === \"high\";\n }\n });\n\n return relevantChanges.length > 0;\n}\n\n/**\n * Main comparison function\n * Compares current schema with previous snapshot and returns complete diff\n *\n * @param currentSchema - Current schema definition\n * @param previousSnapshot - Previous schema snapshot (null for first run)\n * @param config - Optional configuration\n * @returns Complete SchemaDiff with all detected changes\n */\nexport function compare(\n currentSchema: SchemaDefinition,\n previousSnapshot: SchemaSnapshot | null,\n config?: DiffEngineConfig\n): SchemaDiff {\n return aggregateChanges(currentSchema, previousSnapshot, config);\n}\n\n/**\n * DiffEngine class for object-oriented usage\n * Provides a stateful interface for schema comparison\n */\nexport class DiffEngine {\n private config: Required<DiffEngineConfig>;\n\n constructor(config?: DiffEngineConfig) {\n this.config = mergeConfig(config);\n }\n\n /**\n * Compares current schema with previous snapshot\n */\n compare(currentSchema: SchemaDefinition, previousSnapshot: SchemaSnapshot | null): SchemaDiff {\n return compare(currentSchema, previousSnapshot, this.config);\n }\n\n /**\n * Detects destructive changes in a diff\n */\n detectDestructiveChanges(diff: SchemaDiff): DestructiveChange[] {\n return detectDestructiveChanges(diff, this.config);\n }\n\n /**\n * Categorizes changes by severity\n */\n categorizeChangesBySeverity(diff: SchemaDiff): { destructive: string[]; nonDestructive: string[] } {\n return categorizeChangesBySeverity(diff, this.config);\n }\n\n /**\n * Generates a summary of changes\n */\n generateChangeSummary(diff: SchemaDiff): ChangeSummary {\n return generateChangeSummary(diff, this.config);\n }\n\n /**\n * Checks if force flag is required\n */\n requiresForceFlag(diff: SchemaDiff): boolean {\n return requiresForceFlag(diff, this.config);\n }\n}\n"]}
|