mysql2-core 0.3.10 → 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 CHANGED
@@ -45,7 +45,7 @@ npm install mysql2 mysql2-core
45
45
  Pool PoolConnection
46
46
  ```
47
47
 
48
- ### Executor
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[], firstAffected?: boolean, ctx?: any): Promise<number>
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
- ### DB
65
+ A **transaction** extends the same interface:
66
66
 
67
- `DB` extends `Executor` with transaction creation:
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
- ### Transaction
82
+ This makes application-level persistence code independent from the concrete MySQL connection implementation.
76
83
 
77
- `Transaction` extends `Executor` with:
84
+
85
+ ## SQL Parameters
86
+
87
+ The default MySQL parameter builder returns:
78
88
 
79
89
  ```ts
80
- interface Transaction extends Executor {
81
- commit(): Promise<void>
82
- rollback(): Promise<void>
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, PoolManager} from "mysql2-core"
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
- const users = await db.query(
122
- "SELECT id, name FROM users WHERE active = ?",
123
- [1]
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()` returns the first column of the first row.
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
- A missing scalar result is returned as `0`.
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
- ## `firstAffected`
399
+ ## `requireFirstAffected`
287
400
 
288
- `executeBatch()` supports an optional `firstAffected` flag:
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 `firstAffected` is `true`, the first statement determines whether the remaining statements are executed.
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 Helpers
393
-
394
- The library provides promise-based wrappers around MySQL connection operations.
507
+ ## Connection and Transaction helpers
395
508
 
396
- ### Get a connection
509
+ The library exposes transaction helpers:
397
510
 
398
511
  ```ts
399
- import { getConnection } from "mysql2-core"
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
- ### Begin a transaction
515
+ Example:
411
516
 
412
517
  ```ts
413
- import { beginTransaction } from "mysql2-core"
414
-
415
- await beginTransaction(connection)
416
- ```
518
+ const connection = await getConnection(pool)
417
519
 
418
- ### Commit
520
+ try {
521
+ await beginTransaction(connection)
419
522
 
420
- ```ts
421
- import { commit } from "mysql2-core"
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
- ### Rollback
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
- ## Transactions
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
- The library exposes transaction helpers:
1405
+ `multipleStatements` is specifically a round-trip optimization.
1272
1406
 
1273
- ```ts
1274
- import { beginTransaction, commit, rollback } from "mysql2-core"
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
- Example:
1411
+ Application
1412
+ │
1413
+ ├── SQL 1 ──► MySQL
1414
+ ├── SQL 2 ──► MySQL
1415
+ ├── SQL 3 ──► MySQL
1416
+ │
1417
+ └── SQL 100 ─► MySQL
1278
1418
 
1279
- ```ts
1280
- const connection = await getConnection(pool)
1419
+ ≈ 100 database round trips
1420
+ ```
1281
1421
 
1282
- try {
1283
- await beginTransaction(connection)
1422
+ versus:
1284
1423
 
1285
- await execute(
1286
- connection,
1287
- "insert into users(name) values(?)",
1288
- ["John"]
1289
- )
1424
+ ```text
1425
+ multipleStatements = true
1290
1426
 
1291
- await commit(connection)
1292
- } catch (err) {
1293
- await rollback(connection)
1294
- throw err
1295
- } finally {
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
- ## Multiple Statements
1434
+ For a batch containing many relatively small statements, that can be a meaningful performance improvement.
1435
+
1301
1436
 
1302
- The batch transaction executor can optionally combine multiple statements into a single MySQL request.
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
- The statements are combined and executed through `connection.query()`.
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