@effect/sql-pg 0.50.3 → 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} +20 -25
- package/dist/PgClient.d.ts.map +1 -0
- package/dist/{esm/PgClient.js → PgClient.js} +118 -144
- 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 +332 -377
- 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 -434
- 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
|
|
@@ -96,381 +96,277 @@ export interface PgClientConfig {
|
|
|
96
96
|
readonly types?: Pg.CustomTypesConfig | undefined
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const makeClient = (
|
|
107
|
-
pool: Pg.Pool,
|
|
108
|
-
config: PgClientConfig,
|
|
109
|
-
options: ClientOptions
|
|
99
|
+
/**
|
|
100
|
+
* @category constructors
|
|
101
|
+
* @since 1.0.0
|
|
102
|
+
*/
|
|
103
|
+
export const make = (
|
|
104
|
+
options: PgClientConfig
|
|
110
105
|
): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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) => {})
|
|
134
|
+
|
|
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
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
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>
|
|
168
|
+
|
|
169
|
+
readonly applicationName?: string | undefined
|
|
170
|
+
readonly spanAttributes?: Record<string, unknown> | undefined
|
|
171
|
+
|
|
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,
|
|
114
185
|
options.transformJson
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
Statement.defaultTransforms(
|
|
118
|
-
options.transformResultNames,
|
|
119
|
-
options.transformJson
|
|
120
|
-
).array :
|
|
121
|
-
undefined
|
|
122
|
-
|
|
123
|
-
class ConnectionImpl implements Connection {
|
|
124
|
-
readonly pg: Pg.PoolClient | undefined
|
|
125
|
-
constructor(pg?: Pg.PoolClient) {
|
|
126
|
-
this.pg = pg
|
|
127
|
-
}
|
|
186
|
+
).array :
|
|
187
|
+
undefined
|
|
128
188
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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" })))
|
|
135
211
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
pool.connect((cause, client_) => {
|
|
150
|
-
if (cause) {
|
|
151
|
-
return resume(Effect.fail(new SqlError({ cause, message: "Failed to acquire connection" })))
|
|
152
|
-
} else if (!client_) {
|
|
153
|
-
return resume(
|
|
154
|
-
Effect.fail(
|
|
155
|
-
new SqlError({ message: "Failed to acquire connection", cause: new Error("No client returned") })
|
|
156
|
-
)
|
|
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") })
|
|
157
224
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
})
|
|
170
|
-
return Effect.suspend(() => {
|
|
171
|
-
if (!cancel) {
|
|
172
|
-
cleanup()
|
|
173
|
-
return Effect.void
|
|
174
|
-
}
|
|
175
|
-
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)
|
|
176
236
|
})
|
|
177
237
|
})
|
|
178
|
-
|
|
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
|
+
}
|
|
179
247
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
264
|
+
|
|
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) => {
|
|
183
297
|
if (err) {
|
|
184
298
|
resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
|
|
185
299
|
} else {
|
|
186
|
-
|
|
187
|
-
resume(Effect.succeed(
|
|
188
|
-
Array.isArray(result)
|
|
189
|
-
? result.map((r) => r.rows ?? [])
|
|
190
|
-
: result.rows ?? []
|
|
191
|
-
))
|
|
300
|
+
resume(Effect.succeed(result.rows))
|
|
192
301
|
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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) => {
|
|
209
327
|
if (err) {
|
|
210
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))
|
|
211
331
|
} else {
|
|
212
|
-
resume(
|
|
332
|
+
resume(Cause.done())
|
|
213
333
|
}
|
|
214
334
|
})
|
|
215
335
|
})
|
|
216
|
-
}
|
|
217
|
-
executeWithoutTransform(sql: string, params: ReadonlyArray<unknown>) {
|
|
218
|
-
return this.run(sql, params)
|
|
219
|
-
}
|
|
220
|
-
executeValues(sql: string, params: ReadonlyArray<unknown>) {
|
|
221
|
-
return this.runWithClient<ReadonlyArray<any>>((client, resume) => {
|
|
222
|
-
client.query(
|
|
223
|
-
{
|
|
224
|
-
text: sql,
|
|
225
|
-
rowMode: "array",
|
|
226
|
-
values: params as Array<string>
|
|
227
|
-
},
|
|
228
|
-
(err, result) => {
|
|
229
|
-
if (err) {
|
|
230
|
-
resume(Effect.fail(new SqlError({ cause: err, message: "Failed to execute statement" })))
|
|
231
|
-
} else {
|
|
232
|
-
resume(Effect.succeed(result.rows))
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
executeUnprepared(
|
|
239
|
-
sql: string,
|
|
240
|
-
params: ReadonlyArray<unknown>,
|
|
241
|
-
transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
|
|
242
|
-
) {
|
|
243
|
-
return this.execute(sql, params, transformRows)
|
|
244
|
-
}
|
|
245
|
-
executeStream(
|
|
246
|
-
sql: string,
|
|
247
|
-
params: ReadonlyArray<unknown>,
|
|
248
|
-
transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
|
|
249
|
-
) {
|
|
250
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
251
|
-
const self = this
|
|
252
|
-
return Effect.gen(function*() {
|
|
253
|
-
const scope = yield* Effect.scope
|
|
254
|
-
const client = self.pg ?? (yield* reserveRaw)
|
|
255
|
-
yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()))
|
|
256
|
-
const cursor = client.query(new Cursor(sql, params as any))
|
|
257
|
-
const pull = Effect.async<Chunk.Chunk<any>, Option.Option<SqlError>>((resume) => {
|
|
258
|
-
cursor.read(128, (err, rows) => {
|
|
259
|
-
if (err) {
|
|
260
|
-
resume(Effect.fail(Option.some(new SqlError({ cause: err, message: "Failed to execute statement" }))))
|
|
261
|
-
} else if (Arr.isNonEmptyArray(rows)) {
|
|
262
|
-
resume(Effect.succeed(Chunk.unsafeFromArray(transformRows ? transformRows(rows) as any : rows)))
|
|
263
|
-
} else {
|
|
264
|
-
resume(Effect.fail(Option.none()))
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
return Stream.repeatEffectChunkOption(pull)
|
|
269
|
-
}).pipe(
|
|
270
|
-
Stream.unwrapScoped
|
|
271
|
-
)
|
|
272
|
-
}
|
|
336
|
+
})))
|
|
273
337
|
}
|
|
338
|
+
}
|
|
274
339
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (err) {
|
|
284
|
-
resume(Effect.fail(new SqlError({ cause: err, message: "Failed to acquire connection for transaction" })))
|
|
285
|
-
return
|
|
286
|
-
} else if (!client) {
|
|
287
|
-
resume(
|
|
288
|
-
Effect.fail(
|
|
289
|
-
new SqlError({
|
|
290
|
-
message: "Failed to acquire connection for transaction",
|
|
291
|
-
cause: new Error("No client returned")
|
|
292
|
-
})
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Else we know we have client defined, so we can proceed with the connection
|
|
299
|
-
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 {
|
|
300
348
|
resume(Effect.as(
|
|
301
349
|
Scope.addFinalizer(
|
|
302
350
|
scope,
|
|
303
351
|
Effect.sync(() => {
|
|
304
|
-
client
|
|
352
|
+
client!.off("error", onError)
|
|
305
353
|
release(cause)
|
|
306
354
|
})
|
|
307
355
|
),
|
|
308
|
-
client
|
|
356
|
+
client!
|
|
309
357
|
))
|
|
310
|
-
})
|
|
311
|
-
})
|
|
312
|
-
const reserve = Effect.map(reserveRaw, (client) => new ConnectionImpl(client))
|
|
313
|
-
|
|
314
|
-
const listenClient = yield* RcRef.make({
|
|
315
|
-
acquire: reserveRaw
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
return Object.assign(
|
|
319
|
-
yield* Client.make({
|
|
320
|
-
acquirer: Effect.succeed(new ConnectionImpl()),
|
|
321
|
-
transactionAcquirer: reserve,
|
|
322
|
-
compiler,
|
|
323
|
-
spanAttributes: [
|
|
324
|
-
...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
|
|
325
|
-
[ATTR_DB_SYSTEM_NAME, "postgresql"],
|
|
326
|
-
[ATTR_DB_NAMESPACE, config.database ?? config.username ?? "postgres"],
|
|
327
|
-
[ATTR_SERVER_ADDRESS, config.host ?? "localhost"],
|
|
328
|
-
[ATTR_SERVER_PORT, config.port ?? 5432]
|
|
329
|
-
],
|
|
330
|
-
transformRows
|
|
331
|
-
}),
|
|
332
|
-
{
|
|
333
|
-
[TypeId]: TypeId as TypeId,
|
|
334
|
-
config,
|
|
335
|
-
json: (_: unknown) => PgJson(_),
|
|
336
|
-
listen: (channel: string) =>
|
|
337
|
-
Stream.asyncPush<string, SqlError>(Effect.fnUntraced(function*(emit) {
|
|
338
|
-
const client = yield* RcRef.get(listenClient)
|
|
339
|
-
function onNotification(msg: Pg.Notification) {
|
|
340
|
-
if (msg.channel === channel && msg.payload) {
|
|
341
|
-
emit.single(msg.payload)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
yield* Effect.addFinalizer(() =>
|
|
345
|
-
Effect.promise(() => {
|
|
346
|
-
client.off("notification", onNotification)
|
|
347
|
-
return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`)
|
|
348
|
-
})
|
|
349
|
-
)
|
|
350
|
-
yield* Effect.tryPromise({
|
|
351
|
-
try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
|
|
352
|
-
catch: (cause) => new SqlError({ cause, message: "Failed to listen" })
|
|
353
|
-
})
|
|
354
|
-
client.on("notification", onNotification)
|
|
355
|
-
})),
|
|
356
|
-
notify: (channel: string, payload: string) =>
|
|
357
|
-
Effect.async<void, SqlError>((resume) => {
|
|
358
|
-
pool.query(`NOTIFY ${Pg.escapeIdentifier(channel)}, $1`, [payload], (err) => {
|
|
359
|
-
if (err) {
|
|
360
|
-
resume(Effect.fail(new SqlError({ cause: err, message: "Failed to notify" })))
|
|
361
|
-
} else {
|
|
362
|
-
resume(Effect.void)
|
|
363
|
-
}
|
|
364
|
-
})
|
|
365
|
-
})
|
|
366
358
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* @category constructors
|
|
372
|
-
* @since 1.0.0
|
|
373
|
-
*/
|
|
374
|
-
export const make = (
|
|
375
|
-
options: PgClientConfig
|
|
376
|
-
): Effect.Effect<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
|
|
377
|
-
Effect.gen(function*() {
|
|
378
|
-
const pool = new Pg.Pool({
|
|
379
|
-
connectionString: options.url ? Redacted.value(options.url) : undefined,
|
|
380
|
-
user: options.username,
|
|
381
|
-
host: options.host,
|
|
382
|
-
database: options.database,
|
|
383
|
-
password: options.password ? Redacted.value(options.password) : undefined,
|
|
384
|
-
ssl: options.ssl,
|
|
385
|
-
port: options.port,
|
|
386
|
-
...(options.stream ? { stream: options.stream } : {}),
|
|
387
|
-
connectionTimeoutMillis: options.connectTimeout
|
|
388
|
-
? Duration.toMillis(options.connectTimeout)
|
|
389
|
-
: undefined,
|
|
390
|
-
idleTimeoutMillis: options.idleTimeout
|
|
391
|
-
? Duration.toMillis(options.idleTimeout)
|
|
392
|
-
: undefined,
|
|
393
|
-
max: options.maxConnections,
|
|
394
|
-
min: options.minConnections,
|
|
395
|
-
maxLifetimeSeconds: options.connectionTTL
|
|
396
|
-
? Duration.toSeconds(options.connectionTTL)
|
|
397
|
-
: undefined,
|
|
398
|
-
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
399
|
-
types: options.types
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
pool.on("error", (_err) => {
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
yield* Effect.acquireRelease(
|
|
406
|
-
Effect.tryPromise({
|
|
407
|
-
try: () => pool.query("SELECT 1"),
|
|
408
|
-
catch: (cause) => new SqlError({ cause, message: "PgClient: Failed to connect" })
|
|
409
|
-
}),
|
|
410
|
-
() =>
|
|
411
|
-
Effect.promise(() => pool.end()).pipe(
|
|
412
|
-
Effect.interruptible,
|
|
413
|
-
Effect.timeoutOption(1000)
|
|
414
|
-
)
|
|
415
|
-
).pipe(
|
|
416
|
-
Effect.timeoutFail({
|
|
417
|
-
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
418
|
-
onTimeout: () =>
|
|
419
|
-
new SqlError({
|
|
420
|
-
cause: new Error("Connection timed out"),
|
|
421
|
-
message: "PgClient: Connection timed out"
|
|
422
|
-
})
|
|
423
|
-
})
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
let config = options
|
|
427
|
-
if (pool.options.connectionString) {
|
|
428
|
-
try {
|
|
429
|
-
const parsed = PgConnString.parse(pool.options.connectionString)
|
|
430
|
-
config = {
|
|
431
|
-
...config,
|
|
432
|
-
host: config.host ?? parsed.host ?? undefined,
|
|
433
|
-
port: config.port ?? (parsed.port ? Option.getOrUndefined(Number.parse(parsed.port)) : undefined),
|
|
434
|
-
username: config.username ?? parsed.user ?? undefined,
|
|
435
|
-
password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
|
|
436
|
-
database: config.database ?? parsed.database ?? undefined
|
|
437
|
-
}
|
|
438
|
-
} catch {
|
|
439
|
-
//
|
|
359
|
+
function onError(cause_: Error) {
|
|
360
|
+
cause = cause_
|
|
440
361
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return yield* makeClient(pool, config, options)
|
|
362
|
+
client!.on("error", onError)
|
|
363
|
+
})
|
|
444
364
|
})
|
|
365
|
+
const reserve = Effect.map(reserveRaw, (client) => new ConnectionImpl(client))
|
|
445
366
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
*/
|
|
450
|
-
export interface PgClientFromPoolOptions {
|
|
451
|
-
readonly acquire: Effect.Effect<Pg.Pool, SqlError, Scope.Scope>
|
|
452
|
-
|
|
453
|
-
readonly applicationName?: string | undefined
|
|
454
|
-
readonly spanAttributes?: Record<string, unknown> | undefined
|
|
455
|
-
|
|
456
|
-
readonly transformResultNames?: ((str: string) => string) | undefined
|
|
457
|
-
readonly transformQueryNames?: ((str: string) => string) | undefined
|
|
458
|
-
readonly transformJson?: boolean | undefined
|
|
459
|
-
readonly types?: Pg.CustomTypesConfig | undefined
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Create a `PgClient` from an existing `pg` pool.
|
|
464
|
-
*
|
|
465
|
-
* You control the pool lifecycle via `acquire` (typically `Effect.acquireRelease`).
|
|
466
|
-
*
|
|
467
|
-
* @category constructors
|
|
468
|
-
* @since 1.0.0
|
|
469
|
-
*/
|
|
470
|
-
export const fromPool = Effect.fnUntraced(function*(
|
|
471
|
-
options: PgClientFromPoolOptions
|
|
472
|
-
): Effect.fn.Return<PgClient, SqlError, Scope.Scope | Reactivity.Reactivity> {
|
|
473
|
-
const pool = yield* options.acquire
|
|
367
|
+
const listenClient = yield* RcRef.make({
|
|
368
|
+
acquire: reserveRaw
|
|
369
|
+
})
|
|
474
370
|
|
|
475
371
|
let config: PgClientConfig = {
|
|
476
372
|
url: pool.options.connectionString ? Redacted.make(pool.options.connectionString) : undefined,
|
|
@@ -480,17 +376,17 @@ export const fromPool = Effect.fnUntraced(function*(
|
|
|
480
376
|
username: pool.options.user,
|
|
481
377
|
password: typeof pool.options.password === "string" ? Redacted.make(pool.options.password) : undefined,
|
|
482
378
|
ssl: pool.options.ssl,
|
|
483
|
-
applicationName:
|
|
379
|
+
applicationName: pool.options.application_name,
|
|
484
380
|
types: pool.options.types
|
|
485
381
|
}
|
|
486
|
-
|
|
487
382
|
if (pool.options.connectionString) {
|
|
383
|
+
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
488
384
|
try {
|
|
489
385
|
const parsed = PgConnString.parse(pool.options.connectionString)
|
|
490
386
|
config = {
|
|
491
387
|
...config,
|
|
492
388
|
host: config.host ?? parsed.host ?? undefined,
|
|
493
|
-
port: config.port ?? (parsed.port ?
|
|
389
|
+
port: config.port ?? (parsed.port ? Number.parse(parsed.port) : undefined),
|
|
494
390
|
username: config.username ?? parsed.user ?? undefined,
|
|
495
391
|
password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
|
|
496
392
|
database: config.database ?? parsed.database ?? undefined
|
|
@@ -500,7 +396,56 @@ export const fromPool = Effect.fnUntraced(function*(
|
|
|
500
396
|
}
|
|
501
397
|
}
|
|
502
398
|
|
|
503
|
-
return
|
|
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)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
yield* Effect.addFinalizer(() =>
|
|
426
|
+
Effect.promise(() => {
|
|
427
|
+
client.off("notification", onNotification)
|
|
428
|
+
return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`)
|
|
429
|
+
})
|
|
430
|
+
)
|
|
431
|
+
yield* Effect.tryPromise({
|
|
432
|
+
try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
|
|
433
|
+
catch: (cause) => new SqlError({ cause, message: "Failed to listen" })
|
|
434
|
+
})
|
|
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
|
+
)
|
|
504
449
|
})
|
|
505
450
|
|
|
506
451
|
const cancelEffects = new WeakMap<Pg.PoolClient, Effect.Effect<void> | undefined>()
|
|
@@ -511,7 +456,7 @@ const makeCancel = (pool: Pg.Pool, client: Pg.PoolClient) => {
|
|
|
511
456
|
const processId = (client as any).processID
|
|
512
457
|
const eff = processId !== undefined
|
|
513
458
|
// query cancelation is best-effort, so we don't fail if it doesn't work
|
|
514
|
-
? Effect.
|
|
459
|
+
? Effect.callback<void>((resume) => {
|
|
515
460
|
if (pool.ending) return resume(Effect.void)
|
|
516
461
|
pool.query(`SELECT pg_cancel_backend(${processId})`, () => {
|
|
517
462
|
resume(Effect.void)
|
|
@@ -529,15 +474,17 @@ const makeCancel = (pool: Pg.Pool, client: Pg.PoolClient) => {
|
|
|
529
474
|
* @category layers
|
|
530
475
|
* @since 1.0.0
|
|
531
476
|
*/
|
|
532
|
-
export const layerConfig
|
|
533
|
-
config: Config.
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
|
|
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(
|
|
537
484
|
Effect.flatMap(make),
|
|
538
485
|
Effect.map((client) =>
|
|
539
|
-
|
|
540
|
-
|
|
486
|
+
ServiceMap.make(PgClient, client).pipe(
|
|
487
|
+
ServiceMap.add(Client.SqlClient, client)
|
|
541
488
|
)
|
|
542
489
|
)
|
|
543
490
|
)
|
|
@@ -550,10 +497,10 @@ export const layerConfig = (
|
|
|
550
497
|
export const layer = (
|
|
551
498
|
config: PgClientConfig
|
|
552
499
|
): Layer.Layer<PgClient | Client.SqlClient, SqlError> =>
|
|
553
|
-
Layer.
|
|
500
|
+
Layer.effectServices(
|
|
554
501
|
Effect.map(make(config), (client) =>
|
|
555
|
-
|
|
556
|
-
|
|
502
|
+
ServiceMap.make(PgClient, client).pipe(
|
|
503
|
+
ServiceMap.add(Client.SqlClient, client)
|
|
557
504
|
))
|
|
558
505
|
).pipe(Layer.provide(Reactivity.layer))
|
|
559
506
|
|
|
@@ -561,13 +508,21 @@ export const layer = (
|
|
|
561
508
|
* @category layers
|
|
562
509
|
* @since 1.0.0
|
|
563
510
|
*/
|
|
564
|
-
export const layerFromPool = (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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(
|
|
568
523
|
Effect.map(fromPool(options), (client) =>
|
|
569
|
-
|
|
570
|
-
|
|
524
|
+
ServiceMap.make(PgClient, client).pipe(
|
|
525
|
+
ServiceMap.add(Client.SqlClient, client)
|
|
571
526
|
))
|
|
572
527
|
).pipe(Layer.provide(Reactivity.layer))
|
|
573
528
|
|
|
@@ -608,8 +563,8 @@ export const makeCompiler = (
|
|
|
608
563
|
placeholder(undefined),
|
|
609
564
|
[
|
|
610
565
|
withoutTransform || transformValue === undefined
|
|
611
|
-
? type.
|
|
612
|
-
: transformValue(type.
|
|
566
|
+
? type.paramA
|
|
567
|
+
: transformValue(type.paramA)
|
|
613
568
|
]
|
|
614
569
|
]
|
|
615
570
|
}
|