@b9g/platform-node 0.1.4 → 0.1.7

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 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.4",
3
+ "version": "0.1.7",
4
4
  "description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,13 +11,11 @@
11
11
  "esbuild"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/platform": "workspace:*",
15
- "@b9g/cache": "workspace:*",
16
- "@remix-run/node-fetch-server": "^0.11.0",
17
- "@aws-sdk/client-s3": "^3.0.0"
14
+ "@b9g/platform": "^0.1.5",
15
+ "@b9g/cache": "^0.1.4"
18
16
  },
19
17
  "devDependencies": {
20
- "@b9g/libuild": "^0.1.10",
18
+ "@b9g/libuild": "^0.1.11",
21
19
  "bun-types": "latest",
22
20
  "@types/node": "^18.0.0"
23
21
  },
@@ -29,14 +27,6 @@
29
27
  "types": "./src/index.d.ts",
30
28
  "import": "./src/index.js"
31
29
  },
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
30
  "./package.json": "./package.json",
41
31
  "./index": {
42
32
  "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
- export { NodePlatform, createNodePlatform, type NodePlatformOptions, } from "./platform.js";
7
- export type { Platform, CacheConfig, StaticConfig, Handler, Server, ServerOptions, } from "@b9g/platform";
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
- NodePlatform,
5
- createNodePlatform
6
- } from "./platform.js";
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
- createNodePlatform
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
- };