@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.
Files changed (49) hide show
  1. package/README.md +37 -48
  2. package/bin/engine.js +27 -0
  3. package/bin/somewhere.js +10 -1
  4. package/dist/commands/api.js +67 -2
  5. package/dist/commands/api.js.map +1 -1
  6. package/dist/commands/auth.js +18 -5
  7. package/dist/commands/auth.js.map +1 -1
  8. package/dist/commands/check.js +9 -3
  9. package/dist/commands/check.js.map +1 -1
  10. package/dist/commands/deploy.js +145 -6
  11. package/dist/commands/deploy.js.map +1 -1
  12. package/dist/commands/dev.js +48 -7
  13. package/dist/commands/dev.js.map +1 -1
  14. package/dist/commands/docs.js +15 -17
  15. package/dist/commands/docs.js.map +1 -1
  16. package/dist/commands/env.js +9 -1
  17. package/dist/commands/env.js.map +1 -1
  18. package/dist/commands/init.js +16 -7
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/logs.js +6 -1
  21. package/dist/commands/logs.js.map +1 -1
  22. package/dist/commands/typecheck.js +44 -20
  23. package/dist/commands/typecheck.js.map +1 -1
  24. package/dist/index.js +16 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/advisor-context.js +0 -9
  27. package/dist/lib/advisor-context.js.map +1 -1
  28. package/dist/lib/claim-handoff.js +23 -1
  29. package/dist/lib/claim-handoff.js.map +1 -1
  30. package/dist/lib/client.js +18 -7
  31. package/dist/lib/client.js.map +1 -1
  32. package/dist/lib/config.js +15 -1
  33. package/dist/lib/config.js.map +1 -1
  34. package/dist/lib/deploy-check.js +45 -44
  35. package/dist/lib/deploy-check.js.map +1 -1
  36. package/dist/lib/files.js +108 -19
  37. package/dist/lib/files.js.map +1 -1
  38. package/dist/lib/last-run.js +24 -26
  39. package/dist/lib/last-run.js.map +1 -1
  40. package/dist/lib/temp-auth.js +16 -24
  41. package/dist/lib/temp-auth.js.map +1 -1
  42. package/dist/lib/typecheck.js +209 -78
  43. package/dist/lib/typecheck.js.map +1 -1
  44. package/npm-shrinkwrap.json +2 -2
  45. package/package.json +1 -1
  46. package/runtime/DECLARED-DATA-VENDOR.json +18 -18
  47. package/runtime/SOURCE-LINT-VENDOR.json +47 -46
  48. package/runtime/declared-data.cjs +188 -108
  49. package/runtime/source-lint.cjs +311 -199
@@ -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
- + '. To restore the table with its data, declare it again in db/schema.ts and deploy.';
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. Check database storage under Usage in the dashboard for your account allowance.";
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
- 'Do not retry the whole action. Inspect the saved data, then correct or retry the failed read.');
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. Please try again later.'
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 \u2014 report it via support_ticket.');
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. Do not retry automatically.');
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. Do not retry automatically.');
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. Do not retry it.');
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. Do not retry automatically.');
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. Do not retry automatically.');
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. Do not retry it.');
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. Do not retry automatically.' : 'The managed query could not be completed.');
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. Do not retry it.');
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. Retry shortly.'
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. Retry shortly.');
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 : '') + '). Retry shortly; if this persists, promote or redeploy the function.'
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. Do not retry the statement. Select the column as CAST(' +
10191
- __sw_quoteResultColumn(column) + ' AS TEXT) in the RETURNING list, or write through sw.db.insert / ' +
10192
- 'sw.db.update / sw.db.remove, which compose that cast for you.'
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. Do not retry the statement. '
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. Do not retry the statement. '
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. Please try again later.'
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. Please try again later.';
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. Please try again later.';
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. Please try again later.';
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. Do not retry automatically.');
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-tech/sdk/server, or its legacy auth shim) that already called
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
- const result = await this.logout({ refresh_token: refresh }).catch(function (err) { throw __sw_markExpected(err); });
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. Please try again later.';
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. Please try again later.';
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 (or on their next groups list).
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", "Supply a valid primary key.");
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", "Supply a valid primary key.");
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', 'Supply a valid primary key.');
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', 'Supply a valid primary key.');
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
- : 'Sign in to use this data operation.';
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', 'Supply a valid reference.');
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', 'Sign in to use this data operation.');
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', 'Sign in to use this data operation.');
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', 'Open the creator link again to continue.');
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', 'Open the link again to continue.');
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 = "Use one of: 'required' (caller must be signed in), 'optional' (user when signed in, otherwise null), 'none' (public).";
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 = `Use '<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'. Remove rateLimit for no limit.`;
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. Use a server function for business logic.");
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}". Use project files for uploads.`);
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}". Use a server operation to publish rows.`);
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. Give at least one predicate field a private default, or use a server operation.");
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}". Use project files for uploads.`;
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. Give at least one predicate field a private default.";
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 \u2014 declare one transition, { status: { from: [...], to: '...' } }`);
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} \u2014 declare { from: [...], to: '...' }`);
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; remove the second entry. Other paid effects run from the signed payment event.`);
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 \u2014 declare { table: ['read', ...] }`);
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; use a server function for other tables.`);
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. Use a server function.`);
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. Remove it there; the server sets prices and the payment sets the status.`);
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; pick another name.`);
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 noExt = file.replace(/\.(ts|tsx|mts|js|mjs|jsx)$/, "");
17967
- const parts = noExt.split("/");
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. Retry shortly.' }),
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. See your function logs for the details.'),
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 pathsByRoute = /* @__PURE__ */ new Map();
18375
+ const pathsByShape = /* @__PURE__ */ new Map();
18315
18376
  for (const path of paths) {
18316
- const route = displayRouteForFunctionFile(path);
18317
- if (!route) continue;
18318
- const existing = pathsByRoute.get(route);
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 pathsByRoute.set(route, [path]);
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
- if (!route) return [];
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
- // ../files-migration-c0-rig/worker/node_modules/acorn/dist/acorn.mjs
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. Reuse fewer copies, or write the shared part once.");
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 }. Reference ${target.value} directly, or write the values inline.`);
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. Remove "export" from the constant.');
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. Declare that part as its own constant, or write it inline.`);
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. Write the literal value instead.`);
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. Write the value inline, or declare it with const using schema helpers and literals.`);
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. Declare it above export default as const ${t.value} = \u2026; or write the value inline.`);
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}). Move ${t.value} above ${owner}; constants that refer to each other in a cycle cannot be resolved.` : `${t.value} is used before its declaration on line ${at2}. Move the const above export default.`);
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. Declare it above export default as const ${t.value} = \u2026; or write the value inline.`);
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. Change "${t.value}" to "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. Declare the final value once with const.`);
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. Declare one constant per name: const name = \u2026;");
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. Choose another name, such as ${name}Rule.`);
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. Choose another name.`);
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. Keep one declaration.`);
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. Remove ": \u2026".`);
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}". Balance its brackets.`);
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; give each constant one value.`);
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. Balance its brackets.`);
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; write one const per name (const ${name} = \u2026;).`);
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. Remove "export" from the constant.');
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. Declare the final value once with const.`);
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. Reuse fewer copies, or write the shared part once.");
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. Choose a relation name that can be returned as an ordinary row field.`
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}". Choose a different relation name so nesting related rows cannot overwrite the root column.`
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}. Remove the relation before the table can go.` : `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.`
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 \u2014 declare 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 \u2014 declare an id() column on "${rel.table}".`
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. Declare it as ${rel.fk}: <type>({ references: '${keySide.name}' }) so the join key is a proven foreign key, not a free-form join.` : `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.`
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. Use ${expectedHelper}() for the foreign key so fetched rows can be stitched to the ${idCol.uuid ? "text" : "whole-number"} key exactly.`
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}". Declare it as a link child of one table.`);
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}". Declare it { unique: true }; a reference must name one row.`);
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. Use owner().`);
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}". Use owner(), member(), anyOf(owner(), member()), parent({ table, via }), shared(), or serverOnly().`);
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: use a whole number of bytes or a size such as '25MB'.`);
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}": use a content type such as 'image/png' or 'image/*'.`);
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. Remove client, or choose a scope the browser may use.`);
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; remove client.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. Remove public, or use owner() or serverOnly().`);
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. Remove public.`);
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. Use lowercase letters, digits and underscores, starting with a letter.`);
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; attach the files to a table nearer the root of the chain.`);
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. Change membership through a server function.`);
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; declare only additional roles.`);
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. Declare it as ${node.column}: text().`);
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. Use owner, admin, member, or a role declared in groups.roles.`);
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. List the group roles that may read, update or delete every row of the group.`);
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. List only app roles declared there; group roles and admin do not apply.`);
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. List the app roles that may read, update or delete every row.`);
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}" \u2014 declare it as { grantableBy: [...] }`);
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 \u2014 use plain 'single-quoted' strings.`);
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. Use letters, digits, and underscores, starting with a letter or underscore.`);
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. Pick a different name \u2014 in particular, names starting with "app_" or "_" belong to the platform.`);
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 keep "${rawName}" declared with its column type.`
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}". Use one of: id, text, number, integer, bigint, boolean, timestamp, json, blob.`
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). Declare the column bigint() and write the default as a string.` : `line ${optLine}: the default for integer column "${tableName}"."${rawName}" must be a whole number.`);
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. Give it a real default, or mark it { nullable: true }.`
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. Use letters, digits, and underscores, starting with a letter or underscore.`);
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. Use hasMany('child_table', 'foreign_key') or belongsTo('parent_table', 'foreign_key').`);
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}". Use owner(), group(), appRole(), member(), anyOf(), allOf(), parent(), shared(), or serverOnly().`
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. To remove this table, mark it ${rawName}: removedTable().`
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 mark the whole table removedTable())" : ""}.`);
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. Remove it from the columns \u2014 or pick a different owner column with owner({ column: '\u2026' }).`
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 \u2014 remove its declaration.`
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. Keep one.`
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; remove its one-column "unique" group.`);
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. Declare at least one table, or remove the file to leave the database unmanaged."] };
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. Declare at least one table, or remove the file to leave the database unmanaged."] };
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()"}. Remove the reference (mark the column removed(), or drop "references" from it) before the table can go.` : `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.`
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}(). Declare it text({ references: '${c.references}' }), or give "${c.references}" an integer key 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}(). Declare it integer({ references: '${c.references}' }), or give "${c.references}" a random text key with id().`);
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. Change membership through a server function.`);
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
- for (const m of source.matchAll(WRITE)) {
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) continue;
26563
- const at2 = m.index + m[0].length;
26564
- const bare = /^([A-Za-z_$][\w$]*)\s*[,)]/.exec(source.slice(at2, at2 + 80));
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
- continue;
26652
+ return;
26568
26653
  }
26569
26654
  const payload = objectLiteral(source, at2);
26570
26655
  if (!payload) {
26571
26656
  if (source[at2] === "{") truncated = true;
26572
- continue;
26657
+ return;
26573
26658
  }
26574
26659
  let list = entries(payload);
26575
- if (m[1] === "update") {
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 = "5";
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: "Remove the credential literal. Store it as a project environment variable without a VITE_ or REACT_APP_ prefix and read it only inside a server handler.",
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: "Correct this declaration using the indicated schema helper, column or access rule. The same schema parser runs during deploy.",
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; confirm this write is authorized.` : `${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; confirm this write is authorized.`,
26920
- fix: `Set ${finding.columns.join(" and ")} from the signed-in user (sw.auth) or use the user-mode sw.db write, which sets ownership itself. If the handler intentionally acts for another user, check the caller's role before this write.`,
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: "If each email is independent of the others, send them a few at a time (for example batches of 5 with Promise.allSettled per batch) or move bulk sending into a job. Keep the loop sequential when order matters or an email depends on an earlier result; avoid one unbounded Promise.all over an arbitrary list.",
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: "Look the user up directly with sw.auth.server.listUsers({ emails: [email] }); it returns only that user (users[0], or none). Keep paging listUsers for real bulk work such as exports or admin lists.",
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: "Keep this filename if /index is intentional; otherwise use the parent route filename after checking for a conflicting handler.",
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
  }