data-api-client 2.0.0 → 2.1.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 +349 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +20 -10
- package/dist/compat/errors.d.ts +28 -0
- package/dist/compat/errors.d.ts.map +1 -0
- package/dist/compat/errors.js +163 -0
- package/dist/compat/index.d.ts +7 -0
- package/dist/compat/index.d.ts.map +1 -0
- package/dist/compat/index.js +12 -0
- package/dist/compat/mysql2.d.ts +134 -0
- package/dist/compat/mysql2.d.ts.map +1 -0
- package/dist/compat/mysql2.js +392 -0
- package/dist/compat/pg.d.ts +142 -0
- package/dist/compat/pg.d.ts.map +1 -0
- package/dist/compat/pg.js +344 -0
- package/dist/params.d.ts +1 -1
- package/dist/params.d.ts.map +1 -1
- package/dist/params.js +2 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -2
- package/dist/retry.d.ts +11 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +76 -0
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +5 -3
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -3
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -3,16 +3,34 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/data-api-client)
|
|
4
4
|
[](https://www.npmjs.com/package/data-api-client)
|
|
5
5
|
|
|
6
|
-
> **Note:** Version 2.
|
|
6
|
+
> **Note:** Version 2.1.0 introduces mysql2 and pg compatibility layers with full ORM support! We welcome your feedback and bug reports. Please [open an issue](https://github.com/jeremydaly/data-api-client/issues) if you encounter any problems or have suggestions for improvement.
|
|
7
7
|
>
|
|
8
8
|
> **Using v1.x?** See [README_v1.md](README_v1.md) for v1.x documentation.
|
|
9
9
|
|
|
10
|
-
The **Data API Client** is a lightweight wrapper that simplifies working with the Amazon Aurora Serverless Data API by abstracting away the notion of field values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types. It's basically a [DocumentClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html) for the Data API. It also dramatically simplifies **transactions** and
|
|
10
|
+
The **Data API Client** is a lightweight wrapper that simplifies working with the Amazon Aurora Serverless Data API by abstracting away the notion of field values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types. It's basically a [DocumentClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html) for the Data API. It also dramatically simplifies **transactions**, provides **automatic retry logic** for scale-to-zero clusters, and includes **compatibility layers** for mysql2 and pg with full **ORM support**.
|
|
11
11
|
|
|
12
|
-
**Version 2.
|
|
12
|
+
**Version 2.1** adds mysql2 and pg compatibility layers, automatic retry logic for cluster wake-ups, and verified support for Drizzle ORM and Kysely query builder.
|
|
13
|
+
|
|
14
|
+
**Version 2.0** introduced support for the new [RDS Data API for Aurora Serverless v2 and Aurora provisioned database instances](https://aws.amazon.com/about-aws/whats-new/2024/09/amazon-aurora-mysql-rds-data-api/), enhanced [Amazon Aurora PostgreSQL-Compatible Edition](https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-aurora-postgresql-rds-data-api/) support, migration to AWS SDK v3, full TypeScript implementation, and comprehensive PostgreSQL data type coverage including **automatic array handling**.
|
|
13
15
|
|
|
14
16
|
For more information about the Aurora Serverless Data API, you can review the [official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) or read [Aurora Serverless Data API: An (updated) First Look](https://www.jeremydaly.com/aurora-serverless-data-api-a-first-look/) for some more insights on performance.
|
|
15
17
|
|
|
18
|
+
## What's New in v2.1
|
|
19
|
+
|
|
20
|
+
- **Automatic Retry Logic**: Built-in retry handling for Aurora Serverless scale-to-zero wake-ups
|
|
21
|
+
- Smart detection of `DatabaseResumingException` with optimized retry delays
|
|
22
|
+
- Automatic handling of connection errors with exponential backoff
|
|
23
|
+
- Configurable and enabled by default
|
|
24
|
+
- **mysql2 Compatibility Layer**: Drop-in replacement for the `mysql2/promise` library
|
|
25
|
+
- Full support for connection pools and transactions
|
|
26
|
+
- Works seamlessly with ORMs like Drizzle and Kysely
|
|
27
|
+
- **pg Compatibility Layer**: Drop-in replacement for the `pg` (node-postgres) library
|
|
28
|
+
- Promise-based and callback-based APIs
|
|
29
|
+
- Compatible with ORMs and query builders
|
|
30
|
+
- **ORM Support**: Tested and verified with popular ORMs:
|
|
31
|
+
- **Drizzle ORM**: Full support for both MySQL and PostgreSQL
|
|
32
|
+
- **Kysely**: Query builder support for both engines
|
|
33
|
+
|
|
16
34
|
## What's New in v2.0
|
|
17
35
|
|
|
18
36
|
- **AWS SDK v3**: Migrated from AWS SDK v2 to v3 for smaller bundle sizes and better tree-shaking
|
|
@@ -186,8 +204,49 @@ Below is a table containing all of the possible configuration options for the `d
|
|
|
186
204
|
| database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
|
|
187
205
|
| engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `pg` |
|
|
188
206
|
| hydrateColumnNames | `boolean` | When `true`, results will be returned as objects with column names as keys. If `false`, results will be returned as an array of values. | `true` |
|
|
207
|
+
| namedPlaceholders | `boolean` | Enable named placeholders (`:name` syntax) for mysql2 compatibility layer. When `true`, parameters use object format. Only applies to mysql2 compat layer. | `false` |
|
|
189
208
|
| options | `object` | An _optional_ configuration object that is passed directly into the RDSDataClient constructor. See [AWS SDK docs](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/classes/rdsdataclient.html) for available options. | `{}` |
|
|
190
209
|
| formatOptions | `object` | Formatting options to auto parse dates and coerce native JavaScript date objects to supported date formats. Valid keys are `deserializeDate` and `treatAsLocalDate`. Both accept boolean values. | `deserializeDate: true, treatAsLocalDate: false` |
|
|
210
|
+
| retryOptions | `object` | Configuration for automatic retry logic. Valid keys are `enabled` (boolean), `maxRetries` (number), and `retryableErrors` (string array). | `enabled: true, maxRetries: 9` |
|
|
211
|
+
|
|
212
|
+
### Automatic Retry Logic
|
|
213
|
+
|
|
214
|
+
Version 2.1 includes built-in retry logic to handle Aurora Serverless scale-to-zero cluster wake-ups automatically. When your cluster is paused and needs to resume, the client will automatically retry your queries with optimized delays.
|
|
215
|
+
|
|
216
|
+
**Features:**
|
|
217
|
+
- **Smart Error Detection**: Automatically detects `DatabaseResumingException` and connection errors
|
|
218
|
+
- **Strategy-Based Retries**: Different retry strategies based on error type:
|
|
219
|
+
- DatabaseResumingException: Up to 10 attempts with progressive delays (0s, 2s, 5s, 10s, 15s, 20s, 25s, 30s, 35s, 40s)
|
|
220
|
+
- Connection errors: 3 quick retries with exponential backoff (0s, 2s, 4s)
|
|
221
|
+
- **Enabled by Default**: Works automatically without any configuration
|
|
222
|
+
- **Configurable**: Customize retry behavior per your needs
|
|
223
|
+
|
|
224
|
+
**Configuration:**
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
const data = dataApiClient({
|
|
228
|
+
secretArn: 'arn:...',
|
|
229
|
+
resourceArn: 'arn:...',
|
|
230
|
+
database: 'myDatabase',
|
|
231
|
+
retryOptions: {
|
|
232
|
+
enabled: true, // Enable/disable retries (default: true)
|
|
233
|
+
maxRetries: 9, // Maximum retry attempts (default: 9 for up to 40s total)
|
|
234
|
+
retryableErrors: [] // Additional error patterns to retry (optional)
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Disable retries** (not recommended for scale-to-zero clusters):
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const data = dataApiClient({
|
|
243
|
+
secretArn: 'arn:...',
|
|
244
|
+
resourceArn: 'arn:...',
|
|
245
|
+
retryOptions: { enabled: false }
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The retry logic works seamlessly across all operations: queries, transactions, batch operations, and compatibility layer methods.
|
|
191
250
|
|
|
192
251
|
### Connection Reuse
|
|
193
252
|
|
|
@@ -455,6 +514,293 @@ const data = dataApiClient({
|
|
|
455
514
|
})
|
|
456
515
|
```
|
|
457
516
|
|
|
517
|
+
## mysql2 and pg Compatibility Layers
|
|
518
|
+
|
|
519
|
+
Version 2.1 introduces compatibility layers that allow you to use the Data API Client as a drop-in replacement for popular database libraries. This makes it easy to migrate existing applications or use ORMs without modification.
|
|
520
|
+
|
|
521
|
+
### mysql2 Compatibility
|
|
522
|
+
|
|
523
|
+
Use the Data API Client as a replacement for `mysql2/promise`:
|
|
524
|
+
|
|
525
|
+
```javascript
|
|
526
|
+
import { createMySQLConnection, createMySQLPool } from 'data-api-client/compat/mysql2'
|
|
527
|
+
|
|
528
|
+
// Create a connection
|
|
529
|
+
const connection = createMySQLConnection({
|
|
530
|
+
resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster',
|
|
531
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
|
|
532
|
+
database: 'myDatabase'
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
// Use like mysql2/promise with positional placeholders
|
|
536
|
+
const [rows, fields] = await connection.query('SELECT * FROM users WHERE id = ?', [123])
|
|
537
|
+
await connection.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Alice', 'alice@example.com'])
|
|
538
|
+
|
|
539
|
+
// Note: connection.end() is optional - it's a no-op for Data API (no connection to close)
|
|
540
|
+
|
|
541
|
+
// Create a pool for connection pooling
|
|
542
|
+
const pool = createMySQLPool({
|
|
543
|
+
resourceArn: 'arn:...',
|
|
544
|
+
secretArn: 'arn:...',
|
|
545
|
+
database: 'myDatabase'
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
// Get connection from pool
|
|
549
|
+
pool.getConnection((err, connection) => {
|
|
550
|
+
if (err) throw err
|
|
551
|
+
connection.query('SELECT * FROM users', (err, results) => {
|
|
552
|
+
connection.release() // Optional - no-op for Data API
|
|
553
|
+
// Handle results
|
|
554
|
+
})
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
// Or use promises
|
|
558
|
+
const connection = await pool.getConnection()
|
|
559
|
+
const [rows] = await connection.query('SELECT * FROM users')
|
|
560
|
+
connection.release() // Optional - no-op for Data API
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
#### Named Placeholders Support
|
|
564
|
+
|
|
565
|
+
The mysql2 compatibility layer supports **named placeholders** (`:name` syntax), matching the behavior of the native mysql2 library's `namedPlaceholders` option:
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
import { createMySQLConnection, createMySQLPool } from 'data-api-client/compat/mysql2'
|
|
569
|
+
|
|
570
|
+
// Create a connection with namedPlaceholders enabled
|
|
571
|
+
const connection = createMySQLConnection({
|
|
572
|
+
resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster',
|
|
573
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
|
|
574
|
+
database: 'myDatabase',
|
|
575
|
+
namedPlaceholders: true // Enable named placeholders
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
// Use named placeholders with object parameters
|
|
579
|
+
const [users] = await connection.query(
|
|
580
|
+
'SELECT * FROM users WHERE name = :name AND age > :age',
|
|
581
|
+
{ name: 'Alice', age: 25 }
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
// INSERT with named placeholders
|
|
585
|
+
await connection.query(
|
|
586
|
+
'INSERT INTO users (name, email, active) VALUES (:name, :email, :active)',
|
|
587
|
+
{ name: 'Bob', email: 'bob@example.com', active: true }
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
// UPDATE with named placeholders
|
|
591
|
+
await connection.query(
|
|
592
|
+
'UPDATE users SET age = :newAge WHERE id = :id',
|
|
593
|
+
{ id: 123, newAge: 30 }
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
// Named placeholders work with transactions
|
|
597
|
+
await connection.beginTransaction()
|
|
598
|
+
try {
|
|
599
|
+
await connection.query(
|
|
600
|
+
'INSERT INTO orders (user_id, total) VALUES (:userId, :total)',
|
|
601
|
+
{ userId: 123, total: 99.99 }
|
|
602
|
+
)
|
|
603
|
+
await connection.query(
|
|
604
|
+
'UPDATE users SET last_order = NOW() WHERE id = :userId',
|
|
605
|
+
{ userId: 123 }
|
|
606
|
+
)
|
|
607
|
+
await connection.commit()
|
|
608
|
+
} catch (err) {
|
|
609
|
+
await connection.rollback()
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Named placeholders also work with pools
|
|
613
|
+
const pool = createMySQLPool({
|
|
614
|
+
resourceArn: 'arn:...',
|
|
615
|
+
secretArn: 'arn:...',
|
|
616
|
+
database: 'myDatabase',
|
|
617
|
+
namedPlaceholders: true
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
const [results] = await pool.query(
|
|
621
|
+
'SELECT * FROM products WHERE category = :category AND price < :maxPrice',
|
|
622
|
+
{ category: 'electronics', maxPrice: 500 }
|
|
623
|
+
)
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Named Placeholders Features:**
|
|
627
|
+
- Use `:paramName` syntax in SQL (colon followed by identifier)
|
|
628
|
+
- Pass parameters as objects: `{ paramName: value }`
|
|
629
|
+
- Same parameter can be referenced multiple times in the query
|
|
630
|
+
- Works with all query types (SELECT, INSERT, UPDATE, DELETE)
|
|
631
|
+
- Fully compatible with transactions, pools, and callbacks
|
|
632
|
+
- Backward compatible: positional `?` placeholders still work when `namedPlaceholders` is disabled (default)
|
|
633
|
+
|
|
634
|
+
**Query-Level namedPlaceholders:**
|
|
635
|
+
|
|
636
|
+
You can also enable or disable named placeholders on a per-query basis, which overrides the connection-level setting:
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
// Connection WITHOUT namedPlaceholders at config level
|
|
640
|
+
const connection = createMySQLConnection({
|
|
641
|
+
resourceArn: 'arn:...',
|
|
642
|
+
secretArn: 'arn:...',
|
|
643
|
+
database: 'myDatabase'
|
|
644
|
+
// namedPlaceholders NOT set (defaults to false)
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
// Enable namedPlaceholders for specific queries
|
|
648
|
+
const [rows] = await connection.query(
|
|
649
|
+
{
|
|
650
|
+
sql: 'SELECT * FROM users WHERE username = :username AND age > :minAge',
|
|
651
|
+
namedPlaceholders: true // Enable for this query only
|
|
652
|
+
},
|
|
653
|
+
{ username: 'john_doe', minAge: 25 }
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
// Or explicitly disable for a specific query (when connection has it enabled)
|
|
657
|
+
const [rows2] = await connection.query(
|
|
658
|
+
{
|
|
659
|
+
sql: 'SELECT * FROM users WHERE id = ?',
|
|
660
|
+
namedPlaceholders: false // Use positional placeholders for this query
|
|
661
|
+
},
|
|
662
|
+
[123]
|
|
663
|
+
)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
This allows you to:
|
|
667
|
+
- Use named placeholders in specific queries without enabling it globally
|
|
668
|
+
- Mix named and positional placeholders in different queries
|
|
669
|
+
- Override connection-level settings when needed
|
|
670
|
+
|
|
671
|
+
### pg Compatibility
|
|
672
|
+
|
|
673
|
+
Use the Data API Client as a replacement for `pg` (node-postgres):
|
|
674
|
+
|
|
675
|
+
```javascript
|
|
676
|
+
import { createPgClient, createPgPool } from 'data-api-client/compat/pg'
|
|
677
|
+
|
|
678
|
+
// Create a client
|
|
679
|
+
const client = createPgClient({
|
|
680
|
+
resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster',
|
|
681
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
|
|
682
|
+
database: 'myDatabase'
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
// Note: client.connect() is optional - it's a no-op for Data API (no connection needed)
|
|
686
|
+
await client.connect() // Optional
|
|
687
|
+
|
|
688
|
+
// Use like pg
|
|
689
|
+
const result = await client.query('SELECT * FROM users WHERE id = $1', [123])
|
|
690
|
+
console.log(result.rows)
|
|
691
|
+
|
|
692
|
+
// With callback style
|
|
693
|
+
client.query('SELECT * FROM users', (err, result) => {
|
|
694
|
+
console.log(result.rows)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
// Note: client.end() is optional - it's a no-op for Data API (no connection to close)
|
|
698
|
+
await client.end() // Optional
|
|
699
|
+
|
|
700
|
+
// Create a pool
|
|
701
|
+
const pool = createPgPool({
|
|
702
|
+
resourceArn: 'arn:...',
|
|
703
|
+
secretArn: 'arn:...',
|
|
704
|
+
database: 'myDatabase'
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
const result = await pool.query('SELECT * FROM users WHERE id = $1', [123])
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Using with ORMs
|
|
711
|
+
|
|
712
|
+
The compatibility layers work seamlessly with popular ORMs:
|
|
713
|
+
|
|
714
|
+
#### Drizzle ORM
|
|
715
|
+
|
|
716
|
+
**MySQL with Drizzle:**
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
import { drizzle } from 'drizzle-orm/mysql2'
|
|
720
|
+
import { createMySQLPool } from 'data-api-client/compat/mysql2'
|
|
721
|
+
|
|
722
|
+
const pool = createMySQLPool({
|
|
723
|
+
resourceArn: 'arn:...',
|
|
724
|
+
secretArn: 'arn:...',
|
|
725
|
+
database: 'myDatabase'
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
const db = drizzle(pool as any)
|
|
729
|
+
|
|
730
|
+
// Use Drizzle normally
|
|
731
|
+
const users = await db.select().from(usersTable).where(eq(usersTable.id, 123))
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
**PostgreSQL with Drizzle:**
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
738
|
+
import { createPgClient } from 'data-api-client/compat/pg'
|
|
739
|
+
|
|
740
|
+
const client = createPgClient({
|
|
741
|
+
resourceArn: 'arn:...',
|
|
742
|
+
secretArn: 'arn:...',
|
|
743
|
+
database: 'myDatabase'
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
// Note: client.connect() is optional (no-op for Data API)
|
|
747
|
+
await client.connect() // Optional - can be omitted
|
|
748
|
+
const db = drizzle(client as any)
|
|
749
|
+
|
|
750
|
+
// Use Drizzle normally
|
|
751
|
+
const users = await db.select().from(usersTable).where(eq(usersTable.id, 123))
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
#### Kysely Query Builder
|
|
755
|
+
|
|
756
|
+
**MySQL with Kysely:**
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
import { Kysely, MysqlDialect } from 'kysely'
|
|
760
|
+
import { createMySQLPool } from 'data-api-client/compat/mysql2'
|
|
761
|
+
|
|
762
|
+
const pool = createMySQLPool({
|
|
763
|
+
resourceArn: 'arn:...',
|
|
764
|
+
secretArn: 'arn:...',
|
|
765
|
+
database: 'myDatabase'
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
const db = new Kysely<Database>({
|
|
769
|
+
dialect: new MysqlDialect({ pool: pool as any })
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
// Use Kysely normally
|
|
773
|
+
const users = await db.selectFrom('users').selectAll().where('id', '=', 123).execute()
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**PostgreSQL with Kysely:**
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
import { Kysely, PostgresDialect } from 'kysely'
|
|
780
|
+
import { createPgPool } from 'data-api-client/compat/pg'
|
|
781
|
+
|
|
782
|
+
const pool = createPgPool({
|
|
783
|
+
resourceArn: 'arn:...',
|
|
784
|
+
secretArn: 'arn:...',
|
|
785
|
+
database: 'myDatabase'
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
const db = new Kysely<Database>({
|
|
789
|
+
dialect: new PostgresDialect({ pool: pool as any })
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
// Use Kysely normally
|
|
793
|
+
const users = await db.selectFrom('users').selectAll().where('id', '=', 123).execute()
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
**Benefits of Compatibility Layers:**
|
|
797
|
+
- **Zero code changes** when migrating from mysql2 or pg
|
|
798
|
+
- **Full ORM support** (Drizzle, Kysely)
|
|
799
|
+
- **Automatic retry logic** for cluster wake-ups
|
|
800
|
+
- **Connection pooling simulation** (getConnection, release)
|
|
801
|
+
- **Both Promise and callback APIs** supported
|
|
802
|
+
- **No-op connection management**: `connect()`, `end()`, and `release()` are optional since the Data API is connectionless - they're included only for backward compatibility with existing code
|
|
803
|
+
|
|
458
804
|
## PostgreSQL Array Support
|
|
459
805
|
|
|
460
806
|
One of the most powerful features in v2.0 is automatic PostgreSQL array handling. While the Data API has limitations with array _parameters_, array _results_ are fully supported and automatically converted to native JavaScript arrays.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAkB,aAAa,EAA6B,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAkB,aAAa,EAA6B,MAAM,SAAS,CAAA;AA0B5G,eAAO,MAAM,IAAI,GAAI,QAAQ,mBAAmB,KAAG,aAoJlD,CAAA"}
|
package/dist/client.js
CHANGED
|
@@ -5,6 +5,7 @@ const client_rds_data_1 = require("@aws-sdk/client-rds-data");
|
|
|
5
5
|
const utils_1 = require("./utils");
|
|
6
6
|
const query_1 = require("./query");
|
|
7
7
|
const transaction_1 = require("./transaction");
|
|
8
|
+
const retry_1 = require("./retry");
|
|
8
9
|
const init = (params) => {
|
|
9
10
|
const options = typeof params.options === 'object'
|
|
10
11
|
? params.options
|
|
@@ -31,39 +32,48 @@ const init = (params) => {
|
|
|
31
32
|
deserializeDate: typeof params.formatOptions === 'object' && params.formatOptions.deserializeDate === false ? false : true,
|
|
32
33
|
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate ? true : false
|
|
33
34
|
},
|
|
35
|
+
retryOptions: {
|
|
36
|
+
enabled: typeof params.retryOptions === 'object' && params.retryOptions.enabled === false ? false : true,
|
|
37
|
+
maxRetries: typeof params.retryOptions === 'object' && typeof params.retryOptions.maxRetries === 'number'
|
|
38
|
+
? params.retryOptions.maxRetries
|
|
39
|
+
: 9,
|
|
40
|
+
retryableErrors: typeof params.retryOptions === 'object' && Array.isArray(params.retryOptions.retryableErrors)
|
|
41
|
+
? params.retryOptions.retryableErrors
|
|
42
|
+
: []
|
|
43
|
+
},
|
|
34
44
|
RDS: params.client ? params.client : new client_rds_data_1.RDSDataClient(options)
|
|
35
45
|
};
|
|
36
46
|
return {
|
|
37
47
|
query: (...x) => query_1.query.call(undefined, config, ...x),
|
|
38
48
|
transaction: (x) => (0, transaction_1.transaction)(config, x),
|
|
39
|
-
batchExecuteStatement: async (args) => config.RDS.send(new client_rds_data_1.BatchExecuteStatementCommand({
|
|
49
|
+
batchExecuteStatement: async (args) => (0, retry_1.withRetry)(() => config.RDS.send(new client_rds_data_1.BatchExecuteStatementCommand({
|
|
40
50
|
...args,
|
|
41
51
|
resourceArn: args.resourceArn || config.resourceArn,
|
|
42
52
|
secretArn: args.secretArn || config.secretArn,
|
|
43
53
|
database: args.database || config.database
|
|
44
|
-
})),
|
|
45
|
-
beginTransaction: async (args) => config.RDS.send(new client_rds_data_1.BeginTransactionCommand({
|
|
54
|
+
})), config.retryOptions),
|
|
55
|
+
beginTransaction: async (args) => (0, retry_1.withRetry)(() => config.RDS.send(new client_rds_data_1.BeginTransactionCommand({
|
|
46
56
|
...(args || {}),
|
|
47
57
|
resourceArn: (args === null || args === void 0 ? void 0 : args.resourceArn) || config.resourceArn,
|
|
48
58
|
secretArn: (args === null || args === void 0 ? void 0 : args.secretArn) || config.secretArn,
|
|
49
59
|
database: (args === null || args === void 0 ? void 0 : args.database) || config.database
|
|
50
|
-
})),
|
|
51
|
-
commitTransaction: async (args) => config.RDS.send(new client_rds_data_1.CommitTransactionCommand({
|
|
60
|
+
})), config.retryOptions),
|
|
61
|
+
commitTransaction: async (args) => (0, retry_1.withRetry)(() => config.RDS.send(new client_rds_data_1.CommitTransactionCommand({
|
|
52
62
|
...args,
|
|
53
63
|
resourceArn: args.resourceArn || config.resourceArn,
|
|
54
64
|
secretArn: args.secretArn || config.secretArn
|
|
55
|
-
})),
|
|
56
|
-
executeStatement: async (args) => config.RDS.send(new client_rds_data_1.ExecuteStatementCommand({
|
|
65
|
+
})), config.retryOptions),
|
|
66
|
+
executeStatement: async (args) => (0, retry_1.withRetry)(() => config.RDS.send(new client_rds_data_1.ExecuteStatementCommand({
|
|
57
67
|
...args,
|
|
58
68
|
resourceArn: args.resourceArn || config.resourceArn,
|
|
59
69
|
secretArn: args.secretArn || config.secretArn,
|
|
60
70
|
database: args.database || config.database
|
|
61
|
-
})),
|
|
62
|
-
rollbackTransaction: async (args) => config.RDS.send(new client_rds_data_1.RollbackTransactionCommand({
|
|
71
|
+
})), config.retryOptions),
|
|
72
|
+
rollbackTransaction: async (args) => (0, retry_1.withRetry)(() => config.RDS.send(new client_rds_data_1.RollbackTransactionCommand({
|
|
63
73
|
...args,
|
|
64
74
|
resourceArn: args.resourceArn || config.resourceArn,
|
|
65
75
|
secretArn: args.secretArn || config.secretArn
|
|
66
|
-
}))
|
|
76
|
+
})), config.retryOptions)
|
|
67
77
|
};
|
|
68
78
|
};
|
|
69
79
|
exports.init = init;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface PostgresError extends Error {
|
|
2
|
+
code?: string;
|
|
3
|
+
severity?: string;
|
|
4
|
+
detail?: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
position?: string;
|
|
7
|
+
internalPosition?: string;
|
|
8
|
+
internalQuery?: string;
|
|
9
|
+
where?: string;
|
|
10
|
+
schema?: string;
|
|
11
|
+
table?: string;
|
|
12
|
+
column?: string;
|
|
13
|
+
dataType?: string;
|
|
14
|
+
constraint?: string;
|
|
15
|
+
file?: string;
|
|
16
|
+
line?: string;
|
|
17
|
+
routine?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MySQLError extends Error {
|
|
20
|
+
code?: string;
|
|
21
|
+
errno?: number;
|
|
22
|
+
sqlState?: string;
|
|
23
|
+
sqlMessage?: string;
|
|
24
|
+
sql?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function mapToPostgresError(error: any): PostgresError;
|
|
27
|
+
export declare function mapToMySQLError(error: any): MySQLError;
|
|
28
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/compat/errors.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAKD,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AA6BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG,aAAa,CAgE5D;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,UAAU,CA+GtD"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapToPostgresError = mapToPostgresError;
|
|
4
|
+
exports.mapToMySQLError = mapToMySQLError;
|
|
5
|
+
function extractConstraintName(message) {
|
|
6
|
+
const match = message.match(/constraint "([^"]+)"/);
|
|
7
|
+
return match ? match[1] : undefined;
|
|
8
|
+
}
|
|
9
|
+
function extractTableName(message) {
|
|
10
|
+
const match = message.match(/table "([^"]+)"/);
|
|
11
|
+
return match ? match[1] : undefined;
|
|
12
|
+
}
|
|
13
|
+
function extractColumnName(message) {
|
|
14
|
+
const match = message.match(/column "([^"]+)"/);
|
|
15
|
+
return match ? match[1] : undefined;
|
|
16
|
+
}
|
|
17
|
+
function mapToPostgresError(error) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
const pgError = new Error(error.message);
|
|
20
|
+
pgError.name = 'error';
|
|
21
|
+
pgError.severity = 'ERROR';
|
|
22
|
+
const message = error.message || '';
|
|
23
|
+
if (message.includes('duplicate key') || message.includes('already exists')) {
|
|
24
|
+
pgError.code = '23505';
|
|
25
|
+
pgError.constraint = extractConstraintName(message);
|
|
26
|
+
pgError.detail = (_a = message.match(/Detail: (.+)/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
27
|
+
}
|
|
28
|
+
else if (message.includes('violates foreign key constraint')) {
|
|
29
|
+
pgError.code = '23503';
|
|
30
|
+
pgError.constraint = extractConstraintName(message);
|
|
31
|
+
pgError.table = extractTableName(message);
|
|
32
|
+
}
|
|
33
|
+
else if (message.includes('violates not-null constraint')) {
|
|
34
|
+
pgError.code = '23502';
|
|
35
|
+
pgError.column = extractColumnName(message);
|
|
36
|
+
pgError.table = extractTableName(message);
|
|
37
|
+
}
|
|
38
|
+
else if (message.includes('violates check constraint')) {
|
|
39
|
+
pgError.code = '23514';
|
|
40
|
+
pgError.constraint = extractConstraintName(message);
|
|
41
|
+
}
|
|
42
|
+
else if (message.includes('syntax error')) {
|
|
43
|
+
pgError.code = '42601';
|
|
44
|
+
pgError.position = (_b = message.match(/at or near "(.+?)"/)) === null || _b === void 0 ? void 0 : _b[1];
|
|
45
|
+
}
|
|
46
|
+
else if (message.includes('column') && message.includes('does not exist')) {
|
|
47
|
+
pgError.code = '42703';
|
|
48
|
+
pgError.column = extractColumnName(message);
|
|
49
|
+
}
|
|
50
|
+
else if (message.includes('relation') && message.includes('does not exist')) {
|
|
51
|
+
pgError.code = '42P01';
|
|
52
|
+
pgError.table = extractTableName(message);
|
|
53
|
+
}
|
|
54
|
+
else if (message.includes('function') && message.includes('does not exist')) {
|
|
55
|
+
pgError.code = '42883';
|
|
56
|
+
}
|
|
57
|
+
else if (message.includes('invalid input syntax')) {
|
|
58
|
+
pgError.code = '22P02';
|
|
59
|
+
}
|
|
60
|
+
else if (message.includes('division by zero')) {
|
|
61
|
+
pgError.code = '22012';
|
|
62
|
+
}
|
|
63
|
+
else if (message.includes('value too long')) {
|
|
64
|
+
pgError.code = '22001';
|
|
65
|
+
}
|
|
66
|
+
else if (message.includes('permission denied')) {
|
|
67
|
+
pgError.code = '42501';
|
|
68
|
+
}
|
|
69
|
+
else if (message.includes('connection') || message.includes('timeout')) {
|
|
70
|
+
pgError.code = '08006';
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
pgError.code = 'EUNKNOWN';
|
|
74
|
+
}
|
|
75
|
+
return pgError;
|
|
76
|
+
}
|
|
77
|
+
function mapToMySQLError(error) {
|
|
78
|
+
const mysqlError = new Error(error.message);
|
|
79
|
+
mysqlError.sqlMessage = error.message || '';
|
|
80
|
+
const message = error.message || '';
|
|
81
|
+
if (message.includes('Duplicate entry') || message.includes('duplicate key')) {
|
|
82
|
+
mysqlError.code = 'ER_DUP_ENTRY';
|
|
83
|
+
mysqlError.errno = 1062;
|
|
84
|
+
mysqlError.sqlState = '23000';
|
|
85
|
+
}
|
|
86
|
+
else if (message.includes('foreign key constraint fails')) {
|
|
87
|
+
if (message.includes('Cannot delete or update a parent row')) {
|
|
88
|
+
mysqlError.code = 'ER_ROW_IS_REFERENCED_2';
|
|
89
|
+
mysqlError.errno = 1451;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
mysqlError.code = 'ER_NO_REFERENCED_ROW_2';
|
|
93
|
+
mysqlError.errno = 1452;
|
|
94
|
+
}
|
|
95
|
+
mysqlError.sqlState = '23000';
|
|
96
|
+
}
|
|
97
|
+
else if (message.includes('cannot be null') || message.includes('NOT NULL')) {
|
|
98
|
+
mysqlError.code = 'ER_BAD_NULL_ERROR';
|
|
99
|
+
mysqlError.errno = 1048;
|
|
100
|
+
mysqlError.sqlState = '23000';
|
|
101
|
+
}
|
|
102
|
+
else if (message.includes("Table") && message.includes("doesn't exist")) {
|
|
103
|
+
mysqlError.code = 'ER_NO_SUCH_TABLE';
|
|
104
|
+
mysqlError.errno = 1146;
|
|
105
|
+
mysqlError.sqlState = '42S02';
|
|
106
|
+
}
|
|
107
|
+
else if (message.includes('Unknown column')) {
|
|
108
|
+
mysqlError.code = 'ER_BAD_FIELD_ERROR';
|
|
109
|
+
mysqlError.errno = 1054;
|
|
110
|
+
mysqlError.sqlState = '42S22';
|
|
111
|
+
}
|
|
112
|
+
else if (message.includes('syntax') || message.includes('SQL syntax')) {
|
|
113
|
+
mysqlError.code = 'ER_PARSE_ERROR';
|
|
114
|
+
mysqlError.errno = 1064;
|
|
115
|
+
mysqlError.sqlState = '42000';
|
|
116
|
+
}
|
|
117
|
+
else if (message.includes('Data too long') || message.includes('too long')) {
|
|
118
|
+
mysqlError.code = 'ER_DATA_TOO_LONG';
|
|
119
|
+
mysqlError.errno = 1406;
|
|
120
|
+
mysqlError.sqlState = '22001';
|
|
121
|
+
}
|
|
122
|
+
else if (message.includes('Division by 0')) {
|
|
123
|
+
mysqlError.code = 'ER_DIVISION_BY_ZERO';
|
|
124
|
+
mysqlError.errno = 1365;
|
|
125
|
+
mysqlError.sqlState = '22012';
|
|
126
|
+
}
|
|
127
|
+
else if (message.includes('Access denied')) {
|
|
128
|
+
mysqlError.code = 'ER_ACCESS_DENIED_ERROR';
|
|
129
|
+
mysqlError.errno = 1045;
|
|
130
|
+
mysqlError.sqlState = '28000';
|
|
131
|
+
}
|
|
132
|
+
else if (message.includes('Deadlock')) {
|
|
133
|
+
mysqlError.code = 'ER_LOCK_DEADLOCK';
|
|
134
|
+
mysqlError.errno = 1213;
|
|
135
|
+
mysqlError.sqlState = '40001';
|
|
136
|
+
}
|
|
137
|
+
else if (message.includes('Lock wait timeout')) {
|
|
138
|
+
mysqlError.code = 'ER_LOCK_WAIT_TIMEOUT';
|
|
139
|
+
mysqlError.errno = 1205;
|
|
140
|
+
mysqlError.sqlState = 'HY000';
|
|
141
|
+
}
|
|
142
|
+
else if (message.includes("Can't connect")) {
|
|
143
|
+
mysqlError.code = 'ER_CONNECTION_ERROR';
|
|
144
|
+
mysqlError.errno = 2003;
|
|
145
|
+
mysqlError.sqlState = 'HY000';
|
|
146
|
+
}
|
|
147
|
+
else if (message.includes('server has gone away')) {
|
|
148
|
+
mysqlError.code = 'ER_SERVER_GONE_ERROR';
|
|
149
|
+
mysqlError.errno = 2006;
|
|
150
|
+
mysqlError.sqlState = 'HY000';
|
|
151
|
+
}
|
|
152
|
+
else if (message.includes('Lost connection')) {
|
|
153
|
+
mysqlError.code = 'ER_SERVER_LOST';
|
|
154
|
+
mysqlError.errno = 2013;
|
|
155
|
+
mysqlError.sqlState = 'HY000';
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
mysqlError.code = 'EUNKNOWN';
|
|
159
|
+
mysqlError.errno = 0;
|
|
160
|
+
mysqlError.sqlState = 'HY000';
|
|
161
|
+
}
|
|
162
|
+
return mysqlError;
|
|
163
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createPgClient, createPgPool } from './pg';
|
|
2
|
+
export type { PgCompatClient, PgCompatPool, PgQueryResult } from './pg';
|
|
3
|
+
export { createMySQLConnection, createMySQLPool } from './mysql2';
|
|
4
|
+
export type { Connection, Pool, PoolConnection, MySQL2QueryResult } from './mysql2';
|
|
5
|
+
export { mapToPostgresError, mapToMySQLError } from './errors';
|
|
6
|
+
export type { PostgresError, MySQLError } from './errors';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compat/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AAEvE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACjE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEnF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC9D,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA"}
|