alien-middleware 0.3.1 → 0.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/dist/index-DQDr9Gkb.d.ts +138 -0
- package/dist/index.d.ts +3 -103
- package/dist/index.js +80 -64
- package/dist/router.d.ts +16 -0
- package/dist/router.js +50 -0
- package/package.json +12 -3
- package/readme.md +125 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { AdapterRequestContext, HattipHandler } from '@hattip/core';
|
|
2
|
+
import { Any } from 'radashi';
|
|
3
|
+
|
|
4
|
+
type RequestPlugin = {
|
|
5
|
+
/**
|
|
6
|
+
* Define properties on the request context.
|
|
7
|
+
*/
|
|
8
|
+
define?: object;
|
|
9
|
+
/**
|
|
10
|
+
* Add type-safe environment variables.
|
|
11
|
+
*/
|
|
12
|
+
env?: object;
|
|
13
|
+
};
|
|
14
|
+
type Inputs<T extends MiddlewareChain> = T['$']['input'];
|
|
15
|
+
type Platform<T extends MiddlewareChain> = T['$']['platform'];
|
|
16
|
+
type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
|
|
17
|
+
type InputEnv<T extends MiddlewareChain> = Inputs<T>['env'];
|
|
18
|
+
type Current<T extends MiddlewareChain> = T['$']['current'];
|
|
19
|
+
type Properties<T extends MiddlewareChain> = Current<T>['properties'];
|
|
20
|
+
type Env<T extends MiddlewareChain> = Current<T>['env'];
|
|
21
|
+
type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any> = {
|
|
22
|
+
properties: TProperties;
|
|
23
|
+
env: TEnv;
|
|
24
|
+
};
|
|
25
|
+
interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
|
|
26
|
+
/**
|
|
27
|
+
* The `request.url` string parsed into a `URL` object. Parsing is performed
|
|
28
|
+
* on-demand and the result is cached.
|
|
29
|
+
*/
|
|
30
|
+
url: URL;
|
|
31
|
+
env<K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
|
|
32
|
+
env(key: never): string | undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Converts a type `T` to something that can be intersected with an object.
|
|
36
|
+
*/
|
|
37
|
+
type Intersectable<T extends object> = [T] extends [never] ? {} : [T] extends [Any] ? Record<PropertyKey, any> : T;
|
|
38
|
+
type RequestContext<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = HattipContext<TPlatform, TEnv> & Intersectable<TProperties>;
|
|
39
|
+
/**
|
|
40
|
+
* Extract a `RequestContext` type from a `MiddlewareChain` type.
|
|
41
|
+
*
|
|
42
|
+
* When type `T` is `never`, a default context is returned.
|
|
43
|
+
*/
|
|
44
|
+
type MiddlewareContext<T extends MiddlewareChain> = [T] extends [never] ? RequestContext<{}, {}, unknown> : RequestContext<Properties<T>, Env<T>, Platform<T>>;
|
|
45
|
+
type Awaitable<T> = T | Promise<T>;
|
|
46
|
+
type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
|
|
47
|
+
type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
|
|
48
|
+
type RequestHandler<TInputs extends MiddlewareTypes = any, TCurrent extends MiddlewareTypes = any, TPlatform = any> = HattipHandler<TPlatform> & MiddlewareChain<TInputs, TCurrent, TPlatform>;
|
|
49
|
+
/**
|
|
50
|
+
* Either a request middleware or a response middleware.
|
|
51
|
+
*
|
|
52
|
+
* If your middleware declares the `response` parameter, it's treated as a
|
|
53
|
+
* response middleware. This means it will run *after* a `Response` is generated
|
|
54
|
+
* by a request middleware.
|
|
55
|
+
*/
|
|
56
|
+
type Middleware<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
|
|
57
|
+
/**
|
|
58
|
+
* Extract a `Middleware` type from a `MiddlewareChain` type.
|
|
59
|
+
*/
|
|
60
|
+
type ExtractMiddleware<T extends MiddlewareChain> = Middleware<Properties<T>, Env<T>, Platform<T>>;
|
|
61
|
+
type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
|
|
62
|
+
[K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
|
|
63
|
+
} : TSource);
|
|
64
|
+
type ApplyRequestPlugin<TParent extends MiddlewareChain, TPlugin extends RequestPlugin> = {
|
|
65
|
+
properties: Merge<Properties<TParent>, TPlugin['define']>;
|
|
66
|
+
env: Merge<Env<TParent>, TPlugin['env']>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* This applies a middleware to a chain. If the type `TMiddleware` is itself a
|
|
70
|
+
* chain, it's treated as a nested chain, which won't leak its plugins into the
|
|
71
|
+
* parent chain.
|
|
72
|
+
*/
|
|
73
|
+
type ApplyMiddleware<TParent extends MiddlewareChain, TMiddleware> = TMiddleware extends MiddlewareChain ? RequestHandler<Inputs<TParent>, Current<TParent>, Platform<TParent>> : TMiddleware extends () => Awaitable<infer TPlugin extends RequestPlugin> ? RequestHandler<Inputs<TParent>, ApplyRequestPlugin<TParent, TPlugin>, Platform<TParent>> : RequestHandler<Inputs<TParent>, Current<TParent>, Platform<TParent>>;
|
|
74
|
+
type ApplyFirstMiddleware<T extends Middleware> = T extends MiddlewareChain ? T : ApplyMiddleware<MiddlewareChain<{
|
|
75
|
+
properties: {};
|
|
76
|
+
env: {};
|
|
77
|
+
}, {
|
|
78
|
+
properties: {};
|
|
79
|
+
env: {};
|
|
80
|
+
}, unknown>, T>;
|
|
81
|
+
type MergeMiddleware<TFirst extends MiddlewareChain, TSecond extends Middleware<Properties<TFirst>, Env<TFirst>, Platform<TFirst>>> = RequestHandler<Inputs<TFirst>, TSecond extends MiddlewareChain ? {
|
|
82
|
+
properties: Merge<Properties<TFirst>, Properties<TSecond>>;
|
|
83
|
+
env: Merge<Env<TFirst>, Env<TSecond>>;
|
|
84
|
+
} : TSecond extends () => Awaitable<infer TPlugin extends RequestPlugin> ? ApplyRequestPlugin<TFirst, TPlugin> : Current<TFirst>, Platform<TFirst>>;
|
|
85
|
+
type RouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD';
|
|
86
|
+
type RouterContext<T extends MiddlewareChain = any, TPathParams extends object = any, TMethod extends RouteMethod = RouteMethod> = MiddlewareContext<T> & {
|
|
87
|
+
params: TPathParams;
|
|
88
|
+
method: TMethod;
|
|
89
|
+
};
|
|
90
|
+
type RouteHandler<T extends MiddlewareChain = any, TPathParams extends object = any, TMethod extends RouteMethod = RouteMethod> = (context: RouterContext<T, TPathParams, TMethod>) => Awaitable<Response | void>;
|
|
91
|
+
|
|
92
|
+
declare const kRequestChain: unique symbol;
|
|
93
|
+
declare const kResponseChain: unique symbol;
|
|
94
|
+
declare class MiddlewareChain<
|
|
95
|
+
/** Values expected by the start of the chain. */
|
|
96
|
+
TInputs extends MiddlewareTypes = any,
|
|
97
|
+
/** Values provided by the end of the chain. */
|
|
98
|
+
TCurrent extends MiddlewareTypes = any,
|
|
99
|
+
/** Values from the host platform. */
|
|
100
|
+
TPlatform = any> {
|
|
101
|
+
/** This property won't exist at runtime. It contains type information for inference purposes. */
|
|
102
|
+
$: {
|
|
103
|
+
input: TInputs;
|
|
104
|
+
current: TCurrent;
|
|
105
|
+
platform: TPlatform;
|
|
106
|
+
};
|
|
107
|
+
/** The number of parameters when called as a function. */
|
|
108
|
+
readonly length: 1;
|
|
109
|
+
protected [kRequestChain]: RequestMiddleware[];
|
|
110
|
+
protected [kResponseChain]: ResponseMiddleware[];
|
|
111
|
+
/**
|
|
112
|
+
* Attach a middleware. If the `response` parameter is declared, it will be
|
|
113
|
+
* treated as a response middleware. Otherwise, it will be treated as a
|
|
114
|
+
* request middleware.
|
|
115
|
+
*
|
|
116
|
+
* @returns a new `MiddlewareChain` instance
|
|
117
|
+
*/
|
|
118
|
+
use<const TMiddleware extends ExtractMiddleware<this>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
|
|
119
|
+
/**
|
|
120
|
+
* Merge two middleware chains. The middlewares from the second chain will be
|
|
121
|
+
* executed after the middlewares from the first chain.
|
|
122
|
+
*
|
|
123
|
+
* For ease of use, this method may be given a middleware function, which
|
|
124
|
+
* short-circuits to the `use` method. You should prefer using the `use`
|
|
125
|
+
* method directly, if possible.
|
|
126
|
+
*/
|
|
127
|
+
merge<TMiddleware extends ExtractMiddleware<this>>(chain: TMiddleware): MergeMiddleware<this, TMiddleware>;
|
|
128
|
+
}
|
|
129
|
+
declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
|
|
130
|
+
env: TEnv;
|
|
131
|
+
properties: TProperties;
|
|
132
|
+
}, {
|
|
133
|
+
env: TEnv;
|
|
134
|
+
properties: TProperties;
|
|
135
|
+
}, TPlatform>;
|
|
136
|
+
declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
|
|
137
|
+
|
|
138
|
+
export { type ApplyMiddleware as A, type ExtractMiddleware as E, MiddlewareChain as M, type RouteHandler as R, type MiddlewareContext as a, type RouteMethod as b, chain as c, type MergeMiddleware as d, type Middleware as e, type RequestContext as f, type RequestHandler as g, type RequestMiddleware as h, type RequestPlugin as i, type ResponseMiddleware as j };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,103 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
type RequestPlugin = {
|
|
5
|
-
/**
|
|
6
|
-
* Define properties on the request context.
|
|
7
|
-
*/
|
|
8
|
-
define?: object;
|
|
9
|
-
/**
|
|
10
|
-
* Add type-safe environment variables.
|
|
11
|
-
*/
|
|
12
|
-
env?: object;
|
|
13
|
-
};
|
|
14
|
-
type Inputs<T extends MiddlewareChain> = T['$']['input'];
|
|
15
|
-
type Platform<T extends MiddlewareChain> = T['$']['platform'];
|
|
16
|
-
type InputProperties<T extends MiddlewareChain> = Inputs<T>['properties'];
|
|
17
|
-
type InputEnv<T extends MiddlewareChain> = Inputs<T>['env'];
|
|
18
|
-
type Current<T extends MiddlewareChain> = T['$']['current'];
|
|
19
|
-
type Properties<T extends MiddlewareChain> = Current<T>['properties'];
|
|
20
|
-
type Env<T extends MiddlewareChain> = Current<T>['env'];
|
|
21
|
-
type MiddlewareTypes<TProperties extends object = any, TEnv extends object = any> = {
|
|
22
|
-
properties: TProperties;
|
|
23
|
-
env: TEnv;
|
|
24
|
-
};
|
|
25
|
-
interface HattipContext<TPlatform, TEnv extends object> extends AdapterRequestContext<TPlatform> {
|
|
26
|
-
/**
|
|
27
|
-
* The `request.url` string parsed into a `URL` object. Parsing is performed
|
|
28
|
-
* on-demand and the result is cached.
|
|
29
|
-
*/
|
|
30
|
-
url: URL;
|
|
31
|
-
env<K extends keyof TEnv>(key: Extract<K, string>): TEnv[K];
|
|
32
|
-
env(key: never): string | undefined;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Converts a type `T` to something that can be intersected with an object.
|
|
36
|
-
*/
|
|
37
|
-
type Intersectable<T extends object> = [T] extends [never] ? {} : [T] extends [Any] ? Record<PropertyKey, any> : T;
|
|
38
|
-
type RequestContext<TProperties extends object = any, TEnv extends object = any, TPlatform = any> = HattipContext<TPlatform, TEnv> & Intersectable<TProperties>;
|
|
39
|
-
type Awaitable<T> = T | PromiseLike<T>;
|
|
40
|
-
type RequestMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>) => Awaitable<Response | RequestPlugin | void>;
|
|
41
|
-
type ResponseMiddleware<T extends MiddlewareChain = MiddlewareChain> = (context: RequestContext<InputProperties<T>, InputEnv<T>, Platform<T>>, response: Response) => Awaitable<Response | void>;
|
|
42
|
-
type RequestHandler<TInputs extends MiddlewareTypes = any, TCurrent extends MiddlewareTypes = any, TPlatform = any> = HattipHandler<TPlatform> & MiddlewareChain<TInputs, TCurrent, TPlatform>;
|
|
43
|
-
/**
|
|
44
|
-
* Either a request middleware or a response middleware.
|
|
45
|
-
*
|
|
46
|
-
* If your middleware declares the `response` parameter, it's treated as a
|
|
47
|
-
* response middleware. This means it will run *after* a `Response` is generated
|
|
48
|
-
* by a request middleware.
|
|
49
|
-
*/
|
|
50
|
-
type Middleware<TProperties extends object = any, TEnv extends object = any, TPlatform = unknown> = (context: RequestContext<TProperties, TEnv, TPlatform>, response: Response) => Awaitable<Response | RequestPlugin | void>;
|
|
51
|
-
type Merge<TSource extends object, TOverrides extends object | undefined> = {} & (TOverrides extends object ? {
|
|
52
|
-
[K in keyof TSource | keyof TOverrides]: K extends keyof TOverrides ? TOverrides[K] : K extends keyof TSource ? TSource[K] : never;
|
|
53
|
-
} : TSource);
|
|
54
|
-
type ApplyMiddleware<TChain extends MiddlewareChain, TMiddleware> = TMiddleware extends MiddlewareChain ? RequestHandler<Inputs<TChain>, Current<TChain>, Platform<TChain>> : TMiddleware extends () => Awaitable<infer TPlugin extends RequestPlugin> ? RequestHandler<Inputs<TChain>, {
|
|
55
|
-
properties: Merge<Properties<TChain>, TPlugin['define']>;
|
|
56
|
-
env: Merge<Env<TChain>, TPlugin['env']>;
|
|
57
|
-
}, Platform<TChain>> : RequestHandler<Inputs<TChain>, Current<TChain>, Platform<TChain>>;
|
|
58
|
-
type ApplyFirstMiddleware<T extends Middleware> = ApplyMiddleware<MiddlewareChain<{
|
|
59
|
-
properties: {};
|
|
60
|
-
env: {};
|
|
61
|
-
}, {
|
|
62
|
-
properties: {};
|
|
63
|
-
env: {};
|
|
64
|
-
}, unknown>, T>;
|
|
65
|
-
|
|
66
|
-
declare const kRequestChain: unique symbol;
|
|
67
|
-
declare const kResponseChain: unique symbol;
|
|
68
|
-
declare class MiddlewareChain<
|
|
69
|
-
/** Values expected by the start of the chain. */
|
|
70
|
-
TInputs extends MiddlewareTypes = any,
|
|
71
|
-
/** Values provided by the end of the chain. */
|
|
72
|
-
TCurrent extends MiddlewareTypes = any,
|
|
73
|
-
/** Values from the host platform. */
|
|
74
|
-
TPlatform = any> {
|
|
75
|
-
/** This property won't exist at runtime. It contains type information for inference purposes. */
|
|
76
|
-
$: {
|
|
77
|
-
input: TInputs;
|
|
78
|
-
current: TCurrent;
|
|
79
|
-
platform: TPlatform;
|
|
80
|
-
};
|
|
81
|
-
/** The number of parameters when called as a function. */
|
|
82
|
-
readonly length: 1;
|
|
83
|
-
protected [kRequestChain]: RequestMiddleware[];
|
|
84
|
-
protected [kResponseChain]: ResponseMiddleware[];
|
|
85
|
-
/**
|
|
86
|
-
* Attach a middleware. If the `response` paramter is declared, it will be
|
|
87
|
-
* treated as a response middleware. Otherwise, it will be treated as a
|
|
88
|
-
* request middleware.
|
|
89
|
-
*
|
|
90
|
-
* @returns a new `MiddlewareChain` instance
|
|
91
|
-
*/
|
|
92
|
-
use<const TMiddleware extends Middleware<TCurrent['properties'], TCurrent['env'], TPlatform>>(middleware: TMiddleware): ApplyMiddleware<this, TMiddleware>;
|
|
93
|
-
}
|
|
94
|
-
declare function chain<TProperties extends object = {}, TEnv extends object = {}, TPlatform = unknown>(): MiddlewareChain<{
|
|
95
|
-
env: TEnv;
|
|
96
|
-
properties: TProperties;
|
|
97
|
-
}, {
|
|
98
|
-
env: TEnv;
|
|
99
|
-
properties: TProperties;
|
|
100
|
-
}, TPlatform>;
|
|
101
|
-
declare function chain<const T extends Middleware = Middleware>(middleware: T): ApplyFirstMiddleware<T>;
|
|
102
|
-
|
|
103
|
-
export { type Middleware, MiddlewareChain, type RequestContext, type RequestHandler, type RequestMiddleware, type RequestPlugin, type ResponseMiddleware, chain };
|
|
1
|
+
export { A as ApplyMiddleware, E as ExtractMiddleware, d as MergeMiddleware, e as Middleware, M as MiddlewareChain, a as MiddlewareContext, f as RequestContext, g as RequestHandler, h as RequestMiddleware, i as RequestPlugin, j as ResponseMiddleware, c as chain } from './index-DQDr9Gkb.js';
|
|
2
|
+
import '@hattip/core';
|
|
3
|
+
import 'radashi';
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
// node_modules/.pnpm/radashi@12.5.0-beta.6d5c035/node_modules/radashi/dist/radashi.js
|
|
2
|
-
var asyncIteratorSymbol = (
|
|
3
|
-
/* c8 ignore next */
|
|
4
|
-
Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator")
|
|
5
|
-
);
|
|
6
|
-
function isFunction(value) {
|
|
7
|
-
return typeof value === "function";
|
|
8
|
-
}
|
|
9
|
-
function isPromise(value) {
|
|
10
|
-
return !!value && isFunction(value.then);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
1
|
// src/index.ts
|
|
14
2
|
var kRequestChain = Symbol("requestChain");
|
|
15
3
|
var kResponseChain = Symbol("responseChain");
|
|
@@ -27,7 +15,7 @@ var MiddlewareChain = class _MiddlewareChain {
|
|
|
27
15
|
[kRequestChain] = [];
|
|
28
16
|
[kResponseChain] = [];
|
|
29
17
|
/**
|
|
30
|
-
* Attach a middleware. If the `response`
|
|
18
|
+
* Attach a middleware. If the `response` parameter is declared, it will be
|
|
31
19
|
* treated as a response middleware. Otherwise, it will be treated as a
|
|
32
20
|
* request middleware.
|
|
33
21
|
*
|
|
@@ -41,66 +29,91 @@ var MiddlewareChain = class _MiddlewareChain {
|
|
|
41
29
|
} else {
|
|
42
30
|
responseChain = [...responseChain, middleware];
|
|
43
31
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
return createHandler(requestChain, responseChain);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Merge two middleware chains. The middlewares from the second chain will be
|
|
36
|
+
* executed after the middlewares from the first chain.
|
|
37
|
+
*
|
|
38
|
+
* For ease of use, this method may be given a middleware function, which
|
|
39
|
+
* short-circuits to the `use` method. You should prefer using the `use`
|
|
40
|
+
* method directly, if possible.
|
|
41
|
+
*/
|
|
42
|
+
merge(chain2) {
|
|
43
|
+
if (chain2 instanceof _MiddlewareChain) {
|
|
44
|
+
return createHandler(
|
|
45
|
+
[...this[kRequestChain], ...chain2[kRequestChain]],
|
|
46
|
+
[...this[kResponseChain], ...chain2[kResponseChain]]
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return this.use(chain2);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function createHandler(requestChain, responseChain) {
|
|
53
|
+
async function handler(parentContext) {
|
|
54
|
+
const context = Object.create(parentContext);
|
|
55
|
+
context[kIgnoreNotFound] = true;
|
|
56
|
+
if (!("url" in context)) {
|
|
57
|
+
Object.defineProperty(context, "url", urlDescriptor);
|
|
58
|
+
}
|
|
59
|
+
const cache = context[kMiddlewareCache] ||= /* @__PURE__ */ new Set();
|
|
60
|
+
let response;
|
|
61
|
+
let env;
|
|
62
|
+
for (const middleware of requestChain) {
|
|
63
|
+
if (cache.has(middleware)) {
|
|
64
|
+
continue;
|
|
49
65
|
}
|
|
50
|
-
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (isPromise(result)) {
|
|
60
|
-
result = await result;
|
|
66
|
+
cache.add(middleware);
|
|
67
|
+
let result = middleware(context);
|
|
68
|
+
if (result instanceof Promise) {
|
|
69
|
+
result = await result;
|
|
70
|
+
}
|
|
71
|
+
if (result) {
|
|
72
|
+
if (result instanceof Response) {
|
|
73
|
+
response = result;
|
|
74
|
+
break;
|
|
61
75
|
}
|
|
62
|
-
if (result) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Object.assign(context, result.define);
|
|
69
|
-
}
|
|
70
|
-
if (result.env) {
|
|
71
|
-
env ||= createExtendedEnv(context);
|
|
72
|
-
Object.assign(env, result.env);
|
|
76
|
+
if (result.define) {
|
|
77
|
+
for (const key in result.define) {
|
|
78
|
+
Object.defineProperty(context, key, {
|
|
79
|
+
...Object.getOwnPropertyDescriptor(result.define, key),
|
|
80
|
+
configurable: false
|
|
81
|
+
});
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return;
|
|
84
|
+
if (result.env) {
|
|
85
|
+
env ||= createExtendedEnv(context);
|
|
86
|
+
Object.assign(env, result.env);
|
|
79
87
|
}
|
|
80
|
-
response = new Response("Not Found", { status: 404 });
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cache.add(middleware2);
|
|
87
|
-
let result = middleware2(context, response);
|
|
88
|
-
if (isPromise(result)) {
|
|
89
|
-
result = await result;
|
|
90
|
-
}
|
|
91
|
-
if (result && result instanceof Response) {
|
|
92
|
-
response = result;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
89
|
+
}
|
|
90
|
+
if (!response) {
|
|
91
|
+
if (parentContext[kIgnoreNotFound]) {
|
|
92
|
+
return;
|
|
95
93
|
}
|
|
96
|
-
|
|
94
|
+
response = new Response("Not Found", { status: 404 });
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
for (const middleware of responseChain) {
|
|
97
|
+
if (cache.has(middleware)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
cache.add(middleware);
|
|
101
|
+
let result = middleware(context, response);
|
|
102
|
+
if (result instanceof Promise) {
|
|
103
|
+
result = await result;
|
|
104
|
+
}
|
|
105
|
+
if (result && result instanceof Response) {
|
|
106
|
+
response = result;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return response;
|
|
102
111
|
}
|
|
103
|
-
|
|
112
|
+
Object.setPrototypeOf(handler, MiddlewareChain.prototype);
|
|
113
|
+
handler[kRequestChain] = requestChain;
|
|
114
|
+
handler[kResponseChain] = responseChain;
|
|
115
|
+
return handler;
|
|
116
|
+
}
|
|
104
117
|
function createExtendedEnv(context) {
|
|
105
118
|
const env = /* @__PURE__ */ Object.create(null);
|
|
106
119
|
const superEnv = context.env;
|
|
@@ -108,6 +121,9 @@ function createExtendedEnv(context) {
|
|
|
108
121
|
return env;
|
|
109
122
|
}
|
|
110
123
|
function chain(middleware) {
|
|
124
|
+
if (middleware instanceof MiddlewareChain) {
|
|
125
|
+
return middleware;
|
|
126
|
+
}
|
|
111
127
|
const handler = new MiddlewareChain();
|
|
112
128
|
return middleware ? handler.use(middleware) : handler;
|
|
113
129
|
}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InferParams } from 'pathic';
|
|
2
|
+
import { M as MiddlewareChain, a as MiddlewareContext, R as RouteHandler, b as RouteMethod } from './index-DQDr9Gkb.js';
|
|
3
|
+
import '@hattip/core';
|
|
4
|
+
import 'radashi';
|
|
5
|
+
|
|
6
|
+
type OneOrMany<T> = T | readonly T[];
|
|
7
|
+
type Router<T extends MiddlewareChain = any> = ReturnType<typeof routes<T>>;
|
|
8
|
+
declare function routes<T extends MiddlewareChain>(middlewares?: T): {
|
|
9
|
+
(context: MiddlewareContext<T>): void | Response | Promise<void | Response> | undefined;
|
|
10
|
+
use: {
|
|
11
|
+
<TPath extends string>(path: TPath, handler: RouteHandler<T, InferParams<TPath>>): /*elided*/ any;
|
|
12
|
+
<TPath extends string, TMethod extends RouteMethod = RouteMethod>(method: OneOrMany<TMethod> | "*", path: TPath, handler: RouteHandler<T, InferParams<TPath>, TMethod>): /*elided*/ any;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { type Router, routes };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/router.ts
|
|
2
|
+
import { compilePaths } from "pathic";
|
|
3
|
+
|
|
4
|
+
// node_modules/.pnpm/radashi@12.5.0-beta.6d5c035/node_modules/radashi/dist/radashi.js
|
|
5
|
+
var isArray = /* @__PURE__ */ (() => Array.isArray)();
|
|
6
|
+
var asyncIteratorSymbol = (
|
|
7
|
+
/* c8 ignore next */
|
|
8
|
+
Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator")
|
|
9
|
+
);
|
|
10
|
+
function isFunction(value) {
|
|
11
|
+
return typeof value === "function";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/router.ts
|
|
15
|
+
function routes(middlewares) {
|
|
16
|
+
const paths = [];
|
|
17
|
+
const filters = [];
|
|
18
|
+
const handlers = [];
|
|
19
|
+
function use(method, path, handler) {
|
|
20
|
+
if (isFunction(path)) {
|
|
21
|
+
paths.push(method);
|
|
22
|
+
filters.push(null);
|
|
23
|
+
handlers.push(path);
|
|
24
|
+
} else {
|
|
25
|
+
paths.push(path);
|
|
26
|
+
filters.push(
|
|
27
|
+
method === "*" ? null : isArray(method) ? (m) => method.includes(m) : (m) => method === m
|
|
28
|
+
);
|
|
29
|
+
handlers.push(handler);
|
|
30
|
+
}
|
|
31
|
+
return router;
|
|
32
|
+
}
|
|
33
|
+
let matcher;
|
|
34
|
+
function router(context) {
|
|
35
|
+
matcher ||= compilePaths(paths);
|
|
36
|
+
const method = context.request.method;
|
|
37
|
+
return matcher(context.request.path, (index, params) => {
|
|
38
|
+
if (!filters[index] || filters[index](method)) {
|
|
39
|
+
context.method = method;
|
|
40
|
+
context.params = params;
|
|
41
|
+
return middlewares ? middlewares.use(handlers[index])(context) : handlers[index](context);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
router.use = use;
|
|
46
|
+
return router;
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
routes
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alien-middleware",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"exports": {
|
|
6
|
-
"
|
|
7
|
-
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"./router": {
|
|
11
|
+
"types": "./dist/router.d.ts",
|
|
12
|
+
"default": "./dist/router.js"
|
|
13
|
+
}
|
|
8
14
|
},
|
|
9
15
|
"files": [
|
|
10
16
|
"dist"
|
|
@@ -31,6 +37,9 @@
|
|
|
31
37
|
"typescript": "^5.8.3",
|
|
32
38
|
"vitest": "^3.1.2"
|
|
33
39
|
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"pathic": "^0.1.6"
|
|
42
|
+
},
|
|
34
43
|
"scripts": {
|
|
35
44
|
"dev": "rimraf dist && tsup --sourcemap --watch",
|
|
36
45
|
"build": "rimraf dist && tsup",
|
package/readme.md
CHANGED
|
@@ -82,6 +82,14 @@ server.listen(3000, () => {
|
|
|
82
82
|
> If no middleware in the chain returns a `Response`, a `404 Not Found` response
|
|
83
83
|
> is automatically returned.
|
|
84
84
|
|
|
85
|
+
#### Middlewares are deduplicated.
|
|
86
|
+
|
|
87
|
+
If you add the same middleware multiple times, it will only run once. This is a safety measure that allows you to use the same middleware in different places without worrying about it running multiple times.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const app = chain().use(myMiddleware).use(myMiddleware)
|
|
91
|
+
```
|
|
92
|
+
|
|
85
93
|
### Request Middleware
|
|
86
94
|
|
|
87
95
|
Request middleware runs sequentially before a `Response` is generated.
|
|
@@ -203,6 +211,16 @@ const finalApp = chain().use(innerChain).use(outerMiddleware)
|
|
|
203
211
|
|
|
204
212
|
If a nested chain does not return a `Response`, execution continues with the next middleware in the outer chain.
|
|
205
213
|
|
|
214
|
+
### Merging Chains
|
|
215
|
+
|
|
216
|
+
You can merge two middleware chains using the `merge` method. This is useful if you want to combine middleware from multiple files or modules.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const mergedApp = chain().use(middleware1).merge(chain().use(middleware2))
|
|
220
|
+
// …is equivalent to…
|
|
221
|
+
const mergedApp = chain().use(middleware1).use(middleware2)
|
|
222
|
+
```
|
|
223
|
+
|
|
206
224
|
### Safe Environment Variables
|
|
207
225
|
|
|
208
226
|
When writing a Hattip handler without this package, the `context.env()` method is inherently unsafe. Its return type is always `string | undefined`, which means you either need to write defensive checks or use type assertions. Neither is ideal.
|
|
@@ -235,3 +253,110 @@ const myMiddleware = (context: RequestContext<any, Env>) => {
|
|
|
235
253
|
```
|
|
236
254
|
|
|
237
255
|
In both examples, we skip declaring any additional context properties (the first type parameter) because we're not using any. The second type parameter is for environment variables. The third is for the special `context.platform` property, whose value is provided by the host platform (e.g. Node.js, Deno, Bun, etc). On principle, a middleware should avoid using the `context.platform` property, since that could make it non-portable unless you write extra fallback logic for other hosts.
|
|
256
|
+
|
|
257
|
+
## Router
|
|
258
|
+
|
|
259
|
+
The `routes` function provides a way to create a router instance for handling different paths and HTTP methods.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { routes } from 'alien-middleware/router'
|
|
263
|
+
|
|
264
|
+
const router = routes()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Path Parameter Type Inference
|
|
268
|
+
|
|
269
|
+
The `routes` function leverages `pathic` to provide type inference for path parameters.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { routes } from 'alien-middleware/router'
|
|
273
|
+
|
|
274
|
+
const router = routes()
|
|
275
|
+
|
|
276
|
+
router.use('/users/:userId', context => {
|
|
277
|
+
// context.params.userId is automatically typed as string
|
|
278
|
+
const userId = context.params.userId
|
|
279
|
+
return new Response(`User ID: ${userId}`)
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Handling Specific HTTP Methods
|
|
284
|
+
|
|
285
|
+
You can specify one or more HTTP methods for a route by providing the method(s) as the first argument to `.use()`.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { routes } from 'alien-middleware/router'
|
|
289
|
+
|
|
290
|
+
const router = routes()
|
|
291
|
+
|
|
292
|
+
// This handler will only run for GET requests to /api/items
|
|
293
|
+
router.use('GET', '/api/items', context => {
|
|
294
|
+
return new Response('List of items')
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// This handler will only run for POST requests to /api/items
|
|
298
|
+
router.use('POST', '/api/items', context => {
|
|
299
|
+
return new Response('Create a new item', { status: 201 })
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// This handler will run for both PUT and PATCH requests to /api/items/:id
|
|
303
|
+
router.use(['PUT', 'PATCH'], '/api/items/:id', context => {
|
|
304
|
+
const itemId = context.params.id
|
|
305
|
+
return new Response(`Update item ${itemId}`)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// This handler will run for any method to /status
|
|
309
|
+
router.use('/status', context => {
|
|
310
|
+
return new Response('Status: OK')
|
|
311
|
+
})
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
> [!NOTE]
|
|
315
|
+
> Your routes don't need to be in any particular order, unless their path
|
|
316
|
+
> patterns are exactly the same. The `pathic` library will match the most
|
|
317
|
+
> specific path first. This allows you to split your routes into multiple files
|
|
318
|
+
> for better organization.
|
|
319
|
+
|
|
320
|
+
### Type-Safe Middleware with `routes()`
|
|
321
|
+
|
|
322
|
+
You can pass a middleware chain to the `routes()` function to apply middleware specifically to the routes defined by that router instance. This provides type safety for context extensions within the router.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import {
|
|
326
|
+
chain,
|
|
327
|
+
type RequestContext,
|
|
328
|
+
type RequestPlugin,
|
|
329
|
+
} from 'alien-middleware'
|
|
330
|
+
import { routes } from 'alien-middleware/router'
|
|
331
|
+
|
|
332
|
+
// Define a middleware that adds a user to the context
|
|
333
|
+
const addUserMiddleware = (context: RequestContext): RequestPlugin => {
|
|
334
|
+
const user = { id: 123, name: 'Alice' }
|
|
335
|
+
return { define: { user } }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Create a chain with the middleware
|
|
339
|
+
const authMiddlewares = chain(addUserMiddleware)
|
|
340
|
+
|
|
341
|
+
// Pass the chain to the routes function
|
|
342
|
+
const authenticatedRouter = routes(authMiddlewares)
|
|
343
|
+
|
|
344
|
+
// Define a route that uses the context property added by the middleware
|
|
345
|
+
authenticatedRouter.use('/profile', context => {
|
|
346
|
+
// context.user is now type-safe and available
|
|
347
|
+
return new Response(`Welcome, ${context.user.name}!`)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// Routes defined on a router without a chain won't have the user property
|
|
351
|
+
const publicRouter = routes()
|
|
352
|
+
|
|
353
|
+
publicRouter.use('/public', context => {
|
|
354
|
+
// context.user is not available here
|
|
355
|
+
// @ts-expect-error - user is not defined on this context
|
|
356
|
+
console.log(context.user)
|
|
357
|
+
return new Response('Public content')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// You can combine routers using .use()
|
|
361
|
+
const app = chain().use(authenticatedRouter).use(publicRouter)
|
|
362
|
+
```
|