@somewhere-cloud/cli 0.38.5 → 0.38.7

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 (51) 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 +19 -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/init-agent-guide.js +8 -1
  39. package/dist/lib/init-agent-guide.js.map +1 -1
  40. package/dist/lib/last-run.js +54 -27
  41. package/dist/lib/last-run.js.map +1 -1
  42. package/dist/lib/temp-auth.js +16 -24
  43. package/dist/lib/temp-auth.js.map +1 -1
  44. package/dist/lib/typecheck.js +209 -78
  45. package/dist/lib/typecheck.js.map +1 -1
  46. package/npm-shrinkwrap.json +2 -2
  47. package/package.json +1 -1
  48. package/runtime/DECLARED-DATA-VENDOR.json +18 -18
  49. package/runtime/SOURCE-LINT-VENDOR.json +47 -46
  50. package/runtime/declared-data.cjs +188 -108
  51. package/runtime/source-lint.cjs +311 -199
@@ -330,7 +330,7 @@ var require_declared_data_contract = __commonJS({
330
330
  });
331
331
  return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
332
332
  }
333
- var LINK_CLIENT_RUNTIME = 'const b64url=s=>btoa(unescape(encodeURIComponent(s))).replace(/\\+/g,"-").replace(/\\//g,"_").replace(/=+$/,"");const createLink=async(table,id,options={})=>{const result=await invoke(table,"link_create",{id,access:options.access,...(options.expiresInDays===undefined?{}:{expires_in_days:options.expiresInDays})});const route=typeof options.returnTo==="string"?options.returnTo:location.pathname+location.search+location.hash;return{link_id:result.link_id,url:location.origin+"/__sw/link#"+result.secret+"."+b64url(route),expires_at:result.expires_at}};const linkDoor=async(path,init)=>{const response=await fetch(path,{credentials:"same-origin",cache:"no-store",...init});const payload=await response.json().catch(()=>({error:"INVALID_DATA_RESPONSE",message:"Link operation returned an invalid response"}));if(!response.ok)throw new DataError(response.status,payload);return payload};const links=Object.freeze({opened:()=>{try{const list=JSON.parse(sessionStorage.getItem("__sw_links_opened")||"[]");return Array.isArray(list)?list:[]}catch(_){return[]}},sessions:async()=>{const payload=await linkDoor("/__sw/link/sessions",{method:"GET"});return{data:payload.data}},forget:async linkId=>{await linkDoor("/__sw/link/forget",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(linkId===undefined?{}:{link_id:linkId})})}});const linkedView=spec=>link=>{if(typeof link!=="string"||!/^lnk_[0-9a-f]{32}$/.test(link))throw new DataError(400,{error:"DATA_INPUT_INVALID",message:"linked() needs a link id"});const view={};for(const[table,canUpdate,children]of spec){const relations={};const root={get:id=>invoke(table,"get",{id,link})};if(canUpdate)root.update=(id,values)=>invoke(table,"update",{id,values,link});for(const[relation,child,canCreate,canUpdateChild]of children){relations[relation]=Object.freeze({list:(parent_id,options={})=>invoke(table,"relation_list",{...options,relation,parent_id,link})});const childOps={};if(canCreate)childOps.create=values=>invoke(child,"create",{values,link});if(canUpdateChild)childOps.update=(id,values)=>invoke(child,"update",{id,values,link});if(canCreate||canUpdateChild)view[child]=Object.freeze(childOps)}if(children.length)root.relations=Object.freeze(relations);view[table]=Object.freeze(root)}return Object.freeze(view)};\n';
333
+ var LINK_CLIENT_RUNTIME = 'const b64url=s=>btoa(unescape(encodeURIComponent(s))).replace(/\\+/g,"-").replace(/\\//g,"_").replace(/=+$/,"");const createLink=async(table,id,options={})=>{const result=await invoke(table,"link_create",{id,access:options.access,...(options.expiresInDays===undefined?{}:{expires_in_days:options.expiresInDays})});const route=typeof options.returnTo==="string"?options.returnTo:location.pathname+location.search+location.hash;return{link_id:result.link_id,url:location.origin+"/__sw/link#"+result.secret+"."+b64url(route),expires_at:result.expires_at}};const linkDoor=async(path,init)=>{swReadSessionVoid();const response=await fetch(path,{credentials:"same-origin",cache:"no-store",...init});const payload=await response.json().catch(()=>({error:"INVALID_DATA_RESPONSE",message:"Link operation returned an invalid response"}));if(!response.ok)throw new DataError(response.status,payload);return payload};const links=Object.freeze({opened:()=>{try{const list=JSON.parse(sessionStorage.getItem("__sw_links_opened")||"[]");return Array.isArray(list)?list:[]}catch(_){return[]}},sessions:async()=>{const payload=await linkDoor("/__sw/link/sessions",{method:"GET"});return{data:payload.data}},forget:async linkId=>{await linkDoor("/__sw/link/forget",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(linkId===undefined?{}:{link_id:linkId})})}});const linkedView=spec=>link=>{if(typeof link!=="string"||!/^lnk_[0-9a-f]{32}$/.test(link))throw new DataError(400,{error:"DATA_INPUT_INVALID",message:"linked() needs a link id"});const view={};for(const[table,canUpdate,children]of spec){const relations={};const root={get:id=>invoke(table,"get",{id,link})};if(canUpdate)root.update=(id,values)=>invoke(table,"update",{id,values,link});for(const[relation,child,canCreate,canUpdateChild]of children){relations[relation]=Object.freeze({list:(parent_id,options={})=>invoke(table,"relation_list",{...options,relation,parent_id,link})});const childOps={};if(canCreate)childOps.create=values=>invoke(child,"create",{values,link});if(canUpdateChild)childOps.update=(id,values)=>invoke(child,"update",{id,values,link});if(canCreate||canUpdateChild)view[child]=Object.freeze(childOps)}if(children.length)root.relations=Object.freeze(relations);view[table]=Object.freeze(root)}return Object.freeze(view)};\n';
334
334
  var INSERT_CLIENT_RUNTIME = String.raw`
335
335
  function insertReceipt(value) {
336
336
  if (!value || typeof value !== 'object' || Array.isArray(value)
@@ -388,6 +388,7 @@ async function createWithReceipt(table, values, extra, eligible) {
388
388
  throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
389
389
  }
390
390
  let response, payload;
391
+ swReadSessionVoid();
391
392
  try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/json'}, body}); }
392
393
  catch (_) { throw attach(insertError(503, {error:'DB_WRITE_OUTCOME_UNKNOWN', message:'The write outcome could not be confirmed. Retry this request with its original key.', outcome:'unknown'})); }
393
394
  try { payload = await response.json(); }
@@ -429,6 +430,7 @@ async function createAnonymous(table, values, extra) {
429
430
  throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
430
431
  }
431
432
  let response, payload;
433
+ swReadSessionVoid();
432
434
  try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', cache:'no-store', headers:{'Content-Type':'application/json'}, body}); }
433
435
  catch (_) { throw attach(insertError(503, {error:'DB_WRITE_OUTCOME_UNKNOWN', message:'The write outcome could not be confirmed. Retry this request with its original key.', outcome:'unknown'})); }
434
436
  try { payload = await response.json(); }
@@ -460,6 +462,7 @@ async function intake(table, reference, values, extra) {
460
462
  throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
461
463
  }
462
464
  let response, payload;
465
+ swReadSessionVoid();
463
466
  try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', cache:'no-store', headers:{'Content-Type':'application/json'}, body}); }
464
467
  catch (_) { throw attach(insertError(503, {error:'DB_WRITE_OUTCOME_UNKNOWN', message:'The write outcome could not be confirmed. Retry this request with its original key.', outcome:'unknown'})); }
465
468
  try { payload = await response.json(); }
@@ -476,7 +479,7 @@ async function intake(table, reference, values, extra) {
476
479
  return send();
477
480
  }
478
481
  `;
479
- var CREATOR_CLIENT_RUNTIME = 'const creatorError=(status,payload)=>{const e=new DataError(status,payload);if(payload&&["committed","unknown","read_failed","not_committed"].includes(payload.outcome))e.outcome=payload.outcome;if(payload&&payload.write_committed===true)e.write_committed=true;e.retry=false;e.retryable=false;return e};const creatorDoor=async(path,init,write)=>{const lost=(status,code)=>creatorError(status,{error:code,message:write?"The creator operation could not be confirmed. Do not repeat it automatically.":"The creator operation result could not be read.",outcome:write?"unknown":"read_failed"});let response;try{response=await fetch(path,{credentials:"same-origin",cache:"no-store",...init})}catch(_){throw lost(503,"CREATOR_OUTCOME_UNKNOWN")}let payload;try{payload=await response.json()}catch(_){throw lost(response.status,"INVALID_DATA_RESPONSE")}if(!response.ok){if(!payload||typeof payload!=="object"||typeof payload.error!=="string")throw lost(response.status,"INVALID_DATA_RESPONSE");throw creatorError(response.status,payload)}if(!payload||typeof payload!=="object"||payload.ok!==true||!("data" in payload)||payload.data===undefined)throw lost(502,"INVALID_DATA_RESPONSE");return payload.data};const creatorPost=(body,write)=>creatorDoor("/__sw/creator",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...body,contract})},write);const claimOutcomes=new Map();const creatorsOpened=()=>{try{const list=JSON.parse(sessionStorage.getItem("__sw_creators_opened")||"[]");return Array.isArray(list)?list:[]}catch(_){return[]}};const creators=Object.freeze({opened:creatorsOpened,sessions:async()=>({data:await creatorDoor("/__sw/creator/sessions",{method:"GET"})}),forget:async creatorId=>{await creatorDoor("/__sw/creator/forget",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(creatorId===undefined?{}:{creator_id:creatorId})},true)}});const creatorView=spec=>creatorId=>{if(typeof creatorId!=="string"||!/^crt_[0-9a-f]{32}$/.test(creatorId))throw new DataError(400,{error:"DATA_INPUT_INVALID",message:"creator() needs a creator id"});const view={};for(const[table,canUpdate,children]of spec){const call=async(action,fields,write)=>{if(action==="relation_list")validateListLimit(fields);return creatorPost({action,creator_id:creatorId,table,...fields},write)};const root={get:id=>call("get",{id}).then(r=>({data:r.data})),links:id=>call("link_list",{id}),revokeLink:(id,linkId)=>call("link_revoke",{id,link_id:linkId},true),createLink:async(id,options={})=>{const result=await call("link_create",{id,access:options.access,...(options.expiresInDays===undefined?{}:{expires_in_days:options.expiresInDays})},true);const route=typeof options.returnTo==="string"?options.returnTo:location.pathname+location.search+location.hash;return{link_id:result.link_id,url:location.origin+"/__sw/link#"+result.secret+"."+b64url(route),expires_at:result.expires_at}},claim:async(id,options={})=>{const key=creatorId+"|"+table+"|"+String(id);let result;try{result=await call("claim",{id,keep_shares:options.keepShares===true},true)}catch(e){const known=claimOutcomes.get(key);if(e.outcome==="committed"||e.write_committed===true)claimOutcomes.set(key,"committed");else if(e.outcome==="unknown")claimOutcomes.set(key,"unknown");else if(known){e.outcome=known==="committed"?"committed":"unknown";if(known==="committed")e.write_committed=true;e.message+=" An earlier attempt of this claim may already have applied; check claimStatus() or your account."}throw e}claimOutcomes.set(key,"committed");try{sessionStorage.setItem("__sw_creators_opened",JSON.stringify(creatorsOpened().filter(e=>e&&e.creator_id!==creatorId)))}catch(_){}return result},claimStatus:id=>call("claim_status",{id})};if(canUpdate)root.update=(id,values)=>call("update",{id,values},true);const relations={};for(const[relation,child,canCreate,canUpdateChild]of children){relations[relation]=Object.freeze({list:(parentId,options={})=>call("relation_list",{relation,parent_id:parentId,...(options.after===undefined?{}:{after:options.after}),...(options.limit===undefined?{}:{limit:options.limit})})});const childOps={};if(canCreate)childOps.create=values=>call("child_create",{child,values},true);if(canUpdateChild)childOps.update=(id,values)=>call("child_update",{child,id,values},true);if(canCreate||canUpdateChild)view[child]=Object.freeze(childOps)}if(children.length)root.relations=Object.freeze(relations);view[table]=Object.freeze(root)}return Object.freeze(view)};\n';
482
+ var CREATOR_CLIENT_RUNTIME = 'const creatorError=(status,payload)=>{const e=new DataError(status,payload);if(payload&&["committed","unknown","read_failed","not_committed"].includes(payload.outcome))e.outcome=payload.outcome;if(payload&&payload.write_committed===true)e.write_committed=true;e.retry=false;e.retryable=false;return e};const creatorDoor=async(path,init,write)=>{const lost=(status,code)=>creatorError(status,{error:code,message:write?"The creator operation could not be confirmed. Do not repeat it automatically.":"The creator operation result could not be read.",outcome:write?"unknown":"read_failed"});let response;swReadSessionVoid();try{response=await fetch(path,{credentials:"same-origin",cache:"no-store",...init})}catch(_){throw lost(503,"CREATOR_OUTCOME_UNKNOWN")}let payload;try{payload=await response.json()}catch(_){throw lost(response.status,"INVALID_DATA_RESPONSE")}if(!response.ok){if(!payload||typeof payload!=="object"||typeof payload.error!=="string")throw lost(response.status,"INVALID_DATA_RESPONSE");throw creatorError(response.status,payload)}if(!payload||typeof payload!=="object"||payload.ok!==true||!("data" in payload)||payload.data===undefined)throw lost(502,"INVALID_DATA_RESPONSE");return payload.data};const creatorPost=(body,write)=>creatorDoor("/__sw/creator",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...body,contract})},write);const claimOutcomes=new Map();const creatorsOpened=()=>{try{const list=JSON.parse(sessionStorage.getItem("__sw_creators_opened")||"[]");return Array.isArray(list)?list:[]}catch(_){return[]}};const creators=Object.freeze({opened:creatorsOpened,sessions:async()=>({data:await creatorDoor("/__sw/creator/sessions",{method:"GET"})}),forget:async creatorId=>{await creatorDoor("/__sw/creator/forget",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(creatorId===undefined?{}:{creator_id:creatorId})},true)}});const creatorView=spec=>creatorId=>{if(typeof creatorId!=="string"||!/^crt_[0-9a-f]{32}$/.test(creatorId))throw new DataError(400,{error:"DATA_INPUT_INVALID",message:"creator() needs a creator id"});const view={};for(const[table,canUpdate,children]of spec){const call=async(action,fields,write)=>{if(action==="relation_list")validateListLimit(fields);return creatorPost({action,creator_id:creatorId,table,...fields},write)};const root={get:id=>call("get",{id}).then(r=>({data:r.data})),links:id=>call("link_list",{id}),revokeLink:(id,linkId)=>call("link_revoke",{id,link_id:linkId},true),createLink:async(id,options={})=>{const result=await call("link_create",{id,access:options.access,...(options.expiresInDays===undefined?{}:{expires_in_days:options.expiresInDays})},true);const route=typeof options.returnTo==="string"?options.returnTo:location.pathname+location.search+location.hash;return{link_id:result.link_id,url:location.origin+"/__sw/link#"+result.secret+"."+b64url(route),expires_at:result.expires_at}},claim:async(id,options={})=>{const key=creatorId+"|"+table+"|"+String(id);let result;try{result=await call("claim",{id,keep_shares:options.keepShares===true},true)}catch(e){const known=claimOutcomes.get(key);if(e.outcome==="committed"||e.write_committed===true)claimOutcomes.set(key,"committed");else if(e.outcome==="unknown")claimOutcomes.set(key,"unknown");else if(known){e.outcome=known==="committed"?"committed":"unknown";if(known==="committed")e.write_committed=true;e.message+=" An earlier attempt of this claim may already have applied; check claimStatus() or your account."}throw e}claimOutcomes.set(key,"committed");try{sessionStorage.setItem("__sw_creators_opened",JSON.stringify(creatorsOpened().filter(e=>e&&e.creator_id!==creatorId)))}catch(_){}return result},claimStatus:id=>call("claim_status",{id})};if(canUpdate)root.update=(id,values)=>call("update",{id,values},true);const relations={};for(const[relation,child,canCreate,canUpdateChild]of children){relations[relation]=Object.freeze({list:(parentId,options={})=>call("relation_list",{relation,parent_id:parentId,...(options.after===undefined?{}:{after:options.after}),...(options.limit===undefined?{}:{limit:options.limit})})});const childOps={};if(canCreate)childOps.create=values=>call("child_create",{child,values},true);if(canUpdateChild)childOps.update=(id,values)=>call("child_update",{child,id,values},true);if(canCreate||canUpdateChild)view[child]=Object.freeze(childOps)}if(children.length)root.relations=Object.freeze(relations);view[table]=Object.freeze(root)}return Object.freeze(view)};\n';
480
483
  var ANONYMOUS_CLIENT_DECLARATIONS = `
481
484
  /** The private creator capability of a row created with createAnonymous. Keep it, and its url, private: anyone opening the url can read, edit, share and claim the row until it is claimed. */
482
485
  export interface CreatorCapability { readonly grant: string; readonly secret: string; readonly url: string; }
@@ -485,12 +488,18 @@ async function intake(table, reference, values, extra) {
485
488
  var INTAKE_CLIENT_DECLARATIONS = `
486
489
  export interface IntakeResult { readonly accepted: true; readonly idempotency: InsertReceipt; readonly request: InsertRequest<IntakeResult> }
487
490
  `;
491
+ var ROW_CAN_DECLARATION = " /** Row capability hint (list/get with can: true): whether the signed-in caller may update or delete this row now, by the same rule the write enforces. A hint for UI; the write is still the final answer. */\n export interface RowCan { readonly update: boolean; readonly delete: boolean; }\n";
488
492
  var INSERT_CLIENT_DECLARATIONS = `
489
493
  export interface InsertReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number; }
490
494
  export interface InsertRequest<T> { readonly key: string; readonly createdAt: number; retry(): Promise<T>; }
491
495
  export type InsertResult<T> = T & { readonly idempotency: InsertReceipt; readonly request: InsertRequest<InsertResult<T>> };
492
496
  `;
493
- function describeDataClient2(authority, contract_digest, insertReceiptCapability, localTypes = false) {
497
+ var CLIENT_GENERATION2 = 3;
498
+ var ACCEPTED_CLIENT_GENERATIONS2 = Object.freeze([3, 2, 1]);
499
+ var PUBLIC_BOOTSTRAP_CLIENT_RUNTIME = 'const swCanon=(v,d)=>{if(v===null||typeof v==="string"||typeof v==="boolean"||(typeof v==="number"&&Number.isFinite(v)))return JSON.stringify(v);if(d===0||typeof v!=="object"||Array.isArray(v))return null;const parts=[];for(const k of Object.keys(v).sort()){if(v[k]===undefined)continue;const e=swCanon(v[k],d-1);if(e===null)return null;parts.push(JSON.stringify(k)+":"+e)}return"{"+parts.join(",")+"}"};let swBoot;const swBootTake=body=>{try{if(swBoot===undefined){swBoot=null;const el=typeof document!=="undefined"&&document.getElementById?document.getElementById("__sw_public_data"):null;if(el&&el.type==="application/json"&&(el.parentNode===document.head||el.parentNode===document.body)){const p=JSON.parse(el.textContent||"null");if(p&&p.v===1&&p.contract===contract&&Number.isSafeInteger(p.validated_at)&&p.validated_at<=Date.now()&&Number.isInteger(p.max_age)&&p.max_age>0&&p.max_age<=120&&p.entries&&typeof p.entries==="object"&&!Array.isArray(p.entries)&&typeof p.path==="string"&&typeof location!=="undefined")swBoot={until:p.validated_at+p.max_age*1000,path:p.path,entries:new Map(Object.entries(p.entries))}}if(el&&el.parentNode)el.parentNode.removeChild(el)}if(!swBoot)return undefined;if(Date.now()>=swBoot.until||location.pathname!==swBoot.path){swBoot=null;return undefined}const key=swCanon(body,2);if(key===null||!swBoot.entries.has(key))return undefined;const value=swBoot.entries.get(key);swBoot.entries.delete(key);return value}catch(error){swBoot=null;return undefined}};';
500
+ var PUBLIC_BOOTSTRAP_INVOKE = 'if(operation==="list"||operation==="get"||operation==="aggregate"||operation==="relation_list"){const booted=swBootTake({...input,contract,table,operation});if(booted!==undefined)return booted}else swBoot=null;';
501
+ function describeDataClient2(authority, contract_digest, insertReceiptCapability, localTypes = false, generation = CLIENT_GENERATION2) {
502
+ if (!ACCEPTED_CLIENT_GENERATIONS2.includes(generation)) throw new Error(`Unknown data client generation ${generation}.`);
494
503
  const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes, insertReceiptCapability) : canonicalize2({}, {}, {}, insertReceiptCapability);
495
504
  const { tables } = JSON.parse(canonical);
496
505
  const declarations = [];
@@ -512,8 +521,16 @@ async function intake(table, reference, values, extra) {
512
521
  const methods = [];
513
522
  if (client.read !== false) {
514
523
  const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t));
515
- operations.push(`list<const L extends number = number>(options?: { limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")} }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
516
- operations.push(`get(id: ${id}): Promise<{ data: ${row} | null }>`);
524
+ if (generation >= 2) {
525
+ const listOptions = `limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")}`;
526
+ operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & { can: RowCan })[]; next: ${id} | null; has_more: boolean }>`);
527
+ operations.push(`list<const L extends number = number>(options?: { ${listOptions}; can?: false }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
528
+ operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & { can: RowCan }) | null }>`);
529
+ operations.push(`get(id: ${id}, options?: { can?: false }): Promise<{ data: ${row} | null }>`);
530
+ } else {
531
+ operations.push(`list<const L extends number = number>(options?: { limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")} }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
532
+ operations.push(`get(id: ${id}): Promise<{ data: ${row} | null }>`);
533
+ }
517
534
  methods.push("list", "get");
518
535
  const numeric = equality.filter((field) => ["integer", "number"].includes(columns.find((column) => column.n === field).t));
519
536
  const fieldUnion = (list) => list.map((field) => JSON.stringify(field)).join(" | ");
@@ -645,15 +662,15 @@ async function intake(table, reference, values, extra) {
645
662
  const declaration = `declare module ${JSON.stringify("somewhere:data")} {
646
663
  type PageSize = ${Array.from({ length: 100 }, (_, i) => i + 1).join(" | ")};
647
664
  export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
648
- ${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
665
+ ${generation >= 2 ? ROW_CAN_DECLARATION : ""}${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
649
666
  export const data: { ${declarations.join(";\n ")} };
650
667
  }
651
668
  `;
652
669
  const runtime = `const contract=${JSON.stringify(contract_digest)};
653
670
  class DataError extends Error{constructor(status,payload){super(payload&&typeof payload.message==="string"?payload.message:payload&&typeof payload.error==="string"?payload.error:"Data operation failed");this.name="DataError";this.status=status;this.code=payload&&typeof payload.error==="string"?payload.error:null}}
654
671
  const validateListLimit=input=>{if(input.limit!==undefined&&(!Number.isInteger(input.limit)||input.limit<1||input.limit>100))throw new DataError(400,{error:"DATA_INPUT_INVALID",message:"limit must be an integer from 1 to 100"})};
655
- const invoke=async(table,operation,input)=>{if(operation==="list"||operation==="relation_list")validateListLimit(input);const response=await fetch("/__sw/data",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({...input,contract,table,operation})});const payload=await response.json().catch(()=>{throw new DataError(response.status,{error:"INVALID_DATA_RESPONSE",message:"Data operation returned an invalid response"})});if(!response.ok)throw new DataError(response.status,payload);return payload};
656
- ${receiptCapable ? INSERT_CLIENT_RUNTIME + "const receiptTables=" + JSON.stringify(receiptTables) + ";\n" : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_RUNTIME + CREATOR_CLIENT_RUNTIME : ""}${intakeTables.length ? INTAKE_CLIENT_RUNTIME : ""}${linkedSpec.length ? LINK_CLIENT_RUNTIME : ""}const data=Object.freeze(Object.fromEntries(${JSON.stringify(runtimeTables)}.map(([table,operations,relations])=>{const entries=operations.map(operation=>[operation,operation==="list"||operation==="aggregate"?(options={})=>invoke(table,operation,options):operation==="create"?${receiptCapable ? "(values,...extra)=>createWithReceipt(table,values,extra,receiptTables.includes(table))" : "values=>invoke(table,operation,{values})"}:${anonymousTables.length ? 'operation==="createAnonymous"?(values,...extra)=>createAnonymous(table,values,extra):' : ""}${intakeTables.length ? 'operation==="intake"?(reference,values,...extra)=>intake(table,reference,values,extra):' : ""}operation==="update"?(id,values)=>invoke(table,operation,{id,values}):operation==="createLink"?(id,options)=>createLink(table,id,options):operation==="links"?id=>invoke(table,"link_list",{id}):operation==="revokeLink"?(id,link_id)=>invoke(table,"link_revoke",{id,link_id}):id=>invoke(table,operation,{id})]);if(relations.length)entries.push(["relations",Object.freeze(Object.fromEntries(relations.map(relation=>[relation,Object.freeze({list:(parent_id,options={})=>invoke(table,"relation_list",{...options,relation,parent_id})})])))]);return[table,Object.freeze(Object.fromEntries(entries))];}).concat(${linkedSpec.length ? `[["links",links],["linked",linkedView(${JSON.stringify(linkedSpec)})]]` : "[]"})${creatorSpec.length ? `.concat([["creators",creators],["creator",creatorView(${JSON.stringify(creatorSpec)})]])` : ""}));
672
+ let swReadSession=null,swInFlight=0,swStarts=0;const swReadSessionVoid=()=>{swReadSession=null;swStarts++};${generation >= 3 ? PUBLIC_BOOTSTRAP_CLIENT_RUNTIME : ""}const invoke=async(table,operation,input)=>{if(operation==="list"||operation==="relation_list")validateListLimit(input);${generation >= 3 ? PUBLIC_BOOTSTRAP_INVOKE : ""}const alone=swInFlight===0,start=++swStarts,isRead=operation==="list"||operation==="get"||operation==="aggregate"||operation==="relation_list";swInFlight++;let response;try{response=await fetch("/__sw/data",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json",...(swReadSession?{"X-Sw-Read-Session":swReadSession}:{})},body:JSON.stringify({...input,contract,table,operation})})}catch(error){if(!isRead)swReadSession=null;throw error}finally{swInFlight--}const next=response.headers&&typeof response.headers.get==="function"?response.headers.get("X-Sw-Read-Session"):null;if(!alone||swStarts!==start||swInFlight!==0)swReadSession=null;else if(next&&/^swrs1[.][A-Za-z0-9_-]+[.][A-Za-z0-9_-]+$/.test(next))swReadSession=next;else if(!isRead||!response.ok)swReadSession=null;const payload=await response.json().catch(()=>{throw new DataError(response.status,{error:"INVALID_DATA_RESPONSE",message:"Data operation returned an invalid response"})});if(!response.ok)throw new DataError(response.status,payload);return payload};
673
+ ${receiptCapable ? INSERT_CLIENT_RUNTIME + "const receiptTables=" + JSON.stringify(receiptTables) + ";\n" : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_RUNTIME + CREATOR_CLIENT_RUNTIME : ""}${intakeTables.length ? INTAKE_CLIENT_RUNTIME : ""}${linkedSpec.length ? LINK_CLIENT_RUNTIME : ""}const data=Object.freeze(Object.fromEntries(${JSON.stringify(runtimeTables)}.map(([table,operations,relations])=>{const entries=operations.map(operation=>[operation,operation==="list"||operation==="aggregate"?(options={})=>invoke(table,operation,options):operation==="create"?${receiptCapable ? "(values,...extra)=>createWithReceipt(table,values,extra,receiptTables.includes(table))" : "values=>invoke(table,operation,{values})"}:${anonymousTables.length ? 'operation==="createAnonymous"?(values,...extra)=>createAnonymous(table,values,extra):' : ""}${intakeTables.length ? 'operation==="intake"?(reference,values,...extra)=>intake(table,reference,values,extra):' : ""}operation==="update"?(id,values)=>invoke(table,operation,{id,values}):operation==="createLink"?(id,options)=>createLink(table,id,options):operation==="links"?id=>invoke(table,"link_list",{id}):operation==="revokeLink"?(id,link_id)=>invoke(table,"link_revoke",{id,link_id}):${generation >= 2 ? 'operation==="get"?(id,options={})=>invoke(table,operation,options&&options.can===true?{id,can:true}:{id}):' : ""}id=>invoke(table,operation,{id})]);if(relations.length)entries.push(["relations",Object.freeze(Object.fromEntries(relations.map(relation=>[relation,Object.freeze({list:(parent_id,options={})=>invoke(table,"relation_list",{...options,relation,parent_id})})])))]);return[table,Object.freeze(Object.fromEntries(entries))];}).concat(${linkedSpec.length ? `[["links",links],["linked",linkedView(${JSON.stringify(linkedSpec)})]]` : "[]"})${creatorSpec.length ? `.concat([["creators",creators],["creator",creatorView(${JSON.stringify(creatorSpec)})]])` : ""}));
657
674
  export{data,DataError};
658
675
  `;
659
676
  return { contract_digest, declaration, runtime, manifest: { version: 1, contract_digest, tables, declaration, ...insertReceiptCapability === void 0 ? {} : { insert_receipt: insertReceiptCapability } } };
@@ -661,7 +678,7 @@ export{data,DataError};
661
678
  function describeDataClientLocalDeclaration2(authority) {
662
679
  return describeDataClient2(authority, "", "insert-v1", true).declaration;
663
680
  }
664
- module2.exports = { canonicalize: canonicalize2, describeDataClient: describeDataClient2, describeDataClientLocalDeclaration: describeDataClientLocalDeclaration2, validateInsertReceiptCapability };
681
+ module2.exports = { canonicalize: canonicalize2, describeDataClient: describeDataClient2, describeDataClientLocalDeclaration: describeDataClientLocalDeclaration2, validateInsertReceiptCapability, CLIENT_GENERATION: CLIENT_GENERATION2, ACCEPTED_CLIENT_GENERATIONS: ACCEPTED_CLIENT_GENERATIONS2 };
665
682
  }
666
683
  });
667
684
 
@@ -793,7 +810,31 @@ interface SomewhereAppRoleGrant {
793
810
  interface SomewhereGroupInvitation {
794
811
  invite: { id: string; email: string; status: 'pending'; redirect_uri: string; expires_at: number; created_at: number };
795
812
  group: { group_id: string; role: SomewhereGroupRole };
796
- delivery: 'sent' | 'pending';
813
+ /** What THIS call sent. not_sent: the address already had this pending
814
+ * invitation; it is returned as is and no second email goes out. */
815
+ delivery: 'sent' | 'pending' | 'not_sent';
816
+ /** With not_sent: what is known about the invitation's one email.
817
+ * sent/queued: the provider took it. unknown: it may have gone out.
818
+ * starting: a send may still begin. not_sent: no email went out.
819
+ * Nothing is re-sent automatically. After not_sent or unknown,
820
+ * revokeInvitation then invite again sends one new email with a new link;
821
+ * after unknown the earlier email may still arrive (its link stops working
822
+ * once revoked). */
823
+ email_state?: SomewhereGroupInvitationEmailState;
824
+ }
825
+
826
+ type SomewhereGroupInvitationEmailState = 'sent' | 'queued' | 'unknown' | 'starting' | 'not_sent';
827
+
828
+ /** A pending invitation, as its sender or a member who may grant its role sees it. */
829
+ interface SomewhereGroupPendingInvitation {
830
+ invite_id: string;
831
+ email: string;
832
+ role: SomewhereGroupRole;
833
+ invited_by: string;
834
+ status: 'pending';
835
+ created_at: number;
836
+ expires_at: number;
837
+ email_state: SomewhereGroupInvitationEmailState;
797
838
  }
798
839
 
799
840
  /**
@@ -830,13 +871,20 @@ type SomewhereGroupsResult<T> = { data: T; error: null } | { data: null; error:
830
871
  */
831
872
  interface SomewhereGroups {
832
873
  create(req: Request, input: { name: string }): Promise<SomewhereGroupsResult<SomewhereGroupDetail>>;
833
- /** Also completes (and reports) up to 3 of the signed-in user's accepted invitations. */
874
+ /** Also completes (and reports) up to 3 of the signed-in user's invitations not yet confirmed at sign-in. */
834
875
  list(req: Request, opts?: SomewhereGroupsPageOptions): Promise<SomewhereGroupsResult<SomewhereGroupsPage<SomewhereGroup> & { invitations: SomewhereGroupInvitationOutcome[] }>>;
835
876
  get(req: Request, groupId: string): Promise<SomewhereGroupsResult<SomewhereGroupDetail>>;
836
877
  members(req: Request, groupId: string, opts?: SomewhereGroupsPageOptions): Promise<SomewhereGroupsResult<SomewhereGroupsPage<SomewhereGroupMember>>>;
837
- /** Invite by email with a role the signed-in user may grant. The platform
838
- * sends one email and adds the member when they accept; no app call is needed. */
878
+ /** Invite any email with a role the signed-in user may grant. The platform
879
+ * sends one email. An address that already belongs to a verified account
880
+ * in this app gets access right away; any other joins when it signs in with
881
+ * that verified email (the link, or any sign-in). The answer is the same in
882
+ * every case; no app call is needed. Inviting an address that already has a
883
+ * pending invitation sends nothing. */
839
884
  invite(req: Request, groupId: string, input: { email: string; role: SomewhereGroupRole; redirect_uri: string; expires_in?: number }): Promise<SomewhereGroupsResult<SomewhereGroupInvitation>>;
885
+ /** Pending invitations of one group (oldest first) that the signed-in member could revoke:
886
+ * ones they sent, and ones whose role they may grant. A non-member gets GROUP_NOT_FOUND. */
887
+ invitations(req: Request, groupId: string, opts?: SomewhereGroupsPageOptions): Promise<SomewhereGroupsResult<SomewhereGroupsPage<SomewhereGroupPendingInvitation>>>;
840
888
  /** Revoke in the invitation's own group, as its sender or a member who may grant its role. */
841
889
  revokeInvitation(req: Request, inviteId: string): Promise<SomewhereGroupsResult<{ revoked: true }>>;
842
890
  leave(req: Request, groupId: string): Promise<SomewhereGroupsResult<{ left: true }>>;
@@ -914,10 +962,17 @@ type SomewhereDbUpdate = {
914
962
  | { set?: never; increment: SomewhereDbIncrement }
915
963
  );
916
964
  interface SomewhereDbRemove { where?: SomewhereDbWhere | null }
917
- type SomewhereDbWriteIntent =
918
- | { op: 'insert'; table: string; values: SomewhereDbValues; options?: SomewhereDbPlainInsertOptions | null }
919
- | ({ op: 'update'; table: string } & SomewhereDbUpdate)
920
- | { op: 'remove'; table: string; where?: SomewhereDbWhere | null };
965
+ // One closed-batch step per declared table, checked like the matching single call
966
+ // (insert values, update set/increment). With no declared tables every name and
967
+ // value stays accepted, as before. Insert and Set pick caller or server identity.
968
+ type __SomewhereDbIntentFor<T extends string, Insert extends 'insert' | 'serverInsert', Set extends 'set' | 'serverSet'> =
969
+ | { op: 'insert'; table: T; values: __SomewhereDeclared<T, Insert, SomewhereDbValues>; options?: SomewhereDbPlainInsertOptions | null }
970
+ | ({ op: 'update'; table: T } & SomewhereDbUpdateFor<T, Set>)
971
+ | { op: 'remove'; table: T; where?: SomewhereDbWhere | null };
972
+ // sw.db.server.tx: server authority may name row identity (owner column, shared authorship).
973
+ type SomewhereDbWriteIntent = { [T in SomewhereDbTable]: __SomewhereDbIntentFor<T, 'serverInsert', 'serverSet'> }[SomewhereDbTable];
974
+ // sw.db.tx: the platform composes row identity from the verified user, as sw.db.insert does.
975
+ type SomewhereDbCallerWriteIntent = { [T in SomewhereDbTable]: __SomewhereDbIntentFor<T, 'insert', 'set'> }[SomewhereDbTable];
921
976
  // db/schema.ts's tables, one entry each, generated beside this declaration
922
977
  // (declaredTablesDeclaration). With no declared tables it stays empty: any
923
978
  // table name is accepted and rows are Record<string, unknown>. Once tables are
@@ -962,6 +1017,7 @@ interface SomewhereDbResult<Row = Record<string, unknown>> {
962
1017
  changes: number;
963
1018
  last_row_id: number | string | null;
964
1019
  live_delivery?: { delivery: 'invalidated' }
1020
+ | { delivery: 'queued' }
965
1021
  | { delivery: 'resync_required'; reason: string };
966
1022
  }
967
1023
  // A keyed insert's result (whether an insert is keyed is decided at run time): the receipt
@@ -994,10 +1050,11 @@ interface SomewhereServerDb {
994
1050
  // an unknown outcome; a rejection never promises rollback or safe replay.
995
1051
  tx(intents: readonly SomewhereDbWriteIntent[]): Promise<SomewhereDbResult[]>;
996
1052
  }
997
- interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | 'insert' | 'update'> {
1053
+ interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | 'insert' | 'update' | 'tx'> {
998
1054
  // The platform composes row identity from the verified user.
999
1055
  insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbInsertResult<SomewhereDbRow<T>>>;
1000
1056
  update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'set'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
1057
+ tx(intents: readonly SomewhereDbCallerWriteIntent[]): Promise<SomewhereDbResult[]>;
1001
1058
  from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
1002
1059
  table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
1003
1060
  count(table: SomewhereDbTable, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
@@ -1206,14 +1263,11 @@ interface SomewhereAuthProfileUser extends SomewhereAuthUser {
1206
1263
  email_verified: boolean;
1207
1264
  banned: boolean;
1208
1265
  metadata: unknown;
1209
- plan: string;
1210
- plan_status: string | null;
1211
1266
  type: string;
1212
1267
  created_at: number;
1213
1268
  last_login_at: number | null;
1214
1269
  locale?: string | null;
1215
1270
  timezone?: string | null;
1216
- entitlements: string[];
1217
1271
  agent?: SomewhereJsonObject;
1218
1272
  }
1219
1273
  interface SomewhereAuthIssuedSession {
@@ -1288,6 +1342,9 @@ interface SomewhereAuthRefreshed {
1288
1342
  expires_in: number;
1289
1343
  token_type: 'Bearer';
1290
1344
  }
1345
+ type SomewhereAuthRefreshedSession =
1346
+ | { user: SomewhereAuthUser; cookie_session: true; expires_at: number }
1347
+ | (SomewhereAuthRefreshed & { user: SomewhereAuthUser; cookie_session?: never; expires_at: number });
1291
1348
  interface SomewhereAuthSignupProfile {
1292
1349
  display_name?: string | null;
1293
1350
  displayName?: string | null;
@@ -1402,6 +1459,7 @@ interface SomewhereRuntimeAuth {
1402
1459
  login(options: SomewhereAuthCredentials): Promise<SomewhereAuthLoginResult>;
1403
1460
  logout(options: SomewhereAuthLogoutOptions): Promise<{ logged_out: true }>;
1404
1461
  refresh(options: { refresh_token: string }): Promise<SomewhereAuthRefreshed>;
1462
+ refreshSession(req: Request): Promise<SomewhereAuthRefreshedSession>;
1405
1463
  // Verifies an app-user access token; throws on an invalid/expired token (no null result).
1406
1464
  me(token: string, options?: { refreshToken?: string | null; noCache?: boolean } | null): Promise<{ user: SomewhereAuthProfileUser }>;
1407
1465
  // Resolves the signed-in user from cookie or Bearer (or options.forwardedToken); null when absent/rejected.
@@ -3000,6 +3058,8 @@ interface SomewhereEmailTestInboxMessage {
3000
3058
  html: string | null;
3001
3059
  text: string | null;
3002
3060
  magic_link: string | null;
3061
+ /** The code an auth message carries (email verification, sign-in code); null otherwise. */
3062
+ verification_code: string | null;
3003
3063
  created_at: string;
3004
3064
  }
3005
3065
  interface SomewhereRuntimeEmail {
@@ -3740,13 +3800,22 @@ type __SomewhereEndpointBody<S> = {
3740
3800
  type SomewhereEndpointAuth = 'none' | 'optional' | 'required';
3741
3801
  interface SomewhereEndpointInput<Auth extends SomewhereEndpointAuth, Body> {
3742
3802
  body: Body;
3743
- user: Auth extends 'required' ? SomewhereEndpointUser : SomewhereEndpointUser | null;
3803
+ user: Auth extends 'required' ? SomewhereEndpointUser & { email: string } : SomewhereEndpointUser | null;
3744
3804
  headers: Headers;
3745
3805
  params: Record<string, string>;
3746
3806
  request: Request;
3747
3807
  }
3808
+ type SomewhereMcpJson = null | boolean | number | string | readonly SomewhereMcpJson[] | { readonly [key: string]: SomewhereMcpJson };
3748
3809
  interface SomewhereEndpointConfig<Auth extends SomewhereEndpointAuth, Schema extends SomewhereEndpointBodySchema | undefined> {
3749
3810
  auth?: Auth;
3811
+ /** Explicit MCP opt-in. Only auth: required endpoints can be tools. */
3812
+ mcp?: Auth extends 'required' ? {
3813
+ name: string;
3814
+ description: string;
3815
+ inputSchema: { readonly type: 'object'; readonly [key: string]: SomewhereMcpJson };
3816
+ outputSchema?: { readonly type: 'object'; readonly [key: string]: SomewhereMcpJson };
3817
+ annotations?: { readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean };
3818
+ } : never;
3750
3819
  body?: Schema;
3751
3820
  rateLimit?: \`\${number}/\${'second' | 'minute' | 'hour' | 'day'}\${'' | 's'}\`;
3752
3821
  cors?: 'same-origin' | '*' | readonly string[];
@@ -3784,7 +3853,7 @@ interface SomewhereEndpointConfig<Auth extends SomewhereEndpointAuth, Schema ext
3784
3853
  if (owner && !columns.some((column) => column.n === owner)) columns.push({ n: owner, t: "text", optional: false, identity: true, type: "string" });
3785
3854
  const declared = columns.filter((column) => !column.identity);
3786
3855
  const relations = (table.relations || []).filter((relation) => Object.prototype.hasOwnProperty.call(schema, relation.table)).map((relation) => key(relation.name) + ": SomewhereDeclaredTables[" + key(relation.table) + "]['row']" + (relation.kind === "belongsTo" ? " | null" : "[]"));
3787
- return " " + key(name) + ": {\n row: " + shape(columns.map((column) => field(column, false))) + ";\n insert: " + shape(declared.map((column) => field(column, column.optional))) + ";\n serverInsert: " + shape(columns.map((column) => field(column, column.optional))) + ";\n set: " + shape(declared.map((column) => field(column, true))) + ";\n serverSet: " + shape(columns.map((column) => field(column, true))) + ";\n numeric: " + union(declared.filter((column) => column.t === "integer" || column.t === "number")) + ";\n scalar: " + union(declared.filter((column) => column.t !== "json" && column.t !== "blob")) + ";\n relations: " + (relations.length ? shape(relations) : "{}") + ";\n };\n";
3856
+ return " " + key(name) + ": {\n row: " + shape(columns.map((column) => field(column, false))) + ";\n insert: " + shape(declared.map((column) => field(column, column.optional))) + ";\n serverInsert: " + shape(columns.map((column) => field(column, column.optional))) + ";\n set: " + shape(declared.map((column) => field(column, true))) + ";\n serverSet: " + shape(columns.map((column) => field(column, true))) + ";\n numeric: " + union(declared.filter((column) => column.t === "integer" || column.t === "number")) + ";\n scalar: " + union(columns.filter((column) => (!column.identity || column.n === owner) && column.t !== "json" && column.t !== "blob")) + ";\n relations: " + (relations.length ? shape(relations) : "{}") + ";\n };\n";
3788
3857
  });
3789
3858
  return "interface SomewhereDeclaredTables {\n" + entries.join("") + "}\n";
3790
3859
  }
@@ -4105,6 +4174,16 @@ var require_checker_contract = __commonJS({
4105
4174
  if (typeShape.has(number)) return "type";
4106
4175
  return null;
4107
4176
  }
4177
+ function diagnosticMessage(code, message) {
4178
+ const number = diagnosticNumber(code);
4179
+ if ((number === 2322 || number === 2345) && message.includes("'string[]'") && /readonly \(\s*["'][^"']+["'](?:\s*\|\s*["'][^"']+["'])*\s*\)\[\]/.test(message)) {
4180
+ return message + "\nHint: For a fixed list, declare the array with `as const` so its values stay literal. For dynamic values, validate and narrow them to the allowed values; do not cast arbitrary strings.";
4181
+ }
4182
+ if ((number === 2322 || number === 2345) && message.includes("Record<string, unknown>")) {
4183
+ return message + "\nHint: Use an object literal or a builder with explicitly typed fields. Validate unknown input before passing it to a typed API; do not hide the mismatch with a cast.";
4184
+ }
4185
+ return message;
4186
+ }
4108
4187
  module2.exports = {
4109
4188
  TYPESCRIPT_VERSION,
4110
4189
  REFERENCE_SOURCE,
@@ -4117,7 +4196,8 @@ var require_checker_contract = __commonJS({
4117
4196
  projectTsconfigCompilerOptions,
4118
4197
  localCheckCompilerOptions,
4119
4198
  isIgnoredDiagnostic,
4120
- classifyDiagnostic
4199
+ classifyDiagnostic,
4200
+ diagnosticMessage
4121
4201
  };
4122
4202
  }
4123
4203
  });
@@ -4238,7 +4318,7 @@ function parseClientPermissions(value, columns, scope) {
4238
4318
  }
4239
4319
  }
4240
4320
  if (!["owner", "member", "shared", "policy"].includes(scope.kind)) {
4241
- return fail("serverOnly() tables cannot declare client access. Use a server function for business logic.");
4321
+ return fail("serverOnly() tables cannot declare client access; their rows are reached only from server functions.");
4242
4322
  }
4243
4323
  if (scope.policy?.kind === "all_of") {
4244
4324
  if (input.publicRead !== void 0 && input.publicRead !== false) return fail("allOf() tables cannot declare client.publicRead; their rows are reachable only by current members.");
@@ -4296,9 +4376,9 @@ function parseClientPermissions(value, columns, scope) {
4296
4376
  if (column.helper === "id" || (scope.kind === "owner" || scope.kind === "policy") && name === scope.column) {
4297
4377
  return fail(`client.${operation} cannot write the platform-assigned identity field "${entry}".`);
4298
4378
  }
4299
- if (column.helper === "blob") return fail(`client.${operation} cannot write binary field "${entry}". Use project files for uploads.`);
4379
+ if (column.helper === "blob") return fail(`client.${operation} cannot write binary field "${entry}"; uploads go to project files.`);
4300
4380
  if (typeof publicRead === "object" && Object.prototype.hasOwnProperty.call(publicRead.where, name)) {
4301
- return fail(`client.${operation} cannot write publication field "${entry}". Use a server operation to publish rows.`);
4381
+ return fail(`client.${operation} cannot write publication field "${entry}"; rows are published by a server operation.`);
4302
4382
  }
4303
4383
  if (operation === "update" && (scope.kind === "member" || scope.kind === "policy") && scope.group?.includes(name)) {
4304
4384
  return fail(`client.update cannot move a row between membership groups by changing "${entry}".`);
@@ -4311,7 +4391,7 @@ function parseClientPermissions(value, columns, scope) {
4311
4391
  }
4312
4392
  if (writes.create !== null) {
4313
4393
  if (typeof publicRead === "object" && !privateByDefault(publicRead.where, fields)) {
4314
- 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.");
4394
+ 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.");
4315
4395
  }
4316
4396
  for (const column of columns) {
4317
4397
  if (column.helper !== "id" && !column.nullable && !column.hasDefault && !column.autoNow && !writes.create.includes(column.name)) {
@@ -4389,7 +4469,7 @@ function parseIntakePermissions(value, columns, scope, publicRead) {
4389
4469
  const column = byName2.get(name);
4390
4470
  if (!column) return `client.intake.fields names undeclared or platform-managed field "${entry}".`;
4391
4471
  if (column.helper === "id" || name === scope.column) return `client.intake.fields cannot set the platform-assigned identity field "${entry}".`;
4392
- if (column.helper === "blob") return `client.intake.fields cannot accept binary field "${entry}". Use project files for uploads.`;
4472
+ if (column.helper === "blob") return `client.intake.fields cannot accept binary field "${entry}"; uploads go to project files.`;
4393
4473
  if (column.autoNow) return `client.intake.fields cannot set "${entry}", which the platform fills in.`;
4394
4474
  if (typeof publicRead === "object" && Object.prototype.hasOwnProperty.call(publicRead.where, name)) {
4395
4475
  return `client.intake.fields cannot set publication field "${entry}".`;
@@ -4403,7 +4483,7 @@ function parseIntakePermissions(value, columns, scope, publicRead) {
4403
4483
  }
4404
4484
  }
4405
4485
  if (typeof publicRead === "object" && !privateByDefault(publicRead.where, byName2)) {
4406
- return "client.intake has no fixed publication-field default that guarantees a private row. Give at least one predicate field a private default.";
4486
+ 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.";
4407
4487
  }
4408
4488
  let where;
4409
4489
  if (input.where !== void 0) {
@@ -4623,10 +4703,10 @@ function readPaymentBindings(r, fail) {
4623
4703
  fields[key] = readString(r, fail, `payments.${name}.${key}`).value.toLowerCase();
4624
4704
  return;
4625
4705
  }
4626
- r.expectPunct("{", `opening payments.${name}.onPaid \u2014 declare one transition, { status: { from: [...], to: '...' } }`);
4706
+ r.expectPunct("{", `opening payments.${name}.onPaid, which takes one transition: { status: { from: [...], to: '...' } }`);
4627
4707
  const column = r.readKey(`for the status column in payments.${name}.onPaid`);
4628
4708
  r.expectPunct(":", `after payments.${name}.onPaid.${column.name}`);
4629
- r.expectPunct("{", `opening payments.${name}.onPaid.${column.name} \u2014 declare { from: [...], to: '...' }`);
4709
+ r.expectPunct("{", `opening payments.${name}.onPaid.${column.name}, which takes { from: [...], to: '...' }`);
4630
4710
  const transition = {};
4631
4711
  while (!r.tryPunct("}")) {
4632
4712
  const opt = r.readKey(`for an option of payments.${name}.onPaid.${column.name}`);
@@ -4644,7 +4724,7 @@ function readPaymentBindings(r, fail) {
4644
4724
  r.tryPunct(",");
4645
4725
  if (!r.tryPunct("}")) {
4646
4726
  const extra = r.peek();
4647
- fail(extra.line, `payments.${name}.onPaid declares one status transition only; remove the second entry. Other paid effects run from the signed payment event.`);
4727
+ 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.`);
4648
4728
  }
4649
4729
  const values = [...transition.from, transition.to];
4650
4730
  for (const t of values) {
@@ -4680,7 +4760,7 @@ function readJobBindings(r, fail) {
4680
4760
  }
4681
4761
  function readServiceRoles(r, fail) {
4682
4762
  const entries = readNamed(r, fail, "serviceRoles", "service role", ["tables"], (_key, fields, name) => {
4683
- r.expectPunct("{", `opening serviceRoles.${name}.tables \u2014 declare { table: ['read', ...] }`);
4763
+ r.expectPunct("{", `opening serviceRoles.${name}.tables, which takes { table: ['read', ...] }`);
4684
4764
  const tables = [];
4685
4765
  const tableLines = {};
4686
4766
  while (!r.tryPunct("}")) {
@@ -4713,7 +4793,7 @@ function validateBindings(input, tableInfo, errors) {
4713
4793
  errors.push(`${at(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", which is not declared in db/schema.ts.`);
4714
4794
  return null;
4715
4795
  }
4716
- if (!info.privateUser) errors.push(`${at(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.`);
4796
+ if (!info.privateUser) errors.push(`${at(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.`);
4717
4797
  if (!info.key) errors.push(`${at(entry, "table")}${kind} "${entry.name}" names table "${entry.table}", which needs exactly one id() key.`);
4718
4798
  return info;
4719
4799
  };
@@ -4722,7 +4802,7 @@ function validateBindings(input, tableInfo, errors) {
4722
4802
  const info = rowTable(p, "payment binding");
4723
4803
  if (!info) continue;
4724
4804
  if (info.table.scope.kind === "policy" && reachesAllOf(info.table.scope.policy)) {
4725
- errors.push(`${at(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.`);
4805
+ errors.push(`${at(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.`);
4726
4806
  continue;
4727
4807
  }
4728
4808
  const column = (name) => info.table.columns.find((c) => c.name === name);
@@ -4740,7 +4820,7 @@ function validateBindings(input, tableInfo, errors) {
4740
4820
  if (c.helper !== helper || c.nullable) errors.push(`${at(p, field)}payment binding "${p.name}": "${p.table}"."${name}" must be ${what}.`);
4741
4821
  if (info.identity.includes(name)) errors.push(`${at(p, field)}payment binding "${p.name}": "${p.table}"."${name}" is the table's access column; a payment never moves a row.`);
4742
4822
  const writable = [...info.table.client?.create ?? [], ...info.table.client?.update ?? [], ...info.table.client?.intake?.fields ?? []];
4743
- if (writable.includes(name)) errors.push(`${at(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.`);
4823
+ if (writable.includes(name)) errors.push(`${at(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.`);
4744
4824
  }
4745
4825
  if ((/* @__PURE__ */ new Set([p.amount, p.currency, p.status.column])).size !== 3) errors.push(`${at(p, "")}payment binding "${p.name}" must name three different columns for amount, currency and status.`);
4746
4826
  const status = column(p.status.column);
@@ -4751,7 +4831,7 @@ function validateBindings(input, tableInfo, errors) {
4751
4831
  for (const j of input.jobs ?? []) rowTable(j, "job binding");
4752
4832
  const taken = /* @__PURE__ */ new Set([...BUILT_IN_GROUP_ROLES, ...input.groups?.roles ?? [], ...(input.appRoles ?? []).map((role) => role.name)]);
4753
4833
  for (const s of input.serviceRoles ?? []) {
4754
- if (taken.has(s.name)) errors.push(`${at(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.`);
4834
+ if (taken.has(s.name)) errors.push(`${at(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.`);
4755
4835
  const tableLines = lines.get(s.tables) ?? {};
4756
4836
  for (const grant of s.tables) {
4757
4837
  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.`);
@@ -5095,7 +5175,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5095
5175
  const budget = (line, adding) => {
5096
5176
  expanded += adding;
5097
5177
  if (expanded > maxTokens) {
5098
- fail(line, "reusing constants here expands the declaration past the size db/schema.ts allows. Reuse fewer copies, or write the shared part once.");
5178
+ 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.");
5099
5179
  }
5100
5180
  };
5101
5181
  const expandRange = (from, to, into, owner) => {
@@ -5112,7 +5192,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5112
5192
  }
5113
5193
  const constant = lookup(target, owner);
5114
5194
  if (opener !== "{") {
5115
- fail(t.line, `...${target.value} is only supported inside an object literal { \u2026 }. Reference ${target.value} directly, or write the values inline.`);
5195
+ 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.`);
5116
5196
  }
5117
5197
  if (!constant.objectLiteral) {
5118
5198
  fail(t.line, `...${target.value} spreads a constant that is not an object literal { \u2026 }. Only object constants can be spread.`);
@@ -5138,7 +5218,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5138
5218
  }
5139
5219
  if (owner === null && STATEMENT_WORDS.has(t.value)) {
5140
5220
  if (isIdent(t, "export") && isIdent(next, "const")) {
5141
- fail(t.line, '"export const" is not supported in db/schema.ts; the file exports only the schema. Remove "export" from the constant.');
5221
+ fail(t.line, '"export const" is not supported in db/schema.ts; the file exports only the schema, and constants are declared without "export".');
5142
5222
  }
5143
5223
  into.push(t);
5144
5224
  continue;
@@ -5149,7 +5229,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5149
5229
  fail(t.line, `${t.value} is a constant, not a function \u2014 write ${t.value} without "( \u2026 )".`);
5150
5230
  }
5151
5231
  if (isPunct(next, ".")) {
5152
- 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.`);
5232
+ 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.`);
5153
5233
  }
5154
5234
  budget(t.line, constant.tokens.length);
5155
5235
  append(into, constant.tokens);
@@ -5164,12 +5244,12 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5164
5244
  continue;
5165
5245
  }
5166
5246
  if (isPunct(next, ".")) {
5167
- 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.`);
5247
+ 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.`);
5168
5248
  }
5169
5249
  if (isPunct(next, "(")) {
5170
- 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.`);
5250
+ 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.`);
5171
5251
  }
5172
- 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.`);
5252
+ 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.`);
5173
5253
  }
5174
5254
  };
5175
5255
  const lookup = (t, owner) => {
@@ -5180,37 +5260,37 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5180
5260
  fail(t.line, `constant ${t.value} refers to itself. A constant must be built from values declared above it.`);
5181
5261
  }
5182
5262
  if (at !== void 0) {
5183
- fail(t.line, owner ? `constant ${owner} uses ${t.value}, which is declared later (line ${at}). 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 ${at}. Move the const above export default.`);
5263
+ fail(t.line, owner ? `constant ${owner} uses ${t.value}, which is declared later (line ${at}); ${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 ${at}; a const must come above export default.`);
5184
5264
  }
5185
- 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.`);
5265
+ 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.`);
5186
5266
  };
5187
5267
  while (!isIdent(tokens[i], "export")) {
5188
5268
  const t = tokens[i];
5189
5269
  if (t.kind === "eof") return [...out, t];
5190
5270
  if (isIdent(t, "let") || isIdent(t, "var")) {
5191
- fail(t.line, `"${t.value}" declares a changeable binding; db/schema.ts allows only const. Change "${t.value}" to "const".`);
5271
+ fail(t.line, `"${t.value}" declares a changeable binding; db/schema.ts allows only const, not "${t.value}".`);
5192
5272
  }
5193
5273
  if (!isIdent(t, "const")) {
5194
5274
  if (t.kind === "ident" && constants.has(t.value)) {
5195
- 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.`);
5275
+ 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.`);
5196
5276
  }
5197
5277
  if (isIdent(t, "import")) fail(t.line, "imports must come first in db/schema.ts, before any const.");
5198
5278
  return [...out, ...tokens.slice(i)];
5199
5279
  }
5200
5280
  const nameTok = tokens[i + 1];
5201
5281
  if (!nameTok || nameTok.kind !== "ident") {
5202
- fail(t.line, "destructuring (const { \u2026 } = / const [ \u2026 ] =) is not supported in db/schema.ts. Declare one constant per name: const name = \u2026;");
5282
+ fail(t.line, "destructuring (const { \u2026 } = / const [ \u2026 ] =) is not supported in db/schema.ts; it accepts one constant per name: const name = \u2026;");
5203
5283
  }
5204
5284
  const name = nameTok.value;
5205
5285
  if (callable.has(name) || LITERAL_WORDS.has(name) || STATEMENT_WORDS.has(name) || name === "schema") {
5206
- fail(nameTok.line, `const ${name} shadows a db/schema.ts helper or keyword. Choose another name, such as ${name}Rule.`);
5286
+ fail(nameTok.line, `const ${name} shadows a db/schema.ts helper or keyword; a different name, such as ${name}Rule, does not.`);
5207
5287
  }
5208
5288
  if (imported.has(name)) {
5209
- fail(nameTok.line, `const ${name} has the same name as an import. Choose another name.`);
5289
+ fail(nameTok.line, `const ${name} has the same name as an import; constant names must differ from imports.`);
5210
5290
  }
5211
- if (constants.has(name)) fail(nameTok.line, `const ${name} is declared twice. Keep one declaration.`);
5291
+ if (constants.has(name)) fail(nameTok.line, `const ${name} is declared twice; a constant is declared once.`);
5212
5292
  if (isPunct(tokens[i + 2], ":")) {
5213
- fail(tokens[i + 2].line, `const ${name} has a type annotation; db/schema.ts constants take their type from the helpers. Remove ": \u2026".`);
5293
+ 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.`);
5214
5294
  }
5215
5295
  if (!isPunct(tokens[i + 2], "=")) fail(nameTok.line, `expected "=" after const ${name}.`);
5216
5296
  const start = i + 3;
@@ -5221,16 +5301,16 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5221
5301
  if (v.kind === "punct" && OPENERS.includes(v.value)) depth++;
5222
5302
  else if (v.kind === "punct" && CLOSERS.includes(v.value)) {
5223
5303
  depth--;
5224
- if (depth < 0) fail(v.line, `const ${name} has an unmatched "${v.value}". Balance its brackets.`);
5304
+ if (depth < 0) fail(v.line, `const ${name} has an unmatched "${v.value}"; its brackets are unbalanced.`);
5225
5305
  } 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;
5226
- if (depth === 0 && isPunct(v, "=")) fail(v.line, `${name} is assigned twice; give each constant one value.`);
5306
+ if (depth === 0 && isPunct(v, "=")) fail(v.line, `${name} is assigned twice; each constant takes one value.`);
5227
5307
  }
5228
- if (depth !== 0) fail(nameTok.line, `const ${name} has an unclosed bracket. Balance its brackets.`);
5308
+ if (depth !== 0) fail(nameTok.line, `const ${name} has an unclosed bracket; its brackets are unbalanced.`);
5229
5309
  let end2 = k;
5230
5310
  if (end2 - start >= 2 && isIdent(tokens[end2 - 2], "as") && isIdent(tokens[end2 - 1], "const")) end2 -= 2;
5231
5311
  if (end2 === start) fail(nameTok.line, `const ${name} has no value.`);
5232
5312
  if (isPunct(tokens[k], ",")) {
5233
- fail(tokens[k].line, `one declaration declares several constants; write one const per name (const ${name} = \u2026;).`);
5313
+ fail(tokens[k].line, `one declaration declares several constants; db/schema.ts accepts one const per name (const ${name} = \u2026;).`);
5234
5314
  }
5235
5315
  const value = [];
5236
5316
  expandRange(start, end2, value, name);
@@ -5239,7 +5319,7 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5239
5319
  i = isPunct(tokens[k], ";") ? k + 1 : k;
5240
5320
  }
5241
5321
  if (isIdent(tokens[i], "export") && isIdent(tokens[i + 1], "const")) {
5242
- fail(tokens[i].line, '"export const" is not supported in db/schema.ts; the file exports only the schema. Remove "export" from the constant.');
5322
+ 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".');
5243
5323
  }
5244
5324
  const body = [];
5245
5325
  const open = tokens.findIndex((v, k) => k > i && isPunct(v, "(") && isIdent(tokens[k - 1], "schema"));
@@ -5249,12 +5329,12 @@ function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
5249
5329
  for (let k = end; k < tokens.length; k++) {
5250
5330
  const v = tokens[k];
5251
5331
  if (v.kind === "ident" && constants.has(v.value) && (isPunct(tokens[k + 1], "=") || isPunct(tokens[k + 1], "."))) {
5252
- 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.`);
5332
+ 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.`);
5253
5333
  }
5254
5334
  body.push(v);
5255
5335
  }
5256
5336
  if (out.length + body.length > maxTokens) {
5257
- 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.");
5337
+ 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.");
5258
5338
  }
5259
5339
  return [...out, ...body];
5260
5340
  }
@@ -5364,18 +5444,18 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
5364
5444
  for (const rel of t.relations) {
5365
5445
  if (rel.name === "__proto__" || rel.name === "prototype" || rel.name === "constructor") {
5366
5446
  errors.push(
5367
- `Relation "${t.name}"."${rel.name}" uses a reserved object name. Choose a relation name that can be returned as an ordinary row field.`
5447
+ `Relation "${t.name}"."${rel.name}" uses a reserved object name; a relation name must be returnable as an ordinary row field.`
5368
5448
  );
5369
5449
  } else if (rootNames.has(rel.name)) {
5370
5450
  errors.push(
5371
- `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.`
5451
+ `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.`
5372
5452
  );
5373
5453
  }
5374
5454
  const related = tableByName.get(rel.table);
5375
5455
  if (!related) {
5376
5456
  const marked = markedGone(rel.table);
5377
5457
  errors.push(
5378
- 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.`
5458
+ 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.`
5379
5459
  );
5380
5460
  continue;
5381
5461
  }
@@ -5384,7 +5464,7 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
5384
5464
  const idCol = keySide.columns.find((c) => c.helper === "id");
5385
5465
  if (!idCol) {
5386
5466
  errors.push(
5387
- 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}".`
5467
+ 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.`
5388
5468
  );
5389
5469
  } else {
5390
5470
  rel.parentKey = idCol.name;
@@ -5398,13 +5478,13 @@ function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone
5398
5478
  }
5399
5479
  if (fkCol.references !== keySide.name) {
5400
5480
  errors.push(
5401
- 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.`
5481
+ 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.`
5402
5482
  );
5403
5483
  } else if (idCol) {
5404
5484
  const expectedHelper = idCol.uuid ? "text" : "integer";
5405
5485
  if (fkCol.helper !== expectedHelper) {
5406
5486
  errors.push(
5407
- `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.`
5487
+ `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.`
5408
5488
  );
5409
5489
  }
5410
5490
  }
@@ -5436,7 +5516,7 @@ function validateRowLinks(tables, tableByName, errors) {
5436
5516
  }
5437
5517
  const earlier = childOf.get(child.name);
5438
5518
  if (earlier && earlier !== t.name) {
5439
- errors.push(`Table "${child.name}" is a link child of both "${earlier}" and "${t.name}". Declare it as a link child of one table.`);
5519
+ 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.`);
5440
5520
  continue;
5441
5521
  }
5442
5522
  childOf.set(child.name, t.name);
@@ -5485,7 +5565,7 @@ function validateIntake(tables, tableByName, errors) {
5485
5565
  continue;
5486
5566
  }
5487
5567
  if (column.helper !== "id" && !column.unique) {
5488
- errors.push(`${at} names reference "${field}", which is not unique on "${target.name}". Declare it { unique: true }; a reference must name one row.`);
5568
+ errors.push(`${at} names reference "${field}", which is not unique on "${target.name}". A reference must name one row, so the field needs { unique: true }.`);
5489
5569
  continue;
5490
5570
  }
5491
5571
  if (column.nullable) {
@@ -5599,7 +5679,7 @@ function readFileOwner(r, fail, name) {
5599
5679
  r.expectPunct("{", `in owner() for file collection "${name}"`);
5600
5680
  const key = r.readKey(`in owner() for file collection "${name}"`);
5601
5681
  if (key.name === "visitors") {
5602
- fail(key.line, `owner({ visitors: true }) is not available for file collections yet: an upload needs a signed-in account. Use owner().`);
5682
+ 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().`);
5603
5683
  }
5604
5684
  fail(key.line, `owner() on file collection "${name}" takes no options \u2014 the owner is always the signed-in uploader.`);
5605
5685
  }
@@ -5662,7 +5742,7 @@ function readFileScope(r, fail, name) {
5662
5742
  if (owners !== 1 || !member) fail(tok.line, `anyOf() on file collection "${name}" requires exactly one owner() and one member().`);
5663
5743
  return { kind: "owner_or_member", member };
5664
5744
  }
5665
- return fail(tok.line, `unknown scope "${tok.value}" on file collection "${name}". Use owner(), member(), anyOf(owner(), member()), parent({ table, via }), shared(), or serverOnly().`);
5745
+ 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().`);
5666
5746
  }
5667
5747
  function readCollection(r, fail, name, errors) {
5668
5748
  const callee = r.expectIdent(`for file collection "${name}"`);
@@ -5716,11 +5796,11 @@ function readCollection(r, fail, name, errors) {
5716
5796
  if (limit.name === "maxSize") {
5717
5797
  const raw = r.readLiteral(`for limits.maxSize of "${name}"`);
5718
5798
  limits.maxSize = parseFileSize(raw);
5719
- 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'.`);
5799
+ 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'.`);
5720
5800
  } else {
5721
5801
  limits.types = readStringList(r, fail, `limits.types of file collection "${name}"`).map((t) => t.toLowerCase());
5722
5802
  for (const type of limits.types) {
5723
- 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/*'.`);
5803
+ 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/*'.`);
5724
5804
  }
5725
5805
  }
5726
5806
  if (!r.tryPunct(",")) {
@@ -5741,16 +5821,16 @@ function readCollection(r, fail, name, errors) {
5741
5821
  if (rootError) errors.push(rootError);
5742
5822
  const declaresClient = FILE_OPERATIONS.some((op) => client[op]);
5743
5823
  if (scope.kind === "server_only" && declaresClient) {
5744
- errors.push(`File collection "${name}" is serverOnly() and cannot declare client operations. Remove client, or choose a scope the browser may use.`);
5824
+ errors.push(`File collection "${name}" is serverOnly() and cannot declare client operations; client operations need a scope the browser may use.`);
5745
5825
  }
5746
5826
  if (isPublic && client.read) {
5747
- errors.push(`File collection "${name}" is public: true and also declares client.read. Public already lets anyone read; remove client.read.`);
5827
+ 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.`);
5748
5828
  }
5749
5829
  if (isPublic && (scope.kind === "member" || scope.kind === "owner_or_member")) {
5750
- 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().`);
5830
+ 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().`);
5751
5831
  }
5752
5832
  if (isPublic && scope.kind === "parent") {
5753
- 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.`);
5833
+ 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.`);
5754
5834
  }
5755
5835
  return { name, path: typeof path === "string" ? path : "", scope, public: isPublic, client, limits };
5756
5836
  }
@@ -5761,7 +5841,7 @@ function readFileCollections(r, fail, errors) {
5761
5841
  while (!r.tryPunct("}")) {
5762
5842
  const key = r.readKey("for a file collection name");
5763
5843
  if (!COLLECTION_NAME.test(key.name)) {
5764
- errors.push(`File collection name "${key.name}" is invalid. Use lowercase letters, digits and underscores, starting with a letter.`);
5844
+ errors.push(`File collection name "${key.name}" is invalid. A name is lowercase letters, digits and underscores, starting with a letter.`);
5765
5845
  }
5766
5846
  if (out.some((c) => c.name === key.name)) fail(key.line, `file collection "${key.name}" is declared twice.`);
5767
5847
  r.expectPunct(":", `after file collection "${key.name}"`);
@@ -5798,7 +5878,7 @@ function validateFileCollections(collections, table, errors) {
5798
5878
  continue;
5799
5879
  }
5800
5880
  if (facts.edges + 1 > MAX_FILE_PARENT_EDGES) {
5801
- 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.`);
5881
+ 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.`);
5802
5882
  continue;
5803
5883
  }
5804
5884
  parent.key = facts.key;
@@ -5813,7 +5893,7 @@ function validateFileCollections(collections, table, errors) {
5813
5893
  continue;
5814
5894
  }
5815
5895
  if (membership.clientWrites) {
5816
- errors.push(`Table "${m.membership}" grants membership access to file collection "${a.name}" and cannot allow client writes. Change membership through a server function.`);
5896
+ 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.`);
5817
5897
  }
5818
5898
  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 }).`);
5819
5899
  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 }).`);
@@ -5867,7 +5947,7 @@ function roleVocabularyErrors(policy) {
5867
5947
  dupes(policy.adminGrantableGroupRoles, "groups.adminGrantableRoles");
5868
5948
  dupes(policy.appRoles, "appRoles");
5869
5949
  for (const role of policy.groupRoles) {
5870
- if (BUILT_IN_GROUP_ROLES2.includes(role)) errors.push(`"${role}" is a built-in group role; declare only additional roles.`);
5950
+ if (BUILT_IN_GROUP_ROLES2.includes(role)) errors.push(`"${role}" is a built-in group role; groups.roles lists only additional roles.`);
5871
5951
  }
5872
5952
  for (const role of policy.adminGrantableGroupRoles) {
5873
5953
  if (!policy.groupRoles.includes(role)) errors.push(`groups.adminGrantableRoles names "${role}", which is not in groups.roles.`);
@@ -5976,7 +6056,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
5976
6056
  const where = `line ${declared.line}: `;
5977
6057
  const column = t.columns.find((c) => c.name === node.column);
5978
6058
  if (!column) {
5979
- 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().`);
6059
+ 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().`);
5980
6060
  } else if (column.helper !== "text" || column.nullable) {
5981
6061
  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.`);
5982
6062
  }
@@ -5984,7 +6064,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
5984
6064
  const list = declared.explicit[op] ?? omitted;
5985
6065
  for (const role of list) {
5986
6066
  if (!allRoles.includes(role)) {
5987
- 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.`);
6067
+ 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.`);
5988
6068
  }
5989
6069
  }
5990
6070
  node.roles[op] = sorted(list);
@@ -5996,7 +6076,7 @@ function resolveGroupScopes(tables, vocabulary, errors) {
5996
6076
  }
5997
6077
  }
5998
6078
  if ((branch || allOf) && GROUP_OPERATIONS.every((op) => node.roles[op].length === 0)) {
5999
- 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.`);
6079
+ 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.`);
6000
6080
  }
6001
6081
  if (!(branch && allOf) && t.client?.create && !t.client.create.includes(node.column)) {
6002
6082
  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.`);
@@ -6121,7 +6201,7 @@ function resolveAppRoleScopes(tables, appRoles, errors) {
6121
6201
  if (!declared) continue;
6122
6202
  for (const { op, name, line } of declared.names) {
6123
6203
  if (!vocabulary.has(name)) {
6124
- 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.`);
6204
+ 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.`);
6125
6205
  }
6126
6206
  }
6127
6207
  for (const op of ["update", "delete"]) {
@@ -6131,7 +6211,7 @@ function resolveAppRoleScopes(tables, appRoles, errors) {
6131
6211
  }
6132
6212
  }
6133
6213
  if (branch && APP_ROLE_OPERATIONS.every((op) => node.roles[op].length === 0)) {
6134
- 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.`);
6214
+ 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.`);
6135
6215
  }
6136
6216
  declaredLines.delete(node);
6137
6217
  }
@@ -6184,7 +6264,7 @@ function readAppRoles(r, fail) {
6184
6264
  const key = r.readKey("for an app role name");
6185
6265
  if (out.some((role2) => role2.name === key.name)) fail(key.line, `app role "${key.name}" is declared twice.`);
6186
6266
  r.expectPunct(":", `after app role "${key.name}"`);
6187
- r.expectPunct("{", `opening app role "${key.name}" \u2014 declare it as { grantableBy: [...] }`);
6267
+ r.expectPunct("{", `opening app role "${key.name}", which takes { grantableBy: [...] }`);
6188
6268
  const role = { name: key.name, grantableBy: [] };
6189
6269
  const seen = /* @__PURE__ */ new Set();
6190
6270
  while (!r.tryPunct("}")) {
@@ -6363,7 +6443,7 @@ function tokenize(source) {
6363
6443
  continue;
6364
6444
  }
6365
6445
  if (ch === "`") {
6366
- throw new SchemaTsError(`line ${line}: template literals are not allowed in db/schema.ts \u2014 use plain 'single-quoted' strings.`);
6446
+ throw new SchemaTsError(`line ${line}: template literals are not allowed in db/schema.ts; strings are plain 'single-quoted' strings.`);
6367
6447
  }
6368
6448
  if (ch === "'" || ch === '"') {
6369
6449
  const startLine = line;
@@ -6564,11 +6644,11 @@ function checkDeclaredName(kind, raw, line) {
6564
6644
  throw new SchemaTsError(`line ${line}: ${kind} name "${raw.slice(0, 40)}\u2026" is longer than ${MAX_NAME_LENGTH} characters.`);
6565
6645
  }
6566
6646
  if (!SAFE_IDENT.test(raw)) {
6567
- throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" is invalid. Use letters, digits, and underscores, starting with a letter or underscore.`);
6647
+ throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" is invalid. A name is letters, digits, and underscores, starting with a letter or underscore.`);
6568
6648
  }
6569
6649
  const name = raw.toLowerCase();
6570
6650
  if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
6571
- 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.`);
6651
+ 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.`);
6572
6652
  }
6573
6653
  return name;
6574
6654
  }
@@ -6582,12 +6662,12 @@ function readColumn(r, tableName, rawName, line) {
6582
6662
  }
6583
6663
  if (TABLE_MARKER_HELPERS.has(helper)) {
6584
6664
  throw new SchemaTsError(
6585
- `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.`
6665
+ `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.`
6586
6666
  );
6587
6667
  }
6588
6668
  if (!COLUMN_HELPERS.has(helper)) {
6589
6669
  throw new SchemaTsError(
6590
- `line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Use one of: id, text, number, integer, bigint, boolean, timestamp, json, blob.`
6670
+ `line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Column types are id, text, number, integer, bigint, boolean, timestamp, json, and blob.`
6591
6671
  );
6592
6672
  }
6593
6673
  r.expectPunct("(", `after "${helper}"`);
@@ -6644,7 +6724,7 @@ function readColumn(r, tableName, rawName, line) {
6644
6724
  );
6645
6725
  }
6646
6726
  if (t === "integer" && typeof value === "number" && !Number.isSafeInteger(value)) {
6647
- 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.`);
6727
+ 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.`);
6648
6728
  }
6649
6729
  if (t === "bigint" && typeof value === "string" && !isCanonicalBigintText(value)) {
6650
6730
  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.`);
@@ -6684,7 +6764,7 @@ function readColumn(r, tableName, rawName, line) {
6684
6764
  if (col.hasDefault && col.default === null) {
6685
6765
  if (!col.nullable) {
6686
6766
  throw new SchemaTsError(
6687
- `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 }.`
6767
+ `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 }.`
6688
6768
  );
6689
6769
  }
6690
6770
  col.hasDefault = false;
@@ -6927,7 +7007,7 @@ function readRelations(r, tableName) {
6927
7007
  while (!r.tryPunct("}")) {
6928
7008
  const key = r.readKey(`for a relation name of table "${tableName}"`);
6929
7009
  if (!SAFE_IDENT.test(key.name) || key.name.length > MAX_NAME_LENGTH) {
6930
- 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.`);
7010
+ 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.`);
6931
7011
  }
6932
7012
  const name = key.name.toLowerCase();
6933
7013
  if (seen.has(name)) throw new SchemaTsError(`line ${key.line}: relation "${key.name}" on table "${tableName}" is declared twice.`);
@@ -6935,7 +7015,7 @@ function readRelations(r, tableName) {
6935
7015
  r.expectPunct(":", `after relation "${key.name}" on table "${tableName}"`);
6936
7016
  const callee = r.expectIdent(`for relation "${key.name}" on table "${tableName}"`);
6937
7017
  if (callee.value !== "hasMany" && callee.value !== "belongsTo") {
6938
- 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').`);
7018
+ 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').`);
6939
7019
  }
6940
7020
  const kind = callee.value;
6941
7021
  const side = kind === "hasMany" ? "child" : "parent";
@@ -6968,7 +7048,7 @@ function readScope(r, tableName) {
6968
7048
  const tok = r.expectIdent(`for the scope of table "${tableName}"`);
6969
7049
  if (!SCOPE_HELPERS.has(tok.value)) {
6970
7050
  throw new SchemaTsError(
6971
- `line ${tok.line}: unknown scope "${tok.value}" on table "${tableName}". Use owner(), group(), appRole(), member(), anyOf(), allOf(), parent(), shared(), or serverOnly().`
7051
+ `line ${tok.line}: unknown scope "${tok.value}" on table "${tableName}". Table scopes are owner(), group(), appRole(), member(), anyOf(), allOf(), parent(), shared(), and serverOnly().`
6972
7052
  );
6973
7053
  }
6974
7054
  if (tok.value === "anyOf") return readAnyOfScope(r, tableName, tok.line);
@@ -7063,7 +7143,7 @@ function readTable(r, rawName, nameLine) {
7063
7143
  const callee = r.expectIdent(`for table "${rawName}"`);
7064
7144
  if (callee.value === "removed") {
7065
7145
  throw new SchemaTsError(
7066
- `line ${callee.line}: table "${rawName}" uses removed(), which marks a single column. To remove this table, mark it ${rawName}: removedTable().`
7146
+ `line ${callee.line}: table "${rawName}" uses removed(), which marks a single column; a whole table is removed with ${rawName}: removedTable().`
7067
7147
  );
7068
7148
  }
7069
7149
  if (TABLE_MARKER_HELPERS.has(callee.value)) {
@@ -7100,7 +7180,7 @@ function readTable(r, rawName, nameLine) {
7100
7180
  }
7101
7181
  }
7102
7182
  if (columns.length === 0) {
7103
- 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())" : ""}.`);
7183
+ 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())" : ""}.`);
7104
7184
  }
7105
7185
  if (columns.length > MAX_COLUMNS) {
7106
7186
  throw new SchemaTsError(`line ${nameLine}: table "${rawName}" declares ${columns.length} columns (max ${MAX_COLUMNS}).`);
@@ -7158,7 +7238,7 @@ function readTable(r, rawName, nameLine) {
7158
7238
  const clash = columns.find((c) => c.name === managedOwner.column);
7159
7239
  if (clash) {
7160
7240
  throw new SchemaTsError(
7161
- `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' }).`
7241
+ `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.`
7162
7242
  );
7163
7243
  }
7164
7244
  if (removedColumns.includes(managedOwner.column)) {
@@ -7173,7 +7253,7 @@ function readTable(r, rawName, nameLine) {
7173
7253
  if (!c.renamedFrom) continue;
7174
7254
  if (declaredNow.has(c.renamedFrom)) {
7175
7255
  throw new SchemaTsError(
7176
- `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.`
7256
+ `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.`
7177
7257
  );
7178
7258
  }
7179
7259
  if (managedOwner && c.renamedFrom === managedOwner.column) {
@@ -7183,7 +7263,7 @@ function readTable(r, rawName, nameLine) {
7183
7263
  }
7184
7264
  if (removedColumns.includes(c.renamedFrom)) {
7185
7265
  throw new SchemaTsError(
7186
- `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.`
7266
+ `line ${nameLine}: "${rawName}"."${c.renamedFrom}" is marked removed() AND named by renamedFrom on "${c.name}" \u2014 it cannot be both removed and renamed.`
7187
7267
  );
7188
7268
  }
7189
7269
  if (renameSources.has(c.renamedFrom)) {
@@ -7218,7 +7298,7 @@ function readTable(r, rawName, nameLine) {
7218
7298
  const single = entry.columns.length === 1 ? columns.find((c) => c.name === entry.columns[0]) : void 0;
7219
7299
  if (single) {
7220
7300
  if (single.helper === "id") {
7221
- throw new SchemaTsError(`line ${entry.line}: the primary key "${single.name}" on table "${rawName}" is already unique; remove its one-column "unique" group.`);
7301
+ 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.`);
7222
7302
  }
7223
7303
  single.unique = true;
7224
7304
  continue;
@@ -7253,7 +7333,7 @@ function readTable(r, rawName, nameLine) {
7253
7333
  }
7254
7334
  function extractSchemaTs(source, options = {}) {
7255
7335
  if (typeof source !== "string" || source.trim().length === 0) {
7256
- return { ok: false, errors: ["db/schema.ts is empty. Declare at least one table, or remove the file to leave the database unmanaged."] };
7336
+ return { ok: false, errors: ["db/schema.ts is empty. It needs at least one table; without the file, the database is unmanaged."] };
7257
7337
  }
7258
7338
  if (source.length > MAX_SOURCE_BYTES) {
7259
7339
  return { ok: false, errors: [`db/schema.ts is larger than ${Math.floor(MAX_SOURCE_BYTES / 1024)}KB \u2014 that is not a schema declaration.`] };
@@ -7312,7 +7392,7 @@ function extractSchemaTs(source, options = {}) {
7312
7392
  );
7313
7393
  }
7314
7394
  if (tables.length === 0 && removedTables.length === 0 && exportedTables.length === 0 && !managedSearch && !fileCollections?.length) {
7315
- return { ok: false, errors: ["db/schema.ts declares no tables. Declare at least one table, or remove the file to leave the database unmanaged."] };
7395
+ return { ok: false, errors: ["db/schema.ts declares no tables. It needs at least one table; without the file, the database is unmanaged."] };
7316
7396
  }
7317
7397
  if (tables.length > MAX_TABLES) {
7318
7398
  return { ok: false, errors: [`db/schema.ts declares ${tables.length} tables (max ${MAX_TABLES}).`] };
@@ -7324,7 +7404,7 @@ function extractSchemaTs(source, options = {}) {
7324
7404
  if (c.references && !names.has(c.references)) {
7325
7405
  const marked = removedTables.includes(c.references) || exportedTables.includes(c.references);
7326
7406
  errors.push(
7327
- 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.`
7407
+ 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.`
7328
7408
  );
7329
7409
  }
7330
7410
  }
@@ -7336,7 +7416,7 @@ function extractSchemaTs(source, options = {}) {
7336
7416
  if (!key) continue;
7337
7417
  const fits = key.uuid ? c.helper === "text" : c.helper === "integer" || c.helper === "number";
7338
7418
  if (fits) continue;
7339
- 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().`);
7419
+ 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().`);
7340
7420
  }
7341
7421
  }
7342
7422
  const tableByName = new Map(tables.map((t) => [t.name, t]));
@@ -7387,7 +7467,7 @@ function extractSchemaTs(source, options = {}) {
7387
7467
  }
7388
7468
  const membershipCols = knownColumns(membershipTable);
7389
7469
  if (membershipTable.client && (membershipTable.client.create !== null || membershipTable.client.update !== null || membershipTable.client.delete || membershipTable.client.intake !== void 0)) {
7390
- errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes. Change membership through a server function.`);
7470
+ errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes; membership changes go through a server function.`);
7391
7471
  }
7392
7472
  if (!membershipCols.has(sc.memberUser)) {
7393
7473
  errors.push(