astro-routify 1.2.2 → 1.5.0
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 +367 -28
- package/dist/core/RouteTrie.d.ts +4 -3
- package/dist/core/RouteTrie.js +132 -35
- package/dist/core/RouterBuilder.d.ts +96 -13
- package/dist/core/RouterBuilder.js +243 -21
- package/dist/core/decorators.d.ts +29 -0
- package/dist/core/decorators.js +49 -0
- package/dist/core/defineGroup.d.ts +33 -17
- package/dist/core/defineGroup.js +66 -23
- package/dist/core/defineHandler.d.ts +31 -13
- package/dist/core/defineHandler.js +1 -23
- package/dist/core/defineRoute.d.ts +32 -3
- package/dist/core/defineRoute.js +36 -9
- package/dist/core/defineRouter.d.ts +11 -1
- package/dist/core/defineRouter.js +79 -16
- package/dist/core/internal/createJsonStreamRoute.d.ts +1 -1
- package/dist/core/internal/createJsonStreamRoute.js +12 -4
- package/dist/core/middlewares.d.ts +47 -0
- package/dist/core/middlewares.js +110 -0
- package/dist/core/openapi.d.ts +17 -0
- package/dist/core/openapi.js +84 -0
- package/dist/core/registry.d.ts +15 -0
- package/dist/core/registry.js +26 -0
- package/dist/core/responseHelpers.d.ts +22 -5
- package/dist/core/responseHelpers.js +121 -20
- package/dist/core/stream.d.ts +3 -3
- package/dist/core/stream.js +16 -9
- package/dist/core/streamJsonArray.d.ts +1 -1
- package/dist/core/streamJsonArray.js +1 -1
- package/dist/index.d.ts +338 -57
- package/dist/index.js +5 -1
- package/package.json +5 -3
|
@@ -27,19 +27,56 @@ import { normalizeMethod } from './HttpMethod';
|
|
|
27
27
|
export function defineRouter(routes, options = {}) {
|
|
28
28
|
const trie = new RouteTrie();
|
|
29
29
|
for (const route of routes) {
|
|
30
|
-
trie.insert(route
|
|
30
|
+
trie.insert(route);
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
let basePath = options.basePath ?? '/api';
|
|
33
|
+
if (basePath !== '' && !basePath.startsWith('/')) {
|
|
34
|
+
basePath = '/' + basePath;
|
|
35
|
+
}
|
|
36
|
+
if (basePath.endsWith('/') && basePath !== '/') {
|
|
37
|
+
basePath = basePath.slice(0, -1);
|
|
38
|
+
}
|
|
39
|
+
if (basePath === '/') {
|
|
40
|
+
basePath = '';
|
|
41
|
+
}
|
|
42
|
+
const handler = defineHandler(async (routifyCtx) => {
|
|
43
|
+
const url = new URL(routifyCtx.request.url);
|
|
44
|
+
const pathname = url.pathname;
|
|
45
|
+
routifyCtx.searchParams = url.searchParams;
|
|
46
|
+
const query = {};
|
|
47
|
+
url.searchParams.forEach((value, key) => {
|
|
48
|
+
const existing = query[key];
|
|
49
|
+
if (existing !== undefined) {
|
|
50
|
+
if (Array.isArray(existing)) {
|
|
51
|
+
existing.push(value);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
query[key] = [existing, value];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
query[key] = value;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
routifyCtx.query = query;
|
|
62
|
+
routifyCtx.state = {};
|
|
63
|
+
let path = pathname;
|
|
64
|
+
if (basePath !== '') {
|
|
65
|
+
if (!pathname.startsWith(basePath)) {
|
|
66
|
+
return toAstroResponse(options.onNotFound ? options.onNotFound() : notFound('Not Found'));
|
|
67
|
+
}
|
|
68
|
+
const nextChar = pathname.charAt(basePath.length);
|
|
69
|
+
if (nextChar !== '' && nextChar !== '/') {
|
|
70
|
+
return toAstroResponse(options.onNotFound ? options.onNotFound() : notFound('Not Found'));
|
|
71
|
+
}
|
|
72
|
+
path = pathname.slice(basePath.length);
|
|
73
|
+
}
|
|
74
|
+
const method = normalizeMethod(routifyCtx.request.method);
|
|
75
|
+
const { route, allowed, params } = trie.find(path, method);
|
|
76
|
+
if (options.debug) {
|
|
77
|
+
console.log(`[RouterBuilder] ${method} ${path} -> ${route ? 'matched' : allowed && allowed.length ? '405' : '404'}`);
|
|
37
78
|
}
|
|
38
|
-
|
|
39
|
-
const path = pathname.replace(basePathRegex, '');
|
|
40
|
-
const method = normalizeMethod(ctx.request.method);
|
|
41
|
-
const { handler, allowed, params } = trie.find(path, method);
|
|
42
|
-
if (!handler) {
|
|
79
|
+
if (!route) {
|
|
43
80
|
// Method exists but not allowed for this route
|
|
44
81
|
if (allowed && allowed.length) {
|
|
45
82
|
return toAstroResponse(methodNotAllowed('Method Not Allowed', {
|
|
@@ -47,10 +84,36 @@ export function defineRouter(routes, options = {}) {
|
|
|
47
84
|
}));
|
|
48
85
|
}
|
|
49
86
|
// No route matched at all → 404
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
return toAstroResponse(options.onNotFound ? options.onNotFound() : notFound('Not Found'));
|
|
88
|
+
}
|
|
89
|
+
// Match found → delegate to handler with middlewares
|
|
90
|
+
routifyCtx.params = { ...routifyCtx.params, ...params };
|
|
91
|
+
try {
|
|
92
|
+
const middlewares = route.middlewares || [];
|
|
93
|
+
let index = -1;
|
|
94
|
+
const next = async () => {
|
|
95
|
+
index++;
|
|
96
|
+
if (index < middlewares.length) {
|
|
97
|
+
const mw = middlewares[index];
|
|
98
|
+
const res = await mw(routifyCtx, next);
|
|
99
|
+
return res instanceof Response ? res : toAstroResponse(res);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const result = await route.handler(routifyCtx);
|
|
103
|
+
return result instanceof Response ? result : toAstroResponse(result);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return await next();
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (options.onError) {
|
|
110
|
+
const errorRes = options.onError(err, routifyCtx);
|
|
111
|
+
return errorRes instanceof Response ? errorRes : toAstroResponse(errorRes);
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
52
114
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
115
|
+
});
|
|
116
|
+
handler.routes = routes;
|
|
117
|
+
handler.options = options;
|
|
118
|
+
return handler;
|
|
56
119
|
}
|
|
@@ -3,7 +3,7 @@ import { HttpMethod } from '../HttpMethod';
|
|
|
3
3
|
import { type Route } from '../defineRoute';
|
|
4
4
|
type JsonValue = any;
|
|
5
5
|
/**
|
|
6
|
-
* A writer interface for streaming JSON
|
|
6
|
+
* A writer interface for streaming JSON state to the response body.
|
|
7
7
|
* Supports both NDJSON and array formats.
|
|
8
8
|
*/
|
|
9
9
|
export interface JsonStreamWriter {
|
|
@@ -69,15 +69,23 @@ export function createJsonStreamRoute(path, handler, options) {
|
|
|
69
69
|
const body = new ReadableStream({
|
|
70
70
|
start(controller) {
|
|
71
71
|
controllerRef = controller;
|
|
72
|
-
|
|
72
|
+
const onAbort = () => {
|
|
73
73
|
closed = true;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
try {
|
|
75
|
+
controllerRef?.close();
|
|
76
|
+
}
|
|
77
|
+
catch { /* noop */ }
|
|
78
|
+
};
|
|
79
|
+
ctx.request.signal.addEventListener('abort', onAbort, { once: true });
|
|
80
|
+
Promise.resolve(handler({ ...ctx, response: writer }))
|
|
81
|
+
.catch((err) => {
|
|
77
82
|
try {
|
|
78
83
|
controller.error(err);
|
|
79
84
|
}
|
|
80
85
|
catch { /* noop */ }
|
|
86
|
+
})
|
|
87
|
+
.finally(() => {
|
|
88
|
+
ctx.request.signal.removeEventListener('abort', onAbort);
|
|
81
89
|
});
|
|
82
90
|
},
|
|
83
91
|
cancel() {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Middleware } from './defineHandler';
|
|
2
|
+
/**
|
|
3
|
+
* CORS options.
|
|
4
|
+
*/
|
|
5
|
+
export interface CorsOptions {
|
|
6
|
+
origin?: string | string[] | ((origin: string) => boolean | string);
|
|
7
|
+
methods?: string[];
|
|
8
|
+
allowedHeaders?: string[];
|
|
9
|
+
exposedHeaders?: string[];
|
|
10
|
+
credentials?: boolean;
|
|
11
|
+
maxAge?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Middleware to enable Cross-Origin Resource Sharing (CORS).
|
|
15
|
+
*/
|
|
16
|
+
export declare function cors(options?: CorsOptions): Middleware;
|
|
17
|
+
/**
|
|
18
|
+
* Middleware to add common security headers (Helmet-like).
|
|
19
|
+
*/
|
|
20
|
+
export declare function securityHeaders(): Middleware;
|
|
21
|
+
/**
|
|
22
|
+
* Interface for schemas compatible with Zod, Valibot, etc.
|
|
23
|
+
*/
|
|
24
|
+
export interface Validatable {
|
|
25
|
+
safeParse: (data: any) => {
|
|
26
|
+
success: true;
|
|
27
|
+
data: any;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
error: any;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validation schema for request components.
|
|
35
|
+
*/
|
|
36
|
+
export interface ValidationSchema {
|
|
37
|
+
body?: Validatable;
|
|
38
|
+
query?: Validatable;
|
|
39
|
+
params?: Validatable;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Middleware for request validation.
|
|
43
|
+
* Supports any schema library with a `safeParse` method (like Zod).
|
|
44
|
+
*
|
|
45
|
+
* Validated state is stored in `ctx.state.body`, `ctx.state.query`, etc.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validate(schema: ValidationSchema): Middleware;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { badRequest, toAstroResponse } from './responseHelpers';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware to enable Cross-Origin Resource Sharing (CORS).
|
|
4
|
+
*/
|
|
5
|
+
export function cors(options = {}) {
|
|
6
|
+
return async (ctx, next) => {
|
|
7
|
+
const origin = ctx.request.headers.get('Origin');
|
|
8
|
+
// Handle preflight
|
|
9
|
+
if (ctx.request.method === 'OPTIONS') {
|
|
10
|
+
const headers = new Headers();
|
|
11
|
+
if (origin) {
|
|
12
|
+
headers.set('Access-Control-Allow-Origin', origin);
|
|
13
|
+
}
|
|
14
|
+
if (options.methods) {
|
|
15
|
+
headers.set('Access-Control-Allow-Methods', options.methods.join(','));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
|
19
|
+
}
|
|
20
|
+
if (options.allowedHeaders) {
|
|
21
|
+
headers.set('Access-Control-Allow-Headers', options.allowedHeaders.join(','));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const requestedHeaders = ctx.request.headers.get('Access-Control-Request-Headers');
|
|
25
|
+
if (requestedHeaders)
|
|
26
|
+
headers.set('Access-Control-Allow-Headers', requestedHeaders);
|
|
27
|
+
}
|
|
28
|
+
if (options.credentials) {
|
|
29
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
30
|
+
}
|
|
31
|
+
if (options.maxAge) {
|
|
32
|
+
headers.set('Access-Control-Max-Age', String(options.maxAge));
|
|
33
|
+
}
|
|
34
|
+
return new Response(null, { status: 204, headers });
|
|
35
|
+
}
|
|
36
|
+
const res = await next();
|
|
37
|
+
const newHeaders = new Headers(res.headers);
|
|
38
|
+
if (origin) {
|
|
39
|
+
newHeaders.set('Access-Control-Allow-Origin', origin);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
newHeaders.set('Access-Control-Allow-Origin', '*');
|
|
43
|
+
}
|
|
44
|
+
if (options.credentials) {
|
|
45
|
+
newHeaders.set('Access-Control-Allow-Credentials', 'true');
|
|
46
|
+
}
|
|
47
|
+
if (options.exposedHeaders) {
|
|
48
|
+
newHeaders.set('Access-Control-Expose-Headers', options.exposedHeaders.join(','));
|
|
49
|
+
}
|
|
50
|
+
return new Response(res.body, {
|
|
51
|
+
status: res.status,
|
|
52
|
+
statusText: res.statusText,
|
|
53
|
+
headers: newHeaders
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Middleware to add common security headers (Helmet-like).
|
|
59
|
+
*/
|
|
60
|
+
export function securityHeaders() {
|
|
61
|
+
return async (ctx, next) => {
|
|
62
|
+
const res = await next();
|
|
63
|
+
const headers = new Headers(res.headers);
|
|
64
|
+
headers.set('X-Content-Type-Options', 'nosniff');
|
|
65
|
+
headers.set('X-Frame-Options', 'SAMEORIGIN');
|
|
66
|
+
headers.set('X-XSS-Protection', '1; mode=block');
|
|
67
|
+
headers.set('Referrer-Policy', 'no-referrer-when-downgrade');
|
|
68
|
+
headers.set('Content-Security-Policy', "default-src 'self'");
|
|
69
|
+
return new Response(res.body, {
|
|
70
|
+
status: res.status,
|
|
71
|
+
statusText: res.statusText,
|
|
72
|
+
headers
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Middleware for request validation.
|
|
78
|
+
* Supports any schema library with a `safeParse` method (like Zod).
|
|
79
|
+
*
|
|
80
|
+
* Validated state is stored in `ctx.state.body`, `ctx.state.query`, etc.
|
|
81
|
+
*/
|
|
82
|
+
export function validate(schema) {
|
|
83
|
+
return async (ctx, next) => {
|
|
84
|
+
if (schema.params) {
|
|
85
|
+
const result = schema.params.safeParse(ctx.params);
|
|
86
|
+
if (!result.success)
|
|
87
|
+
return toAstroResponse(badRequest({ error: 'Invalid parameters', details: result.error }));
|
|
88
|
+
ctx.state.params = result.data;
|
|
89
|
+
}
|
|
90
|
+
if (schema.query) {
|
|
91
|
+
const result = schema.query.safeParse(ctx.query);
|
|
92
|
+
if (!result.success)
|
|
93
|
+
return toAstroResponse(badRequest({ error: 'Invalid query string', details: result.error }));
|
|
94
|
+
ctx.state.query = result.data;
|
|
95
|
+
}
|
|
96
|
+
if (schema.body) {
|
|
97
|
+
try {
|
|
98
|
+
const body = await ctx.request.clone().json();
|
|
99
|
+
const result = schema.body.safeParse(body);
|
|
100
|
+
if (!result.success)
|
|
101
|
+
return toAstroResponse(badRequest({ error: 'Invalid request body', details: result.error }));
|
|
102
|
+
ctx.state.body = result.data;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
return toAstroResponse(badRequest({ error: 'Invalid JSON body' }));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return next();
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic options for OpenAPI generation.
|
|
3
|
+
*/
|
|
4
|
+
export interface OpenAPIOptions {
|
|
5
|
+
title: string;
|
|
6
|
+
version: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
basePath?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generates an OpenAPI 3.0.0 specification from a list of Routify routes.
|
|
12
|
+
*
|
|
13
|
+
* @param router - The router handler function (returned by builder.build() or createRouter())
|
|
14
|
+
* @param options - Basic info for the OpenAPI spec
|
|
15
|
+
* @returns A JSON object representing the OpenAPI specification
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateOpenAPI(router: any, options: OpenAPIOptions): any;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates an OpenAPI 3.0.0 specification from a list of Routify routes.
|
|
3
|
+
*
|
|
4
|
+
* @param router - The router handler function (returned by builder.build() or createRouter())
|
|
5
|
+
* @param options - Basic info for the OpenAPI spec
|
|
6
|
+
* @returns A JSON object representing the OpenAPI specification
|
|
7
|
+
*/
|
|
8
|
+
export function generateOpenAPI(router, options) {
|
|
9
|
+
const routes = router.routes || [];
|
|
10
|
+
const routerOptions = router.options || {};
|
|
11
|
+
const basePath = options.basePath ?? routerOptions.basePath ?? '/api';
|
|
12
|
+
const spec = {
|
|
13
|
+
openapi: '3.0.0',
|
|
14
|
+
info: {
|
|
15
|
+
title: options.title,
|
|
16
|
+
version: options.version,
|
|
17
|
+
description: options.description
|
|
18
|
+
},
|
|
19
|
+
servers: [
|
|
20
|
+
{ url: basePath }
|
|
21
|
+
],
|
|
22
|
+
paths: {}
|
|
23
|
+
};
|
|
24
|
+
for (const route of routes) {
|
|
25
|
+
const path = route.path;
|
|
26
|
+
// OpenAPI paths must start with / and should not include the basePath if it's in servers
|
|
27
|
+
// Convert :param or :param(regex) to {param}
|
|
28
|
+
// Convert ** to {rest} and * to {any}
|
|
29
|
+
let openApiPath = path
|
|
30
|
+
.replace(/:([a-zA-Z0-9_]+)(\(.*?\))?/g, '{$1}')
|
|
31
|
+
.replace(/\*\*/g, '{rest}')
|
|
32
|
+
.replace(/\*/g, '{any}');
|
|
33
|
+
if (!spec.paths[openApiPath])
|
|
34
|
+
spec.paths[openApiPath] = {};
|
|
35
|
+
const method = route.method.toLowerCase();
|
|
36
|
+
const metadata = route.metadata || {};
|
|
37
|
+
spec.paths[openApiPath][method] = {
|
|
38
|
+
summary: metadata.summary || `${route.method} ${path}`,
|
|
39
|
+
description: metadata.description,
|
|
40
|
+
tags: metadata.tags,
|
|
41
|
+
parameters: [],
|
|
42
|
+
responses: {
|
|
43
|
+
'200': {
|
|
44
|
+
description: 'Successful response'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
// Extract path parameters
|
|
49
|
+
const paramRegex = /:([a-zA-Z0-9_]+)(?:\((.*?)\))?/g;
|
|
50
|
+
let pMatch;
|
|
51
|
+
while ((pMatch = paramRegex.exec(path)) !== null) {
|
|
52
|
+
const name = pMatch[1];
|
|
53
|
+
const pattern = pMatch[2];
|
|
54
|
+
spec.paths[openApiPath][method].parameters.push({
|
|
55
|
+
name,
|
|
56
|
+
in: 'path',
|
|
57
|
+
required: true,
|
|
58
|
+
schema: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
pattern: pattern ? pattern : undefined
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (path.includes('**')) {
|
|
65
|
+
spec.paths[openApiPath][method].parameters.push({
|
|
66
|
+
name: 'rest',
|
|
67
|
+
in: 'path',
|
|
68
|
+
required: true,
|
|
69
|
+
schema: { type: 'string' },
|
|
70
|
+
description: 'Catch-all path'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (path.includes('*')) {
|
|
74
|
+
spec.paths[openApiPath][method].parameters.push({
|
|
75
|
+
name: 'any',
|
|
76
|
+
in: 'path',
|
|
77
|
+
required: true,
|
|
78
|
+
schema: { type: 'string' },
|
|
79
|
+
description: 'Wildcard segment'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return spec;
|
|
84
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A global registry for routes and groups to support "agnostic" auto-registration.
|
|
3
|
+
* This allows routes to be defined anywhere in the project and automatically
|
|
4
|
+
* picked up by the router.
|
|
5
|
+
*/
|
|
6
|
+
export declare class InternalRegistry {
|
|
7
|
+
private static instance;
|
|
8
|
+
private items;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): InternalRegistry;
|
|
11
|
+
register(item: any): void;
|
|
12
|
+
getItems(): any[];
|
|
13
|
+
clear(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare const globalRegistry: InternalRegistry;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A global registry for routes and groups to support "agnostic" auto-registration.
|
|
3
|
+
* This allows routes to be defined anywhere in the project and automatically
|
|
4
|
+
* picked up by the router.
|
|
5
|
+
*/
|
|
6
|
+
export class InternalRegistry {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.items = [];
|
|
9
|
+
}
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!InternalRegistry.instance) {
|
|
12
|
+
InternalRegistry.instance = new InternalRegistry();
|
|
13
|
+
}
|
|
14
|
+
return InternalRegistry.instance;
|
|
15
|
+
}
|
|
16
|
+
register(item) {
|
|
17
|
+
this.items.push(item);
|
|
18
|
+
}
|
|
19
|
+
getItems() {
|
|
20
|
+
return [...this.items];
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.items = [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const globalRegistry = InternalRegistry.getInstance();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { BodyInit, HeadersInit } from 'undici';
|
|
2
|
+
/**
|
|
3
|
+
* Supported types that can be returned from a route handler.
|
|
4
|
+
*/
|
|
5
|
+
export type HandlerResult = Response | ResultResponse | string | number | boolean | object | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | Blob | FormData | URLSearchParams | null | undefined;
|
|
2
6
|
/**
|
|
3
7
|
* A standardized shape for internal route result objects.
|
|
4
8
|
* These are later converted into native `Response` instances.
|
|
@@ -57,14 +61,20 @@ export declare const methodNotAllowed: <T = string>(body?: T, headers?: HeadersI
|
|
|
57
61
|
* 429 Too Many Requests
|
|
58
62
|
*/
|
|
59
63
|
export declare const tooManyRequests: <T = string>(body?: T, headers?: HeadersInit) => ResultResponse<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Returns a response with JSON body and specified status.
|
|
66
|
+
*/
|
|
67
|
+
export declare const json: (body: any, status?: number, headers?: HeadersInit) => ResultResponse<any>;
|
|
60
68
|
/**
|
|
61
69
|
* 500 Internal Server Error
|
|
70
|
+
*
|
|
71
|
+
* In production, you might want to avoid leaking error details.
|
|
62
72
|
*/
|
|
63
73
|
export declare const internalError: (err: unknown, headers?: HeadersInit) => ResultResponse<string>;
|
|
64
74
|
/**
|
|
65
75
|
* Sends a binary or stream-based file response with optional content-disposition.
|
|
66
76
|
*
|
|
67
|
-
* @param content - The file
|
|
77
|
+
* @param content - The file state (Blob, ArrayBuffer, or stream)
|
|
68
78
|
* @param contentType - MIME type of the file
|
|
69
79
|
* @param fileName - Optional download filename
|
|
70
80
|
* @param headers - Optional extra headers
|
|
@@ -72,12 +82,19 @@ export declare const internalError: (err: unknown, headers?: HeadersInit) => Res
|
|
|
72
82
|
*/
|
|
73
83
|
export declare const fileResponse: (content: Blob | ArrayBuffer | ReadableStream<Uint8Array>, contentType: string, fileName?: string, headers?: HeadersInit) => ResultResponse<BodyInit>;
|
|
74
84
|
/**
|
|
75
|
-
*
|
|
85
|
+
* Type guard to detect ReadableStreams, used for streamed/binary responses.
|
|
86
|
+
*
|
|
87
|
+
* @param value - Any value to test
|
|
88
|
+
* @returns True if it looks like a ReadableStream
|
|
89
|
+
*/
|
|
90
|
+
export declare function isReadableStream(value: unknown): value is ReadableStream<Uint8Array>;
|
|
91
|
+
/**
|
|
92
|
+
* Converts an internal `ResultResponse` or any `HandlerResult` into a native `Response` object
|
|
76
93
|
* for use inside Astro API routes.
|
|
77
94
|
*
|
|
78
|
-
* Automatically applies
|
|
95
|
+
* Automatically applies appropriate Content-Type headers.
|
|
79
96
|
*
|
|
80
|
-
* @param result - A ResultResponse returned from route handler
|
|
97
|
+
* @param result - A ResultResponse or other supported type returned from route handler
|
|
81
98
|
* @returns A native Response
|
|
82
99
|
*/
|
|
83
|
-
export declare function toAstroResponse(result:
|
|
100
|
+
export declare function toAstroResponse(result: HandlerResult): Response;
|