@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.
- package/LICENSE +23 -0
- package/README.md +29 -22
- package/README.zh-CN.md +182 -0
- package/dist/cjs/decorators/dto/Expect.d.ts +2 -1
- package/dist/cjs/decorators/dto/Expect.js +2 -1
- package/dist/cjs/dto/PaginationSearchDTO.d.ts +6 -4
- package/dist/cjs/dto/PaginationSearchDTO.js +5 -4
- package/dist/cjs/exceptions/dto/InvalidValueException.d.ts +4 -2
- package/dist/cjs/exceptions/dto/InvalidValueException.js +4 -2
- package/dist/cjs/lib/app/Application.d.ts +22 -0
- package/dist/cjs/lib/app/Application.js +56 -0
- package/dist/cjs/lib/base/BaseObject.js +3 -1
- package/dist/cjs/lib/base/internal/ConfigurableRecordsInjection.d.ts +7 -7
- package/dist/cjs/lib/base/internal/ConfigurableRecordsInjection.js +10 -14
- package/dist/cjs/lib/core/Container.js +3 -2
- package/dist/cjs/lib/dto/DTO.d.ts +3 -1
- package/dist/cjs/lib/dto/DTO.js +76 -5
- package/dist/cjs/lib/foundation/Exception.d.ts +2 -1
- package/dist/cjs/lib/foundation/Exception.js +27 -7
- package/dist/esm/decorators/dto/Expect.js +2 -1
- package/dist/esm/dto/PaginationSearchDTO.js +5 -4
- package/dist/esm/exceptions/dto/InvalidValueException.js +4 -2
- package/dist/esm/lib/app/Application.js +56 -0
- package/dist/esm/lib/base/BaseObject.js +3 -1
- package/dist/esm/lib/base/internal/ConfigurableRecordsInjection.js +10 -14
- package/dist/esm/lib/core/Container.js +3 -2
- package/dist/esm/lib/dto/DTO.js +76 -5
- package/dist/esm/lib/foundation/Exception.js +27 -7
- package/dist/types/decorators/dto/Expect.d.ts +2 -1
- package/dist/types/dto/PaginationSearchDTO.d.ts +6 -4
- package/dist/types/exceptions/dto/InvalidValueException.d.ts +4 -2
- package/dist/types/lib/app/Application.d.ts +22 -0
- package/dist/types/lib/base/internal/ConfigurableRecordsInjection.d.ts +7 -7
- package/dist/types/lib/dto/DTO.d.ts +3 -1
- package/dist/types/lib/foundation/Exception.d.ts +2 -1
- 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`, `
|
|
5
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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')`
|
|
94
|
-
`@Inject(SomeClass)` an instance of a class (registered automatically when the class is marked
|
|
95
|
-
|
|
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)`.
|
|
100
|
-
(`DTO.String()`, `DTO.Number()`, `DTO.Object()`, `DTO.Array()`, `DTO.Alternatives()
|
|
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}),
|
|
122
|
-
//when
|
|
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
|
|
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` |
|
|
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` |
|
|
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
|
|
187
|
-
|
|
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
|
|
196
|
+
- `@lakutata/logger`: the logger that `lakutata` presets as `log` (pino-compatible JSON lines).
|
package/README.zh-CN.md
ADDED
|
@@ -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).
|
|
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).
|
|
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
|
|
4
|
-
* and pass the validated input to
|
|
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
|
|
17
|
-
* and pass the validated input to
|
|
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
|
|
7
|
-
*
|
|
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
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
|
4
|
-
* @param
|
|
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
|
|
9
|
+
export declare function setConfigurableRecords(registration: Resolver<any>, configurableRecords: Record<string, any>): void;
|
|
10
10
|
/**
|
|
11
|
-
* Get configurable records from
|
|
12
|
-
* @param
|
|
11
|
+
* Get configurable records from a registration
|
|
12
|
+
* @param registration
|
|
13
13
|
* @constructor
|
|
14
14
|
*/
|
|
15
|
-
export declare function getConfigurableRecords
|
|
15
|
+
export declare function getConfigurableRecords(registration: Resolver<any> | null): Record<string, any>;
|
|
16
16
|
/**
|
|
17
17
|
* Set id to instance
|
|
18
18
|
* @param target
|