asterflow 0.0.6 → 1.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
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- # Asterflow
3
+ # AsterFlow
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)
@@ -10,122 +10,78 @@
10
10
 
11
11
  </div>
12
12
 
13
- > The heart of the AsterFlow framework, providing server initialization and configuration with strong typing.
13
+ > The core framework - ties together adapters, routing, plugins and responses into one typed `AsterFlow` app.
14
14
 
15
15
  ## 📦 Installation
16
16
 
17
17
  ```bash
18
- npm install asterflow
19
- # or
20
18
  bun install asterflow
21
19
  ```
22
20
 
23
- ## 💡 About
21
+ ### Features
24
22
 
25
- Asterflow is the central package of the AsterFlow framework. It provides server initialization, integration with different HTTP adapters, and a typed routing system. The package brings together all other AsterFlow components into a cohesive framework.
23
+ - **`new AsterFlow(options)`** - creates an app around a `driver` (an `@asterflow/adapter` instance, defaults to `adapters.node`)
24
+ - **`.method(...)` / `.router(...)`** - define a single-verb route or a multi-verb route group directly on the app, backed by `@asterflow/router`'s `Method`/`Router`
25
+ - **`.controller(route)`** - registers an already-built `Method` or `Router` instance
26
+ - **`.middleware({ basePath, controllers })`** - registers a group of controllers under a shared path prefix
27
+ - **Route `use` middlewares run first** - a route's middleware chain runs before schema validation, and any middleware can return a response to short-circuit the request before the body is even parsed
28
+ - **Schema validation** - if the route has a schema, the parsed body is validated after middlewares pass and before the handler runs
29
+ - **Plugin system** - `.use(plugin, config)` registers an `@asterflow/plugin` instance, applies its instance extensions, and wires up its `beforeInitialize`/`afterInitialize`/`onRequest`/`onResponse` hooks
30
+ - **Merged plugin context** - every plugin's context is resolved once, when `.listen()` is called, and handed to every handler as `context.plugins` (not recomputed per request)
31
+ - **Trie-based route matching** - routes are stored and matched with `reminist`, keyed by HTTP method
32
+ - **Full type inference** - registering a route narrows the app's type so its path, params, schema and middleware context are known at every call site
26
33
 
27
- ## Features
34
+ ## How to Use
28
35
 
29
- - **HTTP Adapters:** Native support for different HTTP servers (Node.js, Fastify, Express)
30
- - **Routing System:** Typed routing with support for dynamic parameters
31
- - **Middleware:** Flexible middleware system with typed context
32
- - **Type Safety:** Full TypeScript support with type inference
33
- - **High Performance:** Optimized routing system using prefix tree (trie)
34
- - **URL Analysis:** Integrated URL parser with support for dynamic parameters
36
+ Build routes with `.method()`/`.router()` and start the server with `.listen()`. This route validates its body with a middleware-provided context before running:
35
37
 
36
- ## 🚀 Usage
37
-
38
- ### Basic Setup
39
-
40
- ```typescript
38
+ ```ts
41
39
  import { AsterFlow } from 'asterflow'
42
- import { adapters } from '@asterflow/adapter'
43
- import fastify from 'fastify'
44
-
45
- const server = fastify()
46
- const aster = new AsterFlow({
47
- driver: adapters.fastify
48
- })
49
-
50
- aster.listen(server, { port: 3000 })
51
- ```
52
-
53
- ### Defining Routes
54
-
55
- ```typescript
56
- import { Router } from '@asterflow/router'
57
-
58
- const router = new Router({
59
- path: '/:id=number?query#fragment',
60
- methods: {
61
- get({ response, url }) {
62
- const params = url.getParams() // params.id is typed as number
63
- const query = url.getSearchParams()
64
- return response.send('Hello World')
65
- }
66
- }
67
- })
68
-
69
- aster.controller(router)
70
- ```
71
-
72
- ### Using Middleware
73
-
74
- ```typescript
75
- import { Middleware, Router } from '@asterflow/router'
40
+ import { Middleware } from '@asterflow/router'
41
+ import { z } from 'zod'
76
42
 
77
43
  const auth = new Middleware({
78
44
  name: 'auth',
79
- onRun({ next }) {
80
- return next({
81
- auth: false
82
- })
45
+ onRun({ request, response, next }) {
46
+ if (!request.getHeaders().authorization) return response.unauthorized({ message: 'Missing token' })
47
+ return next({ userId: 42 })
83
48
  }
84
49
  })
85
50
 
86
- const router = new Router({
87
- path: '/protected',
51
+ const app = new AsterFlow() // defaults to the Node adapter
52
+
53
+ app.method('post', {
54
+ path: '/users',
88
55
  use: [auth],
89
- methods: {
90
- get({ response, middleware }) {
91
- if (!middleware.auth) {
92
- return response.unauthorized({
93
- message: 'Unauthorized'
94
- })
95
- }
96
- return response.send('Protected area')
97
- }
56
+ schema: z.object({ name: z.string() }),
57
+ handler({ schema, middleware, response }) {
58
+ return response.created({ id: middleware.userId, name: schema.name })
98
59
  }
99
60
  })
61
+
62
+ app.listen({ port: 3000 })
100
63
  ```
101
64
 
102
- ### Individual Routes
65
+ Plugins register onto the same instance with `.use()` and can add their own instance methods:
103
66
 
104
- ```typescript
105
- import { Method } from '@asterflow/router'
67
+ ```ts
68
+ import { AsterFlow } from 'asterflow'
69
+ import { fsRoutingPlugin } from '@asterflow/fs'
106
70
 
107
- const route = new Method({
108
- path: '/users/:id=number',
109
- method: 'get',
110
- handler: ({ response, url }) => {
111
- const { id } = url.getParams() // id is typed as number
112
- return response.send({ id })
113
- }
114
- })
71
+ const app = new AsterFlow()
72
+ .use(fsRoutingPlugin, { routes: [] }) // routes: AnyRouter[]
115
73
 
116
- aster.controller(route)
74
+ app.listen({ port: 3000 })
117
75
  ```
118
76
 
119
77
  ## 🔗 Related Packages
120
78
 
121
- - [@asterflow/adapter](https://www.npmjs.com/package/@asterflow/adapter) - HTTP adapters for different runtimes
122
- - [@asterflow/router](https://www.npmjs.com/package/@asterflow/router) - Type-safe routing system
123
- - [@asterflow/request](https://www.npmjs.com/package/@asterflow/request) - Unified HTTP request system
124
- - [@asterflow/response](https://www.npmjs.com/package/@asterflow/response) - Type-safe HTTP response system
125
- - [reminist](https://www.npmjs.com/package/reminist) - Blazing fast, zero-dependency, TypeScript-native router
126
- - [@asterflow/url-parser](https://www.npmjs.com/package/@asterflow/url-parser) - High-performance typed URL parser with automatic type casting
127
- - [@asterflow/plugin](https://www.npmjs.com/package/@asterflow/plugin) - A modular and typed plugin system
79
+ - [@asterflow/adapter](https://www.npmjs.com/package/@asterflow/adapter) - supplies the `driver` (Bun, Node, Express, Fastify) that `.listen()` delegates to
80
+ - [@asterflow/router](https://www.npmjs.com/package/@asterflow/router) - `Method`/`Router`/`Middleware` classes that back `.method()`, `.router()` and `.controller()`
81
+ - [@asterflow/plugin](https://www.npmjs.com/package/@asterflow/plugin) - plugin instances and types consumed by `.use()`
82
+ - [@asterflow/response](https://www.npmjs.com/package/@asterflow/response) - `AsterResponse` is what every request handler works with and returns
83
+ - [@asterflow/request](https://www.npmjs.com/package/@asterflow/request) - supplies the `Request` type passed into every route and middleware handler
128
84
 
129
85
  ## 📄 License
130
86
 
131
- MIT - See [LICENSE](https://github.com/AsterFlow/AsterFlow/blob/main/LICENSE) for more details.
87
+ This project is licensed under the [MIT License](../LICENSE).
@@ -1,46 +1,47 @@
1
1
  "use strict";
2
2
  var y = Object.defineProperty;
3
- var x = Object.getOwnPropertyDescriptor;
4
- var P = Object.getOwnPropertyNames;
5
- var w = Object.prototype.hasOwnProperty;
6
- var A = (u, e) => {
3
+ var w = Object.getOwnPropertyDescriptor;
4
+ var A = Object.getOwnPropertyNames;
5
+ var P = Object.prototype.hasOwnProperty;
6
+ var M = (l, e) => {
7
7
  for (var t in e)
8
- y(u, t, { get: e[t], enumerable: !0 });
9
- }, M = (u, e, t, s) => {
8
+ y(l, t, { get: e[t], enumerable: !0 });
9
+ }, C = (l, e, t, s) => {
10
10
  if (e && typeof e == "object" || typeof e == "function")
11
- for (let n of P(e))
12
- !w.call(u, n) && n !== t && y(u, n, { get: () => e[n], enumerable: !(s = x(e, n)) || s.enumerable });
13
- return u;
11
+ for (let n of A(e))
12
+ !P.call(l, n) && n !== t && y(l, n, { get: () => e[n], enumerable: !(s = w(e, n)) || s.enumerable });
13
+ return l;
14
14
  };
15
- var I = (u) => M(y({}, "__esModule", { value: !0 }), u);
15
+ var v = (l) => C(y({}, "__esModule", { value: !0 }), l);
16
16
  // core/src/index.ts
17
- var C = {};
18
- A(C, {
19
- AsterFlow: () => v,
20
- AsterFlowInstance: () => c
17
+ var D = {};
18
+ M(D, {
19
+ AsterFlow: () => I,
20
+ AsterFlowInstance: () => h
21
21
  });
22
- module.exports = I(C);
22
+ module.exports = v(D);
23
23
  // core/src/controllers/Asterflow.ts
24
- var f = require("@asterflow/adapter"), m = require("@asterflow/response"), l = require("@asterflow/router"), R = require("@asterflow/url-parser"), g = require("reminist");
24
+ var x = require("@asterflow/adapter"), g = require("@asterflow/response"), R = require("@asterflow/router"), u = require("@asterflow/url-parser"), f = require("reminist");
25
25
  // core/src/utils/parser.ts
26
- function p(u, e) {
27
- return `${u}${e}`.replace(/\/{2,}/g, "/");
26
+ function m(l, e) {
27
+ return `${l}${e}`.replace(/\/{2,}/g, "/");
28
28
  }
29
29
  // core/src/controllers/Asterflow.ts
30
- var c = class {
30
+ var h = class {
31
31
  driver;
32
- reminist = new g.Reminist({ keys: Object.keys(l.MethodType) });
32
+ reminist = new f.Reminist({ keys: Object.keys(R.MethodType) });
33
33
  middlewares = [];
34
34
  plugins = {};
35
35
  onRequestPlugins = [];
36
36
  onResponsePlugins = [];
37
37
  beforeInitializePlugins = [];
38
38
  afterInitializePlugins = [];
39
+ pluginContext = {};
39
40
  constructor(e) {
40
- this.driver = e?.driver ?? f.adapters.node, this.driver.onRequest = this.handleRequest.bind(this);
41
+ this.driver = e?.driver ?? x.adapters.node, this.driver.onRequest = this.handleRequest.bind(this);
41
42
  }
42
43
  async handleRequest(e, t) {
43
- t = t ?? new m.AsterResponse();
44
+ t = t ?? new g.AsterResponse();
44
45
  let s = () => t.notFound({
45
46
  statusCode: 404,
46
47
  code: "NOT_FOUND",
@@ -51,26 +52,26 @@ var c = class {
51
52
  if (!r?.node?.store) return s();
52
53
  let i = r.node.store, a = await this.runHooks("onRequest", this, i, e, t);
53
54
  if (a) return a;
54
- (i.url.ast.expressions.has(R.InternalExpression.Variable) || i.url.ast.expressions.has(R.InternalExpression.Slug)) && (e.url = e.url.withParser(i.url));
55
+ (i.url.ast.expressions.has(u.InternalExpression.Variable) || i.url.ast.expressions.has(u.InternalExpression.Dynamic) || i.url.ast.expressions.has(u.InternalExpression.DynamicCatchAll) || i.url.ast.expressions.has(u.InternalExpression.DynamicOptionalCatchAll) || i.url.ast.expressions.has(u.InternalExpression.Wildcard)) && (e.url = e.url.setParser(i.url));
55
56
  try {
56
57
  return await this.runHandler(i, e, t), await this.runHooks("onResponse", this, i, e, t), t;
57
58
  } catch (o) {
58
59
  return console.log(o), t.badRequest({
59
60
  statusCode: 400,
60
- message: o instanceof R.ErrorLog ? "AST_ERROR" : "ERROR",
61
- error: o instanceof R.ErrorLog || o instanceof Error ? o.message : o
61
+ message: o instanceof u.ErrorLog ? "AST_ERROR" : "ERROR",
62
+ error: o instanceof u.ErrorLog || o instanceof Error ? o.message : o
62
63
  });
63
64
  }
64
65
  }
65
66
  middleware(e) {
66
67
  for (let t of e.controllers) {
67
- let s = p(e.basePath, t.path);
68
+ let s = m(e.basePath, t.path);
68
69
  this.addEntry(t, s);
69
70
  }
70
71
  return this;
71
72
  }
72
73
  controller(e) {
73
- let t = p("/", e.path);
74
+ let t = m("/", e.path);
74
75
  return this.addEntry(e, t), this;
75
76
  }
76
77
  use(e, t) {
@@ -82,10 +83,10 @@ var c = class {
82
83
  return this;
83
84
  }
84
85
  router(e) {
85
- return this.controller(new l.Router(e)), this;
86
+ return this.controller(new R.Router(e)), this;
86
87
  }
87
- method(e) {
88
- return this.controller(new l.Method(e)), this;
88
+ method(e, t) {
89
+ return this.controller(new R.Method(e, t)), this;
89
90
  }
90
91
  async resolvePluginContexts() {
91
92
  for (let e in this.plugins) {
@@ -96,12 +97,13 @@ var c = class {
96
97
  Object.assign(t.context, n);
97
98
  }
98
99
  }
100
+ this.pluginContext = Object.values(this.plugins).reduce((e, t) => ({ ...e, ...t.context }), {});
99
101
  }
100
102
  async listen(...e) {
101
103
  await this.resolvePluginContexts(), await this.runHooks("beforeInitialize", this), await this.driver.listen(...e), await this.runHooks("afterInitialize", this);
102
104
  }
103
105
  addEntry(e, t) {
104
- let s = e instanceof l.Method ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new R.Analyze(t) };
106
+ let s = e instanceof R.Method ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new u.Analyze(t) };
105
107
  for (let r of s)
106
108
  this.reminist.add(r, t, n);
107
109
  }
@@ -124,9 +126,9 @@ var c = class {
124
126
  if (!o) break;
125
127
  if (!n || !r || !s) return;
126
128
  for (let d of o) {
127
- let h = await d({ instance: t, router: s, request: n, response: r, plugin: a });
128
- if (h && h instanceof m.AsterResponse)
129
- return h;
129
+ let c = await d({ instance: t, router: s, request: n, response: r, plugin: a });
130
+ if (c && typeof c == "object" && c.constructor?.name === "AsterResponse")
131
+ return c;
130
132
  }
131
133
  }
132
134
  break;
@@ -141,31 +143,44 @@ var c = class {
141
143
  break;
142
144
  }
143
145
  }
146
+ async runMiddlewares(e, t, s, n) {
147
+ let r = {};
148
+ if (!e || e.length === 0) return { context: r };
149
+ let a = { request: t, response: s, schema: n, next: (o) => o };
150
+ for (let o of e) {
151
+ let d = await o.onRun(a);
152
+ if (d instanceof g.AsterResponse) return { context: r, response: d };
153
+ d && typeof d == "object" && Object.assign(r, d);
154
+ }
155
+ return { context: r };
156
+ }
144
157
  async runHandler({ route: e }, t, s) {
145
- let n = t.getMethod().toLowerCase(), r = e instanceof l.Method ? e.handler : e.methods[n], i = e instanceof l.Method ? e.schema : e.schema?.[n];
158
+ let n = t.getMethod().toLowerCase(), r = e instanceof R.Method ? e.handler : e.methods[n], i = e instanceof R.Method ? e.schema : e.schema?.[n];
146
159
  if (!r) return null;
160
+ let a = await t.getBody(), { context: o, response: d } = await this.runMiddlewares(e.use, t, s, a);
161
+ if (d) return d;
147
162
  if (i) {
148
- let d = i.safeParse(await t.getBody());
149
- if (!d.success)
163
+ let p = i.safeParse(a);
164
+ if (!p.success)
150
165
  return s.validationError({
151
166
  statusCode: 422,
152
167
  message: "VALIDATION_ERROR",
153
- error: JSON.parse(d.error)
168
+ error: JSON.parse(p.error)
154
169
  });
155
- s.send(d.data);
170
+ s.send(p.data);
156
171
  }
157
- let a = Object.values(this.plugins).reduce((d, h) => ({ ...d, ...h.context }), {}), o = {
172
+ let c = {
158
173
  instance: this,
159
174
  request: t,
160
175
  response: s,
161
176
  url: t.url,
162
- schema: await t.getBody(),
163
- middleware: {},
164
- plugins: a
177
+ schema: a,
178
+ middleware: o,
179
+ plugins: this.pluginContext
165
180
  };
166
- return r(o);
181
+ return r(c);
167
182
  }
168
- }, v = c;
183
+ }, I = h;
169
184
  0 && (module.exports = {
170
185
  AsterFlow,
171
186
  AsterFlowInstance
package/dist/mjs/index.js CHANGED
@@ -1,32 +1,33 @@
1
1
  // core/src/controllers/Asterflow.ts
2
- import { adapters as f } from "@asterflow/adapter";
3
- import { AsterResponse as y } from "@asterflow/response";
2
+ import { adapters as x } from "@asterflow/adapter";
3
+ import { AsterResponse as m } from "@asterflow/response";
4
4
  import {
5
- Method as l,
6
- MethodType as g,
7
- Router as x
5
+ Method as R,
6
+ MethodType as f,
7
+ Router as w
8
8
  } from "@asterflow/router";
9
- import { Analyze as P, ErrorLog as p, InternalExpression as m } from "@asterflow/url-parser";
10
- import { Reminist as w } from "reminist";
9
+ import { Analyze as A, ErrorLog as g, InternalExpression as l } from "@asterflow/url-parser";
10
+ import { Reminist as P } from "reminist";
11
11
  // core/src/utils/parser.ts
12
- function R(c, e) {
13
- return `${c}${e}`.replace(/\/{2,}/g, "/");
12
+ function h(y, e) {
13
+ return `${y}${e}`.replace(/\/{2,}/g, "/");
14
14
  }
15
15
  // core/src/controllers/Asterflow.ts
16
- var h = class {
16
+ var p = class {
17
17
  driver;
18
- reminist = new w({ keys: Object.keys(g) });
18
+ reminist = new P({ keys: Object.keys(f) });
19
19
  middlewares = [];
20
20
  plugins = {};
21
21
  onRequestPlugins = [];
22
22
  onResponsePlugins = [];
23
23
  beforeInitializePlugins = [];
24
24
  afterInitializePlugins = [];
25
+ pluginContext = {};
25
26
  constructor(e) {
26
- this.driver = e?.driver ?? f.node, this.driver.onRequest = this.handleRequest.bind(this);
27
+ this.driver = e?.driver ?? x.node, this.driver.onRequest = this.handleRequest.bind(this);
27
28
  }
28
29
  async handleRequest(e, t) {
29
- t = t ?? new y();
30
+ t = t ?? new m();
30
31
  let s = () => t.notFound({
31
32
  statusCode: 404,
32
33
  code: "NOT_FOUND",
@@ -37,26 +38,26 @@ var h = class {
37
38
  if (!r?.node?.store) return s();
38
39
  let i = r.node.store, a = await this.runHooks("onRequest", this, i, e, t);
39
40
  if (a) return a;
40
- (i.url.ast.expressions.has(m.Variable) || i.url.ast.expressions.has(m.Slug)) && (e.url = e.url.withParser(i.url));
41
+ (i.url.ast.expressions.has(l.Variable) || i.url.ast.expressions.has(l.Dynamic) || i.url.ast.expressions.has(l.DynamicCatchAll) || i.url.ast.expressions.has(l.DynamicOptionalCatchAll) || i.url.ast.expressions.has(l.Wildcard)) && (e.url = e.url.setParser(i.url));
41
42
  try {
42
43
  return await this.runHandler(i, e, t), await this.runHooks("onResponse", this, i, e, t), t;
43
44
  } catch (o) {
44
45
  return console.log(o), t.badRequest({
45
46
  statusCode: 400,
46
- message: o instanceof p ? "AST_ERROR" : "ERROR",
47
- error: o instanceof p || o instanceof Error ? o.message : o
47
+ message: o instanceof g ? "AST_ERROR" : "ERROR",
48
+ error: o instanceof g || o instanceof Error ? o.message : o
48
49
  });
49
50
  }
50
51
  }
51
52
  middleware(e) {
52
53
  for (let t of e.controllers) {
53
- let s = R(e.basePath, t.path);
54
+ let s = h(e.basePath, t.path);
54
55
  this.addEntry(t, s);
55
56
  }
56
57
  return this;
57
58
  }
58
59
  controller(e) {
59
- let t = R("/", e.path);
60
+ let t = h("/", e.path);
60
61
  return this.addEntry(e, t), this;
61
62
  }
62
63
  use(e, t) {
@@ -68,10 +69,10 @@ var h = class {
68
69
  return this;
69
70
  }
70
71
  router(e) {
71
- return this.controller(new x(e)), this;
72
+ return this.controller(new w(e)), this;
72
73
  }
73
- method(e) {
74
- return this.controller(new l(e)), this;
74
+ method(e, t) {
75
+ return this.controller(new R(e, t)), this;
75
76
  }
76
77
  async resolvePluginContexts() {
77
78
  for (let e in this.plugins) {
@@ -82,12 +83,13 @@ var h = class {
82
83
  Object.assign(t.context, n);
83
84
  }
84
85
  }
86
+ this.pluginContext = Object.values(this.plugins).reduce((e, t) => ({ ...e, ...t.context }), {});
85
87
  }
86
88
  async listen(...e) {
87
89
  await this.resolvePluginContexts(), await this.runHooks("beforeInitialize", this), await this.driver.listen(...e), await this.runHooks("afterInitialize", this);
88
90
  }
89
91
  addEntry(e, t) {
90
- let s = e instanceof l ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new P(t) };
92
+ let s = e instanceof R ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new A(t) };
91
93
  for (let r of s)
92
94
  this.reminist.add(r, t, n);
93
95
  }
@@ -111,7 +113,7 @@ var h = class {
111
113
  if (!n || !r || !s) return;
112
114
  for (let d of o) {
113
115
  let u = await d({ instance: t, router: s, request: n, response: r, plugin: a });
114
- if (u && u instanceof y)
116
+ if (u && typeof u == "object" && u.constructor?.name === "AsterResponse")
115
117
  return u;
116
118
  }
117
119
  }
@@ -127,32 +129,45 @@ var h = class {
127
129
  break;
128
130
  }
129
131
  }
132
+ async runMiddlewares(e, t, s, n) {
133
+ let r = {};
134
+ if (!e || e.length === 0) return { context: r };
135
+ let a = { request: t, response: s, schema: n, next: (o) => o };
136
+ for (let o of e) {
137
+ let d = await o.onRun(a);
138
+ if (d instanceof m) return { context: r, response: d };
139
+ d && typeof d == "object" && Object.assign(r, d);
140
+ }
141
+ return { context: r };
142
+ }
130
143
  async runHandler({ route: e }, t, s) {
131
- let n = t.getMethod().toLowerCase(), r = e instanceof l ? e.handler : e.methods[n], i = e instanceof l ? e.schema : e.schema?.[n];
144
+ let n = t.getMethod().toLowerCase(), r = e instanceof R ? e.handler : e.methods[n], i = e instanceof R ? e.schema : e.schema?.[n];
132
145
  if (!r) return null;
146
+ let a = await t.getBody(), { context: o, response: d } = await this.runMiddlewares(e.use, t, s, a);
147
+ if (d) return d;
133
148
  if (i) {
134
- let d = i.safeParse(await t.getBody());
135
- if (!d.success)
149
+ let c = i.safeParse(a);
150
+ if (!c.success)
136
151
  return s.validationError({
137
152
  statusCode: 422,
138
153
  message: "VALIDATION_ERROR",
139
- error: JSON.parse(d.error)
154
+ error: JSON.parse(c.error)
140
155
  });
141
- s.send(d.data);
156
+ s.send(c.data);
142
157
  }
143
- let a = Object.values(this.plugins).reduce((d, u) => ({ ...d, ...u.context }), {}), o = {
158
+ let u = {
144
159
  instance: this,
145
160
  request: t,
146
161
  response: s,
147
162
  url: t.url,
148
- schema: await t.getBody(),
149
- middleware: {},
150
- plugins: a
163
+ schema: a,
164
+ middleware: o,
165
+ plugins: this.pluginContext
151
166
  };
152
- return r(o);
167
+ return r(u);
153
168
  }
154
- }, k = h;
169
+ }, O = p;
155
170
  export {
156
- k as AsterFlow,
157
- h as AsterFlowInstance
171
+ O as AsterFlow,
172
+ p as AsterFlowInstance
158
173
  };
@@ -1,15 +1,13 @@
1
1
  import { Runtime, type Adapter, type AnyAdapter } from '@asterflow/adapter';
2
2
  import type { AnyPlugins, InferConfigArgument, InferPluginExtension, Plugin, ResolvedPlugin } from '@asterflow/plugin';
3
3
  import { type Responders } from '@asterflow/response';
4
- import { Method, Router, type AnyMiddleware, type AnyMiddlewares, type AnyRouter, type AnySchema, type MethodHandler, type MethodKeys, type MethodOptions, type Middleware, type MiddlewareOutput, type RouteHandler, type RouterOptions, type SchemaDynamic } from '@asterflow/router';
5
- import { type NormalizePath } from '@asterflow/url-parser';
4
+ import { Method, Router, type AnyMiddleware, type AnyMiddlewares, type AnyRouter, type AnySchema, type MethodCallProps, type MethodConstructorOptions, type MethodHandler, type MethodKeys, type Middleware, type MiddlewareOutput, type RouteHandler, type RouterCallProps, type RouterOptions, type SchemaDynamic } from '@asterflow/router';
6
5
  import { Reminist } from 'reminist';
7
6
  import type { AsterFlowOptions } from '../types/asterflow';
8
- import type { ExtractPaths, InferPath } from '../types/paths';
9
- import type { AnyReminist, InferReministContext, InferReministPath } from '../types/reminist';
7
+ import type { AnyReminist, DefaultReminist, InferReministContext } from '../types/reminist';
10
8
  import type { BuildRouteContext, BuildRoutesContext, RouteEntry } from '../types/routes';
11
9
  import type { AnyRecord } from '../types/utils';
12
- export declare class AsterFlowInstance<const Drive extends AnyAdapter = Adapter<Runtime.Node>, const Routers extends AnyReminist = AnyReminist, const Plugins extends AnyPlugins = {}, const Middlewares extends AnyMiddlewares = [], const Extension extends AnyRecord = {}> {
10
+ export declare class AsterFlowInstance<const Drive extends AnyAdapter = Adapter<Runtime.Node>, const Routers extends AnyReminist = DefaultReminist, const Plugins extends AnyPlugins = {}, const Middlewares extends AnyMiddlewares = [], const Extension extends AnyRecord = {}> {
13
11
  readonly driver: Drive;
14
12
  readonly reminist: Routers;
15
13
  readonly middlewares: Middlewares;
@@ -18,6 +16,8 @@ export declare class AsterFlowInstance<const Drive extends AnyAdapter = Adapter<
18
16
  private readonly onResponsePlugins;
19
17
  private readonly beforeInitializePlugins;
20
18
  private readonly afterInitializePlugins;
19
+ /** Merged view of every plugin's context, cached by `resolvePluginContexts()` at `listen()` time - see `runHandler`. */
20
+ private pluginContext;
21
21
  constructor(options?: AsterFlowOptions<Drive>);
22
22
  /**
23
23
  * Handles incoming requests, executing `onRequest` and `onResponse` plugin hooks.
@@ -31,34 +31,40 @@ export declare class AsterFlowInstance<const Drive extends AnyAdapter = Adapter<
31
31
  middleware<BasePath extends string, const Routes extends readonly AnyRouter[]>(options: {
32
32
  basePath: BasePath;
33
33
  controllers: Routes;
34
- }): AsterFlow<Drive, Reminist<InferReministPath<Routers> extends string[] ? [...InferReministPath<Routers>, ...ExtractPaths<BasePath, Routes>] : ExtractPaths<BasePath, Routes>, InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRoutesContext<BasePath, Routes> : BuildRoutesContext<BasePath, Routes>, MethodKeys[]>, Plugins, Middlewares, Extension>;
34
+ }): AsterFlow<Drive, Reminist<InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRoutesContext<BasePath, Routes> : BuildRoutesContext<BasePath, Routes>, MethodKeys[]>, Plugins, Middlewares, Extension>;
35
35
  /**
36
36
  * Adds a single controller to AsterFlow.
37
37
  * The controller's path is normalized to be relative to the root.
38
38
  */
39
- controller<Route extends AnyRouter>(router: Route): AsterFlow<Drive, Reminist<InferReministPath<Routers> extends string[] ? [...InferReministPath<Routers>, NormalizePath<InferPath<Route>>] : [NormalizePath<InferPath<Route>>], InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
39
+ controller<Route extends AnyRouter>(router: Route): AsterFlow<Drive, Reminist<InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
40
40
  /**
41
41
  * Registers a plugin and its configuration with the AsterFlow instance.
42
42
  * Applies any instance extensions defined by the plugin.
43
43
  */
44
- use<Plug extends Plugin<any, any, any, any, any, any>>(plugin: Plug, config?: InferConfigArgument<Plug>): AsterFlow<Drive, Routers, Plugins & { [K in Plug["name"]]: ResolvedPlugin<Plug>; }, Middlewares, Extension & InferPluginExtension<Plug>>;
44
+ use<Plug extends Plugin<any>>(plugin: Plug, config?: InferConfigArgument<Plug>): AsterFlow<Drive, Routers, Plugins & { [K in Plug["name"]]: ResolvedPlugin<Plug>; }, Middlewares, Extension & InferPluginExtension<Plug>>;
45
45
  /**
46
46
  * Creates and adds a new router to the AsterFlow instance.
47
47
  * A router can contain multiple method handlers for different HTTP verbs.
48
48
  */
49
- router<Responder extends Responders, const Path extends string = string, const Schema extends SchemaDynamic<MethodKeys> = SchemaDynamic<MethodKeys>, const Middlewares extends readonly AnyMiddleware[] = [], const Context extends MiddlewareOutput<Middlewares> = MiddlewareOutput<Middlewares>, const Routers extends {
50
- [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
49
+ router<Responder extends Responders, const Path extends string = string, const Schema extends SchemaDynamic<MethodKeys> = SchemaDynamic<MethodKeys>, const RouteMiddlewares extends readonly AnyMiddleware[] = [], const Context extends MiddlewareOutput<RouteMiddlewares> = MiddlewareOutput<RouteMiddlewares>, const Routers extends {
50
+ [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, RouteMiddlewares, Context>;
51
51
  } = {
52
- [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
53
- }, const Route extends Router<Responder, Path, Schema, Middlewares, Context, Routers> = Router<Responder, Path, Schema, Middlewares, Context, Routers>>(options: RouterOptions<Path, Schema, Responder, Middlewares, Context, Routers>): AsterFlow<Drive, Reminist<InferReministPath<Routers> extends string[] ? [...InferReministPath<Routers>, NormalizePath<InferPath<Route>>] : [NormalizePath<InferPath<Route>>], InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
52
+ [Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, RouteMiddlewares, Context>;
53
+ }, const Route extends Router<RouterCallProps<Responder, Path, Schema, RouteMiddlewares, Context, Routers>> = Router<RouterCallProps<Responder, Path, Schema, RouteMiddlewares, Context, Routers>>>(options: RouterOptions<RouterCallProps<Responder, Path, Schema, RouteMiddlewares, Context, Routers>>): AsterFlow<Drive, Reminist<InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
54
54
  /**
55
55
  * Creates and adds a new method handler (route) to the AsterFlow instance.
56
56
  * Defines a specific route for an HTTP method (GET, POST, etc.).
57
57
  */
58
- method<Responder extends Responders, const Path extends string, const Methoder extends MethodKeys, const Schema extends AnySchema, const Middlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[], const Context extends MiddlewareOutput<Middlewares>, const Instance extends AsterFlowInstance<Drive, Routers, Plugins, Middlewares, Extension>, const Handler extends MethodHandler<Path, Drive['runtime'], Responder, Schema, Middlewares, Context, Instance>, const Route extends Method<Responder, Path, Drive['runtime'], Methoder, Schema, Middlewares, Context, Instance, Handler>>(options: MethodOptions<Responder, Path, Drive['runtime'], Methoder, Schema, Middlewares, Context, Instance, Handler>): AsterFlow<Drive, Reminist<InferReministPath<Routers> extends string[] ? [...InferReministPath<Routers>, NormalizePath<InferPath<Route>>] : [NormalizePath<InferPath<Route>>], InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
58
+ method<Responder extends Responders, const Path extends string = string, const Methoder extends MethodKeys = MethodKeys, const Schema extends AnySchema = AnySchema, const RouteMiddlewares extends readonly Middleware<Responder, Schema, string, Record<string, unknown>>[] = [], const Context extends MiddlewareOutput<RouteMiddlewares> = MiddlewareOutput<RouteMiddlewares>, const Instance extends AsterFlowInstance<Drive, Routers, Plugins, Middlewares, Extension> & AnyAsterflow = AsterFlowInstance<Drive, Routers, Plugins, Middlewares, Extension> & AnyAsterflow, const Handler extends MethodHandler<Path, Drive['runtime'], Responder, Schema, RouteMiddlewares, Context, Instance> = MethodHandler<Path, Drive['runtime'], Responder, Schema, RouteMiddlewares, Context, Instance>, const Route extends Method<MethodCallProps<Responder, Path, Drive['runtime'], Methoder, Schema, RouteMiddlewares, Context, Instance, {}, Handler>> = Method<MethodCallProps<Responder, Path, Drive['runtime'], Methoder, Schema, RouteMiddlewares, Context, Instance, {}, Handler>>>(methodKey: Methoder, options: MethodConstructorOptions<MethodCallProps<Responder, Path, Drive['runtime'], Methoder, Schema, RouteMiddlewares, Context, Instance, {}, Handler>>): AsterFlow<Drive, Reminist<InferReministContext<Routers> extends Record<string, RouteEntry<string, AnyRouter>> ? InferReministContext<Routers> & BuildRouteContext<Route> : BuildRouteContext<Route>, MethodKeys[]>, Plugins, Middlewares, Extension>;
59
59
  /**
60
60
  * Itera sobre todos os plugins registrados e executa seus resolvers
61
61
  * de forma assíncrona, construindo o contexto de cada um.
62
+ *
63
+ * Also caches the merged view of every plugin's context into
64
+ * `this.pluginContext` here, once, instead of re-merging it (via
65
+ * `Object.values(...).reduce(...)`, an O(plugins) allocation) on every
66
+ * single request in `runHandler` - plugin contexts are static after this
67
+ * point.
62
68
  */
63
69
  private resolvePluginContexts;
64
70
  /**
@@ -76,14 +82,24 @@ export declare class AsterFlowInstance<const Drive extends AnyAdapter = Adapter<
76
82
  */
77
83
  private runHooks;
78
84
  /**
79
- * Executes a route handler, processing the request and response.
80
- * Performs schema validation, if present, and invokes the route handler.
85
+ * Runs a route's `use` middleware chain in order, merging each
86
+ * middleware's `next(params)` output into a single accumulated context
87
+ * object. If a middleware returns an `AsterResponse` instead of calling
88
+ * `next(...)` (e.g. `return response.unauthorized({...})`), the chain
89
+ * stops immediately and that response is propagated back as final.
90
+ */
91
+ private runMiddlewares;
92
+ /**
93
+ * Executes a route handler, processing the request and response. Runs
94
+ * `use` middlewares first (so an unauthorized/rejected request short-
95
+ * circuits before paying for body validation), then schema validation if
96
+ * present, then invokes the route handler.
81
97
  */
82
98
  private runHandler;
83
99
  }
84
- export type AsterFlow<Drive extends AnyAdapter = AnyAdapter, Routers extends AnyReminist = AnyReminist, Plugins extends AnyPlugins = AnyPlugins, Middlewares extends readonly AnyMiddleware[] = AnyMiddleware[], Extension extends Record<string, any> = Record<string, any>> = AsterFlowInstance<Drive, Routers, Plugins, Middlewares, Extension> & Extension;
100
+ export type AsterFlow<Drive extends AnyAdapter = AnyAdapter, Routers extends AnyReminist = DefaultReminist, Plugins extends AnyPlugins = AnyPlugins, Middlewares extends readonly AnyMiddleware[] = AnyMiddleware[], Extension extends Record<string, any> = Record<string, any>> = AsterFlowInstance<Drive, Routers, Plugins, Middlewares, Extension> & Extension;
85
101
  export declare const AsterFlow: {
86
- new <Drive extends AnyAdapter = Adapter<Runtime.Node>>(options?: AsterFlowOptions<Drive>): AsterFlow<Drive, AnyReminist, {}, [], {}>;
102
+ new <Drive extends AnyAdapter = Adapter<Runtime.Node>>(options?: AsterFlowOptions<Drive>): AsterFlow<Drive, DefaultReminist, {}, [], {}>;
87
103
  };
88
104
  /**
89
105
  * Represents a generic AsterFlow instance, with all its types defined as `any`.
@@ -1,4 +1,4 @@
1
- import type { AnyRouter, Method, Router } from '@asterflow/router';
1
+ import type { Method, MethodProps, Router, RouterProps } from '@asterflow/router';
2
2
  import type { NormalizePath } from '@asterflow/url-parser';
3
3
  /**
4
4
  * Combines a base path with a relative path and normalizes the result.
@@ -8,10 +8,4 @@ export type CombinePaths<Base extends string, Path extends string> = NormalizePa
8
8
  * Infers the path from a `Router` or `Method` type.
9
9
  * This normalizes the path and combines it with the root.
10
10
  */
11
- export type InferPath<T> = T extends Router<any, infer P, any, any, any, any> ? CombinePaths<'/', P> : T extends Method<any, infer P, any, any, any, any, any, any, any> ? CombinePaths<'/', P> : never;
12
- /**
13
- * Extracts and normalizes the paths from an array of routers (`AnyRouter[]`), combining them with a base path.
14
- */
15
- export type ExtractPaths<Base extends string, Routes extends readonly AnyRouter[]> = {
16
- [K in keyof Routes]: Routes[K] extends infer R extends AnyRouter ? CombinePaths<Base, InferPath<R>> : never;
17
- };
11
+ export type InferPath<T> = T extends Router<infer Props extends RouterProps> ? CombinePaths<'/', Props['path']> : T extends Method<infer Props extends MethodProps> ? CombinePaths<'/', Props['path']> : never;
@@ -1,23 +1,22 @@
1
- import type { AnyRouter, MethodKeys } from '@asterflow/router';
1
+ import type { MethodKeys } from '@asterflow/router';
2
2
  import type { Reminist } from 'reminist';
3
- import type { RouteEntry } from './routes';
4
3
  /**
5
4
  * Represents a generic Reminist instance, used for managing routes.
6
- * Includes a string array for paths, a record of route entries, and an array of HTTP method keys.
5
+ * Includes a record of route entries and an array of HTTP method keys.
7
6
  */
8
- export type AnyReminist = Reminist<readonly string[], Record<string, RouteEntry<string, AnyRouter>>, MethodKeys[]>;
7
+ export type AnyReminist = Reminist<any, any>;
9
8
  /**
10
- * Infers the paths from a Reminist instance.
9
+ * The `Routers` type param's actual starting value for a fresh `AsterFlow`
10
+ * instance (before any `.method()`/`.router()` call). Deliberately NOT
11
+ * `AnyReminist`: a bare `any` context here makes
12
+ * `InferReministContext<Routers> extends Record<...> ? A : B` (used by
13
+ * `.method()`/`.router()`/`.controller()`/`.middleware()`'s return type)
14
+ * collapse to `any` - TS special-cases a naked `any` in a conditional
15
+ * type's checked position to `A | B`, and `any & X` is `any`, so the whole
16
+ * union collapses back to `any`. An empty, concrete context sidesteps that.
11
17
  */
12
- export type InferReministPath<T> = T extends Reminist<infer P, any, any> ? P : never;
18
+ export type DefaultReminist = Reminist<{}, MethodKeys[]>;
13
19
  /**
14
20
  * Infers the context from a Reminist instance.
15
21
  */
16
- export type InferReministContext<T> = T extends Reminist<any, infer C, any> ? C : never;
17
- /**
18
- * Reminist context that preserves specific route information.
19
- * Each path maps to its specific typed route.
20
- */
21
- export type ReministContext<PathsAndRoutes extends Record<string, AnyRouter>> = {
22
- readonly [Path in keyof PathsAndRoutes]: RouteEntry<Path extends string ? Path : never, PathsAndRoutes[Path]>;
23
- };
22
+ export type InferReministContext<T> = T extends Reminist<infer C, any> ? C : never;
@@ -1,4 +1,4 @@
1
- import type { AnyRouter, MethodKeys } from '@asterflow/router';
1
+ import type { AnyRouter, MethodKeys, Prettify } from '@asterflow/router';
2
2
  import type { CombinePaths, InferPath } from './paths';
3
3
  import type { UnionToIntersection } from './utils';
4
4
  import type { Analyze, NormalizePath } from '@asterflow/url-parser';
@@ -18,10 +18,12 @@ export type BuildRoutesContext<Base extends string, Routes extends readonly AnyR
18
18
  }[number] & {}> & Record<string, RouteEntry<string, AnyRouter>>;
19
19
  /**
20
20
  * Defines the specific typing preserved for each route entry in Reminist.
21
+ * Wrapped in `Prettify` so it shows as a labeled `{ path: ..., route: ..., ... }`
22
+ * object on hover instead of `RouteEntry<"...", Method<...>>` by name.
21
23
  */
22
- export type RouteEntry<Path extends string, Route extends AnyRouter> = {
24
+ export type RouteEntry<Path extends string, Route extends AnyRouter> = Prettify<{
23
25
  readonly path: Path;
24
26
  readonly route: Route;
25
27
  readonly methods: readonly MethodKeys[];
26
28
  readonly url: Analyze<string>;
27
- };
29
+ }>;
package/package.json CHANGED
@@ -1,6 +1,15 @@
1
1
  {
2
2
  "name": "asterflow",
3
- "version": "0.0.6",
3
+ "version": "1.0.0",
4
+ "description": "The core framework - ties together adapters, routing, plugins and responses into one typed AsterFlow app.",
5
+ "keywords": [
6
+ "asterflow",
7
+ "http",
8
+ "framework",
9
+ "server",
10
+ "router",
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",
@@ -34,11 +43,11 @@
34
43
  "typescript": "^5.8.3"
35
44
  },
36
45
  "dependencies": {
37
- "@asterflow/adapter": "1.0.14",
38
- "@asterflow/plugin": "1.0.10",
39
- "@asterflow/response": "1.0.11",
40
- "@asterflow/router": "1.0.14",
41
- "@asterflow/url-parser": "^2.0.3",
42
- "reminist": "^1.0.5"
46
+ "@asterflow/adapter": "^1.0.13",
47
+ "@asterflow/plugin": "^1.1.0",
48
+ "@asterflow/response": "^1.1.0",
49
+ "@asterflow/router": "^2.0.0",
50
+ "@asterflow/url-parser": "^4.1.1",
51
+ "reminist": "^1.0.8"
43
52
  }
44
53
  }