@asterflow/router 1.0.13 → 2.0.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 +34 -127
- package/dist/cjs/index.cjs +89 -29
- package/dist/mjs/index.js +68 -12
- package/dist/types/controllers/Method.d.ts +67 -8
- package/dist/types/controllers/Middleware.d.ts +3 -3
- package/dist/types/controllers/Router.d.ts +65 -12
- package/dist/types/controllers/extensionRegistry.d.ts +10 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/types/method.d.ts +83 -11
- package/dist/types/types/mindleware.d.ts +9 -1
- package/dist/types/types/router.d.ts +110 -15
- package/dist/types/types/utils.d.ts +9 -0
- package/package.json +24 -6
package/README.md
CHANGED
|
@@ -4,162 +4,69 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
7
|
-

|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
-
>
|
|
13
|
+
> Typed route and middleware definitions (`Method`, `Router`, `Middleware`) for AsterFlow applications.
|
|
14
14
|
|
|
15
15
|
## 📦 Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install @asterflow/router
|
|
19
|
-
# or
|
|
20
18
|
bun install @asterflow/router
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
### ✨ Features
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
- **`Method`** - defines a single route bound to one HTTP verb, passed as the first argument (`new Method('post', {...})` or `Method.POST`). `Method.create(method)` defers the handler so plugins can chain in request extensions (e.g. `.multipart({...})`) before the terminal `.handler(fn)` call.
|
|
24
|
+
- **`Router`** - groups handlers for several HTTP verbs under one path. `Router.builder()` gives an extensible, per-method builder (`.method('post', b => ...)`) so each verb can carry its own request extensions independently.
|
|
25
|
+
- **`Middleware`** - typed middleware chain. `onRun` either calls `next(params)` to continue (merging `params` into the typed `middleware` context) or returns an `AsterResponse` to short-circuit the chain.
|
|
26
|
+
- **Schema validation** - route schemas accept a Zod schema or a `@caeljs/tsh` shape; the parsed result is inferred straight into the handler's `schema` argument.
|
|
27
|
+
- **Extension registry** - a `WeakMap`-based store (`extensionRegistry.ts`) that plugins use to attach per-router, per-method data, read back at request time.
|
|
26
28
|
|
|
27
|
-
##
|
|
29
|
+
## ❓ How to Use
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
- **Middleware System:** Support for middlewares with typed context and chaining
|
|
31
|
-
- **Parameter Validation:** Built-in support for Zod and @caeljs/tsh
|
|
32
|
-
- **URL Analysis:** Integrated URL parser with support for dynamic parameters, query strings, and fragments
|
|
33
|
-
- **Standardized Responses:** Typed response system with helpers for common HTTP codes
|
|
34
|
-
- **Flexible Organization:** Support for individual routes (Method) and grouped routes (Router)
|
|
31
|
+
For a normal route, use `new Method(method, options)` - the HTTP method comes first, either as a plain string or as one of `Method`'s own constants (`Method.GET`, `Method.POST`, ...) - and validate the body with Zod:
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Basic Router Route
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { Router } from '@asterflow/router'
|
|
42
|
-
|
|
43
|
-
const router = new Router({
|
|
44
|
-
path: '/hello/:name',
|
|
45
|
-
methods: {
|
|
46
|
-
get({ response, url }) {
|
|
47
|
-
const params = url.getParams()
|
|
48
|
-
return response.success({
|
|
49
|
-
message: `Hello ${params.name}!`
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Using Middlewares
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { Middleware, Router } from '@asterflow/router'
|
|
60
|
-
|
|
61
|
-
const authMiddleware = new Middleware({
|
|
62
|
-
name: 'auth',
|
|
63
|
-
onRun({ next }) {
|
|
64
|
-
return next({
|
|
65
|
-
isAuthenticated: true,
|
|
66
|
-
user: { id: 1 }
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const router = new Router({
|
|
72
|
-
path: '/protected',
|
|
73
|
-
use: [authMiddleware],
|
|
74
|
-
methods: {
|
|
75
|
-
get({ response, middleware }) {
|
|
76
|
-
if (!middleware.isAuthenticated) {
|
|
77
|
-
return response.unauthorized({ message: 'Not authenticated' })
|
|
78
|
-
}
|
|
79
|
-
return response.success({ user: middleware.user })
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Validation with Zod
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
33
|
+
```ts
|
|
88
34
|
import { Method } from '@asterflow/router'
|
|
89
35
|
import { z } from 'zod'
|
|
90
36
|
|
|
91
|
-
|
|
37
|
+
export default new Method(Method.POST, {
|
|
92
38
|
path: '/users',
|
|
93
|
-
|
|
94
|
-
schema
|
|
95
|
-
|
|
96
|
-
email: z.string().email()
|
|
97
|
-
}),
|
|
98
|
-
handler: ({ schema, response }) => {
|
|
99
|
-
return response.created({
|
|
100
|
-
user: {
|
|
101
|
-
name: schema.name,
|
|
102
|
-
email: schema.email
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### URL Parameters
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
import { Router } from '@asterflow/router'
|
|
113
|
-
|
|
114
|
-
const router = new Router({
|
|
115
|
-
// Supports dynamic parameters (:id),
|
|
116
|
-
// query strings (?page) and
|
|
117
|
-
// fragments (#section)
|
|
118
|
-
path: '/users/:id=number?page#section',
|
|
119
|
-
methods: {
|
|
120
|
-
get({ url, response }) {
|
|
121
|
-
console.log(url.getParams()) // { id: number }
|
|
122
|
-
console.log(url.getSearchParams()) // { page: string }
|
|
123
|
-
console.log(url.getFragment()) // 'section'
|
|
124
|
-
return response.success({ /* ... */ })
|
|
125
|
-
}
|
|
39
|
+
schema: z.object({ name: z.string(), email: z.string().email() }),
|
|
40
|
+
handler({ schema, response }) {
|
|
41
|
+
return response.created({ user: schema })
|
|
126
42
|
}
|
|
127
43
|
})
|
|
128
44
|
```
|
|
129
45
|
|
|
130
|
-
|
|
46
|
+
Only reach for `Method.create(method, options?)` when you need a plugin's fully-typed extension - like multipart's `.multipart(schema)` - chained in before the handler. `create()` defers the handler so the extension can widen the request type first, and `options` is optional:
|
|
131
47
|
|
|
132
|
-
```
|
|
133
|
-
import {
|
|
134
|
-
import { AsterFlow } from 'asterflow'
|
|
135
|
-
import fastify from 'fastify'
|
|
136
|
-
|
|
137
|
-
const server = fastify()
|
|
138
|
-
const app = new AsterFlow({
|
|
139
|
-
driver: adapters.fastify
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
// Add routes
|
|
143
|
-
app.controller(router)
|
|
48
|
+
```ts
|
|
49
|
+
import { Method } from '@asterflow/router'
|
|
144
50
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
})
|
|
51
|
+
export default Method.create(Method.POST)
|
|
52
|
+
.multipart({
|
|
53
|
+
avatar: { mimeTypes: ['image/png', 'image/jpeg'], maxSize: 5 * 1024 * 1024, required: true }
|
|
54
|
+
})
|
|
55
|
+
.handler(({ request, response }) => {
|
|
56
|
+
const avatar = request.getFile('avatar')
|
|
57
|
+
return response.success({ filename: avatar.filename })
|
|
58
|
+
})
|
|
153
59
|
```
|
|
154
60
|
|
|
61
|
+
Both examples use `export default`: it's what `app.controller(route)` expects when you register a route by hand, and it's the export `@asterflow/fs`'s file-based routing looks for in every route file.
|
|
62
|
+
|
|
155
63
|
## 🔗 Related Packages
|
|
156
64
|
|
|
157
|
-
- [asterflow](https://www.npmjs.com/package/asterflow) -
|
|
158
|
-
- [@asterflow/
|
|
159
|
-
- [@asterflow/
|
|
160
|
-
- [@asterflow/
|
|
161
|
-
- [@asterflow/plugin](https://www.npmjs.com/package/@asterflow/plugin) - A modular and typed plugin system
|
|
65
|
+
- [asterflow](https://www.npmjs.com/package/asterflow) - core framework, depends on this package to register and run routes
|
|
66
|
+
- [@asterflow/request](https://www.npmjs.com/package/@asterflow/request) - request abstraction; this package imports its `Request`/`AsterRequest` types for route and middleware handlers
|
|
67
|
+
- [@asterflow/fs](https://www.npmjs.com/package/@asterflow/fs) - filesystem-based routing plugin, builds routes with `Method`/`Router`
|
|
68
|
+
- [@asterflow/multipart](https://www.npmjs.com/package/@asterflow/multipart) - multipart upload plugin, extends `Method` routes
|
|
162
69
|
|
|
163
70
|
## 📄 License
|
|
164
71
|
|
|
165
|
-
|
|
72
|
+
This project is licensed under the [MIT License](../../LICENSE).
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,45 +1,78 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
for (var
|
|
8
|
-
|
|
9
|
-
},
|
|
2
|
+
var i = Object.defineProperty;
|
|
3
|
+
var x = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var M = Object.getOwnPropertyNames;
|
|
5
|
+
var w = Object.prototype.hasOwnProperty;
|
|
6
|
+
var f = (r, e) => {
|
|
7
|
+
for (var t in e)
|
|
8
|
+
i(r, t, { get: e[t], enumerable: !0 });
|
|
9
|
+
}, g = (r, e, t, s) => {
|
|
10
10
|
if (e && typeof e == "object" || typeof e == "function")
|
|
11
|
-
for (let
|
|
12
|
-
!
|
|
13
|
-
return
|
|
11
|
+
for (let n of M(e))
|
|
12
|
+
!w.call(r, n) && n !== t && i(r, n, { get: () => e[n], enumerable: !(s = x(e, n)) || s.enumerable });
|
|
13
|
+
return r;
|
|
14
14
|
};
|
|
15
|
-
var
|
|
15
|
+
var A = (r) => g(i({}, "__esModule", { value: !0 }), r);
|
|
16
16
|
// packages/router/src/index.ts
|
|
17
|
-
var
|
|
18
|
-
|
|
19
|
-
Method: () =>
|
|
20
|
-
MethodType: () =>
|
|
21
|
-
Middleware: () =>
|
|
22
|
-
|
|
17
|
+
var E = {};
|
|
18
|
+
f(E, {
|
|
19
|
+
Method: () => u,
|
|
20
|
+
MethodType: () => c,
|
|
21
|
+
Middleware: () => l,
|
|
22
|
+
RouteMethodBuilder: () => a,
|
|
23
|
+
Router: () => d,
|
|
24
|
+
RouterBuilder: () => p,
|
|
25
|
+
getRouteExtensions: () => S,
|
|
26
|
+
setRouteExtensions: () => h
|
|
23
27
|
});
|
|
24
|
-
module.exports =
|
|
28
|
+
module.exports = A(E);
|
|
29
|
+
// packages/router/src/controllers/extensionRegistry.ts
|
|
30
|
+
var m = new WeakMap();
|
|
31
|
+
function h(r, e) {
|
|
32
|
+
Object.keys(e).length !== 0 && m.set(r, e);
|
|
33
|
+
}
|
|
34
|
+
function S(r) {
|
|
35
|
+
return m.get(r);
|
|
36
|
+
}
|
|
25
37
|
// packages/router/src/controllers/Router.ts
|
|
26
|
-
var
|
|
38
|
+
var d = class r {
|
|
27
39
|
name;
|
|
28
40
|
path;
|
|
41
|
+
param;
|
|
29
42
|
schema;
|
|
30
43
|
description;
|
|
31
44
|
methods;
|
|
32
45
|
use;
|
|
33
46
|
constructor(e) {
|
|
34
|
-
let { name:
|
|
35
|
-
this.name =
|
|
47
|
+
let { name: t, path: s, param: n, schema: y, description: P, methods: R } = e;
|
|
48
|
+
this.name = t, this.path = s ?? n, this.schema = y, this.description = P, this.methods = R, this.use = e.use;
|
|
36
49
|
}
|
|
37
50
|
static create() {
|
|
38
|
-
return (e) => new
|
|
51
|
+
return (e) => new r(e);
|
|
52
|
+
}
|
|
53
|
+
static builder(e) {
|
|
54
|
+
return new p(e);
|
|
55
|
+
}
|
|
56
|
+
}, a = class {
|
|
57
|
+
registrations = {};
|
|
58
|
+
extend(e, t) {
|
|
59
|
+
return t && Object.assign(this.registrations, t), this;
|
|
60
|
+
}
|
|
61
|
+
handler(e) {
|
|
62
|
+
return { handler: e, registrations: this.registrations };
|
|
63
|
+
}
|
|
64
|
+
}, p = class extends d {
|
|
65
|
+
perMethodRegistrations = {};
|
|
66
|
+
constructor(e = {}) {
|
|
67
|
+
super({ ...e, methods: {} });
|
|
68
|
+
}
|
|
69
|
+
method(e, t) {
|
|
70
|
+
let s = t(new a());
|
|
71
|
+
return this.methods[e] = s.handler, Object.keys(s.registrations).length > 0 && (this.perMethodRegistrations[e] = s.registrations, h(this, this.perMethodRegistrations)), this;
|
|
39
72
|
}
|
|
40
73
|
};
|
|
41
74
|
// packages/router/src/controllers/Middleware.ts
|
|
42
|
-
var
|
|
75
|
+
var l = class {
|
|
43
76
|
name;
|
|
44
77
|
onRun;
|
|
45
78
|
constructor(e) {
|
|
@@ -47,22 +80,49 @@ var i = class {
|
|
|
47
80
|
}
|
|
48
81
|
};
|
|
49
82
|
// packages/router/src/controllers/Method.ts
|
|
50
|
-
var
|
|
83
|
+
var u = class r {
|
|
84
|
+
static ALL = "all";
|
|
85
|
+
static GET = "get";
|
|
86
|
+
static POST = "post";
|
|
87
|
+
static PUT = "put";
|
|
88
|
+
static DELETE = "delete";
|
|
89
|
+
static OPTIONS = "options";
|
|
90
|
+
static HEAD = "head";
|
|
91
|
+
static PATCH = "patch";
|
|
51
92
|
path;
|
|
93
|
+
param;
|
|
52
94
|
method;
|
|
53
95
|
schema;
|
|
54
96
|
name;
|
|
55
97
|
use;
|
|
56
98
|
handler;
|
|
57
|
-
|
|
58
|
-
|
|
99
|
+
extensions = {};
|
|
100
|
+
constructor(e, t) {
|
|
101
|
+
this.path = t.path ?? t.param, this.method = e, this.schema = t.schema, this.use = t.use, this.handler = t.handler ?? ((s) => {
|
|
102
|
+
if (typeof s != "function")
|
|
103
|
+
throw new TypeError("This route was built with Method.create(...) but never finished with a terminal .handler(...) call before being used.");
|
|
104
|
+
return this.handler = s, this;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
static create(e, t = {}) {
|
|
108
|
+
return new r(e, {
|
|
109
|
+
...t,
|
|
110
|
+
handler: void 0
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
extend(e, t) {
|
|
114
|
+
return t && Object.assign(this.extensions, t), this;
|
|
59
115
|
}
|
|
60
116
|
};
|
|
61
117
|
// packages/router/src/types/method.ts
|
|
62
|
-
var
|
|
118
|
+
var c = ((o) => (o.all = "all", o.get = "get", o.post = "post", o.put = "put", o.delete = "delete", o.options = "options", o.head = "head", o.patch = "patch", o))(c || {});
|
|
63
119
|
0 && (module.exports = {
|
|
64
120
|
Method,
|
|
65
121
|
MethodType,
|
|
66
122
|
Middleware,
|
|
67
|
-
|
|
123
|
+
RouteMethodBuilder,
|
|
124
|
+
Router,
|
|
125
|
+
RouterBuilder,
|
|
126
|
+
getRouteExtensions,
|
|
127
|
+
setRouteExtensions
|
|
68
128
|
});
|
package/dist/mjs/index.js
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
|
+
// packages/router/src/controllers/extensionRegistry.ts
|
|
2
|
+
var p = new WeakMap();
|
|
3
|
+
function i(r, e) {
|
|
4
|
+
Object.keys(e).length !== 0 && p.set(r, e);
|
|
5
|
+
}
|
|
6
|
+
function R(r) {
|
|
7
|
+
return p.get(r);
|
|
8
|
+
}
|
|
1
9
|
// packages/router/src/controllers/Router.ts
|
|
2
|
-
var
|
|
10
|
+
var n = class r {
|
|
3
11
|
name;
|
|
4
12
|
path;
|
|
13
|
+
param;
|
|
5
14
|
schema;
|
|
6
15
|
description;
|
|
7
16
|
methods;
|
|
8
17
|
use;
|
|
9
18
|
constructor(e) {
|
|
10
|
-
let { name:
|
|
11
|
-
this.name =
|
|
19
|
+
let { name: t, path: s, param: u, schema: m, description: c, methods: y } = e;
|
|
20
|
+
this.name = t, this.path = s ?? u, this.schema = m, this.description = c, this.methods = y, this.use = e.use;
|
|
12
21
|
}
|
|
13
22
|
static create() {
|
|
14
23
|
return (e) => new r(e);
|
|
15
24
|
}
|
|
25
|
+
static builder(e) {
|
|
26
|
+
return new a(e);
|
|
27
|
+
}
|
|
28
|
+
}, d = class {
|
|
29
|
+
registrations = {};
|
|
30
|
+
extend(e, t) {
|
|
31
|
+
return t && Object.assign(this.registrations, t), this;
|
|
32
|
+
}
|
|
33
|
+
handler(e) {
|
|
34
|
+
return { handler: e, registrations: this.registrations };
|
|
35
|
+
}
|
|
36
|
+
}, a = class extends n {
|
|
37
|
+
perMethodRegistrations = {};
|
|
38
|
+
constructor(e = {}) {
|
|
39
|
+
super({ ...e, methods: {} });
|
|
40
|
+
}
|
|
41
|
+
method(e, t) {
|
|
42
|
+
let s = t(new d());
|
|
43
|
+
return this.methods[e] = s.handler, Object.keys(s.registrations).length > 0 && (this.perMethodRegistrations[e] = s.registrations, i(this, this.perMethodRegistrations)), this;
|
|
44
|
+
}
|
|
16
45
|
};
|
|
17
46
|
// packages/router/src/controllers/Middleware.ts
|
|
18
|
-
var
|
|
47
|
+
var h = class {
|
|
19
48
|
name;
|
|
20
49
|
onRun;
|
|
21
50
|
constructor(e) {
|
|
@@ -23,22 +52,49 @@ var n = class {
|
|
|
23
52
|
}
|
|
24
53
|
};
|
|
25
54
|
// packages/router/src/controllers/Method.ts
|
|
26
|
-
var
|
|
55
|
+
var l = class r {
|
|
56
|
+
static ALL = "all";
|
|
57
|
+
static GET = "get";
|
|
58
|
+
static POST = "post";
|
|
59
|
+
static PUT = "put";
|
|
60
|
+
static DELETE = "delete";
|
|
61
|
+
static OPTIONS = "options";
|
|
62
|
+
static HEAD = "head";
|
|
63
|
+
static PATCH = "patch";
|
|
27
64
|
path;
|
|
65
|
+
param;
|
|
28
66
|
method;
|
|
29
67
|
schema;
|
|
30
68
|
name;
|
|
31
69
|
use;
|
|
32
70
|
handler;
|
|
33
|
-
|
|
34
|
-
|
|
71
|
+
extensions = {};
|
|
72
|
+
constructor(e, t) {
|
|
73
|
+
this.path = t.path ?? t.param, this.method = e, this.schema = t.schema, this.use = t.use, this.handler = t.handler ?? ((s) => {
|
|
74
|
+
if (typeof s != "function")
|
|
75
|
+
throw new TypeError("This route was built with Method.create(...) but never finished with a terminal .handler(...) call before being used.");
|
|
76
|
+
return this.handler = s, this;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
static create(e, t = {}) {
|
|
80
|
+
return new r(e, {
|
|
81
|
+
...t,
|
|
82
|
+
handler: void 0
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
extend(e, t) {
|
|
86
|
+
return t && Object.assign(this.extensions, t), this;
|
|
35
87
|
}
|
|
36
88
|
};
|
|
37
89
|
// packages/router/src/types/method.ts
|
|
38
|
-
var
|
|
90
|
+
var P = ((o) => (o.all = "all", o.get = "get", o.post = "post", o.put = "put", o.delete = "delete", o.options = "options", o.head = "head", o.patch = "patch", o))(P || {});
|
|
39
91
|
export {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
92
|
+
l as Method,
|
|
93
|
+
P as MethodType,
|
|
94
|
+
h as Middleware,
|
|
95
|
+
d as RouteMethodBuilder,
|
|
96
|
+
n as Router,
|
|
97
|
+
a as RouterBuilder,
|
|
98
|
+
R as getRouteExtensions,
|
|
99
|
+
i as setRouteExtensions
|
|
44
100
|
};
|
|
@@ -1,16 +1,75 @@
|
|
|
1
1
|
import type { Runtime } from '@asterflow/adapter';
|
|
2
2
|
import type { AnyAsterflow } from 'asterflow';
|
|
3
3
|
import type { Responders } from '@asterflow/response';
|
|
4
|
-
import type { MethodHandler, MethodKeys,
|
|
4
|
+
import type { DefaultMethodProps, MethodBuilderOptions, MethodCallProps, MethodConstructorOptions, MethodHandler, MethodKeys, MethodProps } from '../types/method';
|
|
5
5
|
import type { MiddlewareOutput } from '../types/mindleware';
|
|
6
6
|
import type { AnySchema } from '../types/schema';
|
|
7
|
+
import type { MergeProps } from '../types/utils';
|
|
7
8
|
import type { Middleware } from './Middleware';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `handler`'s field type: a concrete `MethodHandler<...>` once finished, or
|
|
11
|
+
* - while `Handler` is `undefined` (pending) - the terminal chain call
|
|
12
|
+
* itself, `<H extends MethodHandler<...>>(fn: H) => Method<..., H>`.
|
|
13
|
+
*/
|
|
14
|
+
export type MethodHandlerSlot<Props extends MethodProps> = Props['handler'] extends undefined ? <H extends MethodHandler<Props['path'], Props['drive'], Props['responder'], Props['schema'], Props['middlewares'], Props['context'], Props['instance'], Props['requestExt']>>(fn: H) => Method<MergeProps<Props, {
|
|
15
|
+
handler: H;
|
|
16
|
+
}>> : Props['handler'];
|
|
17
|
+
export declare class Method<const Props extends MethodProps = DefaultMethodProps> {
|
|
18
|
+
/** Convenience constants for `new Method(Method.POST, {...})` / `Method.create(Method.POST)` - a plain `'post'` string works just as well. */
|
|
19
|
+
static readonly ALL: 'all';
|
|
20
|
+
static readonly GET: 'get';
|
|
21
|
+
static readonly POST: 'post';
|
|
22
|
+
static readonly PUT: 'put';
|
|
23
|
+
static readonly DELETE: 'delete';
|
|
24
|
+
static readonly OPTIONS: 'options';
|
|
25
|
+
static readonly HEAD: 'head';
|
|
26
|
+
static readonly PATCH: 'patch';
|
|
27
|
+
path: Props['path'];
|
|
28
|
+
param?: Props['path'];
|
|
29
|
+
method: Props['methodKey'];
|
|
30
|
+
schema?: Props['schema'];
|
|
12
31
|
name?: string;
|
|
13
|
-
use?:
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
use?: Props['middlewares'];
|
|
33
|
+
/**
|
|
34
|
+
* While pending (`Handler` is `undefined`), this field's runtime value is
|
|
35
|
+
* a self-replacing closure: calling it with the real handler assigns that
|
|
36
|
+
* function back onto `handler` and returns `this`. Lexically bound to
|
|
37
|
+
* `this`, so it still works detached from `route` (as `Asterflow`'s
|
|
38
|
+
* `runHandler` does). Throws if called with anything but a function.
|
|
39
|
+
*/
|
|
40
|
+
handler: MethodHandlerSlot<Props>;
|
|
41
|
+
/** Plugin registrations from `extend` (e.g. multipart's `.multipart(schema)`), read back via `route.extensions.multipart`. */
|
|
42
|
+
readonly extensions: Record<string, unknown>;
|
|
43
|
+
constructor(method: Props['methodKey'], options: MethodConstructorOptions<Props>);
|
|
44
|
+
/**
|
|
45
|
+
* Defers `handler` to a terminal `.handler(fn)` call so plugins can chain
|
|
46
|
+
* `request` extensions first, e.g.
|
|
47
|
+
* `Method.create(Method.POST).multipart({...}).handler(...)`. Returns a
|
|
48
|
+
* real `Method`, not a separate class.
|
|
49
|
+
*
|
|
50
|
+
* `options` is optional - every one of its fields already is - so a route
|
|
51
|
+
* with nothing but a method and a deferred handler can skip it entirely:
|
|
52
|
+
* `Method.create(Method.GET).handler(...)`.
|
|
53
|
+
*
|
|
54
|
+
* Static, not folded into the constructor: a constructor shares one set
|
|
55
|
+
* of type-parameter defaults, and the default that infers an eager inline
|
|
56
|
+
* handler's parameters isn't the one that correctly types a pending
|
|
57
|
+
* route's `handler` field - verified empirically. Only a method can give
|
|
58
|
+
* each its own.
|
|
59
|
+
*
|
|
60
|
+
* Plugins add their own chain method via `declare module '@asterflow/router'
|
|
61
|
+
* { interface Method<...> { theirMethod(...): Method<...> } }` plus a real
|
|
62
|
+
* `Method.prototype.theirMethod = ...` implementation calling `extend`.
|
|
63
|
+
*/
|
|
64
|
+
static create<Responder extends Responders, const Path extends string = string, const Drive extends Runtime = Runtime, const MethodKey extends MethodKeys = MethodKeys, const Schema extends AnySchema = AnySchema, const Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[] = []>(method: MethodKey, options?: MethodBuilderOptions<MethodCallProps<Responder, Path, Drive, MethodKey, Schema, Middlewares, MiddlewareOutput<Middlewares>, AnyAsterflow, {}, undefined>>): Method<MethodCallProps<Responder, Path, Drive, MethodKey, Schema, Middlewares, MiddlewareOutput<Middlewares>, AnyAsterflow, {}, undefined>>;
|
|
65
|
+
/**
|
|
66
|
+
* Primitive every plugin's chain method calls. `typeFragment` only drives
|
|
67
|
+
* inference of `E`, never read at runtime; `runtimeData` merges into
|
|
68
|
+
* `this.extensions`. Meaningful only pre-`.handler(fn)` - calling it after
|
|
69
|
+
* loses precise `Handler` typing (`any`) rather than fighting the bound.
|
|
70
|
+
*/
|
|
71
|
+
extend<E extends Record<string, unknown>>(_typeFragment: E, runtimeData?: Record<string, unknown>): Method<MergeProps<Props, {
|
|
72
|
+
requestExt: Props['requestExt'] & E;
|
|
73
|
+
handler: Props['handler'] extends undefined ? undefined : any;
|
|
74
|
+
}>>;
|
|
16
75
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Responders } from '@asterflow/response';
|
|
2
2
|
import type { MiddlewareOptions } from '../types/mindleware';
|
|
3
3
|
import type { AnySchema } from '../types/schema';
|
|
4
|
-
export declare class Middleware<Responder extends Responders, Schema extends AnySchema, const Name extends string = string, const Parameters extends Record<string, unknown> = Record<string, unknown>> {
|
|
4
|
+
export declare class Middleware<Responder extends Responders = Responders, Schema extends AnySchema = AnySchema, const Name extends string = string, const Parameters extends Record<string, unknown> = Record<string, unknown>> {
|
|
5
5
|
readonly name: Name;
|
|
6
6
|
readonly onRun: <RequestType extends import("@asterflow/adapter").Runtime>(args: {
|
|
7
7
|
response: import("@asterflow/response").AsterResponse<Responder, import("@asterflow/response").BodyMap<Responder>, keyof Responder, import("@asterflow/response").BaseContext>;
|
|
8
8
|
request: import("@asterflow/request").AsterRequest<RequestType, {}>;
|
|
9
|
-
schema: import("
|
|
9
|
+
schema: import("@asterflow/router").InferSchema<Schema>;
|
|
10
10
|
next: <Parameter extends Record<string, unknown>>(params: Parameter) => MiddlewareOptions<Responder, Schema, Name, Parameter>;
|
|
11
|
-
}) =>
|
|
11
|
+
}) => import("@asterflow/router").MiddlewareResult<Responder, Schema, Name, Parameters> | Promise<import("@asterflow/router").MiddlewareResult<Responder, Schema, Name, Parameters>>;
|
|
12
12
|
constructor(options: MiddlewareOptions<Responder, Schema, Name, Parameters>);
|
|
13
13
|
}
|
|
@@ -1,20 +1,73 @@
|
|
|
1
1
|
import type { Responders } from '@asterflow/response';
|
|
2
2
|
import type { MethodKeys } from '../types/method';
|
|
3
3
|
import type { MiddlewareOutput } from '../types/mindleware';
|
|
4
|
-
import type { RouteHandler, RouterOptions } from '../types/router';
|
|
4
|
+
import type { BuiltRouteHandler, DefaultRouterProps, DefaultRouteMethodBuilderProps, DefaultRouterBuilderProps, RouteBuilderHandler, RouteHandler, RouteMethodBuilderProps, RouterBuilderOptions, RouterBuilderProps, RouterCallProps, RouterOptions, RouterProps } from '../types/router';
|
|
5
5
|
import type { AnySchema, SchemaDynamic } from '../types/schema';
|
|
6
|
+
import type { MergeProps } from '../types/utils';
|
|
6
7
|
import type { Middleware } from './Middleware';
|
|
7
|
-
export declare class Router<
|
|
8
|
-
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
9
|
-
} = {
|
|
10
|
-
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
11
|
-
}> {
|
|
8
|
+
export declare class Router<const Props extends RouterProps = DefaultRouterProps> {
|
|
12
9
|
name?: string;
|
|
13
|
-
path:
|
|
14
|
-
|
|
10
|
+
path: Props['path'];
|
|
11
|
+
param?: Props['path'];
|
|
12
|
+
schema?: Props['schema'];
|
|
15
13
|
description?: string;
|
|
16
|
-
methods:
|
|
17
|
-
use?:
|
|
18
|
-
constructor(options: RouterOptions<
|
|
19
|
-
static create<Responder extends Responders>(): <const Path extends string, const Schema extends SchemaDynamic<MethodKeys>, const Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[], const Context extends MiddlewareOutput<Middlewares>, const Routers extends { [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>; }>(options: RouterOptions<Path, Schema,
|
|
14
|
+
methods: Props['routers'];
|
|
15
|
+
use?: Props['middlewares'];
|
|
16
|
+
constructor(options: RouterOptions<Props>);
|
|
17
|
+
static create<Responder extends Responders>(): <const Path extends string, const Schema extends SchemaDynamic<MethodKeys>, const Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[], const Context extends MiddlewareOutput<Middlewares>, const Routers extends { [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>; }>(options: RouterOptions<RouterCallProps<Responder, Path, Schema, Middlewares, Context, Routers>>) => Router<RouterCallProps<Responder, Path, Schema, Middlewares, Context, Routers>>;
|
|
18
|
+
/**
|
|
19
|
+
* Entry point for the extensible builder (`RouterBuilder`, which extends
|
|
20
|
+
* `Router` itself): each HTTP method is added via `.method(key, ...)`
|
|
21
|
+
* instead of one `methods: {...}` object, so plugins can chain in
|
|
22
|
+
* per-method `request` extensions, e.g.
|
|
23
|
+
* `Router.builder({ use: [authMiddleware] }).method('post', b => b.multipart({...}).handler(...))`.
|
|
24
|
+
* `options` is entirely optional (name/path/use/schema) - `Router.builder()`
|
|
25
|
+
* with no arguments at all is valid too. The result is already a real
|
|
26
|
+
* `Router` - no finalization call needed.
|
|
27
|
+
*/
|
|
28
|
+
static builder<Responder extends Responders, const Path extends string = string, const Schema extends SchemaDynamic<MethodKeys> = SchemaDynamic<MethodKeys>, const Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[] = []>(options?: RouterBuilderOptions<RouterCallProps<Responder, Path, Schema, Middlewares, MiddlewareOutput<Middlewares>, {}>>): RouterBuilder<RouterCallProps<Responder, Path, Schema, Middlewares, MiddlewareOutput<Middlewares>, {}>>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Scoped to a single HTTP method within a `RouterBuilder.method(key, ...)`
|
|
32
|
+
* call - plugins' chain methods work identically here as on `Method` (same
|
|
33
|
+
* `extend` primitive), but the accumulated `RequestExt` only ever
|
|
34
|
+
* affects *this* method's handler, not sibling methods on the same router.
|
|
35
|
+
*/
|
|
36
|
+
export declare class RouteMethodBuilder<const Props extends RouteMethodBuilderProps = DefaultRouteMethodBuilderProps> {
|
|
37
|
+
private readonly registrations;
|
|
38
|
+
extend<E extends Record<string, unknown>>(_typeFragment: E, runtimeData?: Record<string, unknown>): RouteMethodBuilder<MergeProps<Props, {
|
|
39
|
+
requestExt: Props['requestExt'] & E;
|
|
40
|
+
}>>;
|
|
41
|
+
handler<Handler extends RouteBuilderHandler<Props['path'], Props['responder'], Props['methodKey'], Props['schema'], Props['context'], Props['requestExt']>>(fn: Handler): BuiltRouteHandler<Handler>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The extensible counterpart to `new Router({...})`. Each HTTP method is
|
|
45
|
+
* added via `.method(key, builder => builder.somePluginExtension({...}).handler(...))`,
|
|
46
|
+
* so different methods on the same router can have independently typed
|
|
47
|
+
* `request` extensions - e.g. `multipart` criteria declared for `post` only.
|
|
48
|
+
*
|
|
49
|
+
* Extends `Router` directly rather than staging into a separate object: each
|
|
50
|
+
* `.method(...)` call mutates `this.methods` in place and returns `this`, so
|
|
51
|
+
* the instance is a real, usable `Router` from the moment `Router.builder(...)`
|
|
52
|
+
* is called (`instanceof Router` holds immediately) - no finalization step.
|
|
53
|
+
* Same self-completing shape as `Method.create(...)`'s pending `handler`.
|
|
54
|
+
*
|
|
55
|
+
* Defined in this same file (not a separate one) because it extends `Router`
|
|
56
|
+
* at class-definition time - splitting the two across mutually-importing
|
|
57
|
+
* modules creates a circular value import, which throws
|
|
58
|
+
* "Cannot access 'Router' before initialization" depending on load order.
|
|
59
|
+
*/
|
|
60
|
+
export declare class RouterBuilder<const Props extends RouterBuilderProps = DefaultRouterBuilderProps> extends Router<Props> {
|
|
61
|
+
private readonly perMethodRegistrations;
|
|
62
|
+
constructor(options?: RouterBuilderOptions<Props>);
|
|
63
|
+
method<MethodKey extends MethodKeys, Handler>(key: MethodKey, build: (builder: RouteMethodBuilder<{
|
|
64
|
+
responder: Props['responder'];
|
|
65
|
+
path: Props['path'];
|
|
66
|
+
methodKey: MethodKey;
|
|
67
|
+
schema: Props['schema'];
|
|
68
|
+
context: Props['context'];
|
|
69
|
+
requestExt: {};
|
|
70
|
+
}>) => BuiltRouteHandler<Handler>): RouterBuilder<MergeProps<Props, {
|
|
71
|
+
routers: Props['routers'] & Record<MethodKey, Handler>;
|
|
72
|
+
}>>;
|
|
20
73
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Called by `RouterBuilder.method(...)` each time a method registers plugin data. */
|
|
2
|
+
export declare function setRouteExtensions(route: object, extensions: Record<string, unknown>): void;
|
|
3
|
+
/**
|
|
4
|
+
* Reads back whatever a plugin stashed for this router, e.g.
|
|
5
|
+
* `getRouteExtensions(router)?.post?.multipart`. Returns `undefined` for
|
|
6
|
+
* routers built without `RouterBuilder` (`new Router({...})`) or that never
|
|
7
|
+
* called any extension method. `Method` routes never go through here - see
|
|
8
|
+
* `Method#extensions` instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getRouteExtensions(route: object): Record<string, unknown> | undefined;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './controllers/Router';
|
|
2
2
|
export * from './controllers/Middleware';
|
|
3
3
|
export * from './controllers/Method';
|
|
4
|
+
export * from './controllers/extensionRegistry';
|
|
4
5
|
export * from './types/router';
|
|
5
6
|
export * from './types/schema';
|
|
6
7
|
export * from './types/method';
|
|
7
8
|
export * from './types/mindleware';
|
|
9
|
+
export * from './types/utils';
|
|
@@ -2,10 +2,12 @@ import type { Runtime } from '@asterflow/adapter';
|
|
|
2
2
|
import type { AnyAsterflow } from 'asterflow';
|
|
3
3
|
import type { AsterRequest } from '@asterflow/request';
|
|
4
4
|
import type { Responders, AsterResponse } from '@asterflow/response';
|
|
5
|
-
import type { Analyze
|
|
5
|
+
import type { Analyze } from '@asterflow/url-parser';
|
|
6
|
+
import type { Method } from '../controllers/Method';
|
|
6
7
|
import type { Middleware } from '../controllers/Middleware';
|
|
7
8
|
import type { AnyMiddleware, MiddlewareOutput } from './mindleware';
|
|
8
9
|
import type { AnySchema, InferSchema } from './schema';
|
|
10
|
+
import type { Prettify } from './utils';
|
|
9
11
|
export declare enum MethodType {
|
|
10
12
|
all = "all",
|
|
11
13
|
get = "get",
|
|
@@ -16,22 +18,92 @@ export declare enum MethodType {
|
|
|
16
18
|
head = "head",
|
|
17
19
|
patch = "patch"
|
|
18
20
|
}
|
|
19
|
-
export type AnyMethodHandler = MethodHandler<string, Runtime, Responders, AnySchema, AnyMiddleware[], MiddlewareOutput<AnyMiddleware[]>, AnyAsterflow>;
|
|
20
21
|
export type MethodKeys = keyof typeof MethodType;
|
|
21
|
-
|
|
22
|
+
/** `Method`'s single generic parameter - the fields it needs to type a single route. */
|
|
23
|
+
export interface MethodProps {
|
|
24
|
+
responder: Responders;
|
|
25
|
+
path: string;
|
|
26
|
+
drive: Runtime;
|
|
27
|
+
methodKey: MethodKeys;
|
|
28
|
+
schema: AnySchema;
|
|
29
|
+
middlewares: readonly AnyMiddleware[];
|
|
30
|
+
context: unknown;
|
|
31
|
+
instance: AnyAsterflow;
|
|
32
|
+
requestExt: Record<string, unknown>;
|
|
33
|
+
handler: ((args: any) => any) | undefined;
|
|
34
|
+
}
|
|
35
|
+
export type AnyMethod = Method<any>;
|
|
36
|
+
/**
|
|
37
|
+
* Assembles `MethodProps`'s fields from independently-inferred generics into
|
|
38
|
+
* one named object type - same rationale as `RouterCallProps` in
|
|
39
|
+
* `types/router.ts`: `Method.create()`/`AsterFlow.method()` keep their own
|
|
40
|
+
* independent generics (needed for contextual typing of the handler
|
|
41
|
+
* callback), this alias just replaces the repeated object-literal with a name.
|
|
42
|
+
*
|
|
43
|
+
* Wrapped in `Prettify` so a resolved `Method<MethodCallProps<...>>` shows as
|
|
44
|
+
* a labeled `{ responder: ..., path: "...", ... }` object on hover instead of
|
|
45
|
+
* `MethodCallProps<Responders, "...", Runtime.Node, "post", ...>` - a
|
|
46
|
+
* positional arg list you'd otherwise have to cross-reference against this
|
|
47
|
+
* declaration to read.
|
|
48
|
+
*/
|
|
49
|
+
export type MethodCallProps<Responder extends Responders, Path extends string, Drive extends Runtime, MethodKey extends MethodKeys, Schema extends AnySchema, Middlewares extends readonly AnyMiddleware[], Context, Instance extends AnyAsterflow, RequestExt extends Record<string, unknown>, Handler> = Prettify<{
|
|
50
|
+
responder: Responder;
|
|
51
|
+
path: Path;
|
|
52
|
+
drive: Drive;
|
|
53
|
+
methodKey: MethodKey;
|
|
54
|
+
schema: Schema;
|
|
55
|
+
middlewares: Middlewares;
|
|
56
|
+
context: Context;
|
|
22
57
|
instance: Instance;
|
|
23
|
-
|
|
58
|
+
requestExt: RequestExt;
|
|
59
|
+
handler: Handler;
|
|
60
|
+
}>;
|
|
61
|
+
export type DefaultMethodProps = {
|
|
62
|
+
responder: Responders;
|
|
63
|
+
path: string;
|
|
64
|
+
drive: Runtime;
|
|
65
|
+
methodKey: MethodKeys;
|
|
66
|
+
schema: AnySchema;
|
|
67
|
+
middlewares: [];
|
|
68
|
+
context: MiddlewareOutput<[]>;
|
|
69
|
+
instance: AnyAsterflow;
|
|
70
|
+
requestExt: {};
|
|
71
|
+
handler: MethodHandler<string, Runtime, Responders, AnySchema, [], MiddlewareOutput<[]>, AnyAsterflow, {}>;
|
|
72
|
+
};
|
|
73
|
+
export type MethodHandler<Path extends string, Drive extends Runtime, Responder extends Responders, Schema extends AnySchema, Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[], Context, Instance extends AnyAsterflow, RequestExt extends Record<string, unknown> = {}> = (args: {
|
|
74
|
+
instance: Instance;
|
|
75
|
+
request: ExtendedRequest<Drive, RequestExt>;
|
|
24
76
|
response: AsterResponse<Responder>;
|
|
25
|
-
url: Analyze<
|
|
77
|
+
url: Analyze<string, Analyze<Path>>;
|
|
26
78
|
schema: InferSchema<Schema>;
|
|
27
79
|
middleware: Context;
|
|
28
80
|
}) => Promise<AsterResponse<Responder>> | AsterResponse<Responder>;
|
|
29
|
-
export type MethodOptions<
|
|
30
|
-
path
|
|
81
|
+
export type MethodOptions<Props extends MethodProps> = {
|
|
82
|
+
path?: Props['path'];
|
|
83
|
+
param?: Props['path'];
|
|
31
84
|
name?: string;
|
|
32
85
|
description?: string;
|
|
33
|
-
use?:
|
|
34
|
-
method:
|
|
35
|
-
schema?:
|
|
36
|
-
handler:
|
|
86
|
+
use?: Props['middlewares'];
|
|
87
|
+
method: Props['methodKey'];
|
|
88
|
+
schema?: Props['schema'];
|
|
89
|
+
handler: MethodHandler<Props['path'], Props['drive'], Props['responder'], Props['schema'], Props['middlewares'], Props['context'], Props['instance']>;
|
|
37
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* `new Method(method, options)`'s second argument - everything
|
|
93
|
+
* `MethodOptions` has except `method` itself, which is passed positionally
|
|
94
|
+
* instead of repeated inside the options object.
|
|
95
|
+
*/
|
|
96
|
+
export type MethodConstructorOptions<Props extends MethodProps> = Omit<MethodOptions<Props>, 'method'>;
|
|
97
|
+
/**
|
|
98
|
+
* `Method.create(method, options?)`'s second argument: everything
|
|
99
|
+
* `MethodConstructorOptions` has except `handler`, supplied later via the
|
|
100
|
+
* terminal `.handler()` call instead. Every remaining field is optional, so
|
|
101
|
+
* this argument can be omitted entirely, e.g. `Method.create(Method.POST)`.
|
|
102
|
+
*/
|
|
103
|
+
export type MethodBuilderOptions<Props extends MethodProps> = Omit<MethodConstructorOptions<Props>, 'handler'>;
|
|
104
|
+
/**
|
|
105
|
+
* `RequestExt`'s keys replace (not merely add to) the base request's -
|
|
106
|
+
* `Omit` then intersect, so a plugin's narrowed method (e.g. multipart's
|
|
107
|
+
* `getFile`) is the only signature available, not an extra overload.
|
|
108
|
+
*/
|
|
109
|
+
export type ExtendedRequest<Drive extends Runtime, RequestExt extends Record<string, unknown>> = Omit<AsterRequest<Drive>, keyof RequestExt> & RequestExt;
|
|
@@ -9,6 +9,14 @@ export type AnyMiddlewares = readonly AnyMiddleware[];
|
|
|
9
9
|
* Accumulate the output types (`P`) from an array of middlewares into a single object
|
|
10
10
|
*/
|
|
11
11
|
export type MiddlewareOutput<Ms extends readonly AnyMiddleware[]> = Ms extends readonly [infer First, ...infer Rest] ? First extends Middleware<any, any, any, infer P> ? Rest extends readonly AnyMiddleware[] ? P & MiddlewareOutput<Rest> : P : unknown : unknown;
|
|
12
|
+
/**
|
|
13
|
+
* What a middleware's `onRun` may return: either `next(params)`'s result -
|
|
14
|
+
* continue the chain, merging `params` into the accumulated `middleware`
|
|
15
|
+
* context - or an `AsterResponse` (e.g. `response.unauthorized({...})`) to
|
|
16
|
+
* short-circuit the chain and send that response immediately, without
|
|
17
|
+
* running the remaining middlewares or the route handler.
|
|
18
|
+
*/
|
|
19
|
+
export type MiddlewareResult<Responder extends Responders = Responders, Schema extends AnySchema = AnySchema, Name extends string = string, Parameters extends Record<string, unknown> = Record<string, unknown>> = MiddlewareOptions<Responder, Schema, Name, Parameters> | AsterResponse<Responder>;
|
|
12
20
|
export type MiddlewareOptions<Responder extends Responders = Responders, Schema extends AnySchema = AnySchema, Name extends string = string, Parameters extends Record<string, unknown> = Record<string, unknown>> = {
|
|
13
21
|
name: Name;
|
|
14
22
|
onRun<RequestType extends Runtime>(args: {
|
|
@@ -16,5 +24,5 @@ export type MiddlewareOptions<Responder extends Responders = Responders, Schema
|
|
|
16
24
|
request: Request<RequestType>;
|
|
17
25
|
schema: InferSchema<Schema>;
|
|
18
26
|
next: <Parameter extends Record<string, unknown>>(params: Parameter) => MiddlewareOptions<Responder, Schema, Name, Parameter>;
|
|
19
|
-
}):
|
|
27
|
+
}): MiddlewareResult<Responder, Schema, Name, Parameters> | Promise<MiddlewareResult<Responder, Schema, Name, Parameters>>;
|
|
20
28
|
};
|
|
@@ -1,31 +1,126 @@
|
|
|
1
1
|
import type { Runtime } from '@asterflow/adapter';
|
|
2
2
|
import type { Request } from '@asterflow/request';
|
|
3
3
|
import type { AsterResponse, Responders } from '@asterflow/response';
|
|
4
|
-
import type { Analyze
|
|
4
|
+
import type { Analyze } from '@asterflow/url-parser';
|
|
5
5
|
import type { Method } from '../controllers/Method';
|
|
6
6
|
import type { Middleware } from '../controllers/Middleware';
|
|
7
|
-
import type { Router } from '../controllers/Router';
|
|
7
|
+
import type { Router, RouteMethodBuilder } from '../controllers/Router';
|
|
8
8
|
import type { MethodKeys } from './method';
|
|
9
|
-
import type { AnyMiddleware
|
|
9
|
+
import type { AnyMiddleware } from './mindleware';
|
|
10
10
|
import type { AnySchema, InferredData, SchemaDynamic } from './schema';
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import type { Prettify } from './utils';
|
|
12
|
+
export type AnyRouter = Router<any> | Method<any>;
|
|
13
|
+
/** `Router`'s single generic parameter - the fields it needs to type a finished router. */
|
|
14
|
+
export interface RouterProps {
|
|
15
|
+
responder: Responders;
|
|
16
|
+
path: string;
|
|
17
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
18
|
+
middlewares: readonly AnyMiddleware[];
|
|
19
|
+
context: unknown;
|
|
20
|
+
routers: Partial<Record<MethodKeys, unknown>>;
|
|
21
|
+
}
|
|
22
|
+
export type DefaultRouterProps = {
|
|
23
|
+
responder: Responders;
|
|
24
|
+
path: string;
|
|
25
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
26
|
+
middlewares: [];
|
|
27
|
+
context: unknown;
|
|
28
|
+
routers: {};
|
|
13
29
|
};
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Assembles `RouterProps`'s fields from independently-inferred generics into
|
|
32
|
+
* one named object type. Entry points (`Router.create()`, `.builder()`,
|
|
33
|
+
* `AsterFlow.router()`) can't infer a single `Props` object directly from
|
|
34
|
+
* their call site - each field is inferred from a different part of
|
|
35
|
+
* `options`, and contextual typing of the `methods` handlers depends on
|
|
36
|
+
* `Path`/`Responder`/`Schema`/`Middlewares`/`Context` existing as separate
|
|
37
|
+
* type-vars at that point - so they keep independent generics. This alias
|
|
38
|
+
* just replaces the "respell the object literal 2-3x per signature" pattern
|
|
39
|
+
* with one name.
|
|
40
|
+
*
|
|
41
|
+
* Wrapped in `Prettify` so a resolved `Router<RouterCallProps<...>>` shows as
|
|
42
|
+
* a labeled `{ responder: ..., path: "...", ... }` object on hover instead of
|
|
43
|
+
* `RouterCallProps<Responders, "...", ...>` - a positional arg list you'd
|
|
44
|
+
* otherwise have to cross-reference against this declaration to read.
|
|
45
|
+
*/
|
|
46
|
+
export type RouterCallProps<Responder extends Responders, Path extends string, Schema extends SchemaDynamic<MethodKeys>, Middlewares extends readonly AnyMiddleware[], Context, Routers extends Partial<Record<MethodKeys, unknown>>> = Prettify<{
|
|
47
|
+
responder: Responder;
|
|
48
|
+
path: Path;
|
|
49
|
+
schema: Schema;
|
|
50
|
+
middlewares: Middlewares;
|
|
51
|
+
context: Context;
|
|
52
|
+
routers: Routers;
|
|
53
|
+
}>;
|
|
54
|
+
/** `RouterBuilder`'s single generic parameter - same fields as `RouterProps`, but `routers` accumulates as `.method(...)` is chained. */
|
|
55
|
+
export interface RouterBuilderProps {
|
|
56
|
+
responder: Responders;
|
|
57
|
+
path: string;
|
|
58
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
59
|
+
middlewares: readonly AnyMiddleware[];
|
|
60
|
+
context: unknown;
|
|
61
|
+
routers: Partial<Record<MethodKeys, unknown>>;
|
|
62
|
+
}
|
|
63
|
+
export type DefaultRouterBuilderProps = {
|
|
64
|
+
responder: Responders;
|
|
65
|
+
path: string;
|
|
66
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
67
|
+
middlewares: [];
|
|
68
|
+
context: unknown;
|
|
69
|
+
routers: {};
|
|
70
|
+
};
|
|
71
|
+
/** `RouteMethodBuilder`'s single generic parameter - the fields it needs to type a scoped-to-one-HTTP-method builder. */
|
|
72
|
+
export interface RouteMethodBuilderProps {
|
|
73
|
+
responder: Responders;
|
|
74
|
+
path: string;
|
|
75
|
+
methodKey: MethodKeys;
|
|
76
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
77
|
+
context: unknown;
|
|
78
|
+
requestExt: Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
export type DefaultRouteMethodBuilderProps = {
|
|
81
|
+
responder: Responders;
|
|
82
|
+
path: string;
|
|
83
|
+
methodKey: MethodKeys;
|
|
84
|
+
schema: SchemaDynamic<MethodKeys>;
|
|
85
|
+
context: unknown;
|
|
86
|
+
requestExt: {};
|
|
87
|
+
};
|
|
88
|
+
export type AnyRouteMethodBuilder = RouteMethodBuilder<any>;
|
|
89
|
+
export type RouteHandler<Path extends string, Responder extends Responders, Method extends MethodKeys, Schema extends SchemaDynamic<Method>, Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[], Context> = <RequestType extends Runtime>(args: {
|
|
16
90
|
request: Request<RequestType>;
|
|
17
91
|
response: AsterResponse<Responder>;
|
|
18
|
-
url: Analyze<
|
|
92
|
+
url: Analyze<string, Analyze<Path>>;
|
|
19
93
|
schema: InferredData<Method, Schema>;
|
|
20
94
|
middleware: Context;
|
|
21
95
|
}) => Promise<AsterResponse> | AsterResponse;
|
|
22
|
-
export type RouterOptions<
|
|
23
|
-
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
24
|
-
}> = {
|
|
96
|
+
export type RouterOptions<Props extends RouterProps> = {
|
|
25
97
|
name?: string;
|
|
26
98
|
description?: string;
|
|
27
|
-
path
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
99
|
+
path?: Props['path'];
|
|
100
|
+
param?: Props['path'];
|
|
101
|
+
use?: Props['middlewares'];
|
|
102
|
+
schema?: Props['schema'];
|
|
103
|
+
methods: Props['routers'];
|
|
104
|
+
};
|
|
105
|
+
/** `RouterBuilder`'s constructor options: `RouterOptions` minus `methods` - each method is added via `.method(key, ...)` instead. */
|
|
106
|
+
export type RouterBuilderOptions<Props extends RouterBuilderProps> = {
|
|
107
|
+
name?: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
path?: Props['path'];
|
|
110
|
+
param?: Props['path'];
|
|
111
|
+
use?: Props['middlewares'];
|
|
112
|
+
schema?: Props['schema'];
|
|
113
|
+
};
|
|
114
|
+
/** A single HTTP method's handler on a `RouterBuilder`, with `RequestExt` replacing (not merely adding to) the base request's matching keys - same `Omit`-then-intersect as `ExtendedRequest`. */
|
|
115
|
+
export type RouteBuilderHandler<Path extends string, Responder extends Responders, Method extends MethodKeys, Schema extends SchemaDynamic<Method>, Context, RequestExt extends Record<string, unknown>> = <RequestType extends Runtime>(args: {
|
|
116
|
+
request: Omit<Request<RequestType>, keyof RequestExt> & RequestExt;
|
|
117
|
+
response: AsterResponse<Responder>;
|
|
118
|
+
url: Analyze<string, Analyze<Path>>;
|
|
119
|
+
schema: InferredData<Method, Schema>;
|
|
120
|
+
middleware: Context;
|
|
121
|
+
}) => Promise<AsterResponse> | AsterResponse;
|
|
122
|
+
/** What a `RouterBuilder.method(key, build)` callback must return - built via `RouteMethodBuilder`. */
|
|
123
|
+
export type BuiltRouteHandler<Handler> = {
|
|
124
|
+
handler: Handler;
|
|
125
|
+
registrations: Record<string, unknown>;
|
|
31
126
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Prettify<T> = {
|
|
2
|
+
[K in keyof T]: T[K];
|
|
3
|
+
} & {};
|
|
4
|
+
/**
|
|
5
|
+
* Produces a new `Props` object type equal to `Props` with `Patch`'s keys
|
|
6
|
+
* overridden. Lets a fluent builder method name only the field(s) actually
|
|
7
|
+
* changing instead of respelling every unchanged generic slot.
|
|
8
|
+
*/
|
|
9
|
+
export type MergeProps<Props, Patch> = Prettify<Omit<Props, keyof Patch> & Patch>;
|
package/package.json
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asterflow/router",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Typed route and middleware definitions (Method, Router, Middleware) for AsterFlow applications.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"asterflow",
|
|
7
|
+
"router",
|
|
8
|
+
"routing",
|
|
9
|
+
"middleware",
|
|
10
|
+
"http",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
4
13
|
"main": "dist/cjs/index.cjs",
|
|
5
14
|
"module": "dist/mjs/index.js",
|
|
6
15
|
"types": "dist/types/index.d.ts",
|
|
7
16
|
"typings": "dist/types/index.d.ts",
|
|
8
17
|
"type": "module",
|
|
9
18
|
"license": "MIT",
|
|
19
|
+
"author": "Ashu11-A",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/AsterFlow/AsterFlow.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/AsterFlow/AsterFlow/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/AsterFlow/AsterFlow",
|
|
10
28
|
"exports": {
|
|
11
29
|
".": {
|
|
12
30
|
"types": "./dist/types/index.d.ts",
|
|
@@ -18,14 +36,14 @@
|
|
|
18
36
|
"node": ">=20"
|
|
19
37
|
},
|
|
20
38
|
"devDependencies": {
|
|
21
|
-
"@asterflow/request": "1.0.13",
|
|
22
|
-
"@asterflow/response": "1.0.10",
|
|
23
|
-
"@asterflow/url-parser": "^
|
|
24
|
-
"asterflow": "0.0
|
|
39
|
+
"@asterflow/request": "^1.0.13",
|
|
40
|
+
"@asterflow/response": "^1.0.10",
|
|
41
|
+
"@asterflow/url-parser": "^4.1.1",
|
|
42
|
+
"asterflow": "^1.0.0"
|
|
25
43
|
},
|
|
26
44
|
"peerDependencies": {
|
|
27
45
|
"@caeljs/tsh": "^1.1.3",
|
|
28
|
-
"zod": "^3.25.
|
|
46
|
+
"zod": "^3.25.67",
|
|
29
47
|
"typescript": "^5.8.3"
|
|
30
48
|
}
|
|
31
49
|
}
|