@somewhere-tech/cli 0.37.20 → 0.37.22

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.
@@ -45,7 +45,7 @@ var require_declared_data_contract = __commonJS({
45
45
  function fail(detail) {
46
46
  throw new Error(`Invalid declared data contract: ${detail}`);
47
47
  }
48
- function record(value, name) {
48
+ function record2(value, name) {
49
49
  if (!value || typeof value !== "object" || Array.isArray(value)) fail(name);
50
50
  }
51
51
  function fields(value, columns, name, empty = false) {
@@ -55,11 +55,11 @@ var require_declared_data_contract = __commonJS({
55
55
  return result.sort();
56
56
  }
57
57
  function policy(value, label, edges = 0) {
58
- record(value, label);
58
+ record2(value, label);
59
59
  const keys = Object.keys(value).sort().join(",");
60
60
  if (value.k === "o" && keys === "c,k" && identifier.test(value.c)) return { k: "o", c: value.c };
61
61
  if (value.k === "m" && keys === "k,m") {
62
- record(value.m, label + ".member");
62
+ record2(value.m, label + ".member");
63
63
  const { g, m, u, mg, o } = value.m;
64
64
  const memberKeys = Object.keys(value.m).sort().join(",");
65
65
  if (!["g,m,mg,u", "g,m,mg,o,u"].includes(memberKeys) || !identifier.test(m) || !identifier.test(u) || !Array.isArray(g) || !g.length || !Array.isArray(mg) || g.length !== mg.length || [...g, ...mg].some((field) => typeof field !== "string" || !identifier.test(field)) || o !== void 0 && (!Array.isArray(o) || JSON.stringify(o) !== JSON.stringify(["read", "create", "update", "delete"].filter((operation) => o.includes(operation))) || !o.includes("read") && (o.includes("update") || o.includes("delete")))) fail(label);
@@ -86,7 +86,7 @@ var require_declared_data_contract = __commonJS({
86
86
  fail(label);
87
87
  }
88
88
  function policyRoles(value, label) {
89
- record(value, label + ".roles");
89
+ record2(value, label + ".roles");
90
90
  if (Object.keys(value).sort().join(",") !== "create,delete,read,update") fail(label);
91
91
  const roles = {};
92
92
  for (const op of ["read", "create", "update", "delete"]) {
@@ -102,26 +102,31 @@ var require_declared_data_contract = __commonJS({
102
102
  else if (value.k === "a") value.p.forEach((item) => policyMembershipTables(item, out));
103
103
  else if (value.k === "p") policyMembershipTables(value.p, out);
104
104
  }
105
- function canonicalize2(schema, intents, scopes) {
106
- record(schema, "schema");
107
- record(intents, "intents");
108
- record(scopes, "scopes");
105
+ function validateInsertReceiptCapability(value) {
106
+ if (value !== void 0 && value !== "insert-v1") fail("insert receipt capability");
107
+ return value;
108
+ }
109
+ function canonicalize2(schema, intents, scopes, insertReceiptCapability) {
110
+ validateInsertReceiptCapability(insertReceiptCapability);
111
+ record2(schema, "schema");
112
+ record2(intents, "intents");
113
+ record2(scopes, "scopes");
109
114
  const tables = [];
110
115
  const membershipTables = new Set(Object.values(schema).filter((table) => table && table.member).map((table) => table.member.m));
111
116
  for (const table of Object.values(schema)) if (table?.policy) policyMembershipTables(table.policy, membershipTables);
112
117
  for (const name of Object.keys(schema).sort()) {
113
118
  const table = schema[name];
114
- record(table, `table ${name}`);
119
+ record2(table, `table ${name}`);
115
120
  if (table.client === void 0) {
116
121
  if (table.clientPrimaryKey !== void 0) fail(`${name} has a primary key without client permissions`);
117
122
  continue;
118
123
  }
119
124
  if (!identifier.test(name) || name.startsWith("_") || name.startsWith("sqlite_")) fail(`table ${name}`);
120
- record(table.client, `${name}.client`);
125
+ record2(table.client, `${name}.client`);
121
126
  if (!Array.isArray(table.columns) || !table.columns.length) fail(`${name}.columns`);
122
127
  const seen = /* @__PURE__ */ new Set();
123
128
  const columns = table.columns.map((column) => {
124
- record(column, `${name}.column`);
129
+ record2(column, `${name}.column`);
125
130
  if (typeof column.n !== "string" || !identifier.test(column.n) || seen.has(column.n) || !types.has(column.t)) fail(`${name}.column`);
126
131
  if (column.nul !== void 0 && column.nul !== 1 || column.d !== void 0 && column.d !== 1) fail(`${name}.${column.n} flags`);
127
132
  seen.add(column.n);
@@ -130,14 +135,14 @@ var require_declared_data_contract = __commonJS({
130
135
  const pk = table.clientPrimaryKey;
131
136
  if (!seen.has(pk) || !["integer", "text"].includes(columns.find((column) => column.n === pk).t)) fail(`${name} primary key`);
132
137
  const input = table.client;
133
- if (Object.keys(input).some((key) => !["read", "publicRead", "create", "update", "delete", "identity", "links"].includes(key))) fail(`${name} permissions`);
138
+ if (Object.keys(input).some((key) => !["read", "publicRead", "create", "update", "delete", "identity", "links", "intake"].includes(key))) fail(`${name} permissions`);
134
139
  const read = typeof input.read === "boolean" ? input.read : fields(input.read, seen, `${name}.read`);
135
140
  if (Array.isArray(read) && !read.includes(pk)) fail(`${name}.read must include the primary key`);
136
141
  let publicRead = input.publicRead;
137
142
  if (typeof publicRead !== "boolean") {
138
- record(publicRead, `${name} public read`);
143
+ record2(publicRead, `${name} public read`);
139
144
  if (Object.keys(publicRead).some((key) => key !== "where")) fail(`${name} public read`);
140
- record(publicRead.where, `${name} public read predicate`);
145
+ record2(publicRead.where, `${name} public read predicate`);
141
146
  const entries = Object.entries(publicRead.where);
142
147
  if (!entries.length || entries.length > 8) fail(`${name} public read predicate`);
143
148
  const where = {};
@@ -171,7 +176,7 @@ var require_declared_data_contract = __commonJS({
171
176
  if ([...create || [], ...update || []].some((field) => field === pk || field === owner || field === author)) fail(`${name} identity writes`);
172
177
  let member = null;
173
178
  if (intent === "member") {
174
- record(table.member, `${name}.member`);
179
+ record2(table.member, `${name}.member`);
175
180
  const { g, m, u, mg, o } = table.member;
176
181
  if (typeof m !== "string" || typeof u !== "string" || !identifier.test(m) || !identifier.test(u) || !Array.isArray(mg) || mg.some((field) => typeof field !== "string" || !identifier.test(field))) fail(`${name}.member`);
177
182
  fields(g, seen, `${name}.member.g`);
@@ -194,8 +199,9 @@ var require_declared_data_contract = __commonJS({
194
199
  if (normalizedPolicy?.k === "p" && create !== null && !create.includes(normalizedPolicy.v)) fail(`${name}.policy parent create`);
195
200
  let links = null;
196
201
  if (input.links !== void 0) {
197
- record(input.links, `${name}.links`);
198
- if (Object.keys(input.links).sort().join() !== "children,edit,maxDays,read") fail(`${name}.links`);
202
+ record2(input.links, `${name}.links`);
203
+ const linkKeys = Object.keys(input.links).sort().join();
204
+ if (linkKeys !== "children,edit,maxDays,read" && !(linkKeys === "anonymousCreate,children,edit,maxDays,read" && input.links.anonymousCreate === true)) fail(`${name}.links`);
199
205
  if (!(intent === "scoped" && !visitors) && normalizedPolicy?.k !== "a") fail(`${name}.links owner`);
200
206
  const readable = read === true ? columns.filter((column) => column.n !== author).map((column) => column.n) : read || [];
201
207
  const linkRead = fields(input.links.read, new Set(readable), `${name}.links.read`);
@@ -205,12 +211,31 @@ var require_declared_data_contract = __commonJS({
205
211
  if (!Array.isArray(children) || children.some((child) => typeof child !== "string" || !identifier.test(child)) || new Set(children).size !== children.length) fail(`${name}.links.children`);
206
212
  const maxDays = input.links.maxDays;
207
213
  if (maxDays !== null && !(Number.isSafeInteger(maxDays) && maxDays >= 1 && maxDays <= 365)) fail(`${name}.links.maxDays`);
208
- links = { read: linkRead, edit: linkEdit, children: [...children].sort(), maxDays };
214
+ const anonymousCreate = input.links.anonymousCreate === true;
215
+ if (anonymousCreate && (intent !== "scoped" || visitors || normalizedPolicy || !create || create.length === 0 || columns.find((column) => column.n === pk).t !== "integer" || columns.some((column) => column.t === "blob"))) fail(`${name}.links.anonymousCreate`);
216
+ links = { read: linkRead, edit: linkEdit, children: [...children].sort(), maxDays, ...anonymousCreate ? { anonymousCreate: true } : {} };
217
+ }
218
+ let intake;
219
+ if (input.intake !== void 0) {
220
+ record2(input.intake, `${name}.intake`);
221
+ if (!["fields,target", "fields,target,where"].includes(Object.keys(input.intake).sort().join(","))) fail(`${name}.intake`);
222
+ record2(input.intake.target, `${name}.intake.target`);
223
+ if (Object.keys(input.intake.target).sort().join(",") !== "field,table" || ![input.intake.target.table, input.intake.target.field].every((value) => typeof value === "string" && identifier.test(value))) fail(`${name}.intake.target`);
224
+ if (membershipTables.has(name) || intent !== "scoped" || visitors || normalizedPolicy || columns.find((column) => column.n === pk).t !== "integer" || publicRead === true) fail(`${name}.intake owner`);
225
+ const accepted = fields(input.intake.fields, seen, `${name}.intake.fields`);
226
+ if (accepted.some((field) => field === pk || field === owner || field === author || columns.find((column) => column.n === field).t === "blob" || typeof publicRead === "object" && Object.hasOwn(publicRead.where, field)) || columns.some((column) => column.n !== pk && column.n !== owner && column.nul !== 1 && column.d !== 1 && !accepted.includes(column.n))) fail(`${name}.intake.fields`);
227
+ intake = { target: { table: input.intake.target.table, field: input.intake.target.field }, fields: accepted };
228
+ if (input.intake.where !== void 0) {
229
+ record2(input.intake.where, `${name}.intake.where`);
230
+ const keys = Object.keys(input.intake.where).sort();
231
+ if (!keys.length || keys.length > 8) fail(`${name}.intake.where`);
232
+ intake.where = Object.fromEntries(keys.map((field) => [field, input.intake.where[field]]));
233
+ }
209
234
  }
210
235
  tables.push({
211
236
  name,
212
237
  columns,
213
- client: { identity: input.identity, read, publicRead, create, update, delete: input.delete, ...links ? { links } : {} },
238
+ client: { identity: input.identity, read, publicRead, create, update, delete: input.delete, ...links ? { links } : {}, ...intake ? { intake } : {} },
214
239
  primaryKey: pk,
215
240
  intent,
216
241
  owner,
@@ -221,6 +246,19 @@ var require_declared_data_contract = __commonJS({
221
246
  });
222
247
  }
223
248
  const tableByName = new Map(tables.map((table) => [table.name, table]));
249
+ for (const table of tables) {
250
+ const intake = table.client.intake;
251
+ if (!intake) continue;
252
+ const target = tableByName.get(intake.target.table);
253
+ const column = target?.columns.find((column2) => column2.n === intake.target.field);
254
+ const rawColumn = schema[intake.target.table]?.columns?.find((column2) => column2.n === intake.target.field);
255
+ if (!target || target.intent !== "scoped" || target.visitors || !target.client.publicRead || !Array.isArray(target.client.read) || !target.client.read.includes(intake.target.field) || !column || column.nul === 1 || !["text", "integer", "bigint"].includes(column.t) || rawColumn?.u !== 1) fail(`${table.name}.intake target`);
256
+ for (const [field, value] of Object.entries(intake.where || {})) {
257
+ const c = target.columns.find((column2) => column2.n === field);
258
+ if (!c) fail(`${table.name}.intake.where field`);
259
+ if (value === null ? c.nul !== 1 : c.t === "boolean" ? typeof value !== "boolean" : c.t === "integer" ? !Number.isSafeInteger(value) : c.t === "number" ? typeof value !== "number" || !Number.isFinite(value) : c.t === "bigint" ? !canonicalBigintText(value) : ["text", "timestamp"].includes(c.t) ? typeof value !== "string" || value.length > 256 : true) fail(`${table.name}.intake.where type`);
260
+ }
261
+ }
224
262
  for (const table of tables) {
225
263
  const declared = schema[table.name].relations;
226
264
  if (declared === void 0) continue;
@@ -228,7 +266,7 @@ var require_declared_data_contract = __commonJS({
228
266
  const relations = [];
229
267
  const names = /* @__PURE__ */ new Set();
230
268
  for (const relation of declared) {
231
- record(relation, `${table.name}.relation`);
269
+ record2(relation, `${table.name}.relation`);
232
270
  if (Object.keys(relation).some((key) => !["name", "kind", "table", "fk", "parentKey"].includes(key))) fail(`${table.name}.relation`);
233
271
  const { name, kind, table: relatedName, fk, parentKey } = relation;
234
272
  if (![name, relatedName, fk, parentKey].every((value) => typeof value === "string" && identifier.test(value)) || names.has(name)) {
@@ -258,7 +296,7 @@ var require_declared_data_contract = __commonJS({
258
296
  if (!relation || !child || child.policy?.k !== "p" || child.policy.v !== relation.fk || child.policy.t !== table.name) fail(`${table.name}.links.children`);
259
297
  }
260
298
  }
261
- return JSON.stringify({ version: 1, tables });
299
+ return JSON.stringify({ version: 1, tables, ...insertReceiptCapability === void 0 ? {} : { insert_receipt: insertReceiptCapability } });
262
300
  }
263
301
  function columnType(column) {
264
302
  const type = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
@@ -273,11 +311,176 @@ var require_declared_data_contract = __commonJS({
273
311
  return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
274
312
  }
275
313
  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';
276
- function describeDataClient2(authority, contract_digest) {
277
- const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes) : canonicalize2({}, {}, {});
314
+ var INSERT_CLIENT_RUNTIME = String.raw`
315
+ function insertReceipt(value) {
316
+ if (!value || typeof value !== 'object' || Array.isArray(value)
317
+ || Object.keys(value).sort().join(',') !== 'created_at,refuse_until,replayed,result_until'
318
+ || typeof value.replayed !== 'boolean' || !Number.isSafeInteger(value.created_at) || value.created_at <= 0
319
+ || value.result_until !== value.created_at + 86400000 || value.refuse_until !== value.created_at + 691200000) return null;
320
+ return Object.freeze({...value});
321
+ }
322
+ function insertError(status, payload) {
323
+ const error = new DataError(status, payload);
324
+ if (payload && ['committed', 'unknown', 'read_failed', 'not_committed'].includes(payload.outcome)) error.outcome = payload.outcome;
325
+ if (payload && payload.write_committed === true) error.write_committed = true;
326
+ error.retry = false; error.retryable = false;
327
+ const receipt = insertReceipt(payload && payload.idempotency);
328
+ if (receipt) error.idempotency = receipt;
329
+ return error;
330
+ }
331
+ // One logical request's outcome across its explicit retries (one value per request handle). A later
332
+ // refusal, or this client's local retry horizon, describes only that attempt: it never erases an
333
+ // earlier committed result, and after an unknown attempt it never proves the write absent.
334
+ function requestOutcome() {
335
+ let known = null;
336
+ return {
337
+ done(result) { known = 'committed'; return result; },
338
+ failed(error) {
339
+ if (error.outcome === 'committed' || error.write_committed === true) { known = 'committed'; return error; }
340
+ if (known === 'committed') {
341
+ error.outcome = 'committed'; error.write_committed = true;
342
+ error.message += ' An earlier attempt of this request was already applied; do not create it again.';
343
+ } else if (error.outcome === 'unknown') known = 'unknown';
344
+ else if (known === 'unknown') {
345
+ error.outcome = 'unknown';
346
+ error.message += ' An earlier attempt of this request may still have been applied.';
347
+ }
348
+ return error;
349
+ },
350
+ };
351
+ }
352
+ async function createWithReceipt(table, values, extra, eligible) {
353
+ // create takes only values: the retry key is a private random nonce this client allocates,
354
+ // never a caller choice. Anything more refuses before the network.
355
+ if (extra.length !== 0) throw new DataError(400, {error:'DATA_INPUT_INVALID', message:'create takes only the values to insert. Nothing was sent.'});
356
+ if (!eligible) return invoke(table, 'create', {values});
357
+ // 128 random bits as 32 lowercase hex characters: the platform's exact client key format.
358
+ const key = Array.from(crypto.getRandomValues(new Uint8Array(16)), byte => byte.toString(16).padStart(2,'0')).join('');
359
+ const createdAt = Date.now();
360
+ // Serialize once: retries never observe later caller mutations or re-run toJSON.
361
+ const body = JSON.stringify({values, contract, table, operation:'create', idempotency_key:key});
362
+ let handle;
363
+ const attach = target => { Object.defineProperty(target, 'request', {value:handle, enumerable:false, writable:false, configurable:false}); return target; };
364
+ const outcome = requestOutcome();
365
+ const attempt = async () => {
366
+ const now = Date.now();
367
+ if (!Number.isFinite(createdAt) || !Number.isFinite(now) || now < createdAt || now >= createdAt + 691200000) {
368
+ throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
369
+ }
370
+ let response, payload;
371
+ try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/json'}, body}); }
372
+ 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'})); }
373
+ try { payload = await response.json(); }
374
+ catch (_) { throw attach(insertError(response.status, {error:'INVALID_DATA_RESPONSE', message:'The write response could not be read. Retry this request with its original key.', outcome:'unknown'})); }
375
+ if (!response.ok) throw attach(insertError(response.status, payload));
376
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !insertReceipt(payload.idempotency)) {
377
+ // A successful old/malformed response cannot establish receipt protection.
378
+ throw insertError(502, {error:'IDEMPOTENCY_RECEIPT_INVALID', message:'The write succeeded, but retry protection could not be confirmed. Do not retry it.', outcome:'committed', write_committed:true});
379
+ }
380
+ payload.idempotency = insertReceipt(payload.idempotency);
381
+ return attach(payload);
382
+ };
383
+ const send = () => attempt().then(outcome.done, error => { throw outcome.failed(error); });
384
+ handle = Object.freeze({key, createdAt, retry:send});
385
+ return send();
386
+ }
387
+ `;
388
+ var ANONYMOUS_CLIENT_RUNTIME = String.raw`
389
+ function creatorCapability(grant, secret, table) {
390
+ const capability = {grant};
391
+ Object.defineProperty(capability, 'secret', {value:secret, enumerable:false, writable:false, configurable:false});
392
+ // The creator link: the platform landing page, with everything in the fragment (never sent to a server).
393
+ const route = location.pathname + location.search + location.hash;
394
+ Object.defineProperty(capability, 'url', {value:location.origin + '/__sw/creator#' + grant + '.' + secret + '.' + table + '.' + b64url(route), enumerable:false, writable:false, configurable:false});
395
+ Object.defineProperty(capability, 'toJSON', {value:() => ({grant}), enumerable:false, writable:false, configurable:false});
396
+ return Object.freeze(capability);
397
+ }
398
+ async function createAnonymous(table, values, extra) {
399
+ if (extra.length !== 0) throw new DataError(400, {error:'DATA_INPUT_INVALID', message:'createAnonymous takes only the values to insert. Nothing was sent.'});
400
+ const hex = size => Array.from(crypto.getRandomValues(new Uint8Array(size)), byte => byte.toString(16).padStart(2,'0')).join('');
401
+ const key = hex(16), secret = hex(32), createdAt = Date.now();
402
+ const body = JSON.stringify({values, contract, table, operation:'create_anonymous', operation_key:key, creator_secret:secret});
403
+ let handle;
404
+ const attach = target => { Object.defineProperty(target, 'request', {value:handle, enumerable:false, writable:false, configurable:false}); return target; };
405
+ const outcome = requestOutcome();
406
+ const attempt = async () => {
407
+ const now = Date.now();
408
+ if (!Number.isFinite(createdAt) || !Number.isFinite(now) || now < createdAt || now >= createdAt + 691200000) {
409
+ throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
410
+ }
411
+ let response, payload;
412
+ try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', cache:'no-store', headers:{'Content-Type':'application/json'}, body}); }
413
+ 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'})); }
414
+ try { payload = await response.json(); }
415
+ catch (_) { throw attach(insertError(response.status, {error:'INVALID_DATA_RESPONSE', message:'The write response could not be read. Retry this request with its original key.', outcome:'unknown'})); }
416
+ if (!response.ok) throw attach(insertError(response.status, payload));
417
+ const receipt = payload && typeof payload === 'object' && !Array.isArray(payload) ? insertReceipt(payload.idempotency) : null;
418
+ if (!receipt || typeof payload.grant !== 'string' || !/^crt_[0-9a-f]{32}$/.test(payload.grant)) {
419
+ throw insertError(502, {error:'IDEMPOTENCY_RECEIPT_INVALID', message:'The row was created, but retry protection could not be confirmed. Do not retry it.', outcome:'committed', write_committed:true});
420
+ }
421
+ return attach({data:payload.data === undefined ? null : payload.data, grant:payload.grant, idempotency:receipt, creator:creatorCapability(payload.grant, secret, table)});
422
+ };
423
+ const send = () => attempt().then(outcome.done, error => { throw outcome.failed(error); });
424
+ handle = Object.freeze({key, createdAt, retry:send});
425
+ return send();
426
+ }
427
+ `;
428
+ var INTAKE_CLIENT_RUNTIME = String.raw`
429
+ async function intake(table, reference, values, extra) {
430
+ if (extra.length !== 0) throw new DataError(400, {error:'DATA_INPUT_INVALID', message:'intake takes only the reference and the values to submit. Nothing was sent.'});
431
+ const key = Array.from(crypto.getRandomValues(new Uint8Array(16)), byte => byte.toString(16).padStart(2,'0')).join('');
432
+ const createdAt = Date.now();
433
+ const body = JSON.stringify({reference, values, contract, table, operation:'intake', operation_key:key});
434
+ let handle;
435
+ const attach = target => { Object.defineProperty(target, 'request', {value:handle, enumerable:false, writable:false, configurable:false}); return target; };
436
+ const outcome = requestOutcome();
437
+ const attempt = async () => {
438
+ const now = Date.now();
439
+ if (!Number.isFinite(createdAt) || !Number.isFinite(now) || now < createdAt || now >= createdAt + 691200000) {
440
+ throw attach(insertError(410, {error:'IDEMPOTENCY_RETRY_EXPIRED', message:'This request can no longer be retried safely. Nothing was sent.'}));
441
+ }
442
+ let response, payload;
443
+ try { response = await fetch('/__sw/data', {method:'POST', credentials:'same-origin', cache:'no-store', headers:{'Content-Type':'application/json'}, body}); }
444
+ 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'})); }
445
+ try { payload = await response.json(); }
446
+ catch (_) { throw attach(insertError(response.status, {error:'INVALID_DATA_RESPONSE', message:'The write response could not be read. Retry this request with its original key.', outcome:'unknown'})); }
447
+ if (!response.ok) throw attach(insertError(response.status, payload));
448
+ const receipt = payload && typeof payload === 'object' && !Array.isArray(payload) && payload.accepted === true ? insertReceipt(payload.idempotency) : null;
449
+ if (!receipt) {
450
+ throw attach(insertError(502, {error:'IDEMPOTENCY_RECEIPT_INVALID', message:'The submission was accepted, but retry protection could not be confirmed. Do not retry it.', outcome:'committed', write_committed:true}));
451
+ }
452
+ return attach({accepted:true, idempotency:receipt});
453
+ };
454
+ const send = () => attempt().then(outcome.done, error => { throw outcome.failed(error); });
455
+ handle = Object.freeze({key, createdAt, retry:send});
456
+ return send();
457
+ }
458
+ `;
459
+ 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=(action,fields,write)=>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';
460
+ var ANONYMOUS_CLIENT_DECLARATIONS = `
461
+ /** 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. */
462
+ export interface CreatorCapability { readonly grant: string; readonly secret: string; readonly url: string; }
463
+ export type AnonymousCreateResult<T> = T & { readonly grant: string; readonly idempotency: InsertReceipt; readonly creator: CreatorCapability; readonly request: InsertRequest<AnonymousCreateResult<T>> };
464
+ `;
465
+ var INTAKE_CLIENT_DECLARATIONS = `
466
+ export interface IntakeResult { readonly accepted: true; readonly idempotency: InsertReceipt; readonly request: InsertRequest<IntakeResult> }
467
+ `;
468
+ var INSERT_CLIENT_DECLARATIONS = `
469
+ export interface InsertReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number; }
470
+ export interface InsertRequest<T> { readonly key: string; readonly createdAt: number; retry(): Promise<T>; }
471
+ export type InsertResult<T> = T & { readonly idempotency: InsertReceipt; readonly request: InsertRequest<InsertResult<T>> };
472
+ `;
473
+ function describeDataClient2(authority, contract_digest, insertReceiptCapability, localTypes = false) {
474
+ const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes, insertReceiptCapability) : canonicalize2({}, {}, {}, insertReceiptCapability);
278
475
  const { tables } = JSON.parse(canonical);
279
476
  const declarations = [];
280
477
  const runtimeTables = [];
478
+ const receiptTables = [];
479
+ const receiptCapable = insertReceiptCapability === "insert-v1";
480
+ const anonymousTables = [];
481
+ const intakeTables = [];
482
+ const creatorDeclarations = [];
483
+ const creatorSpec = [];
281
484
  const linkedDeclarations = [];
282
485
  const linkedSpec = [];
283
486
  for (const table of tables) {
@@ -310,8 +513,51 @@ var require_declared_data_contract = __commonJS({
310
513
  const mutation = client.read === false ? "{ count: number; changes: number }" : `{ data: ${row} | null; count: number; changes: number }`;
311
514
  if (client.create !== null) {
312
515
  const required = table.policy?.k === "p" ? /* @__PURE__ */ new Set([table.policy.v]) : /* @__PURE__ */ new Set();
313
- operations.push(`create(values: ${shape(columns, client.create, "create", required)}): Promise<${mutation}>`);
516
+ const eligible = columns.some((column) => column.n === primaryKey && column.t === "integer") && !columns.some((column) => column.t === "blob");
517
+ if (receiptCapable && eligible) receiptTables.push(name);
518
+ operations.push(`create(values: ${shape(columns, client.create, "create", required)}): Promise<${receiptCapable && eligible ? `InsertResult<${mutation}>` : mutation}>`);
314
519
  methods.push("create");
520
+ if (receiptCapable && eligible && client.links && client.links.anonymousCreate === true) {
521
+ operations.push(`createAnonymous(values: ${shape(columns, client.create, "create", required)}): Promise<AnonymousCreateResult<{ data: ${row} | null }>>`);
522
+ methods.push("createAnonymous");
523
+ anonymousTables.push(name);
524
+ const ownerUpdate = (client.update || []).filter((field) => field !== table.owner && field !== primaryKey);
525
+ const creatorRow = shape(columns, readable, "read");
526
+ const rootOps = [
527
+ `get(id: ${id}): Promise<{ data: ${creatorRow} | null }>`,
528
+ `links(id: ${id}): Promise<{ data: Array<{ link_id: string; access: 'read' | 'edit'; created_at: number; expires_at: number | null }> }>`,
529
+ `createLink(id: ${id}, options: { access: ${client.links.edit.length ? "'read' | 'edit'" : "'read'"}; expiresInDays?: number; returnTo?: string }): Promise<{ link_id: string; url: string; expires_at: number | null }>`,
530
+ `revokeLink(id: ${id}, linkId: string): Promise<{ revoked: boolean }>`,
531
+ `claim(id: ${id}, options?: { keepShares?: boolean }): Promise<{ claimed: true; table: string; id: ${id}; shares: 'kept' | 'revoked' | 'unknown'; reconciled?: true }>`,
532
+ `claimStatus(id: ${id}): Promise<{ status: 'claimed_by_you' | 'not_claimed' | 'unknown' }>`
533
+ ];
534
+ if (ownerUpdate.length) rootOps.push(`update(id: ${id}, values: ${shape(columns, ownerUpdate, "update")}): Promise<{ data: ${creatorRow} | null; count: number; changes: number }>`);
535
+ const childRelations = [], childSpecs = [];
536
+ for (const relationName of client.links.children) {
537
+ const relation = table.relations.find((item) => item.name === relationName);
538
+ const child = tables.find((candidate) => candidate.name === relation.table);
539
+ const childReadable = child.client.read === true ? child.columns.filter((column) => column.n !== child.author).map((column) => column.n) : child.client.read;
540
+ const childRow = shape(child.columns, childReadable, "read");
541
+ const childId = columnType(child.columns.find((column) => column.n === child.primaryKey));
542
+ childRelations.push(`${JSON.stringify(relationName)}: { list(parentId: ${id}, options?: { limit?: number; after?: ${childId} }): Promise<{ data: ${childRow}[]; next: ${childId} | null; has_more: boolean }> }`);
543
+ const childUpdate = child.client.update ? child.client.update.filter((field) => field !== relation.fk) : [];
544
+ const childOps = [];
545
+ if (child.client.create !== null) childOps.push(`create(values: ${shape(child.columns, child.client.create, "create", /* @__PURE__ */ new Set([relation.fk]))}): Promise<{ data: ${childRow} | null; count: number; changes: number }>`);
546
+ if (childUpdate.length) childOps.push(`update(id: ${childId}, values: ${shape(child.columns, childUpdate, "update")}): Promise<{ data: ${childRow} | null; count: number; changes: number }>`);
547
+ if (childOps.length) creatorDeclarations.push(`${JSON.stringify(child.name)}: { ${childOps.join("; ")} }`);
548
+ childSpecs.push([relationName, child.name, child.client.create !== null, childUpdate.length > 0]);
549
+ }
550
+ if (childRelations.length) rootOps.push(`relations: { ${childRelations.join("; ")} }`);
551
+ creatorDeclarations.push(`${JSON.stringify(name)}: { ${rootOps.join("; ")} }`);
552
+ creatorSpec.push([name, ownerUpdate.length > 0, childSpecs]);
553
+ }
554
+ }
555
+ if (receiptCapable && client.intake) {
556
+ const target = tables.find((candidate) => candidate.name === client.intake.target.table);
557
+ const reference = columnType({ ...target.columns.find((column) => column.n === client.intake.target.field), nul: void 0 });
558
+ operations.push(`intake(reference: ${reference}, values: ${shape(columns, client.intake.fields, "create")}): Promise<IntakeResult>`);
559
+ methods.push("intake");
560
+ intakeTables.push(name);
315
561
  }
316
562
  if (client.update !== null) {
317
563
  operations.push(`update(id: ${id}, values: ${shape(columns, client.update, "update")}): Promise<${mutation}>`);
@@ -368,25 +614,32 @@ var require_declared_data_contract = __commonJS({
368
614
  declarations.push(`${JSON.stringify(name)}: { ${operations.join("; ")} }`);
369
615
  runtimeTables.push([name, methods, relationNames]);
370
616
  }
617
+ if (creatorSpec.length) {
618
+ declarations.push(`creators: { opened(): Array<{ creator_id: string; table: string; row_id: string }>; sessions(): Promise<{ data: Array<{ creator_id: string; table: string; row_id: string; expires_at: number }> }>; forget(creatorId?: string): Promise<void> }`);
619
+ declarations.push(`creator(creatorId: string): { ${creatorDeclarations.join("; ")} }`);
620
+ }
371
621
  if (linkedSpec.length) {
372
622
  declarations.push(`links: { opened(): Array<{ link_id: string; table: string; row_id: string; access: 'read' | 'edit' }>; sessions(): Promise<{ data: Array<{ link_id: string; table: string; row_id: string; access: 'read' | 'edit'; expires_at: number }> }>; forget(linkId?: string): Promise<void> }`);
373
623
  declarations.push(`linked(linkId: string): { ${linkedDeclarations.join("; ")} }`);
374
624
  }
375
625
  const declaration = `declare module ${JSON.stringify("somewhere:data")} {
376
626
  export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
377
- export class DataError extends Error { readonly status: number; readonly code: string | null; constructor(status: number, payload: unknown); }
627
+ ${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); }
378
628
  export const data: { ${declarations.join(";\n ")} };
379
629
  }
380
630
  `;
381
631
  const runtime = `const contract=${JSON.stringify(contract_digest)};
382
632
  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}}
383
633
  const invoke=async(table,operation,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};
384
- ${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"?values=>invoke(table,operation,{values}):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)})]]` : "[]"})));
634
+ ${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)})]])` : ""}));
385
635
  export{data,DataError};
386
636
  `;
387
- return { contract_digest, declaration, runtime, manifest: { version: 1, contract_digest, tables, declaration } };
637
+ return { contract_digest, declaration, runtime, manifest: { version: 1, contract_digest, tables, declaration, ...insertReceiptCapability === void 0 ? {} : { insert_receipt: insertReceiptCapability } } };
638
+ }
639
+ function describeDataClientLocalDeclaration2(authority) {
640
+ return describeDataClient2(authority, "", "insert-v1", true).declaration;
388
641
  }
389
- module2.exports = { canonicalize: canonicalize2, describeDataClient: describeDataClient2 };
642
+ module2.exports = { canonicalize: canonicalize2, describeDataClient: describeDataClient2, describeDataClientLocalDeclaration: describeDataClientLocalDeclaration2, validateInsertReceiptCapability };
390
643
  }
391
644
  });
392
645
 
@@ -399,9 +652,9 @@ var require_typed_data = __commonJS({
399
652
  var MODULE_NAME = "somewhere:data";
400
653
  var CLIENT_FILE2 = "__somewhere_data.d.ts";
401
654
  var MANIFEST_PATH = "_internal/declared-data.json";
402
- function generateDataClient2(authority) {
403
- const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes) : canonicalize2({}, {}, {});
404
- return describeDataClient2(authority, crypto2.createHash("sha256").update(canonical).digest("hex"));
655
+ function generateDataClient2(authority, insertReceiptCapability) {
656
+ const canonical = authority ? canonicalize2(authority.schema, authority.intents, authority.scopes, insertReceiptCapability) : canonicalize2({}, {}, {}, insertReceiptCapability);
657
+ return describeDataClient2(authority, crypto2.createHash("sha256").update(canonical).digest("hex"), insertReceiptCapability);
405
658
  }
406
659
  function virtualDataPlugin(analysis) {
407
660
  return {
@@ -619,7 +872,15 @@ interface SomewhereDbReadOptions {
619
872
  has?: Readonly<Record<string, SomewhereDbWhere>> | null;
620
873
  }
621
874
  interface SomewhereDbCountOptions { where?: SomewhereDbWhere | null }
622
- interface SomewhereDbInsertOptions { onConflict?: 'ignore' | 'update' | null }
875
+ interface SomewhereDbPlainInsertOptions { onConflict?: 'ignore' | 'update' | null }
876
+ // idempotencyKey: an explicit key (16-128 of A-Z a-z 0-9 - _) for a single plain insert by a
877
+ // signed-in user or visitor on a table keyed by id() with no blob() column. Any other insert
878
+ // given a key refuses before anything runs. Eligible inserts without one get a fresh key.
879
+ interface SomewhereDbInsertOptions extends SomewhereDbPlainInsertOptions { idempotencyKey?: string }
880
+ interface SomewhereDbReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number }
881
+ // Process-local: retry() repeats this exact request with the same key under the current
882
+ // context's authority, until eight days after createdAt. It never runs by itself.
883
+ interface SomewhereDbInsertRequest<R> { readonly key: string; readonly createdAt: number; retry(): Promise<R> }
623
884
  type SomewhereDbValues = Readonly<Record<string, __SomewhereJson>>;
624
885
  type SomewhereDbIncrement = Readonly<Record<string, number>>;
625
886
  type SomewhereDbUpdate = {
@@ -630,7 +891,7 @@ type SomewhereDbUpdate = {
630
891
  );
631
892
  interface SomewhereDbRemove { where?: SomewhereDbWhere | null }
632
893
  type SomewhereDbWriteIntent =
633
- | { op: 'insert'; table: string; values: SomewhereDbValues; options?: SomewhereDbInsertOptions | null }
894
+ | { op: 'insert'; table: string; values: SomewhereDbValues; options?: SomewhereDbPlainInsertOptions | null }
634
895
  | ({ op: 'update'; table: string } & SomewhereDbUpdate)
635
896
  | { op: 'remove'; table: string; where?: SomewhereDbWhere | null };
636
897
  // db/schema.ts's tables, one entry each, generated beside this declaration
@@ -679,6 +940,12 @@ interface SomewhereDbResult<Row = Record<string, unknown>> {
679
940
  live_delivery?: { delivery: 'invalidated' }
680
941
  | { delivery: 'resync_required'; reason: string };
681
942
  }
943
+ // A keyed insert's result (whether an insert is keyed is decided at run time): the receipt
944
+ // bounds, and a non-enumerable request handle. A replay carries the original values.
945
+ interface SomewhereDbInsertResult<Row = Record<string, unknown>> extends SomewhereDbResult<Row> {
946
+ idempotency?: SomewhereDbReceipt;
947
+ readonly request?: SomewhereDbInsertRequest<SomewhereDbInsertResult<Row>>;
948
+ }
682
949
  interface SomewhereRawDbStatement {
683
950
  sql: string;
684
951
  params?: readonly unknown[];
@@ -695,7 +962,7 @@ interface SomewhereServerDb {
695
962
  table: T, options?: SomewhereDbReadOptionsFor<T, C, I> | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
696
963
  count(table: SomewhereDbTable, options?: SomewhereDbCountOptions | null): Promise<{ data: number; error: null }>;
697
964
  // Server authority names row identity (owner column or shared authorship) explicitly on insert.
698
- insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'serverInsert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
965
+ insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'serverInsert', SomewhereDbValues>, options?: SomewhereDbPlainInsertOptions | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
699
966
  update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'serverSet'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
700
967
  remove<T extends SomewhereDbTable>(table: T, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
701
968
  // Closed atomic batch, 1-100 intents checked before execution. No callback,
@@ -705,7 +972,7 @@ interface SomewhereServerDb {
705
972
  }
706
973
  interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | 'insert' | 'update'> {
707
974
  // The platform composes row identity from the verified user.
708
- insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
975
+ insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbInsertOptions | null): Promise<SomewhereDbInsertResult<SomewhereDbRow<T>>>;
709
976
  update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'set'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
710
977
  from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
711
978
  table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
@@ -3364,7 +3631,7 @@ interface SomewhereEndpointConfig<Auth extends SomewhereEndpointAuth, Schema ext
3364
3631
  if (owner && !columns.some((column) => column.n === owner)) columns.push({ n: owner, t: "text", optional: false, identity: true, type: "string" });
3365
3632
  const declared = columns.filter((column) => !column.identity);
3366
3633
  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" : "[]"));
3367
- 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 && !column.identity))) + ";\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";
3634
+ 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";
3368
3635
  });
3369
3636
  return "interface SomewhereDeclaredTables {\n" + entries.join("") + "}\n";
3370
3637
  }
@@ -3565,9 +3832,10 @@ var require_schema_types = __commonJS({
3565
3832
  identity?: 'authenticated' | 'visitor'; read?: boolean | readonly Field[]; publicRead?: boolean | { where: Partial<Record<Field, string | number | boolean | null>> };
3566
3833
  create?: readonly Field[] | false | null; update?: readonly Field[] | false | null; delete?: boolean;
3567
3834
  links?: ClientLinkPermissions<Field>;
3835
+ intake?: { target: { table: string; field: string }; fields: readonly Field[]; where?: Record<string, string | number | boolean | null> };
3568
3836
  }
3569
3837
  interface ClientLinkPermissions<Field extends string> {
3570
- read: true | readonly Field[]; edit?: readonly Field[] | false; children?: readonly string[]; maxDays?: number;
3838
+ read: true | readonly Field[]; edit?: readonly Field[] | false; children?: readonly string[]; maxDays?: number; anonymousCreate?: boolean;
3571
3839
  }
3572
3840
  interface FileCollection { readonly __somewhereFileCollection: unique symbol }
3573
3841
  type FileOperation = 'read' | 'upload' | 'replace' | 'delete';
@@ -3634,13 +3902,11 @@ __export(declared_data_vendor_entry_exports, {
3634
3902
  declaredTablesFromFiles: () => declaredTablesFromFiles,
3635
3903
  filesDeclarationFromFiles: () => filesDeclarationFromFiles,
3636
3904
  generateFromFiles: () => generateFromFiles,
3905
+ localDeclarationFromFiles: () => localDeclarationFromFiles,
3637
3906
  runtimeDeclarationFromFiles: () => runtimeDeclarationFromFiles
3638
3907
  });
3639
3908
  module.exports = __toCommonJS(declared_data_vendor_entry_exports);
3640
3909
 
3641
- // worker/src/utils/db-schema-deploy/schema-ownership.ts
3642
- var SHARED_AUTHOR_COLUMN = "_sw_author_id";
3643
-
3644
3910
  // worker/src/utils/db-schema-deploy/bigint-text.ts
3645
3911
  function isCanonicalBigintText(value) {
3646
3912
  if (!/^(0|-?[1-9][0-9]*)$/.test(value)) return false;
@@ -3658,7 +3924,7 @@ function parseClientPermissions(value, columns, scope) {
3658
3924
  }
3659
3925
  const input = value;
3660
3926
  for (const key of Object.keys(input)) {
3661
- if (!["identity", "read", "publicRead", "create", "update", "delete", "links"].includes(key)) {
3927
+ if (!["identity", "read", "publicRead", "create", "update", "delete", "links", "intake"].includes(key)) {
3662
3928
  return fail(`Unknown client permission "${key}".`);
3663
3929
  }
3664
3930
  }
@@ -3680,25 +3946,9 @@ function parseClientPermissions(value, columns, scope) {
3680
3946
  if (!rawPublicRead || typeof rawPublicRead !== "object" || Array.isArray(rawPublicRead) || Object.keys(rawPublicRead).some((key) => key !== "where")) {
3681
3947
  return fail("client.publicRead must be true, false, or { where: { field: literal } }.");
3682
3948
  }
3683
- const where = rawPublicRead.where;
3684
- if (!where || typeof where !== "object" || Array.isArray(where)) return fail("client.publicRead.where must be a non-empty object.");
3685
- const entries = Object.entries(where);
3686
- if (entries.length === 0 || entries.length > 8) return fail("client.publicRead.where must declare 1 to 8 equality fields.");
3687
- const normalized = {};
3688
- for (const [rawName, literal] of entries) {
3689
- const name = rawName.toLowerCase();
3690
- const column = columns.find((candidate) => candidate.name === name);
3691
- if (Object.prototype.hasOwnProperty.call(normalized, name)) return fail(`client.publicRead.where declares field "${rawName}" twice.`);
3692
- if (!column || ["json", "blob"].includes(column.helper)) return fail(`client.publicRead.where names undeclared or non-scalar field "${rawName}".`);
3693
- if (!(literal === null || typeof literal === "boolean" || typeof literal === "string" || typeof literal === "number" && Number.isFinite(literal))) return fail(`client.publicRead.where field "${rawName}" must equal a literal scalar.`);
3694
- if (typeof literal === "string" && literal.length > 256) return fail(`client.publicRead.where field "${rawName}" exceeds 256 characters.`);
3695
- if (literal === null && !column.nullable) return fail(`client.publicRead.where field "${rawName}" cannot equal null because it is required.`);
3696
- if (literal !== null && (column.helper === "boolean" && typeof literal !== "boolean" || ["integer", "number"].includes(column.helper) && typeof literal !== "number" || column.helper === "integer" && !Number.isSafeInteger(literal) || column.helper === "bigint" && (typeof literal !== "string" || !isCanonicalBigintText(literal)) || ["text", "timestamp"].includes(column.helper) && typeof literal !== "string")) {
3697
- return fail(`client.publicRead.where field "${rawName}" must match its declared type.`);
3698
- }
3699
- normalized[name] = literal;
3700
- }
3701
- publicRead = { where: Object.fromEntries(Object.entries(normalized).sort(([a], [b]) => a.localeCompare(b))) };
3949
+ const where = parseEqualityWhere(rawPublicRead.where, columns, "client.publicRead.where");
3950
+ if (typeof where === "string") return fail(where);
3951
+ publicRead = { where };
3702
3952
  }
3703
3953
  if (read !== true && read !== false && !Array.isArray(read)) return fail("client.read must list readable fields, or be true or false.");
3704
3954
  if (publicRead && (!Array.isArray(read) || read.length === 0)) {
@@ -3746,17 +3996,8 @@ function parseClientPermissions(value, columns, scope) {
3746
3996
  writes[operation] = allowed.sort();
3747
3997
  }
3748
3998
  if (writes.create !== null) {
3749
- if (typeof publicRead === "object") {
3750
- let guaranteedPrivate = false;
3751
- for (const [name, publishedValue] of Object.entries(publicRead.where)) {
3752
- const column = fields.get(name);
3753
- if (column.autoNow) continue;
3754
- const defaultValue = column.hasDefault ? column.default : column.nullable ? null : void 0;
3755
- if (defaultValue !== void 0 && defaultValue !== publishedValue) guaranteedPrivate = true;
3756
- }
3757
- if (!guaranteedPrivate) {
3758
- 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.");
3759
- }
3999
+ if (typeof publicRead === "object" && !privateByDefault(publicRead.where, fields)) {
4000
+ 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.");
3760
4001
  }
3761
4002
  for (const column of columns) {
3762
4003
  if (column.helper !== "id" && !column.nullable && !column.hasDefault && !column.autoNow && !writes.create.includes(column.name)) {
@@ -3766,11 +4007,97 @@ function parseClientPermissions(value, columns, scope) {
3766
4007
  }
3767
4008
  let links;
3768
4009
  if (input.links !== void 0) {
3769
- const parsed = parseLinkPermissions(input.links, read, writes.update, columns, scope);
4010
+ const parsed = parseLinkPermissions(input.links, read, writes.update, writes.create, columns, scope);
3770
4011
  if (typeof parsed === "string") return fail(parsed);
3771
4012
  links = parsed;
3772
4013
  }
3773
- return { ok: true, permissions: { identity, read, publicRead, ...writes, delete: remove, ...links ? { links } : {} } };
4014
+ let intake;
4015
+ if (input.intake !== void 0) {
4016
+ const parsed = parseIntakePermissions(input.intake, columns, scope, publicRead);
4017
+ if (typeof parsed === "string") return fail(parsed);
4018
+ intake = parsed;
4019
+ }
4020
+ return { ok: true, permissions: { identity, read, publicRead, ...writes, delete: remove, ...links ? { links } : {}, ...intake ? { intake } : {} } };
4021
+ }
4022
+ function parseEqualityWhere(where, columns, label) {
4023
+ if (!where || typeof where !== "object" || Array.isArray(where)) return `${label} must be a non-empty object.`;
4024
+ const entries = Object.entries(where);
4025
+ if (entries.length === 0 || entries.length > 8) return `${label} must declare 1 to 8 equality fields.`;
4026
+ const normalized = {};
4027
+ for (const [rawName, literal] of entries) {
4028
+ const name = rawName.toLowerCase();
4029
+ const column = columns?.find((candidate) => candidate.name === name);
4030
+ if (Object.prototype.hasOwnProperty.call(normalized, name)) return `${label} declares field "${rawName}" twice.`;
4031
+ if (columns && (!column || ["json", "blob"].includes(column.helper))) return `${label} names undeclared or non-scalar field "${rawName}".`;
4032
+ if (!(literal === null || typeof literal === "boolean" || typeof literal === "string" || typeof literal === "number" && Number.isFinite(literal))) return `${label} field "${rawName}" must equal a literal scalar.`;
4033
+ if (typeof literal === "string" && literal.length > 256) return `${label} field "${rawName}" exceeds 256 characters.`;
4034
+ if (column && literal === null && !column.nullable) return `${label} field "${rawName}" cannot equal null because it is required.`;
4035
+ if (column && literal !== null && (column.helper === "boolean" && typeof literal !== "boolean" || ["integer", "number"].includes(column.helper) && typeof literal !== "number" || column.helper === "integer" && !Number.isSafeInteger(literal) || column.helper === "bigint" && (typeof literal !== "string" || !isCanonicalBigintText(literal)) || ["text", "timestamp"].includes(column.helper) && typeof literal !== "string")) {
4036
+ return `${label} field "${rawName}" must match its declared type.`;
4037
+ }
4038
+ normalized[name] = literal;
4039
+ }
4040
+ return Object.fromEntries(Object.entries(normalized).sort(([a], [b]) => a.localeCompare(b)));
4041
+ }
4042
+ function privateByDefault(where, fields) {
4043
+ for (const [name, publishedValue] of Object.entries(where)) {
4044
+ const column = fields.get(name);
4045
+ if (column.autoNow) continue;
4046
+ const defaultValue = column.hasDefault ? column.default : column.nullable ? null : void 0;
4047
+ if (defaultValue !== void 0 && defaultValue !== publishedValue) return true;
4048
+ }
4049
+ return false;
4050
+ }
4051
+ function parseIntakePermissions(value, columns, scope, publicRead) {
4052
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "client.intake must be an object: { target: { table, field }, fields, where? }.";
4053
+ const input = value;
4054
+ for (const key of Object.keys(input)) {
4055
+ if (!["target", "fields", "where"].includes(key)) return `Unknown client.intake option "${key}". Allowed: target, fields, where.`;
4056
+ }
4057
+ if (scope.kind !== "owner" || scope.visitors === true) return "client.intake needs owner() without visitors: each accepted row belongs to the target row's owner.";
4058
+ const id = columns.find((column) => column.helper === "id");
4059
+ if (!id || id.uuid === true) return "client.intake needs an integer id(), not id({ uuid: true }).";
4060
+ if (publicRead === true) return "client.intake cannot accept rows into a table whose rows are all public (client.publicRead: true).";
4061
+ const target = input.target;
4062
+ if (!target || typeof target !== "object" || Array.isArray(target) || Object.keys(target).sort().join() !== "field,table") {
4063
+ return "client.intake.target must be { table, field }: the public table and its unique reference field.";
4064
+ }
4065
+ const { table, field } = target;
4066
+ if (typeof table !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(table) || typeof field !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(field)) {
4067
+ return "client.intake.target must name a table and one of its fields.";
4068
+ }
4069
+ if (!Array.isArray(input.fields) || input.fields.length === 0) return "client.intake.fields must list at least one field a submission supplies.";
4070
+ const byName2 = new Map(columns.map((column) => [column.name, column]));
4071
+ const fields = [];
4072
+ for (const entry of input.fields) {
4073
+ if (typeof entry !== "string") return "client.intake.fields must be column names.";
4074
+ const name = entry.toLowerCase();
4075
+ const column = byName2.get(name);
4076
+ if (!column) return `client.intake.fields names undeclared or platform-managed field "${entry}".`;
4077
+ if (column.helper === "id" || name === scope.column) return `client.intake.fields cannot set the platform-assigned identity field "${entry}".`;
4078
+ if (column.helper === "blob") return `client.intake.fields cannot accept binary field "${entry}". Use project files for uploads.`;
4079
+ if (column.autoNow) return `client.intake.fields cannot set "${entry}", which the platform fills in.`;
4080
+ if (typeof publicRead === "object" && Object.prototype.hasOwnProperty.call(publicRead.where, name)) {
4081
+ return `client.intake.fields cannot set publication field "${entry}".`;
4082
+ }
4083
+ if (fields.includes(name)) return `client.intake.fields declares field "${entry}" twice.`;
4084
+ fields.push(name);
4085
+ }
4086
+ for (const column of columns) {
4087
+ if (column.helper !== "id" && !column.nullable && !column.hasDefault && !column.autoNow && !fields.includes(column.name)) {
4088
+ return `client.intake.fields must include required field "${column.name}", or that field needs a declared default.`;
4089
+ }
4090
+ }
4091
+ if (typeof publicRead === "object" && !privateByDefault(publicRead.where, byName2)) {
4092
+ return "client.intake has no fixed publication-field default that guarantees a private row. Give at least one predicate field a private default.";
4093
+ }
4094
+ let where;
4095
+ if (input.where !== void 0) {
4096
+ const parsed = parseEqualityWhere(input.where, null, "client.intake.where");
4097
+ if (typeof parsed === "string") return parsed;
4098
+ where = parsed;
4099
+ }
4100
+ return { target: { table: table.toLowerCase(), field: field.toLowerCase() }, fields: fields.sort(), ...where ? { where } : {} };
3774
4101
  }
3775
4102
  function authoredClientFromCanonical(client) {
3776
4103
  if (!client || typeof client !== "object" || Array.isArray(client)) return client;
@@ -3778,16 +4105,17 @@ function authoredClientFromCanonical(client) {
3778
4105
  if (!links || typeof links !== "object" || Array.isArray(links)) return client;
3779
4106
  const l = links;
3780
4107
  const keys = Object.keys(l).sort().join(",");
3781
- if (keys !== "children,edit,maxDays,read" || !Array.isArray(l.read) || !Array.isArray(l.edit) || !Array.isArray(l.children) || !(l.maxDays === null || typeof l.maxDays === "number")) return client;
4108
+ if (keys !== "children,edit,maxDays,read" && !(keys === "anonymousCreate,children,edit,maxDays,read" && l.anonymousCreate === true) || !Array.isArray(l.read) || !Array.isArray(l.edit) || !Array.isArray(l.children) || !(l.maxDays === null || typeof l.maxDays === "number")) return client;
3782
4109
  const authored = { read: l.read, edit: l.edit.length === 0 ? false : l.edit, children: l.children };
3783
4110
  if (l.maxDays !== null) authored.maxDays = l.maxDays;
4111
+ if (l.anonymousCreate === true) authored.anonymousCreate = true;
3784
4112
  return { ...client, links: authored };
3785
4113
  }
3786
- function parseLinkPermissions(value, read, update, columns, scope) {
3787
- if (!value || typeof value !== "object" || Array.isArray(value)) return "client.links must be an object: { read, edit?, children?, maxDays? }.";
4114
+ function parseLinkPermissions(value, read, update, create, columns, scope) {
4115
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "client.links must be an object: { read, edit?, children?, maxDays?, anonymousCreate? }.";
3788
4116
  const input = value;
3789
4117
  for (const key2 of Object.keys(input)) {
3790
- if (!["read", "edit", "children", "maxDays"].includes(key2)) return `Unknown client.links option "${key2}". Allowed: read, edit, children, maxDays.`;
4118
+ if (!["read", "edit", "children", "maxDays", "anonymousCreate"].includes(key2)) return `Unknown client.links option "${key2}". Allowed: read, edit, children, maxDays, anonymousCreate.`;
3791
4119
  }
3792
4120
  const owned = scope.kind === "owner" && scope.visitors !== true || scope.kind === "policy" && typeof scope.column === "string";
3793
4121
  if (!owned) return "client.links needs rows with one signed-in owner: owner() (without visitors) or anyOf(owner(), ...).";
@@ -3834,9 +4162,64 @@ function parseLinkPermissions(value, read, update, columns, scope) {
3834
4162
  }
3835
4163
  maxDays = input.maxDays;
3836
4164
  }
3837
- return { read: linkRead, edit, children, maxDays };
4165
+ if (input.anonymousCreate !== void 0 && typeof input.anonymousCreate !== "boolean") return "client.links.anonymousCreate must be true or false.";
4166
+ if (input.anonymousCreate === true) {
4167
+ if (scope.kind !== "owner" || scope.visitors === true) return "client.links.anonymousCreate needs owner() without visitors.";
4168
+ if (!create || create.length === 0) return "client.links.anonymousCreate needs client.create: it creates rows with the fields the app data client creates.";
4169
+ if (columns.find((column) => column.helper === "id")?.uuid === true) return "client.links.anonymousCreate needs an integer id(), not id({ uuid: true }).";
4170
+ if (columns.some((column) => column.helper === "blob")) return "client.links.anonymousCreate is not available on a table with a blob() column.";
4171
+ }
4172
+ return { read: linkRead, edit, children, maxDays, ...input.anonymousCreate === true ? { anonymousCreate: true } : {} };
4173
+ }
4174
+
4175
+ // worker/src/utils/authority/intake-bake.ts
4176
+ function record(value) {
4177
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
4178
+ }
4179
+ function bakeIntakePermissions(schema, declarations, intents, scopes) {
4180
+ const membershipTables = /* @__PURE__ */ new Set();
4181
+ const members = (policy) => {
4182
+ if (policy.k === "m") membershipTables.add(policy.m.m);
4183
+ else if (policy.k === "a") policy.p.forEach(members);
4184
+ else if (policy.k === "p") members(policy.p);
4185
+ };
4186
+ for (const table of Object.values(schema)) {
4187
+ if (table.member) membershipTables.add(table.member.m);
4188
+ if (table.policy) members(table.policy);
4189
+ }
4190
+ for (const [name, table] of Object.entries(schema)) {
4191
+ const intake = table.client?.intake;
4192
+ if (!intake) continue;
4193
+ let valid = false;
4194
+ const target = schema[intake.target.table];
4195
+ try {
4196
+ const declared = record(JSON.parse(declarations[intake.target.table]));
4197
+ const scope = record(declared?.scope);
4198
+ const destination = record(JSON.parse(declarations[name]));
4199
+ const destinationScope = record(destination?.scope);
4200
+ const rawColumns = declared?.columns;
4201
+ const matches = Array.isArray(rawColumns) ? rawColumns.map(record).filter((c) => c?.name === intake.target.field) : [];
4202
+ const column = matches.length === 1 ? matches[0] : null;
4203
+ const bakedColumn = target?.columns.find((c) => c.n === intake.target.field);
4204
+ const owner = scope?.column;
4205
+ if (!membershipTables.has(name) && destination?.name === name && destinationScope?.kind === "owner" && destinationScope.column === scopes[name] && declared?.name === intake.target.table && scope?.kind === "owner" && scope.visitors !== true && typeof owner === "string" && scopes[intake.target.table] === owner && intents[intake.target.table] === "scoped" && intents[name] === "scoped" && scopes[name] && !table.visitors && !table.member && !table.policy && target && !target.visitors && !target.member && !target.policy && target.client?.publicRead && Array.isArray(target.client.read) && target.client.read.includes(intake.target.field) && column && column.nullable === false && ["id", "text", "integer", "bigint"].includes(String(column.helper)) && (column.helper === "id" || column.unique === true) && bakedColumn && !bakedColumn.nul && bakedColumn.t === (column.helper === "id" ? column.uuid === true ? "text" : "integer" : column.helper)) {
4206
+ const predicateColumns = rawColumns;
4207
+ const columns = predicateColumns.map((c) => ({ name: String(c.name), helper: String(c.helper), nullable: c.nullable === true }));
4208
+ valid = !intake.where || typeof parseEqualityWhere(intake.where, columns, "client.intake.where") !== "string";
4209
+ if (valid) target.columns = target.columns.map((c) => c.n === intake.target.field ? { ...c, u: 1 } : c);
4210
+ }
4211
+ } catch {
4212
+ }
4213
+ if (!valid && table.client) {
4214
+ const { intake: _intake, ...client } = table.client;
4215
+ table.client = client;
4216
+ }
4217
+ }
3838
4218
  }
3839
4219
 
4220
+ // worker/src/utils/db-schema-deploy/schema-ownership.ts
4221
+ var SHARED_AUTHOR_COLUMN = "_sw_author_id";
4222
+
3840
4223
  // worker/src/services/jobs/limits.ts
3841
4224
  var STALE_JOB_WINDOW_MS = 45 * 60 * 1e3;
3842
4225
 
@@ -4034,8 +4417,8 @@ function validateBindings(input, tableInfo, errors) {
4034
4417
  }
4035
4418
  if (c.helper !== helper || c.nullable) errors.push(`${at(p, field)}payment binding "${p.name}": "${p.table}"."${name}" must be ${what}.`);
4036
4419
  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.`);
4037
- const writable = [...info.table.client?.create ?? [], ...info.table.client?.update ?? []];
4038
- if (writable.includes(name)) errors.push(`${at(p, field)}payment binding "${p.name}": "${p.table}"."${name}" is browser-writable through client.create or client.update. Remove it there; the server sets prices and the payment sets the status.`);
4420
+ const writable = [...info.table.client?.create ?? [], ...info.table.client?.update ?? [], ...info.table.client?.intake?.fields ?? []];
4421
+ 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.`);
4039
4422
  }
4040
4423
  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.`);
4041
4424
  const status = column(p.status.column);
@@ -4102,6 +4485,7 @@ function bakedTableSchemaFromDeclared(declaredJson) {
4102
4485
  permissionColumns.push({
4103
4486
  name: entry.n,
4104
4487
  helper: col.helper,
4488
+ ...col.uuid === true ? { uuid: true } : {},
4105
4489
  nullable: col.nullable === true,
4106
4490
  hasDefault: col.hasDefault === true,
4107
4491
  autoNow: col.autoNow === true,
@@ -4117,7 +4501,7 @@ function bakedTableSchemaFromDeclared(declaredJson) {
4117
4501
  if (declaredScope.kind !== "owner" || typeof declaredScope.visitors !== "boolean") return null;
4118
4502
  if (declaredScope.visitors) visitors = true;
4119
4503
  }
4120
- let browser = {};
4504
+ let browser = primaryKeys.length === 1 ? { primaryKey: primaryKeys[0] } : {};
4121
4505
  if (Object.prototype.hasOwnProperty.call(shape, "client")) {
4122
4506
  if (!scope || typeof scope !== "object" || primaryKeys.length !== 1) return null;
4123
4507
  const sc = scope;
@@ -4132,7 +4516,7 @@ function bakedTableSchemaFromDeclared(declaredJson) {
4132
4516
  });
4133
4517
  if (!parsed.ok) return null;
4134
4518
  if (Array.isArray(parsed.permissions.read) && !parsed.permissions.read.includes(primaryKeys[0])) return null;
4135
- browser = { client: parsed.permissions, clientPrimaryKey: primaryKeys[0] };
4519
+ browser = { ...browser, client: parsed.permissions, clientPrimaryKey: primaryKeys[0] };
4136
4520
  }
4137
4521
  if (scope !== null && typeof scope === "object" && scope.kind === "member") {
4138
4522
  const member = bakedMemberFromDeclared(scope);
@@ -4651,6 +5035,56 @@ function validateRowLinks(tables, tableByName, errors) {
4651
5035
  }
4652
5036
  }
4653
5037
 
5038
+ // worker/src/utils/db-schema-deploy/extract-schema-intake.ts
5039
+ var REFERENCE_HELPERS = ["id", "text", "integer", "bigint"];
5040
+ function validateIntake(tables, tableByName, errors) {
5041
+ for (const t of tables) {
5042
+ const intake = t.client?.intake;
5043
+ if (!intake) continue;
5044
+ const at = `Table "${t.name}" client.intake`;
5045
+ const target = tableByName.get(intake.target.table);
5046
+ if (!target) {
5047
+ errors.push(`${at} targets "${intake.target.table}", which is not a table declared in db/schema.ts.`);
5048
+ continue;
5049
+ }
5050
+ if (target.scope.kind !== "owner" || target.scope.visitors === true) {
5051
+ errors.push(`${at} targets "${target.name}", which must be scoped owner() without visitors: its owner receives the submissions.`);
5052
+ continue;
5053
+ }
5054
+ const client = target.client;
5055
+ if (!client || !client.publicRead || !Array.isArray(client.read)) {
5056
+ errors.push(`${at} targets "${target.name}", which declares no client.publicRead. Only a published table can receive public submissions.`);
5057
+ continue;
5058
+ }
5059
+ const field = intake.target.field;
5060
+ const column = target.columns.find((c) => c.name === field);
5061
+ if (!column) {
5062
+ errors.push(`${at} names reference "${field}", which is not a declared column of "${target.name}".`);
5063
+ continue;
5064
+ }
5065
+ if (!client.read.includes(field)) {
5066
+ errors.push(`${at} names reference "${field}", which "${target.name}" does not list in client.read. The reference must be a public field.`);
5067
+ continue;
5068
+ }
5069
+ if (!REFERENCE_HELPERS.includes(column.helper)) {
5070
+ errors.push(`${at} names reference "${field}", a ${column.helper} column. A reference is an id, text, integer or bigint column.`);
5071
+ continue;
5072
+ }
5073
+ if (column.helper !== "id" && !column.unique) {
5074
+ errors.push(`${at} names reference "${field}", which is not unique on "${target.name}". Declare it { unique: true }; a reference must name one row.`);
5075
+ continue;
5076
+ }
5077
+ if (column.nullable) {
5078
+ errors.push(`${at} names reference "${field}", which is nullable on "${target.name}". A reference must be required.`);
5079
+ continue;
5080
+ }
5081
+ if (intake.where) {
5082
+ const typed = parseEqualityWhere(intake.where, target.columns, "client.intake.where");
5083
+ if (typeof typed === "string") errors.push(`${at} (target "${target.name}"): ${typed}`);
5084
+ }
5085
+ }
5086
+ }
5087
+
4654
5088
  // worker/src/utils/db-schema-deploy/extract-schema-files.ts
4655
5089
  var FILE_OPERATIONS = ["read", "upload", "replace", "delete"];
4656
5090
  var MAX_FILE_COLLECTIONS = 20;
@@ -5659,7 +6093,7 @@ function readClientPermissions(r) {
5659
6093
  }
5660
6094
  out[key.name] = fields;
5661
6095
  } else {
5662
- out[key.name] = key.name === "links" && r.peek().kind === "punct" && r.peek().value === "{" ? readClientPermissions(r) : r.readLiteral(`for client.${key.name}`);
6096
+ out[key.name] = ["links", "intake", "target", "where"].includes(key.name) && r.peek().kind === "punct" && r.peek().value === "{" ? readClientPermissions(r) : r.readLiteral(`for client.${key.name}`);
5663
6097
  }
5664
6098
  if (!r.tryPunct(",")) {
5665
6099
  r.expectPunct("}", "closing client permissions");
@@ -6452,7 +6886,7 @@ function extractSchemaTs(source) {
6452
6886
  continue;
6453
6887
  }
6454
6888
  const membershipCols = knownColumns(membershipTable);
6455
- if (membershipTable.client && (membershipTable.client.create !== null || membershipTable.client.update !== null || membershipTable.client.delete)) {
6889
+ if (membershipTable.client && (membershipTable.client.create !== null || membershipTable.client.update !== null || membershipTable.client.delete || membershipTable.client.intake !== void 0)) {
6456
6890
  errors.push(`Table "${sc.membership}" grants membership access to "${t.name}" and cannot allow client writes. Change membership through a server function.`);
6457
6891
  }
6458
6892
  if (!membershipCols.has(sc.memberUser)) {
@@ -6471,11 +6905,12 @@ function extractSchemaTs(source) {
6471
6905
  }
6472
6906
  validateDeclaredRelations(tables, tableByName, knownColumns, (table) => removedTables.includes(table) ? "removedTable()" : exportedTables.includes(table) ? "exported()" : null, errors);
6473
6907
  validateRowLinks(tables, tableByName, errors);
6908
+ validateIntake(tables, tableByName, errors);
6474
6909
  if (fileCollections) {
6475
6910
  errors.push(...fileErrors);
6476
6911
  validateFileCollections(fileCollections, (name) => {
6477
6912
  const t = tableByName.get(name);
6478
- return t ? { columns: knownColumns(t), clientWrites: Boolean(t.client && (t.client.create !== null || t.client.update !== null || t.client.delete)), parentTarget: fileParentTarget(t) } : null;
6913
+ return t ? { columns: knownColumns(t), clientWrites: Boolean(t.client && (t.client.create !== null || t.client.update !== null || t.client.delete || t.client.intake !== void 0)), parentTarget: fileParentTarget(t) } : null;
6479
6914
  }, errors);
6480
6915
  }
6481
6916
  if (groupRoles || appRoles) errors.push(...validateDeclaredRoles(groupRoles, appRoles));
@@ -6572,6 +7007,7 @@ function schemaAuthorityFromSource(files) {
6572
7007
  const ownerColumn = declaredOwnerColumn(table.scope);
6573
7008
  if (declaredScopeIntent(table.scope) === "scoped" && ownerColumn) declareEntry(scopes, table.name, ownerColumn);
6574
7009
  }
7010
+ bakeIntakePermissions(schema, Object.fromEntries(parsed.declaration.tables.map((table) => [table.name, JSON.stringify(canonicalTableShape(table))])), intents, scopes);
6575
7011
  return { schema, intents, scopes };
6576
7012
  }
6577
7013
  function declaredFilesFromSource(files) {
@@ -6589,6 +7025,7 @@ function declaredRolesFromSource(files) {
6589
7025
  }
6590
7026
 
6591
7027
  // declared-data-vendor-entry.js
7028
+ var import_declared_data_contract2 = __toESM(require_declared_data_contract());
6592
7029
  var import_typed_data = __toESM(require_typed_data());
6593
7030
  var import_runtime_types = __toESM(require_runtime_types());
6594
7031
  var import_groups_types = __toESM(require_groups_types());
@@ -6600,6 +7037,10 @@ function generateFromFiles(files) {
6600
7037
  const authority = clientAuthorityFromSource(files);
6601
7038
  return authority ? (0, import_typed_data.generateDataClient)(authority) : void 0;
6602
7039
  }
7040
+ function localDeclarationFromFiles(files) {
7041
+ const authority = clientAuthorityFromSource(files);
7042
+ return authority ? (0, import_declared_data_contract2.describeDataClientLocalDeclaration)(authority) : void 0;
7043
+ }
6603
7044
  function runtimeDeclarationFromFiles(files) {
6604
7045
  return (0, import_runtime_types.runtimeContextDeclaration)((0, import_groups_types.declaredRoles)(declaredRolesFromSource(files)));
6605
7046
  }
@@ -6619,5 +7060,6 @@ function filesDeclarationFromFiles(files) {
6619
7060
  declaredTablesFromFiles,
6620
7061
  filesDeclarationFromFiles,
6621
7062
  generateFromFiles,
7063
+ localDeclarationFromFiles,
6622
7064
  runtimeDeclarationFromFiles
6623
7065
  });