@effect/sql-pg 0.50.2 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/PgClient.ts CHANGED
@@ -1,27 +1,27 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import * as Reactivity from "@effect/experimental/Reactivity"
5
- import * as Client from "@effect/sql/SqlClient"
6
- import type { Connection } from "@effect/sql/SqlConnection"
7
- import { SqlError } from "@effect/sql/SqlError"
8
- import type { Custom, Fragment } from "@effect/sql/Statement"
9
- import * as Statement from "@effect/sql/Statement"
10
4
  import * as Arr from "effect/Array"
11
- import * as Chunk from "effect/Chunk"
5
+ import * as Cause from "effect/Cause"
6
+ import * as Channel from "effect/Channel"
12
7
  import * as Config from "effect/Config"
13
- import type * as ConfigError from "effect/ConfigError"
14
- import * as Context from "effect/Context"
15
8
  import * as Duration from "effect/Duration"
16
9
  import * as Effect from "effect/Effect"
17
10
  import * as Fiber from "effect/Fiber"
18
11
  import * as Layer from "effect/Layer"
19
12
  import * as Number from "effect/Number"
20
- import * as Option from "effect/Option"
13
+ import * as Queue from "effect/Queue"
21
14
  import * as RcRef from "effect/RcRef"
22
15
  import * as Redacted from "effect/Redacted"
23
16
  import * as Scope from "effect/Scope"
17
+ import * as ServiceMap from "effect/ServiceMap"
24
18
  import * as Stream from "effect/Stream"
19
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity"
20
+ import * as Client from "effect/unstable/sql/SqlClient"
21
+ import type { Connection } from "effect/unstable/sql/SqlConnection"
22
+ import { SqlError } from "effect/unstable/sql/SqlError"
23
+ import type { Custom, Fragment } from "effect/unstable/sql/Statement"
24
+ import * as Statement from "effect/unstable/sql/Statement"
25
25
  import type { Duplex } from "node:stream"
26
26
  import type { ConnectionOptions } from "node:tls"
27
27
  import * as Pg from "pg"
@@ -61,7 +61,7 @@ export interface PgClient extends Client.SqlClient {
61
61
  * @category tags
62
62
  * @since 1.0.0
63
63
  */
64
- export const PgClient = Context.GenericTag<PgClient>("@effect/sql-pg/PgClient")
64
+ export const PgClient = ServiceMap.Service<PgClient>("@effect/sql-pg/PgClient")
65
65
 
66
66
  /**
67
67
  * @category constructors
@@ -103,329 +103,350 @@ export interface PgClientConfig {
103
103
  export const make = (
104
104
  options: PgClientConfig
105
105
  ): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
106
- Effect.gen(function*() {
107
- const compiler = makeCompiler(
108
- options.transformQueryNames,
109
- options.transformJson
110
- )
111
- const transformRows = options.transformResultNames ?
112
- Statement.defaultTransforms(
113
- options.transformResultNames,
114
- options.transformJson
115
- ).array :
116
- undefined
117
-
118
- const pool = new Pg.Pool({
119
- connectionString: options.url ? Redacted.value(options.url) : undefined,
120
- user: options.username,
121
- host: options.host,
122
- database: options.database,
123
- password: options.password ? Redacted.value(options.password) : undefined,
124
- ssl: options.ssl,
125
- port: options.port,
126
- stream: options.stream!,
127
- connectionTimeoutMillis: options.connectTimeout
128
- ? Duration.toMillis(options.connectTimeout)
129
- : undefined,
130
- idleTimeoutMillis: options.idleTimeout
131
- ? Duration.toMillis(options.idleTimeout)
132
- : undefined,
133
- max: options.maxConnections,
134
- min: options.minConnections,
135
- maxLifetimeSeconds: options.connectionTTL
136
- ? Duration.toSeconds(options.connectionTTL)
137
- : undefined,
138
- application_name: options.applicationName ?? "@effect/sql-pg",
139
- types: options.types
140
- })
106
+ fromPool({
107
+ ...options,
108
+ acquire: Effect.gen(function*() {
109
+ const pool = new Pg.Pool({
110
+ connectionString: options.url ? Redacted.value(options.url) : undefined,
111
+ user: options.username,
112
+ host: options.host,
113
+ database: options.database,
114
+ password: options.password ? Redacted.value(options.password) : undefined,
115
+ ssl: options.ssl,
116
+ port: options.port,
117
+ ...(options.stream ? { stream: options.stream } : {}),
118
+ connectionTimeoutMillis: options.connectTimeout
119
+ ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.connectTimeout))
120
+ : undefined,
121
+ idleTimeoutMillis: options.idleTimeout
122
+ ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.idleTimeout))
123
+ : undefined,
124
+ max: options.maxConnections,
125
+ min: options.minConnections,
126
+ maxLifetimeSeconds: options.connectionTTL
127
+ ? Duration.toSeconds(Duration.fromDurationInputUnsafe(options.connectionTTL))
128
+ : undefined,
129
+ application_name: options.applicationName ?? "@effect/sql-pg",
130
+ types: options.types
131
+ })
132
+
133
+ pool.on("error", (_err) => {})
141
134
 
142
- pool.on("error", (_err) => {
135
+ yield* Effect.acquireRelease(
136
+ Effect.tryPromise({
137
+ try: () => pool.query("SELECT 1"),
138
+ catch: (cause) => new SqlError({ cause, message: "PgClient: Failed to connect" })
139
+ }),
140
+ () =>
141
+ Effect.promise(() => pool.end()).pipe(
142
+ Effect.timeoutOption(1000)
143
+ )
144
+ ).pipe(
145
+ Effect.timeoutOrElse({
146
+ duration: options.connectTimeout ?? Duration.seconds(5),
147
+ onTimeout: () =>
148
+ Effect.fail(
149
+ new SqlError({
150
+ cause: new Error("Connection timed out"),
151
+ message: "PgClient: Connection timed out"
152
+ })
153
+ )
154
+ })
155
+ )
156
+
157
+ return pool
143
158
  })
159
+ })
144
160
 
145
- yield* Effect.acquireRelease(
146
- Effect.tryPromise({
147
- try: () => pool.query("SELECT 1"),
148
- catch: (cause) => new SqlError({ cause, message: "PgClient: Failed to connect" })
149
- }),
150
- () =>
151
- Effect.promise(() => pool.end()).pipe(
152
- Effect.interruptible,
153
- Effect.timeoutOption(1000)
154
- )
155
- ).pipe(
156
- Effect.timeoutFail({
157
- duration: options.connectTimeout ?? Duration.seconds(5),
158
- onTimeout: () =>
159
- new SqlError({
160
- cause: new Error("Connection timed out"),
161
- message: "PgClient: Connection timed out"
162
- })
163
- })
164
- )
161
+ /**
162
+ * @category constructors
163
+ * @since 1.0.0
164
+ */
165
+ export const fromPool = Effect.fnUntraced(function*(
166
+ options: {
167
+ readonly acquire: Effect.Effect<Pg.Pool, SqlError, Scope.Scope>
165
168
 
166
- class ConnectionImpl implements Connection {
167
- readonly pg: Pg.PoolClient | undefined
168
- constructor(pg?: Pg.PoolClient) {
169
- this.pg = pg
170
- }
169
+ readonly applicationName?: string | undefined
170
+ readonly spanAttributes?: Record<string, unknown> | undefined
171
171
 
172
- private runWithClient<A>(f: (client: Pg.PoolClient, resume: (_: Effect.Effect<A, SqlError>) => void) => void) {
173
- if (this.pg !== undefined) {
174
- return Effect.async<A, SqlError>((resume) => {
175
- f(this.pg!, resume)
176
- return makeCancel(pool, this.pg!)
177
- })
172
+ readonly transformResultNames?: ((str: string) => string) | undefined
173
+ readonly transformQueryNames?: ((str: string) => string) | undefined
174
+ readonly transformJson?: boolean | undefined
175
+ readonly types?: Pg.CustomTypesConfig | undefined
176
+ }
177
+ ): Effect.fn.Return<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> {
178
+ const compiler = makeCompiler(
179
+ options.transformQueryNames,
180
+ options.transformJson
181
+ )
182
+ const transformRows = options.transformResultNames ?
183
+ Statement.defaultTransforms(
184
+ options.transformResultNames,
185
+ options.transformJson
186
+ ).array :
187
+ undefined
188
+
189
+ const pool = yield* options.acquire
190
+
191
+ class ConnectionImpl implements Connection {
192
+ readonly pg: Pg.PoolClient | undefined
193
+ constructor(pg?: Pg.PoolClient) {
194
+ this.pg = pg
195
+ }
196
+
197
+ private runWithClient<A>(f: (client: Pg.PoolClient, resume: (_: Effect.Effect<A, SqlError>) => void) => void) {
198
+ if (this.pg !== undefined) {
199
+ return Effect.callback<A, SqlError>((resume) => {
200
+ f(this.pg!, resume)
201
+ return makeCancel(pool, this.pg!)
202
+ })
203
+ }
204
+ return Effect.callback<A, SqlError>((resume) => {
205
+ let done = false
206
+ let cancel: Effect.Effect<void> | undefined = undefined
207
+ let client: Pg.PoolClient | undefined = undefined
208
+ function onError(cause: Error) {
209
+ cleanup(cause)
210
+ resume(Effect.fail(new SqlError({ cause, message: "Connection error" })))
178
211
  }
179
- return Effect.async<A, SqlError>((resume) => {
180
- let done = false
181
- let cancel: Effect.Effect<void> | undefined = undefined
182
- let client: Pg.PoolClient | undefined = undefined
183
- function onError(cause: Error) {
184
- cleanup(cause)
185
- resume(Effect.fail(new SqlError({ cause, message: "Connection error" })))
186
- }
187
- function cleanup(cause?: Error) {
188
- if (!done) client?.release(cause)
189
- done = true
190
- client?.off("error", onError)
191
- }
192
- pool.connect((cause, client_) => {
193
- if (cause) {
194
- return resume(Effect.fail(new SqlError({ cause, message: "Failed to acquire connection" })))
195
- } else if (!client_) {
196
- return resume(
197
- Effect.fail(
198
- new SqlError({ message: "Failed to acquire connection", cause: new Error("No client returned") })
199
- )
212
+ function cleanup(cause?: Error) {
213
+ if (!done) client?.release(cause)
214
+ done = true
215
+ client?.off("error", onError)
216
+ }
217
+ pool.connect((cause, client_) => {
218
+ if (cause) {
219
+ return resume(Effect.fail(new SqlError({ cause, message: "Failed to acquire connection" })))
220
+ } else if (!client_) {
221
+ return resume(
222
+ Effect.fail(
223
+ new SqlError({ message: "Failed to acquire connection", cause: new Error("No client returned") })
200
224
  )
201
- } else if (done) {
202
- client_.release()
203
- return
204
- }
205
- client = client_
206
- client.once("error", onError)
207
- cancel = makeCancel(pool, client)
208
- f(client, (eff) => {
209
- cleanup()
210
- resume(eff)
211
- })
212
- })
213
- return Effect.suspend(() => {
214
- if (!cancel) {
215
- cleanup()
216
- return Effect.void
217
- }
218
- return Effect.ensuring(cancel, Effect.sync(cleanup))
225
+ )
226
+ } else if (done) {
227
+ client_.release()
228
+ return
229
+ }
230
+ client = client_
231
+ client.once("error", onError)
232
+ cancel = makeCancel(pool, client)
233
+ f(client, (eff) => {
234
+ cleanup()
235
+ resume(eff)
219
236
  })
220
237
  })
221
- }
238
+ return Effect.suspend(() => {
239
+ if (!cancel) {
240
+ cleanup()
241
+ return Effect.void
242
+ }
243
+ return Effect.ensuring(cancel, Effect.sync(cleanup))
244
+ })
245
+ })
246
+ }
247
+
248
+ private run(query: string, params: ReadonlyArray<unknown>) {
249
+ return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
250
+ client.query(query, params as any, (err, result) => {
251
+ if (err) {
252
+ resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
253
+ } else {
254
+ // Multi-statement queries return an array of results
255
+ resume(Effect.succeed(
256
+ Array.isArray(result)
257
+ ? result.map((r) => r.rows ?? [])
258
+ : result.rows ?? []
259
+ ))
260
+ }
261
+ })
262
+ })
263
+ }
222
264
 
223
- private run(query: string, params: ReadonlyArray<unknown>) {
224
- return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
225
- client.query(query, params as any, (err, result) => {
265
+ execute(
266
+ sql: string,
267
+ params: ReadonlyArray<unknown>,
268
+ transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
269
+ ) {
270
+ return transformRows
271
+ ? Effect.map(this.run(sql, params), transformRows)
272
+ : this.run(sql, params)
273
+ }
274
+ executeRaw(sql: string, params: ReadonlyArray<unknown>) {
275
+ return this.runWithClient<Pg.Result>((client, resume) => {
276
+ client.query(sql, params as any, (err, result) => {
277
+ if (err) {
278
+ resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
279
+ } else {
280
+ resume(Effect.succeed(result))
281
+ }
282
+ })
283
+ })
284
+ }
285
+ executeWithoutTransform(sql: string, params: ReadonlyArray<unknown>) {
286
+ return this.run(sql, params)
287
+ }
288
+ executeValues(sql: string, params: ReadonlyArray<unknown>) {
289
+ return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
290
+ client.query(
291
+ {
292
+ text: sql,
293
+ rowMode: "array",
294
+ values: params as Array<string>
295
+ },
296
+ (err, result) => {
226
297
  if (err) {
227
298
  resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
228
299
  } else {
229
- // Multi-statement queries return an array of results
230
- resume(Effect.succeed(
231
- Array.isArray(result)
232
- ? result.map((r) => r.rows ?? [])
233
- : result.rows ?? []
234
- ))
300
+ resume(Effect.succeed(result.rows))
235
301
  }
236
- })
237
- })
238
- }
239
-
240
- execute(
241
- sql: string,
242
- params: ReadonlyArray<unknown>,
243
- transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
244
- ) {
245
- return transformRows
246
- ? Effect.map(this.run(sql, params), transformRows)
247
- : this.run(sql, params)
248
- }
249
- executeRaw(sql: string, params: ReadonlyArray<unknown>) {
250
- return this.runWithClient<Pg.Result>((client, resume) => {
251
- client.query(sql, params as any, (err, result) => {
302
+ }
303
+ )
304
+ })
305
+ }
306
+ executeUnprepared(
307
+ sql: string,
308
+ params: ReadonlyArray<unknown>,
309
+ transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
310
+ ) {
311
+ return this.execute(sql, params, transformRows)
312
+ }
313
+ executeStream(
314
+ sql: string,
315
+ params: ReadonlyArray<unknown>,
316
+ transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
317
+ ) {
318
+ // oxlint-disable-next-line @typescript-eslint/no-this-alias
319
+ const self = this
320
+ return Stream.fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(_, scope) {
321
+ const client = self.pg ?? (yield* Scope.provide(reserveRaw, scope))
322
+ yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()))
323
+ const cursor = client.query(new Cursor(sql, params as any))
324
+ // @effect-diagnostics-next-line returnEffectInGen:off
325
+ return Effect.callback<Arr.NonEmptyReadonlyArray<any>, SqlError | Cause.Done>((resume) => {
326
+ cursor.read(128, (err, rows) => {
252
327
  if (err) {
253
328
  resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
329
+ } else if (Arr.isArrayNonEmpty(rows)) {
330
+ resume(Effect.succeed(transformRows ? transformRows(rows) as any : rows))
254
331
  } else {
255
- resume(Effect.succeed(result))
332
+ resume(Cause.done())
256
333
  }
257
334
  })
258
335
  })
259
- }
260
- executeWithoutTransform(sql: string, params: ReadonlyArray<unknown>) {
261
- return this.run(sql, params)
262
- }
263
- executeValues(sql: string, params: ReadonlyArray<unknown>) {
264
- return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
265
- client.query(
266
- {
267
- text: sql,
268
- rowMode: "array",
269
- values: params as Array<string>
270
- },
271
- (err, result) => {
272
- if (err) {
273
- resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
274
- } else {
275
- resume(Effect.succeed(result.rows))
276
- }
277
- }
278
- )
279
- })
280
- }
281
- executeUnprepared(
282
- sql: string,
283
- params: ReadonlyArray<unknown>,
284
- transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
285
- ) {
286
- return this.execute(sql, params, transformRows)
287
- }
288
- executeStream(
289
- sql: string,
290
- params: ReadonlyArray<unknown>,
291
- transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
292
- ) {
293
- // eslint-disable-next-line @typescript-eslint/no-this-alias
294
- const self = this
295
- return Effect.gen(function*() {
296
- const scope = yield* Effect.scope
297
- const client = self.pg ?? (yield* reserveRaw)
298
- yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()))
299
- const cursor = client.query(new Cursor(sql, params as any))
300
- const pull = Effect.async<Chunk.Chunk<any>, Option.Option<SqlError>>((resume) => {
301
- cursor.read(128, (err, rows) => {
302
- if (err) {
303
- resume(Effect.fail(Option.some(new SqlError({ cause: err, message: "Failed to execute statement" }))))
304
- } else if (Arr.isNonEmptyArray(rows)) {
305
- resume(Effect.succeed(Chunk.unsafeFromArray(transformRows ? transformRows(rows) as any : rows)))
306
- } else {
307
- resume(Effect.fail(Option.none()))
308
- }
309
- })
310
- })
311
- return Stream.repeatEffectChunkOption(pull)
312
- }).pipe(
313
- Stream.unwrapScoped
314
- )
315
- }
336
+ })))
316
337
  }
338
+ }
317
339
 
318
- const reserveRaw = Effect.async<Pg.PoolClient, SqlError, Scope.Scope>((resume) => {
319
- const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
320
- const scope = Context.unsafeGet(fiber.currentContext, Scope.Scope)
321
- let cause: Error | undefined = undefined
322
- function onError(cause_: Error) {
323
- cause = cause_
324
- }
325
- pool.connect((err, client, release) => {
326
- if (err) {
327
- resume(Effect.fail(new SqlError({ cause: err, message: "Failed to acquire connection for transaction" })))
328
- return
329
- } else if (!client) {
330
- resume(
331
- Effect.fail(
332
- new SqlError({
333
- message: "Failed to acquire connection for transaction",
334
- cause: new Error("No client returned")
335
- })
336
- )
337
- )
338
- return
339
- }
340
-
341
- // Else we know we have client defined, so we can proceed with the connection
342
- client.on("error", onError)
340
+ const reserveRaw = Effect.callback<Pg.PoolClient, SqlError, Scope.Scope>((resume) => {
341
+ const fiber = Fiber.getCurrent()!
342
+ const scope = ServiceMap.getUnsafe(fiber.services, Scope.Scope)
343
+ let cause: Error | undefined = undefined
344
+ pool.connect((err, client, release) => {
345
+ if (err) {
346
+ resume(Effect.fail(new SqlError({ cause: err, message: "Failed to acquire connection for transaction" })))
347
+ } else {
343
348
  resume(Effect.as(
344
349
  Scope.addFinalizer(
345
350
  scope,
346
351
  Effect.sync(() => {
347
- client.off("error", onError)
352
+ client!.off("error", onError)
348
353
  release(cause)
349
354
  })
350
355
  ),
351
- client
356
+ client!
352
357
  ))
353
- })
358
+ }
359
+ function onError(cause_: Error) {
360
+ cause = cause_
361
+ }
362
+ client!.on("error", onError)
354
363
  })
355
- const reserve = Effect.map(reserveRaw, (client) => new ConnectionImpl(client))
364
+ })
365
+ const reserve = Effect.map(reserveRaw, (client) => new ConnectionImpl(client))
356
366
 
357
- const listenClient = yield* RcRef.make({
358
- acquire: reserveRaw
359
- })
367
+ const listenClient = yield* RcRef.make({
368
+ acquire: reserveRaw
369
+ })
360
370
 
361
- let config = options
362
- if (pool.options.connectionString) {
363
- try {
364
- const parsed = PgConnString.parse(pool.options.connectionString)
365
- config = {
366
- ...config,
367
- host: config.host ?? parsed.host ?? undefined,
368
- port: config.port ?? (parsed.port ? Option.getOrUndefined(Number.parse(parsed.port)) : undefined),
369
- username: config.username ?? parsed.user ?? undefined,
370
- password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
371
- database: config.database ?? parsed.database ?? undefined
372
- }
373
- } catch {
374
- //
371
+ let config: PgClientConfig = {
372
+ url: pool.options.connectionString ? Redacted.make(pool.options.connectionString) : undefined,
373
+ host: pool.options.host,
374
+ port: pool.options.port,
375
+ database: pool.options.database,
376
+ username: pool.options.user,
377
+ password: typeof pool.options.password === "string" ? Redacted.make(pool.options.password) : undefined,
378
+ ssl: pool.options.ssl,
379
+ applicationName: pool.options.application_name,
380
+ types: pool.options.types
381
+ }
382
+ if (pool.options.connectionString) {
383
+ // @effect-diagnostics-next-line tryCatchInEffectGen:off
384
+ try {
385
+ const parsed = PgConnString.parse(pool.options.connectionString)
386
+ config = {
387
+ ...config,
388
+ host: config.host ?? parsed.host ?? undefined,
389
+ port: config.port ?? (parsed.port ? Number.parse(parsed.port) : undefined),
390
+ username: config.username ?? parsed.user ?? undefined,
391
+ password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
392
+ database: config.database ?? parsed.database ?? undefined
375
393
  }
394
+ } catch {
395
+ //
376
396
  }
397
+ }
377
398
 
378
- return Object.assign(
379
- yield* Client.make({
380
- acquirer: Effect.succeed(new ConnectionImpl()),
381
- transactionAcquirer: reserve,
382
- compiler,
383
- spanAttributes: [
384
- ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
385
- [ATTR_DB_SYSTEM_NAME, "postgresql"],
386
- [ATTR_DB_NAMESPACE, options.database ?? options.username ?? "postgres"],
387
- [ATTR_SERVER_ADDRESS, options.host ?? "localhost"],
388
- [ATTR_SERVER_PORT, options.port ?? 5432]
389
- ],
390
- transformRows
391
- }),
392
- {
393
- [TypeId]: TypeId as TypeId,
394
- config,
395
- json: (_: unknown) => PgJson(_),
396
- listen: (channel: string) =>
397
- Stream.asyncPush<string, SqlError>(Effect.fnUntraced(function*(emit) {
398
- const client = yield* RcRef.get(listenClient)
399
- function onNotification(msg: Pg.Notification) {
400
- if (msg.channel === channel && msg.payload) {
401
- emit.single(msg.payload)
402
- }
399
+ return Object.assign(
400
+ yield* Client.make({
401
+ acquirer: Effect.succeed(new ConnectionImpl()),
402
+ transactionAcquirer: reserve,
403
+ compiler,
404
+ spanAttributes: [
405
+ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
406
+ [ATTR_DB_SYSTEM_NAME, "postgresql"],
407
+ [ATTR_DB_NAMESPACE, config.database ?? config.username ?? "postgres"],
408
+ [ATTR_SERVER_ADDRESS, config.host ?? "localhost"],
409
+ [ATTR_SERVER_PORT, config.port ?? 5432]
410
+ ],
411
+ transformRows
412
+ }),
413
+ {
414
+ [TypeId]: TypeId as TypeId,
415
+ config,
416
+ json: (_: unknown) => Statement.fragment([PgJson(_)]),
417
+ listen: (channel: string) =>
418
+ Stream.callback<string, SqlError>(Effect.fnUntraced(function*(queue) {
419
+ const client = yield* RcRef.get(listenClient)
420
+ function onNotification(msg: Pg.Notification) {
421
+ if (msg.channel === channel && msg.payload) {
422
+ Queue.offerUnsafe(queue, msg.payload)
403
423
  }
404
- yield* Effect.addFinalizer(() =>
405
- Effect.promise(() => {
406
- client.off("notification", onNotification)
407
- return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`)
408
- })
409
- )
410
- yield* Effect.tryPromise({
411
- try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
412
- catch: (cause) => new SqlError({ cause, message: "Failed to listen" })
413
- })
414
- client.on("notification", onNotification)
415
- })),
416
- notify: (channel: string, payload: string) =>
417
- Effect.async<void, SqlError>((resume) => {
418
- pool.query(`NOTIFY ${Pg.escapeIdentifier(channel)}, $1`, [payload], (err) => {
419
- if (err) {
420
- resume(Effect.fail(new SqlError({ cause: err, message: "Failed to notify" })))
421
- } else {
422
- resume(Effect.void)
423
- }
424
+ }
425
+ yield* Effect.addFinalizer(() =>
426
+ Effect.promise(() => {
427
+ client.off("notification", onNotification)
428
+ return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`)
424
429
  })
430
+ )
431
+ yield* Effect.tryPromise({
432
+ try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
433
+ catch: (cause) => new SqlError({ cause, message: "Failed to listen" })
425
434
  })
426
- }
427
- )
428
- })
435
+ client.on("notification", onNotification)
436
+ })),
437
+ notify: (channel: string, payload: string) =>
438
+ Effect.callback<void, SqlError>((resume) => {
439
+ pool.query(`NOTIFY ${Pg.escapeIdentifier(channel)}, $1`, [payload], (err) => {
440
+ if (err) {
441
+ resume(Effect.fail(new SqlError({ cause: err, message: "Failed to notify" })))
442
+ } else {
443
+ resume(Effect.void)
444
+ }
445
+ })
446
+ })
447
+ }
448
+ )
449
+ })
429
450
 
430
451
  const cancelEffects = new WeakMap<Pg.PoolClient, Effect.Effect<void> | undefined>()
431
452
  const makeCancel = (pool: Pg.Pool, client: Pg.PoolClient) => {
@@ -435,7 +456,7 @@ const makeCancel = (pool: Pg.Pool, client: Pg.PoolClient) => {
435
456
  const processId = (client as any).processID
436
457
  const eff = processId !== undefined
437
458
  // query cancelation is best-effort, so we don't fail if it doesn't work
438
- ? Effect.async<void>((resume) => {
459
+ ? Effect.callback<void>((resume) => {
439
460
  if (pool.ending) return resume(Effect.void)
440
461
  pool.query(`SELECT pg_cancel_backend(${processId})`, () => {
441
462
  resume(Effect.void)
@@ -453,15 +474,17 @@ const makeCancel = (pool: Pg.Pool, client: Pg.PoolClient) => {
453
474
  * @category layers
454
475
  * @since 1.0.0
455
476
  */
456
- export const layerConfig = (
457
- config: Config.Config.Wrap<PgClientConfig>
458
- ): Layer.Layer<PgClient | Client.SqlClient, ConfigError.ConfigError | SqlError> =>
459
- Layer.scopedContext(
460
- Config.unwrap(config).pipe(
477
+ export const layerConfig: (
478
+ config: Config.Wrap<PgClientConfig>
479
+ ) => Layer.Layer<PgClient | Client.SqlClient, Config.ConfigError | SqlError> = (
480
+ config: Config.Wrap<PgClientConfig>
481
+ ): Layer.Layer<PgClient | Client.SqlClient, Config.ConfigError | SqlError> =>
482
+ Layer.effectServices(
483
+ Config.unwrap(config).asEffect().pipe(
461
484
  Effect.flatMap(make),
462
485
  Effect.map((client) =>
463
- Context.make(PgClient, client).pipe(
464
- Context.add(Client.SqlClient, client)
486
+ ServiceMap.make(PgClient, client).pipe(
487
+ ServiceMap.add(Client.SqlClient, client)
465
488
  )
466
489
  )
467
490
  )
@@ -474,10 +497,32 @@ export const layerConfig = (
474
497
  export const layer = (
475
498
  config: PgClientConfig
476
499
  ): Layer.Layer<PgClient | Client.SqlClient, SqlError> =>
477
- Layer.scopedContext(
500
+ Layer.effectServices(
478
501
  Effect.map(make(config), (client) =>
479
- Context.make(PgClient, client).pipe(
480
- Context.add(Client.SqlClient, client)
502
+ ServiceMap.make(PgClient, client).pipe(
503
+ ServiceMap.add(Client.SqlClient, client)
504
+ ))
505
+ ).pipe(Layer.provide(Reactivity.layer))
506
+
507
+ /**
508
+ * @category layers
509
+ * @since 1.0.0
510
+ */
511
+ export const layerFromPool = (options: {
512
+ readonly acquire: Effect.Effect<Pg.Pool, SqlError, Scope.Scope>
513
+
514
+ readonly applicationName?: string | undefined
515
+ readonly spanAttributes?: Record<string, unknown> | undefined
516
+
517
+ readonly transformResultNames?: ((str: string) => string) | undefined
518
+ readonly transformQueryNames?: ((str: string) => string) | undefined
519
+ readonly transformJson?: boolean | undefined
520
+ readonly types?: Pg.CustomTypesConfig | undefined
521
+ }): Layer.Layer<PgClient | Client.SqlClient, SqlError> =>
522
+ Layer.effectServices(
523
+ Effect.map(fromPool(options), (client) =>
524
+ ServiceMap.make(PgClient, client).pipe(
525
+ ServiceMap.add(Client.SqlClient, client)
481
526
  ))
482
527
  ).pipe(Layer.provide(Reactivity.layer))
483
528
 
@@ -518,8 +563,8 @@ export const makeCompiler = (
518
563
  placeholder(undefined),
519
564
  [
520
565
  withoutTransform || transformValue === undefined
521
- ? type.i0
522
- : transformValue(type.i0)
566
+ ? type.paramA
567
+ : transformValue(type.paramA)
523
568
  ]
524
569
  ]
525
570
  }