@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 CHANGED
@@ -4,162 +4,69 @@
4
4
 
5
5
  ![license-info](https://img.shields.io/github/license/AsterFlow/AsterFlow?style=for-the-badge&colorA=302D41&colorB=f9e2af&logoColor=f9e2af)
6
6
  ![stars-info](https://img.shields.io/github/stars/AsterFlow/AsterFlow?colorA=302D41&colorB=f9e2af&style=for-the-badge)
7
- ![last-commit](https://img.shields.io/github/last-commit/AsterFlow/AsterFlow?path=packages%2Frequest&style=for-the-badge&colorA=302D41&colorB=b4befe)
7
+ ![last-commit](https://img.shields.io/github/last-commit/AsterFlow/AsterFlow?path=packages%2Frouter&style=for-the-badge&colorA=302D41&colorB=b4befe)
8
8
 
9
- ![bundle-size](https://img.shields.io/bundlejs/size/@asterflow/request?style=for-the-badge&colorA=302D41&colorB=3ac97b)
9
+ ![bundle-size](https://img.shields.io/bundlejs/size/@asterflow/router?style=for-the-badge&colorA=302D41&colorB=3ac97b)
10
10
 
11
11
  </div>
12
12
 
13
- > Type-safe and flexible routing system for AsterFlow applications.
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
- ## 💡 About
21
+ ### Features
24
22
 
25
- @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.
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
- ## Features
29
+ ## How to Use
28
30
 
29
- - **Complete Type Safety:** Full TypeScript support with type inference for routes, parameters, and responses
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
- ## 🚀 Usage
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
- const createUser = new Method({
37
+ export default new Method(Method.POST, {
92
38
  path: '/users',
93
- method: 'post',
94
- schema: z.object({
95
- name: z.string(),
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
- ### Integrating with Fastify
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
- ```typescript
133
- import { adapters } from '@asterflow/adapter'
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
- // Start the server
146
- app.listen(server, { port: 3333 }, (err) => {
147
- if (err) {
148
- console.error(err)
149
- process.exit(1)
150
- }
151
- console.log('Server listening!')
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) - Core framework
158
- - [@asterflow/adapter](https://www.npmjs.com/package/@asterflow/adapter) - Adapters for different HTTP servers
159
- - [@asterflow/response](https://www.npmjs.com/package/@asterflow/response) - Type-safe HTTP response system
160
- - [@asterflow/request](https://www.npmjs.com/package/@asterflow/request) - Unified HTTP request system
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
- MIT - See [LICENSE](https://github.com/AsterFlow/AsterFlow/blob/main/LICENSE) for more details.
72
+ This project is licensed under the [MIT License](../../LICENSE).
@@ -1,45 +1,78 @@
1
1
  "use strict";
2
- var o = Object.defineProperty;
3
- var c = Object.getOwnPropertyDescriptor;
4
- var w = Object.getOwnPropertyNames;
5
- var y = Object.prototype.hasOwnProperty;
6
- var M = (t, e) => {
7
- for (var n in e)
8
- o(t, n, { get: e[n], enumerable: !0 });
9
- }, u = (t, e, n, d) => {
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 s of w(e))
12
- !y.call(t, s) && s !== n && o(t, s, { get: () => e[s], enumerable: !(d = c(e, s)) || d.enumerable });
13
- return t;
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 x = (t) => u(o({}, "__esModule", { value: !0 }), t);
15
+ var A = (r) => g(i({}, "__esModule", { value: !0 }), r);
16
16
  // packages/router/src/index.ts
17
- var R = {};
18
- M(R, {
19
- Method: () => p,
20
- MethodType: () => m,
21
- Middleware: () => i,
22
- Router: () => a
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 = x(R);
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 a = class t {
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: n, path: d, schema: s, description: h, methods: l } = e;
35
- this.name = n, this.path = d, this.schema = s, this.description = h, this.methods = l, this.use = e.use;
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 t(e);
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 i = class {
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 p = class {
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
- constructor(e) {
58
- this.path = e.path, this.method = e.method, this.schema = e.schema, this.handler = e.handler, this.use = e.use;
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 m = ((r) => (r.all = "all", r.get = "get", r.post = "post", r.put = "put", r.delete = "delete", r.options = "options", r.head = "head", r.patch = "patch", r))(m || {});
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
- Router
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 s = class r {
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: o, path: a, schema: i, description: p, methods: m } = e;
11
- this.name = o, this.path = a, this.schema = i, this.description = p, this.methods = m, this.use = e.use;
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 n = class {
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 d = class {
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
- constructor(e) {
34
- this.path = e.path, this.method = e.method, this.schema = e.schema, this.handler = e.handler, this.use = e.use;
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 h = ((t) => (t.all = "all", t.get = "get", t.post = "post", t.put = "put", t.delete = "delete", t.options = "options", t.head = "head", t.patch = "patch", t))(h || {});
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
- d as Method,
41
- h as MethodType,
42
- n as Middleware,
43
- s as Router
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, MethodOptions } from '../types/method';
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
- export declare class Method<Responder extends Responders, const Path extends string = string, const Drive extends Runtime = Runtime, 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 Instance extends AnyAsterflow = AnyAsterflow, const Handler extends MethodHandler<Path, Drive, Responder, Schema, Middlewares, Context, Instance> = MethodHandler<Path, Drive, Responder, Schema, Middlewares, Context, Instance>> {
9
- path: Path;
10
- method: Method;
11
- schema?: Schema;
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?: Middlewares;
14
- handler: Handler;
15
- constructor(options: MethodOptions<Responder, Path, Drive, Method, Schema, Middlewares, Context, Instance, Handler>);
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("..").InferSchema<Schema>;
9
+ schema: import("@asterflow/router").InferSchema<Schema>;
10
10
  next: <Parameter extends Record<string, unknown>>(params: Parameter) => MiddlewareOptions<Responder, Schema, Name, Parameter>;
11
- }) => MiddlewareOptions<Responder, Schema, Name, Parameters>;
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<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>>[] = [], const Context extends MiddlewareOutput<Middlewares> = MiddlewareOutput<Middlewares>, const Routers extends {
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: Path;
14
- schema?: Schema;
10
+ path: Props['path'];
11
+ param?: Props['path'];
12
+ schema?: Props['schema'];
15
13
  description?: string;
16
- methods: Routers;
17
- use?: Middlewares;
18
- constructor(options: RouterOptions<Path, Schema, Responder, Middlewares, Context, Routers>);
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, Responder, Middlewares, Context, Routers>) => Router<Responder, Path, Schema, Middlewares, Context, Routers>;
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;
@@ -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, ParsePath } from '@asterflow/url-parser';
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
- 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 extends MiddlewareOutput<Middlewares>, Instance extends AnyAsterflow> = (args: {
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
- request: AsterRequest<Drive>;
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<Path, ParsePath<Path>, Analyze<Path>>;
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<Responder extends Responders, Path extends string, Drive extends Runtime, Method extends MethodKeys, Schema extends AnySchema, Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[], Context extends MiddlewareOutput<Middlewares>, Instance extends AnyAsterflow, Handler extends MethodHandler<Path, Drive, Responder, Schema, Middlewares, Context, Instance>> = {
30
- path: 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?: Middlewares;
34
- method: Method;
35
- schema?: Schema;
36
- handler: 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
- }): MiddlewareOptions<Responder, Schema, Name, Parameters>;
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, ParsePath } from '@asterflow/url-parser';
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, MiddlewareOutput } from './mindleware';
9
+ import type { AnyMiddleware } from './mindleware';
10
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[]>>;
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
- export type AnyRouter = Router<any, any, any, any, any, any> | Method<any, any, any, any, any, any, any, any, 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 extends Runtime>(args: {
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<Path, ParsePath<Path>, Analyze<Path>>;
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<Path extends string, Schema extends SchemaDynamic<MethodKeys>, 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
- }> = {
96
+ export type RouterOptions<Props extends RouterProps> = {
25
97
  name?: string;
26
98
  description?: string;
27
- path: Path;
28
- use?: Middlewares;
29
- schema?: Schema;
30
- methods: Routers;
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": "1.0.13",
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": "^2.0.3",
24
- "asterflow": "0.0.5"
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.63",
46
+ "zod": "^3.25.67",
29
47
  "typescript": "^5.8.3"
30
48
  }
31
49
  }