@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 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.213",
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.213"
18
+ "@ripple-ts/adapter": "0.2.215"
19
19
  },
20
- "homepage": "https://ripplejs.com",
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 = 'public', ...static_handler_options } = static_options;
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
  *
@@ -322,12 +322,10 @@ describe('@ripple-ts/adapter-node serve()', () => {
322
322
  }
323
323
  });
324
324
 
325
- it('serves files from ./public by default', async () => {
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
- const public_dir = join(temp_dir, 'public');
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
- const public_dir = join(temp_dir, 'public');
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "noEmit": true
5
+ },
6
+ "include": ["./src/**/*", "./tests/**/*"],
7
+ "exclude": ["dist", "node_modules"]
8
+ }
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;