@b9g/platform-node 0.1.14-beta.0 → 0.1.15

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.14-beta.0",
3
+ "version": "0.1.15",
4
4
  "description": "Node.js platform adapter for Shovel with hot reloading and ESBuild integration",
5
5
  "keywords": [
6
6
  "shovel",
@@ -11,10 +11,10 @@
11
11
  "esbuild"
12
12
  ],
13
13
  "dependencies": {
14
- "@b9g/cache": "^0.2.0-beta.0",
15
- "@b9g/http-errors": "^0.2.0-beta.0",
16
- "@b9g/node-webworker": "^0.2.0-beta.1",
17
- "@b9g/platform": "^0.1.14-beta.0",
14
+ "@b9g/cache": "^0.2.0",
15
+ "@b9g/http-errors": "^0.2.0",
16
+ "@b9g/node-webworker": "^0.2.0",
17
+ "@b9g/platform": "^0.1.15",
18
18
  "@logtape/logtape": "^1.2.0"
19
19
  },
20
20
  "devDependencies": {
@@ -37,6 +37,14 @@
37
37
  "./index.js": {
38
38
  "types": "./src/index.d.ts",
39
39
  "import": "./src/index.js"
40
+ },
41
+ "./platform": {
42
+ "types": "./src/platform.d.ts",
43
+ "import": "./src/platform.js"
44
+ },
45
+ "./platform.js": {
46
+ "types": "./src/platform.d.ts",
47
+ "import": "./src/platform.js"
40
48
  }
41
49
  }
42
50
  }
package/src/index.d.ts CHANGED
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * Provides hot reloading, ESBuild integration, and optimized caching for Node.js environments.
5
5
  */
6
- import { CustomCacheStorage } from "@b9g/cache";
7
- import { CustomDirectoryStorage } from "@b9g/filesystem";
8
- import { BasePlatform, type PlatformConfig, type PlatformDefaults, type Handler, type Server, type ServerOptions, type PlatformESBuildConfig, type ProductionEntryPoints, ServiceWorkerPool, CustomLoggerStorage, CustomDatabaseStorage } from "@b9g/platform";
6
+ import { type PlatformDefaults, type Handler, type Server, type ServerOptions, type PlatformESBuildConfig, type EntryPoints, ServiceWorkerPool } from "@b9g/platform";
9
7
  import { type ShovelConfig } from "@b9g/platform/runtime";
10
- export interface NodePlatformOptions extends PlatformConfig {
8
+ export interface NodePlatformOptions {
11
9
  /** Port for development server (default: 3000) */
12
10
  port?: number;
13
11
  /** Host for development server (default: localhost) */
@@ -68,7 +66,7 @@ export declare class NodeServiceWorkerContainer extends EventTarget implements S
68
66
  * Node.js platform implementation
69
67
  * ServiceWorker entrypoint loader for Node.js with ESBuild VM system
70
68
  */
71
- export declare class NodePlatform extends BasePlatform {
69
+ export declare class NodePlatform {
72
70
  #private;
73
71
  readonly name: string;
74
72
  readonly serviceWorker: NodeServiceWorkerContainer;
@@ -97,39 +95,7 @@ export declare class NodePlatform extends BasePlatform {
97
95
  config?: ShovelConfig;
98
96
  };
99
97
  /**
100
- * Create cache storage for Node.js
101
- *
102
- * Default: MemoryCache (in-process LRU cache).
103
- * Override via shovel.json caches config.
104
- * Note: Used for dev/testing - production uses generated config module.
105
- */
106
- createCaches(): Promise<CustomCacheStorage>;
107
- /**
108
- * Create directory storage for Node.js
109
- *
110
- * Defaults:
111
- * - server: NodeFSDirectory at cwd (app files)
112
- * - public: NodeFSDirectory at cwd (static assets)
113
- * - tmp: NodeFSDirectory at OS temp dir
114
- *
115
- * Override via shovel.json directories config.
116
- */
117
- createDirectories(): Promise<CustomDirectoryStorage>;
118
- /**
119
- * Create logger storage for Node.js
120
- *
121
- * Uses LogTape for structured logging.
122
- */
123
- createLoggers(): Promise<CustomLoggerStorage>;
124
- /**
125
- * Create database storage for Node.js
126
- *
127
- * Returns undefined if no databases configured in shovel.json.
128
- * Supports SQLite via better-sqlite3.
129
- */
130
- createDatabases(configOverride?: NodePlatformOptions["config"]): CustomDatabaseStorage | undefined;
131
- /**
132
- * SUPPORTING UTILITY - Create HTTP server for Node.js
98
+ * Create HTTP server for Node.js
133
99
  */
134
100
  createServer(handler: Handler, options?: ServerOptions): Server;
135
101
  /**
@@ -139,13 +105,16 @@ export declare class NodePlatform extends BasePlatform {
139
105
  */
140
106
  reloadWorkers(entrypoint: string): Promise<void>;
141
107
  /**
142
- * Get production entry points for bundling.
108
+ * Get entry points for bundling.
109
+ *
110
+ * Development mode:
111
+ * - worker.js: Single worker with message loop (develop command acts as supervisor)
143
112
  *
144
- * Node.js produces two files:
113
+ * Production mode:
145
114
  * - index.js: Supervisor that spawns workers and owns the HTTP server
146
115
  * - worker.js: Worker that handles requests via message loop
147
116
  */
148
- getProductionEntryPoints(userEntryPath: string): ProductionEntryPoints;
117
+ getEntryPoints(userEntryPath: string, mode: "development" | "production"): EntryPoints;
149
118
  /**
150
119
  * Get Node.js-specific esbuild configuration
151
120
  *
package/src/index.js CHANGED
@@ -6,25 +6,16 @@ import { tmpdir } from "node:os";
6
6
  import * as Path from "node:path";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { CustomCacheStorage } from "@b9g/cache";
9
- import { MemoryCache } from "@b9g/cache/memory";
10
- import { CustomDirectoryStorage } from "@b9g/filesystem";
11
- import { NodeFSDirectory } from "@b9g/filesystem/node-fs";
12
9
  import { InternalServerError, isHTTPError } from "@b9g/http-errors";
13
10
  import {
14
- BasePlatform,
15
- ServiceWorkerPool,
16
- CustomLoggerStorage,
17
- CustomDatabaseStorage,
18
- createDatabaseFactory,
19
- mergeConfigWithDefaults
11
+ ServiceWorkerPool
20
12
  } from "@b9g/platform";
21
13
  import {
22
14
  ShovelServiceWorkerRegistration,
23
15
  kServiceWorker,
24
- createCacheFactory,
25
- createDirectoryFactory
16
+ createCacheFactory
26
17
  } from "@b9g/platform/runtime";
27
- import { MemoryCache as MemoryCache2 } from "@b9g/cache/memory";
18
+ import { MemoryCache } from "@b9g/cache/memory";
28
19
  var logger = getLogger(["shovel", "platform"]);
29
20
  var NodeServiceWorkerContainer = class extends EventTarget {
30
21
  #platform;
@@ -140,14 +131,12 @@ var NodeServiceWorkerContainer = class extends EventTarget {
140
131
  await this.#pool?.reloadWorkers(entrypoint);
141
132
  }
142
133
  };
143
- var NodePlatform = class extends BasePlatform {
134
+ var NodePlatform = class {
144
135
  name;
145
136
  serviceWorker;
146
137
  #options;
147
- #databaseStorage;
148
138
  #server;
149
139
  constructor(options = {}) {
150
- super(options);
151
140
  this.name = "node";
152
141
  const cwd = options.cwd || process.cwd();
153
142
  this.#options = {
@@ -195,66 +184,7 @@ var NodePlatform = class extends BasePlatform {
195
184
  return this.#options;
196
185
  }
197
186
  /**
198
- * Create cache storage for Node.js
199
- *
200
- * Default: MemoryCache (in-process LRU cache).
201
- * Override via shovel.json caches config.
202
- * Note: Used for dev/testing - production uses generated config module.
203
- */
204
- async createCaches() {
205
- const defaults = { default: { impl: MemoryCache } };
206
- const configs = mergeConfigWithDefaults(
207
- defaults,
208
- this.#options.config?.caches
209
- );
210
- return new CustomCacheStorage(createCacheFactory({ configs }));
211
- }
212
- /**
213
- * Create directory storage for Node.js
214
- *
215
- * Defaults:
216
- * - server: NodeFSDirectory at cwd (app files)
217
- * - public: NodeFSDirectory at cwd (static assets)
218
- * - tmp: NodeFSDirectory at OS temp dir
219
- *
220
- * Override via shovel.json directories config.
221
- */
222
- async createDirectories() {
223
- const defaults = {
224
- server: { impl: NodeFSDirectory, path: this.#options.cwd },
225
- public: { impl: NodeFSDirectory, path: this.#options.cwd },
226
- tmp: { impl: NodeFSDirectory, path: tmpdir() }
227
- };
228
- const configs = mergeConfigWithDefaults(
229
- defaults,
230
- this.#options.config?.directories
231
- );
232
- return new CustomDirectoryStorage(createDirectoryFactory(configs));
233
- }
234
- /**
235
- * Create logger storage for Node.js
236
- *
237
- * Uses LogTape for structured logging.
238
- */
239
- async createLoggers() {
240
- return new CustomLoggerStorage((categories) => getLogger(categories));
241
- }
242
- /**
243
- * Create database storage for Node.js
244
- *
245
- * Returns undefined if no databases configured in shovel.json.
246
- * Supports SQLite via better-sqlite3.
247
- */
248
- createDatabases(configOverride) {
249
- const config = configOverride ?? this.#options.config;
250
- if (config?.databases && Object.keys(config.databases).length > 0) {
251
- const factory = createDatabaseFactory(config.databases);
252
- return new CustomDatabaseStorage(factory);
253
- }
254
- return void 0;
255
- }
256
- /**
257
- * SUPPORTING UTILITY - Create HTTP server for Node.js
187
+ * Create HTTP server for Node.js
258
188
  */
259
189
  createServer(handler, options = {}) {
260
190
  const port = options.port ?? this.#options.port;
@@ -291,8 +221,15 @@ var NodePlatform = class extends BasePlatform {
291
221
  }
292
222
  } catch (error) {
293
223
  const err = error instanceof Error ? error : new Error(String(error));
294
- logger.error("Request error: {error}", { error: err });
295
224
  const httpError = isHTTPError(error) ? error : new InternalServerError(err.message, { cause: err });
225
+ if (httpError.status >= 500) {
226
+ logger.error("Request error: {error}", { error: err });
227
+ } else {
228
+ logger.warn("Request error: {status} {error}", {
229
+ status: httpError.status,
230
+ error: err
231
+ });
232
+ }
296
233
  const isDev = import.meta.env?.MODE !== "production";
297
234
  const response = httpError.toResponse(isDev);
298
235
  res.statusCode = response.status;
@@ -351,13 +288,45 @@ var NodePlatform = class extends BasePlatform {
351
288
  await this.serviceWorker.reloadWorkers(entrypoint);
352
289
  }
353
290
  /**
354
- * Get production entry points for bundling.
291
+ * Get entry points for bundling.
292
+ *
293
+ * Development mode:
294
+ * - worker.js: Single worker with message loop (develop command acts as supervisor)
355
295
  *
356
- * Node.js produces two files:
296
+ * Production mode:
357
297
  * - index.js: Supervisor that spawns workers and owns the HTTP server
358
298
  * - worker.js: Worker that handles requests via message loop
359
299
  */
360
- getProductionEntryPoints(userEntryPath) {
300
+ getEntryPoints(userEntryPath, mode) {
301
+ const workerCode = `// Node.js Worker
302
+ import {parentPort} from "node:worker_threads";
303
+ import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop} from "@b9g/platform/runtime";
304
+ import {config} from "shovel:config";
305
+
306
+ await configureLogging(config.logging);
307
+
308
+ // Initialize worker runtime (installs ServiceWorker globals)
309
+ const {registration, databases} = await initWorkerRuntime({config});
310
+
311
+ // Import user code (registers event handlers)
312
+ await import("${userEntryPath}");
313
+
314
+ // Run ServiceWorker lifecycle (stage from config.lifecycle if present)
315
+ await runLifecycle(registration, config.lifecycle?.stage);
316
+
317
+ // Start message loop for request handling, or signal ready and exit in lifecycle-only mode
318
+ if (config.lifecycle) {
319
+ parentPort?.postMessage({type: "ready"});
320
+ // Clean shutdown after lifecycle
321
+ if (databases) await databases.closeAll();
322
+ process.exit(0);
323
+ } else {
324
+ startWorkerMessageLoop({registration, databases});
325
+ }
326
+ `;
327
+ if (mode === "development") {
328
+ return { worker: workerCode };
329
+ }
361
330
  const supervisorCode = `// Node.js Production Supervisor
362
331
  import {Worker} from "@b9g/node-webworker";
363
332
  import {getLogger} from "@logtape/logtape";
@@ -390,35 +359,9 @@ const handleShutdown = async () => {
390
359
  };
391
360
  process.on("SIGINT", handleShutdown);
392
361
  process.on("SIGTERM", handleShutdown);
393
- `;
394
- const workerCode = `// Node.js Production Worker
395
- import {parentPort} from "node:worker_threads";
396
- import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop} from "@b9g/platform/runtime";
397
- import {config} from "shovel:config";
398
-
399
- await configureLogging(config.logging);
400
-
401
- // Initialize worker runtime (installs ServiceWorker globals)
402
- const {registration, databases} = await initWorkerRuntime({config});
403
-
404
- // Import user code (registers event handlers)
405
- await import("${userEntryPath}");
406
-
407
- // Run ServiceWorker lifecycle (stage from config.lifecycle if present)
408
- await runLifecycle(registration, config.lifecycle?.stage);
409
-
410
- // Start message loop for request handling, or signal ready and exit in lifecycle-only mode
411
- if (config.lifecycle) {
412
- parentPort?.postMessage({type: "ready"});
413
- // Clean shutdown after lifecycle
414
- if (databases) await databases.closeAll();
415
- process.exit(0);
416
- } else {
417
- startWorkerMessageLoop({registration, databases});
418
- }
419
362
  `;
420
363
  return {
421
- index: supervisorCode,
364
+ supervisor: supervisorCode,
422
365
  worker: workerCode
423
366
  };
424
367
  }
@@ -477,10 +420,6 @@ if (config.lifecycle) {
477
420
  async dispose() {
478
421
  await this.close();
479
422
  await this.serviceWorker.terminate();
480
- if (this.#databaseStorage) {
481
- await this.#databaseStorage.closeAll();
482
- this.#databaseStorage = void 0;
483
- }
484
423
  }
485
424
  // =========================================================================
486
425
  // Config Expression Method Overrides
@@ -494,7 +433,7 @@ if (config.lifecycle) {
494
433
  };
495
434
  var src_default = NodePlatform;
496
435
  export {
497
- MemoryCache2 as DefaultCache,
436
+ MemoryCache as DefaultCache,
498
437
  NodePlatform,
499
438
  NodeServiceWorkerContainer,
500
439
  src_default as default
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Node.js Platform Module
3
+ *
4
+ * Build-time and dev-time functions for Node.js.
5
+ * Runtime functions are in ./runtime.ts
6
+ */
7
+ import type { EntryPoints, ESBuildConfig, PlatformDefaults, DevServerOptions, DevServer } from "@b9g/platform/module";
8
+ export declare const name = "node";
9
+ /**
10
+ * Get entry points for bundling.
11
+ *
12
+ * Development mode:
13
+ * - worker.js: Single worker with message loop (develop command acts as supervisor)
14
+ *
15
+ * Production mode:
16
+ * - supervisor.js: Spawns workers and owns the HTTP server
17
+ * - worker.js: Handles requests via message loop
18
+ */
19
+ export declare function getEntryPoints(userEntryPath: string, mode: "development" | "production"): EntryPoints;
20
+ /**
21
+ * Get ESBuild configuration for Node.js.
22
+ *
23
+ * Note: Node.js doesn't support import.meta.env natively, so we alias it
24
+ * to process.env for compatibility with code that uses Vite-style env access.
25
+ */
26
+ export declare function getESBuildConfig(): ESBuildConfig;
27
+ /**
28
+ * Get platform defaults for config generation.
29
+ *
30
+ * Provides default directories (server, public, tmp) that work
31
+ * out of the box for Node.js deployments.
32
+ */
33
+ export declare function getDefaults(): PlatformDefaults;
34
+ /**
35
+ * Create a dev server using ServiceWorkerPool.
36
+ *
37
+ * Dynamically imports the platform class to keep heavy dependencies
38
+ * out of production bundles.
39
+ */
40
+ export declare function createDevServer(options: DevServerOptions): Promise<DevServer>;
@@ -0,0 +1,146 @@
1
+ /// <reference types="./platform.d.ts" />
2
+ // src/platform.ts
3
+ import { builtinModules } from "node:module";
4
+ import { getLogger } from "@logtape/logtape";
5
+ var logger = getLogger(["shovel", "platform"]);
6
+ var name = "node";
7
+ function getEntryPoints(userEntryPath, mode) {
8
+ const safePath = JSON.stringify(userEntryPath);
9
+ const workerCode = `// Node.js Worker
10
+ import {parentPort} from "node:worker_threads";
11
+ import {configureLogging, initWorkerRuntime, runLifecycle, startWorkerMessageLoop} from "@b9g/platform/runtime";
12
+ import {config} from "shovel:config";
13
+
14
+ await configureLogging(config.logging);
15
+
16
+ // Initialize worker runtime (installs ServiceWorker globals)
17
+ const {registration, databases} = await initWorkerRuntime({config});
18
+
19
+ // Import user code (registers event handlers)
20
+ await import(${safePath});
21
+
22
+ // Run ServiceWorker lifecycle (stage from config.lifecycle if present)
23
+ await runLifecycle(registration, config.lifecycle?.stage);
24
+
25
+ // Start message loop for request handling, or signal ready and exit in lifecycle-only mode
26
+ if (config.lifecycle) {
27
+ parentPort?.postMessage({type: "ready"});
28
+ // Clean shutdown after lifecycle
29
+ if (databases) await databases.closeAll();
30
+ process.exit(0);
31
+ } else {
32
+ startWorkerMessageLoop({registration, databases});
33
+ }
34
+ `;
35
+ if (mode === "development") {
36
+ return { worker: workerCode };
37
+ }
38
+ const supervisorCode = `// Node.js Production Supervisor
39
+ import {Worker} from "@b9g/node-webworker";
40
+ import {getLogger} from "@logtape/logtape";
41
+ import {configureLogging} from "@b9g/platform/runtime";
42
+ import NodePlatform from "@b9g/platform-node";
43
+ import {config} from "shovel:config";
44
+
45
+ await configureLogging(config.logging);
46
+ const logger = getLogger(["shovel", "platform"]);
47
+
48
+ logger.info("Starting production server", {port: config.port, workers: config.workers});
49
+
50
+ // Initialize platform and register ServiceWorker
51
+ // Override createWorker to use the imported Worker class (avoids require() issues with ESM)
52
+ const platform = new NodePlatform({port: config.port, host: config.host, workers: config.workers});
53
+ platform.createWorker = (entrypoint) => new Worker(entrypoint);
54
+ await platform.serviceWorker.register(new URL("./worker.js", import.meta.url).href);
55
+ await platform.serviceWorker.ready;
56
+
57
+ // Start HTTP server
58
+ await platform.listen();
59
+
60
+ logger.info("Server started", {port: config.port, host: config.host, workers: config.workers});
61
+
62
+ // Graceful shutdown
63
+ const handleShutdown = async () => {
64
+ logger.info("Shutting down");
65
+ await platform.close();
66
+ process.exit(0);
67
+ };
68
+ process.on("SIGINT", handleShutdown);
69
+ process.on("SIGTERM", handleShutdown);
70
+ `;
71
+ return {
72
+ supervisor: supervisorCode,
73
+ worker: workerCode
74
+ };
75
+ }
76
+ function getESBuildConfig() {
77
+ return {
78
+ platform: "node",
79
+ external: ["node:*", ...builtinModules],
80
+ define: {
81
+ // Node.js doesn't support import.meta.env, alias to process.env
82
+ "import.meta.env": "process.env"
83
+ }
84
+ };
85
+ }
86
+ function getDefaults() {
87
+ return {
88
+ caches: {
89
+ default: {
90
+ module: "@b9g/cache/memory",
91
+ export: "MemoryCache"
92
+ }
93
+ },
94
+ directories: {
95
+ server: {
96
+ module: "@b9g/filesystem/node-fs",
97
+ export: "NodeFSDirectory",
98
+ path: "[outdir]/server"
99
+ },
100
+ public: {
101
+ module: "@b9g/filesystem/node-fs",
102
+ export: "NodeFSDirectory",
103
+ path: "[outdir]/public"
104
+ },
105
+ tmp: {
106
+ module: "@b9g/filesystem/node-fs",
107
+ export: "NodeFSDirectory",
108
+ path: "[tmpdir]"
109
+ }
110
+ }
111
+ };
112
+ }
113
+ async function createDevServer(options) {
114
+ const { port, host, workerPath, workers = 1 } = options;
115
+ logger.info("Starting Node.js dev server", { workerPath, workers });
116
+ const { default: NodePlatform } = await import("./index.js");
117
+ const platform = new NodePlatform({
118
+ port,
119
+ host,
120
+ workers
121
+ });
122
+ await platform.serviceWorker.register(workerPath);
123
+ await platform.serviceWorker.ready;
124
+ await platform.listen();
125
+ logger.info("Node.js dev server ready");
126
+ const url = `http://${host}:${port}`;
127
+ return {
128
+ url,
129
+ async reload(newWorkerPath) {
130
+ logger.info("Reloading workers", { workerPath: newWorkerPath });
131
+ await platform.serviceWorker.reloadWorkers(newWorkerPath);
132
+ logger.info("Workers reloaded");
133
+ },
134
+ async close() {
135
+ logger.info("Stopping Node.js dev server");
136
+ await platform.dispose();
137
+ }
138
+ };
139
+ }
140
+ export {
141
+ createDevServer,
142
+ getDefaults,
143
+ getESBuildConfig,
144
+ getEntryPoints,
145
+ name
146
+ };