@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 +7 -4
- package/src/index.d.ts +28 -4
- package/src/index.js +140 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-bun",
|
|
3
|
-
"version": "0.1.
|
|
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/
|
|
16
|
-
"@b9g/
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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 ??
|
|
29
|
-
host: options.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(
|
|
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
|
|
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", {
|
|
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}:${
|
|
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.#
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
*/
|