@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 +3 -3
- package/src/index.d.ts +3 -2
- package/src/index.js +72 -10
- package/src/platform.d.ts +3 -2
- package/src/platform.js +65 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-node",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
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.
|
|
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
|
|
115
|
-
* - worker.js: Worker that
|
|
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:
|
|
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
|
|
298
|
-
* - worker.js: Worker that
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
350
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
17
|
-
* - worker.js:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
58
|
-
|
|
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:
|
|
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
|
-
|
|
147
|
+
"*": {
|
|
90
148
|
module: "@b9g/cache/memory",
|
|
91
149
|
export: "MemoryCache"
|
|
92
150
|
}
|