astro-routify 1.1.0 → 1.2.1

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/dist/index.d.ts CHANGED
@@ -16,41 +16,187 @@ declare const ALLOWED_HTTP_METHODS: Set<string>;
16
16
  */
17
17
  declare function normalizeMethod(method: string): HttpMethod;
18
18
 
19
+ /**
20
+ * A standardized shape for internal route result objects.
21
+ * These are later converted into native `Response` instances.
22
+ */
19
23
  interface ResultResponse<T = unknown> {
24
+ /**
25
+ * Optional body content (can be a string, object, or binary).
26
+ */
20
27
  body?: T;
28
+ /**
29
+ * HTTP status code (e.g. 200, 404, 500).
30
+ */
21
31
  status: number;
32
+ /**
33
+ * Optional response headers.
34
+ */
22
35
  headers?: HeadersInit;
23
36
  }
37
+ /**
38
+ * 200 OK
39
+ */
24
40
  declare const ok: <T>(body: T, headers?: HeadersInit) => ResultResponse<T>;
41
+ /**
42
+ * 201 Created
43
+ */
25
44
  declare const created: <T>(body: T, headers?: HeadersInit) => ResultResponse<T>;
45
+ /**
46
+ * 204 No Content
47
+ */
26
48
  declare const noContent: (headers?: HeadersInit) => ResultResponse<undefined>;
49
+ /**
50
+ * 304 Not Modified
51
+ */
27
52
  declare const notModified: (headers?: HeadersInit) => ResultResponse<undefined>;
53
+ /**
54
+ * 400 Bad Request
55
+ */
28
56
  declare const badRequest: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
57
+ /**
58
+ * 401 Unauthorized
59
+ */
29
60
  declare const unauthorized: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
61
+ /**
62
+ * 403 Forbidden
63
+ */
30
64
  declare const forbidden: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
65
+ /**
66
+ * 404 Not Found
67
+ */
31
68
  declare const notFound: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
69
+ /**
70
+ * 405 Method Not Allowed
71
+ */
32
72
  declare const methodNotAllowed: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
73
+ /**
74
+ * 500 Internal Server Error
75
+ */
33
76
  declare const internalError: (err: unknown, headers?: HeadersInit) => ResultResponse<string>;
77
+ /**
78
+ * Sends a binary or stream-based file response with optional content-disposition.
79
+ *
80
+ * @param content - The file data (Blob, ArrayBuffer, or stream)
81
+ * @param contentType - MIME type of the file
82
+ * @param fileName - Optional download filename
83
+ * @param headers - Optional extra headers
84
+ * @returns A ResultResponse for download-ready content
85
+ */
34
86
  declare const fileResponse: (content: Blob | ArrayBuffer | ReadableStream<Uint8Array>, contentType: string, fileName?: string, headers?: HeadersInit) => ResultResponse<BodyInit>;
87
+ /**
88
+ * Converts an internal `ResultResponse` into a native `Response` object
89
+ * for use inside Astro API routes.
90
+ *
91
+ * Automatically applies `Content-Type: application/json` for object bodies.
92
+ *
93
+ * @param result - A ResultResponse returned from route handler
94
+ * @returns A native Response
95
+ */
35
96
  declare function toAstroResponse(result: ResultResponse | undefined): Response;
36
97
 
98
+ /**
99
+ * A flexible route handler that can return:
100
+ * - a native `Response` object,
101
+ * - a structured `ResultResponse` object,
102
+ * - or a file stream (Blob, ArrayBuffer, or ReadableStream).
103
+ */
37
104
  type Handler = (ctx: APIContext) => Promise<ResultResponse | Response> | ResultResponse | Response;
105
+ /**
106
+ * Wraps a `Handler` function into an `APIRoute` that:
107
+ * - logs requests and responses,
108
+ * - supports all valid `ResultResponse` return formats,
109
+ * - auto-converts structured responses into Astro `Response`s,
110
+ * - handles errors with standardized 500 output.
111
+ *
112
+ * @param handler - A handler function returning a `Response` or `ResultResponse`
113
+ * @returns An Astro-compatible `APIRoute` function
114
+ */
38
115
  declare function defineHandler(handler: Handler): APIRoute;
116
+ /**
117
+ * Type guard to detect ReadableStreams, used for streamed/binary responses.
118
+ *
119
+ * @param value - Any value to test
120
+ * @returns True if it looks like a ReadableStream
121
+ */
39
122
  declare function isReadableStream(value: unknown): value is ReadableStream<Uint8Array>;
40
123
 
124
+ /**
125
+ * Represents a single route definition.
126
+ */
41
127
  interface Route {
128
+ /**
129
+ * HTTP method to match (GET, POST, PUT, etc.).
130
+ */
42
131
  method: HttpMethod;
132
+ /**
133
+ * Path pattern, starting with `/`, supporting static or param segments (e.g., `/users/:id`).
134
+ */
43
135
  path: string;
136
+ /**
137
+ * The function that handles the request when matched.
138
+ */
44
139
  handler: Handler;
45
140
  }
141
+ /**
142
+ * Defines a route using a `Route` object.
143
+ *
144
+ * @example
145
+ * defineRoute({ method: 'GET', path: '/users', handler });
146
+ *
147
+ * @param route - A fully constructed route object
148
+ * @returns The validated Route object
149
+ */
46
150
  declare function defineRoute(route: Route): Route;
151
+ /**
152
+ * Defines a route by specifying its method, path, and handler explicitly.
153
+ *
154
+ * @example
155
+ * defineRoute('POST', '/login', handler);
156
+ *
157
+ * @param method - HTTP method to match
158
+ * @param path - Route path (must start with `/`)
159
+ * @param handler - Function to handle the matched request
160
+ * @returns The validated Route object
161
+ */
47
162
  declare function defineRoute(method: HttpMethod, path: string, handler: Handler): Route;
48
163
 
164
+ /**
165
+ * Optional configuration for the router instance.
166
+ */
49
167
  interface RouterOptions {
168
+ /**
169
+ * A base path to strip from the incoming request path (default: `/api`).
170
+ * Only routes beneath this prefix will be matched.
171
+ */
50
172
  basePath?: string;
51
- /** Custom 404 handler */
173
+ /**
174
+ * Custom handler to return when no route is matched (404).
175
+ */
52
176
  onNotFound?: () => ReturnType<typeof notFound>;
53
177
  }
178
+ /**
179
+ * Defines a router that dynamically matches registered routes based on method and path.
180
+ *
181
+ * This allows building a clean, centralized API routing system with features like:
182
+ * - Trie-based fast route lookup
183
+ * - Per-method matching with 405 fallback
184
+ * - Parameter extraction (e.g. `/users/:id`)
185
+ * - Customizable basePath and 404 behavior
186
+ *
187
+ * @example
188
+ * defineRouter([
189
+ * defineRoute('GET', '/users', handler),
190
+ * defineRoute('POST', '/login', loginHandler),
191
+ * ], {
192
+ * basePath: '/api',
193
+ * onNotFound: () => notFound('No such route')
194
+ * });
195
+ *
196
+ * @param routes - An array of route definitions (see `defineRoute`)
197
+ * @param options - Optional router config (basePath, custom 404)
198
+ * @returns An Astro-compatible APIRoute handler
199
+ */
54
200
  declare function defineRouter(routes: Route[], options?: RouterOptions): APIRoute;
55
201
 
56
202
  /**
@@ -267,5 +413,167 @@ declare class RouterBuilder {
267
413
  build(): astro.APIRoute;
268
414
  }
269
415
 
270
- export { ALLOWED_HTTP_METHODS, HttpMethod, RouteGroup, RouteTrie, RouterBuilder, badRequest, created, defineGroup, defineHandler, defineRoute, defineRouter, fileResponse, forbidden, internalError, isReadableStream, methodNotAllowed, noContent, normalizeMethod, notFound, notModified, ok, toAstroResponse, unauthorized };
271
- export type { Handler, ResultResponse, Route, RouterOptions };
416
+ /**
417
+ * A writer for streaming raw data to the response body.
418
+ *
419
+ * This is used inside the `stream()` route handler to emit bytes
420
+ * or strings directly to the client with backpressure awareness.
421
+ */
422
+ interface StreamWriter {
423
+ /**
424
+ * Write a string or Uint8Array chunk to the response stream.
425
+ * @param chunk - Text or byte chunk to emit
426
+ */
427
+ write: (chunk: string | Uint8Array) => void;
428
+ /**
429
+ * Close the stream and signal end-of-response to the client.
430
+ */
431
+ close: () => void;
432
+ /**
433
+ * Dynamically change the Content-Type header.
434
+ * Should be called before the first `write()` to take effect.
435
+ * @param type - MIME type (e.g., `text/html`, `application/json`)
436
+ */
437
+ setContentType: (type: string) => void;
438
+ }
439
+ /**
440
+ * Configuration options for the `stream()` route helper.
441
+ */
442
+ interface StreamOptions {
443
+ /**
444
+ * HTTP method (defaults to GET).
445
+ */
446
+ method?: HttpMethod;
447
+ /**
448
+ * Content-Type header (defaults to `text/event-stream`).
449
+ */
450
+ contentType?: string;
451
+ /**
452
+ * Additional custom headers.
453
+ */
454
+ headers?: HeadersInit;
455
+ /**
456
+ * Enable SSE keep-alive headers (defaults to true for SSE).
457
+ */
458
+ keepAlive?: boolean;
459
+ }
460
+ /**
461
+ * Defines a generic streaming route that can write raw chunks of data
462
+ * to the response in real time using a `ReadableStream`.
463
+ *
464
+ * Suitable for Server-Sent Events (SSE), long-polling, streamed HTML,
465
+ * logs, LLM output, or NDJSON responses.
466
+ *
467
+ * @example
468
+ * stream('/clock', async ({ response }) => {
469
+ * const timer = setInterval(() => {
470
+ * response.write(`data: ${new Date().toISOString()}\n\n`);
471
+ * }, 1000);
472
+ *
473
+ * setTimeout(() => {
474
+ * clearInterval(timer);
475
+ * response.close();
476
+ * }, 5000);
477
+ * });
478
+ *
479
+ * @param path - The route path (e.g. `/clock`)
480
+ * @param handler - Handler that receives the API context and response writer
481
+ * @param options - Optional stream configuration
482
+ * @returns A Route object ready to be used in `defineRouter()`
483
+ */
484
+ declare function stream(path: string, handler: (ctx: APIContext & {
485
+ response: StreamWriter;
486
+ }) => void | Promise<void>, options?: StreamOptions): Route;
487
+
488
+ type JsonValue = any;
489
+ /**
490
+ * A writer interface for streaming JSON data to the response body.
491
+ * Supports both NDJSON and array formats.
492
+ */
493
+ interface JsonStreamWriter {
494
+ /**
495
+ * Send a JSON-serializable value to the response stream.
496
+ * - In `ndjson` mode: appends a newline after each object.
497
+ * - In `array` mode: adds commas and wraps with brackets.
498
+ * @param value - Any serializable object or array item
499
+ */
500
+ send: (value: JsonValue) => void;
501
+ /**
502
+ * Write raw text or bytes to the stream.
503
+ * Used for low-level control if needed.
504
+ */
505
+ write: (chunk: string | Uint8Array) => void;
506
+ /**
507
+ * Close the stream. In `array` mode, it writes the closing `]`.
508
+ */
509
+ close: () => void;
510
+ /**
511
+ * Override response headers dynamically before the response is sent.
512
+ * Only safe before the first write.
513
+ * @param key - Header name
514
+ * @param value - Header value
515
+ */
516
+ setHeader: (key: string, value: string) => void;
517
+ }
518
+
519
+ /**
520
+ * Defines a JSON streaming route using NDJSON (Newline Delimited JSON) format.
521
+ *
522
+ * This helper streams individual JSON objects separated by newlines (`\n`)
523
+ * instead of a single JSON array. It sets the `Content-Type` to
524
+ * `application/x-ndjson` and disables buffering to allow low-latency
525
+ * delivery of each item.
526
+ *
527
+ * Ideal for real-time updates, event logs, progressive loading, or
528
+ * integrations with LLMs or dashboards where each object can be processed
529
+ * as soon as it's received.
530
+ *
531
+ * @example
532
+ * streamJsonND('/events', async ({ response }) => {
533
+ * response.send({ type: 'start' });
534
+ * await delay(100);
535
+ * response.send({ type: 'progress', percent: 25 });
536
+ * response.close();
537
+ * });
538
+ *
539
+ * @param path - The route path (e.g. `/events`)
540
+ * @param handler - A function that receives `ctx` and a JSON stream writer
541
+ * @param options - Optional HTTP method override (default is GET)
542
+ * @returns A Route object ready to be registered with `defineRouter`
543
+ */
544
+ declare function streamJsonND(path: string, handler: (ctx: APIContext & {
545
+ response: JsonStreamWriter;
546
+ }) => void | Promise<void>, options?: {
547
+ method?: HttpMethod;
548
+ }): Route;
549
+
550
+ /**
551
+ * Defines a JSON streaming route that emits a valid JSON array.
552
+ *
553
+ * This helper returns a valid `application/json` response containing
554
+ * a streamable array of JSON values. Useful for large data exports
555
+ * or APIs where the full array can be streamed as it's generated.
556
+ *
557
+ * Unlike `streamJsonND()`, this wraps all values in `[` and `]`
558
+ * and separates them with commas.
559
+ *
560
+ * @example
561
+ * streamJsonArray('/users', async ({ response }) => {
562
+ * response.send({ id: 1 });
563
+ * response.send({ id: 2 });
564
+ * response.close();
565
+ * });
566
+ *
567
+ * @param path - The route path (e.g. `/users`)
568
+ * @param handler - A function that receives `ctx` and a JSON stream writer
569
+ * @param options - Optional HTTP method override (default is GET)
570
+ * @returns A Route object ready to be registered with `defineRouter`
571
+ */
572
+ declare function streamJsonArray(path: string, handler: (ctx: APIContext & {
573
+ response: JsonStreamWriter;
574
+ }) => void | Promise<void>, options?: {
575
+ method?: HttpMethod;
576
+ }): Route;
577
+
578
+ export { ALLOWED_HTTP_METHODS, HttpMethod, RouteGroup, RouteTrie, RouterBuilder, badRequest, created, defineGroup, defineHandler, defineRoute, defineRouter, fileResponse, forbidden, internalError, isReadableStream, methodNotAllowed, noContent, normalizeMethod, notFound, notModified, ok, stream, streamJsonArray, streamJsonND, toAstroResponse, unauthorized };
579
+ export type { Handler, JsonStreamWriter, ResultResponse, Route, RouterOptions, StreamOptions, StreamWriter };
package/dist/index.js CHANGED
@@ -6,3 +6,6 @@ export * from './core/RouteTrie';
6
6
  export * from './core/HttpMethod';
7
7
  export * from './core/responseHelpers';
8
8
  export * from './core/RouterBuilder';
9
+ export * from './core/stream';
10
+ export * from './core/streamJsonND';
11
+ export * from './core/streamJsonArray';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-routify",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "A high-performance API router for Astro using a Trie-based matcher.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,13 +16,19 @@
16
16
  ],
17
17
  "keywords": [
18
18
  "astro",
19
- "router",
19
+ "astrojs",
20
20
  "api-router",
21
- "typescript",
21
+ "router",
22
22
  "routing",
23
- "astrojs",
23
+ "typescript",
24
24
  "esm",
25
- "trie"
25
+ "trie",
26
+ "streaming",
27
+ "sse",
28
+ "ndjson",
29
+ "json-stream",
30
+ "response-helpers",
31
+ "astro-api"
26
32
  ],
27
33
  "author": "Alex Mora",
28
34
  "license": "MIT",