@lunora/testing 1.0.0-alpha.56 → 1.0.0-alpha.57

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AgentMessageRow, AgentRunInput, AgentGenerateResult, AgentRunResult, AgentDefinition } from '@lunora/agent';
2
- import { ScheduledJob, RegisteredAction, RegisteredMutation, RegisteredQuery, ArgsValidator, InferArgs, ActionCtx, MutationCtx, QueryCtx, TableDefinition, Schema } from '@lunora/server';
2
+ import { ScheduledJob, RegisteredAction, RegisteredMutation, RegisteredQuery, ArgsValidator, InferArgs, ActionCtx, MutationCtx, QueryCtx, LogFields, TableDefinition, Schema } from '@lunora/server';
3
3
  export { type InboxOptions, type WaitForMailOptions, extractLink, listCapturedMail, waitForMail } from '@lunora/mail/testing';
4
4
  /** A thread message as the in-memory runtime stores it (superset of {@link AgentMessageRow}). */
5
5
  interface HarnessMessage extends AgentMessageRow {
@@ -410,9 +410,41 @@ interface TestHarness {
410
410
  <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): TestSubscription<R>;
411
411
  <R>(inline: InlineQueryFunction<R>): TestSubscription<R>;
412
412
  };
413
+ /**
414
+ * What handlers attached to `ctx.span` — the **wide event** — during this
415
+ * harness's runs, so a test can assert the instrumentation itself:
416
+ *
417
+ * ```ts
418
+ * await t.mutation(checkout, { items: 3 });
419
+ * expect(t.wideEvent().attributes["cart.items"]).toBe(3);
420
+ * ```
421
+ *
422
+ * Accumulates across calls on this view (it is not reset per run), mirroring
423
+ * the harness's single shared database. Shared with any `withIdentity` view.
424
+ */
425
+ wideEvent: () => RecordedWideEvent;
413
426
  /** Return a harness view that shares this harness's db but reports the given identity on `ctx.auth`. */
414
427
  withIdentity: (identity: TestIdentity) => TestHarness;
415
428
  }
429
+ /** What a handler attached to the dispatch's span (`ctx.span`) during a harness run. */
430
+ interface RecordedWideEvent {
431
+ /**
432
+ * Attributes accumulated across the run, merged in call order. Values are
433
+ * recorded AS PASSED (not coerced the way the real pipeline normalizes them),
434
+ * so a test asserts on what the handler meant rather than on the wire form.
435
+ */
436
+ attributes: LogFields;
437
+ /** Span events recorded via `ctx.span.addEvent` / `recordException`, in order. */
438
+ events: {
439
+ attributes?: LogFields;
440
+ name: string;
441
+ }[];
442
+ /** Links recorded via `ctx.span.addLink`, in order. */
443
+ links: {
444
+ spanId: string;
445
+ traceId: string;
446
+ }[];
447
+ }
416
448
  /**
417
449
  * Spin up an in-memory Lunora function harness for `schema`.
418
450
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AgentMessageRow, AgentRunInput, AgentGenerateResult, AgentRunResult, AgentDefinition } from '@lunora/agent';
2
- import { ScheduledJob, RegisteredAction, RegisteredMutation, RegisteredQuery, ArgsValidator, InferArgs, ActionCtx, MutationCtx, QueryCtx, TableDefinition, Schema } from '@lunora/server';
2
+ import { ScheduledJob, RegisteredAction, RegisteredMutation, RegisteredQuery, ArgsValidator, InferArgs, ActionCtx, MutationCtx, QueryCtx, LogFields, TableDefinition, Schema } from '@lunora/server';
3
3
  export { type InboxOptions, type WaitForMailOptions, extractLink, listCapturedMail, waitForMail } from '@lunora/mail/testing';
4
4
  /** A thread message as the in-memory runtime stores it (superset of {@link AgentMessageRow}). */
5
5
  interface HarnessMessage extends AgentMessageRow {
@@ -410,9 +410,41 @@ interface TestHarness {
410
410
  <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): TestSubscription<R>;
411
411
  <R>(inline: InlineQueryFunction<R>): TestSubscription<R>;
412
412
  };
413
+ /**
414
+ * What handlers attached to `ctx.span` — the **wide event** — during this
415
+ * harness's runs, so a test can assert the instrumentation itself:
416
+ *
417
+ * ```ts
418
+ * await t.mutation(checkout, { items: 3 });
419
+ * expect(t.wideEvent().attributes["cart.items"]).toBe(3);
420
+ * ```
421
+ *
422
+ * Accumulates across calls on this view (it is not reset per run), mirroring
423
+ * the harness's single shared database. Shared with any `withIdentity` view.
424
+ */
425
+ wideEvent: () => RecordedWideEvent;
413
426
  /** Return a harness view that shares this harness's db but reports the given identity on `ctx.auth`. */
414
427
  withIdentity: (identity: TestIdentity) => TestHarness;
415
428
  }
429
+ /** What a handler attached to the dispatch's span (`ctx.span`) during a harness run. */
430
+ interface RecordedWideEvent {
431
+ /**
432
+ * Attributes accumulated across the run, merged in call order. Values are
433
+ * recorded AS PASSED (not coerced the way the real pipeline normalizes them),
434
+ * so a test asserts on what the handler meant rather than on the wire form.
435
+ */
436
+ attributes: LogFields;
437
+ /** Span events recorded via `ctx.span.addEvent` / `recordException`, in order. */
438
+ events: {
439
+ attributes?: LogFields;
440
+ name: string;
441
+ }[];
442
+ /** Links recorded via `ctx.span.addLink`, in order. */
443
+ links: {
444
+ spanId: string;
445
+ traceId: string;
446
+ }[];
447
+ }
416
448
  /**
417
449
  * Spin up an in-memory Lunora function harness for `schema`.
418
450
  *
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{agentHarness as o,finalTurn as t,toolCallTurn as a}from"./packem_shared/agentHarness-0b923YYC.mjs";import{evaluationAttributes as c,recordEvaluation as n}from"./packem_shared/evaluationAttributes-GZvtFa1N.mjs";import{lunoraTest as u}from"./packem_shared/lunoraTest-BAkd1qwt.mjs";import{containsScorer as m,evaluate as p,exactMatchScorer as s,keywordScorer as f,llmScorer as S,regexScorer as d,scoreSample as v}from"./packem_shared/containsScorer-Dr5tjL-M.mjs";import{extractLink as T,listCapturedMail as g,waitForMail as k}from"@lunora/mail/testing";export{o as agentHarness,m as containsScorer,p as evaluate,c as evaluationAttributes,s as exactMatchScorer,T as extractLink,t as finalTurn,f as keywordScorer,g as listCapturedMail,S as llmScorer,u as lunoraTest,n as recordEvaluation,d as regexScorer,v as scoreSample,a as toolCallTurn,k as waitForMail};
1
+ import{agentHarness as o,finalTurn as t,toolCallTurn as a}from"./packem_shared/agentHarness-0b923YYC.mjs";import{evaluationAttributes as c,recordEvaluation as n}from"./packem_shared/evaluationAttributes-GZvtFa1N.mjs";import{lunoraTest as u}from"./packem_shared/lunoraTest-GHwCKPPj.mjs";import{containsScorer as m,evaluate as p,exactMatchScorer as s,keywordScorer as f,llmScorer as S,regexScorer as d,scoreSample as v}from"./packem_shared/containsScorer-Dr5tjL-M.mjs";import{extractLink as T,listCapturedMail as g,waitForMail as k}from"@lunora/mail/testing";export{o as agentHarness,m as containsScorer,p as evaluate,c as evaluationAttributes,s as exactMatchScorer,T as extractLink,t as finalTurn,f as keywordScorer,g as listCapturedMail,S as llmScorer,u as lunoraTest,n as recordEvaluation,d as regexScorer,v as scoreSample,a as toolCallTurn,k as waitForMail};
@@ -0,0 +1 @@
1
+ import{runShardMigrations as G,createShardCtxDb as H}from"@lunora/do";import{LunoraError as T}from"@lunora/errors";import{DatabaseSync as J}from"node:sqlite";const U=(o,i,n,l,x)=>{let m=x,v=1;const u=new Map,h=[],g=(t,a,d={})=>{const e=`fake-job-${String(v)}`;return v+=1,u.set(e,{args:d,enqueuedAt:m,functionPath:a,id:e,scheduledFor:t}),e},w=t=>typeof t=="string"?t:t.name??t.binding??"",A={cancel:t=>{const a=u.has(t);return u.delete(t),Promise.resolve({cancelled:a})},get:t=>Promise.resolve(u.get(t)??null),list:()=>Promise.resolve([...u.values()]),runAfter:(t,a,d)=>{const e=g(m+t,w(a),d);return Promise.resolve(e)},runAt:(t,a,d)=>{const e=g(t,w(a),d);return Promise.resolve(e)}},j=async t=>{u.delete(t.id);const a=l().get(t.functionPath);if(a===void 0){console.warn(`[fake-scheduler] unknown functionPath "${t.functionPath}" — job ${t.id} dropped`);return}if(a.kind==="mutation"||a.kind==="action"){const d=o(),e=a.kind==="action"?n():i();await d(a.kind,a,e,t.args)}else console.warn(`[fake-scheduler] functionPath "${t.functionPath}" is a ${a.kind} — only mutations and actions can be scheduled; job ${t.id} dropped`)},E=async t=>{const a=[...u.values()].filter(s=>s.scheduledFor<=t).toSorted((s,f)=>s.scheduledFor-f.scheduledFor),d=[];let e=0;for(const s of a)if(u.has(s.id)){e+=1;try{await j(s)}catch(f){const N={args:s.args,error:f,functionPath:s.functionPath,id:s.id};d.push(N),h.push(N)}}return{executed:e,failed:d}},I=async(t,a)=>{const{executed:d,failed:e}=await E(t);if(e.length>0&&(a?.throwOnError??!0)){const[s]=e;throw e.length===1&&s!==void 0?s.error:new AggregateError(e.map(f=>f.error),`${String(e.length)} scheduled jobs failed: ${e.map(f=>f.functionPath).join(", ")}`)}return d};return{controls:{advance:(t,a)=>(m+=t,I(m,a)),failures:()=>[...h],list:()=>[...u.values()],runPending:t=>I(Number.POSITIVE_INFINITY,t)},scheduler:A}},W=()=>{const o=new J(":memory:"),i=n=>({one(){if(n.length!==1)throw new T("INTERNAL",`expected exactly one row, received ${String(n.length)}`);const[l]=n;return l},[Symbol.iterator](){return n[Symbol.iterator]()},toArray(){return n}});return{close:()=>{o.close()},sql:{exec:(n,...l)=>{const x=o.prepare(n).all(...l);return i(x)}}}},L=o=>{if(typeof o!="object"||o===null)return;const{kind:i}=o;if(i==="query"||i==="mutation"||i==="action")return i},Z=o=>typeof o=="object"&&o!==null&&o.visibility==="internal"?"internal":"public",Q=o=>{throw new T("INTERNAL",`ctx.${o} is not available in the in-memory @lunora/testing harness (v1)`)},p=o=>new Proxy((...i)=>Q(o),{apply:()=>Q(o),get:()=>Q(o)}),K={spanId:"0000000000000001",traceId:"00000000000000000000000000000001"},X={addEvent:()=>{},addLink:()=>{},recordException:()=>{},setAttribute:()=>{},setAttributes:()=>{},spanContext:()=>K},ee=()=>{const o={attributes:{},events:[],links:[]},i={addEvent:(n,l)=>{o.events.push({...l===void 0?{}:{attributes:{...l}},name:n})},addLink:n=>{o.links.push({spanId:n.spanId,traceId:n.traceId})},recordException:n=>{const l={"exception.message":n instanceof Error?n.message:String(n),"exception.type":n instanceof Error?n.constructor.name:"Error"};n instanceof Error&&n.stack!==void 0&&(l["exception.stacktrace"]=n.stack),i.addEvent("exception",l)},setAttribute:(n,l)=>{o.attributes[n]=l},setAttributes:n=>{Object.assign(o.attributes,n)},spanContext:()=>K};return{handle:i,recorded:o}},M=async(o,i)=>await i(M,X),B={count:()=>{},gauge:()=>{},record:()=>{}},F={debug:()=>{},error:()=>{},event:()=>{},fatal:()=>{},info:()=>{},log:()=>{},trace:()=>{},warn:()=>{},with:()=>F},te=(o,i,n)=>(l,x)=>{let m=!1;const v=[];let u,h,g=0,w=0;const A=()=>L(l)?o("query",l,i,x,!1):Promise.resolve(l(i)),j=(e,s)=>{if(e<w)return;w=e;const f={done:!1,value:s};if(v.length===0)u=f,h=void 0;else{u=void 0,h=void 0;for(const N of v.splice(0))N.resolve(f)}},E=(e,s)=>{if(!(e<w))if(w=e,v.length===0)h={error:s},u=void 0;else{u=void 0,h=void 0;for(const f of v.splice(0))f.reject(s)}},I=e=>s=>{j(e,s)},t=e=>s=>{E(e,s)},a=()=>{if(m)return;g+=1;const e=g;A().then(I(e)).catch(t(e))};n.add(a);const d={[Symbol.asyncIterator](){return d},next:()=>{if(m)return Promise.resolve({done:!0,value:void 0});if(w===g){if(h!==void 0){const{error:e}=h;return h=void 0,Promise.reject(e)}if(u!==void 0){const e=u;return u=void 0,Promise.resolve(e)}}return w<g?new Promise((e,s)=>{v.push({reject:s,resolve:e})}):A().then(e=>{if(h!==void 0){const{error:s}=h;throw h=void 0,s}if(u!==void 0){const s=u;return u=void 0,s}return{done:!1,value:e}})},return:()=>{m=!0,n.delete(a);for(const e of v.splice(0))e.resolve({done:!0,value:void 0});return Promise.resolve({done:!0,value:void 0})}};return A().then(I(0)).catch(t(0)),d},se=(o,i)=>{const{close:n,sql:l}=W(),x=o;G(l,x);const m=H({schema:x,sql:l}),v=b=>{l.exec.call(l,b)};let u=Promise.resolve();const h=b=>{const $=async()=>{v("BEGIN");try{const y=await b();return v("COMMIT"),y}catch(y){try{v("ROLLBACK")}catch{}throw y}},k=u.then($);return u=k.then(()=>{},()=>{}),k};let g=!1;const w=()=>{g||(g=!0,n())},A=new Map(Object.entries(i?.functions??{}).map(([b,$])=>[b,$])),j=new Set,E=()=>{for(const b of j)b()};let I,t,a;const d=i?.now??Date.now(),e=ee(),{controls:s,scheduler:f}=U(()=>{if(I===void 0)throw new T("INTERNAL","[fake-scheduler] dispatch not yet available — scheduler.advance called before harness construction completed");return I},()=>{if(t===void 0)throw new T("INTERNAL","[fake-scheduler] mutationContext not yet available — scheduler.advance called before harness construction completed");return t},()=>{if(a===void 0)throw new T("INTERNAL","[fake-scheduler] actionContext not yet available — scheduler.advance called before harness construction completed");return a},()=>A,d),N=b=>{const $={getIdentity:()=>Promise.resolve(b??null),userId:b?.userId??null},k={auth:$,db:m,env:i?.env,log:F,metrics:B,now:d,span:e.handle,trace:M,runQuery:(r,c)=>S("query",r,k,c),secrets:p("secrets"),storage:p("storage"),vectors:p("vectors")},y={auth:$,db:m,env:i?.env,log:F,metrics:B,now:d,span:e.handle,trace:M,runMutation:(r,c)=>S("mutation",r,y,c),runQuery:(r,c)=>S("query",r,k,c),scheduler:f,secrets:p("secrets"),storage:p("storage"),vectors:p("vectors"),workflows:p("workflows")};t??=y;const R={auth:$,db:m,env:i?.env,fetch:i?.fetch??p("fetch"),log:F,metrics:B,now:d,span:e.handle,trace:M,runAction:(r,c)=>S("action",r,R,c),runMutation:(r,c)=>S("mutation",r,y,c),runQuery:(r,c)=>S("query",r,k,c),scheduler:f,secrets:p("secrets"),storage:p("storage"),vectors:p("vectors"),workflows:p("workflows")};a??=R;const q=(r,c,P,C,O)=>{const D=L(c);if(D!==r)throw new T("INTERNAL",`expected a registered ${r}, received a ${D??"non-function"} reference`);if(!O&&Z(c)==="internal")throw new T("INTERNAL",`This ${r} is an internal function — it is unreachable from the external RPC boundary in production. Call it through ctx.run${r.charAt(0).toUpperCase()}${r.slice(1)} from another function instead.`);return Promise.resolve(c.handler(P,C??{}))},S=(r,c,P,C)=>q(r,c,P,C,!0);I??=(r,c,P,C)=>r==="mutation"?h(()=>q("mutation",c,P,C,!0)).then(O=>(E(),O)):S("action",c,P,C);const V=((r,c)=>L(r)?q("query",r,k,c,!1):Promise.resolve(r(k))),Y=((r,c)=>L(r)?h(()=>q("mutation",r,y,c,!1)).then(P=>(E(),P)):h(()=>r(y)).then(P=>(E(),P))),_=((r,c)=>L(r)?q("action",r,R,c,!1):Promise.resolve(r(R))),z=te(q,k,j);return{action:_,close:w,mutation:Y,query:V,run:r=>h(()=>r(y)).then(c=>(E(),c)),scheduler:s,subscribe:z,wideEvent:()=>e.recorded,withIdentity:r=>N(r)}};return N(null)};export{se as lunoraTest};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/testing",
3
- "version": "1.0.0-alpha.56",
3
+ "version": "1.0.0-alpha.57",
4
4
  "description": "Testing toolkit for Lunora: an in-memory harness for queries, mutations, and actions",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -50,11 +50,11 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@lunora/agent": "1.0.0-alpha.11",
54
- "@lunora/do": "1.0.0-alpha.45",
53
+ "@lunora/agent": "1.0.0-alpha.12",
54
+ "@lunora/do": "1.0.0-alpha.46",
55
55
  "@lunora/errors": "1.0.0-alpha.8",
56
56
  "@lunora/mail": "1.0.0-alpha.18",
57
- "@lunora/server": "1.0.0-alpha.32"
57
+ "@lunora/server": "1.0.0-alpha.33"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@playwright/test": "^1.61.1"
@@ -1 +0,0 @@
1
- import{runShardMigrations as z,createShardCtxDb as G}from"@lunora/do";import{LunoraError as j}from"@lunora/errors";import{DatabaseSync as H}from"node:sqlite";const K=(c,a,f,v,I)=>{let w=I,m=1;const i=new Map,u=[],p=(t,o,l={})=>{const e=`fake-job-${String(m)}`;return m+=1,i.set(e,{args:l,enqueuedAt:w,functionPath:o,id:e,scheduledFor:t}),e},y=t=>typeof t=="string"?t:t.name??t.binding??"",x={cancel:t=>{const o=i.has(t);return i.delete(t),Promise.resolve({cancelled:o})},get:t=>Promise.resolve(i.get(t)??null),list:()=>Promise.resolve([...i.values()]),runAfter:(t,o,l)=>{const e=p(w+t,y(o),l);return Promise.resolve(e)},runAt:(t,o,l)=>{const e=p(t,y(o),l);return Promise.resolve(e)}},q=async t=>{i.delete(t.id);const o=v().get(t.functionPath);if(o===void 0){console.warn(`[fake-scheduler] unknown functionPath "${t.functionPath}" — job ${t.id} dropped`);return}if(o.kind==="mutation"||o.kind==="action"){const l=c(),e=o.kind==="action"?f():a();await l(o.kind,o,e,t.args)}else console.warn(`[fake-scheduler] functionPath "${t.functionPath}" is a ${o.kind} — only mutations and actions can be scheduled; job ${t.id} dropped`)},N=async t=>{const o=[...i.values()].filter(n=>n.scheduledFor<=t).toSorted((n,d)=>n.scheduledFor-d.scheduledFor),l=[];let e=0;for(const n of o)if(i.has(n.id)){e+=1;try{await q(n)}catch(d){const h={args:n.args,error:d,functionPath:n.functionPath,id:n.id};l.push(h),u.push(h)}}return{executed:e,failed:l}},k=async(t,o)=>{const{executed:l,failed:e}=await N(t);if(e.length>0&&(o?.throwOnError??!0)){const[n]=e;throw e.length===1&&n!==void 0?n.error:new AggregateError(e.map(d=>d.error),`${String(e.length)} scheduled jobs failed: ${e.map(d=>d.functionPath).join(", ")}`)}return l};return{controls:{advance:(t,o)=>(w+=t,k(w,o)),failures:()=>[...u],list:()=>[...i.values()],runPending:t=>k(Number.POSITIVE_INFINITY,t)},scheduler:x}},U=()=>{const c=new H(":memory:"),a=f=>({one(){if(f.length!==1)throw new j("INTERNAL",`expected exactly one row, received ${String(f.length)}`);const[v]=f;return v},[Symbol.iterator](){return f[Symbol.iterator]()},toArray(){return f}});return{close:()=>{c.close()},sql:{exec:(f,...v)=>{const I=c.prepare(f).all(...v);return a(I)}}}},R=c=>{if(typeof c!="object"||c===null)return;const{kind:a}=c;if(a==="query"||a==="mutation"||a==="action")return a},W=c=>typeof c=="object"&&c!==null&&c.visibility==="internal"?"internal":"public",O=c=>{throw new j("INTERNAL",`ctx.${c} is not available in the in-memory @lunora/testing harness (v1)`)},g=c=>new Proxy((...a)=>O(c),{apply:()=>O(c),get:()=>O(c)}),X={setAttribute:()=>{},setAttributes:()=>{}},C=async(c,a)=>await a(C,X),Q={count:()=>{},gauge:()=>{},record:()=>{}},M={debug:()=>{},error:()=>{},fatal:()=>{},info:()=>{},log:()=>{},trace:()=>{},warn:()=>{},with:()=>M},Z=(c,a,f)=>(v,I)=>{let w=!1;const m=[];let i,u,p=0,y=0;const x=()=>R(v)?c("query",v,a,I,!1):Promise.resolve(v(a)),q=(e,n)=>{if(e<y)return;y=e;const d={done:!1,value:n};if(m.length===0)i=d,u=void 0;else{i=void 0,u=void 0;for(const h of m.splice(0))h.resolve(d)}},N=(e,n)=>{if(!(e<y))if(y=e,m.length===0)u={error:n},i=void 0;else{i=void 0,u=void 0;for(const d of m.splice(0))d.reject(n)}},k=e=>n=>{q(e,n)},t=e=>n=>{N(e,n)},o=()=>{if(w)return;p+=1;const e=p;x().then(k(e)).catch(t(e))};f.add(o);const l={[Symbol.asyncIterator](){return l},next:()=>{if(w)return Promise.resolve({done:!0,value:void 0});if(y===p){if(u!==void 0){const{error:e}=u;return u=void 0,Promise.reject(e)}if(i!==void 0){const e=i;return i=void 0,Promise.resolve(e)}}return y<p?new Promise((e,n)=>{m.push({reject:n,resolve:e})}):x().then(e=>{if(u!==void 0){const{error:n}=u;throw u=void 0,n}if(i!==void 0){const n=i;return i=void 0,n}return{done:!1,value:e}})},return:()=>{w=!0,f.delete(o);for(const e of m.splice(0))e.resolve({done:!0,value:void 0});return Promise.resolve({done:!0,value:void 0})}};return x().then(k(0)).catch(t(0)),l},re=(c,a)=>{const{close:f,sql:v}=U(),I=c;z(v,I);const w=G({schema:I,sql:v}),m=h=>{v.exec.call(v,h)};let i=Promise.resolve();const u=h=>{const $=async()=>{m("BEGIN");try{const b=await h();return m("COMMIT"),b}catch(b){try{m("ROLLBACK")}catch{}throw b}},A=i.then($);return i=A.then(()=>{},()=>{}),A};let p=!1;const y=()=>{p||(p=!0,f())},x=new Map(Object.entries(a?.functions??{}).map(([h,$])=>[h,$])),q=new Set,N=()=>{for(const h of q)h()};let k,t,o;const l=a?.now??Date.now(),{controls:e,scheduler:n}=K(()=>{if(k===void 0)throw new j("INTERNAL","[fake-scheduler] dispatch not yet available — scheduler.advance called before harness construction completed");return k},()=>{if(t===void 0)throw new j("INTERNAL","[fake-scheduler] mutationContext not yet available — scheduler.advance called before harness construction completed");return t},()=>{if(o===void 0)throw new j("INTERNAL","[fake-scheduler] actionContext not yet available — scheduler.advance called before harness construction completed");return o},()=>x,l),d=h=>{const $={getIdentity:()=>Promise.resolve(h??null),userId:h?.userId??null},A={auth:$,db:w,env:a?.env,log:M,metrics:Q,now:l,trace:C,runQuery:(r,s)=>S("query",r,A,s),secrets:g("secrets"),storage:g("storage"),vectors:g("vectors")},b={auth:$,db:w,env:a?.env,log:M,metrics:Q,now:l,trace:C,runMutation:(r,s)=>S("mutation",r,b,s),runQuery:(r,s)=>S("query",r,A,s),scheduler:n,secrets:g("secrets"),storage:g("storage"),vectors:g("vectors"),workflows:g("workflows")};t??=b;const T={auth:$,db:w,env:a?.env,fetch:a?.fetch??g("fetch"),log:M,metrics:Q,now:l,trace:C,runAction:(r,s)=>S("action",r,T,s),runMutation:(r,s)=>S("mutation",r,b,s),runQuery:(r,s)=>S("query",r,A,s),scheduler:n,secrets:g("secrets"),storage:g("storage"),vectors:g("vectors"),workflows:g("workflows")};o??=T;const E=(r,s,P,L,F)=>{const D=R(s);if(D!==r)throw new j("INTERNAL",`expected a registered ${r}, received a ${D??"non-function"} reference`);if(!F&&W(s)==="internal")throw new j("INTERNAL",`This ${r} is an internal function — it is unreachable from the external RPC boundary in production. Call it through ctx.run${r.charAt(0).toUpperCase()}${r.slice(1)} from another function instead.`);return Promise.resolve(s.handler(P,L??{}))},S=(r,s,P,L)=>E(r,s,P,L,!0);k??=(r,s,P,L)=>r==="mutation"?u(()=>E("mutation",s,P,L,!0)).then(F=>(N(),F)):S("action",s,P,L);const B=((r,s)=>R(r)?E("query",r,A,s,!1):Promise.resolve(r(A))),V=((r,s)=>R(r)?u(()=>E("mutation",r,b,s,!1)).then(P=>(N(),P)):u(()=>r(b)).then(P=>(N(),P))),Y=((r,s)=>R(r)?E("action",r,T,s,!1):Promise.resolve(r(T))),_=Z(E,A,q);return{action:Y,close:y,mutation:V,query:B,run:r=>u(()=>r(b)).then(s=>(N(),s)),scheduler:e,subscribe:_,withIdentity:r=>d(r)}};return d(null)};export{re as lunoraTest};