@happyvertical/sql 0.80.3 → 0.80.4
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/dist/index.js +96 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1963,6 +1963,9 @@ async function createDatabase(options, cacheKey) {
|
|
|
1963
1963
|
database,
|
|
1964
1964
|
max: poolMax
|
|
1965
1965
|
});
|
|
1966
|
+
pool.on("error", (error) => {
|
|
1967
|
+
console.warn("Warning: PostgreSQL pool client error (connection discarded):", error instanceof Error ? error.message : String(error));
|
|
1968
|
+
});
|
|
1966
1969
|
const originalEnd = pool.end.bind(pool);
|
|
1967
1970
|
const evictFromCache = () => {
|
|
1968
1971
|
for (const [key, cached] of connectionCache.entries()) if (cached.client === pool) connectionCache.delete(key);
|
|
@@ -2076,18 +2079,83 @@ async function createDatabase(options, cacheKey) {
|
|
|
2076
2079
|
affected: (await executor.query(insertSql, values)).rowCount ?? 0
|
|
2077
2080
|
};
|
|
2078
2081
|
};
|
|
2079
|
-
|
|
2082
|
+
/**
|
|
2083
|
+
* Absorbs 'error' events for the clients currently checked out for a
|
|
2084
|
+
* transaction, keyed by client so the listener can be removed on release.
|
|
2085
|
+
*/
|
|
2086
|
+
const txErrorAbsorbers = /* @__PURE__ */ new WeakMap();
|
|
2087
|
+
const releasedTxClients = /* @__PURE__ */ new WeakSet();
|
|
2088
|
+
/**
|
|
2089
|
+
* Checks out a pooled client for a transaction.
|
|
2090
|
+
*
|
|
2091
|
+
* pg-pool drops its own idle 'error' listener while a client is checked out,
|
|
2092
|
+
* so a backend that dies mid-transaction would emit an unhandled 'error' and
|
|
2093
|
+
* terminate the process. Absorb it here — the failure still reaches the
|
|
2094
|
+
* caller as a rejected query — and drop the listener again on release so
|
|
2095
|
+
* repeated checkouts of the same client cannot accumulate them.
|
|
2096
|
+
*/
|
|
2097
|
+
const acquireTxClient = async () => {
|
|
2080
2098
|
const txClient = await client.connect();
|
|
2099
|
+
releasedTxClients.delete(txClient);
|
|
2100
|
+
const absorb = () => {};
|
|
2101
|
+
txClient.on("error", absorb);
|
|
2102
|
+
txErrorAbsorbers.set(txClient, absorb);
|
|
2103
|
+
return txClient;
|
|
2104
|
+
};
|
|
2105
|
+
/**
|
|
2106
|
+
* Returns a transaction client to the pool exactly once.
|
|
2107
|
+
*
|
|
2108
|
+
* Every teardown path must run this, including the ones reached by a throwing
|
|
2109
|
+
* COMMIT or ROLLBACK — a client that is never released stays checked out for
|
|
2110
|
+
* the life of the process, and enough of them exhaust the pool. Releasing
|
|
2111
|
+
* twice throws, so the released clients are tracked rather than assumed.
|
|
2112
|
+
*
|
|
2113
|
+
* @param txClient - The pooled client backing a transaction
|
|
2114
|
+
* @param destroy - Destroy the connection instead of reusing it, for when the
|
|
2115
|
+
* transaction state could not be cleaned up
|
|
2116
|
+
*/
|
|
2117
|
+
const releaseTxClient = (txClient, destroy = false) => {
|
|
2118
|
+
if (releasedTxClients.has(txClient)) return;
|
|
2119
|
+
releasedTxClients.add(txClient);
|
|
2120
|
+
const absorb = txErrorAbsorbers.get(txClient);
|
|
2121
|
+
if (absorb) {
|
|
2122
|
+
txClient.off("error", absorb);
|
|
2123
|
+
txErrorAbsorbers.delete(txClient);
|
|
2124
|
+
}
|
|
2125
|
+
txClient.release(destroy || void 0);
|
|
2126
|
+
};
|
|
2127
|
+
/**
|
|
2128
|
+
* Rolls back a failed transaction and returns its client to the pool.
|
|
2129
|
+
*
|
|
2130
|
+
* The connection may still be inside a transaction, so ROLLBACK is attempted
|
|
2131
|
+
* to normalize it. If that works the connection is clean and worth reusing;
|
|
2132
|
+
* if it does not, the state is unknown and the connection is destroyed rather
|
|
2133
|
+
* than handed to unrelated code later.
|
|
2134
|
+
*
|
|
2135
|
+
* @returns The rollback failure, if the connection could not be normalized
|
|
2136
|
+
*/
|
|
2137
|
+
const discardTxClient = async (txClient) => {
|
|
2138
|
+
try {
|
|
2139
|
+
await txClient.query("ROLLBACK");
|
|
2140
|
+
releaseTxClient(txClient);
|
|
2141
|
+
return;
|
|
2142
|
+
} catch (rollbackError) {
|
|
2143
|
+
releaseTxClient(txClient, true);
|
|
2144
|
+
return rollbackError;
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
const executeNullAwarePostgresUpsertInTransaction = async (table, conflictColumns, serializedData) => {
|
|
2148
|
+
const txClient = await acquireTxClient();
|
|
2081
2149
|
try {
|
|
2082
2150
|
await txClient.query("BEGIN");
|
|
2083
2151
|
const result = await executeNullAwarePostgresUpsert(txClient, table, conflictColumns, serializedData);
|
|
2084
2152
|
await txClient.query("COMMIT");
|
|
2153
|
+
releaseTxClient(txClient);
|
|
2085
2154
|
return result;
|
|
2086
2155
|
} catch (error) {
|
|
2087
|
-
await txClient
|
|
2156
|
+
const rollbackError = await discardTxClient(txClient);
|
|
2157
|
+
if (rollbackError !== void 0 && error instanceof Error) error.cause ??= rollbackError;
|
|
2088
2158
|
throw error;
|
|
2089
|
-
} finally {
|
|
2090
|
-
txClient.release();
|
|
2091
2159
|
}
|
|
2092
2160
|
};
|
|
2093
2161
|
const executePostgresUpsert = async (executor, table, conflictColumns, data, options, acquireTransaction) => {
|
|
@@ -2583,7 +2651,7 @@ async function createDatabase(options, cacheKey) {
|
|
|
2583
2651
|
* @returns Promise resolving to callback result
|
|
2584
2652
|
*/
|
|
2585
2653
|
const transaction = async (callback) => {
|
|
2586
|
-
const txClient = await
|
|
2654
|
+
const txClient = await acquireTxClient();
|
|
2587
2655
|
try {
|
|
2588
2656
|
await txClient.query("BEGIN");
|
|
2589
2657
|
const txDb = {
|
|
@@ -2719,12 +2787,12 @@ async function createDatabase(options, cacheKey) {
|
|
|
2719
2787
|
};
|
|
2720
2788
|
const result = await callback(txDb);
|
|
2721
2789
|
await txClient.query("COMMIT");
|
|
2790
|
+
releaseTxClient(txClient);
|
|
2722
2791
|
return result;
|
|
2723
2792
|
} catch (error) {
|
|
2724
|
-
await txClient
|
|
2793
|
+
const rollbackError = await discardTxClient(txClient);
|
|
2794
|
+
if (rollbackError !== void 0 && error instanceof Error) error.cause ??= rollbackError;
|
|
2725
2795
|
throw error;
|
|
2726
|
-
} finally {
|
|
2727
|
-
txClient.release();
|
|
2728
2796
|
}
|
|
2729
2797
|
};
|
|
2730
2798
|
/**
|
|
@@ -2736,21 +2804,29 @@ async function createDatabase(options, cacheKey) {
|
|
|
2736
2804
|
* @returns Promise resolving to a TransactionHandle
|
|
2737
2805
|
*/
|
|
2738
2806
|
const beginTransaction = async () => {
|
|
2739
|
-
const txClient = await
|
|
2740
|
-
|
|
2807
|
+
const txClient = await acquireTxClient();
|
|
2808
|
+
try {
|
|
2809
|
+
await txClient.query("BEGIN");
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
releaseTxClient(txClient, true);
|
|
2812
|
+
throw error;
|
|
2813
|
+
}
|
|
2741
2814
|
let active = true;
|
|
2742
|
-
const
|
|
2815
|
+
const end = async (command) => {
|
|
2743
2816
|
if (!active) throw new DatabaseError("Transaction already ended", {});
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2817
|
+
try {
|
|
2818
|
+
await txClient.query(command);
|
|
2819
|
+
active = false;
|
|
2820
|
+
releaseTxClient(txClient);
|
|
2821
|
+
} catch (error) {
|
|
2822
|
+
active = false;
|
|
2823
|
+
const rollbackError = await discardTxClient(txClient);
|
|
2824
|
+
if (rollbackError !== void 0 && error instanceof Error) error.cause ??= rollbackError;
|
|
2825
|
+
throw error;
|
|
2826
|
+
}
|
|
2753
2827
|
};
|
|
2828
|
+
const commit = () => end("COMMIT");
|
|
2829
|
+
const rollback = () => end("ROLLBACK");
|
|
2754
2830
|
const isActive = () => active;
|
|
2755
2831
|
const txHandle = {
|
|
2756
2832
|
url,
|