@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/dist/{dts/PgClient.d.ts → PgClient.d.ts} +35 -13
- package/dist/PgClient.d.ts.map +1 -0
- package/dist/{esm/PgClient.js → PgClient.js} +105 -83
- package/dist/PgClient.js.map +1 -0
- package/dist/PgMigrator.d.ts +23 -0
- package/dist/PgMigrator.d.ts.map +1 -0
- package/dist/PgMigrator.js +74 -0
- package/dist/PgMigrator.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/{esm/index.js → index.js} +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -54
- package/src/PgClient.ts +353 -308
- package/src/PgMigrator.ts +63 -74
- package/src/index.ts +8 -2
- package/PgClient/package.json +0 -6
- package/PgMigrator/package.json +0 -6
- package/dist/cjs/PgClient.js +0 -385
- package/dist/cjs/PgClient.js.map +0 -1
- package/dist/cjs/PgMigrator.js +0 -104
- package/dist/cjs/PgMigrator.js.map +0 -1
- package/dist/cjs/index.js +0 -12
- package/dist/cjs/index.js.map +0 -1
- package/dist/dts/PgClient.d.ts.map +0 -1
- package/dist/dts/PgMigrator.d.ts +0 -28
- package/dist/dts/PgMigrator.d.ts.map +0 -1
- package/dist/dts/index.d.ts +0 -9
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/esm/PgClient.js.map +0 -1
- package/dist/esm/PgMigrator.js +0 -68
- package/dist/esm/PgMigrator.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/index/package.json +0 -6
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
options.
|
|
114
|
-
options.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
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
|
-
|
|
364
|
+
})
|
|
365
|
+
const reserve = Effect.map(reserveRaw, (client) => new ConnectionImpl(client))
|
|
356
366
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
367
|
+
const listenClient = yield* RcRef.make({
|
|
368
|
+
acquire: reserveRaw
|
|
369
|
+
})
|
|
360
370
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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.
|
|
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.
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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.
|
|
500
|
+
Layer.effectServices(
|
|
478
501
|
Effect.map(make(config), (client) =>
|
|
479
|
-
|
|
480
|
-
|
|
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.
|
|
522
|
-
: transformValue(type.
|
|
566
|
+
? type.paramA
|
|
567
|
+
: transformValue(type.paramA)
|
|
523
568
|
]
|
|
524
569
|
]
|
|
525
570
|
}
|