data-api-client 1.3.1 → 2.0.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
@@ -3,26 +3,48 @@
3
3
  [![npm](https://img.shields.io/npm/v/data-api-client.svg)](https://www.npmjs.com/package/data-api-client)
4
4
  [![npm](https://img.shields.io/npm/l/data-api-client.svg)](https://www.npmjs.com/package/data-api-client)
5
5
 
6
- > #### Project Update: October 7, 2024
6
+ > **Note:** Version 2.0.0 is currently in active development. 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
- > With the recent announcement that Amazon Aurora MySQL-Compatible Edition now supports a redesigned [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/), there have been several requests to add support to this project. The new RDS Data API also supports [Amazon Aurora PostgreSQL-Compatible Edition](https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-aurora-postgresql-rds-data-api/) (more detail [here](https://aws.amazon.com/blogs/database/introducing-the-data-api-for-amazon-aurora-serverless-v2-and-amazon-aurora-provisioned-clusters/)).
9
- >
10
- > Star and watch the project for the 2.0 branch updates.
8
+ > **Using v1.x?** See [README_v1.md](README_v1.md) for v1.x documentation.
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 uses modern async/await patterns with AWS SDK v3.
11
11
 
12
- 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 promisifies the `AWS.RDSDataService` client to make working with `async/await` or Promise chains easier AND dramatically simplifies **transactions**.
12
+ **Version 2.0** introduces 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
13
 
14
14
  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
15
 
16
+ ## What's New in v2.0
17
+
18
+ - **AWS SDK v3**: Migrated from AWS SDK v2 to v3 for smaller bundle sizes and better tree-shaking
19
+ - **TypeScript**: Full TypeScript implementation with comprehensive type definitions
20
+ - **PostgreSQL Array Support**: Automatic conversion of PostgreSQL arrays to native JavaScript arrays in query results
21
+ - **Comprehensive Data Type Coverage**: Extensive support for PostgreSQL data types including:
22
+ - All numeric types (SMALLINT, INT, BIGINT, DECIMAL, NUMERIC, REAL, DOUBLE PRECISION)
23
+ - String types (CHAR, VARCHAR, TEXT)
24
+ - Boolean, Date/Time types (DATE, TIME, TIMESTAMP, TIMESTAMPTZ)
25
+ - Binary data (BYTEA)
26
+ - JSON and JSONB with nested structures
27
+ - UUID with type casting support
28
+ - Network types (INET, CIDR)
29
+ - Range types (INT4RANGE, NUMRANGE, TSTZRANGE)
30
+ - Arrays of all supported types
31
+ - **Modern Build System**: TypeScript compilation with ES6+ output
32
+ - **Enhanced Type Casting**: Improved support for PostgreSQL type casting with inline (`::type`) and parameter-based casting
33
+ - **Better Error Handling**: More informative error messages and validation
34
+
16
35
  ## Simple Examples
17
36
 
18
- 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.
37
+ 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.
19
38
 
20
39
  ```javascript
21
- // Require and instantiate data-api-client with secret and cluster
22
- const data = require('data-api-client')({
40
+ // Import and instantiate data-api-client with secret and cluster
41
+ import dataApiClient from 'data-api-client'
42
+
43
+ const data = dataApiClient({
23
44
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
24
45
  resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
25
- database: 'myDatabase' // default database
46
+ database: 'myDatabase', // default database
47
+ engine: 'pg' // or 'mysql'
26
48
  })
27
49
 
28
50
  /*** Assuming we're in an async function ***/
@@ -41,21 +63,22 @@ let result = await data.query(`SELECT * FROM myTable`)
41
63
  let resultParams = await data.query(`SELECT * FROM myTable WHERE id = :id`, { id: 2 })
42
64
  // { records: [ { id: 2, name: 'Mike', age: 52 } ] }
43
65
 
44
- // INSERT with named parameters
45
- let insert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, {
66
+ // INSERT with named parameters (PostgreSQL with RETURNING)
67
+ let insert = await data.query(`INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls) RETURNING id`, {
46
68
  name: 'Greg',
47
69
  age: 18,
48
70
  curls: false
49
71
  })
50
72
 
51
73
  // BATCH INSERT with named parameters
52
- let batchInsert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, [
74
+ let batchInsert = await data.query(`INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls)`, [
53
75
  [{ name: 'Marcia', age: 17, curls: false }],
54
76
  [{ name: 'Peter', age: 15, curls: false }],
55
77
  [{ name: 'Jan', age: 15, curls: false }],
56
78
  [{ name: 'Cindy', age: 12, curls: true }],
57
79
  [{ name: 'Bobby', age: 12, curls: false }]
58
80
  ])
81
+
59
82
  // Update with named parameters
60
83
  let update = await data.query(`UPDATE myTable SET age = :age WHERE id = :id`, { age: 13, id: 5 })
61
84
 
@@ -65,57 +88,77 @@ let remove = await data.query(
65
88
  { name: 'Jan' } // Sorry Jan :(
66
89
  )
67
90
 
68
- // A slightly more advanced example
69
- let custom = data.query({
70
- sql: `SELECT * FROM myOtherTable WHERE id = :id AND active = :isActive`,
71
- continueAfterTimeout: true,
72
- database: 'myOtherDatabase',
73
- parameters: [{ id: 123 }, { name: 'isActive', value: { booleanValue: true } }]
74
- })
91
+ // PostgreSQL with type casting and JSONB
92
+ let pgExample = await data.query(`INSERT INTO users (id, email, metadata) VALUES(:id, :email, :metadata)`, [
93
+ { name: 'id', value: '550e8400-e29b-41d4-a716-446655440000', cast: 'uuid' },
94
+ { name: 'email', value: 'user@example.com' },
95
+ { name: 'metadata', value: JSON.stringify({ role: 'admin' }), cast: 'jsonb' }
96
+ ])
97
+
98
+ // PostgreSQL array result (automatically converted to native JavaScript array)
99
+ let arrayResult = await data.query(`SELECT tags FROM products WHERE id = :id`, { id: 123 })
100
+ // { records: [ { tags: ['new', 'featured', 'sale'] } ] }
75
101
  ```
76
102
 
77
103
  ## Why do I need this?
78
104
 
79
- 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.RDSDataService` class:
105
+ 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:
80
106
 
81
107
  ```javascript
82
- const AWS = require('aws-sdk')
83
- const data = new AWS.RDSDataService()
108
+ import { RDSDataClient, ExecuteStatementCommand } from '@aws-sdk/client-rds-data'
109
+ const client = new RDSDataClient()
84
110
 
85
111
  /*** Assuming we're in an async function ***/
86
112
 
87
113
  // INSERT with named parameters
88
- let insert = await data.executeStatement({
89
- secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
90
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
91
- database: 'myDatabase',
92
- sql: 'INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)',
93
- parameters: [
94
- { name: 'name', value: { stringValue: 'Cousin Oliver' } },
95
- { name: 'age', value: { longValue: 10 } },
96
- { name: 'curls', value: { booleanValue: false } }
97
- ]
98
- ).promise()
114
+ let insert = await client.send(
115
+ new ExecuteStatementCommand({
116
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
117
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
118
+ database: 'myDatabase',
119
+ sql: 'INSERT INTO myTable (name, age, has_curls) VALUES(:name, :age, :curls)',
120
+ parameters: [
121
+ { name: 'name', value: { stringValue: 'Cousin Oliver' } },
122
+ { name: 'age', value: { longValue: 10 } },
123
+ { name: 'curls', value: { booleanValue: false } }
124
+ ]
125
+ })
126
+ )
99
127
  ```
100
128
 
101
129
  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:
102
130
 
103
131
  ```javascript
104
- { // id field
105
- "longValue": 9
132
+ {
133
+ // id field
134
+ longValue: 9
106
135
  },
107
- { // name field
108
- "stringValue": "Cousin Oliver"
136
+ {
137
+ // name field
138
+ stringValue: 'Cousin Oliver'
109
139
  },
110
- { // age field
111
- "longValue": 10
140
+ {
141
+ // age field
142
+ longValue: 10
112
143
  },
113
- { // has_curls field
114
- "booleanValue": false
144
+ {
145
+ // has_curls field
146
+ booleanValue: false
147
+ }
148
+ ```
149
+
150
+ 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:
151
+
152
+ ```javascript
153
+ {
154
+ // tags field (PostgreSQL array)
155
+ arrayValue: {
156
+ stringValues: ['admin', 'editor', 'viewer']
157
+ }
115
158
  }
116
159
  ```
117
160
 
118
- 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. 😀
161
+ Lots of extra work that the **Data API Client** handles automatically for you, converting arrays to native JavaScript arrays and providing clean, usable data. 😀
119
162
 
120
163
  ## Installation and Setup
121
164
 
@@ -123,43 +166,48 @@ Not only are there no column names, but you have to pull the value from the data
123
166
  npm i data-api-client
124
167
  ```
125
168
 
169
+ 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:
170
+
171
+ ```
172
+ npm i @aws-sdk/client-rds-data
173
+ ```
174
+
126
175
  For more information on enabling Data API, see [Enabling Data API](#enabling-data-api).
127
176
 
128
177
  ## Configuration Options
129
178
 
130
179
  Below is a table containing all of the possible configuration options for the `data-api-client`. Additional details are provided throughout the documentation.
131
180
 
132
- | Property | Type | Description | Default |
133
- | --------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
134
- | AWS | `AWS` | A custom `aws-sdk` instance | |
135
- | resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is _required_, but can be overridden when querying. | |
136
- | secretArn | `string` | The ARN of the secret associated with your database credentials. This is _required_, but can be overridden when querying. | |
137
- | database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
138
- | engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `mysql` |
139
- | 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` |
140
- | ~~keepAlive~~ (deprecated) | `boolean` | See [Connection Reuse](#connection-reuse) below. | |
141
- | ~~sslEnabled~~ (deprecated) | `boolean` | Set this in the `options` | `true` |
142
- | options | `object` | An _optional_ configuration object that is passed directly into the RDSDataService constructor. See [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/RDSDataService.html#constructor-property) for available options. | `{}` |
143
- | ~~region~~ (deprecated) | `string` | Set this in the `options` | |
144
- | 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` |
181
+ | Property | Type | Description | Default |
182
+ | ------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
183
+ | client | `RDSDataClient` | A custom `@aws-sdk/client-rds-data` instance (for X-Ray tracing, custom config, etc.) | |
184
+ | resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is _required_, but can be overridden when querying. | |
185
+ | secretArn | `string` | The ARN of the secret associated with your database credentials. This is _required_, but can be overridden when querying. | |
186
+ | database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
187
+ | engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `pg` |
188
+ | 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` |
189
+ | 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
+ | 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` |
145
191
 
146
192
  ### Connection Reuse
147
193
 
148
- 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
149
- `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).
194
+ 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).
150
195
 
151
196
  ## How to use this module
152
197
 
153
- The **Data API Client** wraps the [RDSDataService Class](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/RDSDataService.html), providing you with a number of convenience features to make your workflow easier. The module also exposes **promisified** versions of all the standard `RDSDataService` methods, with your default configuration information already merged in. 😉
198
+ 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. 😉
154
199
 
155
- 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.
200
+ 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.
156
201
 
157
202
  ```javascript
158
- // Require and instantiate data-api-client with secret and cluster arns
159
- const data = require('data-api-client')({
203
+ // Import and instantiate data-api-client with secret and cluster arns
204
+ import dataApiClient from 'data-api-client'
205
+
206
+ const data = dataApiClient({
160
207
  secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
161
208
  resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
162
- database: 'myDatabase' // set a default database
209
+ database: 'myDatabase', // set a default database
210
+ engine: 'pg' // specify 'pg' for PostgreSQL or 'mysql' for MySQL
163
211
  })
164
212
  ```
165
213
 
@@ -181,7 +229,7 @@ By default, this will return your rows as an array of objects with column names
181
229
  ]
182
230
  ```
183
231
 
184
- 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:
232
+ 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:
185
233
 
186
234
  ```javascript
187
235
  let result = await data.query(
@@ -191,14 +239,14 @@ let result = await data.query(
191
239
  )
192
240
  ```
193
241
 
194
- 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 `RDSDataService` format:
242
+ 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:
195
243
 
196
244
  ```javascript
197
245
  let result = await data.query(`SELECT * FROM myTable WHERE id = :id AND created > :createDate`, [
198
246
  // An array of objects is totally cool, too. We'll merge them for you.
199
247
  { id: 2 },
200
- // Data API Client just passes this straight on through
201
- { name: 'createDate', value: { blobValue: new Buffer('2019-06-01') } }
248
+ // Extended format for more control
249
+ { name: 'createDate', value: '2019-06-01' }
202
250
  ])
203
251
  ```
204
252
 
@@ -207,17 +255,16 @@ If you want even more control, you can pass in an `object` as the first paramete
207
255
  ```javascript
208
256
  let result = await data.query({
209
257
  sql: `SELECT * FROM myTable WHERE id = :id`,
210
- parameters: [ { id: 2 } ], // or just { id: 2 }
258
+ parameters: [{ id: 2 }], // or just { id: 2 }
211
259
  database: 'someOtherDatabase', // override default database
212
- schema: 'mySchema', // RDSDataService config option
213
260
  continueAfterTimeout: true, // RDSDataService config option (non-batch only)
214
261
  includeResultMetadata: true, // RDSDataService config option (non-batch only)
215
262
  hydrateColumnNames: false, // Returns each record as an arrays of values
216
263
  transactionId: 'AQC5SRDIm...ZHXP/WORU=' // RDSDataService config option
217
- }
264
+ })
218
265
  ```
219
266
 
220
- Sometimes you might want to have _dynamic identifiers_ in your SQL statements. Unfortunately, the `RDSDataService` 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:
267
+ 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:
221
268
 
222
269
  ```javascript
223
270
  let result = await data.query(`SELECT ::fields FROM ::table WHERE id > :id`, {
@@ -227,17 +274,35 @@ let result = await data.query(`SELECT ::fields FROM ::table WHERE id > :id`, {
227
274
  })
228
275
  ```
229
276
 
230
- Which will produce a query like this:
277
+ Which will produce a query like this for PostgreSQL:
278
+
279
+ ```sql
280
+ SELECT "id", "name", "created" FROM "table_123abc" WHERE id > :id
281
+ ```
282
+
283
+ Or for MySQL:
231
284
 
232
285
  ```sql
233
- SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id LIMIT 10
286
+ SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id
234
287
  ```
235
288
 
236
- You'll notice that we leave the _named parameters_ alone. Anything that Data API and the `RDSDataService` Class currently handles, we defer to them.
289
+ You'll notice that we leave the _named parameters_ alone. Anything that Data API and the native SDK currently handles, we defer to them.
237
290
 
238
291
  ### Type-Casting
239
292
 
240
- 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.
293
+ 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.
294
+
295
+ **PostgreSQL inline casting** (recommended):
296
+
297
+ ```javascript
298
+ const result = await data.query('INSERT INTO users(id, email, metadata) VALUES(:id, :email, :metadata::jsonb)', {
299
+ id: newUserId, // will be cast as string by default
300
+ email: email,
301
+ metadata: JSON.stringify(userMetadata) // cast to jsonb via ::jsonb in SQL
302
+ })
303
+ ```
304
+
305
+ **Parameter-based casting** (alternative approach):
241
306
 
242
307
  ```javascript
243
308
  const result = await data.query(
@@ -265,9 +330,11 @@ const result = await data.query(
265
330
  )
266
331
  ```
267
332
 
333
+ Both approaches produce the same result, but inline casting is generally cleaner for simple cases.
334
+
268
335
  ### Batch Queries
269
336
 
270
- The `RDSDataService` 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.
337
+ 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.
271
338
 
272
339
  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:
273
340
 
@@ -286,6 +353,16 @@ Whenever a batch query is executed, it returns an `updateResults` field. Other t
286
353
 
287
354
  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.
288
355
 
356
+ For PostgreSQL, use the `RETURNING` clause to get generated values:
357
+
358
+ ```javascript
359
+ let result = await data.query(`INSERT INTO users (name, email) VALUES (:name, :email) RETURNING id`, {
360
+ name: 'Alice',
361
+ email: 'alice@example.com'
362
+ })
363
+ // result.records[0].id contains the generated ID
364
+ ```
365
+
289
366
  ## Transaction Support
290
367
 
291
368
  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.
@@ -293,20 +370,23 @@ Transaction support in the Data API Client has been dramatically simplified. Sta
293
370
  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.
294
371
 
295
372
  ```javascript
296
- let results = await mysql.transaction()
373
+ let results = await data
374
+ .transaction()
297
375
  .query('INSERT INTO myTable (name) VALUES(:name)', { name: 'Tiger' })
298
- .query('UPDATE myTable SET age = :age WHERE name = :name' { age: 4, name: 'Tiger' })
299
- .rollback((e,status) => { /* do something with the error */ }) // optional
376
+ .query('UPDATE myTable SET age = :age WHERE name = :name', { age: 4, name: 'Tiger' })
377
+ .rollback((e, status) => {
378
+ /* do something with the error */
379
+ }) // optional
300
380
  .commit() // execute the queries
301
381
  ```
302
382
 
303
383
  With a function to get the `insertId` from the previous query:
304
384
 
305
385
  ```javascript
306
- let results = await mysql
386
+ let results = await data
307
387
  .transaction()
308
- .query('INSERT INTO myTable (name) VALUES(:name)', { name: 'Tiger' })
309
- .query((r) => ['UPDATE myTable SET age = :age WHERE id = :id', { age: 4, id: r.insertId }])
388
+ .query('INSERT INTO myTable (name) VALUES(:name) RETURNING id', { name: 'Tiger' })
389
+ .query((r) => ['UPDATE myTable SET age = :age WHERE id = :id', { age: 4, id: r.records[0].id }])
310
390
  .rollback((e, status) => {
311
391
  /* do something with the error */
312
392
  }) // optional
@@ -319,7 +399,7 @@ By default, the `transaction()` method will use the `resourceArn`, `secretArn` a
319
399
 
320
400
  ### Using native methods directly
321
401
 
322
- The Data API Client exposes _promisified_ versions of the five RDSDataService methods. These are:
402
+ The Data API Client exposes the five RDSDataClient command methods. These are:
323
403
 
324
404
  - `batchExecuteStatement`
325
405
  - `beginTransaction`
@@ -332,96 +412,269 @@ The default configuration information (`resourceArn`, `secretArn`, and `database
332
412
  ```javascript
333
413
  let result = await data.executeStatement({
334
414
  sql: `SELECT * FROM myTable WHERE id = :id`,
335
- parameters: [
336
- { name: 'id', value: { longValue: 1 } }
337
- ],
415
+ parameters: [{ name: 'id', value: { longValue: 1 } }],
338
416
  transactionId: 'AQC5SRDIm...ZHXP/WORU='
339
- )
417
+ })
340
418
  ```
341
419
 
342
- ## Custom AWS instance
420
+ ## Custom AWS SDK Client
343
421
 
344
- `data-api-client` allows for introducing a custom `AWS` as a parameter. This parameter is optional. If not present - `data-api-client` will fall back to the default `AWS` instance that comes with the library.
422
+ `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.
345
423
 
346
424
  ```javascript
347
- // Instantiate data-api-client with a custom AWS instance
348
- const data = require('data-api-client')({
349
- AWS: customAWS,
350
- ...
425
+ import { RDSDataClient } from '@aws-sdk/client-rds-data'
426
+ import dataApiClient from 'data-api-client'
427
+
428
+ // Create a custom client instance
429
+ const rdsClient = new RDSDataClient({
430
+ region: 'us-east-1'
431
+ // other configuration options
432
+ })
433
+
434
+ // Instantiate data-api-client with the custom client
435
+ const data = dataApiClient({
436
+ client: rdsClient,
437
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
438
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name'
351
439
  })
352
440
  ```
353
441
 
354
- Custom AWS parameter allows to introduce, e.g. tracing Data API calls through X-Ray with:
442
+ Custom client parameter allows you to introduce X-Ray tracing:
355
443
 
356
444
  ```javascript
357
- const AWSXRay = require('aws-xray-sdk')
358
- const AWS = AWSXRay.captureAWS(require('aws-sdk'))
445
+ import { RDSDataClient } from '@aws-sdk/client-rds-data'
446
+ import { captureAWSv3Client } from 'aws-xray-sdk-core'
447
+ import dataApiClient from 'data-api-client'
448
+
449
+ const rdsClient = captureAWSv3Client(new RDSDataClient({ region: 'us-east-1' }))
359
450
 
360
- const data = require('data-api-client')({
361
- AWS: AWS,
362
- ...
451
+ const data = dataApiClient({
452
+ client: rdsClient,
453
+ secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
454
+ resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name'
363
455
  })
364
456
  ```
365
457
 
366
- ## Data API Limitations / Wonkiness
458
+ ## PostgreSQL Array Support
367
459
 
368
- 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.
460
+ 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.
369
461
 
370
- ### You can't send in an array of values
462
+ ### Array Results (Automatic Conversion)
371
463
 
372
- The GitHub repo for RDSDataService 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:
464
+ When you query PostgreSQL arrays, the Data API Client automatically converts them to native JavaScript arrays:
373
465
 
374
466
  ```javascript
375
- let result = await data.executeStatement({
376
- secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
377
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
378
- database: 'myDatabase',
379
- sql: 'SELECT * FROM myTable WHERE id IN (:ids)',
380
- parameters: [
381
- { name: 'id', value: { blobValue: [1,2,3,4,5] } }
382
- ]
383
- ).promise()
467
+ // Query returns PostgreSQL array
468
+ let result = await data.query(`SELECT tags FROM products WHERE id = :id`, { id: 123 })
469
+
470
+ // Automatic conversion to JavaScript array
471
+ // result.records[0].tags = ['new', 'featured', 'sale']
384
472
  ```
385
473
 
386
- 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.
474
+ **Supported Array Types:**
387
475
 
388
- ### ~~Named parameters MUST be sent in order~~
476
+ - Integer arrays: `INT[]`, `SMALLINT[]`, `BIGINT[]`
477
+ - Float arrays: `REAL[]`, `DOUBLE PRECISION[]`, `NUMERIC[]`
478
+ - String arrays: `TEXT[]`, `VARCHAR[]`
479
+ - Boolean arrays: `BOOL[]`
480
+ - Date/Time arrays: `DATE[]`, `TIMESTAMP[]`
481
+ - Other types: `UUID[]`, `JSON[]`, `JSONB[]`
389
482
 
390
- ~~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!
483
+ ### Array Parameters (Workarounds Required)
391
484
 
392
- ### You can't parameterize identifiers
485
+ The RDS Data API does **not support binding array parameters** directly. You'll need to use one of these workarounds:
393
486
 
394
- 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.
487
+ **1. CSV string with `string_to_array()` (for integer arrays):**
395
488
 
396
489
  ```javascript
397
- let result = await data.executeStatement({
398
- secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
399
- resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
400
- database: 'myDatabase',
401
- sql: 'SELECT ::fields FROM myTable WHERE id = :id',
402
- parameters: [
403
- // Note: 'arrayValues' is not a real thing
404
- { name: 'fields', value: { arrayValues: ['id','name','created'] } },
405
- { name: 'id', value: { longValue: 1 } }
406
- ]
407
- ).promise()
490
+ await data.query("INSERT INTO products (tags) VALUES (string_to_array(:csv, ',')::int[])", {
491
+ csv: '1,2,3'
492
+ })
493
+ ```
494
+
495
+ **2. PostgreSQL array literal syntax:**
496
+
497
+ ```javascript
498
+ await data.query('INSERT INTO products (tags) VALUES (:literal::text[])', {
499
+ literal: '{"admin","editor","viewer"}'
500
+ })
501
+ ```
502
+
503
+ **3. ARRAY[] constructor with individual parameters:**
504
+
505
+ ```javascript
506
+ await data.query('INSERT INTO products (tags) VALUES (ARRAY[:tag1, :tag2, :tag3])', {
507
+ tag1: 'blue',
508
+ tag2: 'sale',
509
+ tag3: 'featured'
510
+ })
511
+ ```
512
+
513
+ 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.
514
+
515
+ ## PostgreSQL Data Type Support
516
+
517
+ Version 2.0 provides comprehensive support for PostgreSQL data types:
518
+
519
+ ### Numeric Types
520
+
521
+ - `SMALLINT`, `INT`, `BIGINT` - Integer types of various sizes
522
+ - `DECIMAL`, `NUMERIC` - Exact numeric types with precision
523
+ - `REAL`, `DOUBLE PRECISION` - Floating-point types
524
+
525
+ ```javascript
526
+ await data.query('INSERT INTO products (price, quantity) VALUES (:price, :quantity)', {
527
+ price: 19.99,
528
+ quantity: 100
529
+ })
530
+ ```
531
+
532
+ ### String Types
533
+
534
+ - `CHAR`, `VARCHAR`, `TEXT` - Character types
535
+ - Full Unicode support
536
+
537
+ ```javascript
538
+ await data.query('INSERT INTO posts (title, content) VALUES (:title, :content)', {
539
+ title: 'Hello 世界 🌍',
540
+ content: 'A very long text...'
541
+ })
542
+ ```
543
+
544
+ ### Boolean Type
545
+
546
+ ```javascript
547
+ await data.query('INSERT INTO users (active) VALUES (:active)', { active: true })
548
+ ```
549
+
550
+ ### Date and Time Types
551
+
552
+ - `DATE` - Calendar date
553
+ - `TIME`, `TIME WITH TIME ZONE` - Time of day
554
+ - `TIMESTAMP`, `TIMESTAMP WITH TIME ZONE` - Date and time
555
+
556
+ ```javascript
557
+ await data.query('INSERT INTO events (event_date, event_time) VALUES (:date, :time)', {
558
+ date: '2024-12-25',
559
+ time: new Date()
560
+ })
561
+ ```
562
+
563
+ ### Binary Data (BYTEA)
564
+
565
+ ```javascript
566
+ const binaryData = Buffer.from('Binary content', 'utf-8')
567
+ await data.query('INSERT INTO files (content) VALUES (:content)', { content: binaryData })
568
+ ```
569
+
570
+ ### JSON and JSONB
571
+
572
+ ```javascript
573
+ const metadata = { role: 'admin', permissions: ['read', 'write'] }
574
+ await data.query('INSERT INTO users (metadata) VALUES (:metadata::jsonb)', {
575
+ metadata: JSON.stringify(metadata)
576
+ })
577
+
578
+ // Query result
579
+ let result = await data.query('SELECT metadata FROM users WHERE id = :id', { id: 1 })
580
+ const parsed = JSON.parse(result.records[0].metadata)
408
581
  ```
409
582
 
410
- 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.
583
+ ### UUID
584
+
585
+ ```javascript
586
+ await data.query('INSERT INTO sessions (session_id) VALUES (:id::uuid)', {
587
+ id: '550e8400-e29b-41d4-a716-446655440000'
588
+ })
589
+
590
+ // Or with explicit cast parameter
591
+ await data.query('INSERT INTO sessions (session_id) VALUES (:id)', [
592
+ { name: 'id', value: '550e8400-e29b-41d4-a716-446655440000', cast: 'uuid' }
593
+ ])
594
+ ```
595
+
596
+ ### Network Types
597
+
598
+ - `INET` - IPv4 or IPv6 host address
599
+ - `CIDR` - IPv4 or IPv6 network
600
+
601
+ ```javascript
602
+ await data.query('INSERT INTO servers (ip_address, network) VALUES (:ip::inet, :net::cidr)', {
603
+ ip: '192.168.1.1',
604
+ net: '10.0.0.0/8'
605
+ })
606
+ ```
607
+
608
+ ### Range Types
609
+
610
+ - `INT4RANGE`, `NUMRANGE` - Numeric ranges
611
+ - `TSTZRANGE` - Timestamp ranges
612
+
613
+ ```javascript
614
+ await data.query('INSERT INTO bookings (date_range) VALUES (:range::INT4RANGE)', {
615
+ range: '[1,10)'
616
+ })
617
+ ```
618
+
619
+ ## TypeScript Support
620
+
621
+ Version 2.0 is written in TypeScript and provides comprehensive type definitions:
622
+
623
+ ```typescript
624
+ import dataApiClient from 'data-api-client'
625
+ import type { DataAPIClientConfig, QueryResult } from 'data-api-client/types'
626
+
627
+ const config: DataAPIClientConfig = {
628
+ secretArn: 'arn:...',
629
+ resourceArn: 'arn:...',
630
+ database: 'mydb',
631
+ engine: 'pg'
632
+ }
633
+
634
+ const client = dataApiClient(config)
635
+
636
+ interface User {
637
+ id: number
638
+ name: string
639
+ email: string
640
+ tags: string[]
641
+ }
642
+
643
+ const result: QueryResult<User> = await client.query<User>('SELECT * FROM users WHERE id = :id', { id: 123 })
644
+ ```
645
+
646
+ ## Data API Limitations / Wonkiness
647
+
648
+ While the Data API is powerful, there are some limitations to be aware of:
649
+
650
+ ### Array Parameters Not Supported
651
+
652
+ 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.
653
+
654
+ ### Array Results ARE Supported
655
+
656
+ Despite parameter limitations, array **results** work great! The Data API Client automatically converts PostgreSQL arrays in query results to native JavaScript arrays.
657
+
658
+ ### Some Advanced Types Have Limitations
659
+
660
+ - **MACADDR**: Not supported by the Data API
661
+ - **Multidimensional Arrays**: Limited support for arrays with more than one dimension
662
+ - **NULL values in arrays**: May not work correctly in all cases
663
+ - **Some Range Types**: INT8RANGE, DATERANGE, TSRANGE have casting issues
411
664
 
412
- ### Batch statements do not give you updated record counts
665
+ ### Batch operations have limited feedback
413
666
 
414
- 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.
667
+ Batch operations don't return `numberOfRecordsUpdated` for UPDATE/DELETE statements.
415
668
 
416
669
  ## Enabling Data API
417
670
 
418
- 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.
671
+ 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.
419
672
 
420
- ### Enable Data API on your Aurora Serverless Cluster
673
+ ### Enable Data API on your Aurora Cluster
421
674
 
422
675
  ![Enable Data API in Network & Security settings of your cluster](https://user-images.githubusercontent.com/2053544/58768968-79ee4300-8570-11e9-9266-1433182e0db2.png)
423
676
 
424
- 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.
677
+ 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.
425
678
 
426
679
  ### Set up a secret in the Secrets Manager
427
680
 
@@ -429,11 +682,11 @@ Next you need to set up a secret in the Secrets Manager. This is actually quite
429
682
 
430
683
  ![Enter database credentials and select database to access](https://user-images.githubusercontent.com/2053544/58768974-912d3080-8570-11e9-8878-636dfb742b00.png)
431
684
 
432
- 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.
685
+ 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.
433
686
 
434
687
  ![Give your secret a name and add a description](https://user-images.githubusercontent.com/2053544/58768984-a7d38780-8570-11e9-8b21-199db5548c73.png)
435
688
 
436
- 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.
689
+ 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.
437
690
 
438
691
  ![Click on your secret to get the arn.](https://user-images.githubusercontent.com/2053544/58768989-bae65780-8570-11e9-94fb-51f6fa7d34bf.png)
439
692
 
@@ -486,4 +739,4 @@ Statement:
486
739
 
487
740
  ## Contributions
488
741
 
489
- 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).
742
+ 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/).