@ripple-ts/adapter-node 0.2.213 → 0.2.215
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/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/src/index.js +119 -2
- package/tests/serve.test.js +5 -9
- package/tsconfig.json +8 -0
- package/types/index.d.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ripple-ts/adapter-node
|
|
2
2
|
|
|
3
|
+
## 0.2.215
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies []:
|
|
8
|
+
- @ripple-ts/adapter@0.2.215
|
|
9
|
+
|
|
10
|
+
## 0.2.214
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies []:
|
|
15
|
+
- @ripple-ts/adapter@0.2.214
|
|
16
|
+
|
|
3
17
|
## 0.2.213
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Node.js adapter for Ripple metaframework (Web Request/Response bridge)",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.215",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/index.js",
|
|
9
9
|
"main": "src/index.js",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@ripple-ts/adapter": "0.2.
|
|
18
|
+
"@ripple-ts/adapter": "0.2.215"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://
|
|
20
|
+
"homepage": "https://ripple-ts.com",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "git+https://github.com/Ripple-TS/ripple.git",
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,50 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
+
import { createReadStream, existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { resolve, sep } from 'node:path';
|
|
2
4
|
import { Readable } from 'node:stream';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
7
|
import {
|
|
4
8
|
DEFAULT_HOSTNAME,
|
|
5
9
|
DEFAULT_PORT,
|
|
10
|
+
DEFAULT_STATIC_PREFIX,
|
|
11
|
+
DEFAULT_STATIC_MAX_AGE,
|
|
12
|
+
get_mime_type,
|
|
13
|
+
get_static_cache_control,
|
|
6
14
|
internal_server_error_response,
|
|
7
15
|
run_next_middleware,
|
|
8
|
-
serveStatic as create_static_handler,
|
|
9
16
|
} from '@ripple-ts/adapter';
|
|
10
17
|
|
|
18
|
+
// Re-export conversion helpers for use by serverless function wrappers (e.g. Vercel)
|
|
19
|
+
export { node_request_to_web_request as nodeRequestToWebRequest };
|
|
20
|
+
export { web_response_to_node_response as webResponseToNodeResponse };
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Runtime primitives — platform-specific capabilities for Ripple's server runtime
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Node.js runtime primitives for the Ripple adapter contract.
|
|
28
|
+
*
|
|
29
|
+
* Provides:
|
|
30
|
+
* - `hash`: SHA-256 hex digest truncated to 8 chars (matches compiler output)
|
|
31
|
+
* - `createAsyncContext`: AsyncLocalStorage-backed request-scoped context
|
|
32
|
+
*
|
|
33
|
+
* @type {import('@ripple-ts/adapter').RuntimePrimitives}
|
|
34
|
+
*/
|
|
35
|
+
export const runtime = {
|
|
36
|
+
hash(str) {
|
|
37
|
+
return createHash('sha256').update(str).digest('hex').slice(0, 8);
|
|
38
|
+
},
|
|
39
|
+
createAsyncContext() {
|
|
40
|
+
const als = new AsyncLocalStorage();
|
|
41
|
+
return {
|
|
42
|
+
run: (store, fn) => als.run(store, fn),
|
|
43
|
+
getStore: () => als.getStore(),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
11
48
|
/**
|
|
12
49
|
* @param {string | string[] | undefined} value
|
|
13
50
|
* @returns {string | undefined}
|
|
@@ -170,7 +207,7 @@ export function serve(fetch_handler, options = {}) {
|
|
|
170
207
|
/** @type {ReturnType<typeof serveStatic> | null} */
|
|
171
208
|
let static_middleware = null;
|
|
172
209
|
if (static_options !== false) {
|
|
173
|
-
const { dir = '
|
|
210
|
+
const { dir = '.', ...static_handler_options } = static_options;
|
|
174
211
|
static_middleware = serveStatic(dir, static_handler_options);
|
|
175
212
|
}
|
|
176
213
|
|
|
@@ -256,6 +293,86 @@ export function serve(fetch_handler, options = {}) {
|
|
|
256
293
|
};
|
|
257
294
|
}
|
|
258
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @param {string} base_dir
|
|
298
|
+
* @param {string} pathname
|
|
299
|
+
* @returns {string | null}
|
|
300
|
+
*/
|
|
301
|
+
function resolve_static_file_path(base_dir, pathname) {
|
|
302
|
+
const file_path = resolve(base_dir, `.${pathname}`);
|
|
303
|
+
const is_within_base_dir = file_path === base_dir || file_path.startsWith(base_dir + sep);
|
|
304
|
+
return is_within_base_dir ? file_path : null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Create a request-based static file handler.
|
|
309
|
+
*
|
|
310
|
+
* @param {string} dir - Directory to serve files from (relative to cwd or absolute)
|
|
311
|
+
* @param {{ prefix?: string, maxAge?: number, immutable?: boolean }} [options]
|
|
312
|
+
* @returns {(request: Request) => Response | null}
|
|
313
|
+
*/
|
|
314
|
+
function create_static_handler(dir, options = {}) {
|
|
315
|
+
const {
|
|
316
|
+
prefix = DEFAULT_STATIC_PREFIX,
|
|
317
|
+
maxAge = DEFAULT_STATIC_MAX_AGE,
|
|
318
|
+
immutable = false,
|
|
319
|
+
} = options;
|
|
320
|
+
|
|
321
|
+
const base_dir = resolve(dir);
|
|
322
|
+
|
|
323
|
+
return function serve_static_request(request) {
|
|
324
|
+
const request_method = (request.method || 'GET').toUpperCase();
|
|
325
|
+
if (request_method !== 'GET' && request_method !== 'HEAD') {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let pathname;
|
|
330
|
+
try {
|
|
331
|
+
pathname = decodeURIComponent(new URL(request.url, 'http://localhost').pathname);
|
|
332
|
+
} catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!pathname.startsWith(prefix)) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
pathname = pathname.slice(prefix.length) || '/';
|
|
341
|
+
if (!pathname.startsWith('/')) {
|
|
342
|
+
pathname = '/' + pathname;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const file_path = resolve_static_file_path(base_dir, pathname);
|
|
346
|
+
if (file_path === null || !existsSync(file_path)) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let file_stats;
|
|
351
|
+
try {
|
|
352
|
+
file_stats = statSync(file_path);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (file_stats.isDirectory()) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const headers = new Headers();
|
|
362
|
+
headers.set('Content-Type', get_mime_type(file_path));
|
|
363
|
+
headers.set('Content-Length', String(file_stats.size));
|
|
364
|
+
headers.set('Cache-Control', get_static_cache_control(pathname, maxAge, immutable));
|
|
365
|
+
|
|
366
|
+
if (request_method === 'HEAD') {
|
|
367
|
+
return new Response(null, { status: 200, headers });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** @type {BodyInit} */
|
|
371
|
+
const file_body = /** @type {any} */ (Readable.toWeb(createReadStream(file_path)));
|
|
372
|
+
return new Response(file_body, { status: 200, headers });
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
259
376
|
/**
|
|
260
377
|
* Create a middleware that serves static files from a directory
|
|
261
378
|
*
|
package/tests/serve.test.js
CHANGED
|
@@ -322,12 +322,10 @@ describe('@ripple-ts/adapter-node serve()', () => {
|
|
|
322
322
|
}
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
-
it('serves files from ./
|
|
326
|
-
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-node-default-static-'));
|
|
325
|
+
it('serves files from ./ by default', async () => {
|
|
326
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-node-default-static-dir'));
|
|
327
327
|
try {
|
|
328
|
-
|
|
329
|
-
mkdirSync(public_dir);
|
|
330
|
-
writeFileSync(join(public_dir, 'llms.txt'), 'hello llms');
|
|
328
|
+
writeFileSync(join(temp_dir, 'llms.txt'), 'hello llms');
|
|
331
329
|
|
|
332
330
|
const fetch_handler = vi.fn(() => new Response('fallback', { status: 404 }));
|
|
333
331
|
|
|
@@ -346,11 +344,9 @@ describe('@ripple-ts/adapter-node serve()', () => {
|
|
|
346
344
|
});
|
|
347
345
|
|
|
348
346
|
it('can disable default static serving via options.static = false', async () => {
|
|
349
|
-
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-node-default-static-disabled-'));
|
|
347
|
+
const temp_dir = mkdtempSync(join(tmpdir(), 'adapter-node-default-static-disabled-dir'));
|
|
350
348
|
try {
|
|
351
|
-
|
|
352
|
-
mkdirSync(public_dir);
|
|
353
|
-
writeFileSync(join(public_dir, 'llms.txt'), 'hello llms');
|
|
349
|
+
writeFileSync(join(temp_dir, 'llms.txt'), 'hello llms');
|
|
354
350
|
|
|
355
351
|
const fetch_handler = vi.fn(() => new Response('fallback', { status: 404 }));
|
|
356
352
|
|
package/tsconfig.json
ADDED
package/types/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AdapterCoreOptions,
|
|
3
|
+
RuntimePrimitives,
|
|
3
4
|
ServeFunction,
|
|
4
5
|
ServeStaticOptions as BaseServeStaticOptions,
|
|
5
6
|
ServeStaticDirectoryOptions as BaseServeStaticDirectoryOptions,
|
|
6
7
|
} from '@ripple-ts/adapter';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Node.js runtime primitives for the Ripple adapter contract.
|
|
11
|
+
*
|
|
12
|
+
* - `hash`: SHA-256 (truncated to 8 hex chars) via `node:crypto`
|
|
13
|
+
* - `createAsyncContext`: `AsyncLocalStorage` from `node:async_hooks`
|
|
14
|
+
*/
|
|
15
|
+
export const runtime: RuntimePrimitives;
|
|
16
|
+
|
|
8
17
|
export type ServeOptions = AdapterCoreOptions & {
|
|
9
18
|
middleware?:
|
|
10
19
|
| ((
|
|
@@ -37,3 +46,21 @@ export const serve: ServeFunction<
|
|
|
37
46
|
* Create a middleware that serves static files from a directory
|
|
38
47
|
*/
|
|
39
48
|
export function serveStatic(dir: string, options?: ServeStaticOptions): StaticMiddleware;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert a Node.js IncomingMessage to a Web Request.
|
|
52
|
+
*/
|
|
53
|
+
export function nodeRequestToWebRequest(
|
|
54
|
+
node_request: import('node:http').IncomingMessage,
|
|
55
|
+
signal: AbortSignal,
|
|
56
|
+
trust_proxy?: boolean,
|
|
57
|
+
): Request;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Write a Web Response to a Node.js ServerResponse.
|
|
61
|
+
*/
|
|
62
|
+
export function webResponseToNodeResponse(
|
|
63
|
+
web_response: Response,
|
|
64
|
+
node_response: import('node:http').ServerResponse,
|
|
65
|
+
request_method: string,
|
|
66
|
+
): void;
|