@lakutata/core 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +29 -22
  3. package/README.zh-CN.md +182 -0
  4. package/dist/cjs/decorators/dto/Expect.d.ts +2 -1
  5. package/dist/cjs/decorators/dto/Expect.js +2 -1
  6. package/dist/cjs/dto/PaginationSearchDTO.d.ts +6 -4
  7. package/dist/cjs/dto/PaginationSearchDTO.js +5 -4
  8. package/dist/cjs/exceptions/dto/InvalidValueException.d.ts +4 -2
  9. package/dist/cjs/exceptions/dto/InvalidValueException.js +4 -2
  10. package/dist/cjs/lib/app/Application.d.ts +22 -0
  11. package/dist/cjs/lib/app/Application.js +56 -0
  12. package/dist/cjs/lib/base/BaseObject.js +3 -1
  13. package/dist/cjs/lib/base/internal/ConfigurableRecordsInjection.d.ts +7 -7
  14. package/dist/cjs/lib/base/internal/ConfigurableRecordsInjection.js +10 -14
  15. package/dist/cjs/lib/core/Container.js +3 -2
  16. package/dist/cjs/lib/dto/DTO.d.ts +3 -1
  17. package/dist/cjs/lib/dto/DTO.js +76 -5
  18. package/dist/cjs/lib/foundation/Exception.d.ts +2 -1
  19. package/dist/cjs/lib/foundation/Exception.js +27 -7
  20. package/dist/esm/decorators/dto/Expect.js +2 -1
  21. package/dist/esm/dto/PaginationSearchDTO.js +5 -4
  22. package/dist/esm/exceptions/dto/InvalidValueException.js +4 -2
  23. package/dist/esm/lib/app/Application.js +56 -0
  24. package/dist/esm/lib/base/BaseObject.js +3 -1
  25. package/dist/esm/lib/base/internal/ConfigurableRecordsInjection.js +10 -14
  26. package/dist/esm/lib/core/Container.js +3 -2
  27. package/dist/esm/lib/dto/DTO.js +76 -5
  28. package/dist/esm/lib/foundation/Exception.js +27 -7
  29. package/dist/types/decorators/dto/Expect.d.ts +2 -1
  30. package/dist/types/dto/PaginationSearchDTO.d.ts +6 -4
  31. package/dist/types/exceptions/dto/InvalidValueException.d.ts +4 -2
  32. package/dist/types/lib/app/Application.d.ts +22 -0
  33. package/dist/types/lib/base/internal/ConfigurableRecordsInjection.d.ts +7 -7
  34. package/dist/types/lib/dto/DTO.d.ts +3 -1
  35. package/dist/types/lib/foundation/Exception.d.ts +2 -1
  36. package/package.json +28 -4
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Lakutata
4
+
5
+ All rights reserved
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,16 @@
1
1
  # @lakutata/core
2
2
 
3
+ English | [中文](README.zh-CN.md)
4
+
3
5
  The core of the lakutata framework: the IoC container and dependency injection, the object model (`BaseObject`,
4
- `Component`, `Provider`, `Module`, `Application`) with its asynchronous construction and destruction, and the schema
5
- validation (`DTO`, built on joi) used by the decorators `@Configurable`, `@Expect`, `@Accept` and `@Return`.
6
+ `Component`, `Provider`, `Application`) with its asynchronous construction and destruction, and schema validation
7
+ (`DTO`, a validation engine with the API of joi), which the `@Configurable`, `@Expect`, `@Accept` and `@Return`
8
+ decorators use.
6
9
 
7
10
  Most applications install the umbrella package `lakutata`, which re-exports this package (`lakutata`,
8
- `lakutata/decorator/di`, `lakutata/decorator/dto`, `lakutata/decorator/asst`, `lakutata/dtos`) and presets a pino `log`
9
- component and the `entrypoint` component. `@lakutata/core` alone is enough for a library or an application without
10
- them (it logs to the console then).
11
+ `lakutata/decorator/di`, `lakutata/decorator/dto`, `lakutata/decorator/asst`, `lakutata/dtos`) and presets the `log`
12
+ and `entrypoint` components. `@lakutata/core` alone is enough for a library, or for an application that needs neither
13
+ (it then logs to the console).
11
14
 
12
15
  ## Installation
13
16
 
@@ -16,7 +19,8 @@ npm install lakutata # the framework
16
19
  npm install @lakutata/core # or the core only
17
20
  ```
18
21
 
19
- Node.js 22 or later, ESM (`"type": "module"`). The TypeScript options the decorators need:
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:
20
24
 
21
25
  ```json
22
26
  {
@@ -33,8 +37,8 @@ Node.js 22 or later, ESM (`"type": "module"`). The TypeScript options the decora
33
37
 
34
38
  ### An application
35
39
 
36
- An application is a tree of objects created by its container. `Application.run()` validates its options, registers its
37
- components and providers, creates the objects listed in `bootstrap`, then calls `onLaunched`.
40
+ An application is a tree of objects that its container creates. `Application.run()` validates the application's
41
+ options, registers its components and providers, creates the objects listed in `bootstrap`, then calls `onLaunched`.
38
42
 
39
43
  ```typescript
40
44
  import {Application, Component, DTO, type ILogger} from 'lakutata'
@@ -42,7 +46,7 @@ import {Configurable, Inject} from 'lakutata/decorator/di'
42
46
 
43
47
  //A component: a singleton of the application, created when it is first needed (or by bootstrap)
44
48
  class Mailer extends Component {
45
- //Its option, validated when it is created: given by the application options below
49
+ //An option, validated when the component is created; its value comes from the application options below
46
50
  @Configurable(DTO.String().required())
47
51
  protected readonly host: string
48
52
 
@@ -90,14 +94,14 @@ Application
90
94
 
91
95
  ### Dependency injection
92
96
 
93
- `@Inject()` injects the object registered under the property name, `@Inject('name')` under another name,
94
- `@Inject(SomeClass)` an instance of a class (registered automatically when the class is marked `@Autoload()`). The
95
- injected objects are set before `init()` runs.
97
+ `@Inject()` injects the object registered under the property's name, `@Inject('name')` the one registered under
98
+ another name, and `@Inject(SomeClass)` an instance of a class (registered automatically when the class is marked
99
+ `@Autoload()`). Injected objects are set before `init()` runs.
96
100
 
97
101
  ### Validation
98
102
 
99
- A `DTO` subclass declares its fields with `@Expect(schema)`. The schemas are built by the static methods of `DTO`
100
- (`DTO.String()`, `DTO.Number()`, `DTO.Object()`, `DTO.Array()`, `DTO.Alternatives()`…, the joi API):
103
+ A `DTO` subclass declares its fields with `@Expect(schema)`. Schemas are built with the static methods of `DTO`
104
+ (`DTO.String()`, `DTO.Number()`, `DTO.Object()`, `DTO.Array()`, `DTO.Alternatives()`…), which follow the joi API:
101
105
 
102
106
  ```typescript
103
107
  import {DTO} from 'lakutata'
@@ -118,8 +122,8 @@ class Users {
118
122
  }
119
123
  }
120
124
 
121
- //Validated directly: the value with its defaults ({email: 'a@example.com', credit: 0}), InvalidValueException thrown
122
- //when it is invalid
125
+ //Validated directly: resolves to the value with its defaults ({email: 'a@example.com', credit: 0}), or rejects with
126
+ //InvalidValueException when the value is invalid
123
127
  const input = await CreateUserDTO.validateAsync({email: 'a@example.com'})
124
128
  ```
125
129
 
@@ -138,7 +142,7 @@ class Mailer extends Component {
138
142
  }
139
143
  }
140
144
 
141
- //Created with its options, its init() awaited
145
+ //Created with its options; set() resolves once its init() has completed
142
146
  const container: Container = new Container()
143
147
  const mailer: Mailer = await container.set(Mailer, {host: 'localhost'})
144
148
  await mailer.send('someone@example.com', 'Hello')
@@ -151,7 +155,7 @@ await container.destroy()
151
155
  | Export | Subpath | What it is |
152
156
  |---|---|---|
153
157
  | `Application` | `lakutata` | The application: `run()`, `env()`, `alias()`, `onLaunched()`, `onDone()`, `onFatalException()`, `getObject()`, `exit()` |
154
- | `Module` | `lakutata` | A group of components, providers and objects with its own container |
158
+ | `Module` | `lakutata` | The base class of `Application`: the container and the declared objects live here |
155
159
  | `Component` | `lakutata` | A singleton service with events (`emit`, `on`…) |
156
160
  | `Provider` | `lakutata` | A scoped object (one per scope) |
157
161
  | `BaseObject` | `lakutata` | The root class: asynchronous construction (`init`), destruction (`destroy`), `getObject` |
@@ -164,7 +168,7 @@ await container.destroy()
164
168
  | `Expect`, `Accept`, `Return`, `IndexSignature` | `lakutata/decorator/dto` | DTO fields, method argument and return validation |
165
169
  | `Before`, `After` | `lakutata/decorator/asst` | Functions run before and after a method |
166
170
  | `PaginationSearchDTO`, `PaginationResultDTO`, `paginator`, `FlexibleDTO`, `toJsonSchema`, `fromJsonSchema` | `lakutata/dtos` | Pagination, open DTOs and JSON Schema conversion |
167
- | `ConsoleLogger`, `ILogger` | `lakutata/com/logger` | The logger interface and its console implementation |
171
+ | `ConsoleLogger`, `ILogger` | `@lakutata/core/com/logger` | The logger interface and its console implementation |
168
172
 
169
173
  The typings document each declaration with examples.
170
174
 
@@ -183,7 +187,10 @@ The typings document each declaration with examples.
183
187
 
184
188
  ## See also
185
189
 
186
- - The guides: `doc/en/Core.md`, `doc/en/DTOValidation.md`, `doc/en/Decorator.md` of the package `lakutata` (Chinese
187
- in `doc/zh`).
190
+ - The guides on the website: [Objects and Lifetimes](https://lakutata.com/guide/Objects),
191
+ [Dependency Injection](https://lakutata.com/guide/Injection) and
192
+ [DTO and Validation](https://lakutata.com/guide/DTOValidation).
193
+ - The guides `doc/en/Application.md`, `doc/en/Objects.md`, `doc/en/Injection.md`, `doc/en/DTOValidation.md` and
194
+ `doc/en/Decorator.md` in the `lakutata` package (Chinese in `doc/zh`).
188
195
  - `@lakutata/entrypoint`: the controllers and their HTTP, CLI and service transports.
189
- - `@lakutata/logger`: the pino logger preset by `lakutata`.
196
+ - `@lakutata/logger`: the logger that `lakutata` presets as `log` (pino-compatible JSON lines).
@@ -0,0 +1,182 @@
1
+ # @lakutata/core
2
+
3
+ [English](README.md) | 中文
4
+
5
+ lakutata 框架的核心:IoC 容器和依赖注入,对象模型(`BaseObject`、`Component`、`Provider`、`Application`)及其异步的创建与销毁,以及 `@Configurable`、`@Expect`、`@Accept` 和 `@Return` 装饰器所用的数据校验(`DTO`,一个 API 和 joi 一样的校验引擎)。
6
+
7
+ 大多数应用安装的是 `lakutata` 包:它重新导出这个包(`lakutata`、`lakutata/decorator/di`、`lakutata/decorator/dto`、`lakutata/decorator/asst`、`lakutata/dtos`),并预置 `log` 和 `entrypoint` 组件。对于库,或者这两个组件都不需要的应用,只用 `@lakutata/core` 就够了(此时日志输出到控制台)。
8
+
9
+ ## 安装
10
+
11
+ ```shell
12
+ npm install lakutata # 框架
13
+ npm install @lakutata/core # 或者只安装核心
14
+ ```
15
+
16
+ 这个包需要 Node.js `^22.13.0 || >=24.11.0`。项目使用 ES 模块(`"type": "module"`);包里也带有 CommonJS 构建。装饰器需要以下 TypeScript 选项:
17
+
18
+ ```json
19
+ {
20
+ "compilerOptions": {
21
+ "module": "Node16",
22
+ "moduleResolution": "Node16",
23
+ "experimentalDecorators": true,
24
+ "emitDecoratorMetadata": true
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## 用法
30
+
31
+ ### 一个应用
32
+
33
+ 应用是一棵由它的容器创建的对象树。`Application.run()` 校验应用的选项,注册它的组件和提供者,创建 `bootstrap` 中列出的对象,然后调用 `onLaunched`。
34
+
35
+ ```typescript
36
+ import {Application, Component, DTO, type ILogger} from 'lakutata'
37
+ import {Configurable, Inject} from 'lakutata/decorator/di'
38
+
39
+ //组件:应用中的单例,在第一次被需要时创建(或者由 bootstrap 创建)
40
+ class Mailer extends Component {
41
+ //一个选项,在组件创建时校验;它的值来自下面的应用选项
42
+ @Configurable(DTO.String().required())
43
+ protected readonly host: string
44
+
45
+ @Inject('log')
46
+ protected readonly log: ILogger
47
+
48
+ //注入的对象和选项都设置好之后调用
49
+ protected async init(): Promise<void> {
50
+ this.log.info('Mailer connected to %s', this.host)
51
+ }
52
+
53
+ public async send(to: string, text: string): Promise<void> {
54
+ this.log.info('Mail to %s: %s', to, text)
55
+ }
56
+
57
+ //应用停止时调用
58
+ protected async destroy(): Promise<void> {
59
+ }
60
+ }
61
+
62
+ Application
63
+ .run(() => ({
64
+ id: 'com.example.app',
65
+ name: 'Example',
66
+ components: {
67
+ mailer: {class: Mailer, host: 'smtp.example.com'}
68
+ },
69
+ bootstrap: ['mailer']
70
+ }))
71
+ .onLaunched(async (app: Application, log: ILogger): Promise<void> => {
72
+ const mailer: Mailer = await app.getObject('mailer')
73
+ await mailer.send('someone@example.com', 'Hello')
74
+ log.info('%s started', app.appName)
75
+ })
76
+ .onFatalException((error: Error, log: ILogger): void => log.error(error.message))
77
+ ```
78
+
79
+ ### 生命周期
80
+
81
+ | 装饰器 | 实例数量 | 默认用于 |
82
+ |---|---|---|
83
+ | `@Singleton()` | 每个容器一个 | `Component` |
84
+ | `@Scoped()` | 每个作用域一个(`container.createScope()`,例如一次请求) | `Provider`(包括控制器) |
85
+ | `@Transient()` | 每次解析一个 | `BaseObject` |
86
+
87
+ ### 依赖注入
88
+
89
+ `@Inject()` 注入以属性名注册的对象,`@Inject('name')` 注入以另一个名称注册的对象,`@Inject(SomeClass)` 注入一个类的实例(类标记了 `@Autoload()` 时会自动注册)。注入的对象在 `init()` 执行之前就已设置好。
90
+
91
+ ### 校验
92
+
93
+ `DTO` 的子类用 `@Expect(schema)` 声明它的字段。校验规则由 `DTO` 的静态方法构建(`DTO.String()`、`DTO.Number()`、`DTO.Object()`、`DTO.Array()`、`DTO.Alternatives()`……),API 与 joi 相同:
94
+
95
+ ```typescript
96
+ import {DTO} from 'lakutata'
97
+ import {Accept, Expect} from 'lakutata/decorator/dto'
98
+
99
+ class CreateUserDTO extends DTO {
100
+ @Expect(DTO.String().email().required())
101
+ public email: string
102
+
103
+ @Expect(DTO.Number().integer().min(0).default(0))
104
+ public credit: number
105
+ }
106
+
107
+ class Users {
108
+ //方法执行之前校验参数(并填充默认值)
109
+ @Accept(CreateUserDTO.required())
110
+ public async create(input: CreateUserDTO): Promise<void> {
111
+ }
112
+ }
113
+
114
+ //直接校验:得到补上默认值后的对象({email: 'a@example.com', credit: 0});值不合法时
115
+ //以 InvalidValueException 拒绝
116
+ const input = await CreateUserDTO.validateAsync({email: 'a@example.com'})
117
+ ```
118
+
119
+ ### 测试与库:不使用应用的容器
120
+
121
+ ```typescript
122
+ import {Component, Container, DTO} from 'lakutata'
123
+ import {Configurable} from 'lakutata/decorator/di'
124
+
125
+ class Mailer extends Component {
126
+ @Configurable(DTO.String().required())
127
+ protected readonly host: string
128
+
129
+ public async send(to: string, text: string): Promise<string> {
130
+ return `${this.host} -> ${to}: ${text}`
131
+ }
132
+ }
133
+
134
+ //按选项创建;set() 会等到它的 init() 完成
135
+ const container: Container = new Container()
136
+ const mailer: Mailer = await container.set(Mailer, {host: 'localhost'})
137
+ await mailer.send('someone@example.com', 'Hello')
138
+ //销毁它创建的对象
139
+ await container.destroy()
140
+ ```
141
+
142
+ ## API
143
+
144
+ | 导出 | 子路径 | 说明 |
145
+ |---|---|---|
146
+ | `Application` | `lakutata` | 应用:`run()`、`env()`、`alias()`、`onLaunched()`、`onDone()`、`onFatalException()`、`getObject()`、`exit()` |
147
+ | `Module` | `lakutata` | `Application` 的基类:容器和声明的对象都在这里 |
148
+ | `Component` | `lakutata` | 带事件(`emit`、`on`……)的单例服务 |
149
+ | `Provider` | `lakutata` | 作用域对象(每个作用域一个) |
150
+ | `BaseObject` | `lakutata` | 根类:异步构造(`init`)、销毁(`destroy`)、`getObject` |
151
+ | `Container` | `lakutata` | IoC 容器:`set`、`get`、`getShared`、`register`、`createScope`、`destroy` |
152
+ | `DTO` | `lakutata` | 带校验的数据类,以及构建校验规则的方法 |
153
+ | `Exception` | `lakutata` | 框架中各种异常的基类(`errno`、`statusCode`) |
154
+ | `isPackaged`、`resourcesPath` | `lakutata` | 应用是否以打包形式运行,以及它的资源目录 |
155
+ | `registerValidationMessages`、`runInValidationLanguage`…… | `lakutata` | 校验消息的语言 |
156
+ | `Inject`、`Configurable`、`Autoload`、`Singleton`、`Scoped`、`Transient`、`Lifetime` | `lakutata/decorator/di` | 依赖注入与生命周期 |
157
+ | `Expect`、`Accept`、`Return`、`IndexSignature` | `lakutata/decorator/dto` | DTO 字段,以及方法参数和返回值的校验 |
158
+ | `Before`、`After` | `lakutata/decorator/asst` | 在方法之前和之后执行的函数 |
159
+ | `PaginationSearchDTO`、`PaginationResultDTO`、`paginator`、`FlexibleDTO`、`toJsonSchema`、`fromJsonSchema` | `lakutata/dtos` | 分页、接受任意字段的 DTO、JSON Schema 转换 |
160
+ | `ConsoleLogger`、`ILogger` | `@lakutata/core/com/logger` | 日志记录器接口及其控制台实现 |
161
+
162
+ 类型声明中的每一项都附有说明和示例。
163
+
164
+ ## 错误
165
+
166
+ | 异常 | 什么时候抛出 |
167
+ |---|---|
168
+ | `InvalidValueException` | 值不符合它的校验规则(`DTO.validate`、`@Configurable`、`@Expect`) |
169
+ | `InvalidMethodAcceptException`、`InvalidMethodReturnException` | 方法的参数(`@Accept`)或返回值(`@Return`)不合法 |
170
+ | `DependencyInjectionException` | 要注入的对象无法解析 |
171
+ | `InvalidObjectOptionsException`、`InvalidObjectTypeException` | 所声明对象的选项或类不合法 |
172
+ | `LifetimeLockedException` | 类的生命周期锁定之后又被修改 |
173
+ | `ObjectDestroyedException` | 使用了已销毁的对象 |
174
+ | `AliasExistsException`、`AliasNotFoundException`、`InvalidAliasNameException` | 路径别名重复声明、不存在或不合法 |
175
+ | `MethodNotFoundException` | 按名称调用的方法不存在 |
176
+
177
+ ## 另见
178
+
179
+ - 网站上的指南:[对象与生命周期](https://lakutata.com/zh/guide/Objects)、[依赖注入](https://lakutata.com/zh/guide/Injection)、[DTO 与数据校验](https://lakutata.com/zh/guide/DTOValidation)。
180
+ - `lakutata` 包中的指南 `doc/zh/Application.md`、`doc/zh/Objects.md`、`doc/zh/Injection.md`、`doc/zh/DTOValidation.md` 和 `doc/zh/Decorator.md`。
181
+ - `@lakutata/entrypoint`:控制器,以及它们的 HTTP、命令行和服务调用传输方式。
182
+ - `@lakutata/logger`:`lakutata` 预置为 `log` 的日志记录器(与 pino 兼容的 JSON 行)。
@@ -5,7 +5,8 @@ import { Schema } from '../../lib/validation/types/Schema.js';
5
5
  * Declare the schema of a property of a DTO class. The DTO validates the property against it when it is constructed
6
6
  * (the whole object at once: defaults applied, the keys without schema removed unless the class has an
7
7
  * `@IndexSignature`), and each time the property, or a value nested in it, is changed afterwards (the change is undone
8
- * when invalid). The schema is also part of the class schema (`MyDTO.Schema()`) used by `@Accept`, `@Return` and the
8
+ * when invalid). With `DTO.Any()`, an object of another class the property holds is given as it is, not watched: no
9
+ * change of it can be invalid. The schema is also part of the class schema (`MyDTO.Schema()`) used by `@Accept`, `@Return` and the
9
10
  * actions' input validation. Subclasses inherit the property schemas, and may declare a property again to replace its
10
11
  * schema. Ignored on a symbol property or on a class which is not a DTO.
11
12
  * @param schema The schema of the property, built with the static methods of `DTO` (`DTO.String().required()`); a
@@ -6,7 +6,8 @@ const ObjectSchemaValidation_js_1 = require("../../lib/dto/ObjectSchemaValidatio
6
6
  * Declare the schema of a property of a DTO class. The DTO validates the property against it when it is constructed
7
7
  * (the whole object at once: defaults applied, the keys without schema removed unless the class has an
8
8
  * `@IndexSignature`), and each time the property, or a value nested in it, is changed afterwards (the change is undone
9
- * when invalid). The schema is also part of the class schema (`MyDTO.Schema()`) used by `@Accept`, `@Return` and the
9
+ * when invalid). With `DTO.Any()`, an object of another class the property holds is given as it is, not watched: no
10
+ * change of it can be invalid. The schema is also part of the class schema (`MyDTO.Schema()`) used by `@Accept`, `@Return` and the
10
11
  * actions' input validation. Subclasses inherit the property schemas, and may declare a property again to replace its
11
12
  * schema. Ignored on a symbol property or on a class which is not a DTO.
12
13
  * @param schema The schema of the property, built with the static methods of `DTO` (`DTO.String().required()`); a
@@ -1,7 +1,8 @@
1
1
  import { DTO } from '../lib/dto/DTO.js';
2
2
  /**
3
- * The input of a paginated search: how many items to return and how many to skip. Extend it with the search criteria,
4
- * and pass the validated input to `paginator()`.
3
+ * The input of a paginated search: how many items to return and how many to skip, numbers or their text (the query
4
+ * string of a request, `?limit=20&offset=40`). Extend it with the search criteria, and pass the validated input to
5
+ * `paginator()`.
5
6
  * @example
6
7
  * ```typescript
7
8
  * import {DTO} from 'lakutata'
@@ -21,12 +22,13 @@ import { DTO } from '../lib/dto/DTO.js';
21
22
  */
22
23
  export declare class PaginationSearchDTO extends DTO {
23
24
  /**
24
- * The maximum number of items to return: an integer, 0 or more.
25
+ * The maximum number of items to return: an integer, 0 or more (its text too, as a query string gives it).
25
26
  * @default 10
26
27
  */
27
28
  limit?: number;
28
29
  /**
29
- * The number of items to skip before the first one returned: an integer, 0 or more.
30
+ * The number of items to skip before the first one returned: an integer, 0 or more (its text too, as a query string
31
+ * gives it).
30
32
  * @default 0
31
33
  */
32
34
  offset?: number;
@@ -13,8 +13,9 @@ exports.PaginationSearchDTO = void 0;
13
13
  const DTO_js_1 = require("../lib/dto/DTO.js");
14
14
  const Expect_js_1 = require("../decorators/dto/Expect.js");
15
15
  /**
16
- * The input of a paginated search: how many items to return and how many to skip. Extend it with the search criteria,
17
- * and pass the validated input to `paginator()`.
16
+ * The input of a paginated search: how many items to return and how many to skip, numbers or their text (the query
17
+ * string of a request, `?limit=20&offset=40`). Extend it with the search criteria, and pass the validated input to
18
+ * `paginator()`.
18
19
  * @example
19
20
  * ```typescript
20
21
  * import {DTO} from 'lakutata'
@@ -36,10 +37,10 @@ class PaginationSearchDTO extends DTO_js_1.DTO {
36
37
  }
37
38
  exports.PaginationSearchDTO = PaginationSearchDTO;
38
39
  __decorate([
39
- (0, Expect_js_1.Expect)(DTO_js_1.DTO.Number().integer().positive().allow(0).optional().default(10)),
40
+ (0, Expect_js_1.Expect)(DTO_js_1.DTO.Number().integer().positive().allow(0).strict(false).optional().default(10)),
40
41
  __metadata("design:type", Number)
41
42
  ], PaginationSearchDTO.prototype, "limit", void 0);
42
43
  __decorate([
43
- (0, Expect_js_1.Expect)(DTO_js_1.DTO.Number().integer().positive().allow(0).optional().default(0)),
44
+ (0, Expect_js_1.Expect)(DTO_js_1.DTO.Number().integer().positive().allow(0).strict(false).optional().default(0)),
44
45
  __metadata("design:type", Number)
45
46
  ], PaginationSearchDTO.prototype, "offset", void 0);
@@ -3,9 +3,11 @@ import { Exception } from '../../lib/foundation/Exception.js';
3
3
  * Thrown when a value does not match its schema: by `DTO.validate()` and `DTO.validateAsync()`, by the DTO constructor
4
4
  * and the assignments to a DTO property, and by the creation of an object whose `@Configurable` value is invalid. Its
5
5
  * message describes the first error; its causes lead to the `SchemaValidationException` holding the details (the path
6
- * and the rule of each error). Its `errno` is `E_INVALID_VALUE`; the HTTP entrypoints answer an invalid action input with the
7
- * status 400, JSON-RPC with the error "invalid params".
6
+ * and the rule of each error). Its `errno` is `E_INVALID_VALUE` and its `statusCode` 400, a value refused being an
7
+ * error of the caller: the HTTP entrypoints answer an invalid action input with the status 400, the service transports
8
+ * send the error with it, JSON-RPC answers with the error "invalid params".
8
9
  */
9
10
  export declare class InvalidValueException extends Exception {
10
11
  errno: string | number;
12
+ readonly statusCode: number;
11
13
  }
@@ -6,13 +6,15 @@ const Exception_js_1 = require("../../lib/foundation/Exception.js");
6
6
  * Thrown when a value does not match its schema: by `DTO.validate()` and `DTO.validateAsync()`, by the DTO constructor
7
7
  * and the assignments to a DTO property, and by the creation of an object whose `@Configurable` value is invalid. Its
8
8
  * message describes the first error; its causes lead to the `SchemaValidationException` holding the details (the path
9
- * and the rule of each error). Its `errno` is `E_INVALID_VALUE`; the HTTP entrypoints answer an invalid action input with the
10
- * status 400, JSON-RPC with the error "invalid params".
9
+ * and the rule of each error). Its `errno` is `E_INVALID_VALUE` and its `statusCode` 400, a value refused being an
10
+ * error of the caller: the HTTP entrypoints answer an invalid action input with the status 400, the service transports
11
+ * send the error with it, JSON-RPC answers with the error "invalid params".
11
12
  */
12
13
  class InvalidValueException extends Exception_js_1.Exception {
13
14
  constructor() {
14
15
  super(...arguments);
15
16
  this.errno = 'E_INVALID_VALUE';
17
+ this.statusCode = 400;
16
18
  }
17
19
  }
18
20
  exports.InvalidValueException = InvalidValueException;
@@ -1,4 +1,5 @@
1
1
  import { Module } from '../core/Module.js';
2
+ import { __destroy } from '../base/BaseObject.js';
2
3
  import { ApplicationConfigLoader } from './ApplicationConfigLoader.js';
3
4
  import { ApplicationOptions } from '../../options/ApplicationOptions.js';
4
5
  import { Alias } from '../core/Alias.js';
@@ -270,6 +271,20 @@ export declare class Application extends Module {
270
271
  * @protected
271
272
  */
272
273
  protected static listenShutdownSignals(signals: NodeJS.Signals[]): void;
274
+ /**
275
+ * End the launched application gracefully once nothing keeps the process busy (no server, connection or timer
276
+ * left): its exit, with the exit code the process has (`process.exitCode`, 0 by default), as with `exit()`. A
277
+ * process ending so does not wait for an exit, so the done handlers and the teardown would not run otherwise.
278
+ * @param app The launched application.
279
+ * @protected
280
+ */
281
+ protected static listenNaturalEnd(app: Application): void;
282
+ /**
283
+ * Stop ending the application with the process: it exits, or it is destroyed (disposed by a test, which leaves the
284
+ * end of the process to its runner)
285
+ * @protected
286
+ */
287
+ protected static stopListeningNaturalEnd(): void;
273
288
  /**
274
289
  * Launch the application: environment variables, aliases, options (or the database migration command given by the
275
290
  * `MIGRATION_*` environment variables), then the application created in a new root container.
@@ -328,6 +343,13 @@ export declare class Application extends Module {
328
343
  * variable.
329
344
  */
330
345
  get timezone(): string;
346
+ /**
347
+ * The framework's destroyer, see {@link Module}: a destroyed application no longer ends with the process.
348
+ * @param hooks The steps the framework's subclasses run before `destroy()`.
349
+ * @returns Resolves once the application and its objects are destroyed.
350
+ * @protected
351
+ */
352
+ protected [__destroy](...hooks: (() => Promise<void>)[]): Promise<void>;
331
353
  /**
332
354
  * The time the process has been running, in seconds (rounded down).
333
355
  */
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.Application = exports.ApplicationState = void 0;
16
16
  const Module_js_1 = require("../core/Module.js");
17
+ const BaseObject_js_1 = require("../base/BaseObject.js");
17
18
  const Container_js_1 = require("../core/Container.js");
18
19
  const ApplicationConfigLoader_js_1 = require("./ApplicationConfigLoader.js");
19
20
  const ApplicationOptions_js_1 = require("../../options/ApplicationOptions.js");
@@ -136,6 +137,16 @@ class Application extends Module_js_1.Module {
136
137
  * @private
137
138
  */
138
139
  static #consoleLogger;
140
+ /**
141
+ * Whether the application is exiting: a step of its bootstrap that exits it (a command of the migrations) ends its
142
+ * launch
143
+ */
144
+ static #exiting = false;
145
+ /**
146
+ * The listener of the end of the process once nothing keeps it busy (beforeExit): the launched application exits
147
+ * gracefully then, as with exit()
148
+ */
149
+ static #naturalEnd;
139
150
  /**
140
151
  * Preset the options of the applications, before they run: the packages assembling the framework preset their
141
152
  * components (the `lakutata` package presets its Logger as `log` and its Entrypoint as `entrypoint`). An object
@@ -374,6 +385,36 @@ class Application extends Module_js_1.Module {
374
385
  });
375
386
  }
376
387
  }
388
+ /**
389
+ * End the launched application gracefully once nothing keeps the process busy (no server, connection or timer
390
+ * left): its exit, with the exit code the process has (`process.exitCode`, 0 by default), as with `exit()`. A
391
+ * process ending so does not wait for an exit, so the done handlers and the teardown would not run otherwise.
392
+ * @param app The launched application.
393
+ * @protected
394
+ */
395
+ static listenNaturalEnd(app) {
396
+ Application.stopListeningNaturalEnd();
397
+ const listener = () => {
398
+ Application.stopListeningNaturalEnd();
399
+ //Exiting already (a signal, exit()): its teardown runs
400
+ if (Application.#exiting)
401
+ return;
402
+ const code = Number(process.exitCode ?? 0);
403
+ Application.eventEmitter.emit('exit', app, Number.isInteger(code) ? code : 1);
404
+ };
405
+ Application.#naturalEnd = listener;
406
+ process.once('beforeExit', listener);
407
+ }
408
+ /**
409
+ * Stop ending the application with the process: it exits, or it is destroyed (disposed by a test, which leaves the
410
+ * end of the process to its runner)
411
+ * @protected
412
+ */
413
+ static stopListeningNaturalEnd() {
414
+ if (Application.#naturalEnd)
415
+ process.removeListener('beforeExit', Application.#naturalEnd);
416
+ Application.#naturalEnd = undefined;
417
+ }
377
418
  /**
378
419
  * Launch the application: environment variables, aliases, options (or the database migration command given by the
379
420
  * `MIGRATION_*` environment variables), then the application created in a new root container.
@@ -382,6 +423,7 @@ class Application extends Module_js_1.Module {
382
423
  */
383
424
  static async launchApplication() {
384
425
  this.eventEmitter.once('exit', async (app, exitCode, signal) => {
426
+ this.#exiting = true;
385
427
  try {
386
428
  //The done handler, told why the application exits
387
429
  const reason = signal ? { code: exitCode ? exitCode : 0, signal: signal } : { code: exitCode ? exitCode : 0 };
@@ -497,6 +539,9 @@ class Application extends Module_js_1.Module {
497
539
  if (applicationOptions.shutdownSignals)
498
540
  this.listenShutdownSignals(applicationOptions.shutdownSignals);
499
541
  applicationOptions.bootstrap?.push(async (target) => {
542
+ //Exited by a step of the bootstrap: its container is being destroyed, it does not launch
543
+ if (Application.#exiting)
544
+ return;
500
545
  const launchedCallbackRemover = function () {
501
546
  this.options.bootstrap?.pop();
502
547
  (0, As_1.As)(Reflect.getOwnMetadata('#bootstrap', this))?.pop();
@@ -508,6 +553,7 @@ class Application extends Module_js_1.Module {
508
553
  (0, ApplicationLaunch_js_1.markApplicationLaunched)(target);
509
554
  //The application launched is the one the signals end (its launched handlers may receive them)
510
555
  Reflect.set(this, 'appInstance', target);
556
+ Application.listenNaturalEnd((0, As_1.As)(target));
511
557
  this.eventEmitter.emit(ApplicationState.Launched, (0, As_1.As)(target));
512
558
  });
513
559
  //Resolved once the application is initialized (its objects loaded, its bootstrap run), rejected with
@@ -582,6 +628,16 @@ class Application extends Module_js_1.Module {
582
628
  get timezone() {
583
629
  return (0, BasicInfo_js_1.getBasicInfo)().timezone;
584
630
  }
631
+ /**
632
+ * The framework's destroyer, see {@link Module}: a destroyed application no longer ends with the process.
633
+ * @param hooks The steps the framework's subclasses run before `destroy()`.
634
+ * @returns Resolves once the application and its objects are destroyed.
635
+ * @protected
636
+ */
637
+ async [BaseObject_js_1.__destroy](...hooks) {
638
+ Application.stopListeningNaturalEnd();
639
+ return await super[BaseObject_js_1.__destroy](...hooks);
640
+ }
585
641
  /**
586
642
  * The time the process has been running, in seconds (rounded down).
587
643
  */
@@ -559,7 +559,9 @@ let BaseObject = BaseObject_1 = class BaseObject extends AsyncConstructor_js_1.A
559
559
  */
560
560
  getMethod(name, throwExceptionIfNotFound = false) {
561
561
  if (this.hasMethod(name)) {
562
- return (...args) => this[name](...args);
562
+ //Named: else V8 names it after the code below in the stack traces (MethodNotFoundException.methodName)
563
+ const callMethod = (...args) => this[name](...args);
564
+ return callMethod;
563
565
  }
564
566
  else if (throwExceptionIfNotFound) {
565
567
  throw new MethodNotFoundException_js_1.MethodNotFoundException('Method "{methodName}" not found in "{className}"', {
@@ -1,18 +1,18 @@
1
1
  import { BaseObject } from '../BaseObject.js';
2
+ import { type Resolver } from '../../ioc/Resolvers.js';
2
3
  /**
3
- * Set configurable records to target constructor with its registration name
4
- * @param target
5
- * @param registrationName
4
+ * Set configurable records to a registration (the options it was declared with)
5
+ * @param registration
6
6
  * @param configurableRecords
7
7
  * @constructor
8
8
  */
9
- export declare function setConfigurableRecords<ClassConstructor extends typeof BaseObject>(target: ClassConstructor, registrationName: string | symbol, configurableRecords: Record<string, any>): void;
9
+ export declare function setConfigurableRecords(registration: Resolver<any>, configurableRecords: Record<string, any>): void;
10
10
  /**
11
- * Get configurable records from target constructor by its registration name
12
- * @param target
11
+ * Get configurable records from a registration
12
+ * @param registration
13
13
  * @constructor
14
14
  */
15
- export declare function getConfigurableRecords<ClassConstructor extends typeof BaseObject>(target: ClassConstructor, registrationName: string | symbol): Record<string, any>;
15
+ export declare function getConfigurableRecords(registration: Resolver<any> | null): Record<string, any>;
16
16
  /**
17
17
  * Set id to instance
18
18
  * @param target