@lunora/testing 1.0.0-alpha.83 → 1.0.0-alpha.84

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
@@ -289,6 +289,35 @@ type FunctionRegistry = Record<string, RegisteredAction<any, any> | RegisteredMu
289
289
  * clearly-throwing stubs for unsupported surfaces.
290
290
  */
291
291
  interface LunoraTestOptions {
292
+ /**
293
+ * Enforce the secure-by-default RLS guard on the writer registered
294
+ * procedures dispatch through (`query`/`mutation`/`action`, via
295
+ * `reference.handler`) — the same `enforceRls: true` production's generated
296
+ * `buildCtx` always passes. Under a `.rls("required")` schema, a procedure
297
+ * that touches a known, non-`.public()` table without `.use(rls(...))` in
298
+ * its chain rejects with `RlsRequiredError`, exactly as it would on first
299
+ * dispatch in production. Defaults to `true` so a green suite means the
300
+ * deploy is RLS-safe; the harness's other surfaces — `t.run` and any
301
+ * `@lunora/seed` helper built on it — stay on the trusted, UNGUARDED writer
302
+ * regardless of this flag (mirroring production's admin/migration system
303
+ * paths).
304
+ *
305
+ * Set to `false` to opt back into the pre-guard permissive behaviour (every
306
+ * `lunoraTest` release before this option existed): every procedure's
307
+ * `ctx.db` goes unguarded even under a `.rls("required")` schema. This
308
+ * forfeits the "a passing suite means the deploy is safe" guarantee — a
309
+ * procedure that forgot `.use(rls(...))` will pass in tests and throw
310
+ * `RlsRequiredError` on its first production request. No effect when the
311
+ * schema does not declare `.rls("required")` (the guard is a no-op there
312
+ * either way).
313
+ * @default true
314
+ * @example
315
+ * ```ts
316
+ * // Restores the old permissive behavior for a suite not yet migrated.
317
+ * const t = lunoraTest(schema, { enforceRls: false });
318
+ * ```
319
+ */
320
+ enforceRls?: boolean;
292
321
  /**
293
322
  * Injectable `ctx.env` for every context (query / mutation / action). When
294
323
  * provided, handlers that read `ctx.env.SOME_KEY` (the validated `defineEnv`
@@ -368,7 +397,16 @@ interface TestHarness {
368
397
  <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): Promise<R>;
369
398
  <R>(inline: InlineQueryFunction<R>): Promise<R>;
370
399
  };
371
- /** Direct db access at mutation-level (read + write), mirroring `convexTest`'s `run`. */
400
+ /**
401
+ * Direct db access at mutation-level (read + write), mirroring `convexTest`'s
402
+ * `run`. This is the harness's trusted escape hatch: `ctx.db` here is always
403
+ * the UNGUARDED writer, regardless of `options.enforceRls` or the schema's
404
+ * RLS mode — seeding/asserting against a protected table never trips the
405
+ * secure-by-default guard. A `ctx.runMutation`/`ctx.runQuery` call from
406
+ * inside the body still dispatches the target as a real registered
407
+ * procedure, so it is guarded exactly as `t.mutation`/`t.query` would guard
408
+ * it.
409
+ */
372
410
  run: <R>(function_: InlineMutationFunction<R>) => Promise<R>;
373
411
  /**
374
412
  * Controls for the fake in-memory scheduler. Always present; scheduler
@@ -450,9 +488,14 @@ interface RecordedWideEvent {
450
488
  *
451
489
  * `lunoraTest(schema)` runs the migrations against a fresh `node:sqlite`
452
490
  * database, builds the same `ctx.db` writer the real Durable Object builds (via
453
- * `@lunora/do`'s `createShardCtxDb`), and returns a harness whose `query` /
454
- * `mutation` / `action` / `run` execute a registered function's `handler`
455
- * directly no Durable Object, no `wrangler`, no network.
491
+ * `@lunora/shard-engine`'s `createShardCtxDb`, with the same `enforceRls: true`
492
+ * production's generated `buildCtx` passes), and returns a harness whose
493
+ * `query` / `mutation` / `action` execute a registered function's `handler`
494
+ * directly — no Durable Object, no `wrangler`, no network. Under a
495
+ * `.rls("required")` schema a procedure missing `.use(rls(...))` therefore
496
+ * rejects here exactly as it would on its first production dispatch — see
497
+ * `LunoraTestOptions.enforceRls` to opt out, and `run`'s doc for the trusted
498
+ * escape hatch (always unguarded).
456
499
  *
457
500
  * **v1 surfaces now supported:**
458
501
  *
package/dist/index.d.ts CHANGED
@@ -289,6 +289,35 @@ type FunctionRegistry = Record<string, RegisteredAction<any, any> | RegisteredMu
289
289
  * clearly-throwing stubs for unsupported surfaces.
290
290
  */
291
291
  interface LunoraTestOptions {
292
+ /**
293
+ * Enforce the secure-by-default RLS guard on the writer registered
294
+ * procedures dispatch through (`query`/`mutation`/`action`, via
295
+ * `reference.handler`) — the same `enforceRls: true` production's generated
296
+ * `buildCtx` always passes. Under a `.rls("required")` schema, a procedure
297
+ * that touches a known, non-`.public()` table without `.use(rls(...))` in
298
+ * its chain rejects with `RlsRequiredError`, exactly as it would on first
299
+ * dispatch in production. Defaults to `true` so a green suite means the
300
+ * deploy is RLS-safe; the harness's other surfaces — `t.run` and any
301
+ * `@lunora/seed` helper built on it — stay on the trusted, UNGUARDED writer
302
+ * regardless of this flag (mirroring production's admin/migration system
303
+ * paths).
304
+ *
305
+ * Set to `false` to opt back into the pre-guard permissive behaviour (every
306
+ * `lunoraTest` release before this option existed): every procedure's
307
+ * `ctx.db` goes unguarded even under a `.rls("required")` schema. This
308
+ * forfeits the "a passing suite means the deploy is safe" guarantee — a
309
+ * procedure that forgot `.use(rls(...))` will pass in tests and throw
310
+ * `RlsRequiredError` on its first production request. No effect when the
311
+ * schema does not declare `.rls("required")` (the guard is a no-op there
312
+ * either way).
313
+ * @default true
314
+ * @example
315
+ * ```ts
316
+ * // Restores the old permissive behavior for a suite not yet migrated.
317
+ * const t = lunoraTest(schema, { enforceRls: false });
318
+ * ```
319
+ */
320
+ enforceRls?: boolean;
292
321
  /**
293
322
  * Injectable `ctx.env` for every context (query / mutation / action). When
294
323
  * provided, handlers that read `ctx.env.SOME_KEY` (the validated `defineEnv`
@@ -368,7 +397,16 @@ interface TestHarness {
368
397
  <A extends ArgsValidator, R>(reference: RegisteredQuery<A, R>, args: InferArgs<A>): Promise<R>;
369
398
  <R>(inline: InlineQueryFunction<R>): Promise<R>;
370
399
  };
371
- /** Direct db access at mutation-level (read + write), mirroring `convexTest`'s `run`. */
400
+ /**
401
+ * Direct db access at mutation-level (read + write), mirroring `convexTest`'s
402
+ * `run`. This is the harness's trusted escape hatch: `ctx.db` here is always
403
+ * the UNGUARDED writer, regardless of `options.enforceRls` or the schema's
404
+ * RLS mode — seeding/asserting against a protected table never trips the
405
+ * secure-by-default guard. A `ctx.runMutation`/`ctx.runQuery` call from
406
+ * inside the body still dispatches the target as a real registered
407
+ * procedure, so it is guarded exactly as `t.mutation`/`t.query` would guard
408
+ * it.
409
+ */
372
410
  run: <R>(function_: InlineMutationFunction<R>) => Promise<R>;
373
411
  /**
374
412
  * Controls for the fake in-memory scheduler. Always present; scheduler
@@ -450,9 +488,14 @@ interface RecordedWideEvent {
450
488
  *
451
489
  * `lunoraTest(schema)` runs the migrations against a fresh `node:sqlite`
452
490
  * database, builds the same `ctx.db` writer the real Durable Object builds (via
453
- * `@lunora/do`'s `createShardCtxDb`), and returns a harness whose `query` /
454
- * `mutation` / `action` / `run` execute a registered function's `handler`
455
- * directly no Durable Object, no `wrangler`, no network.
491
+ * `@lunora/shard-engine`'s `createShardCtxDb`, with the same `enforceRls: true`
492
+ * production's generated `buildCtx` passes), and returns a harness whose
493
+ * `query` / `mutation` / `action` execute a registered function's `handler`
494
+ * directly — no Durable Object, no `wrangler`, no network. Under a
495
+ * `.rls("required")` schema a procedure missing `.use(rls(...))` therefore
496
+ * rejects here exactly as it would on its first production dispatch — see
497
+ * `LunoraTestOptions.enforceRls` to opt out, and `run`'s doc for the trusted
498
+ * escape hatch (always unguarded).
456
499
  *
457
500
  * **v1 surfaces now supported:**
458
501
  *
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-B5WBM42D.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-BUsuJZQn.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{LunoraError as R}from"@lunora/errors";import{runShardMigrations as J,createShardCtxDb as W,RLS_UNWRAP_SYMBOL as z}from"@lunora/shard-engine";import{evaluationAttributes as X}from"./evaluationAttributes-GZvtFa1N.mjs";import{DatabaseSync as Z}from"node:sqlite";const ee=(s,i,n,l,E)=>{let f=E,g=1;const u=new Map,h=[],m=(t,a,d={})=>{const e=`fake-job-${String(g)}`;return g+=1,u.set(e,{args:d,enqueuedAt:f,functionPath:a,id:e,scheduledFor:t}),e},p=t=>typeof t=="string"?t:t.name??t.binding??"",x={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=m(f+t,p(a),d);return Promise.resolve(e)},runAt:(t,a,d)=>{const e=m(t,p(a),d);return Promise.resolve(e)}},T=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=s(),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`)},j=async t=>{const a=[...u.values()].filter(o=>o.scheduledFor<=t).toSorted((o,v)=>o.scheduledFor-v.scheduledFor),d=[];let e=0;for(const o of a)if(u.has(o.id)){e+=1;try{await T(o)}catch(v){const A={args:o.args,error:v,functionPath:o.functionPath,id:o.id};d.push(A),h.push(A)}}return{executed:e,failed:d}},b=async(t,a)=>{const{executed:d,failed:e}=await j(t);if(e.length>0&&(a?.throwOnError??!0)){const[o]=e;throw e.length===1&&o!==void 0?o.error:new AggregateError(e.map(v=>v.error),`${String(e.length)} scheduled jobs failed: ${e.map(v=>v.functionPath).join(", ")}`)}return d};return{controls:{advance:(t,a)=>(f+=t,b(f,a)),failures:()=>[...h],list:()=>[...u.values()],runPending:t=>b(Number.POSITIVE_INFINITY,t)},scheduler:x}},te=()=>{const s=new Z(":memory:"),i=n=>({one(){if(n.length!==1)throw new R("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:()=>{s.close()},sql:{exec:(n,...l)=>{const E=s.prepare(n).all(...l);return i(E)}}}},q=s=>{if(typeof s!="object"||s===null)return;const{kind:i}=s;if(i==="query"||i==="mutation"||i==="action")return i},re=s=>typeof s=="object"&&s!==null&&s.visibility==="internal"?"internal":"public",Q=s=>{throw new R("INTERNAL",`ctx.${s} is not available in the in-memory @lunora/testing harness (v1)`)},w=s=>new Proxy((...i)=>Q(s),{apply:()=>Q(s),get:()=>Q(s)}),U={spanId:"0000000000000001",traceId:"00000000000000000000000000000001"},ne={addEvent:()=>{},addLink:()=>{},recordEvaluation:()=>{},recordException:()=>{},setAttribute:()=>{},setAttributes:()=>{},spanContext:()=>U},oe=()=>{const s={attributes:{},events:[],links:[]},i={addEvent:(n,l)=>{s.events.push({...l===void 0?{}:{attributes:{...l}},name:n})},addLink:n=>{s.links.push({spanId:n.spanId,traceId:n.traceId})},recordEvaluation:n=>{Object.assign(s.attributes,X(n))},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)=>{s.attributes[n]=l},setAttributes:n=>{Object.assign(s.attributes,n)},spanContext:()=>U};return{handle:i,recorded:s}},M=async(s,i)=>await i(M,ne),B={count:()=>{},gauge:()=>{},record:()=>{}},O={debug:()=>{},error:()=>{},event:()=>{},fatal:()=>{},info:()=>{},log:()=>{},trace:()=>{},warn:()=>{},with:()=>O},se=(s,i,n)=>(l,E)=>{let f=!1;const g=[];let u,h,m=0,p=0;const x=()=>q(l)?s("query",l,i,E,!1):Promise.resolve(l(i)),T=(e,o)=>{if(e<p)return;p=e;const v={done:!1,value:o};if(g.length===0)u=v,h=void 0;else{u=void 0,h=void 0;for(const A of g.splice(0))A.resolve(v)}},j=(e,o)=>{if(!(e<p))if(p=e,g.length===0)h={error:o},u=void 0;else{u=void 0,h=void 0;for(const v of g.splice(0))v.reject(o)}},b=e=>o=>{T(e,o)},t=e=>o=>{j(e,o)},a=()=>{if(f)return;m+=1;const e=m;x().then(b(e)).catch(t(e))};n.add(a);const d={[Symbol.asyncIterator](){return d},next:()=>{if(f)return Promise.resolve({done:!0,value:void 0});if(p===m){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 p<m?new Promise((e,o)=>{g.push({reject:o,resolve:e})}):x().then(e=>{if(h!==void 0){const{error:o}=h;throw h=void 0,o}if(u!==void 0){const o=u;return u=void 0,o}return{done:!1,value:e}})},return:()=>{f=!0,n.delete(a);for(const e of g.splice(0))e.resolve({done:!0,value:void 0});return Promise.resolve({done:!0,value:void 0})}};return x().then(b(0)).catch(t(0)),d},le=(s,i)=>{const{close:n,sql:l}=te(),E=s;J(l,E);const f=W({enforceRls:i?.enforceRls??!0,schema:E,sql:l}),g=f[z]??f,u=y=>{l.exec.call(l,y)};let h=Promise.resolve();const m=y=>{const N=async()=>{u("BEGIN");try{const P=await y();return u("COMMIT"),P}catch(P){try{u("ROLLBACK")}catch{}throw P}},k=h.then(N);return h=k.then(()=>{},()=>{}),k};let p=!1;const x=()=>{p||(p=!0,n())},T=new Map(Object.entries(i?.functions??{}).map(([y,N])=>[y,N])),j=new Set,b=()=>{for(const y of j)y()};let t,a,d;const e=i?.now??Date.now(),o=oe(),{controls:v,scheduler:A}=ee(()=>{if(t===void 0)throw new R("INTERNAL","[fake-scheduler] dispatch not yet available — scheduler.advance called before harness construction completed");return t},()=>{if(a===void 0)throw new R("INTERNAL","[fake-scheduler] mutationContext not yet available — scheduler.advance called before harness construction completed");return a},()=>{if(d===void 0)throw new R("INTERNAL","[fake-scheduler] actionContext not yet available — scheduler.advance called before harness construction completed");return d},()=>T,e),D=y=>{const N={getIdentity:()=>Promise.resolve(y??null),userId:y?.userId??null},k={auth:N,db:f,env:i?.env,log:O,metrics:B,now:e,span:o.handle,trace:M,runQuery:((r,c)=>$("query",r,k,c)),secrets:w("secrets"),storage:w("storage"),vectors:w("vectors")},P={auth:N,db:f,env:i?.env,log:O,metrics:B,now:e,span:o.handle,trace:M,runMutation:((r,c)=>$("mutation",r,P,c)),runQuery:((r,c)=>$("query",r,k,c)),scheduler:A,secrets:w("secrets"),storage:w("storage"),vectors:w("vectors"),workflows:w("workflows")};a??=P;const K={...P,db:g},C={auth:N,db:f,env:i?.env,fetch:i?.fetch??w("fetch"),log:O,metrics:B,now:e,span:o.handle,trace:M,runAction:((r,c)=>$("action",r,C,c)),runMutation:((r,c)=>$("mutation",r,P,c)),runQuery:((r,c)=>$("query",r,k,c)),scheduler:A,secrets:w("secrets"),storage:w("storage"),vectors:w("vectors"),workflows:w("workflows")};d??=C;const L=(r,c,I,S,F)=>{const _=q(c);if(_!==r)throw new R("INTERNAL",`expected a registered ${r}, received a ${_??"non-function"} reference`);if(!F&&re(c)==="internal")throw new R("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(I,S??{}))},$=(r,c,I,S)=>L(r,c,I,S,!0);t??=(r,c,I,S)=>r==="mutation"?m(()=>L("mutation",c,I,S,!0)).then(F=>(b(),F)):$("action",c,I,S);const V=((r,c)=>q(r)?L("query",r,k,c,!1):Promise.resolve(r(k))),Y=((r,c)=>q(r)?m(()=>L("mutation",r,P,c,!1)).then(I=>(b(),I)):m(()=>r(P)).then(I=>(b(),I))),G=((r,c)=>q(r)?L("action",r,C,c,!1):Promise.resolve(r(C))),H=se(L,k,j);return{action:G,close:x,mutation:Y,query:V,run:r=>m(()=>r(K)).then(c=>(b(),c)),scheduler:v,subscribe:H,wideEvent:()=>o.recorded,withIdentity:r=>D(r)}};return D(null)};export{le as lunoraTest};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/testing",
3
- "version": "1.0.0-alpha.83",
3
+ "version": "1.0.0-alpha.84",
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.35",
54
- "@lunora/errors": "1.0.0-alpha.10",
55
- "@lunora/mail": "1.0.0-alpha.37",
56
- "@lunora/server": "1.0.0-alpha.56",
57
- "@lunora/shard-engine": "1.0.0-alpha.4"
53
+ "@lunora/agent": "1.0.0-alpha.36",
54
+ "@lunora/errors": "1.0.0-alpha.12",
55
+ "@lunora/mail": "1.0.0-alpha.39",
56
+ "@lunora/server": "1.0.0-alpha.57",
57
+ "@lunora/shard-engine": "1.0.0-alpha.6"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@playwright/test": "^1.61.1"
@@ -1 +0,0 @@
1
- import{LunoraError as S}from"@lunora/errors";import{runShardMigrations as H,createShardCtxDb as J}from"@lunora/shard-engine";import{evaluationAttributes as U}from"./evaluationAttributes-GZvtFa1N.mjs";import{DatabaseSync as W}from"node:sqlite";const Z=(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)}},T=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 T(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}},z=()=>{const o=new W(":memory:"),i=n=>({one(){if(n.length!==1)throw new S("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},X=o=>typeof o=="object"&&o!==null&&o.visibility==="internal"?"internal":"public",Q=o=>{throw new S("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"},ee={addEvent:()=>{},addLink:()=>{},recordEvaluation:()=>{},recordException:()=>{},setAttribute:()=>{},setAttributes:()=>{},spanContext:()=>K},te=()=>{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})},recordEvaluation:n=>{Object.assign(o.attributes,U(n))},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}},F=async(o,i)=>await i(F,ee),B={count:()=>{},gauge:()=>{},record:()=>{}},M={debug:()=>{},error:()=>{},event:()=>{},fatal:()=>{},info:()=>{},log:()=>{},trace:()=>{},warn:()=>{},with:()=>M},re=(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)),T=(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=>{T(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},ce=(o,i)=>{const{close:n,sql:l}=z(),x=o;H(l,x);const m=J({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,$])),T=new Set,E=()=>{for(const b of T)b()};let I,t,a;const d=i?.now??Date.now(),e=te(),{controls:s,scheduler:f}=Z(()=>{if(I===void 0)throw new S("INTERNAL","[fake-scheduler] dispatch not yet available — scheduler.advance called before harness construction completed");return I},()=>{if(t===void 0)throw new S("INTERNAL","[fake-scheduler] mutationContext not yet available — scheduler.advance called before harness construction completed");return t},()=>{if(a===void 0)throw new S("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:M,metrics:B,now:d,span:e.handle,trace:F,runQuery:((r,c)=>j("query",r,k,c)),secrets:p("secrets"),storage:p("storage"),vectors:p("vectors")},y={auth:$,db:m,env:i?.env,log:M,metrics:B,now:d,span:e.handle,trace:F,runMutation:((r,c)=>j("mutation",r,y,c)),runQuery:((r,c)=>j("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:M,metrics:B,now:d,span:e.handle,trace:F,runAction:((r,c)=>j("action",r,R,c)),runMutation:((r,c)=>j("mutation",r,y,c)),runQuery:((r,c)=>j("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 S("INTERNAL",`expected a registered ${r}, received a ${D??"non-function"} reference`);if(!O&&X(c)==="internal")throw new S("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??{}))},j=(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)):j("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))),G=re(q,k,T);return{action:_,close:w,mutation:Y,query:V,run:r=>h(()=>r(y)).then(c=>(E(),c)),scheduler:s,subscribe:G,wideEvent:()=>e.recorded,withIdentity:r=>N(r)}};return N(null)};export{ce as lunoraTest};