@lunora/codegen 1.0.0-alpha.143 → 1.0.0-alpha.144
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/index.d.mts +52 -32
- package/dist/index.d.ts +52 -32
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/DEFAULT_TARGET-CwB3hjFH.mjs +1 -0
- package/dist/packem_shared/{GENERATED_HEADER-BgmuZY9e.mjs → GENERATED_HEADER-F2VhjmJG.mjs} +1 -1
- package/dist/packem_shared/{OPENRPC_VERSION-B9ZC0SrC.mjs → OPENRPC_VERSION-C30LtXay.mjs} +1 -1
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-D7qF6qrd.mjs +9 -0
- package/dist/packem_shared/{buildOpenApiDocument-DLEWgA5Y.mjs → buildOpenApiDocument-CmLfuJpW.mjs} +1 -1
- package/dist/packem_shared/context-property-calls-DXH-4MTZ.mjs +1 -0
- package/dist/packem_shared/discoverHyperdriveCalls-De_QiFIl.mjs +1 -0
- package/dist/packem_shared/discoverR2sqlCalls-BdkZw-D2.mjs +1 -0
- package/dist/packem_shared/{emit-s6mHBmXu.mjs → emit-EJw8shd_.mjs} +188 -134
- package/dist/packem_shared/{emitApp-D1qxNeh1.mjs → emitApp-DftAzAA2.mjs} +52 -39
- package/package.json +6 -6
- package/dist/packem_shared/DEFAULT_TARGET-CrhWLUK7.mjs +0 -1
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-lbo63hy2.mjs +0 -9
- package/dist/packem_shared/discoverR2sqlCalls-BbfOpwE4.mjs +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{agentComponent as
|
|
1
|
+
import{agentComponent as ct}from"@lunora/agent/component";import{LunoraError as X}from"@lunora/errors";import{serializeSchemaSnapshot as kn,hashSchemaSnapshot as xn}from"./SCHEMA_SNAPSHOT_VERSION-DLXXalg2.mjs";import{s as T}from"./paths-C-9fwEgQ.mjs";const dt=[{contextProperty:"access",key:"access",moduleSpecifier:"@lunora/cloudflare-access"},{appMethod:{configKey:"ai",doc:"Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`).",method:"ai"},contextProperty:"ai",key:"ai",moduleSpecifier:"@lunora/ai"},{appMethod:{configKey:"analytics",doc:"Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`).",method:"analytics"},contextProperty:"analytics",key:"analytics",moduleSpecifier:"@lunora/bindings/analytics",serverCtxField:{field:`
|
|
2
2
|
/** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */
|
|
3
3
|
readonly analytics: import("@lunora/bindings/analytics").AnalyticsClient;`,tier:"every"}},{appMethod:{configKey:"browser",doc:"Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`).",method:"browser"},contextProperty:"browser",key:"browser",moduleSpecifier:"@lunora/browser",serverCtxField:{field:`
|
|
4
4
|
/** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */
|
|
@@ -15,40 +15,40 @@ import{agentComponent as dt}from"@lunora/agent/component";import{LunoraError as
|
|
|
15
15
|
/**
|
|
16
16
|
* R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.
|
|
17
17
|
*/
|
|
18
|
-
readonly r2sql: import("@lunora/bindings/r2sql").R2SqlClient;`,tier:"action"}},{contextProperty:"scheduler",key:"scheduler",moduleSpecifier:"@lunora/scheduler"},{contextProperty:"storage",key:"storage",moduleSpecifier:"@lunora/storage"},{appMethod:{configKey:"vectors",doc:"Wire the Vectorize index map backing `ctx.vectors`.",method:"vectors"},contextProperty:"vectors",key:"vectors",moduleSpecifier:"@lunora/bindings/vectors"},{contextProperty:"workflows",key:"workflows",moduleSpecifier:"@lunora/workflow"}],vn=
|
|
18
|
+
readonly r2sql: import("@lunora/bindings/r2sql").R2SqlClient;`,tier:"action"}},{contextProperty:"scheduler",key:"scheduler",moduleSpecifier:"@lunora/scheduler"},{contextProperty:"storage",key:"storage",moduleSpecifier:"@lunora/storage"},{appMethod:{configKey:"vectors",doc:"Wire the Vectorize index map backing `ctx.vectors`.",method:"vectors"},contextProperty:"vectors",key:"vectors",moduleSpecifier:"@lunora/bindings/vectors"},{contextProperty:"workflows",key:"workflows",moduleSpecifier:"@lunora/workflow"}],vn=dt,Rn=new Map(vn.flatMap(e=>e.serverCtxField?[[e.key,e.serverCtxField]]:[])),Xr=dt.flatMap(e=>"appMethod"in e?[{appMethod:e.appMethod,key:e.key}]:[]),ut=/^(?:"(?:[^"\\]|\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4}))*"|'[^'\\]*'|-?\d+(?:\.\d+)?|true|false|null)$/u,Sn=new Set(["any","bigint","boolean","date","id","null","number","storage","string","timestamp"]),pt=e=>e.column!==void 0,$n=(e,t)=>{switch(e){case"bigint":return`if (typeof ${t} !== "bigint") return DEFER;
|
|
19
19
|
`;case"boolean":return`if (typeof ${t} !== "boolean") return DEFER;
|
|
20
20
|
`;case"date":case"number":case"timestamp":return`if (typeof ${t} !== "number" || !Number.isFinite(${t})) return DEFER;
|
|
21
21
|
`;case"null":return`if (${t} !== null) return DEFER;
|
|
22
22
|
`;default:return`if (typeof ${t} !== "string") return DEFER;
|
|
23
|
-
`}},
|
|
24
|
-
`}},He=(e,t,n)=>{if(!
|
|
23
|
+
`}},Tn=(e,t)=>{const n=e.literalValue?.trim();if(!(n===void 0||!ut.test(n)))return{out:t,pre:`if (${t} !== ${n}) return DEFER;
|
|
24
|
+
`}},He=(e,t,n)=>{if(!pt(e)&&!(e.hasRefinement||e.sourceText!==void 0)){if(Sn.has(e.kind))return e.kind==="any"?{out:t,pre:""}:{out:t,pre:$n(e.kind,t)};switch(e.kind){case"array":return Nn(e,t,n);case"literal":return Tn(e,t);case"object":return In(e,t,n);default:return}}},Nn=(e,t,n)=>{const{inner:r}=e;if(!r)return;const o=n.next(),s=`__arr${String(o)}`,i=`__i${String(o)}`,a=`__e${String(o)}`,c=He(r,a,n);if(!c)return;const d=`if (!Array.isArray(${t})) return DEFER;
|
|
25
25
|
const ${s} = new Array(${t}.length);
|
|
26
26
|
for (let ${i} = 0; ${i} < ${t}.length; ${i}++) {
|
|
27
27
|
const ${a} = ${t}[${i}];
|
|
28
28
|
${c.pre}${s}[${i}] = ${c.out};
|
|
29
29
|
}
|
|
30
|
-
`;return{out:s,pre:d}},An=(e,t,n,r)=>{const o=JSON.stringify(e);if(t.kind==="optional"){if(t.hasRefinement||t.sourceText!==void 0||
|
|
30
|
+
`;return{out:s,pre:d}},An=(e,t,n,r)=>{const o=JSON.stringify(e);if(t.kind==="optional"){if(t.hasRefinement||t.sourceText!==void 0||pt(t))return;const{inner:i}=t;if(!i)return;const a=He(i,n,r);if(!a)return;const c=r.next(),d=`__has${String(c)}`,p=`__val${String(c)}`,m=`let ${d} = false;
|
|
31
31
|
let ${p};
|
|
32
32
|
if (${n} !== undefined) {
|
|
33
33
|
${a.pre}${p} = ${a.out};
|
|
34
34
|
${d} = true;
|
|
35
35
|
}
|
|
36
|
-
`;return{entry:`...(${d} ? { ${o}: ${p} } : {})`,pre:m}}const s=He(t,n,r);if(s)return{entry:`${o}: ${s.out}`,pre:s.pre}},On=new Set(Object.getOwnPropertyNames(Object.prototype)),
|
|
37
|
-
`,c=p}const d=An(s,i,c,n);if(!d)return;r+=d.pre,o.push(d.entry)}return{entries:o.join(", "),pre:r}},
|
|
38
|
-
`},In=(e,t,n)=>{const r=e.shape??{},o=
|
|
39
|
-
${
|
|
40
|
-
`;return{out:i,pre:a}},Cn=e=>{let t=0;const r=
|
|
36
|
+
`;return{entry:`...(${d} ? { ${o}: ${p} } : {})`,pre:m}}const s=He(t,n,r);if(s)return{entry:`${o}: ${s.out}`,pre:s.pre}},On=new Set(Object.getOwnPropertyNames(Object.prototype)),ht=(e,t,n)=>{let r="";const o=[];for(const s of Object.keys(e)){const i=e[s];if(!i)return;const a=JSON.stringify(s);let c=`${t}[${a}]`;if(On.has(s)){const p=`__f${String(n.next())}`;r+=`const ${p} = Object.hasOwn(${t}, ${a}) ? ${t}[${a}] : undefined;
|
|
37
|
+
`,c=p}const d=An(s,i,c,n);if(!d)return;r+=d.pre,o.push(d.entry)}return{entries:o.join(", "),pre:r}},mt=e=>{const t=`Object.getPrototypeOf(${e})`;return`if (${t} !== Object.prototype && ${t} !== null) return DEFER;
|
|
38
|
+
`},In=(e,t,n)=>{const r=e.shape??{},o=ht(r,t,n);if(!o)return;const s=n.next(),i=`__obj${String(s)}`,a=`if (typeof ${t} !== "object" || ${t} === null || Array.isArray(${t})) return DEFER;
|
|
39
|
+
${mt(t)}${o.pre}const ${i} = { ${o.entries} };
|
|
40
|
+
`;return{out:i,pre:a}},Cn=e=>{let t=0;const r=ht(e,"source",{next:()=>(t+=1,t)});if(r)return`(source) => {
|
|
41
41
|
if (typeof source !== "object" || source === null || Array.isArray(source)) return DEFER;
|
|
42
|
-
${
|
|
42
|
+
${mt("source")}${r.pre}return { ${r.entries} };
|
|
43
43
|
}`},$=`// GENERATED by @lunora/codegen — do not edit.
|
|
44
44
|
// Run \`lunora codegen\` to regenerate.
|
|
45
45
|
|
|
46
|
-
`,oe=(e=!1)=>e?{client:"lunorash/client",do:"lunorash/do",flags:"lunorash/flags",server:"lunorash/server",serverDataModel:"lunorash/server/data-model",serverDrizzle:"lunorash/server/drizzle",serverTypes:"lunorash/server/types",values:"lunorash/values"}:{client:"@lunora/client",do:"@lunora/do",flags:"@lunora/flags",server:"@lunora/server",serverDataModel:"@lunora/server/data-model",serverDrizzle:"@lunora/server/drizzle",serverTypes:"@lunora/server/types",values:"@lunora/values"},
|
|
46
|
+
`,oe=(e=!1)=>e?{client:"lunorash/client",do:"lunorash/do",flags:"lunorash/flags",server:"lunorash/server",serverDataModel:"lunorash/server/data-model",serverDrizzle:"lunorash/server/drizzle",serverTypes:"lunorash/server/types",values:"lunorash/values"}:{client:"@lunora/client",do:"@lunora/do",flags:"@lunora/flags",server:"@lunora/server",serverDataModel:"@lunora/server/data-model",serverDrizzle:"@lunora/server/drizzle",serverTypes:"@lunora/server/types",values:"@lunora/values"},gt=/^[A-Za-z_$][\w$]*$/u,Ln=/^[\w./-]+$/u,g=(e,t)=>{if(!gt.test(e))throw new X("INTERNAL",`@lunora/codegen: ${t} is not a valid JS identifier: ${JSON.stringify(e)}`)},Ve=e=>e.charAt(0).toUpperCase()+e.slice(1),j=e=>gt.test(e)?e:JSON.stringify(e),Y=e=>e==="__proto__"?`[${JSON.stringify(e)}]`:j(e),_n={any:"unknown",bigint:"bigint",boolean:"boolean",bytes:"ArrayBuffer",date:"number",geoPoint:"{ lat: number; lng: number }",null:"null",number:"number",storage:"string",string:"string",timestamp:"number"},En=e=>{if(e===void 0)return"unknown";if(!ut.test(e))throw new X("INTERNAL",`@lunora/codegen: v.literal() argument is not a parseable string/number/boolean/null literal: ${JSON.stringify(e)}`);return e},A=e=>{const t=_n[e.kind];if(t!==void 0)return t;switch(e.kind){case"array":return`Array<${e.inner?A(e.inner):"unknown"}>`;case"from":return e.tsType??"unknown";case"id":{const n=e.tableName??"_unknown_";return g(n,"v.id() table name"),`Id<"${n}">`}case"literal":return En(e.literalValue);case"object":return e.shape?`{ ${Object.entries(e.shape).map(([r,o])=>ft(r,o)).join("; ")} }`:"Record<string, unknown>";case"optional":return`${e.inner?A(e.inner):"unknown"} | undefined`;case"record":{const n=e.keyType?A(e.keyType):"string",r=e.valueType?A(e.valueType):"unknown";return`Record<${n}, ${r}>`}case"union":return e.members&&e.members.length>0?e.members.map(n=>A(n)).join(" | "):"never";default:return"unknown"}},ft=(e,t)=>{const n=j(e);if(t.kind==="optional"){const r=t.inner?A(t.inner):"unknown";return`${n}?: ${r}`}return`${n}: ${A(t)}`},xe=e=>{const t=Object.entries(e);return t.length===0?"{}":`{ ${t.map(([n,r])=>ft(n,r)).join("; ")} }`},bt=e=>e.kind==="optional"||!!e.column?.hasDefault,ae=e=>e.kind==="optional"&&e.inner?e.inner:e,Dn=e=>{g(e.name,"table name");const t=Object.entries(e.shape).map(([r,o])=>{const s=ae(o),i=A(s),a=j(r);return bt(o)?` ${a}?: ${i};`:` ${a}: ${i};`}).join(`
|
|
47
47
|
`),n=t?`
|
|
48
48
|
${t}
|
|
49
49
|
`:"";return`export interface Insert_${e.name} {
|
|
50
50
|
_id?: Id<"${e.name}">;
|
|
51
|
-
_creationTime?: number;${n}}`},Ge="schema",Zr=e=>{for(const u of e.tables)g(u.name,"table name");const t=e.tables.map(u=>`"${u.name}"`).join(" | ")||"never",n=e.tables.filter(u=>u.extensionKey===void 0).map(u=>`"${u.name}"`).join(" | ")||"never",r=e.tables.map(u=>{const l=Object.entries(u.shape).map(([R,x])=>{const I=
|
|
51
|
+
_creationTime?: number;${n}}`},Ge="schema",Zr=e=>{for(const u of e.tables)g(u.name,"table name");const t=e.tables.map(u=>`"${u.name}"`).join(" | ")||"never",n=e.tables.filter(u=>u.extensionKey===void 0).map(u=>`"${u.name}"`).join(" | ")||"never",r=e.tables.map(u=>{const l=Object.entries(u.shape).map(([R,x])=>{const I=j(R);if(x.kind==="optional"){const N=x.inner?A(x.inner):"unknown";return` ${I}?: ${N};`}return` ${I}: ${A(x)};`}).join(`
|
|
52
52
|
`),f=l?`
|
|
53
53
|
${l}
|
|
54
54
|
`:"",k=u.commitOrdered===!0?`
|
|
@@ -64,7 +64,7 @@ ${l}
|
|
|
64
64
|
`),d=e.vectorIndexes.map(u=>JSON.stringify(u.name)).join(" | ")||"never",p=e.tables.map(u=>Dn(u)).join(`
|
|
65
65
|
|
|
66
66
|
`),m=e.tables.map(u=>` ${u.name}: Insert_${u.name};`).join(`
|
|
67
|
-
`),y=e.tables.map(u=>{const l=u.relations.map(f=>(g(f.table,"relation target table"),` ${
|
|
67
|
+
`),y=e.tables.map(u=>{const l=u.relations.map(f=>(g(f.table,"relation target table"),` ${j(f.name)}: ${f.kind==="one"?"OneRelation":"ManyRelation"}<"${f.table}">;`)).join(`
|
|
68
68
|
`);return l?` ${u.name}: {
|
|
69
69
|
${l}
|
|
70
70
|
};`:` ${u.name}: {};`}).join(`
|
|
@@ -160,8 +160,8 @@ export interface Relations {
|
|
|
160
160
|
${y}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
`,Ge)},Pn=/import\("(?<spec>(?:\.\.?\/)*_generated\/[^"]+)"\)/gu,
|
|
164
|
-
`);return` ${
|
|
163
|
+
`,Ge)},Pn=/import\("(?<spec>(?:\.\.?\/)*_generated\/[^"]+)"\)/gu,yt=/^(?:\.\.?\/)*_generated\//u,wt=/\.[A-Za-z]\w*$/u,Fn=e=>e.replaceAll(Pn,(t,n)=>{const r=n.replace(yt,"./");return`import("${wt.test(r)?r:`${r}.js`}")`}),Mn=/import\("(?<spec>[^"]+)"\)/gu,st="node_modules/",Bn=/^@types\//u,qn=e=>e.replaceAll(Mn,(t,n)=>{const r=n.lastIndexOf(st);if(r===-1||r>0&&n[r-1]!=="/")return t;const o=n.slice(r+st.length);return o===""||Bn.test(o)?t:`import("${o}")`}),jn=/import\("(?<spec>\.\.?\/[^"]+)"\)/gu,Wn=/^(?:\.\.?\/)+/u,Un=e=>{const t=[];for(const n of e.split("/"))if(!(n===""||n===".")){if(n===".."&&t.length>0&&t.at(-1)!==".."){t.pop();continue}t.push(n)}return t.join("/")},Kn=(e,t)=>e.replaceAll(jn,(n,r)=>{if(yt.test(r.replace(Wn,"")))return n;const o=t.includes("/")?t.slice(0,t.lastIndexOf("/")):"",i=`../${Un(`${o}/${r}`)}`;return`import("${wt.test(i)?i:`${i}.js`}")`}),E=(e,t)=>Fn(Kn(qn(e),t)),Qn=["client","do","errors","flags","observability","platform","ratelimit","runtime","server","values"],Vn=new RegExp(String.raw`import\("@lunora/(?<pkg>${Qn.join("|")})(?<subpath>/[^"]*)?"\)`,"gu"),Hn={flags:new Set(["/web"]),platform:new Set},kt=(e,t)=>t?e.replaceAll(Vn,(n,r,o)=>{const s=Hn[r];return o&&s&&!s.has(o)?n:`import("lunorash/${r}${o??""}")`}):e,Je=e=>["Doc","Id"].filter(t=>new RegExp(String.raw`(?<![.$\w])${t}<`,"u").test(e)),xt=e=>e.output===void 0||e.output.kind==="any"||e.kind==="stream"?e.returnType:A(e.output),ze=e=>{const t=new Map;for(const n of e){const r=t.get(n.filePath)??[];r.push(n),t.set(n.filePath,r)}return[...t.entries()].toSorted(([n],[r])=>n.localeCompare(r))},it=e=>{const t=([n,r])=>{const o=r.toSorted((s,i)=>s.exportName.localeCompare(i.exportName)).map(s=>{const i=E(xe(s.args),s.filePath),a=E(xt(s),s.filePath);return` ${s.exportName}: FunctionReference<"${s.kind}", ${i}, ${a}>;`}).join(`
|
|
164
|
+
`);return` ${j(T(n))}: {
|
|
165
165
|
${o}
|
|
166
166
|
};`};return ze(e).map(n=>t(n)).join(`
|
|
167
167
|
`)},Gn=(e,t)=>{if(e.length===0&&t.length===0)return{block:"",importLine:""};const n=[];let r="";if(e.length>0&&(r+="\n/** Params type carried by a `defineWorkflow` definition (its phantom `__params`). */\ntype WorkflowParamsOf<Definition> = Definition extends { __params?: infer Params } ? (unknown extends Params ? Record<string, unknown> : Params) : Record<string, unknown>;\n"),r+=`
|
|
@@ -173,7 +173,7 @@ export interface WorkflowReference<Params = Record<string, unknown>> {
|
|
|
173
173
|
readonly name: string;
|
|
174
174
|
}
|
|
175
175
|
`,e.length>0){const o=[...e].toSorted((a,c)=>a.exportName.localeCompare(c.exportName)),s=o.map(a=>` ${a.exportName}: WorkflowReference<WorkflowParamsOf<typeof lunoraWorkflowDefinitions.${a.exportName}>>;`).join(`
|
|
176
|
-
`),i=o.map(a=>` ${
|
|
176
|
+
`),i=o.map(a=>` ${Y(a.exportName)}: { isLunoraWorkflow: true, binding: ${JSON.stringify(a.bindingName)}, name: ${JSON.stringify(a.exportName)} },`).join(`
|
|
177
177
|
`);r+=`
|
|
178
178
|
/** This project's durable workflows, addressable as typed references (e.g. \`crons.daily("digest", …, workflows.digestPipeline, params)\`). */
|
|
179
179
|
export interface WorkflowsRef {
|
|
@@ -185,7 +185,7 @@ ${i}
|
|
|
185
185
|
};
|
|
186
186
|
`,n.push(`import type * as lunoraWorkflowDefinitions from "../workflows.js";
|
|
187
187
|
`)}if(t.length>0){const o=[...t].toSorted((a,c)=>a.exportName.localeCompare(c.exportName)),s=o.map(a=>` ${a.exportName}: WorkflowReference<AgentRunInput>;`).join(`
|
|
188
|
-
`),i=o.map(a=>` ${
|
|
188
|
+
`),i=o.map(a=>` ${Y(a.exportName)}: { isLunoraWorkflow: true, binding: ${JSON.stringify(a.bindingName)}, name: ${JSON.stringify(a.name)} },`).join(`
|
|
189
189
|
`);r+=`
|
|
190
190
|
/** This project's durable agents, addressable as typed references for \`cronJobs()\` — each fire starts a fresh agent run (e.g. \`crons.daily("sweep", …, agents.support, { input, threadKey })\`). */
|
|
191
191
|
export interface AgentsRef {
|
|
@@ -196,7 +196,7 @@ export const agents: AgentsRef = {
|
|
|
196
196
|
${i}
|
|
197
197
|
};
|
|
198
198
|
`,n.push(`import type { AgentRunInput } from "@lunora/agent";
|
|
199
|
-
`)}return{block:r,importLine:n.join("")}},Jn=()=>Object.keys(
|
|
199
|
+
`)}return{block:r,importLine:n.join("")}},Jn=()=>Object.keys(ct().functions).toSorted((e,t)=>e.localeCompare(t)),vt=e=>new Set(e.filter(t=>T(t.filePath)==="agents").map(t=>t.exportName)),zn=()=>new Set(Object.entries(ct().functions).filter(([,e])=>e.visibility==="internal").map(([e])=>e)),Yn=(e,t)=>{const n={importLine:"",lines:"",prelude:""};if(e.length===0)return n;const r=vt(t),o=zn(),s=t.find(a=>T(a.filePath)==="agents"&&o.has(a.exportName));if(s)throw new X("INTERNAL",`@lunora/codegen: "agents:${s.exportName}" is reserved for the durable agent loop's internal dispatch — rename the "${s.exportName}" export in ${s.filePath}`);const i=Jn().filter(a=>!r.has(a));return i.length===0?n:{importLine:`import { agentComponent } from "@lunora/agent/component";
|
|
200
200
|
`,lines:i.map(a=>` "agents:${a}": lunoraAgentRuntimeFunctions.${a} as unknown as RegisteredLunoraFunction,`).join(`
|
|
201
201
|
`),prelude:`
|
|
202
202
|
/**
|
|
@@ -205,13 +205,13 @@ ${i}
|
|
|
205
205
|
* subscribe to the public queries (\`agents:agentMessages\` is the live thread view).
|
|
206
206
|
*/
|
|
207
207
|
const lunoraAgentRuntimeFunctions = agentComponent().functions;
|
|
208
|
-
`}},Xn=(e,t)=>{if(!e)return{importLine:"",lines:"",prelude:""};const n=t.find(r=>
|
|
209
|
-
`,lines:' "sandbox:invoke": lunoraSandbox.invoke as unknown as RegisteredLunoraFunction,',prelude:"\n/**\n * The `@lunora/agent` sandbox dispatcher — auto-registered because `lunora/`\n * imports a sandbox tool (`browserTool`/`containerTool`). The batteries-included\n * tools dispatch to this internal action, which runs on an action ctx carrying\n * `ctx.browser` + `ctx.containers` (the durable tool step itself has neither).\n */\nconst lunoraSandbox = sandboxComponent();\n"}},Zn=(e,t)=>{if(e.length===0)return[];const n=
|
|
210
|
-
`);return` ${
|
|
208
|
+
`}},Xn=(e,t)=>{if(!e)return{importLine:"",lines:"",prelude:""};const n=t.find(r=>T(r.filePath)==="sandbox"&&r.exportName==="invoke");if(n)throw new X("INTERNAL",`@lunora/codegen: "sandbox:invoke" is reserved for the batteries-included sandbox tool dispatcher (browserTool/containerTool) — rename the "invoke" export in ${n.filePath}`);return{importLine:`import { sandboxComponent } from "@lunora/agent/component";
|
|
209
|
+
`,lines:' "sandbox:invoke": lunoraSandbox.invoke as unknown as RegisteredLunoraFunction,',prelude:"\n/**\n * The `@lunora/agent` sandbox dispatcher — auto-registered because `lunora/`\n * imports a sandbox tool (`browserTool`/`containerTool`). The batteries-included\n * tools dispatch to this internal action, which runs on an action ctx carrying\n * `ctx.browser` + `ctx.containers` (the durable tool step itself has neither).\n */\nconst lunoraSandbox = sandboxComponent();\n"}},Zn=(e,t)=>{if(e.length===0)return[];const n=vt(t),r=[{args:{key:{kind:"string"},limit:{inner:{kind:"number"},kind:"optional"}},exportName:"agentMessages",filePath:"agents",kind:"query",returnType:"Record<string, unknown>[]"},{args:{decision:{kind:"union",members:[{kind:"literal",literalValue:'"approve"'},{kind:"literal",literalValue:'"reject"'}]},instanceId:{kind:"string"},note:{inner:{kind:"string"},kind:"optional"},threadKey:{kind:"string"},toolCallId:{kind:"string"}},exportName:"agentResolveApproval",filePath:"agents",kind:"mutation",returnType:"{ resolved: boolean }"},{args:{agent:{kind:"string"},input:{kind:"string"},threadKey:{kind:"string"},title:{inner:{kind:"string"},kind:"optional"}},exportName:"agentRun",filePath:"agents",kind:"mutation",returnType:"{ id: string; threadKey: string }"},{args:{key:{kind:"string"}},exportName:"agentState",filePath:"agents",kind:"query",returnType:"Record<string, unknown> | undefined"},{args:{key:{kind:"string"}},exportName:"agentThread",filePath:"agents",kind:"query",returnType:"Record<string, unknown> | undefined"}];for(const o of e)o.voice&&r.push({args:{threadKey:{kind:"string"}},exportName:`${o.exportName}Voice`,filePath:"agents",kind:"stream",returnType:"Record<string, unknown>"});return r.filter(o=>!n.has(o.exportName))},er=(e,t)=>{if(e.length===0)return[];const n=new Set(t.filter(r=>T(r.filePath)==="mutators").map(r=>r.exportName));return e.filter(r=>!n.has(r.exportName)).map(r=>({args:r.args,exportName:r.exportName,filePath:r.filePath,kind:"mutation",returnType:r.returnType}))},tr=e=>{const t=e.filter(i=>i.stream);if(t.length===0)return{block:"",body:""};const n=ze(t),r=n.map(([i,a])=>{const c=a.map(d=>{const p=E(d.chunkType??"unknown",d.filePath),m=E(xe(d.searchParams),d.filePath),y=E(xe(d.params),d.filePath);return` ${j(d.exportName)}: HttpStreamRef<${p}, ${m}, ${y}>;`}).join(`
|
|
210
|
+
`);return` ${j(T(i))}: {
|
|
211
211
|
${c}
|
|
212
212
|
};`}).join(`
|
|
213
|
-
`),o=n.map(([i,a])=>{const c=a.map(d=>` ${
|
|
214
|
-
`);return` ${
|
|
213
|
+
`),o=n.map(([i,a])=>{const c=a.map(d=>` ${Y(d.exportName)}: { method: ${JSON.stringify(d.method)}, path: ${JSON.stringify(d.path)} },`).join(`
|
|
214
|
+
`);return` ${Y(T(i))}: {
|
|
215
215
|
${c}
|
|
216
216
|
},`}).join(`
|
|
217
217
|
`);return{block:`
|
|
@@ -223,7 +223,7 @@ ${r}
|
|
|
223
223
|
export const httpStreams: HttpStreamsRef = {
|
|
224
224
|
${o}
|
|
225
225
|
};
|
|
226
|
-
`,body:r}},eo=e=>{const{agents:t=[],functions:n,httpRoutes:r=[],mutators:o=[],useUmbrella:s=!1,workflows:i=[]}=e,a=oe(s),c=[...n.filter(v=>v.visibility!=="internal"),...Zn(t,n),...er(o,n)],d=n.filter(v=>v.visibility==="internal"),p=
|
|
226
|
+
`,body:r}},eo=e=>{const{agents:t=[],functions:n,httpRoutes:r=[],mutators:o=[],useUmbrella:s=!1,workflows:i=[]}=e,a=oe(s),c=[...n.filter(v=>v.visibility!=="internal"),...Zn(t,n),...er(o,n)],d=n.filter(v=>v.visibility==="internal"),p=it(c),m=it(d),y=tr(r),u=`${p}
|
|
227
227
|
${m}
|
|
228
228
|
${y.body}`,l=Je(u),f=l.length>0?`
|
|
229
229
|
import type { ${l.join(", ")} } from "./dataModel.js";
|
|
@@ -239,8 +239,8 @@ export const api = anyApi as unknown as ApiTypes;
|
|
|
239
239
|
export interface InternalApiTypes {${R}}
|
|
240
240
|
|
|
241
241
|
export const internal = anyApi as unknown as InternalApiTypes;
|
|
242
|
-
${x.block}${y.block}`,
|
|
243
|
-
`:"";return
|
|
242
|
+
${x.block}${y.block}`,N=[...I.includes("FunctionReference<")?["FunctionReference"]:[],...y.block===""?[]:["HttpStreamRef"]],_=N.length>0?`import type { ${N.join(", ")} } from "${a.client}";
|
|
243
|
+
`:"";return kt(`${$}import { anyApi } from "${a.client}";
|
|
244
244
|
${_}${x.importLine}${f}
|
|
245
245
|
${I}`,s)},to=e=>e?`${$}import { createSeedClient as createSeedClientBase } from "@lunora/seed";
|
|
246
246
|
import type { SeedClient, SeedClientOptions } from "@lunora/seed";
|
|
@@ -313,7 +313,7 @@ import type { Collection } from "@tanstack/db";
|
|
|
313
313
|
import { createCollection } from "@tanstack/db";
|
|
314
314
|
${a}
|
|
315
315
|
${s}
|
|
316
|
-
`},nr=(e,t)=>`lunora_${
|
|
316
|
+
`},nr=(e,t)=>`lunora_${T(e)}_${String(t)}`,rr=`const callRegistered = async <R>(context: CallerCtx, functionPath: string, args: Record<string, unknown> | undefined): Promise<R> => {
|
|
317
317
|
const registered = LUNORA_FUNCTIONS[functionPath];
|
|
318
318
|
|
|
319
319
|
if (!registered) {
|
|
@@ -321,12 +321,12 @@ ${s}
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
return (await registered.handler(context, args ?? {})) as R;
|
|
324
|
-
};`,or=(e,t,n=[],r=[])=>{const o=new Map,s=l=>{o.has(l)||o.set(l,nr(l,o.size))};for(const l of e)s(l.filePath);for(const l of t)s(l.filePath);for(const l of n)s(l.filePath);for(const l of r)s(l.filePath);const i=[...o.entries()].map(([l,f])=>{if(!Ln.test(l))throw new
|
|
325
|
-
`),a=e.map(l=>` "${
|
|
324
|
+
};`,or=(e,t,n=[],r=[])=>{const o=new Map,s=l=>{o.has(l)||o.set(l,nr(l,o.size))};for(const l of e)s(l.filePath);for(const l of t)s(l.filePath);for(const l of n)s(l.filePath);for(const l of r)s(l.filePath);const i=[...o.entries()].map(([l,f])=>{if(!Ln.test(l))throw new X("INTERNAL",`@lunora/codegen: refusing to emit import for unsafe file path: ${JSON.stringify(l)}`);return`import * as ${f} from "../${l}.js";`}).join(`
|
|
325
|
+
`),a=e.map(l=>` "${T(l.filePath)}:${l.exportName}": ${o.get(l.filePath)??""}.${l.exportName} as unknown as RegisteredLunoraFunction,`).join(`
|
|
326
326
|
`),c=t.map(l=>` ${JSON.stringify(l.id)}: ${o.get(l.filePath)??""}.${l.exportName} as unknown as RegisteredDataMigration,`).join(`
|
|
327
|
-
`),d=n.map(l=>` "${
|
|
327
|
+
`),d=n.map(l=>` "${T(l.filePath)}:${l.exportName}": ${o.get(l.filePath)??""}.${l.exportName} as unknown as RegisteredLunoraFunction,`).join(`
|
|
328
328
|
`),p=r.map(l=>` "${l.exportName}": ${o.get(l.filePath)??""}.${l.exportName} as unknown as RegisteredShape,`).join(`
|
|
329
|
-
`),m=n.map(l=>`${
|
|
329
|
+
`),m=n.map(l=>`${T(l.filePath)}:${l.exportName}`),y=[a,d].filter(l=>l.length>0).join(`
|
|
330
330
|
`),u=e.map(l=>{if(l.lifecycle||Object.keys(l.args).length===0)return;const f=Cn(l.args);return f===void 0?void 0:`installCompiledValidatorMap(${o.get(l.filePath)??""}.${l.exportName}.args, ${f});`}).filter(l=>l!==void 0).join(`
|
|
331
331
|
`);return{dispatchBody:y.length>0?`
|
|
332
332
|
${y}
|
|
@@ -336,22 +336,22 @@ ${y}
|
|
|
336
336
|
${c}
|
|
337
337
|
`:"",mutatorPaths:m,shapeBody:p.length>0?`
|
|
338
338
|
${p}
|
|
339
|
-
`:""}},ar=e=>{const t=ze(e),n=t.map(([o,s])=>{const i=s.map(a=>{const c=E(xe(a.args),a.filePath),d=c==="{}"?"?":"",p=E(
|
|
340
|
-
`);return` ${
|
|
339
|
+
`:""}},ar=e=>{const t=ze(e),n=t.map(([o,s])=>{const i=s.map(a=>{const c=E(xe(a.args),a.filePath),d=c==="{}"?"?":"",p=E(xt(a),a.filePath);return a.kind==="stream"?` ${a.exportName}: (args${d}: ${c}) => Promise<AsyncIterable<${p}>>;`:` ${a.exportName}: (args${d}: ${c}) => Promise<${p}>;`}).join(`
|
|
340
|
+
`);return` ${j(T(o))}: {
|
|
341
341
|
${i}
|
|
342
342
|
};`}).join(`
|
|
343
|
-
`);return{implementation:t.map(([o,s])=>{const i=
|
|
344
|
-
`);return` ${
|
|
343
|
+
`);return{implementation:t.map(([o,s])=>{const i=T(o),a=s.map(c=>` ${Y(c.exportName)}: (args) => callRegistered(context, "${i}:${c.exportName}", args),`).join(`
|
|
344
|
+
`);return` ${Y(i)}: {
|
|
345
345
|
${a}
|
|
346
346
|
},`}).join(`
|
|
347
|
-
`),types:n}},sr=(e,t=[])=>{const n=new Set;for(const r of e.tables)for(const o of Object.values(r.shape)){const s=ae(o);s.kind==="storage"&&typeof s.bucket=="string"&&s.bucket!==""&&n.add(s.bucket)}for(const r of t)r!==""&&r!=="default"&&n.add(r);return["default",...[...n].toSorted((r,o)=>r.localeCompare(o))]},ro=({agents:e=[],containers:t=[],env:n,hasAccessFacade:r=!1,hasAi:o=!1,hasAnalytics:s=!1,hasBrowser:i=!1,hasFlags:a=!1,hasHyperdrive:c=!1,hasImages:d=!1,hasKv:p=!1,hasNotify:m=!1,hasPayments:y=!1,hasPipelines:u=!1,hasR2sql:l=!1,hasX402:f=!1,identity:k,queues:R=[],schema:x,storageRuleBuckets:I=[],useUmbrella:
|
|
347
|
+
`),types:n}},sr=(e,t=[])=>{const n=new Set;for(const r of e.tables)for(const o of Object.values(r.shape)){const s=ae(o);s.kind==="storage"&&typeof s.bucket=="string"&&s.bucket!==""&&n.add(s.bucket)}for(const r of t)r!==""&&r!=="default"&&n.add(r);return["default",...[...n].toSorted((r,o)=>r.localeCompare(o))]},ro=({agents:e=[],containers:t=[],env:n,hasAccessFacade:r=!1,hasAi:o=!1,hasAnalytics:s=!1,hasBrowser:i=!1,hasFlags:a=!1,hasHyperdrive:c=!1,hasImages:d=!1,hasKv:p=!1,hasNotify:m=!1,hasPayments:y=!1,hasPipelines:u=!1,hasR2sql:l=!1,hasX402:f=!1,identity:k,queues:R=[],schema:x,storageRuleBuckets:I=[],useUmbrella:N=!1,workflows:_=[]}={})=>{const v=oe(N),w=sr(x??{tables:[]},I).map(h=>JSON.stringify(h)).join(" | "),F=o?`import type { LunoraAi } from "@lunora/ai";
|
|
348
348
|
`:"",se=o?`
|
|
349
349
|
readonly ai: LunoraAi;`:"",M=y?`import type { LunoraPayment } from "@lunora/payment";
|
|
350
350
|
`:"",ie=y?`
|
|
351
351
|
readonly payments: LunoraPayment;`:"",le=f?`import type { X402Pay } from "@lunora/x402/pay";
|
|
352
352
|
`:"",W=f?`
|
|
353
353
|
readonly x402: X402Pay;`:"",C=t.length>0?`import type { ContainerAccessor } from "@lunora/container";
|
|
354
|
-
`:"",
|
|
354
|
+
`:"",Z=t.length>0?`
|
|
355
355
|
readonly containers: {${t.map(h=>`
|
|
356
356
|
readonly ${h.exportName}: ContainerAccessor;`).join("")}
|
|
357
357
|
};`:"",L=[...o?[" /** Workers AI binding (the conventional `env.AI`), narrowing `ctx.ai`. */\n readonly AI?: unknown;"]:[],...t.map(h=>(g(h.bindingName,`container binding "${h.bindingName}"`),` /** Durable Object namespace for the \`${h.exportName}\` container. */
|
|
@@ -377,7 +377,7 @@ ${L}`:""}
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
/** Alias for {@link CloudflareBindings} — the typed shape of \`env\`. */
|
|
380
|
-
export type Env = CloudflareBindings;`,B=(h,
|
|
380
|
+
export type Env = CloudflareBindings;`,B=(h,je,Te)=>{const ne=Rn.get(h);if(ne!==void 0&&ne.tier!==Te)throw new X("INTERNAL",`@lunora/codegen: capability "${h}" declares tier "${ne.tier}" in SERVER_CTX_FIELDS but is emitted onto the "${Te}" context — update whichever is wrong.`);return je?ne?.field??"":""},ce=B("kv",p,"every"),de=B("analytics",s,"every"),Ae=B("hyperdrive",c,"action"),Oe=B("browser",i,"action"),Ie=B("images",d,"action"),ue=B("pipelines",u,"action"),V=B("r2sql",l,"action"),P=(x?.vectorIndexes.length??0)>0,H=P?' | "vectors"':"",ee=P?`
|
|
381
381
|
readonly vectors: VectorSearch<VectorIndexName>;`:"",pe=P?`
|
|
382
382
|
readonly vectors: VectorSearchReader<VectorIndexName>;`:"",G=P?`import type { VectorSearch, VectorSearchReader } from "${v.server}";
|
|
383
383
|
import type { VectorIndexName } from "./dataModel.js";
|
|
@@ -389,9 +389,9 @@ import type { VectorIndexName } from "./dataModel.js";
|
|
|
389
389
|
/** Multi-channel notifications (@lunora/notify): send / chat / inApp / webhook plus the push device sub-facade. Sends are external I/O — confine them to action handlers. */
|
|
390
390
|
readonly notify: import("@lunora/notify").LunoraNotify;
|
|
391
391
|
/** Device push sub-facade — the same object as ctx.notify.push (register / send / broadcast). Sends belong in action handlers. */
|
|
392
|
-
readonly push: import("@lunora/notify").LunoraPush;`:"",
|
|
392
|
+
readonly push: import("@lunora/notify").LunoraPush;`:"",q=_.length>0,te=q?`import type { WorkflowHandle } from "@lunora/workflow";
|
|
393
393
|
import type * as lunoraWorkflowDefinitions from "../workflows.js";
|
|
394
|
-
`:"",Ce=
|
|
394
|
+
`:"",Ce=q?`
|
|
395
395
|
|
|
396
396
|
/** Params type carried by a \`defineWorkflow\` definition (its phantom \`__params\`). */
|
|
397
397
|
type WorkflowParamsOf<Definition> = Definition extends { __params?: infer Params }
|
|
@@ -404,7 +404,7 @@ type WorkflowParamsOf<Definition> = Definition extends { __params?: infer Params
|
|
|
404
404
|
export interface LunoraWorkflows {
|
|
405
405
|
${_.map(h=>` get(name: ${JSON.stringify(h.exportName)}): WorkflowHandle<WorkflowParamsOf<typeof lunoraWorkflowDefinitions.${h.exportName}>>;`).join(`
|
|
406
406
|
`)}
|
|
407
|
-
}`:"",ve=
|
|
407
|
+
}`:"",ve=q?' | "workflows"':"",Re=q?`
|
|
408
408
|
readonly workflows: LunoraWorkflows;`:"",he=R.length>0,Le=he?`import type { QueueProducer } from "@lunora/queue";
|
|
409
409
|
import type * as lunoraQueueDefinitions from "../queues.js";
|
|
410
410
|
`:"",_e=he?`
|
|
@@ -435,7 +435,7 @@ export type Identity = InferIdentity<typeof lunoraIdentityContract.${k.exportNam
|
|
|
435
435
|
/** \`ctx.auth\` narrowed so \`getIdentity()\` resolves the declared {@link Identity} contract instead of the untyped claim bag. */
|
|
436
436
|
type NarrowedAuth = Omit<QueryCtxBase["auth"], "getIdentity"> & { getIdentity: () => Promise<Identity | null> };`:"",ge=k?' | "auth"':"",fe=k?`
|
|
437
437
|
readonly auth: NarrowedAuth;`:"",Me=k?", Identity":"",Be=n?`import type * as lunoraEnvContract from "../env.js";
|
|
438
|
-
`:"",
|
|
438
|
+
`:"",qe=n?`
|
|
439
439
|
|
|
440
440
|
/** This app's declared env contract (\`defineEnv\` in \`lunora/env.ts\`) — the validated, coercion-aware shape of \`ctx.env\`. */
|
|
441
441
|
export type LunoraEnv = ReturnType<typeof lunoraEnvContract.${n.exportName}>;`:"",be=n?' | "env"':"",ye=n?"\n /** Validated, typed environment declared by `defineEnv` in `lunora/env.ts` — parsed & coercion-aware config values (`ctx.env.STRIPE_KEY`); a missing or invalid value throws at read time. */\n readonly env: LunoraEnv;":"";return`${$}import { createPolicyDsl, defineMutator as defineMutatorBase, initLunora, v as vBase } from "${v.server}";
|
|
@@ -495,7 +495,7 @@ export type {
|
|
|
495
495
|
} from "${v.serverDataModel}";
|
|
496
496
|
|
|
497
497
|
import type { DataModel, Doc, GeoIndexNamesByTable, Id as IdOfTable, IndexNamesByTable, Insert, InsertModel, RankIndexNamesByTable, Relations, SearchIndexNamesByTable, TableName } from "./dataModel.js";
|
|
498
|
-
${G}${F}${M}${le}${C}${
|
|
498
|
+
${G}${F}${M}${le}${C}${te}${Le}${Ee}${Pe}${Be}
|
|
499
499
|
export type { AppTableName, DataModel, Doc, Id, TableName } from "./dataModel.js";
|
|
500
500
|
|
|
501
501
|
/**
|
|
@@ -555,7 +555,7 @@ export interface OrmWriter extends OrmReader {
|
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
/** Storage buckets this schema declares (\`v.storage("name")\`), narrowing \`ctx.storage.bucket(name)\`. */
|
|
558
|
-
export type StorageBucketName = ${w};${S}${Ce}${_e}${De}${Fe}${
|
|
558
|
+
export type StorageBucketName = ${w};${S}${Ce}${_e}${De}${Fe}${qe}
|
|
559
559
|
|
|
560
560
|
/**
|
|
561
561
|
* Project-typed contexts. The base contexts from \`@lunora/server\` are
|
|
@@ -627,13 +627,13 @@ export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"${H}${ge}${
|
|
|
627
627
|
export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${H}${ve}${ge}${be}> {
|
|
628
628
|
readonly db: Omit<DatabaseWriter, "asId" | "query" | "get"> & DatabaseWriterFacade & { asId: TypedAsId; query: TypedTableQuery; get: TypedTableGet };
|
|
629
629
|
readonly orm: OrmWriter;
|
|
630
|
-
readonly storage: MutationStorage<StorageBucketName>;${
|
|
630
|
+
readonly storage: MutationStorage<StorageBucketName>;${ee}${U}${ce}${K}${Q}${de}${ye}${Re}${Se}${$e}${fe}
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${H}${ve}${ge}${be}> {
|
|
634
634
|
readonly db: Omit<DatabaseWriter, "asId" | "query" | "get"> & DatabaseWriterFacade & { asId: TypedAsId; query: TypedTableQuery; get: TypedTableGet };
|
|
635
635
|
readonly orm: OrmWriter;
|
|
636
|
-
readonly storage: StorageBase<StorageBucketName>;${
|
|
636
|
+
readonly storage: StorageBase<StorageBucketName>;${ee}${U}${se}${ie}${W}${Z}${ce}${K}${Q}${Ae}${Oe}${Ie}${de}${ue}${V}${ye}${Re}${Se}${$e}${fe}
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
/**
|
|
@@ -708,12 +708,12 @@ export const definePolicy = createPolicyDsl<DataModel, Relations${Me}>();
|
|
|
708
708
|
export const v = vBase as unknown as Omit<typeof vBase, "id"> & {
|
|
709
709
|
id: <T extends TableName>(table: T) => ColumnValidator<IdOfTable<T>, IdOfTable<T>>;
|
|
710
710
|
};
|
|
711
|
-
`},ir=e=>{const t={connect:[],disconnect:[],init:[],reactor:[]};for(const n of e)n.lifecycle&&t[n.lifecycle].push(`${
|
|
711
|
+
`},ir=e=>{const t={connect:[],disconnect:[],init:[],reactor:[]};for(const n of e)n.lifecycle&&t[n.lifecycle].push(`${T(n.filePath)}:${n.exportName}`);return t},oo=e=>{const{agents:t=[],functions:n,migrations:r=[],mutators:o=[],shapes:s=[],useUmbrella:i=!1,usesSandbox:a=!1}=e,c=n.length>0,d=oe(i),{dispatchBody:p,importBlock:m,installBlock:y,migrationBody:u,mutatorPaths:l,shapeBody:f}=or(n,r,o,s),k=Yn(t,n),R=Xn(a,n),x=[k.lines,R.lines].filter(S=>S.length>0).join(`
|
|
712
712
|
`);let I=p;x.length>0&&(I=p.length>0?`${p.trimEnd()}
|
|
713
713
|
${x}
|
|
714
714
|
`:`
|
|
715
715
|
${x}
|
|
716
|
-
`);const
|
|
716
|
+
`);const N=ir(n),_=s.length>0?`import type { RegisteredShape } from "${d.server}";
|
|
717
717
|
`:"",v=s.length>0?`
|
|
718
718
|
/**
|
|
719
719
|
* Replication-shape registry — one entry per \`defineShape\` in \`lunora/shapes.ts\`.
|
|
@@ -742,9 +742,9 @@ ${M.types}
|
|
|
742
742
|
`:"",le=M.implementation?`
|
|
743
743
|
${M.implementation}
|
|
744
744
|
`:"",W=Je(M.types),C=W.length>0?`import type { ${W.join(", ")} } from "./dataModel.js";
|
|
745
|
-
`:"",
|
|
745
|
+
`:"",Z=c?"context":"_context",L=c?`${rr}
|
|
746
746
|
|
|
747
|
-
`:"";return
|
|
747
|
+
`:"";return kt(`${$}${m}${k.importLine}${R.importLine}${F}${_}import { LunoraError } from "${d.server}";
|
|
748
748
|
import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
|
|
749
749
|
${C}
|
|
750
750
|
/**
|
|
@@ -795,10 +795,10 @@ ${se}${v}${w}
|
|
|
795
795
|
* anonymous — all via system dispatch.
|
|
796
796
|
*/
|
|
797
797
|
export const LUNORA_LIFECYCLE_HOOKS: { connect: readonly string[]; disconnect: readonly string[]; init: readonly string[]; reactor: readonly string[] } = {
|
|
798
|
-
connect: [${
|
|
799
|
-
disconnect: [${
|
|
800
|
-
init: [${
|
|
801
|
-
reactor: [${
|
|
798
|
+
connect: [${N.connect.map(S=>JSON.stringify(S)).join(", ")}],
|
|
799
|
+
disconnect: [${N.disconnect.map(S=>JSON.stringify(S)).join(", ")}],
|
|
800
|
+
init: [${N.init.map(S=>JSON.stringify(S)).join(", ")}],
|
|
801
|
+
reactor: [${N.reactor.map(S=>JSON.stringify(S)).join(", ")}],
|
|
802
802
|
};
|
|
803
803
|
|
|
804
804
|
/**
|
|
@@ -832,7 +832,7 @@ export type CallerCtx = ActionCtx | MutationCtx | QueryCtx;
|
|
|
832
832
|
export interface Caller {${ie}}
|
|
833
833
|
|
|
834
834
|
${L}/** Build a {@link Caller} bound to \`context\` (typically a handler's \`ctx\`). */
|
|
835
|
-
export const createCaller = (${
|
|
835
|
+
export const createCaller = (${Z}: CallerCtx): Caller => ({${le}});
|
|
836
836
|
|
|
837
837
|
/**
|
|
838
838
|
* Single registered data migration, narrowed to the shape the per-shard runner
|
|
@@ -852,7 +852,7 @@ export interface RegisteredDataMigration {
|
|
|
852
852
|
* shard DO's admin RPC and the CLI resolve migrations to run through this map.
|
|
853
853
|
*/
|
|
854
854
|
export const LUNORA_MIGRATIONS: Record<string, RegisteredDataMigration> = {${u}};
|
|
855
|
-
`,i)},lr=e=>{const t={};for(const n of e.tables){const r={};for(const[o,s]of Object.entries(n.shape)){const i=ae(s);i.kind==="id"&&i.tableName!==void 0&&(r[o]=i.tableName)}Object.keys(r).length>0&&(t[n.name]=r)}return t},cr=e=>{const t={};for(const n of e.tables){const r=[];for(const[o,s]of Object.entries(n.shape))ae(s).kind==="storage"&&r.push(o);r.length>0&&(t[n.name]=r)}return t},dr=e=>{const t={};for(const n of e.tables){const r=[...n.indexes.map(o=>({fields:[...o.fields],name:o.name,type:"index",...o.unique===!0?{unique:!0}:{}})),...n.searchIndexes.map(o=>({fields:[o.field,...o.filterFields??[]],name:o.name,type:"search"})),...(n.geoIndexes??[]).map(o=>({fields:[o.field],name:o.name,type:"geo"})),...n.rankIndexes.map(o=>({fields:o.sortBy.map(s=>s.field),name:o.name,type:"rank"})),...n.vectorIndexes.map(o=>({fields:o.field===void 0?[]:[o.field],name:o.name,type:"vector"}))];r.length>0&&(t[n.name]=r)}return t},ur=e=>{const t=[];for(const n of e.tables)!n.ttl||n.shardMode==="global"||t.push({...n.ttl.after===void 0?{}:{after:n.ttl.after},field:n.ttl.field,...n.softDelete?{softDeleteField:n.softDelete.field}:{},table:n.name});return t},pr=e=>{if(e.kind!=="union"||e.members===void 0||e.members.length===0)return;const t=[];for(const n of e.members){if(n.kind!=="literal"||n.literalValue===void 0)return;try{const r=JSON.parse(n.literalValue);if(typeof r!="string")return;t.push(r)}catch{return}}return t},hr=(e,t)=>{const n=ae(t),r={name:e,optional:
|
|
855
|
+
`,i)},lr=e=>{const t={};for(const n of e.tables){const r={};for(const[o,s]of Object.entries(n.shape)){const i=ae(s);i.kind==="id"&&i.tableName!==void 0&&(r[o]=i.tableName)}Object.keys(r).length>0&&(t[n.name]=r)}return t},cr=e=>{const t={};for(const n of e.tables){const r=[];for(const[o,s]of Object.entries(n.shape))ae(s).kind==="storage"&&r.push(o);r.length>0&&(t[n.name]=r)}return t},dr=e=>{const t={};for(const n of e.tables){const r=[...n.indexes.map(o=>({fields:[...o.fields],name:o.name,type:"index",...o.unique===!0?{unique:!0}:{}})),...n.searchIndexes.map(o=>({fields:[o.field,...o.filterFields??[]],name:o.name,type:"search"})),...(n.geoIndexes??[]).map(o=>({fields:[o.field],name:o.name,type:"geo"})),...n.rankIndexes.map(o=>({fields:o.sortBy.map(s=>s.field),name:o.name,type:"rank"})),...n.vectorIndexes.map(o=>({fields:o.field===void 0?[]:[o.field],name:o.name,type:"vector"}))];r.length>0&&(t[n.name]=r)}return t},ur=e=>{const t=[];for(const n of e.tables)!n.ttl||n.shardMode==="global"||t.push({...n.ttl.after===void 0?{}:{after:n.ttl.after},field:n.ttl.field,...n.softDelete?{softDeleteField:n.softDelete.field}:{},table:n.name});return t},pr=e=>{if(e.kind!=="union"||e.members===void 0||e.members.length===0)return;const t=[];for(const n of e.members){if(n.kind!=="literal"||n.literalValue===void 0)return;try{const r=JSON.parse(n.literalValue);if(typeof r!="string")return;t.push(r)}catch{return}}return t},hr=(e,t)=>{const n=ae(t),r={name:e,optional:bt(t),type:n.kind};n.kind==="id"&&n.tableName!==void 0&&(r.ref=n.tableName),n.kind==="storage"&&(r.isStorage=!0,n.bucket!==void 0&&(r.bucket=n.bucket));const o=pr(n);return o!==void 0&&(r.enumValues=o),n.column?.notNull===!1&&(r.nullable=!0),r},mr=e=>{const t={};for(const n of e.tables){const r=[{name:"_id",optional:!1,pk:!0,type:"id"},{name:"_creationTime",optional:!1,type:"number"},...n.commitOrdered===!0?[{name:"_commitSeq",optional:!1,type:"number"}]:[]];for(const[o,s]of Object.entries(n.shape))r.push(hr(o,s));t[n.name]=r}return t},gr=e=>e?{importFragment:"serveRelationFanout, ",override:`
|
|
856
856
|
protected override async runRelationFanoutRead(functionPath: string, args: Record<string, unknown>): Promise<unknown> {
|
|
857
857
|
this.ensureMigrated();
|
|
858
858
|
|
|
@@ -1005,11 +1005,11 @@ const LUNORA_FLAG_KEYS: ReadonlyArray<{ key: string; type: "boolean" | "number"
|
|
|
1005
1005
|
const sql: SqlClient = config.sql ? config.sql(env) : sqlStub;
|
|
1006
1006
|
`,configField:`
|
|
1007
1007
|
sql?: (env: Record<string, unknown>) => SqlClient;`,contextField:`
|
|
1008
|
-
sql,`,importLines:['import type { SqlClient } from "@lunora/hyperdrive";'],stub:O("sqlStub: SqlClient",'throw new Error("ctx.sql: provide a \\`sql\\` config thunk that builds a SqlClient from your driver, e.g. \\`sql: (env) => fromPostgresJs(postgres(env.HYPERDRIVE.connectionString))\\`.");',["query"])}:D,
|
|
1008
|
+
sql,`,importLines:['import type { SqlClient } from "@lunora/hyperdrive";'],stub:O("sqlStub: SqlClient",'throw new Error("ctx.sql: provide a \\`sql\\` config thunk that builds a SqlClient from your driver, e.g. \\`sql: (env) => fromPostgresJs(postgres(env.HYPERDRIVE.connectionString))\\`.");',["query"])}:D,Tr=e=>e?{build:`
|
|
1009
1009
|
const browser: Browser = config.browser ? config.browser(env) : browserStub;
|
|
1010
1010
|
`,configField:`
|
|
1011
1011
|
browser?: (env: Record<string, unknown>) => Browser;`,contextField:`
|
|
1012
|
-
browser,`,importLines:['import type { Browser } from "@lunora/browser";'],stub:O("browserStub: Browser","throw new Error(\"ctx.browser: provide a \\`browser\\` config thunk, e.g. \\`browser: (env) => createBrowser({ binding: env.BROWSER, launch })\\` with \\`import { launch } from '@cloudflare/playwright'\\`. Session reuse (connect/sessions) additionally needs those two exports passed the same way.\");",["connect","content","launch","pdf","scrape","screenshot","sessions"])}:D,
|
|
1012
|
+
browser,`,importLines:['import type { Browser } from "@lunora/browser";'],stub:O("browserStub: Browser","throw new Error(\"ctx.browser: provide a \\`browser\\` config thunk, e.g. \\`browser: (env) => createBrowser({ binding: env.BROWSER, launch })\\` with \\`import { launch } from '@cloudflare/playwright'\\`. Session reuse (connect/sessions) additionally needs those two exports passed the same way.\");",["connect","content","launch","pdf","scrape","screenshot","sessions"])}:D,Nr=e=>e?{build:`
|
|
1013
1013
|
const r2sqlEnv = env as Record<string, unknown>;
|
|
1014
1014
|
const r2sqlAccountId = (r2sqlEnv.R2_SQL_ACCOUNT_ID ?? r2sqlEnv.CLOUDFLARE_ACCOUNT_ID) as string | undefined;
|
|
1015
1015
|
const r2sqlToken = r2sqlEnv.R2_SQL_TOKEN as string | undefined;
|
|
@@ -1156,14 +1156,14 @@ ${t}
|
|
|
1156
1156
|
const LUNORA_AGENTS: ReadonlyArray<AgentBindingSpec> = [
|
|
1157
1157
|
${t}
|
|
1158
1158
|
];
|
|
1159
|
-
`}},
|
|
1159
|
+
`}},Rt=(e,t,n,r,o)=>({constant:`
|
|
1160
1160
|
${o}
|
|
1161
1161
|
const ${e}: ${t} = ${JSON.stringify(r,void 0,4)};
|
|
1162
1162
|
`,override:`
|
|
1163
1163
|
protected override ${n}(): ${t} {
|
|
1164
1164
|
return ${e};
|
|
1165
1165
|
}
|
|
1166
|
-
`}),_r=e=>{if(e.length===0)return{constant:"",override:""};const t={workflows:e.map(n=>({binding:n.bindingName,className:n.className,exportName:n.exportName,name:n.name}))};return
|
|
1166
|
+
`}),_r=e=>{if(e.length===0)return{constant:"",override:""};const t={workflows:e.map(n=>({binding:n.bindingName,className:n.className,exportName:n.exportName,name:n.name}))};return Rt("LUNORA_WORKFLOWS_INFO","WorkflowsResult","workflowsMetadata",t,"/** Read-only declared-workflow metadata (discovered from `lunora/workflows.ts`) served via `__lunora_admin__:listWorkflows` for the studio's workflows view. */")},Er=e=>{if(e.length===0)return{constant:"",override:""};const t={queues:e.map(n=>({binding:n.bindingName,...n.tuning.deadLetterQueue===void 0?{}:{deadLetterQueue:n.tuning.deadLetterQueue},exportName:n.exportName,mode:n.mode,name:n.name}))};return Rt("LUNORA_QUEUES_INFO","QueuesResult","queuesMetadata",t,"/** Read-only declared-queue metadata (discovered from `lunora/queues.ts`) served via `__lunora_admin__:listQueues` for the studio's queues view. */")},Dr=e=>e?{imports:['import type { LunoraDatabaseLike as LunoraPaymentDbLike, LunoraPayment, PaymentsFromContextOptions } from "@lunora/payment";','import { paymentsFromContext } from "@lunora/payment";'],build:`
|
|
1167
1167
|
const payments: LunoraPayment = config.payment
|
|
1168
1168
|
? paymentsFromContext({ auth: { userId: userId ?? null }, db: db as unknown as LunoraPaymentDbLike }, config.payment(env))
|
|
1169
1169
|
: paymentStub;
|
|
@@ -1174,10 +1174,10 @@ const ${e}: ${t} = ${JSON.stringify(r,void 0,4)};
|
|
|
1174
1174
|
? lazyX402Pay(config.x402(env), { getSecret: (name: string) => secrets.get(name) })
|
|
1175
1175
|
: x402Stub;
|
|
1176
1176
|
`,configField:`
|
|
1177
|
-
x402?: (env: Record<string, unknown>) => X402PayConfig;`,stub:O("x402Stub: X402Pay",'throw new Error("ctx.x402: no pay rail configured. Pass \\`x402\\` to createShardDO().");',["fetch"],{cast:" as unknown as X402Pay",sync:["fetch"]})}:{build:"",configField:"",imports:[],stub:""},Fr=(e,t,n,r,o)=>["AdvisorProcedure","AdvisoryFinding","DatabaseWriterLike","DataMigrationLike","ExportRow",...r?["FlagsResult"]:[],"ImportShardResult","KeyRange","MaskPoliciesResult","MigrationRunResult",...n?["QueuesResult"]:[],"RunShardApplyCdcArgs","RunShardExportArgs","RunShardImportArgs","RunShardMigrationArgs","RlsPoliciesResult","RunShardRankBeforeArgs","RunShardRankPageArgs","RunShardWriteArgs","RunShardWriteResult","SchedulerLike",...o?["SearchBackfillProgress"]:[],"TransactionHeadroomTracker","SchemaLike","ShardDOState","ShardRankPageResult","SqlExec","StorageRulesResult","StudioFeaturesResult","SystemReaderStorageLike","TelemetrySink",...t?["WorkflowsResult"]:[],...e?["WriteHook"]:[]],co=({advisories:e=[],advisorProcedures:t=[],agents:n=[],containers:r=[],env:o,flagKeys:s=[],hasAccessFacade:i=!1,hasAi:a=!1,hasAnalytics:c=!1,hasBrowser:d=!1,hasFlags:p=!1,hasHyperdrive:m=!1,hasImages:y=!1,hasKv:u=!1,hasNotify:l=!1,hasPayments:f=!1,hasPipelines:k=!1,hasR2sql:R=!1,hasX402:x=!1,maskMetadata:I,mutators:
|
|
1177
|
+
x402?: (env: Record<string, unknown>) => X402PayConfig;`,stub:O("x402Stub: X402Pay",'throw new Error("ctx.x402: no pay rail configured. Pass \\`x402\\` to createShardDO().");',["fetch"],{cast:" as unknown as X402Pay",sync:["fetch"]})}:{build:"",configField:"",imports:[],stub:""},Fr=(e,t,n,r,o)=>["AdvisorProcedure","AdvisoryFinding","DatabaseWriterLike","DataMigrationLike","ExportRow",...r?["FlagsResult"]:[],"ImportShardResult","KeyRange","MaskPoliciesResult","MigrationRunResult","QueryReadScope",...n?["QueuesResult"]:[],"RunShardApplyCdcArgs","RunShardExportArgs","RunShardImportArgs","RunShardMigrationArgs","RlsPoliciesResult","RunShardRankBeforeArgs","RunShardRankPageArgs","RunShardWriteArgs","RunShardWriteResult","SchedulerLike",...o?["SearchBackfillProgress"]:[],"TransactionHeadroomTracker","SchemaLike","ShardDOState","ShardRankPageResult","SqlExec","StorageRulesResult","StudioFeaturesResult","SystemReaderStorageLike","TelemetrySink",...t?["WorkflowsResult"]:[],...e?["WriteHook"]:[]],co=({advisories:e=[],advisorProcedures:t=[],agents:n=[],containers:r=[],env:o,flagKeys:s=[],hasAccessFacade:i=!1,hasAi:a=!1,hasAnalytics:c=!1,hasBrowser:d=!1,hasFlags:p=!1,hasHyperdrive:m=!1,hasImages:y=!1,hasKv:u=!1,hasNotify:l=!1,hasPayments:f=!1,hasPipelines:k=!1,hasR2sql:R=!1,hasX402:x=!1,maskMetadata:I,mutators:N=[],queues:_=[],rlsMetadata:v,schema:w,schemaSnapshot:F,shapes:se=[],storageRules:M,studioFeatures:ie,useUmbrella:le=!1,workflows:W=[]})=>{const C=oe(le),Z=N.length>0,L=se.length>0,S=F===void 0?"":kn(F),B=F===void 0?"":`
|
|
1178
1178
|
/** Structural schema snapshot + its content hash, recorded in the shard's \`__lunora_schema_history\` ledger on cold start so the studio can show a schema-version timeline and diff any two versions. */
|
|
1179
1179
|
const LUNORA_SCHEMA_SNAPSHOT: { hash: string; json: string } = { hash: ${JSON.stringify(xn(F))}, json: ${JSON.stringify(S)} };
|
|
1180
|
-
`,ce=F===void 0?"":", schemaSnapshot: LUNORA_SCHEMA_SNAPSHOT",{build:de,configField:Ae,contextField:Oe,stub:Ie}=fr(a),ue=xr(i),V=br(u),P=yr(p,C.flags),H=vr(s,p,C.flags),
|
|
1180
|
+
`,ce=F===void 0?"":", schemaSnapshot: LUNORA_SCHEMA_SNAPSHOT",{build:de,configField:Ae,contextField:Oe,stub:Ie}=fr(a),ue=xr(i),V=br(u),P=yr(p,C.flags),H=vr(s,p,C.flags),ee=wr(l),pe=kr(o),G=Rr(c),U=Sr(y),K=$r(m),Q=Tr(d),q=Nr(R),te=Ar(k),{build:Ce,contextField:ve,importLines:Re,specs:he}=Cr(_),{build:Le,contextField:_e,importLines:Se,specs:me}=Or(r,w.jurisdiction),{build:Ee,contextField:De,importLines:$e,specs:Pe}=Ir(W),{build:Fe,contextField:ge,importLines:fe,specs:Me}=Lr(n),{build:Be,configField:qe,contextField:be,imports:ye,stub:Xe}=Dr(f),{build:h,configField:je,imports:Te,stub:ne}=Pr(x),$t=e,Tt=t,Nt=v??{policies:[],roles:[]},At=I??{columns:[]},Ot=M??{rules:[]},It=ie??{analytics:!1,auth:!1,containers:!1,flags:!1,kv:!1,mail:!1,notifications:!1,payments:!1,queues:!1,scheduler:!1,storage:!1,vectors:!1,workflows:!1},{constant:Ct,override:Lt}=_r(W),{constant:_t,override:Et}=Er(_),J=w.vectorIndexes.length>0,Ze=new Set(w.tables.filter(b=>typeof b.shardMode=="object"&&b.shardMode.kind==="shardBy").map(b=>b.name)),Ne=w.vectorIndexes.some(b=>Ze.has(b.table)),we=w.tables.some(b=>b.shardMode==="global"),We=w.tables.some(b=>b.shardMode==="global"&&b.globalBackend==="hyperdrive"),et=w.tables.some(b=>b.shardMode==="global"&&b.globalBackend!=="hyperdrive"),re=w.tables.some(b=>b.externalSource!==void 0),tt=w.tables.some(b=>b.memory===!0),Ue=w.tables.some(b=>b.shardMode!=="global"&&b.searchIndexes.length>0);if(et&&We)throw new X("INTERNAL",'lunora codegen: mixing `.global()` (D1) and `.global({ backend: "hyperdrive" })` tables in one app is not supported yet — use a single global backend.');const Ke=w.tables.length>0,Dt=lr(w),Pt=dr(w),Ft=mr(w),Mt=cr(w),nt=ur(w),rt=Fr(J,W.length>0,_.length>0,p,Ue);L&&rt.push("WhereInput");const ot=gr(we),Bt=L?`
|
|
1181
1181
|
protected override resolveShape(name: string, args: Record<string, unknown>, identity?: { identity?: Record<string, unknown>; userId?: string }): { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string } | undefined {
|
|
1182
1182
|
const shape = LUNORA_SHAPES[name];
|
|
1183
1183
|
|
|
@@ -1230,25 +1230,25 @@ const LUNORA_SCHEMA_SNAPSHOT: { hash: string; json: string } = { hash: ${JSON.st
|
|
|
1230
1230
|
|
|
1231
1231
|
return { columns: shape.columns, effectiveWhere, global: isGlobal, table: shape.table };
|
|
1232
1232
|
}
|
|
1233
|
-
`:"",qt=
|
|
1233
|
+
`:"",qt=Z?`
|
|
1234
1234
|
protected override isCustomMutator(functionPath: string): boolean {
|
|
1235
1235
|
return LUNORA_MUTATOR_PATHS.has(functionPath);
|
|
1236
1236
|
}
|
|
1237
|
-
`:"",
|
|
1238
|
-
vectors?: (env: Record<string, unknown>) => Record<string, VectorizeIndexLike>;`:"",
|
|
1239
|
-
d1?: (env: Record<string, unknown>, request?: { bookmark?: string; cdc?: boolean; cdcRetentionMs?: number; identity?: Record<string, unknown>; onBookmark?: (bookmark: string | undefined) => void; userId?: string }) => DatabaseWriterLike | undefined;`:"",
|
|
1240
|
-
hyperdriveGlobal?: (env: Record<string, unknown>, request?: { cdc?: boolean; cdcRetentionMs?: number; identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;`:"",
|
|
1241
|
-
sourceClient?: (env: Record<string, unknown>, binding: string) => { query: <Row = Record<string, unknown>>(text: string, params?: readonly unknown[]) => Promise<Row[]> } | undefined;`:"",
|
|
1237
|
+
`:"",jt=L?"\n/** Per-table RLS read policies (hoisted from `.use(rls(...))` chains) the shape resolver AND-merges into each `defineShape` predicate so partial replication honours read policies. */\nconst LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCTIONS));\n":"",Wt=L?"assertShapeShardable, ":"",ke=[`import type { ${rt.join(", ")} } from "${C.do}";`,`import { applyCdcChanges, ${Ue?"backfillSearchIndexes, ":""}buildReprojectionMigration, ${tt?"clearMemoryTables, ":""}${Wt}createReadFootprint, createShardCtxDb, exportShardRows, importShardRows, ${re?"isSourceDue, pullExternalSourceIncrementalTick, pullExternalSourceTick, ":""}markUnvouchableReads, ${Ne?"ROOT_SHARD_NAME, ":""}runDataMigration, runShardMigrations, ${ot.importFragment}ShardDO as ShardDOBase } from "${C.do}";`,...re?[`import type { ExternalSourceLike, SourceClientLike, TraceRefLike } from "${C.do}";`]:[],L?`import { asBucketStorage, buildRlsReadRegistry, composeShapeReadWhere, createSecrets, flushDeferredDeletes, LunoraError, withDeferredDeletes } from "${C.server}";`:`import { asBucketStorage, createSecrets, flushDeferredDeletes, LunoraError, withDeferredDeletes } from "${C.server}";`];Ke&&ke.push(`import { bindOrm, bindTableFacade } from "${C.server}";`),J&&ke.push('import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/bindings/vectors";','import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/bindings/vectors";'),a&&ke.push('import type { AiBindingLike, LunoraAi } from "@lunora/ai";','import { createAi } from "@lunora/ai";'),ke.push(...ue.importLines,...V.importLines,...P.importLines,...ee.importLines,...pe.importLines,...G.importLines,...U.importLines,...K.importLines,...Q.importLines,...q.importLines,...te.importLines,...Se,...$e,...Re,...fe,...ye,...Te,"",'import schema from "../schema.js";',`import { ${["LUNORA_FUNCTIONS","LUNORA_LIFECYCLE_HOOKS","LUNORA_MIGRATIONS",...Z?["LUNORA_MUTATOR_PATHS"]:[],...L?["LUNORA_SHAPES"]:[]].join(", ")} } from "./functions.js";`);const Ut=J?`
|
|
1238
|
+
vectors?: (env: Record<string, unknown>) => Record<string, VectorizeIndexLike>;`:"",Kt=et?`
|
|
1239
|
+
d1?: (env: Record<string, unknown>, request?: { bookmark?: string; cdc?: boolean; cdcRetentionMs?: number; identity?: Record<string, unknown>; onBookmark?: (bookmark: string | undefined) => void; userId?: string }) => DatabaseWriterLike | undefined;`:"",Qt=We?`
|
|
1240
|
+
hyperdriveGlobal?: (env: Record<string, unknown>, request?: { cdc?: boolean; cdcRetentionMs?: number; identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;`:"",Vt=re?`
|
|
1241
|
+
sourceClient?: (env: Record<string, unknown>, binding: string) => { query: <Row = Record<string, unknown>>(text: string, params?: readonly unknown[]) => Promise<Row[]> } | undefined;`:"",Ht='throw new Error("ctx.db.<globalTable>: no global backend configured. Pass `d1` or `hyperdriveGlobal` to createShardDO().");',Gt='throw new Error("ctx.vectors: no vectors configured. Pass `vectors` to createShardDO().");',Jt='throw new Error("ctx.scheduler: no scheduler configured. Pass `scheduler` to createShardDO().");',zt='throw new Error("ctx.storage: no storage configured. Pass `storage` to createShardDO().");',Yt=we?O("globalDbStub: DatabaseWriterLike",Ht,["aggregate","count","delete","findFirst","findFirstOrThrow","findMany","get","groupBy","insert","normalizeId","patch","query","rank","rankPage","replace"],{sync:["normalizeId","query"]}):"",Xt=J?O("vectorsStub: VectorSearchLike",Gt,["deleteByIds","getByIds","query","upsert","upsertNow"]):"",Zt=Ne?`
|
|
1242
1242
|
const vectorShardKey = this.currentShardKey();
|
|
1243
|
-
`:"",
|
|
1243
|
+
`:"",en=Ne?"namespace: vectorShardKey === ROOT_SHARD_NAME ? undefined : vectorShardKey, ":"",tn=Ne?`, { namespace: vectorShardKey === ROOT_SHARD_NAME ? undefined : vectorShardKey, shardedIndexNames: [${w.vectorIndexes.filter(b=>Ze.has(b.table)).map(b=>JSON.stringify(b.name)).join(", ")}] }`:"",nn=J?`
|
|
1244
1244
|
let vectors: VectorSearchLike;
|
|
1245
1245
|
let onWrite: WriteHook | undefined;
|
|
1246
1246
|
|
|
1247
1247
|
if (config.vectors) {
|
|
1248
1248
|
const lunora = createVectors({ indexes: config.vectors(env) });
|
|
1249
|
-
${
|
|
1250
|
-
vectors = createContextVectors(lunora${
|
|
1251
|
-
onWrite = createVectorSyncHook({ ${
|
|
1249
|
+
${Zt}
|
|
1250
|
+
vectors = createContextVectors(lunora${tn});
|
|
1251
|
+
onWrite = createVectorSyncHook({ ${en}schema: schema as unknown as VectorSchemaLike, vectors });
|
|
1252
1252
|
} else {
|
|
1253
1253
|
vectors = vectorsStub;
|
|
1254
1254
|
onWrite = undefined;
|
|
@@ -1260,7 +1260,8 @@ ${en}
|
|
|
1260
1260
|
// \`__cdc_log\`. Wrapped AFTER \`onWrite\` is built so the write-through
|
|
1261
1261
|
// vector-sync hook keeps calling the bare facade.
|
|
1262
1262
|
vectors = markUnvouchableReads(vectors, options.onRead, ["getByIds", "query"]);
|
|
1263
|
-
`:"",
|
|
1263
|
+
`:"",rn=`{
|
|
1264
|
+
...this.ctxDbTuning(),
|
|
1264
1265
|
auth: { identity: identity ?? null, userId: userId ?? null },
|
|
1265
1266
|
broadcast: (delta) => {
|
|
1266
1267
|
this.recordChangedTable(delta.table, delta.indexKeys);
|
|
@@ -1281,17 +1282,30 @@ ${en}
|
|
|
1281
1282
|
// each of its writes allocates its own.
|
|
1282
1283
|
inTransaction: () => this.isInTransaction(),
|
|
1283
1284
|
onIndexUse: this.getCtxDbIndexUseHook(),
|
|
1284
|
-
|
|
1285
|
-
|
|
1285
|
+
// Bound to THIS dispatch's reactive-cache read scope (\`handleRpc\`
|
|
1286
|
+
// threads it in), so two concurrent queries never stamp each
|
|
1287
|
+
// other's dep sets. \`executeSubscription\` overrides both with its
|
|
1288
|
+
// own \`ReadFootprint\` pair instead, and a dispatch with neither
|
|
1289
|
+
// gets an unbound hook that stamps no deps.
|
|
1290
|
+
onRead: options.onRead ?? this.getCtxDbReadHook(options.scope),
|
|
1291
|
+
// Left \`undefined\` when there is no scope so \`createShardCtxDb\`
|
|
1292
|
+
// keeps its own "degrade a provable slice to a whole-table dep"
|
|
1293
|
+
// fallback rather than reporting into a no-op.
|
|
1294
|
+
onReadRange: options.onReadRange ?? (options.scope === undefined ? undefined : this.getCtxDbReadRangeHook(options.scope)),${J?`
|
|
1286
1295
|
onWrite,`:""}
|
|
1287
1296
|
scheduler,
|
|
1288
1297
|
schema: schema as unknown as SchemaLike,
|
|
1289
1298
|
sql: this.sql as SqlExec,
|
|
1290
1299
|
storage,${we?`
|
|
1291
1300
|
globalDb,`:""}
|
|
1292
|
-
}`,
|
|
1301
|
+
}`,z=` const env = (this.env ?? {}) as Record<string, unknown>;
|
|
1293
1302
|
const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
|
|
1294
1303
|
const writer = createShardCtxDb({
|
|
1304
|
+
// Admin and maintenance writes go through the SAME reactive-cache hooks as
|
|
1305
|
+
// a user mutation. Without this, a studio row edit, a TTL sweep, an admin
|
|
1306
|
+
// import, a CDC apply or a data-migration backfill writes without
|
|
1307
|
+
// invalidating, and the next query answers from the pre-write snapshot.
|
|
1308
|
+
...this.ctxDbTuning(),
|
|
1295
1309
|
broadcast: (delta) => {
|
|
1296
1310
|
this.recordChangedTable(delta.table, delta.indexKeys);
|
|
1297
1311
|
},
|
|
@@ -1301,11 +1315,11 @@ ${en}
|
|
|
1301
1315
|
scheduler,
|
|
1302
1316
|
schema: schema as unknown as SchemaLike,
|
|
1303
1317
|
sql: this.sql as SqlExec,
|
|
1304
|
-
});`,
|
|
1305
|
-
vectors,`:"",
|
|
1306
|
-
orm: bindOrm(facade),`:"",Qe=We?"config.hyperdriveGlobal":"config.d1",
|
|
1318
|
+
});`,on=J?`
|
|
1319
|
+
vectors,`:"",an=Ke?`
|
|
1320
|
+
orm: bindOrm(facade),`:"",Qe=We?"config.hyperdriveGlobal":"config.d1",sn=we?` const globalRequest = { ...this.globalCdcOptions(config.cdc ?? false), bookmark: this.getInboundBookmark(), identity, onBookmark: (bookmarkValue: string | undefined) => { this.setOutboundBookmark(bookmarkValue); }, userId };
|
|
1307
1321
|
const globalDb: DatabaseWriterLike = ${Qe}?.(env, globalRequest) ?? globalDbStub;
|
|
1308
|
-
`:"",
|
|
1322
|
+
`:"",ln=L&&we?`
|
|
1309
1323
|
protected override async readGlobalShapeRows(resolved: { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string }, identity?: { identity?: Record<string, unknown>; userId?: string }): Promise<Array<{ doc: Record<string, unknown>; id: string }>> {
|
|
1310
1324
|
const env = this.env as Record<string, unknown>;
|
|
1311
1325
|
// A shape read performs no writes, so the widened request only needs
|
|
@@ -1350,17 +1364,17 @@ ${en}
|
|
|
1350
1364
|
|
|
1351
1365
|
return globalDb.cdcChangedTables?.(sinceSeq, { cursorOnly });
|
|
1352
1366
|
}
|
|
1353
|
-
`:"",
|
|
1367
|
+
`:"",cn=re?`
|
|
1354
1368
|
const sourceClientCache = new WeakMap<object, Map<string, SourceClientLike>>();
|
|
1355
1369
|
const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
|
|
1356
|
-
`:"",
|
|
1370
|
+
`:"",dn=`
|
|
1357
1371
|
protected override async runShardInit(): Promise<void> {
|
|
1358
1372
|
this.ensureMigrated();
|
|
1359
1373
|
${tt?` clearMemoryTables(this.sql as SqlExec, schema as unknown as SchemaLike);
|
|
1360
1374
|
`:""}
|
|
1361
1375
|
await this.dispatchShardInit();
|
|
1362
1376
|
}
|
|
1363
|
-
`,
|
|
1377
|
+
`,un=re?`
|
|
1364
1378
|
protected override async pollExternalSources(trace?: TraceRefLike): Promise<number | undefined> {
|
|
1365
1379
|
const env = (this.env ?? {}) as Record<string, unknown>;
|
|
1366
1380
|
const sourced = Object.entries((schema as unknown as SchemaLike).tables)
|
|
@@ -1425,6 +1439,11 @@ ${tt?` clearMemoryTables(this.sql as SqlExec, schema as unknown as Sc
|
|
|
1425
1439
|
// sibling table needs, and a limit hit here must not block
|
|
1426
1440
|
// that sibling from getting its own full budget this tick.
|
|
1427
1441
|
const writer = createShardCtxDb({
|
|
1442
|
+
// Admin and maintenance writes go through the SAME reactive-cache hooks as
|
|
1443
|
+
// a user mutation. Without this, a studio row edit, a TTL sweep, an admin
|
|
1444
|
+
// import, a CDC apply or a data-migration backfill writes without
|
|
1445
|
+
// invalidating, and the next query answers from the pre-write snapshot.
|
|
1446
|
+
...this.ctxDbTuning(),
|
|
1428
1447
|
broadcast: (delta) => {
|
|
1429
1448
|
this.recordChangedTable(delta.table, delta.indexKeys);
|
|
1430
1449
|
},
|
|
@@ -1495,10 +1514,20 @@ ${tt?` clearMemoryTables(this.sql as SqlExec, schema as unknown as Sc
|
|
|
1495
1514
|
|
|
1496
1515
|
return nextDueAt;
|
|
1497
1516
|
}
|
|
1498
|
-
`:"",
|
|
1517
|
+
`:"",pn=nt.length>0,hn=`
|
|
1499
1518
|
public constructor(state: ShardDOState, env: unknown) {
|
|
1500
|
-
super(state, env
|
|
1501
|
-
|
|
1519
|
+
super(state, env, {
|
|
1520
|
+
// Every writer this file builds spreads the ctxDbTuning() slice —
|
|
1521
|
+
// the user-facing ctx and all three admin/maintenance writers — so
|
|
1522
|
+
// the base class can drop its coarse invalidation backstop. Only the
|
|
1523
|
+
// emitter can know that; a hand-written subclass leaves this unset
|
|
1524
|
+
// and keeps the backstop.
|
|
1525
|
+
ctxDbCacheWired: true,
|
|
1526
|
+
...(config.maxRelationKeys === undefined ? {} : { maxRelationKeys: config.maxRelationKeys }),
|
|
1527
|
+
...(config.reactiveCache ? { reactiveCache: config.reactiveCache === true ? {} : config.reactiveCache } : {}),
|
|
1528
|
+
...(config.relationExistsPushDown === undefined ? {} : { relationExistsPushDown: config.relationExistsPushDown }),
|
|
1529
|
+
});
|
|
1530
|
+
${re?`
|
|
1502
1531
|
const autoSourced = Object.values((schema as unknown as SchemaLike).tables).some((definition) => {
|
|
1503
1532
|
const source = (definition as { externalSource?: ExternalSourceLike }).externalSource;
|
|
1504
1533
|
|
|
@@ -1508,26 +1537,30 @@ ${z?`
|
|
|
1508
1537
|
if (autoSourced) {
|
|
1509
1538
|
void this.scheduleSourcePoll();
|
|
1510
1539
|
}
|
|
1511
|
-
`:""}${
|
|
1540
|
+
`:""}${pn?`
|
|
1512
1541
|
if (LUNORA_TTL_SWEEPS.length > 0) {
|
|
1513
1542
|
void this.scheduleTtlSweep();
|
|
1514
1543
|
}
|
|
1515
1544
|
`:""} }
|
|
1516
|
-
|
|
1545
|
+
|
|
1546
|
+
protected override isQueryFunction(functionPath: string): boolean {
|
|
1547
|
+
return LUNORA_FUNCTIONS[functionPath]?.kind === "query";
|
|
1548
|
+
}
|
|
1549
|
+
`,mn=Ke?`
|
|
1517
1550
|
const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
|
|
1518
1551
|
${w.tables.map(b=>` facade[${JSON.stringify(b.name)}] = bindTableFacade(db, ${JSON.stringify(b.name)});`).join(`
|
|
1519
1552
|
`)}
|
|
1520
1553
|
`:"",gn=`${ue.build}${V.build}${P.build}${G.build}${pe.build}
|
|
1521
1554
|
const secrets = createSecrets(env);
|
|
1522
|
-
`,fn=`${ue.contextField}${V.contextField}${P.contextField}${
|
|
1523
|
-
secrets,`,bn=
|
|
1555
|
+
`,fn=`${ue.contextField}${V.contextField}${P.contextField}${ee.contextField}${G.contextField}${pe.contextField}
|
|
1556
|
+
secrets,`,bn=ee.build,at=y||m||d||R||k||x,yn=at?`
|
|
1524
1557
|
// ActionCtx-only helpers (external, non-deterministic I/O): constructed
|
|
1525
1558
|
// and attached only for an \`action\` so query/mutation ctx never carry them.
|
|
1526
1559
|
if (isAction) {
|
|
1527
|
-
${U.build}${K.build}${Q.build}${
|
|
1560
|
+
${U.build}${K.build}${Q.build}${q.build}${te.build}${h}${[...y?[" ctx.images = images;"]:[],...m?[" ctx.sql = sql;"]:[],...d?[" ctx.browser = browser;"]:[],...R?[" ctx.r2sql = r2sql;"]:[],...k?[" ctx.pipelines = pipelines;"]:[],...x?[" ctx.x402 = x402;"]:[]].join(`
|
|
1528
1561
|
`)}
|
|
1529
1562
|
}
|
|
1530
|
-
`:"",wn=
|
|
1563
|
+
`:"",wn=at?` const isAction = LUNORA_FUNCTIONS[options.functionPath ?? ""]?.kind === "action";
|
|
1531
1564
|
`:"";return`${$}${ke.join(`
|
|
1532
1565
|
`)}
|
|
1533
1566
|
|
|
@@ -1538,50 +1571,56 @@ interface FunctionReference {
|
|
|
1538
1571
|
}
|
|
1539
1572
|
|
|
1540
1573
|
/** Foreign-key columns per table (\`v.id("target")\` fields) for the data browser. */
|
|
1541
|
-
const LUNORA_TABLE_REFS: Record<string, Record<string, string>> = ${JSON.stringify(
|
|
1574
|
+
const LUNORA_TABLE_REFS: Record<string, Record<string, string>> = ${JSON.stringify(Dt,void 0,4)};
|
|
1542
1575
|
|
|
1543
1576
|
/** Declared indexes per table (secondary, search, geo, rank, vector) for the schema viewer. */
|
|
1544
|
-
const LUNORA_TABLE_INDEXES: Record<string, Array<{ fields: string[]; name: string; type: "geo" | "index" | "rank" | "search" | "vector"; unique?: boolean }>> = ${JSON.stringify(
|
|
1577
|
+
const LUNORA_TABLE_INDEXES: Record<string, Array<{ fields: string[]; name: string; type: "geo" | "index" | "rank" | "search" | "vector"; unique?: boolean }>> = ${JSON.stringify(Pt,void 0,4)};
|
|
1545
1578
|
|
|
1546
1579
|
/** Columns per table (typed, with PK/FK markers) for the studio's schema diagram, served via \`__lunora_admin__:describeTable\`. */
|
|
1547
1580
|
const LUNORA_TABLE_COLUMNS: Record<
|
|
1548
1581
|
string,
|
|
1549
1582
|
Array<{ bucket?: string; enumValues?: string[]; isStorage?: boolean; name: string; nullable?: boolean; optional: boolean; pk?: boolean; ref?: string; type: string }>
|
|
1550
|
-
> = ${JSON.stringify(
|
|
1583
|
+
> = ${JSON.stringify(Ft,void 0,4)};
|
|
1551
1584
|
|
|
1552
1585
|
/** Storage-key columns per table (\`v.storage(...)\` fields) for the file browser's records↔files join. */
|
|
1553
|
-
const LUNORA_STORAGE_COLUMNS: Record<string, string[]> = ${JSON.stringify(
|
|
1586
|
+
const LUNORA_STORAGE_COLUMNS: Record<string, string[]> = ${JSON.stringify(Mt,void 0,4)};
|
|
1554
1587
|
|
|
1555
1588
|
/** Declarative TTL policies (\`.ttl(field, { after? })\`) the DO alarm sweep auto-expires rows for. */
|
|
1556
1589
|
const LUNORA_TTL_SWEEPS: Array<{ after?: number; field: string; softDeleteField?: string; table: string }> = ${JSON.stringify(nt,void 0,4)};
|
|
1557
1590
|
|
|
1558
1591
|
/** Static schema advisories (computed by @lunora/advisor at codegen time) served via \`__lunora_admin__:getAdvisories\`. */
|
|
1559
|
-
const LUNORA_ADVISORIES: AdvisoryFinding[] = ${JSON.stringify(
|
|
1592
|
+
const LUNORA_ADVISORIES: AdvisoryFinding[] = ${JSON.stringify($t,void 0,4)};
|
|
1560
1593
|
|
|
1561
1594
|
/** Every declared procedure (discovered by @lunora/codegen) served via \`__lunora_admin__:getAdvisorProcedures\` — the health map's denominator. */
|
|
1562
1595
|
const LUNORA_ADVISOR_PROCEDURES: AdvisorProcedure[] = ${JSON.stringify(Tt,void 0,4)};
|
|
1563
1596
|
|
|
1564
1597
|
/** Read-only RLS metadata (policies + roles discovered from \`.use(rls(...))\` chains) served via \`__lunora_admin__:rlsPolicies\` for the studio's RLS inspector. */
|
|
1565
|
-
const LUNORA_RLS_METADATA: RlsPoliciesResult = ${JSON.stringify(
|
|
1598
|
+
const LUNORA_RLS_METADATA: RlsPoliciesResult = ${JSON.stringify(Nt,void 0,4)};
|
|
1566
1599
|
|
|
1567
1600
|
/** Read-only masking metadata (table + column + strategy discovered from \`.use(mask(...))\` chains) served via \`__lunora_admin__:maskPolicies\` for the studio's data-browser mask preview. */
|
|
1568
|
-
const LUNORA_MASK_METADATA: MaskPoliciesResult = ${JSON.stringify(
|
|
1601
|
+
const LUNORA_MASK_METADATA: MaskPoliciesResult = ${JSON.stringify(At,void 0,4)};
|
|
1569
1602
|
|
|
1570
1603
|
/** Read-only storage access-rule metadata (discovered from \`.use(storageRules(...))\` chains) served via \`__lunora_admin__:storageRules\` for the studio's access-rules view. */
|
|
1571
|
-
const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(
|
|
1604
|
+
const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(Ot,void 0,4)};
|
|
1572
1605
|
|
|
1573
1606
|
/** Which optional package-backed features this app wires up (discovered from imports / \`ctx.*\` reads / schema signals) served via \`__lunora_admin__:studioFeatures\` so the studio hides nav pages whose package isn't enabled. */
|
|
1574
|
-
const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(
|
|
1575
|
-
${B}${H.constant}${
|
|
1607
|
+
const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(It,void 0,4)};
|
|
1608
|
+
${B}${H.constant}${jt}${Ct}${_t}${me}${Pe}${he}${Me}
|
|
1576
1609
|
export interface ShardDOConfig {
|
|
1577
1610
|
/** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
|
|
1578
1611
|
cdc?: boolean;
|
|
1612
|
+
/** Ceiling on the join keys one relation-crossing \`where\` predicate may pre-resolve via semijoin before failing closed. Omit for the engine default. */
|
|
1613
|
+
maxRelationKeys?: number;
|
|
1614
|
+
/** Enable the per-shard reactive query cache: \`true\` for the defaults, or an options object to tune the caps. Query results are memoized by \`(functionPath, args, identity)\` and invalidated by the ctx-db write hooks before the subscription broadcast, so subscribers never observe a pre-write value. Omitted (or \`false\`) keeps every dispatch re-running its handler. */
|
|
1615
|
+
reactiveCache?: boolean | { maxBytes?: number; maxEntries?: number };
|
|
1616
|
+
/** Resolution policy for a relation-crossing \`where\` whose child is co-located in this shard: \`"auto"\` (cost-based, the engine default), \`"always"\` (inline correlated EXISTS) or \`"never"\` (universal semijoin). All three return identical rows. */
|
|
1617
|
+
relationExistsPushDown?: "always" | "auto" | "never";
|
|
1579
1618
|
/** Optional telemetry sink. When supplied, each \`ctx.log.*\` call is forwarded to \`sink.onLog\`. Pass the SAME sink you give \`createWorker({ observability })\` (which drives \`onRpc\`) to route both RPC and log events. */
|
|
1580
1619
|
observability?: (env: Record<string, unknown>) => TelemetrySink | undefined;
|
|
1581
1620
|
scheduler?: (env: Record<string, unknown>) => unknown;
|
|
1582
|
-
storage?: (env: Record<string, unknown>) => unknown;${
|
|
1621
|
+
storage?: (env: Record<string, unknown>) => unknown;${Ut}${Ae}${V.configField}${P.configField}${G.configField}${U.configField}${K.configField}${Q.configField}${q.configField}${te.configField}${qe}${je}${Kt}${Qt}${Vt}
|
|
1583
1622
|
}
|
|
1584
|
-
${O("schedulerStub",
|
|
1623
|
+
${O("schedulerStub",Jt,["cancel","runAfter","runAt"])}${O("storageStub",zt,["delete","download","getMetadata","getSignedUrl","getUrl","head","list","upload"],{sync:["getUrl"]})}${Yt}${cn}${Xt}${Ie}${V.stub}${P.stub}${G.stub}${U.stub}${K.stub}${Q.stub}${q.stub}${te.stub}${Xe}${ne}
|
|
1585
1624
|
// Bound in-process \`ctx.run*\` composition depth so a self- or cyclically-
|
|
1586
1625
|
// referencing call fails loudly with a clear error instead of overflowing the
|
|
1587
1626
|
// stack. Tracked across the awaited handler chain (one DO invocation is
|
|
@@ -1621,7 +1660,7 @@ export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOSt
|
|
|
1621
1660
|
class extends ShardDOBase {${hn}
|
|
1622
1661
|
private migrated = false;
|
|
1623
1662
|
|
|
1624
|
-
public override async handleRpc(functionPath: string, args: Record<string, unknown>, headroom?: TransactionHeadroomTracker): Promise<unknown> {
|
|
1663
|
+
public override async handleRpc(functionPath: string, args: Record<string, unknown>, headroom?: TransactionHeadroomTracker, scope?: QueryReadScope): Promise<unknown> {
|
|
1625
1664
|
const registered = LUNORA_FUNCTIONS[functionPath];
|
|
1626
1665
|
|
|
1627
1666
|
// Internal functions are reachable server-side only: via \`ctx.run*\`
|
|
@@ -1640,7 +1679,15 @@ export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOSt
|
|
|
1640
1679
|
// The main \`/rpc\` path always supplies one; \`dispatchLifecycle\` /
|
|
1641
1680
|
// \`handleRunAs\` don't mint their own and omit it, so they keep the
|
|
1642
1681
|
// prior fallback behavior unchanged.
|
|
1643
|
-
|
|
1682
|
+
//
|
|
1683
|
+
// \`scope\` is the reactive cache's per-dispatch read capture, threaded
|
|
1684
|
+
// the same way and for the same reason: the ctx-db read hooks resolve
|
|
1685
|
+
// their tracker at READ time, so binding them to the scope THIS
|
|
1686
|
+
// dispatch was handed is what keeps a concurrent query's reads out of
|
|
1687
|
+
// our dep set — and ours out of theirs. A dispatch with no scope (a
|
|
1688
|
+
// mutation, an action, a cache-less shard) builds unbound hooks that
|
|
1689
|
+
// stamp no deps.
|
|
1690
|
+
const ctx = this.buildCtx({ functionPath, headroom, scope, trusted: registered.lifecycle === "init" });
|
|
1644
1691
|
|
|
1645
1692
|
// A mutation's writes must commit all-or-nothing: wrap its dispatch in
|
|
1646
1693
|
// the DO's BEGIN/COMMIT span so any throw (a validator, an RLS denial,
|
|
@@ -1734,11 +1781,11 @@ ${ot.override}
|
|
|
1734
1781
|
iterator: (signal) => (registered.handler as (context: unknown, args: Record<string, unknown>, signal: AbortSignal) => AsyncIterable<unknown>)(this.buildCtx({ functionPath }), args, signal),
|
|
1735
1782
|
};
|
|
1736
1783
|
}
|
|
1737
|
-
${qt}${
|
|
1784
|
+
${qt}${Bt}${ln}${un}
|
|
1738
1785
|
protected override lifecycleHookPaths(event: "connect" | "disconnect" | "init" | "reactor"): readonly string[] {
|
|
1739
1786
|
return LUNORA_LIFECYCLE_HOOKS[event];
|
|
1740
1787
|
}
|
|
1741
|
-
${
|
|
1788
|
+
${dn}
|
|
1742
1789
|
// One \`onQueryChange\` dispatch. Mirrors \`executeSubscription\` — the
|
|
1743
1790
|
// socket-terminated half of the same reactivity — and differs only in who
|
|
1744
1791
|
// consumes the result: the base's \`dispatchReactors\` stores the digest as
|
|
@@ -1820,7 +1867,7 @@ ${un}
|
|
|
1820
1867
|
protected override studioFeatures(): StudioFeaturesResult {
|
|
1821
1868
|
return LUNORA_STUDIO_FEATURES;
|
|
1822
1869
|
}
|
|
1823
|
-
${H.evaluateOverride}${H.subscriptionOverride}${
|
|
1870
|
+
${H.evaluateOverride}${H.subscriptionOverride}${Lt}${Et}
|
|
1824
1871
|
protected override advisories(): AdvisoryFinding[] {
|
|
1825
1872
|
return LUNORA_ADVISORIES;
|
|
1826
1873
|
}
|
|
@@ -1844,7 +1891,7 @@ ${H.evaluateOverride}${H.subscriptionOverride}${_t}${Dt}
|
|
|
1844
1891
|
throw new LunoraError("MIGRATION_NOT_FOUND", \`data migration "\${args.id}" is not registered\`, { status: 404 });
|
|
1845
1892
|
}
|
|
1846
1893
|
|
|
1847
|
-
${
|
|
1894
|
+
${z}
|
|
1848
1895
|
|
|
1849
1896
|
return runDataMigration({
|
|
1850
1897
|
batchSize: args.batchSize,
|
|
@@ -1877,7 +1924,7 @@ ${Y}
|
|
|
1877
1924
|
|
|
1878
1925
|
this.ensureMigrated();
|
|
1879
1926
|
|
|
1880
|
-
${
|
|
1927
|
+
${z}
|
|
1881
1928
|
|
|
1882
1929
|
if (args.op === "insert") {
|
|
1883
1930
|
const id = await writer.insert(args.table, args.doc ?? {});
|
|
@@ -1921,7 +1968,7 @@ ${Y}
|
|
|
1921
1968
|
protected override async runShardExport(args: RunShardExportArgs): Promise<ExportRow[]> {
|
|
1922
1969
|
this.ensureMigrated();
|
|
1923
1970
|
|
|
1924
|
-
${
|
|
1971
|
+
${z}
|
|
1925
1972
|
|
|
1926
1973
|
const rows: ExportRow[] = [];
|
|
1927
1974
|
|
|
@@ -1938,7 +1985,7 @@ ${Y}
|
|
|
1938
1985
|
protected override async runShardImport(args: RunShardImportArgs): Promise<ImportShardResult> {
|
|
1939
1986
|
this.ensureMigrated();
|
|
1940
1987
|
|
|
1941
|
-
${
|
|
1988
|
+
${z}
|
|
1942
1989
|
|
|
1943
1990
|
// \`importShardRows\` inserts with \`allowExplicitId\`, so a source
|
|
1944
1991
|
// database's \`_id\`s carry across verbatim and every foreign key
|
|
@@ -1964,6 +2011,11 @@ ${Y}
|
|
|
1964
2011
|
const env = (this.env ?? {}) as Record<string, unknown>;
|
|
1965
2012
|
const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
|
|
1966
2013
|
const writer = createShardCtxDb({
|
|
2014
|
+
// Admin and maintenance writes go through the SAME reactive-cache hooks as
|
|
2015
|
+
// a user mutation. Without this, a studio row edit, a TTL sweep, an admin
|
|
2016
|
+
// import, a CDC apply or a data-migration backfill writes without
|
|
2017
|
+
// invalidating, and the next query answers from the pre-write snapshot.
|
|
2018
|
+
...this.ctxDbTuning(),
|
|
1967
2019
|
broadcast: (delta) => {
|
|
1968
2020
|
this.recordChangedTable(delta.table, delta.indexKeys);
|
|
1969
2021
|
},
|
|
@@ -1989,7 +2041,7 @@ ${Y}
|
|
|
1989
2041
|
protected override async runShardRankBefore(args: RunShardRankBeforeArgs): Promise<{ before: number; total: number }> {
|
|
1990
2042
|
this.ensureMigrated();
|
|
1991
2043
|
|
|
1992
|
-
${
|
|
2044
|
+
${z}
|
|
1993
2045
|
|
|
1994
2046
|
// \`rankBefore\` is optional on \`DatabaseWriterLike\` (the D1 twin omits it),
|
|
1995
2047
|
// but the shard writer from \`createShardCtxDb\` always defines it.
|
|
@@ -2007,7 +2059,7 @@ ${Y}
|
|
|
2007
2059
|
protected override async runShardRankPage(args: RunShardRankPageArgs): Promise<ShardRankPageResult> {
|
|
2008
2060
|
this.ensureMigrated();
|
|
2009
2061
|
|
|
2010
|
-
${
|
|
2062
|
+
${z}
|
|
2011
2063
|
|
|
2012
2064
|
// \`rankPageRows\` is optional on \`DatabaseWriterLike\` (the D1 twin omits it),
|
|
2013
2065
|
// but the shard writer from \`createShardCtxDb\` always defines it. The sort
|
|
@@ -2028,7 +2080,7 @@ ${Y}
|
|
|
2028
2080
|
protected override async runShardApplyCdc(args: RunShardApplyCdcArgs): Promise<{ applied: number }> {
|
|
2029
2081
|
this.ensureMigrated();
|
|
2030
2082
|
|
|
2031
|
-
${
|
|
2083
|
+
${z}
|
|
2032
2084
|
|
|
2033
2085
|
await applyCdcChanges(writer, args.changes);
|
|
2034
2086
|
|
|
@@ -2053,7 +2105,7 @@ ${Ue?`
|
|
|
2053
2105
|
return backfillSearchIndexes(this.sql as SqlExec, schema as unknown as SchemaLike, options);
|
|
2054
2106
|
}
|
|
2055
2107
|
`:""}
|
|
2056
|
-
private buildCtx(options: { functionPath?: string; headroom?: TransactionHeadroomTracker; identity?: { identity?: Record<string, unknown>; userId?: string }; onRead?: (table: string, idOrScan?: string) => void; onReadRange?: (range: KeyRange) => void; trusted?: boolean } = {}): unknown {
|
|
2108
|
+
private buildCtx(options: { functionPath?: string; headroom?: TransactionHeadroomTracker; identity?: { identity?: Record<string, unknown>; userId?: string }; onRead?: (table: string, idOrScan?: string) => void; onReadRange?: (range: KeyRange) => void; scope?: QueryReadScope; trusted?: boolean } = {}): unknown {
|
|
2057
2109
|
const env = (this.env ?? {}) as Record<string, unknown>;
|
|
2058
2110
|
// When the caller threads an explicit identity (subscription seed /
|
|
2059
2111
|
// refresh — both run in deferred/interleaved contexts), use it by
|
|
@@ -2062,7 +2114,7 @@ ${Ue?`
|
|
|
2062
2114
|
// dispatch path) fall back to the per-request fields as before.
|
|
2063
2115
|
const userId = options.identity ? options.identity.userId : this.getCurrentUserId();
|
|
2064
2116
|
const identity = options.identity ? options.identity.identity : this.getCurrentIdentity();
|
|
2065
|
-
${
|
|
2117
|
+
${nn}${de}${gn}${Le}${Ee}${Ce}${Fe}
|
|
2066
2118
|
// \`list\`/\`get\` are the two methods \`ctx.db.system.query("_scheduled_functions")\`
|
|
2067
2119
|
// reaches through, and pending jobs live in the SchedulerDO — nothing the
|
|
2068
2120
|
// CDC changelog records — so reading them must forfeit a delta resume.
|
|
@@ -2124,12 +2176,12 @@ ${rn}${de}${gn}${Le}${Ee}${Ce}${Fe}
|
|
|
2124
2176
|
// so both write to the same trace.
|
|
2125
2177
|
const span = this.makeDispatchSpan(traceAnchor, observability);
|
|
2126
2178
|
|
|
2127
|
-
${
|
|
2179
|
+
${sn} // \`ctx.db\`, wrapped in automatic instrumentation: by default this
|
|
2128
2180
|
// adds aggregate counters (call count, total time, per-operation
|
|
2129
2181
|
// breakdown) to the wide event rather than a span per call, so a
|
|
2130
2182
|
// handler making hundreds of queries stays readable. See
|
|
2131
2183
|
// \`instrumentDatabase\` for the \`"spans"\` / \`"off"\` levels.
|
|
2132
|
-
const db: DatabaseWriterLike = this.instrumentDb(createShardCtxDb(${
|
|
2184
|
+
const db: DatabaseWriterLike = this.instrumentDb(createShardCtxDb(${rn}), logFunctionPath, traceAnchor, observability);
|
|
2133
2185
|
${mn}${Be}
|
|
2134
2186
|
|
|
2135
2187
|
// \`ctx.trace\` / \`ctx.metrics\`: spans and measurements to the same sink.
|
|
@@ -2161,22 +2213,24 @@ ${bn}
|
|
|
2161
2213
|
ip: this.getCurrentIp(),
|
|
2162
2214
|
log,
|
|
2163
2215
|
metrics,
|
|
2164
|
-
now,${
|
|
2216
|
+
now,${an}
|
|
2165
2217
|
scheduler,
|
|
2166
2218
|
span,
|
|
2167
2219
|
storage: contextStorage,
|
|
2168
|
-
trace,${
|
|
2220
|
+
trace,${on}${Oe}${fn}${be}${_e}${De}${ve}${ge}
|
|
2169
2221
|
};
|
|
2170
2222
|
${wn}${yn}
|
|
2171
2223
|
ctx.runAction = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("action", reference.__lunoraRef, fnArgs, ctx);
|
|
2172
2224
|
ctx.runMutation = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("mutation", reference.__lunoraRef, fnArgs, ctx);
|
|
2173
2225
|
// \`ctx.runQuery(ref, args, { untracked: true })\` runs the sub-query on
|
|
2174
|
-
// its OWN context, built without the read-footprint
|
|
2175
|
-
// reads never enter this subscription's footprint and a
|
|
2176
|
-
// tables it touched does not re-run us. Everything else is
|
|
2177
|
-
// \`functionPath\` (log/metric attribution), \`headroom\` (the
|
|
2178
|
-
// must not escape this dispatch's resource ceiling),
|
|
2179
|
-
//
|
|
2226
|
+
// its OWN context, built without the subscription's read-footprint
|
|
2227
|
+
// hooks — so its reads never enter this subscription's footprint and a
|
|
2228
|
+
// write to the tables it touched does not re-run us. Everything else is
|
|
2229
|
+
// inherited: \`functionPath\` (log/metric attribution), \`headroom\` (the
|
|
2230
|
+
// sub-query must not escape this dispatch's resource ceiling),
|
|
2231
|
+
// \`scope\` (the reactive-cache capture — an untracked sub-query's reads
|
|
2232
|
+
// must still be deps of the entry the OUTER query is memoized as, or
|
|
2233
|
+
// the memo goes stale), and — load-bearing — the identity BY VALUE. Omitting identity would let \`buildCtx\` fall
|
|
2180
2234
|
// back to the shared per-request fields, which a concurrent RPC may
|
|
2181
2235
|
// have re-set, and an RLS-scoped sub-query would then read as the wrong
|
|
2182
2236
|
// user. A tracked call keeps sharing \`ctx\` exactly as before.
|
|
@@ -2186,28 +2240,28 @@ ${wn}${yn}
|
|
|
2186
2240
|
reference.__lunoraRef,
|
|
2187
2241
|
fnArgs,
|
|
2188
2242
|
runOptions?.untracked === true
|
|
2189
|
-
? this.buildCtx({ functionPath: options.functionPath, headroom: options.headroom, identity: { identity, userId } })
|
|
2243
|
+
? this.buildCtx({ functionPath: options.functionPath, headroom: options.headroom, identity: { identity, userId }, scope: options.scope })
|
|
2190
2244
|
: ctx,
|
|
2191
2245
|
);
|
|
2192
2246
|
|
|
2193
2247
|
return ctx;
|
|
2194
2248
|
}
|
|
2195
2249
|
};
|
|
2196
|
-
`},Ye=e=>{switch(e.kind){case"array":case"object":case"record":case"union":return{builder:"text",mode:"json",notNull:!0,typeAnnotation:A(e)};case"bigint":return{builder:"blob",mode:"bigint",notNull:!0};case"boolean":return{builder:"integer",mode:"boolean",notNull:!0};case"bytes":return{builder:"blob",mode:"buffer",notNull:!0};case"date":case"timestamp":return{builder:"integer",notNull:!0};case"from":return{builder:"text",notNull:!0,typeAnnotation:A(e)};case"id":return{builder:"text",notNull:!0};case"literal":{const t=e.literalValue??"";return t.startsWith('"')||t.startsWith("'")?{builder:"text",notNull:!0}:t==="true"||t==="false"?{builder:"integer",mode:"boolean",notNull:!0}:t==="null"?{builder:"text",notNull:!0}:{builder:"real",notNull:!0}}case"null":return{builder:"text",notNull:!0};case"number":return{builder:"real",notNull:!0};case"optional":return{...e.inner?Ye(e.inner):{builder:"text",notNull:!1},notNull:!1};case"storage":case"string":return{builder:"text",notNull:!0};default:return{builder:"text",notNull:!0}}}
|
|
2250
|
+
`},Ye=e=>{switch(e.kind){case"array":case"object":case"record":case"union":return{builder:"text",mode:"json",notNull:!0,typeAnnotation:A(e)};case"bigint":return{builder:"blob",mode:"bigint",notNull:!0};case"boolean":return{builder:"integer",mode:"boolean",notNull:!0};case"bytes":return{builder:"blob",mode:"buffer",notNull:!0};case"date":case"timestamp":return{builder:"integer",notNull:!0};case"from":return{builder:"text",notNull:!0,typeAnnotation:A(e)};case"id":return{builder:"text",notNull:!0};case"literal":{const t=e.literalValue??"";return t.startsWith('"')||t.startsWith("'")?{builder:"text",notNull:!0}:t==="true"||t==="false"?{builder:"integer",mode:"boolean",notNull:!0}:t==="null"?{builder:"text",notNull:!0}:{builder:"real",notNull:!0}}case"null":return{builder:"text",notNull:!0};case"number":return{builder:"real",notNull:!0};case"optional":return{...e.inner?Ye(e.inner):{builder:"text",notNull:!1},notNull:!1};case"storage":case"string":return{builder:"text",notNull:!0};default:return{builder:"text",notNull:!0}}},St=(e,t)=>{const n=ae(e);return n.kind==="id"&&n.tableName!==void 0&&t.has(n.tableName)?n.tableName:void 0},Mr=(e,t,n)=>{g(e,"drizzle column name");const r=Ye(t),o=r.mode?`, { mode: "${r.mode}" }`:"";let s=`${r.builder}("${e}"${o})`;r.typeAnnotation&&(s+=`.$type<${r.typeAnnotation}>()`);const i=St(t,n);return i!==void 0&&(s+=`.references((): AnySQLiteColumn => ${i}._id)`),r.notNull&&(s+=".notNull()"),s},Br=e=>{const t=e.unique?"uniqueIndex":"index",n=e.fields.map(r=>(g(r,"drizzle index field"),`t.${r}`)).join(", ");return` ${Y(e.name)}: ${t}(${JSON.stringify(e.name)}).on(${n}),`},qr=(e,t)=>{g(e.name,"table name");const n=[' _id: text("_id").primaryKey(),',' _creationTime: integer("_creationTime").notNull(),',...Object.entries(e.shape).map(([o,s])=>` ${o}: ${Mr(o,s,t)},`)].join(`
|
|
2197
2251
|
`),r=e.indexes.length>0?`, (t) => ({
|
|
2198
2252
|
${e.indexes.map(o=>Br(o)).join(`
|
|
2199
2253
|
`)}
|
|
2200
2254
|
})`:"";return`export const ${e.name} = sqliteTable("${e.name}", {
|
|
2201
2255
|
${n}
|
|
2202
|
-
}${r});`},
|
|
2203
|
-
`;const n=oe(t),r=new Set(e.map(m=>m.name)),{columns:o,indexes:s,needsAnyColumn:i,needsId:a}=Wr(e,r),c=[...s,...o].toSorted((m,y)=>m.localeCompare(y)),d=e.map(m=>
|
|
2256
|
+
}${r});`},jr=e=>e.typeAnnotation?.includes('Id<"')??!1,Wr=(e,t)=>{const n=new Set(["integer","sqliteTable","text"]),r=new Set;let o=!1,s=!1;for(const i of e){for(const a of Object.values(i.shape)){const c=Ye(a);n.add(c.builder),s=s||jr(c),o=o||St(a,t)!==void 0}for(const a of i.indexes)r.add(a.unique?"uniqueIndex":"index")}return{columns:[...n].toSorted((i,a)=>i.localeCompare(a)),indexes:[...r].toSorted((i,a)=>i.localeCompare(a)),needsAnyColumn:o,needsId:s}},lt=(e,t=!1)=>{if(e.length===0)return`${$}export {};
|
|
2257
|
+
`;const n=oe(t),r=new Set(e.map(m=>m.name)),{columns:o,indexes:s,needsAnyColumn:i,needsId:a}=Wr(e,r),c=[...s,...o].toSorted((m,y)=>m.localeCompare(y)),d=e.map(m=>qr(m,r)).join(`
|
|
2204
2258
|
|
|
2205
2259
|
`),p=[i?`import type { AnySQLiteColumn } from "${n.serverDrizzle}";
|
|
2206
2260
|
`:"",a?`import type { Id } from "./dataModel.js";
|
|
2207
2261
|
`:""].join("");return`${$}import { ${c.join(", ")} } from "${n.serverDrizzle}";
|
|
2208
2262
|
${p}
|
|
2209
2263
|
${d}
|
|
2210
|
-
`},uo=(e,t=!1)=>{const n=e.tables.filter(o=>o.shardMode==="global"),r=e.tables.filter(o=>o.shardMode!=="global");return{global:E(
|
|
2264
|
+
`},uo=(e,t=!1)=>{const n=e.tables.filter(o=>o.shardMode==="global"),r=e.tables.filter(o=>o.shardMode!=="global");return{global:E(lt(n,t),Ge),shard:E(lt(r,t),Ge)}},po=e=>{const t=new Map;for(const i of e){const a=t.get(i.cron);a?a.push(i):t.set(i.cron,[i])}const n=[...t.keys()].map(i=>` ${JSON.stringify(i)},`).join(`
|
|
2211
2265
|
`),r=n.length>0?`
|
|
2212
2266
|
${n}
|
|
2213
2267
|
`:"",o=[...t.entries()].map(([i,a])=>{const c=a.map(d=>{const p=d.workflow?`workflow: ${JSON.stringify(d.workflow.binding)}`:`functionPath: ${JSON.stringify(d.functionPath)}`;return` { name: ${JSON.stringify(d.name)}, ${p}, args: ${JSON.stringify(d.args)} },`}).join(`
|