@lunora/platform-node 1.0.0-alpha.67 → 1.0.0-alpha.69

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 +1 @@
1
- import{createNodeShardKvStore as N}from"../packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeSchedulerHost as k}from"../packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as D}from"../packem_shared/createNodeShardHost-CfX2IrZ8.mjs";import{createNodeShardRegistry as H}from"../packem_shared/createNodeShardRegistry-Dzqi0Q8l.mjs";import{createNodeSocketHost as T}from"../packem_shared/createNodeSocketHost-DQs85G7W.mjs";const P=()=>{const{database:o,dispose:m,host:p}=D(),h=N(o),c=H(),{directory:l}=c,{readFrames:w,restoreSocket:u,simulateRecycle:S,socket:y}=T(o),d=new Set,s=new Map,{dispose:f,scheduler:i,simulateDeadLetter:g}=k(o,{onDispatch:(e,a,t)=>{d.add(t.id),s.set(e,(s.get(e)??0)+1)}}),n=()=>{f(),m(),c.close()};return{awaitAlarmFired:async e=>{await new Promise(a=>{setTimeout(a,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await i.list?.())?.find(r=>r.id===e);return t!==void 0&&await new Promise(r=>{setTimeout(r,Math.max(0,t.scheduledFor-Date.now())+30)}),d.has(e)},cleanup:n,cronTicks:e=>s.get(e)??0,directory:l,disposeTerminally:n,kv:h,readFrames:w,restoreSocket:u,scheduler:i,shard:p,simulateDeadLetter:g,simulateRecycle:S,socket:y}};export{P as createNodeConformanceHost};
1
+ import{createNodeShardKvStore as N}from"../packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeSchedulerHost as k}from"../packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as D}from"../packem_shared/createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as H}from"../packem_shared/createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeSocketHost as T}from"../packem_shared/createNodeSocketHost-DQs85G7W.mjs";const P=()=>{const{database:o,dispose:m,host:p}=D(),h=N(o),c=H(),{directory:l}=c,{readFrames:w,restoreSocket:u,simulateRecycle:S,socket:y}=T(o),d=new Set,s=new Map,{dispose:f,scheduler:i,simulateDeadLetter:g}=k(o,{onDispatch:(e,a,t)=>{d.add(t.id),s.set(e,(s.get(e)??0)+1)}}),n=()=>{f(),m(),c.close()};return{awaitAlarmFired:async e=>{await new Promise(a=>{setTimeout(a,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await i.list?.())?.find(r=>r.id===e);return t!==void 0&&await new Promise(r=>{setTimeout(r,Math.max(0,t.scheduledFor-Date.now())+30)}),d.has(e)},cleanup:n,cronTicks:e=>s.get(e)??0,directory:l,disposeTerminally:n,kv:h,readFrames:w,restoreSocket:u,scheduler:i,shard:p,simulateDeadLetter:g,simulateRecycle:S,socket:y}};export{P as createNodeConformanceHost};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createNodeGlobalStore as r,createNodeSqlExec as t}from"./packem_shared/createNodeGlobalStore-nAJ4WO7w.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-BnplJiAw.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost--mBLGSxF.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-BzNH1lBp.mjs";import{createNodeSchedulerHost as l}from"./packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as h}from"./packem_shared/createNodeShardHost-CfX2IrZ8.mjs";import{createNodeShardRegistry as k}from"./packem_shared/createNodeShardRegistry-Dzqi0Q8l.mjs";import{createNodeShardState as w}from"./packem_shared/createNodeShardState-D6XMWbFs.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as g}from"./packem_shared/createNodeWorkflowHost-CTVBYiuj.mjs";import{createNodeWorkflowStore as q}from"./packem_shared/createNodeWorkflowStore-CL7RYP6H.mjs";export{r as createNodeGlobalStore,f as createNodePlatform,x as createNodeQueueHost,p as createNodeR2Bucket,l as createNodeSchedulerHost,h as createNodeShardHost,d as createNodeShardKvStore,k as createNodeShardRegistry,w as createNodeShardState,W as createNodeSocketHost,t as createNodeSqlExec,g as createNodeWorkflowHost,q as createNodeWorkflowStore};
1
+ import{createNodeGlobalStore as r,createNodeSqlExec as t}from"./packem_shared/createNodeGlobalStore-nAJ4WO7w.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-D5wfsfSD.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-CajTvuOy.mjs";import{createNodeSchedulerHost as l}from"./packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as h}from"./packem_shared/createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as k}from"./packem_shared/createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeShardState as w}from"./packem_shared/createNodeShardState-D6XMWbFs.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as g}from"./packem_shared/createNodeWorkflowHost-B5Aiwj3L.mjs";import{createNodeWorkflowStore as q}from"./packem_shared/createNodeWorkflowStore-CL7RYP6H.mjs";export{r as createNodeGlobalStore,f as createNodePlatform,x as createNodeQueueHost,p as createNodeR2Bucket,l as createNodeSchedulerHost,h as createNodeShardHost,d as createNodeShardKvStore,k as createNodeShardRegistry,w as createNodeShardState,W as createNodeSocketHost,t as createNodeSqlExec,g as createNodeWorkflowHost,q as createNodeWorkflowStore};
@@ -1 +1 @@
1
- import{LunoraError as S}from"@lunora/errors";import{NODE_CAPABILITIES as b}from"@lunora/platform";import{createNodeGlobalStore as N}from"./createNodeGlobalStore-nAJ4WO7w.mjs";import{createNodeShardKvStore as k}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as g}from"./createNodeQueueHost--mBLGSxF.mjs";import{createNodeR2Bucket as y}from"./createNodeR2Bucket-BzNH1lBp.mjs";import{createNodeSchedulerHost as R}from"./createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-CfX2IrZ8.mjs";import{createNodeShardRegistry as B}from"./createNodeShardRegistry-Dzqi0Q8l.mjs";import{createNodeSocketHost as H}from"./createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as I}from"./createNodeWorkflowHost-CTVBYiuj.mjs";import{createNodeWorkflowStore as P}from"./createNodeWorkflowStore-CL7RYP6H.mjs";const G=(o={})=>{const e=[],{database:r,dispose:d,drain:u,host:i}=q(o);e.push(d);try{const s=k(r),t=B(o);e.push(()=>{t.close()});const{directory:l}=t,{socket:h}=H(r),{dispose:m,scheduler:f}=R(r,o);e.push(m);const n=o.queues===void 0?void 0:g(r,{onBatch:o.onQueueBatch??(()=>{throw new S("VALIDATION_ERROR","@lunora/platform-node: createNodePlatform was given `queues` without `onQueueBatch`, so a delivered batch has nowhere to go — pass dispatchQueueBatch from @lunora/queue")}),queues:o.queues}),p=o.objectStorageDirectory===void 0?void 0:y({directory:o.objectStorageDirectory}),a=o.globalTablesPath===void 0?void 0:N({path:o.globalTablesPath});a!==void 0&&e.push(()=>{a.dispose()});const w=o.workflows===void 0?void 0:I({store:P(r),workflows:o.workflows}),c=()=>{for(const v of e.toReversed())v()};return{capabilities:b,close:c,directory:l,drain:u,globalTables:a,kv:s,objectStorage:p,queues:n,scheduler:f,shard:i,sockets:h,workflows:w,[Symbol.dispose]:c}}catch(s){for(const t of e.toReversed())try{t()}catch{}throw s}};export{G as createNodePlatform};
1
+ import{LunoraError as S}from"@lunora/errors";import{NODE_CAPABILITIES as b}from"@lunora/platform";import{createNodeGlobalStore as N}from"./createNodeGlobalStore-nAJ4WO7w.mjs";import{createNodeShardKvStore as k}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as g}from"./createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as y}from"./createNodeR2Bucket-CajTvuOy.mjs";import{createNodeSchedulerHost as R}from"./createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as B}from"./createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeSocketHost as H}from"./createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as I}from"./createNodeWorkflowHost-B5Aiwj3L.mjs";import{createNodeWorkflowStore as P}from"./createNodeWorkflowStore-CL7RYP6H.mjs";const G=(o={})=>{const e=[],{database:r,dispose:d,drain:u,host:i}=q(o);e.push(d);try{const s=k(r),t=B(o);e.push(()=>{t.close()});const{directory:l}=t,{socket:h}=H(r),{dispose:m,scheduler:f}=R(r,o);e.push(m);const n=o.queues===void 0?void 0:g(r,{onBatch:o.onQueueBatch??(()=>{throw new S("VALIDATION_ERROR","@lunora/platform-node: createNodePlatform was given `queues` without `onQueueBatch`, so a delivered batch has nowhere to go — pass dispatchQueueBatch from @lunora/queue")}),queues:o.queues}),p=o.objectStorageDirectory===void 0?void 0:y({directory:o.objectStorageDirectory}),a=o.globalTablesPath===void 0?void 0:N({path:o.globalTablesPath});a!==void 0&&e.push(()=>{a.dispose()});const w=o.workflows===void 0?void 0:I({store:P(r),workflows:o.workflows}),c=()=>{for(const v of e.toReversed())v()};return{capabilities:b,close:c,directory:l,drain:u,globalTables:a,kv:s,objectStorage:p,queues:n,scheduler:f,shard:i,sockets:h,workflows:w,[Symbol.dispose]:c}}catch(s){for(const t of e.toReversed())try{t()}catch{}throw s}};export{G as createNodePlatform};
@@ -0,0 +1,17 @@
1
+ import{randomUUID as F}from"node:crypto";import{deserialize as H,serialize as W}from"node:v8";import{LunoraError as m}from"@lunora/errors";import{isQueueDefinition as X,queueDefaultName as $,queueBindingName as Q}from"@lunora/queue";const _={maxBatchSize:10,maxBatchTimeoutSeconds:5,maxRetries:3,retryDelaySeconds:0,visibilityTimeoutMs:3e4},V=43200,A=131072,S=262144,k=(e,i)=>{switch(i){case"bytes":{if(e instanceof ArrayBuffer)return Buffer.from(e);if(ArrayBuffer.isView(e))return Buffer.from(e.buffer,e.byteOffset,e.byteLength);throw new m("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "bytes" needs an ArrayBuffer or a typed array')}case"text":{if(typeof e!="string")throw new m("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "text" needs a string');return Buffer.from(e,"utf8")}case"v8":return W(e);default:return Buffer.from(JSON.stringify(e??null),"utf8")}},O=(e,i,R)=>{const E=k(e,i);if(E.byteLength>A)throw new m("VALIDATION_ERROR",`@lunora/platform-node: a message for queue "${R}" encodes to ${String(E.byteLength)} bytes, over the Cloudflare Queues ceiling of ${String(A)} (128 KiB) per message — store the payload and enqueue a reference to it`);return E},D=(e,i)=>{switch(i){case"bytes":return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);case"text":return e.toString("utf8");case"v8":return H(e);default:return JSON.parse(e.toString("utf8"))}},Y=e=>{try{return D(e.body,e.content_type)}catch{return}},y=e=>e===void 0||!Number.isFinite(e)?0:Math.min(Math.max(0,Math.floor(e)),V)*1e3,K=(e,i)=>{e.pragma("journal_mode = WAL"),e.exec(`CREATE TABLE IF NOT EXISTS _lunora_queue_messages (
2
+ id TEXT PRIMARY KEY,
3
+ queue TEXT NOT NULL,
4
+ body BLOB NOT NULL,
5
+ content_type TEXT NOT NULL,
6
+ attempts INTEGER NOT NULL DEFAULT 0,
7
+ visible_at INTEGER NOT NULL,
8
+ enqueued_at INTEGER NOT NULL,
9
+ state TEXT NOT NULL DEFAULT 'pending'
10
+ )`),e.exec("CREATE INDEX IF NOT EXISTS _lunora_queue_messages_due ON _lunora_queue_messages (queue, state, visible_at)");const R=e.prepare("INSERT INTO _lunora_queue_messages (id, queue, body, content_type, visible_at, enqueued_at) VALUES (?, ?, ?, ?, ?, ?)"),E=e.prepare(`UPDATE _lunora_queue_messages
11
+ SET attempts = attempts + 1, visible_at = ?
12
+ WHERE id IN (
13
+ SELECT id FROM _lunora_queue_messages
14
+ WHERE queue = ? AND state = 'pending' AND visible_at <= ?
15
+ ORDER BY visible_at, enqueued_at LIMIT ?
16
+ )
17
+ RETURNING *`),q=e.prepare("UPDATE _lunora_queue_messages SET visible_at = ? WHERE id = ?"),h=e.prepare("DELETE FROM _lunora_queue_messages WHERE id = ?"),v=e.prepare("UPDATE _lunora_queue_messages SET state = 'dead' WHERE id = ?"),B=e.prepare("SELECT enqueued_at FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ? ORDER BY enqueued_at LIMIT 1"),I=e.prepare("SELECT COUNT(*) AS n FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ?"),M=e.prepare("SELECT * FROM _lunora_queue_messages WHERE queue = ? AND state = 'dead' ORDER BY enqueued_at"),b=e.prepare("UPDATE _lunora_queue_messages SET state = 'pending', attempts = 0, visible_at = ? WHERE id = ? AND state = 'dead'"),p=Object.entries(i.queues).map(([t,n])=>{if(!X(n))throw new m("VALIDATION_ERROR",`@lunora/platform-node: "${t}" is not a defineQueue result`);return{definition:n,exportName:t,name:n.name??$(t)}}),x=new Set(p.map(t=>t.name));for(const t of p){const n=t.definition.deadLetterQueue;if(n!==void 0){if(n===t.name)throw new m("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names itself as its own deadLetterQueue, which would redeliver forever`);if(!x.has(n))throw new m("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names deadLetterQueue "${n}", which no declared queue provides — its dead letters would be unreachable`)}}const U=i.visibilityTimeoutMs??_.visibilityTimeoutMs,T=i.now??Date.now,g=(t,n,s,u,o)=>{R.run(F(),t,n,s,o+u,o)},w=(t,n,s,u,o)=>{if(s==="ack"){h.run(t.id);return}const c=n.definition.maxRetries??_.maxRetries;if(t.attempts>c){const l=n.definition.deadLetterQueue;l===void 0?v.run(t.id):(g(l,t.body,t.content_type,0,o),h.run(t.id));return}const f=y(u??n.definition.retryDelay??_.retryDelaySeconds);q.run(o+f,t.id)},N={},L={...i.env};for(const t of p){const n={send:async(s,u)=>{const o=u?.contentType??"json";g(t.name,O(s,o,t.name),o,y(u?.delaySeconds),T())},sendBatch:async(s,u)=>{const o=T(),c=y(u?.delaySeconds),f=[...s].map(a=>{const r=a.contentType??"json";return{contentType:r,delay:a.delaySeconds===void 0?c:y(a.delaySeconds),encoded:O(a.body,r,t.name)}}),l=f.reduce((a,r)=>a+r.encoded.byteLength,0);if(l>S)throw new m("VALIDATION_ERROR",`@lunora/platform-node: a sendBatch for queue "${t.name}" encodes to ${String(l)} bytes, over the Cloudflare Queues ceiling of ${String(S)} (256 KiB) per batch — split it across more calls`);e.transaction(()=>{for(const a of f)g(t.name,a.encoded,a.contentType,a.delay,o)})()}};N[t.exportName]=n,L[Q(t.exportName)]=n}const C=async(t,n)=>{const s=Math.min(Math.max(1,t.definition.maxBatchSize??_.maxBatchSize),100),u=I.get(t.name,n)?.n??0;if(u===0)return!1;if(u<s){const r=B.get(t.name,n)?.enqueued_at,d=Math.min(Math.max(0,t.definition.maxBatchTimeout??_.maxBatchTimeoutSeconds),60)*1e3;if(r!==void 0&&n-r<d)return!1}const o=e.transaction(()=>E.all(n+U,t.name,n,s)).immediate();if(o.length===0)return!1;const c=new Map,f=o.map(r=>({ack:()=>{c.set(r.id,{outcome:"ack"})},attempts:r.attempts,body:D(r.body,r.content_type),id:r.id,retry:d=>{c.set(r.id,{delaySeconds:d?.delaySeconds,outcome:"retry"})},timestamp:new Date(r.enqueued_at)})),l={ackAll:()=>{for(const r of o)c.set(r.id,{outcome:"ack"})},messages:f,queue:t.name,retryAll:r=>{for(const d of o)c.set(d.id,{delaySeconds:r?.delaySeconds,outcome:"retry"})}};let a="ack";try{await i.onBatch(l)}catch{a="retry"}return e.transaction(()=>{for(const r of o){const d=c.get(r.id);w(r,t,d?.outcome??a,d?.delaySeconds,n)}})(),!0};return{bindings:N,deadLetters:{list:t=>M.all(t).map(n=>({attempts:n.attempts,body:Y(n),id:n.id})),requeue:t=>b.run(T(),t).changes>0},env:L,poll:async(t=T())=>{let n=0;for(const s of p)s.definition.mode!=="pull"&&await C(s,t)&&(n+=1);return n}}};export{K as createNodeQueueHost};
@@ -0,0 +1 @@
1
+ import{randomUUID as z,createHash as G}from"node:crypto";import{mkdir as N,open as j,rename as L,rmdir as U,unlink as W,readdir as X}from"node:fs/promises";import{join as R,dirname as v,sep as Y}from"node:path";import{LunoraError as c}from"@lunora/errors";import{t as P}from"./to-array-buffer-DVyXU4Ep.mjs";new TextEncoder;const J=Array.from({length:32},(e,t)=>t),Z=new RegExp(`[${J.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),q=e=>Z.test(e),S=".lunora-tmp",H="LNR1",p=8,D=1024,B=255,M=2048,Q=/[%A-Z:]/g,k=e=>{const t=e.replaceAll(Q,n=>`%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),r=t.at(-1);return r==="."||r===" "?`${t.slice(0,-1)}%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:t},tt=e=>{try{return decodeURIComponent(e)}catch{return}},A=e=>e.split("/").map(t=>k(t)).join("/"),et=e=>{const t=[];for(const r of e.split("/")){const n=tt(r);if(n===void 0)return;t.push(n)}return t.join("/")},rt=new TextEncoder,nt=e=>rt.encode(e).length,g=e=>{if(typeof e!="string"||e.length===0)throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(nt(e)>D)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(D)}-byte limit`);if(e.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(q(e))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(e.includes("\\"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(e.startsWith("/"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const t of e.split("/")){if(Buffer.byteLength(k(t),"utf8")>B)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${t}" exceeds ${String(B)} bytes once escaped for the filesystem`);if(t===""||t==="."||t==="..")throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key has an empty, \`.\` or \`..\` path segment ("${e}")`);if(t===S)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${S}")`)}},ot=e=>{if(e===void 0)return;let t=0;for(const[r,n]of Object.entries(e))t+=Buffer.byteLength(r,"utf8")+Buffer.byteLength(n,"utf8");if(t>M)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 customMetadata is ${String(t)} bytes, over the ${String(M)}-byte ceiling R2 applies to the summed keys and values — your metadata headers exceed the maximum allowed metadata size`)},at=/^[\da-f]{64}$/,st=e=>{if(typeof e=="string"){const t=e.toLowerCase();if(!at.test(t))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return t}if(e.byteLength!==32)throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return Buffer.from(e).toString("hex")},b=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",C=(e,t)=>{const r=t instanceof Error&&"code"in t?t.code:void 0;return r==="EEXIST"||r==="EISDIR"||r==="ENOTDIR"?new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key "${e}" collides with an existing object at one of its path prefixes — this host stores one file per key, so a key and a prefix of it cannot both hold objects`):t},it=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),r=Buffer.alloc(p);return r.writeUInt32BE(t.byteLength,0),r.write(H,4,"ascii"),Buffer.concat([t,r])},ct=e=>{if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.sha256Hex=="string"&&typeof t.size=="number"&&typeof t.uploaded=="string"},ft=async e=>{const t=await e.stat();if(!t.isFile()||t.size<p)return;const r=Buffer.alloc(p);if(await e.read(r,0,p,t.size-p),r.toString("ascii",4,p)!==H)return;const n=r.readUInt32BE(0),o=t.size-p-n;if(n===0||o<0)return;const s=Buffer.alloc(n),{bytesRead:i}=await e.read(s,0,n,o);if(i!==n)return;let a;try{a=JSON.parse(s.toString("utf8"))}catch{return}return ct(a)?{bodySize:o,meta:a}:void 0},F=async e=>{let t;try{t=await j(e,"r")}catch(r){if(b(r))return;throw r}try{const r=await ft(t);if(r===void 0){await t.close();return}return{...r,handle:t}}catch(r){throw await t.close(),r}},$=new FinalizationRegistry(e=>{e.close().catch(()=>{})}),V=async e=>{const t=await F(e);if(t!==void 0)return await t.handle.close(),{bodySize:t.bodySize,meta:t.meta}},I=(e,t)=>{const{sha256Hex:r}=t,n=Buffer.from(r,"hex");return{checksums:{sha256:P(n)},customMetadata:t.customMetadata,etag:r,httpEtag:`"${r}"`,httpMetadata:t.httpMetadata,key:e,sha256:r,sha256Base64:n.toString("base64"),size:t.size,uploaded:new Date(t.uploaded)}},O=(e,t,r)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),r),dt=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-O(t.suffix,e,e)};const r=O(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(r+O(t.length,0,e),e),start:r}},lt=(e,t,r,n)=>{const o=e.createReadStream({autoClose:!1,end:r-1,start:t})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await n()},pull:async s=>{let i;try{i=await o.next()}catch(a){throw await n(),a}i.done===!0?(s.close(),await n()):s.enqueue(i.value)}})},ut=async(e,t,r)=>{const n=r-t;if(n<=0)return Buffer.alloc(0);const o=Buffer.alloc(n),{bytesRead:s}=await e.read(o,0,n,t);return o.subarray(0,s)},K=async function*(e){if(e!==null){if(typeof e=="string"){yield new TextEncoder().encode(e);return}if(e instanceof ArrayBuffer){yield new Uint8Array(e);return}if(ArrayBuffer.isView(e)){yield new Uint8Array(e.buffer,e.byteOffset,e.byteLength);return}if(e instanceof Blob){yield*K(e.stream());return}if(e instanceof ReadableStream){for await(const t of e)if(t instanceof Uint8Array)yield t;else if(t instanceof ArrayBuffer)yield new Uint8Array(t);else throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put stream must yield byte chunks");return}throw new c("VALIDATION_ERROR","@lunora/platform-node: unsupported R2 put body")}},ht=async e=>{const t=[],r=async(n,o)=>{let s;try{s=await X(n,{withFileTypes:!0})}catch(a){if(b(a))return;throw a}const i=[];for(const a of s){if(a.name===S)continue;const l=o===""?a.name:`${o}/${a.name}`;if(a.isDirectory())i.push(r(R(n,a.name),l));else{const h=et(l);h!==void 0&&t.push(h)}}await Promise.all(i)};return await r(e,""),t.sort((n,o)=>n<o?-1:+(n>o)),t},wt=(e,t)=>{let r=0,n=e.length;for(;r<n;){const o=r+Math.floor((n-r)/2),s=e[o];s===void 0||s<=t?r=o+1:n=o}return r},mt=new Set(["EISDIR","ENOENT","ENOTDIR","EPERM"]),_=async e=>{try{await W(e)}catch(t){const r=t instanceof Error&&"code"in t?t.code:void 0;if(r===void 0||!mt.has(r))throw t}},Rt=async(e,t)=>{let r=e;for(;r!==t&&r.startsWith(`${t}${Y}`);){try{await U(r)}catch(n){if(!b(n))return}r=v(r)}},It=e=>{const{directory:t}=e;return{delete:async r=>{g(r);const n=R(t,A(r));await _(n),await Rt(n,t)},get:async(r,n)=>{g(r);const o=await F(R(t,A(r)));if(o===void 0)return null;const{bodySize:s,handle:i,meta:a}=o,{end:l,start:h}=dt(s,n?.range),u={};let f=!1;const w=async()=>{f||(f=!0,$.unregister(u),await i.close())},m=()=>{if(f)throw new c("BAD_REQUEST",`@lunora/platform-node: R2 object "${r}" body has already been consumed`)},T=async()=>{m();try{return await ut(i,h,l)}finally{await w()}},E={...I(r,a),arrayBuffer:async()=>P(await T()),get body(){return m(),l<=h?(w().catch(()=>{}),new Blob([]).stream()):lt(i,h,l,w)},text:async()=>(await T()).toString("utf8")};return $.register(E,{close:w},u),E},head:async r=>{g(r);const n=await V(R(t,A(r)));return n===void 0?null:I(r,n.meta)},list:async(r={})=>{const n=Math.max(1,O(r.limit,1e3,1e3));if(r.prefix?.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await ht(t),s=r.prefix??"",i=o.filter(d=>d.startsWith(s)),{delimiter:a}=r,l=a===void 0?i:i.filter(d=>!d.slice(s.length).includes(a)),h=a===void 0?void 0:[...new Set(i.map(d=>{const x=d.slice(s.length).indexOf(a);return x===-1?void 0:d.slice(0,s.length+x+a.length)}).filter(d=>d!==void 0))].toSorted((d,y)=>d<y?-1:+(d>y));let u=r.cursor;r.startAfter!==void 0&&(u===void 0||r.startAfter>u)&&(u=r.startAfter);const f=u===void 0?0:wt(l,u),w=l.slice(f,f+n),m=f+n<l.length,E=(await Promise.all(w.map(async d=>{const y=await V(R(t,A(d)));return y===void 0?void 0:I(d,y.meta)}))).filter(d=>d!==void 0);return{cursor:m?w.at(-1):void 0,delimitedPrefixes:h,objects:E,truncated:m}},put:async(r,n,o)=>{g(r),ot(o?.customMetadata);const s=o?.sha256===void 0?void 0:st(o.sha256),i=R(t,A(r)),a=R(t,S,z());try{await N(v(i),{recursive:!0})}catch(f){throw C(r,f)}await N(v(a),{recursive:!0});const l=G("sha256");let h=0,u;try{const f=await j(a,"w");try{for await(const m of K(n))l.update(m),h+=m.byteLength,await f.write(m);const w=l.digest("hex");if(s!==void 0&&s!==w)throw new c("VALIDATION_ERROR",`@lunora/platform-node: the SHA-256 checksum you specified for R2 key "${r}" did not match what we received. You provided a SHA-256 checksum with value: ${s}. Actual SHA-256 was: ${w}`);u={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:w,size:h,uploaded:new Date().toISOString()},await f.write(it(u))}finally{await f.close()}}catch(f){throw await _(a),f}try{await L(a,i)}catch{try{await U(i)}catch{}try{await N(v(i),{recursive:!0}),await L(a,i)}catch(f){throw await _(a),C(r,f)}}return I(r,u)}}};export{It as createNodeR2Bucket};
@@ -0,0 +1 @@
1
+ import{AsyncLocalStorage as S}from"node:async_hooks";import{LunoraError as v}from"@lunora/errors";import L from"better-sqlite3";const y=2147483647,R=6,O=100,I=e=>e===void 0?null:e,M=(e,t)=>({get databaseSize(){const l=e.pragma("page_count",{simple:!0}),r=e.pragma("page_size",{simple:!0});return l*r},exec:(l,...r)=>{t("run SQL");const o=e.prepare(l),s=r.map(c=>I(c)),n=o.reader?o.all(...s):(o.run(...s),[]);return{get columnNames(){return o.reader?o.columns().map(c=>c.name):void 0},[Symbol.iterator]:()=>n[Symbol.iterator](),one:()=>{if(n.length!==1)throw new Error(`expected exactly one row, got ${String(n.length)}`);return n[0]},...o.reader?{raw:()=>e.prepare(l).raw(!0).all(...s)[Symbol.iterator]()}:{},toArray:()=>[...n]}}}),N=(e,t,l)=>{let r,o,s=0,n;e.exec("CREATE TABLE IF NOT EXISTS _lunora_alarm (id INTEGER PRIMARY KEY CHECK (id = 0), scheduled_for INTEGER NOT NULL)");const c=a=>{a===void 0?e.prepare("DELETE FROM _lunora_alarm WHERE id = 0").run():e.prepare("INSERT INTO _lunora_alarm (id, scheduled_for) VALUES (0, ?) ON CONFLICT (id) DO UPDATE SET scheduled_for = excluded.scheduled_for").run(a)},p=()=>{o!==void 0&&(clearTimeout(o),o=void 0)},m=a=>{p();const d=Math.max(0,a-Date.now());if(d>y){o=setTimeout(()=>{m(a)},y);return}o=setTimeout(()=>{r=void 0,o=void 0,e.open&&(c(void 0),(async()=>{await l?.(),s=0})().catch(()=>{if(!e.open||r!==void 0)return;if(s+=1,s>R){s=0;return}const E=Date.now()+O*2**(s-1);r=E,c(E),m(E)}))},d)},f={delete:()=>{if(!e.open)throw new Error("platform closed: cannot delete an alarm");if(t.assertOwnTurn("delete an alarm"),s=0,c(void 0),t.inside()){n={at:void 0};return}r=void 0,p()},get:()=>(n===void 0?r:n.at)??null,set:a=>{if(!e.open)throw new Error("platform closed: cannot set an alarm");t.assertOwnTurn("set an alarm");const d=typeof a=="number"?a:a.getTime();if(s=0,c(d),t.inside()){n={at:d};return}r=d,m(d)}},h=()=>{if(n===void 0)return;const{at:a}=n;if(n=void 0,a===void 0){r=void 0,p();return}r=a,m(a)},T=()=>{n=void 0},w=e.prepare("SELECT scheduled_for FROM _lunora_alarm WHERE id = 0").get();return w!==void 0&&(r=w.scheduled_for,m(w.scheduled_for)),{alarms:f,commit:h,dispose:p,rollback:T}},k=(e={})=>{const t=new L(e.path??":memory:");t.pragma("journal_mode = WAL");const l=new S;let r=!1;const o=i=>{if(r&&l.getStore()!==!0)throw new v("SHARD_UNAVAILABLE",`shard busy: cannot ${i} while another task holds this shard's transaction`)},s=M(t,o),{alarms:n,commit:c,dispose:p,rollback:m}=N(t,{assertOwnTurn:o,inside:()=>l.getStore()===!0},e.onAlarm),f=new Set,h=new S;let T=Promise.resolve();const w=async i=>{if(h.getStore()===!0)return await i();const u=T;let A=()=>{};T=new Promise(g=>{A=g}),await u;try{return await h.run(!0,i)}finally{A()}},a=i=>w(i),d=async i=>{t.exec("BEGIN"),r=!0;try{const u=await l.run(!0,i);return t.exec("COMMIT"),r=!1,c(),u}catch(u){r=!1,m();try{t.exec("ROLLBACK")}catch{}throw u}},E=i=>w(async()=>d(i)),_={alarms:n,runSerialized:a,shardKey:e.shardKey,sql:s,transaction:E,waitUntil:i=>{const u=i.catch(()=>{}).finally(()=>{f.delete(u)});f.add(u)}};return{database:t,dispose:()=>{p(),t.open&&t.close()},drain:async()=>{for(;f.size>0;)await Promise.all(f)},host:_}};export{k as createNodeShardHost};
@@ -1 +1 @@
1
- import{mkdirSync as y,readdirSync as S}from"node:fs";import{join as h}from"node:path";import{LunoraError as l}from"@lunora/errors";import{createNodeShardKvStore as v}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as w}from"./createNodeShardHost-CfX2IrZ8.mjs";import{createNodeSocketHost as A}from"./createNodeSocketHost-DQs85G7W.mjs";const c=".sqlite3",f=r=>r.replaceAll(/[^\da-z._-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),g=r=>decodeURIComponent(r),$=(r,t)=>{const o=t.slice(0,-c.length),d=h(r,t);let a;try{a=g(o)}catch{throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" has a basename that is not valid percent-encoding, so the shard key it holds cannot be recovered. Rename or remove the file.`)}const e=`${f(a)}${c}`;if(e!==t)throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" was written under an older shard-key encoding. It decodes to shard key "${a}", which this build stores as "${e}" — so the key would be listed for fan-out while every read opened a new, empty database. Rename it: mv "${d}" "${h(r,e)}"`);return a},I=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){y(r.directory,{recursive:!0});for(const e of S(r.directory))e.endsWith(c)&&o.add($(r.directory,e))}const d=e=>{const s=t.get(e);if(s!==void 0)return s.shard;const i=r.directory===void 0?void 0:h(r.directory,`${f(e)}${c}`);let n;const{database:m,dispose:u,host:p}=w({onAlarm:()=>r.onAlarm?.(n),path:i,shardKey:e});return n={kv:v(m),shard:p,shardKey:e,sockets:A(m).socket},t.set(e,{dispose:u,shard:n}),o.add(e),n},a=e=>({fetch:async s=>{const i=d(e);return r.onFetch===void 0?new Response(e):r.onFetch(s,i)}});return{close:()=>{for(const e of t.values())e.dispose();t.clear()},directory:{get:e=>a(String(e)),getByName:e=>a(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:d}};export{I as createNodeShardRegistry};
1
+ import{mkdirSync as y,readdirSync as S}from"node:fs";import{join as h}from"node:path";import{LunoraError as l}from"@lunora/errors";import{createNodeShardKvStore as v}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as w}from"./createNodeShardHost-Da19DFX0.mjs";import{createNodeSocketHost as A}from"./createNodeSocketHost-DQs85G7W.mjs";const c=".sqlite3",f=r=>r.replaceAll(/[^\da-z._-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),g=r=>decodeURIComponent(r),$=(r,t)=>{const o=t.slice(0,-c.length),d=h(r,t);let a;try{a=g(o)}catch{throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" has a basename that is not valid percent-encoding, so the shard key it holds cannot be recovered. Rename or remove the file.`)}const e=`${f(a)}${c}`;if(e!==t)throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" was written under an older shard-key encoding. It decodes to shard key "${a}", which this build stores as "${e}" — so the key would be listed for fan-out while every read opened a new, empty database. Rename it: mv "${d}" "${h(r,e)}"`);return a},I=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){y(r.directory,{recursive:!0});for(const e of S(r.directory))e.endsWith(c)&&o.add($(r.directory,e))}const d=e=>{const s=t.get(e);if(s!==void 0)return s.shard;const i=r.directory===void 0?void 0:h(r.directory,`${f(e)}${c}`);let n;const{database:m,dispose:u,host:p}=w({onAlarm:()=>r.onAlarm?.(n),path:i,shardKey:e});return n={kv:v(m),shard:p,shardKey:e,sockets:A(m).socket},t.set(e,{dispose:u,shard:n}),o.add(e),n},a=e=>({fetch:async s=>{const i=d(e);return r.onFetch===void 0?new Response(e):r.onFetch(s,i)}});return{close:()=>{for(const e of t.values())e.dispose();t.clear()},directory:{get:e=>a(String(e)),getByName:e=>a(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:d}};export{I as createNodeShardRegistry};
@@ -0,0 +1 @@
1
+ import{LunoraError as l}from"@lunora/errors";import{isWorkflowDefinition as N,workflowDefaultName as T,createWorkflowRunContext as D,workflowBindingName as _}from"@lunora/workflow";import{defineWorkflow as b,createRuntime as M}from"@visulima/workflow";const v="@lunora/platform-node:terminated",O={d:864e5,day:864e5,days:864e5,h:36e5,hour:36e5,hours:36e5,m:6e4,millisecond:1,milliseconds:1,minute:6e4,minutes:6e4,ms:1,s:1e3,second:1e3,seconds:1e3,w:6048e5,week:6048e5,weeks:6048e5,month:2592e6,months:2592e6},$=/^(\d+(?:\.\d+)?)\s*([a-z]+)$/i,E=n=>{if(typeof n=="number"){if(!Number.isFinite(n))throw new l("VALIDATION_ERROR",`@lunora/platform-node: workflow duration must be finite, got ${String(n)}`);return Math.max(0,n)}const i=$.exec(n.trim());if(i===null)throw new l("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${n}"`);const o=Number(i[1]),r=(i[2]??"").toLowerCase(),s=O[r];if(s===void 0)throw new l("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${n}"`);const c=o*s;if(!Number.isFinite(c))throw new l("VALIDATION_ERROR",`@lunora/platform-node: workflow duration "${n}" overflows`);return Math.max(0,c)},R="@lunora/platform-node:alias",A=(n,i,o)=>({attempt:i,config:o,step:{count:1,name:n}}),L={retries:{limit:1}},S=2147483647,F=async n=>new Promise(i=>{setTimeout(i,Math.min(n,S))}),x=(n,i,o)=>{switch(i){case"exponential":return n*2**(o-1);case"linear":return n*o;default:return n}},U=async(n,i)=>{const o=i instanceof Error?i:new Error(String(i));for(const r of n.splice(0).toSorted((s,c)=>c.order-s.order))try{await r.handler({ctx:r.context,error:o,output:r.output,stepName:r.name})}catch{}},V=n=>{switch(n){case"completed":return"complete";case"failed":return"errored";default:return"waiting"}},P=n=>{const i=[];let o=0;return{do:async(r,s,c,y)=>{const w=o;o+=1;const m=typeof s!="function",I=m?c:s,h=m?y:c;if(typeof I!="function")throw new l("VALIDATION_ERROR",`@lunora/platform-node: step.do("${r}") requires a callback`);const f=m?s:L,t=f.retries?.limit??1;if(!Number.isFinite(t))throw new l("VALIDATION_ERROR",`@lunora/platform-node: step.do("${r}") retries.limit must be a finite number, got ${String(t)}`);const e=Math.max(1,Math.trunc(t)),a=f.retries?.delay===void 0?0:E(f.retries.delay);let u=A(r,1,f);try{const d=await n.step(r,async()=>{for(let p=1;;p+=1){u=A(r,p,f);try{return await I(u)}catch(k){if(p>=e)throw k;const g=x(a,f.retries?.backoff,p);g>0&&await F(g)}}});return h?.rollback!==void 0&&i.push({context:u,handler:h.rollback,name:r,order:w,output:d}),d}catch(d){throw h?.rollback!==void 0&&i.push({context:u,handler:h.rollback,name:r,order:w,output:void 0}),await U(i,d),d}},sleep:async(r,s)=>{const c=E(s);c!==0&&await n.sleep(r,c)},sleepUntil:async(r,s)=>{const c=typeof s=="number"?s:s.getTime();if(!Number.isFinite(c))throw new l("VALIDATION_ERROR",`@lunora/platform-node: step.sleepUntil("${r}") needs a valid timestamp`);const y=Math.max(0,c-Date.now());y!==0&&await n.sleep(r,y)},waitForEvent:(async(r,s)=>({payload:await n.waitForEvent(r,s.type,{timeout:s.timeout===void 0?void 0:E(s.timeout)}),type:s.type}))}},j=n=>{const i=Object.entries(n.workflows).map(([t,e])=>{if(!N(e))throw new l("VALIDATION_ERROR",`@lunora/platform-node: "${t}" is not a defineWorkflow result`);return{definition:e,exportName:t,id:e.name??T(t)}}),{store:o}=n,r={...n.env},s=i.map(({definition:t,exportName:e,id:a})=>b({id:a,tags:[e],run:async u=>{const d=P(u),p=D({env:r,event:{instanceId:u.runId,payload:u.payload,timestamp:new Date,workflowName:a},exportName:e,step:d});return await t.handler(p)}})),c=new Set,y={acquire:o.acquire===void 0?void 0:async(t,e,a)=>o.acquire?.(t,e,a)??!1,delete:async t=>o.delete(t),due:async(t,e)=>o.due(t,e),load:async t=>{const e=await o.load(t);return e===void 0||e.definitionId===v||e.definitionId===R?void 0:e},release:o.release===void 0?void 0:async(t,e)=>o.release?.(t,e),save:async t=>{c.has(t.runId)||await o.save(t)}},w=M({leaseTtlMs:n.leaseTtlMs,store:y,workflows:s}),m=t=>({id:t,pause:()=>Promise.reject(new l("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${t}" cannot be paused — the visulima engine has no pause equivalent`)),restart:()=>Promise.reject(new l("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${t}" cannot be restarted — the visulima engine has no restart equivalent`)),resume:async()=>{if((await o.load(t))?.definitionId===v)throw new l("BAD_REQUEST",`@lunora/platform-node: workflow instance "${t}" was terminated and cannot be resumed`);await w.resume(t)},sendEvent:async e=>{const a=await w.getRun(t);if(a?.status!=="waiting"||a.pending?.kind!=="event"||a.pending.eventName!==e.type)throw new l("BAD_REQUEST",`@lunora/platform-node: workflow instance "${t}" is not waiting for event "${e.type}"`);await w.signal(t,e.type,e.payload)},status:async()=>{const e=await o.load(t);if(e===void 0)return{status:"unknown"};if(e.definitionId===v)return{status:"terminated"};const a=await w.getRun(t);return a===void 0?{status:"unknown"}:{error:a.error===void 0?void 0:{message:a.error.message,name:a.error.name},output:a.output,status:V(a.status)}},terminate:async()=>{await o.load(t)!==void 0&&(c.add(t),await o.delete(t),await o.save({definitionId:v,runId:t,snapshot:void 0,status:"failed",updatedAt:Date.now()}))}}),I=async t=>{const e=await o.load(t);return e?.definitionId===R?e.snapshot:t},h=async(t,e)=>{if(e?.id===void 0){const d=await w.trigger(t,e?.params);return m(d.runId)}const a=await o.load(e.id);if(a?.definitionId===R)return m(a.snapshot);if(a!==void 0)throw new l("BAD_REQUEST",`@lunora/platform-node: workflow create({ id: "${e.id}" }) names an existing run — pass the id a previous create returned, or a fresh one`);const u=await w.trigger(t,e.params);return await o.save({definitionId:R,runId:e.id,snapshot:u.runId,status:"failed",updatedAt:Date.now()}),m(u.runId)},f={};for(const{exportName:t,id:e}of i){const a={create:async u=>h(e,u),createBatch:async u=>{const d=[];for(const p of u)d.push(await h(e,p));return d},get:async u=>m(await I(u))};f[t]=a,r[_(t)]=a}return{bindings:f,env:r,runtime:w}};export{j as createNodeWorkflowHost};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform-node",
3
- "version": "1.0.0-alpha.67",
3
+ "version": "1.0.0-alpha.69",
4
4
  "description": "Node implementation of the Lunora platform contracts: ShardHost, SocketHost, ShardDirectory, ShardKvStore, SchedulerHost over better-sqlite3 and an in-process socket registry (dev/test target)",
5
5
  "keywords": [
6
6
  "better-sqlite3",
@@ -46,12 +46,12 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/d1": "1.0.0-alpha.116",
49
+ "@lunora/d1": "1.0.0-alpha.118",
50
50
  "@lunora/errors": "1.0.0-alpha.36",
51
- "@lunora/platform": "1.0.0-alpha.30",
52
- "@lunora/queue": "1.0.0-alpha.54",
53
- "@lunora/sql-store": "1.0.0-alpha.116",
54
- "@lunora/workflow": "1.0.0-alpha.51",
51
+ "@lunora/platform": "1.0.0-alpha.32",
52
+ "@lunora/queue": "1.0.0-alpha.56",
53
+ "@lunora/sql-store": "1.0.0-alpha.119",
54
+ "@lunora/workflow": "1.0.0-alpha.52",
55
55
  "@visulima/workflow": "3.0.0",
56
56
  "better-sqlite3": "^13.0.3",
57
57
  "cron-parser": "5.10.0"
@@ -1,17 +0,0 @@
1
- import{randomUUID as b}from"node:crypto";import{deserialize as F,serialize as H}from"node:v8";import{LunoraError as f}from"@lunora/errors";import{isQueueDefinition as W,queueDefaultName as k,queueBindingName as C}from"@lunora/queue";const l={maxBatchSize:10,maxBatchTimeoutSeconds:5,maxRetries:3,retryDelaySeconds:0,visibilityTimeoutMs:3e4},X=43200,w=(e,o)=>{switch(o){case"bytes":{if(e instanceof ArrayBuffer)return Buffer.from(e);if(ArrayBuffer.isView(e))return Buffer.from(e.buffer,e.byteOffset,e.byteLength);throw new f("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "bytes" needs an ArrayBuffer or a typed array')}case"text":{if(typeof e!="string")throw new f("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "text" needs a string');return Buffer.from(e,"utf8")}case"v8":return H(e);default:return Buffer.from(JSON.stringify(e??null),"utf8")}},y=(e,o)=>{switch(o){case"bytes":return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);case"text":return e.toString("utf8");case"v8":return F(e);default:return JSON.parse(e.toString("utf8"))}},P=e=>{try{return y(e.body,e.content_type)}catch{return}},p=e=>e===void 0||!Number.isFinite(e)?0:Math.min(Math.max(0,Math.floor(e)),X)*1e3,j=(e,o)=>{e.pragma("journal_mode = WAL"),e.exec(`CREATE TABLE IF NOT EXISTS _lunora_queue_messages (
2
- id TEXT PRIMARY KEY,
3
- queue TEXT NOT NULL,
4
- body BLOB NOT NULL,
5
- content_type TEXT NOT NULL,
6
- attempts INTEGER NOT NULL DEFAULT 0,
7
- visible_at INTEGER NOT NULL,
8
- enqueued_at INTEGER NOT NULL,
9
- state TEXT NOT NULL DEFAULT 'pending'
10
- )`),e.exec("CREATE INDEX IF NOT EXISTS _lunora_queue_messages_due ON _lunora_queue_messages (queue, state, visible_at)");const h=e.prepare("INSERT INTO _lunora_queue_messages (id, queue, body, content_type, visible_at, enqueued_at) VALUES (?, ?, ?, ?, ?, ?)"),A=e.prepare(`UPDATE _lunora_queue_messages
11
- SET attempts = attempts + 1, visible_at = ?
12
- WHERE id IN (
13
- SELECT id FROM _lunora_queue_messages
14
- WHERE queue = ? AND state = 'pending' AND visible_at <= ?
15
- ORDER BY visible_at, enqueued_at LIMIT ?
16
- )
17
- RETURNING *`),D=e.prepare("UPDATE _lunora_queue_messages SET visible_at = ? WHERE id = ?"),R=e.prepare("DELETE FROM _lunora_queue_messages WHERE id = ?"),O=e.prepare("UPDATE _lunora_queue_messages SET state = 'dead' WHERE id = ?"),S=e.prepare("SELECT enqueued_at FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ? ORDER BY enqueued_at LIMIT 1"),q=e.prepare("SELECT COUNT(*) AS n FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ?"),v=e.prepare("SELECT * FROM _lunora_queue_messages WHERE queue = ? AND state = 'dead' ORDER BY enqueued_at"),I=e.prepare("UPDATE _lunora_queue_messages SET state = 'pending', attempts = 0, visible_at = ? WHERE id = ? AND state = 'dead'"),E=Object.entries(o.queues).map(([t,n])=>{if(!W(n))throw new f("VALIDATION_ERROR",`@lunora/platform-node: "${t}" is not a defineQueue result`);return{definition:n,exportName:t,name:n.name??k(t)}}),B=new Set(E.map(t=>t.name));for(const t of E){const n=t.definition.deadLetterQueue;if(n!==void 0){if(n===t.name)throw new f("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names itself as its own deadLetterQueue, which would redeliver forever`);if(!B.has(n))throw new f("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names deadLetterQueue "${n}", which no declared queue provides — its dead letters would be unreachable`)}}const M=o.visibilityTimeoutMs??l.visibilityTimeoutMs,_=o.now??Date.now,T=(t,n,s,a,i)=>{h.run(b(),t,w(n,s),s,i+a,i)},x=(t,n,s,a,i)=>{if(s==="ack"){R.run(t.id);return}const u=n.definition.maxRetries??l.maxRetries;if(t.attempts>u){const m=n.definition.deadLetterQueue;m===void 0?O.run(t.id):(T(m,y(t.body,t.content_type),t.content_type,0,i),R.run(t.id));return}const c=p(a??n.definition.retryDelay??l.retryDelaySeconds);D.run(i+c,t.id)},N={},L={...o.env};for(const t of E){const n={send:async(s,a)=>{T(t.name,s,a?.contentType??"json",p(a?.delaySeconds),_())},sendBatch:async(s,a)=>{const i=_(),u=p(a?.delaySeconds);e.transaction(()=>{for(const c of s){const m=c.delaySeconds===void 0?u:p(c.delaySeconds);T(t.name,c.body,c.contentType??"json",m,i)}})()}};N[t.exportName]=n,L[C(t.exportName)]=n}const U=async(t,n)=>{const s=Math.min(Math.max(1,t.definition.maxBatchSize??l.maxBatchSize),100),a=q.get(t.name,n)?.n??0;if(a===0)return!1;if(a<s){const r=S.get(t.name,n)?.enqueued_at,d=Math.min(Math.max(0,t.definition.maxBatchTimeout??l.maxBatchTimeoutSeconds),60)*1e3;if(r!==void 0&&n-r<d)return!1}const i=e.transaction(()=>A.all(n+M,t.name,n,s)).immediate();if(i.length===0)return!1;const u=new Map,c=i.map(r=>({ack:()=>{u.set(r.id,{outcome:"ack"})},attempts:r.attempts,body:y(r.body,r.content_type),id:r.id,retry:d=>{u.set(r.id,{delaySeconds:d?.delaySeconds,outcome:"retry"})},timestamp:new Date(r.enqueued_at)})),m={ackAll:()=>{for(const r of i)u.set(r.id,{outcome:"ack"})},messages:c,queue:t.name,retryAll:r=>{for(const d of i)u.set(d.id,{delaySeconds:r?.delaySeconds,outcome:"retry"})}};let g="ack";try{await o.onBatch(m)}catch{g="retry"}return e.transaction(()=>{for(const r of i){const d=u.get(r.id);x(r,t,d?.outcome??g,d?.delaySeconds,n)}})(),!0};return{bindings:N,deadLetters:{list:t=>v.all(t).map(n=>({attempts:n.attempts,body:P(n),id:n.id})),requeue:t=>I.run(_(),t).changes>0},env:L,poll:async(t=_())=>{let n=0;for(const s of E)s.definition.mode!=="pull"&&await U(s,t)&&(n+=1);return n}}};export{j as createNodeQueueHost};
@@ -1 +0,0 @@
1
- import{randomUUID as k,createHash as H}from"node:crypto";import{mkdir as _,open as j,rename as K,unlink as G,readdir as z}from"node:fs/promises";import{join as m,dirname as L}from"node:path";import{LunoraError as l}from"@lunora/errors";import{t as U}from"./to-array-buffer-DVyXU4Ep.mjs";new TextEncoder;const W=Array.from({length:32},(t,e)=>e),X=new RegExp(`[${W.map(t=>String.fromCodePoint(t)).join("")}]`,"u"),Y=t=>X.test(t),O=".lunora-tmp",V="LNR1",p=8,b=1024,B=255,J=/[%A-Z:]/g,$=t=>{const e=t.replaceAll(J,r=>`%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),n=e.at(-1);return n==="."||n===" "?`${e.slice(0,-1)}%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:e},Z=t=>{try{return decodeURIComponent(t)}catch{return}},g=t=>t.split("/").map(e=>$(e)).join("/"),q=t=>{const e=[];for(const n of t.split("/")){const r=Z(n);if(r===void 0)return;e.push(r)}return e.join("/")},Q=new TextEncoder,tt=t=>Q.encode(t).length,E=t=>{if(typeof t!="string"||t.length===0)throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(tt(t)>b)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(b)}-byte limit`);if(t.includes("\0"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(Y(t))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(t.includes("\\"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(t.startsWith("/"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const e of t.split("/")){if(Buffer.byteLength($(e),"utf8")>B)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${e}" exceeds ${String(B)} bytes once escaped for the filesystem`);if(e===""||e==="."||e==="..")throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key has an empty, \`.\` or \`..\` path segment ("${t}")`);if(e===O)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${O}")`)}},x=t=>t instanceof Error&&"code"in t&&t.code==="ENOENT",D=(t,e)=>{const n=e instanceof Error&&"code"in e?e.code:void 0;return n==="EEXIST"||n==="EISDIR"||n==="ENOTDIR"?new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key "${t}" collides with an existing object at one of its path prefixes — this host stores one file per key, so a key and a prefix of it cannot both hold objects`):e},et=t=>{const e=Buffer.from(JSON.stringify(t),"utf8"),n=Buffer.alloc(p);return n.writeUInt32BE(e.byteLength,0),n.write(V,4,"ascii"),Buffer.concat([e,n])},nt=t=>{if(typeof t!="object"||t===null)return!1;const e=t;return typeof e.sha256Hex=="string"&&typeof e.size=="number"&&typeof e.uploaded=="string"},rt=async t=>{const e=await t.stat();if(!e.isFile()||e.size<p)return;const n=Buffer.alloc(p);if(await t.read(n,0,p,e.size-p),n.toString("ascii",4,p)!==V)return;const r=n.readUInt32BE(0),o=e.size-p-r;if(r===0||o<0)return;const s=Buffer.alloc(r),{bytesRead:i}=await t.read(s,0,r,o);if(i!==r)return;let a;try{a=JSON.parse(s.toString("utf8"))}catch{return}return nt(a)?{bodySize:o,meta:a}:void 0},P=async t=>{let e;try{e=await j(t,"r")}catch(n){if(x(n))return;throw n}try{const n=await rt(e);if(n===void 0){await e.close();return}return{...n,handle:e}}catch(n){throw await e.close(),n}},M=new FinalizationRegistry(t=>{t.close().catch(()=>{})}),C=async t=>{const e=await P(t);if(e!==void 0)return await e.handle.close(),{bodySize:e.bodySize,meta:e.meta}},I=(t,e)=>{const{sha256Hex:n}=e,r=Buffer.from(n,"hex");return{checksums:{sha256:U(r)},customMetadata:e.customMetadata,etag:n,httpEtag:`"${n}"`,httpMetadata:e.httpMetadata,key:t,sha256:n,sha256Base64:r.toString("base64"),size:e.size,uploaded:new Date(e.uploaded)}},v=(t,e,n)=>t===void 0||!Number.isFinite(t)?e:Math.min(Math.max(0,Math.floor(t)),n),ot=(t,e)=>{if(e===void 0)return{end:t,start:0};if("suffix"in e)return{end:t,start:t-v(e.suffix,t,t)};const n=v(e.offset,0,t);return{end:e.length===void 0||!Number.isFinite(e.length)?t:Math.min(n+v(e.length,0,t),t),start:n}},at=(t,e,n,r)=>{const o=t.createReadStream({autoClose:!1,end:n-1,start:e})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await r()},pull:async s=>{let i;try{i=await o.next()}catch(a){throw await r(),a}i.done===!0?(s.close(),await r()):s.enqueue(i.value)}})},st=async(t,e,n)=>{const r=n-e;if(r<=0)return Buffer.alloc(0);const o=Buffer.alloc(r),{bytesRead:s}=await t.read(o,0,r,e);return o.subarray(0,s)},F=async function*(t){if(t!==null){if(typeof t=="string"){yield new TextEncoder().encode(t);return}if(t instanceof ArrayBuffer){yield new Uint8Array(t);return}if(ArrayBuffer.isView(t)){yield new Uint8Array(t.buffer,t.byteOffset,t.byteLength);return}if(t instanceof Blob){yield*F(t.stream());return}if(t instanceof ReadableStream){for await(const e of t)if(e instanceof Uint8Array)yield e;else if(e instanceof ArrayBuffer)yield new Uint8Array(e);else throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 put stream must yield byte chunks");return}throw new l("VALIDATION_ERROR","@lunora/platform-node: unsupported R2 put body")}},it=async t=>{const e=[],n=async(r,o)=>{let s;try{s=await z(r,{withFileTypes:!0})}catch(a){if(x(a))return;throw a}const i=[];for(const a of s){if(a.name===O)continue;const d=o===""?a.name:`${o}/${a.name}`;if(a.isDirectory())i.push(n(m(r,a.name),d));else{const u=q(d);u!==void 0&&e.push(u)}}await Promise.all(i)};return await n(t,""),e.sort((r,o)=>r<o?-1:+(r>o)),e},ct=(t,e)=>{let n=0,r=t.length;for(;n<r;){const o=n+Math.floor((r-n)/2),s=t[o];s===void 0||s<=e?n=o+1:r=o}return n},T=async t=>{try{await G(t)}catch(e){if(!x(e))throw e}},wt=t=>{const{directory:e}=t;return{delete:async n=>{E(n),await T(m(e,g(n)))},get:async(n,r)=>{E(n);const o=await P(m(e,g(n)));if(o===void 0)return null;const{bodySize:s,handle:i,meta:a}=o,{end:d,start:u}=ot(s,r?.range),c={};let h=!1;const w=async()=>{h||(h=!0,M.unregister(c),await i.close())},y=()=>{if(h)throw new l("BAD_REQUEST",`@lunora/platform-node: R2 object "${n}" body has already been consumed`)},N=async()=>{y();try{return await st(i,u,d)}finally{await w()}},A={...I(n,a),arrayBuffer:async()=>U(await N()),get body(){return y(),d<=u?(w().catch(()=>{}),new Blob([]).stream()):at(i,u,d,w)},text:async()=>(await N()).toString("utf8")};return M.register(A,{close:w},c),A},head:async n=>{E(n);const r=await C(m(e,g(n)));return r===void 0?null:I(n,r.meta)},list:async(n={})=>{const r=Math.max(1,v(n.limit,1e3,1e3));if(n.prefix?.includes("\0"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await it(e),s=n.prefix??"",i=o.filter(f=>f.startsWith(s)),{delimiter:a}=n,d=a===void 0?i:i.filter(f=>!f.slice(s.length).includes(a)),u=a===void 0?void 0:[...new Set(i.map(f=>{const S=f.slice(s.length).indexOf(a);return S===-1?void 0:f.slice(0,s.length+S+a.length)}).filter(f=>f!==void 0))].toSorted((f,R)=>f<R?-1:+(f>R));let c=n.cursor;n.startAfter!==void 0&&(c===void 0||n.startAfter>c)&&(c=n.startAfter);const h=c===void 0?0:ct(d,c),w=d.slice(h,h+r),y=h+r<d.length,A=(await Promise.all(w.map(async f=>{const R=await C(m(e,g(f)));return R===void 0?void 0:I(f,R.meta)}))).filter(f=>f!==void 0);return{cursor:y?w.at(-1):void 0,delimitedPrefixes:u,objects:A,truncated:y}},put:async(n,r,o)=>{E(n);const s=m(e,g(n)),i=m(e,O,k());try{await _(L(s),{recursive:!0})}catch(c){throw D(n,c)}await _(L(i),{recursive:!0});const a=H("sha256");let d=0,u;try{const c=await j(i,"w");try{for await(const h of F(r))a.update(h),d+=h.byteLength,await c.write(h);u={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:a.digest("hex"),size:d,uploaded:new Date().toISOString()},await c.write(et(u))}finally{await c.close()}}catch(c){throw await T(i),c}try{await K(i,s)}catch(c){throw await T(i),D(n,c)}return I(n,u)}}};export{wt as createNodeR2Bucket};
@@ -1 +0,0 @@
1
- import{AsyncLocalStorage as y}from"node:async_hooks";import{LunoraError as v}from"@lunora/errors";import L from"better-sqlite3";const S=2147483647,R=6,O=100,I=e=>e===void 0?null:e,M=(e,t)=>({get databaseSize(){const c=e.pragma("page_count",{simple:!0}),r=e.pragma("page_size",{simple:!0});return c*r},exec:(c,...r)=>{t("run SQL");const a=e.prepare(c),s=r.map(d=>I(d)),o=a.reader?a.all(...s):(a.run(...s),[]);return{[Symbol.iterator]:()=>o[Symbol.iterator](),one:()=>{if(o.length!==1)throw new Error(`expected exactly one row, got ${String(o.length)}`);return o[0]},toArray:()=>[...o]}}}),N=(e,t,c)=>{let r,a,s=0,o;e.exec("CREATE TABLE IF NOT EXISTS _lunora_alarm (id INTEGER PRIMARY KEY CHECK (id = 0), scheduled_for INTEGER NOT NULL)");const d=n=>{n===void 0?e.prepare("DELETE FROM _lunora_alarm WHERE id = 0").run():e.prepare("INSERT INTO _lunora_alarm (id, scheduled_for) VALUES (0, ?) ON CONFLICT (id) DO UPDATE SET scheduled_for = excluded.scheduled_for").run(n)},p=()=>{a!==void 0&&(clearTimeout(a),a=void 0)},m=n=>{p();const l=Math.max(0,n-Date.now());if(l>S){a=setTimeout(()=>{m(n)},S);return}a=setTimeout(()=>{r=void 0,a=void 0,e.open&&(d(void 0),(async()=>{await c?.(),s=0})().catch(()=>{if(!e.open||r!==void 0)return;if(s+=1,s>R){s=0;return}const h=Date.now()+O*2**(s-1);r=h,d(h),m(h)}))},l)},f={delete:()=>{if(!e.open)throw new Error("platform closed: cannot delete an alarm");if(t.assertOwnTurn("delete an alarm"),s=0,d(void 0),t.inside()){o={at:void 0};return}r=void 0,p()},get:()=>(o===void 0?r:o.at)??null,set:n=>{if(!e.open)throw new Error("platform closed: cannot set an alarm");t.assertOwnTurn("set an alarm");const l=typeof n=="number"?n:n.getTime();if(s=0,d(l),t.inside()){o={at:l};return}r=l,m(l)}},w=()=>{if(o===void 0)return;const{at:n}=o;if(o=void 0,n===void 0){r=void 0,p();return}r=n,m(n)},T=()=>{o=void 0},E=e.prepare("SELECT scheduled_for FROM _lunora_alarm WHERE id = 0").get();return E!==void 0&&(r=E.scheduled_for,m(E.scheduled_for)),{alarms:f,commit:w,dispose:p,rollback:T}},k=(e={})=>{const t=new L(e.path??":memory:");t.pragma("journal_mode = WAL");const c=new y;let r=!1;const a=i=>{if(r&&c.getStore()!==!0)throw new v("SHARD_UNAVAILABLE",`shard busy: cannot ${i} while another task holds this shard's transaction`)},s=M(t,a),{alarms:o,commit:d,dispose:p,rollback:m}=N(t,{assertOwnTurn:a,inside:()=>c.getStore()===!0},e.onAlarm),f=new Set,w=new y;let T=Promise.resolve();const E=async i=>{if(w.getStore()===!0)return await i();const u=T;let A=()=>{};T=new Promise(g=>{A=g}),await u;try{return await w.run(!0,i)}finally{A()}},n=i=>E(i),l=async i=>{t.exec("BEGIN"),r=!0;try{const u=await c.run(!0,i);return t.exec("COMMIT"),r=!1,d(),u}catch(u){r=!1,m();try{t.exec("ROLLBACK")}catch{}throw u}},h=i=>E(async()=>l(i)),_={alarms:o,runSerialized:n,shardKey:e.shardKey,sql:s,transaction:h,waitUntil:i=>{const u=i.catch(()=>{}).finally(()=>{f.delete(u)});f.add(u)}};return{database:t,dispose:()=>{p(),t.open&&t.close()},drain:async()=>{for(;f.size>0;)await Promise.all(f)},host:_}};export{k as createNodeShardHost};
@@ -1 +0,0 @@
1
- import{LunoraError as u}from"@lunora/errors";import{isWorkflowDefinition as E,workflowDefaultName as k,createWorkflowRunContext as A,workflowBindingName as N}from"@lunora/workflow";import{defineWorkflow as D,createRuntime as T}from"@visulima/workflow";const m="@lunora/platform-node:terminated",_={d:864e5,day:864e5,days:864e5,h:36e5,hour:36e5,hours:36e5,m:6e4,millisecond:1,milliseconds:1,minute:6e4,minutes:6e4,ms:1,s:1e3,second:1e3,seconds:1e3,w:6048e5,week:6048e5,weeks:6048e5,month:2592e6,months:2592e6},O=/^(\d+(?:\.\d+)?)\s*([a-z]+)$/i,v=o=>{if(typeof o=="number"){if(!Number.isFinite(o))throw new u("VALIDATION_ERROR",`@lunora/platform-node: workflow duration must be finite, got ${String(o)}`);return Math.max(0,o)}const r=O.exec(o.trim());if(r===null)throw new u("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${o}"`);const n=Number(r[1]),s=(r[2]??"").toLowerCase(),d=_[s];if(d===void 0)throw new u("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${o}"`);const l=n*d;if(!Number.isFinite(l))throw new u("VALIDATION_ERROR",`@lunora/platform-node: workflow duration "${o}" overflows`);return Math.max(0,l)},p="@lunora/platform-node:alias",b=o=>({attempt:1,config:{retries:{limit:1}},step:{count:1,name:o}}),$=o=>{switch(o){case"completed":return"complete";case"failed":return"errored";default:return"waiting"}},L=o=>({do:(async(r,n,s,d)=>{const l=typeof n=="function"?n:s;if(typeof l!="function")throw new u("VALIDATION_ERROR",`@lunora/platform-node: step.do("${r}") requires a callback`);return o.step(r,async()=>l(b(r)))}),sleep:async(r,n)=>{const s=v(n);s!==0&&await o.sleep(r,s)},sleepUntil:async(r,n)=>{const s=typeof n=="number"?n:n.getTime();if(!Number.isFinite(s))throw new u("VALIDATION_ERROR",`@lunora/platform-node: step.sleepUntil("${r}") needs a valid timestamp`);const d=Math.max(0,s-Date.now());d!==0&&await o.sleep(r,d)},waitForEvent:(async(r,n)=>({payload:await o.waitForEvent(r,n.type,{timeout:n.timeout===void 0?void 0:v(n.timeout)}),type:n.type}))}),U=o=>{const r=Object.entries(o.workflows).map(([e,t])=>{if(!E(t))throw new u("VALIDATION_ERROR",`@lunora/platform-node: "${e}" is not a defineWorkflow result`);return{definition:t,exportName:e,id:t.name??k(e)}}),{store:n}=o,s={...o.env},d=r.map(({definition:e,exportName:t,id:a})=>D({id:a,tags:[t],run:async i=>{const w=L(i),I=A({env:s,event:{instanceId:i.runId,payload:i.payload,timestamp:new Date,workflowName:a},exportName:t,step:w});return await e.handler(I)}})),l=new Set,R={acquire:n.acquire===void 0?void 0:async(e,t,a)=>n.acquire?.(e,t,a)??!1,delete:async e=>n.delete(e),due:async(e,t)=>n.due(e,t),load:async e=>{const t=await n.load(e);return t===void 0||t.definitionId===m||t.definitionId===p?void 0:t},release:n.release===void 0?void 0:async(e,t)=>n.release?.(e,t),save:async e=>{l.has(e.runId)||await n.save(e)}},c=T({leaseTtlMs:o.leaseTtlMs,store:R,workflows:d}),f=e=>({id:e,pause:()=>Promise.reject(new u("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${e}" cannot be paused — the visulima engine has no pause equivalent`)),restart:()=>Promise.reject(new u("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${e}" cannot be restarted — the visulima engine has no restart equivalent`)),resume:async()=>{if((await n.load(e))?.definitionId===m)throw new u("BAD_REQUEST",`@lunora/platform-node: workflow instance "${e}" was terminated and cannot be resumed`);await c.resume(e)},sendEvent:async t=>{const a=await c.getRun(e);if(a?.status!=="waiting"||a.pending?.kind!=="event"||a.pending.eventName!==t.type)throw new u("BAD_REQUEST",`@lunora/platform-node: workflow instance "${e}" is not waiting for event "${t.type}"`);await c.signal(e,t.type,t.payload)},status:async()=>{const t=await n.load(e);if(t===void 0)return{status:"unknown"};if(t.definitionId===m)return{status:"terminated"};const a=await c.getRun(e);return a===void 0?{status:"unknown"}:{error:a.error===void 0?void 0:{message:a.error.message,name:a.error.name},output:a.output,status:$(a.status)}},terminate:async()=>{await n.load(e)!==void 0&&(l.add(e),await n.delete(e),await n.save({definitionId:m,runId:e,snapshot:void 0,status:"failed",updatedAt:Date.now()}))}}),g=async e=>{const t=await n.load(e);return t?.definitionId===p?t.snapshot:e},y=async(e,t)=>{if(t?.id===void 0){const w=await c.trigger(e,t?.params);return f(w.runId)}const a=await n.load(t.id);if(a?.definitionId===p)return f(a.snapshot);if(a!==void 0)throw new u("BAD_REQUEST",`@lunora/platform-node: workflow create({ id: "${t.id}" }) names an existing run — pass the id a previous create returned, or a fresh one`);const i=await c.trigger(e,t.params);return await n.save({definitionId:p,runId:t.id,snapshot:i.runId,status:"failed",updatedAt:Date.now()}),f(i.runId)},h={};for(const{exportName:e,id:t}of r){const a={create:async i=>y(t,i),createBatch:async i=>{const w=[];for(const I of i)w.push(await y(t,I));return w},get:async i=>f(await g(i))};h[e]=a,s[N(e)]=a}return{bindings:h,env:s,runtime:c}};export{U as createNodeWorkflowHost};