@b9g/platform-node 0.1.6 → 0.1.8
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/README.md +128 -0
- package/package.json +2 -13
- package/src/index.d.ts +55 -2
- package/src/index.js +306 -4
- package/src/platform.d.ts +0 -71
- package/src/platform.js +0 -265
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @b9g/platform-node
|
|
2
|
+
|
|
3
|
+
Node.js platform adapter for Shovel. Runs ServiceWorker applications on Node.js with HTTP server integration, hot reloading, and worker thread concurrency.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Node.js HTTP server with Web API Request/Response conversion
|
|
8
|
+
- Hot module reloading for development via VM module system
|
|
9
|
+
- Worker thread pool for concurrent request handling
|
|
10
|
+
- Memory and filesystem cache backends
|
|
11
|
+
- File System Access API implementation via NodeBucket
|
|
12
|
+
- ServiceWorker lifecycle support (install, activate, fetch events)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @b9g/platform-node @b9g/platform @b9g/cache @b9g/filesystem
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Server
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import NodePlatform from '@b9g/platform-node';
|
|
26
|
+
|
|
27
|
+
const platform = new NodePlatform({
|
|
28
|
+
caches: { type: 'memory' },
|
|
29
|
+
filesystem: { type: 'local', directory: './dist' }
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const server = platform.createServer(async (request) => {
|
|
33
|
+
return new Response('Hello from Node.js');
|
|
34
|
+
}, { port: 3000, host: 'localhost' });
|
|
35
|
+
|
|
36
|
+
await server.listen();
|
|
37
|
+
console.log('Server running at http://localhost:3000');
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### ServiceWorker App
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
import NodePlatform from '@b9g/platform-node';
|
|
44
|
+
|
|
45
|
+
const platform = new NodePlatform({
|
|
46
|
+
cwd: process.cwd()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Load ServiceWorker entrypoint
|
|
50
|
+
const instance = await platform.loadServiceWorker('./src/server.js', {
|
|
51
|
+
workerCount: 4 // Number of worker threads
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ServiceWorker is now handling requests
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Exports
|
|
58
|
+
|
|
59
|
+
### Classes
|
|
60
|
+
|
|
61
|
+
- `NodePlatform` - Node.js platform implementation (extends BasePlatform)
|
|
62
|
+
|
|
63
|
+
### Re-exports from @b9g/platform
|
|
64
|
+
|
|
65
|
+
- `Platform`, `CacheConfig`, `StaticConfig`, `Handler`, `Server`, `ServerOptions`
|
|
66
|
+
|
|
67
|
+
### Default Export
|
|
68
|
+
|
|
69
|
+
- `NodePlatform` - The platform class
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
### `new NodePlatform(options?)`
|
|
74
|
+
|
|
75
|
+
Creates a new Node.js platform instance.
|
|
76
|
+
|
|
77
|
+
**Options:**
|
|
78
|
+
- `caches`: Cache configuration object (see @b9g/platform)
|
|
79
|
+
- `filesystem`: Filesystem configuration object
|
|
80
|
+
- `port`: Default port for servers (default: 3000)
|
|
81
|
+
- `host`: Default host for servers (default: localhost)
|
|
82
|
+
- `cwd`: Working directory for file resolution (default: process.cwd())
|
|
83
|
+
|
|
84
|
+
### `platform.createServer(handler, options): Server`
|
|
85
|
+
|
|
86
|
+
Creates an HTTP server with automatic Request/Response conversion.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
- `handler`: `(request: Request) => Promise<Response>` - Request handler function
|
|
90
|
+
- `options`: Server options (port, host)
|
|
91
|
+
|
|
92
|
+
**Returns:** Server instance with:
|
|
93
|
+
- `listen()`: Start the server
|
|
94
|
+
- `close()`: Stop the server
|
|
95
|
+
- `url`: Server URL (after listen)
|
|
96
|
+
- `ready`: Promise that resolves when server is ready
|
|
97
|
+
|
|
98
|
+
### `platform.loadServiceWorker(entrypoint, options): Promise<ServiceWorkerInstance>`
|
|
99
|
+
|
|
100
|
+
Loads and runs a ServiceWorker entrypoint.
|
|
101
|
+
|
|
102
|
+
**Parameters:**
|
|
103
|
+
- `entrypoint`: Path to ServiceWorker entry file
|
|
104
|
+
- `options`: ServiceWorker options (workerCount, caches, etc.)
|
|
105
|
+
|
|
106
|
+
**Returns:** ServiceWorkerInstance with:
|
|
107
|
+
- `handleRequest(request)`: Handle a request through the ServiceWorker
|
|
108
|
+
- `ready`: Promise that resolves when ServiceWorker is ready
|
|
109
|
+
|
|
110
|
+
## Cache Backends
|
|
111
|
+
|
|
112
|
+
Configured via `caches` option:
|
|
113
|
+
|
|
114
|
+
- `memory`: In-memory caching (MemoryCache)
|
|
115
|
+
- `filesystem`: File-based caching (NodeBucket)
|
|
116
|
+
|
|
117
|
+
## Worker Thread Architecture
|
|
118
|
+
|
|
119
|
+
The Node.js platform uses worker threads for true concurrency:
|
|
120
|
+
|
|
121
|
+
- Each worker runs the ServiceWorker code in isolation
|
|
122
|
+
- Round-robin load balancing across workers
|
|
123
|
+
- Shared cache storage coordinated via PostMessage
|
|
124
|
+
- Automatic request timeout (30s default)
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shovel",
|
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
"esbuild"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@b9g/platform": "^0.1.
|
|
15
|
-
"@b9g/cache": "^0.1.3",
|
|
16
|
-
"@remix-run/node-fetch-server": "^0.11.0",
|
|
17
|
-
"@aws-sdk/client-s3": "^3.0.0"
|
|
14
|
+
"@b9g/platform": "^0.1.6"
|
|
18
15
|
},
|
|
19
16
|
"devDependencies": {
|
|
20
17
|
"@b9g/libuild": "^0.1.11",
|
|
@@ -29,14 +26,6 @@
|
|
|
29
26
|
"types": "./src/index.d.ts",
|
|
30
27
|
"import": "./src/index.js"
|
|
31
28
|
},
|
|
32
|
-
"./platform": {
|
|
33
|
-
"types": "./src/platform.d.ts",
|
|
34
|
-
"import": "./src/platform.js"
|
|
35
|
-
},
|
|
36
|
-
"./platform.js": {
|
|
37
|
-
"types": "./src/platform.d.ts",
|
|
38
|
-
"import": "./src/platform.js"
|
|
39
|
-
},
|
|
40
29
|
"./package.json": "./package.json",
|
|
41
30
|
"./index": {
|
|
42
31
|
"types": "./src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -3,5 +3,58 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides hot reloading, ESBuild integration, and optimized caching for Node.js environments.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { BasePlatform, PlatformConfig, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance, ServiceWorkerPool } from "@b9g/platform";
|
|
7
|
+
import { CustomCacheStorage } from "@b9g/cache";
|
|
8
|
+
export type { Platform, Handler, Server, ServerOptions } from "@b9g/platform";
|
|
9
|
+
export interface NodePlatformOptions extends PlatformConfig {
|
|
10
|
+
/** Port for development server (default: 3000) */
|
|
11
|
+
port?: number;
|
|
12
|
+
/** Host for development server (default: localhost) */
|
|
13
|
+
host?: string;
|
|
14
|
+
/** Working directory for file resolution */
|
|
15
|
+
cwd?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Node.js platform implementation
|
|
19
|
+
* ServiceWorker entrypoint loader for Node.js with ESBuild VM system
|
|
20
|
+
*/
|
|
21
|
+
export declare class NodePlatform extends BasePlatform {
|
|
22
|
+
#private;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
constructor(options?: NodePlatformOptions);
|
|
25
|
+
/**
|
|
26
|
+
* Get options for testing
|
|
27
|
+
*/
|
|
28
|
+
get options(): Required<NodePlatformOptions>;
|
|
29
|
+
/**
|
|
30
|
+
* Get/set worker pool for testing
|
|
31
|
+
*/
|
|
32
|
+
get workerPool(): ServiceWorkerPool | undefined;
|
|
33
|
+
set workerPool(pool: ServiceWorkerPool | undefined);
|
|
34
|
+
/**
|
|
35
|
+
* THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
|
|
36
|
+
* Uses Worker threads with coordinated cache storage for isolation and standards compliance
|
|
37
|
+
*/
|
|
38
|
+
loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
|
|
39
|
+
/**
|
|
40
|
+
* SUPPORTING UTILITY - Create cache storage
|
|
41
|
+
* Uses config from package.json shovel field
|
|
42
|
+
*/
|
|
43
|
+
createCaches(): Promise<CustomCacheStorage>;
|
|
44
|
+
/**
|
|
45
|
+
* SUPPORTING UTILITY - Create HTTP server for Node.js
|
|
46
|
+
*/
|
|
47
|
+
createServer(handler: Handler, options?: ServerOptions): Server;
|
|
48
|
+
/**
|
|
49
|
+
* Reload workers for hot reloading (called by CLI)
|
|
50
|
+
*/
|
|
51
|
+
reloadWorkers(version?: number | string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Dispose of platform resources
|
|
54
|
+
*/
|
|
55
|
+
dispose(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Default export for easy importing
|
|
59
|
+
*/
|
|
60
|
+
export default NodePlatform;
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,312 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
2
|
// src/index.ts
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
BasePlatform,
|
|
5
|
+
ServiceWorkerPool,
|
|
6
|
+
SingleThreadedRuntime,
|
|
7
|
+
loadConfig,
|
|
8
|
+
createCacheFactory
|
|
9
|
+
} from "@b9g/platform";
|
|
10
|
+
import { CustomCacheStorage } from "@b9g/cache";
|
|
11
|
+
import * as HTTP from "http";
|
|
12
|
+
import * as Path from "path";
|
|
13
|
+
import { getLogger } from "@logtape/logtape";
|
|
14
|
+
var logger = getLogger(["platform-node"]);
|
|
15
|
+
var NodePlatform = class extends BasePlatform {
|
|
16
|
+
name;
|
|
17
|
+
#options;
|
|
18
|
+
#workerPool;
|
|
19
|
+
#singleThreadedRuntime;
|
|
20
|
+
#cacheStorage;
|
|
21
|
+
#config;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
super(options);
|
|
24
|
+
this.name = "node";
|
|
25
|
+
const cwd = options.cwd || process.cwd();
|
|
26
|
+
this.#config = loadConfig(cwd);
|
|
27
|
+
logger.info("Loaded configuration", { config: this.#config });
|
|
28
|
+
this.#options = {
|
|
29
|
+
port: options.port ?? this.#config.port,
|
|
30
|
+
host: options.host ?? this.#config.host,
|
|
31
|
+
cwd,
|
|
32
|
+
...options
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get options for testing
|
|
37
|
+
*/
|
|
38
|
+
get options() {
|
|
39
|
+
return this.#options;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get/set worker pool for testing
|
|
43
|
+
*/
|
|
44
|
+
get workerPool() {
|
|
45
|
+
return this.#workerPool;
|
|
46
|
+
}
|
|
47
|
+
set workerPool(pool) {
|
|
48
|
+
this.#workerPool = pool;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
|
|
52
|
+
* Uses Worker threads with coordinated cache storage for isolation and standards compliance
|
|
53
|
+
*/
|
|
54
|
+
async loadServiceWorker(entrypoint, options = {}) {
|
|
55
|
+
const workerCount = options.workerCount ?? this.#config.workers ?? 1;
|
|
56
|
+
if (workerCount === 1 && !options.hotReload) {
|
|
57
|
+
return this.#loadServiceWorkerDirect(entrypoint, options);
|
|
58
|
+
}
|
|
59
|
+
return this.#loadServiceWorkerWithPool(entrypoint, options, workerCount);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load ServiceWorker directly in main thread (single-threaded mode)
|
|
63
|
+
* No postMessage overhead - maximum performance for production
|
|
64
|
+
*/
|
|
65
|
+
async #loadServiceWorkerDirect(entrypoint, _options) {
|
|
66
|
+
const entryPath = Path.resolve(this.#options.cwd, entrypoint);
|
|
67
|
+
const entryDir = Path.dirname(entryPath);
|
|
68
|
+
if (!this.#cacheStorage) {
|
|
69
|
+
this.#cacheStorage = await this.createCaches();
|
|
70
|
+
}
|
|
71
|
+
if (this.#singleThreadedRuntime) {
|
|
72
|
+
await this.#singleThreadedRuntime.terminate();
|
|
73
|
+
}
|
|
74
|
+
if (this.#workerPool) {
|
|
75
|
+
await this.#workerPool.terminate();
|
|
76
|
+
this.#workerPool = void 0;
|
|
77
|
+
}
|
|
78
|
+
logger.info("Creating single-threaded ServiceWorker runtime", { entryPath });
|
|
79
|
+
this.#singleThreadedRuntime = new SingleThreadedRuntime({
|
|
80
|
+
baseDir: entryDir,
|
|
81
|
+
cacheStorage: this.#cacheStorage,
|
|
82
|
+
config: this.#config
|
|
83
|
+
});
|
|
84
|
+
await this.#singleThreadedRuntime.init();
|
|
85
|
+
const version = Date.now();
|
|
86
|
+
await this.#singleThreadedRuntime.loadEntrypoint(entryPath, version);
|
|
87
|
+
const runtime = this.#singleThreadedRuntime;
|
|
88
|
+
const platform = this;
|
|
89
|
+
const instance = {
|
|
90
|
+
runtime,
|
|
91
|
+
handleRequest: async (request) => {
|
|
92
|
+
if (!platform.#singleThreadedRuntime) {
|
|
93
|
+
throw new Error("SingleThreadedRuntime not initialized");
|
|
94
|
+
}
|
|
95
|
+
return platform.#singleThreadedRuntime.handleRequest(request);
|
|
96
|
+
},
|
|
97
|
+
install: async () => {
|
|
98
|
+
logger.info("ServiceWorker installed", { method: "single_threaded" });
|
|
99
|
+
},
|
|
100
|
+
activate: async () => {
|
|
101
|
+
logger.info("ServiceWorker activated", { method: "single_threaded" });
|
|
102
|
+
},
|
|
103
|
+
get ready() {
|
|
104
|
+
return runtime?.ready ?? false;
|
|
105
|
+
},
|
|
106
|
+
dispose: async () => {
|
|
107
|
+
if (platform.#singleThreadedRuntime) {
|
|
108
|
+
await platform.#singleThreadedRuntime.terminate();
|
|
109
|
+
platform.#singleThreadedRuntime = void 0;
|
|
110
|
+
}
|
|
111
|
+
logger.info("ServiceWorker disposed", {});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
logger.info("ServiceWorker loaded", {
|
|
115
|
+
features: ["single_threaded", "no_postmessage_overhead"]
|
|
116
|
+
});
|
|
117
|
+
return instance;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Load ServiceWorker using worker pool (multi-threaded mode or dev mode)
|
|
121
|
+
*/
|
|
122
|
+
async #loadServiceWorkerWithPool(entrypoint, _options, workerCount) {
|
|
123
|
+
const entryPath = Path.resolve(this.#options.cwd, entrypoint);
|
|
124
|
+
if (!this.#cacheStorage) {
|
|
125
|
+
this.#cacheStorage = await this.createCaches();
|
|
126
|
+
}
|
|
127
|
+
if (this.#singleThreadedRuntime) {
|
|
128
|
+
await this.#singleThreadedRuntime.terminate();
|
|
129
|
+
this.#singleThreadedRuntime = void 0;
|
|
130
|
+
}
|
|
131
|
+
if (this.#workerPool) {
|
|
132
|
+
await this.#workerPool.terminate();
|
|
133
|
+
}
|
|
134
|
+
logger.info("Creating ServiceWorker pool", {
|
|
135
|
+
entryPath,
|
|
136
|
+
workerCount
|
|
137
|
+
});
|
|
138
|
+
this.#workerPool = new ServiceWorkerPool(
|
|
139
|
+
{
|
|
140
|
+
workerCount,
|
|
141
|
+
requestTimeout: 3e4,
|
|
142
|
+
cwd: this.#options.cwd
|
|
143
|
+
},
|
|
144
|
+
entryPath,
|
|
145
|
+
this.#cacheStorage,
|
|
146
|
+
this.#config
|
|
147
|
+
);
|
|
148
|
+
await this.#workerPool.init();
|
|
149
|
+
const version = Date.now();
|
|
150
|
+
await this.#workerPool.reloadWorkers(version);
|
|
151
|
+
const workerPool = this.#workerPool;
|
|
152
|
+
const platform = this;
|
|
153
|
+
const instance = {
|
|
154
|
+
runtime: workerPool,
|
|
155
|
+
handleRequest: async (request) => {
|
|
156
|
+
if (!platform.#workerPool) {
|
|
157
|
+
throw new Error("ServiceWorkerPool not initialized");
|
|
158
|
+
}
|
|
159
|
+
return platform.#workerPool.handleRequest(request);
|
|
160
|
+
},
|
|
161
|
+
install: async () => {
|
|
162
|
+
logger.info("ServiceWorker installed", {
|
|
163
|
+
method: "worker_threads"
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
activate: async () => {
|
|
167
|
+
logger.info("ServiceWorker activated", {
|
|
168
|
+
method: "worker_threads"
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
get ready() {
|
|
172
|
+
return workerPool?.ready ?? false;
|
|
173
|
+
},
|
|
174
|
+
dispose: async () => {
|
|
175
|
+
if (platform.#workerPool) {
|
|
176
|
+
await platform.#workerPool.terminate();
|
|
177
|
+
platform.#workerPool = void 0;
|
|
178
|
+
}
|
|
179
|
+
logger.info("ServiceWorker disposed", {});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
logger.info("ServiceWorker loaded", {
|
|
183
|
+
features: ["worker_threads", "coordinated_caches"]
|
|
184
|
+
});
|
|
185
|
+
return instance;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* SUPPORTING UTILITY - Create cache storage
|
|
189
|
+
* Uses config from package.json shovel field
|
|
190
|
+
*/
|
|
191
|
+
async createCaches() {
|
|
192
|
+
return new CustomCacheStorage(createCacheFactory({ config: this.#config }));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* SUPPORTING UTILITY - Create HTTP server for Node.js
|
|
196
|
+
*/
|
|
197
|
+
createServer(handler, options = {}) {
|
|
198
|
+
const port = options.port ?? this.#options.port;
|
|
199
|
+
const host = options.host ?? this.#options.host;
|
|
200
|
+
const httpServer = HTTP.createServer(async (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const url = `http://${req.headers.host}${req.url}`;
|
|
203
|
+
const request = new Request(url, {
|
|
204
|
+
method: req.method,
|
|
205
|
+
headers: req.headers,
|
|
206
|
+
// Node.js IncomingMessage can be used as body (it's a readable stream)
|
|
207
|
+
body: req.method !== "GET" && req.method !== "HEAD" ? req : void 0
|
|
208
|
+
});
|
|
209
|
+
const response = await handler(request);
|
|
210
|
+
res.statusCode = response.status;
|
|
211
|
+
res.statusMessage = response.statusText;
|
|
212
|
+
response.headers.forEach((value, key) => {
|
|
213
|
+
res.setHeader(key, value);
|
|
214
|
+
});
|
|
215
|
+
if (response.body) {
|
|
216
|
+
const reader = response.body.getReader();
|
|
217
|
+
const pump = async () => {
|
|
218
|
+
const { done, value } = await reader.read();
|
|
219
|
+
if (done) {
|
|
220
|
+
res.end();
|
|
221
|
+
} else {
|
|
222
|
+
res.write(value);
|
|
223
|
+
await pump();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
await pump();
|
|
227
|
+
} else {
|
|
228
|
+
res.end();
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error("Request error", {
|
|
232
|
+
error: error instanceof Error ? error.message : String(error),
|
|
233
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
234
|
+
});
|
|
235
|
+
res.statusCode = 500;
|
|
236
|
+
res.setHeader("Content-Type", "text/plain");
|
|
237
|
+
res.end("Internal Server Error");
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
let isListening = false;
|
|
241
|
+
let actualPort = port;
|
|
242
|
+
return {
|
|
243
|
+
async listen() {
|
|
244
|
+
return new Promise((resolve2, reject) => {
|
|
245
|
+
httpServer.listen(port, host, () => {
|
|
246
|
+
const addr = httpServer.address();
|
|
247
|
+
if (addr && typeof addr === "object") {
|
|
248
|
+
actualPort = addr.port;
|
|
249
|
+
}
|
|
250
|
+
logger.info("Server started", {
|
|
251
|
+
host,
|
|
252
|
+
port: actualPort,
|
|
253
|
+
url: `http://${host}:${actualPort}`
|
|
254
|
+
});
|
|
255
|
+
isListening = true;
|
|
256
|
+
resolve2();
|
|
257
|
+
});
|
|
258
|
+
httpServer.on("error", (error) => {
|
|
259
|
+
reject(error);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
async close() {
|
|
264
|
+
return new Promise((resolve2) => {
|
|
265
|
+
httpServer.close(() => {
|
|
266
|
+
isListening = false;
|
|
267
|
+
resolve2();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
address: () => ({ port: actualPort, host }),
|
|
272
|
+
get url() {
|
|
273
|
+
return `http://${host}:${actualPort}`;
|
|
274
|
+
},
|
|
275
|
+
get ready() {
|
|
276
|
+
return isListening;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Reload workers for hot reloading (called by CLI)
|
|
282
|
+
*/
|
|
283
|
+
async reloadWorkers(version) {
|
|
284
|
+
if (this.#workerPool) {
|
|
285
|
+
await this.#workerPool.reloadWorkers(version);
|
|
286
|
+
} else if (this.#singleThreadedRuntime) {
|
|
287
|
+
await this.#singleThreadedRuntime.reloadWorkers(version);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Dispose of platform resources
|
|
292
|
+
*/
|
|
293
|
+
async dispose() {
|
|
294
|
+
if (this.#singleThreadedRuntime) {
|
|
295
|
+
await this.#singleThreadedRuntime.terminate();
|
|
296
|
+
this.#singleThreadedRuntime = void 0;
|
|
297
|
+
}
|
|
298
|
+
if (this.#workerPool) {
|
|
299
|
+
await this.#workerPool.terminate();
|
|
300
|
+
this.#workerPool = void 0;
|
|
301
|
+
}
|
|
302
|
+
if (this.#cacheStorage) {
|
|
303
|
+
await this.#cacheStorage.dispose();
|
|
304
|
+
this.#cacheStorage = void 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var src_default = NodePlatform;
|
|
7
309
|
export {
|
|
8
310
|
NodePlatform,
|
|
9
|
-
|
|
311
|
+
src_default as default
|
|
10
312
|
};
|
package/src/platform.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node.js platform implementation - ServiceWorker entrypoint loader for Node.js
|
|
3
|
-
*
|
|
4
|
-
* Handles the complex ESBuild VM system, hot reloading, and module linking
|
|
5
|
-
* to make ServiceWorker-style apps run in Node.js environments.
|
|
6
|
-
*/
|
|
7
|
-
import { BasePlatform, PlatformConfig, CacheConfig, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance } from "@b9g/platform";
|
|
8
|
-
import { CustomCacheStorage } from "@b9g/cache";
|
|
9
|
-
export interface NodePlatformOptions extends PlatformConfig {
|
|
10
|
-
/** Enable hot reloading (default: true in development) */
|
|
11
|
-
hotReload?: boolean;
|
|
12
|
-
/** Port for development server (default: 3000) */
|
|
13
|
-
port?: number;
|
|
14
|
-
/** Host for development server (default: localhost) */
|
|
15
|
-
host?: string;
|
|
16
|
-
/** Working directory for file resolution */
|
|
17
|
-
cwd?: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Node.js platform implementation
|
|
21
|
-
* ServiceWorker entrypoint loader for Node.js with ESBuild VM system
|
|
22
|
-
*/
|
|
23
|
-
export declare class NodePlatform extends BasePlatform {
|
|
24
|
-
readonly name = "node";
|
|
25
|
-
private options;
|
|
26
|
-
private workerPool?;
|
|
27
|
-
private cacheStorage?;
|
|
28
|
-
constructor(options?: NodePlatformOptions);
|
|
29
|
-
/**
|
|
30
|
-
* Get filesystem directory handle
|
|
31
|
-
*/
|
|
32
|
-
getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
|
|
33
|
-
/**
|
|
34
|
-
* THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
|
|
35
|
-
* Uses Worker threads with coordinated cache storage for isolation and standards compliance
|
|
36
|
-
*/
|
|
37
|
-
loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
|
|
38
|
-
/**
|
|
39
|
-
* Get platform-specific default cache configuration for Node.js
|
|
40
|
-
*/
|
|
41
|
-
protected getDefaultCacheConfig(): CacheConfig;
|
|
42
|
-
/**
|
|
43
|
-
* SUPPORTING UTILITY - Create cache storage optimized for Node.js
|
|
44
|
-
* Uses MemoryCache in main thread, PostMessageCache in workers
|
|
45
|
-
*/
|
|
46
|
-
createCaches(config?: CacheConfig): Promise<CustomCacheStorage>;
|
|
47
|
-
/**
|
|
48
|
-
* SUPPORTING UTILITY - Create HTTP server for Node.js
|
|
49
|
-
*/
|
|
50
|
-
createServer(handler: Handler, options?: ServerOptions): Server;
|
|
51
|
-
/**
|
|
52
|
-
* Get filesystem root for File System Access API
|
|
53
|
-
*/
|
|
54
|
-
getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
55
|
-
/**
|
|
56
|
-
* Reload workers for hot reloading (called by CLI)
|
|
57
|
-
*/
|
|
58
|
-
reloadWorkers(version?: number | string): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Dispose of platform resources
|
|
61
|
-
*/
|
|
62
|
-
dispose(): Promise<void>;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Create a Node.js platform instance
|
|
66
|
-
*/
|
|
67
|
-
export declare function createNodePlatform(options?: NodePlatformOptions): NodePlatform;
|
|
68
|
-
/**
|
|
69
|
-
* Default export for easy importing
|
|
70
|
-
*/
|
|
71
|
-
export default createNodePlatform;
|
package/src/platform.js
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/// <reference types="./platform.d.ts" />
|
|
2
|
-
// src/platform.ts
|
|
3
|
-
import {
|
|
4
|
-
BasePlatform
|
|
5
|
-
} from "@b9g/platform";
|
|
6
|
-
import { WorkerPool } from "@b9g/platform/worker-pool";
|
|
7
|
-
import { CustomCacheStorage, MemoryCache, MemoryCacheManager, PostMessageCache } from "@b9g/cache";
|
|
8
|
-
import { FileSystemRegistry, getDirectoryHandle, LocalBucket } from "@b9g/filesystem";
|
|
9
|
-
import * as Http from "http";
|
|
10
|
-
import * as Path from "path";
|
|
11
|
-
var NodeWorkerPool = class extends WorkerPool {
|
|
12
|
-
memoryCacheManager;
|
|
13
|
-
constructor(cacheStorage, poolOptions, appEntrypoint) {
|
|
14
|
-
super(cacheStorage, poolOptions, appEntrypoint);
|
|
15
|
-
this.memoryCacheManager = new MemoryCacheManager();
|
|
16
|
-
console.info(
|
|
17
|
-
"[NodeWorkerPool] Initialized with entrypoint:",
|
|
18
|
-
appEntrypoint
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Handle Node.js-specific cache coordination
|
|
23
|
-
*/
|
|
24
|
-
handleCacheMessage(message) {
|
|
25
|
-
if (message.type?.startsWith("cache:")) {
|
|
26
|
-
console.warn("[NodeWorkerPool] Cache coordination not fully implemented in abstraction");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Enhanced termination with memory cache cleanup
|
|
31
|
-
*/
|
|
32
|
-
async terminate() {
|
|
33
|
-
await super.terminate();
|
|
34
|
-
await this.memoryCacheManager.dispose();
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
var NodePlatform = class extends BasePlatform {
|
|
38
|
-
name = "node";
|
|
39
|
-
options;
|
|
40
|
-
workerPool;
|
|
41
|
-
cacheStorage;
|
|
42
|
-
constructor(options = {}) {
|
|
43
|
-
super(options);
|
|
44
|
-
this.options = {
|
|
45
|
-
hotReload: process.env.NODE_ENV !== "production",
|
|
46
|
-
port: 3e3,
|
|
47
|
-
host: "localhost",
|
|
48
|
-
cwd: process.cwd(),
|
|
49
|
-
...options
|
|
50
|
-
};
|
|
51
|
-
FileSystemRegistry.register("node", new LocalBucket({
|
|
52
|
-
rootPath: this.options.cwd
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Get filesystem directory handle
|
|
57
|
-
*/
|
|
58
|
-
async getDirectoryHandle(name) {
|
|
59
|
-
const distPath = Path.resolve(this.options.cwd, "dist");
|
|
60
|
-
const adapter = new LocalBucket({ rootPath: distPath });
|
|
61
|
-
return await adapter.getDirectoryHandle(name);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
|
|
65
|
-
* Uses Worker threads with coordinated cache storage for isolation and standards compliance
|
|
66
|
-
*/
|
|
67
|
-
async loadServiceWorker(entrypoint, options = {}) {
|
|
68
|
-
const entryPath = Path.resolve(this.options.cwd, entrypoint);
|
|
69
|
-
if (!this.cacheStorage) {
|
|
70
|
-
this.cacheStorage = await this.createCaches(options.caches);
|
|
71
|
-
}
|
|
72
|
-
if (this.workerPool) {
|
|
73
|
-
await this.workerPool.terminate();
|
|
74
|
-
}
|
|
75
|
-
const workerCount = options.workerCount || 1;
|
|
76
|
-
console.info(
|
|
77
|
-
"[Platform-Node] Creating NodeWorkerPool with entryPath:",
|
|
78
|
-
entryPath
|
|
79
|
-
);
|
|
80
|
-
this.workerPool = new NodeWorkerPool(
|
|
81
|
-
this.cacheStorage,
|
|
82
|
-
{
|
|
83
|
-
workerCount,
|
|
84
|
-
requestTimeout: 3e4,
|
|
85
|
-
hotReload: this.options.hotReload,
|
|
86
|
-
cwd: this.options.cwd
|
|
87
|
-
},
|
|
88
|
-
entryPath
|
|
89
|
-
);
|
|
90
|
-
await this.workerPool.init();
|
|
91
|
-
const version = Date.now();
|
|
92
|
-
await this.workerPool.reloadWorkers(version);
|
|
93
|
-
const instance = {
|
|
94
|
-
runtime: this.workerPool,
|
|
95
|
-
handleRequest: async (request) => {
|
|
96
|
-
if (!this.workerPool) {
|
|
97
|
-
throw new Error("NodeWorkerPool not initialized");
|
|
98
|
-
}
|
|
99
|
-
return this.workerPool.handleRequest(request);
|
|
100
|
-
},
|
|
101
|
-
install: async () => {
|
|
102
|
-
console.info(
|
|
103
|
-
"[Platform-Node] ServiceWorker installed via Worker threads"
|
|
104
|
-
);
|
|
105
|
-
},
|
|
106
|
-
activate: async () => {
|
|
107
|
-
console.info(
|
|
108
|
-
"[Platform-Node] ServiceWorker activated via Worker threads"
|
|
109
|
-
);
|
|
110
|
-
},
|
|
111
|
-
collectStaticRoutes: async () => {
|
|
112
|
-
return [];
|
|
113
|
-
},
|
|
114
|
-
get ready() {
|
|
115
|
-
return this.workerPool?.ready ?? false;
|
|
116
|
-
},
|
|
117
|
-
dispose: async () => {
|
|
118
|
-
if (this.workerPool) {
|
|
119
|
-
await this.workerPool.terminate();
|
|
120
|
-
this.workerPool = void 0;
|
|
121
|
-
}
|
|
122
|
-
console.info("[Platform-Node] ServiceWorker disposed");
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
console.info(
|
|
126
|
-
"[Platform-Node] ServiceWorker loaded with Worker threads and coordinated caches"
|
|
127
|
-
);
|
|
128
|
-
return instance;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get platform-specific default cache configuration for Node.js
|
|
132
|
-
*/
|
|
133
|
-
getDefaultCacheConfig() {
|
|
134
|
-
return {
|
|
135
|
-
pages: { type: "memory" },
|
|
136
|
-
// PostMessage cache for worker coordination
|
|
137
|
-
api: { type: "memory" },
|
|
138
|
-
static: { type: "memory" }
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* SUPPORTING UTILITY - Create cache storage optimized for Node.js
|
|
143
|
-
* Uses MemoryCache in main thread, PostMessageCache in workers
|
|
144
|
-
*/
|
|
145
|
-
async createCaches(config) {
|
|
146
|
-
const { isMainThread } = await import("worker_threads");
|
|
147
|
-
return new CustomCacheStorage((name) => {
|
|
148
|
-
if (isMainThread) {
|
|
149
|
-
return new MemoryCache(name, {
|
|
150
|
-
maxEntries: 1e3,
|
|
151
|
-
maxAge: 60 * 60 * 1e3
|
|
152
|
-
// 1 hour
|
|
153
|
-
});
|
|
154
|
-
} else {
|
|
155
|
-
return new PostMessageCache(name, {
|
|
156
|
-
maxEntries: 1e3,
|
|
157
|
-
maxAge: 60 * 60 * 1e3
|
|
158
|
-
// 1 hour
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* SUPPORTING UTILITY - Create HTTP server for Node.js
|
|
165
|
-
*/
|
|
166
|
-
createServer(handler, options = {}) {
|
|
167
|
-
const port = options.port ?? this.options.port;
|
|
168
|
-
const host = options.host ?? this.options.host;
|
|
169
|
-
const httpServer = Http.createServer(async (req, res) => {
|
|
170
|
-
try {
|
|
171
|
-
const url = `http://${req.headers.host}${req.url}`;
|
|
172
|
-
const request = new Request(url, {
|
|
173
|
-
method: req.method,
|
|
174
|
-
headers: req.headers,
|
|
175
|
-
body: req.method !== "GET" && req.method !== "HEAD" ? req : void 0
|
|
176
|
-
});
|
|
177
|
-
const response = await handler(request);
|
|
178
|
-
res.statusCode = response.status;
|
|
179
|
-
res.statusMessage = response.statusText;
|
|
180
|
-
response.headers.forEach((value, key) => {
|
|
181
|
-
res.setHeader(key, value);
|
|
182
|
-
});
|
|
183
|
-
if (response.body) {
|
|
184
|
-
const reader = response.body.getReader();
|
|
185
|
-
const pump = async () => {
|
|
186
|
-
const { done, value } = await reader.read();
|
|
187
|
-
if (done) {
|
|
188
|
-
res.end();
|
|
189
|
-
} else {
|
|
190
|
-
res.write(value);
|
|
191
|
-
await pump();
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
await pump();
|
|
195
|
-
} else {
|
|
196
|
-
res.end();
|
|
197
|
-
}
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.error("[Platform-Node] Request error:", error);
|
|
200
|
-
res.statusCode = 500;
|
|
201
|
-
res.setHeader("Content-Type", "text/plain");
|
|
202
|
-
res.end("Internal Server Error");
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
let isListening = false;
|
|
206
|
-
return {
|
|
207
|
-
async listen() {
|
|
208
|
-
return new Promise((resolve2) => {
|
|
209
|
-
httpServer.listen(port, host, () => {
|
|
210
|
-
console.info(`\u{1F680} Server running at http://${host}:${port}`);
|
|
211
|
-
isListening = true;
|
|
212
|
-
resolve2();
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
async close() {
|
|
217
|
-
return new Promise((resolve2) => {
|
|
218
|
-
httpServer.close(() => {
|
|
219
|
-
isListening = false;
|
|
220
|
-
resolve2();
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
},
|
|
224
|
-
address: () => ({ port, host }),
|
|
225
|
-
get url() {
|
|
226
|
-
return `http://${host}:${port}`;
|
|
227
|
-
},
|
|
228
|
-
get ready() {
|
|
229
|
-
return isListening;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Get filesystem root for File System Access API
|
|
235
|
-
*/
|
|
236
|
-
async getFileSystemRoot(name = "default") {
|
|
237
|
-
return await getDirectoryHandle(name);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Reload workers for hot reloading (called by CLI)
|
|
241
|
-
*/
|
|
242
|
-
async reloadWorkers(version) {
|
|
243
|
-
if (this.workerPool) {
|
|
244
|
-
await this.workerPool.reloadWorkers(version);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Dispose of platform resources
|
|
249
|
-
*/
|
|
250
|
-
async dispose() {
|
|
251
|
-
if (this.workerPool) {
|
|
252
|
-
await this.workerPool.terminate();
|
|
253
|
-
this.workerPool = void 0;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
function createNodePlatform(options) {
|
|
258
|
-
return new NodePlatform(options);
|
|
259
|
-
}
|
|
260
|
-
var platform_default = createNodePlatform;
|
|
261
|
-
export {
|
|
262
|
-
NodePlatform,
|
|
263
|
-
createNodePlatform,
|
|
264
|
-
platform_default as default
|
|
265
|
-
};
|