@aws-blocks/bb-distributed-data 0.1.8 → 0.2.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/DESIGN.md +2 -2
- package/README.md +5 -3
- package/dist/engines/dsql-mock-engine.d.ts.map +1 -1
- package/dist/engines/dsql-mock-engine.js +4 -1
- package/dist/engines/dsql-mock-engine.test.js +19 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +52 -2
- package/dist/errors.test.js +22 -3
- package/dist/index.aws.d.ts +3 -0
- package/dist/index.aws.d.ts.map +1 -1
- package/dist/index.aws.js +3 -0
- package/dist/index.cdk.d.ts +2 -2
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +10 -4
- package/dist/index.cdk.test.js +26 -0
- package/dist/index.mock.d.ts +3 -0
- package/dist/index.mock.d.ts.map +1 -1
- package/dist/index.mock.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +12 -4
package/DESIGN.md
CHANGED
|
@@ -47,7 +47,7 @@ bb-distributed-data (this package)
|
|
|
47
47
|
- PGlite wrapped with a validation layer
|
|
48
48
|
- `validateStatement()` rejects unsupported SQL before execution
|
|
49
49
|
- `TransactionTracker` enforces DDL/DML separation and 3,000-row limit
|
|
50
|
-
- `simulateConflict()` test helper for OCC testing
|
|
50
|
+
- `simulateConflict()` test helper for OCC unit testing (mock-only hook, absent from the deployed AWS surface). The `40001`→409 serialization-conflict mapping is covered by translator/engine **unit tests** (mirroring `bb-data`), not an over-the-wire e2e test: `simulateConflict()` is a mock-only trigger with no counterpart on the deployed runtime, and there is no deterministic way to raise a genuine `40001` conflict over the JSON-RPC wire in local e2e. A duplicate-key `23505`→409 `UniqueConstraintViolation` conflict (not retriable) is different: the DSQL mock enforces the primary-key constraint, so it **is** deterministically inducible over the wire and is additionally covered by an over-the-wire e2e test (`test-apps/comprehensive/test/dsql.test.ts`), not a mock-only hook.
|
|
51
51
|
- Error translation matches production behavior
|
|
52
52
|
|
|
53
53
|
## Validation Layer
|
|
@@ -128,7 +128,7 @@ Happens in the engine layer (same pattern as bb-data engines):
|
|
|
128
128
|
| pg error code | DistributedDatabaseErrors name |
|
|
129
129
|
|---------------|------------------------|
|
|
130
130
|
| `40001` | `SerializationFailure` |
|
|
131
|
-
| `23505` | `UniqueConstraintViolation` |
|
|
131
|
+
| `23505` | `UniqueConstraintViolation` (→ `ApiError` 409, not retriable) |
|
|
132
132
|
| `08xxx` | `ConnectionFailed` |
|
|
133
133
|
| (other) | `QueryFailed` |
|
|
134
134
|
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ const { rowCount } = await db.execute(
|
|
|
36
36
|
DSQL uses OCC — transactions may fail at commit if another transaction modified the same rows. The callback executes exactly once unless you opt into retry.
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
|
-
// Default: no retry. Throws SerializationFailureException on conflict.
|
|
39
|
+
// Default: no retry. Throws SerializationFailureException (HTTP 409 Conflict, retriable) on conflict.
|
|
40
40
|
await db.transaction(async (tx) => {
|
|
41
41
|
await tx.execute(sql`UPDATE accounts SET balance = balance - ${100} WHERE id = ${fromId}`);
|
|
42
42
|
await tx.execute(sql`UPDATE accounts SET balance = balance + ${100} WHERE id = ${toId}`);
|
|
@@ -157,10 +157,12 @@ try {
|
|
|
157
157
|
await db.transaction(async (tx) => { /* ... */ });
|
|
158
158
|
} catch (e: unknown) {
|
|
159
159
|
if (isBlocksError(e, DistributedDatabaseErrors.SerializationFailure)) {
|
|
160
|
-
// OCC conflict — transaction was NOT committed.
|
|
160
|
+
// OCC conflict — transaction was NOT committed. Serialized as HTTP 409
|
|
161
|
+
// (Conflict), retriable — safe to retry.
|
|
161
162
|
}
|
|
162
163
|
if (isBlocksError(e, DistributedDatabaseErrors.UniqueConstraintViolation)) {
|
|
163
|
-
// Duplicate key
|
|
164
|
+
// Duplicate key — serialized as HTTP 409 (Conflict), not retriable
|
|
165
|
+
// (a blind retry of the same insert fails identically).
|
|
164
166
|
}
|
|
165
167
|
if (isBlocksError(e, DistributedDatabaseErrors.QueryFailed)) {
|
|
166
168
|
// General query failure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dsql-mock-engine.d.ts","sourceRoot":"","sources":["../../src/engines/dsql-mock-engine.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAG9C,OAAO,EAA6B,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAoCjH,qBAAa,cAAe,YAAW,cAAc;IACnD,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,KAAK,CAAC,CAAkB;IAEhC;;;;;OAKG;gBACS,OAAO,EAAE,MAAM,EAAE,YAAY,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAiC;IAMjG,OAAO,CAAC,QAAQ;IAMhB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IA6BnB,yDAAyD;IACzD,gBAAgB,IAAI,IAAI;IAExB;;;OAGG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK5C,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAMvD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAMvE,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAQ9C,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"dsql-mock-engine.d.ts","sourceRoot":"","sources":["../../src/engines/dsql-mock-engine.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAG9C,OAAO,EAA6B,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAoCjH,qBAAa,cAAe,YAAW,cAAc;IACnD,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8B;IAC3D,OAAO,CAAC,KAAK,CAAC,CAAkB;IAEhC;;;;;OAKG;gBACS,OAAO,EAAE,MAAM,EAAE,YAAY,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAiC;IAMjG,OAAO,CAAC,QAAQ;IAMhB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IA6BnB,yDAAyD;IACzD,gBAAgB,IAAI,IAAI;IAExB;;;OAGG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK5C,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAMvD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAMvE,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAQ9C,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3D,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAO/F,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAW/G,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
|
|
@@ -148,7 +148,10 @@ export class DsqlMockEngine {
|
|
|
148
148
|
this.shouldConflict = false;
|
|
149
149
|
await this.db.query('ROLLBACK');
|
|
150
150
|
const err = Object.assign(new Error('SerializationFailureException: OCC conflict — transaction not committed.'), { code: PG_SERIALIZATION_FAILURE, name: DistributedDatabaseErrors.SerializationFailure });
|
|
151
|
-
|
|
151
|
+
// Route through translateDsqlError so the mock surfaces the SAME 409
|
|
152
|
+
// ApiError (name preserved, retriable) the real DSQL engine produces for
|
|
153
|
+
// SQLSTATE 40001 — keeping mock and aws paths behaviorally identical.
|
|
154
|
+
translateDsqlError(err);
|
|
152
155
|
}
|
|
153
156
|
await this.db.query('COMMIT');
|
|
154
157
|
handle.tracker.reset();
|
|
@@ -7,6 +7,7 @@ import { rmSync } from 'node:fs';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { DsqlMockEngine } from './dsql-mock-engine.js';
|
|
9
9
|
import { DistributedDatabaseErrors } from '../errors.js';
|
|
10
|
+
import { ApiError } from '@aws-blocks/core';
|
|
10
11
|
const TEST_DIR = '.bb-data-dsql-mock-test-' + process.pid;
|
|
11
12
|
let engine;
|
|
12
13
|
afterEach(async () => {
|
|
@@ -110,3 +111,21 @@ test('recovers on a later call after the init-retry budget is exhausted', async
|
|
|
110
111
|
assert.deepStrictEqual(rows, [{ id: 'ok' }]);
|
|
111
112
|
assert.ok(creates >= 4, 'engine should build a fresh instance after exhaustion');
|
|
112
113
|
});
|
|
114
|
+
// --- OCC conflict surfaces as HTTP 409 (Conflict) ---
|
|
115
|
+
// A simulated serialization-failure (SQLSTATE 40001) on commit must reach the
|
|
116
|
+
// caller as an ApiError with status 409 — not a plain 500 — while preserving
|
|
117
|
+
// the SerializationFailure name and flagging the conflict retriable, so mock
|
|
118
|
+
// and real DSQL engines behave identically over the JSON-RPC wire.
|
|
119
|
+
test('commit OCC conflict surfaces as an ApiError with status 409', async () => {
|
|
120
|
+
const dir = join(TEST_DIR, 'occ-conflict-409');
|
|
121
|
+
engine = new DsqlMockEngine(dir);
|
|
122
|
+
const handle = await engine.beginTransaction();
|
|
123
|
+
engine.simulateConflict();
|
|
124
|
+
await assert.rejects(() => engine.commitTransaction(handle), (e) => {
|
|
125
|
+
assert.ok(e instanceof ApiError, 'expected an ApiError');
|
|
126
|
+
assert.strictEqual(e.status, 409);
|
|
127
|
+
assert.strictEqual(e.name, DistributedDatabaseErrors.SerializationFailure);
|
|
128
|
+
assert.strictEqual(e.retriable, true);
|
|
129
|
+
return true;
|
|
130
|
+
});
|
|
131
|
+
});
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiError } from '@aws-blocks/core';
|
|
1
2
|
/**
|
|
2
3
|
* DSQL-specific error constants.
|
|
3
4
|
*/
|
|
@@ -24,6 +25,37 @@ export declare const PG_CONNECTION_EXCEPTION_CLASS = "08";
|
|
|
24
25
|
* @see https://docs.aws.amazon.com/aurora-dsql/latest/userguide/working-with-transactions.html
|
|
25
26
|
*/
|
|
26
27
|
export declare const TRANSACTION_ROW_LIMIT = 3000;
|
|
28
|
+
/**
|
|
29
|
+
* Build the 409 ApiError for a serialization-failure (SQLSTATE 40001) OCC
|
|
30
|
+
* conflict. Maps to HTTP 409 (Conflict) so the JSON-RPC serializer emits code
|
|
31
|
+
* 409 instead of a generic 500, preserves the `SerializationFailure` name so
|
|
32
|
+
* `isBlocksError(e, DistributedDatabaseErrors.SerializationFailure)` keeps
|
|
33
|
+
* matching on both server and client, keeps the original engine error as
|
|
34
|
+
* `cause` (server-side), and flags the conflict retriable — the caller can
|
|
35
|
+
* retry the transaction (see `retryOnConflict`).
|
|
36
|
+
*
|
|
37
|
+
* The client-visible message is a fixed, stable string (the raw driver text is
|
|
38
|
+
* verbose); the original error is retained as `cause` for server-side
|
|
39
|
+
* diagnostics.
|
|
40
|
+
*/
|
|
41
|
+
export declare function serializationConflict(cause: Error): ApiError;
|
|
42
|
+
/**
|
|
43
|
+
* Build the 409 ApiError for a unique-constraint / duplicate-key violation
|
|
44
|
+
* (SQLSTATE 23505). Maps to HTTP 409 (Conflict) so the JSON-RPC serializer emits
|
|
45
|
+
* code 409 instead of a generic 500, preserves the `UniqueConstraintViolation`
|
|
46
|
+
* name so `isBlocksError(e, DistributedDatabaseErrors.UniqueConstraintViolation)`
|
|
47
|
+
* keeps matching on both server and client, and keeps the original engine error
|
|
48
|
+
* as `cause` (server-side).
|
|
49
|
+
*
|
|
50
|
+
* Unlike {@link serializationConflict}, this is NOT flagged retriable: a
|
|
51
|
+
* duplicate key is a deterministic constraint failure, so a blind retry of the
|
|
52
|
+
* same insert fails identically (ApiError defaults `retriable` to `false`).
|
|
53
|
+
*
|
|
54
|
+
* The client-visible message is a fixed, stable string; the raw driver text
|
|
55
|
+
* (which can name columns / constraints and varies by engine) is retained only
|
|
56
|
+
* as `cause` for server-side diagnostics, never interpolated into the message.
|
|
57
|
+
*/
|
|
58
|
+
export declare function uniqueConstraintConflict(cause: Error): ApiError;
|
|
27
59
|
/** Translate a pg error code to a DistributedDatabaseErrors name. */
|
|
28
60
|
export declare function translateDsqlError(e: Error): never;
|
|
29
61
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;CAO5B,CAAC;AAEX;;;GAGG;AAEH,qFAAqF;AACrF,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAChD,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAC3C,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;CAO5B,CAAC;AAEX;;;GAGG;AAEH,qFAAqF;AACrF,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAChD,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAC3C,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAM5D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAK/D;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAmBlD"}
|
package/dist/errors.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { ApiError } from '@aws-blocks/core';
|
|
3
4
|
/**
|
|
4
5
|
* DSQL-specific error constants.
|
|
5
6
|
*/
|
|
@@ -26,14 +27,63 @@ export const PG_CONNECTION_EXCEPTION_CLASS = '08';
|
|
|
26
27
|
* @see https://docs.aws.amazon.com/aurora-dsql/latest/userguide/working-with-transactions.html
|
|
27
28
|
*/
|
|
28
29
|
export const TRANSACTION_ROW_LIMIT = 3000;
|
|
30
|
+
/**
|
|
31
|
+
* Build the 409 ApiError for a serialization-failure (SQLSTATE 40001) OCC
|
|
32
|
+
* conflict. Maps to HTTP 409 (Conflict) so the JSON-RPC serializer emits code
|
|
33
|
+
* 409 instead of a generic 500, preserves the `SerializationFailure` name so
|
|
34
|
+
* `isBlocksError(e, DistributedDatabaseErrors.SerializationFailure)` keeps
|
|
35
|
+
* matching on both server and client, keeps the original engine error as
|
|
36
|
+
* `cause` (server-side), and flags the conflict retriable — the caller can
|
|
37
|
+
* retry the transaction (see `retryOnConflict`).
|
|
38
|
+
*
|
|
39
|
+
* The client-visible message is a fixed, stable string (the raw driver text is
|
|
40
|
+
* verbose); the original error is retained as `cause` for server-side
|
|
41
|
+
* diagnostics.
|
|
42
|
+
*/
|
|
43
|
+
export function serializationConflict(cause) {
|
|
44
|
+
return new ApiError('The transaction failed due to a serialization conflict', 409, {
|
|
45
|
+
name: DistributedDatabaseErrors.SerializationFailure,
|
|
46
|
+
cause,
|
|
47
|
+
retriable: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the 409 ApiError for a unique-constraint / duplicate-key violation
|
|
52
|
+
* (SQLSTATE 23505). Maps to HTTP 409 (Conflict) so the JSON-RPC serializer emits
|
|
53
|
+
* code 409 instead of a generic 500, preserves the `UniqueConstraintViolation`
|
|
54
|
+
* name so `isBlocksError(e, DistributedDatabaseErrors.UniqueConstraintViolation)`
|
|
55
|
+
* keeps matching on both server and client, and keeps the original engine error
|
|
56
|
+
* as `cause` (server-side).
|
|
57
|
+
*
|
|
58
|
+
* Unlike {@link serializationConflict}, this is NOT flagged retriable: a
|
|
59
|
+
* duplicate key is a deterministic constraint failure, so a blind retry of the
|
|
60
|
+
* same insert fails identically (ApiError defaults `retriable` to `false`).
|
|
61
|
+
*
|
|
62
|
+
* The client-visible message is a fixed, stable string; the raw driver text
|
|
63
|
+
* (which can name columns / constraints and varies by engine) is retained only
|
|
64
|
+
* as `cause` for server-side diagnostics, never interpolated into the message.
|
|
65
|
+
*/
|
|
66
|
+
export function uniqueConstraintConflict(cause) {
|
|
67
|
+
return new ApiError('The item violates a unique constraint', 409, {
|
|
68
|
+
name: DistributedDatabaseErrors.UniqueConstraintViolation,
|
|
69
|
+
cause,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
29
72
|
/** Translate a pg error code to a DistributedDatabaseErrors name. */
|
|
30
73
|
export function translateDsqlError(e) {
|
|
31
74
|
const code = e.code;
|
|
32
75
|
if (code === PG_SERIALIZATION_FAILURE) {
|
|
33
|
-
|
|
76
|
+
// An OCC / serialization-failure conflict (SQLSTATE 40001) is a Conflict,
|
|
77
|
+
// not an InternalServerError: see serializationConflict() for the full
|
|
78
|
+
// rationale (409 mapping, preserved name, retained cause, retriable flag).
|
|
79
|
+
throw serializationConflict(e);
|
|
34
80
|
}
|
|
35
81
|
else if (code === PG_UNIQUE_VIOLATION) {
|
|
36
|
-
|
|
82
|
+
// A duplicate-key / unique-constraint violation (SQLSTATE 23505) is a
|
|
83
|
+
// Conflict, not an InternalServerError: see uniqueConstraintConflict() for
|
|
84
|
+
// the full rationale (409 mapping, preserved name, retained cause, and why
|
|
85
|
+
// it is NOT retriable).
|
|
86
|
+
throw uniqueConstraintConflict(e);
|
|
37
87
|
}
|
|
38
88
|
else if (code && code.startsWith(PG_CONNECTION_EXCEPTION_CLASS)) {
|
|
39
89
|
e.name = DistributedDatabaseErrors.ConnectionFailed;
|
package/dist/errors.test.js
CHANGED
|
@@ -5,19 +5,38 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { test } from 'node:test';
|
|
7
7
|
import assert from 'node:assert/strict';
|
|
8
|
+
import { ApiError } from '@aws-blocks/core';
|
|
8
9
|
import { translateDsqlError, DistributedDatabaseErrors, PG_SERIALIZATION_FAILURE, PG_UNIQUE_VIOLATION, } from './errors.js';
|
|
10
|
+
test('translateDsqlError: serialization failure (40001) → ApiError status 409, retriable', () => {
|
|
11
|
+
const err = Object.assign(new Error('conflict'), { code: PG_SERIALIZATION_FAILURE });
|
|
12
|
+
assert.throws(() => translateDsqlError(err), (e) => {
|
|
13
|
+
assert.ok(e instanceof ApiError, 'expected an ApiError');
|
|
14
|
+
assert.equal(e.status, 409);
|
|
15
|
+
assert.equal(e.name, DistributedDatabaseErrors.SerializationFailure);
|
|
16
|
+
assert.equal(e.retriable, true);
|
|
17
|
+
assert.equal(e.message, 'The transaction failed due to a serialization conflict');
|
|
18
|
+
assert.equal(e.cause.message, 'conflict');
|
|
19
|
+
return true;
|
|
20
|
+
});
|
|
21
|
+
});
|
|
9
22
|
test('translateDsqlError: serialization failure (40001) → SerializationFailure', () => {
|
|
10
23
|
const err = Object.assign(new Error('conflict'), { code: PG_SERIALIZATION_FAILURE });
|
|
11
24
|
assert.throws(() => translateDsqlError(err), (e) => {
|
|
12
25
|
assert.equal(e.name, DistributedDatabaseErrors.SerializationFailure);
|
|
13
|
-
assert.equal(e.message, 'conflict');
|
|
26
|
+
assert.equal(e.message, 'The transaction failed due to a serialization conflict');
|
|
14
27
|
return true;
|
|
15
28
|
});
|
|
16
29
|
});
|
|
17
|
-
test('translateDsqlError: unique violation (23505) →
|
|
18
|
-
const err = Object.assign(new Error('duplicate key'), { code: PG_UNIQUE_VIOLATION });
|
|
30
|
+
test('translateDsqlError: unique violation (23505) → ApiError status 409, name preserved, not retriable', () => {
|
|
31
|
+
const err = Object.assign(new Error('duplicate key value violates unique constraint "dsql_items_pkey"'), { code: PG_UNIQUE_VIOLATION });
|
|
19
32
|
assert.throws(() => translateDsqlError(err), (e) => {
|
|
33
|
+
assert.ok(e instanceof ApiError, 'expected an ApiError');
|
|
34
|
+
assert.equal(e.status, 409);
|
|
20
35
|
assert.equal(e.name, DistributedDatabaseErrors.UniqueConstraintViolation);
|
|
36
|
+
assert.strictEqual(e.retriable, false, 'a duplicate-key retry fails identically → not retriable');
|
|
37
|
+
assert.equal(e.message, 'The item violates a unique constraint');
|
|
38
|
+
// Raw driver error retained server-side as `cause`, not leaked into the message.
|
|
39
|
+
assert.equal(e.cause, err);
|
|
21
40
|
return true;
|
|
22
41
|
});
|
|
23
42
|
});
|
package/dist/index.aws.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export declare class DistributedDatabase extends Scope {
|
|
|
23
23
|
*
|
|
24
24
|
* DSQL uses Optimistic Concurrency Control. Commit may fail with
|
|
25
25
|
* SerializationFailureException if another transaction modified the same rows.
|
|
26
|
+
* That conflict is an `ApiError` with status 409 (Conflict), flagged retriable,
|
|
27
|
+
* so it serializes to JSON-RPC code 409 (not 500); `isBlocksError(e,
|
|
28
|
+
* DistributedDatabaseErrors.SerializationFailure)` still matches by name.
|
|
26
29
|
*/
|
|
27
30
|
transaction<T>(fn: (tx: Transaction) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
28
31
|
/** @internal */
|
package/dist/index.aws.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,KAAK,CAA6B;IAE1C,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,0BAA0B;IAQjF,OAAO,KAAK,IAAI,GAiBf;IAED,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD
|
|
1
|
+
{"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,KAAK,CAA6B;IAE1C,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,0BAA0B;IAQjF,OAAO,KAAK,IAAI,GAiBf;IAED,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD;;;;;;;;OAQG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAInG,gBAAgB;IAChB,SAAS;CACV;AAED,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.aws.js
CHANGED
|
@@ -49,6 +49,9 @@ export class DistributedDatabase extends Scope {
|
|
|
49
49
|
*
|
|
50
50
|
* DSQL uses Optimistic Concurrency Control. Commit may fail with
|
|
51
51
|
* SerializationFailureException if another transaction modified the same rows.
|
|
52
|
+
* That conflict is an `ApiError` with status 409 (Conflict), flagged retriable,
|
|
53
|
+
* so it serializes to JSON-RPC code 409 (not 500); `isBlocksError(e,
|
|
54
|
+
* DistributedDatabaseErrors.SerializationFailure)` still matches by name.
|
|
52
55
|
*/
|
|
53
56
|
async transaction(fn, options) {
|
|
54
57
|
return transactionWithRetry(this.base, fn, options);
|
package/dist/index.cdk.d.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Provisions Aurora DSQL cluster via CloudFormation.
|
|
4
4
|
* Optionally runs migrations via a CustomResource Lambda.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { BuildingBlockScope } from '@aws-blocks/core/cdk';
|
|
7
7
|
import type { ScopeParent } from '@aws-blocks/core';
|
|
8
8
|
import type { DistributedDatabaseOptions } from './types.js';
|
|
9
|
-
export declare class DistributedDatabase extends
|
|
9
|
+
export declare class DistributedDatabase extends BuildingBlockScope {
|
|
10
10
|
constructor(scope: ScopeParent, id: string, options?: DistributedDatabaseOptions);
|
|
11
11
|
/**
|
|
12
12
|
* Runtime-only. This is the CDK (synth) build: it defines infrastructure and
|
package/dist/index.cdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAA0E,MAAM,sBAAsB,CAAC;AAClI,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AASpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAG7D,qBAAa,mBAAoB,SAAQ,kBAAkB;gBAC7C,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;IA8GhF;;;;;OAKG;IACH,SAAS,IAAI,KAAK;CAGnB;AAaD,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.cdk.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provisions Aurora DSQL cluster via CloudFormation.
|
|
6
6
|
* Optionally runs migrations via a CustomResource Lambda.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { BuildingBlockScope, DEFAULT_NODE_RUNTIME, synthGuard, blocksNodejsBundling, registerConfig } from '@aws-blocks/core/cdk';
|
|
9
9
|
import * as cdk from 'aws-cdk-lib';
|
|
10
10
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
11
11
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
@@ -15,9 +15,9 @@ import { createHash } from 'node:crypto';
|
|
|
15
15
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
16
16
|
import { join, resolve } from 'node:path';
|
|
17
17
|
import { LAMBDA_MIGRATIONS_DIR, MIGRATION_LAMBDA_TIMEOUT_MINUTES, ENV_SANITIZE, sanitizeDbRoleName } from './constants.js';
|
|
18
|
-
export class DistributedDatabase extends
|
|
18
|
+
export class DistributedDatabase extends BuildingBlockScope {
|
|
19
19
|
constructor(scope, id, options) {
|
|
20
|
-
super(id, { parent: scope });
|
|
20
|
+
super(id, { parent: scope, vpc: { requiresEgress: true } });
|
|
21
21
|
const stack = cdk.Stack.of(this);
|
|
22
22
|
const envName = this.fullId.replace(ENV_SANITIZE, '_');
|
|
23
23
|
const region = stack.region;
|
|
@@ -96,9 +96,15 @@ export class DistributedDatabase extends Scope {
|
|
|
96
96
|
const provider = new cr.Provider(stack, `${this.fullId}DsqlMigrationProvider`, {
|
|
97
97
|
onEventHandler: migrationFn,
|
|
98
98
|
});
|
|
99
|
+
// `appRoleArn` is a property, not just a Lambda env var, because CloudFormation only
|
|
100
|
+
// re-invokes a CustomResource when its properties change. Replacing the app's IAM role
|
|
101
|
+
// changes this ARN, which is what re-runs provisionAppRole() and re-issues the DSQL
|
|
102
|
+
// `AWS IAM GRANT`. Without it, a role replacement leaves the grant on the old, deleted
|
|
103
|
+
// ARN and every query fails with 28000 (invalid_authorization_specification). The ARN is
|
|
104
|
+
// a token, so this only differs when the role is genuinely replaced — not on every deploy.
|
|
99
105
|
const migrationCR = new cdk.CustomResource(stack, `${this.fullId}DsqlMigrationCR`, {
|
|
100
106
|
serviceToken: provider.serviceToken,
|
|
101
|
-
properties: { migrationsHash, dbRole },
|
|
107
|
+
properties: { migrationsHash, dbRole, appRoleArn },
|
|
102
108
|
});
|
|
103
109
|
// Ensure migrations run after cluster is created
|
|
104
110
|
migrationCR.node.addDependency(cluster);
|
package/dist/index.cdk.test.js
CHANGED
|
@@ -243,6 +243,32 @@ test('CDK: migration resources created when migrationsPath is provided', () => {
|
|
|
243
243
|
rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
|
+
test('CDK: migration CustomResource tracks the app role ARN', () => {
|
|
247
|
+
rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
|
|
248
|
+
mkdirSync(MIGRATIONS_DIR, { recursive: true });
|
|
249
|
+
writeFileSync(join(MIGRATIONS_DIR, '001_create.sql'), 'CREATE TABLE t (id TEXT PRIMARY KEY)');
|
|
250
|
+
try {
|
|
251
|
+
const template = synth((stack) => {
|
|
252
|
+
new DistributedDatabase(scope(stack), 'mydsql', { migrationsPath: MIGRATIONS_DIR });
|
|
253
|
+
});
|
|
254
|
+
const customResources = template.findResources('AWS::CloudFormation::CustomResource');
|
|
255
|
+
const cr = Object.values(customResources)[0];
|
|
256
|
+
// CloudFormation only re-invokes a CustomResource when its PROPERTIES change. The app
|
|
257
|
+
// role ARN must therefore be a property, not just a Lambda env var — otherwise replacing
|
|
258
|
+
// the app's IAM role leaves the DSQL `AWS IAM GRANT` pointing at the old, deleted ARN and
|
|
259
|
+
// every query fails with 28000 (invalid_authorization_specification).
|
|
260
|
+
assert.ok(cr.Properties?.appRoleArn, 'CustomResource should have appRoleArn property');
|
|
261
|
+
// It must be the same reference the migration Lambda receives, so the two can never drift.
|
|
262
|
+
const fns = template.findResources('AWS::Lambda::Function');
|
|
263
|
+
const migrationFnEntry = Object.entries(fns).find(([id]) => id.includes('MigrationFn'));
|
|
264
|
+
assert.ok(migrationFnEntry, 'Migration Lambda should exist');
|
|
265
|
+
const env = migrationFnEntry[1].Properties?.Environment?.Variables ?? {};
|
|
266
|
+
assert.deepStrictEqual(cr.Properties.appRoleArn, env.APP_ROLE_ARN, 'CustomResource appRoleArn should match the migration Lambda APP_ROLE_ARN env var');
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
246
272
|
test('CDK: migration CustomResource has migrationsHash property', () => {
|
|
247
273
|
rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
|
|
248
274
|
mkdirSync(MIGRATIONS_DIR, { recursive: true });
|
package/dist/index.mock.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export declare class DistributedDatabase extends Scope {
|
|
|
25
25
|
*
|
|
26
26
|
* DSQL uses Optimistic Concurrency Control. Commit may fail with
|
|
27
27
|
* SerializationFailureException if another transaction modified the same rows.
|
|
28
|
+
* That conflict is an `ApiError` with status 409 (Conflict), flagged retriable,
|
|
29
|
+
* so it serializes to JSON-RPC code 409 (not 500); `isBlocksError(e,
|
|
30
|
+
* DistributedDatabaseErrors.SerializationFailure)` still matches by name.
|
|
28
31
|
*/
|
|
29
32
|
transaction<T>(fn: (tx: Transaction) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
30
33
|
/** Test helper: simulate OCC conflict on next commit. */
|
package/dist/index.mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,aAAa,CAA8B;IAEnD,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;YAelE,KAAK;IAEnB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD
|
|
1
|
+
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,aAAa,CAA8B;IAEnD,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;YAelE,KAAK;IAEnB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD;;;;;;;;OAQG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAKnG,yDAAyD;IACzD,gBAAgB,IAAI,IAAI;IAExB,gBAAgB;IAChB,SAAS;CACV;AAED,OAAO,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.mock.js
CHANGED
|
@@ -40,6 +40,9 @@ export class DistributedDatabase extends Scope {
|
|
|
40
40
|
*
|
|
41
41
|
* DSQL uses Optimistic Concurrency Control. Commit may fail with
|
|
42
42
|
* SerializationFailureException if another transaction modified the same rows.
|
|
43
|
+
* That conflict is an `ApiError` with status 409 (Conflict), flagged retriable,
|
|
44
|
+
* so it serializes to JSON-RPC code 409 (not 500); `isBlocksError(e,
|
|
45
|
+
* DistributedDatabaseErrors.SerializationFailure)` still matches by name.
|
|
43
46
|
*/
|
|
44
47
|
async transaction(fn, options) {
|
|
45
48
|
await this.ready();
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-distributed-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"aws-blocks",
|
|
6
|
+
"database",
|
|
7
|
+
"sql",
|
|
8
|
+
"dsql",
|
|
9
|
+
"serverless",
|
|
10
|
+
"aurora-dsql"
|
|
11
|
+
],
|
|
4
12
|
"repository": {
|
|
5
13
|
"type": "git",
|
|
6
14
|
"url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
|
|
@@ -37,9 +45,9 @@
|
|
|
37
45
|
"test": "node --test 'dist/**/*.test.js'"
|
|
38
46
|
},
|
|
39
47
|
"dependencies": {
|
|
40
|
-
"@aws-blocks/core": "^0.
|
|
41
|
-
"@aws-blocks/data-common": "^0.1.
|
|
42
|
-
"@aws-blocks/bb-logger": "^0.
|
|
48
|
+
"@aws-blocks/core": "^0.5.0",
|
|
49
|
+
"@aws-blocks/data-common": "^0.1.5",
|
|
50
|
+
"@aws-blocks/bb-logger": "^0.2.0",
|
|
43
51
|
"@aws-sdk/dsql-signer": "^3.700.0",
|
|
44
52
|
"@electric-sql/pglite": "^0.2.0",
|
|
45
53
|
"pg": "^8.13.0"
|