@lunora/platform 1.0.0-alpha.54 → 1.0.0-alpha.56

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.
@@ -1,2 +1,2 @@
1
1
  export { type C as ConformanceHost, type a as ConformanceHostFactory, type R as ReferenceHost, type VitestApi, c as createReferenceHost, defineHostContractSuite } from "./suite.mjs";
2
- import "../packem_shared/socket-host.d-BsnGTydH.mjs";
2
+ import "../packem_shared/socket-host.d-VUTw8RC_.mjs";
@@ -1,2 +1,2 @@
1
1
  export { type C as ConformanceHost, type a as ConformanceHostFactory, type R as ReferenceHost, type VitestApi, c as createReferenceHost, defineHostContractSuite } from "./suite.js";
2
- import "../packem_shared/socket-host.d-BsnGTydH.js";
2
+ import "../packem_shared/socket-host.d-VUTw8RC_.js";
@@ -1,4 +1,4 @@
1
- import { e as ShardDirectory, i as ShardKvStore, n as SocketHandle, c as SchedulerHost, f as ShardHost, o as SocketHost } from "../packem_shared/socket-host.d-BsnGTydH.mjs";
1
+ import { e as ShardDirectory, i as ShardKvStore, n as SocketHandle, c as SchedulerHost, f as ShardHost, o as SocketHost } from "../packem_shared/socket-host.d-VUTw8RC_.mjs";
2
2
  /**
3
3
  * A conformance host bundles all four platform contracts so a single factory
4
4
  * can stand up a complete, isolated test environment.
@@ -1,4 +1,4 @@
1
- import { e as ShardDirectory, i as ShardKvStore, n as SocketHandle, c as SchedulerHost, f as ShardHost, o as SocketHost } from "../packem_shared/socket-host.d-BsnGTydH.js";
1
+ import { e as ShardDirectory, i as ShardKvStore, n as SocketHandle, c as SchedulerHost, f as ShardHost, o as SocketHost } from "../packem_shared/socket-host.d-VUTw8RC_.js";
2
2
  /**
3
3
  * A conformance host bundles all four platform contracts so a single factory
4
4
  * can stand up a complete, isolated test environment.
@@ -1 +1 @@
1
- import{resolveShard as E}from"../packem_shared/resolveShard-BzKOUEO4.mjs";const f=/platform closed/u,h=d=>new Promise(k=>{setTimeout(k,d)}),b=(d,k,v)=>{const{describe:w,expect:a,it:o}=v;w(`host contract: ${d}`,()=>{const p=async()=>k(),l=t=>t.createSocket?.()??{},c=async t=>{const e=await p();try{await t(e)}finally{e.cleanup?.()}};w("ShardHost",()=>{o("serializes mutations so no two closures interleave",async()=>{a.assertions(1),await c(async t=>{const e=[];await Promise.all([t.shard.runSerialized(async()=>{e.push("a-start"),await new Promise(i=>{setTimeout(i,10)}),e.push("a-end")}),t.shard.runSerialized(async()=>{e.push("b-start"),await new Promise(i=>{setTimeout(i,5)}),e.push("b-end")})]);const s=e.join("").includes("a-starta-end"),r=e.join("").includes("b-startb-end");a(s&&r).toBe(!0)})}),o("rolls back a transaction that throws",async()=>{a.assertions(2),await c(async t=>{await t.shard.transaction(async()=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS rollback_test (id INTEGER PRIMARY KEY)"),t.shard.sql.exec("INSERT INTO rollback_test (id) VALUES (1)")}),await a(t.shard.transaction(async()=>{throw t.shard.sql.exec("INSERT INTO rollback_test (id) VALUES (2)"),new Error("boom")})).rejects.toThrow("boom");const e=t.shard.sql.exec("SELECT id FROM rollback_test WHERE id = 2").toArray();a(e).toHaveLength(0)})}),o("never lets a task outside a mutation observe its uncommitted writes",async t=>{a.hasAssertions(),await c(async e=>{if(e.isolatesByDispatch===!0){t.skip(`${d} isolates concurrent tasks at the dispatch boundary, which no in-isolate read can stand in for`);return}e.shard.sql.exec("CREATE TABLE IF NOT EXISTS isolation_test (id INTEGER PRIMARY KEY)");let s;const r=new Promise(m=>{s=m}),i=e.shard.runSerialized(async()=>e.shard.transaction(async()=>{throw e.shard.sql.exec("INSERT INTO isolation_test (id) VALUES (1)"),s(),await h(20),new Error("boom")}));await r;let n,u;try{n=e.shard.sql.exec("SELECT id FROM isolation_test").toArray()}catch(m){n=[],u=m}a(n).toStrictEqual([]),u!==void 0&&a(u).toMatchObject({code:"SHARD_UNAVAILABLE",status:503,type:"VisulimaError"}),await a(i).rejects.toThrow("boom"),a(e.shard.sql.exec("SELECT id FROM isolation_test").toArray()).toStrictEqual([])})}),o("rejects with the value the closure threw, and stays usable",async()=>{a.assertions(3),await c(async t=>{const e=Object.assign(new Error("closure failed"),{code:"NOT_FOUND",status:404});await a(t.shard.transaction(()=>Promise.reject(e))).rejects.toBe(e),await a(t.shard.runSerialized(()=>Promise.reject(e))).rejects.toBe(e),await a(t.shard.runSerialized(async()=>t.shard.transaction(async()=>"still here"))).resolves.toBe("still here")})}),o("observes its own writes inside a transaction",async()=>{a.assertions(1),await c(async t=>{const e=await t.shard.transaction(async()=>(t.shard.sql.exec("CREATE TABLE IF NOT EXISTS ryw_test (id INTEGER PRIMARY KEY, value TEXT)"),t.shard.sql.exec("INSERT INTO ryw_test (id, value) VALUES (1, 'hello')"),t.shard.sql.exec("SELECT value FROM ryw_test WHERE id = 1").toArray()[0]?.value));a(e).toBe("hello")})}),o("keeps overlapping transactions atomic",async()=>{a.assertions(2),await c(async t=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS overlap_test (id TEXT PRIMARY KEY)");const e=t.shard.transaction(async()=>{t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('A')"),await new Promise(i=>{setTimeout(i,20)}),t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('B')")}),s=t.shard.transaction(async()=>{t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('C')")});await Promise.all([e,s]);const r=t.shard.sql.exec("SELECT id FROM overlap_test ORDER BY id").toArray();a(r.map(i=>i.id)).toStrictEqual(["A","B","C"]),await a(t.shard.transaction(async()=>{throw t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('D')"),new Error("boom")})).rejects.toThrow("boom")})}),o("returns a cursor that buffers, yields one row, and iterates",async()=>{a.assertions(3),await c(async t=>{await t.shard.transaction(async()=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS cursor_test (id INTEGER PRIMARY KEY)"),t.shard.sql.exec("INSERT INTO cursor_test (id) VALUES (1)"),t.shard.sql.exec("INSERT INTO cursor_test (id) VALUES (2)")}),a(t.shard.sql.exec("SELECT id FROM cursor_test ORDER BY id").toArray()).toHaveLength(2),a(t.shard.sql.exec("SELECT id FROM cursor_test WHERE id = 1").one()).toEqual({id:1}),a([...t.shard.sql.exec("SELECT id FROM cursor_test ORDER BY id")]).toHaveLength(2)})}),o("reports a pending alarm, and clears it once fired",async()=>{a.assertions(2),await c(async t=>{const e=Date.now()+50;if(await t.shard.alarms.set(e),a(await t.shard.alarms.get()).toBe(e),t.awaitAlarmFired===void 0){a(await t.shard.alarms.get()).toBe(e);return}await t.awaitAlarmFired(e),a(await t.shard.alarms.get()).toBeNull()})}),o("deletes a pending alarm",async()=>{a.assertions(1),await c(async t=>{await t.shard.alarms.set(Date.now()+1e4),await t.shard.alarms.delete(),a(await t.shard.alarms.get()).toBeNull()})})}),w("SocketHost",()=>{o("accepts a socket and can send/close",async()=>{await c(async t=>{const e=t.socket.accept(l(t),{user:"ada"});a(t.socket.idFor(e)).toBeDefined(),e.send("hello"),a(t.socket.getSockets().map(s=>t.socket.idFor(s))).toContain(t.socket.idFor(e)),t.readFrames!==void 0&&a(t.readFrames(e)).toStrictEqual(["hello"]),a(()=>{e.close(1e3,"done")}).not.toThrow()})}),o("round-trips an attachment on a live socket",async()=>{a.assertions(1),await c(async t=>{const e={roomId:"room-1",roles:["admin"]},s=t.socket.accept(l(t),e);a(s.deserializeAttachment()).toEqual(e)})}),o("round-trips attachments across a recycle",async t=>{await c(async e=>{if(e.simulateRecycle===void 0||e.restoreSocket===void 0){t.skip(`${d} does not implement simulateRecycle/restoreSocket`);return}a.assertions(1);const s={roomId:"room-1",roles:["admin"]},r=e.socket.accept(l(e),s);e.simulateRecycle();const i=e.restoreSocket(e.socket.idFor(r),s);a(i.deserializeAttachment()).toEqual(s)})}),o("keeps idFor stable across repeated calls within a wake",async()=>{a.assertions(1),await c(async t=>{const e=t.socket.accept(l(t),{}),s=t.socket.idFor(e),r=t.socket.idFor(e);a(r).toBe(s)})}),o("keeps idFor stable across a recycle",async t=>{await c(async e=>{if(e.simulateRecycle===void 0||e.restoreSocket===void 0){t.skip(`${d} does not implement simulateRecycle/restoreSocket`);return}a.assertions(1);const s={roomId:"room-1",roles:["admin"]},r=e.socket.accept(l(e),s),i=e.socket.idFor(r);e.simulateRecycle();const n=e.restoreSocket(i,s);a(e.socket.idFor(n)).toBe(i)})}),o("returns exactly the sockets carrying an accept-time tag",async()=>{a.assertions(4),await c(async t=>{const e=t.socket.accept(l(t),{},["room-a"]),s=t.socket.accept(l(t),{},["room-b"]),r=t.socket.accept(l(t),{}),i=n=>t.socket.idFor(n);a(t.socket.getSockets("room-a").map(i)).toStrictEqual([i(e)]),a(t.socket.getSockets("room-b").map(i)).toStrictEqual([i(s)]),a(t.socket.getSockets("room-c").map(i)).toStrictEqual([]),a(t.socket.getSockets().map(i)).toContain(i(r))})}),o("accepts the portable budget of nine caller tags",async()=>{a.assertions(9);const t=await p(),e=Array.from({length:9},(i,n)=>`tag-${String(n)}`),s=t.socket.accept(l(t),{},e),r=i=>t.socket.idFor(i);for(const i of e)a(t.socket.getSockets(i).map(r)).toStrictEqual([r(s)]);t.cleanup?.()}),o("resolves a raw socket back to its handle",async()=>{a.assertions(2),await c(async t=>{const e=l(t),s=t.socket.accept(e,{}),r=t.socket.handleFor(e);a(r!==void 0&&t.socket.idFor(r)).toBe(t.socket.idFor(s)),a(t.socket.handleFor(l(t))).toBeUndefined()})}),o("reports a plausible outbound queue depth, if any",async()=>{a.assertions(1),await c(async t=>{const e=t.socket.accept(l(t),{}),{bufferedAmount:s}=e;a(s===void 0||typeof s=="number"&&s>=0).toBe(!0)})}),o("retags a live socket when the host declares mutable tags",async t=>{await c(async e=>{if(e.socket.setTag===void 0){t.skip(`${d} does not implement mutable socket tags (setTag)`);return}a.assertions(2);const s=e.socket.accept(l(e),{});e.socket.setTag(s,"room-a"),a(e.socket.getSockets("room-a").map(r=>e.socket.idFor(r))).toStrictEqual([e.socket.idFor(s)]),e.socket.removeTag?.(s,"room-a"),a(e.socket.getSockets("room-a").map(r=>e.socket.idFor(r))).toStrictEqual([])})})}),w("ShardDirectory",()=>{o("resolves shard keys deterministically",async()=>{a.assertions(1),await c(async t=>{const e=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/")),s=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/"));await a(e.text()).resolves.toBe(await s.text())})}),o("dispatches fetch to a resolved stub",async()=>{a.assertions(1),await c(async t=>{const s=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/"));a(s).toBeInstanceOf(Response)})})}),w("SchedulerHost",()=>{o("schedules a job for a future timestamp",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(2);const s=Date.now(),r=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:50});a(r.scheduledFor).toBeGreaterThanOrEqual(s+50),a(r.id).toBeDefined()})}),o("dispatches a scheduled job at least once",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}const s=e.scheduler.deadLetter!==void 0;if(!s&&e.awaitJobDispatched===void 0){t.skip(`${d} does not claim at-least-once delivery (no deadLetter) and supplies no awaitJobDispatched hook`);return}if(s&&a(e.awaitJobDispatched).toBeDefined(),e.awaitJobDispatched===void 0)return;const r=e.scheduler.list!==void 0;a.assertions(1+(s?1:0)+(r?1:0));const i=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:30});if(await a(e.awaitJobDispatched(i.id)).resolves.toBe(!0),r){const n=await e.scheduler.list?.();a(n?.some(u=>u.id===i.id)).toBe(!1)}})}),o("cancels a scheduled job",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(1);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4}),r=await e.scheduler.cancel(s.id);a(r).toBe(!0)})}),o("reports a second cancel of the same job as false",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});a(await e.scheduler.cancel(s.id)).toBe(!0),a(await e.scheduler.cancel(s.id)).toBe(!1)})}),o("gives two identical schedules independently cancellable ids",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(3);const s=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4}),r=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4});a(r.id).not.toBe(s.id),a(await e.scheduler.cancel(s.id)).toBe(!0),a(await e.scheduler.cancel(r.id)).toBe(!0)})}),o("lists a pending job with a zero attempt count",async t=>{await c(async e=>{if(e.scheduler?.list===void 0){t.skip(`${d} does not implement SchedulerHost.list`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4}),i=(await e.scheduler.list()).find(n=>n.id===s.id);a(i?.functionPath).toBe("tasks/remind"),a(i?.attempts).toBe(0)})}),o("keeps the pending and dead-letter listings disjoint",async t=>{await c(async e=>{if(e.scheduler?.list===void 0||e.scheduler.deadLetter===void 0||e.simulateDeadLetter===void 0){t.skip(`${d} does not implement scheduler.list/deadLetter/simulateDeadLetter`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});await e.simulateDeadLetter(s.id);const r=await e.scheduler.list(),i=await e.scheduler.deadLetter.list();a(r.some(n=>n.id===s.id)).toBe(!1),a(i.some(n=>n.id===s.id)).toBe(!0)})}),o("returns a requeued job to the pending set with a fresh budget",async t=>{await c(async e=>{if(e.scheduler?.list===void 0||e.scheduler.deadLetter===void 0||e.simulateDeadLetter===void 0){t.skip(`${d} does not implement scheduler.list/deadLetter/simulateDeadLetter`);return}a.assertions(4);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});await e.simulateDeadLetter(s.id),a(await e.scheduler.deadLetter.requeue(s.id)).toBe(!0);const i=(await e.scheduler.list()).find(u=>u.id===s.id);a(i).toBeDefined(),a(i?.attempts).toBe(0);const n=await e.scheduler.deadLetter.list();a(n.some(u=>u.id===s.id)).toBe(!1)})}),o("reports a requeue of an unparked job as false",async t=>{await c(async e=>{if(e.scheduler?.deadLetter===void 0){t.skip(`${d} does not implement scheduler.deadLetter`);return}a.assertions(1),a(await e.scheduler.deadLetter.requeue("job-does-not-exist")).toBe(!1)})}),o("ticks a cron on schedule, and not before its next occurrence",async t=>{await c(async e=>{if(e.scheduler?.cron===void 0){t.skip(`${d} does not implement SchedulerHost.cron`);return}const{cronTicks:s}=e;if(s===void 0){a.fail(`${d} declares SchedulerHost.cron but no cronTicks — presence of cron is the claim that dynamic cron works, so it must be observable`);return}a.assertions(2),await e.scheduler.cron("* * * * * *","tasks/tick");const r=(new Date().getMonth()+6)%12+1;await e.scheduler.cron(`0 0 1 ${String(r)} *`,"tasks/far"),await new Promise(i=>{setTimeout(i,1200)}),a(s("tasks/tick")).toBeGreaterThanOrEqual(1),a(s("tasks/far")).toBe(0)})})}),w("ShardKvStore",()=>{o("reads back a written value",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(2),await e.kv.put("s:token-1",{userId:"ada"}),a(await e.kv.get("s:token-1")).toEqual({userId:"ada"}),a(await e.kv.get("s:missing")).toBeUndefined()})}),o("deletes a key idempotently",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(3),await e.kv.put("k",1),a(await e.kv.delete("k")).toBe(!0),a(await e.kv.delete("k")).toBe(!1),a(await e.kv.get("k")).toBeUndefined()})}),o("enumerates exactly the keys under a prefix",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(2),await e.kv.put("s:a",1),await e.kv.put("s:b",2),await e.kv.put("other",3);const s=await e.kv.list({prefix:"s:"}),r=await e.kv.list();a([...s.keys()].toSorted((i,n)=>i.localeCompare(n))).toStrictEqual(["s:a","s:b"]),a(r.size).toBe(3)})})}),w("post-dispose",()=>{o("fails closed on every documented surface once the host is terminally disposed",async t=>{const e=await p();if(e.disposeTerminally===void 0){t.skip(`${d} has no terminal dispose the suite can drive from inside a test`);return}const{removeTag:s,setTag:r}=e.socket,{scheduler:i}=e,n=e.socket.accept(l(e),{}),u=l(e);e.disposeTerminally();const m=async y=>{await a(async()=>{await y()}).rejects.toThrow(f)},S=[()=>e.shard.alarms.set(Date.now()+1e3),()=>e.shard.alarms.delete(),()=>e.socket.accept(u,{}),...r===void 0?[]:[()=>{r(n,"room-a")}],...s===void 0?[]:[()=>{s(n,"room-a")}],...i===void 0?[]:[()=>i.schedule("tasks/remind",{},{delayMs:10})]];a.assertions(S.length);for(const y of S)await m(y)})})})};export{b as defineHostContractSuite};
1
+ import{resolveShard as E}from"../packem_shared/LUNORA_ROLE_PREFIX-Da37XHgu.mjs";const f=/platform closed/u,h=d=>new Promise(k=>{setTimeout(k,d)}),b=(d,k,v)=>{const{describe:w,expect:a,it:o}=v;w(`host contract: ${d}`,()=>{const p=async()=>k(),l=t=>t.createSocket?.()??{},c=async t=>{const e=await p();try{await t(e)}finally{e.cleanup?.()}};w("ShardHost",()=>{o("serializes mutations so no two closures interleave",async()=>{a.assertions(1),await c(async t=>{const e=[];await Promise.all([t.shard.runSerialized(async()=>{e.push("a-start"),await new Promise(i=>{setTimeout(i,10)}),e.push("a-end")}),t.shard.runSerialized(async()=>{e.push("b-start"),await new Promise(i=>{setTimeout(i,5)}),e.push("b-end")})]);const s=e.join("").includes("a-starta-end"),r=e.join("").includes("b-startb-end");a(s&&r).toBe(!0)})}),o("rolls back a transaction that throws",async()=>{a.assertions(2),await c(async t=>{await t.shard.transaction(async()=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS rollback_test (id INTEGER PRIMARY KEY)"),t.shard.sql.exec("INSERT INTO rollback_test (id) VALUES (1)")}),await a(t.shard.transaction(async()=>{throw t.shard.sql.exec("INSERT INTO rollback_test (id) VALUES (2)"),new Error("boom")})).rejects.toThrow("boom");const e=t.shard.sql.exec("SELECT id FROM rollback_test WHERE id = 2").toArray();a(e).toHaveLength(0)})}),o("never lets a task outside a mutation observe its uncommitted writes",async t=>{a.hasAssertions(),await c(async e=>{if(e.isolatesByDispatch===!0){t.skip(`${d} isolates concurrent tasks at the dispatch boundary, which no in-isolate read can stand in for`);return}e.shard.sql.exec("CREATE TABLE IF NOT EXISTS isolation_test (id INTEGER PRIMARY KEY)");let s;const r=new Promise(m=>{s=m}),i=e.shard.runSerialized(async()=>e.shard.transaction(async()=>{throw e.shard.sql.exec("INSERT INTO isolation_test (id) VALUES (1)"),s(),await h(20),new Error("boom")}));await r;let n,u;try{n=e.shard.sql.exec("SELECT id FROM isolation_test").toArray()}catch(m){n=[],u=m}a(n).toStrictEqual([]),u!==void 0&&a(u).toMatchObject({code:"SHARD_UNAVAILABLE",status:503,type:"VisulimaError"}),await a(i).rejects.toThrow("boom"),a(e.shard.sql.exec("SELECT id FROM isolation_test").toArray()).toStrictEqual([])})}),o("rejects with the value the closure threw, and stays usable",async()=>{a.assertions(3),await c(async t=>{const e=Object.assign(new Error("closure failed"),{code:"NOT_FOUND",status:404});await a(t.shard.transaction(()=>Promise.reject(e))).rejects.toBe(e),await a(t.shard.runSerialized(()=>Promise.reject(e))).rejects.toBe(e),await a(t.shard.runSerialized(async()=>t.shard.transaction(async()=>"still here"))).resolves.toBe("still here")})}),o("observes its own writes inside a transaction",async()=>{a.assertions(1),await c(async t=>{const e=await t.shard.transaction(async()=>(t.shard.sql.exec("CREATE TABLE IF NOT EXISTS ryw_test (id INTEGER PRIMARY KEY, value TEXT)"),t.shard.sql.exec("INSERT INTO ryw_test (id, value) VALUES (1, 'hello')"),t.shard.sql.exec("SELECT value FROM ryw_test WHERE id = 1").toArray()[0]?.value));a(e).toBe("hello")})}),o("keeps overlapping transactions atomic",async()=>{a.assertions(2),await c(async t=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS overlap_test (id TEXT PRIMARY KEY)");const e=t.shard.transaction(async()=>{t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('A')"),await new Promise(i=>{setTimeout(i,20)}),t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('B')")}),s=t.shard.transaction(async()=>{t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('C')")});await Promise.all([e,s]);const r=t.shard.sql.exec("SELECT id FROM overlap_test ORDER BY id").toArray();a(r.map(i=>i.id)).toStrictEqual(["A","B","C"]),await a(t.shard.transaction(async()=>{throw t.shard.sql.exec("INSERT INTO overlap_test (id) VALUES ('D')"),new Error("boom")})).rejects.toThrow("boom")})}),o("returns a cursor that buffers, yields one row, and iterates",async()=>{a.assertions(3),await c(async t=>{await t.shard.transaction(async()=>{t.shard.sql.exec("CREATE TABLE IF NOT EXISTS cursor_test (id INTEGER PRIMARY KEY)"),t.shard.sql.exec("INSERT INTO cursor_test (id) VALUES (1)"),t.shard.sql.exec("INSERT INTO cursor_test (id) VALUES (2)")}),a(t.shard.sql.exec("SELECT id FROM cursor_test ORDER BY id").toArray()).toHaveLength(2),a(t.shard.sql.exec("SELECT id FROM cursor_test WHERE id = 1").one()).toEqual({id:1}),a([...t.shard.sql.exec("SELECT id FROM cursor_test ORDER BY id")]).toHaveLength(2)})}),o("reports a pending alarm, and clears it once fired",async()=>{a.assertions(2),await c(async t=>{const e=Date.now()+50;if(await t.shard.alarms.set(e),a(await t.shard.alarms.get()).toBe(e),t.awaitAlarmFired===void 0){a(await t.shard.alarms.get()).toBe(e);return}await t.awaitAlarmFired(e),a(await t.shard.alarms.get()).toBeNull()})}),o("deletes a pending alarm",async()=>{a.assertions(1),await c(async t=>{await t.shard.alarms.set(Date.now()+1e4),await t.shard.alarms.delete(),a(await t.shard.alarms.get()).toBeNull()})})}),w("SocketHost",()=>{o("accepts a socket and can send/close",async()=>{await c(async t=>{const e=t.socket.accept(l(t),{user:"ada"});a(t.socket.idFor(e)).toBeDefined(),e.send("hello"),a(t.socket.getSockets().map(s=>t.socket.idFor(s))).toContain(t.socket.idFor(e)),t.readFrames!==void 0&&a(t.readFrames(e)).toStrictEqual(["hello"]),a(()=>{e.close(1e3,"done")}).not.toThrow()})}),o("round-trips an attachment on a live socket",async()=>{a.assertions(1),await c(async t=>{const e={roomId:"room-1",roles:["admin"]},s=t.socket.accept(l(t),e);a(s.deserializeAttachment()).toEqual(e)})}),o("round-trips attachments across a recycle",async t=>{await c(async e=>{if(e.simulateRecycle===void 0||e.restoreSocket===void 0){t.skip(`${d} does not implement simulateRecycle/restoreSocket`);return}a.assertions(1);const s={roomId:"room-1",roles:["admin"]},r=e.socket.accept(l(e),s);e.simulateRecycle();const i=e.restoreSocket(e.socket.idFor(r),s);a(i.deserializeAttachment()).toEqual(s)})}),o("keeps idFor stable across repeated calls within a wake",async()=>{a.assertions(1),await c(async t=>{const e=t.socket.accept(l(t),{}),s=t.socket.idFor(e),r=t.socket.idFor(e);a(r).toBe(s)})}),o("keeps idFor stable across a recycle",async t=>{await c(async e=>{if(e.simulateRecycle===void 0||e.restoreSocket===void 0){t.skip(`${d} does not implement simulateRecycle/restoreSocket`);return}a.assertions(1);const s={roomId:"room-1",roles:["admin"]},r=e.socket.accept(l(e),s),i=e.socket.idFor(r);e.simulateRecycle();const n=e.restoreSocket(i,s);a(e.socket.idFor(n)).toBe(i)})}),o("returns exactly the sockets carrying an accept-time tag",async()=>{a.assertions(4),await c(async t=>{const e=t.socket.accept(l(t),{},["room-a"]),s=t.socket.accept(l(t),{},["room-b"]),r=t.socket.accept(l(t),{}),i=n=>t.socket.idFor(n);a(t.socket.getSockets("room-a").map(i)).toStrictEqual([i(e)]),a(t.socket.getSockets("room-b").map(i)).toStrictEqual([i(s)]),a(t.socket.getSockets("room-c").map(i)).toStrictEqual([]),a(t.socket.getSockets().map(i)).toContain(i(r))})}),o("accepts the portable budget of nine caller tags",async()=>{a.assertions(9);const t=await p(),e=Array.from({length:9},(i,n)=>`tag-${String(n)}`),s=t.socket.accept(l(t),{},e),r=i=>t.socket.idFor(i);for(const i of e)a(t.socket.getSockets(i).map(r)).toStrictEqual([r(s)]);t.cleanup?.()}),o("resolves a raw socket back to its handle",async()=>{a.assertions(2),await c(async t=>{const e=l(t),s=t.socket.accept(e,{}),r=t.socket.handleFor(e);a(r!==void 0&&t.socket.idFor(r)).toBe(t.socket.idFor(s)),a(t.socket.handleFor(l(t))).toBeUndefined()})}),o("reports a plausible outbound queue depth, if any",async()=>{a.assertions(1),await c(async t=>{const e=t.socket.accept(l(t),{}),{bufferedAmount:s}=e;a(s===void 0||typeof s=="number"&&s>=0).toBe(!0)})}),o("retags a live socket when the host declares mutable tags",async t=>{await c(async e=>{if(e.socket.setTag===void 0){t.skip(`${d} does not implement mutable socket tags (setTag)`);return}a.assertions(2);const s=e.socket.accept(l(e),{});e.socket.setTag(s,"room-a"),a(e.socket.getSockets("room-a").map(r=>e.socket.idFor(r))).toStrictEqual([e.socket.idFor(s)]),e.socket.removeTag?.(s,"room-a"),a(e.socket.getSockets("room-a").map(r=>e.socket.idFor(r))).toStrictEqual([])})})}),w("ShardDirectory",()=>{o("resolves shard keys deterministically",async()=>{a.assertions(1),await c(async t=>{const e=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/")),s=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/"));await a(e.text()).resolves.toBe(await s.text())})}),o("dispatches fetch to a resolved stub",async()=>{a.assertions(1),await c(async t=>{const s=await E(t.directory,"tenant-42").fetch(new Request("http://localhost/"));a(s).toBeInstanceOf(Response)})})}),w("SchedulerHost",()=>{o("schedules a job for a future timestamp",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(2);const s=Date.now(),r=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:50});a(r.scheduledFor).toBeGreaterThanOrEqual(s+50),a(r.id).toBeDefined()})}),o("dispatches a scheduled job at least once",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}const s=e.scheduler.deadLetter!==void 0;if(!s&&e.awaitJobDispatched===void 0){t.skip(`${d} does not claim at-least-once delivery (no deadLetter) and supplies no awaitJobDispatched hook`);return}if(s&&a(e.awaitJobDispatched).toBeDefined(),e.awaitJobDispatched===void 0)return;const r=e.scheduler.list!==void 0;a.assertions(1+(s?1:0)+(r?1:0));const i=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:30});if(await a(e.awaitJobDispatched(i.id)).resolves.toBe(!0),r){const n=await e.scheduler.list?.();a(n?.some(u=>u.id===i.id)).toBe(!1)}})}),o("cancels a scheduled job",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(1);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4}),r=await e.scheduler.cancel(s.id);a(r).toBe(!0)})}),o("reports a second cancel of the same job as false",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});a(await e.scheduler.cancel(s.id)).toBe(!0),a(await e.scheduler.cancel(s.id)).toBe(!1)})}),o("gives two identical schedules independently cancellable ids",async t=>{await c(async e=>{if(e.scheduler===void 0){t.skip(`${d} does not implement SchedulerHost`);return}a.assertions(3);const s=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4}),r=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4});a(r.id).not.toBe(s.id),a(await e.scheduler.cancel(s.id)).toBe(!0),a(await e.scheduler.cancel(r.id)).toBe(!0)})}),o("lists a pending job with a zero attempt count",async t=>{await c(async e=>{if(e.scheduler?.list===void 0){t.skip(`${d} does not implement SchedulerHost.list`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{user:"ada"},{delayMs:1e4}),i=(await e.scheduler.list()).find(n=>n.id===s.id);a(i?.functionPath).toBe("tasks/remind"),a(i?.attempts).toBe(0)})}),o("keeps the pending and dead-letter listings disjoint",async t=>{await c(async e=>{if(e.scheduler?.list===void 0||e.scheduler.deadLetter===void 0||e.simulateDeadLetter===void 0){t.skip(`${d} does not implement scheduler.list/deadLetter/simulateDeadLetter`);return}a.assertions(2);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});await e.simulateDeadLetter(s.id);const r=await e.scheduler.list(),i=await e.scheduler.deadLetter.list();a(r.some(n=>n.id===s.id)).toBe(!1),a(i.some(n=>n.id===s.id)).toBe(!0)})}),o("returns a requeued job to the pending set with a fresh budget",async t=>{await c(async e=>{if(e.scheduler?.list===void 0||e.scheduler.deadLetter===void 0||e.simulateDeadLetter===void 0){t.skip(`${d} does not implement scheduler.list/deadLetter/simulateDeadLetter`);return}a.assertions(4);const s=await e.scheduler.schedule("tasks/remind",{},{delayMs:1e4});await e.simulateDeadLetter(s.id),a(await e.scheduler.deadLetter.requeue(s.id)).toBe(!0);const i=(await e.scheduler.list()).find(u=>u.id===s.id);a(i).toBeDefined(),a(i?.attempts).toBe(0);const n=await e.scheduler.deadLetter.list();a(n.some(u=>u.id===s.id)).toBe(!1)})}),o("reports a requeue of an unparked job as false",async t=>{await c(async e=>{if(e.scheduler?.deadLetter===void 0){t.skip(`${d} does not implement scheduler.deadLetter`);return}a.assertions(1),a(await e.scheduler.deadLetter.requeue("job-does-not-exist")).toBe(!1)})}),o("ticks a cron on schedule, and not before its next occurrence",async t=>{await c(async e=>{if(e.scheduler?.cron===void 0){t.skip(`${d} does not implement SchedulerHost.cron`);return}const{cronTicks:s}=e;if(s===void 0){a.fail(`${d} declares SchedulerHost.cron but no cronTicks — presence of cron is the claim that dynamic cron works, so it must be observable`);return}a.assertions(2),await e.scheduler.cron("* * * * * *","tasks/tick");const r=(new Date().getMonth()+6)%12+1;await e.scheduler.cron(`0 0 1 ${String(r)} *`,"tasks/far"),await new Promise(i=>{setTimeout(i,1200)}),a(s("tasks/tick")).toBeGreaterThanOrEqual(1),a(s("tasks/far")).toBe(0)})})}),w("ShardKvStore",()=>{o("reads back a written value",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(2),await e.kv.put("s:token-1",{userId:"ada"}),a(await e.kv.get("s:token-1")).toEqual({userId:"ada"}),a(await e.kv.get("s:missing")).toBeUndefined()})}),o("deletes a key idempotently",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(3),await e.kv.put("k",1),a(await e.kv.delete("k")).toBe(!0),a(await e.kv.delete("k")).toBe(!1),a(await e.kv.get("k")).toBeUndefined()})}),o("enumerates exactly the keys under a prefix",async t=>{await c(async e=>{if(e.kv===void 0){t.skip(`${d} does not implement ShardKvStore`);return}a.assertions(2),await e.kv.put("s:a",1),await e.kv.put("s:b",2),await e.kv.put("other",3);const s=await e.kv.list({prefix:"s:"}),r=await e.kv.list();a([...s.keys()].toSorted((i,n)=>i.localeCompare(n))).toStrictEqual(["s:a","s:b"]),a(r.size).toBe(3)})})}),w("post-dispose",()=>{o("fails closed on every documented surface once the host is terminally disposed",async t=>{const e=await p();if(e.disposeTerminally===void 0){t.skip(`${d} has no terminal dispose the suite can drive from inside a test`);return}const{removeTag:s,setTag:r}=e.socket,{scheduler:i}=e,n=e.socket.accept(l(e),{}),u=l(e);e.disposeTerminally();const m=async y=>{await a(async()=>{await y()}).rejects.toThrow(f)},S=[()=>e.shard.alarms.set(Date.now()+1e3),()=>e.shard.alarms.delete(),()=>e.socket.accept(u,{}),...r===void 0?[]:[()=>{r(n,"room-a")}],...s===void 0?[]:[()=>{s(n,"room-a")}],...i===void 0?[]:[()=>i.schedule("tasks/remind",{},{delayMs:10})]];a.assertions(S.length);for(const y of S)await m(y)})})})};export{b as defineHostContractSuite};
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-BsnGTydH.mjs";
1
+ export { type D as DirectShardDirectory, L as LUNORA_ROLE_PREFIX, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-VUTw8RC_.mjs";
2
2
  /**
3
3
  * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
4
4
  * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
@@ -913,7 +913,8 @@ interface PlatformCapabilities {
913
913
  * tracing shows one nested tree instead of two unrelated ones — the
914
914
  * sink's `fuseCloudflareTraces` opt-in, which reaches `cloudflare:workers`'
915
915
  * `tracing.enterSpan` (plus, where present, `getActiveSpan` for the
916
- * invocation root and a span's `setAttributes` / `recordException`).
916
+ * invocation root and a span's `setAttributes` / `recordException` /
917
+ * `setStatus`).
917
918
  *
918
919
  * Rated because it is the one telemetry surface that reaches past
919
920
  * `ShardHost` into a provider API. Everything else in the pipeline is
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type D as DirectShardDirectory, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-BsnGTydH.js";
1
+ export { type D as DirectShardDirectory, L as LUNORA_ROLE_PREFIX, type S as ScheduleOptions, type a as ScheduledJob, type b as ScheduledJobStatus, type c as SchedulerHost, type d as ShardAlarms, type e as ShardDirectory, type f as ShardHost, type g as ShardJurisdiction, type h as ShardKvListOptions, type i as ShardKvStore, type j as ShardRegionHint, type k as ShardSqlCursor, type l as ShardSqlExec, type m as ShardStub, type n as SocketHandle, type o as SocketHost, type p as SqlRow, type T as TwoStepShardDirectory, r as resolveShard } from "./packem_shared/socket-host.d-VUTw8RC_.js";
2
2
  /**
3
3
  * The subset of the Cloudflare `ExecutionContext` the Lunora worker entry and
4
4
  * the framework mount seams rely on — `waitUntil` for fire-and-forget work that
@@ -913,7 +913,8 @@ interface PlatformCapabilities {
913
913
  * tracing shows one nested tree instead of two unrelated ones — the
914
914
  * sink's `fuseCloudflareTraces` opt-in, which reaches `cloudflare:workers`'
915
915
  * `tracing.enterSpan` (plus, where present, `getActiveSpan` for the
916
- * invocation root and a span's `setAttributes` / `recordException`).
916
+ * invocation root and a span's `setAttributes` / `recordException` /
917
+ * `setStatus`).
917
918
  *
918
919
  * Rated because it is the one telemetry surface that reaches past
919
920
  * `ShardHost` into a provider API. Everything else in the pipeline is
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{NOOP_EXECUTION_CONTEXT as e}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{resolveShard as E}from"./packem_shared/resolveShard-BzKOUEO4.mjs";import{default as t}from"./packem_shared/CELLD_CAPABILITIES-BaeuWhr9.mjs";import{default as A}from"./packem_shared/CLOUDFLARE_CAPABILITIES-CSbB8N6h.mjs";import{default as L}from"./packem_shared/NODE_CAPABILITIES-C9ExnYOh.mjs";export{t as CELLD_CAPABILITIES,A as CLOUDFLARE_CAPABILITIES,L as NODE_CAPABILITIES,e as NOOP_EXECUTION_CONTEXT,E as resolveShard};
1
+ import{NOOP_EXECUTION_CONTEXT as E}from"./packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs";import{LUNORA_ROLE_PREFIX as e,resolveShard as L}from"./packem_shared/LUNORA_ROLE_PREFIX-Da37XHgu.mjs";import{default as t}from"./packem_shared/CELLD_CAPABILITIES-BaeuWhr9.mjs";import{default as O}from"./packem_shared/CLOUDFLARE_CAPABILITIES-B0YUrj1a.mjs";import{default as C}from"./packem_shared/NODE_CAPABILITIES-C9ExnYOh.mjs";export{t as CELLD_CAPABILITIES,O as CLOUDFLARE_CAPABILITIES,e as LUNORA_ROLE_PREFIX,C as NODE_CAPABILITIES,E as NOOP_EXECUTION_CONTEXT,L as resolveShard};
@@ -0,0 +1 @@
1
+ const e={id:"cloudflare",name:"Cloudflare",features:{shardedState:{level:"native",note:"Durable Objects with SQLite"},globalTables:{level:"native",note:"D1 with Sessions API. D1 has a documented, expected baseline error rate — Cloudflare's own team calls a handful of transient errors every few hours 'not unexpected' on a healthy database — so read-only statements are retried automatically; writes are not, because every one of those errors is ambiguous about whether the statement applied and D1 has no interactive transactions to resolve it"},websocketHibernation:{level:"native",note:"DO WebSocket hibernation"},durableStreams:{level:"emulated",note:"Lunora persists each chunk to the shard's SQLite under a monotonic seq and keeps the producer alive past the socket via waitUntil; the platform has no streaming primitive of its own, and a run whose DO is evicted mid-flight ends as STREAM_INTERRUPTED rather than resuming. From compatibility_date 2026-10-01 (or the durable_object_io_tasks_prevent_eviction flag) that pending waitUntil holds off idle eviction for up to 15 minutes, so a client disconnect no longer interrupts a shorter run, unless durable_object_io_tasks_do_not_prevent_eviction opts out. Deployed Workers apply it by date; a local workerd older than 1.20261001 ignores the date and needs the explicit durable_object_io_tasks_prevent_eviction flag (it rejects the durable_object_io_tasks_do_not_prevent_eviction opt-out at startup)"},commitOrderedTables:{level:"native",note:"`state.storage.transaction` makes the `__commit_seq` bump atomic with the rows it stamps, and a Durable Object executes one event at a time — so the allocation order IS the commit order, with no lock of ours in the path"},localSql:{level:"native",note:"state.storage.sql (SQLite)"},serverReactors:{level:"emulated",note:"The wake-up is Lunora's, not the platform's: reactors ride the existing post-write refresh drain, which already exists to push subscription frames. Cloudflare supplies the two properties that make it correct — one event at a time per Durable Object, and `waitUntil` to keep the drain alive past the response — but has no notion of a server-side subscription of its own"},memoryTables:{level:"emulated",note:"The lifetime is real — an eviction drops the DO's heap and the framework clears every `.memory()` table on reconstruction, so the rows behave exactly like heap state, and their writes stay out of the CDC changelog. The STORAGE is not: workerd exposes one SQL handle and no memory-backed database, so a memory row is still written to the DO's SQLite and then deleted. `.memory()` buys the semantics, not the write"},shardAlarms:{level:"native",note:"state.storage.setAlarm"},shardPlacement:{level:"native",note:"DurableObjectNamespace.get/getByName locationHint — best-effort, and honoured only by the resolution that creates the object"},shardReadReplicas:{level:"emulated",note:"Lunora follows the shard's CDC changelog into a replica DO placed in the reader's region; the platform replicates for durability, not for reads, so the follow loop is ours"},crossShardFanout:{level:"emulated",note:"Lunora query coordinator + relay tier over Durable Objects. The shard keys a fan-out reaches come from ShardRegistryDO, which each shard registers with on its first write to a .shardBy() table; a shard written before the registry was bound is not listed until it is written again"},queues:{level:"native",note:"Cloudflare Queues"},relationGraph:{level:"emulated",note:"The graph is Lunora's, built on reads Cloudflare already serves: the edge set is derived from the schema's v.id(...) columns, and each hop is one batched WHERE ... IN (...) against the shard's SQLite, all inside the Durable Object's single-threaded request. There is no graph engine being consumed — workerd offers none — so native would misreport who does the work"},workerLoaders:{level:"native",note:"The `worker_loaders` binding (Dynamic Workers, in open beta): `load()` compiles a script into its own isolate, `globalOutbound: null` removes its network, and `limits.cpuMs` bounds it"},workflowRollback:{level:"native",note:"Workflows step rollback (the step.do rollback option)"},workflowSchedules:{level:"native",note:"wrangler workflows[].schedules — Cloudflare creates an instance on each cron tick, no scheduled() handler involved"},workflows:{level:"native",note:"Cloudflare Workflows declared in wrangler `exports` (no workflows[] binding) and reached through `ctx.exports.<Class>` from the shard DO, the Worker and every WorkflowEntrypoint; needs Wrangler >= 4.142 / @cloudflare/vite-plugin >= 1.61 locally"},scheduler:{level:"emulated",note:"SchedulerDO (Lunora, on DO alarms) + declarative Cron Triggers; no runtime cron registration"},cronTriggers:{level:"native",note:"wrangler triggers.crons, reconciled from the declared crons at build time, delivered to the worker's scheduled() handler — which is the one cron dispatch that ships: it walks the generated LUNORA_CRONS map itself"},authJurisdictionMove:{level:"native",note:"Durable Object jurisdictions. The un-pinned and the pinned auth object are two objects on one namespace (namespace.jurisdiction()), and the __lunora_admin__:copyAuthToJurisdiction / purgeUnpinnedAuth admin ops copy between them over the objects' secret-gated internal route, driven from the worker. Present only for DO-backed auth pinned with .jurisdiction(…, { pinAuth: true })"},agents:{level:"emulated",note:"The durable agent loop is Lunora's: each defineAgent compiles onto a Cloudflare Workflow declared in wrangler `exports` and reached through `ctx.exports` (a voice-enabled agent additionally gets a VoiceSessionDO), and the loop drives Workers AI. Cloudflare supplies the workflow engine, the Durable Object and the inference; the agent is built on them, not consumed as a product"},objectStorage:{level:"native",note:"R2. Resumable end-user uploads (`@lunora/storage/upload`) run over the binding: TUS / chunked-REST chunks are coalesced into equal 5 MiB multipart parts, and the upload's offset, parts and buffered tail live in conditional-put state objects in the same bucket, so a request can land on any isolate. Identical under miniflare; no S3 credentials"},objectStorageBackups:{level:"emulated",note:"`lunora backup create|list|restore --bucket` writes NDJSON snapshots + a manifest sidecar per snapshot through the admin storage routes (checksum-verified upload, admin-gated object read), and `backupCron`/`backupStore` runs the same layout unattended on a Cron Trigger. Both are bounded by what a single request body / a Worker isolate can hold, not by R2. `emulated` because every part of that is Lunora's — R2 supplies a bucket, and Cloudflare has no backup product being consumed here; the snapshot format, the manifest, the checksum gate and the retention report are all ours"},objectStorageCdcArchive:{level:"emulated",note:"R2 supplies the bucket and the `startAfter` listing the segment keys are indexed on; everything above that is Lunora's — the segment format, the archive-before-trim ordering the sweep defers behind `waitUntil`, and the de-overlapping read-back. The platform has no notion of a changelog to tier, so this is not a product being consumed"},keyValueStore:{level:"native",note:"Workers KV"},vectorStore:{level:"native",note:"Vectorize; query/upsert namespace scoping is native (remote filter), but getByIds/deleteByIds id-path tenant isolation is facade-enforced (client-side verification) since Vectorize's id operations take no namespace option. Write sync and the `backfillVectors` admin op (paged embedding of pre-existing rows; re-walks a table only when its fingerprinted config changes: index names, source field, dimensions, metric, metadata keys, declared `model`, or the table's soft-delete field. A Shape B `select`/`metadata` edit, or an `embed` swap with no declared `model`, still needs `restart: true`) are Lunora's, carried by ShardHost: pages are read under `runSerialized` and synced on the shard's after-commit chain. Shard-local tables only: `.global()` plus a vector index is rejected, since D1/Hyperdrive writes never reach that chain"},ai:{level:"native",note:"Workers AI; `<provider>/<model>` and `dynamic/<route>` ids route through AI Gateway over the same binding (Unified Billing for unified-catalog providers, a key stored on the gateway for gateway-path-only ones; `LUNORA_AI_GATEWAY_ID` else the account's `default` gateway). `rejectIfBusy` on `ctx.ai.model` / `ctx.ai.run` is the binding's own option, forwarded as is. `ctx.ai.websearch` is the binding's `websearch()` (Web Search API, beta: Ceramic / Exa / Linkup) on the same gateway, billed to a provider key stored there (the `default` alias unless `byokAlias` names another) or else AI Gateway credits"},aiSearch:{level:"native",note:"AI Search (formerly AutoRAG) through the `ai_search_namespaces` binding (conventionally `AI_SEARCH` on namespace `default`), passed through as-is: `ctx.aiSearch.get(name)` reaches any instance in the namespace, alongside `create` / `delete` / `list` and multi-instance `search` / `chatCompletions`. ActionCtx only. Remote-only even in dev: there is no local simulator, and miniflare proxies the binding to the service. Text-only over the binding — image and file content parts need the REST API. Usage-billed from 2026-11-01"},browser:{level:"native",note:"Browser Run: Playwright sessions (allowedHosts forwarded as session guardrails), Quick Actions through the binding's quickAction, and /crawl over the account REST API (no binding method; needs an API token)"},images:{level:"native",note:"Cloudflare Images binding"},containers:{level:"native",note:"Cloudflare Containers; ctx.containers.<name>.exec rides the same binding over the /__lunora/exec contract, which the container Durable Object answers through the runtime's native ctx.container.exec() (the image serves the route itself only on a runtime without native exec). spawn() and terminal() stream a process (stdin, a PTY, kill and resize) through the same native exec, and have no fallback on a runtime without it"},containerEgressPolicy:{level:"native",note:"@cloudflare/containers outbound interception (allowedHosts / deniedHosts / interceptHttps)"},containerRuntimeScheduling:{level:"native",note:"Cloudflare Containers' durable_object scheduling policy and container snapshots (both public beta): LunoraContainer resolves the named image through ctx.container.images and forwards image / instance / containerSnapshot to ctx.container.start() through the patched @cloudflare/containers base"},containerSandboxTools:{level:"native",note:"@cloudflare/sandbox (Sandbox SDK 1.0): Files, DirectoryBackup and S3Mount drive the sandbox-shim helper in the image through the native ctx.container.exec(), and the backup and mount gateways are WorkerEntrypoints the container reaches through interceptOutboundHttp. The image must ship /usr/local/bin/sandbox-shim, copied from Cloudflare's shim-only cloudflare/sandbox image"},analytics:{level:"native",note:"Analytics Engine"},analyticsSql:{level:"native",note:"Analytics SQL binding (wrangler `analytics` key, wrangler >= 4.145.0): read-only SQL over the account's Analytics SQL API — Analytics Engine datasets as `events.analyticsEngine.<dataset>`, plus Cloudflare's own `events.` / `states.` / `logs.` datasets — with the account derived from the Worker. ActionCtx only. One statement and one dataset per query, a lower `timestamp` bound required. Remote-only even in dev: there is no local simulator, and miniflare proxies the binding to the service. Without the binding, `createAnalyticsSqlRest` runs the same dialect over the REST endpoint with an API token"},artifacts:{level:"native",note:"Cloudflare Artifacts binding (open beta, Workers Paid): repo create/import/fork/list/delete, repo-scoped Git tokens, and reads of commits, trees, blobs and files. It has no write method; writes are a `git push` from a Git client with a minted token. No local simulator, so `lunora dev` reaches the remote service. Namespace jurisdiction is `eu` or `us`, fixed when the namespace is created; a `fedramp` schema using it is a codegen error"},edgeRequestMetadata:{level:"native",note:'request.cf — the edge stamps placement (colo, country) and, where an mTLS-enabled hostname is configured, the verified client certificate under tlsClientAuth. Unforgeable because it is not a header: trustInboundTraceContext: "mtls" and the OTLP placement resource detector both read it directly'},hostTraceFusion:{level:"native",note:"cloudflare:workers' tracing.enterSpan, behind the sink's fuseCloudflareTraces opt-in. Feature-detected on top: span.recordException and span.setStatus (status error) for failed spans (redacted message only; a successful span stays unset). The invocation root span is marked failed when an RPC dispatch answers 5xx, with or without recorded telemetry, and whenever a trigger throws; a 4xx RPC leaves it unset. Also feature-detected: span.setAttributes for the attribute mirror, and tracing.getActiveSpan to put the ctx.span wide event on the invocation's root span. Leave it off unless you want the CF-native nesting: with it on, a deployment that also ships onSpan to a collector emits the same logical span down two pipelines. Fused or not, the edge's cf-ray Ray ID links the two: Lunora stamps it as cloudflare.ray_id (the attribute CF's own Workers traces use) on its spans and as rayId on its request and ctx.log events, informational only. Off Cloudflare no host sets cf-ray, so it is simply absent"},logArchive:{level:"native",note:"R2 Data Catalog (Iceberg) written by pipelineLogSink and queried back over R2 SQL, which the admin route runs server-side because R2 SQL needs a Cloudflare API token that must never reach the browser"},pipelines:{level:"native",note:"Cloudflare Pipelines"},pointInTimeRecovery:{level:"native",note:"SQLite-backed Durable Object bookmarks (getBookmarkForTime / onNextSessionRestoreBookmark) restore SQL and KV to any moment in the last 30 days. Absent under local wrangler dev, where the ops answer PITR_UNAVAILABLE"},mail:{level:"emulated",note:"Resend (third-party) via Cloudflare Queues"},secrets:{level:"native",note:"Secrets Store"},services:{level:"native",note:"Cloudflare service bindings: the bound Worker runs on the same thread of the same server, with no extra request fee. Fetch services and WorkerEntrypoint RPC both work, in deploy and in local dev (auxiliary Workers / multiple wrangler configs)"},topics:{level:"emulated",note:"Lunora's fan-out over Cloudflare Queues — a queue allows one consumer, so each subscription is its own queue (own retries and dead-letter queue) and a publish sends to every one in parallel. Not atomic: a failed send rejects the publish and a retry re-delivers to the subscriptions that already got it. No ordering key"},hyperdrive:{level:"native",note:"Cloudflare Hyperdrive"},httpCache:{level:"native",note:"The colo cache via caches.default. Worker-generated responses are NOT stored by it automatically — the runtime has to caches.default.put() them — and it honours Vary for Accept-Encoding only, so a varying response has to fold those header values into the cache key itself. A 206, a Vary: *, or a Set-Cookie-bearing response is refused by put()"},requestOrigin:{level:"native",note:"request.url carries the hostname the edge routed the request on (a route, custom domain or workers.dev name bound to this Worker), so a caller cannot point it at a host the Worker does not serve"},identityProxy:{level:"native",note:"Cloudflare Access. A policy attached to the Worker covers its custom domains, routes, workers.dev and preview URLs at once, and the authenticated identity arrives on the execution context as ctx.access — no header to verify, and nothing a request can forge to manufacture one. A hostname-scoped Access application instead stamps the Cf-Access-Jwt-Assertion header, which needs no host support at all"}}};export{e as default};
@@ -0,0 +1 @@
1
+ const t="__lunora_do__:",_=(e,r,s)=>{if(r.startsWith(t))throw new Error(`shard key "${r}" uses the reserved "${t}" prefix`);return e.getByName!==void 0?e.getByName(r,s):e.get(e.idForName(r),s)};export{t as LUNORA_ROLE_PREFIX,_ as resolveShard};
@@ -304,6 +304,13 @@ interface TwoStepShardDirectory {
304
304
  * closed when a jurisdiction is requested but the method is absent.
305
305
  */
306
306
  type ShardDirectory = DirectShardDirectory | TwoStepShardDirectory;
307
+ /**
308
+ * The instance-name prefix reserved for framework roles that share the shard
309
+ * directory when an app merges its Durable Object classes (plan 462): the
310
+ * scheduler lives at `__lunora_do__:scheduler:<name>`. No shard key may carry
311
+ * it, so {@link resolveShard} refuses one on every host.
312
+ */
313
+ declare const LUNORA_ROLE_PREFIX = "__lunora_do__:";
307
314
  /**
308
315
  * Resolve a shard key to a stub against either directory shape. Uses direct
309
316
  * name lookup when the provider has it, and falls back to the two-step
@@ -685,4 +692,4 @@ interface SocketHost {
685
692
  */
686
693
  setTag?: (socket: SocketHandle, tag: string) => void;
687
694
  }
688
- export { DirectShardDirectory as D, ScheduleOptions as S, TwoStepShardDirectory as T, ScheduledJob as a, ScheduledJobStatus as b, SchedulerHost as c, ShardAlarms as d, ShardDirectory as e, ShardHost as f, ShardJurisdiction as g, ShardKvListOptions as h, ShardKvStore as i, ShardRegionHint as j, ShardSqlCursor as k, ShardSqlExec as l, ShardStub as m, SocketHandle as n, SocketHost as o, SqlRow as p, resolveShard as r };
695
+ export { DirectShardDirectory as D, LUNORA_ROLE_PREFIX as L, ScheduleOptions as S, TwoStepShardDirectory as T, ScheduledJob as a, ScheduledJobStatus as b, SchedulerHost as c, ShardAlarms as d, ShardDirectory as e, ShardHost as f, ShardJurisdiction as g, ShardKvListOptions as h, ShardKvStore as i, ShardRegionHint as j, ShardSqlCursor as k, ShardSqlExec as l, ShardStub as m, SocketHandle as n, SocketHost as o, SqlRow as p, resolveShard as r };
@@ -304,6 +304,13 @@ interface TwoStepShardDirectory {
304
304
  * closed when a jurisdiction is requested but the method is absent.
305
305
  */
306
306
  type ShardDirectory = DirectShardDirectory | TwoStepShardDirectory;
307
+ /**
308
+ * The instance-name prefix reserved for framework roles that share the shard
309
+ * directory when an app merges its Durable Object classes (plan 462): the
310
+ * scheduler lives at `__lunora_do__:scheduler:<name>`. No shard key may carry
311
+ * it, so {@link resolveShard} refuses one on every host.
312
+ */
313
+ declare const LUNORA_ROLE_PREFIX = "__lunora_do__:";
307
314
  /**
308
315
  * Resolve a shard key to a stub against either directory shape. Uses direct
309
316
  * name lookup when the provider has it, and falls back to the two-step
@@ -685,4 +692,4 @@ interface SocketHost {
685
692
  */
686
693
  setTag?: (socket: SocketHandle, tag: string) => void;
687
694
  }
688
- export { DirectShardDirectory as D, ScheduleOptions as S, TwoStepShardDirectory as T, ScheduledJob as a, ScheduledJobStatus as b, SchedulerHost as c, ShardAlarms as d, ShardDirectory as e, ShardHost as f, ShardJurisdiction as g, ShardKvListOptions as h, ShardKvStore as i, ShardRegionHint as j, ShardSqlCursor as k, ShardSqlExec as l, ShardStub as m, SocketHandle as n, SocketHost as o, SqlRow as p, resolveShard as r };
695
+ export { DirectShardDirectory as D, LUNORA_ROLE_PREFIX as L, ScheduleOptions as S, TwoStepShardDirectory as T, ScheduledJob as a, ScheduledJobStatus as b, SchedulerHost as c, ShardAlarms as d, ShardDirectory as e, ShardHost as f, ShardJurisdiction as g, ShardKvListOptions as h, ShardKvStore as i, ShardRegionHint as j, ShardSqlCursor as k, ShardSqlExec as l, ShardStub as m, SocketHandle as n, SocketHost as o, SqlRow as p, resolveShard as r };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform",
3
- "version": "1.0.0-alpha.54",
3
+ "version": "1.0.0-alpha.56",
4
4
  "description": "Provider-neutral host contracts for Lunora: shard/socket/directory/scheduler interfaces, binding projections, and the platform capability matrix",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
1
- const e={id:"cloudflare",name:"Cloudflare",features:{shardedState:{level:"native",note:"Durable Objects with SQLite"},globalTables:{level:"native",note:"D1 with Sessions API. D1 has a documented, expected baseline error rate — Cloudflare's own team calls a handful of transient errors every few hours 'not unexpected' on a healthy database — so read-only statements are retried automatically; writes are not, because every one of those errors is ambiguous about whether the statement applied and D1 has no interactive transactions to resolve it"},websocketHibernation:{level:"native",note:"DO WebSocket hibernation"},durableStreams:{level:"emulated",note:"Lunora persists each chunk to the shard's SQLite under a monotonic seq and keeps the producer alive past the socket via waitUntil; the platform has no streaming primitive of its own, and a run whose DO is evicted mid-flight ends as STREAM_INTERRUPTED rather than resuming. From compatibility_date 2026-10-01 (or the durable_object_io_tasks_prevent_eviction flag) that pending waitUntil holds off idle eviction for up to 15 minutes, so a client disconnect no longer interrupts a shorter run, unless durable_object_io_tasks_do_not_prevent_eviction opts out. Deployed Workers apply it by date; a local workerd built before that default (wrangler 4.143's 1.20260926 is one) honours only the explicit flag"},commitOrderedTables:{level:"native",note:"`state.storage.transaction` makes the `__commit_seq` bump atomic with the rows it stamps, and a Durable Object executes one event at a time — so the allocation order IS the commit order, with no lock of ours in the path"},localSql:{level:"native",note:"state.storage.sql (SQLite)"},serverReactors:{level:"emulated",note:"The wake-up is Lunora's, not the platform's: reactors ride the existing post-write refresh drain, which already exists to push subscription frames. Cloudflare supplies the two properties that make it correct — one event at a time per Durable Object, and `waitUntil` to keep the drain alive past the response — but has no notion of a server-side subscription of its own"},memoryTables:{level:"emulated",note:"The lifetime is real — an eviction drops the DO's heap and the framework clears every `.memory()` table on reconstruction, so the rows behave exactly like heap state, and their writes stay out of the CDC changelog. The STORAGE is not: workerd exposes one SQL handle and no memory-backed database, so a memory row is still written to the DO's SQLite and then deleted. `.memory()` buys the semantics, not the write"},shardAlarms:{level:"native",note:"state.storage.setAlarm"},shardPlacement:{level:"native",note:"DurableObjectNamespace.get/getByName locationHint — best-effort, and honoured only by the resolution that creates the object"},shardReadReplicas:{level:"emulated",note:"Lunora follows the shard's CDC changelog into a replica DO placed in the reader's region; the platform replicates for durability, not for reads, so the follow loop is ours"},crossShardFanout:{level:"emulated",note:"Lunora query coordinator + relay tier over Durable Objects. The shard keys a fan-out reaches come from ShardRegistryDO, which each shard registers with on its first write to a .shardBy() table; a shard written before the registry was bound is not listed until it is written again"},queues:{level:"native",note:"Cloudflare Queues"},relationGraph:{level:"emulated",note:"The graph is Lunora's, built on reads Cloudflare already serves: the edge set is derived from the schema's v.id(...) columns, and each hop is one batched WHERE ... IN (...) against the shard's SQLite, all inside the Durable Object's single-threaded request. There is no graph engine being consumed — workerd offers none — so native would misreport who does the work"},workerLoaders:{level:"native",note:"The `worker_loaders` binding (Dynamic Workers, in open beta): `load()` compiles a script into its own isolate, `globalOutbound: null` removes its network, and `limits.cpuMs` bounds it"},workflowRollback:{level:"native",note:"Workflows step rollback (the step.do rollback option)"},workflowSchedules:{level:"native",note:"wrangler workflows[].schedules — Cloudflare creates an instance on each cron tick, no scheduled() handler involved"},workflows:{level:"native",note:"Cloudflare Workflows declared in wrangler `exports` (no workflows[] binding) and reached through `ctx.exports.<Class>` from the shard DO, the Worker and every WorkflowEntrypoint; needs Wrangler >= 4.142 / @cloudflare/vite-plugin >= 1.61 locally"},scheduler:{level:"emulated",note:"SchedulerDO (Lunora, on DO alarms) + declarative Cron Triggers; no runtime cron registration"},cronTriggers:{level:"native",note:"wrangler triggers.crons, reconciled from the declared crons at build time, delivered to the worker's scheduled() handler — which is the one cron dispatch that ships: it walks the generated LUNORA_CRONS map itself"},authJurisdictionMove:{level:"native",note:"Durable Object jurisdictions. The un-pinned and the pinned auth object are two objects on one namespace (namespace.jurisdiction()), and the __lunora_admin__:copyAuthToJurisdiction / purgeUnpinnedAuth admin ops copy between them over the objects' secret-gated internal route, driven from the worker. Present only for DO-backed auth pinned with .jurisdiction(…, { pinAuth: true })"},agents:{level:"emulated",note:"The durable agent loop is Lunora's: each defineAgent compiles onto a Cloudflare Workflow declared in wrangler `exports` and reached through `ctx.exports` (a voice-enabled agent additionally gets a VoiceSessionDO), and the loop drives Workers AI. Cloudflare supplies the workflow engine, the Durable Object and the inference; the agent is built on them, not consumed as a product"},objectStorage:{level:"native",note:"R2. Resumable end-user uploads (`@lunora/storage/upload`) run over the binding: TUS / chunked-REST chunks are coalesced into equal 5 MiB multipart parts, and the upload's offset, parts and buffered tail live in conditional-put state objects in the same bucket, so a request can land on any isolate. Identical under miniflare; no S3 credentials"},objectStorageBackups:{level:"emulated",note:"`lunora backup create|list|restore --bucket` writes NDJSON snapshots + a manifest sidecar per snapshot through the admin storage routes (checksum-verified upload, admin-gated object read), and `backupCron`/`backupStore` runs the same layout unattended on a Cron Trigger. Both are bounded by what a single request body / a Worker isolate can hold, not by R2. `emulated` because every part of that is Lunora's — R2 supplies a bucket, and Cloudflare has no backup product being consumed here; the snapshot format, the manifest, the checksum gate and the retention report are all ours"},objectStorageCdcArchive:{level:"emulated",note:"R2 supplies the bucket and the `startAfter` listing the segment keys are indexed on; everything above that is Lunora's — the segment format, the archive-before-trim ordering the sweep defers behind `waitUntil`, and the de-overlapping read-back. The platform has no notion of a changelog to tier, so this is not a product being consumed"},keyValueStore:{level:"native",note:"Workers KV"},vectorStore:{level:"native",note:"Vectorize; query/upsert namespace scoping is native (remote filter), but getByIds/deleteByIds id-path tenant isolation is facade-enforced (client-side verification) since Vectorize's id operations take no namespace option. Write sync and the `backfillVectors` admin op (paged embedding of pre-existing rows; re-walks a table only when its fingerprinted config changes: index names, source field, dimensions, metric, metadata keys, declared `model`, or the table's soft-delete field. A Shape B `select`/`metadata` edit, or an `embed` swap with no declared `model`, still needs `restart: true`) are Lunora's, carried by ShardHost: pages are read under `runSerialized` and synced on the shard's after-commit chain. Shard-local tables only: `.global()` plus a vector index is rejected, since D1/Hyperdrive writes never reach that chain"},ai:{level:"native",note:"Workers AI; `<provider>/<model>` and `dynamic/<route>` ids route through AI Gateway over the same binding (Unified Billing for unified-catalog providers, a key stored on the gateway for gateway-path-only ones; `LUNORA_AI_GATEWAY_ID` else the account's `default` gateway). `rejectIfBusy` on `ctx.ai.model` / `ctx.ai.run` is the binding's own option, forwarded as is. `ctx.ai.websearch` is the binding's `websearch()` (Web Search API, beta: Ceramic / Exa / Linkup) on the same gateway, billed to a provider key stored there (the `default` alias unless `byokAlias` names another) or else AI Gateway credits"},aiSearch:{level:"native",note:"AI Search (formerly AutoRAG) through the `ai_search_namespaces` binding (conventionally `AI_SEARCH` on namespace `default`), passed through as-is: `ctx.aiSearch.get(name)` reaches any instance in the namespace, alongside `create` / `delete` / `list` and multi-instance `search` / `chatCompletions`. ActionCtx only. Remote-only even in dev: there is no local simulator, and miniflare proxies the binding to the service. Text-only over the binding — image and file content parts need the REST API. Usage-billed from 2026-11-01"},browser:{level:"native",note:"Browser Run: Playwright sessions (allowedHosts forwarded as session guardrails), Quick Actions through the binding's quickAction, and /crawl over the account REST API (no binding method; needs an API token)"},images:{level:"native",note:"Cloudflare Images binding"},containers:{level:"native",note:"Cloudflare Containers; ctx.containers.<name>.exec rides the same binding over the /__lunora/exec contract, which the container Durable Object answers through the runtime's native ctx.container.exec() (the image serves the route itself only on a runtime without native exec). spawn() and terminal() stream a process (stdin, a PTY, kill and resize) through the same native exec, and have no fallback on a runtime without it"},containerEgressPolicy:{level:"native",note:"@cloudflare/containers outbound interception (allowedHosts / deniedHosts / interceptHttps)"},containerRuntimeScheduling:{level:"native",note:"Cloudflare Containers' durable_object scheduling policy and container snapshots (both public beta): LunoraContainer resolves the named image through ctx.container.images and forwards image / instance / containerSnapshot to ctx.container.start() through the patched @cloudflare/containers base"},containerSandboxTools:{level:"native",note:"@cloudflare/sandbox (Sandbox SDK 1.0): Files, DirectoryBackup and S3Mount drive the sandbox-shim helper in the image through the native ctx.container.exec(), and the backup and mount gateways are WorkerEntrypoints the container reaches through interceptOutboundHttp. The image must ship /usr/local/bin/sandbox-shim, copied from Cloudflare's shim-only cloudflare/sandbox image"},analytics:{level:"native",note:"Analytics Engine"},analyticsSql:{level:"native",note:"Analytics SQL binding (wrangler `analytics` key, wrangler >= 4.145.0): read-only SQL over the account's Analytics SQL API — Analytics Engine datasets as `events.analyticsEngine.<dataset>`, plus Cloudflare's own `events.` / `states.` / `logs.` datasets — with the account derived from the Worker. ActionCtx only. One statement and one dataset per query, a lower `timestamp` bound required. Remote-only even in dev: there is no local simulator, and miniflare proxies the binding to the service. Without the binding, `createAnalyticsSqlRest` runs the same dialect over the REST endpoint with an API token"},artifacts:{level:"native",note:"Cloudflare Artifacts binding (open beta, Workers Paid): repo create/import/fork/list/delete, repo-scoped Git tokens, and reads of commits, trees, blobs and files. It has no write method; writes are a `git push` from a Git client with a minted token. No local simulator, so `lunora dev` reaches the remote service. Namespace jurisdiction is `eu` or `us`, fixed when the namespace is created; a `fedramp` schema using it is a codegen error"},edgeRequestMetadata:{level:"native",note:'request.cf — the edge stamps placement (colo, country) and, where an mTLS-enabled hostname is configured, the verified client certificate under tlsClientAuth. Unforgeable because it is not a header: trustInboundTraceContext: "mtls" and the OTLP placement resource detector both read it directly'},hostTraceFusion:{level:"native",note:"cloudflare:workers' tracing.enterSpan, behind the sink's fuseCloudflareTraces opt-in. Feature-detected on top: span.recordException for failed spans (redacted message only), span.setAttributes for the attribute mirror, and tracing.getActiveSpan to put the ctx.span wide event on the invocation's root span. Leave it off unless you want the CF-native nesting: with it on, a deployment that also ships onSpan to a collector emits the same logical span down two pipelines. Fused or not, the edge's cf-ray Ray ID links the two: Lunora stamps it as cloudflare.ray_id (the attribute CF's own Workers traces use) on its spans and as rayId on its request and ctx.log events, informational only. Off Cloudflare no host sets cf-ray, so it is simply absent"},logArchive:{level:"native",note:"R2 Data Catalog (Iceberg) written by pipelineLogSink and queried back over R2 SQL, which the admin route runs server-side because R2 SQL needs a Cloudflare API token that must never reach the browser"},pipelines:{level:"native",note:"Cloudflare Pipelines"},pointInTimeRecovery:{level:"native",note:"SQLite-backed Durable Object bookmarks (getBookmarkForTime / onNextSessionRestoreBookmark) restore SQL and KV to any moment in the last 30 days. Absent under local wrangler dev, where the ops answer PITR_UNAVAILABLE"},mail:{level:"emulated",note:"Resend (third-party) via Cloudflare Queues"},secrets:{level:"native",note:"Secrets Store"},services:{level:"native",note:"Cloudflare service bindings: the bound Worker runs on the same thread of the same server, with no extra request fee. Fetch services and WorkerEntrypoint RPC both work, in deploy and in local dev (auxiliary Workers / multiple wrangler configs)"},topics:{level:"emulated",note:"Lunora's fan-out over Cloudflare Queues — a queue allows one consumer, so each subscription is its own queue (own retries and dead-letter queue) and a publish sends to every one in parallel. Not atomic: a failed send rejects the publish and a retry re-delivers to the subscriptions that already got it. No ordering key"},hyperdrive:{level:"native",note:"Cloudflare Hyperdrive"},httpCache:{level:"native",note:"The colo cache via caches.default. Worker-generated responses are NOT stored by it automatically — the runtime has to caches.default.put() them — and it honours Vary for Accept-Encoding only, so a varying response has to fold those header values into the cache key itself. A 206, a Vary: *, or a Set-Cookie-bearing response is refused by put()"},requestOrigin:{level:"native",note:"request.url carries the hostname the edge routed the request on (a route, custom domain or workers.dev name bound to this Worker), so a caller cannot point it at a host the Worker does not serve"},identityProxy:{level:"native",note:"Cloudflare Access. A policy attached to the Worker covers its custom domains, routes, workers.dev and preview URLs at once, and the authenticated identity arrives on the execution context as ctx.access — no header to verify, and nothing a request can forge to manufacture one. A hostname-scoped Access application instead stamps the Cf-Access-Jwt-Assertion header, which needs no host support at all"}}};export{e as default};
@@ -1 +0,0 @@
1
- const a=(e,g,N)=>e.getByName!==void 0?e.getByName(g,N):e.get(e.idForName(g),N);export{a as resolveShard};