@lunora/platform-node 1.0.0-alpha.34 → 1.0.0-alpha.35

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 D}from"../packem_shared/createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as g}from"../packem_shared/createNodeShardHost-C0j8nvSU.mjs";import{createNodeShardRegistry as k}from"../packem_shared/createNodeShardRegistry-DKdc0pV8.mjs";import{createNodeSocketHost as H}from"../packem_shared/createNodeSocketHost-DK3X5PlQ.mjs";const b=()=>{const{database:o,dispose:n,host:m}=g(),h=N(o),r=k(),{directory:p}=r,{readFrames:l,restoreSocket:u,simulateRecycle:w,socket:S}=H(o),i=new Set,{dispose:f,scheduler:d,simulateDeadLetter:y}=D(o,{onDispatch:(e,s,t)=>{i.add(t.id)}}),c=()=>{f(),n(),r.close()};return{awaitAlarmFired:async e=>{await new Promise(s=>{setTimeout(s,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await d.list?.())?.find(a=>a.id===e);return t!==void 0&&await new Promise(a=>{setTimeout(a,Math.max(0,t.scheduledFor-Date.now())+30)}),i.has(e)},cleanup:c,directory:p,disposeTerminally:c,kv:h,readFrames:l,restoreSocket:u,scheduler:d,shard:m,simulateDeadLetter:y,simulateRecycle:w,socket:S}};export{b as createNodeConformanceHost};
1
+ import{createNodeShardKvStore as N}from"../packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeSchedulerHost as D}from"../packem_shared/createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as g}from"../packem_shared/createNodeShardHost-CNoG3Fzy.mjs";import{createNodeShardRegistry as k}from"../packem_shared/createNodeShardRegistry-BmSAs2lp.mjs";import{createNodeSocketHost as H}from"../packem_shared/createNodeSocketHost-DK3X5PlQ.mjs";const b=()=>{const{database:o,dispose:n,host:m}=g(),h=N(o),r=k(),{directory:p}=r,{readFrames:l,restoreSocket:u,simulateRecycle:w,socket:S}=H(o),i=new Set,{dispose:f,scheduler:d,simulateDeadLetter:y}=D(o,{onDispatch:(e,s,t)=>{i.add(t.id)}}),c=()=>{f(),n(),r.close()};return{awaitAlarmFired:async e=>{await new Promise(s=>{setTimeout(s,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await d.list?.())?.find(a=>a.id===e);return t!==void 0&&await new Promise(a=>{setTimeout(a,Math.max(0,t.scheduledFor-Date.now())+30)}),i.has(e)},cleanup:c,directory:p,disposeTerminally:c,kv:h,readFrames:l,restoreSocket:u,scheduler:d,shard:m,simulateDeadLetter:y,simulateRecycle:w,socket:S}};export{b as createNodeConformanceHost};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createNodeGlobalStore as r,createNodeSqlExec as t}from"./packem_shared/createNodeGlobalStore-Cwi33XOk.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-1fAQ_biu.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost--mBLGSxF.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-BP8iXgC4.mjs";import{createNodeSchedulerHost as l}from"./packem_shared/createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as h}from"./packem_shared/createNodeShardHost-C0j8nvSU.mjs";import{createNodeShardRegistry as k}from"./packem_shared/createNodeShardRegistry-DKdc0pV8.mjs";import{createNodeShardState as w}from"./packem_shared/createNodeShardState-CpUzvWwd.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-DK3X5PlQ.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-Cwi33XOk.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-8zj2kszG.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost--mBLGSxF.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-B1zKW_b8.mjs";import{createNodeSchedulerHost as l}from"./packem_shared/createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as h}from"./packem_shared/createNodeShardHost-CNoG3Fzy.mjs";import{createNodeShardRegistry as k}from"./packem_shared/createNodeShardRegistry-BmSAs2lp.mjs";import{createNodeShardState as w}from"./packem_shared/createNodeShardState-CpUzvWwd.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-DK3X5PlQ.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 +1 @@
1
- import{LunoraError as l}from"@lunora/errors";import{NODE_CAPABILITIES as p}from"@lunora/platform";import{createNodeShardKvStore as f}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as N}from"./createNodeQueueHost--mBLGSxF.mjs";import{createNodeSchedulerHost as S}from"./createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-C0j8nvSU.mjs";import{createNodeShardRegistry as v}from"./createNodeShardRegistry-DKdc0pV8.mjs";import{createNodeSocketHost as w}from"./createNodeSocketHost-DK3X5PlQ.mjs";const H=(e={})=>{const{database:o,dispose:s,drain:a,host:c}=q(e),d=f(o),r=v(e),{directory:u}=r,{socket:i}=w(o),{dispose:m,scheduler:h}=S(o,e),n=e.queues===void 0?void 0:N(o,{onBatch:e.onQueueBatch??(()=>{throw new l("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:e.queues}),t=()=>{m(),s(),r.close()};return{capabilities:p,close:t,directory:u,drain:a,kv:d,queues:n,scheduler:h,shard:c,sockets:i,[Symbol.dispose]:t}};export{H as createNodePlatform};
1
+ import{LunoraError as l}from"@lunora/errors";import{NODE_CAPABILITIES as p}from"@lunora/platform";import{createNodeShardKvStore as f}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as N}from"./createNodeQueueHost--mBLGSxF.mjs";import{createNodeSchedulerHost as S}from"./createNodeSchedulerHost-vZgv1CEo.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-CNoG3Fzy.mjs";import{createNodeShardRegistry as v}from"./createNodeShardRegistry-BmSAs2lp.mjs";import{createNodeSocketHost as w}from"./createNodeSocketHost-DK3X5PlQ.mjs";const H=(e={})=>{const{database:o,dispose:s,drain:a,host:c}=q(e),d=f(o),r=v(e),{directory:u}=r,{socket:i}=w(o),{dispose:m,scheduler:h}=S(o,e),n=e.queues===void 0?void 0:N(o,{onBatch:e.onQueueBatch??(()=>{throw new l("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:e.queues}),t=()=>{m(),s(),r.close()};return{capabilities:p,close:t,directory:u,drain:a,kv:d,queues:n,scheduler:h,shard:c,sockets:i,[Symbol.dispose]:t}};export{H as createNodePlatform};
@@ -0,0 +1 @@
1
+ import{randomUUID as $,createHash as P}from"node:crypto";import{mkdir as N,open as D,rename as F,unlink as k,readdir as H}from"node:fs/promises";import{join as w,dirname as _}from"node:path";import{LunoraError as f}from"@lunora/errors";import{t as M}from"./to-array-buffer-DVyXU4Ep.mjs";new TextEncoder;const K=Array.from({length:32},(t,e)=>e),G=new RegExp(`[${K.map(t=>String.fromCodePoint(t)).join("")}]`,"u"),z=t=>G.test(t),I=".lunora-tmp",C="LNR1",m=8,S=1024,x=255,W=/[%A-Z:]/g,j=t=>{const e=t.replaceAll(W,n=>`%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),r=e.at(-1);return r==="."||r===" "?`${e.slice(0,-1)}%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:e},X=t=>{try{return decodeURIComponent(t)}catch{return}},y=t=>t.split("/").map(e=>j(e)).join("/"),Y=t=>{const e=[];for(const r of t.split("/")){const n=X(r);if(n===void 0)return;e.push(n)}return e.join("/")},J=new TextEncoder,Z=t=>J.encode(t).length,g=t=>{if(typeof t!="string"||t.length===0)throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(Z(t)>S)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(S)}-byte limit`);if(t.includes("\0"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(z(t))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(t.includes("\\"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(t.startsWith("/"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const e of t.split("/")){if(Buffer.byteLength(j(e),"utf8")>x)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${e}" exceeds ${String(x)} bytes once escaped for the filesystem`);if(e===""||e==="."||e==="..")throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key has an empty, \`.\` or \`..\` path segment ("${t}")`);if(e===I)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${I}")`)}},v=t=>t instanceof Error&&"code"in t&&t.code==="ENOENT",L=(t,e)=>{const r=e instanceof Error&&"code"in e?e.code:void 0;return r==="EEXIST"||r==="EISDIR"||r==="ENOTDIR"?new f("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},q=t=>{const e=Buffer.from(JSON.stringify(t),"utf8"),r=Buffer.alloc(m);return r.writeUInt32BE(e.byteLength,0),r.write(C,4,"ascii"),Buffer.concat([e,r])},Q=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"},tt=async t=>{const e=await t.stat();if(!e.isFile()||e.size<m)return;const r=Buffer.alloc(m);if(await t.read(r,0,m,e.size-m),r.toString("ascii",4,m)!==C)return;const n=r.readUInt32BE(0),o=e.size-m-n;if(n===0||o<0)return;const i=Buffer.alloc(n),{bytesRead:s}=await t.read(i,0,n,o);if(s!==n)return;let a;try{a=JSON.parse(i.toString("utf8"))}catch{return}return Q(a)?{bodySize:o,meta:a}:void 0},U=async t=>{let e;try{e=await D(t,"r")}catch(r){if(v(r))return;throw r}try{const r=await tt(e);if(r===void 0){await e.close();return}return{...r,handle:e}}catch(r){throw await e.close(),r}},b=new FinalizationRegistry(t=>{t.close().catch(()=>{})}),B=async t=>{const e=await U(t);if(e!==void 0)return await e.handle.close(),{bodySize:e.bodySize,meta:e.meta}},A=(t,e)=>{const{sha256Hex:r}=e,n=Buffer.from(r,"hex");return{checksums:{sha256:M(n)},customMetadata:e.customMetadata,etag:r,httpEtag:`"${r}"`,httpMetadata:e.httpMetadata,key:t,sha256:r,sha256Base64:n.toString("base64"),size:e.size,uploaded:new Date(e.uploaded)}},E=(t,e,r)=>t===void 0||!Number.isFinite(t)?e:Math.min(Math.max(0,Math.floor(t)),r),et=(t,e)=>{if(e===void 0)return{end:t,start:0};if("suffix"in e)return{end:t,start:t-E(e.suffix,t,t)};const r=E(e.offset,0,t);return{end:e.length===void 0||!Number.isFinite(e.length)?t:Math.min(r+E(e.length,0,t),t),start:r}},rt=(t,e,r,n)=>{const o=t.createReadStream({autoClose:!1,end:r-1,start:e})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await n()},pull:async i=>{let s;try{s=await o.next()}catch(a){throw await n(),a}s.done===!0?(i.close(),await n()):i.enqueue(s.value)}})},nt=async(t,e,r)=>{const n=r-e;if(n<=0)return Buffer.alloc(0);const o=Buffer.alloc(n),{bytesRead:i}=await t.read(o,0,n,e);return o.subarray(0,i)},V=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*V(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 f("VALIDATION_ERROR","@lunora/platform-node: R2 put stream must yield byte chunks");return}throw new f("VALIDATION_ERROR","@lunora/platform-node: unsupported R2 put body")}},ot=async t=>{const e=[],r=async(n,o)=>{let i;try{i=await H(n,{withFileTypes:!0})}catch(a){if(v(a))return;throw a}const s=[];for(const a of i){if(a.name===I)continue;const l=o===""?a.name:`${o}/${a.name}`;if(a.isDirectory())s.push(r(w(n,a.name),l));else{const d=Y(l);d!==void 0&&e.push(d)}}await Promise.all(s)};return await r(t,""),e.sort((n,o)=>n<o?-1:+(n>o)),e},at=(t,e)=>{let r=0,n=t.length;for(;r<n;){const o=r+Math.floor((n-r)/2),i=t[o];i===void 0||i<=e?r=o+1:n=o}return r},O=async t=>{try{await k(t)}catch(e){if(!v(e))throw e}},ut=t=>{const{directory:e}=t;return{delete:async r=>{g(r),await O(w(e,y(r)))},get:async(r,n)=>{g(r);const o=await U(w(e,y(r)));if(o===void 0)return null;const{bodySize:i,handle:s,meta:a}=o,{end:l,start:d}=et(i,n?.range),c={};let h=!1;const p=async()=>{h||(h=!0,b.unregister(c),await s.close())},u=()=>{if(h)throw new f("BAD_REQUEST",`@lunora/platform-node: R2 object "${r}" body has already been consumed`)},R=async()=>{u();try{return await nt(s,d,l)}finally{await p()}},T={...A(r,a),arrayBuffer:async()=>M(await R()),get body(){return u(),l<=d?(p().catch(()=>{}),new Blob([]).stream()):rt(s,d,l,p)},text:async()=>(await R()).toString("utf8")};return b.register(T,{close:p},c),T},head:async r=>{g(r);const n=await B(w(e,y(r)));return n===void 0?null:A(r,n.meta)},list:async(r={})=>{const n=Math.max(1,E(r.limit,1e3,1e3));if(r.prefix?.includes("\0"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await ot(e),i=r.prefix??"",s=o.filter(u=>u.startsWith(i)&&(r.delimiter===void 0||!u.slice(i.length).includes(r.delimiter)));let a=r.cursor;r.startAfter!==void 0&&(a===void 0||r.startAfter>a)&&(a=r.startAfter);const l=a===void 0?0:at(s,a),d=s.slice(l,l+n),c=l+n<s.length,p=(await Promise.all(d.map(async u=>{const R=await B(w(e,y(u)));return R===void 0?void 0:A(u,R.meta)}))).filter(u=>u!==void 0);return{cursor:c?d.at(-1):void 0,objects:p,truncated:c}},put:async(r,n,o)=>{g(r);const i=w(e,y(r)),s=w(e,I,$());try{await N(_(i),{recursive:!0})}catch(c){throw L(r,c)}await N(_(s),{recursive:!0});const a=P("sha256");let l=0,d;try{const c=await D(s,"w");try{for await(const h of V(n))a.update(h),l+=h.byteLength,await c.write(h);d={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:a.digest("hex"),size:l,uploaded:new Date().toISOString()},await c.write(q(d))}finally{await c.close()}}catch(c){throw await O(s),c}try{await F(s,i)}catch(c){throw await O(s),L(r,c)}return A(r,d)}}};export{ut as createNodeR2Bucket};
@@ -0,0 +1 @@
1
+ import E from"better-sqlite3";const p=2147483647,T=e=>e===void 0?null:e,f=e=>({get databaseSize(){const r=e.pragma("page_count",{simple:!0}),s=e.pragma("page_size",{simple:!0});return r*s},exec:(r,...s)=>{const o=e.prepare(r),l=s.map(i=>T(i)),t=o.reader?o.all(...l):(o.run(...l),[]);return{[Symbol.iterator]:()=>t[Symbol.iterator](),one:()=>{if(t.length!==1)throw new Error(`expected exactly one row, got ${String(t.length)}`);return t[0]},toArray:()=>[...t]}}}),_=(e,r)=>{let s,o;e.exec("CREATE TABLE IF NOT EXISTS _lunora_alarm (id INTEGER PRIMARY KEY CHECK (id = 0), scheduled_for INTEGER NOT NULL)");const l=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)},t=()=>{o!==void 0&&(clearTimeout(o),o=void 0)},i=n=>{t();const d=Math.max(0,n-Date.now());if(d>p){o=setTimeout(()=>{i(n)},p);return}o=setTimeout(()=>{s=void 0,o=void 0,e.open&&(l(void 0),(async()=>{await r?.()})().catch(()=>{}))},d)},m={delete:()=>{if(!e.open)throw new Error("platform closed: cannot delete an alarm");s=void 0,t(),l(void 0)},get:()=>s??null,set:n=>{if(!e.open)throw new Error("platform closed: cannot set an alarm");const d=typeof n=="number"?n:n.getTime();s=d,l(d),i(d)}},u=e.prepare("SELECT scheduled_for FROM _lunora_alarm WHERE id = 0").get();return u!==void 0&&(s=u.scheduled_for,i(u.scheduled_for)),{alarms:m,dispose:t}},v=(e={})=>{const r=new E(e.path??":memory:");r.pragma("journal_mode = WAL");const s=f(r),{alarms:o,dispose:l}=_(r,e.onAlarm),t=new Set;let i=Promise.resolve();const m=c=>{const a=i.then(c,c);return i=a.then(()=>{},()=>{}),a};let u=Promise.resolve();const n=async c=>{r.exec("BEGIN");try{const a=await c();return r.exec("COMMIT"),a}catch(a){try{r.exec("ROLLBACK")}catch{}throw a}},d=c=>{const a=u.then(()=>n(c),()=>n(c));return u=a.then(()=>{},()=>{}),a},h={alarms:o,runSerialized:m,shardKey:e.shardKey,sql:s,transaction:d,waitUntil:c=>{const a=c.catch(()=>{}).finally(()=>{t.delete(a)});t.add(a)}};return{database:r,dispose:()=>{l(),r.open&&r.close()},drain:async()=>{for(;t.size>0;)await Promise.all(t)},host:h}};export{v as createNodeShardHost};
@@ -1 +1 @@
1
- import{mkdirSync as S,readdirSync as l}from"node:fs";import{join as u}from"node:path";import{createNodeShardKvStore as p}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as y}from"./createNodeShardHost-C0j8nvSU.mjs";import{createNodeSocketHost as v}from"./createNodeSocketHost-DK3X5PlQ.mjs";const n=".sqlite3",g=r=>r.replaceAll(/[^\w.-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),F=r=>decodeURIComponent(r),R=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){S(r.directory,{recursive:!0});for(const e of l(r.directory))e.endsWith(n)&&o.add(F(e.slice(0,-n.length)))}const a=e=>{const d=t.get(e);if(d!==void 0)return d.shard;const c=r.directory===void 0?void 0:u(r.directory,`${g(e)}${n}`);let s;const{database:h,dispose:f,host:m}=y({onAlarm:()=>r.onAlarm?.(s),path:c,shardKey:e});return s={kv:p(h),shard:m,shardKey:e,sockets:v(h).socket},t.set(e,{dispose:f,shard:s}),o.add(e),s},i=e=>({fetch:async d=>{const c=a(e);return r.onFetch===void 0?new Response(e):r.onFetch(d,c)}});return{close:()=>{for(const e of t.values())e.dispose();t.clear()},directory:{get:e=>i(String(e)),getByName:e=>i(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:a}};export{R as createNodeShardRegistry};
1
+ import{mkdirSync as S,readdirSync as l}from"node:fs";import{join as u}from"node:path";import{createNodeShardKvStore as p}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as y}from"./createNodeShardHost-CNoG3Fzy.mjs";import{createNodeSocketHost as v}from"./createNodeSocketHost-DK3X5PlQ.mjs";const n=".sqlite3",g=r=>r.replaceAll(/[^\w.-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),F=r=>decodeURIComponent(r),R=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){S(r.directory,{recursive:!0});for(const e of l(r.directory))e.endsWith(n)&&o.add(F(e.slice(0,-n.length)))}const a=e=>{const d=t.get(e);if(d!==void 0)return d.shard;const c=r.directory===void 0?void 0:u(r.directory,`${g(e)}${n}`);let s;const{database:h,dispose:f,host:m}=y({onAlarm:()=>r.onAlarm?.(s),path:c,shardKey:e});return s={kv:p(h),shard:m,shardKey:e,sockets:v(h).socket},t.set(e,{dispose:f,shard:s}),o.add(e),s},i=e=>({fetch:async d=>{const c=a(e);return r.onFetch===void 0?new Response(e):r.onFetch(d,c)}});return{close:()=>{for(const e of t.values())e.dispose();t.clear()},directory:{get:e=>i(String(e)),getByName:e=>i(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:a}};export{R as createNodeShardRegistry};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform-node",
3
- "version": "1.0.0-alpha.34",
3
+ "version": "1.0.0-alpha.35",
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.95",
50
- "@lunora/errors": "1.0.0-alpha.24",
51
- "@lunora/platform": "1.0.0-alpha.19",
52
- "@lunora/queue": "1.0.0-alpha.36",
53
- "@lunora/sql-store": "1.0.0-alpha.97",
54
- "@lunora/workflow": "1.0.0-alpha.35",
49
+ "@lunora/d1": "1.0.0-alpha.96",
50
+ "@lunora/errors": "1.0.0-alpha.25",
51
+ "@lunora/platform": "1.0.0-alpha.20",
52
+ "@lunora/queue": "1.0.0-alpha.37",
53
+ "@lunora/sql-store": "1.0.0-alpha.98",
54
+ "@lunora/workflow": "1.0.0-alpha.36",
55
55
  "@visulima/workflow": "3.0.0",
56
56
  "better-sqlite3": "^13.0.3",
57
57
  "cron-parser": "5.8.1"
@@ -1 +0,0 @@
1
- import{randomUUID as $,createHash as P}from"node:crypto";import{mkdir as N,open as D,rename as F,unlink as k,readdir as H}from"node:fs/promises";import{join as w,dirname as S}from"node:path";import{LunoraError as f}from"@lunora/errors";import{t as M}from"./to-array-buffer-DVyXU4Ep.mjs";new TextEncoder;const G=Array.from({length:32},(e,t)=>t),K=new RegExp(`[${G.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),z=e=>K.test(e),I=".lunora-tmp",C="LNR1",m=8,_=1024,b=255,W=/[%A-Z:]/g,j=e=>{const t=e.replaceAll(W,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},X=e=>{try{return decodeURIComponent(e)}catch{return}},y=e=>e.split("/").map(t=>j(t)).join("/"),J=e=>{const t=[];for(const r of e.split("/")){const n=X(r);if(n===void 0)return;t.push(n)}return t.join("/")},g=e=>{if(typeof e!="string"||e.length===0)throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(e.length>_)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(_)}-byte limit`);if(e.includes("\0"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(z(e))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(e.includes("\\"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(e.startsWith("/"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const t of e.split("/")){if(Buffer.byteLength(j(t),"utf8")>b)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${t}" exceeds ${String(b)} bytes once escaped for the filesystem`);if(t===""||t==="."||t==="..")throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key has an empty, \`.\` or \`..\` path segment ("${e}")`);if(t===I)throw new f("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${I}")`)}},v=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",x=(e,t)=>{const r=t instanceof Error&&"code"in t?t.code:void 0;return r==="EEXIST"||r==="EISDIR"||r==="ENOTDIR"?new f("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},Y=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),r=Buffer.alloc(m);return r.writeUInt32BE(t.byteLength,0),r.write(C,4,"ascii"),Buffer.concat([t,r])},Z=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"},q=async e=>{const t=await e.stat();if(!t.isFile()||t.size<m)return;const r=Buffer.alloc(m);if(await e.read(r,0,m,t.size-m),r.toString("ascii",4,m)!==C)return;const n=r.readUInt32BE(0),o=t.size-m-n;if(n===0||o<0)return;const i=Buffer.alloc(n),{bytesRead:s}=await e.read(i,0,n,o);if(s!==n)return;let a;try{a=JSON.parse(i.toString("utf8"))}catch{return}return Z(a)?{bodySize:o,meta:a}:void 0},U=async e=>{let t;try{t=await D(e,"r")}catch(r){if(v(r))return;throw r}try{const r=await q(t);if(r===void 0){await t.close();return}return{...r,handle:t}}catch(r){throw await t.close(),r}},L=new FinalizationRegistry(e=>{e.close().catch(()=>{})}),B=async e=>{const t=await U(e);if(t!==void 0)return await t.handle.close(),{bodySize:t.bodySize,meta:t.meta}},A=(e,t)=>{const{sha256Hex:r}=t,n=Buffer.from(r,"hex");return{checksums:{sha256:M(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)}},E=(e,t,r)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),r),Q=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-E(t.suffix,e,e)};const r=E(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(r+E(t.length,0,e),e),start:r}},tt=(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 i=>{let s;try{s=await o.next()}catch(a){throw await n(),a}s.done===!0?(i.close(),await n()):i.enqueue(s.value)}})},et=async(e,t,r)=>{const n=r-t;if(n<=0)return Buffer.alloc(0);const o=Buffer.alloc(n),{bytesRead:i}=await e.read(o,0,n,t);return o.subarray(0,i)},V=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*V(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 f("VALIDATION_ERROR","@lunora/platform-node: R2 put stream must yield byte chunks");return}throw new f("VALIDATION_ERROR","@lunora/platform-node: unsupported R2 put body")}},rt=async e=>{const t=[],r=async(n,o)=>{let i;try{i=await H(n,{withFileTypes:!0})}catch(a){if(v(a))return;throw a}const s=[];for(const a of i){if(a.name===I)continue;const l=o===""?a.name:`${o}/${a.name}`;if(a.isDirectory())s.push(r(w(n,a.name),l));else{const d=J(l);d!==void 0&&t.push(d)}}await Promise.all(s)};return await r(e,""),t.sort((n,o)=>n<o?-1:+(n>o)),t},nt=(e,t)=>{let r=0,n=e.length;for(;r<n;){const o=r+Math.floor((n-r)/2),i=e[o];i===void 0||i<=t?r=o+1:n=o}return r},O=async e=>{try{await k(e)}catch(t){if(!v(t))throw t}},lt=e=>{const{directory:t}=e;return{delete:async r=>{g(r),await O(w(t,y(r)))},get:async(r,n)=>{g(r);const o=await U(w(t,y(r)));if(o===void 0)return null;const{bodySize:i,handle:s,meta:a}=o,{end:l,start:d}=Q(i,n?.range),c={};let h=!1;const p=async()=>{h||(h=!0,L.unregister(c),await s.close())},u=()=>{if(h)throw new f("BAD_REQUEST",`@lunora/platform-node: R2 object "${r}" body has already been consumed`)},R=async()=>{u();try{return await et(s,d,l)}finally{await p()}},T={...A(r,a),arrayBuffer:async()=>M(await R()),get body(){return u(),l<=d?(p().catch(()=>{}),new Blob([]).stream()):tt(s,d,l,p)},text:async()=>(await R()).toString("utf8")};return L.register(T,{close:p},c),T},head:async r=>{g(r);const n=await B(w(t,y(r)));return n===void 0?null:A(r,n.meta)},list:async(r={})=>{const n=Math.max(1,E(r.limit,1e3,1e3));if(r.prefix?.includes("\0"))throw new f("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await rt(t),i=r.prefix??"",s=o.filter(u=>u.startsWith(i)&&(r.delimiter===void 0||!u.slice(i.length).includes(r.delimiter)));let a=r.cursor;r.startAfter!==void 0&&(a===void 0||r.startAfter>a)&&(a=r.startAfter);const l=a===void 0?0:nt(s,a),d=s.slice(l,l+n),c=l+n<s.length,p=(await Promise.all(d.map(async u=>{const R=await B(w(t,y(u)));return R===void 0?void 0:A(u,R.meta)}))).filter(u=>u!==void 0);return{cursor:c?d.at(-1):void 0,objects:p,truncated:c}},put:async(r,n,o)=>{g(r);const i=w(t,y(r)),s=w(t,I,$());try{await N(S(i),{recursive:!0})}catch(c){throw x(r,c)}await N(S(s),{recursive:!0});const a=P("sha256");let l=0,d;try{const c=await D(s,"w");try{for await(const h of V(n))a.update(h),l+=h.byteLength,await c.write(h);d={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:a.digest("hex"),size:l,uploaded:new Date().toISOString()},await c.write(Y(d))}finally{await c.close()}}catch(c){throw await O(s),c}try{await F(s,i)}catch(c){throw await O(s),x(r,c)}return A(r,d)}}};export{lt as createNodeR2Bucket};
@@ -1 +0,0 @@
1
- import h from"better-sqlite3";const p=2147483647,T=e=>e===void 0?null:e,f=e=>({get databaseSize(){const r=e.pragma("page_count",{simple:!0}),s=e.pragma("page_size",{simple:!0});return r*s},exec:(r,...s)=>{const t=e.prepare(r),c=s.map(i=>T(i)),o=t.reader?t.all(...c):(t.run(...c),[]);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]}}}),_=(e,r)=>{let s,t;e.exec("CREATE TABLE IF NOT EXISTS _lunora_alarm (id INTEGER PRIMARY KEY CHECK (id = 0), scheduled_for INTEGER NOT NULL)");const c=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)},o=()=>{t!==void 0&&(clearTimeout(t),t=void 0)},i=n=>{o();const d=Math.max(0,n-Date.now());if(d>p){t=setTimeout(()=>{i(n)},p);return}t=setTimeout(()=>{s=void 0,t=void 0,e.open&&(c(void 0),(async()=>{await r?.()})().catch(()=>{}))},d)},m={delete:()=>{if(!e.open)throw new Error("platform closed: cannot delete an alarm");s=void 0,o(),c(void 0)},get:()=>s??null,set:n=>{if(!e.open)throw new Error("platform closed: cannot set an alarm");const d=typeof n=="number"?n:n.getTime();s=d,c(d),i(d)}},u=e.prepare("SELECT scheduled_for FROM _lunora_alarm WHERE id = 0").get();return u!==void 0&&(s=u.scheduled_for,i(u.scheduled_for)),{alarms:m,dispose:o}},v=(e={})=>{const r=new h(e.path??":memory:");r.pragma("journal_mode = WAL");const s=f(r),{alarms:t,dispose:c}=_(r,e.onAlarm),o=new Set;let i=Promise.resolve();const m=l=>{const a=i.then(l,l);return i=a.then(()=>{},()=>{}),a};let u=Promise.resolve();const n=async l=>{r.exec("BEGIN");try{const a=await l();return r.exec("COMMIT"),a}catch(a){throw r.exec("ROLLBACK"),a}},d=l=>{const a=u.then(()=>n(l),()=>n(l));return u=a.then(()=>{},()=>{}),a},E={alarms:t,runSerialized:m,shardKey:e.shardKey,sql:s,transaction:d,waitUntil:l=>{const a=l.catch(()=>{}).finally(()=>{o.delete(a)});o.add(a)}};return{database:r,dispose:()=>{c(),r.open&&r.close()},drain:async()=>{for(;o.size>0;)await Promise.all(o)},host:E}};export{v as createNodeShardHost};