okgoes-core 0.0.0-stage → 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 +105 -2
- package/dist/application.d.ts +37 -0
- package/dist/application.js +279 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +57 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +6 -0
- package/dist/controller.d.ts +17 -0
- package/dist/controller.js +43 -0
- package/dist/core/compose.d.ts +7 -0
- package/dist/core/compose.js +36 -0
- package/dist/core/context.d.ts +74 -0
- package/dist/core/context.js +200 -0
- package/dist/core/cookies.d.ts +33 -0
- package/dist/core/cookies.js +169 -0
- package/dist/core/error.d.ts +13 -0
- package/dist/core/error.js +47 -0
- package/dist/core/koa.d.ts +34 -0
- package/dist/core/koa.js +168 -0
- package/dist/core/request.d.ts +43 -0
- package/dist/core/request.js +213 -0
- package/dist/core/response.d.ts +40 -0
- package/dist/core/response.js +224 -0
- package/dist/core/statuses.d.ts +7 -0
- package/dist/core/statuses.js +88 -0
- package/dist/core/utils.d.ts +18 -0
- package/dist/core/utils.js +192 -0
- package/dist/decorators.d.ts +10 -0
- package/dist/decorators.js +53 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +62 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +82 -0
- package/dist/middleware/body.d.ts +23 -0
- package/dist/middleware/body.js +231 -0
- package/dist/middleware/cors.d.ts +14 -0
- package/dist/middleware/cors.js +57 -0
- package/dist/middleware/session.d.ts +19 -0
- package/dist/middleware/session.js +62 -0
- package/dist/middleware/static.d.ts +14 -0
- package/dist/middleware/static.js +119 -0
- package/dist/middleware/views.d.ts +10 -0
- package/dist/middleware/views.js +31 -0
- package/dist/model.d.ts +11 -0
- package/dist/model.js +31 -0
- package/dist/request.d.ts +22 -0
- package/dist/request.js +52 -0
- package/dist/response.d.ts +23 -0
- package/dist/response.js +70 -0
- package/dist/route-resolver.d.ts +24 -0
- package/dist/route-resolver.js +72 -0
- package/dist/router.d.ts +20 -0
- package/dist/router.js +68 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +2 -0
- package/dist/view/engine.d.ts +19 -0
- package/dist/view/engine.js +137 -0
- package/package.json +29 -3
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# okgoes-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
零远端依赖的 Node.js MVC 框架:基于 `node:http` 自研 Koa 风格内核与中间件,内置隐性路由(模块/控制器/方法自动映射)、可配置路由模式、插件系统、最小 EJS 兼容模板引擎。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **零远端依赖**:`dependencies` 为空,仅使用 Node.js 内置模块(http/https/crypto/stream/fs/path/querystring)
|
|
8
|
+
- **隐性路由(约定路由)**:`/模块/控制器/方法` 自动映射到 `src/controller/{module}/{controller}.controller.ts` 的对应方法,无需注册
|
|
9
|
+
- **路由模式可配置**:`routing.mode = auto | manual | both`
|
|
10
|
+
- **服务端渲染**:`render()` 无参时按约定定位模板 `views/{module}/{controller}/{action}.ejs`
|
|
11
|
+
- **插件系统**:`OkgoesPlugin.install(app, options)` 可注册中间件 / service
|
|
12
|
+
- **控制器返回值自动响应**:返回对象转 JSON,返回字符串输出 HTML
|
|
13
|
+
- 内置 session / cors / 静态资源 / body 解析 / 访问日志,均为自研实现,可配置开关
|
|
14
|
+
|
|
15
|
+
## 快速开始
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install okgoes-core okgoes-cli -D
|
|
19
|
+
npx okgoes create my-app
|
|
20
|
+
cd my-app && npm install && npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 目录约定
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/
|
|
27
|
+
config/index.ts # 应用配置
|
|
28
|
+
controller/index.controller.ts # GET / -> index()
|
|
29
|
+
controller/admin/post.controller.ts # GET /admin/post -> index()
|
|
30
|
+
model/user.model.ts # 继承 Model
|
|
31
|
+
middleware/auth.ts # 自定义中间件
|
|
32
|
+
plugin/metrics.ts # 自定义插件
|
|
33
|
+
routes.ts # 显式路由(manual/both 模式)
|
|
34
|
+
views/index/index.ejs # 约定模板
|
|
35
|
+
public/ # 静态资源
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 路由配置
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// src/config/index.ts
|
|
42
|
+
const config: AppConfig = {
|
|
43
|
+
routing: {
|
|
44
|
+
mode: "auto", // auto: 隐性路由 | manual: 显式路由 | both
|
|
45
|
+
defaultController: "index",
|
|
46
|
+
defaultAction: "index",
|
|
47
|
+
modules: ["admin"], // 模块白名单,URL 首段命中才解析为模块
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- 约定路由:`/admin/user/read/1` → `admin/user.controller.ts` 的 `read()`,附加段通过 `request.getRouteArgs()` 获取
|
|
53
|
+
- 显式路由:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// src/routes.ts
|
|
57
|
+
export default function register(router: Router) {
|
|
58
|
+
router.get("/api/users/:id", UserController, "read");
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
显式路由 `:id` 自动注入 `request.params.id`;`both` 模式显式优先、隐性兜底。
|
|
63
|
+
|
|
64
|
+
## HTTP 方法限制
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { Controller, Get, Post } from "okgoes-core";
|
|
68
|
+
|
|
69
|
+
export default class IndexController extends Controller {
|
|
70
|
+
@Get()
|
|
71
|
+
async info() {
|
|
72
|
+
return { ok: true }; // 仅 GET,其他方法返回 405;返回对象自动 JSON
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Post()
|
|
76
|
+
async create() {
|
|
77
|
+
return this.service("repository").save(this.request.getBody());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 插件
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
export default function metricsPlugin(): OkgoesPlugin {
|
|
86
|
+
return {
|
|
87
|
+
name: "metrics",
|
|
88
|
+
install(app, options) {
|
|
89
|
+
let count = 0;
|
|
90
|
+
app.service("metrics", { count: () => count }); // controller 内 this.service("metrics")
|
|
91
|
+
app.use(async (ctx, next) => { count++; await next(); });
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
app.usePlugin(metricsPlugin(), { prefix: "[demo]" });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 渲染
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
async index() {
|
|
103
|
+
await this.render({ title: "hi" }); // 约定定位 views/index/index.ejs
|
|
104
|
+
await this.render("admin/list", { list: [] }); // 指定模板
|
|
105
|
+
}
|
|
106
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Koa from "./core/koa";
|
|
2
|
+
import Context from "./core/context";
|
|
3
|
+
import { AppConfig, Middleware, OkgoesPlugin, ResolvedRoute } from "./types";
|
|
4
|
+
import { ResolvedConfig } from "./config";
|
|
5
|
+
import Router from "./router";
|
|
6
|
+
export interface OkgoesLogger {
|
|
7
|
+
info(...args: any[]): void;
|
|
8
|
+
warn(...args: any[]): void;
|
|
9
|
+
error(...args: any[]): void;
|
|
10
|
+
}
|
|
11
|
+
export default class Application {
|
|
12
|
+
readonly baseDir: string;
|
|
13
|
+
readonly koa: Koa;
|
|
14
|
+
readonly router: Router;
|
|
15
|
+
config: ResolvedConfig;
|
|
16
|
+
logger: OkgoesLogger;
|
|
17
|
+
private readonly services;
|
|
18
|
+
private readonly customMiddlewares;
|
|
19
|
+
private readonly pluginRegistrations;
|
|
20
|
+
private isStarted;
|
|
21
|
+
constructor(baseDir: string, config?: AppConfig);
|
|
22
|
+
use(middleware: Middleware): this;
|
|
23
|
+
usePlugin(plugin: OkgoesPlugin, options?: any): this;
|
|
24
|
+
service(name: string, instance: any): this;
|
|
25
|
+
getService<T = any>(name: string): T;
|
|
26
|
+
hasService(name: string): boolean;
|
|
27
|
+
get controllerBaseDir(): string;
|
|
28
|
+
start(): Promise<Koa>;
|
|
29
|
+
private buildCoreMiddlewares;
|
|
30
|
+
private createErrorHandlerMiddleware;
|
|
31
|
+
private createAccessLogMiddleware;
|
|
32
|
+
private mountRouterLayer;
|
|
33
|
+
private resolveSourceFile;
|
|
34
|
+
private createConventionRouteMiddleware;
|
|
35
|
+
/** 实例化控制器、注入请求响应并执行 action,返回值自动写入响应 */
|
|
36
|
+
dispatch(controllerClass: new () => any, actionName: string, ctx: Context, resolvedRoute?: ResolvedRoute | null, routeParams?: Record<string, string>): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const koa_1 = __importDefault(require("./core/koa"));
|
|
42
|
+
const cors_1 = __importDefault(require("./middleware/cors"));
|
|
43
|
+
const session_1 = __importDefault(require("./middleware/session"));
|
|
44
|
+
const body_1 = require("./middleware/body");
|
|
45
|
+
const static_1 = __importDefault(require("./middleware/static"));
|
|
46
|
+
const views_1 = __importDefault(require("./middleware/views"));
|
|
47
|
+
const config_1 = require("./config");
|
|
48
|
+
const route_resolver_1 = __importDefault(require("./route-resolver"));
|
|
49
|
+
const router_1 = __importDefault(require("./router"));
|
|
50
|
+
const request_1 = __importDefault(require("./request"));
|
|
51
|
+
const response_1 = __importDefault(require("./response"));
|
|
52
|
+
const loader_1 = require("./loader");
|
|
53
|
+
const decorators_1 = require("./decorators");
|
|
54
|
+
const constants_1 = require("./constants");
|
|
55
|
+
class Application {
|
|
56
|
+
constructor(baseDir, config = {}) {
|
|
57
|
+
this.logger = console;
|
|
58
|
+
this.services = new Map();
|
|
59
|
+
this.customMiddlewares = [];
|
|
60
|
+
this.pluginRegistrations = [];
|
|
61
|
+
this.isStarted = false;
|
|
62
|
+
this.baseDir = baseDir;
|
|
63
|
+
this.config = (0, config_1.mergeConfig)(config);
|
|
64
|
+
this.koa = new koa_1.default();
|
|
65
|
+
this.router = new router_1.default(this);
|
|
66
|
+
this.koa[constants_1.FRAMEWORK_INSTANCE_KEY] = this;
|
|
67
|
+
}
|
|
68
|
+
use(middleware) {
|
|
69
|
+
this.customMiddlewares.push(middleware);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
usePlugin(plugin, options) {
|
|
73
|
+
if (!plugin || !plugin.name || typeof plugin.install !== "function") {
|
|
74
|
+
throw new Error("plugin must have name and install(app, options)");
|
|
75
|
+
}
|
|
76
|
+
this.pluginRegistrations.push({ plugin, options });
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
service(name, instance) {
|
|
80
|
+
this.services.set(name, instance);
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
getService(name) {
|
|
84
|
+
if (!this.services.has(name)) {
|
|
85
|
+
throw new Error(`service [${name}] is not registered`);
|
|
86
|
+
}
|
|
87
|
+
return this.services.get(name);
|
|
88
|
+
}
|
|
89
|
+
hasService(name) {
|
|
90
|
+
return this.services.has(name);
|
|
91
|
+
}
|
|
92
|
+
get controllerBaseDir() {
|
|
93
|
+
return path.join(this.baseDir, this.config.controllerDir);
|
|
94
|
+
}
|
|
95
|
+
async start() {
|
|
96
|
+
if (this.isStarted) {
|
|
97
|
+
throw new Error("application has already started");
|
|
98
|
+
}
|
|
99
|
+
this.isStarted = true;
|
|
100
|
+
for (const middleware of this.buildCoreMiddlewares()) {
|
|
101
|
+
this.koa.use(middleware);
|
|
102
|
+
}
|
|
103
|
+
for (const { plugin, options } of this.pluginRegistrations) {
|
|
104
|
+
await plugin.install(this, options);
|
|
105
|
+
this.logger.info(`[okgoes] plugin "${plugin.name}" loaded`);
|
|
106
|
+
}
|
|
107
|
+
for (const middleware of this.customMiddlewares) {
|
|
108
|
+
this.koa.use(middleware);
|
|
109
|
+
}
|
|
110
|
+
this.mountRouterLayer();
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const { port, host } = this.config;
|
|
113
|
+
this.koa.listen(port, host, () => {
|
|
114
|
+
this.logger.info(`[okgoes] server started at http://localhost:${port} (routing: ${this.config.routing.mode})`);
|
|
115
|
+
resolve(this.koa);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
buildCoreMiddlewares() {
|
|
120
|
+
const koaApp = this.koa;
|
|
121
|
+
const { session: sessionConfig, static: staticConfig } = this.config;
|
|
122
|
+
const middlewares = [];
|
|
123
|
+
koaApp.keys = [this.config.session.secret];
|
|
124
|
+
koaApp.on("error", (err, ctx) => {
|
|
125
|
+
this.logger.error("[okgoes] server error", (ctx && ctx.path) || "", err);
|
|
126
|
+
});
|
|
127
|
+
middlewares.push(this.createErrorHandlerMiddleware());
|
|
128
|
+
if (this.config.log.enable) {
|
|
129
|
+
middlewares.push(this.createAccessLogMiddleware());
|
|
130
|
+
}
|
|
131
|
+
if (this.config.cors.enable) {
|
|
132
|
+
middlewares.push((0, cors_1.default)({ origin: "*", allowMethods: "*", credentials: true }));
|
|
133
|
+
}
|
|
134
|
+
if (sessionConfig.enable) {
|
|
135
|
+
middlewares.push((0, session_1.default)({
|
|
136
|
+
key: sessionConfig.key,
|
|
137
|
+
maxAge: sessionConfig.maxAge,
|
|
138
|
+
signed: true,
|
|
139
|
+
renew: true,
|
|
140
|
+
}, koaApp));
|
|
141
|
+
}
|
|
142
|
+
middlewares.push((0, body_1.koaBody)({
|
|
143
|
+
jsonLimit: "2mb",
|
|
144
|
+
formLimit: "2mb",
|
|
145
|
+
textLimit: "2mb",
|
|
146
|
+
multipart: true,
|
|
147
|
+
urlencoded: true,
|
|
148
|
+
text: true,
|
|
149
|
+
json: true,
|
|
150
|
+
}));
|
|
151
|
+
const viewDirectory = path.join(this.baseDir, this.config.view.dir);
|
|
152
|
+
if (fs.existsSync(viewDirectory)) {
|
|
153
|
+
koaApp[constants_1.APP_VIEW_EXTENSION_KEY] = this.config.view.extension;
|
|
154
|
+
middlewares.push((0, views_1.default)(viewDirectory, {
|
|
155
|
+
extension: this.config.view.extension,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
if (staticConfig.enable) {
|
|
159
|
+
const publicDirectory = path.join(this.baseDir, staticConfig.dir);
|
|
160
|
+
if (fs.existsSync(publicDirectory)) {
|
|
161
|
+
middlewares.push((0, static_1.default)(publicDirectory, { maxAge: staticConfig.maxAge }));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return middlewares;
|
|
165
|
+
}
|
|
166
|
+
createErrorHandlerMiddleware() {
|
|
167
|
+
return async (ctx, next) => {
|
|
168
|
+
try {
|
|
169
|
+
await next();
|
|
170
|
+
if (ctx.status === 404 && ctx.body === undefined) {
|
|
171
|
+
ctx.status = 404;
|
|
172
|
+
ctx.body = "404 Not Found";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
this.logger.error(`[okgoes] ${ctx.method} ${ctx.path}`, err);
|
|
177
|
+
if (!ctx.headerSent) {
|
|
178
|
+
ctx.status = err.status || 500;
|
|
179
|
+
ctx.body = { code: err.status || 500, message: err.message || "Internal Server Error" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
createAccessLogMiddleware() {
|
|
185
|
+
return async (ctx, next) => {
|
|
186
|
+
const startedAt = Date.now();
|
|
187
|
+
await next();
|
|
188
|
+
const elapsedMs = Date.now() - startedAt;
|
|
189
|
+
ctx.set("X-Response-Time", `${elapsedMs}ms`);
|
|
190
|
+
this.logger.info(`[okgoes] ${ctx.method} ${ctx.originalUrl} ${ctx.status} ${elapsedMs}ms`);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
mountRouterLayer() {
|
|
194
|
+
const mode = this.config.routing.mode;
|
|
195
|
+
if (mode === "manual" || mode === "both") {
|
|
196
|
+
const routesFile = this.resolveSourceFile(path.join(this.baseDir, "src/routes"));
|
|
197
|
+
if (routesFile) {
|
|
198
|
+
const loaded = require(routesFile);
|
|
199
|
+
const registerRoutes = loaded && (loaded.default || loaded);
|
|
200
|
+
if (typeof registerRoutes === "function") {
|
|
201
|
+
registerRoutes(this.router);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.logger.warn(`[okgoes] routing.mode=${mode}, but src/routes.ts was not found`);
|
|
206
|
+
}
|
|
207
|
+
this.koa.use(this.router.createMiddleware());
|
|
208
|
+
}
|
|
209
|
+
if (mode === "auto" || mode === "both") {
|
|
210
|
+
this.koa.use(this.createConventionRouteMiddleware());
|
|
211
|
+
}
|
|
212
|
+
if (mode === "manual") {
|
|
213
|
+
this.koa.use(async (ctx) => {
|
|
214
|
+
ctx.status = 404;
|
|
215
|
+
ctx.body = "404 Not Found";
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
resolveSourceFile(filePathWithoutExtension) {
|
|
220
|
+
for (const extension of [".js", ".ts"]) {
|
|
221
|
+
const candidate = `${filePathWithoutExtension}${extension}`;
|
|
222
|
+
if (fs.existsSync(candidate) && !/\.d\.ts$/.test(candidate)) {
|
|
223
|
+
return candidate;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
createConventionRouteMiddleware() {
|
|
229
|
+
return async (ctx, next) => {
|
|
230
|
+
const resolvedRoute = new route_resolver_1.default(ctx.path, this.config.routing).getResolvedRoute();
|
|
231
|
+
ctx[constants_1.CTX_RESOLVED_ROUTE_KEY] = resolvedRoute;
|
|
232
|
+
const controllerFile = (0, loader_1.resolveControllerFile)(this.controllerBaseDir, resolvedRoute.moduleName, resolvedRoute.controllerName);
|
|
233
|
+
if (!controllerFile) {
|
|
234
|
+
await next();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const controllerClass = (0, loader_1.loadControllerClass)(controllerFile);
|
|
238
|
+
if (!controllerClass) {
|
|
239
|
+
await next();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
await this.dispatch(controllerClass, resolvedRoute.actionName, ctx, resolvedRoute);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** 实例化控制器、注入请求响应并执行 action,返回值自动写入响应 */
|
|
246
|
+
async dispatch(controllerClass, actionName, ctx, resolvedRoute = null, routeParams) {
|
|
247
|
+
const response = new response_1.default(ctx);
|
|
248
|
+
response.setResolvedRoute(resolvedRoute);
|
|
249
|
+
const controller = new controllerClass();
|
|
250
|
+
if (typeof controller[actionName] !== "function") {
|
|
251
|
+
ctx.status = 404;
|
|
252
|
+
ctx.body = "404 action not found";
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const requiredMethod = (0, decorators_1.resolveHttpMethodConstraint)(controllerClass, actionName);
|
|
256
|
+
if (requiredMethod !== "all" && requiredMethod !== ctx.method.toLowerCase()) {
|
|
257
|
+
ctx.status = 405;
|
|
258
|
+
ctx.body = "405 Method Not Allowed";
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const request = new request_1.default(ctx);
|
|
262
|
+
request.setResolvedRoute(resolvedRoute);
|
|
263
|
+
if (routeParams) {
|
|
264
|
+
request.routeParams = routeParams;
|
|
265
|
+
}
|
|
266
|
+
controller.initialize(ctx, request, response, this);
|
|
267
|
+
const returned = controller[actionName]();
|
|
268
|
+
const result = returned instanceof Promise ? await returned : returned;
|
|
269
|
+
if (result !== undefined && ctx.body === undefined && !ctx.headerSent) {
|
|
270
|
+
if (typeof result === "string") {
|
|
271
|
+
response.html(result);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
response.json(result);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
exports.default = Application;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AppConfig, RoutingConfig } from "./types";
|
|
2
|
+
export interface ResolvedConfig {
|
|
3
|
+
port: number;
|
|
4
|
+
host: string;
|
|
5
|
+
controllerDir: string;
|
|
6
|
+
routing: RoutingConfig;
|
|
7
|
+
view: {
|
|
8
|
+
dir: string;
|
|
9
|
+
extension: string;
|
|
10
|
+
};
|
|
11
|
+
session: {
|
|
12
|
+
enable: boolean;
|
|
13
|
+
key: string;
|
|
14
|
+
maxAge: number;
|
|
15
|
+
secret: string;
|
|
16
|
+
};
|
|
17
|
+
cors: {
|
|
18
|
+
enable: boolean;
|
|
19
|
+
};
|
|
20
|
+
static: {
|
|
21
|
+
enable: boolean;
|
|
22
|
+
dir: string;
|
|
23
|
+
maxAge: number;
|
|
24
|
+
};
|
|
25
|
+
log: {
|
|
26
|
+
enable: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare const defaultConfig: ResolvedConfig;
|
|
30
|
+
export declare function mergeConfig(userConfig?: AppConfig): ResolvedConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultConfig = void 0;
|
|
4
|
+
exports.mergeConfig = mergeConfig;
|
|
5
|
+
exports.defaultConfig = {
|
|
6
|
+
port: 3000,
|
|
7
|
+
host: "0.0.0.0",
|
|
8
|
+
controllerDir: "src/controller",
|
|
9
|
+
routing: {
|
|
10
|
+
mode: "auto",
|
|
11
|
+
defaultModule: "default",
|
|
12
|
+
defaultController: "index",
|
|
13
|
+
defaultAction: "index",
|
|
14
|
+
modules: [],
|
|
15
|
+
},
|
|
16
|
+
view: { dir: "views", extension: "ejs" },
|
|
17
|
+
session: { enable: false, key: "okgoes", maxAge: 3600000, secret: "okgoes secret" },
|
|
18
|
+
cors: { enable: false },
|
|
19
|
+
static: { enable: true, dir: "public", maxAge: 1000 * 60 * 60 * 24 },
|
|
20
|
+
log: { enable: true },
|
|
21
|
+
};
|
|
22
|
+
function mergeConfig(userConfig = {}) {
|
|
23
|
+
const pick = (value, fallback) => value ?? fallback;
|
|
24
|
+
return {
|
|
25
|
+
port: pick(userConfig.port, exports.defaultConfig.port),
|
|
26
|
+
host: pick(userConfig.host, exports.defaultConfig.host),
|
|
27
|
+
controllerDir: pick(userConfig.controllerDir, exports.defaultConfig.controllerDir),
|
|
28
|
+
routing: {
|
|
29
|
+
mode: pick(userConfig.routing?.mode, exports.defaultConfig.routing.mode),
|
|
30
|
+
defaultModule: pick(userConfig.routing?.defaultModule, exports.defaultConfig.routing.defaultModule),
|
|
31
|
+
defaultController: pick(userConfig.routing?.defaultController, exports.defaultConfig.routing.defaultController),
|
|
32
|
+
defaultAction: pick(userConfig.routing?.defaultAction, exports.defaultConfig.routing.defaultAction),
|
|
33
|
+
modules: pick(userConfig.routing?.modules, []),
|
|
34
|
+
},
|
|
35
|
+
view: {
|
|
36
|
+
dir: pick(userConfig.view?.dir, exports.defaultConfig.view.dir),
|
|
37
|
+
extension: pick(userConfig.view?.extension, exports.defaultConfig.view.extension),
|
|
38
|
+
},
|
|
39
|
+
session: {
|
|
40
|
+
enable: pick(userConfig.session?.enable, exports.defaultConfig.session.enable),
|
|
41
|
+
key: pick(userConfig.session?.key, exports.defaultConfig.session.key),
|
|
42
|
+
maxAge: pick(userConfig.session?.maxAge, exports.defaultConfig.session.maxAge),
|
|
43
|
+
secret: pick(userConfig.session?.secret, exports.defaultConfig.session.secret),
|
|
44
|
+
},
|
|
45
|
+
cors: {
|
|
46
|
+
enable: pick(userConfig.cors?.enable, exports.defaultConfig.cors.enable),
|
|
47
|
+
},
|
|
48
|
+
static: {
|
|
49
|
+
enable: pick(userConfig.static?.enable, exports.defaultConfig.static.enable),
|
|
50
|
+
dir: pick(userConfig.static?.dir, exports.defaultConfig.static.dir),
|
|
51
|
+
maxAge: pick(userConfig.static?.maxAge, exports.defaultConfig.static.maxAge),
|
|
52
|
+
},
|
|
53
|
+
log: {
|
|
54
|
+
enable: pick(userConfig.log?.enable, exports.defaultConfig.log.enable),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CTX_RESOLVED_ROUTE_KEY = exports.APP_VIEW_EXTENSION_KEY = exports.FRAMEWORK_INSTANCE_KEY = void 0;
|
|
4
|
+
exports.FRAMEWORK_INSTANCE_KEY = "okgoes";
|
|
5
|
+
exports.APP_VIEW_EXTENSION_KEY = "okgoesViewExt";
|
|
6
|
+
exports.CTX_RESOLVED_ROUTE_KEY = "okgoesResolvedRoute";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type Context from "./core/context";
|
|
2
|
+
import Request from "./request";
|
|
3
|
+
import Response from "./response";
|
|
4
|
+
import type Application from "./application";
|
|
5
|
+
export default class Controller {
|
|
6
|
+
request: Request | null;
|
|
7
|
+
response: Response | null;
|
|
8
|
+
ctx: Context | null;
|
|
9
|
+
app: Application | null;
|
|
10
|
+
initialize(ctx: Context, request: Request, response: Response, app: Application): void;
|
|
11
|
+
protected render(templateOrLocals?: string | object, locals?: any): Promise<void>;
|
|
12
|
+
protected json(data: any): void;
|
|
13
|
+
protected text(content: string): void;
|
|
14
|
+
protected html(content: string): void;
|
|
15
|
+
protected redirect(location: string): void;
|
|
16
|
+
protected service<T = any>(name: string): T;
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class Controller {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.request = null;
|
|
6
|
+
this.response = null;
|
|
7
|
+
this.ctx = null;
|
|
8
|
+
this.app = null;
|
|
9
|
+
}
|
|
10
|
+
initialize(ctx, request, response, app) {
|
|
11
|
+
this.ctx = ctx;
|
|
12
|
+
this.request = request;
|
|
13
|
+
this.response = response;
|
|
14
|
+
this.app = app;
|
|
15
|
+
}
|
|
16
|
+
async render(templateOrLocals, locals) {
|
|
17
|
+
if (typeof templateOrLocals === "object") {
|
|
18
|
+
await this.response?.render(undefined, templateOrLocals);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
await this.response?.render(templateOrLocals, locals);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
json(data) {
|
|
25
|
+
this.response?.json(data);
|
|
26
|
+
}
|
|
27
|
+
text(content) {
|
|
28
|
+
this.response?.text(content);
|
|
29
|
+
}
|
|
30
|
+
html(content) {
|
|
31
|
+
this.response?.html(content);
|
|
32
|
+
}
|
|
33
|
+
redirect(location) {
|
|
34
|
+
this.response?.redirect(location);
|
|
35
|
+
}
|
|
36
|
+
service(name) {
|
|
37
|
+
if (!this.app) {
|
|
38
|
+
throw new Error("controller is not bound to an application, cannot resolve service");
|
|
39
|
+
}
|
|
40
|
+
return this.app.getService(name);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.default = Controller;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中间件洋葱模型组合器(等价于 koa-compose,零远端依赖实现)。
|
|
3
|
+
* 依次执行中间件,next() 触发下一个中间件,await next() 后回到当前中间件形成洋葱结构。
|
|
4
|
+
*/
|
|
5
|
+
export type Next = () => Promise<any>;
|
|
6
|
+
export type MiddlewareFn<C = any> = (ctx: C, next: Next) => any;
|
|
7
|
+
export declare function compose<C = any>(middleware: MiddlewareFn<C>[]): (ctx: C, next?: Next) => Promise<any>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compose = compose;
|
|
4
|
+
function compose(middleware) {
|
|
5
|
+
if (!Array.isArray(middleware)) {
|
|
6
|
+
throw new TypeError("middleware stack must be an array");
|
|
7
|
+
}
|
|
8
|
+
for (const fn of middleware) {
|
|
9
|
+
if (typeof fn !== "function") {
|
|
10
|
+
throw new TypeError("middleware must be composed of functions");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return function composed(ctx, next) {
|
|
14
|
+
let index = -1;
|
|
15
|
+
function dispatch(i) {
|
|
16
|
+
if (i <= index) {
|
|
17
|
+
return Promise.reject(new Error("next() called multiple times"));
|
|
18
|
+
}
|
|
19
|
+
index = i;
|
|
20
|
+
let fn = middleware[i];
|
|
21
|
+
if (i === middleware.length) {
|
|
22
|
+
fn = next;
|
|
23
|
+
}
|
|
24
|
+
if (!fn) {
|
|
25
|
+
return Promise.resolve();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return Promise.resolve(fn(ctx, () => dispatch(i + 1)));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return Promise.reject(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return dispatch(0);
|
|
35
|
+
};
|
|
36
|
+
}
|