lakutata 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -30
- package/README.zh-CN.md +130 -0
- package/dist/cjs/exports/QueueComponent.d.ts +1 -0
- package/dist/cjs/exports/QueueComponent.js +20 -0
- package/dist/esm/exports/QueueComponent.js +1 -0
- package/dist/types/exports/QueueComponent.d.ts +1 -0
- package/doc/en/Application.md +107 -0
- package/doc/en/CLI.md +274 -0
- package/doc/en/Cache.md +379 -0
- package/doc/en/Command.md +110 -0
- package/doc/en/ContainerdIntegration.md +17 -16
- package/doc/en/Controllers.md +256 -0
- package/doc/en/DBus.md +291 -0
- package/doc/en/DTOValidation.md +72 -43
- package/doc/en/Database.md +188 -0
- package/doc/en/DatabaseDrivers.md +287 -0
- package/doc/en/Decorator.md +52 -40
- package/doc/en/DockerIntegration.md +7 -6
- package/doc/en/Entities.md +240 -0
- package/doc/en/Entrypoint.md +216 -0
- package/doc/en/Exceptions.md +174 -0
- package/doc/en/GRPC.md +251 -0
- package/doc/en/GettingStarted.md +165 -0
- package/doc/en/HTTP.md +507 -0
- package/doc/en/HelperFunctions.md +11 -11
- package/doc/en/I18n.md +119 -115
- package/doc/en/Injection.md +144 -0
- package/doc/en/Installers.md +280 -0
- package/doc/en/Logger.md +206 -0
- package/doc/en/MCP.md +293 -0
- package/doc/en/Middlewares.md +213 -0
- package/doc/en/Migrations.md +202 -0
- package/doc/en/MongoEmbedded.md +149 -0
- package/doc/en/Monitor.md +164 -0
- package/doc/en/Mutex.md +27 -25
- package/doc/en/NATS.md +246 -0
- package/doc/en/NativeAddons.md +212 -0
- package/doc/en/Objects.md +143 -0
- package/doc/en/Overview.md +62 -85
- package/doc/en/Packaging.md +193 -0
- package/doc/en/ProjectStructure.md +124 -0
- package/doc/en/Queue.md +269 -0
- package/doc/en/Schedules.md +217 -0
- package/doc/en/Security.md +10 -9
- package/doc/en/ServiceCalls.md +504 -0
- package/doc/en/Time.md +19 -20
- package/doc/en/UpgradingFrom1.md +435 -0
- package/doc/en/UpgradingFrom2.md +315 -0
- package/doc/en/Workspaces.md +189 -0
- package/doc/en/examples/BackgroundJobs.md +677 -0
- package/doc/en/examples/CommandLineTool.md +664 -0
- package/doc/en/examples/DesktopApp.md +809 -0
- package/doc/en/examples/DeviceService.md +607 -0
- package/doc/en/examples/MCPServer.md +653 -0
- package/doc/en/examples/README.md +16 -0
- package/doc/en/examples/RestAPI.md +490 -0
- package/doc/en/examples/ServiceCalls.md +829 -0
- package/doc/en/examples/WebApp.md +568 -0
- package/doc/zh/Application.md +75 -0
- package/doc/zh/CLI.md +250 -0
- package/doc/zh/Cache.md +328 -0
- package/doc/zh/Command.md +78 -0
- package/doc/zh/ContainerdIntegration.md +9 -9
- package/doc/zh/Controllers.md +228 -0
- package/doc/zh/DBus.md +257 -0
- package/doc/zh/DTOValidation.md +49 -19
- package/doc/zh/Database.md +175 -0
- package/doc/zh/DatabaseDrivers.md +261 -0
- package/doc/zh/Decorator.md +25 -18
- package/doc/zh/DockerIntegration.md +6 -6
- package/doc/zh/Entities.md +224 -0
- package/doc/zh/Entrypoint.md +193 -0
- package/doc/zh/Exceptions.md +158 -0
- package/doc/zh/GRPC.md +226 -0
- package/doc/zh/GettingStarted.md +148 -0
- package/doc/zh/HTTP.md +445 -0
- package/doc/zh/HelperFunctions.md +2 -2
- package/doc/zh/I18n.md +26 -26
- package/doc/zh/Injection.md +129 -0
- package/doc/zh/Installers.md +232 -0
- package/doc/zh/Logger.md +188 -0
- package/doc/zh/MCP.md +265 -0
- package/doc/zh/Middlewares.md +198 -0
- package/doc/zh/Migrations.md +178 -0
- package/doc/zh/MongoEmbedded.md +118 -0
- package/doc/zh/Monitor.md +145 -0
- package/doc/zh/Mutex.md +10 -10
- package/doc/zh/NATS.md +207 -0
- package/doc/zh/NativeAddons.md +180 -0
- package/doc/zh/Objects.md +119 -0
- package/doc/zh/Overview.md +46 -71
- package/doc/zh/Packaging.md +154 -0
- package/doc/zh/ProjectStructure.md +102 -0
- package/doc/zh/Queue.md +214 -0
- package/doc/zh/Schedules.md +193 -0
- package/doc/zh/Security.md +6 -6
- package/doc/zh/ServiceCalls.md +459 -0
- package/doc/zh/Time.md +2 -2
- package/doc/zh/UpgradingFrom1.md +347 -0
- package/doc/zh/UpgradingFrom2.md +230 -0
- package/doc/zh/Workspaces.md +150 -0
- package/doc/zh/examples/BackgroundJobs.md +618 -0
- package/doc/zh/examples/CommandLineTool.md +618 -0
- package/doc/zh/examples/DesktopApp.md +748 -0
- package/doc/zh/examples/DeviceService.md +546 -0
- package/doc/zh/examples/MCPServer.md +604 -0
- package/doc/zh/examples/README.md +14 -0
- package/doc/zh/examples/RestAPI.md +458 -0
- package/doc/zh/examples/ServiceCalls.md +765 -0
- package/doc/zh/examples/WebApp.md +520 -0
- package/package.json +46 -17
- package/doc/en/ActionPattern.md +0 -408
- package/doc/en/Core.md +0 -312
- package/doc/en/ORMIntegration.md +0 -341
- package/doc/zh/ActionPattern.md +0 -334
- package/doc/zh/Core.md +0 -256
- package/doc/zh/ORMIntegration.md +0 -319
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Lakutata Databases
|
|
2
|
+
|
|
3
|
+
The database layer of lakutata, the package `@lakutata/orm`, is built on [TypeORM](https://typeorm.io) 1. The source
|
|
4
|
+
of TypeORM is part of the package, so no package `typeorm` is installed. `lakutata` re-exports it under four subpaths:
|
|
5
|
+
|
|
6
|
+
| Subpath | What it provides |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `lakutata/com/database` | The `Database` component, a connection shared by the application, and `buildDatabaseOptions` |
|
|
9
|
+
| `lakutata/provider/database` | The `Database` provider, a connection of its own each time it is resolved |
|
|
10
|
+
| `lakutata/decorator/orm` | The decorators of the entities (`@Entity`, `@Column`, the relations…) |
|
|
11
|
+
| `lakutata/orm` | The API of TypeORM: repositories, entity manager, find operators, migrations, errors |
|
|
12
|
+
|
|
13
|
+
The driver of the database is installed separately: see [DatabaseDrivers](DatabaseDrivers.md). The entities, the
|
|
14
|
+
queries and the transactions are described in [Entities](Entities.md), the changes of the schema in
|
|
15
|
+
[Migrations](Migrations.md).
|
|
16
|
+
|
|
17
|
+
## 1. The Database component
|
|
18
|
+
|
|
19
|
+
`buildDatabaseOptions` declares a `Database` component with the connection options of TypeORM:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {Application, Component} from 'lakutata'
|
|
23
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
24
|
+
import {buildDatabaseOptions, Database} from 'lakutata/com/database'
|
|
25
|
+
import {Column, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'
|
|
26
|
+
|
|
27
|
+
@Entity('users')
|
|
28
|
+
class User {
|
|
29
|
+
@PrimaryGeneratedColumn()
|
|
30
|
+
public id: number
|
|
31
|
+
|
|
32
|
+
@Column({type: 'varchar', length: 100})
|
|
33
|
+
public name: string
|
|
34
|
+
|
|
35
|
+
@Column({unique: true})
|
|
36
|
+
public email: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class Users extends Component {
|
|
40
|
+
//The component declared as "db"
|
|
41
|
+
@Inject('db')
|
|
42
|
+
protected readonly db: Database
|
|
43
|
+
|
|
44
|
+
public async register(name: string, email: string): Promise<User> {
|
|
45
|
+
return await this.db.getRepository(User).save({name: name, email: email})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async find(id: number): Promise<User | null> {
|
|
49
|
+
return await this.db.getRepository(User).findOneBy({id: id})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Application
|
|
54
|
+
.run(() => ({
|
|
55
|
+
id: 'users.app',
|
|
56
|
+
name: 'Users',
|
|
57
|
+
components: {
|
|
58
|
+
db: buildDatabaseOptions({
|
|
59
|
+
type: 'postgres',
|
|
60
|
+
url: process.env.DATABASE_URL,
|
|
61
|
+
entities: [User],
|
|
62
|
+
//Development only: the tables follow the entities. In production, use migrations
|
|
63
|
+
synchronize: true
|
|
64
|
+
}),
|
|
65
|
+
users: {class: Users}
|
|
66
|
+
},
|
|
67
|
+
bootstrap: ['db']
|
|
68
|
+
}))
|
|
69
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
70
|
+
const users: Users = await app.getObject<Users>('users')
|
|
71
|
+
await users.register('Ada', 'ada@example.com')
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- The component connects when it is created. Listed in `bootstrap`, it connects while the application launches, and a
|
|
76
|
+
failed connection stops the launch. Otherwise it connects the first time an object needs it.
|
|
77
|
+
- It disconnects when the application stops.
|
|
78
|
+
- Other objects inject it by its name (`@Inject('db')`), or get it with `getObject('db')`.
|
|
79
|
+
|
|
80
|
+
## 2. The connection options
|
|
81
|
+
|
|
82
|
+
The options are the ones of TypeORM, given to it as they are. The options of each driver are in
|
|
83
|
+
[DatabaseDrivers](DatabaseDrivers.md); the common ones are:
|
|
84
|
+
|
|
85
|
+
| Option | Default | Meaning |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `type` | Required | The driver: `'postgres'`, `'mysql'`, `'better-sqlite3'`… |
|
|
88
|
+
| `entities` | | The entity classes, `EntitySchema`s, or globs of their compiled files (`'build/entities/*.js'`) |
|
|
89
|
+
| `synchronize` | `false` | Update the tables from the entities each time the database connects. For development only |
|
|
90
|
+
| `logging` | `false` | What TypeORM logs: `true`, or a list among `'query'`, `'error'`, `'schema'`, `'warn'`, `'info'`, `'log'` |
|
|
91
|
+
| `logger` | | Where TypeORM logs: `'advanced-console'`, `'simple-console'`, `'file'`, `'debug'`, or a logger of your own |
|
|
92
|
+
| `maxQueryExecutionTime` | | Log the queries slower than this, in milliseconds |
|
|
93
|
+
| `poolSize` | | The connections of the pool |
|
|
94
|
+
| `extra` | | Options passed to the client of the driver |
|
|
95
|
+
| `migrations`, `migrationsRun`, `migrationsTableName`, `migrationsTransactionMode` | | The migrations (see [Migrations](Migrations.md)) |
|
|
96
|
+
| `subscribers` | | The entity subscribers |
|
|
97
|
+
| `namingStrategy` | | How tables, columns and indices are named |
|
|
98
|
+
| `cache` | | The cache of query results: `true` (in a table of the database), or `{type: 'redis', options}` |
|
|
99
|
+
| `invalidWhereValuesBehavior` | `{null: 'ignore', undefined: 'ignore'}` | What `null` and `undefined` do in a where condition (below) |
|
|
100
|
+
|
|
101
|
+
**`null` and `undefined` in the conditions.** By default, a property of a where condition whose value is `null` or
|
|
102
|
+
`undefined` is left out of the condition, as in TypeORM 0.3; TypeORM 1 throws instead. So `findOneBy({id: undefined})`
|
|
103
|
+
finds the first row of the table: check the values taken from a request before querying with them, or set
|
|
104
|
+
`invalidWhereValuesBehavior: {undefined: 'throw'}` to throw on them (each key given replaces its default).
|
|
105
|
+
|
|
106
|
+
**Databases created with TypeORM 0.3.** If a table or column name has characters outside ASCII, TypeORM 1 names its
|
|
107
|
+
indices and constraints differently, and generated migrations would rename them.
|
|
108
|
+
`namingStrategy: new NamingStrategyV03()` (from `lakutata/orm`) keeps the names of TypeORM 0.3.
|
|
109
|
+
|
|
110
|
+
**A Redis query cache** (`cache: {type: 'redis'}` or `'ioredis'`) needs the package `redis` or `ioredis`.
|
|
111
|
+
|
|
112
|
+
## 3. What the component provides
|
|
113
|
+
|
|
114
|
+
The component exposes the API of a TypeORM data source:
|
|
115
|
+
|
|
116
|
+
| Method or property | What it does |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `getRepository(Entity)` | The repository of an entity (a `MongoRepository` on MongoDB) |
|
|
119
|
+
| `getMongoRepository(Entity)`, `getTreeRepository(Entity)` | The repository of MongoDB, of a tree entity |
|
|
120
|
+
| `manager` | The entity manager |
|
|
121
|
+
| `transaction(fn)`, `transaction(isolationLevel, fn)` | Run a function in a transaction |
|
|
122
|
+
| `query(sql, parameters)` | Run SQL; the placeholders are `$1, $2…` on PostgreSQL, `?` on MySQL and SQLite |
|
|
123
|
+
| `createQueryBuilder(Entity, alias)` | A query builder |
|
|
124
|
+
| `createQueryRunner(mode)` | A connection of the pool, to control a transaction by hand; `'slave'` reads from a replica |
|
|
125
|
+
| `synchronize()` | Update the tables from the entities |
|
|
126
|
+
| `runMigrations()`, `undoLastMigration()`, `showMigrations()` | Run, revert and list the migrations |
|
|
127
|
+
| `hasMetadata(Entity)`, `getMetadata(Entity)` | Whether an entity is known, and its metadata |
|
|
128
|
+
|
|
129
|
+
## 4. The Database provider
|
|
130
|
+
|
|
131
|
+
The `Database` of `lakutata/provider/database` has the same API, but it is **transient**: each time it is resolved
|
|
132
|
+
(`@Inject`, `getObject()`), a new instance opens its own pool of connections, and closes it when it is destroyed with
|
|
133
|
+
the object or the container that created it. It suits a short task (a job, a script, a database per customer), while
|
|
134
|
+
the requests of an application share the component.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import {Application} from 'lakutata'
|
|
138
|
+
import {buildDatabaseOptions, Database} from 'lakutata/provider/database'
|
|
139
|
+
import {Column, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'
|
|
140
|
+
|
|
141
|
+
@Entity('reports')
|
|
142
|
+
class Report {
|
|
143
|
+
@PrimaryGeneratedColumn()
|
|
144
|
+
public id: number
|
|
145
|
+
|
|
146
|
+
@Column()
|
|
147
|
+
public title: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Application
|
|
151
|
+
.run(() => ({
|
|
152
|
+
id: 'reports.app',
|
|
153
|
+
name: 'Reports',
|
|
154
|
+
providers: {
|
|
155
|
+
reports: {
|
|
156
|
+
...buildDatabaseOptions({type: 'postgres', url: process.env.REPORTS_URL}),
|
|
157
|
+
//Added to the entities of the connection options
|
|
158
|
+
entities: [Report]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}))
|
|
162
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
163
|
+
//A connection opened here, and closed with the application
|
|
164
|
+
const database: Database = await app.getObject<Database>('reports')
|
|
165
|
+
console.log(await database.getRepository(Report).count())
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 5. Several databases
|
|
170
|
+
|
|
171
|
+
Each database is a component of its own, injected by its name:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import {Application} from 'lakutata'
|
|
175
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
176
|
+
|
|
177
|
+
Application.run(() => ({
|
|
178
|
+
id: 'shop.app',
|
|
179
|
+
name: 'Shop',
|
|
180
|
+
components: {
|
|
181
|
+
db: buildDatabaseOptions({type: 'postgres', url: process.env.DATABASE_URL, entities: []}),
|
|
182
|
+
analytics: buildDatabaseOptions({type: 'mysql', url: process.env.ANALYTICS_URL, entities: []})
|
|
183
|
+
},
|
|
184
|
+
bootstrap: ['db', 'analytics']
|
|
185
|
+
}))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
An entity can belong to several databases. Its repository comes from whichever database `getRepository()` is called on.
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Lakutata Database Drivers
|
|
2
|
+
|
|
3
|
+
The `type` option of a database chooses its driver. The driver packages are not installed with `lakutata`: install the
|
|
4
|
+
one for your database alongside it.
|
|
5
|
+
|
|
6
|
+
| Database | `type` | Install |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| PostgreSQL | `postgres` | `pg` (8.5 or later) |
|
|
9
|
+
| CockroachDB | `cockroachdb` | `pg` |
|
|
10
|
+
| MySQL, MariaDB | `mysql`, `mariadb` | `mysql2` (3.15.3 or later) |
|
|
11
|
+
| SQLite | `better-sqlite3` | `better-sqlite3` (12) |
|
|
12
|
+
| SQLite in WebAssembly | `sqljs` | `sql.js` (1.4 or later) |
|
|
13
|
+
| Microsoft SQL Server | `mssql` | `mssql` (12) |
|
|
14
|
+
| Oracle | `oracle` | `oracledb` (6.3 or later, or 7) |
|
|
15
|
+
| MongoDB | `mongodb` | `mongodb` (7) |
|
|
16
|
+
| MongoDB embedded in the application | `mongo-embedded` | `mingo` (7.2 or later) and `bson` (7); see [MongoEmbedded](MongoEmbedded.md) |
|
|
17
|
+
| SAP HANA | `sap` | `@sap/hana-client` (2.14 or later) |
|
|
18
|
+
| Google Cloud Spanner | `spanner` | `@google-cloud/spanner` (8) |
|
|
19
|
+
| Amazon Aurora through the Data API | `aurora-mysql`, `aurora-postgres` | `typeorm-aurora-data-api-driver` (3) |
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install lakutata pg
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
There is no `sqlite` type: SQLite files use `better-sqlite3`, or `sqljs` where a native module cannot run. TypeORM also
|
|
26
|
+
has drivers for the mobile platforms (`capacitor`, `cordova`, `expo`, `nativescript`, `react-native`); lakutata does
|
|
27
|
+
not target them.
|
|
28
|
+
|
|
29
|
+
## What applies to every driver
|
|
30
|
+
|
|
31
|
+
- The connection options go to TypeORM as they are, and TypeORM does not check them: a missing host or password
|
|
32
|
+
is reported by the driver when the database connects.
|
|
33
|
+
- `extra` is passed to the client of the driver (its pool, its connection), after the other options: it reaches the
|
|
34
|
+
settings that TypeORM has no option for.
|
|
35
|
+
- `driver` gives the driver module itself (`driver: require('pg')`), instead of letting lakutata load the package.
|
|
36
|
+
- Every example below declares a `Database` component. The options are the same for the `Database` provider (see
|
|
37
|
+
[Database](Database.md)).
|
|
38
|
+
|
|
39
|
+
## PostgreSQL
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
43
|
+
|
|
44
|
+
export const db = buildDatabaseOptions({
|
|
45
|
+
type: 'postgres',
|
|
46
|
+
host: 'localhost',
|
|
47
|
+
port: 5432,
|
|
48
|
+
username: 'app',
|
|
49
|
+
password: process.env.DB_PASSWORD,
|
|
50
|
+
database: 'app',
|
|
51
|
+
//Or all of them at once: url: 'postgres://app:secret@localhost:5432/app'
|
|
52
|
+
entities: []
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Option | Default | Meaning |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `url` | | The connection URL, instead of the options below |
|
|
59
|
+
| `host`, `port`, `username`, `password`, `database` | | The server and the database; `password` can be a function, sync or async, to read a rotating secret |
|
|
60
|
+
| `ssl` | | `true`, or the TLS options of Node.js (`{ca, rejectUnauthorized}`) |
|
|
61
|
+
| `schema` | The `search_path` of the server (usually `public`) | The schema of the tables |
|
|
62
|
+
| `poolSize` | 10 (the default of `pg`) | The connections of the pool |
|
|
63
|
+
| `connectTimeoutMS` | No limit | The time allowed to connect |
|
|
64
|
+
| `applicationName` | | The name shown in the activity of the server |
|
|
65
|
+
| `uuidExtension` | `'uuid-ossp'` | The extension of the UUID columns: `'uuid-ossp'` or `'pgcrypto'` |
|
|
66
|
+
| `replication` | | `{master, slaves}`: the reads go to the replicas |
|
|
67
|
+
|
|
68
|
+
- **`bigint` and `numeric` columns come back as strings**, to keep their precision (`'9007199254740993'`, `'12.50'`).
|
|
69
|
+
Convert them with a column `transformer` where a number is enough. `parseInt8: true` returns the `bigint` columns as
|
|
70
|
+
numbers, but it changes this setting of `pg` for the whole process.
|
|
71
|
+
- With `pg-native` installed, the native client of `pg` is used. Streams (`stream()` of the query builders) need
|
|
72
|
+
`pg-query-stream`.
|
|
73
|
+
|
|
74
|
+
**CockroachDB** uses the same driver with `type: 'cockroachdb'` and the same connection options. Its option
|
|
75
|
+
`timeTravelQueries` (whether the reads use follower reads) must be given; a transaction failing on a conflict is
|
|
76
|
+
retried 5 times (`maxTransactionRetries`).
|
|
77
|
+
|
|
78
|
+
## MySQL and MariaDB
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
82
|
+
|
|
83
|
+
export const db = buildDatabaseOptions({
|
|
84
|
+
type: 'mysql',
|
|
85
|
+
host: 'localhost',
|
|
86
|
+
port: 3306,
|
|
87
|
+
username: 'app',
|
|
88
|
+
password: process.env.DB_PASSWORD,
|
|
89
|
+
database: 'app',
|
|
90
|
+
charset: 'utf8mb4',
|
|
91
|
+
entities: []
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Option | Default | Meaning |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `url`, `host`, `port`, `username`, `password` | | The server |
|
|
98
|
+
| `database` | | The database. Required, unless every entity names its own |
|
|
99
|
+
| `socketPath` | | A unix socket, instead of the host and the port |
|
|
100
|
+
| `ssl` | | The TLS options, or the name of a TLS profile |
|
|
101
|
+
| `charset` | `'UTF8_GENERAL_CI'` | The character set and collation: use `'utf8mb4'` for the full Unicode (emoji) |
|
|
102
|
+
| `timezone` | `'local'` | How the dates are read and written: `'local'`, `'Z'` or an offset (`'+08:00'`) |
|
|
103
|
+
| `poolSize` | 10 (the default of `mysql2`) | The connections of the pool |
|
|
104
|
+
| `connectTimeout`, `acquireTimeout` | `10000` | The time allowed to connect, and to get a connection of the pool |
|
|
105
|
+
| `replication` | | `{master, slaves}`: the reads go to the replicas |
|
|
106
|
+
|
|
107
|
+
- Use `type: 'mariadb'` for MariaDB: the options are the same. MariaDB has the `uuid` column type from 10.7.
|
|
108
|
+
- **`bigint` and `decimal` columns come back as strings**, as with PostgreSQL.
|
|
109
|
+
- A table has at most one auto-increment column. MySQL has no check constraint in TypeORM's schema synchronization.
|
|
110
|
+
- Only `mysql2` is supported: the old package `mysql` is not.
|
|
111
|
+
|
|
112
|
+
## SQLite: better-sqlite3
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
116
|
+
|
|
117
|
+
export const db = buildDatabaseOptions({
|
|
118
|
+
type: 'better-sqlite3',
|
|
119
|
+
//A file (its directory is created), or ':memory:'
|
|
120
|
+
database: 'data/app.sqlite',
|
|
121
|
+
enableWAL: true,
|
|
122
|
+
entities: []
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Option | Default | Meaning |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `database` | Required | The file of the database, or `':memory:'` |
|
|
129
|
+
| `enableWAL` | `false` | The write-ahead log: the reads no longer wait for the writes |
|
|
130
|
+
| `timeout` | `5000` | How long a statement waits for a database locked by another connection, in milliseconds |
|
|
131
|
+
| `readonly`, `fileMustExist` | `false` | Open the file read-only; fail when it does not exist |
|
|
132
|
+
| `key` | | The encryption key, for a build of SQLite with SQLCipher |
|
|
133
|
+
| `prepareDatabase` | | `(db) => …`, called with the connection of better-sqlite3 once it is open: pragmas, functions |
|
|
134
|
+
| `nativeBinding` | | The path of the compiled module `better_sqlite3.node` |
|
|
135
|
+
|
|
136
|
+
- The foreign keys are enforced (`PRAGMA foreign_keys = ON`). There is one connection: `poolSize` is not supported.
|
|
137
|
+
- **`bigint` columns come back as numbers**: values beyond 2^53 lose precision. Keep such values in a text column.
|
|
138
|
+
- `better-sqlite3` is a native module, compiled for one version of Node.js. An Electron application needs the build of
|
|
139
|
+
its Electron (`electron-rebuild`, or the prebuilt binaries of better-sqlite3 for Electron); Bun cannot load it (use
|
|
140
|
+
`sqljs` there).
|
|
141
|
+
|
|
142
|
+
## SQLite: sql.js
|
|
143
|
+
|
|
144
|
+
`sql.js` is SQLite compiled to WebAssembly: no native module, so it runs the same in Node.js, Bun, Electron and the
|
|
145
|
+
browsers. The whole database is held in memory, and written to its file when it changes.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
149
|
+
|
|
150
|
+
export const db = buildDatabaseOptions({
|
|
151
|
+
type: 'sqljs',
|
|
152
|
+
//Loaded when the database connects (if it exists), written after each change
|
|
153
|
+
location: 'data/app.sqlite',
|
|
154
|
+
autoSave: true,
|
|
155
|
+
entities: []
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Option | Default | Meaning |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `location` | | The file to load and to save (in a browser, the key of `localStorage`) |
|
|
162
|
+
| `autoSave` | `false` | Save after each change. It needs `location` or `autoSaveCallback` |
|
|
163
|
+
| `autoSaveCallback` | | `(data: Uint8Array) => …`, called instead of writing `location` |
|
|
164
|
+
| `database` | | The content of a database to load (a `Uint8Array`), instead of `location` |
|
|
165
|
+
| `useLocalForage` | `false` | In a browser, save to IndexedDB (through `localforage`) instead of `localStorage` |
|
|
166
|
+
| `sqlJsConfig` | | The options of the initialization of sql.js (`locateFile` of its `.wasm` file) |
|
|
167
|
+
|
|
168
|
+
- Without `autoSave`, nothing is written to disk until `saveDatabase()` of the entity manager (`SqljsEntityManager`)
|
|
169
|
+
is called. With `autoSave`, the changes of a transaction are saved once it is committed.
|
|
170
|
+
- The file is written as a whole at each save: sql.js suits small databases.
|
|
171
|
+
- As with better-sqlite3, the foreign keys are enforced and `bigint` columns come back as numbers.
|
|
172
|
+
|
|
173
|
+
## Microsoft SQL Server
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
177
|
+
|
|
178
|
+
export const db = buildDatabaseOptions({
|
|
179
|
+
type: 'mssql',
|
|
180
|
+
host: 'localhost',
|
|
181
|
+
port: 1433,
|
|
182
|
+
username: 'sa',
|
|
183
|
+
password: process.env.DB_PASSWORD,
|
|
184
|
+
database: 'app',
|
|
185
|
+
options: {
|
|
186
|
+
//A server with a self-signed certificate, as in development
|
|
187
|
+
trustServerCertificate: true
|
|
188
|
+
},
|
|
189
|
+
entities: []
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Option | Default | Meaning |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `url`, `host`, `port`, `username`, `password`, `database` | | The server and the database |
|
|
196
|
+
| `authentication` | | Another authentication than the user and password: `{type: 'ntlm', ...}`, `{type: 'azure-active-directory-default', ...}`… |
|
|
197
|
+
| `schema` | The default schema of the user | The schema of the tables |
|
|
198
|
+
| `options.encrypt` | `true` | Encrypt the connection |
|
|
199
|
+
| `options.trustServerCertificate` | `false` | Accept a certificate not signed by a trusted authority |
|
|
200
|
+
| `options.instanceName` | | A named instance |
|
|
201
|
+
| `pool` | | The pool of connections: `max`, `min`, `idleTimeoutMillis` (30000 by default)… (`poolSize` is not supported) |
|
|
202
|
+
| `connectionTimeout`, `requestTimeout` | `15000` | The time allowed to connect, and to run a query |
|
|
203
|
+
|
|
204
|
+
- Since `encrypt` is on, a server with a self-signed certificate (a local server, a container) refuses the connection
|
|
205
|
+
unless `trustServerCertificate` is `true`.
|
|
206
|
+
- Transactions cannot be nested.
|
|
207
|
+
|
|
208
|
+
## Oracle
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
212
|
+
|
|
213
|
+
export const db = buildDatabaseOptions({
|
|
214
|
+
type: 'oracle',
|
|
215
|
+
host: 'localhost',
|
|
216
|
+
port: 1521,
|
|
217
|
+
serviceName: 'FREEPDB1',
|
|
218
|
+
username: 'app',
|
|
219
|
+
password: process.env.DB_PASSWORD,
|
|
220
|
+
entities: []
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| Option | Default | Meaning |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `host`, `port`, `username`, `password` | | The server |
|
|
227
|
+
| `serviceName` or `sid` | | The database service |
|
|
228
|
+
| `connectString` | Built from the options above | A connect string (`'localhost:1521/FREEPDB1'`, a TNS entry), instead of them |
|
|
229
|
+
| `thickMode` | `false` | The thick mode of `oracledb` (with the Oracle Client libraries): `true`, or `{libDir, configDir}` |
|
|
230
|
+
| `useUTC` | `false` | Read and write the dates in UTC |
|
|
231
|
+
| `poolSize` | | The connections of the pool |
|
|
232
|
+
|
|
233
|
+
- `oracledb` 6 and later run in thin mode by default, with no Oracle Client. Set `thickMode` for the features that
|
|
234
|
+
need it.
|
|
235
|
+
- The tables are in the schema of the user. `useUTC` sets the time zone of the sessions of the whole process (the
|
|
236
|
+
variable `ORA_SDTZ`).
|
|
237
|
+
|
|
238
|
+
## MongoDB
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import {buildDatabaseOptions} from 'lakutata/com/database'
|
|
242
|
+
import {Column, Entity, ObjectIdColumn} from 'lakutata/decorator/orm'
|
|
243
|
+
import type {ObjectId} from 'lakutata/orm'
|
|
244
|
+
|
|
245
|
+
@Entity('users')
|
|
246
|
+
export class User {
|
|
247
|
+
@ObjectIdColumn()
|
|
248
|
+
public id: ObjectId
|
|
249
|
+
|
|
250
|
+
@Column()
|
|
251
|
+
public name: string
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const db = buildDatabaseOptions({
|
|
255
|
+
type: 'mongodb',
|
|
256
|
+
url: 'mongodb://localhost:27017/app',
|
|
257
|
+
entities: [User]
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| Option | Default | Meaning |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| `url` | | The connection URL |
|
|
264
|
+
| `host`, `port`, `username`, `password`, `database` | `127.0.0.1`, `27017` | The server, when there is no `url` |
|
|
265
|
+
| `authSource`, `replicaSet`, `tls`, `readPreference`, `writeConcern`… | | The options of the client of MongoDB |
|
|
266
|
+
| `poolSize` | | The largest number of connections (`maxPoolSize`) |
|
|
267
|
+
|
|
268
|
+
- An entity has an `@ObjectIdColumn()`, its `_id`. Its type is the `ObjectId` of `lakutata/orm`: an
|
|
269
|
+
`import {ObjectId} from 'bson'` in an ES module is another class, whose instances are not the ids of the entities.
|
|
270
|
+
- `getRepository()` returns a `MongoRepository`, with the methods of MongoDB (`find` with the MongoDB operators,
|
|
271
|
+
`aggregate`, `createCursor`…).
|
|
272
|
+
- TypeORM does not support for MongoDB: the query builders, `query()`, the relations and the cascades, the tree
|
|
273
|
+
repositories, the streams. **`transaction()` runs its function without a transaction**: the writes of a failed
|
|
274
|
+
function stay written.
|
|
275
|
+
- `synchronize` only creates the indices. The migrations are written by hand (see [Migrations](Migrations.md)).
|
|
276
|
+
|
|
277
|
+
## SAP HANA, Google Cloud Spanner and Amazon Aurora
|
|
278
|
+
|
|
279
|
+
These drivers come from TypeORM as they are:
|
|
280
|
+
|
|
281
|
+
- **SAP HANA** (`sap`): `host`, `port`, `username`, `password`, `database`, `schema`, `encrypt` with `key`, `cert`,
|
|
282
|
+
`ca`, and `pool` (`maxConnectedOrPooled`, 10 by default).
|
|
283
|
+
- **Google Cloud Spanner** (`spanner`): `projectId`, `instanceId`, `databaseId`, and `credentials` (`client_email`,
|
|
284
|
+
`private_key`) unless the credentials of the environment are used. The other options of its type, copied from
|
|
285
|
+
MySQL, are not read.
|
|
286
|
+
- **Amazon Aurora through the Data API** (`aurora-mysql`, `aurora-postgres`): `region`, `secretArn`, `resourceArn` and
|
|
287
|
+
`database` are required; the queries go through the HTTP API of AWS, not through a connection.
|
package/doc/en/Decorator.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Lakutata Decorators
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`
|
|
5
|
-
these subpaths:
|
|
3
|
+
Dependencies, options, actions and validations are declared with decorators (TypeScript `experimentalDecorators` and
|
|
4
|
+
`emitDecoratorMetadata`, both set by `lakutata/tsconfig.json`). These subpaths export them:
|
|
6
5
|
|
|
7
6
|
| Subpath | Decorators |
|
|
8
7
|
|---|---|
|
|
@@ -12,12 +11,13 @@ these subpaths:
|
|
|
12
11
|
| `lakutata/decorator/asst` | `@Before`, `@After` |
|
|
13
12
|
| `lakutata/decorator/orm` | The TypeORM decorators (`@Entity`, `@Column`…) |
|
|
14
13
|
| `lakutata/com/mutex` | `@Synchronized` |
|
|
14
|
+
| `lakutata/com/queue` | `@QueueAction` |
|
|
15
15
|
|
|
16
16
|
## 1. Dependency injection (`lakutata/decorator/di`)
|
|
17
17
|
|
|
18
18
|
### 1.1 `@Inject`
|
|
19
19
|
|
|
20
|
-
Sets a property
|
|
20
|
+
Sets a property to an object of the container when the decorated object is created, before its `init()`:
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
23
|
import {Application, Component, type ILogger} from 'lakutata'
|
|
@@ -53,13 +53,13 @@ class Signup extends Component {
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
When a dependency cannot be resolved, the creation of the object fails with a `DependencyInjectionException`.
|
|
57
57
|
|
|
58
58
|
### 1.2 `@Configurable`
|
|
59
59
|
|
|
60
|
-
`@Configurable(schema?, fn?)` makes a property an option of the object,
|
|
61
|
-
(`{class: Server, port: 8080}`) or by the second argument of `getObject()`. The
|
|
62
|
-
|
|
60
|
+
`@Configurable(schema?, fn?)` makes a property an option of the object, set by the object's declaration
|
|
61
|
+
(`{class: Server, port: 8080}`) or by the second argument of `getObject()`. The schema (`DTO.Any()` by default)
|
|
62
|
+
validates (and converts) the value, which is then passed to `fn` when one is given, with the instance as `this`.
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
import {Application, Component, DTO} from 'lakutata'
|
|
@@ -96,22 +96,21 @@ Application.run(() => ({
|
|
|
96
96
|
- **Value**: the value configured for the property, else its initializer (`protected readonly host: string =
|
|
97
97
|
'127.0.0.1'`), else the default of its schema (`DTO.Number().default(80)`). The initializer is validated and
|
|
98
98
|
transformed like a configured value, and a subclass gives its own default with its own initializer.
|
|
99
|
-
- **`maxListeners`**: every component has this configurable property (11 by default)
|
|
100
|
-
event
|
|
101
|
-
sets its own default with `protected readonly maxListeners: number = 0`, or its declaration sets
|
|
102
|
-
`{class: MyComponent, maxListeners: 0}`.
|
|
103
|
-
- **Startup check**: when
|
|
99
|
+
- **`maxListeners`**: every component has this configurable property (11 by default). Above this number of listeners on
|
|
100
|
+
one event, a warning reports a possible memory leak (0 disables the warning; the number of listeners is never
|
|
101
|
+
limited). A component sets its own default with `protected readonly maxListeners: number = 0`, or its declaration sets
|
|
102
|
+
it: `{class: MyComponent, maxListeners: 0}`.
|
|
103
|
+
- **Startup check**: when the application launches, it checks the options of every object it declares
|
|
104
104
|
(components, providers and objects) before any of them is created, so a mistake shows up at startup even for an
|
|
105
105
|
object that is never used:
|
|
106
106
|
- a value that does not match the schema of its `@Configurable` property fails the startup with an
|
|
107
107
|
`InvalidObjectOptionsException` listing every invalid option;
|
|
108
|
-
- an option that is not a `@Configurable` property of the class
|
|
109
|
-
|
|
108
|
+
- an option that is not a `@Configurable` property of the class (a misspelled option name, for example) is ignored,
|
|
109
|
+
with a warning that lists the configurable properties of the class.
|
|
110
110
|
|
|
111
|
-
A class that reads options without configurable properties (through `getConfigurableRecords()`) overrides the
|
|
112
|
-
`acceptsOption(name, value)` to accept them without a warning.
|
|
113
|
-
are validated when the object is created.
|
|
114
|
-
application or of the module.
|
|
111
|
+
A class that reads its options without configurable properties (through `getConfigurableRecords()`) overrides the
|
|
112
|
+
static `acceptsOption(name, value)` to accept them without a warning. Values checked by external (asynchronous) rules
|
|
113
|
+
are validated when the object is created. `checkOptions: false` in the options of the application disables the check.
|
|
115
114
|
|
|
116
115
|
### 1.3 `@Autoload`
|
|
117
116
|
|
|
@@ -123,19 +122,19 @@ transient class in the requesting container. The mark is not inherited.
|
|
|
123
122
|
|
|
124
123
|
| Decorator | One instance… | Default of |
|
|
125
124
|
|---|---|---|
|
|
126
|
-
| `@Singleton(lock?)` | per container
|
|
125
|
+
| `@Singleton(lock?)` | per container that registers the class | `Component` (locked) |
|
|
127
126
|
| `@Scoped(lock?)` | per scope (a request of an entrypoint) | `Provider`, `Controller` (locked) |
|
|
128
127
|
| `@Transient(lock?)` | per resolution | `BaseObject` |
|
|
129
128
|
|
|
130
|
-
`@Lifetime('SINGLETON' | 'SCOPED' | 'TRANSIENT', lock?)` is their long form. With `lock` set to `true`,
|
|
131
|
-
cannot declare another lifetime (
|
|
129
|
+
`@Lifetime('SINGLETON' | 'SCOPED' | 'TRANSIENT', lock?)` is their long form. With `lock` set to `true`, subclasses
|
|
130
|
+
cannot declare another lifetime (defining such a subclass throws a `LifetimeLockedException`). A `Provider` may be a
|
|
132
131
|
singleton, as in 2.x.
|
|
133
132
|
|
|
134
133
|
```typescript
|
|
135
134
|
import {Provider} from 'lakutata'
|
|
136
135
|
import {Transient} from 'lakutata/decorator/di'
|
|
137
136
|
|
|
138
|
-
//Each
|
|
137
|
+
//Each object that injects the builder gets its own instance, with its own state
|
|
139
138
|
@Transient()
|
|
140
139
|
export class QueryBuilder extends Provider {
|
|
141
140
|
protected readonly clauses: string[] = []
|
|
@@ -147,19 +146,21 @@ export class QueryBuilder extends Provider {
|
|
|
147
146
|
}
|
|
148
147
|
```
|
|
149
148
|
|
|
150
|
-
### 1.5
|
|
149
|
+
### 1.5 Properties of subclasses
|
|
151
150
|
|
|
152
|
-
- **Inheritance**: a subclass inherits the `@Inject` and `@Configurable` properties of its parents.
|
|
153
|
-
|
|
154
|
-
wins (a property injected by the parent and declared again with `@Configurable` is configured
|
|
151
|
+
- **Inheritance**: a subclass inherits the `@Inject` and `@Configurable` properties of its parents. When a subclass
|
|
152
|
+
declares a property again, its declaration replaces those of its parents, whatever the decorator: the closest
|
|
153
|
+
declaration wins (a property injected by the parent and declared again with `@Configurable` is only configured, and
|
|
154
|
+
the other way round).
|
|
155
155
|
- **The type of a property**: TypeScript lets a subclass narrow the type of a property it declares again (a
|
|
156
|
-
`MemoryStore`
|
|
157
|
-
'port' in type 'BrokenServer' is not assignable to the same property in base type 'PlainServer'`),
|
|
158
|
-
option
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
the parent without type
|
|
156
|
+
`MemoryStore` instead of a `Store`), but not change it to an unrelated type: `string` instead of `number` fails with
|
|
157
|
+
`TS2416` (`Property 'port' in type 'BrokenServer' is not assignable to the same property in base type 'PlainServer'`),
|
|
158
|
+
and no compiler option turns this off. A parent whose subclasses may need another type for a property declares it with
|
|
159
|
+
a type parameter:
|
|
160
|
+
- the parent declares a type parameter on the class, named after the property with `Type` at the end (`StoreType` for
|
|
161
|
+
`store`). Its constraint (`extends Store`) is the type the parent can rely on, and it limits the subclasses to
|
|
162
|
+
compatible types; its default (`= Store`) lets a subclass that keeps the type extend the parent without a type
|
|
163
|
+
argument;
|
|
163
164
|
- a subclass gives the type in its `extends` clause, and declares the property again only to change its decorator.
|
|
164
165
|
|
|
165
166
|
```typescript
|
|
@@ -210,10 +211,15 @@ In `MemoryRepository`, the IDE completes `this.store` as a `MemoryStore`, and in
|
|
|
210
211
|
type parameter in its parent, a subclass can only silence the error with `// @ts-expect-error` on the property, while
|
|
211
212
|
the methods of the parent still use the property with its former type.
|
|
212
213
|
|
|
214
|
+
Declaring a property again without an initializer, as `NamedPortServer` does, compiles with
|
|
215
|
+
`useDefineForClassFields: false`, which `lakutata/tsconfig.json` sets. With `true` (the default of TypeScript for
|
|
216
|
+
`ES2022` and later targets), the compiler refuses it with `TS2612`; write `declare` before the property instead
|
|
217
|
+
(`@Configurable(...) declare protected readonly port: string`), and the decorator still applies.
|
|
218
|
+
|
|
213
219
|
## 2. Controller actions (`lakutata/decorator/ctrl`)
|
|
214
220
|
|
|
215
221
|
These decorators apply to the methods (and, for `@Prefix` and `@UseMiddlewares`, to the classes) of the subclasses of
|
|
216
|
-
`Controller`; [
|
|
222
|
+
`Controller`; [Controllers](Controllers.md) describes them in detail.
|
|
217
223
|
|
|
218
224
|
| Decorator | Address |
|
|
219
225
|
|---|---|
|
|
@@ -258,7 +264,7 @@ application'}})`, translated by the key `commands.package.description`).
|
|
|
258
264
|
| Decorator | Effect |
|
|
259
265
|
|---|---|
|
|
260
266
|
| `@Expect(schema)` | The schema of a property of a DTO (`DTO.String().required()`) |
|
|
261
|
-
| `@IndexSignature(schema)` | A DTO class keeps the keys
|
|
267
|
+
| `@IndexSignature(schema)` | A DTO class keeps the keys that have no schema, validating them against this schema |
|
|
262
268
|
| `@Accept(...schemas)` | Validates the arguments of each call of a method, by position |
|
|
263
269
|
| `@Return(schema)` | Validates the result of each call of a method |
|
|
264
270
|
|
|
@@ -297,10 +303,16 @@ class Prices extends Component {
|
|
|
297
303
|
|
|
298
304
|
## 5. Locks (`lakutata/com/mutex`)
|
|
299
305
|
|
|
300
|
-
`@Synchronized` runs a controller action
|
|
301
|
-
and
|
|
306
|
+
`@Synchronized` runs a controller action under a lock of the `Mutex` component, acquired after the input is validated
|
|
307
|
+
and access is allowed. See [Mutex](Mutex.md).
|
|
308
|
+
|
|
309
|
+
## 6. Queues (`lakutata/com/queue`)
|
|
310
|
+
|
|
311
|
+
`@QueueAction(queue, dto?, options?)` makes a controller action the consumer of a queue: the `queueEntrypoint` adapter
|
|
312
|
+
delivers the messages of the queue to it. A message is acknowledged when the action returns, retried after a failure,
|
|
313
|
+
and sent to the dead letter queue once its retries are used up. See [Queue](Queue.md).
|
|
302
314
|
|
|
303
|
-
##
|
|
315
|
+
## 7. Database entities (`lakutata/decorator/orm`)
|
|
304
316
|
|
|
305
317
|
`lakutata/decorator/orm` re-exports the decorators of TypeORM:
|
|
306
318
|
|
|
@@ -334,4 +346,4 @@ export class User {
|
|
|
334
346
|
}
|
|
335
347
|
```
|
|
336
348
|
|
|
337
|
-
See [
|
|
349
|
+
See [Entities](Entities.md).
|