1 | // deno-lint-ignore-file no-explicit-any
|
2 | export { pipe as combine } from 'ts-functional-pipe';
|
3 | import { ResolvablePromise } from '@worker-tools/resolvable-promise'
|
4 |
|
5 | import { AppendOnlyList } from "./utils/append-only-list.js";
|
6 | import { Awaitable, Callable } from "./utils/common-types.js";
|
7 |
|
8 | export type ResponseEffect = (r: Response) => void | Awaitable<Response>
|
9 |
|
10 | export class EffectsList extends AppendOnlyList<ResponseEffect> {}
|
11 |
|
12 | export interface Context {
|
13 | /**
|
14 | * The original request for use in middleware. Also accessible via first argument to user handler.
|
15 | */
|
16 | request: Request,
|
17 |
|
18 | /**
|
19 | * A list of effects/transforms applied to the `Response` after the application handler completes.
|
20 | * Middleware can add effects to the list. Application handlers should ignore it.
|
21 | * @deprecated Prop might change name
|
22 | */
|
23 | effects: AppendOnlyList<ResponseEffect>,
|
24 |
|
25 | /**
|
26 | * TODO
|
27 | */
|
28 | waitUntil: (f: any) => void,
|
29 |
|
30 | /**
|
31 | * A promise that resolves when middleware is done applying effects.
|
32 | * Related: https://github.com/w3c/ServiceWorker/issues/1397
|
33 | */
|
34 | handled: Promise<Response>
|
35 |
|
36 | /**
|
37 | * The URL pattern match that caused this handler to run. See the URL Pattern API for more.
|
38 | */
|
39 | match?: URLPatternResult,
|
40 |
|
41 | /**
|
42 | * Only available if the router is used via `fetchEventListener`.
|
43 | * Many Worker Runtimes such as Deno an CF module workers don't provide fetch events.
|
44 | */
|
45 | event?: FetchEvent,
|
46 |
|
47 | /** Might be present based on usage */
|
48 | env?: any
|
49 |
|
50 | /** Might be present based on usage */
|
51 | ctx?: any
|
52 |
|
53 | /** Might be present based on usage */
|
54 | connInfo?: any
|
55 |
|
56 | /** Might be present based on usage */
|
57 | args?: any[]
|
58 | }
|
59 |
|
60 | /**
|
61 | * Helper type to get the context type of a given middleware function.
|
62 | *
|
63 | * Example:
|
64 | * ```ts
|
65 | * const mw = combine(basics(), contentTypes(['text/html', 'application/json']))
|
66 | * type MWContext = ContextOf<typeof mw>;
|
67 | * const handler = (req: Request, context: MWContext) => ok()
|
68 | * new WorkerRouter().get('/', mw, handler)
|
69 | * ```
|
70 | */
|
71 | export type ContextOf<MW extends (...args: any[]) => Awaitable<Context>> = Awaited<ReturnType<MW>>
|
72 |
|
73 | /**
|
74 | * @deprecated Function might change name
|
75 | */
|
76 | export function executeEffects(effects: EffectsList, response: Awaitable<Response>) {
|
77 | // TODO: to reduce or reduceRight, that is the question...
|
78 | // reduceRight matches the behavior of my initial, non-compose friendly middleware model
|
79 | // which was just increasingly deep levels of wrapped function calls.
|
80 | // In that model, the effects (post-processes) of the last applied middleware were executed first.
|
81 | // Regular reduce matches the order in which middlewares are applied,
|
82 | // which probably is close what users expect to happen, anyway...
|
83 | return [...effects].reduceRight(async (response, effect) => effect(await response) ?? response, response) ?? response
|
84 | }
|
85 |
|
86 | /** Any record of unknown values */
|
87 | export type Rec = Record<PropertyKey, any>
|
88 |
|
89 | /**
|
90 | * A helper function to create user-defined middleware.
|
91 | *
|
92 | * Its main purpose is to allow developers to create correctly typed middleware without dealing with generics.
|
93 | * This is achieved via the `_defaultExt` parameter, which is used to infer the types of the *extension* added to the *context*.
|
94 | * As the `_` prefix implies, it is not actually used.
|
95 | * The purpose of the default extension object is solely to tell the type checker which additional keys to expect on the context object after this middleware is applied.
|
96 | * The job of adding (default) values to the context belongs to the middleware function.
|
97 | *
|
98 | * Here are some example usages. All are valid in JavaScript and TypeScript:
|
99 | *
|
100 | * ```ts
|
101 | * const fn = createMiddleware({}, _ => _)
|
102 | * const gn = createMiddleware({}, async ax => ({ ...await ax }))
|
103 | * const hn = createMiddleware({ foo: '' }, async ax => ({ ...await ax, foo: 'star' }))
|
104 | * const jn = createMiddleware({ bar: '' }, async ax => {
|
105 | * const x = await ax;
|
106 | * x.effects.push(resp => {
|
107 | * resp.headers.set('x-middleware', 'jn')
|
108 | * })
|
109 | * return { ...x, bar: 'star' }
|
110 | * })
|
111 | * const myMW = combine(fn, hn, jn, gn)
|
112 | * //=> Context & { foo: string } & { bar: string }
|
113 | * ```
|
114 | *
|
115 | * @param _defaultExt The default extension to the current context. Can also be a function that returns the extension object, to avoid unnecessary memory allocation.
|
116 | * @param middlewareFn A middleware functions: Adds the keys listed in `defaultExt` to the context
|
117 | * @returns The provided `middlewareFn` with type annotations inferred based on `defaultExt`
|
118 | */
|
119 | export function createMiddleware<Etx extends Rec>(_defaultExt: Callable<Etx>, middlewareFn: <Ctx extends Context>(ax: Awaitable<Ctx>) => Awaitable<Ctx & Etx>) {
|
120 | return middlewareFn;
|
121 | }
|
122 |
|
123 | /** @deprecated Name might change */
|
124 | export type ErrorContext = Context & { error: Error, response: Response }
|
125 | /** @deprecated Name might change */
|
126 | export type Handler<X extends Context> = (request: Request, ctx: X) => Awaitable<Response>;
|
127 | /** @deprecated Name might change */
|
128 | export type ErrorHandler<X extends ErrorContext> = (request: Request, ctx: X) => Awaitable<Response>;
|
129 | /** @deprecated Name might change */
|
130 | export type Middleware<X extends Context, Y extends Context> = (x: Awaitable<X>) => Awaitable<Y>;
|
131 |
|
132 | /**
|
133 | * Takes a handler function of the form `(x: Request, ctx: Context) => Awaitable<Response>` and applies middleware to it.
|
134 | * @deprecated Name might change, errorHandler not implemented
|
135 | */
|
136 | export function withMiddleware<X extends Context, EX extends ErrorContext>(middleware: Middleware<Context, X>, handler: Handler<X>, _errorHandler?: ErrorHandler<EX>) {
|
137 | return async (request: Request, ...args: any[]) => {
|
138 | const handle = new ResolvablePromise<Response>()
|
139 | const handled = Promise.resolve(handle);
|
140 | const effects = new EffectsList();
|
141 | const ctx = { request, effects, handled, args: [request, ...args], waitUntil: () => {} };
|
142 | try {
|
143 | const userCtx = await middleware(ctx);
|
144 | const userRes = await handler(request, userCtx)
|
145 | const response = await executeEffects(effects, userRes)
|
146 | handle.resolve(response)
|
147 | return response;
|
148 | } catch (err) {
|
149 | throw err
|
150 | // TODO
|
151 | // if (fallback && err instanceof Response) {
|
152 | // fallback(request, Object.assign(ctx, { response: err }))
|
153 | // }
|
154 | }
|
155 | }
|
156 | }
|
157 |
|
158 | /**
|
159 | * Extends the lifetime of the install and activate events dispatched on the global scope as part of the
|
160 | * service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it
|
161 | * upgrades database schemas and deletes the outdated cache entries.
|
162 | */
|
163 | export interface ExtendableEvent extends Event {
|
164 | waitUntil(f: any): void;
|
165 | }
|
166 |
|
167 | export interface ExtendableEventInit extends EventInit {
|
168 | new(type: string, eventInitDict?: ExtendableEventInit): ExtendableEvent;
|
169 | }
|
170 |
|
171 | export interface FetchEventInit extends ExtendableEventInit {
|
172 | new(type: string, eventInitDict: FetchEventInit): FetchEvent;
|
173 | clientId?: string;
|
174 | preloadResponse?: Promise<any>;
|
175 | replacesClientId?: string;
|
176 | request: Request;
|
177 | resultingClientId?: string;
|
178 | }
|
179 |
|
180 | /**
|
181 | * This is the event type for fetch events dispatched on the service worker global scope.
|
182 | * It contains information about the fetch, including the request and how the receiver will treat the response.
|
183 | * It provides the event.respondWith() method, which allows us to provide a response to this fetch.
|
184 | */
|
185 | export interface FetchEvent extends ExtendableEvent {
|
186 | readonly clientId: string;
|
187 | readonly preloadResponse: Promise<any>;
|
188 | readonly replacesClientId: string;
|
189 | readonly request: Request;
|
190 | readonly resultingClientId: string;
|
191 | readonly handled: Promise<void>;
|
192 | respondWith(r: Response | Promise<Response>): void;
|
193 | }
|
194 |
|
195 | export type URLPatternInput = URLPatternInit | string;
|
196 |
|
197 | export interface URLPatternInit {
|
198 | baseURL?: string;
|
199 | username?: string;
|
200 | password?: string;
|
201 | protocol?: string;
|
202 | hostname?: string;
|
203 | port?: string;
|
204 | pathname?: string;
|
205 | search?: string;
|
206 | hash?: string;
|
207 | }
|
208 |
|
209 | export interface URLPatternResult {
|
210 | inputs: [URLPatternInit] | [URLPatternInit, string];
|
211 | protocol: URLPatternComponentResult;
|
212 | username: URLPatternComponentResult;
|
213 | password: URLPatternComponentResult;
|
214 | hostname: URLPatternComponentResult;
|
215 | port: URLPatternComponentResult;
|
216 | pathname: URLPatternComponentResult;
|
217 | search: URLPatternComponentResult;
|
218 | hash: URLPatternComponentResult;
|
219 | }
|
220 |
|
221 | export interface URLPatternComponentResult {
|
222 | input: string;
|
223 | groups: {
|
224 | [key: string]: string | undefined;
|
225 | };
|
226 | }
|