@aws-blocks/bb-distributed-data 0.1.7 → 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 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. Safe to retry.
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;IAc3D,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"}
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
- throw err;
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
@@ -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,CAYlD"}
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
- e.name = DistributedDatabaseErrors.SerializationFailure;
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
- e.name = DistributedDatabaseErrors.UniqueConstraintViolation;
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;
@@ -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) → UniqueConstraintViolation', () => {
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
  });
@@ -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 */
@@ -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;;;;;OAKG;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"}
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);
@@ -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 { Scope } from '@aws-blocks/core/cdk';
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 Scope {
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAA0E,MAAM,sBAAsB,CAAC;AACrH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAG7D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B;IAkGhF;;;;;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"}
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,18 +5,19 @@
5
5
  * Provisions Aurora DSQL cluster via CloudFormation.
6
6
  * Optionally runs migrations via a CustomResource Lambda.
7
7
  */
8
- import { Scope, DEFAULT_NODE_RUNTIME, synthGuard, blocksNodejsBundling, registerConfig } from '@aws-blocks/core/cdk';
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';
12
+ import { LogGroup } from 'aws-cdk-lib/aws-logs';
12
13
  import * as cr from 'aws-cdk-lib/custom-resources';
13
14
  import { createHash } from 'node:crypto';
14
15
  import { readdirSync, readFileSync } from 'node:fs';
15
16
  import { join, resolve } from 'node:path';
16
17
  import { LAMBDA_MIGRATIONS_DIR, MIGRATION_LAMBDA_TIMEOUT_MINUTES, ENV_SANITIZE, sanitizeDbRoleName } from './constants.js';
17
- export class DistributedDatabase extends Scope {
18
+ export class DistributedDatabase extends BuildingBlockScope {
18
19
  constructor(scope, id, options) {
19
- super(id, { parent: scope });
20
+ super(id, { parent: scope, vpc: { requiresEgress: true } });
20
21
  const stack = cdk.Stack.of(this);
21
22
  const envName = this.fullId.replace(ENV_SANITIZE, '_');
22
23
  const region = stack.region;
@@ -64,6 +65,12 @@ export class DistributedDatabase extends Scope {
64
65
  handler: 'handler',
65
66
  runtime: DEFAULT_NODE_RUNTIME,
66
67
  timeout: cdk.Duration.minutes(MIGRATION_LAMBDA_TIMEOUT_MINUTES),
68
+ // Own the migration Lambda's log group so its retention follows the
69
+ // stack-wide default instead of AWS's infinite retention.
70
+ logGroup: new LogGroup(stack, `${this.fullId}DsqlMigrationLogs`, {
71
+ retention: this.defaults.logRetention,
72
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
73
+ }),
67
74
  environment: {
68
75
  DSQL_ENDPOINT: endpoint,
69
76
  DSQL_REGION: region,
@@ -89,9 +96,15 @@ export class DistributedDatabase extends Scope {
89
96
  const provider = new cr.Provider(stack, `${this.fullId}DsqlMigrationProvider`, {
90
97
  onEventHandler: migrationFn,
91
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.
92
105
  const migrationCR = new cdk.CustomResource(stack, `${this.fullId}DsqlMigrationCR`, {
93
106
  serviceToken: provider.serviceToken,
94
- properties: { migrationsHash, dbRole },
107
+ properties: { migrationsHash, dbRole, appRoleArn },
95
108
  });
96
109
  // Ensure migrations run after cluster is created
97
110
  migrationCR.node.addDependency(cluster);
@@ -189,6 +189,33 @@ test('CDK: migration/provisioning resources always created', () => {
189
189
  const hasDsqlGrant = policyValues.some((p) => JSON.stringify(p).includes('dsql:DbConnectAdmin'));
190
190
  assert.ok(hasDsqlGrant, 'Migration Lambda should have dsql:DbConnectAdmin');
191
191
  });
192
+ test('CDK: DSQL migration Lambda log group adopts defaults.logRetention', () => {
193
+ const app = new cdk.App();
194
+ const stack = new cdk.Stack(app, 'DsqlMigrationRetentionStack');
195
+ const executionRole = new cdk.aws_iam.Role(stack, 'BlocksRole', {
196
+ assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'),
197
+ });
198
+ const handler = new lambda.Function(stack, 'Handler', {
199
+ runtime: DEFAULT_NODE_RUNTIME,
200
+ handler: 'index.handler',
201
+ code: lambda.Code.fromInline('exports.handler = async () => {};'),
202
+ role: executionRole,
203
+ });
204
+ stack.handler = handler;
205
+ stack.executionRole = executionRole;
206
+ stack.defaults = BlocksPresets.sandbox;
207
+ globalThis.CURRENT_BLOCKS_STACK = stack;
208
+ try {
209
+ new DistributedDatabase(scope(stack), 'mydsql');
210
+ const template = Template.fromStack(stack);
211
+ // The always-created migration Lambda now owns an explicit log group whose
212
+ // retention follows the stack-wide default (sandbox → one week).
213
+ template.hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 7 });
214
+ }
215
+ finally {
216
+ delete globalThis.CURRENT_BLOCKS_STACK;
217
+ }
218
+ });
192
219
  test('CDK: migration resources created when migrationsPath is provided', () => {
193
220
  rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
194
221
  mkdirSync(MIGRATIONS_DIR, { recursive: true });
@@ -216,6 +243,32 @@ test('CDK: migration resources created when migrationsPath is provided', () => {
216
243
  rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
217
244
  }
218
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
+ });
219
272
  test('CDK: migration CustomResource has migrationsHash property', () => {
220
273
  rmSync(MIGRATIONS_DIR, { recursive: true, force: true });
221
274
  mkdirSync(MIGRATIONS_DIR, { recursive: true });
@@ -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. */
@@ -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;;;;;OAKG;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"}
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"}
@@ -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
@@ -1,3 +1,3 @@
1
1
  export declare const BB_NAME = "DistributedDatabase";
2
- export declare const BB_VERSION = "0.1.7";
2
+ export declare const BB_VERSION = "0.2.0";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
2
  export const BB_NAME = 'DistributedDatabase';
3
- export const BB_VERSION = '0.1.7';
3
+ export const BB_VERSION = '0.2.0';
package/package.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "name": "@aws-blocks/bb-distributed-data",
3
- "version": "0.1.7",
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.3.0",
41
- "@aws-blocks/data-common": "^0.1.4",
42
- "@aws-blocks/bb-logger": "^0.1.5",
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"