@b9g/platform-node 0.1.10 → 0.1.12

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 CHANGED
@@ -8,7 +8,7 @@ Node.js platform adapter for Shovel. Runs ServiceWorker applications on Node.js
8
8
  - Hot module reloading for development via VM module system
9
9
  - Worker thread pool for concurrent request handling
10
10
  - Memory and filesystem cache backends
11
- - File System Access API implementation via NodeBucket
11
+ - File System Access API implementation via NodeDirectory
12
12
  - ServiceWorker lifecycle support (install, activate, fetch events)
13
13
 
14
14
  ## Installation
@@ -112,7 +112,7 @@ Loads and runs a ServiceWorker entrypoint.
112
112
  Configured via `caches` option:
113
113
 
114
114
  - `memory`: In-memory caching (MemoryCache)
115
- - `filesystem`: File-based caching (NodeBucket)
115
+ - `filesystem`: File-based caching (NodeDirectory)
116
116
 
117
117
  ## Worker Thread Architecture
118
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/platform-node",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,10 +11,13 @@
11
11
  "esbuild"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/platform": "^0.1.10"
14
+ "@b9g/cache": "^0.1.5",
15
+ "@b9g/http-errors": "^0.1.5",
16
+ "@b9g/platform": "^0.1.12",
17
+ "@logtape/logtape": "^1.2.0"
15
18
  },
16
19
  "devDependencies": {
17
- "@b9g/libuild": "^0.1.11",
20
+ "@b9g/libuild": "^0.1.18",
18
21
  "bun-types": "latest",
19
22
  "@types/node": "^18.0.0"
20
23
  },
package/src/index.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Provides hot reloading, ESBuild integration, and optimized caching for Node.js environments.
5
5
  */
6
- import { BasePlatform, PlatformConfig, Handler, Server, ServerOptions, ServiceWorkerOptions, ServiceWorkerInstance, ServiceWorkerPool } from "@b9g/platform";
6
+ import { BasePlatform, type PlatformConfig, type Handler, type Server, type ServerOptions, type ServiceWorkerOptions, type ServiceWorkerInstance, type EntryWrapperOptions, type PlatformEsbuildConfig, ServiceWorkerPool } from "@b9g/platform";
7
7
  import { CustomCacheStorage } from "@b9g/cache";
8
- export type { Platform, Handler, Server, ServerOptions } from "@b9g/platform";
8
+ import { CustomDirectoryStorage } from "@b9g/filesystem";
9
9
  export interface NodePlatformOptions extends PlatformConfig {
10
10
  /** Port for development server (default: 3000) */
11
11
  port?: number;
@@ -13,6 +13,8 @@ export interface NodePlatformOptions extends PlatformConfig {
13
13
  host?: string;
14
14
  /** Working directory for file resolution */
15
15
  cwd?: string;
16
+ /** Number of worker threads (default: 1) */
17
+ workers?: number;
16
18
  }
17
19
  /**
18
20
  * Node.js platform implementation
@@ -37,10 +39,13 @@ export declare class NodePlatform extends BasePlatform {
37
39
  */
38
40
  loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
39
41
  /**
40
- * SUPPORTING UTILITY - Create cache storage
41
- * Uses config from package.json shovel field
42
+ * Create cache storage (in-memory by default)
42
43
  */
43
44
  createCaches(): Promise<CustomCacheStorage>;
45
+ /**
46
+ * Create directory storage for the given base directory
47
+ */
48
+ createDirectories(baseDir: string): CustomDirectoryStorage;
44
49
  /**
45
50
  * SUPPORTING UTILITY - Create HTTP server for Node.js
46
51
  */
@@ -50,6 +55,25 @@ export declare class NodePlatform extends BasePlatform {
50
55
  * @param entrypoint - Path to the new entrypoint (hashed filename)
51
56
  */
52
57
  reloadWorkers(entrypoint: string): Promise<void>;
58
+ /**
59
+ * Get virtual entry wrapper for Node.js
60
+ *
61
+ * Returns production server entry template that uses:
62
+ * - shovel:config virtual module for configuration
63
+ * - Worker threads via ServiceWorkerPool for multi-worker scaling
64
+ * - Platform's loadServiceWorker and createServer methods
65
+ *
66
+ * The template is a real .ts file (entry-template.ts) for better
67
+ * IDE support and linting. It's imported with {type: "text"}.
68
+ */
69
+ getEntryWrapper(_entryPath: string, _options?: EntryWrapperOptions): string;
70
+ /**
71
+ * Get Node.js-specific esbuild configuration
72
+ *
73
+ * Note: Node.js doesn't support import.meta.env natively, so we alias it
74
+ * to process.env for compatibility with code that uses Vite-style env access.
75
+ */
76
+ getEsbuildConfig(): PlatformEsbuildConfig;
53
77
  /**
54
78
  * Dispose of platform resources
55
79
  */
package/src/index.js CHANGED
@@ -4,31 +4,87 @@ import {
4
4
  BasePlatform,
5
5
  ServiceWorkerPool,
6
6
  SingleThreadedRuntime,
7
- loadConfig,
8
- createCacheFactory
7
+ CustomLoggerStorage
9
8
  } from "@b9g/platform";
10
9
  import { CustomCacheStorage } from "@b9g/cache";
10
+ import { CustomDirectoryStorage } from "@b9g/filesystem";
11
+ import { MemoryCache } from "@b9g/cache/memory";
12
+ import { NodeDirectory } from "@b9g/filesystem/node";
11
13
  import { InternalServerError, isHTTPError } from "@b9g/http-errors";
12
14
  import * as HTTP from "http";
13
15
  import * as Path from "path";
14
16
  import { getLogger } from "@logtape/logtape";
15
- var logger = getLogger(["platform-node"]);
17
+ var entryTemplate = `// Node.js Production Server Entry
18
+ // This file is imported as text and used as the entry wrapper template
19
+ import {getLogger} from "@logtape/logtape";
20
+ import {configureLogging} from "@b9g/platform/runtime";
21
+ import {config} from "shovel:config"; // Virtual module - resolved at build time
22
+ import Platform from "@b9g/platform-node";
23
+
24
+ // Configure logging before anything else
25
+ await configureLogging(config.logging);
26
+
27
+ const logger = getLogger(["platform"]);
28
+
29
+ // Configuration from shovel:config (with process.env fallbacks baked in)
30
+ const PORT = config.port;
31
+ const HOST = config.host;
32
+ const WORKER_COUNT = config.workers;
33
+
34
+ logger.info("Starting production server", {});
35
+ logger.info("Workers", {count: WORKER_COUNT});
36
+
37
+ // Create platform instance
38
+ const platform = new Platform();
39
+
40
+ // Get the path to the user's ServiceWorker code
41
+ const userCodeURL = new URL("./server.js", import.meta.url);
42
+ const userCodePath = userCodeURL.pathname;
43
+
44
+ // Load ServiceWorker with worker pool
45
+ const serviceWorker = await platform.loadServiceWorker(userCodePath, {
46
+ workerCount: WORKER_COUNT,
47
+ });
48
+
49
+ // Create HTTP server
50
+ const server = platform.createServer(serviceWorker.handleRequest, {
51
+ port: PORT,
52
+ host: HOST,
53
+ });
54
+
55
+ await server.listen();
56
+ logger.info("Server running", {url: \`http://\${HOST}:\${PORT}\`});
57
+ logger.info("Load balancing", {workers: WORKER_COUNT});
58
+
59
+ // Graceful shutdown
60
+ const shutdown = async () => {
61
+ logger.info("Shutting down server", {});
62
+ await serviceWorker.dispose();
63
+ await platform.dispose();
64
+ await server.close();
65
+ logger.info("Server stopped", {});
66
+ process.exit(0);
67
+ };
68
+
69
+ process.on("SIGINT", shutdown);
70
+ process.on("SIGTERM", shutdown);
71
+ `;
72
+ var logger = getLogger(["platform"]);
16
73
  var NodePlatform = class extends BasePlatform {
17
74
  name;
18
75
  #options;
19
76
  #workerPool;
20
77
  #singleThreadedRuntime;
21
78
  #cacheStorage;
22
- #config;
79
+ #directoryStorage;
23
80
  constructor(options = {}) {
24
81
  super(options);
25
82
  this.name = "node";
26
83
  const cwd = options.cwd || process.cwd();
27
- this.#config = loadConfig(cwd);
28
- logger.info("Loaded configuration", { config: this.#config });
29
84
  this.#options = {
30
- port: options.port ?? this.#config.port,
31
- host: options.host ?? this.#config.host,
85
+ port: options.port ?? 3e3,
86
+ host: options.host ?? "localhost",
87
+ workers: options.workers ?? 1,
32
88
  cwd,
33
89
  ...options
34
90
  };
@@ -53,7 +109,7 @@ var NodePlatform = class extends BasePlatform {
53
109
  * Uses Worker threads with coordinated cache storage for isolation and standards compliance
54
110
  */
55
111
  async loadServiceWorker(entrypoint, options = {}) {
56
- const workerCount = options.workerCount ?? this.#config.workers ?? 1;
112
+ const workerCount = options.workerCount ?? this.#options.workers;
57
113
  if (workerCount === 1 && !options.hotReload) {
58
114
  return this.#loadServiceWorkerDirect(entrypoint, options);
59
115
  }
@@ -69,6 +125,9 @@ var NodePlatform = class extends BasePlatform {
69
125
  if (!this.#cacheStorage) {
70
126
  this.#cacheStorage = await this.createCaches();
71
127
  }
128
+ if (!this.#directoryStorage) {
129
+ this.#directoryStorage = this.createDirectories(entryDir);
130
+ }
72
131
  if (this.#singleThreadedRuntime) {
73
132
  await this.#singleThreadedRuntime.terminate();
74
133
  }
@@ -78,12 +137,12 @@ var NodePlatform = class extends BasePlatform {
78
137
  }
79
138
  logger.info("Creating single-threaded ServiceWorker runtime", { entryPath });
80
139
  this.#singleThreadedRuntime = new SingleThreadedRuntime({
81
- baseDir: entryDir,
82
- cacheStorage: this.#cacheStorage,
83
- config: this.#config
140
+ caches: this.#cacheStorage,
141
+ directories: this.#directoryStorage,
142
+ loggers: new CustomLoggerStorage((...cats) => getLogger(cats))
84
143
  });
85
144
  await this.#singleThreadedRuntime.init();
86
- await this.#singleThreadedRuntime.loadEntrypoint(entryPath);
145
+ await this.#singleThreadedRuntime.load(entryPath);
87
146
  const runtime = this.#singleThreadedRuntime;
88
147
  const platform = this;
89
148
  const instance = {
@@ -143,7 +202,8 @@ var NodePlatform = class extends BasePlatform {
143
202
  },
144
203
  entryPath,
145
204
  this.#cacheStorage,
146
- this.#config
205
+ {}
206
+ // Empty config - use defaults
147
207
  );
148
208
  await this.#workerPool.init();
149
209
  await this.#workerPool.reloadWorkers(entryPath);
@@ -184,11 +244,26 @@ var NodePlatform = class extends BasePlatform {
184
244
  return instance;
185
245
  }
186
246
  /**
187
- * SUPPORTING UTILITY - Create cache storage
188
- * Uses config from package.json shovel field
247
+ * Create cache storage (in-memory by default)
189
248
  */
190
249
  async createCaches() {
191
- return new CustomCacheStorage(createCacheFactory({ config: this.#config }));
250
+ return new CustomCacheStorage((name) => new MemoryCache(name));
251
+ }
252
+ /**
253
+ * Create directory storage for the given base directory
254
+ */
255
+ createDirectories(baseDir) {
256
+ return new CustomDirectoryStorage((name) => {
257
+ let dirPath;
258
+ if (name === "static") {
259
+ dirPath = Path.resolve(baseDir, "../static");
260
+ } else if (name === "server") {
261
+ dirPath = baseDir;
262
+ } else {
263
+ dirPath = Path.resolve(baseDir, `../${name}`);
264
+ }
265
+ return Promise.resolve(new NodeDirectory(dirPath));
266
+ });
192
267
  }
193
268
  /**
194
269
  * SUPPORTING UTILITY - Create HTTP server for Node.js
@@ -228,10 +303,7 @@ var NodePlatform = class extends BasePlatform {
228
303
  }
229
304
  } catch (error) {
230
305
  const err = error instanceof Error ? error : new Error(String(error));
231
- logger.error("Request error", {
232
- error: err.message,
233
- stack: err.stack
234
- });
306
+ logger.error("Request error: {error}", { error: err });
235
307
  const httpError = isHTTPError(error) ? error : new InternalServerError(err.message, { cause: err });
236
308
  const isDev = import.meta.env?.MODE !== "production";
237
309
  const response = httpError.toResponse(isDev);
@@ -290,9 +362,39 @@ var NodePlatform = class extends BasePlatform {
290
362
  if (this.#workerPool) {
291
363
  await this.#workerPool.reloadWorkers(entrypoint);
292
364
  } else if (this.#singleThreadedRuntime) {
293
- await this.#singleThreadedRuntime.reloadWorkers(entrypoint);
365
+ await this.#singleThreadedRuntime.load(entrypoint);
294
366
  }
295
367
  }
368
+ /**
369
+ * Get virtual entry wrapper for Node.js
370
+ *
371
+ * Returns production server entry template that uses:
372
+ * - shovel:config virtual module for configuration
373
+ * - Worker threads via ServiceWorkerPool for multi-worker scaling
374
+ * - Platform's loadServiceWorker and createServer methods
375
+ *
376
+ * The template is a real .ts file (entry-template.ts) for better
377
+ * IDE support and linting. It's imported with {type: "text"}.
378
+ */
379
+ getEntryWrapper(_entryPath, _options) {
380
+ return entryTemplate;
381
+ }
382
+ /**
383
+ * Get Node.js-specific esbuild configuration
384
+ *
385
+ * Note: Node.js doesn't support import.meta.env natively, so we alias it
386
+ * to process.env for compatibility with code that uses Vite-style env access.
387
+ */
388
+ getEsbuildConfig() {
389
+ return {
390
+ platform: "node",
391
+ external: ["node:*"],
392
+ define: {
393
+ // Node.js doesn't support import.meta.env, alias to process.env
394
+ "import.meta.env": "process.env"
395
+ }
396
+ };
397
+ }
296
398
  /**
297
399
  * Dispose of platform resources
298
400
  */