@lakutata/core 3.1.1 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,8 +19,8 @@ npm install lakutata # the framework
19
19
  npm install @lakutata/core # or the core only
20
20
  ```
21
21
 
22
- The package needs Node.js `^22.13.0 || >=24.11.0`. Projects use ES modules (`"type": "module"`); the package also ships
23
- CommonJS builds. The decorators need these TypeScript options:
22
+ The package needs Node.js 18.10 or later (`>=18.10.0`). Projects use ES modules (`"type": "module"`); the package also
23
+ ships CommonJS builds. The decorators need these TypeScript options:
24
24
 
25
25
  ```json
26
26
  {
@@ -169,6 +169,7 @@ await container.destroy()
169
169
  | `Before`, `After` | `lakutata/decorator/asst` | Functions run before and after a method |
170
170
  | `PaginationSearchDTO`, `PaginationResultDTO`, `paginator`, `FlexibleDTO`, `toJsonSchema`, `fromJsonSchema` | `lakutata/dtos` | Pagination, open DTOs and JSON Schema conversion |
171
171
  | `ConsoleLogger`, `ILogger` | `@lakutata/core/com/logger` | The logger interface and its console implementation |
172
+ | `IInterfaceListening`, `IInterfaceListener`, `IInterfaceListenOptions`, `IListeningAddress`, `NetworkInterfaceSelector` | `lakutata` | The contract of the listening on network interfaces, which the entrypoints use for their `interface` option and `@lakutata/netif` implements (types only) |
172
173
 
173
174
  The typings document each declaration with examples.
174
175
 
package/README.zh-CN.md CHANGED
@@ -13,7 +13,7 @@ npm install lakutata # 框架
13
13
  npm install @lakutata/core # 或者只安装核心
14
14
  ```
15
15
 
16
- 这个包需要 Node.js `^22.13.0 || >=24.11.0`。项目使用 ES 模块(`"type": "module"`);包里也带有 CommonJS 构建。装饰器需要以下 TypeScript 选项:
16
+ 这个包需要 Node.js 18.10 或更高版本(`>=18.10.0`)。项目使用 ES 模块(`"type": "module"`);包里也带有 CommonJS 构建。装饰器需要以下 TypeScript 选项:
17
17
 
18
18
  ```json
19
19
  {
@@ -158,6 +158,7 @@ await container.destroy()
158
158
  | `Before`、`After` | `lakutata/decorator/asst` | 在方法之前和之后执行的函数 |
159
159
  | `PaginationSearchDTO`、`PaginationResultDTO`、`paginator`、`FlexibleDTO`、`toJsonSchema`、`fromJsonSchema` | `lakutata/dtos` | 分页、接受任意字段的 DTO、JSON Schema 转换 |
160
160
  | `ConsoleLogger`、`ILogger` | `@lakutata/core/com/logger` | 日志记录器接口及其控制台实现 |
161
+ | `IInterfaceListening`、`IInterfaceListener`、`IInterfaceListenOptions`、`IListeningAddress`、`NetworkInterfaceSelector` | `lakutata` | 按网口监听的接口:入口的 `interface` 选项通过它监听,`@lakutata/netif` 实现了它(只有类型) |
161
162
 
162
163
  类型声明中的每一项都附有说明和示例。
163
164
 
@@ -90,4 +90,5 @@ export * from '../types/ModuleOptions.js';
90
90
  */
91
91
  export * from '../interfaces/IBaseObjectConstructor.js';
92
92
  export * from '../interfaces/ILogger.js';
93
+ export * from '../interfaces/IInterfaceListening.js';
93
94
  export * from '../interfaces/IConstructor.js';
@@ -121,6 +121,7 @@ __exportStar(require("../types/ModuleOptions.js"), exports);
121
121
  */
122
122
  __exportStar(require("../interfaces/IBaseObjectConstructor.js"), exports);
123
123
  __exportStar(require("../interfaces/ILogger.js"), exports);
124
+ __exportStar(require("../interfaces/IInterfaceListening.js"), exports);
124
125
  __exportStar(require("../interfaces/IConstructor.js"), exports);
125
126
 
126
127
  //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
@@ -0,0 +1,159 @@
1
+ import type { Server } from 'node:net';
2
+ /**
3
+ * A network interface, as an implementation of {@link IInterfaceListening} takes it: its name (`'eth1'`; on Windows the
4
+ * name of the network connection), its hardware address (`{mac: '52:54:00:12:34:56'}`) or its index (`{index: 3}`).
5
+ */
6
+ export type NetworkInterfaceSelector = string | {
7
+ /**
8
+ * The hardware address of the interface.
9
+ */
10
+ mac: string;
11
+ } | {
12
+ /**
13
+ * The index of the interface.
14
+ */
15
+ index: number;
16
+ };
17
+ /**
18
+ * An address a listener of a network interface can be reached at.
19
+ */
20
+ export interface IListeningAddress {
21
+ /**
22
+ * The address (an IPv6 address without its scope).
23
+ */
24
+ address: string;
25
+ /**
26
+ * `'IPv4'` or `'IPv6'`.
27
+ */
28
+ family: 'IPv4' | 'IPv6';
29
+ /**
30
+ * The port.
31
+ */
32
+ port: number;
33
+ /**
34
+ * IPv6 only: the scope of the address (the index of the interface for a link-local one), 0 for a global one.
35
+ */
36
+ scopeid?: number;
37
+ }
38
+ /**
39
+ * The options of {@link IInterfaceListening.listenOnAll}.
40
+ */
41
+ export interface IInterfaceListenOptions {
42
+ /**
43
+ * The port; 0 lets the system choose one (for one interface only).
44
+ */
45
+ port: number;
46
+ /**
47
+ * The families listened on.
48
+ * @default 'both'
49
+ */
50
+ family?: 'IPv4' | 'IPv6' | 'both';
51
+ /**
52
+ * Refuse to listen where the connections reaching the addresses of the interface through another interface cannot
53
+ * be refused, and stop listening while they cannot. An implementation that never refuses them throws.
54
+ * @default false
55
+ */
56
+ strict?: boolean;
57
+ }
58
+ /**
59
+ * The listener of a network interface for a server: it accepts the connections of the interface and gives them to the
60
+ * server, whatever the addresses of the interface, again when they change and when the interface is unplugged and
61
+ * plugged back. The server itself does not listen. The `InterfaceListener` of `@lakutata/netif` implements it.
62
+ */
63
+ export interface IInterfaceListener {
64
+ /**
65
+ * The interface listened on: its name (`undefined` while it does not exist), and whether it is the loopback one.
66
+ */
67
+ readonly networkInterface: {
68
+ readonly name: string | undefined;
69
+ readonly loopback: boolean;
70
+ };
71
+ /**
72
+ * The port, `undefined` until a port 0 is given one by the system.
73
+ */
74
+ readonly port: number | undefined;
75
+ /**
76
+ * The addresses it can be reached at now: none while the interface does not exist or has no address.
77
+ */
78
+ readonly addresses: IListeningAddress[];
79
+ /**
80
+ * Whether the interface is listened on now.
81
+ */
82
+ readonly listening: boolean;
83
+ /**
84
+ * Whether the system refuses the connections that reach the addresses of the interface through another interface.
85
+ */
86
+ readonly enforced: boolean;
87
+ /**
88
+ * Why `enforced` is `false`, when it is.
89
+ */
90
+ readonly reason: string | undefined;
91
+ /**
92
+ * Whether it was closed.
93
+ */
94
+ readonly closed: boolean;
95
+ /**
96
+ * Stop listening; the connections established are left to the server.
97
+ */
98
+ close(): Promise<void>;
99
+ /**
100
+ * The interface is listened on, again after a change.
101
+ * @param event `'listening'`.
102
+ * @param listener Called without argument.
103
+ */
104
+ on(event: 'listening', listener: () => void): this;
105
+ /**
106
+ * A failure of listening again, retried by the listener; with `strict`, the guarantee lost.
107
+ * @param event `'error'`.
108
+ * @param listener Called with the error.
109
+ */
110
+ on(event: 'error', listener: (error: Error) => void): this;
111
+ /**
112
+ * `enforced` changed.
113
+ * @param event `'enforced'`.
114
+ * @param listener Called with its new value.
115
+ */
116
+ on(event: 'enforced', listener: (enforced: boolean) => void): this;
117
+ /**
118
+ * The listener was closed.
119
+ * @param event `'close'`.
120
+ * @param listener Called without argument.
121
+ */
122
+ on(event: 'close', listener: () => void): this;
123
+ /**
124
+ * Remove a listener of an event.
125
+ * @param event The event.
126
+ * @param listener The listener given to `on()`.
127
+ */
128
+ off(event: 'listening' | 'error' | 'enforced' | 'close', listener: (...args: any[]) => void): this;
129
+ }
130
+ /**
131
+ * Listening on network interfaces instead of addresses: what the entrypoints use for their `interface` option. The
132
+ * package `@lakutata/netif` implements it, and is the one used by default; another implementation is given in the
133
+ * `interfaceListening` option of the entrypoints.
134
+ * @example
135
+ * ```typescript
136
+ * import type {IInterfaceListener, IInterfaceListening} from 'lakutata'
137
+ * import {listenOnAll} from '@lakutata/netif'
138
+ *
139
+ * //An implementation that writes each listener made before giving it to the entrypoint
140
+ * const logged: IInterfaceListening = {
141
+ * listenOnAll: async (selectors, server, options): Promise<IInterfaceListener[]> => {
142
+ * const listeners: IInterfaceListener[] = await listenOnAll(selectors, server, options)
143
+ * for (const listener of listeners) console.log(listener.networkInterface.name, listener.addresses)
144
+ * return listeners
145
+ * }
146
+ * }
147
+ * ```
148
+ */
149
+ export interface IInterfaceListening {
150
+ /**
151
+ * Listen on interfaces for a server, one listener by interface; when one cannot be listened on, the others are
152
+ * closed and the error is thrown. An interface that does not exist yet is listened on once it appears.
153
+ * @param selectors The interfaces; several need a port other than 0.
154
+ * @param server The server the connections are given to.
155
+ * @param options The port, the families, and whether the other interfaces must be refused.
156
+ * @returns The listeners, in the order of the interfaces.
157
+ */
158
+ listenOnAll(selectors: NetworkInterfaceSelector | NetworkInterfaceSelector[], server: Server, options: IInterfaceListenOptions): Promise<IInterfaceListener[]>;
159
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -49,7 +49,7 @@ export declare enum ApplicationState {
49
49
  * and `run()` have no effect then):
50
50
  * 1. the environment variables are set, the aliases registered (`@runtime` being the working directory);
51
51
  * 2. the options are read (from the getter given to `run()`, which may be async) and validated, the identity applied
52
- * (`process.title` is the application ID, the time zone and the mode set);
52
+ * (`process.title` is the application ID, except in NW.js; the time zone and the mode set);
53
53
  * 3. the application is created in a new root container: its objects are registered and its bootstrap runs in order;
54
54
  * 4. the `onLaunched()` handlers and the objects' `launched()` hooks run.
55
55
  *
@@ -51,7 +51,7 @@ var ApplicationState;
51
51
  * and `run()` have no effect then):
52
52
  * 1. the environment variables are set, the aliases registered (`@runtime` being the working directory);
53
53
  * 2. the options are read (from the getter given to `run()`, which may be async) and validated, the identity applied
54
- * (`process.title` is the application ID, the time zone and the mode set);
54
+ * (`process.title` is the application ID, except in NW.js; the time zone and the mode set);
55
55
  * 3. the application is created in a new root container: its objects are registered and its bootstrap runs in order;
56
56
  * 4. the `onLaunched()` handlers and the objects' `launched()` hooks run.
57
57
  *
@@ -16,13 +16,17 @@ class ApplicationConfigLoader extends ModuleConfigLoader_js_1.ModuleConfigLoader
16
16
  */
17
17
  constructor(app, applicationOptions, presetLoadOptions = []) {
18
18
  if (ApplicationOptions_js_1.ApplicationOptions.isValid(applicationOptions)) {
19
- process.title = (0, BasicInfo_js_1.setBasicInfo)({
19
+ const appId = (0, BasicInfo_js_1.setBasicInfo)({
20
20
  appId: applicationOptions.id,
21
21
  appName: applicationOptions.name,
22
22
  timezone: applicationOptions.timezone ? applicationOptions.timezone : 'auto',
23
23
  //MODE, NODE_ENV, the options, development
24
24
  mode: (0, BasicInfo_js_1.resolveMode)(applicationOptions.mode)
25
25
  }).appId;
26
+ //The process is named after the application, except in NW.js: its Node.js runs in a renderer process of
27
+ //Chromium, whose title would make it an application of its own (an icon in the Dock of macOS)
28
+ if (!process.versions.nw)
29
+ process.title = appId;
26
30
  }
27
31
  super(app, applicationOptions, presetLoadOptions);
28
32
  }
@@ -27,8 +27,8 @@ import { ModuleOptions } from './ModuleOptions.js';
27
27
  */
28
28
  export declare class ApplicationOptions extends ModuleOptions {
29
29
  /**
30
- * The ID of the application (`'shop.app'`): the `process.title`, read by `Application.appId` and stamped on the
31
- * exceptions. Required.
30
+ * The ID of the application (`'shop.app'`), read by `Application.appId` and stamped on the exceptions; the
31
+ * `process.title` too, except in NW.js. Required.
32
32
  */
33
33
  readonly id: string;
34
34
  /**
@@ -61,4 +61,5 @@ export * from '../types/ModuleOptions.js';
61
61
  */
62
62
  export * from '../interfaces/IBaseObjectConstructor.js';
63
63
  export * from '../interfaces/ILogger.js';
64
+ export * from '../interfaces/IInterfaceListening.js';
64
65
  export * from '../interfaces/IConstructor.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -45,7 +45,7 @@ export var ApplicationState;
45
45
  * and `run()` have no effect then):
46
46
  * 1. the environment variables are set, the aliases registered (`@runtime` being the working directory);
47
47
  * 2. the options are read (from the getter given to `run()`, which may be async) and validated, the identity applied
48
- * (`process.title` is the application ID, the time zone and the mode set);
48
+ * (`process.title` is the application ID, except in NW.js; the time zone and the mode set);
49
49
  * 3. the application is created in a new root container: its objects are registered and its bootstrap runs in order;
50
50
  * 4. the `onLaunched()` handlers and the objects' `launched()` hooks run.
51
51
  *
@@ -13,13 +13,17 @@ export class ApplicationConfigLoader extends ModuleConfigLoader {
13
13
  */
14
14
  constructor(app, applicationOptions, presetLoadOptions = []) {
15
15
  if (ApplicationOptions.isValid(applicationOptions)) {
16
- process.title = setBasicInfo({
16
+ const appId = setBasicInfo({
17
17
  appId: applicationOptions.id,
18
18
  appName: applicationOptions.name,
19
19
  timezone: applicationOptions.timezone ? applicationOptions.timezone : 'auto',
20
20
  //MODE, NODE_ENV, the options, development
21
21
  mode: resolveMode(applicationOptions.mode)
22
22
  }).appId;
23
+ //The process is named after the application, except in NW.js: its Node.js runs in a renderer process of
24
+ //Chromium, whose title would make it an application of its own (an icon in the Dock of macOS)
25
+ if (!process.versions.nw)
26
+ process.title = appId;
23
27
  }
24
28
  super(app, applicationOptions, presetLoadOptions);
25
29
  }
@@ -90,4 +90,5 @@ export * from '../types/ModuleOptions.js';
90
90
  */
91
91
  export * from '../interfaces/IBaseObjectConstructor.js';
92
92
  export * from '../interfaces/ILogger.js';
93
+ export * from '../interfaces/IInterfaceListening.js';
93
94
  export * from '../interfaces/IConstructor.js';
@@ -0,0 +1,159 @@
1
+ import type { Server } from 'node:net';
2
+ /**
3
+ * A network interface, as an implementation of {@link IInterfaceListening} takes it: its name (`'eth1'`; on Windows the
4
+ * name of the network connection), its hardware address (`{mac: '52:54:00:12:34:56'}`) or its index (`{index: 3}`).
5
+ */
6
+ export type NetworkInterfaceSelector = string | {
7
+ /**
8
+ * The hardware address of the interface.
9
+ */
10
+ mac: string;
11
+ } | {
12
+ /**
13
+ * The index of the interface.
14
+ */
15
+ index: number;
16
+ };
17
+ /**
18
+ * An address a listener of a network interface can be reached at.
19
+ */
20
+ export interface IListeningAddress {
21
+ /**
22
+ * The address (an IPv6 address without its scope).
23
+ */
24
+ address: string;
25
+ /**
26
+ * `'IPv4'` or `'IPv6'`.
27
+ */
28
+ family: 'IPv4' | 'IPv6';
29
+ /**
30
+ * The port.
31
+ */
32
+ port: number;
33
+ /**
34
+ * IPv6 only: the scope of the address (the index of the interface for a link-local one), 0 for a global one.
35
+ */
36
+ scopeid?: number;
37
+ }
38
+ /**
39
+ * The options of {@link IInterfaceListening.listenOnAll}.
40
+ */
41
+ export interface IInterfaceListenOptions {
42
+ /**
43
+ * The port; 0 lets the system choose one (for one interface only).
44
+ */
45
+ port: number;
46
+ /**
47
+ * The families listened on.
48
+ * @default 'both'
49
+ */
50
+ family?: 'IPv4' | 'IPv6' | 'both';
51
+ /**
52
+ * Refuse to listen where the connections reaching the addresses of the interface through another interface cannot
53
+ * be refused, and stop listening while they cannot. An implementation that never refuses them throws.
54
+ * @default false
55
+ */
56
+ strict?: boolean;
57
+ }
58
+ /**
59
+ * The listener of a network interface for a server: it accepts the connections of the interface and gives them to the
60
+ * server, whatever the addresses of the interface, again when they change and when the interface is unplugged and
61
+ * plugged back. The server itself does not listen. The `InterfaceListener` of `@lakutata/netif` implements it.
62
+ */
63
+ export interface IInterfaceListener {
64
+ /**
65
+ * The interface listened on: its name (`undefined` while it does not exist), and whether it is the loopback one.
66
+ */
67
+ readonly networkInterface: {
68
+ readonly name: string | undefined;
69
+ readonly loopback: boolean;
70
+ };
71
+ /**
72
+ * The port, `undefined` until a port 0 is given one by the system.
73
+ */
74
+ readonly port: number | undefined;
75
+ /**
76
+ * The addresses it can be reached at now: none while the interface does not exist or has no address.
77
+ */
78
+ readonly addresses: IListeningAddress[];
79
+ /**
80
+ * Whether the interface is listened on now.
81
+ */
82
+ readonly listening: boolean;
83
+ /**
84
+ * Whether the system refuses the connections that reach the addresses of the interface through another interface.
85
+ */
86
+ readonly enforced: boolean;
87
+ /**
88
+ * Why `enforced` is `false`, when it is.
89
+ */
90
+ readonly reason: string | undefined;
91
+ /**
92
+ * Whether it was closed.
93
+ */
94
+ readonly closed: boolean;
95
+ /**
96
+ * Stop listening; the connections established are left to the server.
97
+ */
98
+ close(): Promise<void>;
99
+ /**
100
+ * The interface is listened on, again after a change.
101
+ * @param event `'listening'`.
102
+ * @param listener Called without argument.
103
+ */
104
+ on(event: 'listening', listener: () => void): this;
105
+ /**
106
+ * A failure of listening again, retried by the listener; with `strict`, the guarantee lost.
107
+ * @param event `'error'`.
108
+ * @param listener Called with the error.
109
+ */
110
+ on(event: 'error', listener: (error: Error) => void): this;
111
+ /**
112
+ * `enforced` changed.
113
+ * @param event `'enforced'`.
114
+ * @param listener Called with its new value.
115
+ */
116
+ on(event: 'enforced', listener: (enforced: boolean) => void): this;
117
+ /**
118
+ * The listener was closed.
119
+ * @param event `'close'`.
120
+ * @param listener Called without argument.
121
+ */
122
+ on(event: 'close', listener: () => void): this;
123
+ /**
124
+ * Remove a listener of an event.
125
+ * @param event The event.
126
+ * @param listener The listener given to `on()`.
127
+ */
128
+ off(event: 'listening' | 'error' | 'enforced' | 'close', listener: (...args: any[]) => void): this;
129
+ }
130
+ /**
131
+ * Listening on network interfaces instead of addresses: what the entrypoints use for their `interface` option. The
132
+ * package `@lakutata/netif` implements it, and is the one used by default; another implementation is given in the
133
+ * `interfaceListening` option of the entrypoints.
134
+ * @example
135
+ * ```typescript
136
+ * import type {IInterfaceListener, IInterfaceListening} from 'lakutata'
137
+ * import {listenOnAll} from '@lakutata/netif'
138
+ *
139
+ * //An implementation that writes each listener made before giving it to the entrypoint
140
+ * const logged: IInterfaceListening = {
141
+ * listenOnAll: async (selectors, server, options): Promise<IInterfaceListener[]> => {
142
+ * const listeners: IInterfaceListener[] = await listenOnAll(selectors, server, options)
143
+ * for (const listener of listeners) console.log(listener.networkInterface.name, listener.addresses)
144
+ * return listeners
145
+ * }
146
+ * }
147
+ * ```
148
+ */
149
+ export interface IInterfaceListening {
150
+ /**
151
+ * Listen on interfaces for a server, one listener by interface; when one cannot be listened on, the others are
152
+ * closed and the error is thrown. An interface that does not exist yet is listened on once it appears.
153
+ * @param selectors The interfaces; several need a port other than 0.
154
+ * @param server The server the connections are given to.
155
+ * @param options The port, the families, and whether the other interfaces must be refused.
156
+ * @returns The listeners, in the order of the interfaces.
157
+ */
158
+ listenOnAll(selectors: NetworkInterfaceSelector | NetworkInterfaceSelector[], server: Server, options: IInterfaceListenOptions): Promise<IInterfaceListener[]>;
159
+ }
@@ -49,7 +49,7 @@ export declare enum ApplicationState {
49
49
  * and `run()` have no effect then):
50
50
  * 1. the environment variables are set, the aliases registered (`@runtime` being the working directory);
51
51
  * 2. the options are read (from the getter given to `run()`, which may be async) and validated, the identity applied
52
- * (`process.title` is the application ID, the time zone and the mode set);
52
+ * (`process.title` is the application ID, except in NW.js; the time zone and the mode set);
53
53
  * 3. the application is created in a new root container: its objects are registered and its bootstrap runs in order;
54
54
  * 4. the `onLaunched()` handlers and the objects' `launched()` hooks run.
55
55
  *
@@ -27,8 +27,8 @@ import { ModuleOptions } from './ModuleOptions.js';
27
27
  */
28
28
  export declare class ApplicationOptions extends ModuleOptions {
29
29
  /**
30
- * The ID of the application (`'shop.app'`): the `process.title`, read by `Application.appId` and stamped on the
31
- * exceptions. Required.
30
+ * The ID of the application (`'shop.app'`), read by `Application.appId` and stamped on the exceptions; the
31
+ * `process.title` too, except in NW.js. Required.
32
32
  */
33
33
  readonly id: string;
34
34
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakutata/core",
3
- "version": "3.1.1",
3
+ "version": "3.1.5",
4
4
  "description": "The core of the lakutata framework: the IoC container and dependency injection, the object model with asynchronous construction, and DTO schema validation with the API of joi.",
5
5
  "keywords": [
6
6
  "lakutata",
@@ -107,11 +107,11 @@
107
107
  "test:engine-types": "node scripts/check-engine-types.mjs"
108
108
  },
109
109
  "dependencies": {
110
- "@lakutata/utils": "3.1.1",
110
+ "@lakutata/utils": "3.1.5",
111
111
  "reflect-metadata": "0.2.2"
112
112
  },
113
113
  "engines": {
114
- "node": "^22.13.0 || >=24.11.0"
114
+ "node": ">=18.10.0"
115
115
  },
116
116
  "files": [
117
117
  "dist",