@b9g/platform-node 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform-node",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,8 +11,8 @@
11
11
  "esbuild"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/platform": "^0.1.1",
15
- "@b9g/cache": "^0.1.1",
14
+ "@b9g/platform": "workspace:*",
15
+ "@b9g/cache": "workspace:*",
16
16
  "@remix-run/node-fetch-server": "^0.11.0",
17
17
  "@aws-sdk/client-s3": "^3.0.0"
18
18
  },
package/src/platform.d.ts CHANGED
@@ -23,14 +23,13 @@ export interface NodePlatformOptions extends PlatformConfig {
23
23
  export declare class NodePlatform extends BasePlatform {
24
24
  readonly name = "node";
25
25
  private options;
26
- private workerManager?;
26
+ private workerPool?;
27
27
  private cacheStorage?;
28
- private _dist?;
29
28
  constructor(options?: NodePlatformOptions);
30
29
  /**
31
- * Build artifacts filesystem (install-time only)
30
+ * Get filesystem directory handle
32
31
  */
33
- get distDir(): FileSystemDirectoryHandle;
32
+ getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
34
33
  /**
35
34
  * THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
36
35
  * Uses Worker threads with coordinated cache storage for isolation and standards compliance
@@ -42,7 +41,7 @@ export declare class NodePlatform extends BasePlatform {
42
41
  protected getDefaultCacheConfig(): CacheConfig;
43
42
  /**
44
43
  * SUPPORTING UTILITY - Create cache storage optimized for Node.js
45
- * Now uses the base class implementation with dynamic loading
44
+ * Uses MemoryCache in main thread, PostMessageCache in workers
46
45
  */
47
46
  createCaches(config?: CacheConfig): Promise<CustomCacheStorage>;
48
47
  /**
@@ -53,6 +52,10 @@ export declare class NodePlatform extends BasePlatform {
53
52
  * Get filesystem root for File System Access API
54
53
  */
55
54
  getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
55
+ /**
56
+ * Reload workers for hot reloading (called by CLI)
57
+ */
58
+ reloadWorkers(version?: number | string): Promise<void>;
56
59
  /**
57
60
  * Dispose of platform resources
58
61
  */
package/src/platform.js CHANGED
@@ -3,159 +3,42 @@
3
3
  import {
4
4
  BasePlatform
5
5
  } from "@b9g/platform";
6
- import { CustomCacheStorage, MemoryCacheManager, PostMessageCache } from "@b9g/cache";
7
- import { FileSystemRegistry, getFileSystemRoot, NodeFileSystemAdapter, NodeFileSystemDirectoryHandle } from "@b9g/filesystem";
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";
8
9
  import * as Http from "http";
9
10
  import * as Path from "path";
10
- import { Worker } from "worker_threads";
11
- import { fileURLToPath } from "url";
12
- var __filename = fileURLToPath(import.meta.url);
13
- var __dirname = Path.dirname(__filename);
14
- var WorkerManager = class {
15
- constructor(cacheStorage, options, workerCount = 1, entrypoint) {
16
- this.entrypoint = entrypoint;
17
- this.memoryCacheManager = new MemoryCacheManager();
18
- this.options = options;
19
- console.info(
20
- "[WorkerManager] Constructor called with entrypoint:",
21
- entrypoint
22
- );
23
- this.initWorkers(workerCount);
24
- }
25
- workers = [];
26
- currentWorker = 0;
27
- requestId = 0;
28
- pendingRequests = /* @__PURE__ */ new Map();
11
+ var NodeWorkerPool = class extends WorkerPool {
29
12
  memoryCacheManager;
30
- options;
31
- initWorkers(count) {
32
- for (let i = 0; i < count; i++) {
33
- this.createWorker();
34
- }
35
- }
36
- createWorker() {
37
- let workerScript;
38
- try {
39
- const workerUrl = import.meta.resolve("@b9g/shovel/worker.js");
40
- workerScript = fileURLToPath(workerUrl);
41
- } catch (error) {
42
- throw new Error(
43
- `Could not resolve @b9g/shovel/worker.js: ${error.message}`
44
- );
45
- }
46
- const worker = new Worker(workerScript);
47
- worker.on("message", (message) => {
48
- if (message.type?.startsWith("cache:")) {
49
- this.memoryCacheManager.handleMessage(worker, message);
50
- } else {
51
- this.handleWorkerMessage(message);
52
- }
53
- });
54
- worker.on("error", (error) => {
55
- console.error("[Platform-Node] Worker error:", error);
56
- });
57
- this.workers.push(worker);
58
- return worker;
59
- }
60
- handleWorkerMessage(message) {
61
- if (message.type === "response" && message.requestId) {
62
- const pending = this.pendingRequests.get(message.requestId);
63
- if (pending) {
64
- const response = new Response(message.response.body, {
65
- status: message.response.status,
66
- statusText: message.response.statusText,
67
- headers: message.response.headers
68
- });
69
- pending.resolve(response);
70
- this.pendingRequests.delete(message.requestId);
71
- }
72
- } else if (message.type === "error" && message.requestId) {
73
- const pending = this.pendingRequests.get(message.requestId);
74
- if (pending) {
75
- pending.reject(new Error(message.error));
76
- this.pendingRequests.delete(message.requestId);
77
- }
78
- } else if (message.type === "ready") {
79
- console.info(`[Platform-Node] ServiceWorker ready (v${message.version})`);
80
- } else if (message.type === "worker-ready") {
81
- console.info("[Platform-Node] Worker initialized");
82
- }
83
- }
84
- /**
85
- * Handle HTTP request using round-robin Worker selection
86
- */
87
- async handleRequest(request) {
88
- const worker = this.workers[this.currentWorker];
13
+ constructor(cacheStorage, poolOptions, appEntrypoint) {
14
+ super(cacheStorage, poolOptions, appEntrypoint);
15
+ this.memoryCacheManager = new MemoryCacheManager();
89
16
  console.info(
90
- `[WorkerManager] Dispatching to worker ${this.currentWorker} of ${this.workers.length}`
17
+ "[NodeWorkerPool] Initialized with entrypoint:",
18
+ appEntrypoint
91
19
  );
92
- this.currentWorker = (this.currentWorker + 1) % this.workers.length;
93
- const requestId = ++this.requestId;
94
- return new Promise((resolve2, reject) => {
95
- this.pendingRequests.set(requestId, { resolve: resolve2, reject });
96
- worker.postMessage({
97
- type: "request",
98
- request: {
99
- url: request.url,
100
- method: request.method,
101
- headers: Object.fromEntries(request.headers.entries()),
102
- body: request.body
103
- },
104
- requestId
105
- });
106
- setTimeout(() => {
107
- if (this.pendingRequests.has(requestId)) {
108
- this.pendingRequests.delete(requestId);
109
- reject(new Error("Request timeout"));
110
- }
111
- }, 3e4);
112
- });
113
20
  }
114
21
  /**
115
- * Reload ServiceWorker with new version (hot reload simulation)
22
+ * Handle Node.js-specific cache coordination
116
23
  */
117
- async reloadWorkers(version = Date.now()) {
118
- console.info(`[Platform-Node] Reloading ServiceWorker (v${version})`);
119
- const loadPromises = this.workers.map((worker) => {
120
- return new Promise((resolve2) => {
121
- const handleReady = (message) => {
122
- if (message.type === "ready" && message.version === version) {
123
- worker.off("message", handleReady);
124
- resolve2();
125
- }
126
- };
127
- console.info("[Platform-Node] Sending load message:", {
128
- version,
129
- entrypoint: this.entrypoint
130
- });
131
- worker.on("message", handleReady);
132
- worker.postMessage({
133
- type: "load",
134
- version,
135
- entrypoint: this.entrypoint
136
- });
137
- });
138
- });
139
- await Promise.all(loadPromises);
140
- console.info(`[Platform-Node] All Workers reloaded (v${version})`);
24
+ handleCacheMessage(message) {
25
+ if (message.type?.startsWith("cache:")) {
26
+ console.warn("[NodeWorkerPool] Cache coordination not fully implemented in abstraction");
27
+ }
141
28
  }
142
29
  /**
143
- * Graceful shutdown
30
+ * Enhanced termination with memory cache cleanup
144
31
  */
145
32
  async terminate() {
146
- const terminatePromises = this.workers.map((worker) => worker.terminate());
147
- await Promise.allSettled(terminatePromises);
33
+ await super.terminate();
148
34
  await this.memoryCacheManager.dispose();
149
- this.workers = [];
150
- this.pendingRequests.clear();
151
35
  }
152
36
  };
153
37
  var NodePlatform = class extends BasePlatform {
154
38
  name = "node";
155
39
  options;
156
- workerManager;
40
+ workerPool;
157
41
  cacheStorage;
158
- _dist;
159
42
  constructor(options = {}) {
160
43
  super(options);
161
44
  this.options = {
@@ -165,19 +48,17 @@ var NodePlatform = class extends BasePlatform {
165
48
  cwd: process.cwd(),
166
49
  ...options
167
50
  };
168
- FileSystemRegistry.register("node", new NodeFileSystemAdapter({
51
+ FileSystemRegistry.register("node", new LocalBucket({
169
52
  rootPath: this.options.cwd
170
53
  }));
171
54
  }
172
55
  /**
173
- * Build artifacts filesystem (install-time only)
56
+ * Get filesystem directory handle
174
57
  */
175
- get distDir() {
176
- if (!this._dist) {
177
- const distPath = Path.resolve(this.options.cwd, "dist");
178
- this._dist = new NodeFileSystemDirectoryHandle(distPath);
179
- }
180
- return this._dist;
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);
181
62
  }
182
63
  /**
183
64
  * THE MAIN JOB - Load and run a ServiceWorker-style entrypoint in Node.js
@@ -188,29 +69,34 @@ var NodePlatform = class extends BasePlatform {
188
69
  if (!this.cacheStorage) {
189
70
  this.cacheStorage = await this.createCaches(options.caches);
190
71
  }
191
- if (this.workerManager) {
192
- await this.workerManager.terminate();
72
+ if (this.workerPool) {
73
+ await this.workerPool.terminate();
193
74
  }
194
75
  const workerCount = options.workerCount || 1;
195
76
  console.info(
196
- "[Platform-Node] Creating WorkerManager with entryPath:",
77
+ "[Platform-Node] Creating NodeWorkerPool with entryPath:",
197
78
  entryPath
198
79
  );
199
- this.workerManager = new WorkerManager(
80
+ this.workerPool = new NodeWorkerPool(
200
81
  this.cacheStorage,
201
- this.options,
202
- workerCount,
82
+ {
83
+ workerCount,
84
+ requestTimeout: 3e4,
85
+ hotReload: this.options.hotReload,
86
+ cwd: this.options.cwd
87
+ },
203
88
  entryPath
204
89
  );
90
+ await this.workerPool.init();
205
91
  const version = Date.now();
206
- await this.workerManager.reloadWorkers(version);
92
+ await this.workerPool.reloadWorkers(version);
207
93
  const instance = {
208
- runtime: this.workerManager,
94
+ runtime: this.workerPool,
209
95
  handleRequest: async (request) => {
210
- if (!this.workerManager) {
211
- throw new Error("WorkerManager not initialized");
96
+ if (!this.workerPool) {
97
+ throw new Error("NodeWorkerPool not initialized");
212
98
  }
213
- return this.workerManager.handleRequest(request);
99
+ return this.workerPool.handleRequest(request);
214
100
  },
215
101
  install: async () => {
216
102
  console.info(
@@ -226,12 +112,12 @@ var NodePlatform = class extends BasePlatform {
226
112
  return [];
227
113
  },
228
114
  get ready() {
229
- return this.workerManager !== void 0;
115
+ return this.workerPool?.ready ?? false;
230
116
  },
231
117
  dispose: async () => {
232
- if (this.workerManager) {
233
- await this.workerManager.terminate();
234
- this.workerManager = void 0;
118
+ if (this.workerPool) {
119
+ await this.workerPool.terminate();
120
+ this.workerPool = void 0;
235
121
  }
236
122
  console.info("[Platform-Node] ServiceWorker disposed");
237
123
  }
@@ -254,16 +140,24 @@ var NodePlatform = class extends BasePlatform {
254
140
  }
255
141
  /**
256
142
  * SUPPORTING UTILITY - Create cache storage optimized for Node.js
257
- * Now uses the base class implementation with dynamic loading
143
+ * Uses MemoryCache in main thread, PostMessageCache in workers
258
144
  */
259
145
  async createCaches(config) {
260
- const cacheStorage = await super.createCaches(config);
146
+ const { isMainThread } = await import("worker_threads");
261
147
  return new CustomCacheStorage((name) => {
262
- return new PostMessageCache(name, {
263
- maxEntries: 1e3,
264
- maxSize: 50 * 1024 * 1024
265
- // 50MB
266
- });
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
+ }
267
161
  });
268
162
  }
269
163
  /**
@@ -308,34 +202,55 @@ var NodePlatform = class extends BasePlatform {
308
202
  res.end("Internal Server Error");
309
203
  }
310
204
  });
205
+ let isListening = false;
311
206
  return {
312
- listen: () => {
207
+ async listen() {
313
208
  return new Promise((resolve2) => {
314
209
  httpServer.listen(port, host, () => {
315
210
  console.info(`\u{1F680} Server running at http://${host}:${port}`);
211
+ isListening = true;
316
212
  resolve2();
317
213
  });
318
214
  });
319
215
  },
320
- close: () => new Promise((resolve2) => {
321
- httpServer.close(() => resolve2());
322
- }),
323
- address: () => ({ port, host })
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
+ }
324
231
  };
325
232
  }
326
233
  /**
327
234
  * Get filesystem root for File System Access API
328
235
  */
329
236
  async getFileSystemRoot(name = "default") {
330
- return await getFileSystemRoot(name);
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
+ }
331
246
  }
332
247
  /**
333
248
  * Dispose of platform resources
334
249
  */
335
250
  async dispose() {
336
- if (this.workerManager) {
337
- await this.workerManager.terminate();
338
- this.workerManager = void 0;
251
+ if (this.workerPool) {
252
+ await this.workerPool.terminate();
253
+ this.workerPool = void 0;
339
254
  }
340
255
  }
341
256
  };