pocketbase-zod-schema 0.7.1 → 0.7.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 +7 -0
- package/dist/cli/index.cjs +323 -27
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +323 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrate.cjs +329 -30
- package/dist/cli/migrate.cjs.map +1 -1
- package/dist/cli/migrate.js +329 -30
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/utils/index.cjs +8 -1
- package/dist/cli/utils/index.cjs.map +1 -1
- package/dist/cli/utils/index.d.cts +1 -1
- package/dist/cli/utils/index.d.ts +1 -1
- package/dist/cli/utils/index.js +8 -1
- package/dist/cli/utils/index.js.map +1 -1
- package/dist/index.cjs +90 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/dist/migration/analyzer.cjs +87 -14
- package/dist/migration/analyzer.cjs.map +1 -1
- package/dist/migration/analyzer.d.cts +12 -4
- package/dist/migration/analyzer.d.ts +12 -4
- package/dist/migration/analyzer.js +87 -15
- package/dist/migration/analyzer.js.map +1 -1
- package/dist/migration/diff.cjs +64 -9
- package/dist/migration/diff.cjs.map +1 -1
- package/dist/migration/diff.d.cts +12 -2
- package/dist/migration/diff.d.ts +12 -2
- package/dist/migration/diff.js +64 -10
- package/dist/migration/diff.js.map +1 -1
- package/dist/migration/generator.cjs +100 -2
- package/dist/migration/generator.cjs.map +1 -1
- package/dist/migration/generator.d.cts +32 -2
- package/dist/migration/generator.d.ts +32 -2
- package/dist/migration/generator.js +99 -3
- package/dist/migration/generator.js.map +1 -1
- package/dist/migration/index.cjs +349 -26
- package/dist/migration/index.cjs.map +1 -1
- package/dist/migration/index.d.cts +2 -2
- package/dist/migration/index.d.ts +2 -2
- package/dist/migration/index.js +349 -26
- package/dist/migration/index.js.map +1 -1
- package/dist/migration/snapshot.cjs +99 -1
- package/dist/migration/snapshot.cjs.map +1 -1
- package/dist/migration/snapshot.d.cts +3 -1
- package/dist/migration/snapshot.d.ts +3 -1
- package/dist/migration/snapshot.js +99 -1
- package/dist/migration/snapshot.js.map +1 -1
- package/dist/migration/utils/index.d.cts +1 -1
- package/dist/migration/utils/index.d.ts +1 -1
- package/dist/schema.cjs +90 -1
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +128 -2
- package/dist/schema.d.ts +128 -2
- package/dist/schema.js +87 -2
- package/dist/schema.js.map +1 -1
- package/dist/server.cjs +393 -28
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +390 -29
- package/dist/server.js.map +1 -1
- package/dist/{types-CWHV6ATd.d.cts → types-CnzfX6JH.d.cts} +20 -2
- package/dist/{types-C2nGWHLV.d.ts → types-Do3jyFBm.d.ts} +20 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.2](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.7.1...pocketbase-zod-schema-v0.7.2) (2026-07-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add defineView for view collections ([a239a96](https://github.com/dastron/pocketbase-zod-schema/commit/a239a96b9d9e69019be3625db50dbb27919baaee))
|
|
9
|
+
|
|
3
10
|
## [0.7.1](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.7.0...pocketbase-zod-schema-v0.7.1) (2026-05-06)
|
|
4
11
|
|
|
5
12
|
|
package/dist/cli/index.cjs
CHANGED
|
@@ -1220,6 +1220,60 @@ function unwrapZodType(zodType) {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
return unwrapped;
|
|
1222
1222
|
}
|
|
1223
|
+
function dedentSql(value) {
|
|
1224
|
+
const lines = value.replace(/\r\n/g, "\n").split("\n");
|
|
1225
|
+
while (lines.length > 0 && lines[0].trim() === "") {
|
|
1226
|
+
lines.shift();
|
|
1227
|
+
}
|
|
1228
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
1229
|
+
lines.pop();
|
|
1230
|
+
}
|
|
1231
|
+
if (lines.length === 0) {
|
|
1232
|
+
return "";
|
|
1233
|
+
}
|
|
1234
|
+
let minIndent = Infinity;
|
|
1235
|
+
for (const line of lines) {
|
|
1236
|
+
if (line.trim() === "") continue;
|
|
1237
|
+
const indent = line.length - line.trimStart().length;
|
|
1238
|
+
if (indent < minIndent) {
|
|
1239
|
+
minIndent = indent;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (!Number.isFinite(minIndent) || minIndent === 0) {
|
|
1243
|
+
return lines.map((line) => line.trimEnd()).join("\n");
|
|
1244
|
+
}
|
|
1245
|
+
return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
|
|
1246
|
+
}
|
|
1247
|
+
function stripLeadingComments(query) {
|
|
1248
|
+
let remaining = query.trim();
|
|
1249
|
+
while (remaining.length > 0) {
|
|
1250
|
+
if (remaining.startsWith("--")) {
|
|
1251
|
+
const newline = remaining.indexOf("\n");
|
|
1252
|
+
remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
if (remaining.startsWith("/*")) {
|
|
1256
|
+
const end = remaining.indexOf("*/");
|
|
1257
|
+
remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
return remaining;
|
|
1263
|
+
}
|
|
1264
|
+
function validateViewQuery(collectionName, viewQuery) {
|
|
1265
|
+
if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const body = stripLeadingComments(viewQuery);
|
|
1271
|
+
if (!/^(select|with)\b/i.test(body)) {
|
|
1272
|
+
throw new Error(
|
|
1273
|
+
`View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1223
1277
|
function getCollectionNameFromFile(filePath) {
|
|
1224
1278
|
const filename = path8__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1225
1279
|
return toCollectionName(filename);
|
|
@@ -1243,13 +1297,26 @@ function extractCollectionTypeFromSchema(zodSchema) {
|
|
|
1243
1297
|
}
|
|
1244
1298
|
try {
|
|
1245
1299
|
const metadata = JSON.parse(zodSchema.description);
|
|
1246
|
-
if (metadata.type === "base" || metadata.type === "auth") {
|
|
1300
|
+
if (metadata.type === "base" || metadata.type === "auth" || metadata.type === "view") {
|
|
1247
1301
|
return metadata.type;
|
|
1248
1302
|
}
|
|
1249
1303
|
} catch {
|
|
1250
1304
|
}
|
|
1251
1305
|
return null;
|
|
1252
1306
|
}
|
|
1307
|
+
function extractViewQueryFromSchema(zodSchema) {
|
|
1308
|
+
if (!zodSchema.description) {
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
try {
|
|
1312
|
+
const metadata = JSON.parse(zodSchema.description);
|
|
1313
|
+
if (typeof metadata.viewQuery === "string") {
|
|
1314
|
+
return metadata.viewQuery;
|
|
1315
|
+
}
|
|
1316
|
+
} catch {
|
|
1317
|
+
}
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1253
1320
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema", "Collection"]) {
|
|
1254
1321
|
const result = {};
|
|
1255
1322
|
if (module.default instanceof zod.z.ZodObject) {
|
|
@@ -1408,6 +1475,15 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1408
1475
|
const rawFields = extractFieldDefinitions(zodSchema);
|
|
1409
1476
|
const explicitType = extractCollectionTypeFromSchema(zodSchema);
|
|
1410
1477
|
const collectionType = explicitType ?? (isAuthCollection(rawFields) ? "auth" : "base");
|
|
1478
|
+
const isView = collectionType === "view";
|
|
1479
|
+
const viewQuery = extractViewQueryFromSchema(zodSchema);
|
|
1480
|
+
if (isView) {
|
|
1481
|
+
validateViewQuery(collectionName, viewQuery);
|
|
1482
|
+
} else if (viewQuery !== null) {
|
|
1483
|
+
console.warn(
|
|
1484
|
+
`[${collectionName}] viewQuery is only used by view collections and will be ignored. Use defineView() or set type: "view" to define a view collection.`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1411
1487
|
const fields = rawFields.filter((f) => !["created", "updated"].includes(f.name)).map(({ name, zodType }) => buildFieldDefinition(name, zodType));
|
|
1412
1488
|
if (collectionType === "auth") {
|
|
1413
1489
|
const authSystemFields = [
|
|
@@ -1467,26 +1543,43 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1467
1543
|
}
|
|
1468
1544
|
}
|
|
1469
1545
|
const indexes = extractIndexes(zodSchema) || [];
|
|
1546
|
+
if (isView && indexes.length > 0) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`View collection "${collectionName}" cannot declare indexes. PocketBase view collections are backed by a SQL query and do not support indexes.`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1470
1551
|
const permissionAnalyzer = new PermissionAnalyzer();
|
|
1471
1552
|
let permissions = void 0;
|
|
1472
1553
|
const schemaDescription = zodSchema.description;
|
|
1473
1554
|
const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
|
|
1474
1555
|
if (extractedPermissions) {
|
|
1475
1556
|
const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1485
|
-
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1557
|
+
if (isView) {
|
|
1558
|
+
for (const ruleType of ["createRule", "updateRule", "deleteRule", "manageRule"]) {
|
|
1559
|
+
if (resolvedPermissions[ruleType]) {
|
|
1560
|
+
console.warn(
|
|
1561
|
+
`[${collectionName}] ${ruleType} is not supported on view collections and will be set to null.`
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
resolvedPermissions[ruleType] = null;
|
|
1486
1565
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1566
|
+
}
|
|
1567
|
+
if (!isView) {
|
|
1568
|
+
const validationResults = permissionAnalyzer.validatePermissions(
|
|
1569
|
+
collectionName,
|
|
1570
|
+
resolvedPermissions,
|
|
1571
|
+
fields,
|
|
1572
|
+
collectionType === "auth"
|
|
1573
|
+
);
|
|
1574
|
+
for (const [ruleType, result] of validationResults) {
|
|
1575
|
+
if (!result.valid) {
|
|
1576
|
+
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1577
|
+
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1578
|
+
}
|
|
1579
|
+
if (result.warnings.length > 0) {
|
|
1580
|
+
console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
|
|
1581
|
+
result.warnings.forEach((warning) => console.warn(` - ${warning}`));
|
|
1582
|
+
}
|
|
1490
1583
|
}
|
|
1491
1584
|
}
|
|
1492
1585
|
permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
|
|
@@ -1507,6 +1600,9 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1507
1600
|
},
|
|
1508
1601
|
permissions
|
|
1509
1602
|
};
|
|
1603
|
+
if (isView) {
|
|
1604
|
+
collectionSchema.viewQuery = viewQuery;
|
|
1605
|
+
}
|
|
1510
1606
|
return collectionSchema;
|
|
1511
1607
|
}
|
|
1512
1608
|
var tsxLoaderRegistered = false;
|
|
@@ -1764,8 +1860,9 @@ function convertPocketBaseField(pbField) {
|
|
|
1764
1860
|
}
|
|
1765
1861
|
function convertPocketBaseCollection(pbCollection) {
|
|
1766
1862
|
const fields = [];
|
|
1863
|
+
const isView = pbCollection.type === "view";
|
|
1767
1864
|
const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
|
|
1768
|
-
if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1865
|
+
if (!isView && pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1769
1866
|
for (const pbField of pbCollection.fields) {
|
|
1770
1867
|
if (pbField.system || systemFieldNames.includes(pbField.name)) {
|
|
1771
1868
|
const isAuthSystemField = pbCollection.type === "auth" && ["email", "emailVisibility", "verified", "password", "tokenKey"].includes(pbField.name);
|
|
@@ -1785,6 +1882,9 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
1785
1882
|
if (pbCollection.id) {
|
|
1786
1883
|
schema.id = pbCollection.id;
|
|
1787
1884
|
}
|
|
1885
|
+
if (typeof pbCollection.viewQuery === "string") {
|
|
1886
|
+
schema.viewQuery = dedentSql(pbCollection.viewQuery);
|
|
1887
|
+
}
|
|
1788
1888
|
if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
|
|
1789
1889
|
schema.indexes = pbCollection.indexes;
|
|
1790
1890
|
}
|
|
@@ -1886,6 +1986,24 @@ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
|
|
|
1886
1986
|
return [];
|
|
1887
1987
|
}
|
|
1888
1988
|
}
|
|
1989
|
+
function skipTemplateLiteral(content, start) {
|
|
1990
|
+
let i = start + 1;
|
|
1991
|
+
while (i < content.length) {
|
|
1992
|
+
const char = content[i];
|
|
1993
|
+
if (char === "\\") {
|
|
1994
|
+
i += 2;
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
if (char === "`") {
|
|
1998
|
+
return i + 1;
|
|
1999
|
+
}
|
|
2000
|
+
i++;
|
|
2001
|
+
}
|
|
2002
|
+
return i;
|
|
2003
|
+
}
|
|
2004
|
+
function unescapeTemplateLiteral(raw) {
|
|
2005
|
+
return raw.replace(/\\(`|\$\{|\\)/g, "$1");
|
|
2006
|
+
}
|
|
1889
2007
|
function parseMigrationOperationsFromContent(content) {
|
|
1890
2008
|
const collectionsToCreate = [];
|
|
1891
2009
|
const collectionsToDelete = [];
|
|
@@ -1974,6 +2092,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
1974
2092
|
while (i < content.length && bCount > 0) {
|
|
1975
2093
|
const char = content[i];
|
|
1976
2094
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2095
|
+
if (!inStr && char === "`") {
|
|
2096
|
+
i = skipTemplateLiteral(content, i);
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
1977
2099
|
if (!inStr && (char === '"' || char === "'")) {
|
|
1978
2100
|
inStr = true;
|
|
1979
2101
|
strChar = char;
|
|
@@ -2163,6 +2285,27 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2163
2285
|
}
|
|
2164
2286
|
}
|
|
2165
2287
|
}
|
|
2288
|
+
const viewQueryRegex = /(\w+)\.viewQuery\s*=\s*/g;
|
|
2289
|
+
let viewQueryMatch;
|
|
2290
|
+
while ((viewQueryMatch = viewQueryRegex.exec(content)) !== null) {
|
|
2291
|
+
const varInfo = variables.get(viewQueryMatch[1]);
|
|
2292
|
+
if (!varInfo || varInfo.type !== "collection") continue;
|
|
2293
|
+
const valueStart = viewQueryMatch.index + viewQueryMatch[0].length;
|
|
2294
|
+
if (content[valueStart] === "`") {
|
|
2295
|
+
const end = skipTemplateLiteral(content, valueStart);
|
|
2296
|
+
const raw = content.substring(valueStart + 1, end - 1);
|
|
2297
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2298
|
+
viewQueryRegex.lastIndex = end;
|
|
2299
|
+
} else {
|
|
2300
|
+
const terminator = content.indexOf(";", valueStart);
|
|
2301
|
+
const valueStr = content.substring(valueStart, terminator === -1 ? content.length : terminator);
|
|
2302
|
+
try {
|
|
2303
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(new Function("app", `return ${valueStr}`)(mockApp));
|
|
2304
|
+
} catch {
|
|
2305
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(valueStr.trim());
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2166
2309
|
const unmarshalRegex = /unmarshal\s*\(/g;
|
|
2167
2310
|
let unmarshalMatch;
|
|
2168
2311
|
while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
|
|
@@ -2177,6 +2320,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2177
2320
|
while (i < content.length && braceCount > 0) {
|
|
2178
2321
|
const ch = content[i];
|
|
2179
2322
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2323
|
+
if (!inStr && ch === "`") {
|
|
2324
|
+
i = skipTemplateLiteral(content, i);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2180
2327
|
if (!inStr && (ch === '"' || ch === "'")) {
|
|
2181
2328
|
inStr = true;
|
|
2182
2329
|
strChar = ch;
|
|
@@ -2210,6 +2357,22 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2210
2357
|
getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
|
|
2211
2358
|
}
|
|
2212
2359
|
}
|
|
2360
|
+
const viewQueryKey = objStr.match(/"viewQuery"\s*:\s*/);
|
|
2361
|
+
if (viewQueryKey) {
|
|
2362
|
+
const valueStart = viewQueryKey.index + viewQueryKey[0].length;
|
|
2363
|
+
if (objStr[valueStart] === "`") {
|
|
2364
|
+
const end = skipTemplateLiteral(objStr, valueStart);
|
|
2365
|
+
const raw = objStr.substring(valueStart + 1, end - 1);
|
|
2366
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2367
|
+
} else {
|
|
2368
|
+
const valStr = objStr.substring(valueStart).replace(/[,}\s]+$/, "");
|
|
2369
|
+
try {
|
|
2370
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(new Function("app", `return ${valStr}`)(mockApp));
|
|
2371
|
+
} catch {
|
|
2372
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(valStr);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2213
2376
|
}
|
|
2214
2377
|
const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
|
|
2215
2378
|
let match;
|
|
@@ -2340,6 +2503,10 @@ function parseMigrationOperations(migrationContent) {
|
|
|
2340
2503
|
while (i < migrationContent.length && braceCount > 0) {
|
|
2341
2504
|
const char = migrationContent[i];
|
|
2342
2505
|
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2506
|
+
if (!inString && char === "`") {
|
|
2507
|
+
i = skipTemplateLiteral(migrationContent, i);
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2343
2510
|
if (!inString && (char === '"' || char === "'")) {
|
|
2344
2511
|
inString = true;
|
|
2345
2512
|
stringChar = char;
|
|
@@ -2447,6 +2614,9 @@ function applyMigrationOperations(snapshot, operations) {
|
|
|
2447
2614
|
collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
|
|
2448
2615
|
}
|
|
2449
2616
|
}
|
|
2617
|
+
if (update.viewQuery !== void 0) {
|
|
2618
|
+
collection.viewQuery = update.viewQuery;
|
|
2619
|
+
}
|
|
2450
2620
|
if (Object.keys(update.rulesToUpdate).length > 0) {
|
|
2451
2621
|
if (!collection.rules) collection.rules = {};
|
|
2452
2622
|
if (!collection.permissions) collection.permissions = {};
|
|
@@ -2910,7 +3080,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
|
|
|
2910
3080
|
fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
|
|
2911
3081
|
return { fieldsToAdd, fieldsToRemove, fieldsToModify };
|
|
2912
3082
|
}
|
|
3083
|
+
function normalizeSql(query) {
|
|
3084
|
+
if (!query) {
|
|
3085
|
+
return "";
|
|
3086
|
+
}
|
|
3087
|
+
return query.split("\n").map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim();
|
|
3088
|
+
}
|
|
3089
|
+
function compareViewQuery(currentCollection, previousCollection) {
|
|
3090
|
+
const newValue = currentCollection.viewQuery;
|
|
3091
|
+
if (typeof newValue !== "string") {
|
|
3092
|
+
return void 0;
|
|
3093
|
+
}
|
|
3094
|
+
if (normalizeSql(newValue) === normalizeSql(previousCollection.viewQuery)) {
|
|
3095
|
+
return void 0;
|
|
3096
|
+
}
|
|
3097
|
+
return {
|
|
3098
|
+
oldValue: previousCollection.viewQuery ?? null,
|
|
3099
|
+
newValue
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
2913
3102
|
function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
|
|
3103
|
+
const isView = currentCollection.type === "view" || previousCollection.type === "view";
|
|
3104
|
+
if (isView) {
|
|
3105
|
+
return {
|
|
3106
|
+
collection: currentCollection.name,
|
|
3107
|
+
fieldsToAdd: [],
|
|
3108
|
+
fieldsToRemove: [],
|
|
3109
|
+
fieldsToModify: [],
|
|
3110
|
+
indexesToAdd: [],
|
|
3111
|
+
indexesToRemove: [],
|
|
3112
|
+
rulesToUpdate: compareRules(
|
|
3113
|
+
currentCollection.rules,
|
|
3114
|
+
previousCollection.rules,
|
|
3115
|
+
currentCollection.permissions,
|
|
3116
|
+
previousCollection.permissions
|
|
3117
|
+
),
|
|
3118
|
+
permissionsToUpdate: comparePermissions(currentCollection.permissions, previousCollection.permissions),
|
|
3119
|
+
viewQueryUpdate: compareViewQuery(currentCollection, previousCollection)
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
2914
3122
|
const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
|
|
2915
3123
|
currentCollection,
|
|
2916
3124
|
previousCollection,
|
|
@@ -2942,7 +3150,11 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2942
3150
|
const destructive = [];
|
|
2943
3151
|
const nonDestructive = [];
|
|
2944
3152
|
for (const collection of diff.collectionsToDelete) {
|
|
2945
|
-
|
|
3153
|
+
if (collection.type === "view") {
|
|
3154
|
+
nonDestructive.push(`Delete view collection: ${collection.name}`);
|
|
3155
|
+
} else {
|
|
3156
|
+
destructive.push(`Delete collection: ${collection.name}`);
|
|
3157
|
+
}
|
|
2946
3158
|
}
|
|
2947
3159
|
for (const collection of diff.collectionsToCreate) {
|
|
2948
3160
|
nonDestructive.push(`Create collection: ${collection.name}`);
|
|
@@ -2977,6 +3189,9 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2977
3189
|
for (const rule of modification.rulesToUpdate) {
|
|
2978
3190
|
nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
|
|
2979
3191
|
}
|
|
3192
|
+
if (modification.viewQueryUpdate) {
|
|
3193
|
+
nonDestructive.push(`Update view query: ${collectionName}`);
|
|
3194
|
+
}
|
|
2980
3195
|
}
|
|
2981
3196
|
return { destructive, nonDestructive };
|
|
2982
3197
|
}
|
|
@@ -3005,12 +3220,11 @@ function filterDiff(diff, options) {
|
|
|
3005
3220
|
});
|
|
3006
3221
|
let collectionsToDelete = diff.collectionsToDelete;
|
|
3007
3222
|
if (skipDestructive) {
|
|
3008
|
-
collectionsToDelete =
|
|
3009
|
-
} else {
|
|
3010
|
-
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
3011
|
-
return matchesPattern(col.name, patterns);
|
|
3012
|
-
});
|
|
3223
|
+
collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
|
|
3013
3224
|
}
|
|
3225
|
+
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
3226
|
+
return matchesPattern(col.name, patterns);
|
|
3227
|
+
});
|
|
3014
3228
|
const collectionsToModify = diff.collectionsToModify.map((mod) => {
|
|
3015
3229
|
const collectionMatches = matchesPattern(mod.collection, patterns);
|
|
3016
3230
|
const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
|
|
@@ -3035,6 +3249,7 @@ function filterDiff(diff, options) {
|
|
|
3035
3249
|
const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
|
|
3036
3250
|
const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
|
|
3037
3251
|
const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
|
|
3252
|
+
const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
|
|
3038
3253
|
return {
|
|
3039
3254
|
...mod,
|
|
3040
3255
|
fieldsToAdd,
|
|
@@ -3043,10 +3258,11 @@ function filterDiff(diff, options) {
|
|
|
3043
3258
|
indexesToAdd,
|
|
3044
3259
|
indexesToRemove,
|
|
3045
3260
|
rulesToUpdate,
|
|
3046
|
-
permissionsToUpdate
|
|
3261
|
+
permissionsToUpdate,
|
|
3262
|
+
viewQueryUpdate
|
|
3047
3263
|
};
|
|
3048
3264
|
}).filter((mod) => {
|
|
3049
|
-
return mod.fieldsToAdd.length > 0 || mod.fieldsToRemove.length > 0 || mod.fieldsToModify.length > 0 || mod.indexesToAdd.length > 0 || mod.indexesToRemove.length > 0 || mod.rulesToUpdate.length > 0 || mod.permissionsToUpdate.length > 0;
|
|
3265
|
+
return mod.fieldsToAdd.length > 0 || mod.fieldsToRemove.length > 0 || mod.fieldsToModify.length > 0 || mod.indexesToAdd.length > 0 || mod.indexesToRemove.length > 0 || mod.rulesToUpdate.length > 0 || mod.permissionsToUpdate.length > 0 || mod.viewQueryUpdate !== void 0;
|
|
3050
3266
|
});
|
|
3051
3267
|
return {
|
|
3052
3268
|
...diff,
|
|
@@ -3058,7 +3274,7 @@ function filterDiff(diff, options) {
|
|
|
3058
3274
|
|
|
3059
3275
|
// src/migration/diff/index.ts
|
|
3060
3276
|
function hasChanges(modification) {
|
|
3061
|
-
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;
|
|
3277
|
+
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 || modification.viewQueryUpdate !== void 0;
|
|
3062
3278
|
}
|
|
3063
3279
|
function aggregateChanges(currentSchema, previousSnapshot, config) {
|
|
3064
3280
|
const collectionIdToName = /* @__PURE__ */ new Map();
|
|
@@ -3237,6 +3453,13 @@ function formatValue(value) {
|
|
|
3237
3453
|
}
|
|
3238
3454
|
return String(value);
|
|
3239
3455
|
}
|
|
3456
|
+
function formatSqlTemplate(query, indent = " ") {
|
|
3457
|
+
const escaped = query.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
3458
|
+
const body = escaped.split("\n").map((line) => line.trim() === "" ? "" : `${indent}${line}`).join("\n");
|
|
3459
|
+
return `\`
|
|
3460
|
+
${body}
|
|
3461
|
+
${indent.slice(0, -2)}\``;
|
|
3462
|
+
}
|
|
3240
3463
|
function getFieldConstructorName(fieldType) {
|
|
3241
3464
|
const constructorMap = {
|
|
3242
3465
|
text: "TextField",
|
|
@@ -3597,6 +3820,15 @@ function generateCollectionRules(rules, collectionType = "base") {
|
|
|
3597
3820
|
if (!rules) {
|
|
3598
3821
|
return "";
|
|
3599
3822
|
}
|
|
3823
|
+
if (collectionType === "view") {
|
|
3824
|
+
return [
|
|
3825
|
+
`"listRule": ${formatValue(rules.listRule ?? null)}`,
|
|
3826
|
+
`"viewRule": ${formatValue(rules.viewRule ?? null)}`,
|
|
3827
|
+
`"createRule": null`,
|
|
3828
|
+
`"updateRule": null`,
|
|
3829
|
+
`"deleteRule": null`
|
|
3830
|
+
].join(",\n ");
|
|
3831
|
+
}
|
|
3600
3832
|
const parts = [];
|
|
3601
3833
|
if (rules.listRule !== void 0) {
|
|
3602
3834
|
parts.push(`"listRule": ${formatValue(rules.listRule)}`);
|
|
@@ -3622,6 +3854,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
|
|
|
3622
3854
|
if (!permissions) {
|
|
3623
3855
|
return "";
|
|
3624
3856
|
}
|
|
3857
|
+
if (collectionType === "view") {
|
|
3858
|
+
return [
|
|
3859
|
+
`"listRule": ${formatValue(permissions.listRule ?? null)}`,
|
|
3860
|
+
`"viewRule": ${formatValue(permissions.viewRule ?? null)}`,
|
|
3861
|
+
`"createRule": null`,
|
|
3862
|
+
`"updateRule": null`,
|
|
3863
|
+
`"deleteRule": null`
|
|
3864
|
+
].join(",\n ");
|
|
3865
|
+
}
|
|
3625
3866
|
const parts = [];
|
|
3626
3867
|
if (permissions.listRule !== void 0) {
|
|
3627
3868
|
parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
|
|
@@ -3659,6 +3900,16 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
|
|
|
3659
3900
|
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3660
3901
|
return lines.join("\n");
|
|
3661
3902
|
}
|
|
3903
|
+
function generateViewQueryUpdate(collectionName, viewQuery, varName, isLast = false, collectionIdMap) {
|
|
3904
|
+
const lines = [];
|
|
3905
|
+
const collectionVar = varName || `collection_${collectionName}_viewQuery`;
|
|
3906
|
+
lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
|
|
3907
|
+
lines.push(` unmarshal({`);
|
|
3908
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(viewQuery, " ")},`);
|
|
3909
|
+
lines.push(` }, ${collectionVar})`);
|
|
3910
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3911
|
+
return lines.join("\n");
|
|
3912
|
+
}
|
|
3662
3913
|
function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
|
|
3663
3914
|
const collectionVar = `collection_${collectionName}_${varSuffix}`;
|
|
3664
3915
|
const lines = [];
|
|
@@ -3689,6 +3940,13 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
|
|
|
3689
3940
|
} else if (rulesCode) {
|
|
3690
3941
|
lines.push(` ${rulesCode},`);
|
|
3691
3942
|
}
|
|
3943
|
+
if (collection.type === "view") {
|
|
3944
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(collection.viewQuery ?? "")},`);
|
|
3945
|
+
lines.push(` });`);
|
|
3946
|
+
lines.push(``);
|
|
3947
|
+
lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
|
|
3948
|
+
return lines.join("\n");
|
|
3949
|
+
}
|
|
3692
3950
|
const systemFieldNames = ["created", "updated", "id"];
|
|
3693
3951
|
if (collection.type === "auth") {
|
|
3694
3952
|
systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
|
|
@@ -3729,7 +3987,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
|
|
|
3729
3987
|
const modification = operation.modifications;
|
|
3730
3988
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3731
3989
|
let operationCount = 0;
|
|
3732
|
-
const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
|
|
3990
|
+
const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.viewQueryUpdate ? 1 : 0) + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
|
|
3991
|
+
if (modification.viewQueryUpdate) {
|
|
3992
|
+
operationCount++;
|
|
3993
|
+
const isLast = operationCount === totalOperations;
|
|
3994
|
+
lines.push(
|
|
3995
|
+
generateViewQueryUpdate(
|
|
3996
|
+
collectionName,
|
|
3997
|
+
modification.viewQueryUpdate.newValue,
|
|
3998
|
+
void 0,
|
|
3999
|
+
isLast,
|
|
4000
|
+
collectionIdMap
|
|
4001
|
+
)
|
|
4002
|
+
);
|
|
4003
|
+
if (!isLast) lines.push("");
|
|
4004
|
+
}
|
|
3733
4005
|
for (let i = 0; i < modification.fieldsToAdd.length; i++) {
|
|
3734
4006
|
const field = modification.fieldsToAdd[i];
|
|
3735
4007
|
operationCount++;
|
|
@@ -3830,7 +4102,21 @@ function generateOperationDownMigration(operation, collectionIdMap) {
|
|
|
3830
4102
|
const modification = operation.modifications;
|
|
3831
4103
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3832
4104
|
let operationCount = 0;
|
|
3833
|
-
const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
|
|
4105
|
+
const totalOperations = modification.fieldsToAdd.length + modification.fieldsToModify.length + modification.fieldsToRemove.length + modification.indexesToAdd.length + modification.indexesToRemove.length + (modification.viewQueryUpdate ? 1 : 0) + (modification.permissionsToUpdate.length > 0 ? 1 : modification.rulesToUpdate.length > 0 ? 1 : 0);
|
|
4106
|
+
if (modification.viewQueryUpdate) {
|
|
4107
|
+
operationCount++;
|
|
4108
|
+
const isLast = operationCount === totalOperations;
|
|
4109
|
+
lines.push(
|
|
4110
|
+
generateViewQueryUpdate(
|
|
4111
|
+
collectionName,
|
|
4112
|
+
modification.viewQueryUpdate.oldValue ?? "",
|
|
4113
|
+
`collection_${collectionName}_revert_viewQuery`,
|
|
4114
|
+
isLast,
|
|
4115
|
+
collectionIdMap
|
|
4116
|
+
)
|
|
4117
|
+
);
|
|
4118
|
+
if (!isLast) lines.push("");
|
|
4119
|
+
}
|
|
3834
4120
|
if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
|
|
3835
4121
|
operationCount++;
|
|
3836
4122
|
const isLast = operationCount === totalOperations;
|
|
@@ -4103,6 +4389,9 @@ function generate(diff, config) {
|
|
|
4103
4389
|
function detectCollectionDeletions(diff) {
|
|
4104
4390
|
const changes = [];
|
|
4105
4391
|
for (const collection of diff.collectionsToDelete) {
|
|
4392
|
+
if (collection.type === "view") {
|
|
4393
|
+
continue;
|
|
4394
|
+
}
|
|
4106
4395
|
changes.push({
|
|
4107
4396
|
type: "collection_deletion" /* COLLECTION_DELETION */,
|
|
4108
4397
|
description: `Delete collection: ${collection.name}`,
|
|
@@ -4516,7 +4805,11 @@ function formatChangeSummary(diff) {
|
|
|
4516
4805
|
lines.push(chalk__default.default.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
|
|
4517
4806
|
for (const collection of diff.collectionsToCreate) {
|
|
4518
4807
|
lines.push(chalk__default.default.green(` + ${collection.name} (${collection.type})`));
|
|
4519
|
-
|
|
4808
|
+
if (collection.type === "view") {
|
|
4809
|
+
lines.push(chalk__default.default.gray(` fields derived from the view query`));
|
|
4810
|
+
} else {
|
|
4811
|
+
lines.push(chalk__default.default.gray(` ${collection.fields.length} field(s)`));
|
|
4812
|
+
}
|
|
4520
4813
|
}
|
|
4521
4814
|
lines.push("");
|
|
4522
4815
|
}
|
|
@@ -4561,6 +4854,9 @@ function formatChangeSummary(diff) {
|
|
|
4561
4854
|
if (modification.rulesToUpdate.length > 0) {
|
|
4562
4855
|
lines.push(chalk__default.default.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
|
|
4563
4856
|
}
|
|
4857
|
+
if (modification.viewQueryUpdate) {
|
|
4858
|
+
lines.push(chalk__default.default.yellow(` ~ view query to update`));
|
|
4859
|
+
}
|
|
4564
4860
|
lines.push("");
|
|
4565
4861
|
}
|
|
4566
4862
|
}
|