@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.
- package/dist/lib/declared-data.js +3 -1
- package/dist/lib/declared-data.js.map +1 -1
- package/dist/lib/init-agent-guide.js +9 -9
- package/dist/lib/init-feature-template.js +63 -15
- package/dist/lib/init-feature-template.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/DECLARED-DATA-VENDOR.json +12 -10
- package/runtime/declared-data.cjs +527 -85
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
+
record2(publicRead, `${name} public read`);
|
|
139
144
|
if (Object.keys(publicRead).some((key) => key !== "where")) fail(`${name} public read`);
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
|
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?:
|
|
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?:
|
|
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<
|
|
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
|
|
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 (
|
|
3685
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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] =
|
|
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
|
});
|