@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 ADDED
@@ -0,0 +1,256 @@
1
+ <div align="center">
2
+
3
+ # @asterflow/router
4
+
5
+ ![license-info](https://img.shields.io/github/license/Ashu11-A/AsterFlow?style=for-the-badge&colorA=302D41&colorB=f9e2af&logoColor=f9e2af)
6
+ ![stars-info](https://img.shields.io/github/stars/Ashu11-A/AsterFlow?colorA=302D41&colorB=f9e2af&style=for-the-badge)
7
+
8
+ ![last-commit](https://img.shields.io/github/last-commit/Ashu11-A/AsterFlow?style=for-the-badge&colorA=302D41&colorB=b4befe)
9
+ ![commit-activity](https://img.shields.io/github/commit-activity/y/Ashu11-A/AsterFlow?style=for-the-badge&colorA=302D41&colorB=f9e2af)
10
+ ![code-size](https://img.shields.io/github/languages/code-size/Ashu11-A/AsterFlow?style=for-the-badge&colorA=302D41&colorB=90dceb)
11
+
12
+ ![top-language](https://img.shields.io/github/languages/top/Ashu11-A/AsterFlow?style=for-the-badge&colorA=302D41&colorB=90dceb)
13
+ ![bundle-size](https://img.shields.io/bundlejs/size/@asterflow/request?style=for-the-badge&colorA=302D41&colorB=3ac97b)
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,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -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,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -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
+ }