@itrocks/mysql-maintainer 0.0.16 → 0.0.18
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 +265 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -7,3 +7,268 @@
|
|
|
7
7
|
# mysql-maintainer
|
|
8
8
|
|
|
9
9
|
Reactively maintains database structure by updating schema and retrying on MySQL errors.
|
|
10
|
+
|
|
11
|
+
*This documentation was written by an artificial intelligence and may contain errors or approximations.
|
|
12
|
+
It has not yet been fully reviewed by a human. If anything seems unclear or incomplete,
|
|
13
|
+
please feel free to contact the author of this package.*
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @itrocks/mysql-maintainer
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
`@itrocks/mysql-maintainer` provides two main entry points:
|
|
24
|
+
|
|
25
|
+
- the `Mysql` class, a drop‑in replacement for `@itrocks/mysql` that
|
|
26
|
+
automatically keeps your MySQL/MariaDB schema in sync with your
|
|
27
|
+
TypeScript models while you work with data;
|
|
28
|
+
- the `MysqlMaintainer` class, a lower‑level helper you can call
|
|
29
|
+
directly when you want to create or update tables yourself.
|
|
30
|
+
|
|
31
|
+
In both cases, the package reacts to common MySQL errors
|
|
32
|
+
(`ER_BAD_FIELD_ERROR`, `ER_CANNOT_ADD_FOREIGN`, `ER_NO_SUCH_TABLE`) and
|
|
33
|
+
updates the database structure, then transparently retries the failed
|
|
34
|
+
query.
|
|
35
|
+
|
|
36
|
+
### Minimal example: automatic schema maintenance
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Mysql } from '@itrocks/mysql-maintainer'
|
|
40
|
+
import { Entity } from '@itrocks/storage'
|
|
41
|
+
|
|
42
|
+
class User extends Entity<User>
|
|
43
|
+
{
|
|
44
|
+
name = ''
|
|
45
|
+
email = ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
const mysql = new Mysql({
|
|
50
|
+
// Same connection options as @itrocks/mysql / mariadb
|
|
51
|
+
host: 'localhost',
|
|
52
|
+
user: 'root',
|
|
53
|
+
database: 'demo',
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// On first call, mysql-maintainer will create or update the
|
|
57
|
+
// corresponding table based on the User class definition, then retry
|
|
58
|
+
// the insert.
|
|
59
|
+
await mysql.insert(new User().assign({ name: 'Alice', email: 'a@example.com' }))
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can continue using the `Mysql` instance as you would use
|
|
64
|
+
`@itrocks/mysql`: methods such as `read`, `search`, `update`, and
|
|
65
|
+
collection helpers are forwarded to the base implementation, while
|
|
66
|
+
`mysql-maintainer` keeps track of the involved entities and adjusts the
|
|
67
|
+
schema when needed.
|
|
68
|
+
|
|
69
|
+
### Example: maintaining join tables on collection access
|
|
70
|
+
|
|
71
|
+
When you read many‑to‑many relationships defined as collections on your
|
|
72
|
+
entities, `Mysql` automatically creates or updates the implicit join
|
|
73
|
+
table.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { Mysql } from '@itrocks/mysql-maintainer'
|
|
77
|
+
import { Entity } from '@itrocks/storage'
|
|
78
|
+
|
|
79
|
+
class Tag extends Entity<Tag>
|
|
80
|
+
{
|
|
81
|
+
name = ''
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class Article extends Entity<Article>
|
|
85
|
+
{
|
|
86
|
+
title = ''
|
|
87
|
+
|
|
88
|
+
// Many-to-many with Tag; the framework will map this to a join table
|
|
89
|
+
tags: Tag[] = []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function loadArticleTags(id: number) {
|
|
93
|
+
const mysql = new Mysql({ database: 'demo' })
|
|
94
|
+
|
|
95
|
+
const article = await mysql.read(Article, id)
|
|
96
|
+
|
|
97
|
+
// If the join table does not exist yet or is out of date, it will be
|
|
98
|
+
// created/updated before the query is retried.
|
|
99
|
+
const tags = await mysql.readCollection(article, 'tags')
|
|
100
|
+
return tags
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Example: using MysqlMaintainer directly
|
|
105
|
+
|
|
106
|
+
If you want to control when schema updates happen (for instance during a
|
|
107
|
+
deployment step or a migration command), you can work with
|
|
108
|
+
`MysqlMaintainer` and a `mariadb` connection.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { createConnection } from 'mariadb'
|
|
112
|
+
import { MysqlMaintainer } from '@itrocks/mysql-maintainer'
|
|
113
|
+
import { Entity } from '@itrocks/storage'
|
|
114
|
+
|
|
115
|
+
class Product extends Entity<Product>
|
|
116
|
+
{
|
|
117
|
+
name = ''
|
|
118
|
+
price = 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function ensureSchema() {
|
|
122
|
+
const connection = await createConnection({ database: 'demo' })
|
|
123
|
+
const schemaManager = new MysqlMaintainer(connection)
|
|
124
|
+
|
|
125
|
+
// Create or update the table corresponding to Product
|
|
126
|
+
await schemaManager.updateTable(Product)
|
|
127
|
+
|
|
128
|
+
await connection.end()
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
### `class Mysql extends @itrocks/mysql.Mysql`
|
|
135
|
+
|
|
136
|
+
Enhanced MySQL storage class that automatically tracks the context of
|
|
137
|
+
operations and lets `MysqlMaintainer` react to schema‑related errors.
|
|
138
|
+
|
|
139
|
+
Aside from the behavior described below, it exposes the same public API
|
|
140
|
+
as `@itrocks/mysql.Mysql`.
|
|
141
|
+
|
|
142
|
+
#### Properties
|
|
143
|
+
|
|
144
|
+
- `connection?: ContextualConnection` – the underlying `mariadb`
|
|
145
|
+
connection wrapped with additional context information. You normally
|
|
146
|
+
do not interact with it directly.
|
|
147
|
+
|
|
148
|
+
#### Methods
|
|
149
|
+
|
|
150
|
+
All methods have the same signature and return types as in
|
|
151
|
+
`@itrocks/mysql.Mysql`; the extra behavior is that each call pushes the
|
|
152
|
+
relevant entity type or instance to an internal context stack before the
|
|
153
|
+
query, and pops it afterwards.
|
|
154
|
+
|
|
155
|
+
- `async connect(): Promise<ContextualConnection>`
|
|
156
|
+
|
|
157
|
+
Opens the connection and installs the contextual behavior so that
|
|
158
|
+
errors can trigger schema maintenance.
|
|
159
|
+
|
|
160
|
+
- `async insert<T extends object>(object: T): Promise<Entity<T>>`
|
|
161
|
+
|
|
162
|
+
Inserts a new row for `object`. If a “table does not exist” or
|
|
163
|
+
“column not found / foreign key failed” error occurs, the
|
|
164
|
+
corresponding table (and, for collections, the join table) is created
|
|
165
|
+
or updated and the query is retried.
|
|
166
|
+
|
|
167
|
+
- `async update<T extends object>(object: Entity<T>): Promise<Entity<T>>`
|
|
168
|
+
|
|
169
|
+
Updates an existing row; schema maintenance/automatic retry applies in
|
|
170
|
+
the same way as for `insert`.
|
|
171
|
+
|
|
172
|
+
- `async deleteId<T extends object>(type: ObjectOrType<T>, id: any, property?: KeyOf<Entity<T>>): Promise<void>`
|
|
173
|
+
|
|
174
|
+
Deletes a row by its identifier; the entity `type` is used as context
|
|
175
|
+
for potential schema updates.
|
|
176
|
+
|
|
177
|
+
- `async deleteRelatedId<T extends Entity>(object: T, property: KeyOf<T>, id: Identifier): Promise<void>`
|
|
178
|
+
|
|
179
|
+
Deletes a related entity (for example from a collection association).
|
|
180
|
+
|
|
181
|
+
- `async read<T extends object>(type: Type<T>, id: Identifier): Promise<Entity<T> | undefined>`
|
|
182
|
+
|
|
183
|
+
Reads a single entity instance by identifier.
|
|
184
|
+
|
|
185
|
+
- `async readMultiple<T extends object>(type: Type<T>, ids: Identifier[]): Promise<Entity<T>[]>`
|
|
186
|
+
|
|
187
|
+
Reads several entities by their identifiers.
|
|
188
|
+
|
|
189
|
+
- `async readCollection<T extends object, PT extends object>(object: Entity<T>, property: KeyOf<T>, type?: Type<PT>): Promise<Entity<PT>[]>`
|
|
190
|
+
|
|
191
|
+
Reads a collection relationship (for example `article.tags`). The
|
|
192
|
+
context includes both the owning object and the related type so that
|
|
193
|
+
the implicit join table can be created or updated if necessary.
|
|
194
|
+
|
|
195
|
+
- `async readCollectionIds<T extends object, PT extends object>(object: Entity<T>, property: KeyOf<T>, type?: Type<PT>): Promise<Identifier[]>`
|
|
196
|
+
|
|
197
|
+
Same as `readCollection`, but only retrieves identifiers.
|
|
198
|
+
|
|
199
|
+
- `async search<T extends object>(type: Type<T>, search?: SearchType<T>, options?: Options): Promise<Entity<T>[]>`
|
|
200
|
+
|
|
201
|
+
Searches entities; the entity `type` becomes the context for schema
|
|
202
|
+
maintenance.
|
|
203
|
+
|
|
204
|
+
### `class MysqlMaintainer`
|
|
205
|
+
|
|
206
|
+
Low‑level utility that knows how to translate your TypeScript entity
|
|
207
|
+
definitions (via reflection and schema packages) into MySQL tables and
|
|
208
|
+
how to react to common schema‑related errors.
|
|
209
|
+
|
|
210
|
+
#### Constructor
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const maintainer = new MysqlMaintainer(connection)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
- `connection` – a `mariadb` `Connection` used to inspect and modify the
|
|
217
|
+
database schema.
|
|
218
|
+
|
|
219
|
+
#### Methods
|
|
220
|
+
|
|
221
|
+
- `async createImplicitTable(type1: ObjectOrType, type2: ObjectOrType): Promise<boolean>`
|
|
222
|
+
|
|
223
|
+
Creates a join table between the two entity types, with a composite
|
|
224
|
+
primary key and cascading foreign keys. Returns `true` once the table
|
|
225
|
+
has been created.
|
|
226
|
+
|
|
227
|
+
- `async createTable(type: Type): Promise<boolean>`
|
|
228
|
+
|
|
229
|
+
Creates a table corresponding to the given entity `type`. Throws if no
|
|
230
|
+
table name can be resolved for the type.
|
|
231
|
+
|
|
232
|
+
- `implicitTableName(type1: ObjectOrType, type2: ObjectOrType): string`
|
|
233
|
+
|
|
234
|
+
Returns the name of the join table that would be used for a
|
|
235
|
+
many‑to‑many association between `type1` and `type2`. Throws if one of
|
|
236
|
+
the types is not mapped to a table.
|
|
237
|
+
|
|
238
|
+
- `async manageError(error: SqlError, context: Context, sql: string | QueryOptions, values: any[]): Promise<boolean>`
|
|
239
|
+
|
|
240
|
+
Examines the MySQL error and, when it corresponds to a managed error
|
|
241
|
+
code, updates the tables for the provided `context` (entity type,
|
|
242
|
+
instance, or pair `[owner, relatedType]`). Returns `true` if the error
|
|
243
|
+
has been handled and the caller should retry the query, `false`
|
|
244
|
+
otherwise.
|
|
245
|
+
|
|
246
|
+
- `async updateContextTables(context: Context, tableName?: string): Promise<boolean>`
|
|
247
|
+
|
|
248
|
+
Internal helper that inspects the database to see whether the table
|
|
249
|
+
for each context entity already exists. If not, it calls
|
|
250
|
+
`createTable`; otherwise it calls `updateTable`. When two contexts are
|
|
251
|
+
provided and their implicit join table matches `tableName`, the join
|
|
252
|
+
table is also created.
|
|
253
|
+
|
|
254
|
+
- `async updateTable(type: Type): Promise<boolean>`
|
|
255
|
+
|
|
256
|
+
Computes the difference between the current MySQL table and the table
|
|
257
|
+
that should exist according to the entity `type`, then applies the
|
|
258
|
+
required SQL changes. Returns `true` when a change has been applied,
|
|
259
|
+
`false` when the schema was already up to date.
|
|
260
|
+
|
|
261
|
+
## Typical use cases
|
|
262
|
+
|
|
263
|
+
- Rapid prototyping: evolve your TypeScript entity models and let
|
|
264
|
+
`mysql-maintainer` adjust the database schema on the fly while you
|
|
265
|
+
insert, update, or query data.
|
|
266
|
+
- Developer workstations and test environments where automatic schema
|
|
267
|
+
updates remove the need for manual migrations.
|
|
268
|
+
- Applications with many small deployments where you prefer incremental
|
|
269
|
+
schema evolution handled by the application itself.
|
|
270
|
+
- Projects using the `@itrocks/storage` / `@itrocks/mysql` stack that
|
|
271
|
+
want schema maintenance without writing migration scripts.
|
|
272
|
+
- Custom migration tools or CLI scripts built on top of
|
|
273
|
+
`MysqlMaintainer` to validate and update schema before a new version
|
|
274
|
+
of the application is started.
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"description": "Reactively maintains database structure by updating schema and retrying on MySQL errors",
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"typescript": "~5.
|
|
20
|
+
"typescript": "~5.9"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=18"
|
|
@@ -37,10 +37,13 @@
|
|
|
37
37
|
"automatic",
|
|
38
38
|
"automation",
|
|
39
39
|
"backend",
|
|
40
|
+
"database",
|
|
40
41
|
"it.rocks",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"mysql"
|
|
42
|
+
"mariadb",
|
|
43
|
+
"migration",
|
|
44
|
+
"mysql",
|
|
45
|
+
"schema",
|
|
46
|
+
"synchronization"
|
|
44
47
|
],
|
|
45
48
|
"license": "LGPL-3.0-or-later",
|
|
46
49
|
"name": "@itrocks/mysql-maintainer",
|
|
@@ -54,5 +57,5 @@
|
|
|
54
57
|
"build:esm": "tsc -p tsconfig.esm.json"
|
|
55
58
|
},
|
|
56
59
|
"types": "./esm/mysql-maintainer.d.ts",
|
|
57
|
-
"version": "0.0.
|
|
60
|
+
"version": "0.0.18"
|
|
58
61
|
}
|