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.
Files changed (66) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +7 -7
  3. package/dist/AlpNodeApp-node20.mjs +367 -0
  4. package/dist/AlpNodeApp-node20.mjs.map +1 -0
  5. package/dist/definitions/AlpNodeApp.d.ts +21 -14
  6. package/dist/definitions/AlpNodeApp.d.ts.map +1 -1
  7. package/dist/definitions/config.d.ts +17 -0
  8. package/dist/definitions/config.d.ts.map +1 -0
  9. package/dist/definitions/errors.d.ts +3 -0
  10. package/dist/definitions/errors.d.ts.map +1 -0
  11. package/dist/definitions/index.d.ts +9 -7
  12. package/dist/definitions/index.d.ts.map +1 -1
  13. package/dist/definitions/language.d.ts +7 -0
  14. package/dist/definitions/language.d.ts.map +1 -0
  15. package/dist/definitions/listen.d.ts +6 -0
  16. package/dist/definitions/listen.d.ts.map +1 -0
  17. package/dist/definitions/params/ParamValid.d.ts +8 -0
  18. package/dist/definitions/params/ParamValid.d.ts.map +1 -0
  19. package/dist/definitions/params/ParamValidationResult.d.ts +9 -0
  20. package/dist/definitions/params/ParamValidationResult.d.ts.map +1 -0
  21. package/dist/definitions/params/ParamValidationResult.test.d.ts +2 -0
  22. package/dist/definitions/params/ParamValidationResult.test.d.ts.map +1 -0
  23. package/dist/definitions/params/ParamValueFromContext.d.ts +12 -0
  24. package/dist/definitions/params/ParamValueFromContext.d.ts.map +1 -0
  25. package/dist/definitions/params/ParamValueModelValidator.d.ts +4 -0
  26. package/dist/definitions/params/ParamValueModelValidator.d.ts.map +1 -0
  27. package/dist/definitions/params/ParamValueStringValidator.d.ts +5 -0
  28. package/dist/definitions/params/ParamValueStringValidator.d.ts.map +1 -0
  29. package/dist/definitions/params/ParamValueValidator.d.ts +10 -0
  30. package/dist/definitions/params/ParamValueValidator.d.ts.map +1 -0
  31. package/dist/definitions/params/index.d.ts +19 -0
  32. package/dist/definitions/params/index.d.ts.map +1 -0
  33. package/dist/definitions/router.d.ts +13 -0
  34. package/dist/definitions/router.d.ts.map +1 -0
  35. package/dist/definitions/translate/index.d.ts +11 -0
  36. package/dist/definitions/translate/index.d.ts.map +1 -0
  37. package/dist/definitions/translate/load.d.ts +4 -0
  38. package/dist/definitions/translate/load.d.ts.map +1 -0
  39. package/dist/definitions/types.d.ts +53 -0
  40. package/dist/definitions/types.d.ts.map +1 -0
  41. package/dist/index-node20.mjs +495 -0
  42. package/dist/index-node20.mjs.map +1 -0
  43. package/package.json +27 -28
  44. package/src/AlpNodeApp.ts +71 -50
  45. package/src/config.ts +116 -0
  46. package/src/errors.ts +70 -0
  47. package/src/index.ts +33 -18
  48. package/src/language.ts +35 -0
  49. package/src/listen.ts +68 -0
  50. package/src/params/ParamValid.ts +15 -0
  51. package/src/params/ParamValidationResult.test.ts +65 -0
  52. package/src/params/ParamValidationResult.ts +38 -0
  53. package/src/params/ParamValueFromContext.ts +42 -0
  54. package/src/params/ParamValueModelValidator.ts +36 -0
  55. package/src/params/ParamValueStringValidator.ts +13 -0
  56. package/src/params/ParamValueValidator.ts +23 -0
  57. package/src/params/index.ts +70 -0
  58. package/src/router.ts +64 -0
  59. package/src/translate/index.ts +45 -0
  60. package/src/translate/load.ts +31 -0
  61. package/src/types.ts +67 -0
  62. package/dist/AlpNodeApp-node18.mjs +0 -94
  63. package/dist/AlpNodeApp-node18.mjs.map +0 -1
  64. package/dist/index-node18.mjs +0 -124
  65. package/dist/index-node18.mjs.map +0 -1
  66. package/src/.eslintrc.json +0 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alp-node",
3
- "version": "6.2.0",
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": ">=18.12.0"
25
+ "node": ">=20.11.0"
26
26
  },
27
27
  "sideEffects": false,
28
- "main": "./dist/index-node18.mjs",
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-node18.mjs"
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-node18.mjs"
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 --report-unused-disable-directives --resolve-plugins-relative-to . --quiet packages/alp-node",
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
- "babelEnvs": [
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": "^9.1.0"
80
+ "router-segments": "^11.0.0"
80
81
  },
81
82
  "dependencies": {
82
- "@types/koa": "^2.13.1",
83
- "@types/node": ">=18.0.0",
84
- "alp-listen": "7.1.1",
85
- "alp-node-config": "9.1.1",
86
- "alp-node-errors": "8.1.1",
87
- "alp-node-language": "7.1.1",
88
- "alp-params": "6.2.0",
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
- "nightingale-logger": "^15.0.0"
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.23.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": "38.0.2",
102
- "typescript": "5.3.3"
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
- NodeApplication,
13
- NodeConfig,
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
- ContextRequest,
18
- } from 'alp-types';
19
- import Koa from 'koa';
20
- import type { ParameterizedContext, DefaultState, BaseRequest } from 'koa';
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('alp');
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 'koa' {
36
- // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-shadow
46
+ declare module "koa" {
47
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
37
48
  interface DefaultState extends ContextState {}
38
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
39
- interface DefaultContext extends AlpContext {}
40
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
41
- interface BaseContext extends AlpContext {}
42
- // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-shadow
43
- interface BaseRequest extends ContextRequest {}
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 = _config(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('locales')(this);
104
+ translate("locales")(this);
90
105
 
91
106
  this.use(compress());
92
107
  }
93
108
 
94
- existsConfigSync(name: string): ReturnType<Config['existsConfigSync']> {
109
+ existsConfigSync(name: string): ReturnType<Config["existsConfigSync"]> {
95
110
  return this.config.existsConfigSync(name);
96
111
  }
97
112
 
98
- loadConfigSync(name: string): ReturnType<Config['loadConfigSync']> {
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('Use start instead');
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('close');
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(this.config, this.callback(), this.certPath);
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('started');
140
- if (process.send) process.send('ready');
158
+ logger.success("started");
159
+ if (process.send) process.send("ready");
141
160
  return server;
142
161
  } catch (error: unknown) {
143
- logger.error('start fail', { err: 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 'koa';
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 'node:fs';
2
- import path from 'node:path';
3
- import { Config } from 'alp-node-config';
4
- import { Logger } from 'nightingale-logger';
5
- import type { AlpNodeAppOptions } from './AlpNodeApp';
6
- import { AlpNodeApp } from './AlpNodeApp';
7
-
8
- export type { Context } from './AlpNodeApp';
9
- export { Config } from 'alp-node-config';
10
-
11
- const logger = new Logger('alp');
12
-
13
- export const appDirname = path.resolve('build');
14
-
15
- const packagePath = path.resolve('package.json');
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('init', { appDirname, packageDirname });
27
+ logger.debug("init", { appDirname, packageDirname });
22
28
 
23
29
  export const packageConfig: Record<string, unknown> = JSON.parse(
24
- readFileSync(packagePath, 'utf8'),
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
- 'appDirname' | 'config' | 'packageDirname'
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";
@@ -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
+ }