@asterflow/router 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/dist/cjs/index.cjs +165 -0
- package/dist/cjs/package.json +3 -0
- package/dist/mjs/index.js +140 -0
- package/dist/mjs/package.json +3 -0
- package/dist/types/controllers/Method.d.ts +16 -0
- package/dist/types/controllers/Middleware.d.ts +13 -0
- package/dist/types/controllers/Response.d.ts +37 -0
- package/dist/types/controllers/Router.d.ts +19 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/types/method.d.ts +33 -0
- package/dist/types/types/mindleware.d.ts +19 -0
- package/dist/types/types/response.d.ts +12 -0
- package/dist/types/types/router.d.ts +31 -0
- package/dist/types/types/schema.d.ts +9 -0
- package/package.json +33 -0
- package/tsconfig.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @asterflow/router
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+

|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
> Type-safe and flexible routing system for AsterFlow applications.
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @asterflow/router
|
|
23
|
+
# or
|
|
24
|
+
bun install @asterflow/router
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 💡 About
|
|
28
|
+
|
|
29
|
+
@asterflow/router is the routing foundation of AsterFlow. It provides a powerful, type-safe routing system with support for middleware, parameter validation, and flexible route organization.
|
|
30
|
+
|
|
31
|
+
## ✨ Features
|
|
32
|
+
|
|
33
|
+
- **Complete Type Safety:** Full TypeScript support with type inference for routes, parameters, and responses
|
|
34
|
+
- **Middleware System:** Support for middlewares with typed context and chaining
|
|
35
|
+
- **Parameter Validation:** Built-in support for Zod and @caeljs/tsh
|
|
36
|
+
- **URL Analysis:** Integrated URL parser with support for dynamic parameters, query strings, and fragments
|
|
37
|
+
- **Standardized Responses:** Typed response system with helpers for common HTTP codes
|
|
38
|
+
- **Flexible Organization:** Support for individual routes (Method) and grouped routes (Router)
|
|
39
|
+
|
|
40
|
+
## 🚀 Usage
|
|
41
|
+
|
|
42
|
+
### Basic Router Route
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Router } from '@asterflow/router'
|
|
46
|
+
|
|
47
|
+
const router = new Router({
|
|
48
|
+
path: '/hello/:name',
|
|
49
|
+
methods: {
|
|
50
|
+
get({ response, url }) {
|
|
51
|
+
const params = url.getParams()
|
|
52
|
+
return response.success({
|
|
53
|
+
message: `Hello ${params.name}!`
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Using Middlewares
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { Middleware, Router } from '@asterflow/router'
|
|
64
|
+
|
|
65
|
+
const authMiddleware = new Middleware({
|
|
66
|
+
name: 'auth',
|
|
67
|
+
onRun({ next }) {
|
|
68
|
+
return next({
|
|
69
|
+
isAuthenticated: true,
|
|
70
|
+
user: { id: 1 }
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const router = new Router({
|
|
76
|
+
path: '/protected',
|
|
77
|
+
use: [authMiddleware],
|
|
78
|
+
methods: {
|
|
79
|
+
get({ response, middleware }) {
|
|
80
|
+
if (!middleware.isAuthenticated) {
|
|
81
|
+
return response.unauthorized({ message: 'Not authenticated' })
|
|
82
|
+
}
|
|
83
|
+
return response.success({ user: middleware.user })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Validation with Zod
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Method } from '@asterflow/router'
|
|
93
|
+
import { z } from 'zod'
|
|
94
|
+
|
|
95
|
+
const createUser = new Method({
|
|
96
|
+
path: '/users',
|
|
97
|
+
method: 'post',
|
|
98
|
+
schema: z.object({
|
|
99
|
+
name: z.string(),
|
|
100
|
+
email: z.string().email()
|
|
101
|
+
}),
|
|
102
|
+
handler: ({ schema, response }) => {
|
|
103
|
+
return response.created({
|
|
104
|
+
user: {
|
|
105
|
+
name: schema.name,
|
|
106
|
+
email: schema.email
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### URL Parameters
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { Router } from '@asterflow/router'
|
|
117
|
+
|
|
118
|
+
const router = new Router({
|
|
119
|
+
// Supports dynamic parameters (:id),
|
|
120
|
+
// query strings (?page) and
|
|
121
|
+
// fragments (#section)
|
|
122
|
+
path: '/users/:id=number?page#section',
|
|
123
|
+
methods: {
|
|
124
|
+
get({ url, response }) {
|
|
125
|
+
console.log(url.getParams()) // { id: number }
|
|
126
|
+
console.log(url.getSearchParams()) // { page: string }
|
|
127
|
+
console.log(url.getFragment()) // 'section'
|
|
128
|
+
return response.success({ /* ... */ })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Integrating with Fastify
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { adapters } from '@asterflow/adapter'
|
|
138
|
+
import { AsterFlow } from '@asterflow/core'
|
|
139
|
+
import fastify from 'fastify'
|
|
140
|
+
|
|
141
|
+
const server = fastify()
|
|
142
|
+
const app = new AsterFlow({
|
|
143
|
+
driver: adapters.fastify
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Add routes
|
|
147
|
+
app.controller(router)
|
|
148
|
+
|
|
149
|
+
// Start the server
|
|
150
|
+
app.listen(server, { port: 3333 }, (err) => {
|
|
151
|
+
if (err) {
|
|
152
|
+
console.error(err)
|
|
153
|
+
process.exit(1)
|
|
154
|
+
}
|
|
155
|
+
console.log('Server listening!')
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 📚 API Reference
|
|
160
|
+
|
|
161
|
+
### Router Class
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
class Router<
|
|
165
|
+
Path extends string,
|
|
166
|
+
Method extends MethodKeys,
|
|
167
|
+
Schema extends SchemaDynamic<Method>,
|
|
168
|
+
Responder extends Responders,
|
|
169
|
+
Middlewares extends readonly Middleware[]
|
|
170
|
+
> {
|
|
171
|
+
constructor(options: {
|
|
172
|
+
path: Path
|
|
173
|
+
name?: string
|
|
174
|
+
description?: string
|
|
175
|
+
use?: Middlewares
|
|
176
|
+
schema?: Schema
|
|
177
|
+
methods: {
|
|
178
|
+
[M in MethodKeys]?: RouteHandler
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Method Class
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
class Method<
|
|
188
|
+
Responder extends Responders,
|
|
189
|
+
Path extends string,
|
|
190
|
+
Method extends MethodKeys,
|
|
191
|
+
Schema extends AnySchema,
|
|
192
|
+
Middlewares extends readonly Middleware[]
|
|
193
|
+
> {
|
|
194
|
+
constructor(options: {
|
|
195
|
+
path: Path
|
|
196
|
+
method: Method
|
|
197
|
+
name?: string
|
|
198
|
+
schema?: Schema
|
|
199
|
+
use?: Middlewares
|
|
200
|
+
handler: MethodHandler
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Response Class
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
class Response<Responder extends Responders> {
|
|
209
|
+
// Status Methods
|
|
210
|
+
success(data: Responder[200]): Response
|
|
211
|
+
created(data: Responder[201]): Response
|
|
212
|
+
noContent(data: Responder[204]): Response
|
|
213
|
+
badRequest(data: Responder[400]): Response
|
|
214
|
+
unauthorized(data: Responder[401]): Response
|
|
215
|
+
forbidden(data: Responder[403]): Response
|
|
216
|
+
notFound(data: Responder[404]): Response
|
|
217
|
+
|
|
218
|
+
// Headers and Cookies
|
|
219
|
+
setHeader(name: string, value: string): Response
|
|
220
|
+
setCookie(name: string, value: string): Response
|
|
221
|
+
|
|
222
|
+
// Conversion
|
|
223
|
+
toResponse(): globalThis.Response
|
|
224
|
+
toServerResponse(output: ServerResponse): void
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Middleware Class
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
class Middleware<
|
|
232
|
+
Responder extends Responders,
|
|
233
|
+
Schema extends AnySchema,
|
|
234
|
+
Name extends string,
|
|
235
|
+
Parameters extends Record<string, unknown>
|
|
236
|
+
> {
|
|
237
|
+
constructor(options: {
|
|
238
|
+
name: Name
|
|
239
|
+
onRun: (args: {
|
|
240
|
+
request: Request
|
|
241
|
+
response: Response<Responder>
|
|
242
|
+
schema: InferSchema<Schema>
|
|
243
|
+
next: (params: Parameters) => MiddlewareOptions
|
|
244
|
+
}) => MiddlewareOptions
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 🔗 Related Packages
|
|
250
|
+
|
|
251
|
+
- [@asterflow/core](https://github.com/Ashu11-A/AsterFlow/tree/main/core) - Core framework
|
|
252
|
+
- [@asterflow/adapter](https://github.com/Ashu11-A/AsterFlow/tree/main/packages/adapter) - Adapters for different HTTP servers
|
|
253
|
+
|
|
254
|
+
## 📄 License
|
|
255
|
+
|
|
256
|
+
MIT - See [LICENSE](https://github.com/Ashu11-A/AsterFlow/blob/main/LICENSE) for more details.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var d = Object.defineProperty;
|
|
3
|
+
var M = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var u = Object.getOwnPropertyNames;
|
|
5
|
+
var w = Object.prototype.hasOwnProperty;
|
|
6
|
+
var R = (n, e) => {
|
|
7
|
+
for (var t in e)
|
|
8
|
+
d(n, t, { get: e[t], enumerable: !0 });
|
|
9
|
+
}, x = (n, e, t, o) => {
|
|
10
|
+
if (e && typeof e == "object" || typeof e == "function")
|
|
11
|
+
for (let s of u(e))
|
|
12
|
+
!w.call(n, s) && s !== t && d(n, s, { get: () => e[s], enumerable: !(o = M(e, s)) || o.enumerable });
|
|
13
|
+
return n;
|
|
14
|
+
};
|
|
15
|
+
var f = (n) => x(d({}, "__esModule", { value: !0 }), n);
|
|
16
|
+
|
|
17
|
+
// packages/router/src/index.ts
|
|
18
|
+
var g = {};
|
|
19
|
+
R(g, {
|
|
20
|
+
Method: () => h,
|
|
21
|
+
MethodType: () => l,
|
|
22
|
+
Middleware: () => p,
|
|
23
|
+
Response: () => i,
|
|
24
|
+
Router: () => a
|
|
25
|
+
});
|
|
26
|
+
module.exports = f(g);
|
|
27
|
+
|
|
28
|
+
// packages/router/src/controllers/Router.ts
|
|
29
|
+
var c = require("url-ast"), a = class {
|
|
30
|
+
name;
|
|
31
|
+
path;
|
|
32
|
+
schema;
|
|
33
|
+
description;
|
|
34
|
+
methods;
|
|
35
|
+
use;
|
|
36
|
+
url;
|
|
37
|
+
constructor(e) {
|
|
38
|
+
let { name: t, path: o, schema: s, description: r, methods: y } = e;
|
|
39
|
+
this.name = t, this.path = o, this.schema = s, this.description = r, this.methods = y, this.use = e.use, this.url = new c.Analyze(this.path);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// packages/router/src/controllers/Response.ts
|
|
44
|
+
var i = class n {
|
|
45
|
+
_status;
|
|
46
|
+
body;
|
|
47
|
+
header;
|
|
48
|
+
cookies;
|
|
49
|
+
constructor(e) {
|
|
50
|
+
this._status = e?.code ?? 200, this.body = e?.data, this.header = e?.header ?? /* @__PURE__ */ new Map(), this.cookies = e?.cookies ?? /* @__PURE__ */ new Map();
|
|
51
|
+
}
|
|
52
|
+
clone(e) {
|
|
53
|
+
return new n({
|
|
54
|
+
code: e.code !== void 0 ? e.code : this._status,
|
|
55
|
+
data: e.data !== void 0 ? e.data : this.body,
|
|
56
|
+
header: e.header !== void 0 ? e.header : this.header,
|
|
57
|
+
cookies: e.cookies !== void 0 ? e.cookies : this.cookies
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// --- Core Methods ---
|
|
61
|
+
status(e) {
|
|
62
|
+
return this.clone({ code: e });
|
|
63
|
+
}
|
|
64
|
+
getStatus() {
|
|
65
|
+
return this._status;
|
|
66
|
+
}
|
|
67
|
+
code(e) {
|
|
68
|
+
return this.status(e);
|
|
69
|
+
}
|
|
70
|
+
send(e) {
|
|
71
|
+
return this.clone({ data: e });
|
|
72
|
+
}
|
|
73
|
+
json(e) {
|
|
74
|
+
let t = new Map(this.header);
|
|
75
|
+
return t.set("Content-Type", "application/json"), this.clone({ data: e, header: t });
|
|
76
|
+
}
|
|
77
|
+
// --- Response Helpers ---
|
|
78
|
+
success(e) {
|
|
79
|
+
return this.clone({ code: 200, data: e });
|
|
80
|
+
}
|
|
81
|
+
created(e) {
|
|
82
|
+
return this.clone({ code: 201, data: e });
|
|
83
|
+
}
|
|
84
|
+
noContent(e) {
|
|
85
|
+
return this.clone({ code: 204, data: e });
|
|
86
|
+
}
|
|
87
|
+
badRequest(e) {
|
|
88
|
+
return this.clone({ code: 400, data: e });
|
|
89
|
+
}
|
|
90
|
+
zodError(e) {
|
|
91
|
+
return this.clone({ code: 422, data: e });
|
|
92
|
+
}
|
|
93
|
+
unauthorized(e) {
|
|
94
|
+
return this.clone({ code: 401, data: e });
|
|
95
|
+
}
|
|
96
|
+
forbidden(e) {
|
|
97
|
+
return this.clone({ code: 403, data: e });
|
|
98
|
+
}
|
|
99
|
+
notFound(e) {
|
|
100
|
+
return this.clone({ code: 404, data: e });
|
|
101
|
+
}
|
|
102
|
+
setHeader(e, t) {
|
|
103
|
+
let o = new Map(this.header);
|
|
104
|
+
return o.set(e, t), this.clone({ header: o });
|
|
105
|
+
}
|
|
106
|
+
setCookie(e, t) {
|
|
107
|
+
let o = new Map(this.cookies);
|
|
108
|
+
return o.set(e, t), this.clone({ cookies: o });
|
|
109
|
+
}
|
|
110
|
+
toResponse() {
|
|
111
|
+
let e = new Headers();
|
|
112
|
+
e.set("Content-Type", "text/plain");
|
|
113
|
+
for (let [o, s] of this.header) e.set(o, s);
|
|
114
|
+
(typeof this.body == "object" || Array.isArray(this.body)) && e.set("Content-Type", "application/json");
|
|
115
|
+
for (let [o, s] of this.cookies) e.append("Set-Cookie", `${o}=${s}`);
|
|
116
|
+
let t = e.get("Content-Type") === "application/json" ? JSON.stringify(this.body) : String(this.body);
|
|
117
|
+
return new globalThis.Response(t, {
|
|
118
|
+
status: this._status,
|
|
119
|
+
headers: e
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
toServerResponse(e) {
|
|
123
|
+
let t = new Headers();
|
|
124
|
+
t.set("Content-Type", "text/plain");
|
|
125
|
+
for (let [s, r] of this.header) t.set(s, r);
|
|
126
|
+
(typeof this.body == "object" || Array.isArray(this.body)) && t.set("Content-Type", "application/json");
|
|
127
|
+
for (let [s, r] of this.cookies) t.append("Set-Cookie", `${s}=${r}`);
|
|
128
|
+
let o = t.get("Content-Type") === "application/json" ? JSON.stringify(this.body) : String(this.body);
|
|
129
|
+
e.writeHead(this._status, Object.fromEntries(t)), e.end(o);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// packages/router/src/controllers/Middleware.ts
|
|
134
|
+
var p = class {
|
|
135
|
+
name;
|
|
136
|
+
onRun;
|
|
137
|
+
constructor(e) {
|
|
138
|
+
this.name = e.name, this.onRun = e.onRun;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// packages/router/src/controllers/Method.ts
|
|
143
|
+
var m = require("url-ast"), h = class {
|
|
144
|
+
path;
|
|
145
|
+
url;
|
|
146
|
+
method;
|
|
147
|
+
schema;
|
|
148
|
+
name;
|
|
149
|
+
use;
|
|
150
|
+
handler;
|
|
151
|
+
constructor(e) {
|
|
152
|
+
this.path = e.path, this.url = new m.Analyze(this.path), this.method = e.method, this.schema = e.schema, this.handler = e.handler, this.use = e.use;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// packages/router/src/types/method.ts
|
|
157
|
+
var N = require("url-ast"), l = /* @__PURE__ */ ((s) => (s.get = "get", s.post = "post", s.put = "put", s.delete = "delete", s))(l || {});
|
|
158
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
159
|
+
0 && (module.exports = {
|
|
160
|
+
Method,
|
|
161
|
+
MethodType,
|
|
162
|
+
Middleware,
|
|
163
|
+
Response,
|
|
164
|
+
Router
|
|
165
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// packages/router/src/controllers/Router.ts
|
|
2
|
+
import { Analyze as c } from "url-ast";
|
|
3
|
+
var d = class {
|
|
4
|
+
name;
|
|
5
|
+
path;
|
|
6
|
+
schema;
|
|
7
|
+
description;
|
|
8
|
+
methods;
|
|
9
|
+
use;
|
|
10
|
+
url;
|
|
11
|
+
constructor(e) {
|
|
12
|
+
let { name: t, path: o, schema: s, description: n, methods: h } = e;
|
|
13
|
+
this.name = t, this.path = o, this.schema = s, this.description = n, this.methods = h, this.use = e.use, this.url = new c(this.path);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// packages/router/src/controllers/Response.ts
|
|
18
|
+
var a = class r {
|
|
19
|
+
_status;
|
|
20
|
+
body;
|
|
21
|
+
header;
|
|
22
|
+
cookies;
|
|
23
|
+
constructor(e) {
|
|
24
|
+
this._status = e?.code ?? 200, this.body = e?.data, this.header = e?.header ?? /* @__PURE__ */ new Map(), this.cookies = e?.cookies ?? /* @__PURE__ */ new Map();
|
|
25
|
+
}
|
|
26
|
+
clone(e) {
|
|
27
|
+
return new r({
|
|
28
|
+
code: e.code !== void 0 ? e.code : this._status,
|
|
29
|
+
data: e.data !== void 0 ? e.data : this.body,
|
|
30
|
+
header: e.header !== void 0 ? e.header : this.header,
|
|
31
|
+
cookies: e.cookies !== void 0 ? e.cookies : this.cookies
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// --- Core Methods ---
|
|
35
|
+
status(e) {
|
|
36
|
+
return this.clone({ code: e });
|
|
37
|
+
}
|
|
38
|
+
getStatus() {
|
|
39
|
+
return this._status;
|
|
40
|
+
}
|
|
41
|
+
code(e) {
|
|
42
|
+
return this.status(e);
|
|
43
|
+
}
|
|
44
|
+
send(e) {
|
|
45
|
+
return this.clone({ data: e });
|
|
46
|
+
}
|
|
47
|
+
json(e) {
|
|
48
|
+
let t = new Map(this.header);
|
|
49
|
+
return t.set("Content-Type", "application/json"), this.clone({ data: e, header: t });
|
|
50
|
+
}
|
|
51
|
+
// --- Response Helpers ---
|
|
52
|
+
success(e) {
|
|
53
|
+
return this.clone({ code: 200, data: e });
|
|
54
|
+
}
|
|
55
|
+
created(e) {
|
|
56
|
+
return this.clone({ code: 201, data: e });
|
|
57
|
+
}
|
|
58
|
+
noContent(e) {
|
|
59
|
+
return this.clone({ code: 204, data: e });
|
|
60
|
+
}
|
|
61
|
+
badRequest(e) {
|
|
62
|
+
return this.clone({ code: 400, data: e });
|
|
63
|
+
}
|
|
64
|
+
zodError(e) {
|
|
65
|
+
return this.clone({ code: 422, data: e });
|
|
66
|
+
}
|
|
67
|
+
unauthorized(e) {
|
|
68
|
+
return this.clone({ code: 401, data: e });
|
|
69
|
+
}
|
|
70
|
+
forbidden(e) {
|
|
71
|
+
return this.clone({ code: 403, data: e });
|
|
72
|
+
}
|
|
73
|
+
notFound(e) {
|
|
74
|
+
return this.clone({ code: 404, data: e });
|
|
75
|
+
}
|
|
76
|
+
setHeader(e, t) {
|
|
77
|
+
let o = new Map(this.header);
|
|
78
|
+
return o.set(e, t), this.clone({ header: o });
|
|
79
|
+
}
|
|
80
|
+
setCookie(e, t) {
|
|
81
|
+
let o = new Map(this.cookies);
|
|
82
|
+
return o.set(e, t), this.clone({ cookies: o });
|
|
83
|
+
}
|
|
84
|
+
toResponse() {
|
|
85
|
+
let e = new Headers();
|
|
86
|
+
e.set("Content-Type", "text/plain");
|
|
87
|
+
for (let [o, s] of this.header) e.set(o, s);
|
|
88
|
+
(typeof this.body == "object" || Array.isArray(this.body)) && e.set("Content-Type", "application/json");
|
|
89
|
+
for (let [o, s] of this.cookies) e.append("Set-Cookie", `${o}=${s}`);
|
|
90
|
+
let t = e.get("Content-Type") === "application/json" ? JSON.stringify(this.body) : String(this.body);
|
|
91
|
+
return new globalThis.Response(t, {
|
|
92
|
+
status: this._status,
|
|
93
|
+
headers: e
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
toServerResponse(e) {
|
|
97
|
+
let t = new Headers();
|
|
98
|
+
t.set("Content-Type", "text/plain");
|
|
99
|
+
for (let [s, n] of this.header) t.set(s, n);
|
|
100
|
+
(typeof this.body == "object" || Array.isArray(this.body)) && t.set("Content-Type", "application/json");
|
|
101
|
+
for (let [s, n] of this.cookies) t.append("Set-Cookie", `${s}=${n}`);
|
|
102
|
+
let o = t.get("Content-Type") === "application/json" ? JSON.stringify(this.body) : String(this.body);
|
|
103
|
+
e.writeHead(this._status, Object.fromEntries(t)), e.end(o);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// packages/router/src/controllers/Middleware.ts
|
|
108
|
+
var i = class {
|
|
109
|
+
name;
|
|
110
|
+
onRun;
|
|
111
|
+
constructor(e) {
|
|
112
|
+
this.name = e.name, this.onRun = e.onRun;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// packages/router/src/controllers/Method.ts
|
|
117
|
+
import { Analyze as m } from "url-ast";
|
|
118
|
+
var p = class {
|
|
119
|
+
path;
|
|
120
|
+
url;
|
|
121
|
+
method;
|
|
122
|
+
schema;
|
|
123
|
+
name;
|
|
124
|
+
use;
|
|
125
|
+
handler;
|
|
126
|
+
constructor(e) {
|
|
127
|
+
this.path = e.path, this.url = new m(this.path), this.method = e.method, this.schema = e.schema, this.handler = e.handler, this.use = e.use;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// packages/router/src/types/method.ts
|
|
132
|
+
import "url-ast";
|
|
133
|
+
var l = /* @__PURE__ */ ((s) => (s.get = "get", s.post = "post", s.put = "put", s.delete = "delete", s))(l || {});
|
|
134
|
+
export {
|
|
135
|
+
p as Method,
|
|
136
|
+
l as MethodType,
|
|
137
|
+
i as Middleware,
|
|
138
|
+
a as Response,
|
|
139
|
+
d as Router
|
|
140
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Analyze } from 'url-ast';
|
|
2
|
+
import type { MethodHandler, MethodKeys, MethodOptions } from '../types/method';
|
|
3
|
+
import type { MiddlewareOutput } from '../types/mindleware';
|
|
4
|
+
import type { Responders } from '../types/response';
|
|
5
|
+
import type { AnySchema } from '../types/schema';
|
|
6
|
+
import type { Middleware } from './Middleware';
|
|
7
|
+
export declare class Method<Responder extends Responders, const Path extends string = string, const Method extends MethodKeys = MethodKeys, const Schema extends AnySchema = AnySchema, const Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[] = [], const Context extends MiddlewareOutput<Middlewares> = MiddlewareOutput<Middlewares>, const Handler extends MethodHandler<Path, Responder, Schema, Middlewares, Context> = MethodHandler<Path, Responder, Schema, Middlewares, Context>> {
|
|
8
|
+
path: Path;
|
|
9
|
+
url: Analyze<Path>;
|
|
10
|
+
method: Method;
|
|
11
|
+
schema?: Schema;
|
|
12
|
+
name?: string;
|
|
13
|
+
use?: Middlewares;
|
|
14
|
+
handler: Handler;
|
|
15
|
+
constructor(options: MethodOptions<Responder, Path, Method, Schema, Middlewares, Context, Handler>);
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MiddlewareOptions } from '../types/mindleware';
|
|
2
|
+
import type { Responders } from '../types/response';
|
|
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>> {
|
|
5
|
+
readonly name: Name;
|
|
6
|
+
readonly onRun: <RequestType>(args: {
|
|
7
|
+
response: import("./Response").Response<Responder, import("..").BodyMap<Responder>, keyof Responder, Map<string, string>, Map<string, string>>;
|
|
8
|
+
request: import("@asterflow/request").Request<RequestType>;
|
|
9
|
+
schema: import("..").InferSchema<Schema>;
|
|
10
|
+
next: <Parameter extends Record<string, unknown>>(params: Parameter) => MiddlewareOptions<Responder, Schema, Name, Parameter>;
|
|
11
|
+
}) => MiddlewareOptions<Responder, Schema, Name, Parameters>;
|
|
12
|
+
constructor(options: MiddlewareOptions<Responder, Schema, Name, Parameters>);
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BodyMap, Responders, ResponseOptions } from '../types/response';
|
|
2
|
+
import { ServerResponse } from 'http';
|
|
3
|
+
export declare class Response<Responder extends Responders = Responders, BM extends BodyMap<Responder> = BodyMap<Responder>, Status extends keyof BM = keyof BM, Header extends Map<string, string> = Map<string, string>, Cookies extends Map<string, string> = Map<string, string>> {
|
|
4
|
+
protected readonly _status: Status;
|
|
5
|
+
readonly body?: BM[Status];
|
|
6
|
+
readonly header: Header;
|
|
7
|
+
readonly cookies: Cookies;
|
|
8
|
+
constructor(options?: ResponseOptions<Responder, BM, Status, Header, Cookies>);
|
|
9
|
+
protected clone<NewHeader extends Map<string, string> = Header, NewCookies extends Map<string, string> = Cookies>(overrides: {
|
|
10
|
+
data?: BM[Status];
|
|
11
|
+
header?: NewHeader;
|
|
12
|
+
cookies?: NewCookies;
|
|
13
|
+
}): Response<Responder, BM, Status, NewHeader, NewCookies>;
|
|
14
|
+
protected clone<NewStatus extends keyof BM, NewCookies extends Map<string, string> = Cookies>(overrides: {
|
|
15
|
+
code: NewStatus;
|
|
16
|
+
data?: BM[NewStatus];
|
|
17
|
+
header?: Header;
|
|
18
|
+
cookies?: NewCookies;
|
|
19
|
+
}): Response<Responder, BM, NewStatus, Header, NewCookies>;
|
|
20
|
+
status<NS extends keyof BM>(code: NS): Response<Responder, BM, NS, Header, Cookies>;
|
|
21
|
+
getStatus(): Status;
|
|
22
|
+
code<NS extends keyof BM>(code: NS): Response<Responder, BM, NS, Header, Cookies>;
|
|
23
|
+
send(data: BM[Status]): Response<Responder, BM, Status, Header, Cookies>;
|
|
24
|
+
json(data: BM[Status]): Response<Responder, BM, Status, Header & Map<"Content-Type", "application/json">, Cookies>;
|
|
25
|
+
success(data: BM[200]): Response<Responder, BM, 200, Header, Cookies>;
|
|
26
|
+
created(data: BM[201]): Response<Responder, BM, 201, Header, Cookies>;
|
|
27
|
+
noContent(data: BM[204]): Response<Responder, BM, 204, Header, Cookies>;
|
|
28
|
+
badRequest(data: BM[400]): Response<Responder, BM, 400, Header, Cookies>;
|
|
29
|
+
zodError(data: BM[422]): Response<Responder, BM, 422, Header, Cookies>;
|
|
30
|
+
unauthorized(data: BM[401]): Response<Responder, BM, 401, Header, Cookies>;
|
|
31
|
+
forbidden(data: BM[403]): Response<Responder, BM, 403, Header, Cookies>;
|
|
32
|
+
notFound(data: BM[404]): Response<Responder, BM, 404, Header, Cookies>;
|
|
33
|
+
setHeader<Name extends string, Value extends string>(name: Name, value: Value): Response<Responder, BM, Status, Header & Map<Name, Value>, Cookies>;
|
|
34
|
+
setCookie<Name extends string, Value extends string>(name: Name, value: Value): Response<Responder, BM, Status, Header, Cookies & Map<Name, Value>>;
|
|
35
|
+
toResponse(): globalThis.Response;
|
|
36
|
+
toServerResponse(output: ServerResponse): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Analyze } from 'url-ast';
|
|
2
|
+
import type { MiddlewareOutput } from '../types/mindleware';
|
|
3
|
+
import type { AnySchema, SchemaDynamic } from '../types/schema';
|
|
4
|
+
import type { Middleware } from './Middleware';
|
|
5
|
+
import type { MethodKeys } from '../types/method';
|
|
6
|
+
import type { Responders } from '../types/response';
|
|
7
|
+
import type { RouteHandler, RouterOptions } from '../types/router';
|
|
8
|
+
export declare class Router<Path extends string, Method extends MethodKeys, Schema extends SchemaDynamic<Method>, Responder extends Responders, const Routers extends {
|
|
9
|
+
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
10
|
+
}, const Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[] = [], const Context extends MiddlewareOutput<Middlewares> = MiddlewareOutput<Middlewares>> {
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly path: Path;
|
|
13
|
+
readonly schema?: Schema;
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
readonly methods: Routers;
|
|
16
|
+
readonly use?: Middlewares;
|
|
17
|
+
url: Analyze<Path>;
|
|
18
|
+
constructor(options: RouterOptions<Path, Method, Schema, Responder, Middlewares, Context, Routers>);
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './controllers/Router';
|
|
2
|
+
export * from './controllers/Response';
|
|
3
|
+
export * from './controllers/Middleware';
|
|
4
|
+
export * from './controllers/Method';
|
|
5
|
+
export * from './types/router';
|
|
6
|
+
export * from './types/schema';
|
|
7
|
+
export * from './types/response';
|
|
8
|
+
export * from './types/method';
|
|
9
|
+
export * from './types/mindleware';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Request } from '@asterflow/request';
|
|
2
|
+
import type { Middleware } from '../controllers/Middleware';
|
|
3
|
+
import type { Response } from '../controllers/Response';
|
|
4
|
+
import type { AnyMiddleware, MiddlewareOutput } from './mindleware';
|
|
5
|
+
import type { Responders } from './response';
|
|
6
|
+
import type { AnySchema, InferSchema } from './schema';
|
|
7
|
+
import { Analyze, type ParsePath } from 'url-ast';
|
|
8
|
+
export declare enum MethodType {
|
|
9
|
+
get = "get",
|
|
10
|
+
post = "post",
|
|
11
|
+
put = "put",
|
|
12
|
+
delete = "delete"
|
|
13
|
+
}
|
|
14
|
+
export type MethodKeys = keyof typeof MethodType;
|
|
15
|
+
export type AnyMethodHandler = {
|
|
16
|
+
[Method in MethodKeys]?: MethodHandler<string, Responders, AnySchema, AnyMiddleware[], MiddlewareOutput<AnyMiddleware[]>>;
|
|
17
|
+
};
|
|
18
|
+
export type MethodHandler<Path extends string, Responder extends Responders, Schema extends AnySchema, Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[], Context extends MiddlewareOutput<Middlewares>> = <RequestType>(args: {
|
|
19
|
+
request: Request<RequestType>;
|
|
20
|
+
response: Response<Responder>;
|
|
21
|
+
url: Analyze<Path, ParsePath<Path>, Analyze<any>>;
|
|
22
|
+
schema: InferSchema<Schema>;
|
|
23
|
+
middleware: Context;
|
|
24
|
+
}) => Promise<Response<Responder>> | Response<Responder>;
|
|
25
|
+
export type MethodOptions<Responder extends Responders, Path extends string, Method extends MethodKeys, Schema extends AnySchema, Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[], Context extends MiddlewareOutput<Middlewares>, Handler extends MethodHandler<Path, Responder, Schema, Middlewares, Context>> = {
|
|
26
|
+
path: Path;
|
|
27
|
+
name?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
use?: Middlewares;
|
|
30
|
+
method: Method;
|
|
31
|
+
schema?: Schema;
|
|
32
|
+
handler: Handler;
|
|
33
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Request } from '@asterflow/request';
|
|
2
|
+
import type { Middleware } from '../controllers/Middleware';
|
|
3
|
+
import type { Response } from '../controllers/Response';
|
|
4
|
+
import type { Responders } from './response';
|
|
5
|
+
import type { AnySchema, InferSchema } from './schema';
|
|
6
|
+
export type AnyMiddleware = Middleware<any, any, any, any>;
|
|
7
|
+
/**
|
|
8
|
+
* Accumulate the output types (`P`) from an array of middlewares into a single object
|
|
9
|
+
*/
|
|
10
|
+
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;
|
|
11
|
+
export type MiddlewareOptions<Responder extends Responders = Responders, Schema extends AnySchema = AnySchema, Name extends string = string, Parameters extends Record<string, unknown> = Record<string, unknown>> = {
|
|
12
|
+
name: Name;
|
|
13
|
+
onRun<RequestType>(args: {
|
|
14
|
+
response: Response<Responder>;
|
|
15
|
+
request: Request<RequestType>;
|
|
16
|
+
schema: InferSchema<Schema>;
|
|
17
|
+
next: <Parameter extends Record<string, unknown>>(params: Parameter) => MiddlewareOptions<Responder, Schema, Name, Parameter>;
|
|
18
|
+
}): MiddlewareOptions<Responder, Schema, Name, Parameters>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Responders = {
|
|
2
|
+
[x in number]: unknown;
|
|
3
|
+
};
|
|
4
|
+
export type BodyMap<Responder extends Responders> = {
|
|
5
|
+
[S in keyof Responder]: Responder[S];
|
|
6
|
+
};
|
|
7
|
+
export type ResponseOptions<Responder extends Responders, BM extends BodyMap<Responder>, Status extends keyof BM, Header extends Map<string, string>, Cookies extends Map<string, string>> = {
|
|
8
|
+
data?: BM[Status];
|
|
9
|
+
code?: Status;
|
|
10
|
+
header?: Header;
|
|
11
|
+
cookies?: Cookies;
|
|
12
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Request } from '@asterflow/request';
|
|
2
|
+
import type { Analyze, ParsePath } from 'url-ast';
|
|
3
|
+
import type { Method } from '../controllers/Method';
|
|
4
|
+
import type { Middleware } from '../controllers/Middleware';
|
|
5
|
+
import type { Response } from '../controllers/Response';
|
|
6
|
+
import type { Router } from '../controllers/Router';
|
|
7
|
+
import type { MethodKeys } from './method';
|
|
8
|
+
import type { AnyMiddleware, MiddlewareOutput } from './mindleware';
|
|
9
|
+
import type { Responders } from './response';
|
|
10
|
+
import type { AnySchema, InferredData, SchemaDynamic } from './schema';
|
|
11
|
+
export type AnyRouteHandler = {
|
|
12
|
+
[Method in MethodKeys]?: RouteHandler<string, Responders, Method, SchemaDynamic<Method>, AnyMiddleware[], MiddlewareOutput<AnyMiddleware[]>>;
|
|
13
|
+
};
|
|
14
|
+
export type AnyRouter = Router<string, MethodKeys, SchemaDynamic<MethodKeys>, Responders, AnyRouteHandler, AnyMiddleware[], MiddlewareOutput<AnyMiddleware[]>> | Method<Responders, string, MethodKeys, AnySchema, AnyMiddleware[], MiddlewareOutput<AnyMiddleware[]>, any>;
|
|
15
|
+
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 extends MiddlewareOutput<Middlewares>> = <RequestType>(args: {
|
|
16
|
+
request: Request<RequestType>;
|
|
17
|
+
response: Response<Responder>;
|
|
18
|
+
url: Analyze<Path, ParsePath<Path>, Analyze<any>>;
|
|
19
|
+
schema: InferredData<Method, Schema>;
|
|
20
|
+
middleware: Context;
|
|
21
|
+
}) => Promise<Response> | Response;
|
|
22
|
+
export type RouterOptions<Path extends string, Method extends MethodKeys, Schema extends SchemaDynamic<Method>, Responder extends Responders, Middlewares extends readonly Middleware<Responder, AnySchema, string, Record<string, unknown>>[], Context extends MiddlewareOutput<Middlewares>, Routers extends {
|
|
23
|
+
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
24
|
+
}> = {
|
|
25
|
+
name?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
path: Path;
|
|
28
|
+
use?: Middlewares;
|
|
29
|
+
schema?: Schema;
|
|
30
|
+
methods: Routers;
|
|
31
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TshViewer, AbstractShape } from '@caeljs/tsh';
|
|
2
|
+
import type { TypeOf, ZodTypeAny } from 'zod';
|
|
3
|
+
import type { MethodKeys } from './method';
|
|
4
|
+
export type AnySchema = AbstractShape<any> | ZodTypeAny;
|
|
5
|
+
export type SchemaDynamic<Method extends MethodKeys> = {
|
|
6
|
+
[K in Method]?: AnySchema;
|
|
7
|
+
};
|
|
8
|
+
export type InferSchema<S> = S extends AbstractShape<any> ? TshViewer<ReturnType<S['parse']>> : S extends ZodTypeAny ? TypeOf<S> : never;
|
|
9
|
+
export type InferredData<Method extends MethodKeys, Schema extends SchemaDynamic<Method>> = InferSchema<Schema[Method]>;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asterflow/router",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "dist/cjs/index.cjs",
|
|
5
|
+
"module": "dist/mjs/index.js",
|
|
6
|
+
"types": "dist/types/index.d.ts",
|
|
7
|
+
"typings": "dist/types/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/types/index.d.ts",
|
|
13
|
+
"import": "./dist/mjs/index.js",
|
|
14
|
+
"require": "./dist/cjs/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@asterflow/request": "workspace:^1.0.1",
|
|
22
|
+
"@caeljs/tsh": "^1.1.3",
|
|
23
|
+
"@types/bun": "latest",
|
|
24
|
+
"fastify": "^5.3.3",
|
|
25
|
+
"zod": "^3.25.57"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5.8.3"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"url-ast": "^1.0.2"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"ESNext"
|
|
5
|
+
],
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedIndexedAccess": true,
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": false,
|
|
21
|
+
"noPropertyAccessFromIndexSignature": false
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"dist"
|
|
25
|
+
]
|
|
26
|
+
}
|