@lunora/platform-node 1.0.0-alpha.129 → 1.0.0-alpha.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -4,6 +4,39 @@ import Database from 'better-sqlite3';
4
4
  import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike, SocketHandle } from '@lunora/platform';
5
5
  import { WorkflowBindingLike } from '@lunora/workflow';
6
6
  import { WorkflowRuntime, WorkflowStore } from '@visulima/workflow';
7
+ /** One function call the route has authenticated and parsed. */
8
+ interface NodeDispatchInvocation {
9
+ args: Record<string, unknown>;
10
+ functionPath: string;
11
+ /** The scheduler record or delivery id, used as the shard's idempotency key. */
12
+ id: string | undefined;
13
+ /** Identity the trusted caller forwarded, if any. */
14
+ identity: {
15
+ identity?: string;
16
+ userId?: string;
17
+ } | undefined;
18
+ shardKey: string;
19
+ traceparent: string | undefined;
20
+ }
21
+ interface NodeDispatchRouteOptions {
22
+ /** `LUNORA_ADMIN_TOKEN`. Used as the bearer when no HMAC signature is presented. */
23
+ adminToken?: string;
24
+ /** The shard a body without `shardKey` is sent to. */
25
+ defaultShardKey: string;
26
+ /** Runs the call in-process and returns the shard's response, unchanged. */
27
+ invoke: (call: NodeDispatchInvocation) => Promise<Response>;
28
+ /** `LUNORA_SCHEDULER_SECRET`. When set, a signed request is verified against it. */
29
+ schedulerSecret?: string;
30
+ }
31
+ /**
32
+ * Build the dispatch route. The returned function answers any request it is
33
+ * handed; mounting it at {@link NODE_SCHEDULER_DISPATCH_PATH} is the caller's job.
34
+ * With neither secret configured every request is refused, so an unconfigured
35
+ * route fails closed.
36
+ */
37
+ declare const createNodeDispatchRoute: (options: NodeDispatchRouteOptions) => ((request: Request) => Promise<Response>);
38
+ /** The path `@lunora/dispatch` and `@lunora/scheduler` POST to. */
39
+ declare const NODE_SCHEDULER_DISPATCH_PATH: "/_lunora/scheduler/dispatch";
7
40
  /**
8
41
  * The Node store options — the shared store options minus the two this binding
9
42
  * owns. `dialect` is fixed (SQLite) and `exec` is the store's own connection;
@@ -425,6 +458,16 @@ interface NodePlatform<Queues extends Record<string, {
425
458
  close: () => void;
426
459
  /** In-process shard directory (see `createNodeShardRegistry` for what it can and cannot do). */
427
460
  directory: ShardDirectory;
461
+ /**
462
+ * The scheduler dispatch route, served in-process: `Request` in, shard
463
+ * response out, no listener. It answers the path `@lunora/dispatch` and
464
+ * `@lunora/scheduler` POST to. Pass it to `dispatchQueueBatch` as
465
+ * `fetchImpl` so a queue handler's `message.run` / `ctx.run` reaches the
466
+ * shard; the env you pass there must carry `LUNORA_ORIGIN_URL` and the same
467
+ * `LUNORA_ADMIN_TOKEN` as `dispatch.adminToken`. Every request is refused
468
+ * unless `dispatch` supplies a secret.
469
+ */
470
+ dispatch: (request: Request) => Promise<Response>;
428
471
  /**
429
472
  * Wait for every promise handed to `shard.waitUntil` to settle.
430
473
  *
@@ -477,6 +520,22 @@ interface NodePlatform<Queues extends Record<string, {
477
520
  */
478
521
  workflows?: NodeWorkflowHost<Workflows>;
479
522
  }
523
+ /**
524
+ * The secrets the dispatch route authenticates with. With neither, the route
525
+ * refuses everything.
526
+ *
527
+ * Queue and topic delivery authenticates with `adminToken` alone:
528
+ * `@lunora/dispatch` always sends `Bearer <LUNORA_ADMIN_TOKEN>` and never signs,
529
+ * so the value here must match the token the caller puts on the env it passes
530
+ * `dispatchQueueBatch`. `schedulerSecret` only verifies a signed body, which the
531
+ * scheduler's own delivery sends; queue delivery never does.
532
+ */
533
+ interface NodePlatformDispatchOptions {
534
+ /** `LUNORA_ADMIN_TOKEN`. The bearer queue and topic delivery sends, so it must match the caller's env. */
535
+ adminToken?: string;
536
+ /** `LUNORA_SCHEDULER_SECRET`. When set, a signed body is verified against it. Queue delivery does not sign. */
537
+ schedulerSecret?: string;
538
+ }
480
539
  /**
481
540
  * Options for {@link createNodePlatform} — the shard host's (`path`,
482
541
  * `shardKey`, `onAlarm`), the scheduler's (`onDispatch`), and the shard
@@ -494,6 +553,8 @@ type NodePlatformOptions<Queues extends Record<string, {
494
553
  }> = Record<string, never>, Workflows extends Record<string, {
495
554
  isLunoraWorkflow: true;
496
555
  }> = Record<string, never>> = {
556
+ /** Secrets for the dispatch route. Omit to leave the route refusing every request. */
557
+ dispatch?: NodePlatformDispatchOptions;
497
558
  /**
498
559
  * Database file the `.global()` tables live in — its own file, never a
499
560
  * shard's, because a table every shard reads must not be inside any one of
@@ -617,49 +678,7 @@ declare const createNodeSocketHost: (database: Database.Database) => NodeSocketH
617
678
  * Pass the result as `createNodeWorkflowHost({ store })`.
618
679
  */
619
680
  declare const createNodeWorkflowStore: (database: Database.Database) => WorkflowStore;
620
- export {
621
- /**
622
- * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
623
- * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
624
- * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
625
- * scheduler registry.
626
- *
627
- * Promoted from `@lunora/platform`'s `node:sqlite` reference host
628
- * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
629
- * durability half of each contract actually holds: alarms and scheduler jobs
630
- * are persisted **and re-armed on construction**, socket attachments and tags
631
- * live in SQLite rather than a `Map`, and `.global()` tables run the real
632
- * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
633
- * a restart test — a second host over the same database file — not only by the
634
- * TCK's simulated recycle.
635
- *
636
- * Three suites run against this package: `@lunora/platform/conformance`'s host
637
- * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
638
- * lifecycle/global-store tests.
639
- *
640
- * **Emulated here**, and rated accordingly in `NODE_CAPABILITIES`
641
- * (`@lunora/platform`): queues over a durable table (`./node-queue-host`), R2
642
- * buckets over the local filesystem (`./node-r2-bucket`), workflows over the
643
- * `@visulima/workflow` engine (`./node-workflow-host`), and cross-shard fan-out
644
- * via `@lunora/runtime`'s query coordinator over the in-process shard registry.
645
- * `createNodePlatform` binds all three declarations (`queues`, `workflows`,
646
- * `objectStorageDirectory`) — a capability rated `emulated` with nothing bound
647
- * is the one combination that fails at runtime with no diagnostic before it.
648
- *
649
- * **Still missing:** a dev server. There is no `lunora dev --target node`, and
650
- * nothing here owns a timer, so queue delivery is driven by an explicit
651
- * `poll()`. Also absent are the Cloudflare product bindings with no local
652
- * equivalent — Vectorize, Workers AI, Browser Rendering, Containers, Analytics
653
- * Engine, Pipelines, Secrets Store, Hyperdrive.
654
- *
655
- * `@lunora/config` ships a `node` **deploy** driver, so `--target node` resolves
656
- * for `provision` — which reports, once, which declared features this target
657
- * cannot serve, and writes nothing: there is no hosted control plane to deploy
658
- * to and no `wrangler`-equivalent to shell out to. A deploy driver is not a dev
659
- * server, which is why both statements above hold at once. See
660
- * `plans/234-node-host-findings.md`.
661
- */
662
- type NodeGlobalContextDatabaseOptions,
681
+ export { NODE_SCHEDULER_DISPATCH_PATH,
663
682
  /**
664
683
  * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
665
684
  * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
@@ -701,7 +720,7 @@ type NodeGlobalContextDatabaseOptions,
701
720
  * server, which is why both statements above hold at once. See
702
721
  * `plans/234-node-host-findings.md`.
703
722
  */
704
- type NodeGlobalStore,
723
+ type NodeDispatchInvocation,
705
724
  /**
706
725
  * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
707
726
  * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
@@ -743,4 +762,4 @@ type NodeGlobalStore,
743
762
  * server, which is why both statements above hold at once. See
744
763
  * `plans/234-node-host-findings.md`.
745
764
  */
746
- type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeProfileHandler, type NodeProfileHandlerOptions, 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, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
765
+ type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformDispatchOptions, type NodePlatformOptions, type NodeProfileHandler, type NodeProfileHandlerOptions, 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, createNodeDispatchRoute, createNodeGlobalStore, createNodePlatform, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,39 @@ import Database from 'better-sqlite3';
4
4
  import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike, SocketHandle } from '@lunora/platform';
5
5
  import { WorkflowBindingLike } from '@lunora/workflow';
6
6
  import { WorkflowRuntime, WorkflowStore } from '@visulima/workflow';
7
+ /** One function call the route has authenticated and parsed. */
8
+ interface NodeDispatchInvocation {
9
+ args: Record<string, unknown>;
10
+ functionPath: string;
11
+ /** The scheduler record or delivery id, used as the shard's idempotency key. */
12
+ id: string | undefined;
13
+ /** Identity the trusted caller forwarded, if any. */
14
+ identity: {
15
+ identity?: string;
16
+ userId?: string;
17
+ } | undefined;
18
+ shardKey: string;
19
+ traceparent: string | undefined;
20
+ }
21
+ interface NodeDispatchRouteOptions {
22
+ /** `LUNORA_ADMIN_TOKEN`. Used as the bearer when no HMAC signature is presented. */
23
+ adminToken?: string;
24
+ /** The shard a body without `shardKey` is sent to. */
25
+ defaultShardKey: string;
26
+ /** Runs the call in-process and returns the shard's response, unchanged. */
27
+ invoke: (call: NodeDispatchInvocation) => Promise<Response>;
28
+ /** `LUNORA_SCHEDULER_SECRET`. When set, a signed request is verified against it. */
29
+ schedulerSecret?: string;
30
+ }
31
+ /**
32
+ * Build the dispatch route. The returned function answers any request it is
33
+ * handed; mounting it at {@link NODE_SCHEDULER_DISPATCH_PATH} is the caller's job.
34
+ * With neither secret configured every request is refused, so an unconfigured
35
+ * route fails closed.
36
+ */
37
+ declare const createNodeDispatchRoute: (options: NodeDispatchRouteOptions) => ((request: Request) => Promise<Response>);
38
+ /** The path `@lunora/dispatch` and `@lunora/scheduler` POST to. */
39
+ declare const NODE_SCHEDULER_DISPATCH_PATH: "/_lunora/scheduler/dispatch";
7
40
  /**
8
41
  * The Node store options — the shared store options minus the two this binding
9
42
  * owns. `dialect` is fixed (SQLite) and `exec` is the store's own connection;
@@ -425,6 +458,16 @@ interface NodePlatform<Queues extends Record<string, {
425
458
  close: () => void;
426
459
  /** In-process shard directory (see `createNodeShardRegistry` for what it can and cannot do). */
427
460
  directory: ShardDirectory;
461
+ /**
462
+ * The scheduler dispatch route, served in-process: `Request` in, shard
463
+ * response out, no listener. It answers the path `@lunora/dispatch` and
464
+ * `@lunora/scheduler` POST to. Pass it to `dispatchQueueBatch` as
465
+ * `fetchImpl` so a queue handler's `message.run` / `ctx.run` reaches the
466
+ * shard; the env you pass there must carry `LUNORA_ORIGIN_URL` and the same
467
+ * `LUNORA_ADMIN_TOKEN` as `dispatch.adminToken`. Every request is refused
468
+ * unless `dispatch` supplies a secret.
469
+ */
470
+ dispatch: (request: Request) => Promise<Response>;
428
471
  /**
429
472
  * Wait for every promise handed to `shard.waitUntil` to settle.
430
473
  *
@@ -477,6 +520,22 @@ interface NodePlatform<Queues extends Record<string, {
477
520
  */
478
521
  workflows?: NodeWorkflowHost<Workflows>;
479
522
  }
523
+ /**
524
+ * The secrets the dispatch route authenticates with. With neither, the route
525
+ * refuses everything.
526
+ *
527
+ * Queue and topic delivery authenticates with `adminToken` alone:
528
+ * `@lunora/dispatch` always sends `Bearer <LUNORA_ADMIN_TOKEN>` and never signs,
529
+ * so the value here must match the token the caller puts on the env it passes
530
+ * `dispatchQueueBatch`. `schedulerSecret` only verifies a signed body, which the
531
+ * scheduler's own delivery sends; queue delivery never does.
532
+ */
533
+ interface NodePlatformDispatchOptions {
534
+ /** `LUNORA_ADMIN_TOKEN`. The bearer queue and topic delivery sends, so it must match the caller's env. */
535
+ adminToken?: string;
536
+ /** `LUNORA_SCHEDULER_SECRET`. When set, a signed body is verified against it. Queue delivery does not sign. */
537
+ schedulerSecret?: string;
538
+ }
480
539
  /**
481
540
  * Options for {@link createNodePlatform} — the shard host's (`path`,
482
541
  * `shardKey`, `onAlarm`), the scheduler's (`onDispatch`), and the shard
@@ -494,6 +553,8 @@ type NodePlatformOptions<Queues extends Record<string, {
494
553
  }> = Record<string, never>, Workflows extends Record<string, {
495
554
  isLunoraWorkflow: true;
496
555
  }> = Record<string, never>> = {
556
+ /** Secrets for the dispatch route. Omit to leave the route refusing every request. */
557
+ dispatch?: NodePlatformDispatchOptions;
497
558
  /**
498
559
  * Database file the `.global()` tables live in — its own file, never a
499
560
  * shard's, because a table every shard reads must not be inside any one of
@@ -617,49 +678,7 @@ declare const createNodeSocketHost: (database: Database.Database) => NodeSocketH
617
678
  * Pass the result as `createNodeWorkflowHost({ store })`.
618
679
  */
619
680
  declare const createNodeWorkflowStore: (database: Database.Database) => WorkflowStore;
620
- export {
621
- /**
622
- * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
623
- * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
624
- * `SchedulerHost`) over `better-sqlite3` and an in-process socket/directory/
625
- * scheduler registry.
626
- *
627
- * Promoted from `@lunora/platform`'s `node:sqlite` reference host
628
- * (`src/conformance/reference-host.ts`) under plan 234, then hardened until the
629
- * durability half of each contract actually holds: alarms and scheduler jobs
630
- * are persisted **and re-armed on construction**, socket attachments and tags
631
- * live in SQLite rather than a `Map`, and `.global()` tables run the real
632
- * `@lunora/sql-store` core (`./node-global-store`). Each of those is pinned by
633
- * a restart test — a second host over the same database file — not only by the
634
- * TCK's simulated recycle.
635
- *
636
- * Three suites run against this package: `@lunora/platform/conformance`'s host
637
- * TCK, `@lunora/shard-engine/conformance`'s engine suite, and its own
638
- * lifecycle/global-store tests.
639
- *
640
- * **Emulated here**, and rated accordingly in `NODE_CAPABILITIES`
641
- * (`@lunora/platform`): queues over a durable table (`./node-queue-host`), R2
642
- * buckets over the local filesystem (`./node-r2-bucket`), workflows over the
643
- * `@visulima/workflow` engine (`./node-workflow-host`), and cross-shard fan-out
644
- * via `@lunora/runtime`'s query coordinator over the in-process shard registry.
645
- * `createNodePlatform` binds all three declarations (`queues`, `workflows`,
646
- * `objectStorageDirectory`) — a capability rated `emulated` with nothing bound
647
- * is the one combination that fails at runtime with no diagnostic before it.
648
- *
649
- * **Still missing:** a dev server. There is no `lunora dev --target node`, and
650
- * nothing here owns a timer, so queue delivery is driven by an explicit
651
- * `poll()`. Also absent are the Cloudflare product bindings with no local
652
- * equivalent — Vectorize, Workers AI, Browser Rendering, Containers, Analytics
653
- * Engine, Pipelines, Secrets Store, Hyperdrive.
654
- *
655
- * `@lunora/config` ships a `node` **deploy** driver, so `--target node` resolves
656
- * for `provision` — which reports, once, which declared features this target
657
- * cannot serve, and writes nothing: there is no hosted control plane to deploy
658
- * to and no `wrangler`-equivalent to shell out to. A deploy driver is not a dev
659
- * server, which is why both statements above hold at once. See
660
- * `plans/234-node-host-findings.md`.
661
- */
662
- type NodeGlobalContextDatabaseOptions,
681
+ export { NODE_SCHEDULER_DISPATCH_PATH,
663
682
  /**
664
683
  * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
665
684
  * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
@@ -701,7 +720,7 @@ type NodeGlobalContextDatabaseOptions,
701
720
  * server, which is why both statements above hold at once. See
702
721
  * `plans/234-node-host-findings.md`.
703
722
  */
704
- type NodeGlobalStore,
723
+ type NodeDispatchInvocation,
705
724
  /**
706
725
  * `@lunora/platform-node` — a Node implementation of the `@lunora/platform`
707
726
  * host contracts (`ShardHost`, `SocketHost`, `ShardDirectory`, `ShardKvStore`,
@@ -743,4 +762,4 @@ type NodeGlobalStore,
743
762
  * server, which is why both statements above hold at once. See
744
763
  * `plans/234-node-host-findings.md`.
745
764
  */
746
- type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformOptions, type NodeProfileHandler, type NodeProfileHandlerOptions, 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, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
765
+ type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodePlatform, type NodePlatformDispatchOptions, type NodePlatformOptions, type NodeProfileHandler, type NodeProfileHandlerOptions, 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, createNodeDispatchRoute, createNodeGlobalStore, createNodePlatform, createNodeProfileHandler, 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-DiGxjmD7.mjs";import{createNodeShardKvStore as d}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as f}from"./packem_shared/createNodePlatform-D29HvrOz.mjs";import{createNodeProfileHandler as x}from"./packem_shared/createNodeProfileHandler-MX5xKPg0.mjs";import{createNodeQueueHost as p}from"./packem_shared/createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as l}from"./packem_shared/createNodeR2Bucket-ChHs23n-.mjs";import{createNodeSchedulerHost as H}from"./packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as k}from"./packem_shared/createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as i}from"./packem_shared/createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeShardState as P}from"./packem_shared/createNodeShardState-D6XMWbFs.mjs";import{createNodeSocketHost as W}from"./packem_shared/createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as g}from"./packem_shared/createNodeWorkflowHost-B76TKiw_.mjs";import{createNodeWorkflowStore as q}from"./packem_shared/createNodeWorkflowStore-CL7RYP6H.mjs";export{r as createNodeGlobalStore,f as createNodePlatform,x as createNodeProfileHandler,p as createNodeQueueHost,l as createNodeR2Bucket,H as createNodeSchedulerHost,k as createNodeShardHost,d as createNodeShardKvStore,i as createNodeShardRegistry,P as createNodeShardState,W as createNodeSocketHost,t as createNodeSqlExec,g as createNodeWorkflowHost,q as createNodeWorkflowStore};
1
+ import{NODE_SCHEDULER_DISPATCH_PATH as r,createNodeDispatchRoute as t}from"./packem_shared/NODE_SCHEDULER_DISPATCH_PATH-CNBAtztq.mjs";import{createNodeGlobalStore as d,createNodeSqlExec as c}from"./packem_shared/createNodeGlobalStore-DiGxjmD7.mjs";import{createNodeShardKvStore as N}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as p}from"./packem_shared/createNodePlatform-dBsmRW5b.mjs";import{createNodeProfileHandler as S}from"./packem_shared/createNodeProfileHandler-MX5xKPg0.mjs";import{createNodeQueueHost as H}from"./packem_shared/createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as h}from"./packem_shared/createNodeR2Bucket-C_oO6X63.mjs";import{createNodeSchedulerHost as k}from"./packem_shared/createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as E}from"./packem_shared/createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as R}from"./packem_shared/createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeShardState as _}from"./packem_shared/createNodeShardState-D6XMWbFs.mjs";import{createNodeSocketHost as A}from"./packem_shared/createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as T}from"./packem_shared/createNodeWorkflowHost-B76TKiw_.mjs";import{createNodeWorkflowStore as b}from"./packem_shared/createNodeWorkflowStore-CL7RYP6H.mjs";export{r as NODE_SCHEDULER_DISPATCH_PATH,t as createNodeDispatchRoute,d as createNodeGlobalStore,p as createNodePlatform,S as createNodeProfileHandler,H as createNodeQueueHost,h as createNodeR2Bucket,k as createNodeSchedulerHost,E as createNodeShardHost,N as createNodeShardKvStore,R as createNodeShardRegistry,_ as createNodeShardState,A as createNodeSocketHost,c as createNodeSqlExec,T as createNodeWorkflowHost,b as createNodeWorkflowStore};
@@ -0,0 +1 @@
1
+ import{LunoraError as i}from"@lunora/errors";import{toErrorResponse as h,resolveShard as u}from"@lunora/runtime";import{s as l}from"./hmac-url-BrwGfbCr.mjs";const f=async(t,e)=>{if(!t.body)return"";const n=t.body.getReader(),r=new TextDecoder;let a=0,o="";for(;;){const{done:s,value:d}=await n.read();if(s)break;if(d){if(a+=d.byteLength,a>e)return await n.cancel().catch(()=>{}),null;o+=r.decode(d,{stream:!0})}}return o+=r.decode(),o},c=(t,e)=>{const n=Math.max(t.length,e.length);let r=t.length^e.length;for(let a=0;a<n;a+=1){const o=a<t.length?t.charCodeAt(a):0,s=a<e.length?e.charCodeAt(a):0;r|=o^s}return r===0},y="/_lunora/scheduler/dispatch",g="__lunora_relation__:",p=t=>t.startsWith(g),w=(t,e)=>{if(!e||e.length===0)return!1;const n=t.headers.get("authorization");if(!n)return!1;const[r,...a]=n.split(" ");return r?.toLowerCase()!=="bearer"?!1:c(e,a.join(" ").trim())},_=async(t,e,n)=>{const r=t.headers.get("x-lunora-scheduler-signature"),{schedulerSecret:a}=n;return r!==null&&r.length>0&&a!==void 0&&a.length>0?c(await l(a,e),r):w(t,n.adminToken)},A="x-lunora-paywall",S="1",E=({forwardedIdentity:t,mutationId:e,traceparent:n})=>{const r={"content-type":"application/json",[A]:S,"x-lunora-system":"1"};return t?.userId!==void 0&&t.userId.length>0&&(r["x-lunora-userid"]=t.userId),t?.identity!==void 0&&t.identity.length>0&&(r["x-lunora-identity"]=t.identity),e!==void 0&&e.length>0&&(r["x-lunora-mutation-id"]=e),n!==void 0&&n.length>0&&(r.traceparent=n),r},T=t=>{const e=t.headers.get("x-lunora-userid"),n=t.headers.get("x-lunora-identity");if(!(e===null&&n===null))return{...n===null?{}:{identity:n},...e===null?{}:{userId:e}}},P=1048576,m=async t=>{const e=await f(t,P);if(e===null)throw new i("PAYLOAD_TOO_LARGE","Scheduler dispatch body is too large");return e},D=async(t,e)=>{if(t.method!=="POST")throw new i("METHOD_NOT_ALLOWED","Scheduler dispatch endpoint requires POST",{status:405});const n=await m(t);if(!await _(t,n,{adminToken:e.adminToken,schedulerSecret:e.schedulerSecret}))throw new i("DISPATCH_UNAUTHENTICATED","Scheduler dispatch requires a valid signature or admin bearer");let a;try{a=JSON.parse(n)}catch{throw new i("BAD_REQUEST","Scheduler dispatch body must be valid JSON")}const o=a??{};if(typeof o.workflow=="string"||o.pool!==void 0)throw new i("SERVICE_UNAVAILABLE","Scheduler dispatch for a workflow or pool target is not served by the Node dispatch route");if(typeof o.functionPath!="string"||o.functionPath.length===0)throw new i("BAD_REQUEST","Scheduler dispatch is missing `functionPath`");if(p(o.functionPath))throw new i("FORBIDDEN","`__lunora_relation__:*` is a fan-out-only reserved RPC and cannot be dispatched to a single shard");const s=typeof o.shardKey=="string"&&o.shardKey.length>0?o.shardKey:e.defaultShardKey;return e.invoke({args:o.args??{},functionPath:o.functionPath,id:typeof o.id=="string"&&o.id.length>0?o.id:void 0,identity:T(t),shardKey:s,traceparent:t.headers.get("traceparent")??void 0})},L=(t,e)=>n=>{if(!e)throw new i("SERVICE_UNAVAILABLE","@lunora/platform-node: the dispatch route needs `onFetch` to run a function; without it a shard only echoes its key");const r=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:n.args,functionPath:n.functionPath}),headers:E({forwardedIdentity:n.identity,mutationId:n.id,traceparent:n.traceparent}),method:"POST"});return u(t,n.shardKey).fetch(r)},O=t=>async e=>{try{return await D(e,t)}catch(n){return h(n)}},N=y;export{N as NODE_SCHEDULER_DISPATCH_PATH,O as createNodeDispatchRoute,L as createShardInvoke};
@@ -0,0 +1 @@
1
+ import{LunoraError as k}from"@lunora/errors";import{NODE_CAPABILITIES as N}from"@lunora/platform";import{createNodeDispatchRoute as b,createShardInvoke as g}from"./NODE_SCHEDULER_DISPATCH_PATH-CNBAtztq.mjs";import{createNodeGlobalStore as y}from"./createNodeGlobalStore-DiGxjmD7.mjs";import{createNodeShardKvStore as R}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as T}from"./createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as q}from"./createNodeR2Bucket-C_oO6X63.mjs";import{createNodeSchedulerHost as B}from"./createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as I}from"./createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as _}from"./createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeSocketHost as D}from"./createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as H}from"./createNodeWorkflowHost-B76TKiw_.mjs";import{createNodeWorkflowStore as P}from"./createNodeWorkflowStore-CL7RYP6H.mjs";const z=(e={})=>{const o=[],{database:r,dispose:u,drain:i,host:l}=I(e);o.push(u);try{const a=R(r),t=_(e);o.push(()=>{t.close()});const{directory:d}=t,m=b({adminToken:e.dispatch?.adminToken,defaultShardKey:"__root__",invoke:g(d,e.onFetch!==void 0),schedulerSecret:e.dispatch?.schedulerSecret}),{socket:f}=D(r),{dispose:n,scheduler:p}=B(r,e);o.push(n);const S=e.queues===void 0?void 0:T(r,{onBatch:e.onQueueBatch??(()=>{throw new k("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}),v=e.objectStorageDirectory===void 0?void 0:q({directory:e.objectStorageDirectory}),c=e.globalTablesPath===void 0?void 0:y({path:e.globalTablesPath});c!==void 0&&o.push(()=>{c.dispose()});const w=e.workflows===void 0?void 0:H({store:P(r),workflows:e.workflows}),h=()=>{for(const s of o.toReversed())s()};return{capabilities:N,close:h,directory:d,dispatch:s=>m(s),drain:i,globalTables:c,kv:a,objectStorage:v,queues:S,scheduler:p,shard:l,sockets:f,workflows:w,[Symbol.dispose]:h}}catch(a){for(const t of o.toReversed())try{t()}catch{}throw a}};export{z as createNodePlatform};
@@ -0,0 +1 @@
1
+ import{randomUUID as W,createHash as X}from"node:crypto";import{mkdir as L,open as j,unlink as Y,rename as _,rmdir as H,readdir as J}from"node:fs/promises";import{join as y,dirname as T,sep as Z}from"node:path";import{LunoraError as c}from"@lunora/errors";import{h as q}from"./hmac-url-BrwGfbCr.mjs";import{t as P}from"./to-array-buffer-DVyXU4Ep.mjs";const b=".lunora-tmp",k="LNR1",p=8,B=1024,$=255,V=2048,Q=/[%A-Z:]/g,F=e=>{const t=e.replaceAll(Q,n=>`%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),r=t.at(-1);return r==="."||r===" "?`${t.slice(0,-1)}%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:t},tt=e=>{try{return decodeURIComponent(e)}catch{return}},A=e=>e.split("/").map(t=>F(t)).join("/"),et=e=>{const t=[];for(const r of e.split("/")){const n=tt(r);if(n===void 0)return;t.push(n)}return t.join("/")},rt=new TextEncoder,nt=e=>rt.encode(e).length,I=e=>{if(typeof e!="string"||e.length===0)throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key must be a non-empty string");if(nt(e)>B)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(B)}-byte limit`);if(e.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(q(e))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a control character (including CR/LF)");if(e.includes("\\"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a backslash (a path separator on Windows)");if(e.startsWith("/"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key must not start with `/`");for(const t of e.split("/")){if(Buffer.byteLength(F(t),"utf8")>$)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${t}" exceeds ${String($)} 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===b)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${b}")`)}},ot=e=>{if(e===void 0)return;let t=0;for(const[r,n]of Object.entries(e))t+=Buffer.byteLength(r,"utf8")+Buffer.byteLength(n,"utf8");if(t>V)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 customMetadata is ${String(t)} bytes, over the ${String(V)}-byte ceiling R2 applies to the summed keys and values — your metadata headers exceed the maximum allowed metadata size`)},at=/^[\da-f]{64}$/,st=e=>{if(typeof e=="string"){const t=e.toLowerCase();if(!at.test(t))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return t}if(e.byteLength!==32)throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return Buffer.from(e).toString("hex")},D=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",K=(e,t)=>{const r=t instanceof Error&&"code"in t?t.code:void 0;return r==="EEXIST"||r==="EISDIR"||r==="ENOTDIR"?new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key "${e}" collides with an existing object at one of its path prefixes — this host stores one file per key, so a key and a prefix of it cannot both hold objects`):t},it=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),r=Buffer.alloc(p);return r.writeUInt32BE(t.byteLength,0),r.write(k,4,"ascii"),Buffer.concat([t,r])},ct=e=>{if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.sha256Hex=="string"&&typeof t.size=="number"&&typeof t.uploaded=="string"},ft=async e=>{const t=await e.stat();if(!t.isFile()||t.size<p)return;const r=Buffer.alloc(p);if(await e.read(r,0,p,t.size-p),r.toString("ascii",4,p)!==k)return;const n=r.readUInt32BE(0),o=t.size-p-n;if(n===0||o<0)return;const a=Buffer.alloc(n),{bytesRead:i}=await e.read(a,0,n,o);if(i!==n)return;let s;try{s=JSON.parse(a.toString("utf8"))}catch{return}return ct(s)?{bodySize:o,meta:s}:void 0},z=async e=>{let t;try{t=await j(e,"r")}catch(r){if(D(r))return;throw r}try{const r=await ft(t);if(r===void 0){await t.close();return}return{...r,handle:t}}catch(r){throw await t.close(),r}},C=new FinalizationRegistry(e=>{e.close().catch(()=>{})}),x=async e=>{const t=await z(e);if(t!==void 0)return await t.handle.close(),{bodySize:t.bodySize,meta:t.meta}},E=(e,t)=>{const{sha256Hex:r}=t,n=Buffer.from(r,"hex");return{checksums:{sha256:P(n)},customMetadata:t.customMetadata,etag:r,httpEtag:`"${r}"`,httpMetadata:t.httpMetadata,key:e,sha256:r,sha256Base64:n.toString("base64"),size:t.size,uploaded:new Date(t.uploaded)}},S=(e,t,r)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),r),lt=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-S(t.suffix,e,e)};const r=S(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(r+S(t.length,0,e),e),start:r}},dt=(e,t,r,n)=>{const o=e.createReadStream({autoClose:!1,end:r-1,start:t})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await n()},pull:async a=>{let i;try{i=await o.next()}catch(s){throw await n(),s}i.done===!0?(a.close(),await n()):a.enqueue(i.value)}})},ut=async(e,t,r)=>{const n=r-t;if(n<=0)return Buffer.alloc(0);const o=Buffer.alloc(n),{bytesRead:a}=await e.read(o,0,n,t);return o.subarray(0,a)},G=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*G(e.stream());return}if(e instanceof ReadableStream){for await(const t of e)if(t instanceof Uint8Array)yield t;else if(t instanceof ArrayBuffer)yield new Uint8Array(t);else throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put stream must yield byte chunks");return}throw new c("VALIDATION_ERROR","@lunora/platform-node: unsupported R2 put body")}},ht=async e=>{const t=[],r=async(n,o)=>{let a;try{a=await J(n,{withFileTypes:!0})}catch(s){if(D(s))return;throw s}const i=[];for(const s of a){if(s.name===b)continue;const d=o===""?s.name:`${o}/${s.name}`;if(s.isDirectory())i.push(r(y(n,s.name),d));else{const h=et(d);h!==void 0&&t.push(h)}}await Promise.all(i)};return await r(e,""),t.sort((n,o)=>n<o?-1:+(n>o)),t},wt=(e,t)=>{let r=0,n=e.length;for(;r<n;){const o=r+Math.floor((n-r)/2),a=e[o];a===void 0||a<=t?r=o+1:n=o}return r},mt=new Set(["EISDIR","ENOENT","ENOTDIR","EPERM"]),O=async e=>{try{await Y(e)}catch(t){const r=t instanceof Error&&"code"in t?t.code:void 0;if(r===void 0||!mt.has(r))throw t}},Rt=async(e,t)=>{let r=e;for(;r!==t&&r.startsWith(`${t}${Z}`);){try{await H(r)}catch(n){if(!D(n))return}r=T(r)}},U=async(e,t,r)=>{try{await _(e,t)}catch{try{await H(t)}catch{}try{await L(T(t),{recursive:!0}),await _(e,t)}catch(n){throw await O(e),K(r,n)}}},N=new Map,yt=async(e,t)=>{const n=(N.get(e)??Promise.resolve()).then(t,t),o=n.catch(()=>{});N.set(e,o);try{return await n}finally{N.get(e)===o&&N.delete(e)}},pt=async(e,t)=>{const n=(await x(e))?.meta.sha256Hex,o=a=>n!==void 0&&(a==="*"||a===n);return t.etagMatches!==void 0&&!o(t.etagMatches)?!1:t.etagDoesNotMatch===void 0||!o(t.etagDoesNotMatch)},St=e=>{const{directory:t}=e;return{delete:async r=>{const n=typeof r=="string"?[r]:r;for(const o of n)I(o);for(const o of n){const a=y(t,A(o));await O(a),await Rt(a,t)}},get:async(r,n)=>{I(r);const o=await z(y(t,A(r)));if(o===void 0)return null;const{bodySize:a,handle:i,meta:s}=o,{end:d,start:h}=lt(a,n?.range),u={};let w=!1;const l=async()=>{w||(w=!0,C.unregister(u),await i.close())},m=()=>{if(w)throw new c("BAD_REQUEST",`@lunora/platform-node: R2 object "${r}" body has already been consumed`)},R=async()=>{m();try{return await ut(i,h,d)}finally{await l()}},v={...E(r,s),arrayBuffer:async()=>P(await R()),get body(){return m(),d<=h?(l().catch(()=>{}),new Blob([]).stream()):dt(i,h,d,l)},text:async()=>(await R()).toString("utf8")};return C.register(v,{close:l},u),v},head:async r=>{I(r);const n=await x(y(t,A(r)));return n===void 0?null:E(r,n.meta)},list:async(r={})=>{const n=Math.max(1,S(r.limit,1e3,1e3));if(r.prefix?.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await ht(t),a=r.prefix??"",i=o.filter(f=>f.startsWith(a)),{delimiter:s}=r,d=s===void 0?i:i.filter(f=>!f.slice(a.length).includes(s)),h=s===void 0?void 0:[...new Set(i.map(f=>{const M=f.slice(a.length).indexOf(s);return M===-1?void 0:f.slice(0,a.length+M+s.length)}).filter(f=>f!==void 0))].toSorted((f,g)=>f<g?-1:+(f>g));let u=r.cursor;r.startAfter!==void 0&&(u===void 0||r.startAfter>u)&&(u=r.startAfter);const w=u===void 0?0:wt(d,u),l=d.slice(w,w+n),m=w+n<d.length,v=(await Promise.all(l.map(async f=>{const g=await x(y(t,A(f)));return g===void 0?void 0:E(f,g.meta)}))).filter(f=>f!==void 0);return{cursor:m?l.at(-1):void 0,delimitedPrefixes:h,objects:v,truncated:m}},put:(async(r,n,o)=>{I(r),ot(o?.customMetadata);const a=o?.sha256===void 0?void 0:st(o.sha256),i=y(t,A(r)),s=y(t,b,W());try{await L(T(i),{recursive:!0})}catch(l){throw K(r,l)}await L(T(s),{recursive:!0});const d=X("sha256");let h=0,u;try{const l=await j(s,"w");try{for await(const R of G(n))d.update(R),h+=R.byteLength,await l.write(R);const m=d.digest("hex");if(a!==void 0&&a!==m)throw new c("VALIDATION_ERROR",`@lunora/platform-node: the SHA-256 checksum you specified for R2 key "${r}" did not match what we received. You provided a SHA-256 checksum with value: ${a}. Actual SHA-256 was: ${m}`);u={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:m,size:h,uploaded:new Date().toISOString()},await l.write(it(u))}finally{await l.close()}}catch(l){throw await O(s),l}const w=o?.onlyIf;return w!==void 0?yt(r,async()=>await pt(i,w)?(await U(s,i,r),E(r,u)):(await O(s),null)):(await U(s,i,r),E(r,u))})}};export{St as createNodeR2Bucket};
@@ -0,0 +1 @@
1
+ const c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",l=t=>{let e="",n=0;const r=t.length-2;for(;n<r;n+=3){const o=t[n]<<16|t[n+1]<<8|t[n+2];e+=c.charAt(o>>18&63)+c.charAt(o>>12&63)+c.charAt(o>>6&63)+c.charAt(o&63)}const s=t.length-n;if(s===1){const o=t[n]<<16;e+=c.charAt(o>>18&63)+c.charAt(o>>12&63)}else if(s===2){const o=t[n]<<16|t[n+1]<<8;e+=c.charAt(o>>18&63)+c.charAt(o>>12&63)+c.charAt(o>>6&63)}return e},h=(t,e)=>{if(t.size<e)return;const n=t.keys().next().value;n!==void 0&&t.delete(n)},A=(t,e,n,r)=>{const s=t.get(e);if(s!==void 0)return s;h(t,r);const o=n().catch(i=>{throw t.get(e)===o&&t.delete(e),i});return t.set(e,o),o},a=new TextEncoder,d=Array.from({length:32},(t,e)=>e),C=new RegExp(`[${d.map(t=>String.fromCodePoint(t)).join("")}]`,"u"),g=64,f=new Map,u=async t=>A(f,t,async()=>crypto.subtle.importKey("raw",a.encode(t),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),g),E=t=>C.test(t),w=async(t,e)=>{const n=await u(t),r=await crypto.subtle.sign("HMAC",n,a.encode(e));return l(new Uint8Array(r))};export{E as h,w as s};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/platform-node",
3
- "version": "1.0.0-alpha.129",
3
+ "version": "1.0.0-alpha.130",
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,11 +46,12 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/d1": "1.0.0-alpha.169",
49
+ "@lunora/d1": "1.0.0-alpha.170",
50
50
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
51
- "@lunora/platform": "1.0.0-alpha.58",
52
- "@lunora/queue": "1.0.0-alpha.97",
53
- "@lunora/sql-store": "1.0.0-alpha.170",
51
+ "@lunora/platform": "1.0.0-alpha.59",
52
+ "@lunora/queue": "1.0.0-alpha.98",
53
+ "@lunora/runtime": "1.0.0-alpha.190",
54
+ "@lunora/sql-store": "1.0.0-alpha.171",
54
55
  "@lunora/workflow": "1.0.0-alpha.74",
55
56
  "@visulima/workflow": "3.0.0",
56
57
  "better-sqlite3": "^13.0.3",
@@ -1 +0,0 @@
1
- import{LunoraError as S}from"@lunora/errors";import{NODE_CAPABILITIES as b}from"@lunora/platform";import{createNodeGlobalStore as N}from"./createNodeGlobalStore-DiGxjmD7.mjs";import{createNodeShardKvStore as k}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as g}from"./createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as y}from"./createNodeR2Bucket-ChHs23n-.mjs";import{createNodeSchedulerHost as R}from"./createNodeSchedulerHost-BEAX6I4m.mjs";import{createNodeShardHost as q}from"./createNodeShardHost-Da19DFX0.mjs";import{createNodeShardRegistry as B}from"./createNodeShardRegistry-D-KxwdUc.mjs";import{createNodeSocketHost as H}from"./createNodeSocketHost-DQs85G7W.mjs";import{createNodeWorkflowHost as I}from"./createNodeWorkflowHost-B76TKiw_.mjs";import{createNodeWorkflowStore as P}from"./createNodeWorkflowStore-CL7RYP6H.mjs";const G=(o={})=>{const e=[],{database:r,dispose:d,drain:u,host:i}=q(o);e.push(d);try{const s=k(r),t=B(o);e.push(()=>{t.close()});const{directory:l}=t,{socket:h}=H(r),{dispose:m,scheduler:f}=R(r,o);e.push(m);const n=o.queues===void 0?void 0:g(r,{onBatch:o.onQueueBatch??(()=>{throw new S("VALIDATION_ERROR","@lunora/platform-node: createNodePlatform was given `queues` without `onQueueBatch`, so a delivered batch has nowhere to go — pass dispatchQueueBatch from @lunora/queue")}),queues:o.queues}),p=o.objectStorageDirectory===void 0?void 0:y({directory:o.objectStorageDirectory}),a=o.globalTablesPath===void 0?void 0:N({path:o.globalTablesPath});a!==void 0&&e.push(()=>{a.dispose()});const w=o.workflows===void 0?void 0:I({store:P(r),workflows:o.workflows}),c=()=>{for(const v of e.toReversed())v()};return{capabilities:b,close:c,directory:l,drain:u,globalTables:a,kv:s,objectStorage:p,queues:n,scheduler:f,shard:i,sockets:h,workflows:w,[Symbol.dispose]:c}}catch(s){for(const t of e.toReversed())try{t()}catch{}throw s}};export{G as createNodePlatform};
@@ -1 +0,0 @@
1
- import{randomUUID as W,createHash as X}from"node:crypto";import{mkdir as L,open as U,unlink as Y,rename as M,rmdir as H,readdir as J}from"node:fs/promises";import{join as y,dirname as T,sep as Z}from"node:path";import{LunoraError as c}from"@lunora/errors";import{t as P}from"./to-array-buffer-DVyXU4Ep.mjs";new TextEncoder;const q=Array.from({length:32},(e,t)=>t),Q=new RegExp(`[${q.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),tt=e=>Q.test(e),b=".lunora-tmp",k="LNR1",p=8,B=1024,C=255,$=2048,et=/[%A-Z:]/g,F=e=>{const t=e.replaceAll(et,n=>`%${n.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`),r=t.at(-1);return r==="."||r===" "?`${t.slice(0,-1)}%${r.codePointAt(0)?.toString(16).padStart(2,"0").toUpperCase()??""}`:t},rt=e=>{try{return decodeURIComponent(e)}catch{return}},A=e=>e.split("/").map(t=>F(t)).join("/"),nt=e=>{const t=[];for(const r of e.split("/")){const n=rt(r);if(n===void 0)return;t.push(n)}return t.join("/")},ot=new TextEncoder,at=e=>ot.encode(e).length,I=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(at(e)>B)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key exceeds ${String(B)}-byte limit`);if(e.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 key contains a NUL byte");if(tt(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(F(t),"utf8")>C)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key segment "${t}" exceeds ${String(C)} 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===b)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key is reserved (no segment may be "${b}")`)}},st=e=>{if(e===void 0)return;let t=0;for(const[r,n]of Object.entries(e))t+=Buffer.byteLength(r,"utf8")+Buffer.byteLength(n,"utf8");if(t>$)throw new c("VALIDATION_ERROR",`@lunora/platform-node: R2 customMetadata is ${String(t)} bytes, over the ${String($)}-byte ceiling R2 applies to the summed keys and values — your metadata headers exceed the maximum allowed metadata size`)},it=/^[\da-f]{64}$/,ct=e=>{if(typeof e=="string"){const t=e.toLowerCase();if(!it.test(t))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return t}if(e.byteLength!==32)throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 put sha256 must be 64 hex characters or a 32-byte buffer");return Buffer.from(e).toString("hex")},x=e=>e instanceof Error&&"code"in e&&e.code==="ENOENT",K=(e,t)=>{const r=t instanceof Error&&"code"in t?t.code:void 0;return r==="EEXIST"||r==="EISDIR"||r==="ENOTDIR"?new c("VALIDATION_ERROR",`@lunora/platform-node: R2 key "${e}" collides with an existing object at one of its path prefixes — this host stores one file per key, so a key and a prefix of it cannot both hold objects`):t},ft=e=>{const t=Buffer.from(JSON.stringify(e),"utf8"),r=Buffer.alloc(p);return r.writeUInt32BE(t.byteLength,0),r.write(k,4,"ascii"),Buffer.concat([t,r])},lt=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"},dt=async e=>{const t=await e.stat();if(!t.isFile()||t.size<p)return;const r=Buffer.alloc(p);if(await e.read(r,0,p,t.size-p),r.toString("ascii",4,p)!==k)return;const n=r.readUInt32BE(0),o=t.size-p-n;if(n===0||o<0)return;const a=Buffer.alloc(n),{bytesRead:i}=await e.read(a,0,n,o);if(i!==n)return;let s;try{s=JSON.parse(a.toString("utf8"))}catch{return}return lt(s)?{bodySize:o,meta:s}:void 0},z=async e=>{let t;try{t=await U(e,"r")}catch(r){if(x(r))return;throw r}try{const r=await dt(t);if(r===void 0){await t.close();return}return{...r,handle:t}}catch(r){throw await t.close(),r}},V=new FinalizationRegistry(e=>{e.close().catch(()=>{})}),_=async e=>{const t=await z(e);if(t!==void 0)return await t.handle.close(),{bodySize:t.bodySize,meta:t.meta}},E=(e,t)=>{const{sha256Hex:r}=t,n=Buffer.from(r,"hex");return{checksums:{sha256:P(n)},customMetadata:t.customMetadata,etag:r,httpEtag:`"${r}"`,httpMetadata:t.httpMetadata,key:e,sha256:r,sha256Base64:n.toString("base64"),size:t.size,uploaded:new Date(t.uploaded)}},N=(e,t,r)=>e===void 0||!Number.isFinite(e)?t:Math.min(Math.max(0,Math.floor(e)),r),ut=(e,t)=>{if(t===void 0)return{end:e,start:0};if("suffix"in t)return{end:e,start:e-N(t.suffix,e,e)};const r=N(t.offset,0,e);return{end:t.length===void 0||!Number.isFinite(t.length)?e:Math.min(r+N(t.length,0,e),e),start:r}},ht=(e,t,r,n)=>{const o=e.createReadStream({autoClose:!1,end:r-1,start:t})[Symbol.asyncIterator]();return new ReadableStream({cancel:async()=>{await o.return?.(),await n()},pull:async a=>{let i;try{i=await o.next()}catch(s){throw await n(),s}i.done===!0?(a.close(),await n()):a.enqueue(i.value)}})},wt=async(e,t,r)=>{const n=r-t;if(n<=0)return Buffer.alloc(0);const o=Buffer.alloc(n),{bytesRead:a}=await e.read(o,0,n,t);return o.subarray(0,a)},G=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*G(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")}},mt=async e=>{const t=[],r=async(n,o)=>{let a;try{a=await J(n,{withFileTypes:!0})}catch(s){if(x(s))return;throw s}const i=[];for(const s of a){if(s.name===b)continue;const d=o===""?s.name:`${o}/${s.name}`;if(s.isDirectory())i.push(r(y(n,s.name),d));else{const h=nt(d);h!==void 0&&t.push(h)}}await Promise.all(i)};return await r(e,""),t.sort((n,o)=>n<o?-1:+(n>o)),t},Rt=(e,t)=>{let r=0,n=e.length;for(;r<n;){const o=r+Math.floor((n-r)/2),a=e[o];a===void 0||a<=t?r=o+1:n=o}return r},yt=new Set(["EISDIR","ENOENT","ENOTDIR","EPERM"]),S=async e=>{try{await Y(e)}catch(t){const r=t instanceof Error&&"code"in t?t.code:void 0;if(r===void 0||!yt.has(r))throw t}},pt=async(e,t)=>{let r=e;for(;r!==t&&r.startsWith(`${t}${Z}`);){try{await H(r)}catch(n){if(!x(n))return}r=T(r)}},j=async(e,t,r)=>{try{await M(e,t)}catch{try{await H(t)}catch{}try{await L(T(t),{recursive:!0}),await M(e,t)}catch(n){throw await S(e),K(r,n)}}},O=new Map,gt=async(e,t)=>{const n=(O.get(e)??Promise.resolve()).then(t,t),o=n.catch(()=>{});O.set(e,o);try{return await n}finally{O.get(e)===o&&O.delete(e)}},At=async(e,t)=>{const n=(await _(e))?.meta.sha256Hex,o=a=>n!==void 0&&(a==="*"||a===n);return t.etagMatches!==void 0&&!o(t.etagMatches)?!1:t.etagDoesNotMatch===void 0||!o(t.etagDoesNotMatch)},St=e=>{const{directory:t}=e;return{delete:async r=>{const n=typeof r=="string"?[r]:r;for(const o of n)I(o);for(const o of n){const a=y(t,A(o));await S(a),await pt(a,t)}},get:async(r,n)=>{I(r);const o=await z(y(t,A(r)));if(o===void 0)return null;const{bodySize:a,handle:i,meta:s}=o,{end:d,start:h}=ut(a,n?.range),u={};let w=!1;const l=async()=>{w||(w=!0,V.unregister(u),await i.close())},m=()=>{if(w)throw new c("BAD_REQUEST",`@lunora/platform-node: R2 object "${r}" body has already been consumed`)},R=async()=>{m();try{return await wt(i,h,d)}finally{await l()}},v={...E(r,s),arrayBuffer:async()=>P(await R()),get body(){return m(),d<=h?(l().catch(()=>{}),new Blob([]).stream()):ht(i,h,d,l)},text:async()=>(await R()).toString("utf8")};return V.register(v,{close:l},u),v},head:async r=>{I(r);const n=await _(y(t,A(r)));return n===void 0?null:E(r,n.meta)},list:async(r={})=>{const n=Math.max(1,N(r.limit,1e3,1e3));if(r.prefix?.includes("\0"))throw new c("VALIDATION_ERROR","@lunora/platform-node: R2 list prefix contains a NUL byte");const o=await mt(t),a=r.prefix??"",i=o.filter(f=>f.startsWith(a)),{delimiter:s}=r,d=s===void 0?i:i.filter(f=>!f.slice(a.length).includes(s)),h=s===void 0?void 0:[...new Set(i.map(f=>{const D=f.slice(a.length).indexOf(s);return D===-1?void 0:f.slice(0,a.length+D+s.length)}).filter(f=>f!==void 0))].toSorted((f,g)=>f<g?-1:+(f>g));let u=r.cursor;r.startAfter!==void 0&&(u===void 0||r.startAfter>u)&&(u=r.startAfter);const w=u===void 0?0:Rt(d,u),l=d.slice(w,w+n),m=w+n<d.length,v=(await Promise.all(l.map(async f=>{const g=await _(y(t,A(f)));return g===void 0?void 0:E(f,g.meta)}))).filter(f=>f!==void 0);return{cursor:m?l.at(-1):void 0,delimitedPrefixes:h,objects:v,truncated:m}},put:(async(r,n,o)=>{I(r),st(o?.customMetadata);const a=o?.sha256===void 0?void 0:ct(o.sha256),i=y(t,A(r)),s=y(t,b,W());try{await L(T(i),{recursive:!0})}catch(l){throw K(r,l)}await L(T(s),{recursive:!0});const d=X("sha256");let h=0,u;try{const l=await U(s,"w");try{for await(const R of G(n))d.update(R),h+=R.byteLength,await l.write(R);const m=d.digest("hex");if(a!==void 0&&a!==m)throw new c("VALIDATION_ERROR",`@lunora/platform-node: the SHA-256 checksum you specified for R2 key "${r}" did not match what we received. You provided a SHA-256 checksum with value: ${a}. Actual SHA-256 was: ${m}`);u={customMetadata:o?.customMetadata,httpMetadata:o?.httpMetadata,sha256Hex:m,size:h,uploaded:new Date().toISOString()},await l.write(ft(u))}finally{await l.close()}}catch(l){throw await S(s),l}const w=o?.onlyIf;return w!==void 0?gt(r,async()=>await At(i,w)?(await j(s,i,r),E(r,u)):(await S(s),null)):(await j(s,i,r),E(r,u))})}};export{St as createNodeR2Bucket};