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/dist/cli/migrate.cjs
CHANGED
|
@@ -1225,6 +1225,60 @@ function unwrapZodType(zodType) {
|
|
|
1225
1225
|
}
|
|
1226
1226
|
return unwrapped;
|
|
1227
1227
|
}
|
|
1228
|
+
function dedentSql(value) {
|
|
1229
|
+
const lines = value.replace(/\r\n/g, "\n").split("\n");
|
|
1230
|
+
while (lines.length > 0 && lines[0].trim() === "") {
|
|
1231
|
+
lines.shift();
|
|
1232
|
+
}
|
|
1233
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
1234
|
+
lines.pop();
|
|
1235
|
+
}
|
|
1236
|
+
if (lines.length === 0) {
|
|
1237
|
+
return "";
|
|
1238
|
+
}
|
|
1239
|
+
let minIndent = Infinity;
|
|
1240
|
+
for (const line of lines) {
|
|
1241
|
+
if (line.trim() === "") continue;
|
|
1242
|
+
const indent = line.length - line.trimStart().length;
|
|
1243
|
+
if (indent < minIndent) {
|
|
1244
|
+
minIndent = indent;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (!Number.isFinite(minIndent) || minIndent === 0) {
|
|
1248
|
+
return lines.map((line) => line.trimEnd()).join("\n");
|
|
1249
|
+
}
|
|
1250
|
+
return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
|
|
1251
|
+
}
|
|
1252
|
+
function stripLeadingComments(query) {
|
|
1253
|
+
let remaining = query.trim();
|
|
1254
|
+
while (remaining.length > 0) {
|
|
1255
|
+
if (remaining.startsWith("--")) {
|
|
1256
|
+
const newline = remaining.indexOf("\n");
|
|
1257
|
+
remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (remaining.startsWith("/*")) {
|
|
1261
|
+
const end = remaining.indexOf("*/");
|
|
1262
|
+
remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
return remaining;
|
|
1268
|
+
}
|
|
1269
|
+
function validateViewQuery(collectionName, viewQuery) {
|
|
1270
|
+
if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
`View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const body = stripLeadingComments(viewQuery);
|
|
1276
|
+
if (!/^(select|with)\b/i.test(body)) {
|
|
1277
|
+
throw new Error(
|
|
1278
|
+
`View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1228
1282
|
function getCollectionNameFromFile(filePath) {
|
|
1229
1283
|
const filename = path8__namespace.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1230
1284
|
return toCollectionName(filename);
|
|
@@ -1248,13 +1302,26 @@ function extractCollectionTypeFromSchema(zodSchema) {
|
|
|
1248
1302
|
}
|
|
1249
1303
|
try {
|
|
1250
1304
|
const metadata = JSON.parse(zodSchema.description);
|
|
1251
|
-
if (metadata.type === "base" || metadata.type === "auth") {
|
|
1305
|
+
if (metadata.type === "base" || metadata.type === "auth" || metadata.type === "view") {
|
|
1252
1306
|
return metadata.type;
|
|
1253
1307
|
}
|
|
1254
1308
|
} catch {
|
|
1255
1309
|
}
|
|
1256
1310
|
return null;
|
|
1257
1311
|
}
|
|
1312
|
+
function extractViewQueryFromSchema(zodSchema) {
|
|
1313
|
+
if (!zodSchema.description) {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
const metadata = JSON.parse(zodSchema.description);
|
|
1318
|
+
if (typeof metadata.viewQuery === "string") {
|
|
1319
|
+
return metadata.viewQuery;
|
|
1320
|
+
}
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1258
1325
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema", "Collection"]) {
|
|
1259
1326
|
const result = {};
|
|
1260
1327
|
if (module.default instanceof zod.z.ZodObject) {
|
|
@@ -1413,6 +1480,15 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1413
1480
|
const rawFields = extractFieldDefinitions(zodSchema);
|
|
1414
1481
|
const explicitType = extractCollectionTypeFromSchema(zodSchema);
|
|
1415
1482
|
const collectionType = explicitType ?? (isAuthCollection(rawFields) ? "auth" : "base");
|
|
1483
|
+
const isView = collectionType === "view";
|
|
1484
|
+
const viewQuery = extractViewQueryFromSchema(zodSchema);
|
|
1485
|
+
if (isView) {
|
|
1486
|
+
validateViewQuery(collectionName, viewQuery);
|
|
1487
|
+
} else if (viewQuery !== null) {
|
|
1488
|
+
console.warn(
|
|
1489
|
+
`[${collectionName}] viewQuery is only used by view collections and will be ignored. Use defineView() or set type: "view" to define a view collection.`
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1416
1492
|
const fields = rawFields.filter((f) => !["created", "updated"].includes(f.name)).map(({ name, zodType }) => buildFieldDefinition(name, zodType));
|
|
1417
1493
|
if (collectionType === "auth") {
|
|
1418
1494
|
const authSystemFields = [
|
|
@@ -1472,26 +1548,43 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1472
1548
|
}
|
|
1473
1549
|
}
|
|
1474
1550
|
const indexes = extractIndexes(zodSchema) || [];
|
|
1551
|
+
if (isView && indexes.length > 0) {
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
`View collection "${collectionName}" cannot declare indexes. PocketBase view collections are backed by a SQL query and do not support indexes.`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1475
1556
|
const permissionAnalyzer = new PermissionAnalyzer();
|
|
1476
1557
|
let permissions = void 0;
|
|
1477
1558
|
const schemaDescription = zodSchema.description;
|
|
1478
1559
|
const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
|
|
1479
1560
|
if (extractedPermissions) {
|
|
1480
1561
|
const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1490
|
-
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1562
|
+
if (isView) {
|
|
1563
|
+
for (const ruleType of ["createRule", "updateRule", "deleteRule", "manageRule"]) {
|
|
1564
|
+
if (resolvedPermissions[ruleType]) {
|
|
1565
|
+
console.warn(
|
|
1566
|
+
`[${collectionName}] ${ruleType} is not supported on view collections and will be set to null.`
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
resolvedPermissions[ruleType] = null;
|
|
1491
1570
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1571
|
+
}
|
|
1572
|
+
if (!isView) {
|
|
1573
|
+
const validationResults = permissionAnalyzer.validatePermissions(
|
|
1574
|
+
collectionName,
|
|
1575
|
+
resolvedPermissions,
|
|
1576
|
+
fields,
|
|
1577
|
+
collectionType === "auth"
|
|
1578
|
+
);
|
|
1579
|
+
for (const [ruleType, result] of validationResults) {
|
|
1580
|
+
if (!result.valid) {
|
|
1581
|
+
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1582
|
+
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1583
|
+
}
|
|
1584
|
+
if (result.warnings.length > 0) {
|
|
1585
|
+
console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
|
|
1586
|
+
result.warnings.forEach((warning) => console.warn(` - ${warning}`));
|
|
1587
|
+
}
|
|
1495
1588
|
}
|
|
1496
1589
|
}
|
|
1497
1590
|
permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
|
|
@@ -1512,6 +1605,9 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1512
1605
|
},
|
|
1513
1606
|
permissions
|
|
1514
1607
|
};
|
|
1608
|
+
if (isView) {
|
|
1609
|
+
collectionSchema.viewQuery = viewQuery;
|
|
1610
|
+
}
|
|
1515
1611
|
return collectionSchema;
|
|
1516
1612
|
}
|
|
1517
1613
|
var tsxLoaderRegistered = false;
|
|
@@ -1769,8 +1865,9 @@ function convertPocketBaseField(pbField) {
|
|
|
1769
1865
|
}
|
|
1770
1866
|
function convertPocketBaseCollection(pbCollection) {
|
|
1771
1867
|
const fields = [];
|
|
1868
|
+
const isView = pbCollection.type === "view";
|
|
1772
1869
|
const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
|
|
1773
|
-
if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1870
|
+
if (!isView && pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1774
1871
|
for (const pbField of pbCollection.fields) {
|
|
1775
1872
|
if (pbField.system || systemFieldNames.includes(pbField.name)) {
|
|
1776
1873
|
const isAuthSystemField = pbCollection.type === "auth" && ["email", "emailVisibility", "verified", "password", "tokenKey"].includes(pbField.name);
|
|
@@ -1790,6 +1887,9 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
1790
1887
|
if (pbCollection.id) {
|
|
1791
1888
|
schema.id = pbCollection.id;
|
|
1792
1889
|
}
|
|
1890
|
+
if (typeof pbCollection.viewQuery === "string") {
|
|
1891
|
+
schema.viewQuery = dedentSql(pbCollection.viewQuery);
|
|
1892
|
+
}
|
|
1793
1893
|
if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
|
|
1794
1894
|
schema.indexes = pbCollection.indexes;
|
|
1795
1895
|
}
|
|
@@ -1891,6 +1991,24 @@ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
|
|
|
1891
1991
|
return [];
|
|
1892
1992
|
}
|
|
1893
1993
|
}
|
|
1994
|
+
function skipTemplateLiteral(content, start) {
|
|
1995
|
+
let i = start + 1;
|
|
1996
|
+
while (i < content.length) {
|
|
1997
|
+
const char = content[i];
|
|
1998
|
+
if (char === "\\") {
|
|
1999
|
+
i += 2;
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
if (char === "`") {
|
|
2003
|
+
return i + 1;
|
|
2004
|
+
}
|
|
2005
|
+
i++;
|
|
2006
|
+
}
|
|
2007
|
+
return i;
|
|
2008
|
+
}
|
|
2009
|
+
function unescapeTemplateLiteral(raw) {
|
|
2010
|
+
return raw.replace(/\\(`|\$\{|\\)/g, "$1");
|
|
2011
|
+
}
|
|
1894
2012
|
function parseMigrationOperationsFromContent(content) {
|
|
1895
2013
|
const collectionsToCreate = [];
|
|
1896
2014
|
const collectionsToDelete = [];
|
|
@@ -1979,6 +2097,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
1979
2097
|
while (i < content.length && bCount > 0) {
|
|
1980
2098
|
const char = content[i];
|
|
1981
2099
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2100
|
+
if (!inStr && char === "`") {
|
|
2101
|
+
i = skipTemplateLiteral(content, i);
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
1982
2104
|
if (!inStr && (char === '"' || char === "'")) {
|
|
1983
2105
|
inStr = true;
|
|
1984
2106
|
strChar = char;
|
|
@@ -2168,6 +2290,27 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2168
2290
|
}
|
|
2169
2291
|
}
|
|
2170
2292
|
}
|
|
2293
|
+
const viewQueryRegex = /(\w+)\.viewQuery\s*=\s*/g;
|
|
2294
|
+
let viewQueryMatch;
|
|
2295
|
+
while ((viewQueryMatch = viewQueryRegex.exec(content)) !== null) {
|
|
2296
|
+
const varInfo = variables.get(viewQueryMatch[1]);
|
|
2297
|
+
if (!varInfo || varInfo.type !== "collection") continue;
|
|
2298
|
+
const valueStart = viewQueryMatch.index + viewQueryMatch[0].length;
|
|
2299
|
+
if (content[valueStart] === "`") {
|
|
2300
|
+
const end = skipTemplateLiteral(content, valueStart);
|
|
2301
|
+
const raw = content.substring(valueStart + 1, end - 1);
|
|
2302
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2303
|
+
viewQueryRegex.lastIndex = end;
|
|
2304
|
+
} else {
|
|
2305
|
+
const terminator = content.indexOf(";", valueStart);
|
|
2306
|
+
const valueStr = content.substring(valueStart, terminator === -1 ? content.length : terminator);
|
|
2307
|
+
try {
|
|
2308
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(new Function("app", `return ${valueStr}`)(mockApp));
|
|
2309
|
+
} catch {
|
|
2310
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(valueStr.trim());
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2171
2314
|
const unmarshalRegex = /unmarshal\s*\(/g;
|
|
2172
2315
|
let unmarshalMatch;
|
|
2173
2316
|
while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
|
|
@@ -2182,6 +2325,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2182
2325
|
while (i < content.length && braceCount > 0) {
|
|
2183
2326
|
const ch = content[i];
|
|
2184
2327
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2328
|
+
if (!inStr && ch === "`") {
|
|
2329
|
+
i = skipTemplateLiteral(content, i);
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2185
2332
|
if (!inStr && (ch === '"' || ch === "'")) {
|
|
2186
2333
|
inStr = true;
|
|
2187
2334
|
strChar = ch;
|
|
@@ -2215,6 +2362,22 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2215
2362
|
getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
|
|
2216
2363
|
}
|
|
2217
2364
|
}
|
|
2365
|
+
const viewQueryKey = objStr.match(/"viewQuery"\s*:\s*/);
|
|
2366
|
+
if (viewQueryKey) {
|
|
2367
|
+
const valueStart = viewQueryKey.index + viewQueryKey[0].length;
|
|
2368
|
+
if (objStr[valueStart] === "`") {
|
|
2369
|
+
const end = skipTemplateLiteral(objStr, valueStart);
|
|
2370
|
+
const raw = objStr.substring(valueStart + 1, end - 1);
|
|
2371
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2372
|
+
} else {
|
|
2373
|
+
const valStr = objStr.substring(valueStart).replace(/[,}\s]+$/, "");
|
|
2374
|
+
try {
|
|
2375
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(new Function("app", `return ${valStr}`)(mockApp));
|
|
2376
|
+
} catch {
|
|
2377
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(valStr);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2218
2381
|
}
|
|
2219
2382
|
const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
|
|
2220
2383
|
let match;
|
|
@@ -2345,6 +2508,10 @@ function parseMigrationOperations(migrationContent) {
|
|
|
2345
2508
|
while (i < migrationContent.length && braceCount > 0) {
|
|
2346
2509
|
const char = migrationContent[i];
|
|
2347
2510
|
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2511
|
+
if (!inString && char === "`") {
|
|
2512
|
+
i = skipTemplateLiteral(migrationContent, i);
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2348
2515
|
if (!inString && (char === '"' || char === "'")) {
|
|
2349
2516
|
inString = true;
|
|
2350
2517
|
stringChar = char;
|
|
@@ -2452,6 +2619,9 @@ function applyMigrationOperations(snapshot, operations) {
|
|
|
2452
2619
|
collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
|
|
2453
2620
|
}
|
|
2454
2621
|
}
|
|
2622
|
+
if (update.viewQuery !== void 0) {
|
|
2623
|
+
collection.viewQuery = update.viewQuery;
|
|
2624
|
+
}
|
|
2455
2625
|
if (Object.keys(update.rulesToUpdate).length > 0) {
|
|
2456
2626
|
if (!collection.rules) collection.rules = {};
|
|
2457
2627
|
if (!collection.permissions) collection.permissions = {};
|
|
@@ -2915,7 +3085,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
|
|
|
2915
3085
|
fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
|
|
2916
3086
|
return { fieldsToAdd, fieldsToRemove, fieldsToModify };
|
|
2917
3087
|
}
|
|
3088
|
+
function normalizeSql(query) {
|
|
3089
|
+
if (!query) {
|
|
3090
|
+
return "";
|
|
3091
|
+
}
|
|
3092
|
+
return query.split("\n").map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim();
|
|
3093
|
+
}
|
|
3094
|
+
function compareViewQuery(currentCollection, previousCollection) {
|
|
3095
|
+
const newValue = currentCollection.viewQuery;
|
|
3096
|
+
if (typeof newValue !== "string") {
|
|
3097
|
+
return void 0;
|
|
3098
|
+
}
|
|
3099
|
+
if (normalizeSql(newValue) === normalizeSql(previousCollection.viewQuery)) {
|
|
3100
|
+
return void 0;
|
|
3101
|
+
}
|
|
3102
|
+
return {
|
|
3103
|
+
oldValue: previousCollection.viewQuery ?? null,
|
|
3104
|
+
newValue
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
2918
3107
|
function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
|
|
3108
|
+
const isView = currentCollection.type === "view" || previousCollection.type === "view";
|
|
3109
|
+
if (isView) {
|
|
3110
|
+
return {
|
|
3111
|
+
collection: currentCollection.name,
|
|
3112
|
+
fieldsToAdd: [],
|
|
3113
|
+
fieldsToRemove: [],
|
|
3114
|
+
fieldsToModify: [],
|
|
3115
|
+
indexesToAdd: [],
|
|
3116
|
+
indexesToRemove: [],
|
|
3117
|
+
rulesToUpdate: compareRules(
|
|
3118
|
+
currentCollection.rules,
|
|
3119
|
+
previousCollection.rules,
|
|
3120
|
+
currentCollection.permissions,
|
|
3121
|
+
previousCollection.permissions
|
|
3122
|
+
),
|
|
3123
|
+
permissionsToUpdate: comparePermissions(currentCollection.permissions, previousCollection.permissions),
|
|
3124
|
+
viewQueryUpdate: compareViewQuery(currentCollection, previousCollection)
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
2919
3127
|
const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
|
|
2920
3128
|
currentCollection,
|
|
2921
3129
|
previousCollection,
|
|
@@ -2947,7 +3155,11 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2947
3155
|
const destructive = [];
|
|
2948
3156
|
const nonDestructive = [];
|
|
2949
3157
|
for (const collection of diff.collectionsToDelete) {
|
|
2950
|
-
|
|
3158
|
+
if (collection.type === "view") {
|
|
3159
|
+
nonDestructive.push(`Delete view collection: ${collection.name}`);
|
|
3160
|
+
} else {
|
|
3161
|
+
destructive.push(`Delete collection: ${collection.name}`);
|
|
3162
|
+
}
|
|
2951
3163
|
}
|
|
2952
3164
|
for (const collection of diff.collectionsToCreate) {
|
|
2953
3165
|
nonDestructive.push(`Create collection: ${collection.name}`);
|
|
@@ -2982,6 +3194,9 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2982
3194
|
for (const rule of modification.rulesToUpdate) {
|
|
2983
3195
|
nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
|
|
2984
3196
|
}
|
|
3197
|
+
if (modification.viewQueryUpdate) {
|
|
3198
|
+
nonDestructive.push(`Update view query: ${collectionName}`);
|
|
3199
|
+
}
|
|
2985
3200
|
}
|
|
2986
3201
|
return { destructive, nonDestructive };
|
|
2987
3202
|
}
|
|
@@ -3010,12 +3225,11 @@ function filterDiff(diff, options) {
|
|
|
3010
3225
|
});
|
|
3011
3226
|
let collectionsToDelete = diff.collectionsToDelete;
|
|
3012
3227
|
if (skipDestructive) {
|
|
3013
|
-
collectionsToDelete =
|
|
3014
|
-
} else {
|
|
3015
|
-
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
3016
|
-
return matchesPattern(col.name, patterns);
|
|
3017
|
-
});
|
|
3228
|
+
collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
|
|
3018
3229
|
}
|
|
3230
|
+
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
3231
|
+
return matchesPattern(col.name, patterns);
|
|
3232
|
+
});
|
|
3019
3233
|
const collectionsToModify = diff.collectionsToModify.map((mod) => {
|
|
3020
3234
|
const collectionMatches = matchesPattern(mod.collection, patterns);
|
|
3021
3235
|
const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
|
|
@@ -3040,6 +3254,7 @@ function filterDiff(diff, options) {
|
|
|
3040
3254
|
const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
|
|
3041
3255
|
const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
|
|
3042
3256
|
const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
|
|
3257
|
+
const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
|
|
3043
3258
|
return {
|
|
3044
3259
|
...mod,
|
|
3045
3260
|
fieldsToAdd,
|
|
@@ -3048,10 +3263,11 @@ function filterDiff(diff, options) {
|
|
|
3048
3263
|
indexesToAdd,
|
|
3049
3264
|
indexesToRemove,
|
|
3050
3265
|
rulesToUpdate,
|
|
3051
|
-
permissionsToUpdate
|
|
3266
|
+
permissionsToUpdate,
|
|
3267
|
+
viewQueryUpdate
|
|
3052
3268
|
};
|
|
3053
3269
|
}).filter((mod) => {
|
|
3054
|
-
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;
|
|
3270
|
+
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;
|
|
3055
3271
|
});
|
|
3056
3272
|
return {
|
|
3057
3273
|
...diff,
|
|
@@ -3063,7 +3279,7 @@ function filterDiff(diff, options) {
|
|
|
3063
3279
|
|
|
3064
3280
|
// src/migration/diff/index.ts
|
|
3065
3281
|
function hasChanges(modification) {
|
|
3066
|
-
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;
|
|
3282
|
+
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;
|
|
3067
3283
|
}
|
|
3068
3284
|
function aggregateChanges(currentSchema, previousSnapshot, config) {
|
|
3069
3285
|
const collectionIdToName = /* @__PURE__ */ new Map();
|
|
@@ -3242,6 +3458,13 @@ function formatValue(value) {
|
|
|
3242
3458
|
}
|
|
3243
3459
|
return String(value);
|
|
3244
3460
|
}
|
|
3461
|
+
function formatSqlTemplate(query, indent = " ") {
|
|
3462
|
+
const escaped = query.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
3463
|
+
const body = escaped.split("\n").map((line) => line.trim() === "" ? "" : `${indent}${line}`).join("\n");
|
|
3464
|
+
return `\`
|
|
3465
|
+
${body}
|
|
3466
|
+
${indent.slice(0, -2)}\``;
|
|
3467
|
+
}
|
|
3245
3468
|
function getFieldConstructorName(fieldType) {
|
|
3246
3469
|
const constructorMap = {
|
|
3247
3470
|
text: "TextField",
|
|
@@ -3602,6 +3825,15 @@ function generateCollectionRules(rules, collectionType = "base") {
|
|
|
3602
3825
|
if (!rules) {
|
|
3603
3826
|
return "";
|
|
3604
3827
|
}
|
|
3828
|
+
if (collectionType === "view") {
|
|
3829
|
+
return [
|
|
3830
|
+
`"listRule": ${formatValue(rules.listRule ?? null)}`,
|
|
3831
|
+
`"viewRule": ${formatValue(rules.viewRule ?? null)}`,
|
|
3832
|
+
`"createRule": null`,
|
|
3833
|
+
`"updateRule": null`,
|
|
3834
|
+
`"deleteRule": null`
|
|
3835
|
+
].join(",\n ");
|
|
3836
|
+
}
|
|
3605
3837
|
const parts = [];
|
|
3606
3838
|
if (rules.listRule !== void 0) {
|
|
3607
3839
|
parts.push(`"listRule": ${formatValue(rules.listRule)}`);
|
|
@@ -3627,6 +3859,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
|
|
|
3627
3859
|
if (!permissions) {
|
|
3628
3860
|
return "";
|
|
3629
3861
|
}
|
|
3862
|
+
if (collectionType === "view") {
|
|
3863
|
+
return [
|
|
3864
|
+
`"listRule": ${formatValue(permissions.listRule ?? null)}`,
|
|
3865
|
+
`"viewRule": ${formatValue(permissions.viewRule ?? null)}`,
|
|
3866
|
+
`"createRule": null`,
|
|
3867
|
+
`"updateRule": null`,
|
|
3868
|
+
`"deleteRule": null`
|
|
3869
|
+
].join(",\n ");
|
|
3870
|
+
}
|
|
3630
3871
|
const parts = [];
|
|
3631
3872
|
if (permissions.listRule !== void 0) {
|
|
3632
3873
|
parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
|
|
@@ -3664,6 +3905,16 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
|
|
|
3664
3905
|
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3665
3906
|
return lines.join("\n");
|
|
3666
3907
|
}
|
|
3908
|
+
function generateViewQueryUpdate(collectionName, viewQuery, varName, isLast = false, collectionIdMap) {
|
|
3909
|
+
const lines = [];
|
|
3910
|
+
const collectionVar = varName || `collection_${collectionName}_viewQuery`;
|
|
3911
|
+
lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
|
|
3912
|
+
lines.push(` unmarshal({`);
|
|
3913
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(viewQuery, " ")},`);
|
|
3914
|
+
lines.push(` }, ${collectionVar})`);
|
|
3915
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3916
|
+
return lines.join("\n");
|
|
3917
|
+
}
|
|
3667
3918
|
function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
|
|
3668
3919
|
const collectionVar = `collection_${collectionName}_${varSuffix}`;
|
|
3669
3920
|
const lines = [];
|
|
@@ -3694,6 +3945,13 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
|
|
|
3694
3945
|
} else if (rulesCode) {
|
|
3695
3946
|
lines.push(` ${rulesCode},`);
|
|
3696
3947
|
}
|
|
3948
|
+
if (collection.type === "view") {
|
|
3949
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(collection.viewQuery ?? "")},`);
|
|
3950
|
+
lines.push(` });`);
|
|
3951
|
+
lines.push(``);
|
|
3952
|
+
lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
|
|
3953
|
+
return lines.join("\n");
|
|
3954
|
+
}
|
|
3697
3955
|
const systemFieldNames = ["created", "updated", "id"];
|
|
3698
3956
|
if (collection.type === "auth") {
|
|
3699
3957
|
systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
|
|
@@ -3734,7 +3992,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
|
|
|
3734
3992
|
const modification = operation.modifications;
|
|
3735
3993
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3736
3994
|
let operationCount = 0;
|
|
3737
|
-
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);
|
|
3995
|
+
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);
|
|
3996
|
+
if (modification.viewQueryUpdate) {
|
|
3997
|
+
operationCount++;
|
|
3998
|
+
const isLast = operationCount === totalOperations;
|
|
3999
|
+
lines.push(
|
|
4000
|
+
generateViewQueryUpdate(
|
|
4001
|
+
collectionName,
|
|
4002
|
+
modification.viewQueryUpdate.newValue,
|
|
4003
|
+
void 0,
|
|
4004
|
+
isLast,
|
|
4005
|
+
collectionIdMap
|
|
4006
|
+
)
|
|
4007
|
+
);
|
|
4008
|
+
if (!isLast) lines.push("");
|
|
4009
|
+
}
|
|
3738
4010
|
for (let i = 0; i < modification.fieldsToAdd.length; i++) {
|
|
3739
4011
|
const field = modification.fieldsToAdd[i];
|
|
3740
4012
|
operationCount++;
|
|
@@ -3835,7 +4107,21 @@ function generateOperationDownMigration(operation, collectionIdMap) {
|
|
|
3835
4107
|
const modification = operation.modifications;
|
|
3836
4108
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3837
4109
|
let operationCount = 0;
|
|
3838
|
-
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);
|
|
4110
|
+
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);
|
|
4111
|
+
if (modification.viewQueryUpdate) {
|
|
4112
|
+
operationCount++;
|
|
4113
|
+
const isLast = operationCount === totalOperations;
|
|
4114
|
+
lines.push(
|
|
4115
|
+
generateViewQueryUpdate(
|
|
4116
|
+
collectionName,
|
|
4117
|
+
modification.viewQueryUpdate.oldValue ?? "",
|
|
4118
|
+
`collection_${collectionName}_revert_viewQuery`,
|
|
4119
|
+
isLast,
|
|
4120
|
+
collectionIdMap
|
|
4121
|
+
)
|
|
4122
|
+
);
|
|
4123
|
+
if (!isLast) lines.push("");
|
|
4124
|
+
}
|
|
3839
4125
|
if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
|
|
3840
4126
|
operationCount++;
|
|
3841
4127
|
const isLast = operationCount === totalOperations;
|
|
@@ -4108,6 +4394,9 @@ function generate(diff, config) {
|
|
|
4108
4394
|
function detectCollectionDeletions(diff) {
|
|
4109
4395
|
const changes = [];
|
|
4110
4396
|
for (const collection of diff.collectionsToDelete) {
|
|
4397
|
+
if (collection.type === "view") {
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4111
4400
|
changes.push({
|
|
4112
4401
|
type: "collection_deletion" /* COLLECTION_DELETION */,
|
|
4113
4402
|
description: `Delete collection: ${collection.name}`,
|
|
@@ -4518,7 +4807,11 @@ function formatChangeSummary(diff) {
|
|
|
4518
4807
|
lines.push(chalk__default.default.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
|
|
4519
4808
|
for (const collection of diff.collectionsToCreate) {
|
|
4520
4809
|
lines.push(chalk__default.default.green(` + ${collection.name} (${collection.type})`));
|
|
4521
|
-
|
|
4810
|
+
if (collection.type === "view") {
|
|
4811
|
+
lines.push(chalk__default.default.gray(` fields derived from the view query`));
|
|
4812
|
+
} else {
|
|
4813
|
+
lines.push(chalk__default.default.gray(` ${collection.fields.length} field(s)`));
|
|
4814
|
+
}
|
|
4522
4815
|
}
|
|
4523
4816
|
lines.push("");
|
|
4524
4817
|
}
|
|
@@ -4563,6 +4856,9 @@ function formatChangeSummary(diff) {
|
|
|
4563
4856
|
if (modification.rulesToUpdate.length > 0) {
|
|
4564
4857
|
lines.push(chalk__default.default.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
|
|
4565
4858
|
}
|
|
4859
|
+
if (modification.viewQueryUpdate) {
|
|
4860
|
+
lines.push(chalk__default.default.yellow(` ~ view query to update`));
|
|
4861
|
+
}
|
|
4566
4862
|
lines.push("");
|
|
4567
4863
|
}
|
|
4568
4864
|
}
|
|
@@ -4948,11 +5244,14 @@ var TypeGenerator = class {
|
|
|
4948
5244
|
}
|
|
4949
5245
|
generateCollectionType(collection) {
|
|
4950
5246
|
const typeName = this.toPascalCase(collection.name);
|
|
5247
|
+
const isView = collection.type === "view";
|
|
4951
5248
|
const lines = [];
|
|
4952
5249
|
lines.push(`export interface ${typeName}Record {`);
|
|
4953
5250
|
lines.push(` id: string;`);
|
|
4954
|
-
|
|
4955
|
-
|
|
5251
|
+
if (!isView) {
|
|
5252
|
+
lines.push(` created: string;`);
|
|
5253
|
+
lines.push(` updated: string;`);
|
|
5254
|
+
}
|
|
4956
5255
|
lines.push(` collectionId: string;`);
|
|
4957
5256
|
lines.push(` collectionName: "${collection.name}";`);
|
|
4958
5257
|
for (const field of collection.fields) {
|
|
@@ -4964,7 +5263,7 @@ var TypeGenerator = class {
|
|
|
4964
5263
|
lines.push(`}`);
|
|
4965
5264
|
lines.push(``);
|
|
4966
5265
|
const expandFields = [];
|
|
4967
|
-
for (const field of collection.fields) {
|
|
5266
|
+
for (const field of isView ? [] : collection.fields) {
|
|
4968
5267
|
if (field.type === "relation" && field.relation) {
|
|
4969
5268
|
const targetCollection = field.relation.collection;
|
|
4970
5269
|
let targetType = "RecordModel";
|