@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 +3 -3
- package/src/platform.d.ts +8 -5
- package/src/platform.js +88 -173
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-node",
|
|
3
|
-
"version": "0.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": "
|
|
15
|
-
"@b9g/cache": "
|
|
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
|
|
26
|
+
private workerPool?;
|
|
27
27
|
private cacheStorage?;
|
|
28
|
-
private _dist?;
|
|
29
28
|
constructor(options?: NodePlatformOptions);
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
30
|
+
* Get filesystem directory handle
|
|
32
31
|
*/
|
|
33
|
-
|
|
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
|
-
*
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
22
|
+
* Handle Node.js-specific cache coordination
|
|
116
23
|
*/
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
*
|
|
30
|
+
* Enhanced termination with memory cache cleanup
|
|
144
31
|
*/
|
|
145
32
|
async terminate() {
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
51
|
+
FileSystemRegistry.register("node", new LocalBucket({
|
|
169
52
|
rootPath: this.options.cwd
|
|
170
53
|
}));
|
|
171
54
|
}
|
|
172
55
|
/**
|
|
173
|
-
*
|
|
56
|
+
* Get filesystem directory handle
|
|
174
57
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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.
|
|
192
|
-
await this.
|
|
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
|
|
77
|
+
"[Platform-Node] Creating NodeWorkerPool with entryPath:",
|
|
197
78
|
entryPath
|
|
198
79
|
);
|
|
199
|
-
this.
|
|
80
|
+
this.workerPool = new NodeWorkerPool(
|
|
200
81
|
this.cacheStorage,
|
|
201
|
-
|
|
202
|
-
|
|
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.
|
|
92
|
+
await this.workerPool.reloadWorkers(version);
|
|
207
93
|
const instance = {
|
|
208
|
-
runtime: this.
|
|
94
|
+
runtime: this.workerPool,
|
|
209
95
|
handleRequest: async (request) => {
|
|
210
|
-
if (!this.
|
|
211
|
-
throw new Error("
|
|
96
|
+
if (!this.workerPool) {
|
|
97
|
+
throw new Error("NodeWorkerPool not initialized");
|
|
212
98
|
}
|
|
213
|
-
return this.
|
|
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.
|
|
115
|
+
return this.workerPool?.ready ?? false;
|
|
230
116
|
},
|
|
231
117
|
dispose: async () => {
|
|
232
|
-
if (this.
|
|
233
|
-
await this.
|
|
234
|
-
this.
|
|
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
|
-
*
|
|
143
|
+
* Uses MemoryCache in main thread, PostMessageCache in workers
|
|
258
144
|
*/
|
|
259
145
|
async createCaches(config) {
|
|
260
|
-
const
|
|
146
|
+
const { isMainThread } = await import("worker_threads");
|
|
261
147
|
return new CustomCacheStorage((name) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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.
|
|
337
|
-
await this.
|
|
338
|
-
this.
|
|
251
|
+
if (this.workerPool) {
|
|
252
|
+
await this.workerPool.terminate();
|
|
253
|
+
this.workerPool = void 0;
|
|
339
254
|
}
|
|
340
255
|
}
|
|
341
256
|
};
|