@liveblocks/zenrouter 1.0.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/README.md +104 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +397 -0
- package/dist/index.d.ts +397 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { JSONObject, JSONValue, Decoder } from 'decoders';
|
|
2
|
+
import { Resolve, JsonObject } from '@liveblocks/core';
|
|
3
|
+
import { Pipe, Strings, Tuples, ComposeLeft, Unions, Objects } from 'hotscript';
|
|
4
|
+
|
|
5
|
+
type HeadersInit = Record<string, string> | Headers;
|
|
6
|
+
|
|
7
|
+
declare class HttpError extends Error {
|
|
8
|
+
static readonly codes: {
|
|
9
|
+
[code: number]: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly headers?: HeadersInit;
|
|
13
|
+
constructor(status: number, message?: string, headers?: HeadersInit);
|
|
14
|
+
}
|
|
15
|
+
declare class ValidationError extends HttpError {
|
|
16
|
+
readonly status = 422;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
constructor(reason: string);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checks if a Response is a generic abort response (created by abort()).
|
|
23
|
+
*/
|
|
24
|
+
declare function isGenericAbort(resp: Response): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Returns an empty HTTP 204 response.
|
|
27
|
+
*/
|
|
28
|
+
declare function empty(): Response;
|
|
29
|
+
/**
|
|
30
|
+
* Return a JSON response.
|
|
31
|
+
*/
|
|
32
|
+
declare function json(value: JSONObject, status?: number, headers?: HeadersInit): Response;
|
|
33
|
+
/**
|
|
34
|
+
* Return an HTML response.
|
|
35
|
+
*/
|
|
36
|
+
declare function html(content: string, status?: number, headers?: HeadersInit): Response;
|
|
37
|
+
/**
|
|
38
|
+
* Throws a generic abort Response for the given status code. Use this to
|
|
39
|
+
* terminate the handling of a route and return an HTTP error to the user.
|
|
40
|
+
*
|
|
41
|
+
* The response body will be determined by the configured error handler.
|
|
42
|
+
* To return a custom error body that won't be replaced, throw a json() response instead.
|
|
43
|
+
*/
|
|
44
|
+
declare function abort(status: number, headers?: HeadersInit): never;
|
|
45
|
+
/**
|
|
46
|
+
* Return a streaming text response from a generator that yields strings. The
|
|
47
|
+
* stream will be encoded as UTF-8.
|
|
48
|
+
*
|
|
49
|
+
* Small string chunks will get buffered into emitted chunks of `bufSize` bytes (defaults to 64 kB)
|
|
50
|
+
* at least 64kB that many characters before being encoded and enqueued,
|
|
51
|
+
* reducing per-chunk transfer overhead. (By default buffers chunks of at least
|
|
52
|
+
* 64kB size.)
|
|
53
|
+
*/
|
|
54
|
+
declare function textStream(iterable: Iterable<string>, headers?: HeadersInit, options?: {
|
|
55
|
+
bufSize: number;
|
|
56
|
+
}): Response;
|
|
57
|
+
/**
|
|
58
|
+
* Return a streaming NDJSON (Newline Delimited JSON) response from a generator
|
|
59
|
+
* that yields JSON values. Each value will be serialized as a single line.
|
|
60
|
+
*/
|
|
61
|
+
declare function ndjsonStream(iterable: Iterable<JSONValue>, headers?: HeadersInit): Response;
|
|
62
|
+
/**
|
|
63
|
+
* Return a streaming JSON array response from a generator that yields JSON
|
|
64
|
+
* values. The output will be a valid JSON array: [value1,value2,...]\n
|
|
65
|
+
*/
|
|
66
|
+
declare function jsonArrayStream(iterable: Iterable<JSONValue>, headers?: HeadersInit): Response;
|
|
67
|
+
|
|
68
|
+
type ErrorContext<RC> = {
|
|
69
|
+
req: Request;
|
|
70
|
+
ctx?: RC;
|
|
71
|
+
};
|
|
72
|
+
type ErrorHandlerFn<E, RC = unknown> = (error: E, extra: ErrorContext<RC>) => Response | Promise<Response>;
|
|
73
|
+
/**
|
|
74
|
+
* Central registry instance for handling HTTP errors. Has configured defaults
|
|
75
|
+
* for every known HTTP error code. Allows you to override those defaults and
|
|
76
|
+
* provide your own error handling preferences.
|
|
77
|
+
*/
|
|
78
|
+
declare class ErrorHandler {
|
|
79
|
+
#private;
|
|
80
|
+
/**
|
|
81
|
+
* Registers a custom HTTP error handler.
|
|
82
|
+
*
|
|
83
|
+
* This will get called whenever an `HttpError` is thrown (which also happens
|
|
84
|
+
* with `abort()`) from a route handler.
|
|
85
|
+
*
|
|
86
|
+
* It will *NOT* get called if a `Response` instance is thrown (or returned)
|
|
87
|
+
* from a handler directly!
|
|
88
|
+
*/
|
|
89
|
+
onError(handler: ErrorHandlerFn<HttpError>): void;
|
|
90
|
+
/**
|
|
91
|
+
* Registers a custom uncaught error handler.
|
|
92
|
+
*
|
|
93
|
+
* This will only get called if there is an unexpected error thrown from
|
|
94
|
+
* a route handler, i.e. something that isn't a `Response` instance, or an
|
|
95
|
+
* `HttpError`.
|
|
96
|
+
*/
|
|
97
|
+
onUncaughtError(handler: ErrorHandlerFn<unknown>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Given an error, will find the best (most-specific) error handler for it,
|
|
100
|
+
* and return its response.
|
|
101
|
+
*/
|
|
102
|
+
handle(err: unknown, extra: ErrorContext<unknown>): Promise<Response>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type Method = (typeof ALL_METHODS)[number];
|
|
106
|
+
declare const ALL_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
107
|
+
type PathPattern = `/${string}`;
|
|
108
|
+
type Pattern = `${Method} ${PathPattern}`;
|
|
109
|
+
type PathPrefix = `/${string}/*` | "/*";
|
|
110
|
+
/**
|
|
111
|
+
* From a pattern like:
|
|
112
|
+
*
|
|
113
|
+
* 'GET /foo/<bar>/<qux>/baz'
|
|
114
|
+
*
|
|
115
|
+
* Extracts:
|
|
116
|
+
*
|
|
117
|
+
* { foo: string, bar: string }
|
|
118
|
+
*/
|
|
119
|
+
type ExtractParamsBasic<P extends Pattern> = Pipe<P, // ....................................... 'GET /foo/<bar>/<qux>/baz'
|
|
120
|
+
[
|
|
121
|
+
Strings.TrimLeft<`${Method} `>,
|
|
122
|
+
Strings.Split<"/">,
|
|
123
|
+
Tuples.Filter<Strings.StartsWith<"<">>,
|
|
124
|
+
Tuples.Map<ComposeLeft<[
|
|
125
|
+
Strings.Trim<"<" | ">">,
|
|
126
|
+
Unions.ToTuple,
|
|
127
|
+
Tuples.Append<string>
|
|
128
|
+
]>>,
|
|
129
|
+
Tuples.ToUnion,
|
|
130
|
+
Objects.FromEntries
|
|
131
|
+
]>;
|
|
132
|
+
/**
|
|
133
|
+
* For:
|
|
134
|
+
*
|
|
135
|
+
* {
|
|
136
|
+
* a: Decoder<number>,
|
|
137
|
+
* b: Decoder<'hi'>,
|
|
138
|
+
* c: Decoder<boolean>,
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* Will return:
|
|
142
|
+
*
|
|
143
|
+
* {
|
|
144
|
+
* a: number,
|
|
145
|
+
* b: 'hi',
|
|
146
|
+
* c: boolean,
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
type MapDecoderTypes<T> = {
|
|
151
|
+
[K in keyof T]: T[K] extends Decoder<infer V> ? V : never;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* From a pattern like:
|
|
155
|
+
*
|
|
156
|
+
* 'GET /foo/<bar>/<n>/baz'
|
|
157
|
+
*
|
|
158
|
+
* Extracts:
|
|
159
|
+
*
|
|
160
|
+
* { foo: string, n: number }
|
|
161
|
+
*/
|
|
162
|
+
type ExtractParams<P extends Pattern, TParamTypes extends Record<string, unknown>, E = ExtractParamsBasic<P>> = Resolve<Pick<Omit<E, keyof TParamTypes> & TParamTypes, Extract<keyof E, string>>>;
|
|
163
|
+
|
|
164
|
+
type CorsOptions = {
|
|
165
|
+
/**
|
|
166
|
+
* Send CORS headers only if the requested Origin is in this hardcoded list
|
|
167
|
+
* of origins. Note that the default is '*', but this still required all
|
|
168
|
+
* incoming requests to have an Origin header set.
|
|
169
|
+
*
|
|
170
|
+
* @default '*' (allow any Origin)
|
|
171
|
+
*/
|
|
172
|
+
allowedOrigins: "*" | string[];
|
|
173
|
+
/**
|
|
174
|
+
* When sending back CORS headers, tell the browser which methods are
|
|
175
|
+
* allowed.
|
|
176
|
+
*
|
|
177
|
+
* @default All methods (you should likely not have to change this)
|
|
178
|
+
*/
|
|
179
|
+
allowedMethods: string[];
|
|
180
|
+
/**
|
|
181
|
+
* Specify what headers are safe to allow on _incoming_ CORS requests.
|
|
182
|
+
*
|
|
183
|
+
* By default, all headers requested by the browser client will be allowed
|
|
184
|
+
* (as most headers will typically be ignored by the endpoint handlers), but
|
|
185
|
+
* you can specify a specific whitelist of allowed headers if you need to.
|
|
186
|
+
*
|
|
187
|
+
* Browsers will only ask for non-standard headers if those should be
|
|
188
|
+
* allowed, i.e. a browser can ask in a preflight (OPTIONS) request, if it's
|
|
189
|
+
* okay to send "X-Test", but won't ask if it's okay to send, say,
|
|
190
|
+
* "User-Agent".
|
|
191
|
+
*
|
|
192
|
+
* Note that this is different from the `exposeHeaders` config:
|
|
193
|
+
* - Allowed Headers: which headers a browser may include when
|
|
194
|
+
* _making_ the CORS request
|
|
195
|
+
* - Exposed Headers: which headers _returned_ in the CORS response the
|
|
196
|
+
* browser is allowed to safely expose to scripts
|
|
197
|
+
*
|
|
198
|
+
* @default '*'
|
|
199
|
+
*/
|
|
200
|
+
allowedHeaders: "*" | string[];
|
|
201
|
+
/**
|
|
202
|
+
* The Access-Control-Allow-Credentials response header allows browsers
|
|
203
|
+
* to include include credentials in the next CORS request.
|
|
204
|
+
*
|
|
205
|
+
* Credentials are cookies, TLS client certificates, or WWW-Authentication
|
|
206
|
+
* headers containing a username and password.
|
|
207
|
+
*
|
|
208
|
+
* NOTE: The `Authorization` header is *NOT* considered a credential and as
|
|
209
|
+
* such you don’t need to enable this setting for sending such headers.
|
|
210
|
+
*
|
|
211
|
+
* NOTE: Allowing credentials alone doesn’t cause the browser to send those
|
|
212
|
+
* credentials automatically. For to to happen, make sure to also add `{
|
|
213
|
+
* credentials: "include" }` on the fetch request.
|
|
214
|
+
*
|
|
215
|
+
* WARNING: By default, these credentials are not sent in cross-origin
|
|
216
|
+
* requests, and doing so can make a site vulnerable to CSRF attacks.
|
|
217
|
+
*
|
|
218
|
+
* @default false
|
|
219
|
+
*/
|
|
220
|
+
allowCredentials: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Specify what headers browsers *scripts* can access from the CORS response.
|
|
223
|
+
* This means when a client tries to programmatically read
|
|
224
|
+
* `resp.headers.get('...')`, this header determines which headers will be
|
|
225
|
+
* exposed to that client.
|
|
226
|
+
*
|
|
227
|
+
* Note that this is different from the `allowedHeaders` config:
|
|
228
|
+
* - Allowed Headers: which headers a browser may include when
|
|
229
|
+
* _making_ the CORS request
|
|
230
|
+
* - Exposed Headers: which headers _returned_ in the CORS response the
|
|
231
|
+
* browser is allowed to safely expose to scripts
|
|
232
|
+
*
|
|
233
|
+
* By default, browser scripts can only read the following headers from such
|
|
234
|
+
* responses:
|
|
235
|
+
* - Cache-Control
|
|
236
|
+
* - Content-Language
|
|
237
|
+
* - Content-Type
|
|
238
|
+
* - Expires
|
|
239
|
+
* - Last-Modified
|
|
240
|
+
* - Pragma
|
|
241
|
+
*/
|
|
242
|
+
exposeHeaders: string[];
|
|
243
|
+
maxAge?: number;
|
|
244
|
+
/**
|
|
245
|
+
* When `allowedOrigins` isn't an explicit list of origins but '*' (= the
|
|
246
|
+
* default), normally the Origin will get allowed by echoing the Origin value
|
|
247
|
+
* back. When this option is set, it will instead allow '*'.
|
|
248
|
+
*
|
|
249
|
+
* Do not use this in combination with `allowCredentials` as this is not
|
|
250
|
+
* allowed by the spec.
|
|
251
|
+
*
|
|
252
|
+
* @default false
|
|
253
|
+
*
|
|
254
|
+
*/
|
|
255
|
+
sendWildcard: boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Always send CORS headers on all responses, even if the request didn't
|
|
258
|
+
* contain an Origin header and thus isn't interested in CORS.
|
|
259
|
+
*
|
|
260
|
+
* @default true
|
|
261
|
+
*/
|
|
262
|
+
alwaysSend: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Normally, when returning a CORS response, it's a good idea to set the
|
|
265
|
+
* Vary header to include 'Origin', to behave better with caching. By default
|
|
266
|
+
* this will be done. If you don't want to auto-add the Vary header, set this
|
|
267
|
+
* to false.
|
|
268
|
+
*
|
|
269
|
+
* @default true
|
|
270
|
+
*/
|
|
271
|
+
varyHeader: boolean;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* An Incoming Request is what gets passed to every route handler. It includes
|
|
276
|
+
* the raw (unmodified) request, the derived context (user-defined), the parsed
|
|
277
|
+
* URL, the type-safe params `p`, the parsed query string `q`, and a verified
|
|
278
|
+
* JSON body (if a decoder is provided).
|
|
279
|
+
*/
|
|
280
|
+
type IncomingReq<RC, AC, TParams, TBody> = {
|
|
281
|
+
/**
|
|
282
|
+
* The incoming request.
|
|
283
|
+
*/
|
|
284
|
+
readonly req: Request;
|
|
285
|
+
/**
|
|
286
|
+
* The incoming request parsed URL.
|
|
287
|
+
* This is equivalent to the result of `new URL(req.url)`.
|
|
288
|
+
*/
|
|
289
|
+
readonly url: URL;
|
|
290
|
+
/**
|
|
291
|
+
* The user-defined static context associated with this request. This is the
|
|
292
|
+
* best place to attach metadata you want to carry around along with the
|
|
293
|
+
* request, without having to monkey-patch the request instance.
|
|
294
|
+
*
|
|
295
|
+
* Use this context for static metadata. Do not use it for auth.
|
|
296
|
+
*
|
|
297
|
+
* Basically the result of calling the configured `getContext()` function on
|
|
298
|
+
* the request.
|
|
299
|
+
*/
|
|
300
|
+
readonly ctx: Readonly<RC>;
|
|
301
|
+
/**
|
|
302
|
+
* The result of the authorization check for this request. Basically the
|
|
303
|
+
* result of calling the configured `authorize()` function on the request.
|
|
304
|
+
*/
|
|
305
|
+
readonly auth: Readonly<AC>;
|
|
306
|
+
/**
|
|
307
|
+
* The type-safe params available for this request. Automatically derived
|
|
308
|
+
* from dynamic placeholders in the pattern.
|
|
309
|
+
*/
|
|
310
|
+
readonly p: TParams;
|
|
311
|
+
/**
|
|
312
|
+
* Convenience accessor for the parsed query string.
|
|
313
|
+
* Equivalent to `Object.entries(url.searchParams)`.
|
|
314
|
+
*
|
|
315
|
+
* Will only contain single strings, even if a query param occurs multiple
|
|
316
|
+
* times. If you need to read all of them, use the `url.searchParams` API
|
|
317
|
+
* instead.
|
|
318
|
+
*/
|
|
319
|
+
readonly q: Record<string, string | undefined>;
|
|
320
|
+
/**
|
|
321
|
+
* Verified JSON body for this request, if a decoder instance was provided.
|
|
322
|
+
*/
|
|
323
|
+
readonly body: TBody;
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Limited version of an Incoming Request. This incoming request data is
|
|
327
|
+
* deliberately limited until after a successful auth check. Only once the
|
|
328
|
+
* request has been authorized, further parsing will happen.
|
|
329
|
+
*/
|
|
330
|
+
type PreAuthIncomingReq<RC> = Omit<IncomingReq<Readonly<RC>, never, never, never>, "auth" | "p" | "q" | "body">;
|
|
331
|
+
/**
|
|
332
|
+
* Anything that can be returned from an endpoint implementation that would be
|
|
333
|
+
* considered a valid response.
|
|
334
|
+
*/
|
|
335
|
+
type ResponseLike = Promise<Response | JsonObject> | Response | JsonObject;
|
|
336
|
+
type RouteHandler<RC, AC, TParams, TBody> = (input: IncomingReq<RC, AC, TParams, TBody>) => ResponseLike;
|
|
337
|
+
type RouterOptions<RC, AC, TParams extends Record<string, Decoder<unknown>>> = {
|
|
338
|
+
errorHandler?: ErrorHandler;
|
|
339
|
+
/**
|
|
340
|
+
* Automatically handle CORS requests. Either set to `true` (to use all the
|
|
341
|
+
* default CORS options), or specify a CorsOptions object.
|
|
342
|
+
*
|
|
343
|
+
* When enabled, this will do two things:
|
|
344
|
+
* 1. It will respond to pre-flight requests (OPTIONS) automatically.
|
|
345
|
+
* 2. It will add the correct CORS headers to all returned responses.
|
|
346
|
+
*
|
|
347
|
+
* @default false
|
|
348
|
+
*/
|
|
349
|
+
cors?: Partial<CorsOptions> | boolean;
|
|
350
|
+
getContext?: (req: Request, ...args: readonly any[]) => RC;
|
|
351
|
+
authorize?: AuthFn<RC, AC>;
|
|
352
|
+
params?: TParams;
|
|
353
|
+
debug?: boolean;
|
|
354
|
+
};
|
|
355
|
+
type AuthFn<RC, AC> = (input: PreAuthIncomingReq<RC>) => AC | Promise<AC>;
|
|
356
|
+
declare class ZenRouter<RC, AC, TParams extends Record<string, Decoder<unknown>> = {}> {
|
|
357
|
+
#private;
|
|
358
|
+
constructor(options?: RouterOptions<RC, AC, TParams>);
|
|
359
|
+
get fetch(): (req: Request, ...rest: readonly any[]) => Promise<Response>;
|
|
360
|
+
route<P extends Pattern>(pattern: P, handler: RouteHandler<RC, AC, ExtractParams<P, MapDecoderTypes<TParams>>, never>): void;
|
|
361
|
+
route<P extends Pattern, TBody>(pattern: P, bodyDecoder: Decoder<TBody>, handler: RouteHandler<RC, AC, ExtractParams<P, MapDecoderTypes<TParams>>, TBody>): void;
|
|
362
|
+
onUncaughtError(handler: ErrorHandlerFn<unknown, RC>): this;
|
|
363
|
+
onError(handler: ErrorHandlerFn<HttpError | ValidationError, RC>): this;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
type RequestHandler = (req: Request, ...args: readonly any[]) => Promise<Response>;
|
|
367
|
+
type RelayOptions = {
|
|
368
|
+
errorHandler?: ErrorHandler;
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Relay won't do any route handling itself. It will only hand-off any incoming
|
|
372
|
+
* request to one of the configured routers, based on the incoming request path
|
|
373
|
+
* (first matching prefix path wins).
|
|
374
|
+
*
|
|
375
|
+
* It does NOT check the HTTP verb (GET, POST, etc).
|
|
376
|
+
* It does NOT do any authentication.
|
|
377
|
+
* It does NOT look at any headers.
|
|
378
|
+
*
|
|
379
|
+
* Subrouters (typically Router instances) are responsible for all that
|
|
380
|
+
* themselves.
|
|
381
|
+
*
|
|
382
|
+
* If no matching route is found, it will return a generic 404 error response.
|
|
383
|
+
*/
|
|
384
|
+
declare class ZenRelay {
|
|
385
|
+
#private;
|
|
386
|
+
constructor(options?: RelayOptions);
|
|
387
|
+
get fetch(): (req: Request, ...rest: readonly any[]) => Promise<Response>;
|
|
388
|
+
/**
|
|
389
|
+
* If an incoming request matches the given prefix, forward the request as-is
|
|
390
|
+
* to the child router. Relaying happens strictly based on the request URL.
|
|
391
|
+
* It does not look at headers, or the HTTP method, or anything else to
|
|
392
|
+
* decide if it's a match.
|
|
393
|
+
*/
|
|
394
|
+
relay(prefix: PathPrefix, router: ZenRouter<any, any, any> | RequestHandler): this;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export { type AuthFn, type ErrorContext, ErrorHandler, type ErrorHandlerFn, HttpError, ValidationError, ZenRelay, ZenRouter, abort, empty, html, isGenericAbort, json, jsonArrayStream, ndjsonStream, textStream };
|