@lunora/platform-node 1.0.0-alpha.3 → 1.0.0-alpha.30

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.
Files changed (29) hide show
  1. package/dist/conformance/index.mjs +1 -1
  2. package/dist/index.d.mts +98 -3
  3. package/dist/index.d.ts +98 -3
  4. package/dist/index.mjs +1 -1
  5. package/dist/packem_shared/createNodeGlobalStore-Cwi33XOk.mjs +1 -0
  6. package/dist/packem_shared/createNodePlatform-1fAQ_biu.mjs +1 -0
  7. package/dist/packem_shared/createNodeQueueHost--mBLGSxF.mjs +17 -0
  8. package/dist/packem_shared/createNodeR2Bucket-CmHYlIni.mjs +1 -0
  9. package/dist/packem_shared/createNodeSchedulerHost-vZgv1CEo.mjs +15 -0
  10. package/dist/packem_shared/createNodeShardHost-C0j8nvSU.mjs +1 -0
  11. package/dist/packem_shared/createNodeShardKvStore-DVi1OsvH.mjs +1 -0
  12. package/dist/packem_shared/createNodeShardRegistry-DKdc0pV8.mjs +1 -0
  13. package/dist/packem_shared/createNodeShardState-CpUzvWwd.mjs +1 -0
  14. package/dist/packem_shared/createNodeSocketHost-DK3X5PlQ.mjs +2 -0
  15. package/dist/packem_shared/createNodeWorkflowHost-CTVBYiuj.mjs +1 -0
  16. package/dist/packem_shared/{createNodeWorkflowStore-jlNR6Y3h.mjs → createNodeWorkflowStore-CL7RYP6H.mjs} +5 -5
  17. package/dist/packem_shared/{to-array-buffer-CP4NlF-y.mjs → to-array-buffer-DVyXU4Ep.mjs} +1 -1
  18. package/package.json +10 -10
  19. package/dist/packem_shared/createNodeGlobalStore-BvteXWRK.mjs +0 -1
  20. package/dist/packem_shared/createNodePlatform-BQimmgT9.mjs +0 -1
  21. package/dist/packem_shared/createNodeQueueHost-CLQ5hOBf.mjs +0 -17
  22. package/dist/packem_shared/createNodeR2Bucket-Wb8pyi2X.mjs +0 -1
  23. package/dist/packem_shared/createNodeSchedulerHost-DRjZhuxe.mjs +0 -15
  24. package/dist/packem_shared/createNodeShardHost-DEHe5Vcm.mjs +0 -1
  25. package/dist/packem_shared/createNodeShardKvStore-BfKCQTjE.mjs +0 -1
  26. package/dist/packem_shared/createNodeShardRegistry-0iSMdi9-.mjs +0 -1
  27. package/dist/packem_shared/createNodeShardState-DdOj9FzJ.mjs +0 -1
  28. package/dist/packem_shared/createNodeSocketHost-CytowrYN.mjs +0 -2
  29. package/dist/packem_shared/createNodeWorkflowHost-Dht_jgSv.mjs +0 -1
@@ -1 +1 @@
1
- import{createNodeShardKvStore as f}from"../packem_shared/createNodeShardKvStore-BfKCQTjE.mjs";import{createNodeSchedulerHost as y}from"../packem_shared/createNodeSchedulerHost-DRjZhuxe.mjs";import{createNodeShardHost as k}from"../packem_shared/createNodeShardHost-DEHe5Vcm.mjs";import{createNodeShardRegistry as D}from"../packem_shared/createNodeShardRegistry-0iSMdi9-.mjs";import{createNodeSocketHost as N}from"../packem_shared/createNodeSocketHost-CytowrYN.mjs";const b=()=>{const{database:r,dispose:c,host:i}=k(),m=f(r),a=D(),{directory:n}=a,{readFrames:h,restoreSocket:l,simulateRecycle:u,socket:p}=N(r),s=new Set,{dispose:w,scheduler:d,simulateDeadLetter:S}=y(r,{onDispatch:(e,t,o)=>{s.add(o.id)}});return{awaitAlarmFired:async e=>{await new Promise(t=>{setTimeout(t,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await d.list?.())?.find(o=>o.id===e);return t!==void 0&&await new Promise(o=>{setTimeout(o,Math.max(0,t.scheduledFor-Date.now())+30)}),s.has(e)},cleanup:()=>{w(),c(),a.close()},directory:n,kv:m,readFrames:h,restoreSocket:l,scheduler:d,shard:i,simulateDeadLetter:S,simulateRecycle:u,socket:p}};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-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};
package/dist/index.d.mts CHANGED
@@ -335,8 +335,10 @@ interface NodePlatform<Queues extends Record<string, {
335
335
  * benign); `shard.alarms.set()`/`delete()` throw before mutating any
336
336
  * in-memory state (checked against the connection's own open/closed state,
337
337
  * the single source of truth); `shard.alarms.get()` keeps answering
338
- * whatever it last held. A no-op instead of a throw would be
339
- * indistinguishable from a working call exactly the silent-vanishing
338
+ * whatever it last held; `sockets.accept()`/`setTag()`/`removeTag()` throw
339
+ * the same way, checked against the same connection state, before touching
340
+ * any runtime socket map or durable row. A no-op instead of a throw would
341
+ * be indistinguishable from a working call — exactly the silent-vanishing
340
342
  * this lifecycle exists to end.
341
343
  */
342
344
  close: () => void;
@@ -514,4 +516,97 @@ declare const createNodeWorkflowHost: <Workflows extends Record<string, {
514
516
  * Pass the result as `createNodeWorkflowHost({ store })`.
515
517
  */
516
518
  declare const createNodeWorkflowStore: (database: Database.Database) => WorkflowStore;
517
- export { type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeQueueHost, type NodeQueueHostOptions, type NodeR2BucketOptions, type NodeSchedulerHost, type NodeSchedulerHostOptions, type NodeShard, type NodeShardHostOptions, type NodeShardRegistry, type NodeShardRegistryOptions, type NodeShardState, type NodeSocketHost, type NodeWorkflowHost, type NodeWorkflowHostOptions, createNodeGlobalStore, createNodePlatform, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
519
+ export {
520
+ /**
521
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
522
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
523
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
524
+ * scheduler registry.
525
+ *
526
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
527
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
528
+ * durability half of each contract actually holds: alarms and scheduler jobs
529
+ * are persisted **and re-armed on construction**, socket attachments and tags
530
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
531
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
532
+ * a restart test — a second host over the same database file — not only by the
533
+ * TCK's simulated recycle.
534
+ *
535
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
536
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
537
+ * lifecycle/global-store tests.
538
+ *
539
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
540
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
541
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
542
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
543
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
544
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
545
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
546
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
547
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
548
+ * `plans/234-node-host-findings.md`.
549
+ */
550
+ type NodeGlobalContextDatabaseOptions,
551
+ /**
552
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
553
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
554
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
555
+ * scheduler registry.
556
+ *
557
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
558
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
559
+ * durability half of each contract actually holds: alarms and scheduler jobs
560
+ * are persisted **and re-armed on construction**, socket attachments and tags
561
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
562
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
563
+ * a restart test — a second host over the same database file — not only by the
564
+ * TCK's simulated recycle.
565
+ *
566
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
567
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
568
+ * lifecycle/global-store tests.
569
+ *
570
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
571
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
572
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
573
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
574
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
575
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
576
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
577
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
578
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
579
+ * `plans/234-node-host-findings.md`.
580
+ */
581
+ type NodeGlobalStore,
582
+ /**
583
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
584
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
585
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
586
+ * scheduler registry.
587
+ *
588
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
589
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
590
+ * durability half of each contract actually holds: alarms and scheduler jobs
591
+ * are persisted **and re-armed on construction**, socket attachments and tags
592
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
593
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
594
+ * a restart test — a second host over the same database file — not only by the
595
+ * TCK's simulated recycle.
596
+ *
597
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
598
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
599
+ * lifecycle/global-store tests.
600
+ *
601
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
602
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
603
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
604
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
605
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
606
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
607
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
608
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
609
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
610
+ * `plans/234-node-host-findings.md`.
611
+ */
612
+ type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeQueueHost, type NodeQueueHostOptions, type NodeR2BucketOptions, type NodeSchedulerHost, type NodeSchedulerHostOptions, type NodeShard, type NodeShardHostOptions, type NodeShardRegistry, type NodeShardRegistryOptions, type NodeShardState, type NodeSocketHost, type NodeWorkflowHost, type NodeWorkflowHostOptions, createNodeGlobalStore, createNodePlatform, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
package/dist/index.d.ts CHANGED
@@ -335,8 +335,10 @@ interface NodePlatform<Queues extends Record<string, {
335
335
  * benign); `shard.alarms.set()`/`delete()` throw before mutating any
336
336
  * in-memory state (checked against the connection's own open/closed state,
337
337
  * the single source of truth); `shard.alarms.get()` keeps answering
338
- * whatever it last held. A no-op instead of a throw would be
339
- * indistinguishable from a working call exactly the silent-vanishing
338
+ * whatever it last held; `sockets.accept()`/`setTag()`/`removeTag()` throw
339
+ * the same way, checked against the same connection state, before touching
340
+ * any runtime socket map or durable row. A no-op instead of a throw would
341
+ * be indistinguishable from a working call — exactly the silent-vanishing
340
342
  * this lifecycle exists to end.
341
343
  */
342
344
  close: () => void;
@@ -514,4 +516,97 @@ declare const createNodeWorkflowHost: <Workflows extends Record<string, {
514
516
  * Pass the result as `createNodeWorkflowHost({ store })`.
515
517
  */
516
518
  declare const createNodeWorkflowStore: (database: Database.Database) => WorkflowStore;
517
- export { type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeQueueHost, type NodeQueueHostOptions, type NodeR2BucketOptions, type NodeSchedulerHost, type NodeSchedulerHostOptions, type NodeShard, type NodeShardHostOptions, type NodeShardRegistry, type NodeShardRegistryOptions, type NodeShardState, type NodeSocketHost, type NodeWorkflowHost, type NodeWorkflowHostOptions, createNodeGlobalStore, createNodePlatform, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
519
+ export {
520
+ /**
521
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
522
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
523
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
524
+ * scheduler registry.
525
+ *
526
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
527
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
528
+ * durability half of each contract actually holds: alarms and scheduler jobs
529
+ * are persisted **and re-armed on construction**, socket attachments and tags
530
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
531
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
532
+ * a restart test — a second host over the same database file — not only by the
533
+ * TCK's simulated recycle.
534
+ *
535
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
536
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
537
+ * lifecycle/global-store tests.
538
+ *
539
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
540
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
541
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
542
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
543
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
544
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
545
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
546
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
547
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
548
+ * `plans/234-node-host-findings.md`.
549
+ */
550
+ type NodeGlobalContextDatabaseOptions,
551
+ /**
552
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
553
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
554
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
555
+ * scheduler registry.
556
+ *
557
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
558
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
559
+ * durability half of each contract actually holds: alarms and scheduler jobs
560
+ * are persisted **and re-armed on construction**, socket attachments and tags
561
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
562
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
563
+ * a restart test — a second host over the same database file — not only by the
564
+ * TCK's simulated recycle.
565
+ *
566
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
567
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
568
+ * lifecycle/global-store tests.
569
+ *
570
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
571
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
572
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
573
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
574
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
575
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
576
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
577
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
578
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
579
+ * `plans/234-node-host-findings.md`.
580
+ */
581
+ type NodeGlobalStore,
582
+ /**
583
+ * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
584
+ * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
585
+ * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
586
+ * scheduler registry.
587
+ *
588
+ * Promoted from `@lunora/platform`'s `node:sqlite` reference host
589
+ * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
590
+ * durability half of each contract actually holds: alarms and scheduler jobs
591
+ * are persisted **and re-armed on construction**, socket attachments and tags
592
+ * live in SQLite rather than a `Map`, and `.global()` tables run the real
593
+ * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
594
+ * a restart test — a second host over the same database file — not only by the
595
+ * TCK's simulated recycle.
596
+ *
597
+ * Three suites run against this package: `@lunora/platform/conformance`'s host
598
+ * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
599
+ * lifecycle/global-store tests.
600
+ *
601
+ * **Still missing**, and rated accordingly in `NODE_CAPABILITIES`
602
+ * (`@lunora/platform`): a dev server — so `lunora dev --target node` does not
603
+ * exist yet — plus most Cloudflare product bindings (Queues, Vectorize, Workers
604
+ * AI, Browser Rendering, Containers, Analytics Engine, Pipelines, Secrets Store,
605
+ * Hyperdrive). R2 buckets are emulated over the local filesystem
606
+ * (`./node-r2-bucket`) and Workflows over the `@visulima/workflow` engine
607
+ * (`./node-workflow-host`). Cross-shard fan-out is emulated via
608
+ * `@lunora/runtime`'s query coordinator over the in-process shard registry, and
609
+ * a `@lunora/config` deploy driver makes `--target node` resolve. See
610
+ * `plans/234-node-host-findings.md`.
611
+ */
612
+ type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeQueueHost, type NodeQueueHostOptions, type NodeR2BucketOptions, type NodeSchedulerHost, type NodeSchedulerHostOptions, type NodeShard, type NodeShardHostOptions, type NodeShardRegistry, type NodeShardRegistryOptions, type NodeShardState, type NodeSocketHost, type NodeWorkflowHost, type NodeWorkflowHostOptions, createNodeGlobalStore, createNodePlatform, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createNodeGlobalStore as r,createNodeSqlExec as t}from"./packem_shared/createNodeGlobalStore-BvteXWRK.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-BfKCQTjE.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-BQimmgT9.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost-CLQ5hOBf.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-Wb8pyi2X.mjs";import{createNodeSchedulerHost as l}from"./packem_shared/createNodeSchedulerHost-DRjZhuxe.mjs";import{createNodeShardHost as h}from"./packem_shared/createNodeShardHost-DEHe5Vcm.mjs";import{createNodeShardRegistry as k}from"./packem_shared/createNodeShardRegistry-0iSMdi9-.mjs";import{createNodeShardState as w}from"./packem_shared/createNodeShardState-DdOj9FzJ.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-CytowrYN.mjs";import{createNodeWorkflowHost as g}from"./packem_shared/createNodeWorkflowHost-Dht_jgSv.mjs";import{createNodeWorkflowStore as q}from"./packem_shared/createNodeWorkflowStore-jlNR6Y3h.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-1fAQ_biu.mjs";import{createNodeQueueHost as x}from"./packem_shared/createNodeQueueHost--mBLGSxF.mjs";import{createNodeR2Bucket as p}from"./packem_shared/createNodeR2Bucket-CmHYlIni.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};
@@ -0,0 +1 @@
1
+ import{sqliteDialect as n}from"@lunora/d1";import{createSqlCtxDb as c,runSqlGlobalTableMigrations as s,runSqlAggregateMigrations as p,runSqlRankMigrations as u,runSqlSearchMigrations as g,backfillSqlSearchIndexes as S,runSqlCdcMigration as d}from"@lunora/sql-store";import w from"better-sqlite3";const o=new WeakMap,i=(a,r)=>{let e=o.get(a);e===void 0&&(e=new Map,o.set(a,e));let t=e.get(r);return t===void 0&&(t=a.prepare(r),e.set(r,t)),t},f=a=>({all:async(r,e)=>i(a,r).all(...e),run:async(r,e)=>({rowsAffected:i(a,r).run(...e).changes})}),x=(a={})=>{const r=new w(a.path??":memory:");r.pragma("journal_mode = WAL");const e=f(r);return{database:r,dispose:()=>{r.open&&r.close()},exec:e,migrate:async(t,l={})=>{await s(e,t,n),await p(e,t,n),await u(e,t,n),await g(e,t,n),await S(e,t,n),l.cdc===!0&&await d(e,n)},writer:t=>c({...t,dialect:n,exec:e})}};export{x as createNodeGlobalStore,f as createNodeSqlExec};
@@ -0,0 +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};
@@ -0,0 +1,17 @@
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};
@@ -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 S}from"node:path";import{LunoraError as c}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),E=".lunora-tmp",C="LNR1",m=8,_=1024,b=255,W=/[%A-Z:]/g,j=e=>{const t=e.replaceAll(W,r=>`%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),n=t.at(-1);return n==="."||n===" "?`${t.slice(0,-1)}%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:t},X=e=>{try{return decodeURIComponent(e)}catch{return}},R=e=>e.split("/").map(t=>j(t)).join("/"),J=e=>{const t=[];for(const n of e.split("/")){const r=X(n);if(r===void 0)return;t.push(r)}return t.join("/")},y=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(e.length>_)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(_)}-byte limit`);if(e.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(z(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(j(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===E)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${E}")`)}},O=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",x=(e,t)=>{const n=t instanceof Error&&"code"in t?t.code:void 0;return n==="EEXIST"||n==="EISDIR"||n==="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},Y=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),n=Buffer.alloc(m);return n.writeUInt32BE(t.byteLength,0),n.write(C,4,"ascii"),Buffer.concat([t,n])},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 n=Buffer.alloc(m);if(await e.read(n,0,m,t.size-m),n.toString("ascii",4,m)!==C)return;const r=n.readUInt32BE(0),o=t.size-m-r;if(r===0||o<0)return;const i=Buffer.alloc(r),{bytesRead:s}=await e.read(i,0,r,o);if(s!==r)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(n){if(O(n))return;throw n}try{const n=await q(t);if(n===void 0){await t.close();return}return{...n,handle:t}}catch(n){throw await t.close(),n}},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}},g=(e,t)=>{const{sha256Hex:n}=t,r=Buffer.from(n,"hex");return{checksums:{sha256:M(r)},customMetadata:t.customMetadata,etag:n,httpEtag:`"${n}"`,httpMetadata:t.httpMetadata,key:e,sha256:n,sha256Base64:r.toString("base64"),size:t.size,uploaded:new Date(t.uploaded)}},A=(e,t,n)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),n),Q=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-A(t.suffix,e,e)};const n=A(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(n+A(t.length,0,e),e),start:n}},tt=(e,t,n,r)=>{const o=e.createReadStream({autoClose:!1,end:n-1,start:t})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await r()},pull:async i=>{let s;try{s=await o.next()}catch(a){throw await r(),a}s.done===!0?(i.close(),await r()):i.enqueue(s.value)}})},et=async(e,t,n)=>{const r=n-t;if(r<=0)return Buffer.alloc(0);const o=Buffer.alloc(r),{bytesRead:i}=await e.read(o,0,r,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 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")}},nt=async e=>{const t=[],n=async(r,o)=>{let i;try{i=await H(r,{withFileTypes:!0})}catch(a){if(O(a))return;throw a}const s=[];for(const a of i){if(a.name===E)continue;const d=o===""?a.name:`${o}/${a.name}`;if(a.isDirectory())s.push(n(w(r,a.name),d));else{const l=J(d);l!==void 0&&t.push(l)}}await Promise.all(s)};return await n(e,""),t.sort((r,o)=>r<o?-1:+(r>o)),t},rt=(e,t)=>{let n=0,r=e.length;for(;n<r;){const o=n+Math.floor((r-n)/2),i=e[o];i===void 0||i<=t?n=o+1:r=o}return n},I=async e=>{try{await k(e)}catch(t){if(!O(t))throw t}},ft=e=>{const{directory:t}=e;return{delete:async n=>{y(n),await I(w(t,R(n)))},get:async(n,r)=>{y(n);const o=await U(w(t,R(n)));if(o===void 0)return null;const{bodySize:i,handle:s,meta:a}=o,{end:d,start:l}=Q(i,r?.range),f={};let h=!1;const u=async()=>{h||(h=!0,L.unregister(f),await s.close())},p=()=>{if(h)throw new c("BAD_REQUEST",`@lunora/platform-node: R2 object "${n}" body has already been consumed`)},T=async()=>{p();try{return await et(s,l,d)}finally{await u()}},v={...g(n,a),arrayBuffer:async()=>M(await T()),get body(){return p(),d<=l?(u().catch(()=>{}),new Blob([]).stream()):tt(s,l,d,u)},text:async()=>(await T()).toString("utf8")};return L.register(v,{close:u},f),v},head:async n=>{y(n);const r=await B(w(t,R(n)));return r===void 0?null:g(n,r.meta)},list:async(n={})=>{const r=Math.max(1,A(n.limit,1e3,1e3));if(n.prefix?.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await nt(t),i=n.prefix??"",s=o.filter(u=>u.startsWith(i)&&(n.delimiter===void 0||!u.slice(i.length).includes(n.delimiter))),a=n.cursor===void 0?0:rt(s,n.cursor),d=s.slice(a,a+r),l=a+r<s.length,h=(await Promise.all(d.map(async u=>{const p=await B(w(t,R(u)));return p===void 0?void 0:g(u,p.meta)}))).filter(u=>u!==void 0);return{cursor:l?d.at(-1):void 0,objects:h,truncated:l}},put:async(n,r,o)=>{y(n);const i=w(t,R(n)),s=w(t,E,$());try{await N(S(i),{recursive:!0})}catch(f){throw x(n,f)}await N(S(s),{recursive:!0});const a=P("sha256");let d=0,l;try{const f=await D(s,"w");try{for await(const h of V(r))a.update(h),d+=h.byteLength,await f.write(h);l={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:a.digest("hex"),size:d,uploaded:new Date().toISOString()},await f.write(Y(l))}finally{await f.close()}}catch(f){throw await I(s),f}try{await F(s,i)}catch(f){throw await I(s),x(n,f)}return g(n,l)}}};export{ft as createNodeR2Bucket};
@@ -0,0 +1,15 @@
1
+ import{randomUUID as k}from"node:crypto";import{deserialize as y,serialize as L}from"node:v8";import{CronExpressionParser as D}from"cron-parser";const T={backoffMultiplier:2,initialDelayMs:1e3,maxAttempts:5,maxDelayMs:6e4},x=2147483647,F=(s,i)=>{const f=i?.initialDelayMs??T.initialDelayMs,m=i?.backoffMultiplier??T.backoffMultiplier,u=i?.maxDelayMs??T.maxDelayMs;return Math.min(u,f*m**Math.max(0,s-1))},w=(s,i={})=>{s.exec(`CREATE TABLE IF NOT EXISTS _lunora_scheduler_jobs (
2
+ id TEXT PRIMARY KEY,
3
+ function_path TEXT NOT NULL,
4
+ args BLOB NOT NULL,
5
+ scheduled_for INTEGER NOT NULL,
6
+ attempts INTEGER NOT NULL DEFAULT 0,
7
+ retry BLOB,
8
+ state TEXT NOT NULL DEFAULT 'pending'
9
+ )`),s.exec(`CREATE TABLE IF NOT EXISTS _lunora_scheduler_crons (
10
+ key TEXT PRIMARY KEY,
11
+ expression TEXT NOT NULL,
12
+ function_path TEXT NOT NULL,
13
+ args BLOB NOT NULL
14
+ )`);const f=s.prepare("INSERT INTO _lunora_scheduler_jobs (id, function_path, args, scheduled_for, retry) VALUES (?, ?, ?, ?, ?)"),m=s.prepare("SELECT * FROM _lunora_scheduler_jobs WHERE id = ?"),u=s.prepare("SELECT * FROM _lunora_scheduler_jobs WHERE state = ? ORDER BY scheduled_for"),R=s.prepare("DELETE FROM _lunora_scheduler_jobs WHERE id = ?"),M=s.prepare("DELETE FROM _lunora_scheduler_jobs WHERE id = ? AND state = 'pending'"),O=s.prepare("UPDATE _lunora_scheduler_jobs SET attempts = ?, scheduled_for = ? WHERE id = ?"),N=s.prepare("UPDATE _lunora_scheduler_jobs SET state = 'dead', attempts = ? WHERE id = ?"),U=s.prepare("UPDATE _lunora_scheduler_jobs SET state = 'pending', attempts = 0, scheduled_for = ? WHERE id = ? AND state = 'dead'"),S=s.prepare(`INSERT INTO _lunora_scheduler_crons (key, expression, function_path, args) VALUES (?, ?, ?, ?)
15
+ ON CONFLICT (key) DO UPDATE SET expression = excluded.expression, args = excluded.args`),b=s.prepare("SELECT * FROM _lunora_scheduler_crons"),c=()=>s.open,g=e=>e===null?void 0:y(e);let o=!1;const l=new Map,p=new Map,A=e=>({attempts:e.attempts,functionPath:e.function_path,id:e.id,scheduledFor:e.scheduled_for}),d=e=>{const t=l.get(e);t!==void 0&&(clearTimeout(t),l.delete(e))},E=(e,t)=>{d(e);const r=Math.max(0,t-Date.now());if(r>x){const a=setTimeout(()=>{E(e,t)},x);l.set(e,a);return}const n=setTimeout(()=>{I(e).catch(()=>{})},r);l.set(e,n)},I=async e=>{if(l.delete(e),!c())return;const t=m.get(e);if(t?.state!=="pending")return;const r=t.attempts+1,n=g(t.retry);try{await i.onDispatch?.(t.function_path,y(t.args),{attempts:r,id:e}),c()&&R.run(e)}catch{if(o||!c())return;const a=n?.maxAttempts??T.maxAttempts;if(r>=a){N.run(r,e);return}const h=Date.now()+F(r,n);O.run(r,h,e),E(e,h)}},_=e=>{const t=p.get(e.key);t!==void 0&&clearTimeout(t);let r;try{r=D.parse(e.expression).next().getTime()}catch{return}const n=setTimeout(()=>{p.delete(e.key),(async()=>{try{await i.onDispatch?.(e.function_path,y(e.args),{attempts:0,id:e.key})}catch{}!o&&c()&&_(e)})().catch(()=>{})},Math.max(0,r-Date.now()));p.set(e.key,n)},j={cancel:async e=>o?!1:(d(e),M.run(e).changes>0),cron:async(e,t,r)=>{if(o)throw new Error("platform closed: cannot register a cron");D.parse(e);const n={args:L(r??{}),expression:e,function_path:t,key:`${e} ${t}`};S.run(n.key,n.expression,n.function_path,n.args),_(n)},deadLetter:{list:async()=>o||!c()?[]:u.all("dead").map(e=>A(e)),requeue:async e=>{if(o||!c())return!1;const t=Date.now();return U.run(t,e).changes===0?!1:(E(e,t),!0)}},list:async()=>o||!c()?[]:u.all("pending").map(e=>A(e)),schedule:async(e,t,r)=>{if(o)throw new Error("platform closed: cannot schedule a job");const n=`node-job-${k()}`;let a;return r?.at===void 0?a=Date.now()+(r?.delayMs??0):a=typeof r.at=="number"?r.at:r.at.getTime(),f.run(n,e,L(t),a,r?.retry===void 0?null:L(r.retry)),E(n,a),{id:n,scheduledFor:a}}};for(const e of u.all("pending"))E(e.id,e.scheduled_for);for(const e of b.all())_(e);return{dispose:()=>{for(const e of l.values())clearTimeout(e);for(const e of p.values())clearTimeout(e);l.clear(),p.clear(),o=!0},scheduler:j,simulateDeadLetter:async e=>{const t=m.get(e);if(t?.state!=="pending")return!1;d(e);const r=g(t.retry);return N.run((r?.maxAttempts??T.maxAttempts)+1,e),!0}}};export{w as createNodeSchedulerHost};
@@ -0,0 +1 @@
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};
@@ -0,0 +1 @@
1
+ import{serialize as v,deserialize as u}from"node:v8";const T=r=>{r.exec("CREATE TABLE IF NOT EXISTS _lunora_kv (key TEXT PRIMARY KEY, value BLOB NOT NULL)");const l=r.prepare("SELECT value FROM _lunora_kv WHERE key = ?"),s=r.prepare("INSERT INTO _lunora_kv (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value"),a=r.prepare("DELETE FROM _lunora_kv WHERE key = ?"),E=r.prepare("SELECT key, value FROM _lunora_kv");return{delete:async e=>a.run(e).changes>0,get:async e=>{const t=l.get(e);return t===void 0?void 0:u(t.value)},list:async e=>{const t=e?.prefix??"",c=E.all(),o=new Map;for(const n of c)n.key.startsWith(t)&&o.set(n.key,u(n.value));return o},put:async(e,t)=>{s.run(e,v(t))}}};export{T as createNodeShardKvStore};
@@ -0,0 +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};
@@ -0,0 +1 @@
1
+ const s=t=>({acceptWebSocket:(e,a)=>{const c=t.sockets.accept(e,void 0,a),i=e;i.serializeAttachment=l=>{c.serializeAttachment(l)},i.deserializeAttachment=()=>c.deserializeAttachment()},blockConcurrencyWhile:e=>t.shard.runSerialized(e),getWebSockets:e=>t.sockets.getSockets(e),id:{name:t.shardKey},storage:{delete:e=>t.kv.delete(e),deleteAlarm:async()=>{await t.shard.alarms.delete()},get:e=>t.kv.get(e),getAlarm:async()=>t.shard.alarms.get(),list:e=>t.kv.list(e),put:(e,a)=>t.kv.put(e,a),setAlarm:async e=>{await t.shard.alarms.set(e)},sql:{get databaseSize(){return t.shard.sql.databaseSize},exec:(e,...a)=>t.shard.sql.exec(e,...a)},transaction:e=>t.shard.transaction(e)},waitUntil:e=>{t.shard.waitUntil?.(e)}});export{s as createNodeShardState};
@@ -0,0 +1,2 @@
1
+ import{deserialize as w,serialize as l}from"node:v8";import{t as S}from"./to-array-buffer-DVyXU4Ep.mjs";const p=n=>typeof n=="object"&&n!==null||typeof n=="function",_=n=>{n.exec("CREATE TABLE IF NOT EXISTS _lunora_sockets (id TEXT PRIMARY KEY, attachment BLOB, tags TEXT NOT NULL DEFAULT '[]')");const f=n.prepare(`INSERT INTO _lunora_sockets (id, attachment, tags) VALUES (?, ?, ?)
2
+ ON CONFLICT (id) DO UPDATE SET attachment = excluded.attachment, tags = excluded.tags`),m=n.prepare("UPDATE _lunora_sockets SET attachment = ? WHERE id = ?"),h=n.prepare("UPDATE _lunora_sockets SET tags = ? WHERE id = ?"),E=n.prepare("SELECT attachment, tags FROM _lunora_sockets WHERE id = ?"),T=n.prepare("DELETE FROM _lunora_sockets WHERE id = ?"),c=new Map,i=new WeakMap;let a=new WeakMap;const v=(t,e)=>{n.open&&m.run(e===void 0?null:l(e),t)},u=(t,e)=>{n.open&&h.run(JSON.stringify([...e]),t)},g=t=>{const e=t,o={bufferedAmount:e.bufferedAmount,close:(r,s)=>{e.closed=!0,c.delete(e.id),p(e.raw)&&a.delete(e.raw),n.open&&T.run(e.id)},deserializeAttachment:()=>e.attachment,send:r=>{e.received.push(typeof r=="string"?r:S(r))},serializeAttachment:r=>{e.attachment=r,v(e.id,r)}};return e.handle=o,i.set(o,e.id),o};return{readFrames:t=>(c.get(i.get(t)??"")?.received??[]).filter(e=>typeof e=="string"),restoreSocket:(t,e)=>{const o=n.open?E.get(t):void 0,r=o?.attachment===null?void 0:o?.attachment,s={attachment:r===void 0?e:w(r),bufferedAmount:0,closed:!1,handle:void 0,id:t,raw:void 0,received:[],tags:new Set(o===void 0?[]:JSON.parse(o.tags))};return c.set(t,s),o===void 0&&n.open&&f.run(t,s.attachment===void 0?null:l(s.attachment),JSON.stringify([...s.tags])),g(s)},simulateRecycle:()=>{c.clear(),a=new WeakMap},socket:{accept:(t,e,o)=>{if(!n.open)throw new Error("platform closed: cannot accept a socket");const r=crypto.randomUUID(),s=new Set(o);f.run(r,e===void 0?null:l(e),JSON.stringify([...s]));const d={attachment:e,bufferedAmount:0,closed:!1,handle:void 0,id:r,raw:t,received:[],tags:s};return c.set(r,d),p(t)&&a.set(t,d),g(d)},getSockets:t=>{const e=[...c.values()];return(t===void 0?e:e.filter(r=>r.tags.has(t))).map(r=>r.handle)},handleFor:t=>p(t)?a.get(t)?.handle:void 0,idFor:t=>{const e=i.get(t);if(e===void 0)throw new Error("@lunora/platform-node: idFor called with a handle this host never issued");return e},removeTag:(t,e)=>{if(!n.open)throw new Error("platform closed: cannot remove a tag");const o=c.get(i.get(t)??"");o!==void 0&&(e===void 0?o.tags.clear():o.tags.delete(e),u(o.id,o.tags))},setTag:(t,e)=>{if(!n.open)throw new Error("platform closed: cannot set a tag");const o=c.get(i.get(t)??"");o!==void 0&&(o.tags.add(e),u(o.id,o.tags))}}}};export{_ as createNodeSocketHost};
@@ -0,0 +1 @@
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};
@@ -1,4 +1,4 @@
1
- const i=n=>{n.pragma("journal_mode = WAL"),n.exec(`CREATE TABLE IF NOT EXISTS _lunora_workflow_runs (
1
+ const i=t=>{t.pragma("journal_mode = WAL"),t.exec(`CREATE TABLE IF NOT EXISTS _lunora_workflow_runs (
2
2
  run_id TEXT PRIMARY KEY,
3
3
  definition_id TEXT NOT NULL,
4
4
  status TEXT NOT NULL,
@@ -6,7 +6,7 @@ const i=n=>{n.pragma("journal_mode = WAL"),n.exec(`CREATE TABLE IF NOT EXISTS _l
6
6
  wake_at INTEGER,
7
7
  updated_at INTEGER NOT NULL,
8
8
  snapshot TEXT NOT NULL
9
- )`),n.exec("CREATE INDEX IF NOT EXISTS _lunora_workflow_runs_due ON _lunora_workflow_runs (wake_at)"),n.exec("CREATE TABLE IF NOT EXISTS _lunora_workflow_leases (run_id TEXT PRIMARY KEY, token TEXT NOT NULL, expires_at INTEGER NOT NULL)");const t=n.prepare(`INSERT INTO _lunora_workflow_runs (run_id, definition_id, status, event_name, wake_at, updated_at, snapshot)
9
+ )`),t.exec("CREATE INDEX IF NOT EXISTS _lunora_workflow_runs_due ON _lunora_workflow_runs (wake_at)"),t.exec("CREATE TABLE IF NOT EXISTS _lunora_workflow_leases (run_id TEXT PRIMARY KEY, token TEXT NOT NULL, expires_at INTEGER NOT NULL)");const r=t.prepare(`INSERT INTO _lunora_workflow_runs (run_id, definition_id, status, event_name, wake_at, updated_at, snapshot)
10
10
  VALUES (?, ?, ?, ?, ?, ?, ?)
11
11
  ON CONFLICT (run_id) DO UPDATE SET
12
12
  definition_id = excluded.definition_id,
@@ -14,8 +14,8 @@ const i=n=>{n.pragma("journal_mode = WAL"),n.exec(`CREATE TABLE IF NOT EXISTS _l
14
14
  event_name = excluded.event_name,
15
15
  wake_at = excluded.wake_at,
16
16
  updated_at = excluded.updated_at,
17
- snapshot = excluded.snapshot`),o=n.prepare("SELECT * FROM _lunora_workflow_runs WHERE run_id = ?"),u=n.prepare("DELETE FROM _lunora_workflow_runs WHERE run_id = ?"),d=n.prepare("DELETE FROM _lunora_workflow_leases WHERE run_id = ?"),s=n.prepare(`SELECT run_id FROM _lunora_workflow_runs
17
+ snapshot = excluded.snapshot`),_=t.prepare("SELECT * FROM _lunora_workflow_runs WHERE run_id = ?"),u=t.prepare("DELETE FROM _lunora_workflow_runs WHERE run_id = ?"),s=t.prepare("DELETE FROM _lunora_workflow_leases WHERE run_id = ?"),d=t.prepare(`SELECT run_id FROM _lunora_workflow_runs
18
18
  WHERE status IN ('suspended', 'waiting') AND wake_at IS NOT NULL AND wake_at <= ?
19
- ORDER BY wake_at LIMIT ?`),E=n.prepare(`INSERT INTO _lunora_workflow_leases (run_id, token, expires_at) VALUES (?, ?, ?)
19
+ ORDER BY wake_at LIMIT ?`),E=t.prepare(`INSERT INTO _lunora_workflow_leases (run_id, token, expires_at) VALUES (?, ?, ?)
20
20
  ON CONFLICT (run_id) DO UPDATE SET token = excluded.token, expires_at = excluded.expires_at
21
- WHERE _lunora_workflow_leases.expires_at <= ? OR _lunora_workflow_leases.token = excluded.token`),l=n.prepare("DELETE FROM _lunora_workflow_leases WHERE run_id = ? AND token = ?");return{acquire:async(e,a,r)=>{const _=Date.now();return E.run(e,a,_+r,_).changes>0},delete:async e=>{u.run(e),d.run(e)},due:async(e,a)=>s.all(e,a).map(r=>r.run_id),load:async e=>{const a=o.get(e);if(a!==void 0)return{definitionId:a.definition_id,eventName:a.event_name??void 0,runId:a.run_id,snapshot:JSON.parse(a.snapshot),status:a.status,updatedAt:a.updated_at,wakeAt:a.wake_at??void 0}},release:async(e,a)=>{l.run(e,a)},save:async e=>{t.run(e.runId,e.definitionId,e.status,e.eventName??null,e.wakeAt??null,e.updatedAt,JSON.stringify(e.snapshot??null))}}};export{i as createNodeWorkflowStore};
21
+ WHERE _lunora_workflow_leases.expires_at <= ? OR _lunora_workflow_leases.token = excluded.token`),l=t.prepare("DELETE FROM _lunora_workflow_leases WHERE run_id = ? AND token = ?");return{acquire:async(e,n,a)=>{const o=Date.now();return E.run(e,n,o+a,o).changes>0},delete:async e=>{u.run(e),s.run(e)},due:async(e,n)=>d.all(e,n).map(a=>a.run_id),load:async e=>{const n=_.get(e);if(n!==void 0)return{definitionId:n.definition_id,eventName:n.event_name??void 0,runId:n.run_id,snapshot:JSON.parse(n.snapshot),status:n.status,updatedAt:n.updated_at,wakeAt:n.wake_at??void 0}},release:async(e,n)=>{l.run(e,n)},save:async e=>{r.run(e.runId,e.definitionId,e.status,e.eventName??null,e.wakeAt??null,e.updatedAt,JSON.stringify(e.snapshot??null))}}};export{i as createNodeWorkflowStore};
@@ -1 +1 @@
1
- const f=e=>typeof e=="string"?new TextEncoder().encode(e).buffer:e instanceof ArrayBuffer?e:ArrayBuffer.isView(e)?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):new ArrayBuffer(0);export{f as t};
1
+ const r=e=>typeof e=="string"?new TextEncoder().encode(e).buffer:e instanceof ArrayBuffer?e:ArrayBuffer.isView(e)?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):new ArrayBuffer(0);export{r as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform-node",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.0.0-alpha.30",
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,15 +46,15 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/d1": "1.0.0-alpha.70",
50
- "@lunora/errors": "1.0.0-alpha.17",
51
- "@lunora/platform": "1.0.0-alpha.8",
52
- "@lunora/queue": "1.0.0-alpha.22",
53
- "@lunora/sql-store": "1.0.0-alpha.72",
54
- "@lunora/workflow": "1.0.0-alpha.24",
55
- "@visulima/workflow": "2.0.0",
56
- "better-sqlite3": "^12.11.1",
57
- "cron-parser": "5.6.2"
49
+ "@lunora/d1": "1.0.0-alpha.91",
50
+ "@lunora/errors": "1.0.0-alpha.24",
51
+ "@lunora/platform": "1.0.0-alpha.18",
52
+ "@lunora/queue": "1.0.0-alpha.35",
53
+ "@lunora/sql-store": "1.0.0-alpha.93",
54
+ "@lunora/workflow": "1.0.0-alpha.35",
55
+ "@visulima/workflow": "3.0.0",
56
+ "better-sqlite3": "^13.0.3",
57
+ "cron-parser": "5.8.1"
58
58
  },
59
59
  "engines": {
60
60
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{sqliteDialect as n}from"@lunora/d1";import{createSqlCtxDb as c,runSqlGlobalTableMigrations as s,runSqlAggregateMigrations as g,runSqlRankMigrations as d,runSqlSearchMigrations as p,backfillSqlSearchIndexes as m,runSqlCdcMigration as u}from"@lunora/sql-store";import w from"better-sqlite3";const i=new WeakMap,o=(r,a)=>{let e=i.get(r);e===void 0&&(e=new Map,i.set(r,e));let t=e.get(a);return t===void 0&&(t=r.prepare(a),e.set(a,t)),t},S=r=>({all:async(a,e)=>o(r,a).all(...e),run:async(a,e)=>({rowsAffected:o(r,a).run(...e).changes})}),f=(r={})=>{const a=new w(r.path??":memory:");a.pragma("journal_mode = WAL");const e=S(a);return{database:a,dispose:()=>{a.open&&a.close()},exec:e,migrate:async(t,l={})=>{await s(e,t,n),await g(e,t,n),await d(e,t,n),await p(e,t,n),await m(e,t,n),l.cdc===!0&&await u(e,n)},writer:t=>c({...t,dialect:n,exec:e})}};export{f as createNodeGlobalStore,S as createNodeSqlExec};
@@ -1 +0,0 @@
1
- import{LunoraError as n}from"@lunora/errors";import{NODE_CAPABILITIES as l}from"@lunora/platform";import{createNodeShardKvStore as f}from"./createNodeShardKvStore-BfKCQTjE.mjs";import{createNodeQueueHost as N}from"./createNodeQueueHost-CLQ5hOBf.mjs";import{createNodeSchedulerHost as S}from"./createNodeSchedulerHost-DRjZhuxe.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-DEHe5Vcm.mjs";import{createNodeShardRegistry as v}from"./createNodeShardRegistry-0iSMdi9-.mjs";import{createNodeSocketHost as w}from"./createNodeSocketHost-CytowrYN.mjs";const H=(e={})=>{const{database:o,dispose:a,drain:s,host:d}=q(e),u=f(o),r=v(e),{directory:c}=r,{socket:i}=w(o),{dispose:m,scheduler:h}=S(o,e),p=e.queues===void 0?void 0:N(o,{onBatch:e.onQueueBatch??(()=>{throw new n("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(),a(),r.close()};return{capabilities:l,close:t,directory:c,drain:s,kv:u,queues:p,scheduler:h,shard:d,sockets:i,[Symbol.dispose]:t}};export{H as createNodePlatform};
@@ -1,17 +0,0 @@
1
- import{randomUUID as M}from"node:crypto";import{deserialize as U,serialize as F}from"node:v8";import{LunoraError as l}from"@lunora/errors";import{isQueueDefinition as W,queueDefaultName as H,queueBindingName as Q}from"@lunora/queue";const f={maxBatchSize:10,maxBatchTimeoutSeconds:5,maxRetries:3,retryDelaySeconds:0,visibilityTimeoutMs:3e4},k=43200,C=(e,u)=>{switch(u){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 l("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "bytes" needs an ArrayBuffer or a typed array')}case"text":{if(typeof e!="string")throw new l("VALIDATION_ERROR",'@lunora/platform-node: queue contentType "text" needs a string');return Buffer.from(e,"utf8")}case"v8":return F(e);default:return Buffer.from(JSON.stringify(e??null),"utf8")}},T=(e,u)=>{switch(u){case"bytes":return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);case"text":return e.toString("utf8");case"v8":return U(e);default:return JSON.parse(e.toString("utf8"))}},V=e=>{try{return T(e.body,e.content_type)}catch{return}},p=e=>e===void 0||!Number.isFinite(e)?0:Math.min(Math.max(0,Math.floor(e)),k)*1e3,j=(e,u)=>{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 O=e.prepare("INSERT INTO _lunora_queue_messages (id, queue, body, content_type, visible_at, enqueued_at) VALUES (?, ?, ?, ?, ?, ?)"),b=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 *`),A=e.prepare("UPDATE _lunora_queue_messages SET visible_at = ? WHERE id = ?"),N=e.prepare("DELETE FROM _lunora_queue_messages WHERE id = ?"),S=e.prepare("UPDATE _lunora_queue_messages SET state = 'dead' WHERE id = ?"),g=e.prepare("SELECT enqueued_at FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ? ORDER BY enqueued_at LIMIT 1"),h=e.prepare("SELECT COUNT(*) AS n FROM _lunora_queue_messages WHERE queue = ? AND state = 'pending' AND visible_at <= ?"),D=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(u.queues).map(([t,a])=>{if(!W(a))throw new l("VALIDATION_ERROR",`@lunora/platform-node: "${t}" is not a defineQueue result`);return{definition:a,exportName:t,name:a.name??H(t)}}),v=new Set(E.map(t=>t.name));for(const t of E){const a=t.definition.deadLetterQueue;if(a!==void 0){if(a===t.name)throw new l("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names itself as its own deadLetterQueue, which would redeliver forever`);if(!v.has(a))throw new l("VALIDATION_ERROR",`@lunora/platform-node: queue "${t.name}" names deadLetterQueue "${a}", which no declared queue provides — its dead letters would be unreachable`)}}const w=u.visibilityTimeoutMs??f.visibilityTimeoutMs,_=u.now??Date.now,y=(t,a,r,o,s)=>{O.run(M(),t,C(a,r),r,s+o,s)},B=(t,a,r,o,s)=>{if(r==="ack"){N.run(t.id);return}const i=a.definition.maxRetries??f.maxRetries;if(t.attempts>i){const c=a.definition.deadLetterQueue;c===void 0?S.run(t.id):(y(c,T(t.body,t.content_type),t.content_type,0,s),N.run(t.id));return}const d=p(o??a.definition.retryDelay??f.retryDelaySeconds);A.run(s+d,t.id)},R={},L={...u.env};for(const t of E){const a={send:async(r,o)=>{y(t.name,r,o?.contentType??"json",p(o?.delaySeconds),_())},sendBatch:async(r,o)=>{const s=_(),i=p(o?.delaySeconds);e.transaction(()=>{for(const d of r){const c=d.delaySeconds===void 0?i:p(d.delaySeconds);y(t.name,d.body,d.contentType??"json",c,s)}})()}};R[t.exportName]=a,L[Q(t.exportName)]=a}const x=async(t,a)=>{const r=Math.min(Math.max(1,t.definition.maxBatchSize??f.maxBatchSize),100),o=h.get(t.name,a)?.n??0;if(o===0)return!1;if(o<r){const n=g.get(t.name,a)?.enqueued_at,m=Math.min(Math.max(0,t.definition.maxBatchTimeout??f.maxBatchTimeoutSeconds),60)*1e3;if(n!==void 0&&a-n<m)return!1}const s=e.transaction(()=>b.all(a+w,t.name,a,r)).immediate();if(s.length===0)return!1;const i=new Map,d=s.map(n=>({ack:()=>{i.set(n.id,{outcome:"ack"})},attempts:n.attempts,body:T(n.body,n.content_type),id:n.id,retry:m=>{i.set(n.id,{delaySeconds:m?.delaySeconds,outcome:"retry"})},timestamp:new Date(n.enqueued_at)})),c={ackAll:()=>{for(const n of s)i.set(n.id,{outcome:"ack"})},messages:d,queue:t.name,retryAll:n=>{for(const m of s)i.set(m.id,{delaySeconds:n?.delaySeconds,outcome:"retry"})}};let q="ack";try{await u.onBatch(c)}catch{q="retry"}return e.transaction(()=>{for(const n of s){const m=i.get(n.id);B(n,t,m?.outcome??q,m?.delaySeconds,a)}})(),!0};return{bindings:R,deadLetters:{list:t=>D.all(t).map(a=>({attempts:a.attempts,body:V(a),id:a.id})),requeue:t=>I.run(_(),t).changes>0},env:L,poll:async(t=_())=>{let a=0;for(const r of E)r.definition.mode!=="pull"&&await x(r,t)&&(a+=1);return a}}};export{j as createNodeQueueHost};
@@ -1 +0,0 @@
1
- import{randomUUID as z,createHash as j}from"node:crypto";import{mkdir as N,open as k,rename as C,unlink as F,readdir as P}from"node:fs/promises";import{join as w,dirname as x}from"node:path";import{LunoraError as l}from"@lunora/errors";import{t as M}from"./to-array-buffer-CP4NlF-y.mjs";new TextEncoder;const H=Array.from({length:32},(e,t)=>t),W=new RegExp(`[${H.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),J=e=>W.test(e),b=".lunora-tmp",U="LNR1",h=8,L=1024,v=255,q=/[%A-Z:]/g,V=e=>{const t=e.replaceAll(q,r=>`%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),a=t.at(-1);return a==="."||a===" "?`${t.slice(0,-1)}%${a.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:t},Q=e=>{try{return decodeURIComponent(e)}catch{return}},m=e=>e.split("/").map(t=>V(t)).join("/"),X=e=>{const t=[];for(const a of e.split("/")){const r=Q(a);if(r===void 0)return;t.push(r)}return t.join("/")},p=e=>{if(typeof e!="string"||e.length===0)throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(e.length>L)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(L)}-byte limit`);if(e.includes("\0"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(J(e))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(e.includes("\\"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(e.startsWith("/"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const t of e.split("/")){if(Buffer.byteLength(V(t),"utf8")>v)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${t}" exceeds ${String(v)} bytes once escaped for the filesystem`);if(t===""||t==="."||t==="..")throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key has an empty, \`.\` or \`..\` path segment ("${e}")`);if(t===b)throw new l("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${b}")`)}},I=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",B=(e,t)=>{const a=t instanceof Error&&"code"in t?t.code:void 0;return a==="EEXIST"||a==="EISDIR"||a==="ENOTDIR"?new l("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},Z=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),a=Buffer.alloc(h);return a.writeUInt32BE(t.byteLength,0),a.write(U,4,"ascii"),Buffer.concat([t,a])},G=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"},K=async e=>{const t=await e.stat();if(!t.isFile()||t.size<h)return;const a=Buffer.alloc(h);if(await e.read(a,0,h,t.size-h),a.toString("ascii",4,h)!==U)return;const r=a.readUInt32BE(0),n=t.size-h-r;if(r===0||n<0)return;const s=Buffer.alloc(r),{bytesRead:i}=await e.read(s,0,r,n);if(i!==r)return;let o;try{o=JSON.parse(s.toString("utf8"))}catch{return}return G(o)?{bodySize:n,meta:o}:void 0},_=async e=>{let t;try{t=await k(e,"r")}catch(a){if(I(a))return;throw a}try{const a=await K(t);if(a===void 0){await t.close();return}return{...a,handle:t}}catch(a){throw await t.close(),a}},D=new FinalizationRegistry(e=>{e.close().catch(()=>{})}),T=async e=>{const t=await _(e);if(t!==void 0)return await t.handle.close(),{bodySize:t.bodySize,meta:t.meta}},R=(e,t)=>{const{sha256Hex:a}=t,r=Buffer.from(a,"hex");return{checksums:{sha256:M(r)},customMetadata:t.customMetadata,etag:a,httpEtag:`"${a}"`,httpMetadata:t.httpMetadata,key:e,sha256:a,sha256Base64:r.toString("base64"),size:t.size,uploaded:new Date(t.uploaded)}},g=(e,t,a)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),a),Y=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-g(t.suffix,e,e)};const a=g(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(a+g(t.length,0,e),e),start:a}},tt=(e,t,a,r)=>{const n=e.createReadStream({autoClose:!1,end:a-1,start:t})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await n.return?.(),await r()},pull:async s=>{let i;try{i=await n.next()}catch(o){throw await r(),o}i.done===!0?(s.close(),await r()):s.enqueue(i.value)}})},et=async(e,t,a)=>{const r=a-t;if(r<=0)return Buffer.alloc(0);const n=Buffer.alloc(r),{bytesRead:s}=await e.read(n,0,r,t);return n.subarray(0,s)},$=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*$(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 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")}},at=async e=>{const t=[],a=async(r,n)=>{let s;try{s=await P(r,{withFileTypes:!0})}catch(o){if(I(o))return;throw o}const i=[];for(const o of s){if(o.name===b)continue;const d=n===""?o.name:`${n}/${o.name}`;if(o.isDirectory())i.push(a(w(r,o.name),d));else{const u=X(d);u!==void 0&&t.push(u)}}await Promise.all(i)};return await a(e,""),t.sort((r,n)=>r<n?-1:+(r>n)),t},rt=(e,t)=>{let a=0,r=e.length;for(;a<r;){const n=a+Math.floor((r-a)/2),s=e[n];s===void 0||s<=t?a=n+1:r=n}return a},A=async e=>{try{await F(e)}catch(t){if(!I(t))throw t}},ft=e=>{const{directory:t}=e;return{delete:async a=>{p(a),await A(w(t,m(a)))},get:async(a,r)=>{p(a);const n=await _(w(t,m(a)));if(n===void 0)return null;const{bodySize:s,handle:i,meta:o}=n,{end:d,start:u}=Y(s,r?.range),f={};let c=!1;const y=async()=>{c||(c=!0,D.unregister(f),await i.close())},O=()=>{if(c)throw new l("BAD_REQUEST",`@lunora/platform-node: R2 object "${a}" body has already been consumed`)},E=async()=>{O();try{return await et(i,u,d)}finally{await y()}},S={...R(a,o),arrayBuffer:async()=>M(await E()),get body(){return O(),d<=u?(y().catch(()=>{}),new Blob([]).stream()):tt(i,u,d,y)},text:async()=>(await E()).toString("utf8")};return D.register(S,{close:y},f),S},head:async a=>{p(a);const r=await T(w(t,m(a)));return r===void 0?null:R(a,r.meta)},list:async(a={})=>{const r=Math.max(1,g(a.limit,1e3,1e3));if(a.prefix?.includes("\0"))throw new l("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const n=await at(t),s=a.prefix??"",i=n.filter(c=>c.startsWith(s)&&(a.delimiter===void 0||!c.slice(s.length).includes(a.delimiter))),o=a.cursor===void 0?0:rt(i,a.cursor),d=i.slice(o,o+r),u=o+r<i.length,f=(await Promise.all(d.map(async c=>{const y=await T(w(t,m(c)));return y===void 0?void 0:R(c,y.meta)}))).filter(c=>c!==void 0);return{cursor:u?d.at(-1):void 0,objects:f,truncated:u}},put:async(a,r,n)=>{p(a);const s=w(t,m(a)),i=w(t,b,z());try{await N(x(s),{recursive:!0})}catch(f){throw B(a,f)}await N(x(i),{recursive:!0});const o=j("sha256");let d=0,u;try{const f=await k(i,"w");try{for await(const c of $(r))o.update(c),d+=c.byteLength,await f.write(c);u={customMetadata:n?.customMetadata,httpMetadata:n?.httpMetadata,sha256Hex:o.digest("hex"),size:d,uploaded:new Date().toISOString()},await f.write(Z(u))}finally{await f.close()}}catch(f){throw await A(i),f}try{await C(i,s)}catch(f){throw await A(i),B(a,f)}return R(a,u)}}};export{ft as createNodeR2Bucket};
@@ -1,15 +0,0 @@
1
- import{randomUUID as k}from"node:crypto";import{deserialize as m,serialize as y}from"node:v8";import{CronExpressionParser as x}from"cron-parser";const E={backoffMultiplier:2,initialDelayMs:1e3,maxAttempts:5,maxDelayMs:6e4},O=2147483647,F=(s,i)=>{const _=i?.initialDelayMs??E.initialDelayMs,T=i?.backoffMultiplier??E.backoffMultiplier,u=i?.maxDelayMs??E.maxDelayMs;return Math.min(u,_*T**Math.max(0,s-1))},C=(s,i={})=>{s.exec(`CREATE TABLE IF NOT EXISTS _lunora_scheduler_jobs (
2
- id TEXT PRIMARY KEY,
3
- function_path TEXT NOT NULL,
4
- args BLOB NOT NULL,
5
- scheduled_for INTEGER NOT NULL,
6
- attempts INTEGER NOT NULL DEFAULT 0,
7
- retry BLOB,
8
- state TEXT NOT NULL DEFAULT 'pending'
9
- )`),s.exec(`CREATE TABLE IF NOT EXISTS _lunora_scheduler_crons (
10
- key TEXT PRIMARY KEY,
11
- expression TEXT NOT NULL,
12
- function_path TEXT NOT NULL,
13
- args BLOB NOT NULL
14
- )`);const _=s.prepare("INSERT INTO _lunora_scheduler_jobs (id, function_path, args, scheduled_for, retry) VALUES (?, ?, ?, ?, ?)"),T=s.prepare("SELECT * FROM _lunora_scheduler_jobs WHERE id = ?"),u=s.prepare("SELECT * FROM _lunora_scheduler_jobs WHERE state = ? ORDER BY scheduled_for"),R=s.prepare("DELETE FROM _lunora_scheduler_jobs WHERE id = ?"),M=s.prepare("DELETE FROM _lunora_scheduler_jobs WHERE id = ? AND state = 'pending'"),A=s.prepare("UPDATE _lunora_scheduler_jobs SET attempts = ?, scheduled_for = ? WHERE id = ?"),L=s.prepare("UPDATE _lunora_scheduler_jobs SET state = 'dead', attempts = ? WHERE id = ?"),U=s.prepare("UPDATE _lunora_scheduler_jobs SET state = 'pending', attempts = 0, scheduled_for = ? WHERE id = ? AND state = 'dead'"),S=s.prepare(`INSERT INTO _lunora_scheduler_crons (key, expression, function_path, args) VALUES (?, ?, ?, ?)
15
- ON CONFLICT (key) DO UPDATE SET expression = excluded.expression, args = excluded.args`),b=s.prepare("SELECT * FROM _lunora_scheduler_crons"),c=()=>s.open,N=e=>e===null?void 0:m(e);let n=!1;const l=new Map,d=new Map,D=e=>({attempts:e.attempts,functionPath:e.function_path,id:e.id,scheduledFor:e.scheduled_for}),f=e=>{const t=l.get(e);t!==void 0&&(clearTimeout(t),l.delete(e))},p=(e,t)=>{f(e);const r=Math.max(0,t-Date.now());if(r>O){const o=setTimeout(()=>{p(e,t)},O);l.set(e,o);return}const a=setTimeout(()=>{w(e).catch(()=>{})},r);l.set(e,a)},w=async e=>{if(l.delete(e),!c())return;const t=T.get(e);if(t?.state!=="pending")return;const r=t.attempts+1,a=N(t.retry);try{await i.onDispatch?.(t.function_path,m(t.args),{attempts:r,id:e}),c()&&R.run(e)}catch{if(n||!c())return;const o=a?.maxAttempts??E.maxAttempts;if(r>=o){L.run(r,e);return}const g=Date.now()+F(r,a);A.run(r,g,e),p(e,g)}},h=e=>{const t=d.get(e.key);t!==void 0&&clearTimeout(t);let r;try{r=x.parse(e.expression).next().getTime()}catch{return}const a=setTimeout(()=>{d.delete(e.key),(async()=>{try{await i.onDispatch?.(e.function_path,m(e.args),{attempts:0,id:e.key})}catch{}!n&&c()&&h(e)})().catch(()=>{})},Math.max(0,r-Date.now()));d.set(e.key,a)},I={cancel:async e=>n?!1:(f(e),M.run(e).changes>0),cron:async(e,t,r)=>{if(n)throw new Error("platform closed: cannot register a cron");x.parse(e);const a={args:y(r??{}),expression:e,function_path:t,key:`${e} ${t}`};S.run(a.key,a.expression,a.function_path,a.args),h(a)},deadLetter:{list:async()=>n||!c()?[]:u.all("dead").map(e=>D(e)),requeue:async e=>{if(n||!c())return!1;const t=Date.now();return U.run(t,e).changes===0?!1:(p(e,t),!0)}},list:async()=>n||!c()?[]:u.all("pending").map(e=>D(e)),schedule:async(e,t,r)=>{if(n)throw new Error("platform closed: cannot schedule a job");const a=`node-job-${k()}`;let o;return r?.at===void 0?o=Date.now()+(r?.delayMs??0):o=typeof r.at=="number"?r.at:r.at.getTime(),_.run(a,e,y(t),o,r?.retry===void 0?null:y(r.retry)),p(a,o),{id:a,scheduledFor:o}}};for(const e of u.all("pending"))p(e.id,e.scheduled_for);for(const e of b.all())h(e);return{dispose:()=>{for(const e of l.values())clearTimeout(e);for(const e of d.values())clearTimeout(e);l.clear(),d.clear(),n=!0},scheduler:I,simulateDeadLetter:async e=>{const t=T.get(e);if(t?.state!=="pending")return!1;f(e);const r=N(t.retry);return L.run((r?.maxAttempts??E.maxAttempts)+1,e),!0}}};export{C as createNodeSchedulerHost};
@@ -1 +0,0 @@
1
- import T from"better-sqlite3";const h=2147483647,p=e=>e===void 0?null:e,_=e=>({get databaseSize(){const r=e.pragma("page_count",{simple:!0}),a=e.pragma("page_size",{simple:!0});return r*a},exec:(r,...a)=>{const t=e.prepare(r),c=a.map(l=>p(l)),s=t.reader?t.all(...c):(t.run(...c),[]);return{[Symbol.iterator]:()=>s[Symbol.iterator](),one:()=>{if(s.length!==1)throw new Error(`expected exactly one row, got ${String(s.length)}`);return s[0]},toArray:()=>[...s]}}}),w=e=>({all:async(r,a)=>e.prepare(r).all(...a.map(t=>p(t))),run:async(r,a)=>({rowsAffected:e.prepare(r).run(...a.map(t=>p(t))).changes})}),y=(e,r)=>{let a,t;e.exec("CREATE TABLE IF NOT EXISTS _lunora_alarm (id INTEGER PRIMARY KEY CHECK (id = 0), scheduled_for INTEGER NOT NULL)");const c=o=>{o===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(o)},s=()=>{t!==void 0&&(clearTimeout(t),t=void 0)},l=o=>{s();const d=Math.max(0,o-Date.now());if(d>h){t=setTimeout(()=>{l(o)},h);return}t=setTimeout(()=>{a=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");a=void 0,s(),c(void 0)},get:()=>a??null,set:o=>{if(!e.open)throw new Error("platform closed: cannot set an alarm");const d=typeof o=="number"?o:o.getTime();a=d,c(d),l(d)}},u=e.prepare("SELECT scheduled_for FROM _lunora_alarm WHERE id = 0").get();return u!==void 0&&(a=u.scheduled_for,l(u.scheduled_for)),{alarms:m,dispose:s}},S=(e={})=>{const r=new T(e.path??":memory:");r.pragma("journal_mode = WAL");const a=_(r),t=w(r),{alarms:c,dispose:s}=y(r,e.onAlarm),l=new Set;let m=Promise.resolve();const u=i=>{const n=m.then(i,i);return m=n.then(()=>{},()=>{}),n};let o=Promise.resolve();const d=async i=>{r.exec("BEGIN");try{const n=await i();return r.exec("COMMIT"),n}catch(n){throw r.exec("ROLLBACK"),n}},E=i=>{const n=o.then(()=>d(i),()=>d(i));return o=n.then(()=>{},()=>{}),n},f={alarms:c,asyncSql:t,runSerialized:u,shardKey:e.shardKey,sql:a,transaction:E,waitUntil:i=>{const n=i.catch(()=>{}).finally(()=>{l.delete(n)});l.add(n)}};return{database:r,dispose:()=>{s(),r.open&&r.close()},drain:async()=>{for(;l.size>0;)await Promise.all(l)},host:f}};export{S as createNodeShardHost};
@@ -1 +0,0 @@
1
- import{serialize as T,deserialize as u}from"node:v8";const c=a=>{a.exec("CREATE TABLE IF NOT EXISTS _lunora_kv (key TEXT PRIMARY KEY, value BLOB NOT NULL)");const E=a.prepare("SELECT value FROM _lunora_kv WHERE key = ?"),o=a.prepare("INSERT INTO _lunora_kv (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value"),t=a.prepare("DELETE FROM _lunora_kv WHERE key = ?"),s=a.prepare("SELECT key, value FROM _lunora_kv");return{delete:async e=>t.run(e).changes>0,get:async e=>{const r=E.get(e);return r===void 0?void 0:u(r.value)},list:async e=>{const r=e?.prefix??"",v=s.all(),l=new Map;for(const n of v)n.key.startsWith(r)&&l.set(n.key,u(n.value));return l},put:async(e,r)=>{o.run(e,T(r))}}};export{c as createNodeShardKvStore};
@@ -1 +0,0 @@
1
- import{mkdirSync as f,readdirSync as y}from"node:fs";import{join as l}from"node:path";import{createNodeShardKvStore as S}from"./createNodeShardKvStore-BfKCQTjE.mjs";import{createNodeShardHost as u}from"./createNodeShardHost-DEHe5Vcm.mjs";import{createNodeSocketHost as v}from"./createNodeSocketHost-CytowrYN.mjs";const c=".sqlite3",g=e=>e.replaceAll(/[^\w.-]/gu,r=>[...Buffer.from(r,"utf8")].map(t=>`%${t.toString(16).padStart(2,"0").toUpperCase()}`).join("")),N=e=>decodeURIComponent(e),R=(e={})=>{const r=new Map,t=new Set;if(e.directory!==void 0){f(e.directory,{recursive:!0});for(const o of y(e.directory))o.endsWith(c)&&t.add(N(o.slice(0,-c.length)))}const i=o=>{const s=r.get(o);if(s!==void 0)return s.shard;const a=e.directory===void 0?void 0:l(e.directory,`${g(o)}${c}`);let d;const{database:h,dispose:m,host:p}=u({onAlarm:()=>e.onAlarm?.(d),path:a,shardKey:o});return d={kv:S(h),shard:p,shardKey:o,sockets:v(h).socket},r.set(o,{dispose:m,shard:d}),t.add(o),d},n=o=>({fetch:async s=>{const a=i(o);return e.onFetch===void 0?new Response(o):e.onFetch(s,a)}});return{close:()=>{for(const o of r.values())o.dispose();r.clear()},directory:{get:o=>n(String(o)),getByName:o=>n(o),idForName:o=>o},listShardKeys:()=>[...t],shardFor:i}};export{R as createNodeShardRegistry};
@@ -1 +0,0 @@
1
- const i=a=>({acceptWebSocket:(e,t)=>{const s=a.sockets.accept(e,void 0,t),r=e;r.serializeAttachment=c=>{s.serializeAttachment(c)},r.deserializeAttachment=()=>s.deserializeAttachment()},blockConcurrencyWhile:e=>a.shard.runSerialized(e),getWebSockets:e=>a.sockets.getSockets(e),id:{name:a.shardKey},storage:{delete:e=>a.kv.delete(e),deleteAlarm:async()=>{await a.shard.alarms.delete()},get:e=>a.kv.get(e),getAlarm:async()=>a.shard.alarms.get(),list:e=>a.kv.list(e),put:(e,t)=>a.kv.put(e,t),setAlarm:async e=>{await a.shard.alarms.set(e)},sql:{get databaseSize(){return a.shard.sql.databaseSize},exec:(e,...t)=>a.shard.sql.exec(e,...t)},transaction:e=>a.shard.transaction(e)},waitUntil:e=>{a.shard.waitUntil?.(e)}});export{i as createNodeShardState};
@@ -1,2 +0,0 @@
1
- import{serialize as u,deserialize as T}from"node:v8";import{t as v}from"./to-array-buffer-CP4NlF-y.mjs";const k=o=>{o.exec("CREATE TABLE IF NOT EXISTS _lunora_sockets (id TEXT PRIMARY KEY, attachment BLOB, tags TEXT NOT NULL DEFAULT '[]')");const g=o.prepare(`INSERT INTO _lunora_sockets (id, attachment, tags) VALUES (?, ?, ?)
2
- ON CONFLICT (id) DO UPDATE SET attachment = excluded.attachment, tags = excluded.tags`),E=o.prepare("UPDATE _lunora_sockets SET attachment = ? WHERE id = ?"),m=o.prepare("UPDATE _lunora_sockets SET tags = ? WHERE id = ?"),p=o.prepare("SELECT attachment, tags FROM _lunora_sockets WHERE id = ?"),h=o.prepare("DELETE FROM _lunora_sockets WHERE id = ?"),r=new Map,d=new WeakMap,f=(a,e)=>{o.open&&E.run(e===void 0?null:u(e),a)},i=(a,e)=>{o.open&&m.run(JSON.stringify([...e]),a)},c=a=>{const e=a,t={bufferedAmount:e.bufferedAmount,close:(n,s)=>{e.closed=!0,r.delete(e.id),o.open&&h.run(e.id)},deserializeAttachment:()=>e.attachment,send:n=>{e.received.push(typeof n=="string"?n:v(n))},serializeAttachment:n=>{e.attachment=n,f(e.id,n)}};return e.handle=t,d.set(t,e.id),t};return{readFrames:a=>(r.get(d.get(a)??"")?.received??[]).filter(e=>typeof e=="string"),restoreSocket:(a,e)=>{const t=o.open?p.get(a):void 0,n=t?.attachment===null?void 0:t?.attachment,s={attachment:n===void 0?e:T(n),bufferedAmount:0,closed:!1,handle:void 0,id:a,raw:void 0,received:[],tags:new Set(t===void 0?[]:JSON.parse(t.tags))};return r.set(a,s),c(s)},simulateRecycle:()=>{r.clear()},socket:{accept:(a,e,t)=>{const n=crypto.randomUUID(),s=new Set(t),l={attachment:e,bufferedAmount:0,closed:!1,handle:void 0,id:n,raw:a,received:[],tags:s};return r.set(n,l),o.open&&g.run(n,e===void 0?null:u(e),JSON.stringify([...s])),c(l)},getSockets:a=>{const e=[...r.values()];return(a===void 0?e:e.filter(t=>t.tags.has(a))).map(t=>t.handle)},handleFor:a=>[...r.values()].find(e=>e.raw===a)?.handle,idFor:a=>{const e=d.get(a);if(e===void 0)throw new Error("@lunora/platform-node: idFor called with a handle this host never issued");return e},removeTag:(a,e)=>{const t=r.get(d.get(a)??"");t!==void 0&&(e===void 0?t.tags.clear():t.tags.delete(e),i(t.id,t.tags))},setTag:(a,e)=>{const t=r.get(d.get(a)??"");t!==void 0&&(t.tags.add(e),i(t.id,t.tags))}}}};export{k as createNodeSocketHost};
@@ -1 +0,0 @@
1
- import{LunoraError as d}from"@lunora/errors";import{isWorkflowDefinition as k,workflowDefaultName as N,createWorkflowRunContext as E,workflowBindingName as A}from"@lunora/workflow";import{defineWorkflow as D,createRuntime as O}from"@visulima/workflow";const c="@lunora/platform-node:terminated",T={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},b=/^(\d+(?:\.\d+)?)\s*([a-z]+)$/i,I=n=>{if(typeof n=="number"){if(!Number.isFinite(n))throw new d("VALIDATION_ERROR",`@lunora/platform-node: workflow duration must be finite, got ${String(n)}`);return Math.max(0,n)}const r=b.exec(n.trim());if(r===null)throw new d("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${n}"`);const a=Number(r[1]),i=(r[2]??"").toLowerCase(),u=T[i];if(u===void 0)throw new d("VALIDATION_ERROR",`@lunora/platform-node: unsupported workflow duration "${n}"`);const l=a*u;if(!Number.isFinite(l))throw new d("VALIDATION_ERROR",`@lunora/platform-node: workflow duration "${n}" overflows`);return Math.max(0,l)},p="@lunora/platform-node:alias",$=n=>({attempt:1,config:{retries:{limit:1}},step:{count:1,name:n}}),_=n=>{switch(n){case"completed":return"complete";case"failed":return"errored";default:return"waiting"}},x=n=>({do:(async(r,a,i,u)=>{const l=typeof a=="function"?a:i;if(typeof l!="function")throw new d("VALIDATION_ERROR",`@lunora/platform-node: step.do("${r}") requires a callback`);return n.step(r,async()=>l($(r)))}),sleep:async(r,a)=>{const i=I(a);i!==0&&await n.sleep(r,i)},sleepUntil:async(r,a)=>{const i=typeof a=="number"?a:a.getTime();if(!Number.isFinite(i))throw new d("VALIDATION_ERROR",`@lunora/platform-node: step.sleepUntil("${r}") needs a valid timestamp`);const u=Math.max(0,i-Date.now());u!==0&&await n.sleep(r,u)},waitForEvent:(async(r,a)=>({payload:await n.waitForEvent(r,a.type,{timeout:a.timeout===void 0?void 0:I(a.timeout)}),type:a.type}))}),q=n=>{const r=Object.entries(n.workflows).map(([e,t])=>{if(!k(t))throw new d("VALIDATION_ERROR",`@lunora/platform-node: "${e}" is not a defineWorkflow result`);return{definition:t,exportName:e,id:t.name??N(e)}}),{store:a}=n,i={...n.env},u=r.map(({definition:e,exportName:t,id:o})=>D({id:o,tags:[t],run:async s=>{const f=x(s),v=E({env:i,event:{instanceId:s.runId,payload:s.payload,timestamp:new Date,workflowName:o},exportName:t,step:f});return await e.handler(v)}})),l=new Set,R={acquire:a.acquire===void 0?void 0:async(e,t,o)=>a.acquire?.(e,t,o)??!1,delete:async e=>a.delete(e),due:async(e,t)=>a.due(e,t),load:async e=>{const t=await a.load(e);return t===void 0||t.definitionId===c||t.definitionId===p?void 0:t},release:a.release===void 0?void 0:async(e,t)=>a.release?.(e,t),save:async e=>{l.has(e.runId)||await a.save(e)}},w=O({leaseTtlMs:n.leaseTtlMs,store:R,workflows:u}),m=e=>({id:e,pause:()=>Promise.reject(new d("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${e}" cannot be paused — the visulima engine has no pause equivalent`)),restart:()=>Promise.reject(new d("NOT_IMPLEMENTED",`@lunora/platform-node: workflow instance "${e}" cannot be restarted — the visulima engine has no restart equivalent`)),resume:async()=>{if((await a.load(e))?.definitionId===c)throw new d("BAD_REQUEST",`@lunora/platform-node: workflow instance "${e}" was terminated and cannot be resumed`);await w.resume(e)},sendEvent:async t=>{const o=await w.getRun(e);if(o?.status!=="waiting"||o.pending?.kind!=="event"||o.pending.eventName!==t.type)throw new d("BAD_REQUEST",`@lunora/platform-node: workflow instance "${e}" is not waiting for event "${t.type}"`);await w.signal(e,t.type,t.payload)},status:async()=>{const t=await a.load(e);if(t===void 0)return{status:"unknown"};if(t.definitionId===c)return{status:"terminated"};const o=await w.getRun(e);return o===void 0?{status:"unknown"}:{error:o.error===void 0?void 0:{message:o.error.message,name:o.error.name},output:o.output,status:_(o.status)}},terminate:async()=>{await a.load(e)!==void 0&&(l.add(e),await a.delete(e),await a.save({definitionId:c,runId:e,snapshot:void 0,status:"failed",updatedAt:Date.now()}))}}),g=async e=>{const t=await a.load(e);return t?.definitionId===p?t.snapshot:e},y=async(e,t)=>{if(t?.id===void 0){const f=await w.trigger(e,t?.params);return m(f.runId)}const o=await a.load(t.id);if(o?.definitionId===p)return m(o.snapshot);if(o!==void 0)throw new d("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 s=await w.trigger(e,t.params);return await a.save({definitionId:p,runId:t.id,snapshot:s.runId,status:"failed",updatedAt:Date.now()}),m(s.runId)},h={};for(const{exportName:e,id:t}of r){const o={create:async s=>y(t,s),createBatch:async s=>{const f=[];for(const v of s)f.push(await y(t,v));return f},get:async s=>m(await g(s))};h[e]=o,i[A(e)]=o}return{bindings:h,env:i,runtime:w}};export{q as createNodeWorkflowHost};