data-api-client 2.0.0-beta.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,68 @@
1
1
  ![Aurora Serverless Data API Client](https://user-images.githubusercontent.com/2053544/79285017-44053500-7e8a-11ea-8515-998ccf9c2d2e.png)
2
2
 
3
- [![Build Status](https://travis-ci.org/jeremydaly/data-api-client.svg?branch=master)](https://travis-ci.org/jeremydaly/data-api-client)
4
3
  [![npm](https://img.shields.io/npm/v/data-api-client.svg)](https://www.npmjs.com/package/data-api-client)
5
4
  [![npm](https://img.shields.io/npm/l/data-api-client.svg)](https://www.npmjs.com/package/data-api-client)
6
5
 
7
- ## v2.0 BETA with support for AWS SDK v3
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
+ >
8
+ > **Using v1.x?** See [README_v1.md](README_v1.md) for v1.x documentation.
8
9
 
9
- 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 exposes simplified versions of the native AWS SDK v3 `RDSDataClient` methods to make working with `async/await` or Promise chains easier AND dramatically simplifies **transactions**.
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
+
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**.
10
15
 
11
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.
12
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
+
34
+ ## What's New in v2.0
35
+
36
+ - **AWS SDK v3**: Migrated from AWS SDK v2 to v3 for smaller bundle sizes and better tree-shaking
37
+ - **TypeScript**: Full TypeScript implementation with comprehensive type definitions
38
+ - **PostgreSQL Array Support**: Automatic conversion of PostgreSQL arrays to native JavaScript arrays in query results
39
+ - **Comprehensive Data Type Coverage**: Extensive support for PostgreSQL data types including:
40
+ - All numeric types (SMALLINT, INT, BIGINT, DECIMAL, NUMERIC, REAL, DOUBLE PRECISION)
41
+ - String types (CHAR, VARCHAR, TEXT)
42
+ - Boolean, Date/Time types (DATE, TIME, TIMESTAMP, TIMESTAMPTZ)
43
+ - Binary data (BYTEA)
44
+ - JSON and JSONB with nested structures
45
+ - UUID with type casting support
46
+ - Network types (INET, CIDR)
47
+ - Range types (INT4RANGE, NUMRANGE, TSTZRANGE)
48
+ - Arrays of all supported types
49
+ - **Modern Build System**: TypeScript compilation with ES6+ output
50
+ - **Enhanced Type Casting**: Improved support for PostgreSQL type casting with inline (`::type`) and parameter-based casting
51
+ - **Better Error Handling**: More informative error messages and validation
52
+
13
53
  ## Simple Examples
14
54
 
15
- The **Data API Client** makes working with the Aurora Serverless Data API super simple. Require and instantiate the library with basic configuration information, then use the `query()` method to manage your workflows. Below are some examples.
55
+ The **Data API Client** makes working with the Aurora Serverless Data API super simple. Import and instantiate the library with basic configuration information, then use the `query()` method to manage your workflows. Below are some examples.
16
56
 
17
57
  ```javascript
18
- // Require and instantiate data-api-client with secret and cluster
19
- const data = require('data-api-client')({
58
+ // Import and instantiate data-api-client with secret and cluster
59
+ import dataApiClient from 'data-api-client'
60
+
61
+ const data = dataApiClient({
20
62
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
21
63
  resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
22
- database: 'myDatabase' // default database
64
+ database: 'myDatabase', // default database
65
+ engine: 'pg' // or 'mysql'
23
66
  })
24
67
 
25
68
  /*** Assuming we're in an async function ***/
@@ -35,31 +78,27 @@ let result = await data.query(`SELECT * FROM myTable`)
35
78
  // }
36
79
 
37
80
  // SELECT with named parameters
38
- let resultParams = await data.query(`SELECT * FROM myTable WHERE id = :id`, {
39
- id: 2
40
- })
81
+ let resultParams = await data.query(`SELECT * FROM myTable WHERE id = :id`, { id: 2 })
41
82
  // { records: [ { id: 2, name: 'Mike', age: 52 } ] }
42
83
 
43
- // INSERT with named parameters
44
- let insert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, {
84
+ // INSERT with named parameters (PostgreSQL with RETURNING)
85
+ let insert = await data.query(`INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls) RETURNING id`, {
45
86
  name: 'Greg',
46
87
  age: 18,
47
88
  curls: false
48
89
  })
49
90
 
50
91
  // BATCH INSERT with named parameters
51
- let batchInsert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, [
92
+ let batchInsert = await data.query(`INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls)`, [
52
93
  [{ name: 'Marcia', age: 17, curls: false }],
53
94
  [{ name: 'Peter', age: 15, curls: false }],
54
95
  [{ name: 'Jan', age: 15, curls: false }],
55
96
  [{ name: 'Cindy', age: 12, curls: true }],
56
97
  [{ name: 'Bobby', age: 12, curls: false }]
57
98
  ])
99
+
58
100
  // Update with named parameters
59
- let update = await data.query(`UPDATE myTable SET age = :age WHERE id = :id`, {
60
- age: 13,
61
- id: 5
62
- })
101
+ let update = await data.query(`UPDATE myTable SET age = :age WHERE id = :id`, { age: 13, id: 5 })
63
102
 
64
103
  // Delete with named parameters
65
104
  let remove = await data.query(
@@ -67,64 +106,77 @@ let remove = await data.query(
67
106
  { name: 'Jan' } // Sorry Jan :(
68
107
  )
69
108
 
70
- // A slightly more advanced example
71
- let custom = data.query({
72
- sql: `SELECT * FROM myOtherTable WHERE id = :id AND active = :isActive`,
73
- continueAfterTimeout: true,
74
- database: 'myOtherDatabase',
75
- parameters: [{ id: 123 }, { name: 'isActive', value: { booleanValue: true } }]
76
- })
109
+ // PostgreSQL with type casting and JSONB
110
+ let pgExample = await data.query(`INSERT INTO users (id, email, metadata) VALUES(:id, :email, :metadata)`, [
111
+ { name: 'id', value: '550e8400-e29b-41d4-a716-446655440000', cast: 'uuid' },
112
+ { name: 'email', value: 'user@example.com' },
113
+ { name: 'metadata', value: JSON.stringify({ role: 'admin' }), cast: 'jsonb' }
114
+ ])
115
+
116
+ // PostgreSQL array result (automatically converted to native JavaScript array)
117
+ let arrayResult = await data.query(`SELECT tags FROM products WHERE id = :id`, { id: 123 })
118
+ // { records: [ { tags: ['new', 'featured', 'sale'] } ] }
77
119
  ```
78
120
 
79
121
  ## Why do I need this?
80
122
 
81
- The [Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) requires you to specify data types when passing in parameters. The basic `INSERT` example above would look like this using the native `RDSDataClient` class:
123
+ The [Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) requires you to specify data types when passing in parameters. The basic `INSERT` example above would look like this using the native AWS SDK v3:
82
124
 
83
125
  ```javascript
84
- const { RDSDataClient, ExecuteStatementCommand } = require('@aws-sdk/client-rds-data')
85
- const client = new RDSDataClient({ region: 'us-east-1' })
126
+ import { RDSDataClient, ExecuteStatementCommand } from '@aws-sdk/client-rds-data'
127
+ const client = new RDSDataClient()
86
128
 
87
- const params = {
88
- secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
89
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
90
- database: 'myDatabase',
91
- sql: 'INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)',
92
- parameters: [
93
- { name: 'name', value: { stringValue: 'Cousin Oliver' } },
94
- { name: 'age', value: { longValue: 10 } },
95
- { name: 'curls', value: { booleanValue: false } }
96
- ]
97
- }
98
-
99
- const command = new ExecuteStatementCommand(params)
129
+ /*** Assuming we're in an async function ***/
100
130
 
101
- // async/await.
102
- try {
103
- const data = await client.send(command)
104
- // process data.
105
- } catch (error) {
106
- // error handling.
107
- }
131
+ // INSERT with named parameters
132
+ let insert = await client.send(
133
+ new ExecuteStatementCommand({
134
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
135
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
136
+ database: 'myDatabase',
137
+ sql: 'INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls)',
138
+ parameters: [
139
+ { name: 'name', value: { stringValue: 'Cousin Oliver' } },
140
+ { name: 'age', value: { longValue: 10 } },
141
+ { name: 'curls', value: { booleanValue: false } }
142
+ ]
143
+ })
144
+ )
108
145
  ```
109
146
 
110
- Specifying all of those data types in the parameters and writing all that SDK code is a bit clunky. In addition to requiring types for parameters, it also returns each field as an object with its value assigned to a key that represents its data type, like this:
147
+ Specifying all of those data types in the parameters is a bit clunky. In addition to requiring types for parameters, it also returns each field as an object with its value assigned to a key that represents its data type, like this:
111
148
 
112
149
  ```javascript
113
- { // id field
114
- "longValue": 9
150
+ {
151
+ // id field
152
+ longValue: 9
115
153
  },
116
- { // name field
117
- "stringValue": "Cousin Oliver"
154
+ {
155
+ // name field
156
+ stringValue: 'Cousin Oliver'
118
157
  },
119
- { // age field
120
- "longValue": 10
158
+ {
159
+ // age field
160
+ longValue: 10
121
161
  },
122
- { // has_curls field
123
- "booleanValue": false
162
+ {
163
+ // has_curls field
164
+ booleanValue: false
124
165
  }
125
166
  ```
126
167
 
127
- Not only are there no column names, but you have to pull the value from the data type field. Lots of extra work that the **Data API Client** handles automatically for you. 😀
168
+ Not only are there no column names, but you have to pull the value from the data type field. And if you're using PostgreSQL arrays, you get a complex nested structure:
169
+
170
+ ```javascript
171
+ {
172
+ // tags field (PostgreSQL array)
173
+ arrayValue: {
174
+ stringValues: ['admin', 'editor', 'viewer']
175
+ }
176
+ }
177
+ ```
178
+
179
+ Lots of extra work that the **Data API Client** handles automatically for you, converting arrays to native JavaScript arrays and providing clean, usable data. 😀
128
180
 
129
181
  ## Installation and Setup
130
182
 
@@ -132,40 +184,88 @@ Not only are there no column names, but you have to pull the value from the data
132
184
  npm i data-api-client
133
185
  ```
134
186
 
187
+ The library has AWS SDK v3's `@aws-sdk/client-rds-data` as an optional peer dependency. In AWS Lambda, the SDK is provided by the runtime. For local development or other environments, install it separately:
188
+
189
+ ```
190
+ npm i @aws-sdk/client-rds-data
191
+ ```
192
+
135
193
  For more information on enabling Data API, see [Enabling Data API](#enabling-data-api).
136
194
 
137
195
  ## Configuration Options
138
196
 
139
197
  Below is a table containing all of the possible configuration options for the `data-api-client`. Additional details are provided throughout the documentation.
140
198
 
141
- | Property | Type | Description | Default |
142
- | ------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
143
- | resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is _required_, but can be overridden when querying. | |
144
- | secretArn | `string` | The ARN of the secret associated with your database credentials. This is _required_, but can be overridden when querying. | |
145
- | database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
146
- | engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `mysql` |
147
- | 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` |
148
- | options | `object` | An _optional_ configuration object that is passed directly into the v3 RDSDataClient constructor. Use this to set `region`, `tls`, etc. See [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/interfaces/rdsdataclientconfig.html) for available options. | `{}` |
149
- | formatOptions | `object` | Formatting options to auto parse dates and coerce native JavaScript date objects to MySQL supported date formats. Valid keys are `deserializeDate` and `treatAsLocalDate`. Both accept boolean values. | Both `false` |
150
- | wrapper | `function` | A custom wrapper around `@aws-sdk/client-rds-data` used to enable AWS X-Ray. | |
199
+ | Property | Type | Description | Default |
200
+ | ------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
201
+ | client | `RDSDataClient` | A custom `@aws-sdk/client-rds-data` instance (for X-Ray tracing, custom config, etc.) | |
202
+ | resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is _required_, but can be overridden when querying. | |
203
+ | secretArn | `string` | The ARN of the secret associated with your database credentials. This is _required_, but can be overridden when querying. | |
204
+ | database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
205
+ | engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `pg` |
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
+ | 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. | `{}` |
208
+ | 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` |
209
+ | retryOptions | `object` | Configuration for automatic retry logic. Valid keys are `enabled` (boolean), `maxRetries` (number), and `retryableErrors` (string array). | `enabled: true, maxRetries: 9` |
210
+
211
+ ### Automatic Retry Logic
212
+
213
+ 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.
214
+
215
+ **Features:**
216
+ - **Smart Error Detection**: Automatically detects `DatabaseResumingException` and connection errors
217
+ - **Strategy-Based Retries**: Different retry strategies based on error type:
218
+ - DatabaseResumingException: Up to 10 attempts with progressive delays (0s, 2s, 5s, 10s, 15s, 20s, 25s, 30s, 35s, 40s)
219
+ - Connection errors: 3 quick retries with exponential backoff (0s, 2s, 4s)
220
+ - **Enabled by Default**: Works automatically without any configuration
221
+ - **Configurable**: Customize retry behavior per your needs
222
+
223
+ **Configuration:**
224
+
225
+ ```javascript
226
+ const data = dataApiClient({
227
+ secretArn: 'arn:...',
228
+ resourceArn: 'arn:...',
229
+ database: 'myDatabase',
230
+ retryOptions: {
231
+ enabled: true, // Enable/disable retries (default: true)
232
+ maxRetries: 9, // Maximum retry attempts (default: 9 for up to 40s total)
233
+ retryableErrors: [] // Additional error patterns to retry (optional)
234
+ }
235
+ })
236
+ ```
237
+
238
+ **Disable retries** (not recommended for scale-to-zero clusters):
239
+
240
+ ```javascript
241
+ const data = dataApiClient({
242
+ secretArn: 'arn:...',
243
+ resourceArn: 'arn:...',
244
+ retryOptions: { enabled: false }
245
+ })
246
+ ```
247
+
248
+ The retry logic works seamlessly across all operations: queries, transactions, batch operations, and compatibility layer methods.
151
249
 
152
250
  ### Connection Reuse
153
251
 
154
- It is recommended to enable connection reuse as this dramatically decreases the latency of subsequent calls to the AWS API. This can be done by setting an environment variable
155
- `AWS_NODEJS_CONNECTION_REUSE_ENABLED=1`. For more information see the [AWS SDK documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html).
252
+ It is recommended to enable connection reuse as this dramatically decreases the latency of subsequent calls to the AWS API. This can be done by setting an environment variable `AWS_NODEJS_CONNECTION_REUSE_ENABLED=1`. For more information see the [AWS SDK documentation](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html).
156
253
 
157
254
  ## How to use this module
158
255
 
159
- The **Data API Client** wraps the AWS SDK v3 [RDSDataClient Class](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/index.html), providing you with a number of convenience features to make your workflow easier. The module also exposes simplified versions of all the native `RDSDataClient` methods, with your default configuration information already merged in. 😉
256
+ The **Data API Client** wraps the [RDSDataClient Class](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/classes/rdsdataclient.html), providing you with a number of convenience features to make your workflow easier. The module also exposes all the standard `RDSDataClient` methods with your default configuration information already merged in. 😉
160
257
 
161
- To use the Data API Client, require the module and instantiate it with your [Configuration options](#configuration-options). If you are using it with AWS Lambda, require it **OUTSIDE** your main handler function. This will allow you to reuse the initialized module on subsequent invocations.
258
+ To use the Data API Client, import the module and instantiate it with your [Configuration options](#configuration-options). If you are using it with AWS Lambda, require it **OUTSIDE** your main handler function. This will allow you to reuse the initialized module on subsequent invocations.
162
259
 
163
260
  ```javascript
164
- // Require and instantiate data-api-client with secret and cluster arns
165
- const data = require('data-api-client')({
261
+ // Import and instantiate data-api-client with secret and cluster arns
262
+ import dataApiClient from 'data-api-client'
263
+
264
+ const data = dataApiClient({
166
265
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
167
266
  resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
168
- database: 'myDatabase' // set a default database
267
+ database: 'myDatabase', // set a default database
268
+ engine: 'pg' // specify 'pg' for PostgreSQL or 'mysql' for MySQL
169
269
  })
170
270
  ```
171
271
 
@@ -187,7 +287,7 @@ By default, this will return your rows as an array of objects with column names
187
287
  ]
188
288
  ```
189
289
 
190
- To query with parameters, you can use named parameters in your SQL, and then provider an object containing your parameters as the second argument to the `query()` method:
290
+ To query with parameters, you can use named parameters in your SQL, and then provide an object containing your parameters as the second argument to the `query()` method:
191
291
 
192
292
  ```javascript
193
293
  let result = await data.query(
@@ -197,14 +297,14 @@ let result = await data.query(
197
297
  )
198
298
  ```
199
299
 
200
- The Data API Client will automatically convert your parameters into the correct Data API parameter format using native JavaScript types. If you prefer to use the clunky format, or you need more control over the data type, you can just pass in the `RDSDataClient` format:
300
+ The Data API Client will automatically convert your parameters into the correct Data API parameter format using native JavaScript types. If you prefer more control over the data type, you can use the extended parameter format:
201
301
 
202
302
  ```javascript
203
303
  let result = await data.query(`SELECT * FROM myTable WHERE id = :id AND created > :createDate`, [
204
304
  // An array of objects is totally cool, too. We'll merge them for you.
205
305
  { id: 2 },
206
- // Data API Client just passes this straight on through
207
- { name: 'createDate', value: { blobValue: new Buffer('2019-06-01') } }
306
+ // Extended format for more control
307
+ { name: 'createDate', value: '2019-06-01' }
208
308
  ])
209
309
  ```
210
310
 
@@ -213,17 +313,16 @@ If you want even more control, you can pass in an `object` as the first paramete
213
313
  ```javascript
214
314
  let result = await data.query({
215
315
  sql: `SELECT * FROM myTable WHERE id = :id`,
216
- parameters: [ { id: 2 } ], // or just { id: 2 }
316
+ parameters: [{ id: 2 }], // or just { id: 2 }
217
317
  database: 'someOtherDatabase', // override default database
218
- schema: 'mySchema', // RDSDataClient config option
219
- continueAfterTimeout: true, // RDSDataClient config option (non-batch only)
220
- includeResultMetadata: true, // RDSDataClient config option (non-batch only)
318
+ continueAfterTimeout: true, // RDSDataService config option (non-batch only)
319
+ includeResultMetadata: true, // RDSDataService config option (non-batch only)
221
320
  hydrateColumnNames: false, // Returns each record as an arrays of values
222
- transactionId: 'AQC5SRDIm...ZHXP/WORU=' // RDSDataClient config option
223
- }
321
+ transactionId: 'AQC5SRDIm...ZHXP/WORU=' // RDSDataService config option
322
+ })
224
323
  ```
225
324
 
226
- Sometimes you might want to have _dynamic identifiers_ in your SQL statements. Unfortunately, the `RDSDataClient` doesn't do this, but the **Data API Client** does! We're using the [sqlstring](https://github.com/mysqljs/sqlstring) module under the hood, so as long as [NO_BACKSLASH_ESCAPES](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_no_backslash_escapes) SQL mode is disabled (which is the default state for Aurora Serverless), you're good to go. Use a double colon (`::`) prefix to create _named identifiers_ and you can do cool things like this:
325
+ Sometimes you might want to have _dynamic identifiers_ in your SQL statements. Unfortunately, the native Data API doesn't support this, but the **Data API Client** does! Use a double colon (`::`) prefix to create _named identifiers_ and you can do cool things like this:
227
326
 
228
327
  ```javascript
229
328
  let result = await data.query(`SELECT ::fields FROM ::table WHERE id > :id`, {
@@ -233,17 +332,35 @@ let result = await data.query(`SELECT ::fields FROM ::table WHERE id > :id`, {
233
332
  })
234
333
  ```
235
334
 
236
- Which will produce a query like this:
335
+ Which will produce a query like this for PostgreSQL:
336
+
337
+ ```sql
338
+ SELECT "id", "name", "created" FROM "table_123abc" WHERE id > :id
339
+ ```
340
+
341
+ Or for MySQL:
237
342
 
238
343
  ```sql
239
- SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id LIMIT 10
344
+ SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id
240
345
  ```
241
346
 
242
- You'll notice that we leave the _named parameters_ alone. Anything that Data API and the `RDSDataClient` Class currently handles, we defer to them.
347
+ You'll notice that we leave the _named parameters_ alone. Anything that Data API and the native SDK currently handles, we defer to them.
243
348
 
244
349
  ### Type-Casting
245
350
 
246
- The Aurora Data API can sometimes give you trouble with certain data types, such as uuid, unless you explicitly cast them. While you can certainly do this manually in your SQL string, the Data API Client offers a really easy way to handle this for you.
351
+ The Aurora Data API can sometimes give you trouble with certain data types, such as uuid or jsonb in PostgreSQL, unless you explicitly cast them. While you can certainly do this manually in your SQL string using PostgreSQL's `::` cast syntax, the Data API Client offers an easy way to handle this for you using the parameter `cast` property.
352
+
353
+ **PostgreSQL inline casting** (recommended):
354
+
355
+ ```javascript
356
+ const result = await data.query('INSERT INTO users(id, email, metadata) VALUES(:id, :email, :metadata::jsonb)', {
357
+ id: newUserId, // will be cast as string by default
358
+ email: email,
359
+ metadata: JSON.stringify(userMetadata) // cast to jsonb via ::jsonb in SQL
360
+ })
361
+ ```
362
+
363
+ **Parameter-based casting** (alternative approach):
247
364
 
248
365
  ```javascript
249
366
  const result = await data.query(
@@ -271,9 +388,11 @@ const result = await data.query(
271
388
  )
272
389
  ```
273
390
 
391
+ Both approaches produce the same result, but inline casting is generally cleaner for simple cases.
392
+
274
393
  ### Batch Queries
275
394
 
276
- The `RDSDataClient` Class provides a `batchExecuteStatement` method that allows you to execute a prepared statement multiple times using different parameter sets. This is only allowed for `INSERT`, `UPDATE` and `DELETE` queries, but is much more efficient than issuing multiple `executeStatement` calls. The Data API Client handles the switching for you based on _how_ you send in your parameters.
395
+ The Data API provides a `batchExecuteStatement` method that allows you to execute a prepared statement multiple times using different parameter sets. This is only allowed for `INSERT`, `UPDATE` and `DELETE` queries, but is much more efficient than issuing multiple `executeStatement` calls. The Data API Client handles the switching for you based on _how_ you send in your parameters.
277
396
 
278
397
  To issue a batch query, use the `query()` method (either by passing an object or using the two arity form), and provide multiple parameter sets as nested arrays. For example, if you wanted to update multiple records at once, your query might look like this:
279
398
 
@@ -292,6 +411,16 @@ Whenever a batch query is executed, it returns an `updateResults` field. Other t
292
411
 
293
412
  The Data API returns a `generatedFields` array that contains the value of auto-incrementing primary keys. If this value is returned, the Data API Client will parse this and return it as the `insertId`. This also works for batch queries as well.
294
413
 
414
+ For PostgreSQL, use the `RETURNING` clause to get generated values:
415
+
416
+ ```javascript
417
+ let result = await data.query(`INSERT INTO users (name, email) VALUES (:name, :email) RETURNING id`, {
418
+ name: 'Alice',
419
+ email: 'alice@example.com'
420
+ })
421
+ // result.records[0].id contains the generated ID
422
+ ```
423
+
295
424
  ## Transaction Support
296
425
 
297
426
  Transaction support in the Data API Client has been dramatically simplified. Start a new transaction using the `transaction()` method, and then chain queries using the `query()` method. The `query()` method supports all standard query options. Alternatively, you can specify a function as the only argument in a `query()` method call and return the arguments as an array of values. The function receives two arguments, the result of the last query executed, and an array containing all the previous query results. This is useful if you need values from a previous query as part of your transaction.
@@ -299,20 +428,23 @@ Transaction support in the Data API Client has been dramatically simplified. Sta
299
428
  You can specify an optional `rollback()` method in the chain. This will receive the `error` object and the `transactionStatus` object, allowing you to add additional logging or perform some other action. Call the `commit()` method when you are ready to execute the queries.
300
429
 
301
430
  ```javascript
302
- let results = await mysql.transaction()
431
+ let results = await data
432
+ .transaction()
303
433
  .query('INSERT INTO myTable (name) VALUES(:name)', { name: 'Tiger' })
304
- .query('UPDATE myTable SET age = :age WHERE name = :name' { age: 4, name: 'Tiger' })
305
- .rollback((e,status) => { /* do something with the error */ }) // optional
434
+ .query('UPDATE myTable SET age = :age WHERE name = :name', { age: 4, name: 'Tiger' })
435
+ .rollback((e, status) => {
436
+ /* do something with the error */
437
+ }) // optional
306
438
  .commit() // execute the queries
307
439
  ```
308
440
 
309
441
  With a function to get the `insertId` from the previous query:
310
442
 
311
443
  ```javascript
312
- let results = await mysql
444
+ let results = await data
313
445
  .transaction()
314
- .query('INSERT INTO myTable (name) VALUES(:name)', { name: 'Tiger' })
315
- .query((r) => ['UPDATE myTable SET age = :age WHERE id = :id', { age: 4, id: r.insertId }])
446
+ .query('INSERT INTO myTable (name) VALUES(:name) RETURNING id', { name: 'Tiger' })
447
+ .query((r) => ['UPDATE myTable SET age = :age WHERE id = :id', { age: 4, id: r.records[0].id }])
316
448
  .rollback((e, status) => {
317
449
  /* do something with the error */
318
450
  }) // optional
@@ -325,7 +457,7 @@ By default, the `transaction()` method will use the `resourceArn`, `secretArn` a
325
457
 
326
458
  ### Using native methods directly
327
459
 
328
- The Data API Client exposes simplified versions of the five `RDSDataClient` methods. These are:
460
+ The Data API Client exposes the five RDSDataClient command methods. These are:
329
461
 
330
462
  - `batchExecuteStatement`
331
463
  - `beginTransaction`
@@ -338,87 +470,448 @@ The default configuration information (`resourceArn`, `secretArn`, and `database
338
470
  ```javascript
339
471
  let result = await data.executeStatement({
340
472
  sql: `SELECT * FROM myTable WHERE id = :id`,
341
- parameters: [
342
- { name: 'id', value: { longValue: 1 } }
343
- ],
473
+ parameters: [{ name: 'id', value: { longValue: 1 } }],
344
474
  transactionId: 'AQC5SRDIm...ZHXP/WORU='
345
- )
475
+ })
346
476
  ```
347
477
 
348
- ## Custom SDK Wrapper
478
+ ## Custom AWS SDK Client
349
479
 
350
- `data-api-client` allows you to wrap the instance of `RDSDataClient` to enable additional features like AWS X-Ray. You can specify a function using the optional `wrapper` paramenter. The function will receive one parameter which contains an instantiated `RDSDataClient` instance.
480
+ `data-api-client` allows for introducing a custom RDSDataClient instance as a parameter. This parameter is optional. If not present, `data-api-client` will create a default instance.
351
481
 
352
482
  ```javascript
353
- // Import X-Ray
354
- const AWSXRay = require('aws-xray-sdk');
483
+ import { RDSDataClient } from '@aws-sdk/client-rds-data'
484
+ import dataApiClient from 'data-api-client'
485
+
486
+ // Create a custom client instance
487
+ const rdsClient = new RDSDataClient({
488
+ region: 'us-east-1'
489
+ // other configuration options
490
+ })
355
491
 
356
- // Instantiate data-api-client with the AWSXRay.captureAWSv3Client wrapper
357
- const data = require('data-api-client')({
358
- wrapper: AWSXRay.captureAWSv3Client,
359
- ...
492
+ // Instantiate data-api-client with the custom client
493
+ const data = dataApiClient({
494
+ client: rdsClient,
495
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
496
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name'
360
497
  })
361
498
  ```
362
499
 
363
- ## Data API Limitations / Wonkiness
500
+ Custom client parameter allows you to introduce X-Ray tracing:
364
501
 
365
- The first GA release of the Data API has _a lot_ of promise, unfortunately, there are still quite a few things that make it a bit wonky and may require you to implement some workarounds. I've outlined some of my findings below.
502
+ ```javascript
503
+ import { RDSDataClient } from '@aws-sdk/client-rds-data'
504
+ import { captureAWSv3Client } from 'aws-xray-sdk-core'
505
+ import dataApiClient from 'data-api-client'
366
506
 
367
- ### You can't send in an array of values
507
+ const rdsClient = captureAWSv3Client(new RDSDataClient({ region: 'us-east-1' }))
368
508
 
369
- The GitHub repo for RDSDataClient mentions something about `arrayValues`, but I've been unable to get arrays (including TypedArrays and Buffers) to be used for parameters with `IN` clauses. For example, the following query will **NOT** work:
509
+ const data = dataApiClient({
510
+ client: rdsClient,
511
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
512
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name'
513
+ })
514
+ ```
515
+
516
+ ## mysql2 and pg Compatibility Layers
517
+
518
+ 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.
519
+
520
+ ### mysql2 Compatibility
521
+
522
+ Use the Data API Client as a replacement for `mysql2/promise`:
370
523
 
371
524
  ```javascript
372
- let result = await data.executeStatement({
525
+ import { createMySQLConnection, createMySQLPool } from 'data-api-client/compat/mysql2'
526
+
527
+ // Create a connection
528
+ const connection = createMySQLConnection({
529
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster',
373
530
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
374
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
375
- database: 'myDatabase',
376
- sql: 'SELECT * FROM myTable WHERE id IN (:ids)',
377
- parameters: [
378
- { name: 'id', value: { blobValue: [1,2,3,4,5] } }
379
- ]
380
- ).promise()
381
- ```
531
+ database: 'myDatabase'
532
+ })
382
533
 
383
- I'm using `blobValue` because it's the only generic value field. You could send it in as a string, but then it only uses the first value. Hopefully they will add an `arrayValues` or something similar to support this in the future.
534
+ // Use like mysql2/promise
535
+ const [rows, fields] = await connection.query('SELECT * FROM users WHERE id = ?', [123])
536
+ await connection.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Alice', 'alice@example.com'])
384
537
 
385
- ### ~~Named parameters MUST be sent in order~~
538
+ // Note: connection.end() is optional - it's a no-op for Data API (no connection to close)
386
539
 
387
- ~~Read that again if you need to. So parameters have to be **BOTH** named and _in order_, otherwise the query **may** fail. I stress **may**, because if you send in two fields of compatible type in the wrong order, the query will work, just with your values flipped. 🤦🏻‍♂️ Watch out for this one.~~ 👈This was fixed!
540
+ // Create a pool for connection pooling
541
+ const pool = createMySQLPool({
542
+ resourceArn: 'arn:...',
543
+ secretArn: 'arn:...',
544
+ database: 'myDatabase'
545
+ })
388
546
 
389
- ### You can't parameterize identifiers
547
+ // Get connection from pool
548
+ pool.getConnection((err, connection) => {
549
+ if (err) throw err
550
+ connection.query('SELECT * FROM users', (err, results) => {
551
+ connection.release() // Optional - no-op for Data API
552
+ // Handle results
553
+ })
554
+ })
555
+
556
+ // Or use promises
557
+ const connection = await pool.getConnection()
558
+ const [rows] = await connection.query('SELECT * FROM users')
559
+ connection.release() // Optional - no-op for Data API
560
+ ```
561
+
562
+ ### pg Compatibility
390
563
 
391
- If you want to use dynamic column or field names, there is no way to do it automatically with the Data API. The `mysql` package, for example, lets you use `??` to dynamically insert escaped identifiers. Something like the example below is currently not possible.
564
+ Use the Data API Client as a replacement for `pg` (node-postgres):
392
565
 
393
566
  ```javascript
394
- let result = await data.executeStatement({
567
+ import { createPgClient, createPgPool } from 'data-api-client/compat/pg'
568
+
569
+ // Create a client
570
+ const client = createPgClient({
571
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster',
395
572
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
396
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
397
- database: 'myDatabase',
398
- sql: 'SELECT ::fields FROM myTable WHERE id = :id',
399
- parameters: [
400
- // Note: 'arrayValues' is not a real thing
401
- { name: 'fields', value: { arrayValues: ['id','name','created'] } },
402
- { name: 'id', value: { longValue: 1 } }
403
- ]
404
- ).promise()
573
+ database: 'myDatabase'
574
+ })
575
+
576
+ // Note: client.connect() is optional - it's a no-op for Data API (no connection needed)
577
+ await client.connect() // Optional
578
+
579
+ // Use like pg
580
+ const result = await client.query('SELECT * FROM users WHERE id = $1', [123])
581
+ console.log(result.rows)
582
+
583
+ // With callback style
584
+ client.query('SELECT * FROM users', (err, result) => {
585
+ console.log(result.rows)
586
+ })
587
+
588
+ // Note: client.end() is optional - it's a no-op for Data API (no connection to close)
589
+ await client.end() // Optional
590
+
591
+ // Create a pool
592
+ const pool = createPgPool({
593
+ resourceArn: 'arn:...',
594
+ secretArn: 'arn:...',
595
+ database: 'myDatabase'
596
+ })
597
+
598
+ const result = await pool.query('SELECT * FROM users WHERE id = $1', [123])
405
599
  ```
406
600
 
407
- No worries! The Data API Client gives you the ability to parameterize identifiers and auto escape them. Just use a double colon (`::`) to prefix your named identifiers.
601
+ ### Using with ORMs
602
+
603
+ The compatibility layers work seamlessly with popular ORMs:
604
+
605
+ #### Drizzle ORM
606
+
607
+ **MySQL with Drizzle:**
608
+
609
+ ```typescript
610
+ import { drizzle } from 'drizzle-orm/mysql2'
611
+ import { createMySQLPool } from 'data-api-client/compat/mysql2'
612
+
613
+ const pool = createMySQLPool({
614
+ resourceArn: 'arn:...',
615
+ secretArn: 'arn:...',
616
+ database: 'myDatabase'
617
+ })
618
+
619
+ const db = drizzle(pool as any)
620
+
621
+ // Use Drizzle normally
622
+ const users = await db.select().from(usersTable).where(eq(usersTable.id, 123))
623
+ ```
624
+
625
+ **PostgreSQL with Drizzle:**
626
+
627
+ ```typescript
628
+ import { drizzle } from 'drizzle-orm/node-postgres'
629
+ import { createPgClient } from 'data-api-client/compat/pg'
630
+
631
+ const client = createPgClient({
632
+ resourceArn: 'arn:...',
633
+ secretArn: 'arn:...',
634
+ database: 'myDatabase'
635
+ })
636
+
637
+ // Note: client.connect() is optional (no-op for Data API)
638
+ await client.connect() // Optional - can be omitted
639
+ const db = drizzle(client as any)
640
+
641
+ // Use Drizzle normally
642
+ const users = await db.select().from(usersTable).where(eq(usersTable.id, 123))
643
+ ```
644
+
645
+ #### Kysely Query Builder
646
+
647
+ **MySQL with Kysely:**
648
+
649
+ ```typescript
650
+ import { Kysely, MysqlDialect } from 'kysely'
651
+ import { createMySQLPool } from 'data-api-client/compat/mysql2'
652
+
653
+ const pool = createMySQLPool({
654
+ resourceArn: 'arn:...',
655
+ secretArn: 'arn:...',
656
+ database: 'myDatabase'
657
+ })
658
+
659
+ const db = new Kysely<Database>({
660
+ dialect: new MysqlDialect({ pool: pool as any })
661
+ })
662
+
663
+ // Use Kysely normally
664
+ const users = await db.selectFrom('users').selectAll().where('id', '=', 123).execute()
665
+ ```
666
+
667
+ **PostgreSQL with Kysely:**
668
+
669
+ ```typescript
670
+ import { Kysely, PostgresDialect } from 'kysely'
671
+ import { createPgPool } from 'data-api-client/compat/pg'
672
+
673
+ const pool = createPgPool({
674
+ resourceArn: 'arn:...',
675
+ secretArn: 'arn:...',
676
+ database: 'myDatabase'
677
+ })
678
+
679
+ const db = new Kysely<Database>({
680
+ dialect: new PostgresDialect({ pool: pool as any })
681
+ })
682
+
683
+ // Use Kysely normally
684
+ const users = await db.selectFrom('users').selectAll().where('id', '=', 123).execute()
685
+ ```
686
+
687
+ **Benefits of Compatibility Layers:**
688
+ - **Zero code changes** when migrating from mysql2 or pg
689
+ - **Full ORM support** (Drizzle, Kysely)
690
+ - **Automatic retry logic** for cluster wake-ups
691
+ - **Connection pooling simulation** (getConnection, release)
692
+ - **Both Promise and callback APIs** supported
693
+ - **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
694
+
695
+ ## PostgreSQL Array Support
696
+
697
+ 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.
698
+
699
+ ### Array Results (Automatic Conversion)
700
+
701
+ When you query PostgreSQL arrays, the Data API Client automatically converts them to native JavaScript arrays:
702
+
703
+ ```javascript
704
+ // Query returns PostgreSQL array
705
+ let result = await data.query(`SELECT tags FROM products WHERE id = :id`, { id: 123 })
706
+
707
+ // Automatic conversion to JavaScript array
708
+ // result.records[0].tags = ['new', 'featured', 'sale']
709
+ ```
710
+
711
+ **Supported Array Types:**
712
+
713
+ - Integer arrays: `INT[]`, `SMALLINT[]`, `BIGINT[]`
714
+ - Float arrays: `REAL[]`, `DOUBLE PRECISION[]`, `NUMERIC[]`
715
+ - String arrays: `TEXT[]`, `VARCHAR[]`
716
+ - Boolean arrays: `BOOL[]`
717
+ - Date/Time arrays: `DATE[]`, `TIMESTAMP[]`
718
+ - Other types: `UUID[]`, `JSON[]`, `JSONB[]`
719
+
720
+ ### Array Parameters (Workarounds Required)
721
+
722
+ The RDS Data API does **not support binding array parameters** directly. You'll need to use one of these workarounds:
723
+
724
+ **1. CSV string with `string_to_array()` (for integer arrays):**
725
+
726
+ ```javascript
727
+ await data.query("INSERT INTO products (tags) VALUES (string_to_array(:csv, ',')::int[])", {
728
+ csv: '1,2,3'
729
+ })
730
+ ```
731
+
732
+ **2. PostgreSQL array literal syntax:**
733
+
734
+ ```javascript
735
+ await data.query('INSERT INTO products (tags) VALUES (:literal::text[])', {
736
+ literal: '{"admin","editor","viewer"}'
737
+ })
738
+ ```
739
+
740
+ **3. ARRAY[] constructor with individual parameters:**
741
+
742
+ ```javascript
743
+ await data.query('INSERT INTO products (tags) VALUES (ARRAY[:tag1, :tag2, :tag3])', {
744
+ tag1: 'blue',
745
+ tag2: 'sale',
746
+ tag3: 'featured'
747
+ })
748
+ ```
749
+
750
+ Despite these input limitations, **all array results are automatically converted to native JavaScript arrays**, making it easy to work with PostgreSQL array data in your application.
751
+
752
+ ## PostgreSQL Data Type Support
753
+
754
+ Version 2.0 provides comprehensive support for PostgreSQL data types:
755
+
756
+ ### Numeric Types
757
+
758
+ - `SMALLINT`, `INT`, `BIGINT` - Integer types of various sizes
759
+ - `DECIMAL`, `NUMERIC` - Exact numeric types with precision
760
+ - `REAL`, `DOUBLE PRECISION` - Floating-point types
761
+
762
+ ```javascript
763
+ await data.query('INSERT INTO products (price, quantity) VALUES (:price, :quantity)', {
764
+ price: 19.99,
765
+ quantity: 100
766
+ })
767
+ ```
768
+
769
+ ### String Types
770
+
771
+ - `CHAR`, `VARCHAR`, `TEXT` - Character types
772
+ - Full Unicode support
773
+
774
+ ```javascript
775
+ await data.query('INSERT INTO posts (title, content) VALUES (:title, :content)', {
776
+ title: 'Hello 世界 🌍',
777
+ content: 'A very long text...'
778
+ })
779
+ ```
780
+
781
+ ### Boolean Type
782
+
783
+ ```javascript
784
+ await data.query('INSERT INTO users (active) VALUES (:active)', { active: true })
785
+ ```
786
+
787
+ ### Date and Time Types
788
+
789
+ - `DATE` - Calendar date
790
+ - `TIME`, `TIME WITH TIME ZONE` - Time of day
791
+ - `TIMESTAMP`, `TIMESTAMP WITH TIME ZONE` - Date and time
792
+
793
+ ```javascript
794
+ await data.query('INSERT INTO events (event_date, event_time) VALUES (:date, :time)', {
795
+ date: '2024-12-25',
796
+ time: new Date()
797
+ })
798
+ ```
799
+
800
+ ### Binary Data (BYTEA)
801
+
802
+ ```javascript
803
+ const binaryData = Buffer.from('Binary content', 'utf-8')
804
+ await data.query('INSERT INTO files (content) VALUES (:content)', { content: binaryData })
805
+ ```
806
+
807
+ ### JSON and JSONB
808
+
809
+ ```javascript
810
+ const metadata = { role: 'admin', permissions: ['read', 'write'] }
811
+ await data.query('INSERT INTO users (metadata) VALUES (:metadata::jsonb)', {
812
+ metadata: JSON.stringify(metadata)
813
+ })
814
+
815
+ // Query result
816
+ let result = await data.query('SELECT metadata FROM users WHERE id = :id', { id: 1 })
817
+ const parsed = JSON.parse(result.records[0].metadata)
818
+ ```
819
+
820
+ ### UUID
821
+
822
+ ```javascript
823
+ await data.query('INSERT INTO sessions (session_id) VALUES (:id::uuid)', {
824
+ id: '550e8400-e29b-41d4-a716-446655440000'
825
+ })
826
+
827
+ // Or with explicit cast parameter
828
+ await data.query('INSERT INTO sessions (session_id) VALUES (:id)', [
829
+ { name: 'id', value: '550e8400-e29b-41d4-a716-446655440000', cast: 'uuid' }
830
+ ])
831
+ ```
832
+
833
+ ### Network Types
834
+
835
+ - `INET` - IPv4 or IPv6 host address
836
+ - `CIDR` - IPv4 or IPv6 network
837
+
838
+ ```javascript
839
+ await data.query('INSERT INTO servers (ip_address, network) VALUES (:ip::inet, :net::cidr)', {
840
+ ip: '192.168.1.1',
841
+ net: '10.0.0.0/8'
842
+ })
843
+ ```
844
+
845
+ ### Range Types
846
+
847
+ - `INT4RANGE`, `NUMRANGE` - Numeric ranges
848
+ - `TSTZRANGE` - Timestamp ranges
849
+
850
+ ```javascript
851
+ await data.query('INSERT INTO bookings (date_range) VALUES (:range::INT4RANGE)', {
852
+ range: '[1,10)'
853
+ })
854
+ ```
855
+
856
+ ## TypeScript Support
857
+
858
+ Version 2.0 is written in TypeScript and provides comprehensive type definitions:
859
+
860
+ ```typescript
861
+ import dataApiClient from 'data-api-client'
862
+ import type { DataAPIClientConfig, QueryResult } from 'data-api-client/types'
863
+
864
+ const config: DataAPIClientConfig = {
865
+ secretArn: 'arn:...',
866
+ resourceArn: 'arn:...',
867
+ database: 'mydb',
868
+ engine: 'pg'
869
+ }
870
+
871
+ const client = dataApiClient(config)
872
+
873
+ interface User {
874
+ id: number
875
+ name: string
876
+ email: string
877
+ tags: string[]
878
+ }
879
+
880
+ const result: QueryResult<User> = await client.query<User>('SELECT * FROM users WHERE id = :id', { id: 123 })
881
+ ```
882
+
883
+ ## Data API Limitations / Wonkiness
884
+
885
+ While the Data API is powerful, there are some limitations to be aware of:
886
+
887
+ ### Array Parameters Not Supported
888
+
889
+ The RDS Data API does **not support binding array parameters** directly. Attempts to use `arrayValue` parameters result in `ValidationException: Array parameters are not supported`. See [PostgreSQL Array Support](#postgresql-array-support) for workarounds.
890
+
891
+ ### Array Results ARE Supported
892
+
893
+ Despite parameter limitations, array **results** work great! The Data API Client automatically converts PostgreSQL arrays in query results to native JavaScript arrays.
894
+
895
+ ### Some Advanced Types Have Limitations
896
+
897
+ - **MACADDR**: Not supported by the Data API
898
+ - **Multidimensional Arrays**: Limited support for arrays with more than one dimension
899
+ - **NULL values in arrays**: May not work correctly in all cases
900
+ - **Some Range Types**: INT8RANGE, DATERANGE, TSRANGE have casting issues
408
901
 
409
- ### Batch statements do not give you updated record counts
902
+ ### Batch operations have limited feedback
410
903
 
411
- This one is a bit frustrating. If you execute a standard `executeStatement`, then it will return a `numberOfRecordsUpdated` field for `UPDATE` and `DELETE` queries. This is handy for knowing if your query succeeded. Unfortunately, a `batchExecuteStatement` does not return this field for you.
904
+ Batch operations don't return `numberOfRecordsUpdated` for UPDATE/DELETE statements.
412
905
 
413
906
  ## Enabling Data API
414
907
 
415
- In order to use the Data API, you must enable it on your Aurora Serverless Cluster and create a Secret. You also must grant your execution environment a number of permission as outlined in the following sections.
908
+ In order to use the Data API, you must enable it on your Aurora Serverless Cluster and create a Secret. You also must grant your execution environment a number of permissions as outlined in the following sections.
416
909
 
417
- ### Enable Data API on your Aurora Serverless Cluster
910
+ ### Enable Data API on your Aurora Cluster
418
911
 
419
912
  ![Enable Data API in Network & Security settings of your cluster](https://user-images.githubusercontent.com/2053544/58768968-79ee4300-8570-11e9-9266-1433182e0db2.png)
420
913
 
421
- You need to modify your Aurora Serverless cluster by clicking ACTIONS and then Modify Cluster”. Just check the Data API box in the _Network & Security_ section and youre good to go. Remember that your Aurora Serverless cluster still runs in a VPC, even though you don’t need to run your Lambdas in a VPC to access it via the Data API.
914
+ You need to modify your Aurora cluster by clicking "ACTIONS" and then "Modify Cluster". Check the Data API box in the _Network & Security_ section and you're good to go. This works for Aurora Serverless v1, Aurora Serverless v2, and Aurora provisioned clusters.
422
915
 
423
916
  ### Set up a secret in the Secrets Manager
424
917
 
@@ -426,11 +919,11 @@ Next you need to set up a secret in the Secrets Manager. This is actually quite
426
919
 
427
920
  ![Enter database credentials and select database to access](https://user-images.githubusercontent.com/2053544/58768974-912d3080-8570-11e9-8878-636dfb742b00.png)
428
921
 
429
- Next we give it a name, this is important, because this will be part of the arn when we set up permissions later. You can give it a description as well so you dont forget what this secret is about when you look at it in a few weeks.
922
+ Next we give it a name, this is important, because this will be part of the arn when we set up permissions later. You can give it a description as well so you don't forget what this secret is about when you look at it in a few weeks.
430
923
 
431
924
  ![Give your secret a name and add a description](https://user-images.githubusercontent.com/2053544/58768984-a7d38780-8570-11e9-8b21-199db5548c73.png)
432
925
 
433
- You can then configure your rotation settings, if you want, and then you review and create your secret. Then you can click on your newly created secret and grab the arn, were gonna need that next.
926
+ You can then configure your rotation settings, if you want, and then you review and create your secret. Then you can click on your newly created secret and grab the arn, we're gonna need that next.
434
927
 
435
928
  ![Click on your secret to get the arn.](https://user-images.githubusercontent.com/2053544/58768989-bae65780-8570-11e9-94fb-51f6fa7d34bf.png)
436
929
 
@@ -483,4 +976,4 @@ Statement:
483
976
 
484
977
  ## Contributions
485
978
 
486
- Contributions, ideas and bug reports are welcome and greatly appreciated. Please add [issues](https://github.com/jeremydaly/data-api-client/issues) for suggestions and bug reports or create a pull request. You can also contact me on Twitter: [@jeremy_daly](https://twitter.com/jeremy_daly).
979
+ Contributions, ideas and bug reports are welcome and greatly appreciated. Please add [issues](https://github.com/jeremydaly/data-api-client/issues) for suggestions and bug reports or create a pull request. You can also contact me on X: [@jeremy_daly](https://x.com/jeremy_daly) or LinkedIn: [https://www.linkedin.com/in/jeremydaly/](https://www.linkedin.com/in/jeremydaly/).