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.
Files changed (117) hide show
  1. package/README.md +50 -30
  2. package/README.zh-CN.md +130 -0
  3. package/dist/cjs/exports/QueueComponent.d.ts +1 -0
  4. package/dist/cjs/exports/QueueComponent.js +20 -0
  5. package/dist/esm/exports/QueueComponent.js +1 -0
  6. package/dist/types/exports/QueueComponent.d.ts +1 -0
  7. package/doc/en/Application.md +107 -0
  8. package/doc/en/CLI.md +274 -0
  9. package/doc/en/Cache.md +379 -0
  10. package/doc/en/Command.md +110 -0
  11. package/doc/en/ContainerdIntegration.md +17 -16
  12. package/doc/en/Controllers.md +256 -0
  13. package/doc/en/DBus.md +291 -0
  14. package/doc/en/DTOValidation.md +72 -43
  15. package/doc/en/Database.md +188 -0
  16. package/doc/en/DatabaseDrivers.md +287 -0
  17. package/doc/en/Decorator.md +52 -40
  18. package/doc/en/DockerIntegration.md +7 -6
  19. package/doc/en/Entities.md +240 -0
  20. package/doc/en/Entrypoint.md +216 -0
  21. package/doc/en/Exceptions.md +174 -0
  22. package/doc/en/GRPC.md +251 -0
  23. package/doc/en/GettingStarted.md +165 -0
  24. package/doc/en/HTTP.md +507 -0
  25. package/doc/en/HelperFunctions.md +11 -11
  26. package/doc/en/I18n.md +119 -115
  27. package/doc/en/Injection.md +144 -0
  28. package/doc/en/Installers.md +280 -0
  29. package/doc/en/Logger.md +206 -0
  30. package/doc/en/MCP.md +293 -0
  31. package/doc/en/Middlewares.md +213 -0
  32. package/doc/en/Migrations.md +202 -0
  33. package/doc/en/MongoEmbedded.md +149 -0
  34. package/doc/en/Monitor.md +164 -0
  35. package/doc/en/Mutex.md +27 -25
  36. package/doc/en/NATS.md +246 -0
  37. package/doc/en/NativeAddons.md +212 -0
  38. package/doc/en/Objects.md +143 -0
  39. package/doc/en/Overview.md +62 -85
  40. package/doc/en/Packaging.md +193 -0
  41. package/doc/en/ProjectStructure.md +124 -0
  42. package/doc/en/Queue.md +269 -0
  43. package/doc/en/Schedules.md +217 -0
  44. package/doc/en/Security.md +10 -9
  45. package/doc/en/ServiceCalls.md +504 -0
  46. package/doc/en/Time.md +19 -20
  47. package/doc/en/UpgradingFrom1.md +435 -0
  48. package/doc/en/UpgradingFrom2.md +315 -0
  49. package/doc/en/Workspaces.md +189 -0
  50. package/doc/en/examples/BackgroundJobs.md +677 -0
  51. package/doc/en/examples/CommandLineTool.md +664 -0
  52. package/doc/en/examples/DesktopApp.md +809 -0
  53. package/doc/en/examples/DeviceService.md +607 -0
  54. package/doc/en/examples/MCPServer.md +653 -0
  55. package/doc/en/examples/README.md +16 -0
  56. package/doc/en/examples/RestAPI.md +490 -0
  57. package/doc/en/examples/ServiceCalls.md +829 -0
  58. package/doc/en/examples/WebApp.md +568 -0
  59. package/doc/zh/Application.md +75 -0
  60. package/doc/zh/CLI.md +250 -0
  61. package/doc/zh/Cache.md +328 -0
  62. package/doc/zh/Command.md +78 -0
  63. package/doc/zh/ContainerdIntegration.md +9 -9
  64. package/doc/zh/Controllers.md +228 -0
  65. package/doc/zh/DBus.md +257 -0
  66. package/doc/zh/DTOValidation.md +49 -19
  67. package/doc/zh/Database.md +175 -0
  68. package/doc/zh/DatabaseDrivers.md +261 -0
  69. package/doc/zh/Decorator.md +25 -18
  70. package/doc/zh/DockerIntegration.md +6 -6
  71. package/doc/zh/Entities.md +224 -0
  72. package/doc/zh/Entrypoint.md +193 -0
  73. package/doc/zh/Exceptions.md +158 -0
  74. package/doc/zh/GRPC.md +226 -0
  75. package/doc/zh/GettingStarted.md +148 -0
  76. package/doc/zh/HTTP.md +445 -0
  77. package/doc/zh/HelperFunctions.md +2 -2
  78. package/doc/zh/I18n.md +26 -26
  79. package/doc/zh/Injection.md +129 -0
  80. package/doc/zh/Installers.md +232 -0
  81. package/doc/zh/Logger.md +188 -0
  82. package/doc/zh/MCP.md +265 -0
  83. package/doc/zh/Middlewares.md +198 -0
  84. package/doc/zh/Migrations.md +178 -0
  85. package/doc/zh/MongoEmbedded.md +118 -0
  86. package/doc/zh/Monitor.md +145 -0
  87. package/doc/zh/Mutex.md +10 -10
  88. package/doc/zh/NATS.md +207 -0
  89. package/doc/zh/NativeAddons.md +180 -0
  90. package/doc/zh/Objects.md +119 -0
  91. package/doc/zh/Overview.md +46 -71
  92. package/doc/zh/Packaging.md +154 -0
  93. package/doc/zh/ProjectStructure.md +102 -0
  94. package/doc/zh/Queue.md +214 -0
  95. package/doc/zh/Schedules.md +193 -0
  96. package/doc/zh/Security.md +6 -6
  97. package/doc/zh/ServiceCalls.md +459 -0
  98. package/doc/zh/Time.md +2 -2
  99. package/doc/zh/UpgradingFrom1.md +347 -0
  100. package/doc/zh/UpgradingFrom2.md +230 -0
  101. package/doc/zh/Workspaces.md +150 -0
  102. package/doc/zh/examples/BackgroundJobs.md +618 -0
  103. package/doc/zh/examples/CommandLineTool.md +618 -0
  104. package/doc/zh/examples/DesktopApp.md +748 -0
  105. package/doc/zh/examples/DeviceService.md +546 -0
  106. package/doc/zh/examples/MCPServer.md +604 -0
  107. package/doc/zh/examples/README.md +14 -0
  108. package/doc/zh/examples/RestAPI.md +458 -0
  109. package/doc/zh/examples/ServiceCalls.md +765 -0
  110. package/doc/zh/examples/WebApp.md +520 -0
  111. package/package.json +46 -17
  112. package/doc/en/ActionPattern.md +0 -408
  113. package/doc/en/Core.md +0 -312
  114. package/doc/en/ORMIntegration.md +0 -341
  115. package/doc/zh/ActionPattern.md +0 -334
  116. package/doc/zh/Core.md +0 -256
  117. 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.
@@ -1,8 +1,7 @@
1
1
  # Lakutata Decorators
2
2
 
3
- The framework declares its dependencies, options, actions and validations with decorators (TypeScript
4
- `experimentalDecorators` with `emitDecoratorMetadata`, both set by `lakutata/tsconfig.json`). They are exported by
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, when the object is created and before its `init()`, to an object of the container:
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
- A registration that cannot be resolved fails the creation of the object with a `DependencyInjectionException`.
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, given by its declaration
61
- (`{class: Server, port: 8080}`) or by the second argument of `getObject()`. The value is validated (and converted) by
62
- the schema (`DTO.Any()` by default), then passed to `fn` (called with the instance as `this`) when given.
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): the number of listeners of one
100
- event above which a possible memory leak is warned of (0 for no warning; the listeners are never limited). A component
101
- sets its own default with `protected readonly maxListeners: number = 0`, or its declaration sets it:
102
- `{class: MyComponent, maxListeners: 0}`.
103
- - **Startup check**: when a module (or the application) initializes, it checks the options of every object it declares
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 is ignored and warned of, with the configurable
109
- properties of the class (a misspelled option name).
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 static
112
- `acceptsOption(name, value)` to accept them without a warning. The values validated by external (asynchronous) rules
113
- are validated when the object is created. The check is disabled with `checkOptions: false` in the options of the
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 registering the class | `Component`, `Module` (locked) |
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`, the subclasses
131
- cannot declare another lifetime (a `LifetimeLockedException` is thrown when they are defined). A `Provider` may be a
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 user of the builder gets its own, with its own state
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 The properties of subclasses
149
+ ### 1.5 Properties of subclasses
151
150
 
152
- - **Inheritance**: a subclass inherits the `@Inject` and `@Configurable` properties of its parents. A subclass
153
- declaring a property again replaces the declaration of its parents, whatever its decorator: the closest declaration
154
- wins (a property injected by the parent and declared again with `@Configurable` is configured only, and the opposite).
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` for a `Store`), never replace it by another type: `string` for `number` fails with `TS2416` (`Property
157
- 'port' in type 'BrokenServer' is not assignable to the same property in base type 'PlainServer'`), and no compiler
158
- option disables it. A parent whose subclasses may replace the type of a property declares it with a type parameter:
159
- - the parent declares the type parameter on the class, named after the property and ending in `Type` (`StoreType` for
160
- `store`), with a constraint (`extends Store`): the parent uses the property as its constraint, and its subclasses
161
- replace it by compatible types only; and with a default (`= Store`): a subclass that does not replace it extends
162
- the parent without type argument;
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`; [ActionPattern](ActionPattern.md) describes them in detail.
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 without schema, validated against the schema |
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 holding a lock of the `Mutex` component, acquired once the input is validated
301
- and the access is allowed. See [Mutex](Mutex.md).
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
- ## 6. Database entities (`lakutata/decorator/orm`)
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 [ORMIntegration](ORMIntegration.md).
349
+ See [Entities](Entities.md).