@lokalise/prisma-utils 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
- import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
2
-
1
+ import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
3
2
  /**
4
3
  * Check if the error is a CockroachDB transaction retry error
5
4
  *
@@ -1,8 +1,17 @@
1
- const o = "40001", r = (e) => {
2
- const t = e.meta;
3
- return t ? t.code === o : !1;
1
+ /**
2
+ * https://www.cockroachlabs.com/docs/stable/transaction-retry-error-reference#:~:text=To%20indicate%20that%20a%20transaction,the%20string%20%22restart%20transaction%22%20.
3
+ *
4
+ * All transaction retry errors use the SQLSTATE error code 40001
5
+ */
6
+ const COCKROACHDB_RETRY_TRANSACTION_CODE = '40001';
7
+ /**
8
+ * Check if the error is a CockroachDB transaction retry error
9
+ *
10
+ * @param error
11
+ */
12
+ export const isCockroachDBRetryTransaction = (error) => {
13
+ const meta = error.meta;
14
+ if (!meta)
15
+ return false;
16
+ return meta.code === COCKROACHDB_RETRY_TRANSACTION_CODE;
4
17
  };
5
- export {
6
- r as isCockroachDBRetryTransaction
7
- };
8
- //# sourceMappingURL=cockroachdbError.js.map
@@ -0,0 +1 @@
1
+ export * from './prismaError';
@@ -1,5 +1,4 @@
1
- import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
2
-
1
+ import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
3
2
  /**
4
3
  * What is checked?
5
4
  * 1. error is defined and not null
@@ -1,12 +1,40 @@
1
- import { isError as s } from "@lokalise/node-core";
2
- const n = (R) => !!R && s(R) && "code" in R && typeof R.code == "string" && R.code.startsWith("P"), c = (R) => R.code === o && R.message.toLowerCase().includes("transaction already closed"), E = "P2025", _ = "P2034", i = "P2002", o = "P2028", O = "P1017";
3
- export {
4
- E as PRISMA_NOT_FOUND_ERROR,
5
- _ as PRISMA_SERIALIZATION_ERROR,
6
- O as PRISMA_SERVER_CLOSED_CONNECTION_ERROR,
7
- o as PRISMA_TRANSACTION_ERROR,
8
- i as PRISMA_UNIQUE_CONSTRAINT_ERROR,
9
- n as isPrismaClientKnownRequestError,
10
- c as isPrismaTransactionClosedError
11
- };
12
- //# sourceMappingURL=prismaError.js.map
1
+ import { isError } from '@lokalise/node-core';
2
+ /**
3
+ * What is checked?
4
+ * 1. error is defined and not null
5
+ * 2. error is an `Error`
6
+ * 3. error contains the field code which is a string
7
+ * 4. code starts by `P` ([doc](https://www.prisma.io/docs/reference/api-reference/error-reference#error-codes))
8
+ */
9
+ export const isPrismaClientKnownRequestError = (error) => !!error &&
10
+ isError(error) &&
11
+ 'code' in error &&
12
+ typeof error.code === 'string' &&
13
+ error.code.startsWith('P');
14
+ export const isPrismaTransactionClosedError = (error) => error.code === PRISMA_TRANSACTION_ERROR &&
15
+ error.message.toLowerCase().includes('transaction already closed');
16
+ /**
17
+ * Prisma error code P2025 indicates that the operation failed because it depends on one or more
18
+ * records that were required but not found
19
+ */
20
+ export const PRISMA_NOT_FOUND_ERROR = 'P2025';
21
+ /**
22
+ * Prisma error code P2034 indicates a serialization error and that the transaction must be retried.
23
+ * A different error code would indicate that an internal state error happened and that
24
+ * the cluster itself is experiencing an issue which requires intervention
25
+ */
26
+ export const PRISMA_SERIALIZATION_ERROR = 'P2034';
27
+ /**
28
+ * Prisma error code P2002 indicates that the operation failed because a unique constraint was
29
+ * violated. This can happen if you try to create a record with a unique field that already exists
30
+ */
31
+ export const PRISMA_UNIQUE_CONSTRAINT_ERROR = 'P2002';
32
+ /**
33
+ * Prisma error code P2028 indicates a transaction API error, essentially a placeholder for errors that do not fit into
34
+ * a more specific category. You should look into the error message for more details
35
+ */
36
+ export const PRISMA_TRANSACTION_ERROR = 'P2028';
37
+ /**
38
+ * Prisma error code P1017 indicates that the connection to the database server was prematurely terminated by the server
39
+ */
40
+ export const PRISMA_SERVER_CLOSED_CONNECTION_ERROR = 'P1017';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export type * from './types';
2
2
  export * from './errors';
3
3
  export { prismaTransaction } from './prismaTransaction';
4
+ export { prismaClientFactory } from './prismaClientFactory';
package/dist/index.js CHANGED
@@ -1,13 +1,3 @@
1
- import { PRISMA_NOT_FOUND_ERROR as _, PRISMA_SERIALIZATION_ERROR as E, PRISMA_SERVER_CLOSED_CONNECTION_ERROR as O, PRISMA_TRANSACTION_ERROR as I, PRISMA_UNIQUE_CONSTRAINT_ERROR as N, isPrismaClientKnownRequestError as o, isPrismaTransactionClosedError as A } from "./errors/prismaError.js";
2
- import { prismaTransaction as s } from "./prismaTransaction.js";
3
- export {
4
- _ as PRISMA_NOT_FOUND_ERROR,
5
- E as PRISMA_SERIALIZATION_ERROR,
6
- O as PRISMA_SERVER_CLOSED_CONNECTION_ERROR,
7
- I as PRISMA_TRANSACTION_ERROR,
8
- N as PRISMA_UNIQUE_CONSTRAINT_ERROR,
9
- o as isPrismaClientKnownRequestError,
10
- A as isPrismaTransactionClosedError,
11
- s as prismaTransaction
12
- };
13
- //# sourceMappingURL=index.js.map
1
+ export * from './errors';
2
+ export { prismaTransaction } from './prismaTransaction';
3
+ export { prismaClientFactory } from './prismaClientFactory';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import type { PrismaClientFactoryOptions } from './types';
3
+ export declare const prismaClientFactory: (options?: PrismaClientFactoryOptions) => PrismaClient;
@@ -0,0 +1,12 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ const defaultOptions = {
3
+ transactionOptions: { isolationLevel: 'ReadCommitted' },
4
+ };
5
+ export const prismaClientFactory = (options = {}) => {
6
+ options.transactionOptions = {
7
+ ...defaultOptions.transactionOptions,
8
+ ...options.transactionOptions,
9
+ };
10
+ //@ts-expect-error - ReadCommitted is not accepted by Prisma atm
11
+ return new PrismaClient(options);
12
+ };
@@ -1,8 +1,7 @@
1
- import { Either } from '@lokalise/node-core';
2
- import { Prisma, PrismaClient } from '@prisma/client';
3
- import { PrismaTransactionBasicOptions, PrismaTransactionFn, PrismaTransactionOptions } from './types';
4
-
1
+ import type { Either } from '@lokalise/node-core';
2
+ import type { Prisma, PrismaClient } from '@prisma/client';
5
3
  import type * as runtime from '@prisma/client/runtime/library';
4
+ import type { PrismaTransactionBasicOptions, PrismaTransactionFn, PrismaTransactionOptions } from './types';
6
5
  /**
7
6
  * Perform a Prisma DB transaction with automatic retries if needed.
8
7
  *
@@ -1,61 +1,80 @@
1
- import { setTimeout as n } from "node:timers/promises";
2
- import { deepClone as c } from "@lokalise/node-core";
3
- import { isCockroachDBRetryTransaction as l } from "./errors/cockroachdbError.js";
4
- import { isPrismaClientKnownRequestError as m, isPrismaTransactionClosedError as u, PRISMA_SERIALIZATION_ERROR as R, PRISMA_SERVER_CLOSED_CONNECTION_ERROR as y, PRISMA_TRANSACTION_ERROR as D } from "./errors/prismaError.js";
5
- const T = {
6
- retriesAllowed: 2,
7
- // first try + 2 retries = 3 tries
8
- DbDriver: "CockroachDb",
9
- baseRetryDelayMs: 100,
10
- maxRetryDelayMs: 3e4,
11
- // 30s
12
- timeout: 5e3,
13
- // 5s
14
- maxTimeout: 3e4
15
- // 30s
16
- }, O = async (t, o, e) => {
17
- let r = { ...T, ...e }, i, a = 0;
18
- do {
19
- if (a > 0 && await n(
20
- d(
21
- a,
22
- r.baseRetryDelayMs,
23
- r.maxRetryDelayMs
24
- )
25
- ), i = await f(t, o, r), i.result) break;
26
- const s = E(i, r.DbDriver);
27
- if (!s) break;
28
- s === "increase-timeout" && (r = c(r), r.timeout = Math.min(
29
- r.timeout * 2,
30
- r.maxTimeout
31
- )), a++;
32
- } while (a <= r.retriesAllowed);
33
- return i ?? { error: new Error("No transaction executed") };
34
- }, f = async (t, o, e) => {
35
- try {
36
- return {
37
- // @ts-ignore
38
- result: await t.$transaction(o, e)
39
- };
40
- } catch (r) {
41
- return { error: r };
42
- }
43
- }, d = (t, o, e) => {
44
- const r = Math.pow(2, t - 1) * o;
45
- return Math.min(r, e);
46
- }, A = [
47
- R,
48
- y,
49
- D
50
- ], E = (t, o) => {
51
- if (m(t.error)) {
52
- const e = t.error;
53
- if (u(e)) return "increase-timeout";
54
- if (A.includes(e.code) || o === "CockroachDb" && l(e)) return !0;
55
- }
56
- return !1;
1
+ import { setTimeout } from 'node:timers/promises';
2
+ import { deepClone } from '@lokalise/node-core';
3
+ import { isCockroachDBRetryTransaction } from './errors/cockroachdbError';
4
+ import { PRISMA_SERIALIZATION_ERROR, PRISMA_SERVER_CLOSED_CONNECTION_ERROR, PRISMA_TRANSACTION_ERROR, isPrismaClientKnownRequestError, isPrismaTransactionClosedError, } from './errors/prismaError';
5
+ const DEFAULT_OPTIONS = {
6
+ retriesAllowed: 2, // first try + 2 retries = 3 tries
7
+ dbDriver: 'CockroachDb',
8
+ baseRetryDelayMs: 100,
9
+ maxRetryDelayMs: 30000, // 30s
10
+ timeout: 5000, // 5s
11
+ maxTimeout: 30000, // 30s
57
12
  };
58
- export {
59
- O as prismaTransaction
13
+ /**
14
+ * Perform a Prisma DB transaction with automatic retries if needed.
15
+ *
16
+ * @template T | T extends Prisma.PrismaPromise<unknown>[]
17
+ * @param {PrismaClient} prisma
18
+ * @param {PrismaTransactionFn<T> | Prisma.PrismaPromise<unknown>[]} arg operation to perform into the transaction
19
+ * @param {PrismaTransactionOptions | PrismaTransactionBasicOptions} options transaction configuration
20
+ * @return {Promise<PrismaTransactionReturnType<T>>}
21
+ */
22
+ export const prismaTransaction = (async (prisma, arg, options) => {
23
+ let optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
24
+ let result = undefined;
25
+ let retries = 0;
26
+ do {
27
+ if (retries > 0) {
28
+ await setTimeout(calculateRetryDelay(retries, optionsWithDefaults.baseRetryDelayMs, optionsWithDefaults.maxRetryDelayMs));
29
+ }
30
+ result = await executeTransactionTry(prisma, arg, optionsWithDefaults);
31
+ if (result.result)
32
+ break;
33
+ const retryAllowed = isRetryAllowed(result, optionsWithDefaults.dbDriver);
34
+ if (!retryAllowed)
35
+ break;
36
+ if (retryAllowed === 'increase-timeout') {
37
+ optionsWithDefaults = deepClone(optionsWithDefaults);
38
+ optionsWithDefaults.timeout = Math.min(optionsWithDefaults.timeout * 2, optionsWithDefaults.maxTimeout);
39
+ }
40
+ retries++;
41
+ } while (retries <= optionsWithDefaults.retriesAllowed);
42
+ return result ?? { error: new Error('No transaction executed') };
43
+ });
44
+ const executeTransactionTry = async (prisma, arg, options) => {
45
+ try {
46
+ return {
47
+ // @ts-ignore
48
+ result: await prisma.$transaction(arg, options),
49
+ };
50
+ }
51
+ catch (error) {
52
+ return { error };
53
+ }
54
+ };
55
+ const calculateRetryDelay = (retries, baseRetryDelayMs, maxDelayMs) => {
56
+ // exponential backoff -> 2^(retry-1) * baseRetryDelayMs
57
+ const expDelay = Math.pow(2, retries - 1) * baseRetryDelayMs;
58
+ return Math.min(expDelay, maxDelayMs);
59
+ };
60
+ const PrismaCodesToRetry = [
61
+ PRISMA_SERIALIZATION_ERROR,
62
+ PRISMA_SERVER_CLOSED_CONNECTION_ERROR,
63
+ PRISMA_TRANSACTION_ERROR,
64
+ ];
65
+ const isRetryAllowed = (result, dbDriver) => {
66
+ if (isPrismaClientKnownRequestError(result.error)) {
67
+ const error = result.error;
68
+ // in case transaction is closed (timeout), retry increasing the timeout
69
+ // this should be the first check as the code error is PRISMA_TRANSACTION_ERROR covered also in the next check
70
+ if (isPrismaTransactionClosedError(error))
71
+ return 'increase-timeout';
72
+ // retry if the error code is in the list of codes to retry
73
+ if (PrismaCodesToRetry.includes(error.code))
74
+ return true;
75
+ // retry if the error is a CockroachDB retry transaction error
76
+ if (dbDriver === 'CockroachDb' && isCockroachDBRetryTransaction(error))
77
+ return true;
78
+ }
79
+ return false;
60
80
  };
61
- //# sourceMappingURL=prismaTransaction.js.map
package/dist/types.d.ts CHANGED
@@ -1,15 +1,14 @@
1
- import { Either } from '@lokalise/node-core';
2
- import { Prisma } from '@prisma/client';
3
- import { CockroachDbIsolationLevel } from './isolation_level/isolationLevel';
4
-
1
+ import type { Either } from '@lokalise/node-core';
2
+ import type { Prisma } from '@prisma/client';
5
3
  import type * as runtime from '@prisma/client/runtime/library';
4
+ import type { CockroachDbIsolationLevel } from './isolation_level/isolationLevel';
6
5
  type ObjectValues<T> = T[keyof T];
7
6
  export declare const DbDriverEnum: {
8
7
  readonly COCKROACHDB: "CockroachDb";
9
8
  };
10
9
  export type DbDriver = ObjectValues<typeof DbDriverEnum>;
11
10
  export type PrismaTransactionOptions = {
12
- DbDriver?: DbDriver;
11
+ dbDriver?: DbDriver;
13
12
  retriesAllowed?: number;
14
13
  baseRetryDelayMs?: number;
15
14
  maxRetryDelayMs?: number;
@@ -22,4 +21,24 @@ export type PrismaTransactionBasicOptions = Omit<PrismaTransactionOptions, 'maxW
22
21
  export type PrismaTransactionClient<P> = Omit<P, runtime.ITXClientDenyList>;
23
22
  export type PrismaTransactionFn<T, P> = (prisma: PrismaTransactionClient<P>) => Promise<T>;
24
23
  export type PrismaTransactionReturnType<T> = Either<unknown, T | runtime.Types.Utils.UnwrapTuple<Prisma.PrismaPromise<unknown>[]>>;
24
+ /**
25
+ * If we try to use `Omit<Prisma.PrismaClientOptions['transactionOptions']`, 'isolationLevel'> to override isolationLevel
26
+ * we start to get lint errors about maxWait and timeout not being part of the transactionOptions type.
27
+ *
28
+ * for that reason, and as this is a temporal solution in the meantime Prisma includes ReadCommitted as a valid isolation
29
+ * level for CockroachDB, we are using this type to override the transactionOptions which is basically a copy of
30
+ * Prisma.PrismaClientOptions['transactionOptions']
31
+ */
32
+ type PrismaClientTransactionOptions = {
33
+ isolationLevel?: CockroachDbIsolationLevel;
34
+ maxWait?: number;
35
+ timeout?: number;
36
+ };
37
+ /**
38
+ * this is a temporal solution in the meantime Prisma includes ReadCommitted as a valid isolation level for CockroachDB
39
+ */
40
+ export type PrismaClientFactoryOptions = Omit<Prisma.PrismaClientOptions, 'transactionOptions'> & {
41
+ dbDriver?: DbDriver;
42
+ transactionOptions?: PrismaClientTransactionOptions;
43
+ };
25
44
  export {};
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ export const DbDriverEnum = {
2
+ COCKROACHDB: 'CockroachDb',
3
+ };
package/package.json CHANGED
@@ -1,62 +1,60 @@
1
1
  {
2
- "name": "@lokalise/prisma-utils",
3
- "version": "1.6.0",
4
- "type": "module",
5
- "license": "Apache-2.0",
6
- "files": ["dist", "README.md", "LICENSE.md"],
7
- "main": "./dist/index.cjs",
8
- "module": "./dist/index.js",
9
- "types": "./dist/index.d.ts",
10
- "homepage": "https://github.com/lokalise/shared-ts-libs",
11
- "repository": {
12
- "type": "git",
13
- "url": "git://github.com/lokalise/shared-ts-libs.git"
14
- },
15
- "exports": {
16
- ".": {
17
- "types": "./dist/index.d.ts",
18
- "require": "./dist/index.cjs",
19
- "import": "./dist/index.js"
2
+ "name": "@lokalise/prisma-utils",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "files": ["dist", "README.md", "LICENSE.md"],
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "homepage": "https://github.com/lokalise/shared-ts-libs",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git://github.com/lokalise/shared-ts-libs.git"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "require": "./dist/index.cjs",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "rimraf dist && npm run db:update-client && tsc",
24
+ "dev": "vite watch",
25
+ "clean": "rimraf dist",
26
+ "lint": "biome check . && tsc --project tsconfig.lint.json --noEmit",
27
+ "lint:fix": "biome check --write",
28
+ "docker:start:ci": "docker compose up -d cockroachdb",
29
+ "db:migration:dev": "dotenv -c test -- dotenv prisma migrate dev",
30
+ "db:update-client": "dotenv -c test prisma generate",
31
+ "db:wait": "while ! echo \"SELECT 1;\" | dotenv -c test -- prisma db execute --stdin; do sleep 1; done",
32
+ "test": "vitest run",
33
+ "test:migrate": "cross-env NODE_ENV=test dotenv -c test -- prisma migrate reset --force",
34
+ "pretest:ci": "npm run docker:start:ci && npm run db:wait && npm run test:migrate",
35
+ "test:ci": "npm run test -- --coverage",
36
+ "test:ci:teardown": "docker compose down",
37
+ "prepublishOnly": "npm run build",
38
+ "package-version": "echo $npm_package_version",
39
+ "postversion": "biome check --write package.json"
40
+ },
41
+ "dependencies": {
42
+ "@lokalise/node-core": "^11.2.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@prisma/client": "^5.16.1",
46
+ "prisma": "^5.16.1"
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "^1.8.3",
50
+ "@lokalise/biome-config": "^1.3.0",
51
+ "@prisma/client": "^5.14.0",
52
+ "@vitest/coverage-v8": "^2.0.4",
53
+ "cross-env": "^7.0.3",
54
+ "dotenv-cli": "^7.4.1",
55
+ "prisma": "^5.14.0",
56
+ "rimraf": "^5.0.7",
57
+ "typescript": "5.5.4",
58
+ "vitest": "^2.0.4"
20
59
  }
21
- },
22
- "scripts": {
23
- "build": "rimraf dist && npm run db:update-client && vite build",
24
- "dev": "vite watch",
25
- "clean": "rimraf dist",
26
- "lint": "biome check . && tsc --project tsconfig.lint.json --noEmit",
27
- "lint:fix": "biome check --write",
28
- "docker:start:ci": "docker compose up -d cockroachdb",
29
- "db:migration:dev": "dotenv -c test -- dotenv prisma migrate dev",
30
- "db:update-client": "dotenv -c test prisma generate",
31
- "db:wait": "while ! echo \"SELECT 1;\" | dotenv -c test -- prisma db execute --stdin; do sleep 1; done",
32
- "test": "vitest run",
33
- "test:migrate": "cross-env NODE_ENV=test dotenv -c test -- prisma migrate reset --force",
34
- "pretest:ci": "npm run docker:start:ci && npm run db:wait && npm run test:migrate",
35
- "test:ci": "npm run test -- --coverage",
36
- "test:ci:teardown": "docker compose down",
37
- "prepublishOnly": "npm run build",
38
- "package-version": "echo $npm_package_version",
39
- "postversion": "biome check --write package.json"
40
- },
41
- "dependencies": {
42
- "@lokalise/node-core": "^11.2.0"
43
- },
44
- "peerDependencies": {
45
- "@prisma/client": "^5.16.1",
46
- "prisma": "^5.16.1"
47
- },
48
- "devDependencies": {
49
- "@biomejs/biome": "^1.8.3",
50
- "@lokalise/biome-config": "*",
51
- "@lokalise/package-vite-config": "*",
52
- "@prisma/client": "^5.14.0",
53
- "@vitest/coverage-v8": "^2.0.4",
54
- "cross-env": "^7.0.3",
55
- "dotenv-cli": "^7.4.1",
56
- "prisma": "^5.14.0",
57
- "rimraf": "^5.0.7",
58
- "typescript": "5.5.4",
59
- "vite": "5.3.5",
60
- "vitest": "^2.0.4"
61
- }
62
60
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"cockroachdbError.js","sources":["../../src/errors/cockroachdbError.ts"],"sourcesContent":["import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'\n\n/**\n * https://www.cockroachlabs.com/docs/stable/transaction-retry-error-reference#:~:text=To%20indicate%20that%20a%20transaction,the%20string%20%22restart%20transaction%22%20.\n *\n * All transaction retry errors use the SQLSTATE error code 40001\n */\nconst COCKROACHDB_RETRY_TRANSACTION_CODE = '40001'\n\n/**\n * Check if the error is a CockroachDB transaction retry error\n *\n * @param error\n */\nexport const isCockroachDBRetryTransaction = (error: PrismaClientKnownRequestError): boolean => {\n const meta = error.meta\n if (!meta) return false\n\n return meta.code === COCKROACHDB_RETRY_TRANSACTION_CODE\n}\n"],"names":["COCKROACHDB_RETRY_TRANSACTION_CODE","isCockroachDBRetryTransaction","error","meta"],"mappings":"AAOA,MAAMA,IAAqC,SAO9BC,IAAgC,CAACC,MAAkD;AAC9F,QAAMC,IAAOD,EAAM;AACf,SAACC,IAEEA,EAAK,SAASH,IAFH;AAGpB;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"prismaError.js","sources":["../../src/errors/prismaError.ts"],"sourcesContent":["import { isError } from '@lokalise/node-core'\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'\n\n/**\n * What is checked?\n * \t1. error is defined and not null\n * \t2. error is an `Error`\n * \t3. error contains the field code which is a string\n * \t4. code starts by `P` ([doc](https://www.prisma.io/docs/reference/api-reference/error-reference#error-codes))\n */\nexport const isPrismaClientKnownRequestError = (\n error: unknown,\n): error is PrismaClientKnownRequestError =>\n !!error &&\n isError(error) &&\n 'code' in error &&\n typeof error.code === 'string' &&\n error.code.startsWith('P')\n\nexport const isPrismaTransactionClosedError = (error: PrismaClientKnownRequestError): boolean =>\n error.code === PRISMA_TRANSACTION_ERROR &&\n error.message.toLowerCase().includes('transaction already closed')\n\n/**\n * Prisma error code P2025 indicates that the operation failed because it depends on one or more\n * records that were required but not found\n */\nexport const PRISMA_NOT_FOUND_ERROR = 'P2025'\n\n/**\n * Prisma error code P2034 indicates a serialization error and that the transaction must be retried.\n * A different error code would indicate that an internal state error happened and that\n * the cluster itself is experiencing an issue which requires intervention\n */\nexport const PRISMA_SERIALIZATION_ERROR = 'P2034'\n\n/**\n * Prisma error code P2002 indicates that the operation failed because a unique constraint was\n * violated. This can happen if you try to create a record with a unique field that already exists\n */\nexport const PRISMA_UNIQUE_CONSTRAINT_ERROR = 'P2002'\n\n/**\n * Prisma error code P2028 indicates a transaction API error, essentially a placeholder for errors that do not fit into\n * a more specific category. You should look into the error message for more details\n */\nexport const PRISMA_TRANSACTION_ERROR = 'P2028'\n\n/**\n * Prisma error code P1017 indicates that the connection to the database server was prematurely terminated by the server\n */\nexport const PRISMA_SERVER_CLOSED_CONNECTION_ERROR = 'P1017'\n"],"names":["isPrismaClientKnownRequestError","error","isError","isPrismaTransactionClosedError","PRISMA_TRANSACTION_ERROR","PRISMA_NOT_FOUND_ERROR","PRISMA_SERIALIZATION_ERROR","PRISMA_UNIQUE_CONSTRAINT_ERROR","PRISMA_SERVER_CLOSED_CONNECTION_ERROR"],"mappings":";AAUO,MAAMA,IAAkC,CAC7CC,MAEA,CAAC,CAACA,KACFC,EAAQD,CAAK,KACb,UAAUA,KACV,OAAOA,EAAM,QAAS,YACtBA,EAAM,KAAK,WAAW,GAAG,GAEdE,IAAiC,CAACF,MAC7CA,EAAM,SAASG,KACfH,EAAM,QAAQ,cAAc,SAAS,4BAA4B,GAMtDI,IAAyB,SAOzBC,IAA6B,SAM7BC,IAAiC,SAMjCH,IAA2B,SAK3BI,IAAwC;"}
package/dist/index.cjs DELETED
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("@lokalise/node-core"),_=require("node:timers/promises"),c=e=>!!e&&R.isError(e)&&"code"in e&&typeof e.code=="string"&&e.code.startsWith("P"),l=e=>e.code===a&&e.message.toLowerCase().includes("transaction already closed"),E="P2025",u="P2034",T="P2002",a="P2028",O="P1017",A="40001",m=e=>{const r=e.meta;return r?r.code===A:!1},C={retriesAllowed:2,DbDriver:"CockroachDb",baseRetryDelayMs:100,maxRetryDelayMs:3e4,timeout:5e3,maxTimeout:3e4},I=async(e,r,o)=>{let t={...C,...o},s,n=0;do{if(n>0&&await _.setTimeout(d(n,t.baseRetryDelayMs,t.maxRetryDelayMs)),s=await N(e,r,t),s.result)break;const i=y(s,t.DbDriver);if(!i)break;i==="increase-timeout"&&(t=R.deepClone(t),t.timeout=Math.min(t.timeout*2,t.maxTimeout)),n++}while(n<=t.retriesAllowed);return s??{error:new Error("No transaction executed")}},N=async(e,r,o)=>{try{return{result:await e.$transaction(r,o)}}catch(t){return{error:t}}},d=(e,r,o)=>{const t=Math.pow(2,e-1)*r;return Math.min(t,o)},S=[u,O,a],y=(e,r)=>{if(c(e.error)){const o=e.error;if(l(o))return"increase-timeout";if(S.includes(o.code)||r==="CockroachDb"&&m(o))return!0}return!1};exports.PRISMA_NOT_FOUND_ERROR=E;exports.PRISMA_SERIALIZATION_ERROR=u;exports.PRISMA_SERVER_CLOSED_CONNECTION_ERROR=O;exports.PRISMA_TRANSACTION_ERROR=a;exports.PRISMA_UNIQUE_CONSTRAINT_ERROR=T;exports.isPrismaClientKnownRequestError=c;exports.isPrismaTransactionClosedError=l;exports.prismaTransaction=I;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/errors/prismaError.ts","../src/errors/cockroachdbError.ts","../src/prismaTransaction.ts"],"sourcesContent":["import { isError } from '@lokalise/node-core'\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'\n\n/**\n * What is checked?\n * \t1. error is defined and not null\n * \t2. error is an `Error`\n * \t3. error contains the field code which is a string\n * \t4. code starts by `P` ([doc](https://www.prisma.io/docs/reference/api-reference/error-reference#error-codes))\n */\nexport const isPrismaClientKnownRequestError = (\n error: unknown,\n): error is PrismaClientKnownRequestError =>\n !!error &&\n isError(error) &&\n 'code' in error &&\n typeof error.code === 'string' &&\n error.code.startsWith('P')\n\nexport const isPrismaTransactionClosedError = (error: PrismaClientKnownRequestError): boolean =>\n error.code === PRISMA_TRANSACTION_ERROR &&\n error.message.toLowerCase().includes('transaction already closed')\n\n/**\n * Prisma error code P2025 indicates that the operation failed because it depends on one or more\n * records that were required but not found\n */\nexport const PRISMA_NOT_FOUND_ERROR = 'P2025'\n\n/**\n * Prisma error code P2034 indicates a serialization error and that the transaction must be retried.\n * A different error code would indicate that an internal state error happened and that\n * the cluster itself is experiencing an issue which requires intervention\n */\nexport const PRISMA_SERIALIZATION_ERROR = 'P2034'\n\n/**\n * Prisma error code P2002 indicates that the operation failed because a unique constraint was\n * violated. This can happen if you try to create a record with a unique field that already exists\n */\nexport const PRISMA_UNIQUE_CONSTRAINT_ERROR = 'P2002'\n\n/**\n * Prisma error code P2028 indicates a transaction API error, essentially a placeholder for errors that do not fit into\n * a more specific category. You should look into the error message for more details\n */\nexport const PRISMA_TRANSACTION_ERROR = 'P2028'\n\n/**\n * Prisma error code P1017 indicates that the connection to the database server was prematurely terminated by the server\n */\nexport const PRISMA_SERVER_CLOSED_CONNECTION_ERROR = 'P1017'\n","import type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'\n\n/**\n * https://www.cockroachlabs.com/docs/stable/transaction-retry-error-reference#:~:text=To%20indicate%20that%20a%20transaction,the%20string%20%22restart%20transaction%22%20.\n *\n * All transaction retry errors use the SQLSTATE error code 40001\n */\nconst COCKROACHDB_RETRY_TRANSACTION_CODE = '40001'\n\n/**\n * Check if the error is a CockroachDB transaction retry error\n *\n * @param error\n */\nexport const isCockroachDBRetryTransaction = (error: PrismaClientKnownRequestError): boolean => {\n const meta = error.meta\n if (!meta) return false\n\n return meta.code === COCKROACHDB_RETRY_TRANSACTION_CODE\n}\n","import { setTimeout } from 'node:timers/promises'\n\nimport type { Either } from '@lokalise/node-core'\nimport { deepClone } from '@lokalise/node-core'\nimport type { Prisma, PrismaClient } from '@prisma/client'\nimport type * as runtime from '@prisma/client/runtime/library'\n\nimport { isCockroachDBRetryTransaction } from './errors/cockroachdbError'\nimport {\n PRISMA_SERIALIZATION_ERROR,\n PRISMA_SERVER_CLOSED_CONNECTION_ERROR,\n PRISMA_TRANSACTION_ERROR,\n isPrismaClientKnownRequestError,\n isPrismaTransactionClosedError,\n} from './errors/prismaError'\nimport type {\n DbDriver,\n PrismaTransactionBasicOptions,\n PrismaTransactionFn,\n PrismaTransactionOptions,\n PrismaTransactionReturnType,\n} from './types'\n\nconst DEFAULT_OPTIONS = {\n retriesAllowed: 2, // first try + 2 retries = 3 tries\n DbDriver: 'CockroachDb',\n baseRetryDelayMs: 100,\n maxRetryDelayMs: 30000, // 30s\n timeout: 5000, // 5s\n maxTimeout: 30000, // 30s\n} satisfies Partial<PrismaTransactionOptions>\n\n/**\n * Perform a Prisma DB transaction with automatic retries if needed.\n *\n * @template T | T extends Prisma.PrismaPromise<unknown>[]\n * @param {PrismaClient} prisma\n * @param {PrismaTransactionFn<T> | Prisma.PrismaPromise<unknown>[]} arg\t operation to perform into the transaction\n * @param {PrismaTransactionOptions | PrismaTransactionBasicOptions} options transaction configuration\n * @return {Promise<PrismaTransactionReturnType<T>>}\n */\nexport const prismaTransaction = (async <T, P extends PrismaClient>(\n prisma: P,\n arg: PrismaTransactionFn<T, P> | Prisma.PrismaPromise<unknown>[],\n options?: PrismaTransactionOptions | PrismaTransactionBasicOptions,\n): Promise<PrismaTransactionReturnType<T>> => {\n let optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options }\n let result: PrismaTransactionReturnType<T> | undefined = undefined\n\n let retries = 0\n do {\n if (retries > 0) {\n await setTimeout(\n calculateRetryDelay(\n retries,\n optionsWithDefaults.baseRetryDelayMs,\n optionsWithDefaults.maxRetryDelayMs,\n ),\n )\n }\n\n result = await executeTransactionTry(prisma, arg, optionsWithDefaults)\n if (result.result) break\n\n const retryAllowed = isRetryAllowed(result, optionsWithDefaults.DbDriver)\n if (!retryAllowed) break\n\n if (retryAllowed === 'increase-timeout') {\n optionsWithDefaults = deepClone(optionsWithDefaults)\n optionsWithDefaults.timeout = Math.min(\n optionsWithDefaults.timeout * 2,\n optionsWithDefaults.maxTimeout,\n )\n }\n\n retries++\n } while (retries <= optionsWithDefaults.retriesAllowed)\n\n return result ?? { error: new Error('No transaction executed') }\n}) as {\n <T, P extends PrismaClient>(\n prisma: P,\n fn: PrismaTransactionFn<T, P>,\n options?: PrismaTransactionOptions,\n ): Promise<Either<unknown, T>>\n <T extends Prisma.PrismaPromise<unknown>[], P extends PrismaClient>(\n prisma: P,\n args: [...T],\n options?: PrismaTransactionBasicOptions,\n ): Promise<Either<unknown, runtime.Types.Utils.UnwrapTuple<T>>>\n}\n\nconst executeTransactionTry = async <T, P extends PrismaClient>(\n prisma: P,\n arg: PrismaTransactionFn<T, P> | Prisma.PrismaPromise<unknown>[],\n options?: PrismaTransactionOptions,\n): Promise<PrismaTransactionReturnType<T>> => {\n try {\n return {\n // @ts-ignore\n result: await prisma.$transaction<T>(arg, options),\n }\n } catch (error) {\n return { error }\n }\n}\n\nconst calculateRetryDelay = (\n retries: number,\n baseRetryDelayMs: number,\n maxDelayMs: number,\n): number => {\n // exponential backoff -> 2^(retry-1) * baseRetryDelayMs\n const expDelay = Math.pow(2, retries - 1) * baseRetryDelayMs\n return Math.min(expDelay, maxDelayMs)\n}\n\nconst PrismaCodesToRetry = [\n PRISMA_SERIALIZATION_ERROR,\n PRISMA_SERVER_CLOSED_CONNECTION_ERROR,\n PRISMA_TRANSACTION_ERROR,\n]\ntype isRetryAllowedResult = boolean | 'increase-timeout'\n\nconst isRetryAllowed = <T>(\n result: PrismaTransactionReturnType<T>,\n dbDriver: DbDriver,\n): isRetryAllowedResult => {\n if (isPrismaClientKnownRequestError(result.error)) {\n const error = result.error\n // in case transaction is closed (timeout), retry increasing the timeout\n // this should be the first check as the code error is PRISMA_TRANSACTION_ERROR covered also in the next check\n if (isPrismaTransactionClosedError(error)) return 'increase-timeout'\n // retry if the error code is in the list of codes to retry\n if (PrismaCodesToRetry.includes(error.code)) return true\n // retry if the error is a CockroachDB retry transaction error\n if (dbDriver === 'CockroachDb' && isCockroachDBRetryTransaction(error)) return true\n }\n\n return false\n}\n"],"names":["isPrismaClientKnownRequestError","error","isError","isPrismaTransactionClosedError","PRISMA_TRANSACTION_ERROR","PRISMA_NOT_FOUND_ERROR","PRISMA_SERIALIZATION_ERROR","PRISMA_UNIQUE_CONSTRAINT_ERROR","PRISMA_SERVER_CLOSED_CONNECTION_ERROR","COCKROACHDB_RETRY_TRANSACTION_CODE","isCockroachDBRetryTransaction","meta","DEFAULT_OPTIONS","prismaTransaction","prisma","arg","options","optionsWithDefaults","result","retries","setTimeout","calculateRetryDelay","executeTransactionTry","retryAllowed","isRetryAllowed","deepClone","baseRetryDelayMs","maxDelayMs","expDelay","PrismaCodesToRetry","dbDriver"],"mappings":"yJAUaA,EACXC,GAEA,CAAC,CAACA,GACFC,UAAQD,CAAK,GACb,SAAUA,GACV,OAAOA,EAAM,MAAS,UACtBA,EAAM,KAAK,WAAW,GAAG,EAEdE,EAAkCF,GAC7CA,EAAM,OAASG,GACfH,EAAM,QAAQ,cAAc,SAAS,4BAA4B,EAMtDI,EAAyB,QAOzBC,EAA6B,QAM7BC,EAAiC,QAMjCH,EAA2B,QAK3BI,EAAwC,QC5C/CC,EAAqC,QAO9BC,EAAiCT,GAAkD,CAC9F,MAAMU,EAAOV,EAAM,KACf,OAACU,EAEEA,EAAK,OAASF,EAFH,EAGpB,ECIMG,EAAkB,CACtB,eAAgB,EAChB,SAAU,cACV,iBAAkB,IAClB,gBAAiB,IACjB,QAAS,IACT,WAAY,GACd,EAWaC,EAAqB,MAChCC,EACAC,EACAC,IAC4C,CAC5C,IAAIC,EAAsB,CAAE,GAAGL,EAAiB,GAAGI,CAAQ,EACvDE,EAEAC,EAAU,EACX,EAAA,CAYD,GAXIA,EAAU,GACN,MAAAC,EAAA,WACJC,EACEF,EACAF,EAAoB,iBACpBA,EAAoB,eACtB,CAAA,EAIJC,EAAS,MAAMI,EAAsBR,EAAQC,EAAKE,CAAmB,EACjEC,EAAO,OAAQ,MAEnB,MAAMK,EAAeC,EAAeN,EAAQD,EAAoB,QAAQ,EACxE,GAAI,CAACM,EAAc,MAEfA,IAAiB,qBACnBN,EAAsBQ,EAAAA,UAAUR,CAAmB,EACnDA,EAAoB,QAAU,KAAK,IACjCA,EAAoB,QAAU,EAC9BA,EAAoB,UAAA,GAIxBE,GAAA,OACOA,GAAWF,EAAoB,gBAExC,OAAOC,GAAU,CAAE,MAAO,IAAI,MAAM,yBAAyB,CAAE,CACjE,EAaMI,EAAwB,MAC5BR,EACAC,EACAC,IAC4C,CACxC,GAAA,CACK,MAAA,CAEL,OAAQ,MAAMF,EAAO,aAAgBC,EAAKC,CAAO,CAAA,QAE5Cf,EAAO,CACd,MAAO,CAAE,MAAAA,CAAM,CACjB,CACF,EAEMoB,EAAsB,CAC1BF,EACAO,EACAC,IACW,CAEX,MAAMC,EAAW,KAAK,IAAI,EAAGT,EAAU,CAAC,EAAIO,EACrC,OAAA,KAAK,IAAIE,EAAUD,CAAU,CACtC,EAEME,EAAqB,CACzBvB,EACAE,EACAJ,CACF,EAGMoB,EAAiB,CACrBN,EACAY,IACyB,CACrB,GAAA9B,EAAgCkB,EAAO,KAAK,EAAG,CACjD,MAAMjB,EAAQiB,EAAO,MAGjB,GAAAf,EAA+BF,CAAK,EAAU,MAAA,mBAIlD,GAFI4B,EAAmB,SAAS5B,EAAM,IAAI,GAEtC6B,IAAa,eAAiBpB,EAA8BT,CAAK,EAAU,MAAA,EACjF,CAEO,MAAA,EACT"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"prismaTransaction.js","sources":["../src/prismaTransaction.ts"],"sourcesContent":["import { setTimeout } from 'node:timers/promises'\n\nimport type { Either } from '@lokalise/node-core'\nimport { deepClone } from '@lokalise/node-core'\nimport type { Prisma, PrismaClient } from '@prisma/client'\nimport type * as runtime from '@prisma/client/runtime/library'\n\nimport { isCockroachDBRetryTransaction } from './errors/cockroachdbError'\nimport {\n PRISMA_SERIALIZATION_ERROR,\n PRISMA_SERVER_CLOSED_CONNECTION_ERROR,\n PRISMA_TRANSACTION_ERROR,\n isPrismaClientKnownRequestError,\n isPrismaTransactionClosedError,\n} from './errors/prismaError'\nimport type {\n DbDriver,\n PrismaTransactionBasicOptions,\n PrismaTransactionFn,\n PrismaTransactionOptions,\n PrismaTransactionReturnType,\n} from './types'\n\nconst DEFAULT_OPTIONS = {\n retriesAllowed: 2, // first try + 2 retries = 3 tries\n DbDriver: 'CockroachDb',\n baseRetryDelayMs: 100,\n maxRetryDelayMs: 30000, // 30s\n timeout: 5000, // 5s\n maxTimeout: 30000, // 30s\n} satisfies Partial<PrismaTransactionOptions>\n\n/**\n * Perform a Prisma DB transaction with automatic retries if needed.\n *\n * @template T | T extends Prisma.PrismaPromise<unknown>[]\n * @param {PrismaClient} prisma\n * @param {PrismaTransactionFn<T> | Prisma.PrismaPromise<unknown>[]} arg\t operation to perform into the transaction\n * @param {PrismaTransactionOptions | PrismaTransactionBasicOptions} options transaction configuration\n * @return {Promise<PrismaTransactionReturnType<T>>}\n */\nexport const prismaTransaction = (async <T, P extends PrismaClient>(\n prisma: P,\n arg: PrismaTransactionFn<T, P> | Prisma.PrismaPromise<unknown>[],\n options?: PrismaTransactionOptions | PrismaTransactionBasicOptions,\n): Promise<PrismaTransactionReturnType<T>> => {\n let optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options }\n let result: PrismaTransactionReturnType<T> | undefined = undefined\n\n let retries = 0\n do {\n if (retries > 0) {\n await setTimeout(\n calculateRetryDelay(\n retries,\n optionsWithDefaults.baseRetryDelayMs,\n optionsWithDefaults.maxRetryDelayMs,\n ),\n )\n }\n\n result = await executeTransactionTry(prisma, arg, optionsWithDefaults)\n if (result.result) break\n\n const retryAllowed = isRetryAllowed(result, optionsWithDefaults.DbDriver)\n if (!retryAllowed) break\n\n if (retryAllowed === 'increase-timeout') {\n optionsWithDefaults = deepClone(optionsWithDefaults)\n optionsWithDefaults.timeout = Math.min(\n optionsWithDefaults.timeout * 2,\n optionsWithDefaults.maxTimeout,\n )\n }\n\n retries++\n } while (retries <= optionsWithDefaults.retriesAllowed)\n\n return result ?? { error: new Error('No transaction executed') }\n}) as {\n <T, P extends PrismaClient>(\n prisma: P,\n fn: PrismaTransactionFn<T, P>,\n options?: PrismaTransactionOptions,\n ): Promise<Either<unknown, T>>\n <T extends Prisma.PrismaPromise<unknown>[], P extends PrismaClient>(\n prisma: P,\n args: [...T],\n options?: PrismaTransactionBasicOptions,\n ): Promise<Either<unknown, runtime.Types.Utils.UnwrapTuple<T>>>\n}\n\nconst executeTransactionTry = async <T, P extends PrismaClient>(\n prisma: P,\n arg: PrismaTransactionFn<T, P> | Prisma.PrismaPromise<unknown>[],\n options?: PrismaTransactionOptions,\n): Promise<PrismaTransactionReturnType<T>> => {\n try {\n return {\n // @ts-ignore\n result: await prisma.$transaction<T>(arg, options),\n }\n } catch (error) {\n return { error }\n }\n}\n\nconst calculateRetryDelay = (\n retries: number,\n baseRetryDelayMs: number,\n maxDelayMs: number,\n): number => {\n // exponential backoff -> 2^(retry-1) * baseRetryDelayMs\n const expDelay = Math.pow(2, retries - 1) * baseRetryDelayMs\n return Math.min(expDelay, maxDelayMs)\n}\n\nconst PrismaCodesToRetry = [\n PRISMA_SERIALIZATION_ERROR,\n PRISMA_SERVER_CLOSED_CONNECTION_ERROR,\n PRISMA_TRANSACTION_ERROR,\n]\ntype isRetryAllowedResult = boolean | 'increase-timeout'\n\nconst isRetryAllowed = <T>(\n result: PrismaTransactionReturnType<T>,\n dbDriver: DbDriver,\n): isRetryAllowedResult => {\n if (isPrismaClientKnownRequestError(result.error)) {\n const error = result.error\n // in case transaction is closed (timeout), retry increasing the timeout\n // this should be the first check as the code error is PRISMA_TRANSACTION_ERROR covered also in the next check\n if (isPrismaTransactionClosedError(error)) return 'increase-timeout'\n // retry if the error code is in the list of codes to retry\n if (PrismaCodesToRetry.includes(error.code)) return true\n // retry if the error is a CockroachDB retry transaction error\n if (dbDriver === 'CockroachDb' && isCockroachDBRetryTransaction(error)) return true\n }\n\n return false\n}\n"],"names":["DEFAULT_OPTIONS","prismaTransaction","prisma","arg","options","optionsWithDefaults","result","retries","setTimeout","calculateRetryDelay","executeTransactionTry","retryAllowed","isRetryAllowed","deepClone","error","baseRetryDelayMs","maxDelayMs","expDelay","PrismaCodesToRetry","PRISMA_SERIALIZATION_ERROR","PRISMA_SERVER_CLOSED_CONNECTION_ERROR","PRISMA_TRANSACTION_ERROR","dbDriver","isPrismaClientKnownRequestError","isPrismaTransactionClosedError","isCockroachDBRetryTransaction"],"mappings":";;;;AAuBA,MAAMA,IAAkB;AAAA,EACtB,gBAAgB;AAAA;AAAA,EAChB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,iBAAiB;AAAA;AAAA,EACjB,SAAS;AAAA;AAAA,EACT,YAAY;AAAA;AACd,GAWaC,IAAqB,OAChCC,GACAC,GACAC,MAC4C;AAC5C,MAAIC,IAAsB,EAAE,GAAGL,GAAiB,GAAGI,EAAQ,GACvDE,GAEAC,IAAU;AACX,KAAA;AAYD,QAXIA,IAAU,KACN,MAAAC;AAAA,MACJC;AAAA,QACEF;AAAA,QACAF,EAAoB;AAAA,QACpBA,EAAoB;AAAA,MACtB;AAAA,IAAA,GAIJC,IAAS,MAAMI,EAAsBR,GAAQC,GAAKE,CAAmB,GACjEC,EAAO,OAAQ;AAEnB,UAAMK,IAAeC,EAAeN,GAAQD,EAAoB,QAAQ;AACxE,QAAI,CAACM,EAAc;AAEnB,IAAIA,MAAiB,uBACnBN,IAAsBQ,EAAUR,CAAmB,GACnDA,EAAoB,UAAU,KAAK;AAAA,MACjCA,EAAoB,UAAU;AAAA,MAC9BA,EAAoB;AAAA,IAAA,IAIxBE;AAAA,EAAA,SACOA,KAAWF,EAAoB;AAExC,SAAOC,KAAU,EAAE,OAAO,IAAI,MAAM,yBAAyB,EAAE;AACjE,GAaMI,IAAwB,OAC5BR,GACAC,GACAC,MAC4C;AACxC,MAAA;AACK,WAAA;AAAA;AAAA,MAEL,QAAQ,MAAMF,EAAO,aAAgBC,GAAKC,CAAO;AAAA,IAAA;AAAA,WAE5CU,GAAO;AACd,WAAO,EAAE,OAAAA,EAAM;AAAA,EACjB;AACF,GAEML,IAAsB,CAC1BF,GACAQ,GACAC,MACW;AAEX,QAAMC,IAAW,KAAK,IAAI,GAAGV,IAAU,CAAC,IAAIQ;AACrC,SAAA,KAAK,IAAIE,GAAUD,CAAU;AACtC,GAEME,IAAqB;AAAA,EACzBC;AAAA,EACAC;AAAA,EACAC;AACF,GAGMT,IAAiB,CACrBN,GACAgB,MACyB;AACrB,MAAAC,EAAgCjB,EAAO,KAAK,GAAG;AACjD,UAAMQ,IAAQR,EAAO;AAGjB,QAAAkB,EAA+BV,CAAK,EAAU,QAAA;AAIlD,QAFII,EAAmB,SAASJ,EAAM,IAAI,KAEtCQ,MAAa,iBAAiBG,EAA8BX,CAAK,EAAU,QAAA;AAAA,EACjF;AAEO,SAAA;AACT;"}