@ripple-ts/adapter-bun 0.2.208
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 +78 -0
- package/package.json +35 -0
- package/src/index.js +119 -0
- package/tests/serve.test.js +272 -0
- package/tests/types.test.js +18 -0
- package/types/index.d.ts +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @ripple-ts/adapter-bun
|
|
2
|
+
|
|
3
|
+
Bun adapter for Ripple metaframework apps.
|
|
4
|
+
|
|
5
|
+
It exposes the same `serve(fetch_handler, options?)` contract as
|
|
6
|
+
`@ripple-ts/adapter-node`, backed by `Bun.serve`.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @ripple-ts/adapter-bun
|
|
12
|
+
# or
|
|
13
|
+
npm install @ripple-ts/adapter-bun
|
|
14
|
+
# or
|
|
15
|
+
yarn add @ripple-ts/adapter-bun
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { serve } from '@ripple-ts/adapter-bun';
|
|
22
|
+
|
|
23
|
+
const app = serve(async (request) => {
|
|
24
|
+
const url = new URL(request.url);
|
|
25
|
+
|
|
26
|
+
if (url.pathname === '/health') {
|
|
27
|
+
return new Response('ok');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return new Response('Hello from Ripple adapter-bun!', {
|
|
31
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
app.listen(3000);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
### `serve(fetch_handler, options?)`
|
|
41
|
+
|
|
42
|
+
- `fetch_handler`:
|
|
43
|
+
`(request: Request, platform?: any) => Response | Promise<Response>`
|
|
44
|
+
- `options.port` (default: `3000`)
|
|
45
|
+
- `options.hostname` (default: `localhost`)
|
|
46
|
+
- `options.static` (default: `{ dir: 'public' }`): serves static files before
|
|
47
|
+
middleware/handler
|
|
48
|
+
- `options.static.dir` (default: `public`, resolved from `process.cwd()`)
|
|
49
|
+
- `options.static.prefix` (default: `/`)
|
|
50
|
+
- `options.static.maxAge` (default: `86400`)
|
|
51
|
+
- `options.static.immutable` (default: `false`)
|
|
52
|
+
- set `options.static = false` to disable automatic static serving
|
|
53
|
+
- `options.middleware` (optional):
|
|
54
|
+
`(request, server, next) => Response | Promise<Response> | void`
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
|
|
58
|
+
- `listen(port?)`: starts Bun server and returns the Bun server instance
|
|
59
|
+
- `close()`: stops the current Bun server instance
|
|
60
|
+
|
|
61
|
+
### `serveStatic(dir, options?)`
|
|
62
|
+
|
|
63
|
+
Creates a Bun middleware that serves static assets from `dir`.
|
|
64
|
+
|
|
65
|
+
- `options.prefix` (default: `/`)
|
|
66
|
+
- `options.maxAge` (default: `86400`)
|
|
67
|
+
- `options.immutable` (default: `false`)
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- Requires Bun runtime (`Bun.serve`).
|
|
72
|
+
- Static file logic and MIME type mappings are shared from `@ripple-ts/adapter`.
|
|
73
|
+
- `platform` passed to `fetch_handler` contains `{ bun_server }`.
|
|
74
|
+
- Unhandled errors return `500 Internal Server Error`.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ripple-ts/adapter-bun",
|
|
3
|
+
"description": "Bun adapter for Ripple metaframework (Web Request/Response bridge)",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Dominic Gannaway",
|
|
6
|
+
"version": "0.2.208",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"module": "src/index.js",
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
|
+
"import": "./src/index.js",
|
|
14
|
+
"default": "./src/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "pnpm -w test --project adapter-bun"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@ripple-ts/adapter": "workspace:*"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://ripplejs.com",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Ripple-TS/ripple.git",
|
|
27
|
+
"directory": "packages/adapter-bun"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"bun": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/Ripple-TS/ripple/issues"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** @typedef {typeof import('bun')} Bun */
|
|
2
|
+
/** @typedef {Bun.Server<undefined>} Server */
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_HOSTNAME,
|
|
6
|
+
DEFAULT_PORT,
|
|
7
|
+
internal_server_error_response,
|
|
8
|
+
run_next_middleware,
|
|
9
|
+
serveStatic as create_static_handler,
|
|
10
|
+
} from '@ripple-ts/adapter';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('@ripple-ts/adapter').ServeStaticDirectoryOptions} StaticServeOptions */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {{
|
|
16
|
+
* port?: number,
|
|
17
|
+
* hostname?: string,
|
|
18
|
+
* middleware?: ((
|
|
19
|
+
* request: Request,
|
|
20
|
+
* server: Server,
|
|
21
|
+
* next: () => Promise<Response>
|
|
22
|
+
* ) => Response | Promise<Response> | void) | null,
|
|
23
|
+
* static?: StaticServeOptions | false,
|
|
24
|
+
* }} ServeOptions
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {(request: Request, platform?: any) => Response | Promise<Response>} fetch_handler
|
|
29
|
+
* @param {ServeOptions} [options]
|
|
30
|
+
* @returns {{ listen: (port?: number) => Server, close: () => void }}
|
|
31
|
+
*/
|
|
32
|
+
export function serve(fetch_handler, options = {}) {
|
|
33
|
+
const {
|
|
34
|
+
port = DEFAULT_PORT,
|
|
35
|
+
hostname = DEFAULT_HOSTNAME,
|
|
36
|
+
middleware = null,
|
|
37
|
+
static: static_options = {},
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
/** @type {ReturnType<typeof serveStatic> | null} */
|
|
41
|
+
let static_middleware = null;
|
|
42
|
+
if (static_options !== false) {
|
|
43
|
+
const { dir = 'public', ...static_handler_options } = static_options;
|
|
44
|
+
static_middleware = serveStatic(dir, static_handler_options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @type {Server | null} */
|
|
48
|
+
let bun_server = null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
listen(listen_port = port) {
|
|
52
|
+
/** @type {typeof import('bun')} */
|
|
53
|
+
const bun = globalThis.Bun;
|
|
54
|
+
if (bun == null || typeof bun.serve !== 'function') {
|
|
55
|
+
throw new Error('@ripple-ts/adapter-bun requires Bun runtime');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
bun_server = bun.serve({
|
|
59
|
+
port: listen_port,
|
|
60
|
+
hostname,
|
|
61
|
+
async fetch(request, server) {
|
|
62
|
+
const platform = { bun_server: server };
|
|
63
|
+
try {
|
|
64
|
+
const run_fetch_handler = async () => {
|
|
65
|
+
return await fetch_handler(request, platform);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const run_app_middleware = async () => {
|
|
69
|
+
if (middleware !== null) {
|
|
70
|
+
return await run_next_middleware(middleware, request, server, run_fetch_handler);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return await run_fetch_handler();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (static_middleware !== null) {
|
|
77
|
+
return await run_next_middleware(static_middleware, request, server, async () => {
|
|
78
|
+
return await run_app_middleware();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return await run_app_middleware();
|
|
83
|
+
} catch {
|
|
84
|
+
return internal_server_error_response();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return bun_server;
|
|
90
|
+
},
|
|
91
|
+
close() {
|
|
92
|
+
if (bun_server && typeof bun_server.stop === 'function') {
|
|
93
|
+
bun_server.stop();
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create a Bun middleware that serves static files from a directory
|
|
101
|
+
*
|
|
102
|
+
* @param {string} dir - Directory to serve files from (relative to cwd or absolute)
|
|
103
|
+
* @param {import('@ripple-ts/adapter').ServeStaticOptions} [options]
|
|
104
|
+
* @returns {(request: Request, server: Server, next: () => Promise<Response>) => Promise<Response>}
|
|
105
|
+
*/
|
|
106
|
+
export function serveStatic(dir, options = {}) {
|
|
107
|
+
const serve_static_request = create_static_handler(dir, options);
|
|
108
|
+
|
|
109
|
+
return async function static_middleware(request, server, next) {
|
|
110
|
+
void server;
|
|
111
|
+
|
|
112
|
+
const response = serve_static_request(request);
|
|
113
|
+
if (response !== null) {
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return await next();
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { serve, serveStatic } from '../src/index.js';
|
|
6
|
+
|
|
7
|
+
/** @type {any} */
|
|
8
|
+
const original_bun = globalThis.Bun;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (original_bun === undefined) {
|
|
12
|
+
Reflect.deleteProperty(globalThis, 'Bun');
|
|
13
|
+
} else {
|
|
14
|
+
globalThis.Bun = original_bun;
|
|
15
|
+
}
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {{
|
|
21
|
+
* serve_spy: import('vitest').Mock,
|
|
22
|
+
* server: { stop: import('vitest').Mock },
|
|
23
|
+
* get_fetch: () => (request: Request, server: any) => Promise<Response>
|
|
24
|
+
* }}
|
|
25
|
+
*/
|
|
26
|
+
function create_bun_mock() {
|
|
27
|
+
/** @type {(request: Request, server: any) => Promise<Response>} */
|
|
28
|
+
let fetch_handler = async () => new Response('not-configured', { status: 500 });
|
|
29
|
+
|
|
30
|
+
const server = { stop: vi.fn() };
|
|
31
|
+
const serve_spy = vi.fn((options) => {
|
|
32
|
+
fetch_handler = options.fetch;
|
|
33
|
+
return server;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
globalThis.Bun = {
|
|
37
|
+
serve: serve_spy,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
serve_spy,
|
|
42
|
+
server,
|
|
43
|
+
get_fetch() {
|
|
44
|
+
return fetch_handler;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} cwd
|
|
51
|
+
* @param {() => Promise<void>} run
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
async function with_cwd(cwd, run) {
|
|
55
|
+
const previous_cwd = process.cwd();
|
|
56
|
+
process.chdir(cwd);
|
|
57
|
+
try {
|
|
58
|
+
await run();
|
|
59
|
+
} finally {
|
|
60
|
+
process.chdir(previous_cwd);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('@ripple-ts/adapter-bun serve()', () => {
|
|
65
|
+
it('throws when Bun runtime is unavailable', () => {
|
|
66
|
+
Reflect.deleteProperty(globalThis, 'Bun');
|
|
67
|
+
|
|
68
|
+
const app = serve(() => new Response('ok'));
|
|
69
|
+
expect(() => app.listen()).toThrow('@ripple-ts/adapter-bun requires Bun runtime');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('starts bun server and forwards request to fetch handler', async () => {
|
|
73
|
+
const { serve_spy, server, get_fetch } = create_bun_mock();
|
|
74
|
+
const fetch_handler = vi.fn(() => new Response('ok'));
|
|
75
|
+
|
|
76
|
+
const app = serve(fetch_handler);
|
|
77
|
+
const returned_server = app.listen();
|
|
78
|
+
|
|
79
|
+
expect(returned_server).toBe(server);
|
|
80
|
+
expect(serve_spy).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(serve_spy).toHaveBeenCalledWith(expect.objectContaining({ port: 3000 }));
|
|
82
|
+
expect(serve_spy).toHaveBeenCalledWith(expect.objectContaining({ hostname: 'localhost' }));
|
|
83
|
+
|
|
84
|
+
const request = new Request('http://localhost/users');
|
|
85
|
+
const response = await get_fetch()(request, server);
|
|
86
|
+
|
|
87
|
+
expect(fetch_handler).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(fetch_handler).toHaveBeenCalledWith(request, { bun_server: server });
|
|
89
|
+
expect(await response.text()).toBe('ok');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('uses explicit listen port over default option port', () => {
|
|
93
|
+
const { serve_spy } = create_bun_mock();
|
|
94
|
+
|
|
95
|
+
const app = serve(() => new Response('ok'), { port: 8080, hostname: '0.0.0.0' });
|
|
96
|
+
app.listen(9090);
|
|
97
|
+
|
|
98
|
+
expect(serve_spy).toHaveBeenCalledWith(expect.objectContaining({ port: 9090 }));
|
|
99
|
+
expect(serve_spy).toHaveBeenCalledWith(expect.objectContaining({ hostname: '0.0.0.0' }));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('supports middleware short-circuit responses', async () => {
|
|
103
|
+
const { server, get_fetch } = create_bun_mock();
|
|
104
|
+
const fetch_handler = vi.fn(() => new Response('handler'));
|
|
105
|
+
const middleware = vi.fn(() => new Response('middleware', { status: 202 }));
|
|
106
|
+
|
|
107
|
+
const app = serve(fetch_handler, { middleware });
|
|
108
|
+
app.listen();
|
|
109
|
+
|
|
110
|
+
const response = await get_fetch()(new Request('http://localhost/'), server);
|
|
111
|
+
expect(middleware).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(fetch_handler).not.toHaveBeenCalled();
|
|
113
|
+
expect(response.status).toBe(202);
|
|
114
|
+
expect(await response.text()).toBe('middleware');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('supports middleware next() flow', async () => {
|
|
118
|
+
const { server, get_fetch } = create_bun_mock();
|
|
119
|
+
const fetch_handler = vi.fn(() => new Response('handler'));
|
|
120
|
+
const middleware = vi.fn(async (request, bun_server, next) => {
|
|
121
|
+
void request;
|
|
122
|
+
void bun_server;
|
|
123
|
+
return await next();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const app = serve(fetch_handler, { middleware });
|
|
127
|
+
app.listen();
|
|
128
|
+
|
|
129
|
+
const response = await get_fetch()(new Request('http://localhost/'), server);
|
|
130
|
+
expect(middleware).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(fetch_handler).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(await response.text()).toBe('handler');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns 500 response when fetch handler throws', async () => {
|
|
136
|
+
const { server, get_fetch } = create_bun_mock();
|
|
137
|
+
const app = serve(() => {
|
|
138
|
+
throw new Error('boom');
|
|
139
|
+
});
|
|
140
|
+
app.listen();
|
|
141
|
+
|
|
142
|
+
const response = await get_fetch()(new Request('http://localhost/'), server);
|
|
143
|
+
expect(response.status).toBe(500);
|
|
144
|
+
expect(response.headers.get('content-type')).toBe('text/plain; charset=utf-8');
|
|
145
|
+
expect(await response.text()).toBe('Internal Server Error');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('stops server on close()', () => {
|
|
149
|
+
const { server } = create_bun_mock();
|
|
150
|
+
|
|
151
|
+
const app = serve(() => new Response('ok'));
|
|
152
|
+
app.listen();
|
|
153
|
+
app.close();
|
|
154
|
+
|
|
155
|
+
expect(server.stop).toHaveBeenCalledTimes(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('serveStatic middleware serves matching files', async () => {
|
|
159
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-bun-static-'));
|
|
160
|
+
try {
|
|
161
|
+
writeFileSync(join(temp_dir, 'app.js'), 'console.log("bun");');
|
|
162
|
+
|
|
163
|
+
const static_middleware = serveStatic(temp_dir, { prefix: '/assets' });
|
|
164
|
+
const next = vi.fn(async () => new Response('next'));
|
|
165
|
+
|
|
166
|
+
const response = await static_middleware(
|
|
167
|
+
new Request('http://localhost/assets/app.js'),
|
|
168
|
+
/** @type {any} */ ({}),
|
|
169
|
+
next,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect(next).not.toHaveBeenCalled();
|
|
173
|
+
expect(response.status).toBe(200);
|
|
174
|
+
expect(response.headers.get('content-type')).toBe('text/javascript; charset=utf-8');
|
|
175
|
+
expect(await response.text()).toContain('console.log');
|
|
176
|
+
} finally {
|
|
177
|
+
rmSync(temp_dir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('serveStatic middleware falls through when no file is found', async () => {
|
|
182
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-bun-static-fallthrough-'));
|
|
183
|
+
try {
|
|
184
|
+
const static_middleware = serveStatic(temp_dir, { prefix: '/assets' });
|
|
185
|
+
const next = vi.fn(async () => new Response('next'));
|
|
186
|
+
|
|
187
|
+
const response = await static_middleware(
|
|
188
|
+
new Request('http://localhost/assets/missing.js'),
|
|
189
|
+
/** @type {any} */ ({}),
|
|
190
|
+
next,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(await response.text()).toBe('next');
|
|
195
|
+
} finally {
|
|
196
|
+
rmSync(temp_dir, { recursive: true, force: true });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('serves files from ./public by default', async () => {
|
|
201
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-bun-default-static-'));
|
|
202
|
+
try {
|
|
203
|
+
const public_dir = join(temp_dir, 'public');
|
|
204
|
+
mkdirSync(public_dir);
|
|
205
|
+
writeFileSync(join(public_dir, 'llms.txt'), 'hello llms');
|
|
206
|
+
|
|
207
|
+
await with_cwd(temp_dir, async () => {
|
|
208
|
+
const { server, get_fetch } = create_bun_mock();
|
|
209
|
+
const fetch_handler = vi.fn(() => new Response('fallback', { status: 404 }));
|
|
210
|
+
const app = serve(fetch_handler);
|
|
211
|
+
app.listen();
|
|
212
|
+
|
|
213
|
+
const response = await get_fetch()(new Request('http://localhost/llms.txt'), server);
|
|
214
|
+
expect(response.status).toBe(200);
|
|
215
|
+
expect(await response.text()).toBe('hello llms');
|
|
216
|
+
expect(fetch_handler).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
} finally {
|
|
219
|
+
rmSync(temp_dir, { recursive: true, force: true });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('can disable default static serving via options.static = false', async () => {
|
|
224
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-bun-default-static-disabled-'));
|
|
225
|
+
try {
|
|
226
|
+
const public_dir = join(temp_dir, 'public');
|
|
227
|
+
mkdirSync(public_dir);
|
|
228
|
+
writeFileSync(join(public_dir, 'llms.txt'), 'hello llms');
|
|
229
|
+
|
|
230
|
+
await with_cwd(temp_dir, async () => {
|
|
231
|
+
const { server, get_fetch } = create_bun_mock();
|
|
232
|
+
const fetch_handler = vi.fn(() => new Response('fallback', { status: 404 }));
|
|
233
|
+
const app = serve(fetch_handler, { static: false });
|
|
234
|
+
app.listen();
|
|
235
|
+
|
|
236
|
+
const response = await get_fetch()(new Request('http://localhost/llms.txt'), server);
|
|
237
|
+
expect(response.status).toBe(404);
|
|
238
|
+
expect(await response.text()).toBe('fallback');
|
|
239
|
+
expect(fetch_handler).toHaveBeenCalledTimes(1);
|
|
240
|
+
});
|
|
241
|
+
} finally {
|
|
242
|
+
rmSync(temp_dir, { recursive: true, force: true });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('serves static files via options.static with custom prefix and cache settings', async () => {
|
|
247
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-bun-static-options-'));
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(join(temp_dir, 'asset.txt'), 'asset-data');
|
|
250
|
+
|
|
251
|
+
const { server, get_fetch } = create_bun_mock();
|
|
252
|
+
const fetch_handler = vi.fn(() => new Response('fallback', { status: 404 }));
|
|
253
|
+
const app = serve(fetch_handler, {
|
|
254
|
+
static: {
|
|
255
|
+
dir: temp_dir,
|
|
256
|
+
prefix: '/public',
|
|
257
|
+
maxAge: 60,
|
|
258
|
+
immutable: true,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
app.listen();
|
|
262
|
+
|
|
263
|
+
const response = await get_fetch()(new Request('http://localhost/public/asset.txt'), server);
|
|
264
|
+
expect(response.status).toBe(200);
|
|
265
|
+
expect(response.headers.get('cache-control')).toBe('public, max-age=31536000, immutable');
|
|
266
|
+
expect(await response.text()).toBe('asset-data');
|
|
267
|
+
expect(fetch_handler).not.toHaveBeenCalled();
|
|
268
|
+
} finally {
|
|
269
|
+
rmSync(temp_dir, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const types_source = readFileSync(new URL('../types/index.d.ts', import.meta.url), 'utf8');
|
|
5
|
+
|
|
6
|
+
describe('@ripple-ts/adapter-bun types', () => {
|
|
7
|
+
it('uses shared ServeStaticDirectoryOptions alias from @ripple-ts/adapter', () => {
|
|
8
|
+
expect(types_source).toContain(
|
|
9
|
+
'ServeStaticDirectoryOptions as BaseServeStaticDirectoryOptions',
|
|
10
|
+
);
|
|
11
|
+
expect(types_source).toContain('static?: BaseServeStaticDirectoryOptions | false;');
|
|
12
|
+
expect(types_source).toContain('export type ServeStaticOptions = BaseServeStaticOptions;');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('does not inline static dir type shape locally', () => {
|
|
16
|
+
expect(types_source).not.toMatch(/BaseServeStaticOptions\s*&\s*\{\s*dir\?: string;\s*\}/);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdapterCoreOptions,
|
|
3
|
+
FetchHandler,
|
|
4
|
+
NextMiddleware,
|
|
5
|
+
ServeStaticOptions as BaseServeStaticOptions,
|
|
6
|
+
ServeStaticDirectoryOptions as BaseServeStaticDirectoryOptions,
|
|
7
|
+
ServeResult,
|
|
8
|
+
} from '@ripple-ts/adapter';
|
|
9
|
+
|
|
10
|
+
export type ServeOptions = AdapterCoreOptions & {
|
|
11
|
+
middleware?: NextMiddleware<Request, any> | null;
|
|
12
|
+
static?: BaseServeStaticDirectoryOptions | false;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ServeStaticOptions = BaseServeStaticOptions;
|
|
16
|
+
|
|
17
|
+
export function serve(
|
|
18
|
+
fetch_handler: FetchHandler<{ bun_server: any }>,
|
|
19
|
+
options?: ServeOptions,
|
|
20
|
+
): ServeResult<any>;
|
|
21
|
+
|
|
22
|
+
export function serveStatic(
|
|
23
|
+
dir: string,
|
|
24
|
+
options?: ServeStaticOptions,
|
|
25
|
+
): NextMiddleware<Request, any, Response>;
|