@lunora/platform-node 1.0.0-alpha.157 → 1.0.0-alpha.159
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/conformance/index.mjs +1 -1
- package/dist/index.d.mts +66 -23
- package/dist/index.d.ts +66 -23
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{createNodeShardState-D6XMWbFs.mjs → bindShardSockets-9GvSocBW.mjs} +1 -1
- package/dist/packem_shared/createNodeHttpServer-D4YVdhJn.mjs +4 -0
- package/dist/packem_shared/{createNodePlatform-bL7EdY_5.mjs → createNodePlatform-C1z36ZqT.mjs} +1 -1
- package/dist/packem_shared/createNodeShardRegistry-D1gAjQBl.mjs +1 -0
- package/dist/packem_shared/createNodeSocketHost-Bpn7V-ru.mjs +2 -0
- package/dist/packem_shared/node-socket-pair-0baNnoFw.mjs +1 -0
- package/package.json +9 -8
- package/dist/packem_shared/createNodeHttpServer-BmIbUCl7.mjs +0 -1
- package/dist/packem_shared/createNodeShardRegistry-q-46Oj8M.mjs +0 -1
- package/dist/packem_shared/createNodeSocketHost-DQs85G7W.mjs +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createNodeShardKvStore as N}from"../packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeSchedulerHost as k}from"../packem_shared/createNodeSchedulerHost-BIJ4H38-.mjs";import{createNodeShardHost as D}from"../packem_shared/createNodeShardHost-B6mt1S-h.mjs";import{createNodeShardRegistry as H}from"../packem_shared/createNodeShardRegistry-
|
|
1
|
+
import{createNodeShardKvStore as N}from"../packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeSchedulerHost as k}from"../packem_shared/createNodeSchedulerHost-BIJ4H38-.mjs";import{createNodeShardHost as D}from"../packem_shared/createNodeShardHost-B6mt1S-h.mjs";import{createNodeShardRegistry as H}from"../packem_shared/createNodeShardRegistry-D1gAjQBl.mjs";import{createNodeSocketHost as T}from"../packem_shared/createNodeSocketHost-Bpn7V-ru.mjs";const P=()=>{const{database:o,dispose:m,host:p}=D(),h=N(o),c=H(),{directory:l}=c,{readFrames:w,restoreSocket:u,simulateRecycle:S,socket:y}=T(o),d=new Set,s=new Map,{dispose:f,scheduler:i,simulateDeadLetter:g}=k(o,{onDispatch:(e,a,t)=>{d.add(t.id),s.set(e,(s.get(e)??0)+1)}}),n=()=>{f(),m(),c.close()};return{awaitAlarmFired:async e=>{await new Promise(a=>{setTimeout(a,Math.max(0,e-Date.now())+30)})},awaitJobDispatched:async e=>{const t=(await i.list?.())?.find(r=>r.id===e);return t!==void 0&&await new Promise(r=>{setTimeout(r,Math.max(0,t.scheduledFor-Date.now())+30)}),d.has(e)},cleanup:n,cronTicks:e=>s.get(e)??0,directory:l,disposeTerminally:n,kv:h,readFrames:w,restoreSocket:u,scheduler:i,shard:p,simulateDeadLetter:g,simulateRecycle:S,socket:y}};export{P as createNodeConformanceHost};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SchemaLike } from '@lunora/shard-engine';
|
|
2
2
|
import { SqlCtxDbOptions, SqlCtxExec, createSqlCtxDb } from '@lunora/sql-store';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
|
-
import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike
|
|
4
|
+
import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHandle, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike } from '@lunora/platform';
|
|
5
5
|
import { WorkflowBindingLike } from '@lunora/workflow';
|
|
6
6
|
import { WorkflowRuntime, WorkflowStore } from '@visulima/workflow';
|
|
7
7
|
import { ShardNamespaceLike } from '@lunora/runtime';
|
|
@@ -350,8 +350,55 @@ declare const createNodeShardHost: (options?: NodeShardHostOptions) => {
|
|
|
350
350
|
drain: () => Promise<void>;
|
|
351
351
|
host: ShardHost;
|
|
352
352
|
};
|
|
353
|
+
/** The end a shard accepts. Its `send` and `close` are what the socket host calls through its handle. */
|
|
354
|
+
interface NodeServerEnd {
|
|
355
|
+
close: (code?: number, reason?: string) => void;
|
|
356
|
+
send: (data: string | ArrayBuffer) => void;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The socket-host half of a Node platform instance, plus the test-only hooks
|
|
360
|
+
* `@lunora/platform/conformance`'s `ConformanceHost` needs to drive a recycle
|
|
361
|
+
* from inside a test.
|
|
362
|
+
*/
|
|
363
|
+
interface NodeSocketHost {
|
|
364
|
+
/**
|
|
365
|
+
* Wait for every close handler still running, including ones they start. `undefined` when none are,
|
|
366
|
+
* so a caller can dispose synchronously.
|
|
367
|
+
*/
|
|
368
|
+
drain: () => Promise<void> | undefined;
|
|
369
|
+
/** Deliver a connection's close to `handler`, which calls `webSocketClose`. One handler per host. */
|
|
370
|
+
onClose: (handler: (socket: NodeServerEnd, code: number, reason: string, wasClean: boolean) => void | Promise<void>) => void;
|
|
371
|
+
/**
|
|
372
|
+
* Deliver a connection's inbound frames to `handler`. The handler is the
|
|
373
|
+
* shard that accepted the connection, so it calls `webSocketMessage`. One
|
|
374
|
+
* handler per host: a later call replaces the earlier one.
|
|
375
|
+
*/
|
|
376
|
+
onMessage: (handler: (socket: NodeServerEnd, data: string | ArrayBuffer) => void) => void;
|
|
377
|
+
/** Read back the frames sent to a socket, oldest first, text frames only. */
|
|
378
|
+
readFrames: (handle: SocketHandle) => string[];
|
|
379
|
+
/**
|
|
380
|
+
* Re-create a runtime socket from durable state.
|
|
381
|
+
*
|
|
382
|
+
* `attachment` is a fallback only: this host restores what it persisted,
|
|
383
|
+
* because that is what a real wake looks like. The argument covers an id
|
|
384
|
+
* this host never durably tracked (a synthetic id a test constructs).
|
|
385
|
+
*/
|
|
386
|
+
restoreSocket: (id: string, attachment: unknown) => SocketHandle;
|
|
387
|
+
/** Drop the runtime socket map while keeping durable attachments/tags. */
|
|
388
|
+
simulateRecycle: () => void;
|
|
389
|
+
/** The `SocketHost` contract implementation. */
|
|
390
|
+
socket: SocketHost;
|
|
391
|
+
}
|
|
392
|
+
/** Build the socket registry, persisting attachments and tags to `database`. */
|
|
393
|
+
declare const createNodeSocketHost: (database: Database.Database) => NodeSocketHost;
|
|
353
394
|
/** The contracts scoped to one shard key. */
|
|
354
395
|
interface NodeShard {
|
|
396
|
+
/**
|
|
397
|
+
* Inbound socket events for this shard's registry. Bind the shard that owns
|
|
398
|
+
* the sockets here, so a frame or close reaches its `webSocketMessage` and
|
|
399
|
+
* `webSocketClose`. See `bindShardSockets`.
|
|
400
|
+
*/
|
|
401
|
+
inbound: Pick<NodeSocketHost, "onClose" | "onMessage">;
|
|
355
402
|
/** Durable key-value storage on this shard's database. */
|
|
356
403
|
kv: ShardKvStore;
|
|
357
404
|
/** Single-writer execution, local SQL, transactions, durable alarms. */
|
|
@@ -659,6 +706,11 @@ interface NodeShardState {
|
|
|
659
706
|
id: {
|
|
660
707
|
name?: string;
|
|
661
708
|
};
|
|
709
|
+
/** Open a connection pair. Read by `@lunora/platform-cloudflare`'s socket host, which has no `WebSocketPair` here. */
|
|
710
|
+
openSocketPair: () => {
|
|
711
|
+
client: unknown;
|
|
712
|
+
server: unknown;
|
|
713
|
+
};
|
|
662
714
|
storage: {
|
|
663
715
|
delete: (key: string) => Promise<boolean>;
|
|
664
716
|
deleteAlarm: () => Promise<void>;
|
|
@@ -675,6 +727,8 @@ interface NodeShardState {
|
|
|
675
727
|
};
|
|
676
728
|
transaction: <T>(closure: () => Promise<T>) => Promise<T>;
|
|
677
729
|
};
|
|
730
|
+
/** Build the upgrade response for a pair's client end. A Node `Response` cannot carry status 101, so this is a marker. */
|
|
731
|
+
upgradeResponse: (client: unknown) => Response;
|
|
678
732
|
waitUntil: (promise: Promise<unknown>) => void;
|
|
679
733
|
}
|
|
680
734
|
/**
|
|
@@ -689,29 +743,18 @@ interface NodeShardState {
|
|
|
689
743
|
* report a point-in-time restore that never happened.
|
|
690
744
|
*/
|
|
691
745
|
declare const createNodeShardState: (shard: NodeShard) => NodeShardState;
|
|
746
|
+
/** The two socket hooks a `ShardDO` runs on the Cloudflare runtime's behalf. */
|
|
747
|
+
interface ShardSocketHooks {
|
|
748
|
+
webSocketClose(ws: unknown, code: number, reason: string, wasClean: boolean): unknown;
|
|
749
|
+
webSocketMessage(ws: unknown, message: string | ArrayBuffer): unknown;
|
|
750
|
+
}
|
|
692
751
|
/**
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
752
|
+
* Deliver a shard's inbound socket events. The Cloudflare runtime calls
|
|
753
|
+
* `webSocketMessage` and `webSocketClose` on the Durable Object for a hibernated
|
|
754
|
+
* socket. On Node nothing does, so the embedder calls this once per shard
|
|
755
|
+
* instance, at the same point it builds the shard.
|
|
696
756
|
*/
|
|
697
|
-
|
|
698
|
-
/** Read back the frames sent to a socket, oldest first, text frames only. */
|
|
699
|
-
readFrames: (handle: SocketHandle) => string[];
|
|
700
|
-
/**
|
|
701
|
-
* Re-create a runtime socket from durable state.
|
|
702
|
-
*
|
|
703
|
-
* `attachment` is a fallback only: this host restores what it persisted,
|
|
704
|
-
* because that is what a real wake looks like. The argument covers an id
|
|
705
|
-
* this host never durably tracked (a synthetic id a test constructs).
|
|
706
|
-
*/
|
|
707
|
-
restoreSocket: (id: string, attachment: unknown) => SocketHandle;
|
|
708
|
-
/** Drop the runtime socket map while keeping durable attachments/tags. */
|
|
709
|
-
simulateRecycle: () => void;
|
|
710
|
-
/** The `SocketHost` contract implementation. */
|
|
711
|
-
socket: SocketHost;
|
|
712
|
-
}
|
|
713
|
-
/** Build the socket registry, persisting attachments and tags to `database`. */
|
|
714
|
-
declare const createNodeSocketHost: (database: Database.Database) => NodeSocketHost;
|
|
757
|
+
declare const bindShardSockets: (shard: ShardSocketHooks, inbound: Pick<NodeShard["inbound"], "onClose" | "onMessage">) => void;
|
|
715
758
|
/**
|
|
716
759
|
* Build a durable {@link WorkflowStore} over a `better-sqlite3` connection.
|
|
717
760
|
* Pass the result as `createNodeWorkflowHost({ store })`.
|
|
@@ -809,4 +852,4 @@ type NodeDispatchInvocation,
|
|
|
809
852
|
* server, which is why both statements above hold at once. See
|
|
810
853
|
* `plans/234-node-host-findings.md`.
|
|
811
854
|
*/
|
|
812
|
-
type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodeHttpServer, type NodeHttpServerOptions, 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, createNodeHttpServer, createNodePlatform, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardNamespace, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
|
|
855
|
+
type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodeHttpServer, type NodeHttpServerOptions, 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, type ShardSocketHooks, bindShardSockets, createNodeDispatchRoute, createNodeGlobalStore, createNodeHttpServer, createNodePlatform, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardNamespace, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SchemaLike } from '@lunora/shard-engine';
|
|
2
2
|
import { SqlCtxDbOptions, SqlCtxExec, createSqlCtxDb } from '@lunora/sql-store';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
|
-
import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike
|
|
4
|
+
import { ShardKvStore, QueueBindingLike, MessageBatchLike, SchedulerHost, ShardHost, SocketHandle, SocketHost, DirectShardDirectory, PlatformCapabilities, ShardDirectory, R2BucketLike } from '@lunora/platform';
|
|
5
5
|
import { WorkflowBindingLike } from '@lunora/workflow';
|
|
6
6
|
import { WorkflowRuntime, WorkflowStore } from '@visulima/workflow';
|
|
7
7
|
import { ShardNamespaceLike } from '@lunora/runtime';
|
|
@@ -350,8 +350,55 @@ declare const createNodeShardHost: (options?: NodeShardHostOptions) => {
|
|
|
350
350
|
drain: () => Promise<void>;
|
|
351
351
|
host: ShardHost;
|
|
352
352
|
};
|
|
353
|
+
/** The end a shard accepts. Its `send` and `close` are what the socket host calls through its handle. */
|
|
354
|
+
interface NodeServerEnd {
|
|
355
|
+
close: (code?: number, reason?: string) => void;
|
|
356
|
+
send: (data: string | ArrayBuffer) => void;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The socket-host half of a Node platform instance, plus the test-only hooks
|
|
360
|
+
* `@lunora/platform/conformance`'s `ConformanceHost` needs to drive a recycle
|
|
361
|
+
* from inside a test.
|
|
362
|
+
*/
|
|
363
|
+
interface NodeSocketHost {
|
|
364
|
+
/**
|
|
365
|
+
* Wait for every close handler still running, including ones they start. `undefined` when none are,
|
|
366
|
+
* so a caller can dispose synchronously.
|
|
367
|
+
*/
|
|
368
|
+
drain: () => Promise<void> | undefined;
|
|
369
|
+
/** Deliver a connection's close to `handler`, which calls `webSocketClose`. One handler per host. */
|
|
370
|
+
onClose: (handler: (socket: NodeServerEnd, code: number, reason: string, wasClean: boolean) => void | Promise<void>) => void;
|
|
371
|
+
/**
|
|
372
|
+
* Deliver a connection's inbound frames to `handler`. The handler is the
|
|
373
|
+
* shard that accepted the connection, so it calls `webSocketMessage`. One
|
|
374
|
+
* handler per host: a later call replaces the earlier one.
|
|
375
|
+
*/
|
|
376
|
+
onMessage: (handler: (socket: NodeServerEnd, data: string | ArrayBuffer) => void) => void;
|
|
377
|
+
/** Read back the frames sent to a socket, oldest first, text frames only. */
|
|
378
|
+
readFrames: (handle: SocketHandle) => string[];
|
|
379
|
+
/**
|
|
380
|
+
* Re-create a runtime socket from durable state.
|
|
381
|
+
*
|
|
382
|
+
* `attachment` is a fallback only: this host restores what it persisted,
|
|
383
|
+
* because that is what a real wake looks like. The argument covers an id
|
|
384
|
+
* this host never durably tracked (a synthetic id a test constructs).
|
|
385
|
+
*/
|
|
386
|
+
restoreSocket: (id: string, attachment: unknown) => SocketHandle;
|
|
387
|
+
/** Drop the runtime socket map while keeping durable attachments/tags. */
|
|
388
|
+
simulateRecycle: () => void;
|
|
389
|
+
/** The `SocketHost` contract implementation. */
|
|
390
|
+
socket: SocketHost;
|
|
391
|
+
}
|
|
392
|
+
/** Build the socket registry, persisting attachments and tags to `database`. */
|
|
393
|
+
declare const createNodeSocketHost: (database: Database.Database) => NodeSocketHost;
|
|
353
394
|
/** The contracts scoped to one shard key. */
|
|
354
395
|
interface NodeShard {
|
|
396
|
+
/**
|
|
397
|
+
* Inbound socket events for this shard's registry. Bind the shard that owns
|
|
398
|
+
* the sockets here, so a frame or close reaches its `webSocketMessage` and
|
|
399
|
+
* `webSocketClose`. See `bindShardSockets`.
|
|
400
|
+
*/
|
|
401
|
+
inbound: Pick<NodeSocketHost, "onClose" | "onMessage">;
|
|
355
402
|
/** Durable key-value storage on this shard's database. */
|
|
356
403
|
kv: ShardKvStore;
|
|
357
404
|
/** Single-writer execution, local SQL, transactions, durable alarms. */
|
|
@@ -659,6 +706,11 @@ interface NodeShardState {
|
|
|
659
706
|
id: {
|
|
660
707
|
name?: string;
|
|
661
708
|
};
|
|
709
|
+
/** Open a connection pair. Read by `@lunora/platform-cloudflare`'s socket host, which has no `WebSocketPair` here. */
|
|
710
|
+
openSocketPair: () => {
|
|
711
|
+
client: unknown;
|
|
712
|
+
server: unknown;
|
|
713
|
+
};
|
|
662
714
|
storage: {
|
|
663
715
|
delete: (key: string) => Promise<boolean>;
|
|
664
716
|
deleteAlarm: () => Promise<void>;
|
|
@@ -675,6 +727,8 @@ interface NodeShardState {
|
|
|
675
727
|
};
|
|
676
728
|
transaction: <T>(closure: () => Promise<T>) => Promise<T>;
|
|
677
729
|
};
|
|
730
|
+
/** Build the upgrade response for a pair's client end. A Node `Response` cannot carry status 101, so this is a marker. */
|
|
731
|
+
upgradeResponse: (client: unknown) => Response;
|
|
678
732
|
waitUntil: (promise: Promise<unknown>) => void;
|
|
679
733
|
}
|
|
680
734
|
/**
|
|
@@ -689,29 +743,18 @@ interface NodeShardState {
|
|
|
689
743
|
* report a point-in-time restore that never happened.
|
|
690
744
|
*/
|
|
691
745
|
declare const createNodeShardState: (shard: NodeShard) => NodeShardState;
|
|
746
|
+
/** The two socket hooks a `ShardDO` runs on the Cloudflare runtime's behalf. */
|
|
747
|
+
interface ShardSocketHooks {
|
|
748
|
+
webSocketClose(ws: unknown, code: number, reason: string, wasClean: boolean): unknown;
|
|
749
|
+
webSocketMessage(ws: unknown, message: string | ArrayBuffer): unknown;
|
|
750
|
+
}
|
|
692
751
|
/**
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
752
|
+
* Deliver a shard's inbound socket events. The Cloudflare runtime calls
|
|
753
|
+
* `webSocketMessage` and `webSocketClose` on the Durable Object for a hibernated
|
|
754
|
+
* socket. On Node nothing does, so the embedder calls this once per shard
|
|
755
|
+
* instance, at the same point it builds the shard.
|
|
696
756
|
*/
|
|
697
|
-
|
|
698
|
-
/** Read back the frames sent to a socket, oldest first, text frames only. */
|
|
699
|
-
readFrames: (handle: SocketHandle) => string[];
|
|
700
|
-
/**
|
|
701
|
-
* Re-create a runtime socket from durable state.
|
|
702
|
-
*
|
|
703
|
-
* `attachment` is a fallback only: this host restores what it persisted,
|
|
704
|
-
* because that is what a real wake looks like. The argument covers an id
|
|
705
|
-
* this host never durably tracked (a synthetic id a test constructs).
|
|
706
|
-
*/
|
|
707
|
-
restoreSocket: (id: string, attachment: unknown) => SocketHandle;
|
|
708
|
-
/** Drop the runtime socket map while keeping durable attachments/tags. */
|
|
709
|
-
simulateRecycle: () => void;
|
|
710
|
-
/** The `SocketHost` contract implementation. */
|
|
711
|
-
socket: SocketHost;
|
|
712
|
-
}
|
|
713
|
-
/** Build the socket registry, persisting attachments and tags to `database`. */
|
|
714
|
-
declare const createNodeSocketHost: (database: Database.Database) => NodeSocketHost;
|
|
757
|
+
declare const bindShardSockets: (shard: ShardSocketHooks, inbound: Pick<NodeShard["inbound"], "onClose" | "onMessage">) => void;
|
|
715
758
|
/**
|
|
716
759
|
* Build a durable {@link WorkflowStore} over a `better-sqlite3` connection.
|
|
717
760
|
* Pass the result as `createNodeWorkflowHost({ store })`.
|
|
@@ -809,4 +852,4 @@ type NodeDispatchInvocation,
|
|
|
809
852
|
* server, which is why both statements above hold at once. See
|
|
810
853
|
* `plans/234-node-host-findings.md`.
|
|
811
854
|
*/
|
|
812
|
-
type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodeHttpServer, type NodeHttpServerOptions, 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, createNodeHttpServer, createNodePlatform, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardNamespace, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
|
|
855
|
+
type NodeDispatchRouteOptions, type NodeGlobalContextDatabaseOptions, type NodeGlobalStore, type NodeGlobalStoreOptions, type NodeHttpServer, type NodeHttpServerOptions, 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, type ShardSocketHooks, bindShardSockets, createNodeDispatchRoute, createNodeGlobalStore, createNodeHttpServer, createNodePlatform, createNodeProfileHandler, createNodeQueueHost, createNodeR2Bucket, createNodeSchedulerHost, createNodeShardHost, createNodeShardKvStore, createNodeShardNamespace, createNodeShardRegistry, createNodeShardState, createNodeSocketHost, createNodeSqlExec, createNodeWorkflowHost, createNodeWorkflowStore };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
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-DhHyIT8I.mjs";import{createNodeHttpServer as p}from"./packem_shared/createNodeHttpServer-
|
|
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-DhHyIT8I.mjs";import{createNodeHttpServer as p}from"./packem_shared/createNodeHttpServer-D4YVdhJn.mjs";import{createNodeShardKvStore as m}from"./packem_shared/createNodeShardKvStore-DVi1OsvH.mjs";import{createNodePlatform as S}from"./packem_shared/createNodePlatform-C1z36ZqT.mjs";import{createNodeProfileHandler as l}from"./packem_shared/createNodeProfileHandler-MX5xKPg0.mjs";import{createNodeQueueHost as h}from"./packem_shared/createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as u}from"./packem_shared/createNodeR2Bucket-6I2HobcB.mjs";import{createNodeSchedulerHost as D}from"./packem_shared/createNodeSchedulerHost-BIJ4H38-.mjs";import{createNodeShardHost as P}from"./packem_shared/createNodeShardHost-B6mt1S-h.mjs";import{createNodeShardNamespace as _}from"./packem_shared/createNodeShardNamespace-BgQjDv7V.mjs";import{createNodeShardRegistry as n}from"./packem_shared/createNodeShardRegistry-D1gAjQBl.mjs";import{bindShardSockets as w,createNodeShardState as A}from"./packem_shared/bindShardSockets-9GvSocBW.mjs";import{createNodeSocketHost as T}from"./packem_shared/createNodeSocketHost-Bpn7V-ru.mjs";import{createNodeWorkflowHost as g}from"./packem_shared/createNodeWorkflowHost-B76TKiw_.mjs";import{createNodeWorkflowStore as y}from"./packem_shared/createNodeWorkflowStore-CL7RYP6H.mjs";export{r as NODE_SCHEDULER_DISPATCH_PATH,w as bindShardSockets,t as createNodeDispatchRoute,d as createNodeGlobalStore,p as createNodeHttpServer,S as createNodePlatform,l as createNodeProfileHandler,h as createNodeQueueHost,u as createNodeR2Bucket,D as createNodeSchedulerHost,P as createNodeShardHost,m as createNodeShardKvStore,_ as createNodeShardNamespace,n as createNodeShardRegistry,A as createNodeShardState,T as createNodeSocketHost,c as createNodeSqlExec,g as createNodeWorkflowHost,y as createNodeWorkflowStore};
|
package/dist/packem_shared/{createNodeShardState-D6XMWbFs.mjs → bindShardSockets-9GvSocBW.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const l=t=>({acceptWebSocket:(e,a)=>{t.sockets.accept(e,void 0,a)},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)},openSocketPair:()=>t.sockets.newPair(),upgradeResponse:e=>t.sockets.upgradeResponse(e)}),i=(t,e)=>{const a=async(s,o)=>{await t.webSocketMessage(s,o)};e.onMessage((s,o)=>{a(s,o).catch(c=>{console.error("[lunora] webSocketMessage threw:",c)})}),e.onClose(async(s,o,c,r)=>{try{await t.webSocketClose(s,o,c,r)}catch(n){console.error("[lunora] webSocketClose threw:",n)}})};export{i as bindShardSockets,l as createNodeShardState};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{once as R}from"node:events";import{createServer as m}from"node:http";import{WebSocketServer as E}from"ws";import{t as L}from"./node-socket-pair-0baNnoFw.mjs";const y=e=>e.includes(":")&&!e.startsWith("[")?`[${e}]`:e,$=e=>{const t=new URL(e);if(t.protocol!=="http:"&&t.protocol!=="https:")throw new RangeError(`origin must be an http or https URL, got scheme ${t.protocol}`);return t.origin},U=/^(?:127\.\d+\.\d+\.\d+|::1|localhost)$/,A=1048576,T=5e3,H=(e,t)=>{const o=e[Symbol.asyncIterator]();return new ReadableStream({cancel(){if(t.headersSent){e.destroy();return}if(e.complete){e.resume();return}let s=0,n=!1;const a=()=>{if(!e.complete){if(n=!0,t.writableFinished){e.destroy();return}e.removeAllListeners("readable"),e.pause()}},d=setTimeout(a,T),c=()=>{clearTimeout(d)};t.once("finish",()=>{n&&e.destroy()}),e.once("end",c),e.once("close",c),e.removeAllListeners("readable"),e.on("readable",()=>{let l=e.read();for(;l!==null;){if(s+=l.byteLength,s>A){a();return}l=e.read()}})},async pull(s){const n=await o.next();n.done?s.close():s.enqueue(n.value)}})},x=(e,t)=>{const o=new URL(e??"/",t),s=new URL(t);return s.pathname=o.pathname,s.search=o.search,s},b=(e,t,o,s)=>{const n=new Headers;for(const[c,l]of Object.entries(e.headers))if(Array.isArray(l))for(const i of l)n.append(c,i);else l!==void 0&&n.set(c,l);const a=e.method??"GET",d={headers:n,method:a,signal:o};return a!=="GET"&&a!=="HEAD"&&s!==void 0&&(d.body=H(e,s),d.duplex="half"),new Request(x(e.url,t),d)},B=e=>e.destroyed,C=async(e,t)=>{if(t.on("error",()=>{t.destroy()}),B(t)){await e.body?.cancel().catch(()=>{});return}const o={};e.headers.forEach((d,c)=>{c!=="set-cookie"&&(o[c]=d)});const s=e.headers.getSetCookie();if(s.length>0&&(o["set-cookie"]=s),t.writeHead(e.status,o),e.body===null){t.end();return}const n=e.body.getReader();t.once("close",()=>{n.cancel().catch(()=>{})});let a=await n.read();for(;!a.done&&!t.destroyed;)t.write(a.value)||await new Promise(d=>{const c=()=>{t.off("drain",c),t.off("close",c),d()};t.on("drain",c),t.on("close",c)}),a=await n.read();t.end()},_=65536,v=async(e,t,o=0)=>{const s=await e.read();if(s.done)return t;const n=_-o;return s.value.byteLength>=n?(t.push(s.value.subarray(0,n)),await e.cancel().catch(()=>{}),t):(t.push(s.value),v(e,t,o+s.value.byteLength))},q=async(e,t)=>{const o=e.body?.getReader(),s=()=>{o?.cancel().catch(()=>{})};if(t.destroyed){s();return}t.once("close",s);const n=o===void 0?[]:await v(o,[]);if(t.off("close",s),!t.writable)return;const a=Buffer.concat(n),d=[`HTTP/1.1 ${String(e.status)} ${e.statusText||"Refused"}`,"connection: close",`content-length: ${String(a.byteLength)}`];e.headers.forEach((c,l)=>{l!=="content-length"&&l!=="connection"&&d.push(`${l}: ${c}`)}),t.end(Buffer.concat([Buffer.from(`${d.join(`\r
|
|
2
|
+
`)}\r
|
|
3
|
+
\r
|
|
4
|
+
`),a]))},I=async e=>{const t=e.host??"127.0.0.1",o=e.origin===void 0?void 0:$(e.origin);if(o===void 0&&!U.test(t))throw new Error(`createNodeHttpServer: host "${t}" is not a loopback address, so set origin to the public URL (e.g. https://app.example). Without it, browser requests fail the CSRF origin check and signed URLs point at the bound address.`);const s=new E({noServer:!0}),n=new Set,a=m(),d=()=>o??`http://${y(t)}:${String(a.address().port)}`;a.on("request",(i,r)=>{const h=d(),f=new AbortController;r.once("close",()=>{r.writableFinished||f.abort()});let u;try{u=b(i,h,f.signal,r)}catch{r.writeHead(400,{"content-type":"text/plain; charset=utf-8"}),r.end("Bad Request");return}Promise.resolve().then(()=>e.fetch(u)).then(w=>C(w,r),()=>{r.headersSent||r.writeHead(500,{"content-type":"text/plain; charset=utf-8"}),r.end("Internal Server Error")}).catch(()=>{r.destroy()})});const c=async(i,r,h)=>{const f=new AbortController,u={closed:!1};n.add(r),r.on("error",()=>{r.destroy()}),r.once("close",()=>{u.closed=!0,n.delete(r),f.abort()});const w=await e.fetch(b(i,d(),f.signal)),p=L(w);if(p===void 0){await q(w,r);return}if(u.closed){p.abandon();return}r.once("close",()=>{p.abandon()}),s.handleUpgrade(i,r,h,S=>{p.attach(S)})};a.on("upgrade",(i,r,h)=>{c(i,r,h).catch(()=>{r.destroy()})}),await new Promise((i,r)=>{a.once("error",r),a.listen(e.port??0,t,()=>{a.off("error",r),i()})});const{port:l}=a.address();return{close:async()=>{const i=[...s.clients].map(async r=>R(r,"close"));for(const r of s.clients)r.terminate();for(const r of n)r.destroy();s.close(),a.closeAllConnections(),await new Promise((r,h)=>{a.close(f=>{f===void 0?r():h(f)})}),await Promise.all(i)},origin:`http://${y(t)}:${String(l)}`}};export{I as createNodeHttpServer};
|
package/dist/packem_shared/{createNodePlatform-bL7EdY_5.mjs → createNodePlatform-C1z36ZqT.mjs}
RENAMED
|
@@ -1 +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-DhHyIT8I.mjs";import{createNodeShardKvStore as R}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as T}from"./createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as q}from"./createNodeR2Bucket-6I2HobcB.mjs";import{createNodeSchedulerHost as I}from"./createNodeSchedulerHost-BIJ4H38-.mjs";import{createNodeShardHost as _}from"./createNodeShardHost-B6mt1S-h.mjs";import{createNodeShardRegistry as B}from"./createNodeShardRegistry-
|
|
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-DhHyIT8I.mjs";import{createNodeShardKvStore as R}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeQueueHost as T}from"./createNodeQueueHost-CttDb5sY.mjs";import{createNodeR2Bucket as q}from"./createNodeR2Bucket-6I2HobcB.mjs";import{createNodeSchedulerHost as I}from"./createNodeSchedulerHost-BIJ4H38-.mjs";import{createNodeShardHost as _}from"./createNodeShardHost-B6mt1S-h.mjs";import{createNodeShardRegistry as B}from"./createNodeShardRegistry-D1gAjQBl.mjs";import{createNodeSocketHost as D}from"./createNodeSocketHost-Bpn7V-ru.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:i,drain:u,host:m}=_(e);o.push(i);try{const c=R(r),t=B(e);o.push(()=>{t.close()});const{directory:d}=t,l=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}=I(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 the worker's queue export from createWorker")}),queues:e.queues}),v=e.objectStorageDirectory===void 0?void 0:q({directory:e.objectStorageDirectory}),a=e.globalTablesPath===void 0?void 0:y({path:e.globalTablesPath});a!==void 0&&o.push(()=>{a.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=>l(s),drain:u,globalTables:a,kv:c,objectStorage:v,queues:S,scheduler:p,shard:m,sockets:f,workflows:w,[Symbol.dispose]:h}}catch(c){for(const t of o.toReversed())try{t()}catch{}throw c}};export{z as createNodePlatform};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{mkdirSync as S,readdirSync as v}from"node:fs";import{join as l}from"node:path";import{LunoraError as f}from"@lunora/errors";import{createNodeShardKvStore as w}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as g}from"./createNodeShardHost-B6mt1S-h.mjs";import{createNodeSocketHost as A}from"./createNodeSocketHost-Bpn7V-ru.mjs";const c=".sqlite3",u=r=>r.replaceAll(/[^\da-z._-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),b=r=>decodeURIComponent(r),$=(r,t)=>{const o=t.slice(0,-c.length),s=l(r,t);let d;try{d=b(o)}catch{throw new f("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${s}" has a basename that is not valid percent-encoding, so the shard key it holds cannot be recovered. Rename or remove the file.`)}const e=`${u(d)}${c}`;if(e!==t)throw new f("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${s}" was written under an older shard-key encoding. It decodes to shard key "${d}", which this build stores as "${e}" — so the key would be listed for fan-out while every read opened a new, empty database. Rename it: mv "${s}" "${l(r,e)}"`);return d},I=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){S(r.directory,{recursive:!0});for(const e of v(r.directory))e.endsWith(c)&&o.add($(r.directory,e))}const s=e=>{const a=t.get(e);if(a!==void 0)return a.shard;const i=r.directory===void 0?void 0:l(r.directory,`${u(e)}${c}`);let n;const{database:m,dispose:p,host:y}=g({onAlarm:()=>r.onAlarm?.(n),path:i,shardKey:e}),h=A(m);return n={inbound:h,kv:w(m),shard:y,shardKey:e,sockets:h.socket},t.set(e,{dispose:p,drain:h.drain,shard:n}),o.add(e),n},d=e=>({fetch:async a=>{const i=s(e);return r.onFetch===void 0?new Response(e):r.onFetch(a,i)}});return{close:()=>{for(const e of t.values()){const a=e.drain();a===void 0?e.dispose():a.finally(e.dispose).catch(()=>{})}t.clear()},directory:{get:e=>d(String(e)),getByName:e=>d(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:s}};export{I as createNodeShardRegistry};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{deserialize as L,serialize as u}from"node:v8";import{N as M,u as P,c as C}from"./node-socket-pair-0baNnoFw.mjs";import{t as y}from"./to-array-buffer-DVyXU4Ep.mjs";const m=s=>typeof s=="object"&&s!==null||typeof s=="function",D=s=>{s.exec("CREATE TABLE IF NOT EXISTS _lunora_sockets (id TEXT PRIMARY KEY, attachment BLOB, tags TEXT NOT NULL DEFAULT '[]')");const E=s.prepare(`INSERT INTO _lunora_sockets (id, attachment, tags) VALUES (?, ?, ?)
|
|
2
|
+
ON CONFLICT (id) DO UPDATE SET attachment = excluded.attachment, tags = excluded.tags`),R=s.prepare("UPDATE _lunora_sockets SET attachment = ? WHERE id = ?"),A=s.prepare("UPDATE _lunora_sockets SET tags = ? WHERE id = ?"),N=s.prepare("SELECT attachment, tags FROM _lunora_sockets WHERE id = ?"),_=s.prepare("DELETE FROM _lunora_sockets WHERE id = ?"),i=new Map,d=new WeakMap;let l=new WeakMap;const v=(t,e)=>{s.open&&R.run(e===void 0?null:u(e),t)},T=(t,e)=>{s.open&&A.run(JSON.stringify([...e]),t)},g=t=>{const e=t;e.closed=!0,i.delete(e.id),m(e.raw)&&l.delete(e.raw),s.open&&_.run(e.id)},w=(t,e)=>{const o=t;return o.handle=e,d.set(e,o.id),e},O=(t,e)=>{const o=t,n=e,c=typeof n.close=="function"?n.close.bind(e):void 0;return typeof n.send!="function"&&(n.send=r=>{o.received.push(typeof r=="string"?r:y(r))}),n.close=(r,f)=>{g(o),c?.(r,f)},n.deserializeAttachment=()=>o.attachment,n.serializeAttachment=r=>{o.attachment=r,v(o.id,r)},w(o,e)},S=t=>{const e=t;return w(e,{close:(n,c)=>{g(e)},deserializeAttachment:()=>e.attachment,send:n=>{e.received.push(typeof n=="string"?n:y(n))},serializeAttachment:n=>{e.attachment=n,v(e.id,n)}})},p={},h=new Set,k=()=>h.size===0?void 0:Promise.all(h).then(()=>k()),F={close:(t,e,o,n)=>{let c;try{c=p.close?.(t,e,o,n)}catch(a){c=Promise.reject(a instanceof Error?a:new Error(String(a)))}const r=()=>{const a=l.get(t);a!==void 0&&g(a)},f=Promise.resolve(c).then(r,r).catch(()=>{}).finally(()=>{h.delete(f)});h.add(f)},message:(t,e)=>{try{p.message?.(t,e)}catch(o){console.error("[@lunora/platform-node] socket message handler threw",o)}}};return{drain:k,onClose:t=>{p.close=t},onMessage:t=>{p.message=t},readFrames:t=>(i.get(d.get(t)??"")?.received??[]).filter(e=>typeof e=="string"),restoreSocket:(t,e)=>{const o=s.open?N.get(t):void 0,n=o?.attachment===null?void 0:o?.attachment,c={attachment:n===void 0?e:L(n),closed:!1,handle:void 0,id:t,raw:void 0,received:[],tags:new Set(o===void 0?[]:JSON.parse(o.tags))};return i.set(t,c),o===void 0&&s.open&&E.run(t,c.attachment===void 0?null:u(c.attachment),JSON.stringify([...c.tags])),S(c)},simulateRecycle:()=>{i.clear(),l=new WeakMap},socket:{newPair:()=>C(F),upgradeResponse:t=>{if(!(t instanceof M))throw new TypeError("@lunora/platform-node: upgradeResponse called with a client end this host did not open");return P(t)},accept:(t,e,o)=>{if(!s.open)throw new Error("platform closed: cannot accept a socket");const n=crypto.randomUUID(),c=new Set(o);E.run(n,e===void 0?null:u(e),JSON.stringify([...c]));const r={attachment:e,closed:!1,handle:void 0,id:n,raw:t,received:[],tags:c};return i.set(n,r),m(t)?(l.set(t,r),O(r,t)):S(r)},getSockets:t=>{const e=[...i.values()];return(t===void 0?e:e.filter(n=>n.tags.has(t))).map(n=>n.handle)},handleFor:t=>m(t)?l.get(t)?.handle:void 0,idFor:t=>{const e=d.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(!s.open)throw new Error("platform closed: cannot remove a tag");const o=i.get(d.get(t)??"");o!==void 0&&(e===void 0?o.tags.clear():o.tags.delete(e),T(o.id,o.tags))},setTag:(t,e)=>{if(!s.open)throw new Error("platform closed: cannot set a tag");const o=i.get(d.get(t)??"");o!==void 0&&(o.tags.add(e),T(o.id,o.tags))}}}};export{D as createNodeSocketHost};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as h}from"@lunora/errors";import{t as p}from"./to-array-buffer-DVyXU4Ep.mjs";const E=256,g="lunora-ping",m="lunora-pong";class A{#e;#t;constructor(e,o){this.#t=e,this.#e=o}attach(e){this.#t(e)}abandon(){this.#e()}}const P=r=>{let e,o=!1,u=!1;const d=[],c=(n,t,s,i)=>{u||(u=!0,r.close(n,t,s,i))},a={close:(n,t)=>{o||(o=!0,e===void 0?c(a,n??1e3,t??"",!0):e.close(n,t))},send:n=>{if(!o){if(e!==void 0){e.send(n);return}if(d.length>=E){o=!0,c(a,1011,"handshake stalled",!1);return}d.push(n)}}};return{client:new A(n=>{if(o){n.close(1001,"connection closed before the handshake completed");return}if(e!==void 0)throw new h("INTERNAL","a Node socket pair attaches to exactly one live transport");e={close:(t,s)=>{n.close(t,s)},send:t=>{n.send(t)}};for(const t of d.splice(0))n.send(t);n.on("message",(t,s)=>{if(o||Array.isArray(t))return;const i=s?void 0:new TextDecoder().decode(t);if(i===g){n.send(m);return}r.message(a,i??p(t))}),n.on("error",()=>{}),n.on("close",(t,s)=>{o=!0,c(a,t,s.toString("utf8"),t===1e3||t===1005)})},()=>{e===void 0&&(o=!0,c(a,1006,"handshake did not complete",!1))}),server:a}},f="x-lunora-node-upgrade",k=3e4,l=new Map,T=r=>{const e=crypto.randomUUID();return l.set(e,r),setTimeout(()=>{l.delete(e)&&r.abandon()},k).unref(),new Response(null,{headers:{[f]:e},status:200})},U=r=>{const e=r.headers.get(f);if(e===null)return;const o=l.get(e);return l.delete(e),o};export{A as N,P as c,U as t,T as u};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/platform-node",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.159",
|
|
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,17 +46,18 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/d1": "1.0.0-alpha.
|
|
49
|
+
"@lunora/d1": "1.0.0-alpha.190",
|
|
50
50
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
51
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
52
|
-
"@lunora/queue": "1.0.0-alpha.
|
|
53
|
-
"@lunora/runtime": "1.0.0-alpha.
|
|
54
|
-
"@lunora/sql-store": "1.0.0-alpha.
|
|
55
|
-
"@lunora/workflow": "1.0.0-alpha.
|
|
51
|
+
"@lunora/platform": "1.0.0-alpha.69",
|
|
52
|
+
"@lunora/queue": "1.0.0-alpha.115",
|
|
53
|
+
"@lunora/runtime": "1.0.0-alpha.218",
|
|
54
|
+
"@lunora/sql-store": "1.0.0-alpha.191",
|
|
55
|
+
"@lunora/workflow": "1.0.0-alpha.84",
|
|
56
56
|
"@visulima/cron-parser": "2.0.0",
|
|
57
57
|
"@visulima/workflow": "3.0.0",
|
|
58
58
|
"better-sqlite3": "^13.0.3",
|
|
59
|
-
"pprof-format": "2.3.1"
|
|
59
|
+
"pprof-format": "2.3.1",
|
|
60
|
+
"ws": "^8.21.0"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"@lunora/shard-engine": ">=1.0.0-alpha.45 <2.0.0-0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createServer as f}from"node:http";const i=e=>e.includes(":")&&!e.startsWith("[")?`[${e}]`:e,p=e=>{const t=new URL(e);if(t.protocol!=="http:"&&t.protocol!=="https:")throw new RangeError(`origin must be an http or https URL, got scheme ${t.protocol}`);return t.origin},w=/^(?:127\.\d+\.\d+\.\d+|::1|localhost)$/,u=1048576,b=5e3,y=(e,t)=>{const n=e[Symbol.asyncIterator]();return new ReadableStream({cancel(){if(t.headersSent){e.destroy();return}if(e.complete){e.resume();return}let s=0,c=!1;const o=()=>{if(!e.complete){if(c=!0,t.writableFinished){e.destroy();return}e.removeAllListeners("readable"),e.pause()}},r=setTimeout(o,b),a=()=>{clearTimeout(r)};t.once("finish",()=>{c&&e.destroy()}),e.once("end",a),e.once("close",a),e.removeAllListeners("readable"),e.on("readable",()=>{let d=e.read();for(;d!==null;){if(s+=d.byteLength,s>u){o();return}d=e.read()}})},async pull(s){const c=await n.next();c.done?s.close():s.enqueue(c.value)}})},v=(e,t)=>{const n=new URL(e??"/",t),s=new URL(t);return s.pathname=n.pathname,s.search=n.search,s},R=(e,t,n,s)=>{const c=new Headers;for(const[a,d]of Object.entries(e.headers))if(Array.isArray(d))for(const l of d)c.append(a,l);else d!==void 0&&c.set(a,d);const o=e.method??"GET",r={headers:c,method:o,signal:s};return o!=="GET"&&o!=="HEAD"&&(r.body=y(e,t),r.duplex="half"),new Request(v(e.url,n),r)},S=e=>e.destroyed,E=async(e,t)=>{if(t.on("error",()=>{t.destroy()}),S(t)){await e.body?.cancel().catch(()=>{});return}const n={};e.headers.forEach((r,a)=>{a!=="set-cookie"&&(n[a]=r)});const s=e.headers.getSetCookie();if(s.length>0&&(n["set-cookie"]=s),t.writeHead(e.status,n),e.body===null){t.end();return}const c=e.body.getReader();t.once("close",()=>{c.cancel().catch(()=>{})});let o=await c.read();for(;!o.done&&!t.destroyed;)t.write(o.value)||await new Promise(r=>{const a=()=>{t.off("drain",a),t.off("close",a),r()};t.on("drain",a),t.on("close",a)}),o=await c.read();t.end()},H=async e=>{const t=e.host??"127.0.0.1",n=e.origin===void 0?void 0:p(e.origin);if(n===void 0&&!w.test(t))throw new Error(`createNodeHttpServer: host "${t}" is not a loopback address, so set origin to the public URL (e.g. https://app.example). Without it, browser requests fail the CSRF origin check and signed URLs point at the bound address.`);const s=f();s.on("request",(o,r)=>{const a=n??`http://${i(t)}:${String(s.address().port)}`,d=new AbortController;r.once("close",()=>{r.writableFinished||d.abort()});let l;try{l=R(o,r,a,d.signal)}catch{r.writeHead(400,{"content-type":"text/plain; charset=utf-8"}),r.end("Bad Request");return}Promise.resolve().then(()=>e.fetch(l)).then(h=>E(h,r),()=>{r.headersSent||r.writeHead(500,{"content-type":"text/plain; charset=utf-8"}),r.end("Internal Server Error")}).catch(()=>{r.destroy()})}),await new Promise((o,r)=>{s.once("error",r),s.listen(e.port??0,t,()=>{s.off("error",r),o()})});const{port:c}=s.address();return{close:()=>new Promise((o,r)=>{s.closeAllConnections(),s.close(a=>{a===void 0?o():r(a)})}),origin:`http://${i(t)}:${String(c)}`}};export{H as createNodeHttpServer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{mkdirSync as y,readdirSync as S}from"node:fs";import{join as h}from"node:path";import{LunoraError as l}from"@lunora/errors";import{createNodeShardKvStore as v}from"./createNodeShardKvStore-DVi1OsvH.mjs";import{createNodeShardHost as w}from"./createNodeShardHost-B6mt1S-h.mjs";import{createNodeSocketHost as A}from"./createNodeSocketHost-DQs85G7W.mjs";const c=".sqlite3",f=r=>r.replaceAll(/[^\da-z._-]/gu,t=>[...Buffer.from(t,"utf8")].map(o=>`%${o.toString(16).padStart(2,"0").toUpperCase()}`).join("")),g=r=>decodeURIComponent(r),$=(r,t)=>{const o=t.slice(0,-c.length),d=h(r,t);let a;try{a=g(o)}catch{throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" has a basename that is not valid percent-encoding, so the shard key it holds cannot be recovered. Rename or remove the file.`)}const e=`${f(a)}${c}`;if(e!==t)throw new l("SHARD_UNAVAILABLE",`@lunora/platform-node: shard database "${d}" was written under an older shard-key encoding. It decodes to shard key "${a}", which this build stores as "${e}" — so the key would be listed for fan-out while every read opened a new, empty database. Rename it: mv "${d}" "${h(r,e)}"`);return a},I=(r={})=>{const t=new Map,o=new Set;if(r.directory!==void 0){y(r.directory,{recursive:!0});for(const e of S(r.directory))e.endsWith(c)&&o.add($(r.directory,e))}const d=e=>{const s=t.get(e);if(s!==void 0)return s.shard;const i=r.directory===void 0?void 0:h(r.directory,`${f(e)}${c}`);let n;const{database:m,dispose:u,host:p}=w({onAlarm:()=>r.onAlarm?.(n),path:i,shardKey:e});return n={kv:v(m),shard:p,shardKey:e,sockets:A(m).socket},t.set(e,{dispose:u,shard:n}),o.add(e),n},a=e=>({fetch:async s=>{const i=d(e);return r.onFetch===void 0?new Response(e):r.onFetch(s,i)}});return{close:()=>{for(const e of t.values())e.dispose();t.clear()},directory:{get:e=>a(String(e)),getByName:e=>a(e),idForName:e=>e},listShardKeys:()=>[...o],shardFor:d}};export{I as createNodeShardRegistry};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{deserialize as A,serialize as l}from"node:v8";import{t as T}from"./to-array-buffer-DVyXU4Ep.mjs";const p=s=>typeof s=="object"&&s!==null||typeof s=="function",F=s=>{s.exec("CREATE TABLE IF NOT EXISTS _lunora_sockets (id TEXT PRIMARY KEY, attachment BLOB, tags TEXT NOT NULL DEFAULT '[]')");const f=s.prepare(`INSERT INTO _lunora_sockets (id, attachment, tags) VALUES (?, ?, ?)
|
|
2
|
-
ON CONFLICT (id) DO UPDATE SET attachment = excluded.attachment, tags = excluded.tags`),v=s.prepare("UPDATE _lunora_sockets SET attachment = ? WHERE id = ?"),S=s.prepare("UPDATE _lunora_sockets SET tags = ? WHERE id = ?"),k=s.prepare("SELECT attachment, tags FROM _lunora_sockets WHERE id = ?"),w=s.prepare("DELETE FROM _lunora_sockets WHERE id = ?"),i=new Map,a=new WeakMap;let d=new WeakMap;const u=(t,e)=>{s.open&&v.run(e===void 0?null:l(e),t)},g=(t,e)=>{s.open&&S.run(JSON.stringify([...e]),t)},h=t=>{const e=t;e.closed=!0,i.delete(e.id),p(e.raw)&&d.delete(e.raw),s.open&&w.run(e.id)},m=(t,e)=>{const o=t;return o.handle=e,a.set(e,o.id),e},R=(t,e)=>{const o=t,n=e,c=typeof n.close=="function"?n.close.bind(e):void 0;return typeof n.send!="function"&&(n.send=r=>{o.received.push(typeof r=="string"?r:T(r))}),n.close=(r,y)=>{h(o),c?.(r,y)},n.deserializeAttachment=()=>o.attachment,n.serializeAttachment=r=>{o.attachment=r,u(o.id,r)},m(o,e)},E=t=>{const e=t;return m(e,{close:(n,c)=>{h(e)},deserializeAttachment:()=>e.attachment,send:n=>{e.received.push(typeof n=="string"?n:T(n))},serializeAttachment:n=>{e.attachment=n,u(e.id,n)}})};return{readFrames:t=>(i.get(a.get(t)??"")?.received??[]).filter(e=>typeof e=="string"),restoreSocket:(t,e)=>{const o=s.open?k.get(t):void 0,n=o?.attachment===null?void 0:o?.attachment,c={attachment:n===void 0?e:A(n),closed:!1,handle:void 0,id:t,raw:void 0,received:[],tags:new Set(o===void 0?[]:JSON.parse(o.tags))};return i.set(t,c),o===void 0&&s.open&&f.run(t,c.attachment===void 0?null:l(c.attachment),JSON.stringify([...c.tags])),E(c)},simulateRecycle:()=>{i.clear(),d=new WeakMap},socket:{accept:(t,e,o)=>{if(!s.open)throw new Error("platform closed: cannot accept a socket");const n=crypto.randomUUID(),c=new Set(o);f.run(n,e===void 0?null:l(e),JSON.stringify([...c]));const r={attachment:e,closed:!1,handle:void 0,id:n,raw:t,received:[],tags:c};return i.set(n,r),p(t)?(d.set(t,r),R(r,t)):E(r)},getSockets:t=>{const e=[...i.values()];return(t===void 0?e:e.filter(n=>n.tags.has(t))).map(n=>n.handle)},handleFor:t=>p(t)?d.get(t)?.handle:void 0,idFor:t=>{const e=a.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(!s.open)throw new Error("platform closed: cannot remove a tag");const o=i.get(a.get(t)??"");o!==void 0&&(e===void 0?o.tags.clear():o.tags.delete(e),g(o.id,o.tags))},setTag:(t,e)=>{if(!s.open)throw new Error("platform closed: cannot set a tag");const o=i.get(a.get(t)??"");o!==void 0&&(o.tags.add(e),g(o.id,o.tags))}}}};export{F as createNodeSocketHost};
|