@b9g/platform-bun 0.1.8 → 0.1.10

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-bun",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Bun platform adapter for Shovel with hot reloading and built-in TypeScript/JSX support",
5
5
  "keywords": [
6
6
  "shovel",
@@ -12,11 +12,14 @@
12
12
  "jsx"
13
13
  ],
14
14
  "dependencies": {
15
- "@b9g/platform": "^0.1.10",
16
- "@b9g/assets": "^0.1.13"
15
+ "@b9g/assets": "^0.1.15",
16
+ "@b9g/cache": "^0.1.5",
17
+ "@b9g/http-errors": "^0.1.5",
18
+ "@b9g/platform": "^0.1.12",
19
+ "@logtape/logtape": "^1.2.0"
17
20
  },
18
21
  "devDependencies": {
19
- "@b9g/libuild": "^0.1.11",
22
+ "@b9g/libuild": "^0.1.18",
20
23
  "bun-types": "latest"
21
24
  },
22
25
  "type": "module",
package/src/index.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Provides built-in TypeScript/JSX support and simplified server setup for Bun 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, ServiceWorkerOptions, ServiceWorkerInstance, } from "@b9g/platform";
8
+ import { CustomDirectoryStorage } from "@b9g/filesystem";
9
9
  export interface BunPlatformOptions extends PlatformConfig {
10
10
  /** Port for development server (default: 3000) */
11
11
  port?: number;
@@ -13,6 +13,8 @@ export interface BunPlatformOptions 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
  * Bun platform implementation
@@ -32,10 +34,13 @@ export declare class BunPlatform extends BasePlatform {
32
34
  get workerPool(): ServiceWorkerPool | undefined;
33
35
  set workerPool(pool: ServiceWorkerPool | undefined);
34
36
  /**
35
- * Create cache storage
36
- * Uses config from package.json shovel field
37
+ * Create cache storage (in-memory by default)
37
38
  */
38
39
  createCaches(): Promise<CustomCacheStorage>;
40
+ /**
41
+ * Create directory storage for the given base directory
42
+ */
43
+ createDirectories(baseDir: string): CustomDirectoryStorage;
39
44
  /**
40
45
  * Create HTTP server using Bun.serve
41
46
  */
@@ -50,6 +55,25 @@ export declare class BunPlatform 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 Bun
60
+ *
61
+ * Returns production server entry template that uses:
62
+ * - shovel:config virtual module for configuration
63
+ * - Bun.serve with reusePort for multi-worker scaling
64
+ * - Direct import of user's server code
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 Bun-specific esbuild configuration
72
+ *
73
+ * Note: Bun natively supports import.meta.env, so no define alias is needed.
74
+ * We use platform: "node" since Bun is Node-compatible for module resolution.
75
+ */
76
+ getEsbuildConfig(): PlatformEsbuildConfig;
53
77
  /**
54
78
  * Dispose of platform resources
55
79
  */
package/src/index.js CHANGED
@@ -4,29 +4,97 @@ 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
- import * as Path from "path";
13
14
  import { getLogger } from "@logtape/logtape";
14
- var logger = getLogger(["platform-bun"]);
15
+ import * as Path from "path";
16
+ var entryTemplate = `// Bun Production Server Entry
17
+ import {getLogger} from "@logtape/logtape";
18
+ import {configureLogging} from "@b9g/platform/runtime";
19
+ import {config} from "shovel:config"; // Virtual module - resolved at build time
20
+ import BunPlatform from "@b9g/platform-bun";
21
+
22
+ // Configure logging before anything else
23
+ await configureLogging(config.logging);
24
+
25
+ const logger = getLogger(["platform"]);
26
+
27
+ // Configuration from shovel:config
28
+ const PORT = config.port;
29
+ const HOST = config.host;
30
+ const WORKERS = config.workers;
31
+ const isWorker = !Bun.isMainThread;
32
+
33
+ // Worker thread entry - each worker runs its own Bun.serve with reusePort
34
+ if (isWorker) {
35
+ const platform = new BunPlatform({port: PORT, host: HOST, workers: 1});
36
+ const userCodePath = new URL("./server.js", import.meta.url).pathname;
37
+ const serviceWorker = await platform.loadServiceWorker(userCodePath);
38
+
39
+ Bun.serve({
40
+ port: PORT,
41
+ hostname: HOST,
42
+ reusePort: true,
43
+ fetch: serviceWorker.handleRequest,
44
+ });
45
+
46
+ logger.info("Worker started", {port: PORT, thread: Bun.threadId});
47
+ } else {
48
+ // Main thread - spawn worker threads, each binds to same port with reusePort
49
+ if (WORKERS > 1) {
50
+ for (let i = 0; i < WORKERS; i++) {
51
+ new Worker(import.meta.path);
52
+ }
53
+ logger.info("Spawned workers", {count: WORKERS, port: PORT});
54
+ } else {
55
+ // Single worker mode - run directly in main thread
56
+ const platform = new BunPlatform({port: PORT, host: HOST, workers: 1});
57
+ const userCodePath = new URL("./server.js", import.meta.url).pathname;
58
+ const serviceWorker = await platform.loadServiceWorker(userCodePath);
59
+
60
+ const server = platform.createServer(serviceWorker.handleRequest, {
61
+ port: PORT,
62
+ host: HOST,
63
+ });
64
+ await server.listen();
65
+
66
+ logger.info("Server started", {url: server.url});
67
+
68
+ // Graceful shutdown
69
+ const shutdown = async () => {
70
+ logger.info("Shutting down");
71
+ await serviceWorker.dispose();
72
+ await platform.dispose();
73
+ await server.close();
74
+ process.exit(0);
75
+ };
76
+
77
+ process.on("SIGINT", shutdown);
78
+ process.on("SIGTERM", shutdown);
79
+ }
80
+ }
81
+ `;
82
+ var logger = getLogger(["platform"]);
15
83
  var BunPlatform = class extends BasePlatform {
16
84
  name;
17
85
  #options;
18
86
  #workerPool;
19
87
  #singleThreadedRuntime;
20
88
  #cacheStorage;
21
- #config;
89
+ #directoryStorage;
22
90
  constructor(options = {}) {
23
91
  super(options);
24
92
  this.name = "bun";
25
93
  const cwd = options.cwd || process.cwd();
26
- this.#config = loadConfig(cwd);
27
94
  this.#options = {
28
- port: options.port ?? this.#config.port,
29
- host: options.host ?? this.#config.host,
95
+ port: options.port ?? 3e3,
96
+ host: options.host ?? "localhost",
97
+ workers: options.workers ?? 1,
30
98
  cwd,
31
99
  ...options
32
100
  };
@@ -47,46 +115,61 @@ var BunPlatform = class extends BasePlatform {
47
115
  this.#workerPool = pool;
48
116
  }
49
117
  /**
50
- * Create cache storage
51
- * Uses config from package.json shovel field
118
+ * Create cache storage (in-memory by default)
52
119
  */
53
120
  async createCaches() {
54
- return new CustomCacheStorage(createCacheFactory({ config: this.#config }));
121
+ return new CustomCacheStorage((name) => new MemoryCache(name));
122
+ }
123
+ /**
124
+ * Create directory storage for the given base directory
125
+ */
126
+ createDirectories(baseDir) {
127
+ return new CustomDirectoryStorage((name) => {
128
+ let dirPath;
129
+ if (name === "static") {
130
+ dirPath = Path.resolve(baseDir, "../static");
131
+ } else if (name === "server") {
132
+ dirPath = baseDir;
133
+ } else {
134
+ dirPath = Path.resolve(baseDir, `../${name}`);
135
+ }
136
+ return Promise.resolve(new NodeDirectory(dirPath));
137
+ });
55
138
  }
56
139
  /**
57
140
  * Create HTTP server using Bun.serve
58
141
  */
59
142
  createServer(handler, options = {}) {
60
- const port = options.port ?? this.#options.port;
143
+ const requestedPort = options.port ?? this.#options.port;
61
144
  const hostname = options.host ?? this.#options.host;
62
145
  const server = Bun.serve({
63
- port,
146
+ port: requestedPort,
64
147
  hostname,
65
148
  async fetch(request) {
66
149
  try {
67
150
  return await handler(request);
68
151
  } catch (error) {
69
152
  const err = error instanceof Error ? error : new Error(String(error));
70
- logger.error("Request error", {
71
- error: err.message,
72
- stack: err.stack
73
- });
153
+ logger.error("Request error: {error}", { error: err });
74
154
  const httpError = isHTTPError(error) ? error : new InternalServerError(err.message, { cause: err });
75
155
  const isDev = import.meta.env?.MODE !== "production";
76
156
  return httpError.toResponse(isDev);
77
157
  }
78
158
  }
79
159
  });
160
+ const actualPort = server.port;
80
161
  return {
81
162
  async listen() {
82
- logger.info("Bun server running", { url: `http://${hostname}:${port}` });
163
+ logger.info("Bun server running", {
164
+ url: `http://${hostname}:${actualPort}`
165
+ });
83
166
  },
84
167
  async close() {
85
168
  server.stop();
86
169
  },
87
- address: () => ({ port, host: hostname }),
170
+ address: () => ({ port: actualPort, host: hostname }),
88
171
  get url() {
89
- return `http://${hostname}:${port}`;
172
+ return `http://${hostname}:${actualPort}`;
90
173
  },
91
174
  get ready() {
92
175
  return true;
@@ -98,7 +181,7 @@ var BunPlatform = class extends BasePlatform {
98
181
  * Uses native Web Workers with the common WorkerPool
99
182
  */
100
183
  async loadServiceWorker(entrypoint, options = {}) {
101
- const workerCount = options.workerCount ?? this.#config.workers ?? 1;
184
+ const workerCount = options.workerCount ?? this.#options.workers;
102
185
  if (workerCount === 1 && !options.hotReload) {
103
186
  return this.#loadServiceWorkerDirect(entrypoint, options);
104
187
  }
@@ -114,6 +197,9 @@ var BunPlatform = class extends BasePlatform {
114
197
  if (!this.#cacheStorage) {
115
198
  this.#cacheStorage = await this.createCaches();
116
199
  }
200
+ if (!this.#directoryStorage) {
201
+ this.#directoryStorage = this.createDirectories(entryDir);
202
+ }
117
203
  if (this.#singleThreadedRuntime) {
118
204
  await this.#singleThreadedRuntime.terminate();
119
205
  }
@@ -123,12 +209,12 @@ var BunPlatform = class extends BasePlatform {
123
209
  }
124
210
  logger.info("Creating single-threaded ServiceWorker runtime", { entryPath });
125
211
  this.#singleThreadedRuntime = new SingleThreadedRuntime({
126
- baseDir: entryDir,
127
- cacheStorage: this.#cacheStorage,
128
- config: this.#config
212
+ caches: this.#cacheStorage,
213
+ directories: this.#directoryStorage,
214
+ loggers: new CustomLoggerStorage((...cats) => getLogger(cats))
129
215
  });
130
216
  await this.#singleThreadedRuntime.init();
131
- await this.#singleThreadedRuntime.loadEntrypoint(entryPath);
217
+ await this.#singleThreadedRuntime.load(entryPath);
132
218
  const runtime = this.#singleThreadedRuntime;
133
219
  const platform = this;
134
220
  const instance = {
@@ -186,7 +272,8 @@ var BunPlatform = class extends BasePlatform {
186
272
  poolOptions,
187
273
  entryPath,
188
274
  this.#cacheStorage,
189
- this.#config
275
+ {}
276
+ // Empty config - use defaults
190
277
  );
191
278
  await this.#workerPool.init();
192
279
  await this.#workerPool.reloadWorkers(entryPath);
@@ -230,9 +317,35 @@ var BunPlatform = class extends BasePlatform {
230
317
  if (this.#workerPool) {
231
318
  await this.#workerPool.reloadWorkers(entrypoint);
232
319
  } else if (this.#singleThreadedRuntime) {
233
- await this.#singleThreadedRuntime.reloadWorkers(entrypoint);
320
+ await this.#singleThreadedRuntime.load(entrypoint);
234
321
  }
235
322
  }
323
+ /**
324
+ * Get virtual entry wrapper for Bun
325
+ *
326
+ * Returns production server entry template that uses:
327
+ * - shovel:config virtual module for configuration
328
+ * - Bun.serve with reusePort for multi-worker scaling
329
+ * - Direct import of user's server code
330
+ *
331
+ * The template is a real .ts file (entry-template.ts) for better
332
+ * IDE support and linting. It's imported with {type: "text"}.
333
+ */
334
+ getEntryWrapper(_entryPath, _options) {
335
+ return entryTemplate;
336
+ }
337
+ /**
338
+ * Get Bun-specific esbuild configuration
339
+ *
340
+ * Note: Bun natively supports import.meta.env, so no define alias is needed.
341
+ * We use platform: "node" since Bun is Node-compatible for module resolution.
342
+ */
343
+ getEsbuildConfig() {
344
+ return {
345
+ platform: "node",
346
+ external: ["node:*"]
347
+ };
348
+ }
236
349
  /**
237
350
  * Dispose of platform resources
238
351
  */