@effect/sql-pg 4.0.0-rc.112 → 4.0.0-rc.113
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/AGENTS.md +24 -9
- package/CLAUDE.md +24 -9
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +18 -5
- package/ai-docs/src/01_effect/01_basics/index.md +5 -3
- package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
- package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +2 -2
- package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +2 -2
- package/ai-docs/src/03_stream/30_encoding.ts +5 -7
- package/ai-docs/src/08_observability/10_logging.ts +1 -1
- package/ai-docs/src/70_cli/10_basics.ts +7 -7
- package/ai-docs/src/71_ai/10_language-model.ts +2 -2
- package/ai-docs/src/71_ai/20_tools.ts +1 -1
- package/ai-docs/src/71_ai/30_chat.ts +1 -1
- package/dist/PgAuth.d.ts +24 -0
- package/dist/PgAuth.d.ts.map +1 -1
- package/dist/PgAuth.js +29 -2
- package/dist/PgAuth.js.map +1 -1
- package/dist/PgClient.d.ts +49 -82
- package/dist/PgClient.d.ts.map +1 -1
- package/dist/PgClient.js +58 -510
- package/dist/PgClient.js.map +1 -1
- package/dist/PgConnection.d.ts +177 -0
- package/dist/PgConnection.d.ts.map +1 -0
- package/dist/PgConnection.js +1786 -0
- package/dist/PgConnection.js.map +1 -0
- package/dist/PgPool.d.ts +107 -0
- package/dist/PgPool.d.ts.map +1 -0
- package/dist/PgPool.js +124 -0
- package/dist/PgPool.js.map +1 -0
- package/dist/PgProtocol.d.ts +63 -30
- package/dist/PgProtocol.d.ts.map +1 -1
- package/dist/PgProtocol.js +64 -22
- package/dist/PgProtocol.js.map +1 -1
- package/dist/PgTypes.d.ts +85 -17
- package/dist/PgTypes.d.ts.map +1 -1
- package/dist/PgTypes.js +166 -169
- package/dist/PgTypes.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/connection.d.ts +2 -0
- package/dist/internal/connection.d.ts.map +1 -0
- package/dist/internal/connection.js +5 -0
- package/dist/internal/connection.js.map +1 -0
- package/dist/internal/sqlError.d.ts +10 -0
- package/dist/internal/sqlError.d.ts.map +1 -0
- package/dist/internal/sqlError.js +57 -0
- package/dist/internal/sqlError.js.map +1 -0
- package/package.json +6 -14
- package/src/PgAuth.ts +33 -2
- package/src/PgClient.ts +138 -681
- package/src/PgConnection.ts +2294 -0
- package/src/PgPool.ts +231 -0
- package/src/PgProtocol.ts +93 -50
- package/src/PgTypes.ts +254 -186
- package/src/index.ts +10 -0
- package/src/internal/connection.ts +26 -0
- package/src/internal/sqlError.ts +75 -0
package/src/PgClient.ts
CHANGED
|
@@ -1,61 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This module provides constructors and layers for building a PostgreSQL
|
|
5
|
-
* client from pool settings, a managed `pg.Client`, an existing `pg.Pool`, or
|
|
6
|
-
* custom connection code. The client runs Effect SQL queries against
|
|
7
|
-
* PostgreSQL, including transactions and streamed results, and adds helpers for
|
|
8
|
-
* JSON values and LISTEN/NOTIFY messages. It also maps common PostgreSQL
|
|
9
|
-
* failures, such as connection, authentication, constraint, timeout, and
|
|
10
|
-
* deadlock errors, into Effect SQL errors.
|
|
2
|
+
* PostgreSQL support for Effect SQL, backed by the native wire protocol client.
|
|
11
3
|
*
|
|
12
4
|
* @since 4.0.0
|
|
13
5
|
*/
|
|
14
|
-
import * as Arr from "effect/Array"
|
|
15
|
-
import * as Cause from "effect/Cause"
|
|
16
|
-
import * as Channel from "effect/Channel"
|
|
6
|
+
import type * as Arr from "effect/Array"
|
|
17
7
|
import * as Config from "effect/Config"
|
|
18
8
|
import * as Context from "effect/Context"
|
|
19
|
-
import * as Duration from "effect/Duration"
|
|
9
|
+
import type * as Duration from "effect/Duration"
|
|
20
10
|
import * as Effect from "effect/Effect"
|
|
21
|
-
import * as Fiber from "effect/Fiber"
|
|
22
11
|
import * as Layer from "effect/Layer"
|
|
23
|
-
import * as
|
|
24
|
-
import * as
|
|
25
|
-
import * as
|
|
26
|
-
import * as RcRef from "effect/RcRef"
|
|
27
|
-
import * as Redacted from "effect/Redacted"
|
|
28
|
-
import * as Scope from "effect/Scope"
|
|
29
|
-
import * as Semaphore from "effect/Semaphore"
|
|
12
|
+
import type * as Queue from "effect/Queue"
|
|
13
|
+
import type * as Redacted from "effect/Redacted"
|
|
14
|
+
import type * as Scope from "effect/Scope"
|
|
30
15
|
import * as Stream from "effect/Stream"
|
|
31
16
|
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
|
32
17
|
import * as Client from "effect/unstable/sql/SqlClient"
|
|
33
|
-
import type { Connection } from "effect/unstable/sql/SqlConnection"
|
|
34
|
-
import type
|
|
35
|
-
import {
|
|
36
|
-
AuthenticationError,
|
|
37
|
-
AuthorizationError,
|
|
38
|
-
ConnectionError,
|
|
39
|
-
ConstraintError,
|
|
40
|
-
DeadlockError,
|
|
41
|
-
LockTimeoutError,
|
|
42
|
-
SerializationError,
|
|
43
|
-
SqlError,
|
|
44
|
-
SqlSyntaxError,
|
|
45
|
-
StatementTimeoutError,
|
|
46
|
-
UniqueViolation,
|
|
47
|
-
UnknownError
|
|
48
|
-
} from "effect/unstable/sql/SqlError"
|
|
18
|
+
import type { Borrower, Connection } from "effect/unstable/sql/SqlConnection"
|
|
19
|
+
import type { SqlError } from "effect/unstable/sql/SqlError"
|
|
49
20
|
import type { Custom, Fragment } from "effect/unstable/sql/Statement"
|
|
50
21
|
import * as Statement from "effect/unstable/sql/Statement"
|
|
51
22
|
import type { Duplex } from "node:stream"
|
|
52
23
|
import type { ConnectionOptions } from "node:tls"
|
|
53
|
-
import
|
|
54
|
-
import * as
|
|
55
|
-
import
|
|
24
|
+
import { validateChannelName } from "./internal/sqlError.ts"
|
|
25
|
+
import * as PgConnection from "./PgConnection.ts"
|
|
26
|
+
import * as PgPool from "./PgPool.ts"
|
|
27
|
+
import * as PgTypes from "./PgTypes.ts"
|
|
56
28
|
|
|
57
29
|
/**
|
|
58
|
-
*
|
|
30
|
+
* The runtime type identifier for `PgClient`.
|
|
59
31
|
*
|
|
60
32
|
* @category type IDs
|
|
61
33
|
* @since 4.0.0
|
|
@@ -63,7 +35,7 @@ import Cursor from "pg-cursor"
|
|
|
63
35
|
export const TypeId: TypeId = "~@effect/sql-pg/PgClient"
|
|
64
36
|
|
|
65
37
|
/**
|
|
66
|
-
*
|
|
38
|
+
* The type-level identifier for `PgClient`.
|
|
67
39
|
*
|
|
68
40
|
* @category type IDs
|
|
69
41
|
* @since 4.0.0
|
|
@@ -71,7 +43,7 @@ export const TypeId: TypeId = "~@effect/sql-pg/PgClient"
|
|
|
71
43
|
export type TypeId = "~@effect/sql-pg/PgClient"
|
|
72
44
|
|
|
73
45
|
/**
|
|
74
|
-
* PostgreSQL
|
|
46
|
+
* A PostgreSQL `SqlClient` with JSON and `LISTEN`/`NOTIFY` helpers.
|
|
75
47
|
*
|
|
76
48
|
* @category services
|
|
77
49
|
* @since 4.0.0
|
|
@@ -80,16 +52,19 @@ export interface PgClient extends Client.SqlClient {
|
|
|
80
52
|
readonly [TypeId]: TypeId
|
|
81
53
|
readonly config: PgClientConfig
|
|
82
54
|
readonly json: (_: unknown) => Fragment
|
|
83
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Registers a channel listener and returns its non-empty payload queue after
|
|
57
|
+
* PostgreSQL confirms `LISTEN`. The listener holds a connection until the
|
|
58
|
+
* scope closes.
|
|
59
|
+
*/
|
|
60
|
+
readonly listen: (
|
|
61
|
+
channel: string
|
|
62
|
+
) => Effect.Effect<Queue.Dequeue<PgConnection.Notification>, SqlError, Scope.Scope>
|
|
84
63
|
readonly notify: (channel: string, payload: string) => Effect.Effect<void, SqlError>
|
|
85
64
|
}
|
|
86
65
|
|
|
87
66
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* **When to use**
|
|
91
|
-
*
|
|
92
|
-
* Use to access or provide a PostgreSQL client through the Effect context.
|
|
67
|
+
* The service tag for `PgClient`.
|
|
93
68
|
*
|
|
94
69
|
* @category services
|
|
95
70
|
* @since 4.0.0
|
|
@@ -97,7 +72,7 @@ export interface PgClient extends Client.SqlClient {
|
|
|
97
72
|
export const PgClient = Context.Service<PgClient>("@effect/sql-pg/PgClient")
|
|
98
73
|
|
|
99
74
|
/**
|
|
100
|
-
*
|
|
75
|
+
* Connection and query settings for a PostgreSQL client.
|
|
101
76
|
*
|
|
102
77
|
* @category models
|
|
103
78
|
* @since 4.0.0
|
|
@@ -123,11 +98,32 @@ export interface PgClientConfig {
|
|
|
123
98
|
readonly transformResultNames?: ((str: string) => string) | undefined
|
|
124
99
|
readonly transformQueryNames?: ((str: string) => string) | undefined
|
|
125
100
|
readonly transformJson?: boolean | undefined
|
|
126
|
-
readonly types?:
|
|
101
|
+
readonly types?: PgTypes.Registry | undefined
|
|
102
|
+
/**
|
|
103
|
+
* Pipelines queries from multiple fibers on each pooled connection.
|
|
104
|
+
* Transactions, streams, and listeners still reserve a connection.
|
|
105
|
+
*/
|
|
106
|
+
readonly multiplex?: boolean | undefined
|
|
107
|
+
/**
|
|
108
|
+
* How many statements may share one connection when `multiplex` is on.
|
|
109
|
+
* Defaults to `32`. Higher values trade tail latency for throughput: the
|
|
110
|
+
* statements sharing a connection are pipelined into one write, and they
|
|
111
|
+
* also queue behind the slowest of them.
|
|
112
|
+
*/
|
|
113
|
+
readonly multiplexConcurrency?: number | undefined
|
|
114
|
+
/**
|
|
115
|
+
* Caches prepared statements by name. Enabled by default. Disable it for
|
|
116
|
+
* poolers that cannot preserve named statements between queries.
|
|
117
|
+
*/
|
|
118
|
+
readonly prepare?: boolean | undefined
|
|
119
|
+
/** How many statements a connection keeps prepared. Defaults to `100`. */
|
|
120
|
+
readonly preparedStatementCacheSize?: number | undefined
|
|
121
|
+
/** Maximum backend message size in bytes. Defaults to 16 MiB. */
|
|
122
|
+
readonly maxMessageSize?: number | undefined
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
/**
|
|
130
|
-
* PostgreSQL
|
|
126
|
+
* PostgreSQL client settings with connection pool limits and timeouts.
|
|
131
127
|
*
|
|
132
128
|
* @category models
|
|
133
129
|
* @since 4.0.0
|
|
@@ -141,73 +137,23 @@ export interface PgPoolConfig extends PgClientConfig {
|
|
|
141
137
|
}
|
|
142
138
|
|
|
143
139
|
/**
|
|
144
|
-
* Creates a scoped PostgreSQL client backed by a
|
|
140
|
+
* Creates a scoped PostgreSQL client backed by a connection pool.
|
|
145
141
|
*
|
|
146
142
|
* @category constructors
|
|
147
143
|
* @since 4.0.0
|
|
148
144
|
*/
|
|
149
145
|
export const make = (options: PgPoolConfig): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
password: options.password ? Redacted.value(options.password) : undefined,
|
|
159
|
-
ssl: options.ssl,
|
|
160
|
-
port: options.port,
|
|
161
|
-
...(options.stream ? { stream: options.stream } : {}),
|
|
162
|
-
connectionTimeoutMillis: options.connectTimeout
|
|
163
|
-
? Duration.toMillis(Duration.fromInputUnsafe(options.connectTimeout))
|
|
164
|
-
: undefined,
|
|
165
|
-
idleTimeoutMillis: options.idleTimeout
|
|
166
|
-
? Duration.toMillis(Duration.fromInputUnsafe(options.idleTimeout))
|
|
167
|
-
: undefined,
|
|
168
|
-
max: options.maxConnections,
|
|
169
|
-
min: options.minConnections,
|
|
170
|
-
maxLifetimeSeconds: options.connectionTTL
|
|
171
|
-
? Duration.toSeconds(Duration.fromInputUnsafe(options.connectionTTL))
|
|
172
|
-
: undefined,
|
|
173
|
-
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
174
|
-
types: options.types
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
pool.on("error", (_err) => {})
|
|
178
|
-
|
|
179
|
-
yield* Effect.acquireRelease(
|
|
180
|
-
Effect.tryPromise({
|
|
181
|
-
try: () => pool.query("SELECT 1"),
|
|
182
|
-
catch: (cause) => new SqlError({ reason: classifyError(cause, "PgClient: Failed to connect", "connect") })
|
|
183
|
-
}),
|
|
184
|
-
() =>
|
|
185
|
-
Effect.promise(() => pool.end()).pipe(
|
|
186
|
-
Effect.timeoutOption(1000)
|
|
187
|
-
),
|
|
188
|
-
{ interruptible: true }
|
|
189
|
-
).pipe(
|
|
190
|
-
Effect.timeoutOrElse({
|
|
191
|
-
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
192
|
-
orElse: () =>
|
|
193
|
-
Effect.fail(
|
|
194
|
-
new SqlError({
|
|
195
|
-
reason: new ConnectionError({
|
|
196
|
-
cause: new Error("Connection timed out"),
|
|
197
|
-
message: "PgClient: Connection timed out",
|
|
198
|
-
operation: "connect"
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
)
|
|
202
|
-
})
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
return pool
|
|
206
|
-
})
|
|
207
|
-
})
|
|
146
|
+
Effect.flatMap(PgPool.make(options), (pool) =>
|
|
147
|
+
makeImpl({
|
|
148
|
+
acquirer: Effect.map(pool.get, makeConnection),
|
|
149
|
+
borrower: (f) => pool.use((connection) => f(makeConnection(connection))),
|
|
150
|
+
transactionAcquirer: Effect.map(pool.reserve, makeConnection),
|
|
151
|
+
listenAcquirer: pool.reserve,
|
|
152
|
+
config: options
|
|
153
|
+
}))
|
|
208
154
|
|
|
209
155
|
/**
|
|
210
|
-
* Creates a scoped PostgreSQL client backed by
|
|
156
|
+
* Creates a scoped PostgreSQL client backed by one connection.
|
|
211
157
|
*
|
|
212
158
|
* @category constructors
|
|
213
159
|
* @since 4.0.0
|
|
@@ -215,398 +161,62 @@ export const make = (options: PgPoolConfig): Effect.Effect<PgClient, SqlError, S
|
|
|
215
161
|
export const makeClient = (
|
|
216
162
|
options: PgClientConfig & {
|
|
217
163
|
/**
|
|
218
|
-
*
|
|
164
|
+
* Opens a separate connection for each stream and listener when enabled.
|
|
219
165
|
*/
|
|
220
166
|
readonly acquireForStream?: boolean | undefined
|
|
221
167
|
}
|
|
222
|
-
): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
database: options.database,
|
|
234
|
-
password: options.password ? Redacted.value(options.password) : undefined,
|
|
235
|
-
ssl: options.ssl,
|
|
236
|
-
port: options.port,
|
|
237
|
-
...(options.stream ? { stream: options.stream } : {}),
|
|
238
|
-
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
239
|
-
types: options.types
|
|
240
|
-
})
|
|
241
|
-
client.on("error", onError)
|
|
242
|
-
await client.connect()
|
|
243
|
-
return client
|
|
244
|
-
},
|
|
245
|
-
catch: (cause) => new SqlError({ reason: classifyError(cause, "PgClient: Failed to connect", "connect") })
|
|
246
|
-
}),
|
|
247
|
-
(client) =>
|
|
248
|
-
Effect.promise(() => {
|
|
249
|
-
client.off("error", onError)
|
|
250
|
-
return client.end()
|
|
251
|
-
}).pipe(
|
|
252
|
-
Effect.timeoutOption(1000)
|
|
253
|
-
),
|
|
254
|
-
{ interruptible: true }
|
|
255
|
-
).pipe(
|
|
256
|
-
Effect.timeoutOrElse({
|
|
257
|
-
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
258
|
-
orElse: () =>
|
|
259
|
-
Effect.fail(
|
|
260
|
-
new SqlError({
|
|
261
|
-
reason: new ConnectionError({
|
|
262
|
-
cause: new Error("Connection timed out"),
|
|
263
|
-
message: "PgClient: Connection timed out",
|
|
264
|
-
operation: "connect"
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
)
|
|
268
|
-
})
|
|
269
|
-
),
|
|
270
|
-
acquireForStream: options.acquireForStream ?? false
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Builds a PostgreSQL client from a scoped `pg` pool acquisition effect, deriving transaction, streaming, and LISTEN/NOTIFY support from that pool.
|
|
276
|
-
*
|
|
277
|
-
* @category constructors
|
|
278
|
-
* @since 4.0.0
|
|
279
|
-
*/
|
|
280
|
-
export const fromPool = Effect.fnUntraced(function*(
|
|
281
|
-
options: {
|
|
282
|
-
readonly acquire: Effect.Effect<Pg.Pool, SqlError, Scope.Scope>
|
|
283
|
-
|
|
284
|
-
readonly applicationName?: string | undefined
|
|
285
|
-
readonly spanAttributes?: Record<string, unknown> | undefined
|
|
286
|
-
|
|
287
|
-
readonly transformResultNames?: ((str: string) => string) | undefined
|
|
288
|
-
readonly transformQueryNames?: ((str: string) => string) | undefined
|
|
289
|
-
readonly transformJson?: boolean | undefined
|
|
290
|
-
readonly types?: Pg.CustomTypesConfig | undefined
|
|
291
|
-
}
|
|
292
|
-
): Effect.fn.Return<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> {
|
|
293
|
-
const pool = yield* options.acquire
|
|
294
|
-
|
|
295
|
-
const makeConection = (client?: Pg.PoolClient) =>
|
|
296
|
-
new ConnectionImpl(
|
|
297
|
-
function runWithClient<A>(f: (client: Pg.ClientBase, resume: (_: Effect.Effect<A, SqlError>) => void) => void) {
|
|
298
|
-
if (client !== undefined) {
|
|
299
|
-
return Effect.callback<A, SqlError>((resume) => {
|
|
300
|
-
f(client!, resume)
|
|
301
|
-
return makeCancel(pool, client!)
|
|
302
|
-
})
|
|
303
|
-
}
|
|
304
|
-
return Effect.callback<A, SqlError>((resume) => {
|
|
305
|
-
let done = false
|
|
306
|
-
let cancel: Effect.Effect<void> | undefined = undefined
|
|
307
|
-
let client: Pg.PoolClient | undefined = undefined
|
|
308
|
-
function onError(cause: Error) {
|
|
309
|
-
cleanup(cause)
|
|
310
|
-
resume(Effect.fail(new SqlError({ reason: classifyError(cause, "Connection error", "acquireConnection") })))
|
|
311
|
-
}
|
|
312
|
-
function cleanup(cause?: Error) {
|
|
313
|
-
if (!done) client?.release(cause)
|
|
314
|
-
done = true
|
|
315
|
-
client?.off("error", onError)
|
|
316
|
-
}
|
|
317
|
-
pool.connect((cause, client_) => {
|
|
318
|
-
if (cause) {
|
|
319
|
-
return resume(
|
|
320
|
-
Effect.fail(
|
|
321
|
-
new SqlError({
|
|
322
|
-
reason: classifyError(cause, "Failed to acquire connection", "acquireConnection")
|
|
323
|
-
})
|
|
324
|
-
)
|
|
325
|
-
)
|
|
326
|
-
} else if (!client_) {
|
|
327
|
-
return resume(
|
|
328
|
-
Effect.fail(
|
|
329
|
-
new SqlError({
|
|
330
|
-
reason: new ConnectionError({
|
|
331
|
-
message: "Failed to acquire connection",
|
|
332
|
-
cause: new Error("No client returned"),
|
|
333
|
-
operation: "acquireConnection"
|
|
334
|
-
})
|
|
335
|
-
})
|
|
336
|
-
)
|
|
337
|
-
)
|
|
338
|
-
} else if (done) {
|
|
339
|
-
client_.release()
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
client = client_
|
|
343
|
-
client.once("error", onError)
|
|
344
|
-
cancel = makeCancel(pool, client)
|
|
345
|
-
f(client, (eff) => {
|
|
346
|
-
cleanup()
|
|
347
|
-
resume(eff)
|
|
348
|
-
})
|
|
349
|
-
})
|
|
350
|
-
return Effect.suspend(() => {
|
|
351
|
-
if (!cancel) {
|
|
352
|
-
cleanup()
|
|
353
|
-
return Effect.void
|
|
354
|
-
}
|
|
355
|
-
return Effect.ensuring(cancel, Effect.sync(cleanup))
|
|
356
|
-
})
|
|
357
|
-
})
|
|
358
|
-
},
|
|
359
|
-
client ? Effect.succeed(client) : reserveRaw
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
const reserveRaw = Effect.callback<Pg.PoolClient, SqlError, Scope.Scope>((resume) => {
|
|
363
|
-
const fiber = Fiber.getCurrent()!
|
|
364
|
-
const scope = Context.getUnsafe(fiber.context, Scope.Scope)
|
|
365
|
-
let cause: Error | undefined = undefined
|
|
366
|
-
function onError(cause_: Error) {
|
|
367
|
-
cause = cause_
|
|
368
|
-
}
|
|
369
|
-
pool.connect((err, client, release) => {
|
|
370
|
-
if (err) {
|
|
371
|
-
return resume(
|
|
372
|
-
Effect.fail(
|
|
373
|
-
new SqlError({
|
|
374
|
-
reason: classifyError(
|
|
375
|
-
err,
|
|
376
|
-
"Failed to acquire connection for transaction",
|
|
377
|
-
"acquireConnection"
|
|
378
|
-
)
|
|
379
|
-
})
|
|
380
|
-
)
|
|
381
|
-
)
|
|
382
|
-
} else if (!client) {
|
|
383
|
-
return resume(
|
|
384
|
-
Effect.fail(
|
|
385
|
-
new SqlError({
|
|
386
|
-
reason: new ConnectionError({
|
|
387
|
-
message: "Failed to acquire connection for transaction",
|
|
388
|
-
cause: new Error("No client returned"),
|
|
389
|
-
operation: "acquireConnection"
|
|
390
|
-
})
|
|
391
|
-
})
|
|
392
|
-
)
|
|
393
|
-
)
|
|
394
|
-
}
|
|
395
|
-
client.on("error", onError)
|
|
396
|
-
resume(Effect.as(
|
|
397
|
-
Scope.addFinalizer(
|
|
398
|
-
scope,
|
|
399
|
-
Effect.sync(() => {
|
|
400
|
-
client.off("error", onError)
|
|
401
|
-
release(cause)
|
|
402
|
-
})
|
|
403
|
-
),
|
|
404
|
-
client
|
|
405
|
-
))
|
|
406
|
-
})
|
|
407
|
-
})
|
|
408
|
-
const reserve = Effect.map(reserveRaw, makeConection)
|
|
409
|
-
|
|
410
|
-
const onListenClientError = (_: Error) => {
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const listenAcquirer = yield* RcRef.make({
|
|
414
|
-
acquire: Effect.acquireRelease(
|
|
415
|
-
Effect.tryPromise({
|
|
416
|
-
try: async () => {
|
|
417
|
-
const client = new Pg.Client(pool.options)
|
|
418
|
-
await client.connect()
|
|
419
|
-
client.on("error", onListenClientError)
|
|
420
|
-
return client
|
|
421
|
-
},
|
|
422
|
-
catch: (cause) =>
|
|
423
|
-
new SqlError({
|
|
424
|
-
reason: classifyError(cause, "Failed to acquire connection for listen", "acquireConnection")
|
|
425
|
-
})
|
|
426
|
-
}),
|
|
427
|
-
(client) =>
|
|
428
|
-
Effect.promise(() => {
|
|
429
|
-
client.off("error", onListenClientError)
|
|
430
|
-
return client.end()
|
|
431
|
-
}).pipe(
|
|
432
|
-
Effect.timeoutOption(1000)
|
|
433
|
-
),
|
|
434
|
-
{ interruptible: true }
|
|
435
|
-
)
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
let config: PgClientConfig = {
|
|
439
|
-
url: pool.options.connectionString ? Redacted.make(pool.options.connectionString) : undefined,
|
|
440
|
-
host: pool.options.host,
|
|
441
|
-
port: pool.options.port,
|
|
442
|
-
database: pool.options.database,
|
|
443
|
-
username: pool.options.user,
|
|
444
|
-
password: typeof pool.options.password === "string" ? Redacted.make(pool.options.password) : undefined,
|
|
445
|
-
ssl: pool.options.ssl,
|
|
446
|
-
applicationName: pool.options.application_name,
|
|
447
|
-
types: pool.options.types
|
|
448
|
-
}
|
|
449
|
-
if (pool.options.connectionString) {
|
|
450
|
-
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
451
|
-
try {
|
|
452
|
-
const parsed = PgConnString.parse(pool.options.connectionString)
|
|
453
|
-
config = {
|
|
454
|
-
...config,
|
|
455
|
-
host: config.host ?? parsed.host ?? undefined,
|
|
456
|
-
port: config.port ?? (parsed.port ? Option.getOrUndefined(Number.parse(parsed.port)) : undefined),
|
|
457
|
-
username: config.username ?? parsed.user ?? undefined,
|
|
458
|
-
password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
|
|
459
|
-
database: config.database ?? parsed.database ?? undefined
|
|
460
|
-
}
|
|
461
|
-
} catch {
|
|
462
|
-
//
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return yield* makeWith({
|
|
467
|
-
acquirer: Effect.succeed(makeConection()),
|
|
468
|
-
transactionAcquirer: reserve,
|
|
469
|
-
listenAcquirer: RcRef.get(listenAcquirer),
|
|
470
|
-
config,
|
|
471
|
-
spanAttributes: options.spanAttributes,
|
|
472
|
-
transformResultNames: options.transformResultNames,
|
|
473
|
-
transformQueryNames: options.transformQueryNames,
|
|
474
|
-
transformJson: options.transformJson
|
|
475
|
-
})
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Builds a PostgreSQL client from a scoped `pg` client acquisition effect, serializing access when sharing the client and optionally using separate clients for streams and LISTEN.
|
|
480
|
-
*
|
|
481
|
-
* @category constructors
|
|
482
|
-
* @since 4.0.0
|
|
483
|
-
*/
|
|
484
|
-
export const fromClient = Effect.fnUntraced(function*(
|
|
485
|
-
options: {
|
|
486
|
-
readonly acquire: Effect.Effect<Pg.Client, SqlError, Scope.Scope>
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Whether to acquire a separate client for each sql.stream / sql.listen.
|
|
490
|
-
*/
|
|
491
|
-
readonly acquireForStream: boolean
|
|
492
|
-
|
|
493
|
-
readonly applicationName?: string | undefined
|
|
494
|
-
readonly spanAttributes?: Record<string, unknown> | undefined
|
|
495
|
-
|
|
496
|
-
readonly transformResultNames?: ((str: string) => string) | undefined
|
|
497
|
-
readonly transformQueryNames?: ((str: string) => string) | undefined
|
|
498
|
-
readonly transformJson?: boolean | undefined
|
|
499
|
-
readonly types?: Pg.CustomTypesConfig | undefined
|
|
500
|
-
}
|
|
501
|
-
): Effect.fn.Return<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> {
|
|
502
|
-
function onError() {}
|
|
503
|
-
const acquireWithErrorHandler = options.acquire.pipe(
|
|
504
|
-
Effect.tap((client) => {
|
|
505
|
-
client.on("error", onError)
|
|
506
|
-
return Effect.addFinalizer(() => {
|
|
507
|
-
client.off("error", onError)
|
|
508
|
-
return Effect.void
|
|
509
|
-
})
|
|
510
|
-
})
|
|
511
|
-
)
|
|
512
|
-
const client = yield* acquireWithErrorHandler
|
|
513
|
-
|
|
514
|
-
const semaphore = Semaphore.makeUnsafe(1)
|
|
515
|
-
let streamClient = options.acquireForStream ? acquireWithErrorHandler : Effect.acquireRelease(
|
|
516
|
-
Effect.as(semaphore.take(1), client),
|
|
517
|
-
() => semaphore.release(1)
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
const makeConection = (client: Pg.Client) =>
|
|
521
|
-
new ConnectionImpl(
|
|
522
|
-
function runWithClient<A>(f: (client: Pg.ClientBase, resume: (_: Effect.Effect<A, SqlError>) => void) => void) {
|
|
523
|
-
return Effect.callback<A, SqlError>((resume) => {
|
|
524
|
-
f(client, resume)
|
|
525
|
-
})
|
|
526
|
-
},
|
|
527
|
-
streamClient
|
|
528
|
-
)
|
|
529
|
-
const connection = makeConection(client)
|
|
530
|
-
const acquirer = semaphore.withPermit(Effect.succeed(connection))
|
|
531
|
-
const transactionAcquirer = Effect.uninterruptibleMask((restore) => {
|
|
532
|
-
const fiber = Fiber.getCurrent()!
|
|
533
|
-
const scope = Context.getUnsafe(fiber.context, Scope.Scope)
|
|
534
|
-
return Effect.as(
|
|
535
|
-
Effect.tap(
|
|
536
|
-
restore(semaphore.take(1)),
|
|
537
|
-
() => Scope.addFinalizer(scope, semaphore.release(1))
|
|
538
|
-
),
|
|
539
|
-
connection
|
|
540
|
-
)
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
const config: PgClientConfig = {
|
|
544
|
-
...options,
|
|
545
|
-
host: client.host,
|
|
546
|
-
port: client.port,
|
|
547
|
-
database: client.database,
|
|
548
|
-
username: client.user,
|
|
549
|
-
password: typeof client.password === "string" ? Redacted.make(client.password) : undefined,
|
|
550
|
-
ssl: client.ssl
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return yield* makeWith({
|
|
554
|
-
acquirer,
|
|
555
|
-
transactionAcquirer,
|
|
556
|
-
listenAcquirer: streamClient,
|
|
557
|
-
config,
|
|
558
|
-
spanAttributes: options.spanAttributes,
|
|
559
|
-
transformResultNames: options.transformResultNames,
|
|
560
|
-
transformQueryNames: options.transformQueryNames,
|
|
561
|
-
transformJson: options.transformJson
|
|
562
|
-
})
|
|
563
|
-
})
|
|
168
|
+
): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
|
|
169
|
+
Effect.flatMap(PgConnection.make(options), (connection) =>
|
|
170
|
+
makeImpl({
|
|
171
|
+
acquirer: Effect.succeed(makeConnection(
|
|
172
|
+
connection,
|
|
173
|
+
options.acquireForStream ? PgConnection.make(options) : undefined
|
|
174
|
+
)),
|
|
175
|
+
transactionAcquirer: Effect.map(connection.pin, makeConnection),
|
|
176
|
+
listenAcquirer: options.acquireForStream ? PgConnection.make(options) : connection.pin,
|
|
177
|
+
config: options
|
|
178
|
+
}))
|
|
564
179
|
|
|
565
180
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* **When to use**
|
|
569
|
-
*
|
|
570
|
-
* Use to build a PostgreSQL client from custom connection acquisition logic
|
|
571
|
-
* instead of the built-in pool or single-client constructors.
|
|
572
|
-
*
|
|
573
|
-
* @category constructors
|
|
574
|
-
* @since 4.0.0
|
|
181
|
+
* Builds the shared SQL facade around native connection acquirers.
|
|
575
182
|
*/
|
|
576
|
-
|
|
183
|
+
const makeImpl = Effect.fnUntraced(function*(
|
|
577
184
|
options: {
|
|
578
|
-
readonly acquirer:
|
|
579
|
-
readonly
|
|
580
|
-
readonly
|
|
581
|
-
|
|
185
|
+
readonly acquirer: Effect.Effect<Connection, SqlError, Scope.Scope>
|
|
186
|
+
readonly borrower?: Borrower | undefined
|
|
187
|
+
readonly transactionAcquirer: Effect.Effect<Connection, SqlError, Scope.Scope>
|
|
188
|
+
readonly listenAcquirer: Effect.Effect<PgConnection.PgConnection, SqlError, Scope.Scope>
|
|
582
189
|
readonly config: PgClientConfig
|
|
583
|
-
readonly spanAttributes?: Record<string, unknown> | undefined
|
|
584
|
-
|
|
585
|
-
readonly transformResultNames?: ((str: string) => string) | undefined
|
|
586
|
-
readonly transformQueryNames?: ((str: string) => string) | undefined
|
|
587
|
-
readonly transformJson?: boolean | undefined
|
|
588
190
|
}
|
|
589
191
|
): Effect.fn.Return<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> {
|
|
192
|
+
const config = options.config
|
|
590
193
|
const compiler = makeCompiler(
|
|
591
|
-
|
|
592
|
-
|
|
194
|
+
config.transformQueryNames,
|
|
195
|
+
config.transformJson
|
|
593
196
|
)
|
|
594
|
-
const transformRows =
|
|
197
|
+
const transformRows = config.transformResultNames ?
|
|
595
198
|
Statement.defaultTransforms(
|
|
596
|
-
|
|
597
|
-
|
|
199
|
+
config.transformResultNames,
|
|
200
|
+
config.transformJson
|
|
598
201
|
).array :
|
|
599
202
|
undefined
|
|
600
203
|
|
|
601
|
-
const
|
|
204
|
+
const listen = (
|
|
205
|
+
channel: string
|
|
206
|
+
): Effect.Effect<Queue.Dequeue<PgConnection.Notification>, SqlError, Scope.Scope> =>
|
|
207
|
+
Effect.flatMap(options.listenAcquirer, (connection) => connection.listen(channel))
|
|
602
208
|
|
|
603
209
|
return Object.assign(
|
|
604
210
|
yield* Client.make({
|
|
605
211
|
acquirer: options.acquirer,
|
|
212
|
+
borrower: options.borrower,
|
|
213
|
+
// Postgres prepares transaction control like anything else, and a client
|
|
214
|
+
// with preparation turned off falls back to the unnamed path anyway.
|
|
215
|
+
prepareTransactionControls: true,
|
|
606
216
|
transactionAcquirer: options.transactionAcquirer,
|
|
607
217
|
compiler,
|
|
608
218
|
spanAttributes: [
|
|
609
|
-
...(
|
|
219
|
+
...(config.spanAttributes ? Object.entries(config.spanAttributes) : []),
|
|
610
220
|
[ATTR_DB_SYSTEM_NAME, "postgresql"],
|
|
611
221
|
[ATTR_DB_NAMESPACE, config.database ?? config.username ?? "postgres"],
|
|
612
222
|
[ATTR_SERVER_ADDRESS, config.host ?? "localhost"],
|
|
@@ -616,70 +226,36 @@ export const makeWith = Effect.fnUntraced(function*(
|
|
|
616
226
|
}),
|
|
617
227
|
{
|
|
618
228
|
[TypeId]: TypeId as TypeId,
|
|
619
|
-
config
|
|
229
|
+
config,
|
|
620
230
|
json: (_: unknown) => Statement.fragment([PgJson(_)]),
|
|
621
|
-
listen
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
client.off("notification", onNotification)
|
|
632
|
-
return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`)
|
|
633
|
-
})
|
|
634
|
-
)
|
|
635
|
-
yield* Effect.tryPromise({
|
|
636
|
-
try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
|
|
637
|
-
catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to listen", "listen") })
|
|
638
|
-
})
|
|
639
|
-
client.on("notification", onNotification)
|
|
640
|
-
})),
|
|
641
|
-
notify: (channel: string, payload: string) =>
|
|
642
|
-
Effect.asVoid(Effect.scoped(Effect.flatMap(
|
|
643
|
-
options.acquirer,
|
|
644
|
-
(conn) => conn.executeRaw(`SELECT pg_notify($1, $2)`, [channel, payload])
|
|
645
|
-
)))
|
|
231
|
+
listen,
|
|
232
|
+
notify: (channel: string, payload: string) => {
|
|
233
|
+
const channelError = validateChannelName(channel, "notify")
|
|
234
|
+
return channelError !== undefined ?
|
|
235
|
+
Effect.fail(channelError) :
|
|
236
|
+
Effect.asVoid(Effect.scoped(Effect.flatMap(
|
|
237
|
+
options.acquirer,
|
|
238
|
+
(conn) => conn.executeRaw(`SELECT pg_notify($1, $2)`, [channel, payload])
|
|
239
|
+
)))
|
|
240
|
+
}
|
|
646
241
|
}
|
|
647
242
|
)
|
|
648
243
|
})
|
|
649
244
|
|
|
650
245
|
class ConnectionImpl implements Connection {
|
|
246
|
+
readonly connection: PgConnection.PgConnection
|
|
247
|
+
readonly streamAcquirer: Effect.Effect<PgConnection.PgConnection, SqlError, Scope.Scope> | undefined
|
|
248
|
+
|
|
651
249
|
constructor(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
) => Effect.Effect<A, SqlError>,
|
|
655
|
-
reserve: Effect.Effect<Pg.ClientBase, SqlError, Scope.Scope>
|
|
250
|
+
connection: PgConnection.PgConnection,
|
|
251
|
+
streamAcquirer?: Effect.Effect<PgConnection.PgConnection, SqlError, Scope.Scope> | undefined
|
|
656
252
|
) {
|
|
657
|
-
this.
|
|
658
|
-
this.
|
|
253
|
+
this.connection = connection
|
|
254
|
+
this.streamAcquirer = streamAcquirer
|
|
659
255
|
}
|
|
660
256
|
|
|
661
|
-
private
|
|
662
|
-
|
|
663
|
-
) => Effect.Effect<A, SqlError>
|
|
664
|
-
private readonly reserve: Effect.Effect<Pg.ClientBase, SqlError, Scope.Scope>
|
|
665
|
-
|
|
666
|
-
private run(query: string, params: ReadonlyArray<unknown>) {
|
|
667
|
-
return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
|
|
668
|
-
client.query(query, params as any, (err, result) => {
|
|
669
|
-
if (err) {
|
|
670
|
-
resume(
|
|
671
|
-
Effect.fail(new SqlError({ reason: classifyError(err, "Failed to execute statement", "execute") }))
|
|
672
|
-
)
|
|
673
|
-
} else {
|
|
674
|
-
// Multi-statement queries return an array of results
|
|
675
|
-
resume(Effect.succeed(
|
|
676
|
-
Array.isArray(result)
|
|
677
|
-
? result.map((r) => r.rows ?? [])
|
|
678
|
-
: result.rows ?? []
|
|
679
|
-
))
|
|
680
|
-
}
|
|
681
|
-
})
|
|
682
|
-
})
|
|
257
|
+
private run(query: string, params: ReadonlyArray<unknown>, prepare = true) {
|
|
258
|
+
return Effect.map(this.connection.query(query, params, prepare), (result) => result.rows)
|
|
683
259
|
}
|
|
684
260
|
|
|
685
261
|
execute(
|
|
@@ -692,102 +268,48 @@ class ConnectionImpl implements Connection {
|
|
|
692
268
|
: this.run(sql, params)
|
|
693
269
|
}
|
|
694
270
|
executeRaw(sql: string, params: ReadonlyArray<unknown>) {
|
|
695
|
-
return this.
|
|
696
|
-
client.query(sql, params as any, (err, result) => {
|
|
697
|
-
if (err) {
|
|
698
|
-
resume(
|
|
699
|
-
Effect.fail(new SqlError({ reason: classifyError(err, "Failed to execute statement", "execute") }))
|
|
700
|
-
)
|
|
701
|
-
} else {
|
|
702
|
-
resume(Effect.succeed(result))
|
|
703
|
-
}
|
|
704
|
-
})
|
|
705
|
-
})
|
|
271
|
+
return this.connection.query(sql, params)
|
|
706
272
|
}
|
|
707
273
|
executeWithoutTransform(sql: string, params: ReadonlyArray<unknown>) {
|
|
708
274
|
return this.run(sql, params)
|
|
709
275
|
}
|
|
710
276
|
executeValues(sql: string, params: ReadonlyArray<unknown>) {
|
|
711
|
-
return this.
|
|
712
|
-
client.query(
|
|
713
|
-
{
|
|
714
|
-
text: sql,
|
|
715
|
-
rowMode: "array",
|
|
716
|
-
values: params as Array<string>
|
|
717
|
-
},
|
|
718
|
-
(err, result) => {
|
|
719
|
-
if (err) {
|
|
720
|
-
resume(
|
|
721
|
-
Effect.fail(new SqlError({ reason: classifyError(err, "Failed to execute statement", "execute") }))
|
|
722
|
-
)
|
|
723
|
-
} else {
|
|
724
|
-
resume(Effect.succeed(result.rows))
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
)
|
|
728
|
-
})
|
|
277
|
+
return this.connection.queryValues(sql, params)
|
|
729
278
|
}
|
|
730
279
|
executeValuesUnprepared(sql: string, params: ReadonlyArray<unknown>) {
|
|
731
|
-
return this.
|
|
280
|
+
return this.connection.queryValues(sql, params, false)
|
|
732
281
|
}
|
|
733
282
|
executeUnprepared(
|
|
734
283
|
sql: string,
|
|
735
284
|
params: ReadonlyArray<unknown>,
|
|
736
285
|
transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
|
|
737
286
|
) {
|
|
738
|
-
|
|
287
|
+
const operation = this.run(sql, params, false)
|
|
288
|
+
return transformRows
|
|
289
|
+
? Effect.map(operation, transformRows)
|
|
290
|
+
: operation
|
|
739
291
|
}
|
|
740
292
|
executeStream(
|
|
741
293
|
sql: string,
|
|
742
294
|
params: ReadonlyArray<unknown>,
|
|
743
295
|
transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
|
|
744
296
|
) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
// @effect-diagnostics-next-line returnEffectInGen:off
|
|
752
|
-
return Effect.callback<Arr.NonEmptyReadonlyArray<any>, SqlError | Cause.Done>((resume) => {
|
|
753
|
-
cursor.read(128, (err, rows) => {
|
|
754
|
-
if (err) {
|
|
755
|
-
resume(Effect.fail(new SqlError({ reason: classifyError(err, "Failed to execute statement", "stream") })))
|
|
756
|
-
} else if (Arr.isArrayNonEmpty(rows)) {
|
|
757
|
-
resume(Effect.succeed(transformRows ? transformRows(rows) as any : rows))
|
|
758
|
-
} else {
|
|
759
|
-
resume(Cause.done())
|
|
760
|
-
}
|
|
761
|
-
})
|
|
762
|
-
})
|
|
763
|
-
})))
|
|
297
|
+
const stream = this.streamAcquirer === undefined
|
|
298
|
+
? this.connection.stream(sql, params)
|
|
299
|
+
: Stream.unwrap(Effect.map(this.streamAcquirer, (connection) => connection.stream(sql, params)))
|
|
300
|
+
return transformRows
|
|
301
|
+
? Stream.mapArray(stream, (rows) => transformRows(rows) as Arr.NonEmptyReadonlyArray<PgConnection.Row>)
|
|
302
|
+
: stream
|
|
764
303
|
}
|
|
765
304
|
}
|
|
766
305
|
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
const processId = (client as any).processID
|
|
773
|
-
const eff = processId !== undefined
|
|
774
|
-
// query cancelation is best-effort, so we don't fail if it doesn't work
|
|
775
|
-
? Effect.callback<void>((resume) => {
|
|
776
|
-
if (pool.ending) return resume(Effect.void)
|
|
777
|
-
pool.query(`SELECT pg_cancel_backend(${processId})`, () => {
|
|
778
|
-
resume(Effect.void)
|
|
779
|
-
})
|
|
780
|
-
}).pipe(
|
|
781
|
-
Effect.interruptible,
|
|
782
|
-
Effect.timeoutOption(5000)
|
|
783
|
-
)
|
|
784
|
-
: undefined
|
|
785
|
-
cancelEffects.set(client, eff)
|
|
786
|
-
return eff
|
|
787
|
-
}
|
|
306
|
+
const makeConnection = (
|
|
307
|
+
connection: PgConnection.PgConnection,
|
|
308
|
+
streamAcquirer?: Effect.Effect<PgConnection.PgConnection, SqlError, Scope.Scope> | undefined
|
|
309
|
+
): Connection => new ConnectionImpl(connection, streamAcquirer)
|
|
788
310
|
|
|
789
311
|
/**
|
|
790
|
-
*
|
|
312
|
+
* Provides both `PgClient` and `SqlClient` from an acquisition effect.
|
|
791
313
|
*
|
|
792
314
|
* @category layers
|
|
793
315
|
* @since 4.0.0
|
|
@@ -803,14 +325,12 @@ export const layerFrom = <E, R>(
|
|
|
803
325
|
).pipe(Layer.provide(Reactivity.layer)) as any
|
|
804
326
|
|
|
805
327
|
/**
|
|
806
|
-
* Creates a layer from
|
|
328
|
+
* Creates a client layer from wrapped pool configuration.
|
|
807
329
|
*
|
|
808
330
|
* @category layers
|
|
809
331
|
* @since 4.0.0
|
|
810
332
|
*/
|
|
811
|
-
export const layerConfig
|
|
812
|
-
config: Config.Wrap<PgPoolConfig>
|
|
813
|
-
) => Layer.Layer<PgClient | Client.SqlClient, Config.ConfigError | SqlError> = (
|
|
333
|
+
export const layerConfig = (
|
|
814
334
|
config: Config.Wrap<PgPoolConfig>
|
|
815
335
|
): Layer.Layer<PgClient | Client.SqlClient, Config.ConfigError | SqlError> =>
|
|
816
336
|
layerFrom(Effect.flatMap(
|
|
@@ -819,7 +339,7 @@ export const layerConfig: (
|
|
|
819
339
|
))
|
|
820
340
|
|
|
821
341
|
/**
|
|
822
|
-
* Creates a layer from
|
|
342
|
+
* Creates a client layer from pool configuration.
|
|
823
343
|
*
|
|
824
344
|
* @category layers
|
|
825
345
|
* @since 4.0.0
|
|
@@ -829,7 +349,7 @@ export const layer = (
|
|
|
829
349
|
): Layer.Layer<PgClient | Client.SqlClient, SqlError> => layerFrom(make(config))
|
|
830
350
|
|
|
831
351
|
/**
|
|
832
|
-
* Creates the PostgreSQL statement compiler
|
|
352
|
+
* Creates the PostgreSQL statement compiler.
|
|
833
353
|
*
|
|
834
354
|
* @category constructors
|
|
835
355
|
* @since 4.0.0
|
|
@@ -863,13 +383,12 @@ export const makeCompiler = (
|
|
|
863
383
|
onCustom(type, placeholder, withoutTransform) {
|
|
864
384
|
switch (type.kind) {
|
|
865
385
|
case "PgJson": {
|
|
386
|
+
const value = withoutTransform || transformValue === undefined
|
|
387
|
+
? type.paramA
|
|
388
|
+
: transformValue(type.paramA)
|
|
866
389
|
return [
|
|
867
390
|
placeholder(undefined),
|
|
868
|
-
[
|
|
869
|
-
withoutTransform || transformValue === undefined
|
|
870
|
-
? type.paramA
|
|
871
|
-
: transformValue(type.paramA)
|
|
872
|
-
]
|
|
391
|
+
[PgTypes.jsonb(value)]
|
|
873
392
|
]
|
|
874
393
|
}
|
|
875
394
|
}
|
|
@@ -880,7 +399,7 @@ export const makeCompiler = (
|
|
|
880
399
|
const escape = Statement.defaultEscape("\"")
|
|
881
400
|
|
|
882
401
|
/**
|
|
883
|
-
* PostgreSQL-specific
|
|
402
|
+
* PostgreSQL-specific statement fragments.
|
|
884
403
|
*
|
|
885
404
|
* @category models
|
|
886
405
|
* @since 4.0.0
|
|
@@ -902,65 +421,3 @@ const ATTR_DB_SYSTEM_NAME = "db.system.name"
|
|
|
902
421
|
const ATTR_DB_NAMESPACE = "db.namespace"
|
|
903
422
|
const ATTR_SERVER_ADDRESS = "server.address"
|
|
904
423
|
const ATTR_SERVER_PORT = "server.port"
|
|
905
|
-
|
|
906
|
-
const pgCodeFromCause = (cause: unknown): string | undefined => {
|
|
907
|
-
if (typeof cause !== "object" || cause === null || !("code" in cause)) {
|
|
908
|
-
return undefined
|
|
909
|
-
}
|
|
910
|
-
const code = cause.code
|
|
911
|
-
return typeof code === "string" ? code : undefined
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const pgConstraintFromCause = (cause: unknown): string => {
|
|
915
|
-
if (typeof cause !== "object" || cause === null || !("constraint" in cause)) {
|
|
916
|
-
return "unknown"
|
|
917
|
-
}
|
|
918
|
-
const constraint = cause.constraint
|
|
919
|
-
if (typeof constraint !== "string") {
|
|
920
|
-
return "unknown"
|
|
921
|
-
}
|
|
922
|
-
const normalized = constraint.trim()
|
|
923
|
-
return normalized.length === 0 ? "unknown" : normalized
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
const classifyError = (
|
|
927
|
-
cause: unknown,
|
|
928
|
-
message: string,
|
|
929
|
-
operation: string
|
|
930
|
-
) => {
|
|
931
|
-
const props = { cause, message, operation }
|
|
932
|
-
const code = pgCodeFromCause(cause)
|
|
933
|
-
if (code !== undefined) {
|
|
934
|
-
if (code.startsWith("08")) {
|
|
935
|
-
return new ConnectionError(props)
|
|
936
|
-
}
|
|
937
|
-
if (code.startsWith("28")) {
|
|
938
|
-
return new AuthenticationError(props)
|
|
939
|
-
}
|
|
940
|
-
if (code === "42501") {
|
|
941
|
-
return new AuthorizationError(props)
|
|
942
|
-
}
|
|
943
|
-
if (code.startsWith("42")) {
|
|
944
|
-
return new SqlSyntaxError(props)
|
|
945
|
-
}
|
|
946
|
-
if (code === "23505") {
|
|
947
|
-
return new UniqueViolation({ ...props, constraint: pgConstraintFromCause(cause) })
|
|
948
|
-
}
|
|
949
|
-
if (code.startsWith("23")) {
|
|
950
|
-
return new ConstraintError(props)
|
|
951
|
-
}
|
|
952
|
-
if (code === "40P01") {
|
|
953
|
-
return new DeadlockError(props)
|
|
954
|
-
}
|
|
955
|
-
if (code === "40001") {
|
|
956
|
-
return new SerializationError(props)
|
|
957
|
-
}
|
|
958
|
-
if (code === "55P03") {
|
|
959
|
-
return new LockTimeoutError(props)
|
|
960
|
-
}
|
|
961
|
-
if (code === "57014") {
|
|
962
|
-
return new StatementTimeoutError(props)
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
return new UnknownError(props)
|
|
966
|
-
}
|