asterflow 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -0
- package/dist/cjs/index.cjs +159 -0
- package/dist/cjs/package.json +3 -0
- package/dist/mjs/index.js +144 -0
- package/dist/mjs/package.json +3 -0
- package/dist/types/controllers/Asterflow.d.ts +90 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/types/asterflow.d.ts +9 -0
- package/dist/types/types/paths.d.ts +46 -0
- package/dist/types/types/plugin.d.ts +8 -0
- package/dist/types/types/reminist.d.ts +23 -0
- package/dist/types/types/routes.d.ts +27 -0
- package/dist/types/types/utils.d.ts +6 -0
- package/dist/types/utils/parser.d.ts +4 -0
- package/package.json +45 -0
- package/tsconfig.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Asterflow
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
> The heart of the AsterFlow framework, providing server initialization and configuration with strong typing.
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install asterflow
|
|
19
|
+
# or
|
|
20
|
+
bun install asterflow
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 💡 About
|
|
24
|
+
|
|
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.
|
|
26
|
+
|
|
27
|
+
## ✨ Features
|
|
28
|
+
|
|
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
|
|
35
|
+
|
|
36
|
+
## 🚀 Usage
|
|
37
|
+
|
|
38
|
+
### Basic Setup
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
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'
|
|
76
|
+
|
|
77
|
+
const auth = new Middleware({
|
|
78
|
+
name: 'auth',
|
|
79
|
+
onRun({ next }) {
|
|
80
|
+
return next({
|
|
81
|
+
auth: false
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const router = new Router({
|
|
87
|
+
path: '/protected',
|
|
88
|
+
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Individual Routes
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { Method } from '@asterflow/router'
|
|
106
|
+
|
|
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
|
+
})
|
|
115
|
+
|
|
116
|
+
aster.controller(route)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 🔗 Related Packages
|
|
120
|
+
|
|
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
|
|
128
|
+
|
|
129
|
+
## 📄 License
|
|
130
|
+
|
|
131
|
+
MIT - See [LICENSE](https://github.com/AsterFlow/AsterFlow/blob/main/LICENSE) for more details.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var y = Object.defineProperty;
|
|
3
|
+
var f = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var P = Object.getOwnPropertyNames;
|
|
5
|
+
var w = Object.prototype.hasOwnProperty;
|
|
6
|
+
var A = (r, e) => {
|
|
7
|
+
for (var t in e)
|
|
8
|
+
y(r, t, { get: e[t], enumerable: !0 });
|
|
9
|
+
}, M = (r, e, t, s) => {
|
|
10
|
+
if (e && typeof e == "object" || typeof e == "function")
|
|
11
|
+
for (let n of P(e))
|
|
12
|
+
!w.call(r, n) && n !== t && y(r, n, { get: () => e[n], enumerable: !(s = f(e, n)) || s.enumerable });
|
|
13
|
+
return r;
|
|
14
|
+
};
|
|
15
|
+
var I = (r) => M(y({}, "__esModule", { value: !0 }), r);
|
|
16
|
+
// core/src/index.ts
|
|
17
|
+
var C = {};
|
|
18
|
+
A(C, {
|
|
19
|
+
AsterFlow: () => v,
|
|
20
|
+
AsterFlowInstance: () => h,
|
|
21
|
+
joinPaths: () => R
|
|
22
|
+
});
|
|
23
|
+
module.exports = I(C);
|
|
24
|
+
// core/src/controllers/Asterflow.ts
|
|
25
|
+
var m = require("@asterflow/adapter"), p = require("@asterflow/response"), a = require("@asterflow/router"), l = require("@asterflow/url-parser"), x = require("reminist");
|
|
26
|
+
// core/src/utils/parser.ts
|
|
27
|
+
function R(r, e) {
|
|
28
|
+
return `${r}${e}`.replace(/\/{2,}/g, "/");
|
|
29
|
+
}
|
|
30
|
+
// core/src/controllers/Asterflow.ts
|
|
31
|
+
var h = class {
|
|
32
|
+
driver;
|
|
33
|
+
reminist = new x.Reminist({ keys: Object.keys(a.MethodType) });
|
|
34
|
+
middlewares = [];
|
|
35
|
+
plugins = {};
|
|
36
|
+
onRequestPlugins = [];
|
|
37
|
+
onResponsePlugins = [];
|
|
38
|
+
beforeInitializePlugins = [];
|
|
39
|
+
afterInitializePlugins = [];
|
|
40
|
+
constructor(e) {
|
|
41
|
+
this.driver = e?.driver ?? m.adapters.node, this.driver.onRequest = this.handleRequest.bind(this);
|
|
42
|
+
}
|
|
43
|
+
async handleRequest(e, t) {
|
|
44
|
+
t = t ?? new p.AsterResponse(), await this.runHooks("onRequest", e, t);
|
|
45
|
+
let s = () => t.notFound({
|
|
46
|
+
statusCode: 404,
|
|
47
|
+
code: "NOT_FOUND",
|
|
48
|
+
message: `Unable to find route: ${e.getPathname()}`
|
|
49
|
+
}), n = e.getMethod().toLowerCase();
|
|
50
|
+
if (!n) return s();
|
|
51
|
+
let o = this.reminist.find(n, e.url.getPathname());
|
|
52
|
+
if (!o?.node?.store) return s();
|
|
53
|
+
let i = o.node.store;
|
|
54
|
+
e.url = e.url.withParser(i.url);
|
|
55
|
+
try {
|
|
56
|
+
return await this.runHandler(i, e, t), await this.runHooks("onResponse", e, t), t;
|
|
57
|
+
} catch (d) {
|
|
58
|
+
let c = {
|
|
59
|
+
statusCode: 400,
|
|
60
|
+
message: d instanceof l.ErrorLog ? "AST_ERROR" : "ERROR",
|
|
61
|
+
error: d instanceof l.ErrorLog ? d.message : d
|
|
62
|
+
};
|
|
63
|
+
return t.badRequest(c);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
middleware(e) {
|
|
67
|
+
for (let t of e.controllers) {
|
|
68
|
+
let s = R(e.basePath, t.path);
|
|
69
|
+
this.addEntry(t, s);
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
controller(e) {
|
|
74
|
+
let t = R("/", e.path);
|
|
75
|
+
return this.addEntry(e, t), this;
|
|
76
|
+
}
|
|
77
|
+
use(e, t) {
|
|
78
|
+
let n = e.defineInstance(this)._build(t);
|
|
79
|
+
if (this.plugins[n.name] = n, n.hooks.onRequest && this.onRequestPlugins.push(n), n.hooks.onResponse && this.onResponsePlugins.push(n), n.hooks.beforeInitialize && this.beforeInitializePlugins.push(n), n.hooks.afterInitialize && this.afterInitializePlugins.push(n), n._extensionFn) {
|
|
80
|
+
let o = n._extensionFn(this, n.context);
|
|
81
|
+
Object.assign(this, o);
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
router(e) {
|
|
86
|
+
return this.controller(new a.Router(e)), this;
|
|
87
|
+
}
|
|
88
|
+
method(e) {
|
|
89
|
+
return this.controller(new a.Method(e)), this;
|
|
90
|
+
}
|
|
91
|
+
async resolvePluginContexts() {
|
|
92
|
+
for (let e in this.plugins) {
|
|
93
|
+
let t = this.plugins[e];
|
|
94
|
+
if (t && t.resolvers)
|
|
95
|
+
for (let s of t.resolvers) {
|
|
96
|
+
let n = await s(t.context, t.context);
|
|
97
|
+
Object.assign(t.context, n);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async listen(...e) {
|
|
102
|
+
await this.resolvePluginContexts(), await this.runHooks("beforeInitialize"), await this.driver.listen(...e), await this.runHooks("afterInitialize");
|
|
103
|
+
}
|
|
104
|
+
addEntry(e, t) {
|
|
105
|
+
let s = e instanceof a.Method ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new l.Analyze(t) };
|
|
106
|
+
for (let o of s)
|
|
107
|
+
this.reminist.add(o, t, n);
|
|
108
|
+
}
|
|
109
|
+
async runHooks(e, t, s) {
|
|
110
|
+
let n = this[`${e}Plugins`];
|
|
111
|
+
for (let o of n)
|
|
112
|
+
switch (e) {
|
|
113
|
+
case "beforeInitialize":
|
|
114
|
+
case "afterInitialize":
|
|
115
|
+
{
|
|
116
|
+
let i = o.hooks[e];
|
|
117
|
+
if (i)
|
|
118
|
+
for (let d of i)
|
|
119
|
+
await d(this, o.context);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case "onRequest":
|
|
123
|
+
case "onResponse": {
|
|
124
|
+
if (!t || !s) return;
|
|
125
|
+
for (let i of o.hooks.onRequest)
|
|
126
|
+
await i({ request: t, response: s, context: o.context });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async runHandler({ route: e }, t, s) {
|
|
131
|
+
let n = t.getMethod().toLowerCase(), o = e instanceof a.Method ? e.handler : e.methods[n], i = e instanceof a.Method ? e.schema : e.schema?.[n];
|
|
132
|
+
if (!o) return null;
|
|
133
|
+
if (i) {
|
|
134
|
+
let u = i.safeParse(t.getBody());
|
|
135
|
+
if (!u.success)
|
|
136
|
+
return s.validationError({
|
|
137
|
+
statusCode: 422,
|
|
138
|
+
message: "VALIDATION_ERROR",
|
|
139
|
+
error: u.error
|
|
140
|
+
});
|
|
141
|
+
s.send(u.data);
|
|
142
|
+
}
|
|
143
|
+
let d = Object.values(this.plugins).reduce((u, g) => ({ ...u, ...g.context }), {}), c = {
|
|
144
|
+
instance: this,
|
|
145
|
+
request: t,
|
|
146
|
+
response: s,
|
|
147
|
+
url: t.url,
|
|
148
|
+
schema: t.getBody(),
|
|
149
|
+
middleware: {},
|
|
150
|
+
plugins: d
|
|
151
|
+
};
|
|
152
|
+
return o(c);
|
|
153
|
+
}
|
|
154
|
+
}, v = h;
|
|
155
|
+
0 && (module.exports = {
|
|
156
|
+
AsterFlow,
|
|
157
|
+
AsterFlowInstance,
|
|
158
|
+
joinPaths
|
|
159
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// core/src/controllers/Asterflow.ts
|
|
2
|
+
import { adapters as m } from "@asterflow/adapter";
|
|
3
|
+
import { AsterResponse as p } from "@asterflow/response";
|
|
4
|
+
import {
|
|
5
|
+
Method as d,
|
|
6
|
+
MethodType as x,
|
|
7
|
+
Router as g
|
|
8
|
+
} from "@asterflow/router";
|
|
9
|
+
import { Analyze as f, ErrorLog as c } from "@asterflow/url-parser";
|
|
10
|
+
import { Reminist as P } from "reminist";
|
|
11
|
+
// core/src/utils/parser.ts
|
|
12
|
+
function l(h, e) {
|
|
13
|
+
return `${h}${e}`.replace(/\/{2,}/g, "/");
|
|
14
|
+
}
|
|
15
|
+
// core/src/controllers/Asterflow.ts
|
|
16
|
+
var R = class {
|
|
17
|
+
driver;
|
|
18
|
+
reminist = new P({ keys: Object.keys(x) });
|
|
19
|
+
middlewares = [];
|
|
20
|
+
plugins = {};
|
|
21
|
+
onRequestPlugins = [];
|
|
22
|
+
onResponsePlugins = [];
|
|
23
|
+
beforeInitializePlugins = [];
|
|
24
|
+
afterInitializePlugins = [];
|
|
25
|
+
constructor(e) {
|
|
26
|
+
this.driver = e?.driver ?? m.node, this.driver.onRequest = this.handleRequest.bind(this);
|
|
27
|
+
}
|
|
28
|
+
async handleRequest(e, t) {
|
|
29
|
+
t = t ?? new p(), await this.runHooks("onRequest", e, t);
|
|
30
|
+
let s = () => t.notFound({
|
|
31
|
+
statusCode: 404,
|
|
32
|
+
code: "NOT_FOUND",
|
|
33
|
+
message: `Unable to find route: ${e.getPathname()}`
|
|
34
|
+
}), n = e.getMethod().toLowerCase();
|
|
35
|
+
if (!n) return s();
|
|
36
|
+
let o = this.reminist.find(n, e.url.getPathname());
|
|
37
|
+
if (!o?.node?.store) return s();
|
|
38
|
+
let r = o.node.store;
|
|
39
|
+
e.url = e.url.withParser(r.url);
|
|
40
|
+
try {
|
|
41
|
+
return await this.runHandler(r, e, t), await this.runHooks("onResponse", e, t), t;
|
|
42
|
+
} catch (i) {
|
|
43
|
+
let u = {
|
|
44
|
+
statusCode: 400,
|
|
45
|
+
message: i instanceof c ? "AST_ERROR" : "ERROR",
|
|
46
|
+
error: i instanceof c ? i.message : i
|
|
47
|
+
};
|
|
48
|
+
return t.badRequest(u);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
middleware(e) {
|
|
52
|
+
for (let t of e.controllers) {
|
|
53
|
+
let s = l(e.basePath, t.path);
|
|
54
|
+
this.addEntry(t, s);
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
controller(e) {
|
|
59
|
+
let t = l("/", e.path);
|
|
60
|
+
return this.addEntry(e, t), this;
|
|
61
|
+
}
|
|
62
|
+
use(e, t) {
|
|
63
|
+
let n = e.defineInstance(this)._build(t);
|
|
64
|
+
if (this.plugins[n.name] = n, n.hooks.onRequest && this.onRequestPlugins.push(n), n.hooks.onResponse && this.onResponsePlugins.push(n), n.hooks.beforeInitialize && this.beforeInitializePlugins.push(n), n.hooks.afterInitialize && this.afterInitializePlugins.push(n), n._extensionFn) {
|
|
65
|
+
let o = n._extensionFn(this, n.context);
|
|
66
|
+
Object.assign(this, o);
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
router(e) {
|
|
71
|
+
return this.controller(new g(e)), this;
|
|
72
|
+
}
|
|
73
|
+
method(e) {
|
|
74
|
+
return this.controller(new d(e)), this;
|
|
75
|
+
}
|
|
76
|
+
async resolvePluginContexts() {
|
|
77
|
+
for (let e in this.plugins) {
|
|
78
|
+
let t = this.plugins[e];
|
|
79
|
+
if (t && t.resolvers)
|
|
80
|
+
for (let s of t.resolvers) {
|
|
81
|
+
let n = await s(t.context, t.context);
|
|
82
|
+
Object.assign(t.context, n);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async listen(...e) {
|
|
87
|
+
await this.resolvePluginContexts(), await this.runHooks("beforeInitialize"), await this.driver.listen(...e), await this.runHooks("afterInitialize");
|
|
88
|
+
}
|
|
89
|
+
addEntry(e, t) {
|
|
90
|
+
let s = e instanceof d ? [e.method] : Object.keys(e.methods), n = { path: t, route: e, methods: s, url: new f(t) };
|
|
91
|
+
for (let o of s)
|
|
92
|
+
this.reminist.add(o, t, n);
|
|
93
|
+
}
|
|
94
|
+
async runHooks(e, t, s) {
|
|
95
|
+
let n = this[`${e}Plugins`];
|
|
96
|
+
for (let o of n)
|
|
97
|
+
switch (e) {
|
|
98
|
+
case "beforeInitialize":
|
|
99
|
+
case "afterInitialize":
|
|
100
|
+
{
|
|
101
|
+
let r = o.hooks[e];
|
|
102
|
+
if (r)
|
|
103
|
+
for (let i of r)
|
|
104
|
+
await i(this, o.context);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case "onRequest":
|
|
108
|
+
case "onResponse": {
|
|
109
|
+
if (!t || !s) return;
|
|
110
|
+
for (let r of o.hooks.onRequest)
|
|
111
|
+
await r({ request: t, response: s, context: o.context });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async runHandler({ route: e }, t, s) {
|
|
116
|
+
let n = t.getMethod().toLowerCase(), o = e instanceof d ? e.handler : e.methods[n], r = e instanceof d ? e.schema : e.schema?.[n];
|
|
117
|
+
if (!o) return null;
|
|
118
|
+
if (r) {
|
|
119
|
+
let a = r.safeParse(t.getBody());
|
|
120
|
+
if (!a.success)
|
|
121
|
+
return s.validationError({
|
|
122
|
+
statusCode: 422,
|
|
123
|
+
message: "VALIDATION_ERROR",
|
|
124
|
+
error: a.error
|
|
125
|
+
});
|
|
126
|
+
s.send(a.data);
|
|
127
|
+
}
|
|
128
|
+
let i = Object.values(this.plugins).reduce((a, y) => ({ ...a, ...y.context }), {}), u = {
|
|
129
|
+
instance: this,
|
|
130
|
+
request: t,
|
|
131
|
+
response: s,
|
|
132
|
+
url: t.url,
|
|
133
|
+
schema: t.getBody(),
|
|
134
|
+
middleware: {},
|
|
135
|
+
plugins: i
|
|
136
|
+
};
|
|
137
|
+
return o(u);
|
|
138
|
+
}
|
|
139
|
+
}, O = R;
|
|
140
|
+
export {
|
|
141
|
+
O as AsterFlow,
|
|
142
|
+
R as AsterFlowInstance,
|
|
143
|
+
l as joinPaths
|
|
144
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Runtime, type Adapter, type AnyAdapter } from '@asterflow/adapter';
|
|
2
|
+
import type { AnyPlugins, InferConfigArgument, InferPluginExtension, Plugin, ResolvedPlugin } from '@asterflow/plugin';
|
|
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 { Reminist } from 'reminist';
|
|
6
|
+
import type { AsterFlowOptions } from '../types/asterflow';
|
|
7
|
+
import type { ExtractPaths, InferPath, NormalizePath } from '../types/paths';
|
|
8
|
+
import type { AnyReminist, InferReministContext, InferReministPath } from '../types/reminist';
|
|
9
|
+
import type { BuildRouteContext, BuildRoutesContext, RouteEntry } from '../types/routes';
|
|
10
|
+
import type { AnyRecord } from '../types/utils';
|
|
11
|
+
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 = {}> {
|
|
12
|
+
readonly driver: Drive;
|
|
13
|
+
readonly reminist: Routers;
|
|
14
|
+
readonly middlewares: Middlewares;
|
|
15
|
+
plugins: Plugins;
|
|
16
|
+
private readonly onRequestPlugins;
|
|
17
|
+
private readonly onResponsePlugins;
|
|
18
|
+
private readonly beforeInitializePlugins;
|
|
19
|
+
private readonly afterInitializePlugins;
|
|
20
|
+
constructor(options?: AsterFlowOptions<Drive>);
|
|
21
|
+
/**
|
|
22
|
+
* Handles incoming requests, executing `onRequest` and `onResponse` plugin hooks.
|
|
23
|
+
* Finds the matching route and executes its handler. Manages errors and "not found" responses.
|
|
24
|
+
*/
|
|
25
|
+
private handleRequest;
|
|
26
|
+
/**
|
|
27
|
+
* Adds a group of controllers with a common `basePath`.
|
|
28
|
+
* Each route within the provided controllers will be prefixed with the `basePath`.
|
|
29
|
+
*/
|
|
30
|
+
middleware<BasePath extends string, const Routes extends readonly AnyRouter[]>(options: {
|
|
31
|
+
basePath: BasePath;
|
|
32
|
+
controllers: Routes;
|
|
33
|
+
}): 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
|
+
/**
|
|
35
|
+
* Adds a single controller to AsterFlow.
|
|
36
|
+
* The controller's path is normalized to be relative to the root.
|
|
37
|
+
*/
|
|
38
|
+
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
|
+
/**
|
|
40
|
+
* Registers a plugin and its configuration with the AsterFlow instance.
|
|
41
|
+
* Applies any instance extensions defined by the plugin.
|
|
42
|
+
*/
|
|
43
|
+
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
|
+
/**
|
|
45
|
+
* Creates and adds a new router to the AsterFlow instance.
|
|
46
|
+
* A router can contain multiple method handlers for different HTTP verbs.
|
|
47
|
+
*/
|
|
48
|
+
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 {
|
|
49
|
+
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
50
|
+
} = {
|
|
51
|
+
[Method in MethodKeys]?: RouteHandler<Path, Responder, Method, Schema, Middlewares, Context>;
|
|
52
|
+
}, 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>;
|
|
53
|
+
/**
|
|
54
|
+
* Creates and adds a new method handler (route) to the AsterFlow instance.
|
|
55
|
+
* Defines a specific route for an HTTP method (GET, POST, etc.).
|
|
56
|
+
*/
|
|
57
|
+
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
|
+
/**
|
|
59
|
+
* Itera sobre todos os plugins registrados e executa seus resolvers
|
|
60
|
+
* de forma assÃncrona, construindo o contexto de cada um.
|
|
61
|
+
*/
|
|
62
|
+
private resolvePluginContexts;
|
|
63
|
+
/**
|
|
64
|
+
* Starts the application server, triggering `beforeInitialize` and `afterInitialize` lifecycle hooks.
|
|
65
|
+
*/
|
|
66
|
+
listen(...args: Parameters<Drive['listen']>): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Adds a route entry to Reminist, associating it with specific HTTP methods.
|
|
69
|
+
* Normalizes the route path and registers it for each supported method.
|
|
70
|
+
*/
|
|
71
|
+
private addEntry;
|
|
72
|
+
/**
|
|
73
|
+
* Executes the hook handlers for a specific hook name.
|
|
74
|
+
*/
|
|
75
|
+
private runHooks;
|
|
76
|
+
/**
|
|
77
|
+
* Executes a route handler, processing the request and response.
|
|
78
|
+
* Performs schema validation, if present, and invokes the route handler.
|
|
79
|
+
*/
|
|
80
|
+
private runHandler;
|
|
81
|
+
}
|
|
82
|
+
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;
|
|
83
|
+
export declare const AsterFlow: {
|
|
84
|
+
new <Drive extends AnyAdapter = Adapter<Runtime.Node>>(options?: AsterFlowOptions<Drive>): AsterFlow<Drive, AnyReminist, {}, [], {}>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Represents a generic AsterFlow instance, with all its types defined as `any`.
|
|
88
|
+
* This allows flexibility when referencing AsterFlow without specifying all its type parameters.
|
|
89
|
+
*/
|
|
90
|
+
export type AnyAsterflow = AsterFlowInstance<AnyAdapter, AnyReminist, AnyPlugins, AnyMiddlewares, AnyRecord>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './controllers/Asterflow';
|
|
2
|
+
export * from './types/asterflow';
|
|
3
|
+
export * from './types/paths';
|
|
4
|
+
export * from './types/plugin';
|
|
5
|
+
export * from './types/reminist';
|
|
6
|
+
export * from './types/routes';
|
|
7
|
+
export * from './types/utils';
|
|
8
|
+
export * from './utils/parser';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnyAdapter } from '@asterflow/adapter';
|
|
2
|
+
import type { AnyAsterflow, AsterFlow } from '../controllers/Asterflow';
|
|
3
|
+
export type ExtendedAsterflow<AF extends AnyAsterflow> = AF extends AsterFlow<any, any, any, any, infer E> ? AF & E : AF;
|
|
4
|
+
/**
|
|
5
|
+
* Defines the options for initializing an AsterFlow instance.
|
|
6
|
+
*/
|
|
7
|
+
export type AsterFlowOptions<Drive extends AnyAdapter> = {
|
|
8
|
+
driver?: Drive;
|
|
9
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AnyRouter, Method, Router } from '@asterflow/router';
|
|
2
|
+
/**
|
|
3
|
+
* Removes the fragment identifier from a URL path.
|
|
4
|
+
* @example
|
|
5
|
+
* // '/page#about' -> '/page'
|
|
6
|
+
*/
|
|
7
|
+
type RemoveFragment<Path extends string> = Path extends `${infer CleanPath}#${string}` ? CleanPath : Path;
|
|
8
|
+
/**
|
|
9
|
+
* Removes the query string from a URL path.
|
|
10
|
+
* @example
|
|
11
|
+
* // '/users?data=1' -> '/users'
|
|
12
|
+
*/
|
|
13
|
+
type RemoveQueryString<Path extends string> = Path extends `${infer CleanPath}?${string}` ? CleanPath : Path;
|
|
14
|
+
/**
|
|
15
|
+
* Removes type definitions from path segments.
|
|
16
|
+
* Recursively processes the path to clean parts like '=number'.
|
|
17
|
+
* @example
|
|
18
|
+
* // '/users/:id=number' -> '/users/:id'
|
|
19
|
+
*/
|
|
20
|
+
type SanitizeSegments<Path extends string> = Path extends `/${infer Rest}` ? `/${SanitizeSegments<Rest>}` : Path extends `${infer Segment}/${infer Rest}` ? `${(Segment extends `${infer Name}=${string}` ? Name : Segment)}/${SanitizeSegments<Rest>}` : Path extends `${infer Name}=${string}` ? Name : Path;
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes a URL path by performing several cleaning operations:
|
|
23
|
+
* 1. Replaces double slashes ('//') with a single slash.
|
|
24
|
+
* 2. Removes the URL fragment (e.g., '#about').
|
|
25
|
+
* 3. Removes the query string (e.g., '?data=1').
|
|
26
|
+
* 4. Removes type definitions from path segments (e.g., ':id=number' -> ':id').
|
|
27
|
+
* @example
|
|
28
|
+
* // '/users//:id=number?data=1#profile' -> '/users/:id'
|
|
29
|
+
*/
|
|
30
|
+
export type NormalizePath<Path extends string> = Path extends `${infer Head}//${infer Tail}` ? NormalizePath<`${Head}/${Tail}`> : SanitizeSegments<RemoveQueryString<RemoveFragment<Path>>>;
|
|
31
|
+
/**
|
|
32
|
+
* Combines a base path with a relative path and normalizes the result.
|
|
33
|
+
*/
|
|
34
|
+
export type CombinePaths<Base extends string, Path extends string> = NormalizePath<`${Base}${Path}`>;
|
|
35
|
+
/**
|
|
36
|
+
* Infers the path from a `Router` or `Method` type.
|
|
37
|
+
* This normalizes the path and combines it with the root.
|
|
38
|
+
*/
|
|
39
|
+
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;
|
|
40
|
+
/**
|
|
41
|
+
* Extracts and normalizes the paths from an array of routers (`AnyRouter[]`), combining them with a base path.
|
|
42
|
+
*/
|
|
43
|
+
export type ExtractPaths<Base extends string, Routes extends readonly AnyRouter[]> = {
|
|
44
|
+
[K in keyof Routes]: Routes[K] extends infer R extends AnyRouter ? CombinePaths<Base, InferPath<R>> : never;
|
|
45
|
+
};
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UnionToIntersection } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Combines the contexts of multiple plugins into a single intersection type.
|
|
4
|
+
* This allows safe access to all properties of the merged plugin contexts.
|
|
5
|
+
*/
|
|
6
|
+
export type MergedPluginContexts<Plugins extends Record<string, {
|
|
7
|
+
context: any;
|
|
8
|
+
}>> = UnionToIntersection<Plugins[keyof Plugins]['context']>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AnyRouter, MethodKeys } from '@asterflow/router';
|
|
2
|
+
import type { Reminist } from 'reminist';
|
|
3
|
+
import type { RouteEntry } from './routes';
|
|
4
|
+
/**
|
|
5
|
+
* 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.
|
|
7
|
+
*/
|
|
8
|
+
export type AnyReminist = Reminist<readonly string[], Record<string, RouteEntry<string, AnyRouter>>, MethodKeys[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Infers the paths from a Reminist instance.
|
|
11
|
+
*/
|
|
12
|
+
export type InferReministPath<T> = T extends Reminist<infer P, any, any> ? P : never;
|
|
13
|
+
/**
|
|
14
|
+
* Infers the context from a Reminist instance.
|
|
15
|
+
*/
|
|
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
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnyRouter, MethodKeys } from '@asterflow/router';
|
|
2
|
+
import type { CombinePaths, InferPath, NormalizePath } from './paths';
|
|
3
|
+
import type { UnionToIntersection } from './utils';
|
|
4
|
+
import type { Analyze } from '@asterflow/url-parser';
|
|
5
|
+
/**
|
|
6
|
+
* Builds the context for a single route, inferring the path and associating the route entry.
|
|
7
|
+
*/
|
|
8
|
+
export type BuildRouteContext<Route extends AnyRouter, Path extends string = NormalizePath<InferPath<Route>>> = {
|
|
9
|
+
readonly [K in Path]: RouteEntry<Path, Route>;
|
|
10
|
+
} & Record<string, RouteEntry<string, AnyRouter>>;
|
|
11
|
+
/**
|
|
12
|
+
* Builds the context for multiple routes, combining base paths and inferring route entries.
|
|
13
|
+
*/
|
|
14
|
+
export type BuildRoutesContext<Base extends string, Routes extends readonly AnyRouter[]> = UnionToIntersection<{
|
|
15
|
+
[K in keyof Routes]: Routes[K] extends AnyRouter ? {
|
|
16
|
+
readonly [P in CombinePaths<Base, InferPath<Routes[K]>>]: RouteEntry<CombinePaths<Base, InferPath<Routes[K]>>, Routes[K]>;
|
|
17
|
+
} : never;
|
|
18
|
+
}[number] & {}> & Record<string, RouteEntry<string, AnyRouter>>;
|
|
19
|
+
/**
|
|
20
|
+
* Defines the specific typing preserved for each route entry in Reminist.
|
|
21
|
+
*/
|
|
22
|
+
export type RouteEntry<Path extends string, Route extends AnyRouter> = {
|
|
23
|
+
readonly path: Path;
|
|
24
|
+
readonly route: Route;
|
|
25
|
+
readonly methods: readonly MethodKeys[];
|
|
26
|
+
readonly url: Analyze<string>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a union of types into an intersection of types.
|
|
3
|
+
* Useful for combining properties from multiple types into a single coalesced type.
|
|
4
|
+
*/
|
|
5
|
+
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
6
|
+
export type AnyRecord = Record<string, any>;
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "asterflow",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/cjs/index.cjs",
|
|
5
|
+
"module": "dist/mjs/index.js",
|
|
6
|
+
"types": "dist/types/index.d.ts",
|
|
7
|
+
"typings": "dist/types/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "Ashu11-A",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/AsterFlow/AsterFlow.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/AsterFlow/AsterFlow/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/AsterFlow/AsterFlow",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"import": "./dist/mjs/index.js",
|
|
23
|
+
"require": "./dist/cjs/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "bun run --inspect=ws://localhost:6499 src/index.ts",
|
|
31
|
+
"exemple": "bun run --inspect-brk=ws://localhost:6499 src/exemple.ts"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"zod": "^3.25.63",
|
|
35
|
+
"typescript": "^5.8.3"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@asterflow/adapter": "1.0.9",
|
|
39
|
+
"@asterflow/plugin": "1.0.5",
|
|
40
|
+
"@asterflow/response": "1.0.6",
|
|
41
|
+
"@asterflow/router": "1.0.9",
|
|
42
|
+
"@asterflow/url-parser": "^2.0.1",
|
|
43
|
+
"reminist": "^1.0.5"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"esnext"
|
|
5
|
+
],
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedIndexedAccess": true,
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": false,
|
|
21
|
+
"noPropertyAccessFromIndexSignature": false
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"dist"
|
|
25
|
+
]
|
|
26
|
+
}
|