alp-node 6.2.0 → 8.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/CHANGELOG.md +38 -0
- package/README.md +7 -7
- package/dist/AlpNodeApp-node20.mjs +367 -0
- package/dist/AlpNodeApp-node20.mjs.map +1 -0
- package/dist/definitions/AlpNodeApp.d.ts +21 -14
- package/dist/definitions/AlpNodeApp.d.ts.map +1 -1
- package/dist/definitions/config.d.ts +17 -0
- package/dist/definitions/config.d.ts.map +1 -0
- package/dist/definitions/errors.d.ts +3 -0
- package/dist/definitions/errors.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +9 -7
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/language.d.ts +7 -0
- package/dist/definitions/language.d.ts.map +1 -0
- package/dist/definitions/listen.d.ts +6 -0
- package/dist/definitions/listen.d.ts.map +1 -0
- package/dist/definitions/params/ParamValid.d.ts +8 -0
- package/dist/definitions/params/ParamValid.d.ts.map +1 -0
- package/dist/definitions/params/ParamValidationResult.d.ts +9 -0
- package/dist/definitions/params/ParamValidationResult.d.ts.map +1 -0
- package/dist/definitions/params/ParamValidationResult.test.d.ts +2 -0
- package/dist/definitions/params/ParamValidationResult.test.d.ts.map +1 -0
- package/dist/definitions/params/ParamValueFromContext.d.ts +12 -0
- package/dist/definitions/params/ParamValueFromContext.d.ts.map +1 -0
- package/dist/definitions/params/ParamValueModelValidator.d.ts +4 -0
- package/dist/definitions/params/ParamValueModelValidator.d.ts.map +1 -0
- package/dist/definitions/params/ParamValueStringValidator.d.ts +5 -0
- package/dist/definitions/params/ParamValueStringValidator.d.ts.map +1 -0
- package/dist/definitions/params/ParamValueValidator.d.ts +10 -0
- package/dist/definitions/params/ParamValueValidator.d.ts.map +1 -0
- package/dist/definitions/params/index.d.ts +19 -0
- package/dist/definitions/params/index.d.ts.map +1 -0
- package/dist/definitions/router.d.ts +13 -0
- package/dist/definitions/router.d.ts.map +1 -0
- package/dist/definitions/translate/index.d.ts +11 -0
- package/dist/definitions/translate/index.d.ts.map +1 -0
- package/dist/definitions/translate/load.d.ts +4 -0
- package/dist/definitions/translate/load.d.ts.map +1 -0
- package/dist/definitions/types.d.ts +53 -0
- package/dist/definitions/types.d.ts.map +1 -0
- package/dist/index-node20.mjs +495 -0
- package/dist/index-node20.mjs.map +1 -0
- package/package.json +27 -28
- package/src/AlpNodeApp.ts +71 -50
- package/src/config.ts +116 -0
- package/src/errors.ts +70 -0
- package/src/index.ts +33 -18
- package/src/language.ts +35 -0
- package/src/listen.ts +68 -0
- package/src/params/ParamValid.ts +15 -0
- package/src/params/ParamValidationResult.test.ts +65 -0
- package/src/params/ParamValidationResult.ts +38 -0
- package/src/params/ParamValueFromContext.ts +42 -0
- package/src/params/ParamValueModelValidator.ts +36 -0
- package/src/params/ParamValueStringValidator.ts +13 -0
- package/src/params/ParamValueValidator.ts +23 -0
- package/src/params/index.ts +70 -0
- package/src/router.ts +64 -0
- package/src/translate/index.ts +45 -0
- package/src/translate/load.ts +31 -0
- package/src/types.ts +67 -0
- package/dist/AlpNodeApp-node18.mjs +0 -94
- package/dist/AlpNodeApp-node18.mjs.map +0 -1
- package/dist/index-node18.mjs +0 -124
- package/dist/index-node18.mjs.map +0 -1
- package/src/.eslintrc.json +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alp-node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "framework based on koa 2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"springbokjs",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
25
|
+
"node": ">=20.11.0"
|
|
26
26
|
},
|
|
27
27
|
"sideEffects": false,
|
|
28
|
-
"main": "./dist/index-
|
|
28
|
+
"main": "./dist/index-node20.mjs",
|
|
29
29
|
"types": "./dist/definitions/index.d.ts",
|
|
30
30
|
"typesVersions": {
|
|
31
31
|
">=3.1": {
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
".": {
|
|
40
40
|
"types": "./dist/definitions/index.d.ts",
|
|
41
41
|
"node": {
|
|
42
|
-
"import": "./dist/index-
|
|
42
|
+
"import": "./dist/index-node20.mjs"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"./AlpNodeApp": {
|
|
46
46
|
"types": "./dist/definitions/AlpNodeApp.d.ts",
|
|
47
47
|
"node": {
|
|
48
|
-
"import": "./dist/AlpNodeApp-
|
|
48
|
+
"import": "./dist/AlpNodeApp-node20.mjs"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
},
|
|
@@ -59,46 +59,45 @@
|
|
|
59
59
|
"clean": "yarn clean:build",
|
|
60
60
|
"clean:build": "pob-babel-clean-out dist",
|
|
61
61
|
"lint": "yarn run lint:eslint",
|
|
62
|
-
"lint:eslint": "yarn ../.. run eslint --
|
|
62
|
+
"lint:eslint": "yarn ../.. run eslint --quiet packages/alp-node",
|
|
63
63
|
"watch": "yarn clean:build && rollup --config rollup.config.mjs --watch"
|
|
64
64
|
},
|
|
65
65
|
"pob": {
|
|
66
|
-
"
|
|
67
|
-
{
|
|
68
|
-
"target": "node",
|
|
69
|
-
"version": "18"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
66
|
+
"bundler": "rollup-babel",
|
|
72
67
|
"entries": [
|
|
73
68
|
"index",
|
|
74
69
|
"AlpNodeApp"
|
|
70
|
+
],
|
|
71
|
+
"envs": [
|
|
72
|
+
{
|
|
73
|
+
"target": "node",
|
|
74
|
+
"version": "20"
|
|
75
|
+
}
|
|
75
76
|
]
|
|
76
77
|
},
|
|
77
78
|
"prettier": "@pob/root/prettier-config",
|
|
78
79
|
"peerDependencies": {
|
|
79
|
-
"router-segments": "^
|
|
80
|
+
"router-segments": "^11.0.0"
|
|
80
81
|
},
|
|
81
82
|
"dependencies": {
|
|
82
|
-
"@types/koa": "^
|
|
83
|
-
"@types/node": ">=
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"alp-translate": "8.1.1",
|
|
90
|
-
"alp-types": "3.1.1",
|
|
91
|
-
"koa": "^2.13.1",
|
|
92
|
-
"koa-compress": "^5.0.0",
|
|
83
|
+
"@types/koa": "^3.0.0",
|
|
84
|
+
"@types/node": ">=20.0.0",
|
|
85
|
+
"deep-freeze-es6": "^4.0.1",
|
|
86
|
+
"error-html": "^0.3.5",
|
|
87
|
+
"intl-messageformat": "^10.0.0",
|
|
88
|
+
"koa": "^3.0.1",
|
|
89
|
+
"koa-compress": "^5.1.1",
|
|
93
90
|
"koa-static": "^5.0.0",
|
|
94
|
-
"
|
|
91
|
+
"minimist": "^1.2.8",
|
|
92
|
+
"nightingale-logger": "^15.0.0",
|
|
93
|
+
"object-properties": "^9.0.1"
|
|
95
94
|
},
|
|
96
95
|
"devDependencies": {
|
|
97
|
-
"@babel/core": "7.
|
|
96
|
+
"@babel/core": "7.28.0",
|
|
98
97
|
"@types/koa-compress": "4.0.6",
|
|
99
98
|
"@types/koa-static": "4.0.4",
|
|
100
99
|
"@types/minimist": "1.2.5",
|
|
101
|
-
"pob-babel": "
|
|
102
|
-
"typescript": "5.
|
|
100
|
+
"pob-babel": "43.7.0",
|
|
101
|
+
"typescript": "5.8.3"
|
|
103
102
|
}
|
|
104
103
|
}
|
package/src/AlpNodeApp.ts
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { deprecate } from 'node:util';
|
|
4
|
-
import _listen from 'alp-listen';
|
|
5
|
-
import type { Config } from 'alp-node-config';
|
|
6
|
-
import _config from 'alp-node-config';
|
|
7
|
-
import errors from 'alp-node-errors';
|
|
8
|
-
import language from 'alp-node-language';
|
|
9
|
-
import params from 'alp-params';
|
|
10
|
-
import translate from 'alp-translate';
|
|
11
1
|
import type {
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
IncomingMessage,
|
|
3
|
+
RequestListener,
|
|
4
|
+
Server,
|
|
5
|
+
ServerResponse,
|
|
6
|
+
} from "node:http";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import Koa from "koa";
|
|
9
|
+
import type { DefaultState, ParameterizedContext } from "koa";
|
|
10
|
+
import compress from "koa-compress";
|
|
11
|
+
import serve from "koa-static";
|
|
12
|
+
import { Logger } from "nightingale-logger";
|
|
13
|
+
import type { Router } from "router-segments";
|
|
14
|
+
import type { Config } from "./config";
|
|
15
|
+
import errors from "./errors";
|
|
16
|
+
import type { AlpLanguageContext } from "./language";
|
|
17
|
+
import language from "./language";
|
|
18
|
+
import _listen from "./listen";
|
|
19
|
+
import type { AlpParamsContext, AlpParamsRequest } from "./params/index";
|
|
20
|
+
import params from "./params/index";
|
|
21
|
+
import type {
|
|
22
|
+
AlpRouteRef,
|
|
23
|
+
RouterContext as AlpRouterContext,
|
|
24
|
+
UrlGenerator,
|
|
25
|
+
} from "./router";
|
|
26
|
+
import type { TranslateBaseContext, TranslateContext } from "./translate/index";
|
|
27
|
+
import translate from "./translate/index";
|
|
28
|
+
import type {
|
|
14
29
|
Context as AlpContext,
|
|
15
|
-
ContextState,
|
|
16
30
|
ContextSanitizedState,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import compress from 'koa-compress';
|
|
22
|
-
import serve from 'koa-static';
|
|
23
|
-
import { Logger } from 'nightingale-logger';
|
|
31
|
+
ContextState,
|
|
32
|
+
NodeApplication,
|
|
33
|
+
NodeConfig,
|
|
34
|
+
} from "./types";
|
|
24
35
|
|
|
25
|
-
const logger = new Logger(
|
|
36
|
+
const logger = new Logger("alp");
|
|
26
37
|
|
|
27
38
|
export interface AlpNodeAppOptions {
|
|
28
39
|
appDirname: string;
|
|
@@ -32,15 +43,26 @@ export interface AlpNodeAppOptions {
|
|
|
32
43
|
publicPath?: string;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
declare module
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
46
|
+
declare module "koa" {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
37
48
|
interface DefaultState extends ContextState {}
|
|
38
|
-
|
|
39
|
-
interface DefaultContext
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
interface DefaultContext
|
|
51
|
+
extends AlpContext,
|
|
52
|
+
AlpParamsContext,
|
|
53
|
+
AlpRouterContext,
|
|
54
|
+
AlpLanguageContext,
|
|
55
|
+
TranslateContext {}
|
|
56
|
+
|
|
57
|
+
interface BaseContext extends AlpContext, TranslateBaseContext {
|
|
58
|
+
urlGenerator: UrlGenerator;
|
|
59
|
+
redirectTo: <P extends Record<string, unknown>>(
|
|
60
|
+
to: string,
|
|
61
|
+
params?: P,
|
|
62
|
+
) => void;
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
65
|
+
interface BaseRequest extends AlpParamsRequest {}
|
|
44
66
|
}
|
|
45
67
|
|
|
46
68
|
export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
@@ -52,10 +74,10 @@ export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
|
52
74
|
|
|
53
75
|
config: Config & NodeConfig;
|
|
54
76
|
|
|
55
|
-
declare request: BaseRequest & ContextRequest;
|
|
56
|
-
|
|
57
77
|
_server?: Server;
|
|
58
78
|
|
|
79
|
+
router?: Router<any, AlpRouteRef>;
|
|
80
|
+
|
|
59
81
|
/**
|
|
60
82
|
* @param {Object} [options]
|
|
61
83
|
* @param {string} [options.certPath] directory of the ssl certificates
|
|
@@ -71,31 +93,24 @@ export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
|
71
93
|
super();
|
|
72
94
|
|
|
73
95
|
this.dirname = path.normalize(appDirname);
|
|
74
|
-
|
|
75
|
-
Object.defineProperty(this, 'packageDirname', {
|
|
76
|
-
get: deprecate(() => packageDirname, 'packageDirname'),
|
|
77
|
-
configurable: false,
|
|
78
|
-
enumerable: false,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
96
|
this.certPath = certPath || `${packageDirname}/config/cert`;
|
|
82
97
|
this.publicPath = publicPath || `${packageDirname}/public/`;
|
|
83
98
|
|
|
84
|
-
this.config =
|
|
99
|
+
this.config = config;
|
|
85
100
|
this.context.config = this.config;
|
|
86
101
|
|
|
87
102
|
params(this);
|
|
88
103
|
language(this);
|
|
89
|
-
translate(
|
|
104
|
+
translate("locales")(this);
|
|
90
105
|
|
|
91
106
|
this.use(compress());
|
|
92
107
|
}
|
|
93
108
|
|
|
94
|
-
existsConfigSync(name: string): ReturnType<Config[
|
|
109
|
+
existsConfigSync(name: string): ReturnType<Config["existsConfigSync"]> {
|
|
95
110
|
return this.config.existsConfigSync(name);
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
loadConfigSync(name: string): ReturnType<Config[
|
|
113
|
+
loadConfigSync(name: string): ReturnType<Config["loadConfigSync"]> {
|
|
99
114
|
return this.config.loadConfigSync(name);
|
|
100
115
|
}
|
|
101
116
|
|
|
@@ -104,6 +119,7 @@ export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
|
104
119
|
res: ServerResponse,
|
|
105
120
|
): ParameterizedContext<StateT> {
|
|
106
121
|
const ctx = super.createContext<StateT>(req, res);
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
107
123
|
ctx.sanitizedState = {} as ContextSanitizedState;
|
|
108
124
|
return ctx;
|
|
109
125
|
}
|
|
@@ -116,9 +132,8 @@ export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
|
116
132
|
this.use(errors);
|
|
117
133
|
}
|
|
118
134
|
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
120
135
|
override listen(): never {
|
|
121
|
-
throw new Error(
|
|
136
|
+
throw new Error("Use start instead");
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
/**
|
|
@@ -127,23 +142,29 @@ export class AlpNodeApp extends Koa<ContextState> implements NodeApplication {
|
|
|
127
142
|
close(): void {
|
|
128
143
|
if (this._server) {
|
|
129
144
|
this._server.close();
|
|
130
|
-
this.emit(
|
|
145
|
+
this.emit("close");
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
async start(fn: () => Promise<void> | void): Promise<Server> {
|
|
135
150
|
await fn();
|
|
136
151
|
try {
|
|
137
|
-
const server = await _listen(
|
|
152
|
+
const server = await _listen(
|
|
153
|
+
this.config,
|
|
154
|
+
this.callback() as RequestListener,
|
|
155
|
+
this.certPath,
|
|
156
|
+
);
|
|
138
157
|
this._server = server;
|
|
139
|
-
logger.success(
|
|
140
|
-
if (process.send) process.send(
|
|
158
|
+
logger.success("started");
|
|
159
|
+
if (process.send) process.send("ready");
|
|
141
160
|
return server;
|
|
142
161
|
} catch (error: unknown) {
|
|
143
|
-
logger.error(
|
|
162
|
+
logger.error("start fail", { err: error });
|
|
144
163
|
throw error;
|
|
145
164
|
}
|
|
146
165
|
}
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
export type { Context } from
|
|
168
|
+
export type { Context } from "koa";
|
|
169
|
+
|
|
170
|
+
export { type NodeApplication } from "./types";
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import deepFreeze from "deep-freeze-es6";
|
|
3
|
+
import minimist from "minimist";
|
|
4
|
+
import type { NodeConfig, PackageConfig } from "./types";
|
|
5
|
+
|
|
6
|
+
const argv = minimist(process.argv.slice(2));
|
|
7
|
+
|
|
8
|
+
function _existsConfigSync(dirname: string, name: string): boolean {
|
|
9
|
+
return existsSync(`${dirname}${name}.json`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function _loadConfigSync(
|
|
13
|
+
dirname: string,
|
|
14
|
+
name: string,
|
|
15
|
+
): Record<string, unknown> {
|
|
16
|
+
const content = readFileSync(`${dirname}${name}.json`, "utf8");
|
|
17
|
+
return JSON.parse(content) as Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ConfigOptions {
|
|
21
|
+
argv?: string[];
|
|
22
|
+
packageConfig?: PackageConfig;
|
|
23
|
+
version?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Config {
|
|
27
|
+
packageConfig?: PackageConfig;
|
|
28
|
+
|
|
29
|
+
private _record: Record<string, unknown>;
|
|
30
|
+
|
|
31
|
+
private readonly _dirname: string;
|
|
32
|
+
|
|
33
|
+
constructor(dirname: string, options?: ConfigOptions) {
|
|
34
|
+
this._record = {};
|
|
35
|
+
this._dirname = dirname.replace(/\/*$/, "/");
|
|
36
|
+
if (options) {
|
|
37
|
+
this.loadSync(options);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
loadSync(options: ConfigOptions = {}): Config & NodeConfig {
|
|
42
|
+
const env = process.env.CONFIG_ENV || process.env.NODE_ENV || "development";
|
|
43
|
+
const { argv: argvOverrides = [], packageConfig, version } = options;
|
|
44
|
+
this.packageConfig = packageConfig;
|
|
45
|
+
|
|
46
|
+
const config = _loadConfigSync(this._dirname, "common");
|
|
47
|
+
for (const [key, value] of Object.entries(
|
|
48
|
+
_loadConfigSync(this._dirname, env),
|
|
49
|
+
)) {
|
|
50
|
+
config[key] = value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.existsConfigSync("local")) {
|
|
54
|
+
for (const [key, value] of Object.entries(
|
|
55
|
+
_loadConfigSync(this._dirname, "local"),
|
|
56
|
+
)) {
|
|
57
|
+
config[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.version) {
|
|
62
|
+
throw new Error('Cannot have "version", in config.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
config.version = String(version || argv.version || packageConfig?.version);
|
|
66
|
+
|
|
67
|
+
const socketPath: string | undefined = (argv.socket ||
|
|
68
|
+
argv["socket-path"] ||
|
|
69
|
+
argv.socketPath) as string | undefined;
|
|
70
|
+
if (socketPath) {
|
|
71
|
+
config.socketPath = socketPath;
|
|
72
|
+
} else if (argv.port) {
|
|
73
|
+
config.port = argv.port;
|
|
74
|
+
delete config.socketPath;
|
|
75
|
+
} else if (process.env.PORT) {
|
|
76
|
+
config.port = Number(process.env.PORT);
|
|
77
|
+
delete config.socketPath;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
argvOverrides.forEach((key) => {
|
|
81
|
+
const splitted = key.split(".");
|
|
82
|
+
const value =
|
|
83
|
+
splitted.length > 0 &&
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,unicorn/no-array-reduce
|
|
85
|
+
splitted.reduce((config, partialKey) => config[partialKey], argv);
|
|
86
|
+
if (value !== undefined) {
|
|
87
|
+
const last = splitted.pop()!;
|
|
88
|
+
const v =
|
|
89
|
+
splitted.length === 0
|
|
90
|
+
? config
|
|
91
|
+
: // eslint-disable-next-line unicorn/no-array-reduce
|
|
92
|
+
splitted.reduce(
|
|
93
|
+
(config, partialKey) =>
|
|
94
|
+
config[partialKey] as Record<string, unknown>,
|
|
95
|
+
config,
|
|
96
|
+
);
|
|
97
|
+
v[last] = value;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this._record = deepFreeze(config);
|
|
102
|
+
return this as unknown as Config & NodeConfig;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get<T>(key: string): Readonly<T> {
|
|
106
|
+
return this._record[key] as T;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
existsConfigSync(name: string): boolean {
|
|
110
|
+
return _existsConfigSync(this._dirname, name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
loadConfigSync(name: string): Readonly<Record<string, unknown>> {
|
|
114
|
+
return _loadConfigSync(this._dirname, name);
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { STATUS_CODES } from "node:http";
|
|
2
|
+
import ErrorHtmlRenderer from "error-html";
|
|
3
|
+
import { Logger } from "nightingale-logger";
|
|
4
|
+
import type { Context } from "./AlpNodeApp";
|
|
5
|
+
import type { HtmlError } from "./types";
|
|
6
|
+
|
|
7
|
+
const logger = new Logger("alp:errors");
|
|
8
|
+
const errorHtmlRenderer = new ErrorHtmlRenderer({
|
|
9
|
+
appPath: `${process.cwd()}/`,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default async function alpNodeErrors(
|
|
13
|
+
ctx: Context,
|
|
14
|
+
next: () => Promise<void> | void,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
try {
|
|
17
|
+
await next();
|
|
18
|
+
} catch (error: unknown) {
|
|
19
|
+
// eslint-disable-next-line no-ex-assign
|
|
20
|
+
if (!error) error = new Error("Unknown error");
|
|
21
|
+
// eslint-disable-next-line no-ex-assign
|
|
22
|
+
if (typeof error === "string") error = new Error(error);
|
|
23
|
+
|
|
24
|
+
ctx.status = (error as HtmlError).status || 500;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
26
|
+
logger.error(error as any);
|
|
27
|
+
|
|
28
|
+
switch (ctx.request.accepts("html", "text", "json")) {
|
|
29
|
+
case "json":
|
|
30
|
+
ctx.type = "application/json";
|
|
31
|
+
if (
|
|
32
|
+
process.env.NODE_ENV !== "production" ||
|
|
33
|
+
(error as HtmlError).expose
|
|
34
|
+
) {
|
|
35
|
+
ctx.body = { error: (error as Error).message };
|
|
36
|
+
} else {
|
|
37
|
+
ctx.body = { error: STATUS_CODES[ctx.status] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
case "html":
|
|
43
|
+
ctx.type = "text/html";
|
|
44
|
+
if (process.env.NODE_ENV !== "production") {
|
|
45
|
+
ctx.body = errorHtmlRenderer.render(error as Error);
|
|
46
|
+
} else if ((error as HtmlError).expose) {
|
|
47
|
+
ctx.body = (error as Error).message;
|
|
48
|
+
} else {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case "text":
|
|
55
|
+
case false:
|
|
56
|
+
default:
|
|
57
|
+
ctx.type = "text/plain";
|
|
58
|
+
if (
|
|
59
|
+
process.env.NODE_ENV !== "production" ||
|
|
60
|
+
(error as HtmlError).expose
|
|
61
|
+
) {
|
|
62
|
+
ctx.body = (error as Error).message;
|
|
63
|
+
} else {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from
|
|
2
|
-
import path from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
export type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Logger } from "nightingale-logger";
|
|
4
|
+
import type { AlpNodeAppOptions } from "./AlpNodeApp";
|
|
5
|
+
import { AlpNodeApp } from "./AlpNodeApp";
|
|
6
|
+
import { Config } from "./config";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
BaseContext,
|
|
10
|
+
NodeApplication,
|
|
11
|
+
NodeConfig,
|
|
12
|
+
ContextState,
|
|
13
|
+
ContextSanitizedState,
|
|
14
|
+
} from "./types";
|
|
15
|
+
export type { Context } from "./AlpNodeApp";
|
|
16
|
+
|
|
17
|
+
const logger = new Logger("alp");
|
|
18
|
+
|
|
19
|
+
export const appDirname = path.resolve("build");
|
|
20
|
+
|
|
21
|
+
const packagePath = path.resolve("package.json");
|
|
16
22
|
if (!packagePath) {
|
|
17
23
|
throw new Error(`Could not find package.json: "${String(packagePath)}"`);
|
|
18
24
|
}
|
|
19
25
|
export const packageDirname = path.dirname(packagePath);
|
|
20
26
|
|
|
21
|
-
logger.debug(
|
|
27
|
+
logger.debug("init", { appDirname, packageDirname });
|
|
22
28
|
|
|
23
29
|
export const packageConfig: Record<string, unknown> = JSON.parse(
|
|
24
|
-
readFileSync(packagePath,
|
|
30
|
+
readFileSync(packagePath, "utf8"),
|
|
25
31
|
) as Record<string, unknown>;
|
|
26
32
|
|
|
27
33
|
const buildedConfigPath = `${appDirname}/build/config/`;
|
|
@@ -33,7 +39,7 @@ export const config = new Config(configPath).loadSync({ packageConfig });
|
|
|
33
39
|
|
|
34
40
|
export type AppOptions = Omit<
|
|
35
41
|
AlpNodeAppOptions,
|
|
36
|
-
|
|
42
|
+
"appDirname" | "config" | "packageDirname"
|
|
37
43
|
>;
|
|
38
44
|
|
|
39
45
|
export default class App extends AlpNodeApp {
|
|
@@ -46,3 +52,12 @@ export default class App extends AlpNodeApp {
|
|
|
46
52
|
});
|
|
47
53
|
}
|
|
48
54
|
}
|
|
55
|
+
|
|
56
|
+
export { Config } from "./config";
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
default as router,
|
|
60
|
+
createAlpRouterBuilder,
|
|
61
|
+
type AlpRouteRef,
|
|
62
|
+
type AlpRouter,
|
|
63
|
+
} from "./router";
|
package/src/language.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Context } from "koa";
|
|
2
|
+
import { defineLazyProperty } from "object-properties";
|
|
3
|
+
import type { AlpNodeApp } from "./AlpNodeApp";
|
|
4
|
+
|
|
5
|
+
export interface AlpLanguageContext {
|
|
6
|
+
readonly firstAcceptedLanguage: string;
|
|
7
|
+
readonly language: string;
|
|
8
|
+
}
|
|
9
|
+
export default function alpLanguage(app: AlpNodeApp): void {
|
|
10
|
+
const config = app.context.config;
|
|
11
|
+
const availableLanguages: string[] = config.get("availableLanguages");
|
|
12
|
+
if (!availableLanguages) {
|
|
13
|
+
throw new Error('Missing config "availableLanguages"');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
defineLazyProperty(
|
|
17
|
+
app.context,
|
|
18
|
+
"language",
|
|
19
|
+
function language(this: Context): string {
|
|
20
|
+
return (
|
|
21
|
+
this.acceptsLanguages(availableLanguages) ||
|
|
22
|
+
availableLanguages[0] ||
|
|
23
|
+
"en"
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
defineLazyProperty(
|
|
29
|
+
app.context,
|
|
30
|
+
"firstAcceptedLanguage",
|
|
31
|
+
function firstAcceptedLanguage(this: Context): string {
|
|
32
|
+
return this.acceptsLanguages()[0] || availableLanguages[0] || "en";
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/listen.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { chmodSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { createServer as createServerHttp } from "node:http";
|
|
3
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
4
|
+
import { createServer as createServerHttps } from "node:https";
|
|
5
|
+
import { Logger } from "nightingale-logger";
|
|
6
|
+
import type { Config } from "./config";
|
|
7
|
+
|
|
8
|
+
const logger = new Logger("alp:listen");
|
|
9
|
+
|
|
10
|
+
type RequestListener = (req: IncomingMessage, res: ServerResponse) => void;
|
|
11
|
+
|
|
12
|
+
const createServer = (
|
|
13
|
+
callback: RequestListener,
|
|
14
|
+
socketPath?: string,
|
|
15
|
+
tls?: boolean,
|
|
16
|
+
dirname = "",
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
18
|
+
): Server => {
|
|
19
|
+
const createHttpServer =
|
|
20
|
+
!socketPath && tls ? createServerHttps : createServerHttp;
|
|
21
|
+
|
|
22
|
+
if (!tls) {
|
|
23
|
+
return (createHttpServer as typeof createServerHttps)(callback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const options = {
|
|
27
|
+
key: readFileSync(`${dirname}/server.key`),
|
|
28
|
+
cert: readFileSync(`${dirname}/server.crt`),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (createHttpServer as typeof createServerHttps)(options, callback);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function alpListen(
|
|
35
|
+
config: Config,
|
|
36
|
+
callback: RequestListener,
|
|
37
|
+
dirname?: string,
|
|
38
|
+
): Promise<Server> {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const socketPath = config.get<string>("socketPath");
|
|
41
|
+
const port = config.get<number>("port");
|
|
42
|
+
const hostname = config.get<string>("hostname");
|
|
43
|
+
const tls = config.get<boolean>("tls");
|
|
44
|
+
|
|
45
|
+
logger.info("Creating server", socketPath ? { socketPath } : { port });
|
|
46
|
+
const server = createServer(callback, socketPath, tls, dirname);
|
|
47
|
+
|
|
48
|
+
if (socketPath) {
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(socketPath);
|
|
51
|
+
} catch {}
|
|
52
|
+
|
|
53
|
+
server.listen(socketPath, () => {
|
|
54
|
+
if (socketPath) {
|
|
55
|
+
chmodSync(socketPath, "777");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logger.info("Server listening", { socketPath });
|
|
59
|
+
resolve(server);
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
server.listen(port, hostname, () => {
|
|
63
|
+
logger.info("Server listening", { port });
|
|
64
|
+
resolve(server);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Context } from "../AlpNodeApp";
|
|
2
|
+
import { ParamValidationResult } from "./ParamValidationResult";
|
|
3
|
+
|
|
4
|
+
export default class ParamValid extends ParamValidationResult {
|
|
5
|
+
context: Context;
|
|
6
|
+
|
|
7
|
+
constructor(context: Context) {
|
|
8
|
+
super();
|
|
9
|
+
this.context = context;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override _error(): void {
|
|
13
|
+
this.context.throw(400, "Invalid params", { validator: this });
|
|
14
|
+
}
|
|
15
|
+
}
|