@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/dist/PgClient.js
CHANGED
|
@@ -1,532 +1,141 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Connects Effect SQL to PostgreSQL using the `pg` package.
|
|
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.
|
|
11
|
-
*
|
|
12
|
-
* @since 4.0.0
|
|
13
|
-
*/
|
|
14
|
-
import * as Arr from "effect/Array";
|
|
15
|
-
import * as Cause from "effect/Cause";
|
|
16
|
-
import * as Channel from "effect/Channel";
|
|
17
1
|
import * as Config from "effect/Config";
|
|
18
2
|
import * as Context from "effect/Context";
|
|
19
|
-
import * as Duration from "effect/Duration";
|
|
20
3
|
import * as Effect from "effect/Effect";
|
|
21
|
-
import * as Fiber from "effect/Fiber";
|
|
22
4
|
import * as Layer from "effect/Layer";
|
|
23
|
-
import * as Number from "effect/Number";
|
|
24
|
-
import * as Option from "effect/Option";
|
|
25
|
-
import * as Queue from "effect/Queue";
|
|
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";
|
|
30
5
|
import * as Stream from "effect/Stream";
|
|
31
6
|
import * as Reactivity from "effect/unstable/reactivity/Reactivity";
|
|
32
7
|
import * as Client from "effect/unstable/sql/SqlClient";
|
|
33
|
-
import { AuthenticationError, AuthorizationError, ConnectionError, ConstraintError, DeadlockError, LockTimeoutError, SerializationError, SqlError, SqlSyntaxError, StatementTimeoutError, UniqueViolation, UnknownError } from "effect/unstable/sql/SqlError";
|
|
34
8
|
import * as Statement from "effect/unstable/sql/Statement";
|
|
35
|
-
import
|
|
36
|
-
import * as
|
|
37
|
-
import
|
|
9
|
+
import { validateChannelName } from "./internal/sqlError.js";
|
|
10
|
+
import * as PgConnection from "./PgConnection.js";
|
|
11
|
+
import * as PgPool from "./PgPool.js";
|
|
12
|
+
import * as PgTypes from "./PgTypes.js";
|
|
38
13
|
/**
|
|
39
|
-
*
|
|
14
|
+
* The runtime type identifier for `PgClient`.
|
|
40
15
|
*
|
|
41
16
|
* @category type IDs
|
|
42
17
|
* @since 4.0.0
|
|
43
18
|
*/
|
|
44
19
|
export const TypeId = "~@effect/sql-pg/PgClient";
|
|
45
20
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* **When to use**
|
|
49
|
-
*
|
|
50
|
-
* Use to access or provide a PostgreSQL client through the Effect context.
|
|
21
|
+
* The service tag for `PgClient`.
|
|
51
22
|
*
|
|
52
23
|
* @category services
|
|
53
24
|
* @since 4.0.0
|
|
54
25
|
*/
|
|
55
26
|
export const PgClient = /*#__PURE__*/Context.Service("@effect/sql-pg/PgClient");
|
|
56
27
|
/**
|
|
57
|
-
* Creates a scoped PostgreSQL client backed by a
|
|
58
|
-
*
|
|
59
|
-
* @category constructors
|
|
60
|
-
* @since 4.0.0
|
|
61
|
-
*/
|
|
62
|
-
export const make = options => fromPool({
|
|
63
|
-
...options,
|
|
64
|
-
acquire: Effect.gen(function* () {
|
|
65
|
-
const pool = new Pg.Pool({
|
|
66
|
-
connectionString: options.url ? Redacted.value(options.url) : undefined,
|
|
67
|
-
user: options.username,
|
|
68
|
-
host: options.host,
|
|
69
|
-
database: options.database,
|
|
70
|
-
password: options.password ? Redacted.value(options.password) : undefined,
|
|
71
|
-
ssl: options.ssl,
|
|
72
|
-
port: options.port,
|
|
73
|
-
...(options.stream ? {
|
|
74
|
-
stream: options.stream
|
|
75
|
-
} : {}),
|
|
76
|
-
connectionTimeoutMillis: options.connectTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.connectTimeout)) : undefined,
|
|
77
|
-
idleTimeoutMillis: options.idleTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.idleTimeout)) : undefined,
|
|
78
|
-
max: options.maxConnections,
|
|
79
|
-
min: options.minConnections,
|
|
80
|
-
maxLifetimeSeconds: options.connectionTTL ? Duration.toSeconds(Duration.fromInputUnsafe(options.connectionTTL)) : undefined,
|
|
81
|
-
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
82
|
-
types: options.types
|
|
83
|
-
});
|
|
84
|
-
pool.on("error", _err => {});
|
|
85
|
-
yield* Effect.acquireRelease(Effect.tryPromise({
|
|
86
|
-
try: () => pool.query("SELECT 1"),
|
|
87
|
-
catch: cause => new SqlError({
|
|
88
|
-
reason: classifyError(cause, "PgClient: Failed to connect", "connect")
|
|
89
|
-
})
|
|
90
|
-
}), () => Effect.promise(() => pool.end()).pipe(Effect.timeoutOption(1000)), {
|
|
91
|
-
interruptible: true
|
|
92
|
-
}).pipe(Effect.timeoutOrElse({
|
|
93
|
-
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
94
|
-
orElse: () => Effect.fail(new SqlError({
|
|
95
|
-
reason: new ConnectionError({
|
|
96
|
-
cause: new Error("Connection timed out"),
|
|
97
|
-
message: "PgClient: Connection timed out",
|
|
98
|
-
operation: "connect"
|
|
99
|
-
})
|
|
100
|
-
}))
|
|
101
|
-
}));
|
|
102
|
-
return pool;
|
|
103
|
-
})
|
|
104
|
-
});
|
|
105
|
-
/**
|
|
106
|
-
* Creates a scoped PostgreSQL client backed by a managed single `pg` client, optionally acquiring a separate client for streaming and LISTEN operations.
|
|
107
|
-
*
|
|
108
|
-
* @category constructors
|
|
109
|
-
* @since 4.0.0
|
|
110
|
-
*/
|
|
111
|
-
export const makeClient = options => {
|
|
112
|
-
function onError() {}
|
|
113
|
-
return fromClient({
|
|
114
|
-
...options,
|
|
115
|
-
acquire: Effect.acquireRelease(Effect.tryPromise({
|
|
116
|
-
try: async () => {
|
|
117
|
-
const client = new Pg.Client({
|
|
118
|
-
connectionString: options.url ? Redacted.value(options.url) : undefined,
|
|
119
|
-
user: options.username,
|
|
120
|
-
host: options.host,
|
|
121
|
-
database: options.database,
|
|
122
|
-
password: options.password ? Redacted.value(options.password) : undefined,
|
|
123
|
-
ssl: options.ssl,
|
|
124
|
-
port: options.port,
|
|
125
|
-
...(options.stream ? {
|
|
126
|
-
stream: options.stream
|
|
127
|
-
} : {}),
|
|
128
|
-
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
129
|
-
types: options.types
|
|
130
|
-
});
|
|
131
|
-
client.on("error", onError);
|
|
132
|
-
await client.connect();
|
|
133
|
-
return client;
|
|
134
|
-
},
|
|
135
|
-
catch: cause => new SqlError({
|
|
136
|
-
reason: classifyError(cause, "PgClient: Failed to connect", "connect")
|
|
137
|
-
})
|
|
138
|
-
}), client => Effect.promise(() => {
|
|
139
|
-
client.off("error", onError);
|
|
140
|
-
return client.end();
|
|
141
|
-
}).pipe(Effect.timeoutOption(1000)), {
|
|
142
|
-
interruptible: true
|
|
143
|
-
}).pipe(Effect.timeoutOrElse({
|
|
144
|
-
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
145
|
-
orElse: () => Effect.fail(new SqlError({
|
|
146
|
-
reason: new ConnectionError({
|
|
147
|
-
cause: new Error("Connection timed out"),
|
|
148
|
-
message: "PgClient: Connection timed out",
|
|
149
|
-
operation: "connect"
|
|
150
|
-
})
|
|
151
|
-
}))
|
|
152
|
-
})),
|
|
153
|
-
acquireForStream: options.acquireForStream ?? false
|
|
154
|
-
});
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Builds a PostgreSQL client from a scoped `pg` pool acquisition effect, deriving transaction, streaming, and LISTEN/NOTIFY support from that pool.
|
|
28
|
+
* Creates a scoped PostgreSQL client backed by a connection pool.
|
|
158
29
|
*
|
|
159
30
|
* @category constructors
|
|
160
31
|
* @since 4.0.0
|
|
161
32
|
*/
|
|
162
|
-
export const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
return Effect.callback(resume => {
|
|
172
|
-
let done = false;
|
|
173
|
-
let cancel = undefined;
|
|
174
|
-
let client = undefined;
|
|
175
|
-
function onError(cause) {
|
|
176
|
-
cleanup(cause);
|
|
177
|
-
resume(Effect.fail(new SqlError({
|
|
178
|
-
reason: classifyError(cause, "Connection error", "acquireConnection")
|
|
179
|
-
})));
|
|
180
|
-
}
|
|
181
|
-
function cleanup(cause) {
|
|
182
|
-
if (!done) client?.release(cause);
|
|
183
|
-
done = true;
|
|
184
|
-
client?.off("error", onError);
|
|
185
|
-
}
|
|
186
|
-
pool.connect((cause, client_) => {
|
|
187
|
-
if (cause) {
|
|
188
|
-
return resume(Effect.fail(new SqlError({
|
|
189
|
-
reason: classifyError(cause, "Failed to acquire connection", "acquireConnection")
|
|
190
|
-
})));
|
|
191
|
-
} else if (!client_) {
|
|
192
|
-
return resume(Effect.fail(new SqlError({
|
|
193
|
-
reason: new ConnectionError({
|
|
194
|
-
message: "Failed to acquire connection",
|
|
195
|
-
cause: new Error("No client returned"),
|
|
196
|
-
operation: "acquireConnection"
|
|
197
|
-
})
|
|
198
|
-
})));
|
|
199
|
-
} else if (done) {
|
|
200
|
-
client_.release();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
client = client_;
|
|
204
|
-
client.once("error", onError);
|
|
205
|
-
cancel = makeCancel(pool, client);
|
|
206
|
-
f(client, eff => {
|
|
207
|
-
cleanup();
|
|
208
|
-
resume(eff);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
return Effect.suspend(() => {
|
|
212
|
-
if (!cancel) {
|
|
213
|
-
cleanup();
|
|
214
|
-
return Effect.void;
|
|
215
|
-
}
|
|
216
|
-
return Effect.ensuring(cancel, Effect.sync(cleanup));
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}, client ? Effect.succeed(client) : reserveRaw);
|
|
220
|
-
const reserveRaw = Effect.callback(resume => {
|
|
221
|
-
const fiber = Fiber.getCurrent();
|
|
222
|
-
const scope = Context.getUnsafe(fiber.context, Scope.Scope);
|
|
223
|
-
let cause = undefined;
|
|
224
|
-
function onError(cause_) {
|
|
225
|
-
cause = cause_;
|
|
226
|
-
}
|
|
227
|
-
pool.connect((err, client, release) => {
|
|
228
|
-
if (err) {
|
|
229
|
-
return resume(Effect.fail(new SqlError({
|
|
230
|
-
reason: classifyError(err, "Failed to acquire connection for transaction", "acquireConnection")
|
|
231
|
-
})));
|
|
232
|
-
} else if (!client) {
|
|
233
|
-
return resume(Effect.fail(new SqlError({
|
|
234
|
-
reason: new ConnectionError({
|
|
235
|
-
message: "Failed to acquire connection for transaction",
|
|
236
|
-
cause: new Error("No client returned"),
|
|
237
|
-
operation: "acquireConnection"
|
|
238
|
-
})
|
|
239
|
-
})));
|
|
240
|
-
}
|
|
241
|
-
client.on("error", onError);
|
|
242
|
-
resume(Effect.as(Scope.addFinalizer(scope, Effect.sync(() => {
|
|
243
|
-
client.off("error", onError);
|
|
244
|
-
release(cause);
|
|
245
|
-
})), client));
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
const reserve = Effect.map(reserveRaw, makeConection);
|
|
249
|
-
const onListenClientError = _ => {};
|
|
250
|
-
const listenAcquirer = yield* RcRef.make({
|
|
251
|
-
acquire: Effect.acquireRelease(Effect.tryPromise({
|
|
252
|
-
try: async () => {
|
|
253
|
-
const client = new Pg.Client(pool.options);
|
|
254
|
-
await client.connect();
|
|
255
|
-
client.on("error", onListenClientError);
|
|
256
|
-
return client;
|
|
257
|
-
},
|
|
258
|
-
catch: cause => new SqlError({
|
|
259
|
-
reason: classifyError(cause, "Failed to acquire connection for listen", "acquireConnection")
|
|
260
|
-
})
|
|
261
|
-
}), client => Effect.promise(() => {
|
|
262
|
-
client.off("error", onListenClientError);
|
|
263
|
-
return client.end();
|
|
264
|
-
}).pipe(Effect.timeoutOption(1000)), {
|
|
265
|
-
interruptible: true
|
|
266
|
-
})
|
|
267
|
-
});
|
|
268
|
-
let config = {
|
|
269
|
-
url: pool.options.connectionString ? Redacted.make(pool.options.connectionString) : undefined,
|
|
270
|
-
host: pool.options.host,
|
|
271
|
-
port: pool.options.port,
|
|
272
|
-
database: pool.options.database,
|
|
273
|
-
username: pool.options.user,
|
|
274
|
-
password: typeof pool.options.password === "string" ? Redacted.make(pool.options.password) : undefined,
|
|
275
|
-
ssl: pool.options.ssl,
|
|
276
|
-
applicationName: pool.options.application_name,
|
|
277
|
-
types: pool.options.types
|
|
278
|
-
};
|
|
279
|
-
if (pool.options.connectionString) {
|
|
280
|
-
// @effect-diagnostics-next-line tryCatchInEffectGen:off
|
|
281
|
-
try {
|
|
282
|
-
const parsed = PgConnString.parse(pool.options.connectionString);
|
|
283
|
-
config = {
|
|
284
|
-
...config,
|
|
285
|
-
host: config.host ?? parsed.host ?? undefined,
|
|
286
|
-
port: config.port ?? (parsed.port ? Option.getOrUndefined(Number.parse(parsed.port)) : undefined),
|
|
287
|
-
username: config.username ?? parsed.user ?? undefined,
|
|
288
|
-
password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
|
|
289
|
-
database: config.database ?? parsed.database ?? undefined
|
|
290
|
-
};
|
|
291
|
-
} catch {
|
|
292
|
-
//
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return yield* makeWith({
|
|
296
|
-
acquirer: Effect.succeed(makeConection()),
|
|
297
|
-
transactionAcquirer: reserve,
|
|
298
|
-
listenAcquirer: RcRef.get(listenAcquirer),
|
|
299
|
-
config,
|
|
300
|
-
spanAttributes: options.spanAttributes,
|
|
301
|
-
transformResultNames: options.transformResultNames,
|
|
302
|
-
transformQueryNames: options.transformQueryNames,
|
|
303
|
-
transformJson: options.transformJson
|
|
304
|
-
});
|
|
305
|
-
});
|
|
33
|
+
export const make = options => Effect.flatMap(PgPool.make(options), pool => makeImpl({
|
|
34
|
+
acquirer: Effect.map(pool.get, makeConnection),
|
|
35
|
+
borrower: f => pool.use(connection => f(makeConnection(connection))),
|
|
36
|
+
transactionAcquirer: Effect.map(pool.reserve, makeConnection),
|
|
37
|
+
listenAcquirer: pool.reserve,
|
|
38
|
+
config: options
|
|
39
|
+
}));
|
|
306
40
|
/**
|
|
307
|
-
*
|
|
41
|
+
* Creates a scoped PostgreSQL client backed by one connection.
|
|
308
42
|
*
|
|
309
43
|
* @category constructors
|
|
310
44
|
* @since 4.0.0
|
|
311
45
|
*/
|
|
312
|
-
export const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return Effect.void;
|
|
319
|
-
});
|
|
320
|
-
}));
|
|
321
|
-
const client = yield* acquireWithErrorHandler;
|
|
322
|
-
const semaphore = Semaphore.makeUnsafe(1);
|
|
323
|
-
let streamClient = options.acquireForStream ? acquireWithErrorHandler : Effect.acquireRelease(Effect.as(semaphore.take(1), client), () => semaphore.release(1));
|
|
324
|
-
const makeConection = client => new ConnectionImpl(function runWithClient(f) {
|
|
325
|
-
return Effect.callback(resume => {
|
|
326
|
-
f(client, resume);
|
|
327
|
-
});
|
|
328
|
-
}, streamClient);
|
|
329
|
-
const connection = makeConection(client);
|
|
330
|
-
const acquirer = semaphore.withPermit(Effect.succeed(connection));
|
|
331
|
-
const transactionAcquirer = Effect.uninterruptibleMask(restore => {
|
|
332
|
-
const fiber = Fiber.getCurrent();
|
|
333
|
-
const scope = Context.getUnsafe(fiber.context, Scope.Scope);
|
|
334
|
-
return Effect.as(Effect.tap(restore(semaphore.take(1)), () => Scope.addFinalizer(scope, semaphore.release(1))), connection);
|
|
335
|
-
});
|
|
336
|
-
const config = {
|
|
337
|
-
...options,
|
|
338
|
-
host: client.host,
|
|
339
|
-
port: client.port,
|
|
340
|
-
database: client.database,
|
|
341
|
-
username: client.user,
|
|
342
|
-
password: typeof client.password === "string" ? Redacted.make(client.password) : undefined,
|
|
343
|
-
ssl: client.ssl
|
|
344
|
-
};
|
|
345
|
-
return yield* makeWith({
|
|
346
|
-
acquirer,
|
|
347
|
-
transactionAcquirer,
|
|
348
|
-
listenAcquirer: streamClient,
|
|
349
|
-
config,
|
|
350
|
-
spanAttributes: options.spanAttributes,
|
|
351
|
-
transformResultNames: options.transformResultNames,
|
|
352
|
-
transformQueryNames: options.transformQueryNames,
|
|
353
|
-
transformJson: options.transformJson
|
|
354
|
-
});
|
|
355
|
-
});
|
|
46
|
+
export const makeClient = options => Effect.flatMap(PgConnection.make(options), connection => makeImpl({
|
|
47
|
+
acquirer: Effect.succeed(makeConnection(connection, options.acquireForStream ? PgConnection.make(options) : undefined)),
|
|
48
|
+
transactionAcquirer: Effect.map(connection.pin, makeConnection),
|
|
49
|
+
listenAcquirer: options.acquireForStream ? PgConnection.make(options) : connection.pin,
|
|
50
|
+
config: options
|
|
51
|
+
}));
|
|
356
52
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* **When to use**
|
|
360
|
-
*
|
|
361
|
-
* Use to build a PostgreSQL client from custom connection acquisition logic
|
|
362
|
-
* instead of the built-in pool or single-client constructors.
|
|
363
|
-
*
|
|
364
|
-
* @category constructors
|
|
365
|
-
* @since 4.0.0
|
|
53
|
+
* Builds the shared SQL facade around native connection acquirers.
|
|
366
54
|
*/
|
|
367
|
-
|
|
368
|
-
const compiler = makeCompiler(options.transformQueryNames, options.transformJson);
|
|
369
|
-
const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames, options.transformJson).array : undefined;
|
|
55
|
+
const makeImpl = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
370
56
|
const config = options.config;
|
|
57
|
+
const compiler = makeCompiler(config.transformQueryNames, config.transformJson);
|
|
58
|
+
const transformRows = config.transformResultNames ? Statement.defaultTransforms(config.transformResultNames, config.transformJson).array : undefined;
|
|
59
|
+
const listen = channel => Effect.flatMap(options.listenAcquirer, connection => connection.listen(channel));
|
|
371
60
|
return Object.assign(yield* Client.make({
|
|
372
61
|
acquirer: options.acquirer,
|
|
62
|
+
borrower: options.borrower,
|
|
63
|
+
// Postgres prepares transaction control like anything else, and a client
|
|
64
|
+
// with preparation turned off falls back to the unnamed path anyway.
|
|
65
|
+
prepareTransactionControls: true,
|
|
373
66
|
transactionAcquirer: options.transactionAcquirer,
|
|
374
67
|
compiler,
|
|
375
|
-
spanAttributes: [...(
|
|
68
|
+
spanAttributes: [...(config.spanAttributes ? Object.entries(config.spanAttributes) : []), [ATTR_DB_SYSTEM_NAME, "postgresql"], [ATTR_DB_NAMESPACE, config.database ?? config.username ?? "postgres"], [ATTR_SERVER_ADDRESS, config.host ?? "localhost"], [ATTR_SERVER_PORT, config.port ?? 5432]],
|
|
376
69
|
transformRows
|
|
377
70
|
}), {
|
|
378
71
|
[TypeId]: TypeId,
|
|
379
|
-
config
|
|
72
|
+
config,
|
|
380
73
|
json: _ => Statement.fragment([PgJson(_)]),
|
|
381
|
-
listen
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
yield* Effect.addFinalizer(() => Effect.promise(() => {
|
|
389
|
-
client.off("notification", onNotification);
|
|
390
|
-
return client.query(`UNLISTEN ${Pg.escapeIdentifier(channel)}`);
|
|
391
|
-
}));
|
|
392
|
-
yield* Effect.tryPromise({
|
|
393
|
-
try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
|
|
394
|
-
catch: cause => new SqlError({
|
|
395
|
-
reason: classifyError(cause, "Failed to listen", "listen")
|
|
396
|
-
})
|
|
397
|
-
});
|
|
398
|
-
client.on("notification", onNotification);
|
|
399
|
-
})),
|
|
400
|
-
notify: (channel, payload) => Effect.asVoid(Effect.scoped(Effect.flatMap(options.acquirer, conn => conn.executeRaw(`SELECT pg_notify($1, $2)`, [channel, payload]))))
|
|
74
|
+
listen,
|
|
75
|
+
notify: (channel, payload) => {
|
|
76
|
+
const channelError = validateChannelName(channel, "notify");
|
|
77
|
+
return channelError !== undefined ? Effect.fail(channelError) : Effect.asVoid(Effect.scoped(Effect.flatMap(options.acquirer, conn => conn.executeRaw(`SELECT pg_notify($1, $2)`, [channel, payload]))));
|
|
78
|
+
}
|
|
401
79
|
});
|
|
402
80
|
});
|
|
403
81
|
class ConnectionImpl {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
82
|
+
connection;
|
|
83
|
+
streamAcquirer;
|
|
84
|
+
constructor(connection, streamAcquirer) {
|
|
85
|
+
this.connection = connection;
|
|
86
|
+
this.streamAcquirer = streamAcquirer;
|
|
407
87
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
run(query, params) {
|
|
411
|
-
return this.runWithClient((client, resume) => {
|
|
412
|
-
client.query(query, params, (err, result) => {
|
|
413
|
-
if (err) {
|
|
414
|
-
resume(Effect.fail(new SqlError({
|
|
415
|
-
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
416
|
-
})));
|
|
417
|
-
} else {
|
|
418
|
-
// Multi-statement queries return an array of results
|
|
419
|
-
resume(Effect.succeed(Array.isArray(result) ? result.map(r => r.rows ?? []) : result.rows ?? []));
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
});
|
|
88
|
+
run(query, params, prepare = true) {
|
|
89
|
+
return Effect.map(this.connection.query(query, params, prepare), result => result.rows);
|
|
423
90
|
}
|
|
424
91
|
execute(sql, params, transformRows) {
|
|
425
92
|
return transformRows ? Effect.map(this.run(sql, params), transformRows) : this.run(sql, params);
|
|
426
93
|
}
|
|
427
94
|
executeRaw(sql, params) {
|
|
428
|
-
return this.
|
|
429
|
-
client.query(sql, params, (err, result) => {
|
|
430
|
-
if (err) {
|
|
431
|
-
resume(Effect.fail(new SqlError({
|
|
432
|
-
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
433
|
-
})));
|
|
434
|
-
} else {
|
|
435
|
-
resume(Effect.succeed(result));
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
});
|
|
95
|
+
return this.connection.query(sql, params);
|
|
439
96
|
}
|
|
440
97
|
executeWithoutTransform(sql, params) {
|
|
441
98
|
return this.run(sql, params);
|
|
442
99
|
}
|
|
443
100
|
executeValues(sql, params) {
|
|
444
|
-
return this.
|
|
445
|
-
client.query({
|
|
446
|
-
text: sql,
|
|
447
|
-
rowMode: "array",
|
|
448
|
-
values: params
|
|
449
|
-
}, (err, result) => {
|
|
450
|
-
if (err) {
|
|
451
|
-
resume(Effect.fail(new SqlError({
|
|
452
|
-
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
453
|
-
})));
|
|
454
|
-
} else {
|
|
455
|
-
resume(Effect.succeed(result.rows));
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
});
|
|
101
|
+
return this.connection.queryValues(sql, params);
|
|
459
102
|
}
|
|
460
103
|
executeValuesUnprepared(sql, params) {
|
|
461
|
-
return this.
|
|
104
|
+
return this.connection.queryValues(sql, params, false);
|
|
462
105
|
}
|
|
463
106
|
executeUnprepared(sql, params, transformRows) {
|
|
464
|
-
|
|
107
|
+
const operation = this.run(sql, params, false);
|
|
108
|
+
return transformRows ? Effect.map(operation, transformRows) : operation;
|
|
465
109
|
}
|
|
466
110
|
executeStream(sql, params, transformRows) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return Stream.fromChannel(Channel.fromTransform(Effect.fnUntraced(function* (_, scope) {
|
|
470
|
-
const client = yield* Scope.provide(self.reserve, scope);
|
|
471
|
-
yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()));
|
|
472
|
-
const cursor = client.query(new Cursor(sql, params));
|
|
473
|
-
// @effect-diagnostics-next-line returnEffectInGen:off
|
|
474
|
-
return Effect.callback(resume => {
|
|
475
|
-
cursor.read(128, (err, rows) => {
|
|
476
|
-
if (err) {
|
|
477
|
-
resume(Effect.fail(new SqlError({
|
|
478
|
-
reason: classifyError(err, "Failed to execute statement", "stream")
|
|
479
|
-
})));
|
|
480
|
-
} else if (Arr.isArrayNonEmpty(rows)) {
|
|
481
|
-
resume(Effect.succeed(transformRows ? transformRows(rows) : rows));
|
|
482
|
-
} else {
|
|
483
|
-
resume(Cause.done());
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
})));
|
|
111
|
+
const stream = this.streamAcquirer === undefined ? this.connection.stream(sql, params) : Stream.unwrap(Effect.map(this.streamAcquirer, connection => connection.stream(sql, params)));
|
|
112
|
+
return transformRows ? Stream.mapArray(stream, rows => transformRows(rows)) : stream;
|
|
488
113
|
}
|
|
489
114
|
}
|
|
490
|
-
const
|
|
491
|
-
const makeCancel = (pool, client) => {
|
|
492
|
-
if (cancelEffects.has(client)) {
|
|
493
|
-
return cancelEffects.get(client);
|
|
494
|
-
}
|
|
495
|
-
const processId = client.processID;
|
|
496
|
-
const eff = processId !== undefined
|
|
497
|
-
// query cancelation is best-effort, so we don't fail if it doesn't work
|
|
498
|
-
? Effect.callback(resume => {
|
|
499
|
-
if (pool.ending) return resume(Effect.void);
|
|
500
|
-
pool.query(`SELECT pg_cancel_backend(${processId})`, () => {
|
|
501
|
-
resume(Effect.void);
|
|
502
|
-
});
|
|
503
|
-
}).pipe(Effect.interruptible, Effect.timeoutOption(5000)) : undefined;
|
|
504
|
-
cancelEffects.set(client, eff);
|
|
505
|
-
return eff;
|
|
506
|
-
};
|
|
115
|
+
const makeConnection = (connection, streamAcquirer) => new ConnectionImpl(connection, streamAcquirer);
|
|
507
116
|
/**
|
|
508
|
-
*
|
|
117
|
+
* Provides both `PgClient` and `SqlClient` from an acquisition effect.
|
|
509
118
|
*
|
|
510
119
|
* @category layers
|
|
511
120
|
* @since 4.0.0
|
|
512
121
|
*/
|
|
513
122
|
export const layerFrom = acquire => Layer.effectContext(Effect.map(acquire, client => Context.make(PgClient, client).pipe(Context.add(Client.SqlClient, client)))).pipe(Layer.provide(Reactivity.layer));
|
|
514
123
|
/**
|
|
515
|
-
* Creates a layer from
|
|
124
|
+
* Creates a client layer from wrapped pool configuration.
|
|
516
125
|
*
|
|
517
126
|
* @category layers
|
|
518
127
|
* @since 4.0.0
|
|
519
128
|
*/
|
|
520
129
|
export const layerConfig = config => layerFrom(Effect.flatMap(Config.unwrap(config), make));
|
|
521
130
|
/**
|
|
522
|
-
* Creates a layer from
|
|
131
|
+
* Creates a client layer from pool configuration.
|
|
523
132
|
*
|
|
524
133
|
* @category layers
|
|
525
134
|
* @since 4.0.0
|
|
526
135
|
*/
|
|
527
136
|
export const layer = config => layerFrom(make(config));
|
|
528
137
|
/**
|
|
529
|
-
* Creates the PostgreSQL statement compiler
|
|
138
|
+
* Creates the PostgreSQL statement compiler.
|
|
530
139
|
*
|
|
531
140
|
* @category constructors
|
|
532
141
|
* @since 4.0.0
|
|
@@ -548,7 +157,8 @@ export const makeCompiler = (transform, transformJson = true) => {
|
|
|
548
157
|
switch (type.kind) {
|
|
549
158
|
case "PgJson":
|
|
550
159
|
{
|
|
551
|
-
|
|
160
|
+
const value = withoutTransform || transformValue === undefined ? type.paramA : transformValue(type.paramA);
|
|
161
|
+
return [placeholder(undefined), [PgTypes.jsonb(value)]];
|
|
552
162
|
}
|
|
553
163
|
}
|
|
554
164
|
}
|
|
@@ -564,66 +174,4 @@ const ATTR_DB_SYSTEM_NAME = "db.system.name";
|
|
|
564
174
|
const ATTR_DB_NAMESPACE = "db.namespace";
|
|
565
175
|
const ATTR_SERVER_ADDRESS = "server.address";
|
|
566
176
|
const ATTR_SERVER_PORT = "server.port";
|
|
567
|
-
const pgCodeFromCause = cause => {
|
|
568
|
-
if (typeof cause !== "object" || cause === null || !("code" in cause)) {
|
|
569
|
-
return undefined;
|
|
570
|
-
}
|
|
571
|
-
const code = cause.code;
|
|
572
|
-
return typeof code === "string" ? code : undefined;
|
|
573
|
-
};
|
|
574
|
-
const pgConstraintFromCause = cause => {
|
|
575
|
-
if (typeof cause !== "object" || cause === null || !("constraint" in cause)) {
|
|
576
|
-
return "unknown";
|
|
577
|
-
}
|
|
578
|
-
const constraint = cause.constraint;
|
|
579
|
-
if (typeof constraint !== "string") {
|
|
580
|
-
return "unknown";
|
|
581
|
-
}
|
|
582
|
-
const normalized = constraint.trim();
|
|
583
|
-
return normalized.length === 0 ? "unknown" : normalized;
|
|
584
|
-
};
|
|
585
|
-
const classifyError = (cause, message, operation) => {
|
|
586
|
-
const props = {
|
|
587
|
-
cause,
|
|
588
|
-
message,
|
|
589
|
-
operation
|
|
590
|
-
};
|
|
591
|
-
const code = pgCodeFromCause(cause);
|
|
592
|
-
if (code !== undefined) {
|
|
593
|
-
if (code.startsWith("08")) {
|
|
594
|
-
return new ConnectionError(props);
|
|
595
|
-
}
|
|
596
|
-
if (code.startsWith("28")) {
|
|
597
|
-
return new AuthenticationError(props);
|
|
598
|
-
}
|
|
599
|
-
if (code === "42501") {
|
|
600
|
-
return new AuthorizationError(props);
|
|
601
|
-
}
|
|
602
|
-
if (code.startsWith("42")) {
|
|
603
|
-
return new SqlSyntaxError(props);
|
|
604
|
-
}
|
|
605
|
-
if (code === "23505") {
|
|
606
|
-
return new UniqueViolation({
|
|
607
|
-
...props,
|
|
608
|
-
constraint: pgConstraintFromCause(cause)
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
if (code.startsWith("23")) {
|
|
612
|
-
return new ConstraintError(props);
|
|
613
|
-
}
|
|
614
|
-
if (code === "40P01") {
|
|
615
|
-
return new DeadlockError(props);
|
|
616
|
-
}
|
|
617
|
-
if (code === "40001") {
|
|
618
|
-
return new SerializationError(props);
|
|
619
|
-
}
|
|
620
|
-
if (code === "55P03") {
|
|
621
|
-
return new LockTimeoutError(props);
|
|
622
|
-
}
|
|
623
|
-
if (code === "57014") {
|
|
624
|
-
return new StatementTimeoutError(props);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return new UnknownError(props);
|
|
628
|
-
};
|
|
629
177
|
//# sourceMappingURL=PgClient.js.map
|