@lokalise/prisma-utils 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -5
- package/dist/bulk-update/prismaBulkUpdate.d.ts +15 -6
- package/dist/bulk-update/prismaBulkUpdate.js +57 -31
- package/dist/bulk-update/prismaBulkUpdate.js.map +1 -1
- package/dist/errors/cockroachdbError.d.ts +5 -0
- package/dist/errors/cockroachdbError.js +16 -2
- package/dist/errors/cockroachdbError.js.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/prismaError.d.ts +7 -0
- package/dist/errors/prismaError.js +9 -0
- package/dist/errors/prismaError.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/retry/calculateRetryDelay.d.ts +1 -0
- package/dist/retry/calculateRetryDelay.js +6 -0
- package/dist/retry/calculateRetryDelay.js.map +1 -0
- package/dist/retry/index.d.ts +2 -0
- package/dist/retry/index.js +2 -0
- package/dist/retry/index.js.map +1 -0
- package/dist/retry/retryOnSerializationFailure.d.ts +20 -0
- package/dist/retry/retryOnSerializationFailure.js +43 -0
- package/dist/retry/retryOnSerializationFailure.js.map +1 -0
- package/dist/retry/types.d.ts +5 -0
- package/dist/retry/types.js +2 -0
- package/dist/retry/types.js.map +1 -0
- package/dist/transaction/prismaTransaction.js +1 -5
- package/dist/transaction/prismaTransaction.js.map +1 -1
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -143,6 +143,38 @@ errors, providing a robust and efficient way to ensure your transactions succeed
|
|
|
143
143
|
By automatically retrying failed transactions with an intelligent backoff strategy, you can significantly improve the
|
|
144
144
|
reliability and performance of your application when using distributed databases.
|
|
145
145
|
|
|
146
|
+
### Retry On Serialization Failure
|
|
147
|
+
|
|
148
|
+
Retries a single statement while it fails with a serialization failure, without opening a transaction around it.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { retryOnSerializationFailure } from '@lokalise/prisma-utils'
|
|
152
|
+
|
|
153
|
+
const rows = await retryOnSerializationFailure(() =>
|
|
154
|
+
prisma.$queryRaw<Row[]>`SELECT ... FROM "segment" WHERE "project_id" = ${projectId}`,
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Retries when the error is Prisma's `P2034` or a CockroachDB transaction retry error (SQLSTATE `40001`), in both the query-engine and the driver-adapter shape. Anything else is rethrown immediately, and the last error is rethrown once the retries are exhausted.
|
|
159
|
+
|
|
160
|
+
#### Options
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const rows = await retryOnSerializationFailure(query, {
|
|
164
|
+
retriesAllowed: 2, // Default: 2 (total 3 attempts)
|
|
165
|
+
baseRetryDelayMs: 100, // Default: 100ms
|
|
166
|
+
maxRetryDelayMs: 30000, // Default: 30s
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The backoff is the same as `prismaTransaction`'s: `2^(retry-1) × baseRetryDelayMs`, capped at `maxRetryDelayMs`.
|
|
171
|
+
|
|
172
|
+
#### When To Use It Instead Of `prismaTransaction`
|
|
173
|
+
|
|
174
|
+
For a single read, wrapping the statement in an explicit transaction costs a BEGIN/COMMIT round trip and takes away CockroachDB's own ability to refresh and retry the implicit single-statement transaction server-side, which it can no longer do once the client has opened the transaction.
|
|
175
|
+
|
|
176
|
+
It is only safe around a **single read-only statement**. A write, or a statement that runs inside an interactive transaction, has to be retried at the transaction boundary (`prismaTransaction`) instead — replaying it alone would either duplicate its effects or re-run it against an aborted transaction.
|
|
177
|
+
|
|
146
178
|
### Prisma Bulk Update
|
|
147
179
|
|
|
148
180
|
Updates many rows in a single atomic SQL statement (`UPDATE ... FROM (VALUES ...)`), instead of issuing one `UPDATE` per row. Because it is a single statement, it either fully applies or fully rolls back, so no surrounding transaction is required. Works on both CockroachDB and PostgreSQL.
|
|
@@ -154,10 +186,10 @@ import { prismaBulkUpdate } from '@lokalise/prisma-utils'
|
|
|
154
186
|
|
|
155
187
|
await prismaBulkUpdate(prisma, 'segment', {
|
|
156
188
|
dbDriver: 'CockroachDb',
|
|
157
|
-
typeByColumn: { id: 'uuid', value: 'text', words_count: 'int4' },
|
|
189
|
+
typeByColumn: { project_id: 'uuid', id: 'uuid', value: 'text', words_count: 'int4' },
|
|
158
190
|
}, [
|
|
159
|
-
{ where: { id: id1 }, data: { value: 'a', words_count: 1 } },
|
|
160
|
-
{ where: { id: id2 }, data: { value: 'b', words_count: 2 } },
|
|
191
|
+
{ where: { project_id: projectId, id: id1 }, data: { value: 'a', words_count: 1 } },
|
|
192
|
+
{ where: { project_id: projectId, id: id2 }, data: { value: 'b', words_count: 2 } },
|
|
161
193
|
])
|
|
162
194
|
```
|
|
163
195
|
|
|
@@ -168,9 +200,11 @@ UPDATE "segment"
|
|
|
168
200
|
SET "value" = updates."value"::text, "words_count" = updates."words_count"::int4
|
|
169
201
|
FROM (VALUES ($1::uuid, $2::text, $3::int4), ($4::uuid, $5::text, $6::int4))
|
|
170
202
|
AS updates("id", "value", "words_count")
|
|
171
|
-
WHERE "segment"."id" = updates."id"::uuid
|
|
203
|
+
WHERE "segment"."project_id" = $7::uuid AND "segment"."id" = updates."id"::uuid
|
|
172
204
|
```
|
|
173
205
|
|
|
206
|
+
`project_id` has the same value on every entry, so it is emitted as a constant predicate instead of a `VALUES` column (see "Constant `where` columns" below).
|
|
207
|
+
|
|
174
208
|
#### Parameters
|
|
175
209
|
|
|
176
210
|
- **`prisma`** — a Prisma client or a transaction client (e.g. the `tx` passed to `prismaTransaction`'s callback).
|
|
@@ -200,5 +234,6 @@ const updated = await prismaBulkUpdate<{ id: string; value: string }>(
|
|
|
200
234
|
)
|
|
201
235
|
```
|
|
202
236
|
|
|
203
|
-
- **
|
|
237
|
+
- **Constant `where` columns**: a `where` column whose value is the same on every entry becomes `"tbl"."col" = $n::type` instead of a `VALUES` column. With a tenant column such as `project_id` joined from `VALUES`, CockroachDB can pick a lookup join on a primary key that starts with it and read every row of the tenant; as a constant, the planner uses the index on the other key. A value counts as the same only when it is a string, number, boolean or bigint and is `===` to the first entry's value; `null`, `Date`, `Buffer` and objects stay in `VALUES`. When every `where` column is constant, `VALUES` holds only the `data` columns.
|
|
238
|
+
- **Limits**: at most 1000 entries per call, and the total bound parameters (`entries × (VALUES where columns + data columns) + constant where columns`) must not exceed 65535, the shared protocol limit for PostgreSQL and CockroachDB. Both are validated up front. A column may not appear in both `where` and `data`.
|
|
204
239
|
- **Trusted identifiers** — `tableName`, all column names, the column types, and the `returning` keys/aliases are interpolated as raw SQL identifiers (only row values are bound). They must be static, trusted configuration, never end-user input.
|
|
@@ -9,18 +9,27 @@ import type { BulkUpdateEntry, PrismaBulkUpdateOptions } from './types.ts';
|
|
|
9
9
|
*
|
|
10
10
|
* The executed query follows the below structure for provided example data:
|
|
11
11
|
* [
|
|
12
|
-
* { where: { id: 1 }, data: { col1: 11, col2: 12 } },
|
|
13
|
-
* { where: { id: 2 }, data: { col1: 21, col2: 22 } },
|
|
12
|
+
* { where: { tenant_id: 7, id: 1 }, data: { col1: 11, col2: 12 } },
|
|
13
|
+
* { where: { tenant_id: 7, id: 2 }, data: { col1: 21, col2: 22 } },
|
|
14
14
|
* ]
|
|
15
15
|
*
|
|
16
16
|
* UPDATE "tbl"
|
|
17
|
-
* SET "col1" = updates."col1", "col2" = updates."col2"
|
|
17
|
+
* SET "col1" = updates."col1"::int4, "col2" = updates."col2"::int4
|
|
18
18
|
* FROM (
|
|
19
19
|
* VALUES
|
|
20
|
-
* (1,
|
|
21
|
-
* (
|
|
20
|
+
* ($1::int4, $2::int4, $3::int4),
|
|
21
|
+
* ($4::int4, $5::int4, $6::int4)
|
|
22
22
|
* ) AS updates("id", "col1", "col2")
|
|
23
|
-
* WHERE "tbl"."id" = updates."id"
|
|
23
|
+
* WHERE "tbl"."tenant_id" = $7::int4 AND "tbl"."id" = updates."id"::int4
|
|
24
|
+
*
|
|
25
|
+
* A "where" column whose value is the same on every entry (`tenant_id` above) is
|
|
26
|
+
* emitted as a constant predicate instead of a `VALUES` column. Joining a tenant
|
|
27
|
+
* column from `VALUES` lets CockroachDB plan a lookup join on a primary key that
|
|
28
|
+
* starts with it and read every row of the tenant; a constant lets it use the
|
|
29
|
+
* index on the other key. A value counts as the same only when it is a string,
|
|
30
|
+
* number, boolean or bigint and is `===` to the first entry's value. Anything else
|
|
31
|
+
* (`null`, `Date`, `Buffer`, objects) stays in `VALUES`. When every "where" column
|
|
32
|
+
* is constant, `VALUES` holds only the "data" columns.
|
|
24
33
|
*
|
|
25
34
|
* "data" values follow Prisma's convention: an `undefined` value leaves the
|
|
26
35
|
* column untouched (it is dropped from the statement), while `null` sets it to
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { join, raw, sqltag as sql } from '@prisma/client/runtime/client';
|
|
2
2
|
const JSON_COLUMN_TYPES = new Set(['json', 'jsonb']);
|
|
3
|
+
// `typeof` results for which `a === b` guarantees both bind to the same SQL value.
|
|
4
|
+
const COMPARABLE_VALUE_TYPES = new Set(['string', 'number', 'boolean', 'bigint']);
|
|
3
5
|
const ENTRIES_LIMIT = 1000;
|
|
4
6
|
/**
|
|
5
|
-
* Every
|
|
6
|
-
* placeholder
|
|
7
|
-
* `entries × (
|
|
8
|
-
* statement at 65535 bound parameters and
|
|
9
|
-
* guard the total up front to fail with a
|
|
10
|
-
*
|
|
7
|
+
* Every "data" cell and every "where" cell in the `VALUES` list is one bound
|
|
8
|
+
* placeholder, and each constant "where" predicate adds one more, so the
|
|
9
|
+
* statement uses `entries × (valuesWhereColumns + dataColumns) + constantWhereColumns`
|
|
10
|
+
* parameters. PostgreSQL caps a single statement at 65535 bound parameters and
|
|
11
|
+
* CockroachDB has its own ceiling, so we guard the total up front to fail with a
|
|
12
|
+
* clear message instead of an opaque driver error.
|
|
11
13
|
*/
|
|
12
14
|
const BIND_PARAMETERS_LIMIT = 65535;
|
|
13
15
|
/**
|
|
@@ -18,18 +20,27 @@ const BIND_PARAMETERS_LIMIT = 65535;
|
|
|
18
20
|
*
|
|
19
21
|
* The executed query follows the below structure for provided example data:
|
|
20
22
|
* [
|
|
21
|
-
* { where: { id: 1 }, data: { col1: 11, col2: 12 } },
|
|
22
|
-
* { where: { id: 2 }, data: { col1: 21, col2: 22 } },
|
|
23
|
+
* { where: { tenant_id: 7, id: 1 }, data: { col1: 11, col2: 12 } },
|
|
24
|
+
* { where: { tenant_id: 7, id: 2 }, data: { col1: 21, col2: 22 } },
|
|
23
25
|
* ]
|
|
24
26
|
*
|
|
25
27
|
* UPDATE "tbl"
|
|
26
|
-
* SET "col1" = updates."col1", "col2" = updates."col2"
|
|
28
|
+
* SET "col1" = updates."col1"::int4, "col2" = updates."col2"::int4
|
|
27
29
|
* FROM (
|
|
28
30
|
* VALUES
|
|
29
|
-
* (1,
|
|
30
|
-
* (
|
|
31
|
+
* ($1::int4, $2::int4, $3::int4),
|
|
32
|
+
* ($4::int4, $5::int4, $6::int4)
|
|
31
33
|
* ) AS updates("id", "col1", "col2")
|
|
32
|
-
* WHERE "tbl"."id" = updates."id"
|
|
34
|
+
* WHERE "tbl"."tenant_id" = $7::int4 AND "tbl"."id" = updates."id"::int4
|
|
35
|
+
*
|
|
36
|
+
* A "where" column whose value is the same on every entry (`tenant_id` above) is
|
|
37
|
+
* emitted as a constant predicate instead of a `VALUES` column. Joining a tenant
|
|
38
|
+
* column from `VALUES` lets CockroachDB plan a lookup join on a primary key that
|
|
39
|
+
* starts with it and read every row of the tenant; a constant lets it use the
|
|
40
|
+
* index on the other key. A value counts as the same only when it is a string,
|
|
41
|
+
* number, boolean or bigint and is `===` to the first entry's value. Anything else
|
|
42
|
+
* (`null`, `Date`, `Buffer`, objects) stays in `VALUES`. When every "where" column
|
|
43
|
+
* is constant, `VALUES` holds only the "data" columns.
|
|
33
44
|
*
|
|
34
45
|
* "data" values follow Prisma's convention: an `undefined` value leaves the
|
|
35
46
|
* column untouched (it is dropped from the statement), while `null` sets it to
|
|
@@ -88,10 +99,14 @@ export const prismaBulkUpdate = (prisma, tableName, options, entries) => {
|
|
|
88
99
|
if (overlappingColumn) {
|
|
89
100
|
throw new Error(`Column "${overlappingColumn.name}" must not appear in both "where" and "data"`);
|
|
90
101
|
}
|
|
91
|
-
const
|
|
102
|
+
const constantWhereColumns = resolveConstantWhereColumns(whereColumns, entries);
|
|
103
|
+
const valuesWhereColumns = whereColumns.filter((column) => !constantWhereColumns.has(column));
|
|
104
|
+
const valuesColumns = [...valuesWhereColumns, ...dataColumns];
|
|
105
|
+
const bindParametersCount = entries.length * valuesColumns.length + constantWhereColumns.size;
|
|
92
106
|
if (bindParametersCount > BIND_PARAMETERS_LIMIT) {
|
|
107
|
+
const constantsBreakdown = constantWhereColumns.size > 0 ? ` + ${constantWhereColumns.size} constant "where" values` : '';
|
|
93
108
|
throw new Error(`Bulk update would use ${bindParametersCount} bind parameters ` +
|
|
94
|
-
`(${entries.length} entries × ${
|
|
109
|
+
`(${entries.length} entries × ${valuesColumns.length} columns${constantsBreakdown}), ` +
|
|
95
110
|
`exceeding the limit of ${BIND_PARAMETERS_LIMIT}`);
|
|
96
111
|
}
|
|
97
112
|
// Quote each part of a (possibly schema-qualified) table name separately,
|
|
@@ -104,12 +119,15 @@ export const prismaBulkUpdate = (prisma, tableName, options, entries) => {
|
|
|
104
119
|
const sqlSetExpressions = dataColumns.map((column) => {
|
|
105
120
|
return sql([`"${column.name}" = updates."${column.name}"::${column.type}`]);
|
|
106
121
|
});
|
|
107
|
-
const sqlValuesExpressions = entries.map((entry, index) => prepareSqlValuesExpression(entry, index, whereColumns, dataColumns));
|
|
108
|
-
const sqlValuesColumnAliases =
|
|
122
|
+
const sqlValuesExpressions = entries.map((entry, index) => prepareSqlValuesExpression(entry, index, whereColumns, valuesWhereColumns, dataColumns));
|
|
123
|
+
const sqlValuesColumnAliases = valuesColumns.map((column) => {
|
|
109
124
|
return sql([`"${column.name}"`]);
|
|
110
125
|
});
|
|
111
126
|
const sqlWhereConditions = whereColumns.map((column) => {
|
|
112
|
-
|
|
127
|
+
const qualifiedColumn = `${quotedTableName}."${column.name}"`;
|
|
128
|
+
return constantWhereColumns.has(column)
|
|
129
|
+
? sql `${raw(qualifiedColumn)} = ${renderTypedValue(firstEntry.where[column.name], column.type)}`
|
|
130
|
+
: sql([`${qualifiedColumn} = updates."${column.name}"::${column.type}`]);
|
|
113
131
|
});
|
|
114
132
|
const updateStatement = sql `
|
|
115
133
|
UPDATE ${sql([quotedTableName])}
|
|
@@ -128,23 +146,22 @@ export const prismaBulkUpdate = (prisma, tableName, options, entries) => {
|
|
|
128
146
|
return prisma.$queryRaw(sql `${updateStatement} RETURNING ${join(sqlReturningExpressions, ', ')}`);
|
|
129
147
|
};
|
|
130
148
|
/**
|
|
131
|
-
* Builds the parenthesized `(where..., set...)` tuple for a single entry
|
|
132
|
-
*
|
|
133
|
-
* columns
|
|
134
|
-
*
|
|
135
|
-
*
|
|
149
|
+
* Builds the parenthesized `(where..., set...)` tuple for a single entry: one row
|
|
150
|
+
* of the `VALUES` source, holding the `valuesWhereColumns` subset of the "where"
|
|
151
|
+
* columns. The entry must supply exactly the given `where` columns and the same
|
|
152
|
+
* set of defined `data` columns (the shape established by the first entry); a
|
|
153
|
+
* mismatch throws so the generated `VALUES` list stays rectangular and aligned
|
|
154
|
+
* with the column aliases.
|
|
136
155
|
*/
|
|
137
|
-
const prepareSqlValuesExpression = (entry, index, whereColumns, dataColumns) => {
|
|
156
|
+
const prepareSqlValuesExpression = (entry, index, whereColumns, valuesWhereColumns, dataColumns) => {
|
|
138
157
|
if (whereColumns.length !== Object.keys(entry.where).length) {
|
|
139
158
|
throw new Error(`Entry "where" columns are not the same (at index ${index})`);
|
|
140
159
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return renderTypedValue(whereConditionValue, whereConditionColumn.type);
|
|
147
|
-
});
|
|
160
|
+
const missingWhereColumn = whereColumns.find((column) => entry.where[column.name] === undefined);
|
|
161
|
+
if (missingWhereColumn) {
|
|
162
|
+
throw new Error(`Entry "where" column "${missingWhereColumn.name}" was not found (at index ${index})`);
|
|
163
|
+
}
|
|
164
|
+
const sqlWhereValues = valuesWhereColumns.map((column) => renderTypedValue(entry.where[column.name], column.type));
|
|
148
165
|
if (dataColumns.length !== definedColumnNames(entry.data).length) {
|
|
149
166
|
throw new Error(`Entry "data" columns are not the same (at index ${index})`);
|
|
150
167
|
}
|
|
@@ -155,8 +172,17 @@ const prepareSqlValuesExpression = (entry, index, whereColumns, dataColumns) =>
|
|
|
155
172
|
}
|
|
156
173
|
return renderTypedValue(setExpressionValue, setExpressionColumn.type);
|
|
157
174
|
});
|
|
158
|
-
return sql `(${join(sqlWhereValues,
|
|
175
|
+
return sql `(${join([...sqlWhereValues, ...sqlSetValues], ',')})`;
|
|
159
176
|
};
|
|
177
|
+
/**
|
|
178
|
+
* Picks the "where" columns to emit as constant predicates, following the rule in
|
|
179
|
+
* the `prismaBulkUpdate` docblock.
|
|
180
|
+
*/
|
|
181
|
+
const resolveConstantWhereColumns = (whereColumns, entries) => new Set(whereColumns.filter((column) => {
|
|
182
|
+
const firstValue = entries[0]?.where[column.name];
|
|
183
|
+
return (COMPARABLE_VALUE_TYPES.has(typeof firstValue) &&
|
|
184
|
+
entries.every((entry) => entry.where[column.name] === firstValue));
|
|
185
|
+
}));
|
|
160
186
|
/**
|
|
161
187
|
* Pairs each requested column name with its SQL type from `typeByColumn`,
|
|
162
188
|
* throwing when a column has no type mapping (it would otherwise produce an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prismaBulkUpdate.js","sourceRoot":"","sources":["../../src/bulk-update/prismaBulkUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,IAAI,GAAG,EAAE,MAAM,+BAA+B,CAAA;AAKxE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"prismaBulkUpdate.js","sourceRoot":"","sources":["../../src/bulk-update/prismaBulkUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,IAAI,GAAG,EAAE,MAAM,+BAA+B,CAAA;AAKxE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAE5D,mFAAmF;AACnF,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;AAOzF,MAAM,aAAa,GAAG,IAAI,CAAA;AAE1B;;;;;;;GAOG;AACH,MAAM,qBAAqB,GAAG,KAAK,CAAA;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,MAAsC,EACtC,SAAiB,EACjB,OAAgC,EAChC,OAA0B,EACZ,EAAE;IAChB,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAE3C,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO,CAAA;IAE5B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,aAAa,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,wCAAwC,aAAa,EAAE,CAAC,CAAA;IAC1E,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IAChF,MAAM,WAAW,GAAG,cAAc,CAAC,YAAY,EAAE,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAErF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;IAC3D,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IAC1D,CAAC;IAED,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAC7D,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,SAAS,CAC3D,CAAA;IACD,IAAI,oBAAoB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,yBAAyB,oBAAoB,yBAAyB,CAAC,CAAA;IACzF,CAAC;IAED,8EAA8E;IAC9E,6EAA6E;IAC7E,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CACxD,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CACzE,CAAA;IACD,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,WAAW,iBAAiB,CAAC,IAAI,8CAA8C,CAAC,CAAA;IAClG,CAAC;IAED,MAAM,oBAAoB,GAAG,2BAA2B,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAC/E,MAAM,kBAAkB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7F,MAAM,aAAa,GAAG,CAAC,GAAG,kBAAkB,EAAE,GAAG,WAAW,CAAC,CAAA;IAE7D,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAA;IAC7F,IAAI,mBAAmB,GAAG,qBAAqB,EAAE,CAAC;QAChD,MAAM,kBAAkB,GACtB,oBAAoB,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,IAAI,0BAA0B,CAAC,CAAC,CAAC,EAAE,CAAA;QAChG,MAAM,IAAI,KAAK,CACb,yBAAyB,mBAAmB,mBAAmB;YAC7D,IAAI,OAAO,CAAC,MAAM,cAAc,aAAa,CAAC,MAAM,WAAW,kBAAkB,KAAK;YACtF,0BAA0B,qBAAqB,EAAE,CACpD,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,8EAA8E;IAC9E,iDAAiD;IACjD,MAAM,eAAe,GAAG,SAAS;SAC9B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC;SAC1B,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACnD,OAAO,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,gBAAgB,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACxD,0BAA0B,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,CAAC,CACxF,CAAA;IAED,MAAM,sBAAsB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC1D,OAAO,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,MAAM,kBAAkB,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACrD,MAAM,eAAe,GAAG,GAAG,eAAe,KAAK,MAAM,CAAC,IAAI,GAAG,CAAA;QAC7D,OAAO,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC;YACrC,CAAC,CAAC,GAAG,CAAA,GAAG,GAAG,CAAC,eAAe,CAAC,MAAM,gBAAgB,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;YAChG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,eAAe,eAAe,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,MAAM,eAAe,GAAG,GAAG,CAAA;aAChB,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;UACzB,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC;;iBAEtB,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC;mBAC/B,IAAI,CAAC,sBAAsB,EAAE,IAAI,CAAC;YACzC,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;GAC1C,CAAA;IAED,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,OAAO,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3D,CAAC;IAED,MAAM,uBAAuB,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE;QAChF,OAAO,GAAG,CAAC,CAAC,GAAG,eAAe,KAAK,MAAM,SAAS,KAAK,GAAG,CAAC,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAC,SAAS,CACrB,GAAG,CAAA,GAAG,eAAe,cAAc,IAAI,CAAC,uBAAuB,EAAE,IAAI,CAAC,EAAE,CACzE,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,0BAA0B,GAAG,CACjC,KAAsB,EACtB,KAAa,EACb,YAAsB,EACtB,kBAA4B,EAC5B,WAAqB,EACrB,EAAE;IACF,IAAI,YAAY,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,oDAAoD,KAAK,GAAG,CAAC,CAAA;IAC/E,CAAC;IAED,MAAM,kBAAkB,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAA;IAChG,IAAI,kBAAkB,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,yBAAyB,kBAAkB,CAAC,IAAI,6BAA6B,KAAK,GAAG,CACtF,CAAA;IACH,CAAC;IAED,MAAM,cAAc,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACvD,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CACxD,CAAA;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,mDAAmD,KAAK,GAAG,CAAC,CAAA;IAC9E,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,EAAE;QAC3D,MAAM,kBAAkB,GAAG,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAE/D,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,wBAAwB,mBAAmB,CAAC,IAAI,6BAA6B,KAAK,GAAG,CACtF,CAAA;QACH,CAAC;QAED,OAAO,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,CAAA,IAAI,IAAI,CAAC,CAAC,GAAG,cAAc,EAAE,GAAG,YAAY,CAAC,EAAE,GAAG,CAAC,GAAG,CAAA;AAClE,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,2BAA2B,GAAG,CAClC,YAAsB,EACtB,OAA0B,EACb,EAAE,CACf,IAAI,GAAG,CACL,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;IAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO,CACL,sBAAsB,CAAC,GAAG,CAAC,OAAO,UAAU,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAClE,CAAA;AACH,CAAC,CAAC,CACH,CAAA;AAEH;;;;GAIG;AACH,MAAM,cAAc,GAAG,CAAC,YAAoC,EAAE,WAAqB,EAAY,EAAE,CAC/F,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;IAC7B,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IAErC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,uCAAuC,UAAU,GAAG,CAAC,CAAA;IACvE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC,CAAC,CAAA;AAEJ;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,CAAC,YAAqC,EAAY,EAAE,CAC7E,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC,CAAA;AAE1F;;;;;;;;;;;;GAYG;AACH,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAE,IAAY,EAAE,EAAE,CACxD,KAAK,KAAK,IAAI,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;IAC3C,CAAC,CAAC,GAAG,CAAA,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,EAAE;IAC7C,CAAC,CAAC,GAAG,CAAA,GAAG,KAAK,KAAK,GAAG,CAAC,IAAI,CAAC,EAAE,CAAA"}
|
|
@@ -2,6 +2,11 @@ import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/clien
|
|
|
2
2
|
/**
|
|
3
3
|
* Check if the error is a CockroachDB transaction retry error
|
|
4
4
|
*
|
|
5
|
+
* Two shapes carry the SQLSTATE, depending on how the query reached the database:
|
|
6
|
+
* - the Rust query engine reports it flat, as `meta.code`
|
|
7
|
+
* - a driver adapter (e.g. `@prisma/adapter-pg`) reports a `P2010` whose SQLSTATE is nested
|
|
8
|
+
* under `meta.driverAdapterError.cause.originalCode`, with no `meta.code` at all
|
|
9
|
+
*
|
|
5
10
|
* @param error
|
|
6
11
|
*/
|
|
7
12
|
export declare const isCockroachDBRetryTransaction: (error: PrismaClientKnownRequestError) => boolean;
|
|
@@ -4,15 +4,29 @@
|
|
|
4
4
|
* All transaction retry errors use the SQLSTATE error code 40001
|
|
5
5
|
*/
|
|
6
6
|
const COCKROACHDB_RETRY_TRANSACTION_CODE = '40001';
|
|
7
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
7
8
|
/**
|
|
8
9
|
* Check if the error is a CockroachDB transaction retry error
|
|
9
10
|
*
|
|
11
|
+
* Two shapes carry the SQLSTATE, depending on how the query reached the database:
|
|
12
|
+
* - the Rust query engine reports it flat, as `meta.code`
|
|
13
|
+
* - a driver adapter (e.g. `@prisma/adapter-pg`) reports a `P2010` whose SQLSTATE is nested
|
|
14
|
+
* under `meta.driverAdapterError.cause.originalCode`, with no `meta.code` at all
|
|
15
|
+
*
|
|
10
16
|
* @param error
|
|
11
17
|
*/
|
|
12
18
|
export const isCockroachDBRetryTransaction = (error) => {
|
|
13
19
|
const meta = error.meta;
|
|
14
|
-
if (!meta)
|
|
20
|
+
if (!isRecord(meta))
|
|
21
|
+
return false;
|
|
22
|
+
if (meta.code === COCKROACHDB_RETRY_TRANSACTION_CODE)
|
|
23
|
+
return true;
|
|
24
|
+
const driverAdapterError = meta.driverAdapterError;
|
|
25
|
+
if (!isRecord(driverAdapterError))
|
|
26
|
+
return false;
|
|
27
|
+
const cause = driverAdapterError.cause;
|
|
28
|
+
if (!isRecord(cause))
|
|
15
29
|
return false;
|
|
16
|
-
return
|
|
30
|
+
return cause.originalCode === COCKROACHDB_RETRY_TRANSACTION_CODE;
|
|
17
31
|
};
|
|
18
32
|
//# sourceMappingURL=cockroachdbError.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cockroachdbError.js","sourceRoot":"","sources":["../../src/errors/cockroachdbError.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,kCAAkC,GAAG,OAAO,CAAA;AAElD
|
|
1
|
+
{"version":3,"file":"cockroachdbError.js","sourceRoot":"","sources":["../../src/errors/cockroachdbError.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,kCAAkC,GAAG,OAAO,CAAA;AAElD,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAA;AAE7C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,KAAoC,EAAW,EAAE;IAC7F,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAEjC,IAAI,IAAI,CAAC,IAAI,KAAK,kCAAkC;QAAE,OAAO,IAAI,CAAA;IAEjE,MAAM,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,CAAA;IAClD,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,KAAK,CAAA;IAE/C,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAA;IACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAElC,OAAO,KAAK,CAAC,YAAY,KAAK,kCAAkC,CAAA;AAClE,CAAC,CAAA"}
|
package/dist/errors/index.d.ts
CHANGED
package/dist/errors/index.js
CHANGED
package/dist/errors/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA"}
|
|
@@ -8,6 +8,13 @@ import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/clien
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const isPrismaClientKnownRequestError: (error: unknown) => error is PrismaClientKnownRequestError;
|
|
10
10
|
export declare const isPrismaTransactionClosedError: (error: PrismaClientKnownRequestError) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Check if the error is a serialization failure, and therefore safe to retry.
|
|
13
|
+
*
|
|
14
|
+
* Covers both Prisma's own `P2034` and the SQLSTATE 40001 reported by CockroachDB, whose shape
|
|
15
|
+
* depends on whether the query went through the Rust query engine or a driver adapter.
|
|
16
|
+
*/
|
|
17
|
+
export declare const isSerializationFailure: (error: unknown) => boolean;
|
|
11
18
|
/**
|
|
12
19
|
* Prisma error code P2025 indicates that the operation failed because it depends on one or more
|
|
13
20
|
* records that were required but not found
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isError } from '@lokalise/node-core';
|
|
2
|
+
import { isCockroachDBRetryTransaction } from './cockroachdbError.js';
|
|
2
3
|
/**
|
|
3
4
|
* What is checked?
|
|
4
5
|
* 1. error is defined and not null
|
|
@@ -13,6 +14,14 @@ export const isPrismaClientKnownRequestError = (error) => !!error &&
|
|
|
13
14
|
error.code.startsWith('P');
|
|
14
15
|
export const isPrismaTransactionClosedError = (error) => error.code === PRISMA_TRANSACTION_ERROR &&
|
|
15
16
|
error.message.toLowerCase().includes('transaction already closed');
|
|
17
|
+
/**
|
|
18
|
+
* Check if the error is a serialization failure, and therefore safe to retry.
|
|
19
|
+
*
|
|
20
|
+
* Covers both Prisma's own `P2034` and the SQLSTATE 40001 reported by CockroachDB, whose shape
|
|
21
|
+
* depends on whether the query went through the Rust query engine or a driver adapter.
|
|
22
|
+
*/
|
|
23
|
+
export const isSerializationFailure = (error) => isPrismaClientKnownRequestError(error) &&
|
|
24
|
+
(error.code === PRISMA_SERIALIZATION_ERROR || isCockroachDBRetryTransaction(error));
|
|
16
25
|
/**
|
|
17
26
|
* Prisma error code P2025 indicates that the operation failed because it depends on one or more
|
|
18
27
|
* records that were required but not found
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prismaError.js","sourceRoot":"","sources":["../../src/errors/prismaError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"prismaError.js","sourceRoot":"","sources":["../../src/errors/prismaError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE7C,OAAO,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAA;AAErE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAC7C,KAAc,EAC0B,EAAE,CAC1C,CAAC,CAAC,KAAK;IACP,OAAO,CAAC,KAAK,CAAC;IACd,MAAM,IAAI,KAAK;IACf,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;IAC9B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;AAE5B,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,KAAoC,EAAW,EAAE,CAC9F,KAAK,CAAC,IAAI,KAAK,wBAAwB;IACvC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAAW,EAAE,CAChE,+BAA+B,CAAC,KAAK,CAAC;IACtC,CAAC,KAAK,CAAC,IAAI,KAAK,0BAA0B,IAAI,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAA;AAErF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAA;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,OAAO,CAAA;AAEjD;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,OAAO,CAAA;AAErD;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAA;AAE/C;;GAEG;AACH,MAAM,CAAC,MAAM,qCAAqC,GAAG,OAAO,CAAA"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,wBAAwB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,wBAAwB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const calculateRetryDelay: (retries: number, baseRetryDelayMs: number, maxDelayMs: number) => number;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const calculateRetryDelay = (retries, baseRetryDelayMs, maxDelayMs) => {
|
|
2
|
+
// exponential backoff -> 2^(retry-1) * baseRetryDelayMs
|
|
3
|
+
const expDelay = Math.pow(2, retries - 1) * baseRetryDelayMs;
|
|
4
|
+
return Math.min(expDelay, maxDelayMs);
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=calculateRetryDelay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculateRetryDelay.js","sourceRoot":"","sources":["../../src/retry/calculateRetryDelay.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,OAAe,EACf,gBAAwB,EACxB,UAAkB,EACV,EAAE;IACV,wDAAwD;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAA;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;AACvC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RetryOnSerializationFailureOptions } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Run a single statement, retrying it with exponential backoff while it fails with a
|
|
4
|
+
* serialization failure. Any other error is rethrown immediately, and the last serialization
|
|
5
|
+
* failure is rethrown once the retries are exhausted.
|
|
6
|
+
*
|
|
7
|
+
* Use this instead of {@link prismaTransaction} when the statement is a single read: wrapping one
|
|
8
|
+
* statement in an explicit transaction costs a BEGIN/COMMIT round trip and takes away
|
|
9
|
+
* CockroachDB's own ability to refresh and retry the implicit single-statement transaction
|
|
10
|
+
* server-side, which it can no longer do once the client has opened the transaction.
|
|
11
|
+
*
|
|
12
|
+
* Only safe around a single read-only statement. A write, or a statement that runs inside an
|
|
13
|
+
* interactive transaction, has to be retried at the transaction boundary instead - replaying it
|
|
14
|
+
* alone would either duplicate its effects or re-run it against an aborted transaction.
|
|
15
|
+
*
|
|
16
|
+
* @param {() => Promise<T>} query function issuing the statement, called once per attempt
|
|
17
|
+
* @param {RetryOnSerializationFailureOptions} options retry configuration
|
|
18
|
+
* @return {Promise<T>}
|
|
19
|
+
*/
|
|
20
|
+
export declare const retryOnSerializationFailure: <T>(query: () => Promise<T>, options?: RetryOnSerializationFailureOptions) => Promise<T>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { setTimeout } from 'node:timers/promises';
|
|
2
|
+
import { isSerializationFailure } from '../errors/index.js';
|
|
3
|
+
import { calculateRetryDelay } from './calculateRetryDelay.js';
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
retriesAllowed: 2, // first try + 2 retries = 3 tries
|
|
6
|
+
baseRetryDelayMs: 100,
|
|
7
|
+
maxRetryDelayMs: 30000, // 30s
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Run a single statement, retrying it with exponential backoff while it fails with a
|
|
11
|
+
* serialization failure. Any other error is rethrown immediately, and the last serialization
|
|
12
|
+
* failure is rethrown once the retries are exhausted.
|
|
13
|
+
*
|
|
14
|
+
* Use this instead of {@link prismaTransaction} when the statement is a single read: wrapping one
|
|
15
|
+
* statement in an explicit transaction costs a BEGIN/COMMIT round trip and takes away
|
|
16
|
+
* CockroachDB's own ability to refresh and retry the implicit single-statement transaction
|
|
17
|
+
* server-side, which it can no longer do once the client has opened the transaction.
|
|
18
|
+
*
|
|
19
|
+
* Only safe around a single read-only statement. A write, or a statement that runs inside an
|
|
20
|
+
* interactive transaction, has to be retried at the transaction boundary instead - replaying it
|
|
21
|
+
* alone would either duplicate its effects or re-run it against an aborted transaction.
|
|
22
|
+
*
|
|
23
|
+
* @param {() => Promise<T>} query function issuing the statement, called once per attempt
|
|
24
|
+
* @param {RetryOnSerializationFailureOptions} options retry configuration
|
|
25
|
+
* @return {Promise<T>}
|
|
26
|
+
*/
|
|
27
|
+
export const retryOnSerializationFailure = async (query, options) => {
|
|
28
|
+
// resolved per option, as spreading `options` would let an explicit `undefined` erase a default
|
|
29
|
+
const retriesAllowed = options?.retriesAllowed ?? DEFAULT_OPTIONS.retriesAllowed;
|
|
30
|
+
const baseRetryDelayMs = options?.baseRetryDelayMs ?? DEFAULT_OPTIONS.baseRetryDelayMs;
|
|
31
|
+
const maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_OPTIONS.maxRetryDelayMs;
|
|
32
|
+
for (let retries = 0;; retries++) {
|
|
33
|
+
try {
|
|
34
|
+
return await query();
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (retries >= retriesAllowed || !isSerializationFailure(error))
|
|
38
|
+
throw error;
|
|
39
|
+
await setTimeout(calculateRetryDelay(retries + 1, baseRetryDelayMs, maxRetryDelayMs));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=retryOnSerializationFailure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retryOnSerializationFailure.js","sourceRoot":"","sources":["../../src/retry/retryOnSerializationFailure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAG9D,MAAM,eAAe,GAAG;IACtB,cAAc,EAAE,CAAC,EAAE,kCAAkC;IACrD,gBAAgB,EAAE,GAAG;IACrB,eAAe,EAAE,KAAK,EAAE,MAAM;CACwB,CAAA;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAC9C,KAAuB,EACvB,OAA4C,EAChC,EAAE;IACd,gGAAgG;IAChG,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,eAAe,CAAC,cAAc,CAAA;IAChF,MAAM,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,IAAI,eAAe,CAAC,gBAAgB,CAAA;IACtF,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,eAAe,CAAC,eAAe,CAAA;IAEnF,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,EAAE,CAAA;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,IAAI,cAAc,IAAI,CAAC,sBAAsB,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAA;YAC5E,MAAM,UAAU,CAAC,mBAAmB,CAAC,OAAO,GAAG,CAAC,EAAE,gBAAgB,EAAE,eAAe,CAAC,CAAC,CAAA;QACvF,CAAC;IACH,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/retry/types.ts"],"names":[],"mappings":""}
|
|
@@ -2,6 +2,7 @@ import { setTimeout } from 'node:timers/promises';
|
|
|
2
2
|
import { deepClone } from '@lokalise/node-core';
|
|
3
3
|
import { isCockroachDBRetryTransaction } from '../errors/cockroachdbError.js';
|
|
4
4
|
import { isPrismaClientKnownRequestError, isPrismaTransactionClosedError, PRISMA_SERIALIZATION_ERROR, PRISMA_SERVER_CLOSED_CONNECTION_ERROR, PRISMA_TRANSACTION_ERROR, } from '../errors/index.js';
|
|
5
|
+
import { calculateRetryDelay } from '../retry/calculateRetryDelay.js';
|
|
5
6
|
const DEFAULT_OPTIONS = {
|
|
6
7
|
retriesAllowed: 2, // first try + 2 retries = 3 tries
|
|
7
8
|
baseRetryDelayMs: 100,
|
|
@@ -51,11 +52,6 @@ const executeTransactionTry = async (prisma, arg, options) => {
|
|
|
51
52
|
return { error };
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
|
-
const calculateRetryDelay = (retries, baseRetryDelayMs, maxDelayMs) => {
|
|
55
|
-
// exponential backoff -> 2^(retry-1) * baseRetryDelayMs
|
|
56
|
-
const expDelay = Math.pow(2, retries - 1) * baseRetryDelayMs;
|
|
57
|
-
return Math.min(expDelay, maxDelayMs);
|
|
58
|
-
};
|
|
59
55
|
const PrismaCodesToRetry = [
|
|
60
56
|
PRISMA_SERIALIZATION_ERROR,
|
|
61
57
|
PRISMA_SERVER_CLOSED_CONNECTION_ERROR,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prismaTransaction.js","sourceRoot":"","sources":["../../src/transaction/prismaTransaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAG/C,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAA;AAC7E,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,0BAA0B,EAC1B,qCAAqC,EACrC,wBAAwB,GACzB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"prismaTransaction.js","sourceRoot":"","sources":["../../src/transaction/prismaTransaction.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokalise/prisma-utils",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"files": [
|
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
"prisma": "^7.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@biomejs/biome": "^2.5.
|
|
30
|
-
"dotenv": "^
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
29
|
+
"@biomejs/biome": "^2.5.12",
|
|
30
|
+
"dotenv": "^18.0.8",
|
|
31
|
+
"@lokalise/biome-config": "^4.0.0",
|
|
32
|
+
"@lokalise/tsconfig": "^5.0.0",
|
|
33
|
+
"@prisma/adapter-pg": "^7.10.1",
|
|
34
|
+
"@prisma/client": "^7.10.0",
|
|
33
35
|
"@vitest/coverage-v8": "^4.1.11",
|
|
34
36
|
"cross-env": "^10.0.0",
|
|
35
37
|
"prisma": "^7.10.0",
|
|
36
38
|
"rimraf": "^6.1.3",
|
|
37
39
|
"typescript": "7.0.2",
|
|
38
|
-
"vitest": "^4.1.11"
|
|
39
|
-
"@lokalise/biome-config": "^3.1.1",
|
|
40
|
-
"@lokalise/tsconfig": "^5.0.0"
|
|
40
|
+
"vitest": "^4.1.11"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "rimraf dist && pnpm run db:update-client && tsc --project tsconfig.build.json",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"pretest:ci": "docker compose up -d --wait && pnpm run test:migrate",
|
|
52
52
|
"test:ci": "pnpm run test",
|
|
53
53
|
"posttest:ci": "docker compose down",
|
|
54
|
+
"bench:seed": "vitest run --config vitest.bench.config.ts",
|
|
55
|
+
"bench": "vitest bench --run --config vitest.bench.config.ts",
|
|
54
56
|
"package-version": "echo $npm_package_version",
|
|
55
57
|
"postversion": "biome check --write package.json"
|
|
56
58
|
}
|