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.js
CHANGED
|
@@ -1196,6 +1196,60 @@ function unwrapZodType(zodType) {
|
|
|
1196
1196
|
}
|
|
1197
1197
|
return unwrapped;
|
|
1198
1198
|
}
|
|
1199
|
+
function dedentSql(value) {
|
|
1200
|
+
const lines = value.replace(/\r\n/g, "\n").split("\n");
|
|
1201
|
+
while (lines.length > 0 && lines[0].trim() === "") {
|
|
1202
|
+
lines.shift();
|
|
1203
|
+
}
|
|
1204
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
1205
|
+
lines.pop();
|
|
1206
|
+
}
|
|
1207
|
+
if (lines.length === 0) {
|
|
1208
|
+
return "";
|
|
1209
|
+
}
|
|
1210
|
+
let minIndent = Infinity;
|
|
1211
|
+
for (const line of lines) {
|
|
1212
|
+
if (line.trim() === "") continue;
|
|
1213
|
+
const indent = line.length - line.trimStart().length;
|
|
1214
|
+
if (indent < minIndent) {
|
|
1215
|
+
minIndent = indent;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (!Number.isFinite(minIndent) || minIndent === 0) {
|
|
1219
|
+
return lines.map((line) => line.trimEnd()).join("\n");
|
|
1220
|
+
}
|
|
1221
|
+
return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
|
|
1222
|
+
}
|
|
1223
|
+
function stripLeadingComments(query) {
|
|
1224
|
+
let remaining = query.trim();
|
|
1225
|
+
while (remaining.length > 0) {
|
|
1226
|
+
if (remaining.startsWith("--")) {
|
|
1227
|
+
const newline = remaining.indexOf("\n");
|
|
1228
|
+
remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (remaining.startsWith("/*")) {
|
|
1232
|
+
const end = remaining.indexOf("*/");
|
|
1233
|
+
remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
return remaining;
|
|
1239
|
+
}
|
|
1240
|
+
function validateViewQuery(collectionName, viewQuery) {
|
|
1241
|
+
if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
`View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
const body = stripLeadingComments(viewQuery);
|
|
1247
|
+
if (!/^(select|with)\b/i.test(body)) {
|
|
1248
|
+
throw new Error(
|
|
1249
|
+
`View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1199
1253
|
function getCollectionNameFromFile(filePath) {
|
|
1200
1254
|
const filename = path8.basename(filePath).replace(/\.(ts|js)$/, "");
|
|
1201
1255
|
return toCollectionName(filename);
|
|
@@ -1219,13 +1273,26 @@ function extractCollectionTypeFromSchema(zodSchema) {
|
|
|
1219
1273
|
}
|
|
1220
1274
|
try {
|
|
1221
1275
|
const metadata = JSON.parse(zodSchema.description);
|
|
1222
|
-
if (metadata.type === "base" || metadata.type === "auth") {
|
|
1276
|
+
if (metadata.type === "base" || metadata.type === "auth" || metadata.type === "view") {
|
|
1223
1277
|
return metadata.type;
|
|
1224
1278
|
}
|
|
1225
1279
|
} catch {
|
|
1226
1280
|
}
|
|
1227
1281
|
return null;
|
|
1228
1282
|
}
|
|
1283
|
+
function extractViewQueryFromSchema(zodSchema) {
|
|
1284
|
+
if (!zodSchema.description) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
try {
|
|
1288
|
+
const metadata = JSON.parse(zodSchema.description);
|
|
1289
|
+
if (typeof metadata.viewQuery === "string") {
|
|
1290
|
+
return metadata.viewQuery;
|
|
1291
|
+
}
|
|
1292
|
+
} catch {
|
|
1293
|
+
}
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1229
1296
|
function extractSchemaDefinitions(module, patterns = ["Schema", "InputSchema", "Collection"]) {
|
|
1230
1297
|
const result = {};
|
|
1231
1298
|
if (module.default instanceof z.ZodObject) {
|
|
@@ -1384,6 +1451,15 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1384
1451
|
const rawFields = extractFieldDefinitions(zodSchema);
|
|
1385
1452
|
const explicitType = extractCollectionTypeFromSchema(zodSchema);
|
|
1386
1453
|
const collectionType = explicitType ?? (isAuthCollection(rawFields) ? "auth" : "base");
|
|
1454
|
+
const isView = collectionType === "view";
|
|
1455
|
+
const viewQuery = extractViewQueryFromSchema(zodSchema);
|
|
1456
|
+
if (isView) {
|
|
1457
|
+
validateViewQuery(collectionName, viewQuery);
|
|
1458
|
+
} else if (viewQuery !== null) {
|
|
1459
|
+
console.warn(
|
|
1460
|
+
`[${collectionName}] viewQuery is only used by view collections and will be ignored. Use defineView() or set type: "view" to define a view collection.`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1387
1463
|
const fields = rawFields.filter((f) => !["created", "updated"].includes(f.name)).map(({ name, zodType }) => buildFieldDefinition(name, zodType));
|
|
1388
1464
|
if (collectionType === "auth") {
|
|
1389
1465
|
const authSystemFields = [
|
|
@@ -1443,26 +1519,43 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1443
1519
|
}
|
|
1444
1520
|
}
|
|
1445
1521
|
const indexes = extractIndexes(zodSchema) || [];
|
|
1522
|
+
if (isView && indexes.length > 0) {
|
|
1523
|
+
throw new Error(
|
|
1524
|
+
`View collection "${collectionName}" cannot declare indexes. PocketBase view collections are backed by a SQL query and do not support indexes.`
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1446
1527
|
const permissionAnalyzer = new PermissionAnalyzer();
|
|
1447
1528
|
let permissions = void 0;
|
|
1448
1529
|
const schemaDescription = zodSchema.description;
|
|
1449
1530
|
const extractedPermissions = permissionAnalyzer.extractPermissions(schemaDescription);
|
|
1450
1531
|
if (extractedPermissions) {
|
|
1451
1532
|
const resolvedPermissions = permissionAnalyzer.resolvePermissions(extractedPermissions);
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1461
|
-
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1533
|
+
if (isView) {
|
|
1534
|
+
for (const ruleType of ["createRule", "updateRule", "deleteRule", "manageRule"]) {
|
|
1535
|
+
if (resolvedPermissions[ruleType]) {
|
|
1536
|
+
console.warn(
|
|
1537
|
+
`[${collectionName}] ${ruleType} is not supported on view collections and will be set to null.`
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
resolvedPermissions[ruleType] = null;
|
|
1462
1541
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1542
|
+
}
|
|
1543
|
+
if (!isView) {
|
|
1544
|
+
const validationResults = permissionAnalyzer.validatePermissions(
|
|
1545
|
+
collectionName,
|
|
1546
|
+
resolvedPermissions,
|
|
1547
|
+
fields,
|
|
1548
|
+
collectionType === "auth"
|
|
1549
|
+
);
|
|
1550
|
+
for (const [ruleType, result] of validationResults) {
|
|
1551
|
+
if (!result.valid) {
|
|
1552
|
+
console.error(`[${collectionName}] Permission validation failed for ${ruleType}:`);
|
|
1553
|
+
result.errors.forEach((error) => console.error(` - ${error}`));
|
|
1554
|
+
}
|
|
1555
|
+
if (result.warnings.length > 0) {
|
|
1556
|
+
console.warn(`[${collectionName}] Permission warnings for ${ruleType}:`);
|
|
1557
|
+
result.warnings.forEach((warning) => console.warn(` - ${warning}`));
|
|
1558
|
+
}
|
|
1466
1559
|
}
|
|
1467
1560
|
}
|
|
1468
1561
|
permissions = permissionAnalyzer.mergeWithDefaults(resolvedPermissions);
|
|
@@ -1483,6 +1576,9 @@ function convertZodSchemaToCollectionSchema(collectionName, zodSchema) {
|
|
|
1483
1576
|
},
|
|
1484
1577
|
permissions
|
|
1485
1578
|
};
|
|
1579
|
+
if (isView) {
|
|
1580
|
+
collectionSchema.viewQuery = viewQuery;
|
|
1581
|
+
}
|
|
1486
1582
|
return collectionSchema;
|
|
1487
1583
|
}
|
|
1488
1584
|
var tsxLoaderRegistered = false;
|
|
@@ -1740,8 +1836,9 @@ function convertPocketBaseField(pbField) {
|
|
|
1740
1836
|
}
|
|
1741
1837
|
function convertPocketBaseCollection(pbCollection) {
|
|
1742
1838
|
const fields = [];
|
|
1839
|
+
const isView = pbCollection.type === "view";
|
|
1743
1840
|
const systemFieldNames = ["id", "created", "updated", "collectionId", "collectionName", "expand"];
|
|
1744
|
-
if (pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1841
|
+
if (!isView && pbCollection.fields && Array.isArray(pbCollection.fields)) {
|
|
1745
1842
|
for (const pbField of pbCollection.fields) {
|
|
1746
1843
|
if (pbField.system || systemFieldNames.includes(pbField.name)) {
|
|
1747
1844
|
const isAuthSystemField = pbCollection.type === "auth" && ["email", "emailVisibility", "verified", "password", "tokenKey"].includes(pbField.name);
|
|
@@ -1761,6 +1858,9 @@ function convertPocketBaseCollection(pbCollection) {
|
|
|
1761
1858
|
if (pbCollection.id) {
|
|
1762
1859
|
schema.id = pbCollection.id;
|
|
1763
1860
|
}
|
|
1861
|
+
if (typeof pbCollection.viewQuery === "string") {
|
|
1862
|
+
schema.viewQuery = dedentSql(pbCollection.viewQuery);
|
|
1863
|
+
}
|
|
1764
1864
|
if (pbCollection.indexes && Array.isArray(pbCollection.indexes)) {
|
|
1765
1865
|
schema.indexes = pbCollection.indexes;
|
|
1766
1866
|
}
|
|
@@ -1862,6 +1962,24 @@ function findMigrationsAfterSnapshot(migrationsPath, snapshotTimestamp) {
|
|
|
1862
1962
|
return [];
|
|
1863
1963
|
}
|
|
1864
1964
|
}
|
|
1965
|
+
function skipTemplateLiteral(content, start) {
|
|
1966
|
+
let i = start + 1;
|
|
1967
|
+
while (i < content.length) {
|
|
1968
|
+
const char = content[i];
|
|
1969
|
+
if (char === "\\") {
|
|
1970
|
+
i += 2;
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
if (char === "`") {
|
|
1974
|
+
return i + 1;
|
|
1975
|
+
}
|
|
1976
|
+
i++;
|
|
1977
|
+
}
|
|
1978
|
+
return i;
|
|
1979
|
+
}
|
|
1980
|
+
function unescapeTemplateLiteral(raw) {
|
|
1981
|
+
return raw.replace(/\\(`|\$\{|\\)/g, "$1");
|
|
1982
|
+
}
|
|
1865
1983
|
function parseMigrationOperationsFromContent(content) {
|
|
1866
1984
|
const collectionsToCreate = [];
|
|
1867
1985
|
const collectionsToDelete = [];
|
|
@@ -1950,6 +2068,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
1950
2068
|
while (i < content.length && bCount > 0) {
|
|
1951
2069
|
const char = content[i];
|
|
1952
2070
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2071
|
+
if (!inStr && char === "`") {
|
|
2072
|
+
i = skipTemplateLiteral(content, i);
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
1953
2075
|
if (!inStr && (char === '"' || char === "'")) {
|
|
1954
2076
|
inStr = true;
|
|
1955
2077
|
strChar = char;
|
|
@@ -2139,6 +2261,27 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2139
2261
|
}
|
|
2140
2262
|
}
|
|
2141
2263
|
}
|
|
2264
|
+
const viewQueryRegex = /(\w+)\.viewQuery\s*=\s*/g;
|
|
2265
|
+
let viewQueryMatch;
|
|
2266
|
+
while ((viewQueryMatch = viewQueryRegex.exec(content)) !== null) {
|
|
2267
|
+
const varInfo = variables.get(viewQueryMatch[1]);
|
|
2268
|
+
if (!varInfo || varInfo.type !== "collection") continue;
|
|
2269
|
+
const valueStart = viewQueryMatch.index + viewQueryMatch[0].length;
|
|
2270
|
+
if (content[valueStart] === "`") {
|
|
2271
|
+
const end = skipTemplateLiteral(content, valueStart);
|
|
2272
|
+
const raw = content.substring(valueStart + 1, end - 1);
|
|
2273
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2274
|
+
viewQueryRegex.lastIndex = end;
|
|
2275
|
+
} else {
|
|
2276
|
+
const terminator = content.indexOf(";", valueStart);
|
|
2277
|
+
const valueStr = content.substring(valueStart, terminator === -1 ? content.length : terminator);
|
|
2278
|
+
try {
|
|
2279
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(new Function("app", `return ${valueStr}`)(mockApp));
|
|
2280
|
+
} catch {
|
|
2281
|
+
getUpdate(varInfo.name).viewQuery = dedentSql(valueStr.trim());
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2142
2285
|
const unmarshalRegex = /unmarshal\s*\(/g;
|
|
2143
2286
|
let unmarshalMatch;
|
|
2144
2287
|
while ((unmarshalMatch = unmarshalRegex.exec(content)) !== null) {
|
|
@@ -2153,6 +2296,10 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2153
2296
|
while (i < content.length && braceCount > 0) {
|
|
2154
2297
|
const ch = content[i];
|
|
2155
2298
|
const prev = i > 0 ? content[i - 1] : "";
|
|
2299
|
+
if (!inStr && ch === "`") {
|
|
2300
|
+
i = skipTemplateLiteral(content, i);
|
|
2301
|
+
continue;
|
|
2302
|
+
}
|
|
2156
2303
|
if (!inStr && (ch === '"' || ch === "'")) {
|
|
2157
2304
|
inStr = true;
|
|
2158
2305
|
strChar = ch;
|
|
@@ -2186,6 +2333,22 @@ function parseMigrationOperationsFromContent(content) {
|
|
|
2186
2333
|
getUpdate(colInfo.name).rulesToUpdate[ruleKey] = valStr;
|
|
2187
2334
|
}
|
|
2188
2335
|
}
|
|
2336
|
+
const viewQueryKey = objStr.match(/"viewQuery"\s*:\s*/);
|
|
2337
|
+
if (viewQueryKey) {
|
|
2338
|
+
const valueStart = viewQueryKey.index + viewQueryKey[0].length;
|
|
2339
|
+
if (objStr[valueStart] === "`") {
|
|
2340
|
+
const end = skipTemplateLiteral(objStr, valueStart);
|
|
2341
|
+
const raw = objStr.substring(valueStart + 1, end - 1);
|
|
2342
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(unescapeTemplateLiteral(raw));
|
|
2343
|
+
} else {
|
|
2344
|
+
const valStr = objStr.substring(valueStart).replace(/[,}\s]+$/, "");
|
|
2345
|
+
try {
|
|
2346
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(new Function("app", `return ${valStr}`)(mockApp));
|
|
2347
|
+
} catch {
|
|
2348
|
+
getUpdate(colInfo.name).viewQuery = dedentSql(valStr);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2189
2352
|
}
|
|
2190
2353
|
const idxPushRegex = /(\w+)\.indexes\.push\s*\(/g;
|
|
2191
2354
|
let match;
|
|
@@ -2316,6 +2479,10 @@ function parseMigrationOperations(migrationContent) {
|
|
|
2316
2479
|
while (i < migrationContent.length && braceCount > 0) {
|
|
2317
2480
|
const char = migrationContent[i];
|
|
2318
2481
|
const prevChar = i > 0 ? migrationContent[i - 1] : "";
|
|
2482
|
+
if (!inString && char === "`") {
|
|
2483
|
+
i = skipTemplateLiteral(migrationContent, i);
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2319
2486
|
if (!inString && (char === '"' || char === "'")) {
|
|
2320
2487
|
inString = true;
|
|
2321
2488
|
stringChar = char;
|
|
@@ -2423,6 +2590,9 @@ function applyMigrationOperations(snapshot, operations) {
|
|
|
2423
2590
|
collection.indexes = collection.indexes.filter((idx) => !update.indexesToRemove.includes(idx));
|
|
2424
2591
|
}
|
|
2425
2592
|
}
|
|
2593
|
+
if (update.viewQuery !== void 0) {
|
|
2594
|
+
collection.viewQuery = update.viewQuery;
|
|
2595
|
+
}
|
|
2426
2596
|
if (Object.keys(update.rulesToUpdate).length > 0) {
|
|
2427
2597
|
if (!collection.rules) collection.rules = {};
|
|
2428
2598
|
if (!collection.permissions) collection.permissions = {};
|
|
@@ -2886,7 +3056,45 @@ function compareCollectionFields(currentCollection, previousCollection, config,
|
|
|
2886
3056
|
fieldsToRemove = fieldsToRemove.filter((_, index) => !processedRemoveIndices.has(index));
|
|
2887
3057
|
return { fieldsToAdd, fieldsToRemove, fieldsToModify };
|
|
2888
3058
|
}
|
|
3059
|
+
function normalizeSql(query) {
|
|
3060
|
+
if (!query) {
|
|
3061
|
+
return "";
|
|
3062
|
+
}
|
|
3063
|
+
return query.split("\n").map((line) => line.trim()).join(" ").replace(/\s+/g, " ").trim();
|
|
3064
|
+
}
|
|
3065
|
+
function compareViewQuery(currentCollection, previousCollection) {
|
|
3066
|
+
const newValue = currentCollection.viewQuery;
|
|
3067
|
+
if (typeof newValue !== "string") {
|
|
3068
|
+
return void 0;
|
|
3069
|
+
}
|
|
3070
|
+
if (normalizeSql(newValue) === normalizeSql(previousCollection.viewQuery)) {
|
|
3071
|
+
return void 0;
|
|
3072
|
+
}
|
|
3073
|
+
return {
|
|
3074
|
+
oldValue: previousCollection.viewQuery ?? null,
|
|
3075
|
+
newValue
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
2889
3078
|
function buildCollectionModification(currentCollection, previousCollection, config, collectionIdToName) {
|
|
3079
|
+
const isView = currentCollection.type === "view" || previousCollection.type === "view";
|
|
3080
|
+
if (isView) {
|
|
3081
|
+
return {
|
|
3082
|
+
collection: currentCollection.name,
|
|
3083
|
+
fieldsToAdd: [],
|
|
3084
|
+
fieldsToRemove: [],
|
|
3085
|
+
fieldsToModify: [],
|
|
3086
|
+
indexesToAdd: [],
|
|
3087
|
+
indexesToRemove: [],
|
|
3088
|
+
rulesToUpdate: compareRules(
|
|
3089
|
+
currentCollection.rules,
|
|
3090
|
+
previousCollection.rules,
|
|
3091
|
+
currentCollection.permissions,
|
|
3092
|
+
previousCollection.permissions
|
|
3093
|
+
),
|
|
3094
|
+
permissionsToUpdate: comparePermissions(currentCollection.permissions, previousCollection.permissions),
|
|
3095
|
+
viewQueryUpdate: compareViewQuery(currentCollection, previousCollection)
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
2890
3098
|
const { fieldsToAdd, fieldsToRemove, fieldsToModify } = compareCollectionFields(
|
|
2891
3099
|
currentCollection,
|
|
2892
3100
|
previousCollection,
|
|
@@ -2918,7 +3126,11 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2918
3126
|
const destructive = [];
|
|
2919
3127
|
const nonDestructive = [];
|
|
2920
3128
|
for (const collection of diff.collectionsToDelete) {
|
|
2921
|
-
|
|
3129
|
+
if (collection.type === "view") {
|
|
3130
|
+
nonDestructive.push(`Delete view collection: ${collection.name}`);
|
|
3131
|
+
} else {
|
|
3132
|
+
destructive.push(`Delete collection: ${collection.name}`);
|
|
3133
|
+
}
|
|
2922
3134
|
}
|
|
2923
3135
|
for (const collection of diff.collectionsToCreate) {
|
|
2924
3136
|
nonDestructive.push(`Create collection: ${collection.name}`);
|
|
@@ -2953,6 +3165,9 @@ function categorizeChangesBySeverity(diff, _config) {
|
|
|
2953
3165
|
for (const rule of modification.rulesToUpdate) {
|
|
2954
3166
|
nonDestructive.push(`Update rule: ${collectionName}.${rule.ruleType}`);
|
|
2955
3167
|
}
|
|
3168
|
+
if (modification.viewQueryUpdate) {
|
|
3169
|
+
nonDestructive.push(`Update view query: ${collectionName}`);
|
|
3170
|
+
}
|
|
2956
3171
|
}
|
|
2957
3172
|
return { destructive, nonDestructive };
|
|
2958
3173
|
}
|
|
@@ -2981,12 +3196,11 @@ function filterDiff(diff, options) {
|
|
|
2981
3196
|
});
|
|
2982
3197
|
let collectionsToDelete = diff.collectionsToDelete;
|
|
2983
3198
|
if (skipDestructive) {
|
|
2984
|
-
collectionsToDelete =
|
|
2985
|
-
} else {
|
|
2986
|
-
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
2987
|
-
return matchesPattern(col.name, patterns);
|
|
2988
|
-
});
|
|
3199
|
+
collectionsToDelete = collectionsToDelete.filter((col) => col.type === "view");
|
|
2989
3200
|
}
|
|
3201
|
+
collectionsToDelete = collectionsToDelete.filter((col) => {
|
|
3202
|
+
return matchesPattern(col.name, patterns);
|
|
3203
|
+
});
|
|
2990
3204
|
const collectionsToModify = diff.collectionsToModify.map((mod) => {
|
|
2991
3205
|
const collectionMatches = matchesPattern(mod.collection, patterns);
|
|
2992
3206
|
const fieldsToAdd = mod.fieldsToAdd.filter((field) => {
|
|
@@ -3011,6 +3225,7 @@ function filterDiff(diff, options) {
|
|
|
3011
3225
|
const indexesToRemove = collectionMatches ? mod.indexesToRemove : [];
|
|
3012
3226
|
const rulesToUpdate = collectionMatches ? mod.rulesToUpdate : [];
|
|
3013
3227
|
const permissionsToUpdate = collectionMatches ? mod.permissionsToUpdate : [];
|
|
3228
|
+
const viewQueryUpdate = collectionMatches ? mod.viewQueryUpdate : void 0;
|
|
3014
3229
|
return {
|
|
3015
3230
|
...mod,
|
|
3016
3231
|
fieldsToAdd,
|
|
@@ -3019,10 +3234,11 @@ function filterDiff(diff, options) {
|
|
|
3019
3234
|
indexesToAdd,
|
|
3020
3235
|
indexesToRemove,
|
|
3021
3236
|
rulesToUpdate,
|
|
3022
|
-
permissionsToUpdate
|
|
3237
|
+
permissionsToUpdate,
|
|
3238
|
+
viewQueryUpdate
|
|
3023
3239
|
};
|
|
3024
3240
|
}).filter((mod) => {
|
|
3025
|
-
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;
|
|
3241
|
+
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;
|
|
3026
3242
|
});
|
|
3027
3243
|
return {
|
|
3028
3244
|
...diff,
|
|
@@ -3034,7 +3250,7 @@ function filterDiff(diff, options) {
|
|
|
3034
3250
|
|
|
3035
3251
|
// src/migration/diff/index.ts
|
|
3036
3252
|
function hasChanges(modification) {
|
|
3037
|
-
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;
|
|
3253
|
+
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;
|
|
3038
3254
|
}
|
|
3039
3255
|
function aggregateChanges(currentSchema, previousSnapshot, config) {
|
|
3040
3256
|
const collectionIdToName = /* @__PURE__ */ new Map();
|
|
@@ -3213,6 +3429,13 @@ function formatValue(value) {
|
|
|
3213
3429
|
}
|
|
3214
3430
|
return String(value);
|
|
3215
3431
|
}
|
|
3432
|
+
function formatSqlTemplate(query, indent = " ") {
|
|
3433
|
+
const escaped = query.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
3434
|
+
const body = escaped.split("\n").map((line) => line.trim() === "" ? "" : `${indent}${line}`).join("\n");
|
|
3435
|
+
return `\`
|
|
3436
|
+
${body}
|
|
3437
|
+
${indent.slice(0, -2)}\``;
|
|
3438
|
+
}
|
|
3216
3439
|
function getFieldConstructorName(fieldType) {
|
|
3217
3440
|
const constructorMap = {
|
|
3218
3441
|
text: "TextField",
|
|
@@ -3573,6 +3796,15 @@ function generateCollectionRules(rules, collectionType = "base") {
|
|
|
3573
3796
|
if (!rules) {
|
|
3574
3797
|
return "";
|
|
3575
3798
|
}
|
|
3799
|
+
if (collectionType === "view") {
|
|
3800
|
+
return [
|
|
3801
|
+
`"listRule": ${formatValue(rules.listRule ?? null)}`,
|
|
3802
|
+
`"viewRule": ${formatValue(rules.viewRule ?? null)}`,
|
|
3803
|
+
`"createRule": null`,
|
|
3804
|
+
`"updateRule": null`,
|
|
3805
|
+
`"deleteRule": null`
|
|
3806
|
+
].join(",\n ");
|
|
3807
|
+
}
|
|
3576
3808
|
const parts = [];
|
|
3577
3809
|
if (rules.listRule !== void 0) {
|
|
3578
3810
|
parts.push(`"listRule": ${formatValue(rules.listRule)}`);
|
|
@@ -3598,6 +3830,15 @@ function generateCollectionPermissions(permissions, collectionType = "base") {
|
|
|
3598
3830
|
if (!permissions) {
|
|
3599
3831
|
return "";
|
|
3600
3832
|
}
|
|
3833
|
+
if (collectionType === "view") {
|
|
3834
|
+
return [
|
|
3835
|
+
`"listRule": ${formatValue(permissions.listRule ?? null)}`,
|
|
3836
|
+
`"viewRule": ${formatValue(permissions.viewRule ?? null)}`,
|
|
3837
|
+
`"createRule": null`,
|
|
3838
|
+
`"updateRule": null`,
|
|
3839
|
+
`"deleteRule": null`
|
|
3840
|
+
].join(",\n ");
|
|
3841
|
+
}
|
|
3601
3842
|
const parts = [];
|
|
3602
3843
|
if (permissions.listRule !== void 0) {
|
|
3603
3844
|
parts.push(`"listRule": ${formatValue(permissions.listRule)}`);
|
|
@@ -3635,6 +3876,16 @@ function generatePermissionUpdate(collectionName, ruleType, newValue, varName, i
|
|
|
3635
3876
|
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3636
3877
|
return lines.join("\n");
|
|
3637
3878
|
}
|
|
3879
|
+
function generateViewQueryUpdate(collectionName, viewQuery, varName, isLast = false, collectionIdMap) {
|
|
3880
|
+
const lines = [];
|
|
3881
|
+
const collectionVar = varName || `collection_${collectionName}_viewQuery`;
|
|
3882
|
+
lines.push(` const ${collectionVar} = ${generateFindCollectionCode(collectionName, collectionIdMap)};`);
|
|
3883
|
+
lines.push(` unmarshal({`);
|
|
3884
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(viewQuery, " ")},`);
|
|
3885
|
+
lines.push(` }, ${collectionVar})`);
|
|
3886
|
+
lines.push(isLast ? ` return app.save(${collectionVar});` : ` app.save(${collectionVar});`);
|
|
3887
|
+
return lines.join("\n");
|
|
3888
|
+
}
|
|
3638
3889
|
function generateGroupedRuleUpdates(collectionName, entries, varSuffix, isLast = false, collectionIdMap) {
|
|
3639
3890
|
const collectionVar = `collection_${collectionName}_${varSuffix}`;
|
|
3640
3891
|
const lines = [];
|
|
@@ -3665,6 +3916,13 @@ function generateCollectionCreation(collection, varName = "collection", isLast =
|
|
|
3665
3916
|
} else if (rulesCode) {
|
|
3666
3917
|
lines.push(` ${rulesCode},`);
|
|
3667
3918
|
}
|
|
3919
|
+
if (collection.type === "view") {
|
|
3920
|
+
lines.push(` "viewQuery": ${formatSqlTemplate(collection.viewQuery ?? "")},`);
|
|
3921
|
+
lines.push(` });`);
|
|
3922
|
+
lines.push(``);
|
|
3923
|
+
lines.push(isLast ? ` return app.save(${varName});` : ` app.save(${varName});`);
|
|
3924
|
+
return lines.join("\n");
|
|
3925
|
+
}
|
|
3668
3926
|
const systemFieldNames = ["created", "updated", "id"];
|
|
3669
3927
|
if (collection.type === "auth") {
|
|
3670
3928
|
systemFieldNames.push(...getAuthSystemFields().map((f) => f.name));
|
|
@@ -3705,7 +3963,21 @@ function generateOperationUpMigration(operation, collectionIdMap) {
|
|
|
3705
3963
|
const modification = operation.modifications;
|
|
3706
3964
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3707
3965
|
let operationCount = 0;
|
|
3708
|
-
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);
|
|
3966
|
+
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);
|
|
3967
|
+
if (modification.viewQueryUpdate) {
|
|
3968
|
+
operationCount++;
|
|
3969
|
+
const isLast = operationCount === totalOperations;
|
|
3970
|
+
lines.push(
|
|
3971
|
+
generateViewQueryUpdate(
|
|
3972
|
+
collectionName,
|
|
3973
|
+
modification.viewQueryUpdate.newValue,
|
|
3974
|
+
void 0,
|
|
3975
|
+
isLast,
|
|
3976
|
+
collectionIdMap
|
|
3977
|
+
)
|
|
3978
|
+
);
|
|
3979
|
+
if (!isLast) lines.push("");
|
|
3980
|
+
}
|
|
3709
3981
|
for (let i = 0; i < modification.fieldsToAdd.length; i++) {
|
|
3710
3982
|
const field = modification.fieldsToAdd[i];
|
|
3711
3983
|
operationCount++;
|
|
@@ -3806,7 +4078,21 @@ function generateOperationDownMigration(operation, collectionIdMap) {
|
|
|
3806
4078
|
const modification = operation.modifications;
|
|
3807
4079
|
const collectionName = typeof operation.collection === "string" ? operation.collection : operation.collection?.name ?? modification.collection;
|
|
3808
4080
|
let operationCount = 0;
|
|
3809
|
-
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);
|
|
4081
|
+
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);
|
|
4082
|
+
if (modification.viewQueryUpdate) {
|
|
4083
|
+
operationCount++;
|
|
4084
|
+
const isLast = operationCount === totalOperations;
|
|
4085
|
+
lines.push(
|
|
4086
|
+
generateViewQueryUpdate(
|
|
4087
|
+
collectionName,
|
|
4088
|
+
modification.viewQueryUpdate.oldValue ?? "",
|
|
4089
|
+
`collection_${collectionName}_revert_viewQuery`,
|
|
4090
|
+
isLast,
|
|
4091
|
+
collectionIdMap
|
|
4092
|
+
)
|
|
4093
|
+
);
|
|
4094
|
+
if (!isLast) lines.push("");
|
|
4095
|
+
}
|
|
3810
4096
|
if (modification.permissionsToUpdate && modification.permissionsToUpdate.length > 0) {
|
|
3811
4097
|
operationCount++;
|
|
3812
4098
|
const isLast = operationCount === totalOperations;
|
|
@@ -4079,6 +4365,9 @@ function generate(diff, config) {
|
|
|
4079
4365
|
function detectCollectionDeletions(diff) {
|
|
4080
4366
|
const changes = [];
|
|
4081
4367
|
for (const collection of diff.collectionsToDelete) {
|
|
4368
|
+
if (collection.type === "view") {
|
|
4369
|
+
continue;
|
|
4370
|
+
}
|
|
4082
4371
|
changes.push({
|
|
4083
4372
|
type: "collection_deletion" /* COLLECTION_DELETION */,
|
|
4084
4373
|
description: `Delete collection: ${collection.name}`,
|
|
@@ -4489,7 +4778,11 @@ function formatChangeSummary(diff) {
|
|
|
4489
4778
|
lines.push(chalk.green.bold(`\u2713 ${totalCollectionsToCreate} collection(s) to create:`));
|
|
4490
4779
|
for (const collection of diff.collectionsToCreate) {
|
|
4491
4780
|
lines.push(chalk.green(` + ${collection.name} (${collection.type})`));
|
|
4492
|
-
|
|
4781
|
+
if (collection.type === "view") {
|
|
4782
|
+
lines.push(chalk.gray(` fields derived from the view query`));
|
|
4783
|
+
} else {
|
|
4784
|
+
lines.push(chalk.gray(` ${collection.fields.length} field(s)`));
|
|
4785
|
+
}
|
|
4493
4786
|
}
|
|
4494
4787
|
lines.push("");
|
|
4495
4788
|
}
|
|
@@ -4534,6 +4827,9 @@ function formatChangeSummary(diff) {
|
|
|
4534
4827
|
if (modification.rulesToUpdate.length > 0) {
|
|
4535
4828
|
lines.push(chalk.yellow(` ~ ${modification.rulesToUpdate.length} rule(s) to update`));
|
|
4536
4829
|
}
|
|
4830
|
+
if (modification.viewQueryUpdate) {
|
|
4831
|
+
lines.push(chalk.yellow(` ~ view query to update`));
|
|
4832
|
+
}
|
|
4537
4833
|
lines.push("");
|
|
4538
4834
|
}
|
|
4539
4835
|
}
|
|
@@ -4919,11 +5215,14 @@ var TypeGenerator = class {
|
|
|
4919
5215
|
}
|
|
4920
5216
|
generateCollectionType(collection) {
|
|
4921
5217
|
const typeName = this.toPascalCase(collection.name);
|
|
5218
|
+
const isView = collection.type === "view";
|
|
4922
5219
|
const lines = [];
|
|
4923
5220
|
lines.push(`export interface ${typeName}Record {`);
|
|
4924
5221
|
lines.push(` id: string;`);
|
|
4925
|
-
|
|
4926
|
-
|
|
5222
|
+
if (!isView) {
|
|
5223
|
+
lines.push(` created: string;`);
|
|
5224
|
+
lines.push(` updated: string;`);
|
|
5225
|
+
}
|
|
4927
5226
|
lines.push(` collectionId: string;`);
|
|
4928
5227
|
lines.push(` collectionName: "${collection.name}";`);
|
|
4929
5228
|
for (const field of collection.fields) {
|
|
@@ -4935,7 +5234,7 @@ var TypeGenerator = class {
|
|
|
4935
5234
|
lines.push(`}`);
|
|
4936
5235
|
lines.push(``);
|
|
4937
5236
|
const expandFields = [];
|
|
4938
|
-
for (const field of collection.fields) {
|
|
5237
|
+
for (const field of isView ? [] : collection.fields) {
|
|
4939
5238
|
if (field.type === "relation" && field.relation) {
|
|
4940
5239
|
const targetCollection = field.relation.collection;
|
|
4941
5240
|
let targetType = "RecordModel";
|