mysql2-core 0.3.9 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +666 -78
- package/lib/index.js +214 -278
- package/package.json +1 -1
- package/src/build.ts +129 -116
- package/src/index.ts +93 -151
- package/src/metadata.ts +6 -6
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ npm install mysql2 mysql2-core
|
|
|
45
45
|
Pool PoolConnection
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
###
|
|
48
|
+
### DB and Transaction
|
|
49
49
|
|
|
50
50
|
`Executor` defines the common database operations:
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ interface Executor {
|
|
|
54
54
|
driver: string
|
|
55
55
|
param(i: number): string
|
|
56
56
|
execute(sql: string, args?: any[], ctx?: any): Promise<number>
|
|
57
|
-
executeBatch(statements: Statement[],
|
|
57
|
+
executeBatch(statements: Statement[], requireFirstAffected?: boolean, ctx?: any): Promise<number>
|
|
58
58
|
query<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[], ctx?: any): Promise<T[]>
|
|
59
59
|
queryOne<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[], ctx?: any): Promise<T | null>
|
|
60
60
|
executeScalar<T>(sql: string, args?: any[], ctx?: any): Promise<T | null>
|
|
@@ -62,9 +62,16 @@ interface Executor {
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
A **transaction** extends the same interface:
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
```ts
|
|
68
|
+
interface Transaction extends Executor {
|
|
69
|
+
commit(): Promise<void>
|
|
70
|
+
rollback(): Promise<void>
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A database implementation can start a transaction through:
|
|
68
75
|
|
|
69
76
|
```ts
|
|
70
77
|
interface DB extends Executor {
|
|
@@ -72,17 +79,83 @@ interface DB extends Executor {
|
|
|
72
79
|
}
|
|
73
80
|
```
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
This makes application-level persistence code independent from the concrete MySQL connection implementation.
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
|
|
85
|
+
## SQL Parameters
|
|
86
|
+
|
|
87
|
+
The default MySQL parameter builder returns:
|
|
78
88
|
|
|
79
89
|
```ts
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
export function param(i: number): string {
|
|
91
|
+
return "?"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The index is provided to support custom parameter syntaxes.
|
|
96
|
+
|
|
97
|
+
For example:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const buildParam = (i: number) => `$${i}`
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This allows the SQL generation layer to be reused by implementations that require different placeholder conventions.
|
|
104
|
+
|
|
105
|
+
## Metadata
|
|
106
|
+
|
|
107
|
+
The persistence helpers use `Attributes` to describe database fields:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const attrs = {
|
|
111
|
+
id: {
|
|
112
|
+
key: true,
|
|
113
|
+
type: "integer",
|
|
114
|
+
},
|
|
115
|
+
name: {
|
|
116
|
+
type: "string",
|
|
117
|
+
},
|
|
118
|
+
active: {
|
|
119
|
+
type: "boolean",
|
|
120
|
+
true: 1,
|
|
121
|
+
false: 0,
|
|
122
|
+
},
|
|
83
123
|
}
|
|
84
124
|
```
|
|
85
125
|
|
|
126
|
+
An attribute can define:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
export interface Attribute {
|
|
130
|
+
name?: string
|
|
131
|
+
column?: string
|
|
132
|
+
type?: DataType
|
|
133
|
+
default?: string | number | Date | boolean
|
|
134
|
+
key?: boolean
|
|
135
|
+
noinsert?: boolean
|
|
136
|
+
noupdate?: boolean
|
|
137
|
+
version?: boolean
|
|
138
|
+
ignored?: boolean
|
|
139
|
+
true?: string | number
|
|
140
|
+
false?: string | number
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Attribute options
|
|
145
|
+
|
|
146
|
+
| Option | Description |
|
|
147
|
+
| ---------- | --------------------------------------------------------------------------- |
|
|
148
|
+
| `key` | Marks the field as a key used to determine whether an object can be updated |
|
|
149
|
+
| `column` | Database column name when it differs from the object property |
|
|
150
|
+
| `default` | Default value used when the object property is `null` or `undefined` |
|
|
151
|
+
| `noinsert` | Excludes the field from generated `INSERT` statements |
|
|
152
|
+
| `noupdate` | Excludes the field from generated update expressions |
|
|
153
|
+
| `ignored` | Excludes the field from persistence |
|
|
154
|
+
| `version` | Initializes the field to `1` when inserting |
|
|
155
|
+
| `true` | Database value used when a boolean is `true` |
|
|
156
|
+
| `false` | Database value used when a boolean is `false` |
|
|
157
|
+
| `type` | Describes the data type and supports metadata consumers |
|
|
158
|
+
|
|
86
159
|
## Creating a Connection Pool
|
|
87
160
|
|
|
88
161
|
```ts
|
|
@@ -102,12 +175,15 @@ const pool = createPool({
|
|
|
102
175
|
|
|
103
176
|
The library also supports the `max` option as an alias for `connectionLimit` when `connectionLimit` is not explicitly provided.
|
|
104
177
|
|
|
178
|
+
---
|
|
179
|
+
|
|
105
180
|
## Using `PoolManager`
|
|
106
181
|
|
|
107
182
|
`PoolManager` provides the `DB` implementation for a MySQL connection pool.
|
|
108
183
|
|
|
109
184
|
```ts
|
|
110
|
-
import { createPool
|
|
185
|
+
import { createPool } from "mysql2"
|
|
186
|
+
import { PoolManager} from "mysql2-core"
|
|
111
187
|
|
|
112
188
|
const pool = createPool({
|
|
113
189
|
host: "localhost",
|
|
@@ -118,9 +194,22 @@ const pool = createPool({
|
|
|
118
194
|
|
|
119
195
|
const db = new PoolManager(pool)
|
|
120
196
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
197
|
+
interface User {
|
|
198
|
+
id: number
|
|
199
|
+
name: string
|
|
200
|
+
status: string
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const users = await db.query<User>(
|
|
204
|
+
"select id, name from users where status = ?",
|
|
205
|
+
["ACTIVE"]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const affectedRows = await db.execute(
|
|
209
|
+
{
|
|
210
|
+
query: "update users set status = ? where id = ?",
|
|
211
|
+
params: ["ACTIVE", 100]
|
|
212
|
+
}
|
|
124
213
|
)
|
|
125
214
|
```
|
|
126
215
|
|
|
@@ -198,7 +287,25 @@ if (!user) {
|
|
|
198
287
|
|
|
199
288
|
## Execute Scalar
|
|
200
289
|
|
|
201
|
-
`executeScalar()`
|
|
290
|
+
`executeScalar()` is intended for queries that return a single scalar value.
|
|
291
|
+
|
|
292
|
+
It is particularly useful for queries such as:
|
|
293
|
+
|
|
294
|
+
```sql
|
|
295
|
+
SELECT COUNT(*)
|
|
296
|
+
SELECT MAX(id)
|
|
297
|
+
SELECT MIN(id)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const maxId = await db.executeScalar<number>(
|
|
304
|
+
"SELECT MAX(id) FROM users"
|
|
305
|
+
)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Another example:
|
|
202
309
|
|
|
203
310
|
```ts
|
|
204
311
|
const username = await db.executeScalar<string>(
|
|
@@ -207,6 +314,8 @@ const username = await db.executeScalar<string>(
|
|
|
207
314
|
)
|
|
208
315
|
```
|
|
209
316
|
|
|
317
|
+
`executeScalar()` returns the first column of the first row.
|
|
318
|
+
|
|
210
319
|
If there is no row, it returns `null`.
|
|
211
320
|
|
|
212
321
|
## Count
|
|
@@ -220,7 +329,9 @@ const total = await db.count(
|
|
|
220
329
|
)
|
|
221
330
|
```
|
|
222
331
|
|
|
223
|
-
|
|
332
|
+
The result is always a `number`.
|
|
333
|
+
|
|
334
|
+
If the scalar result is `null`, `count()` returns `0`.
|
|
224
335
|
|
|
225
336
|
## Transactions
|
|
226
337
|
|
|
@@ -240,9 +351,9 @@ try {
|
|
|
240
351
|
[100, 2]
|
|
241
352
|
)
|
|
242
353
|
|
|
243
|
-
await tx.commit()
|
|
354
|
+
await tx.commit() // release connection inside commit
|
|
244
355
|
} catch (err) {
|
|
245
|
-
await tx.rollback()
|
|
356
|
+
await tx.rollback() // release connection inside rollback
|
|
246
357
|
throw err
|
|
247
358
|
}
|
|
248
359
|
```
|
|
@@ -251,6 +362,8 @@ try {
|
|
|
251
362
|
|
|
252
363
|
The connection is released when `commit()` or `rollback()` completes.
|
|
253
364
|
|
|
365
|
+
The transaction manager also protects against releasing the same connection more than once.
|
|
366
|
+
|
|
254
367
|
## Batch Execution
|
|
255
368
|
|
|
256
369
|
Statements are represented by:
|
|
@@ -283,15 +396,15 @@ For multiple statements, the pool-level `executeBatch()` obtains a connection an
|
|
|
283
396
|
|
|
284
397
|
The connection is released after completion.
|
|
285
398
|
|
|
286
|
-
## `
|
|
399
|
+
## `requireFirstAffected`
|
|
287
400
|
|
|
288
|
-
`executeBatch()` supports an optional `
|
|
401
|
+
`executeBatch()` supports an optional `requireFirstAffected` flag:
|
|
289
402
|
|
|
290
403
|
```ts
|
|
291
404
|
await db.executeBatch(statements, true)
|
|
292
405
|
```
|
|
293
406
|
|
|
294
|
-
When `
|
|
407
|
+
When `requireFirstAffected` is `true`, the first statement determines whether the remaining statements are executed.
|
|
295
408
|
|
|
296
409
|
If the first statement affects at least one row:
|
|
297
410
|
|
|
@@ -357,6 +470,8 @@ resource.multipleStatements = true
|
|
|
357
470
|
|
|
358
471
|
Each statement is terminated with a semicolon when necessary, and parameters are flattened into a single parameter array.
|
|
359
472
|
|
|
473
|
+
---
|
|
474
|
+
|
|
360
475
|
## Direct Connection Operations
|
|
361
476
|
|
|
362
477
|
The library also exposes functions that work directly with a `Pool` or `PoolConnection`.
|
|
@@ -389,47 +504,36 @@ const rows = await query<User>(
|
|
|
389
504
|
)
|
|
390
505
|
```
|
|
391
506
|
|
|
392
|
-
## Connection
|
|
393
|
-
|
|
394
|
-
The library provides promise-based wrappers around MySQL connection operations.
|
|
507
|
+
## Connection and Transaction helpers
|
|
395
508
|
|
|
396
|
-
|
|
509
|
+
The library exposes transaction helpers:
|
|
397
510
|
|
|
398
511
|
```ts
|
|
399
|
-
import {
|
|
400
|
-
|
|
401
|
-
const connection = await getConnection(pool)
|
|
402
|
-
|
|
403
|
-
try {
|
|
404
|
-
// use connection
|
|
405
|
-
} finally {
|
|
406
|
-
connection.release()
|
|
407
|
-
}
|
|
512
|
+
import { beginTransaction, commit, rollback } from "mysql2-core"
|
|
408
513
|
```
|
|
409
514
|
|
|
410
|
-
|
|
515
|
+
Example:
|
|
411
516
|
|
|
412
517
|
```ts
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
await beginTransaction(connection)
|
|
416
|
-
```
|
|
518
|
+
const connection = await getConnection(pool)
|
|
417
519
|
|
|
418
|
-
|
|
520
|
+
try {
|
|
521
|
+
await beginTransaction(connection)
|
|
419
522
|
|
|
420
|
-
|
|
421
|
-
|
|
523
|
+
await execute(
|
|
524
|
+
connection,
|
|
525
|
+
"insert into users(name) values(?)",
|
|
526
|
+
["John"]
|
|
527
|
+
)
|
|
422
528
|
|
|
423
|
-
await commit(connection)
|
|
529
|
+
await commit(connection) // release connection inside commit
|
|
530
|
+
} catch (err) {
|
|
531
|
+
await rollback(connection) // release connection inside rollback
|
|
532
|
+
throw err
|
|
533
|
+
}
|
|
424
534
|
```
|
|
425
535
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
```ts
|
|
429
|
-
import { rollback } from "mysql2-core"
|
|
430
|
-
|
|
431
|
-
await rollback(connection)
|
|
432
|
-
```
|
|
536
|
+
---
|
|
433
537
|
|
|
434
538
|
## Result Mapping
|
|
435
539
|
|
|
@@ -524,6 +628,8 @@ await db.execute(
|
|
|
524
628
|
|
|
525
629
|
With `resource.string = true`, the object is serialized using `JSON.stringify()`.
|
|
526
630
|
|
|
631
|
+
---
|
|
632
|
+
|
|
527
633
|
## Dynamic SQL Field Helpers
|
|
528
634
|
|
|
529
635
|
### `getFields()`
|
|
@@ -532,18 +638,24 @@ With `resource.string = true`, the object is serialized using `JSON.stringify()`
|
|
|
532
638
|
|
|
533
639
|
```ts
|
|
534
640
|
const fields = getFields(
|
|
535
|
-
["id", "name"],
|
|
641
|
+
["id", "name", "invalid"],
|
|
536
642
|
["id", "name", "email"]
|
|
537
643
|
)
|
|
538
644
|
```
|
|
539
645
|
|
|
646
|
+
The resulting fields are:
|
|
647
|
+
```text
|
|
648
|
+
id
|
|
649
|
+
name
|
|
650
|
+
```
|
|
651
|
+
|
|
540
652
|
### `buildFields()`
|
|
541
653
|
|
|
542
654
|
`buildFields()` produces a comma-separated SQL field list.
|
|
543
655
|
|
|
544
656
|
```ts
|
|
545
657
|
const sqlFields = buildFields(
|
|
546
|
-
["id", "name"],
|
|
658
|
+
["id", "name", "invalid"],
|
|
547
659
|
["id", "name", "email"]
|
|
548
660
|
)
|
|
549
661
|
```
|
|
@@ -560,6 +672,25 @@ When no valid fields are available, `buildFields()` returns:
|
|
|
560
672
|
*
|
|
561
673
|
```
|
|
562
674
|
|
|
675
|
+
Dynamic field names should still come from trusted or validated input because SQL parameters cannot be used for identifiers.
|
|
676
|
+
|
|
677
|
+
```ts
|
|
678
|
+
SELECT ${buildFields(fields, allowedFields)}
|
|
679
|
+
FROM users
|
|
680
|
+
```
|
|
681
|
+
For example:
|
|
682
|
+
```ts
|
|
683
|
+
SELECT ${buildFields(["id", "name", "password", "status"], ["id", "name", "status"])}
|
|
684
|
+
FROM users
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
returns:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
SELECT id, name, status
|
|
691
|
+
FROM users
|
|
692
|
+
```
|
|
693
|
+
|
|
563
694
|
### `getMapField()`
|
|
564
695
|
|
|
565
696
|
`getMapField()` returns a mapped field name when a mapping exists:
|
|
@@ -579,6 +710,8 @@ Result:
|
|
|
579
710
|
id
|
|
580
711
|
```
|
|
581
712
|
|
|
713
|
+
---
|
|
714
|
+
|
|
582
715
|
## Error Handling
|
|
583
716
|
|
|
584
717
|
Database errors are passed through `buildError()`.
|
|
@@ -1266,40 +1399,42 @@ await executeBatchConnection(
|
|
|
1266
1399
|
|
|
1267
1400
|
This allows the statements to participate in an existing transaction.
|
|
1268
1401
|
|
|
1269
|
-
##
|
|
1402
|
+
## Multiple Statements
|
|
1403
|
+
enables batch optimization by combining multiple SQL statements into a single MySQL request. This reduces the number of application-to-database round trips for batch operations. When disabled, statements are executed individually within the same transaction.
|
|
1270
1404
|
|
|
1271
|
-
|
|
1405
|
+
`multipleStatements` is specifically a round-trip optimization.
|
|
1272
1406
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1407
|
+
It enables batch optimization by combining multiple SQL statements into a single MySQL request. This reduces the number of application-to-database round trips for batch operations. When disabled, statements are executed individually within the same transaction.
|
|
1408
|
+
```text
|
|
1409
|
+
multipleStatements = false
|
|
1276
1410
|
|
|
1277
|
-
|
|
1411
|
+
Application
|
|
1412
|
+
│
|
|
1413
|
+
├── SQL 1 ──► MySQL
|
|
1414
|
+
├── SQL 2 ──► MySQL
|
|
1415
|
+
├── SQL 3 ──► MySQL
|
|
1416
|
+
│
|
|
1417
|
+
└── SQL 100 ─► MySQL
|
|
1278
1418
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1419
|
+
≈ 100 database round trips
|
|
1420
|
+
```
|
|
1281
1421
|
|
|
1282
|
-
|
|
1283
|
-
await beginTransaction(connection)
|
|
1422
|
+
versus:
|
|
1284
1423
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
"insert into users(name) values(?)",
|
|
1288
|
-
["John"]
|
|
1289
|
-
)
|
|
1424
|
+
```text
|
|
1425
|
+
multipleStatements = true
|
|
1290
1426
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
connection.release()
|
|
1297
|
-
}
|
|
1427
|
+
Application
|
|
1428
|
+
│
|
|
1429
|
+
└── SQL 1; SQL 2; ...; SQL 100 ──► MySQL
|
|
1430
|
+
|
|
1431
|
+
≈ 1 database round trip
|
|
1298
1432
|
```
|
|
1299
1433
|
|
|
1300
|
-
|
|
1434
|
+
For a batch containing many relatively small statements, that can be a meaningful performance improvement.
|
|
1435
|
+
|
|
1301
1436
|
|
|
1302
|
-
|
|
1437
|
+
Anyway, `multipleStatements` depends on the configuration in ConnectionString. So, I use `multipleStatements` as a global configuration.
|
|
1303
1438
|
|
|
1304
1439
|
Enable:
|
|
1305
1440
|
|
|
@@ -1309,10 +1444,18 @@ import { resource } from "mysql2-core"
|
|
|
1309
1444
|
resource.multipleStatements = true
|
|
1310
1445
|
```
|
|
1311
1446
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
This can reduce the number of database round trips for large batches.
|
|
1447
|
+
It gives you a simple deployment-level decision:
|
|
1315
1448
|
|
|
1449
|
+
```text
|
|
1450
|
+
Connection configuration
|
|
1451
|
+
│
|
|
1452
|
+
▼
|
|
1453
|
+
resource.multipleStatements
|
|
1454
|
+
│
|
|
1455
|
+
├── false → individual execution
|
|
1456
|
+
│
|
|
1457
|
+
└── true → combined batch execution
|
|
1458
|
+
```
|
|
1316
1459
|
## Serialization
|
|
1317
1460
|
|
|
1318
1461
|
`toArray()` prepares parameter values before they are passed to `mysql2`.
|
|
@@ -1538,10 +1681,455 @@ This architecture keeps applications lightweight, modular, and easy to maintain.
|
|
|
1538
1681
|
* MySQL
|
|
1539
1682
|
* [`mysql2`](https://www.npmjs.com/package/mysql2)
|
|
1540
1683
|
|
|
1684
|
+
## Important Notes
|
|
1685
|
+
|
|
1686
|
+
Table names and column names are inserted into generated SQL and should therefore be treated as trusted configuration.
|
|
1687
|
+
|
|
1688
|
+
Values are generally represented as query parameters, while SQL identifiers are generated from metadata.
|
|
1689
|
+
|
|
1690
|
+
`buildToSave()` uses MySQL `INSERT ... ON DUPLICATE KEY UPDATE` semantics when key fields are available. The actual duplicate condition is determined by MySQL's unique indexes.
|
|
1691
|
+
|
|
1692
|
+
The `version` attribute currently initializes a version field to `1` during insertion; it does not by itself provide optimistic-locking checks.
|
|
1693
|
+
|
|
1694
|
+
MySQL affected-row counts should be interpreted according to MySQL semantics. In particular, an upsert that does not change an existing row can report zero affected rows.
|
|
1695
|
+
|
|
1541
1696
|
## License
|
|
1542
1697
|
|
|
1543
1698
|
MIT
|
|
1544
1699
|
|
|
1700
|
+
# mysql2-core
|
|
1701
|
+
|
|
1702
|
+
`mysql2-core` is a lightweight TypeScript database utility library for MySQL applications built on top of [`mysql2`](https://www.npmjs.com/package/mysql2).
|
|
1703
|
+
|
|
1704
|
+
It provides reusable abstractions for SQL execution, transactions, metadata-driven persistence, batch operations, and streaming/batch writers while keeping the underlying MySQL driver accessible.
|
|
1705
|
+
|
|
1706
|
+
## Features
|
|
1707
|
+
|
|
1708
|
+
* Promise-based MySQL execution helpers
|
|
1709
|
+
* `Executor`, `DB`, and `Transaction` abstractions
|
|
1710
|
+
* Transaction and connection management
|
|
1711
|
+
* Single-statement and batch execution
|
|
1712
|
+
* Optional MySQL multiple-statement execution
|
|
1713
|
+
* Metadata-driven insert/upsert generation
|
|
1714
|
+
* Configurable column mappings
|
|
1715
|
+
* Boolean value mapping
|
|
1716
|
+
* Default values
|
|
1717
|
+
* Ignored, insert-only, and update-only attributes
|
|
1718
|
+
* Version-field initialization
|
|
1719
|
+
* Scalar and count execution interfaces
|
|
1720
|
+
* Generic `save()` and `saveBatch()` helpers
|
|
1721
|
+
* `MySQLWriter`
|
|
1722
|
+
* `MySQLBatchWriter`
|
|
1723
|
+
* `MySQLStreamWriter`
|
|
1724
|
+
* Custom SQL parameter builders for different SQL dialect conventions
|
|
1725
|
+
* JSON serialization support for object parameters
|
|
1726
|
+
* Duplicate-key error normalization
|
|
1727
|
+
|
|
1728
|
+
## Installation
|
|
1729
|
+
|
|
1730
|
+
```bash
|
|
1731
|
+
npm install mysql2-core mysql2
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
## Basic Usage
|
|
1735
|
+
|
|
1736
|
+
The library works with a `mysql2` pool or connection.
|
|
1737
|
+
|
|
1738
|
+
```ts
|
|
1739
|
+
import mysql from "mysql2"
|
|
1740
|
+
import { execute, getConnection } from "mysql2-core"
|
|
1741
|
+
|
|
1742
|
+
const pool = mysql.createPool({
|
|
1743
|
+
host: "localhost",
|
|
1744
|
+
user: "root",
|
|
1745
|
+
password: "password",
|
|
1746
|
+
database: "app",
|
|
1747
|
+
}).promise()
|
|
1748
|
+
|
|
1749
|
+
const affected = await execute(
|
|
1750
|
+
pool as any,
|
|
1751
|
+
"update users set name = ? where id = ?",
|
|
1752
|
+
["John", 1],
|
|
1753
|
+
)
|
|
1754
|
+
|
|
1755
|
+
console.log(affected)
|
|
1756
|
+
```
|
|
1757
|
+
|
|
1758
|
+
For applications using the callback-oriented `mysql2` pool/connection types directly, the execution helpers operate on `Pool` and `PoolConnection`.
|
|
1759
|
+
|
|
1760
|
+
## Generating Save Statements
|
|
1761
|
+
|
|
1762
|
+
`buildToSave()` converts an object and its metadata into a SQL statement.
|
|
1763
|
+
|
|
1764
|
+
```ts
|
|
1765
|
+
const attrs = {
|
|
1766
|
+
id: {
|
|
1767
|
+
key: true,
|
|
1768
|
+
type: "integer",
|
|
1769
|
+
},
|
|
1770
|
+
name: {
|
|
1771
|
+
type: "string",
|
|
1772
|
+
},
|
|
1773
|
+
active: {
|
|
1774
|
+
type: "boolean",
|
|
1775
|
+
},
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const statement = buildToSave(
|
|
1779
|
+
{
|
|
1780
|
+
id: 10,
|
|
1781
|
+
name: "John",
|
|
1782
|
+
active: true,
|
|
1783
|
+
},
|
|
1784
|
+
"users",
|
|
1785
|
+
attrs,
|
|
1786
|
+
)
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
When a key is available, the generated statement uses MySQL's duplicate-key upsert syntax:
|
|
1790
|
+
|
|
1791
|
+
```sql
|
|
1792
|
+
insert into users(...)
|
|
1793
|
+
values(...)
|
|
1794
|
+
on duplicate key update ...
|
|
1795
|
+
```
|
|
1796
|
+
|
|
1797
|
+
When the key is missing, the operation becomes a normal insert.
|
|
1798
|
+
|
|
1799
|
+
## Saving Objects
|
|
1800
|
+
|
|
1801
|
+
The `save()` helper combines statement generation and execution:
|
|
1802
|
+
|
|
1803
|
+
```ts
|
|
1804
|
+
const affected = await save(
|
|
1805
|
+
pool,
|
|
1806
|
+
{
|
|
1807
|
+
id: 10,
|
|
1808
|
+
name: "John",
|
|
1809
|
+
active: true,
|
|
1810
|
+
},
|
|
1811
|
+
"users",
|
|
1812
|
+
attrs,
|
|
1813
|
+
)
|
|
1814
|
+
```
|
|
1815
|
+
|
|
1816
|
+
The same helper can also receive a custom execution function:
|
|
1817
|
+
|
|
1818
|
+
```ts
|
|
1819
|
+
const affected = await save(
|
|
1820
|
+
async (sql, args) => {
|
|
1821
|
+
return executor.execute(sql, args)
|
|
1822
|
+
},
|
|
1823
|
+
user,
|
|
1824
|
+
"users",
|
|
1825
|
+
attrs,
|
|
1826
|
+
)
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
This makes the SQL-building component usable with a custom database abstraction.
|
|
1830
|
+
|
|
1831
|
+
## Batch Saving
|
|
1832
|
+
|
|
1833
|
+
Multiple objects can be converted into statements with:
|
|
1834
|
+
|
|
1835
|
+
```ts
|
|
1836
|
+
const statements = buildToSaveBatch(
|
|
1837
|
+
users,
|
|
1838
|
+
"users",
|
|
1839
|
+
attrs,
|
|
1840
|
+
)
|
|
1841
|
+
```
|
|
1842
|
+
|
|
1843
|
+
Or directly executed:
|
|
1844
|
+
|
|
1845
|
+
```ts
|
|
1846
|
+
const affected = await saveBatch(
|
|
1847
|
+
pool,
|
|
1848
|
+
users,
|
|
1849
|
+
"users",
|
|
1850
|
+
attrs,
|
|
1851
|
+
)
|
|
1852
|
+
```
|
|
1853
|
+
|
|
1854
|
+
Multiple statements are executed inside a transaction.
|
|
1855
|
+
|
|
1856
|
+
## Batch Execution
|
|
1857
|
+
|
|
1858
|
+
Use `executeBatch()` for a collection of SQL statements:
|
|
1859
|
+
|
|
1860
|
+
```ts
|
|
1861
|
+
const count = await executeBatch(pool, [
|
|
1862
|
+
{
|
|
1863
|
+
query: "update users set active = ? where id = ?",
|
|
1864
|
+
params: [true, 1],
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
query: "update users set active = ? where id = ?",
|
|
1868
|
+
params: [false, 2],
|
|
1869
|
+
},
|
|
1870
|
+
])
|
|
1871
|
+
```
|
|
1872
|
+
|
|
1873
|
+
For multiple statements, the helper obtains a connection, starts a transaction, executes the statements, and commits the transaction.
|
|
1874
|
+
|
|
1875
|
+
If an execution fails, the transaction is rolled back and the original error is re-thrown.
|
|
1876
|
+
|
|
1877
|
+
## Multiple Statements
|
|
1878
|
+
|
|
1879
|
+
The library can optionally combine statements into one MySQL request.
|
|
1880
|
+
|
|
1881
|
+
```ts
|
|
1882
|
+
resource.multipleStatements = true
|
|
1883
|
+
```
|
|
1884
|
+
|
|
1885
|
+
When enabled, statements are concatenated with semicolons and executed together.
|
|
1886
|
+
|
|
1887
|
+
When disabled, statements are executed individually inside the same transaction.
|
|
1888
|
+
|
|
1889
|
+
For safety, table names and column names should always come from trusted application configuration and should never be derived directly from untrusted input.
|
|
1890
|
+
|
|
1891
|
+
## Transactions
|
|
1892
|
+
|
|
1893
|
+
Connection and transaction helpers are available directly:
|
|
1894
|
+
|
|
1895
|
+
```ts
|
|
1896
|
+
const connection = await getConnection(pool)
|
|
1897
|
+
|
|
1898
|
+
await beginTransaction(connection)
|
|
1899
|
+
|
|
1900
|
+
try {
|
|
1901
|
+
await execute(connection, "update ...", [...])
|
|
1902
|
+
await execute(connection, "insert ...", [...])
|
|
1903
|
+
|
|
1904
|
+
await commit(connection)
|
|
1905
|
+
} catch (err) {
|
|
1906
|
+
await rollback(connection)
|
|
1907
|
+
throw err
|
|
1908
|
+
}
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
The batch execution APIs already provide this transaction handling automatically.
|
|
1912
|
+
|
|
1913
|
+
## Boolean Mapping
|
|
1914
|
+
|
|
1915
|
+
Boolean attributes can specify the values stored in MySQL:
|
|
1916
|
+
|
|
1917
|
+
```ts
|
|
1918
|
+
const attrs = {
|
|
1919
|
+
active: {
|
|
1920
|
+
type: "boolean",
|
|
1921
|
+
true: 1,
|
|
1922
|
+
false: 0,
|
|
1923
|
+
},
|
|
1924
|
+
}
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
The generated parameters become:
|
|
1928
|
+
|
|
1929
|
+
```text
|
|
1930
|
+
true -> 1
|
|
1931
|
+
false -> 0
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
Custom values are supported:
|
|
1935
|
+
|
|
1936
|
+
```ts
|
|
1937
|
+
const attrs = {
|
|
1938
|
+
active: {
|
|
1939
|
+
type: "boolean",
|
|
1940
|
+
true: "Y",
|
|
1941
|
+
false: "N",
|
|
1942
|
+
},
|
|
1943
|
+
}
|
|
1944
|
+
```
|
|
1945
|
+
|
|
1946
|
+
## Object Parameters
|
|
1947
|
+
|
|
1948
|
+
`toArray()` normalizes query parameters.
|
|
1949
|
+
|
|
1950
|
+
By default, JavaScript objects are passed to the MySQL driver as objects.
|
|
1951
|
+
|
|
1952
|
+
The library can optionally serialize object values to JSON:
|
|
1953
|
+
|
|
1954
|
+
```ts
|
|
1955
|
+
resource.string = true
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
Dates are preserved as `Date` objects.
|
|
1959
|
+
|
|
1960
|
+
## Duplicate Errors
|
|
1961
|
+
|
|
1962
|
+
MySQL duplicate-key errors are normalized by `buildError()`.
|
|
1963
|
+
|
|
1964
|
+
For:
|
|
1965
|
+
|
|
1966
|
+
```text
|
|
1967
|
+
ER_DUP_ENTRY
|
|
1968
|
+
errno = 1062
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
the error receives:
|
|
1972
|
+
|
|
1973
|
+
```ts
|
|
1974
|
+
error = "duplicate"
|
|
1975
|
+
```
|
|
1976
|
+
|
|
1977
|
+
This allows application code to recognize duplicate persistence failures without depending entirely on MySQL's error-code representation.
|
|
1978
|
+
|
|
1979
|
+
## Writers
|
|
1980
|
+
|
|
1981
|
+
### MySQLWriter
|
|
1982
|
+
|
|
1983
|
+
`MySQLWriter<T>` provides a reusable object writer.
|
|
1984
|
+
|
|
1985
|
+
```ts
|
|
1986
|
+
const writer = new MySQLWriter(
|
|
1987
|
+
pool,
|
|
1988
|
+
"users",
|
|
1989
|
+
attrs,
|
|
1990
|
+
)
|
|
1991
|
+
|
|
1992
|
+
await writer.write({
|
|
1993
|
+
id: 1,
|
|
1994
|
+
name: "John",
|
|
1995
|
+
active: true,
|
|
1996
|
+
})
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
An optional mapper can transform the object before persistence:
|
|
2000
|
+
|
|
2001
|
+
```ts
|
|
2002
|
+
const writer = new MySQLWriter(
|
|
2003
|
+
pool,
|
|
2004
|
+
"users",
|
|
2005
|
+
attrs,
|
|
2006
|
+
false,
|
|
2007
|
+
user => ({
|
|
2008
|
+
...user,
|
|
2009
|
+
name: user.name.trim(),
|
|
2010
|
+
}),
|
|
2011
|
+
)
|
|
2012
|
+
```
|
|
2013
|
+
|
|
2014
|
+
The `oneIfSuccess` option converts a positive affected-row count into `1`:
|
|
2015
|
+
|
|
2016
|
+
```ts
|
|
2017
|
+
const writer = new MySQLWriter(
|
|
2018
|
+
pool,
|
|
2019
|
+
"users",
|
|
2020
|
+
attrs,
|
|
2021
|
+
true,
|
|
2022
|
+
)
|
|
2023
|
+
```
|
|
2024
|
+
|
|
2025
|
+
## MySQLBatchWriter
|
|
2026
|
+
|
|
2027
|
+
`MySQLBatchWriter<T>` accepts a collection of objects and writes them as one transactional batch.
|
|
2028
|
+
|
|
2029
|
+
```ts
|
|
2030
|
+
const writer = new MySQLBatchWriter(
|
|
2031
|
+
pool,
|
|
2032
|
+
"users",
|
|
2033
|
+
attrs,
|
|
2034
|
+
)
|
|
2035
|
+
|
|
2036
|
+
await writer.write(users)
|
|
2037
|
+
```
|
|
2038
|
+
|
|
2039
|
+
A mapper can also be supplied:
|
|
2040
|
+
|
|
2041
|
+
```ts
|
|
2042
|
+
const writer = new MySQLBatchWriter(
|
|
2043
|
+
pool,
|
|
2044
|
+
"users",
|
|
2045
|
+
attrs,
|
|
2046
|
+
user => ({
|
|
2047
|
+
...user,
|
|
2048
|
+
name: user.name.trim(),
|
|
2049
|
+
}),
|
|
2050
|
+
)
|
|
2051
|
+
```
|
|
2052
|
+
|
|
2053
|
+
## MySQLStreamWriter
|
|
2054
|
+
|
|
2055
|
+
`MySQLStreamWriter<T>` accumulates objects until a configurable batch size is reached.
|
|
2056
|
+
|
|
2057
|
+
```ts
|
|
2058
|
+
const writer = new MySQLStreamWriter(
|
|
2059
|
+
pool,
|
|
2060
|
+
"users",
|
|
2061
|
+
attrs,
|
|
2062
|
+
1000,
|
|
2063
|
+
)
|
|
2064
|
+
|
|
2065
|
+
for (const user of users) {
|
|
2066
|
+
await writer.write(user)
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
await writer.flush()
|
|
2070
|
+
```
|
|
2071
|
+
|
|
2072
|
+
This is useful when processing large collections without building one large SQL request.
|
|
2073
|
+
|
|
2074
|
+
The final `flush()` should be called after the input stream has ended so that the remaining objects are persisted.
|
|
2075
|
+
|
|
2076
|
+
## Number Conversion
|
|
2077
|
+
|
|
2078
|
+
`toString()` converts valid JavaScript numbers into SQL-safe numeric literals:
|
|
2079
|
+
|
|
2080
|
+
```ts
|
|
2081
|
+
toString(123)
|
|
2082
|
+
// "123"
|
|
2083
|
+
|
|
2084
|
+
toString(NaN)
|
|
2085
|
+
// "null"
|
|
2086
|
+
|
|
2087
|
+
toString(Infinity)
|
|
2088
|
+
// "null"
|
|
2089
|
+
```
|
|
2090
|
+
|
|
2091
|
+
Non-finite numbers are represented as SQL `null`.
|
|
2092
|
+
|
|
2093
|
+
## Design
|
|
2094
|
+
|
|
2095
|
+
`mysql2-core` intentionally keeps the persistence model small.
|
|
2096
|
+
|
|
2097
|
+
The library does not attempt to become a full ORM. Instead, it provides a thin layer around:
|
|
2098
|
+
|
|
2099
|
+
```text
|
|
2100
|
+
mysql2
|
|
2101
|
+
│
|
|
2102
|
+
├── connection / pool
|
|
2103
|
+
├── query execution
|
|
2104
|
+
└── transactions
|
|
2105
|
+
│
|
|
2106
|
+
▼
|
|
2107
|
+
mysql2-core
|
|
2108
|
+
│
|
|
2109
|
+
├── Executor
|
|
2110
|
+
├── Metadata
|
|
2111
|
+
├── SQL builders
|
|
2112
|
+
├── save helpers
|
|
2113
|
+
└── writers
|
|
2114
|
+
```
|
|
2115
|
+
|
|
2116
|
+
The application remains responsible for defining its entities, metadata, business rules, and queries.
|
|
2117
|
+
|
|
2118
|
+
## Important Notes
|
|
2119
|
+
|
|
2120
|
+
Table names and column names are inserted into generated SQL and should therefore be treated as trusted configuration.
|
|
2121
|
+
|
|
2122
|
+
Values are generally represented as query parameters, while SQL identifiers are generated from metadata.
|
|
2123
|
+
|
|
2124
|
+
`buildToSave()` uses MySQL `INSERT ... ON DUPLICATE KEY UPDATE` semantics when key fields are available. The actual duplicate condition is determined by MySQL's unique indexes.
|
|
2125
|
+
|
|
2126
|
+
The `version` attribute currently initializes a version field to `1` during insertion; it does not by itself provide optimistic-locking checks.
|
|
2127
|
+
|
|
2128
|
+
MySQL affected-row counts should be interpreted according to MySQL semantics. In particular, an upsert that does not change an existing row can report zero affected rows.
|
|
2129
|
+
|
|
2130
|
+
## License
|
|
2131
|
+
|
|
2132
|
+
MIT2
|
|
1545
2133
|
|
|
1546
2134
|
|
|
1547
2135
|
# mysql2-core
|