@effect/sql-pg 4.0.0-rc.116 → 4.0.0-rc.118

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.
Files changed (63) hide show
  1. package/AGENTS.md +4 -4
  2. package/CLAUDE.md +4 -4
  3. package/ai-docs/package.json +3 -3
  4. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
  5. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +1 -1
  6. package/ai-docs/src/03_stream/30_encoding.ts +1 -1
  7. package/ai-docs/src/08_observability/20_otlp-tracing.ts +2 -2
  8. package/ai-docs/src/08_observability/index.md +1 -1
  9. package/ai-docs/src/40_sql/10_basics.ts +2 -2
  10. package/ai-docs/src/40_sql/index.md +1 -1
  11. package/ai-docs/src/50_http-client/10_basics.ts +1 -1
  12. package/ai-docs/src/51_http-server/10_basics.ts +2 -2
  13. package/ai-docs/src/51_http-server/20_testing.ts +2 -2
  14. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +1 -1
  15. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +1 -1
  16. package/ai-docs/src/51_http-server/fixtures/api/System.ts +1 -1
  17. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +1 -1
  18. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +1 -1
  19. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +1 -1
  20. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +1 -1
  21. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +1 -1
  22. package/ai-docs/src/60_child-process/index.md +1 -1
  23. package/ai-docs/src/70_cli/10_basics.ts +1 -1
  24. package/ai-docs/src/70_cli/index.md +1 -1
  25. package/ai-docs/src/71_ai/10_language-model.ts +2 -2
  26. package/ai-docs/src/71_ai/20_tools.ts +2 -2
  27. package/ai-docs/src/71_ai/30_chat.ts +2 -2
  28. package/ai-docs/src/80_cluster/10_entities.ts +3 -3
  29. package/dist/PgAuth.js +3 -3
  30. package/dist/PgAuth.js.map +1 -1
  31. package/dist/PgClient.d.ts +5 -5
  32. package/dist/PgClient.d.ts.map +1 -1
  33. package/dist/PgClient.js +14 -3
  34. package/dist/PgClient.js.map +1 -1
  35. package/dist/PgConnection.d.ts +20 -6
  36. package/dist/PgConnection.d.ts.map +1 -1
  37. package/dist/PgConnection.js +48 -29
  38. package/dist/PgConnection.js.map +1 -1
  39. package/dist/PgMigrator.d.ts +5 -5
  40. package/dist/PgMigrator.d.ts.map +1 -1
  41. package/dist/PgMigrator.js +4 -4
  42. package/dist/PgMigrator.js.map +1 -1
  43. package/dist/PgPool.d.ts +1 -1
  44. package/dist/PgPool.d.ts.map +1 -1
  45. package/dist/PgPool.js +4 -7
  46. package/dist/PgPool.js.map +1 -1
  47. package/dist/PgTypes.d.ts +2 -0
  48. package/dist/PgTypes.d.ts.map +1 -1
  49. package/dist/PgTypes.js +19 -16
  50. package/dist/PgTypes.js.map +1 -1
  51. package/dist/internal/sqlError.d.ts +1 -1
  52. package/dist/internal/sqlError.d.ts.map +1 -1
  53. package/dist/internal/sqlError.js +1 -1
  54. package/dist/internal/sqlError.js.map +1 -1
  55. package/package.json +4 -4
  56. package/src/PgAuth.ts +3 -3
  57. package/src/PgClient.ts +26 -6
  58. package/src/PgConnection.ts +80 -52
  59. package/src/PgMigrator.ts +6 -6
  60. package/src/PgPool.ts +5 -8
  61. package/src/PgTypes.ts +25 -22
  62. package/src/internal/connection.ts +3 -3
  63. package/src/internal/sqlError.ts +1 -1
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Native PostgreSQL sessions built on the `PgProtocol` wire codec.
3
3
  *
4
+ * **Details**
5
+ *
4
6
  * Sessions support queries, streaming, `LISTEN`/`NOTIFY`, cancellation, and
5
7
  * exclusive ownership for transactions.
6
8
  *
@@ -19,14 +21,8 @@ import * as Redacted from "effect/Redacted"
19
21
  import * as EffectResult from "effect/Result"
20
22
  import * as Scope from "effect/Scope"
21
23
  import * as Semaphore from "effect/Semaphore"
24
+ import { AuthenticationError, ConnectionError, SqlError, type SqlErrorReason, UnknownError } from "effect/sql/SqlError"
22
25
  import * as Stream from "effect/Stream"
23
- import {
24
- AuthenticationError,
25
- ConnectionError,
26
- SqlError,
27
- type SqlErrorReason,
28
- UnknownError
29
- } from "effect/unstable/sql/SqlError"
30
26
  import { Buffer } from "node:buffer"
31
27
  import { randomBytes } from "node:crypto"
32
28
  import * as Net from "node:net"
@@ -199,13 +195,28 @@ export interface PgConnection {
199
195
  * ownership queue, while calls through the original connection wait.
200
196
  */
201
197
  readonly pin: Effect.Effect<PgConnection, never, Scope.Scope>
202
- /** Runs a query and returns rows keyed by column name. Pass `false` to skip the prepared statement cache. */
198
+ /**
199
+ * Runs a query and returns rows keyed by column name. Pass `false` to skip
200
+ * the prepared statement cache.
201
+ *
202
+ * **Details**
203
+ *
204
+ * On interruption, the connection drains to `ReadyForQuery` and sends a
205
+ * `CancelRequest` if needed. Unless the backend confirms cancellation with
206
+ * `57014`, a pool retires the session before its next checkout. A retained
207
+ * checkout and an unpooled session remain exposed to a late cancel.
208
+ * `statement_timeout` also reports `57014` and can be mistaken for
209
+ * confirmation.
210
+ */
203
211
  readonly query: (
204
212
  sql: string,
205
213
  params?: ReadonlyArray<unknown>,
206
214
  prepare?: boolean
207
215
  ) => Effect.Effect<Result, SqlError>
208
- /** Runs a query and returns positional rows. Pass `false` to skip the prepared statement cache. */
216
+ /**
217
+ * Runs a query and returns positional rows. Pass `false` to skip the
218
+ * prepared statement cache. Interruption behaves as for `query`.
219
+ */
209
220
  readonly queryValues: (
210
221
  sql: string,
211
222
  params?: ReadonlyArray<unknown>,
@@ -213,9 +224,8 @@ export interface PgConnection {
213
224
  ) => Effect.Effect<ReadonlyArray<ReadonlyArray<unknown>>, SqlError>
214
225
  /**
215
226
  * Streams rows without collecting the full result. The session is pinned for
216
- * the lifetime of the stream. Aborting the stream
217
- * before the result completes cancels the statement with a `CancelRequest`
218
- * and drains the connection back to `ReadyForQuery`.
227
+ * the lifetime of the stream. An early abort behaves like an interrupted
228
+ * `query`.
219
229
  */
220
230
  readonly stream: (
221
231
  sql: string,
@@ -317,6 +327,9 @@ interface PipelineEntry {
317
327
  }
318
328
 
319
329
  const abortDrainTimeoutMillis = 5000
330
+ const abortDrainGraceMillis = 10
331
+ /** PostgreSQL `query_canceled` SQLSTATE. */
332
+ const queryCanceledCode = "57014"
320
333
  const cancelRequestTimeoutMillis = 5000
321
334
  /** How many statements a multiplexed session keeps on the wire at once. */
322
335
  const maxPipelineDepth = 128
@@ -342,8 +355,9 @@ class PgConnectionImpl implements PgConnection {
342
355
  consumer: Consumer | undefined
343
356
  deadWith: SqlError | undefined
344
357
  closed = false
358
+ cancelPending = false
345
359
  readonly channels = new Map<string, Set<Queue.Queue<Notification, SqlError>>>()
346
- readonly fatalHooks = new Set<() => void>()
360
+ readonly retireHooks = new Set<() => void>()
347
361
  /** Queued but not yet written; drained into `pipelineInFlight` on flush. */
348
362
  readonly pipelinePending: Array<PipelineEntry> = []
349
363
  /** Written and awaiting their `ReadyForQuery`, oldest first. */
@@ -367,7 +381,7 @@ class PgConnectionImpl implements PgConnection {
367
381
  this[internalsKey] = {
368
382
  base: this,
369
383
  deadError: () => this.deadWith,
370
- fatalHooks: this.fatalHooks
384
+ retireHooks: this.retireHooks
371
385
  }
372
386
  this.pinnedView = new PinnedPgConnection(this)
373
387
  session.socket.on("data", this.onData)
@@ -399,6 +413,15 @@ class PgConnectionImpl implements PgConnection {
399
413
  private readonly dispatch = (message: PgProtocol.BackendMessage<unknown>): void => {
400
414
  if (this.deadWith !== undefined) return
401
415
  switch (message._tag) {
416
+ case "ErrorResponse":
417
+ if (message.fields.code === queryCanceledCode) this.cancelPending = false
418
+ break
419
+ case "ReadyForQuery":
420
+ if (this.cancelPending) {
421
+ this.cancelPending = false
422
+ this.retire()
423
+ }
424
+ break
402
425
  case "NotificationResponse": {
403
426
  const queues = this.channels.get(message.channel)
404
427
  if (queues !== undefined) {
@@ -440,6 +463,7 @@ class PgConnectionImpl implements PgConnection {
440
463
  if (destroySocket) this.session.socket.destroy()
441
464
  const consumer = this.consumer
442
465
  this.consumer = undefined
466
+ this.retire()
443
467
  consumer?.onFatal(error)
444
468
  const sets = Array.from(this.channels.values())
445
469
  this.channels.clear()
@@ -447,9 +471,11 @@ class PgConnectionImpl implements PgConnection {
447
471
  for (const set of sets) {
448
472
  for (const queue of set) Queue.failCauseUnsafe(queue, cause)
449
473
  }
450
- if (!this.closed) {
451
- for (const hook of this.fatalHooks) hook()
452
- }
474
+ }
475
+
476
+ private retire(): void {
477
+ if (this.closed) return
478
+ for (const hook of this.retireHooks) hook()
453
479
  }
454
480
 
455
481
  closeUnsafe(): void {
@@ -695,6 +721,8 @@ class PgConnectionImpl implements PgConnection {
695
721
  /** Sends a `CancelRequest` for this session on a side connection. */
696
722
  readonly cancel: Effect.Effect<void> = Effect.suspend(() => {
697
723
  if (this.deadWith !== undefined) return Effect.void
724
+ this.cancelPending = true
725
+ if (this.consumer === undefined) this.retire()
698
726
  return sendCancelRequest(this.resolved, this.session)
699
727
  })
700
728
 
@@ -1077,6 +1105,7 @@ const encodeQuery = (
1077
1105
  class PreparedCache {
1078
1106
  readonly max: number
1079
1107
  private readonly statements = new Map<string, Prepared>()
1108
+ private readonly namespace = randomBytes(8).toString("hex")
1080
1109
  private closes: Array<Uint8Array> | undefined
1081
1110
  private counter = 0
1082
1111
 
@@ -1096,7 +1125,7 @@ class PreparedCache {
1096
1125
  return found
1097
1126
  }
1098
1127
  const prepared: Prepared = {
1099
- name: `effect${++this.counter}`,
1128
+ name: `effect_${this.namespace}_${++this.counter}`,
1100
1129
  key,
1101
1130
  ready: false,
1102
1131
  parsing: false,
@@ -1457,6 +1486,32 @@ class QueryMachine implements Consumer {
1457
1486
  const emptyRows: ReadonlyArray<Row> = []
1458
1487
  const emptyValues: ReadonlyArray<ReadonlyArray<unknown>> = []
1459
1488
 
1489
+ /** Drains an aborted statement, sending a cancel after a short grace period and failing if it stalls. */
1490
+ const drainAborted = (
1491
+ conn: PgConnectionImpl,
1492
+ timeoutMessage: string,
1493
+ abort: (onDrained: () => void) => void
1494
+ ): Effect.Effect<void> => {
1495
+ const drained = Deferred.makeUnsafe<void>()
1496
+ const timeout = setTimeout(
1497
+ () => conn.fatal(connectionQueryError(new Error(timeoutMessage), `PgConnection: ${timeoutMessage}`)),
1498
+ abortDrainTimeoutMillis
1499
+ )
1500
+ abort(() => {
1501
+ clearTimeout(timeout)
1502
+ Deferred.doneUnsafe(drained, Effect.void)
1503
+ })
1504
+ const grace = Effect.callback<void>((resume) => {
1505
+ const timer = setTimeout(() => resume(Effect.void), abortDrainGraceMillis)
1506
+ return Effect.sync(() => clearTimeout(timer))
1507
+ })
1508
+ const cancelAndDrain = Effect.andThen(conn.cancel, Deferred.await(drained))
1509
+ return Effect.andThen(
1510
+ Effect.raceFirst(Deferred.await(drained), grace),
1511
+ Effect.suspend(() => Deferred.isDoneUnsafe(drained) ? Effect.void : cancelAndDrain)
1512
+ )
1513
+ }
1514
+
1460
1515
  /** One cycle on a connection that carries a single statement at a time. */
1461
1516
  const runQuery = (
1462
1517
  conn: PgConnectionImpl,
@@ -1482,26 +1537,13 @@ const runQuery = (
1482
1537
  conn.fatal(connectionQueryError(cause, "PgConnection: Failed to write query"))
1483
1538
  }
1484
1539
 
1485
- // On interruption: cancel the statement and drain the connection back to
1486
- // ReadyForQuery so it stays usable, destroying it when the drain stalls.
1487
1540
  return Effect.suspend(() => {
1488
1541
  if (machine.isDone()) return Effect.void
1489
- const wait = Effect.callback<void>((resumeWait) => {
1490
- const timer = setTimeout(
1491
- () =>
1492
- conn.fatal(connectionQueryError(
1493
- new Error("Query cancellation timed out"),
1494
- "PgConnection: Query cancellation timed out"
1495
- )),
1496
- abortDrainTimeoutMillis
1497
- )
1542
+ return drainAborted(conn, "Query cancellation timed out", (onDrained) =>
1498
1543
  machine.abort(() => {
1499
- clearTimeout(timer)
1500
1544
  conn.consumer = undefined
1501
- resumeWait(Effect.void)
1502
- })
1503
- })
1504
- return Effect.andThen(conn.cancel, wait)
1545
+ onDrained()
1546
+ }))
1505
1547
  })
1506
1548
  })
1507
1549
 
@@ -1746,30 +1788,16 @@ const streamRows = (
1746
1788
  else if (buffer.length >= streamPauseThreshold) setPaused(true)
1747
1789
  }
1748
1790
 
1749
- // On early abort: cancel the statement and drain back to ReadyForQuery so
1750
- // the pinned connection stays usable, destroying it when the drain stalls.
1751
1791
  yield* Scope.addFinalizer(
1752
1792
  scope,
1753
1793
  Effect.suspend(() => {
1754
1794
  if (done) return Effect.void
1755
1795
  aborted = true
1756
- setPaused(false)
1757
- const wait = Effect.callback<void>((resumeWait) => {
1758
- if (done) return resumeWait(Effect.void)
1759
- const timer = setTimeout(
1760
- () =>
1761
- conn.fatal(connectionQueryError(
1762
- new Error("Stream cancellation timed out"),
1763
- "PgConnection: Stream cancellation timed out"
1764
- )),
1765
- abortDrainTimeoutMillis
1766
- )
1767
- drainDone = () => {
1768
- clearTimeout(timer)
1769
- resumeWait(Effect.void)
1770
- }
1796
+ const drain = drainAborted(conn, "Stream cancellation timed out", (onDrained) => {
1797
+ drainDone = onDrained
1771
1798
  })
1772
- return Effect.andThen(conn.cancel, wait)
1799
+ setPaused(false)
1800
+ return drain
1773
1801
  })
1774
1802
  )
1775
1803
 
package/src/PgMigrator.ts CHANGED
@@ -13,18 +13,18 @@ import * as Effect from "effect/Effect"
13
13
  import * as FileSystem from "effect/FileSystem"
14
14
  import * as Layer from "effect/Layer"
15
15
  import * as Path from "effect/Path"
16
- import * as ChildProcess from "effect/unstable/process/ChildProcess"
17
- import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
18
- import * as Migrator from "effect/unstable/sql/Migrator"
19
- import type { SqlClient } from "effect/unstable/sql/SqlClient"
20
- import type { SqlError } from "effect/unstable/sql/SqlError"
16
+ import * as ChildProcess from "effect/process/ChildProcess"
17
+ import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"
18
+ import * as Migrator from "effect/sql/Migrator"
19
+ import type { SqlClient } from "effect/sql/SqlClient"
20
+ import type { SqlError } from "effect/sql/SqlError"
21
21
  import * as Password from "./internal/password.ts"
22
22
  import { PgClient } from "./PgClient.ts"
23
23
 
24
24
  /**
25
25
  * @since 4.0.0
26
26
  */
27
- export * from "effect/unstable/sql/Migrator"
27
+ export * from "effect/sql/Migrator"
28
28
 
29
29
  /**
30
30
  * Runs PostgreSQL SQL migrations using the configured clients. Schema dumps use `pg_dump` and require child process, filesystem, and path services.
package/src/PgPool.ts CHANGED
@@ -9,7 +9,7 @@ import * as Duration from "effect/Duration"
9
9
  import * as Effect from "effect/Effect"
10
10
  import * as Pool from "effect/Pool"
11
11
  import type * as Scope from "effect/Scope"
12
- import type { SqlError } from "effect/unstable/sql/SqlError"
12
+ import type { SqlError } from "effect/sql/SqlError"
13
13
  import { connectionInternals } from "./internal/connection.ts"
14
14
  import * as PgConnection from "./PgConnection.ts"
15
15
 
@@ -140,7 +140,7 @@ export const make = Effect.fnUntraced(function*(options: Config): Effect.fn.Retu
140
140
  Effect.sync(() => {
141
141
  createdAt.set(connection, clock.currentTimeMillisUnsafe())
142
142
  const internals = connectionInternals(connection)
143
- internals.fatalHooks.add(() => {
143
+ internals.retireHooks.add(() => {
144
144
  deadConnections.add(connection)
145
145
  // `deadConnections` is only read by the next checkout, and a checkout
146
146
  // already waiting for this connection would never get that far. Tell
@@ -204,12 +204,9 @@ export const make = Effect.fnUntraced(function*(options: Config): Effect.fn.Retu
204
204
  // `pin` reserves the pool item itself, so this needs no help.
205
205
  const reserve = Effect.flatMap(get, (connection) => connection.pin)
206
206
 
207
- // `Pool.use` cannot check the session it hands over before running the
208
- // effect, so it is only taken when there is nothing to check: no session is
209
- // known dead, and no lifetime can have run out. Otherwise the scoped
210
- // checkout does its replacement pass first. Checking inside the callback
211
- // instead would hold one lease while acquiring another, which deadlocks a
212
- // pool of one.
207
+ // `Pool.use` cannot pre-check a session. Use it only when no TTL applies and
208
+ // no connection awaits retirement. Checking inside its callback can deadlock
209
+ // a size-one pool while acquiring a replacement.
213
210
  const use = <A, E, R>(
214
211
  f: (connection: PgConnection.PgConnection) => Effect.Effect<A, E, R>
215
212
  ): Effect.Effect<A, E | SqlError, R> =>
package/src/PgTypes.ts CHANGED
@@ -29,10 +29,10 @@
29
29
  * @since 4.0.0
30
30
  */
31
31
  import * as Data from "effect/Data"
32
+ import * as IpInterface from "effect/net/IpInterface"
33
+ import * as IpNetwork from "effect/net/IpNetwork"
34
+ import * as NetAddress from "effect/net/NetAddress"
32
35
  import * as Result from "effect/Result"
33
- import * as IpInterface from "effect/unstable/net/IpInterface"
34
- import * as IpNetwork from "effect/unstable/net/IpNetwork"
35
- import * as NetAddress from "effect/unstable/net/NetAddress"
36
36
  import type * as PgProtocol from "./PgProtocol.ts"
37
37
  import type { ValueSink } from "./PgProtocol.ts"
38
38
 
@@ -313,6 +313,7 @@ export const OID = {
313
313
  timestamptz: 1184,
314
314
  timetz: 1266,
315
315
  numeric: 1700,
316
+ regclass: 2205,
316
317
  uuid: 2950,
317
318
  jsonb: 3802,
318
319
  boolArray: 1000,
@@ -336,6 +337,7 @@ export const OID = {
336
337
  timestamptzArray: 1185,
337
338
  timetzArray: 1270,
338
339
  numericArray: 1231,
340
+ regclassArray: 2210,
339
341
  uuidArray: 2951,
340
342
  jsonbArray: 3807
341
343
  } as const
@@ -362,6 +364,7 @@ const arrayToElement = new Map<number, number>([
362
364
  [OID.timestamptzArray, OID.timestamptz],
363
365
  [OID.timetzArray, OID.timetz],
364
366
  [OID.numericArray, OID.numeric],
367
+ [OID.regclassArray, OID.regclass],
365
368
  [OID.uuidArray, OID.uuid],
366
369
  [OID.jsonbArray, OID.jsonb]
367
370
  ])
@@ -1160,6 +1163,23 @@ const jsonbCodec: UnsafeCodec<any> = codecOf(
1160
1163
  }
1161
1164
  )
1162
1165
 
1166
+ const oidCodec: UnsafeCodec<any> = codecOf(
1167
+ (bytes, offset, size) => {
1168
+ requireSize(size, 4, "oid")
1169
+ return readUint32(bytes, offset)
1170
+ },
1171
+ (value) => {
1172
+ const num = requireInteger(value, "oid", 0, 4294967295)
1173
+ const bytes = new Uint8Array(4)
1174
+ bytes[0] = num >>> 24
1175
+ bytes[1] = num >>> 16
1176
+ bytes[2] = num >>> 8
1177
+ bytes[3] = num
1178
+ return bytes
1179
+ },
1180
+ (sink, value) => sink.int32(requireInteger(value, "oid", 0, 4294967295))
1181
+ )
1182
+
1163
1183
  const builtinScalars = new Map<number, UnsafeCodec<any>>([
1164
1184
  [
1165
1185
  OID.bool,
@@ -1224,25 +1244,8 @@ const builtinScalars = new Map<number, UnsafeCodec<any>>([
1224
1244
  (sink, value) => sink.int32(requireInteger(value, "int4", INT32_MIN, INT32_MAX))
1225
1245
  )
1226
1246
  ],
1227
- [
1228
- OID.oid,
1229
- codecOf(
1230
- (bytes, offset, size) => {
1231
- requireSize(size, 4, "oid")
1232
- return readUint32(bytes, offset)
1233
- },
1234
- (value) => {
1235
- const num = requireInteger(value, "oid", 0, 4294967295)
1236
- const bytes = new Uint8Array(4)
1237
- bytes[0] = num >>> 24
1238
- bytes[1] = num >>> 16
1239
- bytes[2] = num >>> 8
1240
- bytes[3] = num
1241
- return bytes
1242
- },
1243
- (sink, value) => sink.int32(requireInteger(value, "oid", 0, 4294967295))
1244
- )
1245
- ],
1247
+ [OID.oid, oidCodec],
1248
+ [OID.regclass, oidCodec],
1246
1249
  [
1247
1250
  OID.int8,
1248
1251
  codecOf(
@@ -1,6 +1,6 @@
1
1
  import type * as Effect from "effect/Effect"
2
2
  import type * as Scope from "effect/Scope"
3
- import type { SqlError } from "effect/unstable/sql/SqlError"
3
+ import type { SqlError } from "effect/sql/SqlError"
4
4
 
5
5
  /** @internal */
6
6
  export const internalsKey = "~@effect/sql-pg/PgConnection/internals" as const
@@ -10,8 +10,8 @@ export interface ConnectionInternals {
10
10
  /** The pool item identity: the unpinned base connection. */
11
11
  readonly base: object
12
12
  readonly deadError: () => SqlError | undefined
13
- /** Fired once when the connection dies outside its own scope release. */
14
- readonly fatalHooks: Set<() => void>
13
+ /** Fired when a pool must stop reusing the connection. */
14
+ readonly retireHooks: Set<() => void>
15
15
  /**
16
16
  * Installed by a multiplexed pool so that pinning the session also takes it
17
17
  * out of shared circulation for as long as the pin lasts. Every route to an
@@ -12,7 +12,7 @@ import {
12
12
  StatementTimeoutError,
13
13
  UniqueViolation,
14
14
  UnknownError
15
- } from "effect/unstable/sql/SqlError"
15
+ } from "effect/sql/SqlError"
16
16
 
17
17
  interface ErrorProps {
18
18
  readonly cause: unknown