@somewhere-cloud/cli 0.38.5 → 0.38.6
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/README.md +37 -48
- package/bin/engine.js +27 -0
- package/bin/somewhere.js +10 -1
- package/dist/commands/api.js +67 -2
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/auth.js +18 -5
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/check.js +9 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/deploy.js +145 -6
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +48 -7
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docs.js +15 -17
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/env.js +9 -1
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/init.js +16 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/logs.js +6 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/typecheck.js +44 -20
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/advisor-context.js +0 -9
- package/dist/lib/advisor-context.js.map +1 -1
- package/dist/lib/claim-handoff.js +23 -1
- package/dist/lib/claim-handoff.js.map +1 -1
- package/dist/lib/client.js +18 -7
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/config.js +15 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/deploy-check.js +45 -44
- package/dist/lib/deploy-check.js.map +1 -1
- package/dist/lib/files.js +108 -19
- package/dist/lib/files.js.map +1 -1
- package/dist/lib/last-run.js +24 -26
- package/dist/lib/last-run.js.map +1 -1
- package/dist/lib/temp-auth.js +16 -24
- package/dist/lib/temp-auth.js.map +1 -1
- package/dist/lib/typecheck.js +209 -78
- package/dist/lib/typecheck.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/DECLARED-DATA-VENDOR.json +18 -18
- package/runtime/SOURCE-LINT-VENDOR.json +47 -46
- package/runtime/declared-data.cjs +188 -108
- package/runtime/source-lint.cjs +311 -199
package/runtime/source-lint.cjs
CHANGED
|
@@ -31,6 +31,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
));
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
|
+
// worker/containers/compile/function-route.cjs
|
|
35
|
+
var require_function_route = __commonJS({
|
|
36
|
+
"worker/containers/compile/function-route.cjs"(exports, module2) {
|
|
37
|
+
"use strict";
|
|
38
|
+
var SOURCE_EXTENSION = /\.(ts|tsx|mts|js|mjs|jsx)$/;
|
|
39
|
+
var NAME2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
40
|
+
function functionRouteSegments2(file) {
|
|
41
|
+
const parts = file.replace(SOURCE_EXTENSION, "").split("/");
|
|
42
|
+
if (parts.length > 1 && parts[parts.length - 1] === "index") {
|
|
43
|
+
const parent = parts[parts.length - 2];
|
|
44
|
+
const rest = parent.match(/^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/);
|
|
45
|
+
if (rest) {
|
|
46
|
+
throw new Error(`a catch-all folder cannot hold an index file: ${file}; name the file ${parts.slice(0, -1).join("/")}.ts instead.`);
|
|
47
|
+
}
|
|
48
|
+
parts.pop();
|
|
49
|
+
}
|
|
50
|
+
return parts.map((segment, i) => {
|
|
51
|
+
const rest = segment.match(/^\[\.\.\.([^\]]*)\]$/);
|
|
52
|
+
if (rest && NAME2.test(rest[1])) {
|
|
53
|
+
if (i !== parts.length - 1) throw new Error(`catch-all [...${rest[1]}] must be the last path segment: ${file}`);
|
|
54
|
+
return { type: "rest", value: rest[1] };
|
|
55
|
+
}
|
|
56
|
+
const param = segment.match(/^\[([^\]]*)\]$/);
|
|
57
|
+
if (param && NAME2.test(param[1])) return { type: "param", value: param[1] };
|
|
58
|
+
if (/[\[\]]/.test(segment)) {
|
|
59
|
+
throw new Error(`malformed parametric segment "${segment}" in ${file}; accepted forms are [name] and [...name].`);
|
|
60
|
+
}
|
|
61
|
+
return { type: "static", value: segment };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function displayRoute2(segments) {
|
|
65
|
+
return "/" + segments.map((s) => s.type === "static" ? s.value : s.type === "param" ? `:${s.value}` : `*${s.value}`).join("/");
|
|
66
|
+
}
|
|
67
|
+
function routeShape2(segments) {
|
|
68
|
+
return "/" + segments.map((s) => s.type === "static" ? s.value : s.type === "param" ? ":" : "*").join("/");
|
|
69
|
+
}
|
|
70
|
+
function typedClientRoute(clientParts) {
|
|
71
|
+
const parts = clientParts[clientParts.length - 1] === "index" ? clientParts.slice(0, -1) : clientParts;
|
|
72
|
+
return parts.length ? `/api/${parts.join("/")}` : "/api";
|
|
73
|
+
}
|
|
74
|
+
module2.exports = { functionRouteSegments: functionRouteSegments2, displayRoute: displayRoute2, routeShape: routeShape2, typedClientRoute };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
34
78
|
// worker/containers/compile/declared-data-contract.cjs
|
|
35
79
|
var require_declared_data_contract = __commonJS({
|
|
36
80
|
"worker/containers/compile/declared-data-contract.cjs"(exports, module2) {
|
|
@@ -1190,6 +1234,9 @@ function scanSourceForSecrets(files, functions) {
|
|
|
1190
1234
|
return findings;
|
|
1191
1235
|
}
|
|
1192
1236
|
|
|
1237
|
+
// worker/src/utils/function-bundle.ts
|
|
1238
|
+
var import_function_route = __toESM(require_function_route());
|
|
1239
|
+
|
|
1193
1240
|
// worker/src/runtime/verify-signup.ts
|
|
1194
1241
|
var VERIFY_SIGNUP_FETCH_PROLOGUE = " const verifySignup = await __sw_captureVerifySignup(request); request = verifySignup.request;";
|
|
1195
1242
|
|
|
@@ -1367,7 +1414,7 @@ var RETIRED_TABLE_JS = ` // A read of a table retired with removedTable() (
|
|
|
1367
1414
|
if (!(expiresMs > nowMs)) return null;
|
|
1368
1415
|
return 'Table "' + table + '" was removed from db/schema.ts, and its data is retained until '
|
|
1369
1416
|
+ new Date(expiresMs).toISOString().slice(0, 10)
|
|
1370
|
-
+ '.
|
|
1417
|
+
+ '. A deploy that declares it again in db/schema.ts restores the table with its data.';
|
|
1371
1418
|
}
|
|
1372
1419
|
async function __sw_explainRetiredTable(database, err) {
|
|
1373
1420
|
const raw = String((err && err.original && err.original.message) || '');
|
|
@@ -1392,7 +1439,7 @@ var RETIRED_TABLE_JS = ` // A read of a table retired with removedTable() (
|
|
|
1392
1439
|
|
|
1393
1440
|
// worker/src/utils/database-full.ts
|
|
1394
1441
|
var DATABASE_FULL_PATTERN = /SQLITE_FULL|database or disk is full|database is full|at maximum size|exceeds the maximum database size|database size limit (?:has been )?(?:reached|exceeded)/i;
|
|
1395
|
-
var DATABASE_FULL_MESSAGE = "The database could not complete this write because its storage is full.
|
|
1442
|
+
var DATABASE_FULL_MESSAGE = "The database could not complete this write because its storage is full. Database storage and the account allowance are shown under Usage in the dashboard.";
|
|
1396
1443
|
|
|
1397
1444
|
// worker/src/runtime/db-typed-errors.ts
|
|
1398
1445
|
var DB_TYPED_ERRORS_JS = ` // The REST bridge's refusal as the caller sees it. A typed envelope
|
|
@@ -1428,8 +1475,26 @@ var DB_TYPED_ERRORS_JS = ` // The REST bridge's refusal as the caller sees
|
|
|
1428
1475
|
.replace(/^[\\s:;.,]+|[\\s:;.,]+$/g, '')
|
|
1429
1476
|
.trim();
|
|
1430
1477
|
}
|
|
1478
|
+
// A write a unique or primary-key rule refused: the caller's own duplicate,
|
|
1479
|
+
// nothing applied. One shape on every bridge (binding, the REST query and
|
|
1480
|
+
// batch fallbacks, the managed endpoint): code, status and the browser
|
|
1481
|
+
// contract's message. Uncaught, it stays the opaque function error.
|
|
1482
|
+
function __sw_dataConflict(err) {
|
|
1483
|
+
const conflict = new Error('A record with these values already exists.');
|
|
1484
|
+
conflict.code = 'DATA_CONFLICT';
|
|
1485
|
+
conflict.status = 409;
|
|
1486
|
+
conflict.retryable = false;
|
|
1487
|
+
conflict.original = err;
|
|
1488
|
+
return conflict;
|
|
1489
|
+
}
|
|
1490
|
+
// A reply that does not prove the write was refused never becomes a conflict.
|
|
1491
|
+
function __sw_outcomeUnproven(code) {
|
|
1492
|
+
return typeof code === 'string' && /OUTCOME_UNKNOWN|COMMITTED/.test(code);
|
|
1493
|
+
}
|
|
1431
1494
|
function __sw_decorateError(label, sqlPreview, err) {
|
|
1432
1495
|
const msg = (err && err.message) || String(err);
|
|
1496
|
+
// The platform already classified this refusal (platform-data/transport.ts uniqueConflict).
|
|
1497
|
+
if (err && err.code === 'DATA_CONFLICT') return __sw_dataConflict(err);
|
|
1433
1498
|
let sizeCode = null;
|
|
1434
1499
|
let sizeMessage = null;
|
|
1435
1500
|
if (${DATABASE_FULL_PATTERN}.test(msg)) {
|
|
@@ -1486,6 +1551,13 @@ var DB_TYPED_ERRORS_JS = ` // The REST bridge's refusal as the caller sees
|
|
|
1486
1551
|
better.original = err;
|
|
1487
1552
|
return better;
|
|
1488
1553
|
}
|
|
1554
|
+
// The engine's own wording of the same refusal (binding, or a platform
|
|
1555
|
+
// CONSTRAINT_VIOLATION relayed by the REST fallback).
|
|
1556
|
+
if (!__sw_outcomeUnproven(err && err.code)
|
|
1557
|
+
&& /\\b(?:UNIQUE|PRIMARY KEY) constraint failed\\b|\\bSQLITE_CONSTRAINT_(?:UNIQUE|PRIMARYKEY)\\b/i.test(msg)) {
|
|
1558
|
+
console.error('[SW_DB_CONFLICT] ' + label + ' \u2014 ' + msg + ' \u2014 ' + snippet);
|
|
1559
|
+
return __sw_dataConflict(err);
|
|
1560
|
+
}
|
|
1489
1561
|
// Any OTHER database error: the native binding / provider passthrough
|
|
1490
1562
|
// surfaces raw strings like "D1_ERROR: ... : SQLITE_CONSTRAINT" that
|
|
1491
1563
|
// leak the engine and read as noise. Rewrite to product language, but
|
|
@@ -1562,7 +1634,7 @@ var DB_TYPED_ERRORS_JS = ` // The REST bridge's refusal as the caller sees
|
|
|
1562
1634
|
return err;
|
|
1563
1635
|
}
|
|
1564
1636
|
const failed = new Error('A database write earlier in this request was saved, but a later database read failed. ' +
|
|
1565
|
-
'
|
|
1637
|
+
'Repeating the whole action would repeat the saved write; the saved data is in the database, and the failed read alone can be repeated.');
|
|
1566
1638
|
failed.code = 'DB_COMMITTED_READ_FAILED';
|
|
1567
1639
|
failed.prior_write_committed = true;
|
|
1568
1640
|
failed.retryable = false;
|
|
@@ -2847,7 +2919,7 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
|
|
|
2847
2919
|
// console.error), but the message stays neutral (tsk_8a4809cf,
|
|
2848
2920
|
// tsk_d4224d57).
|
|
2849
2921
|
const msg = (r.status === 402 && (code === 'INSUFFICIENT_BALANCE' || code === 'PAID_API_NOT_ACTIVATED' || code === 'PAID_AI_DISABLED'))
|
|
2850
|
-
? 'This AI feature is temporarily unavailable
|
|
2922
|
+
? 'This AI feature is temporarily unavailable; a later request can succeed.'
|
|
2851
2923
|
: ((data && data.message) || ('Platform call failed: ' + r.status));
|
|
2852
2924
|
const err = new Error(msg);
|
|
2853
2925
|
err.code = code;
|
|
@@ -3206,7 +3278,7 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
|
|
|
3206
3278
|
}
|
|
3207
3279
|
|
|
3208
3280
|
function __sw_malformedAuthResponse(method, detail) {
|
|
3209
|
-
const err = new Error('sw.auth.' + method + ': the platform sign-in response ' + detail + '. This is a platform fault, not an error in your code
|
|
3281
|
+
const err = new Error('sw.auth.' + method + ': the platform sign-in response ' + detail + '. This is a platform fault, not an error in your code; platform support requests are accepted at POST /v1/platform-feedback.');
|
|
3210
3282
|
err.code = 'AUTH_RESPONSE_MALFORMED';
|
|
3211
3283
|
err.status = 502;
|
|
3212
3284
|
throw err;
|
|
@@ -4223,7 +4295,7 @@ var DB_ROW_LINKS_JS = `
|
|
|
4223
4295
|
headers['X-Sw-Execution-Attestation'] = env.SW_EXECUTION_ATTESTATION;
|
|
4224
4296
|
if (extra) for (const name of Object.keys(extra)) headers[name] = extra[name];
|
|
4225
4297
|
const unavailable = function () {
|
|
4226
|
-
const e = new Error('The link operation could not be completed
|
|
4298
|
+
const e = new Error('The link operation could not be completed; an automatic repeat is not safe.');
|
|
4227
4299
|
e.code = 'LINK_UNAVAILABLE'; e.status = 503; e.retryable = false; e.__sw_expose = true;
|
|
4228
4300
|
return e;
|
|
4229
4301
|
};
|
|
@@ -4255,7 +4327,7 @@ var DB_ROW_LINKS_JS = `
|
|
|
4255
4327
|
headers['X-Sw-Execution-Attestation'] = env.SW_EXECUTION_ATTESTATION;
|
|
4256
4328
|
if (clientIp) headers['X-Sw-Client-Ip'] = String(clientIp).slice(0, 64);
|
|
4257
4329
|
const unknown = function () {
|
|
4258
|
-
const e = new Error('The write outcome could not be confirmed
|
|
4330
|
+
const e = new Error('The write outcome could not be confirmed; an automatic repeat is not safe.');
|
|
4259
4331
|
e.code = 'DB_WRITE_OUTCOME_UNKNOWN'; e.status = 503; e.retry = false; e.retryable = false; e.outcome = 'unknown'; e.__sw_expose = true;
|
|
4260
4332
|
return e;
|
|
4261
4333
|
};
|
|
@@ -4281,7 +4353,7 @@ var DB_ROW_LINKS_JS = `
|
|
|
4281
4353
|
}
|
|
4282
4354
|
const data = body.data, receipt = data && __sw_managedReceipt(data.idempotency);
|
|
4283
4355
|
if (!receipt || typeof data.grant !== 'string' || !/^crt_[0-9a-f]{32}$/.test(data.grant) || !__sw_arrayIsArray(data.data) || data.data.length !== 1) {
|
|
4284
|
-
const e = new Error('The row was created, but retry protection could not be confirmed
|
|
4356
|
+
const e = new Error('The row was created, but retry protection could not be confirmed; a repeat could create a second row.');
|
|
4285
4357
|
e.code = 'IDEMPOTENCY_RECEIPT_INVALID'; e.status = 502; e.retry = false; e.retryable = false;
|
|
4286
4358
|
e.outcome = 'committed'; e.write_committed = true; e.__sw_expose = true;
|
|
4287
4359
|
throw e;
|
|
@@ -4302,7 +4374,7 @@ var DB_ROW_LINKS_JS = `
|
|
|
4302
4374
|
if (session) headers['X-Sw-Creator'] = session;
|
|
4303
4375
|
if (clientIp) headers['X-Sw-Client-Ip'] = String(clientIp).slice(0, 64);
|
|
4304
4376
|
const unknown = function () {
|
|
4305
|
-
const e = new Error('The creator operation could not be confirmed
|
|
4377
|
+
const e = new Error('The creator operation could not be confirmed; an automatic repeat is not safe.');
|
|
4306
4378
|
e.code = 'CREATOR_OUTCOME_UNKNOWN'; e.status = 503; e.retry = false; e.retryable = false; e.outcome = 'unknown'; e.__sw_expose = true;
|
|
4307
4379
|
return e;
|
|
4308
4380
|
};
|
|
@@ -4353,7 +4425,7 @@ var DB_INTAKE_JS = `
|
|
|
4353
4425
|
headers['X-Sw-Execution-Attestation'] = env.SW_EXECUTION_ATTESTATION;
|
|
4354
4426
|
if (clientIp) headers['X-Sw-Client-Ip'] = String(clientIp).slice(0, 64);
|
|
4355
4427
|
const unknown = function () {
|
|
4356
|
-
const e = new Error('The write outcome could not be confirmed
|
|
4428
|
+
const e = new Error('The write outcome could not be confirmed; an automatic repeat is not safe.');
|
|
4357
4429
|
e.code = 'DB_WRITE_OUTCOME_UNKNOWN'; e.status = 503; e.retry = false; e.retryable = false; e.outcome = 'unknown'; e.__sw_expose = true;
|
|
4358
4430
|
return e;
|
|
4359
4431
|
};
|
|
@@ -4379,7 +4451,7 @@ var DB_INTAKE_JS = `
|
|
|
4379
4451
|
}
|
|
4380
4452
|
const data = body.data, receipt = data && __sw_managedReceipt(data.idempotency);
|
|
4381
4453
|
if (!receipt || data.accepted !== true) {
|
|
4382
|
-
const e = new Error('The submission was accepted, but retry protection could not be confirmed
|
|
4454
|
+
const e = new Error('The submission was accepted, but retry protection could not be confirmed; a repeat could submit it twice.');
|
|
4383
4455
|
e.code = 'IDEMPOTENCY_RECEIPT_INVALID'; e.status = 502; e.retry = false; e.retryable = false;
|
|
4384
4456
|
e.outcome = 'committed'; e.write_committed = true; e.__sw_expose = true;
|
|
4385
4457
|
throw e;
|
|
@@ -4507,7 +4579,7 @@ var DB_MANAGED_JS = `
|
|
|
4507
4579
|
const started = Date.now();
|
|
4508
4580
|
let answer;
|
|
4509
4581
|
const unconfirmed = function () {
|
|
4510
|
-
const e = new Error(writes ? 'The write outcome could not be confirmed
|
|
4582
|
+
const e = new Error(writes ? 'The write outcome could not be confirmed; an automatic repeat is not safe.' : 'The managed query could not be completed.');
|
|
4511
4583
|
e.code = writes ? 'DB_WRITE_OUTCOME_UNKNOWN' : 'DB_UNAVAILABLE';
|
|
4512
4584
|
e.status = 503; e.retry = false; e.retryable = false; e.outcome = writes ? 'unknown' : 'read_failed'; e.__sw_expose = true;
|
|
4513
4585
|
return e;
|
|
@@ -4550,7 +4622,7 @@ var DB_MANAGED_JS = `
|
|
|
4550
4622
|
// The write is confirmed, but without an exact receipt it is not a protected outcome.
|
|
4551
4623
|
const receipt = __sw_managedReceipt(answer[0].idempotency);
|
|
4552
4624
|
if (!receipt) {
|
|
4553
|
-
const e = new Error('The write committed, but its retry protection could not be confirmed
|
|
4625
|
+
const e = new Error('The write committed, but its retry protection could not be confirmed; a repeat could apply it twice.');
|
|
4554
4626
|
e.code = 'IDEMPOTENCY_RECEIPT_INVALID'; e.status = 502; e.retry = false; e.retryable = false;
|
|
4555
4627
|
e.outcome = 'committed'; e.write_committed = true; e.__sw_expose = true;
|
|
4556
4628
|
throw e;
|
|
@@ -7020,7 +7092,7 @@ var DB_JS = ` db: (function () {
|
|
|
7020
7092
|
function __sw_draftDbUnavailable() {
|
|
7021
7093
|
const err = new Error(inlineCheck
|
|
7022
7094
|
? 'A handler check runs without a database, so sw.db is not available here. Deploy, then call the live route to exercise database code.'
|
|
7023
|
-
: 'This draft database clone is unavailable, so the draft cannot run safely
|
|
7095
|
+
: 'This draft database clone is unavailable, so the draft cannot run safely; a later request can succeed.'
|
|
7024
7096
|
);
|
|
7025
7097
|
err.code = 'DRAFT_DB_NOT_ISOLATED';
|
|
7026
7098
|
err.status = inlineCheck ? 409 : 503;
|
|
@@ -7059,7 +7131,7 @@ var DB_JS = ` db: (function () {
|
|
|
7059
7131
|
}
|
|
7060
7132
|
if (code === 'MANAGED_READ_UNAVAILABLE') {
|
|
7061
7133
|
const err = new Error((body && body.message) ||
|
|
7062
|
-
'Managed-mode read capability is temporarily unavailable
|
|
7134
|
+
'Managed-mode read capability is temporarily unavailable; a later request can succeed.');
|
|
7063
7135
|
err.code = 'MANAGED_READ_UNAVAILABLE';
|
|
7064
7136
|
err.status = 503;
|
|
7065
7137
|
err.retryable = true;
|
|
@@ -7100,7 +7172,7 @@ var DB_JS = ` db: (function () {
|
|
|
7100
7172
|
const err = new Error(
|
|
7101
7173
|
'sw.db.' + operation + ' cannot reach the project database: the PROJECT_DB binding is unavailable, ' +
|
|
7102
7174
|
'and the fallback runtime credential was rejected (HTTP ' + response.status +
|
|
7103
|
-
(upstreamCode ? ', ' + upstreamCode : '') + ').
|
|
7175
|
+
(upstreamCode ? ', ' + upstreamCode : '') + '). A later request can succeed; a promote or redeploy of the function issues a new runtime credential.'
|
|
7104
7176
|
);
|
|
7105
7177
|
err.code = 'DB_CONNECTION_NOT_READY';
|
|
7106
7178
|
err.status = 503;
|
|
@@ -9601,7 +9673,7 @@ var DB_COMPOSED_WRITES_JS = ` // Per-column conditions are ANDed. A bounded
|
|
|
9601
9673
|
for (const item of prepared) __sw_emitQueryEvent(item.ir, Date.now() - started, 0, false, error && error.code || 'DB_BATCH_OUTCOME_UNKNOWN');
|
|
9602
9674
|
// A preflight refusal carries its existing remedy. A lost transport
|
|
9603
9675
|
// acknowledgement never proves rollback and must not invite replay.
|
|
9604
|
-
if (error && ['DRAFT_DB_NOT_ISOLATED', 'DEV_DB_NOT_ISOLATED', 'DB_CONNECTION_NOT_READY', 'CONSTRAINT_VIOLATION', 'DATABASE_VALUE_TOO_LARGE',
|
|
9676
|
+
if (error && ['DRAFT_DB_NOT_ISOLATED', 'DEV_DB_NOT_ISOLATED', 'DB_CONNECTION_NOT_READY', 'CONSTRAINT_VIOLATION', 'DATA_CONFLICT', 'DATABASE_VALUE_TOO_LARGE',
|
|
9605
9677
|
'MEMBERSHIP_REQUIRED', 'POLICY_AUTHORITY_REQUIRED'].indexOf(error.code) !== -1) throw error;
|
|
9606
9678
|
throw __sw_batchOutcomeUnknown();
|
|
9607
9679
|
}
|
|
@@ -10187,9 +10259,9 @@ var DB_RESULT_FIDELITY_JS = ` var __sw_declaredResultChecks;
|
|
|
10187
10259
|
const err = new Error(
|
|
10188
10260
|
'The statement ran and its changes were applied, but the row it returned holds an integer outside ' +
|
|
10189
10261
|
"JavaScript's exact number range in column " + JSON.stringify(String(column)) + ', which cannot be read ' +
|
|
10190
|
-
'back exactly afterwards
|
|
10191
|
-
__sw_quoteResultColumn(column) + ' AS TEXT) in the RETURNING list,
|
|
10192
|
-
'sw.db.update / sw.db.remove
|
|
10262
|
+
'back exactly afterwards; repeating the statement would apply its changes again. The column reads back exactly as CAST(' +
|
|
10263
|
+
__sw_quoteResultColumn(column) + ' AS TEXT) in the RETURNING list, and sw.db.insert / ' +
|
|
10264
|
+
'sw.db.update / sw.db.remove compose that cast.'
|
|
10193
10265
|
);
|
|
10194
10266
|
err.code = 'DATABASE_VALUE_TOO_LARGE';
|
|
10195
10267
|
err.status = 400;
|
|
@@ -10239,7 +10311,7 @@ var DB_RESULT_FIDELITY_JS = ` var __sw_declaredResultChecks;
|
|
|
10239
10311
|
function __sw_declaredValue(column, value) { return __sw_declaredResults().value(column, value); }
|
|
10240
10312
|
function __sw_declaredValueError(table, column, applied) {
|
|
10241
10313
|
const err = new Error((applied
|
|
10242
|
-
? 'The statement ran and its changes were applied, but the row it returned could not be read back
|
|
10314
|
+
? 'The statement ran and its changes were applied, but the row it returned could not be read back; repeating the statement would apply its changes again. '
|
|
10243
10315
|
: '') +
|
|
10244
10316
|
'Column ' + JSON.stringify(String(column.n)) + ' on ' + JSON.stringify(String(table)) + ' holds a stored value that is not a valid ' +
|
|
10245
10317
|
column.t + ' as declared in db/schema.ts, so it cannot be returned as that type. ' +
|
|
@@ -10251,7 +10323,7 @@ var DB_RESULT_FIDELITY_JS = ` var __sw_declaredResultChecks;
|
|
|
10251
10323
|
}
|
|
10252
10324
|
function __sw_declaredWideError(table, column, applied) {
|
|
10253
10325
|
const err = new Error((applied
|
|
10254
|
-
? 'The statement ran and its changes were applied, but the row it returned could not be read back
|
|
10326
|
+
? 'The statement ran and its changes were applied, but the row it returned could not be read back; repeating the statement would apply its changes again. '
|
|
10255
10327
|
: '') +
|
|
10256
10328
|
'Column ' + JSON.stringify(String(column.n)) + ' on ' + JSON.stringify(String(table)) + " holds a whole number outside JavaScript's exact " +
|
|
10257
10329
|
'range (-9007199254740991 to 9007199254740991), so it cannot be returned as an integer() number without changing it. ' +
|
|
@@ -11160,7 +11232,7 @@ var AI_JS = ` ai: (function () {
|
|
|
11160
11232
|
try { data = await r.json(); } catch { data = null; }
|
|
11161
11233
|
const code = (data && (data.code || data.error)) || 'PLATFORM_ERROR';
|
|
11162
11234
|
const msg = (r.status === 402 && (code === 'INSUFFICIENT_BALANCE' || code === 'PAID_API_NOT_ACTIVATED' || code === 'PAID_AI_DISABLED'))
|
|
11163
|
-
? 'This AI feature is temporarily unavailable
|
|
11235
|
+
? 'This AI feature is temporarily unavailable; a later request can succeed.'
|
|
11164
11236
|
: ((data && data.message) || ('Platform call failed: ' + r.status));
|
|
11165
11237
|
const err = new Error(msg);
|
|
11166
11238
|
err.code = code;
|
|
@@ -11212,7 +11284,7 @@ var AI_JS = ` ai: (function () {
|
|
|
11212
11284
|
// echo err.message to end users \u2014 never leak the owner billing
|
|
11213
11285
|
// prose. Keep the real code/status for the dev's own logging.
|
|
11214
11286
|
if (r.status === 402 && (code === 'INSUFFICIENT_BALANCE' || code === 'PAID_API_NOT_ACTIVATED' || code === 'PAID_AI_DISABLED')) {
|
|
11215
|
-
msg = 'This AI feature is temporarily unavailable
|
|
11287
|
+
msg = 'This AI feature is temporarily unavailable; a later request can succeed.';
|
|
11216
11288
|
}
|
|
11217
11289
|
const err = new Error(msg); if (code) err.code = code; err.status = r.status; throw err;
|
|
11218
11290
|
}
|
|
@@ -11233,7 +11305,7 @@ var AI_JS = ` ai: (function () {
|
|
|
11233
11305
|
// 402 INSUFFICIENT_BALANCE is end-user-triggerable \u2014 keep the
|
|
11234
11306
|
// real code/status for logging but neutralize the message.
|
|
11235
11307
|
if (r.status === 402 && (code === 'INSUFFICIENT_BALANCE' || code === 'PAID_API_NOT_ACTIVATED' || code === 'PAID_AI_DISABLED')) {
|
|
11236
|
-
msg = 'This AI feature is temporarily unavailable
|
|
11308
|
+
msg = 'This AI feature is temporarily unavailable; a later request can succeed.';
|
|
11237
11309
|
}
|
|
11238
11310
|
const err = new Error(msg); if (code) err.code = code; err.status = r.status; throw err;
|
|
11239
11311
|
}
|
|
@@ -11254,7 +11326,7 @@ var AI_JS = ` ai: (function () {
|
|
|
11254
11326
|
// 402 INSUFFICIENT_BALANCE is end-user-triggerable \u2014 keep the
|
|
11255
11327
|
// real code/status for logging but neutralize the message.
|
|
11256
11328
|
if (r.status === 402 && (code === 'INSUFFICIENT_BALANCE' || code === 'PAID_API_NOT_ACTIVATED' || code === 'PAID_AI_DISABLED')) {
|
|
11257
|
-
msg = 'This AI feature is temporarily unavailable
|
|
11329
|
+
msg = 'This AI feature is temporarily unavailable; a later request can succeed.';
|
|
11258
11330
|
}
|
|
11259
11331
|
const err = new Error(msg); if (code) err.code = code; err.status = r.status; throw err;
|
|
11260
11332
|
}
|
|
@@ -12301,7 +12373,7 @@ var AUTH_MANAGED_VISITOR_JS = ` // Visitor identity is declared when the re
|
|
|
12301
12373
|
body = __sw_jsonParse(await response.text());
|
|
12302
12374
|
} catch (_) { body = null; }
|
|
12303
12375
|
if (!response || !response.ok || !body || body.ok !== true || !body.data || typeof body.data !== 'object') {
|
|
12304
|
-
const err = new Error(body && typeof body.message === 'string' ? body.message : 'The visitor session could not be confirmed
|
|
12376
|
+
const err = new Error(body && typeof body.message === 'string' ? body.message : 'The visitor session could not be confirmed; an automatic repeat is not safe.');
|
|
12305
12377
|
err.code = body && typeof body.error === 'string' ? body.error : 'VISITOR_SESSION_OUTCOME_UNKNOWN';
|
|
12306
12378
|
err.status = response && response.status >= 400 ? response.status : 503;
|
|
12307
12379
|
err.retry = false; err.retryable = false; err.__sw_expose = true;
|
|
@@ -13330,7 +13402,7 @@ var AUTH_JS = ` auth: (function () {
|
|
|
13330
13402
|
},
|
|
13331
13403
|
// Mint the httpOnly session cookies from an existing token pair \u2014
|
|
13332
13404
|
// the primitive the *WithCookie helpers wrap (tsk_1dd4e1b4). For
|
|
13333
|
-
// backends (e.g. @somewhere-
|
|
13405
|
+
// backends (e.g. @somewhere-cloud/sdk/server, or its legacy auth shim) that already called
|
|
13334
13406
|
// login / googleExchange / verifyOtp and want THAT session as
|
|
13335
13407
|
// cookies without a second platform call.
|
|
13336
13408
|
setSessionCookies(access, refresh) {
|
|
@@ -13366,7 +13438,9 @@ var AUTH_JS = ` auth: (function () {
|
|
|
13366
13438
|
__sw_enforceCookieCsrf(__sw_authSnapshotRequest);
|
|
13367
13439
|
}
|
|
13368
13440
|
if (refresh) {
|
|
13369
|
-
|
|
13441
|
+
// The access cookie (signed, names its session) lets the platform confirm a
|
|
13442
|
+
// sign-out whose first response was lost; it revokes nothing by itself.
|
|
13443
|
+
const result = await this.logout(access ? { refresh_token: refresh, access_token: access } : { refresh_token: refresh }).catch(function (err) { throw __sw_markExpected(err); });
|
|
13370
13444
|
if (!result || result.logged_out !== true) {
|
|
13371
13445
|
const err = new Error('Could not confirm sign-out.');
|
|
13372
13446
|
err.code = 'SIGN_OUT_UNCONFIRMED';
|
|
@@ -14223,7 +14297,7 @@ var RENDER_JS = ` render: (function () {
|
|
|
14223
14297
|
// 402 INSUFFICIENT_BALANCE is end-user-triggerable \u2014 keep the
|
|
14224
14298
|
// real code/status for logging but neutralize the message.
|
|
14225
14299
|
if (r.status === 402 && code === 'INSUFFICIENT_BALANCE') {
|
|
14226
|
-
msg = 'This feature is temporarily unavailable
|
|
14300
|
+
msg = 'This feature is temporarily unavailable; a later request can succeed.';
|
|
14227
14301
|
}
|
|
14228
14302
|
const err = new Error(msg); if (code) err.code = code; err.status = r.status; throw err;
|
|
14229
14303
|
}
|
|
@@ -14243,7 +14317,7 @@ var RENDER_JS = ` render: (function () {
|
|
|
14243
14317
|
// 402 INSUFFICIENT_BALANCE is end-user-triggerable \u2014 keep the
|
|
14244
14318
|
// real code/status for logging but neutralize the message.
|
|
14245
14319
|
if (r.status === 402 && code === 'INSUFFICIENT_BALANCE') {
|
|
14246
|
-
msg = 'This feature is temporarily unavailable
|
|
14320
|
+
msg = 'This feature is temporarily unavailable; a later request can succeed.';
|
|
14247
14321
|
}
|
|
14248
14322
|
const err = new Error(msg); if (code) err.code = code; err.status = r.status; throw err;
|
|
14249
14323
|
}
|
|
@@ -14885,13 +14959,14 @@ var GROUPS_JS = ` groups: (() => {
|
|
|
14885
14959
|
get(_req, groupId) { return call('GET', '/' + seg(groupId)); },
|
|
14886
14960
|
members(_req, groupId, opts) { return call('GET', '/' + seg(groupId) + '/members' + page([], opts)); },
|
|
14887
14961
|
// One email; the platform completes the membership when the invitee
|
|
14888
|
-
// signs in through it
|
|
14962
|
+
// signs in, through it or with any sign-in proving that address.
|
|
14889
14963
|
invite(_req, groupId, input) {
|
|
14890
14964
|
return call('POST', '/' + seg(groupId) + '/invitations', {
|
|
14891
14965
|
email: input && input.email, role: input && input.role, redirect_uri: input && input.redirect_uri,
|
|
14892
14966
|
...(input && input.expires_in !== undefined ? { expires_in: input.expires_in } : {}),
|
|
14893
14967
|
});
|
|
14894
14968
|
},
|
|
14969
|
+
invitations(_req, groupId, opts) { return call('GET', '/' + seg(groupId) + '/invitations' + page([], opts)); },
|
|
14895
14970
|
revokeInvitation(_req, inviteId) { return call('POST', '/invitation-grants/' + seg(inviteId) + '/revoke'); },
|
|
14896
14971
|
leave(_req, groupId) { return call('POST', '/' + seg(groupId) + '/leave'); },
|
|
14897
14972
|
// Owner only; the group's own app rows stay, unreachable without server authority.
|
|
@@ -15786,9 +15861,9 @@ function declaredListGetPlan(operation, input, shape, fields, fail) {
|
|
|
15786
15861
|
if (column && column.t === "integer") {
|
|
15787
15862
|
if (typeof value === "number" && Number.isSafeInteger(value)) return value;
|
|
15788
15863
|
if (typeof value === "string" && /^(0|-?[1-9][0-9]*)$/.test(value) && value.length <= 17 && Number.isSafeInteger(Number(value))) return Number(value);
|
|
15789
|
-
return fail(400, "DATA_INPUT_INVALID", "
|
|
15864
|
+
return fail(400, "DATA_INPUT_INVALID", "A valid primary key is required.");
|
|
15790
15865
|
}
|
|
15791
|
-
if (!(typeof value === "string" && value.length > 0 && value.length <= 4096)) return fail(400, "DATA_INPUT_INVALID", "
|
|
15866
|
+
if (!(typeof value === "string" && value.length > 0 && value.length <= 4096)) return fail(400, "DATA_INPUT_INVALID", "A valid primary key is required.");
|
|
15792
15867
|
return value;
|
|
15793
15868
|
},
|
|
15794
15869
|
and(where2, field, predicate) {
|
|
@@ -15948,7 +16023,7 @@ function __sw_dataIsDuplicateConflict(error) {
|
|
|
15948
16023
|
for (let depth = 0; current && depth < 4; depth++) {
|
|
15949
16024
|
const code = typeof current.code === 'string' ? current.code : '';
|
|
15950
16025
|
const message = typeof current.message === 'string' ? current.message : '';
|
|
15951
|
-
if (/^SQLITE_CONSTRAINT_(?:UNIQUE|PRIMARYKEY)$/i.test(code)
|
|
16026
|
+
if (code === 'DATA_CONFLICT' || /^SQLITE_CONSTRAINT_(?:UNIQUE|PRIMARYKEY)$/i.test(code)
|
|
15952
16027
|
|| /^(?:D1_ERROR:\s*)?(?:UNIQUE|PRIMARY KEY) constraint failed(?:\s*:|$)/i.test(message)) return true;
|
|
15953
16028
|
current = current.original;
|
|
15954
16029
|
}
|
|
@@ -16016,9 +16091,9 @@ function __sw_dataKey(value, column) {
|
|
|
16016
16091
|
if (column.t === 'integer') {
|
|
16017
16092
|
if (typeof value === 'number' && Number.isSafeInteger(value)) return value;
|
|
16018
16093
|
if (typeof value === 'string' && /^(0|-?[1-9][0-9]*)$/.test(value) && value.length <= 17 && Number.isSafeInteger(Number(value))) return Number(value);
|
|
16019
|
-
__sw_dataFail(400, 'DATA_INPUT_INVALID', '
|
|
16094
|
+
__sw_dataFail(400, 'DATA_INPUT_INVALID', 'A valid primary key is required.');
|
|
16020
16095
|
}
|
|
16021
|
-
if (!(typeof value === 'string' && value.length > 0 && value.length <= 4096)) __sw_dataFail(400, 'DATA_INPUT_INVALID', '
|
|
16096
|
+
if (!(typeof value === 'string' && value.length > 0 && value.length <= 4096)) __sw_dataFail(400, 'DATA_INPUT_INVALID', 'A valid primary key is required.');
|
|
16022
16097
|
return value;
|
|
16023
16098
|
}
|
|
16024
16099
|
function __sw_dataAnd(where, field, predicate) {
|
|
@@ -16057,7 +16132,7 @@ async function __sw_dataAuthorizeRead(request, sw, shape, policy) {
|
|
|
16057
16132
|
if (!user || user.id === undefined || user.id === null) {
|
|
16058
16133
|
const message = shape.author
|
|
16059
16134
|
? 'This shared table is a signed-in shared view, and this data request has no verified user. Sign in to read it. For intentionally public catalog data, declare an explicit client.read column allowlist plus client.publicRead in db/schema.ts, run \`somewhere deploy\`, then use the generated \`somewhere:data\` client (docs({ topic: \'declared-data\' })). client.publicRead does not grant anonymous authority to ordinary sw.db.from calls in custom functions.'
|
|
16060
|
-
: '
|
|
16135
|
+
: 'This data operation requires a signed-in user.';
|
|
16061
16136
|
__sw_dataFail(401, 'AUTH_REQUIRED', message);
|
|
16062
16137
|
}
|
|
16063
16138
|
}
|
|
@@ -16077,6 +16152,11 @@ async function __sw_dataRead(sw, table, policy, options, verb) {
|
|
|
16077
16152
|
__sw_dataSpan(sw, 'sw_data_op', started);
|
|
16078
16153
|
}
|
|
16079
16154
|
}
|
|
16155
|
+
/** Whether this release's pinned contract grants the browser any operation (hasDeclaredDataAccess, at run time). */
|
|
16156
|
+
function __sw_dataAnyClientGrant() {
|
|
16157
|
+
return Object.values(__SW_DATA_SCHEMA).some(({ client }) => client && (client.read !== false || client.create !== null
|
|
16158
|
+
|| client.update !== null || client.delete || client.intake !== undefined || client.links !== undefined));
|
|
16159
|
+
}
|
|
16080
16160
|
/** The exact current data contract digest (one computation per isolate). */
|
|
16081
16161
|
function __sw_dataContractDigest() {
|
|
16082
16162
|
if (!__sw_dataDigestPromise) {
|
|
@@ -16095,7 +16175,7 @@ function __sw_dataIntakeReference(value, column) {
|
|
|
16095
16175
|
if (column && column.t === 'bigint' && typeof value === 'string' && /^(0|-?[1-9][0-9]{0,18})$/.test(value)
|
|
16096
16176
|
&& BigInt(value) >= -9223372036854775808n && BigInt(value) <= 9223372036854775807n) return value;
|
|
16097
16177
|
if (column && column.t === 'text' && typeof value === 'string') return value;
|
|
16098
|
-
__sw_dataFail(400, 'DATA_INPUT_INVALID', '
|
|
16178
|
+
__sw_dataFail(400, 'DATA_INPUT_INVALID', 'A valid reference is required.');
|
|
16099
16179
|
}
|
|
16100
16180
|
async function __sw_dataIntake(request, sw, input, table, shape) {
|
|
16101
16181
|
if (__sw_artifactExecution !== 'standard') __sw_dataFail(409, 'INTAKE_DRAFT_UNAVAILABLE', 'Public submissions work on the live app only.');
|
|
@@ -16133,6 +16213,12 @@ async function __sw_handleDeclaredData(request, sw, env) {
|
|
|
16133
16213
|
: operation === 'intake' ? ['reference', 'values', 'operation_key'] : null;
|
|
16134
16214
|
if (!extras) __sw_dataFail(400, 'DATA_OPERATION_INVALID', 'Use list, get, relation_list, aggregate, create, update, or delete.');
|
|
16135
16215
|
__sw_dataKeys(input, ['contract', 'table', 'operation', ...extras], 'Request');
|
|
16216
|
+
// A release that grants the browser nothing publishes no contract digest
|
|
16217
|
+
// (app description data.client.status 'not_declared'), so no request can
|
|
16218
|
+
// match one. Refuse as access, not as a stale client, whatever table is named.
|
|
16219
|
+
if (!__sw_dataAnyClientGrant()) {
|
|
16220
|
+
__sw_dataFail(403, 'DATA_ACCESS_DENIED', 'This app declares no browser data access. Use the table from a function with sw.db, or add a client block in db/schema.ts.');
|
|
16221
|
+
}
|
|
16136
16222
|
__sw_dataContractDigest();
|
|
16137
16223
|
const keyed = Object.prototype.hasOwnProperty.call(input, 'idempotency_key');
|
|
16138
16224
|
let matchingContract = input.contract === await __sw_dataDigestPromise;
|
|
@@ -16190,7 +16276,7 @@ async function __sw_handleDeclaredData(request, sw, env) {
|
|
|
16190
16276
|
const authStarted = Date.now();
|
|
16191
16277
|
const user = await sw.auth.fromRequest(request);
|
|
16192
16278
|
__sw_dataSpan(sw, 'sw_data_authz', authStarted);
|
|
16193
|
-
if (!user || user.id === undefined || user.id === null) __sw_dataFail(401, 'AUTH_REQUIRED', '
|
|
16279
|
+
if (!user || user.id === undefined || user.id === null) __sw_dataFail(401, 'AUTH_REQUIRED', 'This data operation requires a signed-in user.');
|
|
16194
16280
|
}
|
|
16195
16281
|
const fields = __sw_dataReadFields(shape);
|
|
16196
16282
|
const primaryKey = shape.clientPrimaryKey;
|
|
@@ -16359,7 +16445,7 @@ async function __sw_handleDeclaredData(request, sw, env) {
|
|
|
16359
16445
|
try { result = ownerRead ? await ownerRead(plan.options) : await __sw_dataRead(sw, table, policy, plan.options); }
|
|
16360
16446
|
catch (error) {
|
|
16361
16447
|
// A selected token's refusal is final. Never refresh or replay after dispatch.
|
|
16362
|
-
if (ownerRead && error && error.status === 401 && (error.code === 'AUTH_REQUIRED' || error.code === 'AUTH_INVALID_CREDS')) __sw_dataFail(401, 'AUTH_REQUIRED', '
|
|
16448
|
+
if (ownerRead && error && error.status === 401 && (error.code === 'AUTH_REQUIRED' || error.code === 'AUTH_INVALID_CREDS')) __sw_dataFail(401, 'AUTH_REQUIRED', 'This data operation requires a signed-in user.');
|
|
16363
16449
|
throw error;
|
|
16364
16450
|
}
|
|
16365
16451
|
// Row capability hints (tsk_668f0ce2 item 7): the platform's per-row answer, narrowed by this
|
|
@@ -16601,7 +16687,7 @@ async function __sw_handleCreatorDoor(request, sw) {
|
|
|
16601
16687
|
const root = __sw_creatorRoot(input.table);
|
|
16602
16688
|
if (!root) invalid();
|
|
16603
16689
|
const entry = entries.find(item => item.grant === input.creator_id);
|
|
16604
|
-
if (!entry) __sw_dataFail(401, 'CREATOR_SESSION_MISSING', '
|
|
16690
|
+
if (!entry) __sw_dataFail(401, 'CREATOR_SESSION_MISSING', 'The creator link session is no longer valid; opening the creator link again starts a new one.');
|
|
16605
16691
|
const owner = root.ownerColumn || 'user_id', key = root.clientPrimaryKey;
|
|
16606
16692
|
const childOf = (table) => {
|
|
16607
16693
|
const relation = (root.relations || []).find(r => r.table === table && root.client.links.children.includes(r.name));
|
|
@@ -16698,7 +16784,7 @@ async function __sw_dataLinked(request, sw, input, table, shape) {
|
|
|
16698
16784
|
if (__sw_artifactExecution !== 'standard') __sw_dataFail(409, 'LINKS_DRAFT_UNAVAILABLE', 'Share links work on the live app only.');
|
|
16699
16785
|
if (typeof input.link !== 'string' || !/^lnk_[0-9a-f]{32}$/.test(input.link)) __sw_dataFail(400, 'DATA_INPUT_INVALID', 'link must be a link id.');
|
|
16700
16786
|
const entry = __sw_linkEntries(request).find(item => item.grant === input.link);
|
|
16701
|
-
if (!entry) __sw_dataFail(401, 'LINK_SESSION_MISSING', '
|
|
16787
|
+
if (!entry) __sw_dataFail(401, 'LINK_SESSION_MISSING', 'The link session is no longer valid; opening the link again starts a new one.');
|
|
16702
16788
|
const operation = input.operation;
|
|
16703
16789
|
const own = shape.client && shape.client.links;
|
|
16704
16790
|
const parent = own ? null : __sw_linkRootFor(table);
|
|
@@ -17443,13 +17529,13 @@ function isRoutedFunctionPath(path) {
|
|
|
17443
17529
|
// worker/src/utils/endpoint-auth-mode.ts
|
|
17444
17530
|
var ENDPOINT_AUTH_MODES = ["required", "optional", "none"];
|
|
17445
17531
|
var ENDPOINT_AUTH_MODE_INVALID = "ENDPOINT_AUTH_MODE_INVALID";
|
|
17446
|
-
var ENDPOINT_AUTH_MODES_HINT = "
|
|
17532
|
+
var ENDPOINT_AUTH_MODES_HINT = "Supported modes: 'required' (caller must be signed in), 'optional' (user when signed in, otherwise null), 'none' (public).";
|
|
17447
17533
|
var MODES = new Set(ENDPOINT_AUTH_MODES);
|
|
17448
17534
|
|
|
17449
17535
|
// worker/src/utils/endpoint-rate-limit.ts
|
|
17450
17536
|
var ENDPOINT_RATE_LIMIT_INVALID = "ENDPOINT_RATE_LIMIT_INVALID";
|
|
17451
17537
|
var ENDPOINT_RATE_LIMIT_MAX_COUNT = 1e6;
|
|
17452
|
-
var ENDPOINT_RATE_LIMIT_HINT = `
|
|
17538
|
+
var ENDPOINT_RATE_LIMIT_HINT = `rateLimit accepts '<count>/<unit>' with a whole count from 1 to ${ENDPOINT_RATE_LIMIT_MAX_COUNT} and a unit of second, minute, hour or day, for example '30/minute'; without rateLimit there is no limit.`;
|
|
17453
17539
|
var ENDPOINT_RATE_LIMIT_JS = `
|
|
17454
17540
|
/** Parse "10/minute" / "60/hour" / "1000/day" into { max, windowSeconds }, or null. */
|
|
17455
17541
|
function __sw_parseRateLimit(spec) {
|
|
@@ -17503,7 +17589,7 @@ function parseClientPermissions(value, columns, scope) {
|
|
|
17503
17589
|
}
|
|
17504
17590
|
}
|
|
17505
17591
|
if (!["owner", "member", "shared", "policy"].includes(scope.kind)) {
|
|
17506
|
-
return fail("serverOnly() tables cannot declare client access
|
|
17592
|
+
return fail("serverOnly() tables cannot declare client access; their rows are reached only from server functions.");
|
|
17507
17593
|
}
|
|
17508
17594
|
if (scope.policy?.kind === "all_of") {
|
|
17509
17595
|
if (input.publicRead !== void 0 && input.publicRead !== false) return fail("allOf() tables cannot declare client.publicRead; their rows are reachable only by current members.");
|
|
@@ -17561,9 +17647,9 @@ function parseClientPermissions(value, columns, scope) {
|
|
|
17561
17647
|
if (column.helper === "id" || (scope.kind === "owner" || scope.kind === "policy") && name === scope.column) {
|
|
17562
17648
|
return fail(`client.${operation} cannot write the platform-assigned identity field "${entry}".`);
|
|
17563
17649
|
}
|
|
17564
|
-
if (column.helper === "blob") return fail(`client.${operation} cannot write binary field "${entry}"
|
|
17650
|
+
if (column.helper === "blob") return fail(`client.${operation} cannot write binary field "${entry}"; uploads go to project files.`);
|
|
17565
17651
|
if (typeof publicRead === "object" && Object.prototype.hasOwnProperty.call(publicRead.where, name)) {
|
|
17566
|
-
return fail(`client.${operation} cannot write publication field "${entry}"
|
|
17652
|
+
return fail(`client.${operation} cannot write publication field "${entry}"; rows are published by a server operation.`);
|
|
17567
17653
|
}
|
|
17568
17654
|
if (operation === "update" && (scope.kind === "member" || scope.kind === "policy") && scope.group?.includes(name)) {
|
|
17569
17655
|
return fail(`client.update cannot move a row between membership groups by changing "${entry}".`);
|
|
@@ -17576,7 +17662,7 @@ function parseClientPermissions(value, columns, scope) {
|
|
|
17576
17662
|
}
|
|
17577
17663
|
if (writes.create !== null) {
|
|
17578
17664
|
if (typeof publicRead === "object" && !privateByDefault(publicRead.where, fields)) {
|
|
17579
|
-
return fail("client.create has no fixed publication-field default that guarantees a private row.
|
|
17665
|
+
return fail("client.create has no fixed publication-field default that guarantees a private row. It is accepted when at least one predicate field has a private default; otherwise rows are created by a server operation.");
|
|
17580
17666
|
}
|
|
17581
17667
|
for (const column of columns) {
|
|
17582
17668
|
if (column.helper !== "id" && !column.nullable && !column.hasDefault && !column.autoNow && !writes.create.includes(column.name)) {
|
|
@@ -17654,7 +17740,7 @@ function parseIntakePermissions(value, columns, scope, publicRead) {
|
|
|
17654
17740
|
const column = byName2.get(name);
|
|
17655
17741
|
if (!column) return `client.intake.fields names undeclared or platform-managed field "${entry}".`;
|
|
17656
17742
|
if (column.helper === "id" || name === scope.column) return `client.intake.fields cannot set the platform-assigned identity field "${entry}".`;
|
|
17657
|
-
if (column.helper === "blob") return `client.intake.fields cannot accept binary field "${entry}"
|
|
17743
|
+
if (column.helper === "blob") return `client.intake.fields cannot accept binary field "${entry}"; uploads go to project files.`;
|
|
17658
17744
|
if (column.autoNow) return `client.intake.fields cannot set "${entry}", which the platform fills in.`;
|
|
17659
17745
|
if (typeof publicRead === "object" && Object.prototype.hasOwnProperty.call(publicRead.where, name)) {
|
|
17660
17746
|
return `client.intake.fields cannot set publication field "${entry}".`;
|
|
@@ -17668,7 +17754,7 @@ function parseIntakePermissions(value, columns, scope, publicRead) {
|
|
|
17668
17754
|
}
|
|
17669
17755
|
}
|
|
17670
17756
|
if (typeof publicRead === "object" && !privateByDefault(publicRead.where, byName2)) {
|
|
17671
|
-
return "client.intake has no fixed publication-field default that guarantees a private row
|
|
17757
|
+
return "client.intake has no fixed publication-field default that guarantees a private row; it is accepted when at least one predicate field has a private default.";
|
|
17672
17758
|
}
|
|
17673
17759
|
let where;
|
|
17674
17760
|
if (input.where !== void 0) {
|
|
@@ -17825,10 +17911,10 @@ function readPaymentBindings(r, fail) {
|
|
|
17825
17911
|
fields[key] = readString(r, fail, `payments.${name}.${key}`).value.toLowerCase();
|
|
17826
17912
|
return;
|
|
17827
17913
|
}
|
|
17828
|
-
r.expectPunct("{", `opening payments.${name}.onPaid
|
|
17914
|
+
r.expectPunct("{", `opening payments.${name}.onPaid, which takes one transition: { status: { from: [...], to: '...' } }`);
|
|
17829
17915
|
const column = r.readKey(`for the status column in payments.${name}.onPaid`);
|
|
17830
17916
|
r.expectPunct(":", `after payments.${name}.onPaid.${column.name}`);
|
|
17831
|
-
r.expectPunct("{", `opening payments.${name}.onPaid.${column.name}
|
|
17917
|
+
r.expectPunct("{", `opening payments.${name}.onPaid.${column.name}, which takes { from: [...], to: '...' }`);
|
|
17832
17918
|
const transition = {};
|
|
17833
17919
|
while (!r.tryPunct("}")) {
|
|
17834
17920
|
const opt = r.readKey(`for an option of payments.${name}.onPaid.${column.name}`);
|
|
@@ -17846,7 +17932,7 @@ function readPaymentBindings(r, fail) {
|
|
|
17846
17932
|
r.tryPunct(",");
|
|
17847
17933
|
if (!r.tryPunct("}")) {
|
|
17848
17934
|
const extra = r.peek();
|
|
17849
|
-
fail(extra.line, `payments.${name}.onPaid declares one status transition only;
|
|
17935
|
+
fail(extra.line, `payments.${name}.onPaid declares one status transition only; this one has a second entry. Other paid effects run from the signed payment event.`);
|
|
17850
17936
|
}
|
|
17851
17937
|
const values = [...transition.from, transition.to];
|
|
17852
17938
|
for (const t of values) {
|
|
@@ -17882,7 +17968,7 @@ function readJobBindings(r, fail) {
|
|
|
17882
17968
|
}
|
|
17883
17969
|
function readServiceRoles(r, fail) {
|
|
17884
17970
|
const entries2 = readNamed(r, fail, "serviceRoles", "service role", ["tables"], (_key, fields, name) => {
|
|
17885
|
-
r.expectPunct("{", `opening serviceRoles.${name}.tables
|
|
17971
|
+
r.expectPunct("{", `opening serviceRoles.${name}.tables, which takes { table: ['read', ...] }`);
|
|
17886
17972
|
const tables = [];
|
|
17887
17973
|
const tableLines = {};
|
|
17888
17974
|
while (!r.tryPunct("}")) {
|
|
@@ -17915,7 +18001,7 @@ function validateBindings(input, tableInfo, errors) {
|
|
|
17915
18001
|
errors.push(`${at2(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", which is not declared in db/schema.ts.`);
|
|
17916
18002
|
return null;
|
|
17917
18003
|
}
|
|
17918
|
-
if (!info.privateUser) errors.push(`${at2(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", whose scope is not owner(), member(), anyOf(), group(), appRole() or parent(). It binds rows a signed-in user holds;
|
|
18004
|
+
if (!info.privateUser) errors.push(`${at2(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", whose scope is not owner(), member(), anyOf(), group(), appRole() or parent(). It binds rows a signed-in user holds; other tables are reached from server functions.`);
|
|
17919
18005
|
if (!info.key) errors.push(`${at2(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", which needs exactly one id() key.`);
|
|
17920
18006
|
return info;
|
|
17921
18007
|
};
|
|
@@ -17924,7 +18010,7 @@ function validateBindings(input, tableInfo, errors) {
|
|
|
17924
18010
|
const info = rowTable(p, "payment binding");
|
|
17925
18011
|
if (!info) continue;
|
|
17926
18012
|
if (info.table.scope.kind === "policy" && reachesAllOf(info.table.scope.policy)) {
|
|
17927
|
-
errors.push(`${at2(p, "table")}payment binding "${p.name}" names table "${p.table}", which uses allOf(); payment bindings on allOf tables are not supported yet
|
|
18013
|
+
errors.push(`${at2(p, "table")}payment binding "${p.name}" names table "${p.table}", which uses allOf(); payment bindings on allOf tables are not supported yet; such tables are reached from server functions.`);
|
|
17928
18014
|
continue;
|
|
17929
18015
|
}
|
|
17930
18016
|
const column = (name) => info.table.columns.find((c) => c.name === name);
|
|
@@ -17942,7 +18028,7 @@ function validateBindings(input, tableInfo, errors) {
|
|
|
17942
18028
|
if (c.helper !== helper || c.nullable) errors.push(`${at2(p, field)}payment binding "${p.name}": "${p.table}"."${name}" must be ${what}.`);
|
|
17943
18029
|
if (info.identity.includes(name)) errors.push(`${at2(p, field)}payment binding "${p.name}": "${p.table}"."${name}" is the table's access column; a payment never moves a row.`);
|
|
17944
18030
|
const writable = [...info.table.client?.create ?? [], ...info.table.client?.update ?? [], ...info.table.client?.intake?.fields ?? []];
|
|
17945
|
-
if (writable.includes(name)) errors.push(`${at2(p, field)}payment binding "${p.name}": "${p.table}"."${name}" is browser-writable through client.create, client.update or client.intake
|
|
18031
|
+
if (writable.includes(name)) errors.push(`${at2(p, field)}payment binding "${p.name}": "${p.table}"."${name}" is browser-writable through client.create, client.update or client.intake, and a payment binding requires it not to be; the server sets prices and the payment sets the status.`);
|
|
17946
18032
|
}
|
|
17947
18033
|
if ((/* @__PURE__ */ new Set([p.amount, p.currency, p.status.column])).size !== 3) errors.push(`${at2(p, "")}payment binding "${p.name}" must name three different columns for amount, currency and status.`);
|
|
17948
18034
|
const status = column(p.status.column);
|
|
@@ -17953,7 +18039,7 @@ function validateBindings(input, tableInfo, errors) {
|
|
|
17953
18039
|
for (const j of input.jobs ?? []) rowTable(j, "job binding");
|
|
17954
18040
|
const taken = /* @__PURE__ */ new Set([...BUILT_IN_GROUP_ROLES, ...input.groups?.roles ?? [], ...(input.appRoles ?? []).map((role) => role.name)]);
|
|
17955
18041
|
for (const s of input.serviceRoles ?? []) {
|
|
17956
|
-
if (taken.has(s.name)) errors.push(`${at2(s, "")}service role "${s.name}" has the name of a group or app role. Service roles are a separate namespace for unattended work
|
|
18042
|
+
if (taken.has(s.name)) errors.push(`${at2(s, "")}service role "${s.name}" has the name of a group or app role. Service roles are a separate namespace for unattended work, so the name must differ.`);
|
|
17957
18043
|
const tableLines = lines.get(s.tables) ?? {};
|
|
17958
18044
|
for (const grant of s.tables) {
|
|
17959
18045
|
if (!tableInfo(grant.table)) errors.push(`line ${tableLines[grant.table] ?? 0}: service role "${s.name}" grants table "${grant.table}", which is not declared in db/schema.ts.`);
|
|
@@ -17963,33 +18049,8 @@ function validateBindings(input, tableInfo, errors) {
|
|
|
17963
18049
|
|
|
17964
18050
|
// worker/src/utils/function-bundle.ts
|
|
17965
18051
|
function compileRoutePattern(file) {
|
|
17966
|
-
const
|
|
17967
|
-
|
|
17968
|
-
const pattern = [];
|
|
17969
|
-
const display = [];
|
|
17970
|
-
parts.forEach((seg, i) => {
|
|
17971
|
-
const rest = seg.match(/^\[\.\.\.([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
|
|
17972
|
-
if (rest) {
|
|
17973
|
-
if (i !== parts.length - 1) {
|
|
17974
|
-
throw new Error(`catch-all [...${rest[1]}] must be the last path segment: ${file}`);
|
|
17975
|
-
}
|
|
17976
|
-
pattern.push({ type: "rest", value: rest[1] });
|
|
17977
|
-
display.push(`*${rest[1]}`);
|
|
17978
|
-
return;
|
|
17979
|
-
}
|
|
17980
|
-
const param = seg.match(/^\[([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
|
|
17981
|
-
if (param) {
|
|
17982
|
-
pattern.push({ type: "param", value: param[1] });
|
|
17983
|
-
display.push(`:${param[1]}`);
|
|
17984
|
-
return;
|
|
17985
|
-
}
|
|
17986
|
-
if (/[\[\]]/.test(seg)) {
|
|
17987
|
-
throw new Error(`malformed parametric segment "${seg}" in ${file}. Use [name] or [...name].`);
|
|
17988
|
-
}
|
|
17989
|
-
pattern.push({ type: "static", value: seg });
|
|
17990
|
-
display.push(seg);
|
|
17991
|
-
});
|
|
17992
|
-
return { pattern, displayPath: "/" + display.join("/") };
|
|
18052
|
+
const pattern = (0, import_function_route.functionRouteSegments)(file).map((s) => ({ type: s.type, value: s.value }));
|
|
18053
|
+
return { pattern, displayPath: (0, import_function_route.displayRoute)(pattern), shape: (0, import_function_route.routeShape)(pattern) };
|
|
17993
18054
|
}
|
|
17994
18055
|
function displayRouteForFunctionFile(file) {
|
|
17995
18056
|
if (typeof file !== "string" || !file) return null;
|
|
@@ -18255,7 +18316,7 @@ globalThis.sw.endpoint = function (config) {
|
|
|
18255
18316
|
);
|
|
18256
18317
|
return __sw_mergeHeaders(
|
|
18257
18318
|
new Response(
|
|
18258
|
-
JSON.stringify({ ok: false, error: 'RATE_LIMIT_UNAVAILABLE', message: 'Rate limiting is temporarily unavailable; this request was blocked.
|
|
18319
|
+
JSON.stringify({ ok: false, error: 'RATE_LIMIT_UNAVAILABLE', message: 'Rate limiting is temporarily unavailable; this request was blocked. A later identical request can succeed.' }),
|
|
18259
18320
|
{ status: 503, headers: { 'Content-Type': 'application/json', 'Retry-After': String(rateLimit.windowSeconds) } }
|
|
18260
18321
|
),
|
|
18261
18322
|
corsHeaders
|
|
@@ -18300,7 +18361,7 @@ globalThis.sw.endpoint = function (config) {
|
|
|
18300
18361
|
return __sw_mergeHeaders(__sw_jsonError(status, code || 'HANDLER_ERROR', 'The request could not be completed.'), corsHeaders);
|
|
18301
18362
|
}
|
|
18302
18363
|
return __sw_mergeHeaders(
|
|
18303
|
-
__sw_jsonError(500, 'HANDLER_ERROR', 'Endpoint handler threw an error
|
|
18364
|
+
__sw_jsonError(500, 'HANDLER_ERROR', 'Endpoint handler threw an error; the details are in your function logs.'),
|
|
18304
18365
|
corsHeaders
|
|
18305
18366
|
);
|
|
18306
18367
|
}
|
|
@@ -18311,33 +18372,38 @@ globalThis.sw.endpoint = function (config) {
|
|
|
18311
18372
|
// worker/src/utils/api-index-route-warnings.ts
|
|
18312
18373
|
function apiIndexRouteFindings(functions) {
|
|
18313
18374
|
const paths = Object.keys(functions ?? {}).sort();
|
|
18314
|
-
const
|
|
18375
|
+
const pathsByShape = /* @__PURE__ */ new Map();
|
|
18315
18376
|
for (const path of paths) {
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18377
|
+
if (!isRoutedFunctionPath(path)) continue;
|
|
18378
|
+
let shape;
|
|
18379
|
+
try {
|
|
18380
|
+
shape = compileRoutePattern(path).shape;
|
|
18381
|
+
} catch {
|
|
18382
|
+
continue;
|
|
18383
|
+
}
|
|
18384
|
+
const existing = pathsByShape.get(shape);
|
|
18319
18385
|
if (existing) existing.push(path);
|
|
18320
|
-
else
|
|
18386
|
+
else pathsByShape.set(shape, [path]);
|
|
18321
18387
|
}
|
|
18322
18388
|
return paths.flatMap((file) => {
|
|
18323
|
-
if (!isApiIndexHandlerPath(file)) return [];
|
|
18389
|
+
if (!isApiIndexHandlerPath(file) || !isRoutedFunctionPath(file)) return [];
|
|
18390
|
+
let shape;
|
|
18391
|
+
try {
|
|
18392
|
+
shape = compileRoutePattern(file).shape;
|
|
18393
|
+
} catch {
|
|
18394
|
+
return [];
|
|
18395
|
+
}
|
|
18396
|
+
const others = (pathsByShape.get(shape) ?? []).filter((path) => path !== file);
|
|
18397
|
+
if (others.length === 0) return [];
|
|
18324
18398
|
const route = displayRouteForFunctionFile(file);
|
|
18325
|
-
|
|
18326
|
-
const alternative = file.replace(/\/index(\.[^.]+)$/, "$1");
|
|
18327
|
-
const parentRoute = displayRouteForFunctionFile(alternative);
|
|
18328
|
-
if (!parentRoute) return [];
|
|
18329
|
-
const existing = pathsByRoute.get(parentRoute) ?? [];
|
|
18330
|
-
if (existing.length > 0) {
|
|
18331
|
-
return [{ file, warning: `Handler ${file} serves ${route}; directory index handlers keep /index in the URL. ${parentRoute} already has a handler: ${existing.join(", ")}. Choose the intended handler and route; both routes remain distinct.` }];
|
|
18332
|
-
}
|
|
18333
|
-
return [{ file, warning: `Handler ${file} serves ${route}; directory index handlers keep /index in the URL. To serve ${parentRoute} instead, use ${alternative}.` }];
|
|
18399
|
+
return [{ file, warning: `Handler ${file} serves ${route}, and so does ${others.join(", ")}. A deploy refuses two handlers for the same path; keep one.` }];
|
|
18334
18400
|
});
|
|
18335
18401
|
}
|
|
18336
18402
|
function isApiIndexHandlerPath(file) {
|
|
18337
|
-
return /^api\/.+\/index\.(?:ts|tsx|mts|js|mjs|jsx)$/.test(file);
|
|
18403
|
+
return /^api\/.+\/index\.(?:ts|tsx|mts|js|mjs|jsx)$/.test(file) || /^api\/index\.(?:ts|tsx|mts|js|mjs|jsx)$/.test(file);
|
|
18338
18404
|
}
|
|
18339
18405
|
|
|
18340
|
-
//
|
|
18406
|
+
// ../../../../../../pmwork/wt/files-migration-c0-rig/worker/node_modules/acorn/dist/acorn.mjs
|
|
18341
18407
|
var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
|
|
18342
18408
|
var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 2, 60, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 328, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 129, 74, 6, 0, 67, 12, 65, 1, 2, 0, 29, 6135, 9, 1237, 42, 9, 8936, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 496, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4153, 7, 221, 3, 5761, 15, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 4191];
|
|
18343
18409
|
var nonASCIIidentifierChars = "\u200C\u200D\xB7\u0300-\u036F\u0387\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0897-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u180F-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19D0-\u19DA\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1ABF-\u1ACE\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u200C\u200D\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\u30FB\uA620-\uA629\uA66F\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F1\uA8FF-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F\uFF65";
|
|
@@ -23996,7 +24062,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
23996
24062
|
const budget = (line, adding) => {
|
|
23997
24063
|
expanded += adding;
|
|
23998
24064
|
if (expanded > maxTokens) {
|
|
23999
|
-
fail(line, "reusing constants here expands the declaration past the size db/schema.ts allows
|
|
24065
|
+
fail(line, "reusing constants here expands the declaration past the size db/schema.ts allows; fewer copies, or the shared part written once, stay within it.");
|
|
24000
24066
|
}
|
|
24001
24067
|
};
|
|
24002
24068
|
const expandRange = (from, to, into, owner) => {
|
|
@@ -24013,7 +24079,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24013
24079
|
}
|
|
24014
24080
|
const constant = lookup(target, owner);
|
|
24015
24081
|
if (opener !== "{") {
|
|
24016
|
-
fail(t.line, `...${target.value} is only supported inside an object literal { \u2026 }
|
|
24082
|
+
fail(t.line, `...${target.value} is only supported inside an object literal { \u2026 }; elsewhere ${target.value} is referenced directly or its values are written inline.`);
|
|
24017
24083
|
}
|
|
24018
24084
|
if (!constant.objectLiteral) {
|
|
24019
24085
|
fail(t.line, `...${target.value} spreads a constant that is not an object literal { \u2026 }. Only object constants can be spread.`);
|
|
@@ -24039,7 +24105,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24039
24105
|
}
|
|
24040
24106
|
if (owner === null && STATEMENT_WORDS.has(t.value)) {
|
|
24041
24107
|
if (isIdent(t, "export") && isIdent(next, "const")) {
|
|
24042
|
-
fail(t.line, '"export const" is not supported in db/schema.ts; the file exports only the schema
|
|
24108
|
+
fail(t.line, '"export const" is not supported in db/schema.ts; the file exports only the schema, and constants are declared without "export".');
|
|
24043
24109
|
}
|
|
24044
24110
|
into.push(t);
|
|
24045
24111
|
continue;
|
|
@@ -24050,7 +24116,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24050
24116
|
fail(t.line, `${t.value} is a constant, not a function \u2014 write ${t.value} without "( \u2026 )".`);
|
|
24051
24117
|
}
|
|
24052
24118
|
if (isPunct(next, ".")) {
|
|
24053
|
-
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a property of a constant, which db/schema.ts cannot evaluate
|
|
24119
|
+
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a property of a constant, which db/schema.ts cannot evaluate; that part is accepted as its own constant or written inline.`);
|
|
24054
24120
|
}
|
|
24055
24121
|
budget(t.line, constant.tokens.length);
|
|
24056
24122
|
append(into, constant.tokens);
|
|
@@ -24065,12 +24131,12 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24065
24131
|
continue;
|
|
24066
24132
|
}
|
|
24067
24133
|
if (isPunct(next, ".")) {
|
|
24068
|
-
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a value at run time; db/schema.ts is read without running it
|
|
24134
|
+
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a value at run time; db/schema.ts is read without running it, so it accepts only the literal value.`);
|
|
24069
24135
|
}
|
|
24070
24136
|
if (isPunct(next, "(")) {
|
|
24071
|
-
fail(t.line, `${t.value}(\u2026) is a function call; db/schema.ts is read without running it, so only the schema helpers can be called.
|
|
24137
|
+
fail(t.line, `${t.value}(\u2026) is a function call; db/schema.ts is read without running it, so only the schema helpers can be called. Values are written inline, or declared with const using schema helpers and literals.`);
|
|
24072
24138
|
}
|
|
24073
|
-
fail(t.line, `"${t.value}" is not defined in db/schema.ts.
|
|
24139
|
+
fail(t.line, `"${t.value}" is not defined in db/schema.ts. A value is either declared above export default as const ${t.value} = \u2026; or written inline.`);
|
|
24074
24140
|
}
|
|
24075
24141
|
};
|
|
24076
24142
|
const lookup = (t, owner) => {
|
|
@@ -24081,37 +24147,37 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24081
24147
|
fail(t.line, `constant ${t.value} refers to itself. A constant must be built from values declared above it.`);
|
|
24082
24148
|
}
|
|
24083
24149
|
if (at2 !== void 0) {
|
|
24084
|
-
fail(t.line, owner ? `constant ${owner} uses ${t.value}, which is declared later (line ${at2})
|
|
24150
|
+
fail(t.line, owner ? `constant ${owner} uses ${t.value}, which is declared later (line ${at2}); ${t.value} must come above ${owner}, and constants that refer to each other in a cycle cannot be resolved.` : `${t.value} is used before its declaration on line ${at2}; a const must come above export default.`);
|
|
24085
24151
|
}
|
|
24086
|
-
return fail(t.line, `"${t.value}" is not defined in db/schema.ts.
|
|
24152
|
+
return fail(t.line, `"${t.value}" is not defined in db/schema.ts. A value is either declared above export default as const ${t.value} = \u2026; or written inline.`);
|
|
24087
24153
|
};
|
|
24088
24154
|
while (!isIdent(tokens[i], "export")) {
|
|
24089
24155
|
const t = tokens[i];
|
|
24090
24156
|
if (t.kind === "eof") return [...out, t];
|
|
24091
24157
|
if (isIdent(t, "let") || isIdent(t, "var")) {
|
|
24092
|
-
fail(t.line, `"${t.value}" declares a changeable binding; db/schema.ts allows only const
|
|
24158
|
+
fail(t.line, `"${t.value}" declares a changeable binding; db/schema.ts allows only const, not "${t.value}".`);
|
|
24093
24159
|
}
|
|
24094
24160
|
if (!isIdent(t, "const")) {
|
|
24095
24161
|
if (t.kind === "ident" && constants.has(t.value)) {
|
|
24096
|
-
fail(t.line, `${t.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated
|
|
24162
|
+
fail(t.line, `${t.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated, so each final value is declared once with const.`);
|
|
24097
24163
|
}
|
|
24098
24164
|
if (isIdent(t, "import")) fail(t.line, "imports must come first in db/schema.ts, before any const.");
|
|
24099
24165
|
return [...out, ...tokens.slice(i)];
|
|
24100
24166
|
}
|
|
24101
24167
|
const nameTok = tokens[i + 1];
|
|
24102
24168
|
if (!nameTok || nameTok.kind !== "ident") {
|
|
24103
|
-
fail(t.line, "destructuring (const { \u2026 } = / const [ \u2026 ] =) is not supported in db/schema.ts
|
|
24169
|
+
fail(t.line, "destructuring (const { \u2026 } = / const [ \u2026 ] =) is not supported in db/schema.ts; it accepts one constant per name: const name = \u2026;");
|
|
24104
24170
|
}
|
|
24105
24171
|
const name = nameTok.value;
|
|
24106
24172
|
if (callable.has(name) || LITERAL_WORDS.has(name) || STATEMENT_WORDS.has(name) || name === "schema") {
|
|
24107
|
-
fail(nameTok.line, `const ${name} shadows a db/schema.ts helper or keyword
|
|
24173
|
+
fail(nameTok.line, `const ${name} shadows a db/schema.ts helper or keyword; a different name, such as ${name}Rule, does not.`);
|
|
24108
24174
|
}
|
|
24109
24175
|
if (imported.has(name)) {
|
|
24110
|
-
fail(nameTok.line, `const ${name} has the same name as an import
|
|
24176
|
+
fail(nameTok.line, `const ${name} has the same name as an import; constant names must differ from imports.`);
|
|
24111
24177
|
}
|
|
24112
|
-
if (constants.has(name)) fail(nameTok.line, `const ${name} is declared twice
|
|
24178
|
+
if (constants.has(name)) fail(nameTok.line, `const ${name} is declared twice; a constant is declared once.`);
|
|
24113
24179
|
if (isPunct(tokens[i + 2], ":")) {
|
|
24114
|
-
fail(tokens[i + 2].line, `const ${name} has a type annotation; db/schema.ts constants take their type from the helpers
|
|
24180
|
+
fail(tokens[i + 2].line, `const ${name} has a type annotation; db/schema.ts constants take their type from the helpers, so ": \u2026" is not accepted.`);
|
|
24115
24181
|
}
|
|
24116
24182
|
if (!isPunct(tokens[i + 2], "=")) fail(nameTok.line, `expected "=" after const ${name}.`);
|
|
24117
24183
|
const start = i + 3;
|
|
@@ -24122,16 +24188,16 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24122
24188
|
if (v.kind === "punct" && OPENERS.includes(v.value)) depth++;
|
|
24123
24189
|
else if (v.kind === "punct" && CLOSERS.includes(v.value)) {
|
|
24124
24190
|
depth--;
|
|
24125
|
-
if (depth < 0) fail(v.line, `const ${name} has an unmatched "${v.value}"
|
|
24191
|
+
if (depth < 0) fail(v.line, `const ${name} has an unmatched "${v.value}"; its brackets are unbalanced.`);
|
|
24126
24192
|
} else if (depth === 0 && (isPunct(v, ";") || isPunct(v, ",") || isIdent(v, "const") && !isIdent(tokens[k - 1], "as") || isIdent(v, "let") || isIdent(v, "var") || isIdent(v, "export"))) break;
|
|
24127
|
-
if (depth === 0 && isPunct(v, "=")) fail(v.line, `${name} is assigned twice;
|
|
24193
|
+
if (depth === 0 && isPunct(v, "=")) fail(v.line, `${name} is assigned twice; each constant takes one value.`);
|
|
24128
24194
|
}
|
|
24129
|
-
if (depth !== 0) fail(nameTok.line, `const ${name} has an unclosed bracket
|
|
24195
|
+
if (depth !== 0) fail(nameTok.line, `const ${name} has an unclosed bracket; its brackets are unbalanced.`);
|
|
24130
24196
|
let end2 = k;
|
|
24131
24197
|
if (end2 - start >= 2 && isIdent(tokens[end2 - 2], "as") && isIdent(tokens[end2 - 1], "const")) end2 -= 2;
|
|
24132
24198
|
if (end2 === start) fail(nameTok.line, `const ${name} has no value.`);
|
|
24133
24199
|
if (isPunct(tokens[k], ",")) {
|
|
24134
|
-
fail(tokens[k].line, `one declaration declares several constants;
|
|
24200
|
+
fail(tokens[k].line, `one declaration declares several constants; db/schema.ts accepts one const per name (const ${name} = \u2026;).`);
|
|
24135
24201
|
}
|
|
24136
24202
|
const value = [];
|
|
24137
24203
|
expandRange(start, end2, value, name);
|
|
@@ -24140,7 +24206,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24140
24206
|
i = isPunct(tokens[k], ";") ? k + 1 : k;
|
|
24141
24207
|
}
|
|
24142
24208
|
if (isIdent(tokens[i], "export") && isIdent(tokens[i + 1], "const")) {
|
|
24143
|
-
fail(tokens[i].line, '"export const" is not supported in db/schema.ts; the file exports only the schema
|
|
24209
|
+
fail(tokens[i].line, '"export const" is not supported in db/schema.ts; the file exports only the schema, and constants are declared without "export".');
|
|
24144
24210
|
}
|
|
24145
24211
|
const body = [];
|
|
24146
24212
|
const open = tokens.findIndex((v, k) => k > i && isPunct(v, "(") && isIdent(tokens[k - 1], "schema"));
|
|
@@ -24150,12 +24216,12 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
|
24150
24216
|
for (let k = end; k < tokens.length; k++) {
|
|
24151
24217
|
const v = tokens[k];
|
|
24152
24218
|
if (v.kind === "ident" && constants.has(v.value) && (isPunct(tokens[k + 1], "=") || isPunct(tokens[k + 1], "."))) {
|
|
24153
|
-
fail(v.line, `${v.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated
|
|
24219
|
+
fail(v.line, `${v.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated, so each final value is declared once with const.`);
|
|
24154
24220
|
}
|
|
24155
24221
|
body.push(v);
|
|
24156
24222
|
}
|
|
24157
24223
|
if (out.length + body.length > maxTokens) {
|
|
24158
|
-
fail(tokens[i].line, "reusing constants here expands the declaration past the size db/schema.ts allows
|
|
24224
|
+
fail(tokens[i].line, "reusing constants here expands the declaration past the size db/schema.ts allows; fewer copies, or the shared part written once, stay within it.");
|
|
24159
24225
|
}
|
|
24160
24226
|
return [...out, ...body];
|
|
24161
24227
|
}
|
|
@@ -24265,18 +24331,18 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
|
|
|
24265
24331
|
for (const rel of t.relations) {
|
|
24266
24332
|
if (rel.name === "__proto__" || rel.name === "prototype" || rel.name === "constructor") {
|
|
24267
24333
|
errors.push(
|
|
24268
|
-
`Relation "${t.name}"."${rel.name}" uses a reserved object name
|
|
24334
|
+
`Relation "${t.name}"."${rel.name}" uses a reserved object name; a relation name must be returnable as an ordinary row field.`
|
|
24269
24335
|
);
|
|
24270
24336
|
} else if (rootNames.has(rel.name)) {
|
|
24271
24337
|
errors.push(
|
|
24272
|
-
`Relation "${t.name}"."${rel.name}" has the same name as a column on "${t.name}".
|
|
24338
|
+
`Relation "${t.name}"."${rel.name}" has the same name as a column on "${t.name}". A relation name must differ from the columns, so nesting related rows cannot overwrite the root column.`
|
|
24273
24339
|
);
|
|
24274
24340
|
}
|
|
24275
24341
|
const related = tableByName.get(rel.table);
|
|
24276
24342
|
if (!related) {
|
|
24277
24343
|
const marked = markedGone(rel.table);
|
|
24278
24344
|
errors.push(
|
|
24279
|
-
marked ? `Relation "${t.name}"."${rel.name}" targets "${rel.table}", which this file marks ${marked}.
|
|
24345
|
+
marked ? `Relation "${t.name}"."${rel.name}" targets "${rel.table}", which this file marks ${marked}. The table can go only once no relation targets it.` : `Relation "${t.name}"."${rel.name}" targets ${rel.kind === "hasMany" ? "child" : "parent"} table "${rel.table}", which is not declared in db/schema.ts. A relation can only target a managed table in the same declaration.`
|
|
24280
24346
|
);
|
|
24281
24347
|
continue;
|
|
24282
24348
|
}
|
|
@@ -24285,7 +24351,7 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
|
|
|
24285
24351
|
const idCol = keySide.columns.find((c) => c.helper === "id");
|
|
24286
24352
|
if (!idCol) {
|
|
24287
24353
|
errors.push(
|
|
24288
|
-
rel.kind === "hasMany" ? `Table "${t.name}" declares relation "${rel.name}" but has no id() column to join on. A hasMany relation joins the child's foreign key to this table's primary key
|
|
24354
|
+
rel.kind === "hasMany" ? `Table "${t.name}" declares relation "${rel.name}" but has no id() column to join on. A hasMany relation joins the child's foreign key to this table's primary key, so the table needs an id() column.` : `Relation "${t.name}"."${rel.name}" belongsTo "${rel.table}", but "${rel.table}" has no id() column to join on. A belongsTo relation joins this table's foreign key to the parent's primary key, so "${rel.table}" needs an id() column.`
|
|
24289
24355
|
);
|
|
24290
24356
|
} else {
|
|
24291
24357
|
rel.parentKey = idCol.name;
|
|
@@ -24299,13 +24365,13 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
|
|
|
24299
24365
|
}
|
|
24300
24366
|
if (fkCol.references !== keySide.name) {
|
|
24301
24367
|
errors.push(
|
|
24302
|
-
fkCol.references === null ? `Relation "${t.name}"."${rel.name}" joins on "${fkSide.name}"."${rel.fk}", but that column declares no foreign key.
|
|
24368
|
+
fkCol.references === null ? `Relation "${t.name}"."${rel.name}" joins on "${fkSide.name}"."${rel.fk}", but that column declares no foreign key. The join key must be a proven foreign key, not a free-form join: ${rel.fk}: <type>({ references: '${keySide.name}' }).` : `Relation "${t.name}"."${rel.name}" joins on "${fkSide.name}"."${rel.fk}", but that column references "${fkCol.references}", not "${keySide.name}". The foreign key must reference the table that holds the primary key of this relation.`
|
|
24303
24369
|
);
|
|
24304
24370
|
} else if (idCol) {
|
|
24305
24371
|
const expectedHelper = idCol.uuid ? "text" : "integer";
|
|
24306
24372
|
if (fkCol.helper !== expectedHelper) {
|
|
24307
24373
|
errors.push(
|
|
24308
|
-
`Relation "${t.name}"."${rel.name}" joins "${fkSide.name}"."${rel.fk}" to "${keySide.name}"."${idCol.name}", but their key types do not match.
|
|
24374
|
+
`Relation "${t.name}"."${rel.name}" joins "${fkSide.name}"."${rel.fk}" to "${keySide.name}"."${idCol.name}", but their key types do not match. The foreign key needs ${expectedHelper}() so fetched rows can be stitched to the ${idCol.uuid ? "text" : "whole-number"} key exactly.`
|
|
24309
24375
|
);
|
|
24310
24376
|
}
|
|
24311
24377
|
}
|
|
@@ -24337,7 +24403,7 @@ function validateRowLinks(tables, tableByName, errors) {
|
|
|
24337
24403
|
}
|
|
24338
24404
|
const earlier = childOf.get(child.name);
|
|
24339
24405
|
if (earlier && earlier !== t.name) {
|
|
24340
|
-
errors.push(`Table "${child.name}" is a link child of both "${earlier}" and "${t.name}"
|
|
24406
|
+
errors.push(`Table "${child.name}" is a link child of both "${earlier}" and "${t.name}"; a table is a link child of one table only.`);
|
|
24341
24407
|
continue;
|
|
24342
24408
|
}
|
|
24343
24409
|
childOf.set(child.name, t.name);
|
|
@@ -24386,7 +24452,7 @@ function validateIntake(tables, tableByName, errors) {
|
|
|
24386
24452
|
continue;
|
|
24387
24453
|
}
|
|
24388
24454
|
if (column.helper !== "id" && !column.unique) {
|
|
24389
|
-
errors.push(`${at2} names reference "${field}", which is not unique on "${target.name}".
|
|
24455
|
+
errors.push(`${at2} names reference "${field}", which is not unique on "${target.name}". A reference must name one row, so the field needs { unique: true }.`);
|
|
24390
24456
|
continue;
|
|
24391
24457
|
}
|
|
24392
24458
|
if (column.nullable) {
|
|
@@ -24500,7 +24566,7 @@ function readFileOwner(r, fail, name) {
|
|
|
24500
24566
|
r.expectPunct("{", `in owner() for file collection "${name}"`);
|
|
24501
24567
|
const key = r.readKey(`in owner() for file collection "${name}"`);
|
|
24502
24568
|
if (key.name === "visitors") {
|
|
24503
|
-
fail(key.line, `owner({ visitors: true }) is not available for file collections yet: an upload needs a signed-in account
|
|
24569
|
+
fail(key.line, `owner({ visitors: true }) is not available for file collections yet: an upload needs a signed-in account, so file collections take owner().`);
|
|
24504
24570
|
}
|
|
24505
24571
|
fail(key.line, `owner() on file collection "${name}" takes no options \u2014 the owner is always the signed-in uploader.`);
|
|
24506
24572
|
}
|
|
@@ -24563,7 +24629,7 @@ function readFileScope(r, fail, name) {
|
|
|
24563
24629
|
if (owners !== 1 || !member) fail(tok.line, `anyOf() on file collection "${name}" requires exactly one owner() and one member().`);
|
|
24564
24630
|
return { kind: "owner_or_member", member };
|
|
24565
24631
|
}
|
|
24566
|
-
return fail(tok.line, `unknown scope "${tok.value}" on file collection "${name}".
|
|
24632
|
+
return fail(tok.line, `unknown scope "${tok.value}" on file collection "${name}". File collection scopes are owner(), member(), anyOf(owner(), member()), parent({ table, via }), shared(), and serverOnly().`);
|
|
24567
24633
|
}
|
|
24568
24634
|
function readCollection(r, fail, name, errors) {
|
|
24569
24635
|
const callee = r.expectIdent(`for file collection "${name}"`);
|
|
@@ -24617,11 +24683,11 @@ function readCollection(r, fail, name, errors) {
|
|
|
24617
24683
|
if (limit.name === "maxSize") {
|
|
24618
24684
|
const raw = r.readLiteral(`for limits.maxSize of "${name}"`);
|
|
24619
24685
|
limits.maxSize = parseFileSize(raw);
|
|
24620
|
-
if (limits.maxSize === null) errors.push(`File collection "${name}" has an invalid limits.maxSize:
|
|
24686
|
+
if (limits.maxSize === null) errors.push(`File collection "${name}" has an invalid limits.maxSize: it takes a whole number of bytes or a size such as '25MB'.`);
|
|
24621
24687
|
} else {
|
|
24622
24688
|
limits.types = readStringList(r, fail, `limits.types of file collection "${name}"`).map((t) => t.toLowerCase());
|
|
24623
24689
|
for (const type of limits.types) {
|
|
24624
|
-
if (!MIME_PATTERN.test(type)) errors.push(`File collection "${name}" has an invalid limits.types entry "${type}":
|
|
24690
|
+
if (!MIME_PATTERN.test(type)) errors.push(`File collection "${name}" has an invalid limits.types entry "${type}": it takes a content type such as 'image/png' or 'image/*'.`);
|
|
24625
24691
|
}
|
|
24626
24692
|
}
|
|
24627
24693
|
if (!r.tryPunct(",")) {
|
|
@@ -24642,16 +24708,16 @@ function readCollection(r, fail, name, errors) {
|
|
|
24642
24708
|
if (rootError) errors.push(rootError);
|
|
24643
24709
|
const declaresClient = FILE_OPERATIONS.some((op) => client[op]);
|
|
24644
24710
|
if (scope.kind === "server_only" && declaresClient) {
|
|
24645
|
-
errors.push(`File collection "${name}" is serverOnly() and cannot declare client operations
|
|
24711
|
+
errors.push(`File collection "${name}" is serverOnly() and cannot declare client operations; client operations need a scope the browser may use.`);
|
|
24646
24712
|
}
|
|
24647
24713
|
if (isPublic && client.read) {
|
|
24648
|
-
errors.push(`File collection "${name}" is public: true and also declares client.read. Public already lets anyone read
|
|
24714
|
+
errors.push(`File collection "${name}" is public: true and also declares client.read. Public already lets anyone read, so client.read is not accepted with it.`);
|
|
24649
24715
|
}
|
|
24650
24716
|
if (isPublic && (scope.kind === "member" || scope.kind === "owner_or_member")) {
|
|
24651
|
-
errors.push(`File collection "${name}" is public: true on a member() scope, which would let anyone read team files.
|
|
24717
|
+
errors.push(`File collection "${name}" is public: true on a member() scope, which would let anyone read team files. public: true is accepted with owner() or serverOnly().`);
|
|
24652
24718
|
}
|
|
24653
24719
|
if (isPublic && scope.kind === "parent") {
|
|
24654
|
-
errors.push(`File collection "${name}" is public: true on a parent() scope, which would let anyone read files that follow a private row
|
|
24720
|
+
errors.push(`File collection "${name}" is public: true on a parent() scope, which would let anyone read files that follow a private row, so public: true is not accepted there.`);
|
|
24655
24721
|
}
|
|
24656
24722
|
return { name, path: typeof path === "string" ? path : "", scope, public: isPublic, client, limits };
|
|
24657
24723
|
}
|
|
@@ -24662,7 +24728,7 @@ function readFileCollections(r, fail, errors) {
|
|
|
24662
24728
|
while (!r.tryPunct("}")) {
|
|
24663
24729
|
const key = r.readKey("for a file collection name");
|
|
24664
24730
|
if (!COLLECTION_NAME.test(key.name)) {
|
|
24665
|
-
errors.push(`File collection name "${key.name}" is invalid.
|
|
24731
|
+
errors.push(`File collection name "${key.name}" is invalid. A name is lowercase letters, digits and underscores, starting with a letter.`);
|
|
24666
24732
|
}
|
|
24667
24733
|
if (out.some((c) => c.name === key.name)) fail(key.line, `file collection "${key.name}" is declared twice.`);
|
|
24668
24734
|
r.expectPunct(":", `after file collection "${key.name}"`);
|
|
@@ -24699,7 +24765,7 @@ function validateFileCollections(collections, table, errors) {
|
|
|
24699
24765
|
continue;
|
|
24700
24766
|
}
|
|
24701
24767
|
if (facts.edges + 1 > MAX_FILE_PARENT_EDGES) {
|
|
24702
|
-
errors.push(`File collection "${a.name}" uses parent({ table: '${parent.table}' }), which makes a chain of ${facts.edges + 1} parent edges counting the file. At most ${MAX_FILE_PARENT_EDGES} are allowed;
|
|
24768
|
+
errors.push(`File collection "${a.name}" uses parent({ table: '${parent.table}' }), which makes a chain of ${facts.edges + 1} parent edges counting the file. At most ${MAX_FILE_PARENT_EDGES} are allowed; a table nearer the root of the chain has fewer edges.`);
|
|
24703
24769
|
continue;
|
|
24704
24770
|
}
|
|
24705
24771
|
parent.key = facts.key;
|
|
@@ -24714,7 +24780,7 @@ function validateFileCollections(collections, table, errors) {
|
|
|
24714
24780
|
continue;
|
|
24715
24781
|
}
|
|
24716
24782
|
if (membership.clientWrites) {
|
|
24717
|
-
errors.push(`Table "${m.membership}" grants membership access to file collection "${a.name}" and cannot allow client writes
|
|
24783
|
+
errors.push(`Table "${m.membership}" grants membership access to file collection "${a.name}" and cannot allow client writes; membership changes go through a server function.`);
|
|
24718
24784
|
}
|
|
24719
24785
|
if (!membership.columns.has(m.memberUser)) errors.push(`The membership table "${m.membership}" (for file collection "${a.name}") has no "${m.memberUser}" column named in member({ member_user }).`);
|
|
24720
24786
|
if (!membership.columns.has(m.memberGroup)) errors.push(`The membership table "${m.membership}" (for file collection "${a.name}") has no "${m.memberGroup}" column named in member({ member_group }).`);
|
|
@@ -24746,7 +24812,7 @@ function roleVocabularyErrors(policy) {
|
|
|
24746
24812
|
dupes(policy.adminGrantableGroupRoles, "groups.adminGrantableRoles");
|
|
24747
24813
|
dupes(policy.appRoles, "appRoles");
|
|
24748
24814
|
for (const role of policy.groupRoles) {
|
|
24749
|
-
if (BUILT_IN_GROUP_ROLES2.includes(role)) errors.push(`"${role}" is a built-in group role;
|
|
24815
|
+
if (BUILT_IN_GROUP_ROLES2.includes(role)) errors.push(`"${role}" is a built-in group role; groups.roles lists only additional roles.`);
|
|
24750
24816
|
}
|
|
24751
24817
|
for (const role of policy.adminGrantableGroupRoles) {
|
|
24752
24818
|
if (!policy.groupRoles.includes(role)) errors.push(`groups.adminGrantableRoles names "${role}", which is not in groups.roles.`);
|
|
@@ -24855,7 +24921,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
|
|
|
24855
24921
|
const where = `line ${declared.line}: `;
|
|
24856
24922
|
const column = t.columns.find((c) => c.name === node.column);
|
|
24857
24923
|
if (!column) {
|
|
24858
|
-
errors.push(`${where}Table "${t.name}" uses group({ column: '${node.column}' }), but "${node.column}" is not one of its declared columns
|
|
24924
|
+
errors.push(`${where}Table "${t.name}" uses group({ column: '${node.column}' }), but "${node.column}" is not one of its declared columns; it needs ${node.column}: text().`);
|
|
24859
24925
|
} else if (column.helper !== "text" || column.nullable) {
|
|
24860
24926
|
errors.push(`${where}Table "${t.name}" uses group({ column: '${node.column}' }), which must be a non-nullable text() column: every row belongs to exactly one group.`);
|
|
24861
24927
|
}
|
|
@@ -24863,7 +24929,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
|
|
|
24863
24929
|
const list = declared.explicit[op] ?? omitted;
|
|
24864
24930
|
for (const role of list) {
|
|
24865
24931
|
if (!allRoles.includes(role)) {
|
|
24866
|
-
errors.push(`${where}group({ roles: { ${op} } }) on table "${t.name}" names "${role}", which is not a group role.
|
|
24932
|
+
errors.push(`${where}group({ roles: { ${op} } }) on table "${t.name}" names "${role}", which is not a group role. Group roles are owner, admin, member, and the roles declared in groups.roles.`);
|
|
24867
24933
|
}
|
|
24868
24934
|
}
|
|
24869
24935
|
node.roles[op] = sorted(list);
|
|
@@ -24875,7 +24941,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
|
|
|
24875
24941
|
}
|
|
24876
24942
|
}
|
|
24877
24943
|
if ((branch || allOf) && GROUP_OPERATIONS.every((op) => node.roles[op].length === 0)) {
|
|
24878
|
-
errors.push(`${where}group() inside ${allOf ? "allOf()" : "anyOf()"} on table "${t.name}" grants no operation
|
|
24944
|
+
errors.push(`${where}group() inside ${allOf ? "allOf()" : "anyOf()"} on table "${t.name}" grants no operation; it needs the group roles that may read, update or delete every row of the group.`);
|
|
24879
24945
|
}
|
|
24880
24946
|
if (!(branch && allOf) && t.client?.create && !t.client.create.includes(node.column)) {
|
|
24881
24947
|
errors.push(`${where}Table "${t.name}" uses group() and client.create must include "${node.column}" so every new row names the group it belongs to.`);
|
|
@@ -25000,7 +25066,7 @@ function resolveAppRoleScopes(tables, appRoles, errors) {
|
|
|
25000
25066
|
if (!declared) continue;
|
|
25001
25067
|
for (const { op, name, line } of declared.names) {
|
|
25002
25068
|
if (!vocabulary.has(name)) {
|
|
25003
|
-
errors.push(`line ${line}: appRole({ roles: { ${op} } }) on table "${t.name}" names "${name}", which is not declared in the schema's appRoles option.
|
|
25069
|
+
errors.push(`line ${line}: appRole({ roles: { ${op} } }) on table "${t.name}" names "${name}", which is not declared in the schema's appRoles option. appRole() accepts only app roles declared there; group roles and admin do not apply.`);
|
|
25004
25070
|
}
|
|
25005
25071
|
}
|
|
25006
25072
|
for (const op of ["update", "delete"]) {
|
|
@@ -25010,7 +25076,7 @@ function resolveAppRoleScopes(tables, appRoles, errors) {
|
|
|
25010
25076
|
}
|
|
25011
25077
|
}
|
|
25012
25078
|
if (branch && APP_ROLE_OPERATIONS.every((op) => node.roles[op].length === 0)) {
|
|
25013
|
-
errors.push(`line ${declared.line}: appRole() inside ${root.kind === "all_of" ? "allOf()" : "anyOf()"} on table "${t.name}" grants no operation
|
|
25079
|
+
errors.push(`line ${declared.line}: appRole() inside ${root.kind === "all_of" ? "allOf()" : "anyOf()"} on table "${t.name}" grants no operation; it needs the app roles that may read, update or delete every row.`);
|
|
25014
25080
|
}
|
|
25015
25081
|
declaredLines.delete(node);
|
|
25016
25082
|
}
|
|
@@ -25063,7 +25129,7 @@ function readAppRoles(r, fail) {
|
|
|
25063
25129
|
const key = r.readKey("for an app role name");
|
|
25064
25130
|
if (out.some((role2) => role2.name === key.name)) fail(key.line, `app role "${key.name}" is declared twice.`);
|
|
25065
25131
|
r.expectPunct(":", `after app role "${key.name}"`);
|
|
25066
|
-
r.expectPunct("{", `opening app role "${key.name}"
|
|
25132
|
+
r.expectPunct("{", `opening app role "${key.name}", which takes { grantableBy: [...] }`);
|
|
25067
25133
|
const role = { name: key.name, grantableBy: [] };
|
|
25068
25134
|
const seen = /* @__PURE__ */ new Set();
|
|
25069
25135
|
while (!r.tryPunct("}")) {
|
|
@@ -25230,7 +25296,7 @@ function tokenize2(source) {
|
|
|
25230
25296
|
continue;
|
|
25231
25297
|
}
|
|
25232
25298
|
if (ch === "`") {
|
|
25233
|
-
throw new SchemaTsError(`line ${line}: template literals are not allowed in db/schema.ts
|
|
25299
|
+
throw new SchemaTsError(`line ${line}: template literals are not allowed in db/schema.ts; strings are plain 'single-quoted' strings.`);
|
|
25234
25300
|
}
|
|
25235
25301
|
if (ch === "'" || ch === '"') {
|
|
25236
25302
|
const startLine = line;
|
|
@@ -25431,11 +25497,11 @@ function checkDeclaredName(kind, raw, line) {
|
|
|
25431
25497
|
throw new SchemaTsError(`line ${line}: ${kind} name "${raw.slice(0, 40)}\u2026" is longer than ${MAX_NAME_LENGTH} characters.`);
|
|
25432
25498
|
}
|
|
25433
25499
|
if (!SAFE_IDENT.test(raw)) {
|
|
25434
|
-
throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" is invalid.
|
|
25500
|
+
throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" is invalid. A name is letters, digits, and underscores, starting with a letter or underscore.`);
|
|
25435
25501
|
}
|
|
25436
25502
|
const name = raw.toLowerCase();
|
|
25437
25503
|
if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
|
|
25438
|
-
throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" uses a reserved platform prefix and cannot be declared
|
|
25504
|
+
throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" uses a reserved platform prefix and cannot be declared: names starting with "app_" or "_" belong to the platform.`);
|
|
25439
25505
|
}
|
|
25440
25506
|
return name;
|
|
25441
25507
|
}
|
|
@@ -25449,12 +25515,12 @@ function readColumn(r, tableName, rawName, line) {
|
|
|
25449
25515
|
}
|
|
25450
25516
|
if (TABLE_MARKER_HELPERS.has(helper)) {
|
|
25451
25517
|
throw new SchemaTsError(
|
|
25452
|
-
`line ${helperTok.line}: "${tableName}"."${rawName}" uses ${helper}(), which marks a whole table, not a column. Removing columns from the live database is not supported yet, so
|
|
25518
|
+
`line ${helperTok.line}: "${tableName}"."${rawName}" uses ${helper}(), which marks a whole table, not a column. Removing columns from the live database is not supported yet, so "${rawName}" stays declared with its column type.`
|
|
25453
25519
|
);
|
|
25454
25520
|
}
|
|
25455
25521
|
if (!COLUMN_HELPERS.has(helper)) {
|
|
25456
25522
|
throw new SchemaTsError(
|
|
25457
|
-
`line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}".
|
|
25523
|
+
`line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Column types are id, text, number, integer, bigint, boolean, timestamp, json, and blob.`
|
|
25458
25524
|
);
|
|
25459
25525
|
}
|
|
25460
25526
|
r.expectPunct("(", `after "${helper}"`);
|
|
@@ -25511,7 +25577,7 @@ function readColumn(r, tableName, rawName, line) {
|
|
|
25511
25577
|
);
|
|
25512
25578
|
}
|
|
25513
25579
|
if (t === "integer" && typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
25514
|
-
throw new SchemaTsError(Number.isInteger(value) ? `line ${optLine}: the default for integer column "${tableName}"."${rawName}" is outside JavaScript's exact whole-number range (\xB19007199254740991)
|
|
25580
|
+
throw new SchemaTsError(Number.isInteger(value) ? `line ${optLine}: the default for integer column "${tableName}"."${rawName}" is outside JavaScript's exact whole-number range (\xB19007199254740991); a bigint() column takes such a default written as a string.` : `line ${optLine}: the default for integer column "${tableName}"."${rawName}" must be a whole number.`);
|
|
25515
25581
|
}
|
|
25516
25582
|
if (t === "bigint" && typeof value === "string" && !isCanonicalBigintText(value)) {
|
|
25517
25583
|
throw new SchemaTsError(`line ${optLine}: the default for bigint column "${tableName}"."${rawName}" must be a whole number between -9223372036854775808 and 9223372036854775807, written as a string with no sign other than a leading minus and no leading zeros.`);
|
|
@@ -25551,7 +25617,7 @@ function readColumn(r, tableName, rawName, line) {
|
|
|
25551
25617
|
if (col.hasDefault && col.default === null) {
|
|
25552
25618
|
if (!col.nullable) {
|
|
25553
25619
|
throw new SchemaTsError(
|
|
25554
|
-
`line ${line}: "${tableName}"."${rawName}" is required but declares { default: null } \u2014 a required column can never hold null.
|
|
25620
|
+
`line ${line}: "${tableName}"."${rawName}" is required but declares { default: null } \u2014 a required column can never hold null. It is accepted with a non-null default or { nullable: true }.`
|
|
25555
25621
|
);
|
|
25556
25622
|
}
|
|
25557
25623
|
col.hasDefault = false;
|
|
@@ -25794,7 +25860,7 @@ function readRelations(r, tableName) {
|
|
|
25794
25860
|
while (!r.tryPunct("}")) {
|
|
25795
25861
|
const key = r.readKey(`for a relation name of table "${tableName}"`);
|
|
25796
25862
|
if (!SAFE_IDENT.test(key.name) || key.name.length > MAX_NAME_LENGTH) {
|
|
25797
|
-
throw new SchemaTsError(`line ${key.line}: relation name "${key.name}" on table "${tableName}" is invalid.
|
|
25863
|
+
throw new SchemaTsError(`line ${key.line}: relation name "${key.name}" on table "${tableName}" is invalid. A name is letters, digits, and underscores, starting with a letter or underscore.`);
|
|
25798
25864
|
}
|
|
25799
25865
|
const name = key.name.toLowerCase();
|
|
25800
25866
|
if (seen.has(name)) throw new SchemaTsError(`line ${key.line}: relation "${key.name}" on table "${tableName}" is declared twice.`);
|
|
@@ -25802,7 +25868,7 @@ function readRelations(r, tableName) {
|
|
|
25802
25868
|
r.expectPunct(":", `after relation "${key.name}" on table "${tableName}"`);
|
|
25803
25869
|
const callee = r.expectIdent(`for relation "${key.name}" on table "${tableName}"`);
|
|
25804
25870
|
if (callee.value !== "hasMany" && callee.value !== "belongsTo") {
|
|
25805
|
-
throw new SchemaTsError(`line ${callee.line}: relation "${key.name}" on table "${tableName}" uses "${callee.value}()", which is not supported.
|
|
25871
|
+
throw new SchemaTsError(`line ${callee.line}: relation "${key.name}" on table "${tableName}" uses "${callee.value}()", which is not supported. Relations are hasMany('child_table', 'foreign_key') and belongsTo('parent_table', 'foreign_key').`);
|
|
25806
25872
|
}
|
|
25807
25873
|
const kind = callee.value;
|
|
25808
25874
|
const side = kind === "hasMany" ? "child" : "parent";
|
|
@@ -25835,7 +25901,7 @@ function readScope(r, tableName) {
|
|
|
25835
25901
|
const tok = r.expectIdent(`for the scope of table "${tableName}"`);
|
|
25836
25902
|
if (!SCOPE_HELPERS.has(tok.value)) {
|
|
25837
25903
|
throw new SchemaTsError(
|
|
25838
|
-
`line ${tok.line}: unknown scope "${tok.value}" on table "${tableName}".
|
|
25904
|
+
`line ${tok.line}: unknown scope "${tok.value}" on table "${tableName}". Table scopes are owner(), group(), appRole(), member(), anyOf(), allOf(), parent(), shared(), and serverOnly().`
|
|
25839
25905
|
);
|
|
25840
25906
|
}
|
|
25841
25907
|
if (tok.value === "anyOf") return readAnyOfScope(r, tableName, tok.line);
|
|
@@ -25930,7 +25996,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
25930
25996
|
const callee = r.expectIdent(`for table "${rawName}"`);
|
|
25931
25997
|
if (callee.value === "removed") {
|
|
25932
25998
|
throw new SchemaTsError(
|
|
25933
|
-
`line ${callee.line}: table "${rawName}" uses removed(), which marks a single column
|
|
25999
|
+
`line ${callee.line}: table "${rawName}" uses removed(), which marks a single column; a whole table is removed with ${rawName}: removedTable().`
|
|
25934
26000
|
);
|
|
25935
26001
|
}
|
|
25936
26002
|
if (TABLE_MARKER_HELPERS.has(callee.value)) {
|
|
@@ -25967,7 +26033,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
25967
26033
|
}
|
|
25968
26034
|
}
|
|
25969
26035
|
if (columns.length === 0) {
|
|
25970
|
-
throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares no columns${removedColumns.length > 0 ? " (removal markers do not count \u2014 a table needs at least one kept column, or
|
|
26036
|
+
throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares no columns${removedColumns.length > 0 ? " (removal markers do not count \u2014 a table needs at least one kept column, or the whole table marked removedTable())" : ""}.`);
|
|
25971
26037
|
}
|
|
25972
26038
|
if (columns.length > MAX_COLUMNS) {
|
|
25973
26039
|
throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares ${columns.length} columns (max ${MAX_COLUMNS}).`);
|
|
@@ -26025,7 +26091,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
26025
26091
|
const clash = columns.find((c) => c.name === managedOwner.column);
|
|
26026
26092
|
if (clash) {
|
|
26027
26093
|
throw new SchemaTsError(
|
|
26028
|
-
`line ${nameLine}: table "${rawName}" declares the column "${managedOwner.column}", but that is its owner column, which the platform creates and manages
|
|
26094
|
+
`line ${nameLine}: table "${rawName}" declares the column "${managedOwner.column}", but that is its owner column, which the platform creates and manages, so it is not declared among the columns; owner({ column: '\u2026' }) names a different owner column.`
|
|
26029
26095
|
);
|
|
26030
26096
|
}
|
|
26031
26097
|
if (removedColumns.includes(managedOwner.column)) {
|
|
@@ -26040,7 +26106,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
26040
26106
|
if (!c.renamedFrom) continue;
|
|
26041
26107
|
if (declaredNow.has(c.renamedFrom)) {
|
|
26042
26108
|
throw new SchemaTsError(
|
|
26043
|
-
`line ${nameLine}: "${rawName}"."${c.name}" declares renamedFrom: '${c.renamedFrom}', but "${c.renamedFrom}" is still declared as a column. A rename replaces the old column
|
|
26109
|
+
`line ${nameLine}: "${rawName}"."${c.name}" declares renamedFrom: '${c.renamedFrom}', but "${c.renamedFrom}" is still declared as a column. A rename replaces the old column, so the old declaration is not accepted alongside it.`
|
|
26044
26110
|
);
|
|
26045
26111
|
}
|
|
26046
26112
|
if (managedOwner && c.renamedFrom === managedOwner.column) {
|
|
@@ -26050,7 +26116,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
26050
26116
|
}
|
|
26051
26117
|
if (removedColumns.includes(c.renamedFrom)) {
|
|
26052
26118
|
throw new SchemaTsError(
|
|
26053
|
-
`line ${nameLine}: "${rawName}"."${c.renamedFrom}" is marked removed() AND named by renamedFrom on "${c.name}" \u2014 it cannot be both removed and renamed
|
|
26119
|
+
`line ${nameLine}: "${rawName}"."${c.renamedFrom}" is marked removed() AND named by renamedFrom on "${c.name}" \u2014 it cannot be both removed and renamed.`
|
|
26054
26120
|
);
|
|
26055
26121
|
}
|
|
26056
26122
|
if (renameSources.has(c.renamedFrom)) {
|
|
@@ -26085,7 +26151,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
26085
26151
|
const single = entry.columns.length === 1 ? columns.find((c) => c.name === entry.columns[0]) : void 0;
|
|
26086
26152
|
if (single) {
|
|
26087
26153
|
if (single.helper === "id") {
|
|
26088
|
-
throw new SchemaTsError(`line ${entry.line}: the primary key "${single.name}" on table "${rawName}" is already unique
|
|
26154
|
+
throw new SchemaTsError(`line ${entry.line}: the primary key "${single.name}" on table "${rawName}" is already unique, so a one-column "unique" group on it is not accepted.`);
|
|
26089
26155
|
}
|
|
26090
26156
|
single.unique = true;
|
|
26091
26157
|
continue;
|
|
@@ -26120,7 +26186,7 @@ function readTable(r, rawName, nameLine) {
|
|
|
26120
26186
|
}
|
|
26121
26187
|
function extractSchemaTs(source, options = {}) {
|
|
26122
26188
|
if (typeof source !== "string" || source.trim().length === 0) {
|
|
26123
|
-
return { ok: false, errors: ["db/schema.ts is empty.
|
|
26189
|
+
return { ok: false, errors: ["db/schema.ts is empty. It needs at least one table; without the file, the database is unmanaged."] };
|
|
26124
26190
|
}
|
|
26125
26191
|
if (source.length > MAX_SOURCE_BYTES) {
|
|
26126
26192
|
return { ok: false, errors: [`db/schema.ts is larger than ${Math.floor(MAX_SOURCE_BYTES / 1024)}KB \u2014 that is not a schema declaration.`] };
|
|
@@ -26179,7 +26245,7 @@ function extractSchemaTs(source, options = {}) {
|
|
|
26179
26245
|
);
|
|
26180
26246
|
}
|
|
26181
26247
|
if (tables.length === 0 && removedTables.length === 0 && exportedTables.length === 0 && !managedSearch && !fileCollections?.length) {
|
|
26182
|
-
return { ok: false, errors: ["db/schema.ts declares no tables.
|
|
26248
|
+
return { ok: false, errors: ["db/schema.ts declares no tables. It needs at least one table; without the file, the database is unmanaged."] };
|
|
26183
26249
|
}
|
|
26184
26250
|
if (tables.length > MAX_TABLES) {
|
|
26185
26251
|
return { ok: false, errors: [`db/schema.ts declares ${tables.length} tables (max ${MAX_TABLES}).`] };
|
|
@@ -26191,7 +26257,7 @@ function extractSchemaTs(source, options = {}) {
|
|
|
26191
26257
|
if (c.references && !names.has(c.references)) {
|
|
26192
26258
|
const marked = removedTables.includes(c.references) || exportedTables.includes(c.references);
|
|
26193
26259
|
errors.push(
|
|
26194
|
-
marked ? `Column "${t.name}"."${c.name}" references "${c.references}", which this file marks ${removedTables.includes(c.references) ? "removedTable()" : "exported()"}.
|
|
26260
|
+
marked ? `Column "${t.name}"."${c.name}" references "${c.references}", which this file marks ${removedTables.includes(c.references) ? "removedTable()" : "exported()"}. The table can go only once no column references it (the column marked removed(), or without "references").` : `Column "${t.name}"."${c.name}" references "${c.references}", which is not declared in db/schema.ts. Foreign keys can only target tables in the same declaration.`
|
|
26195
26261
|
);
|
|
26196
26262
|
}
|
|
26197
26263
|
}
|
|
@@ -26203,7 +26269,7 @@ function extractSchemaTs(source, options = {}) {
|
|
|
26203
26269
|
if (!key) continue;
|
|
26204
26270
|
const fits = key.uuid ? c.helper === "text" : c.helper === "integer" || c.helper === "number";
|
|
26205
26271
|
if (fits) continue;
|
|
26206
|
-
errors.push(key.uuid ? `Column "${t.name}"."${c.name}" references "${c.references}", whose id() is a random text key, but is declared ${c.helper}().
|
|
26272
|
+
errors.push(key.uuid ? `Column "${t.name}"."${c.name}" references "${c.references}", whose id() is a random text key, but is declared ${c.helper}(). The column type must match the key: text({ references: '${c.references}' }), or an integer key on "${c.references}" with id({ uuid: false }).` : `Column "${t.name}"."${c.name}" references "${c.references}", whose key is an integer (id({ uuid: false })), but is declared ${c.helper}(). The column type must match the key: integer({ references: '${c.references}' }), or a random text key on "${c.references}" with id().`);
|
|
26207
26273
|
}
|
|
26208
26274
|
}
|
|
26209
26275
|
const tableByName = new Map(tables.map((t) => [t.name, t]));
|
|
@@ -26254,7 +26320,7 @@ function extractSchemaTs(source, options = {}) {
|
|
|
26254
26320
|
}
|
|
26255
26321
|
const membershipCols = knownColumns(membershipTable);
|
|
26256
26322
|
if (membershipTable.client && (membershipTable.client.create !== null || membershipTable.client.update !== null || membershipTable.client.delete || membershipTable.client.intake !== void 0)) {
|
|
26257
|
-
errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes
|
|
26323
|
+
errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes; membership changes go through a server function.`);
|
|
26258
26324
|
}
|
|
26259
26325
|
if (!membershipCols.has(sc.memberUser)) {
|
|
26260
26326
|
errors.push(
|
|
@@ -26317,6 +26383,8 @@ var MAX_FINDINGS_PER_FILE = 20;
|
|
|
26317
26383
|
var MAX_FINDINGS = 100;
|
|
26318
26384
|
var IDENT2 = /^[A-Za-z_$][\w$]*$/;
|
|
26319
26385
|
var WRITE = /\.(insert|update)\(\s*(['"`])([A-Za-z_]\w{0,63})\2\s*,\s*/g;
|
|
26386
|
+
var TX = /\.tx\(/g;
|
|
26387
|
+
var STEP = /\{(?=\s*['"]?(?:op|table)['"]?\s*:)/g;
|
|
26320
26388
|
var VERIFIED = /\b(?:verifyInvocation|verifyWebhook|constructEvent|verifySignature)\s*\(/;
|
|
26321
26389
|
var VERIFIER_NAMES = /* @__PURE__ */ new Set(["verifyInvocation", "verifyWebhook", "constructEvent", "verifySignature"]);
|
|
26322
26390
|
function hasVerifierCall(source) {
|
|
@@ -26360,7 +26428,7 @@ function stripComments(source) {
|
|
|
26360
26428
|
return out;
|
|
26361
26429
|
}
|
|
26362
26430
|
function bindings(source) {
|
|
26363
|
-
const b = { objects: /* @__PURE__ */ new Set(), forms: /* @__PURE__ */ new Set(), values: /* @__PURE__ */ new Set(), callers: /* @__PURE__ */ new Set() };
|
|
26431
|
+
const b = { objects: /* @__PURE__ */ new Set(), paths: /* @__PURE__ */ new Set(), forms: /* @__PURE__ */ new Set(), values: /* @__PURE__ */ new Set(), callers: /* @__PURE__ */ new Set() };
|
|
26364
26432
|
const input = String.raw`\(?\s*(?:await\s+)?[\w$.]{1,64}\.(json|formData)\(\)`;
|
|
26365
26433
|
for (const m of source.matchAll(new RegExp(String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=;\n]{0,200})?=\s*${input}`, "g"))) {
|
|
26366
26434
|
(m[2] === "formData" ? b.forms : b.objects).add(m[1]);
|
|
@@ -26381,8 +26449,27 @@ function bindings(source) {
|
|
|
26381
26449
|
if (name && IDENT2.test(name)) b.callers.add(name);
|
|
26382
26450
|
}
|
|
26383
26451
|
}
|
|
26452
|
+
if (/\bsw\.endpoint\s*\(/.test(source)) {
|
|
26453
|
+
for (const m of source.matchAll(/\bhandler\s*(?::\s*)?(?:async\s*)?(?:function\s*)?\(\s*(?:\{([^{}]{0,400})\}|([A-Za-z_$][\w$]*))/g)) {
|
|
26454
|
+
if (m[2]) {
|
|
26455
|
+
b.paths.add(m[2] + ".body");
|
|
26456
|
+
continue;
|
|
26457
|
+
}
|
|
26458
|
+
for (const part of m[1].split(",")) {
|
|
26459
|
+
const [key, renamed] = part.split("=")[0].split(":").map((x) => x.trim());
|
|
26460
|
+
const local = renamed || key;
|
|
26461
|
+
if (!local || !IDENT2.test(local)) continue;
|
|
26462
|
+
if (key === "body") b.objects.add(local);
|
|
26463
|
+
else if (key === "user") b.callers.add(local);
|
|
26464
|
+
}
|
|
26465
|
+
}
|
|
26466
|
+
}
|
|
26384
26467
|
return b;
|
|
26385
26468
|
}
|
|
26469
|
+
function requestObject(expression, b) {
|
|
26470
|
+
const v = expression.trim();
|
|
26471
|
+
return b.objects.has(v) || b.paths.has(v);
|
|
26472
|
+
}
|
|
26386
26473
|
function serverReceivers(source) {
|
|
26387
26474
|
const receivers = ["sw.db.server"];
|
|
26388
26475
|
for (const m of source.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*sw\.db\.server\b(?!\.)/g)) receivers.push(m[1]);
|
|
@@ -26450,8 +26537,10 @@ function lineAt2(starts, offset2) {
|
|
|
26450
26537
|
}
|
|
26451
26538
|
function requestValue(value, b) {
|
|
26452
26539
|
const v = value.trim().replace(/^(?:String|Number|BigInt)\(\s*([\s\S]*?)\s*\)$/, "$1").trim();
|
|
26453
|
-
const member = /^([A-Za-z_$][\w$]*)(?:\??\.[A-Za-z_$][\w$]*|\[[^\]]{1,64}\])$/.exec(v);
|
|
26454
|
-
if (member && b.objects.has(member[1])) return true;
|
|
26540
|
+
const member = /^([A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*)?)(?:\??\.[A-Za-z_$][\w$]*|\[[^\]]{1,64}\])$/.exec(v);
|
|
26541
|
+
if (member && (b.objects.has(member[1]) || b.paths.has(member[1].replace("?.", ".")))) return true;
|
|
26542
|
+
const root = /^([A-Za-z_$][\w$]*)(?:\??\.[A-Za-z_$][\w$]*|\[[^\]]{1,64}\])$/.exec(v);
|
|
26543
|
+
if (root && b.objects.has(root[1])) return true;
|
|
26455
26544
|
const form = /^([A-Za-z_$][\w$]*)\.get\(\s*['"`][\w-]{1,64}['"`]\s*\)$/.exec(v);
|
|
26456
26545
|
if (form && b.forms.has(form[1])) return true;
|
|
26457
26546
|
return IDENT2.test(v) && b.values.has(v.toLowerCase());
|
|
@@ -26516,7 +26605,7 @@ function requestOwnershipFindings(functions, schema) {
|
|
|
26516
26605
|
}
|
|
26517
26606
|
}
|
|
26518
26607
|
const b = bindings(source);
|
|
26519
|
-
if (b.objects.size === 0 && b.forms.size === 0 && b.values.size === 0) continue;
|
|
26608
|
+
if (b.objects.size === 0 && b.paths.size === 0 && b.forms.size === 0 && b.values.size === 0) continue;
|
|
26520
26609
|
const lower = source.toLowerCase();
|
|
26521
26610
|
const receivers = serverReceivers(source);
|
|
26522
26611
|
const starts = lineIndex(source);
|
|
@@ -26554,31 +26643,27 @@ function requestOwnershipFindings(functions, schema) {
|
|
|
26554
26643
|
columnsByTable.set(table, columns);
|
|
26555
26644
|
return columns;
|
|
26556
26645
|
};
|
|
26557
|
-
|
|
26558
|
-
if (fileFull) break;
|
|
26559
|
-
if (!receivedBy(source, m.index, receivers)) continue;
|
|
26560
|
-
const table = m[3].toLowerCase();
|
|
26646
|
+
const inspect = (table, kind, at2) => {
|
|
26561
26647
|
const columns = ownershipColumns(table);
|
|
26562
|
-
if (columns.length === 0)
|
|
26563
|
-
const
|
|
26564
|
-
|
|
26565
|
-
if (bare && b.objects.has(bare[1])) {
|
|
26648
|
+
if (columns.length === 0) return;
|
|
26649
|
+
const bare = /^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)\s*[,)}]/.exec(source.slice(at2, at2 + 80));
|
|
26650
|
+
if (bare && requestObject(bare[1], b)) {
|
|
26566
26651
|
report(table, columns, at2);
|
|
26567
|
-
|
|
26652
|
+
return;
|
|
26568
26653
|
}
|
|
26569
26654
|
const payload = objectLiteral(source, at2);
|
|
26570
26655
|
if (!payload) {
|
|
26571
26656
|
if (source[at2] === "{") truncated = true;
|
|
26572
|
-
|
|
26657
|
+
return;
|
|
26573
26658
|
}
|
|
26574
26659
|
let list = entries(payload);
|
|
26575
|
-
if (
|
|
26660
|
+
if (kind === "update") {
|
|
26576
26661
|
list = list.flatMap((e) => {
|
|
26577
26662
|
if (e.key !== "set" && e.key !== "where") return [e];
|
|
26578
26663
|
const valueAt = payload.indexOf(e.value, e.offset);
|
|
26579
26664
|
const inner = valueAt < 0 ? null : objectLiteral(payload, valueAt);
|
|
26580
26665
|
return inner ? entries(inner).map((x) => ({ ...x, offset: valueAt + x.offset })) : [];
|
|
26581
|
-
});
|
|
26666
|
+
}).filter((e) => e.key !== "op" && e.key !== "table");
|
|
26582
26667
|
}
|
|
26583
26668
|
const spreadIndex = list.findIndex((e) => e.spread !== null && b.objects.has(e.spread));
|
|
26584
26669
|
const spreadColumns = [];
|
|
@@ -26589,6 +26674,33 @@ function requestOwnershipFindings(functions, schema) {
|
|
|
26589
26674
|
else if (spreadIndex >= 0 && (!last || last.index < spreadIndex)) spreadColumns.push(column);
|
|
26590
26675
|
}
|
|
26591
26676
|
if (spreadColumns.length > 0) report(table, spreadColumns, at2 + list[spreadIndex].offset);
|
|
26677
|
+
};
|
|
26678
|
+
for (const m of source.matchAll(WRITE)) {
|
|
26679
|
+
if (fileFull) break;
|
|
26680
|
+
if (!receivedBy(source, m.index, receivers)) continue;
|
|
26681
|
+
inspect(m[3].toLowerCase(), m[1] === "update" ? "update" : "insert", m.index + m[0].length);
|
|
26682
|
+
}
|
|
26683
|
+
if (!fileFull && [...source.matchAll(TX)].some((m) => receivedBy(source, m.index, receivers))) {
|
|
26684
|
+
for (const m of source.matchAll(STEP)) {
|
|
26685
|
+
if (fileFull) break;
|
|
26686
|
+
const step = objectLiteral(source, m.index);
|
|
26687
|
+
if (!step) {
|
|
26688
|
+
truncated = true;
|
|
26689
|
+
continue;
|
|
26690
|
+
}
|
|
26691
|
+
const fields = entries(step);
|
|
26692
|
+
const field = (key) => fields.find((e) => e.key === key);
|
|
26693
|
+
const op = /^['"`](insert|update)['"`]$/.exec(field("op")?.value.trim() ?? "")?.[1];
|
|
26694
|
+
const table = /^['"`]([A-Za-z_]\w{0,63})['"`]$/.exec(field("table")?.value.trim() ?? "")?.[1];
|
|
26695
|
+
if (!op || !table) continue;
|
|
26696
|
+
if (op === "update") {
|
|
26697
|
+
inspect(table.toLowerCase(), "update", m.index);
|
|
26698
|
+
continue;
|
|
26699
|
+
}
|
|
26700
|
+
const values = field("values");
|
|
26701
|
+
const valueAt = values ? step.indexOf(values.value, values.offset) : -1;
|
|
26702
|
+
if (valueAt >= 0) inspect(table.toLowerCase(), "insert", m.index + valueAt);
|
|
26703
|
+
}
|
|
26592
26704
|
}
|
|
26593
26705
|
}
|
|
26594
26706
|
return { findings, skippedFiles, truncated };
|
|
@@ -26860,7 +26972,7 @@ function userEmailScanFindings(functions) {
|
|
|
26860
26972
|
}
|
|
26861
26973
|
|
|
26862
26974
|
// worker/src/services/source-lint.ts
|
|
26863
|
-
var SOURCE_LINT_VERSION = "
|
|
26975
|
+
var SOURCE_LINT_VERSION = "6";
|
|
26864
26976
|
async function lintSourceSnapshot(files, functions = {}) {
|
|
26865
26977
|
const entries2 = (map) => Object.keys(map).sort().map((key) => [key, map[key]]);
|
|
26866
26978
|
const sha256 = async (value) => {
|
|
@@ -26881,7 +26993,7 @@ function lintSource(files, functions = {}) {
|
|
|
26881
26993
|
file: finding.file,
|
|
26882
26994
|
line: finding.line,
|
|
26883
26995
|
message: `${finding.label} in source. The existing deploy secret guard may refuse this source.`,
|
|
26884
|
-
fix: "
|
|
26996
|
+
fix: "A credential literal in source is readable by anyone with the source. A project environment variable without a VITE_ or REACT_APP_ prefix, read only inside a server handler, keeps it private.",
|
|
26885
26997
|
docs: "https://somewhere.cloud/docs/sw.env.txt"
|
|
26886
26998
|
}));
|
|
26887
26999
|
const schema = files["db/schema.ts"];
|
|
@@ -26897,7 +27009,7 @@ function lintSource(files, functions = {}) {
|
|
|
26897
27009
|
file: "db/schema.ts",
|
|
26898
27010
|
line: Number(/\bline (\d+):/.exec(message)?.[1] ?? 1),
|
|
26899
27011
|
message,
|
|
26900
|
-
fix: "
|
|
27012
|
+
fix: "The indicated schema helper, column or access rule is what this declaration accepts. The same schema parser runs during deploy.",
|
|
26901
27013
|
docs: "https://somewhere.cloud/docs/declared-data.txt"
|
|
26902
27014
|
});
|
|
26903
27015
|
}
|
|
@@ -26916,8 +27028,8 @@ function lintSource(files, functions = {}) {
|
|
|
26916
27028
|
severity: "warning",
|
|
26917
27029
|
file: finding.file,
|
|
26918
27030
|
line: finding.line,
|
|
26919
|
-
message: finding.columns.length === 1 ? `${finding.table}.${finding.columns[0]} is set or matched through sw.db.server from request input, so a caller can choose whose row this is. Lint cannot see authorization done elsewhere
|
|
26920
|
-
fix:
|
|
27031
|
+
message: finding.columns.length === 1 ? `${finding.table}.${finding.columns[0]} is set or matched through sw.db.server from request input, so a caller can choose whose row this is. Lint cannot see authorization done elsewhere, so whether this write is authorized is not established.` : `${finding.table} ownership columns ${finding.columns.join(", ")} are set through sw.db.server from the whole request body, so a caller can choose whose row this is. Lint cannot see authorization done elsewhere, so whether this write is authorized is not established.`,
|
|
27032
|
+
fix: `${finding.columns.join(" and ")} set from the signed-in user (sw.auth), or by the user-mode sw.db write which sets ownership itself, cannot be chosen by the caller. A handler that intentionally acts for another user is safe only with a check of the caller's role before this write.`,
|
|
26921
27033
|
docs: "https://somewhere.cloud/docs/declared-data.txt"
|
|
26922
27034
|
});
|
|
26923
27035
|
}
|
|
@@ -26934,7 +27046,7 @@ function lintSource(files, functions = {}) {
|
|
|
26934
27046
|
file: finding.file,
|
|
26935
27047
|
line: finding.line,
|
|
26936
27048
|
message: "sw.email.send is awaited inside a loop, so each email waits for the previous one to be delivered and the total time grows with the number of recipients. A handler or job that reaches its time limit stops sending the rest without an error.",
|
|
26937
|
-
fix: "
|
|
27049
|
+
fix: "Independent emails can be sent a few at a time (for example batches of 5 with Promise.allSettled per batch) or from a job. A sequential loop is needed when order matters or an email depends on an earlier result; one unbounded Promise.all over an arbitrary list has no concurrency limit.",
|
|
26938
27050
|
docs: "https://somewhere.cloud/docs/sw.email.txt"
|
|
26939
27051
|
});
|
|
26940
27052
|
}
|
|
@@ -26952,7 +27064,7 @@ function lintSource(files, functions = {}) {
|
|
|
26952
27064
|
file: finding.file,
|
|
26953
27065
|
line: finding.line,
|
|
26954
27066
|
message: "sw.auth listUsers is called without emails and its result is then searched for one exact email. That reads whole directory pages to find one user, so it gets slower as the app grows, and a user beyond the pages read is reported as missing.",
|
|
26955
|
-
fix: "
|
|
27067
|
+
fix: "sw.auth.server.listUsers({ emails: [email] }) looks the user up directly and returns only that user (users[0], or none). Paging listUsers fits real bulk work such as exports or admin lists.",
|
|
26956
27068
|
docs: "https://somewhere.cloud/docs/sw.auth.txt"
|
|
26957
27069
|
});
|
|
26958
27070
|
}
|
|
@@ -26966,7 +27078,7 @@ function lintSource(files, functions = {}) {
|
|
|
26966
27078
|
file: finding.file,
|
|
26967
27079
|
line: 1,
|
|
26968
27080
|
message: finding.warning,
|
|
26969
|
-
fix: "
|
|
27081
|
+
fix: "A folder's index file serves the folder's own path. Keep one of the two handlers; a deploy refuses both.",
|
|
26970
27082
|
docs: "https://somewhere.cloud/docs/functions.txt"
|
|
26971
27083
|
});
|
|
26972
27084
|
}
|