@b9g/platform-node 0.1.16 → 0.1.17

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.16",
3
+ "version": "0.1.17",
4
4
  "description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
5
5
  "keywords": [
6
6
  "shovel",
@@ -16,10 +16,10 @@
16
16
  "directory": "packages/platform-node"
17
17
  },
18
18
  "dependencies": {
19
- "@b9g/cache": "^0.2.1",
19
+ "@b9g/cache": "^0.2.2",
20
20
  "@b9g/http-errors": "^0.2.1",
21
21
  "@b9g/node-webworker": "^0.2.1",
22
- "@b9g/platform": "^0.1.16",
22
+ "@b9g/platform": "^0.1.17",
23
23
  "@logtape/logtape": "^1.2.0"
24
24
  },
25
25
  "devDependencies": {
package/src/index.d.ts CHANGED
@@ -111,8 +111,9 @@ export declare class NodePlatform {
111
111
  * - worker.js: Single worker with message loop (develop command acts as supervisor)
112
112
  *
113
113
  * Production mode:
114
- * - index.js: Supervisor that spawns workers and owns the HTTP server
115
- * - worker.js: Worker that handles requests via message loop
114
+ * - index.js: Supervisor that spawns workers and manages lifecycle
115
+ * - worker.js: Worker that either runs its own HTTP server (single-worker)
116
+ * or uses message loop (multi-worker, supervisor owns the HTTP server)
116
117
  */
117
118
  getEntryPoints(userEntryPath: string, mode: "development" | "production"): EntryPoints;
118
119
  /**
package/src/index.js CHANGED
@@ -192,11 +192,14 @@ var NodePlatform = class {
192
192
  const httpServer = HTTP.createServer(async (req, res) => {
193
193
  try {
194
194
  const url = `http://${req.headers.host}${req.url}`;
195
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
195
196
  const request = new Request(url, {
196
197
  method: req.method,
197
198
  headers: req.headers,
198
199
  // Node.js IncomingMessage can be used as body (it's a readable stream)
199
- body: req.method !== "GET" && req.method !== "HEAD" ? req : void 0
200
+ body: hasBody ? req : void 0,
201
+ // Required by Node.js when Request has a streaming body
202
+ duplex: hasBody ? "half" : void 0
200
203
  });
201
204
  const response = await handler(request);
202
205
  res.statusCode = response.status;
@@ -294,11 +297,12 @@ var NodePlatform = class {
294
297
  * - worker.js: Single worker with message loop (develop command acts as supervisor)
295
298
  *
296
299
  * Production mode:
297
- * - index.js: Supervisor that spawns workers and owns the HTTP server
298
- * - worker.js: Worker that handles requests via message loop
300
+ * - index.js: Supervisor that spawns workers and manages lifecycle
301
+ * - worker.js: Worker that either runs its own HTTP server (single-worker)
302
+ * or uses message loop (multi-worker, supervisor owns the HTTP server)
299
303
  */
300
304
  getEntryPoints(userEntryPath, mode) {
301
- const workerCode = `// Node.js Worker
305
+ const devWorkerCode = `// Node.js Development Worker
302
306
  import {parentPort} from "node:worker_threads";
303
307
  import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop} from "@b9g/platform/runtime";
304
308
  import {config} from "shovel:config";
@@ -306,7 +310,8 @@ import {config} from "shovel:config";
306
310
  await configureLogging(config.logging);
307
311
 
308
312
  // Initialize worker runtime (installs ServiceWorker globals)
309
- const {registration, databases} = await initWorkerRuntime({config});
313
+ // Single-worker dev mode uses direct cache (no PostMessage overhead)
314
+ const {registration, databases} = await initWorkerRuntime({config, usePostMessage: config.workers > 1});
310
315
 
311
316
  // Import user code (registers event handlers)
312
317
  await import("${userEntryPath}");
@@ -325,8 +330,63 @@ if (config.lifecycle) {
325
330
  }
326
331
  `;
327
332
  if (mode === "development") {
328
- return { worker: workerCode };
333
+ return { worker: devWorkerCode };
329
334
  }
335
+ const prodWorkerCode = `// Node.js Production Worker
336
+ import {parentPort} from "node:worker_threads";
337
+ import {getLogger} from "@logtape/logtape";
338
+ import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop, dispatchRequest} from "@b9g/platform/runtime";
339
+ import NodePlatform from "@b9g/platform-node";
340
+ import {config} from "shovel:config";
341
+
342
+ await configureLogging(config.logging);
343
+ const logger = getLogger(["shovel", "platform"]);
344
+
345
+ const directMode = config.workers <= 1;
346
+
347
+ // Track resources for shutdown
348
+ let server;
349
+ let databases;
350
+
351
+ // Register shutdown handler before async startup
352
+ parentPort?.on("message", async (event) => {
353
+ if (event.type === "shutdown") {
354
+ logger.info("Worker shutting down");
355
+ if (server) await server.close();
356
+ if (databases) await databases.closeAll();
357
+ parentPort?.postMessage({type: "shutdown-complete"});
358
+ }
359
+ });
360
+
361
+ // Initialize worker runtime (usePostMessage: false when worker owns the server)
362
+ const result = await initWorkerRuntime({config, usePostMessage: !directMode});
363
+ const registration = result.registration;
364
+ databases = result.databases;
365
+
366
+ // Import user code (registers event handlers)
367
+ await import("${userEntryPath}");
368
+
369
+ // Run ServiceWorker lifecycle (stage from config.lifecycle if present)
370
+ await runLifecycle(registration, config.lifecycle?.stage);
371
+
372
+ if (config.lifecycle) {
373
+ parentPort?.postMessage({type: "ready"});
374
+ if (databases) await databases.closeAll();
375
+ process.exit(0);
376
+ } else if (directMode) {
377
+ // Single worker: create own HTTP server
378
+ const platform = new NodePlatform({port: config.port, host: config.host});
379
+ server = platform.createServer(
380
+ (request) => dispatchRequest(registration, request),
381
+ );
382
+ await server.listen();
383
+ parentPort?.postMessage({type: "ready"});
384
+ logger.info("Worker started (direct mode)", {port: config.port});
385
+ } else {
386
+ // Multi-worker: use message loop (supervisor owns the HTTP server)
387
+ startWorkerMessageLoop({registration, databases});
388
+ }
389
+ `;
330
390
  const supervisorCode = `// Node.js Production Supervisor
331
391
  import {Worker} from "@b9g/node-webworker";
332
392
  import {getLogger} from "@logtape/logtape";
@@ -346,8 +406,10 @@ platform.createWorker = (entrypoint) => new Worker(entrypoint);
346
406
  await platform.serviceWorker.register(new URL("./worker.js", import.meta.url).href);
347
407
  await platform.serviceWorker.ready;
348
408
 
349
- // Start HTTP server
350
- await platform.listen();
409
+ // Single worker owns its own HTTP server \u2014 skip platform.listen()
410
+ if (config.workers > 1) {
411
+ await platform.listen();
412
+ }
351
413
 
352
414
  logger.info("Server started", {port: config.port, host: config.host, workers: config.workers});
353
415
 
@@ -362,7 +424,7 @@ process.on("SIGTERM", handleShutdown);
362
424
  `;
363
425
  return {
364
426
  supervisor: supervisorCode,
365
- worker: workerCode
427
+ worker: prodWorkerCode
366
428
  };
367
429
  }
368
430
  /**
@@ -390,7 +452,7 @@ process.on("SIGTERM", handleShutdown);
390
452
  getDefaults() {
391
453
  return {
392
454
  caches: {
393
- default: {
455
+ "*": {
394
456
  module: "@b9g/cache/memory",
395
457
  export: "MemoryCache"
396
458
  }
package/src/platform.d.ts CHANGED
@@ -13,8 +13,9 @@ export declare const name = "node";
13
13
  * - worker.js: Single worker with message loop (develop command acts as supervisor)
14
14
  *
15
15
  * Production mode:
16
- * - supervisor.js: Spawns workers and owns the HTTP server
17
- * - worker.js: Handles requests via message loop
16
+ * - supervisor.js: Spawns workers and manages lifecycle
17
+ * - worker.js: Worker that either runs its own HTTP server (single-worker)
18
+ * or uses message loop (multi-worker, supervisor owns the HTTP server)
18
19
  */
19
20
  export declare function getEntryPoints(userEntryPath: string, mode: "development" | "production"): EntryPoints;
20
21
  /**
package/src/platform.js CHANGED
@@ -6,7 +6,7 @@ var logger = getLogger(["shovel", "platform"]);
6
6
  var name = "node";
7
7
  function getEntryPoints(userEntryPath, mode) {
8
8
  const safePath = JSON.stringify(userEntryPath);
9
- const workerCode = `// Node.js Worker
9
+ const devWorkerCode = `// Node.js Development Worker
10
10
  import {parentPort} from "node:worker_threads";
11
11
  import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop} from "@b9g/platform/runtime";
12
12
  import {config} from "shovel:config";
@@ -14,7 +14,8 @@ import {config} from "shovel:config";
14
14
  await configureLogging(config.logging);
15
15
 
16
16
  // Initialize worker runtime (installs ServiceWorker globals)
17
- const {registration, databases} = await initWorkerRuntime({config});
17
+ // Single-worker dev mode uses direct cache (no PostMessage overhead)
18
+ const {registration, databases} = await initWorkerRuntime({config, usePostMessage: config.workers > 1});
18
19
 
19
20
  // Import user code (registers event handlers)
20
21
  await import(${safePath});
@@ -33,8 +34,63 @@ if (config.lifecycle) {
33
34
  }
34
35
  `;
35
36
  if (mode === "development") {
36
- return { worker: workerCode };
37
+ return { worker: devWorkerCode };
37
38
  }
39
+ const prodWorkerCode = `// Node.js Production Worker
40
+ import {parentPort} from "node:worker_threads";
41
+ import {getLogger} from "@logtape/logtape";
42
+ import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop, dispatchRequest} from "@b9g/platform/runtime";
43
+ import NodePlatform from "@b9g/platform-node";
44
+ import {config} from "shovel:config";
45
+
46
+ await configureLogging(config.logging);
47
+ const logger = getLogger(["shovel", "platform"]);
48
+
49
+ const directMode = config.workers <= 1;
50
+
51
+ // Track resources for shutdown
52
+ let server;
53
+ let databases;
54
+
55
+ // Register shutdown handler before async startup
56
+ parentPort?.on("message", async (event) => {
57
+ if (event.type === "shutdown") {
58
+ logger.info("Worker shutting down");
59
+ if (server) await server.close();
60
+ if (databases) await databases.closeAll();
61
+ parentPort?.postMessage({type: "shutdown-complete"});
62
+ }
63
+ });
64
+
65
+ // Initialize worker runtime (usePostMessage: false when worker owns the server)
66
+ const result = await initWorkerRuntime({config, usePostMessage: !directMode});
67
+ const registration = result.registration;
68
+ databases = result.databases;
69
+
70
+ // Import user code (registers event handlers)
71
+ await import(${safePath});
72
+
73
+ // Run ServiceWorker lifecycle (stage from config.lifecycle if present)
74
+ await runLifecycle(registration, config.lifecycle?.stage);
75
+
76
+ if (config.lifecycle) {
77
+ parentPort?.postMessage({type: "ready"});
78
+ if (databases) await databases.closeAll();
79
+ process.exit(0);
80
+ } else if (directMode) {
81
+ // Single worker: create own HTTP server
82
+ const platform = new NodePlatform({port: config.port, host: config.host});
83
+ server = platform.createServer(
84
+ (request) => dispatchRequest(registration, request),
85
+ );
86
+ await server.listen();
87
+ parentPort?.postMessage({type: "ready"});
88
+ logger.info("Worker started (direct mode)", {port: config.port});
89
+ } else {
90
+ // Multi-worker: use message loop (supervisor owns the HTTP server)
91
+ startWorkerMessageLoop({registration, databases});
92
+ }
93
+ `;
38
94
  const supervisorCode = `// Node.js Production Supervisor
39
95
  import {Worker} from "@b9g/node-webworker";
40
96
  import {getLogger} from "@logtape/logtape";
@@ -54,8 +110,10 @@ platform.createWorker = (entrypoint) => new Worker(entrypoint);
54
110
  await platform.serviceWorker.register(new URL("./worker.js", import.meta.url).href);
55
111
  await platform.serviceWorker.ready;
56
112
 
57
- // Start HTTP server
58
- await platform.listen();
113
+ // Single worker owns its own HTTP server \u2014 skip platform.listen()
114
+ if (config.workers > 1) {
115
+ await platform.listen();
116
+ }
59
117
 
60
118
  logger.info("Server started", {port: config.port, host: config.host, workers: config.workers});
61
119
 
@@ -70,7 +128,7 @@ process.on("SIGTERM", handleShutdown);
70
128
  `;
71
129
  return {
72
130
  supervisor: supervisorCode,
73
- worker: workerCode
131
+ worker: prodWorkerCode
74
132
  };
75
133
  }
76
134
  function getESBuildConfig() {
@@ -86,7 +144,7 @@ function getESBuildConfig() {
86
144
  function getDefaults() {
87
145
  return {
88
146
  caches: {
89
- default: {
147
+ "*": {
90
148
  module: "@b9g/cache/memory",
91
149
  export: "MemoryCache"
92
150
  }