@effect/sql-pg 4.0.0-beta.6 → 4.0.0-beta.60
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/README.md +1 -1
- package/dist/PgClient.d.ts +59 -19
- package/dist/PgClient.d.ts.map +1 -1
- package/dist/PgClient.js +347 -185
- package/dist/PgClient.js.map +1 -1
- package/dist/PgMigrator.d.ts +8 -4
- package/dist/PgMigrator.d.ts.map +1 -1
- package/dist/PgMigrator.js +49 -56
- package/dist/PgMigrator.js.map +1 -1
- package/package.json +9 -9
- package/src/PgClient.ts +540 -253
- package/src/PgMigrator.ts +74 -60
package/dist/PgClient.js
CHANGED
|
@@ -5,28 +5,26 @@ import * as Arr from "effect/Array";
|
|
|
5
5
|
import * as Cause from "effect/Cause";
|
|
6
6
|
import * as Channel from "effect/Channel";
|
|
7
7
|
import * as Config from "effect/Config";
|
|
8
|
+
import * as Context from "effect/Context";
|
|
8
9
|
import * as Duration from "effect/Duration";
|
|
9
10
|
import * as Effect from "effect/Effect";
|
|
10
11
|
import * as Fiber from "effect/Fiber";
|
|
11
12
|
import * as Layer from "effect/Layer";
|
|
12
13
|
import * as Number from "effect/Number";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
13
15
|
import * as Queue from "effect/Queue";
|
|
14
16
|
import * as RcRef from "effect/RcRef";
|
|
15
17
|
import * as Redacted from "effect/Redacted";
|
|
16
18
|
import * as Scope from "effect/Scope";
|
|
17
|
-
import * as
|
|
19
|
+
import * as Semaphore from "effect/Semaphore";
|
|
18
20
|
import * as Stream from "effect/Stream";
|
|
19
21
|
import * as Reactivity from "effect/unstable/reactivity/Reactivity";
|
|
20
22
|
import * as Client from "effect/unstable/sql/SqlClient";
|
|
21
|
-
import { SqlError } from "effect/unstable/sql/SqlError";
|
|
23
|
+
import { AuthenticationError, AuthorizationError, ConnectionError, ConstraintError, DeadlockError, LockTimeoutError, SerializationError, SqlError, SqlSyntaxError, StatementTimeoutError, UnknownError } from "effect/unstable/sql/SqlError";
|
|
22
24
|
import * as Statement from "effect/unstable/sql/Statement";
|
|
23
25
|
import * as Pg from "pg";
|
|
24
26
|
import * as PgConnString from "pg-connection-string";
|
|
25
27
|
import Cursor from "pg-cursor";
|
|
26
|
-
const ATTR_DB_SYSTEM_NAME = "db.system.name";
|
|
27
|
-
const ATTR_DB_NAMESPACE = "db.namespace";
|
|
28
|
-
const ATTR_SERVER_ADDRESS = "server.address";
|
|
29
|
-
const ATTR_SERVER_PORT = "server.port";
|
|
30
28
|
/**
|
|
31
29
|
* @category type ids
|
|
32
30
|
* @since 1.0.0
|
|
@@ -36,7 +34,7 @@ export const TypeId = "~@effect/sql-pg/PgClient";
|
|
|
36
34
|
* @category tags
|
|
37
35
|
* @since 1.0.0
|
|
38
36
|
*/
|
|
39
|
-
export const PgClient = /*#__PURE__*/
|
|
37
|
+
export const PgClient = /*#__PURE__*/Context.Service("@effect/sql-pg/PgClient");
|
|
40
38
|
/**
|
|
41
39
|
* @category constructors
|
|
42
40
|
* @since 1.0.0
|
|
@@ -55,11 +53,11 @@ export const make = options => fromPool({
|
|
|
55
53
|
...(options.stream ? {
|
|
56
54
|
stream: options.stream
|
|
57
55
|
} : {}),
|
|
58
|
-
connectionTimeoutMillis: options.connectTimeout ? Duration.toMillis(Duration.
|
|
59
|
-
idleTimeoutMillis: options.idleTimeout ? Duration.toMillis(Duration.
|
|
56
|
+
connectionTimeoutMillis: options.connectTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.connectTimeout)) : undefined,
|
|
57
|
+
idleTimeoutMillis: options.idleTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.idleTimeout)) : undefined,
|
|
60
58
|
max: options.maxConnections,
|
|
61
59
|
min: options.minConnections,
|
|
62
|
-
maxLifetimeSeconds: options.connectionTTL ? Duration.toSeconds(Duration.
|
|
60
|
+
maxLifetimeSeconds: options.connectionTTL ? Duration.toSeconds(Duration.fromInputUnsafe(options.connectionTTL)) : undefined,
|
|
63
61
|
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
64
62
|
types: options.types
|
|
65
63
|
});
|
|
@@ -67,193 +65,174 @@ export const make = options => fromPool({
|
|
|
67
65
|
yield* Effect.acquireRelease(Effect.tryPromise({
|
|
68
66
|
try: () => pool.query("SELECT 1"),
|
|
69
67
|
catch: cause => new SqlError({
|
|
70
|
-
cause,
|
|
71
|
-
message: "PgClient: Failed to connect"
|
|
68
|
+
reason: classifyError(cause, "PgClient: Failed to connect", "connect")
|
|
72
69
|
})
|
|
73
|
-
}), () => Effect.promise(() => pool.end()).pipe(Effect.timeoutOption(1000))
|
|
70
|
+
}), () => Effect.promise(() => pool.end()).pipe(Effect.timeoutOption(1000)), {
|
|
71
|
+
interruptible: true
|
|
72
|
+
}).pipe(Effect.timeoutOrElse({
|
|
74
73
|
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
orElse: () => Effect.fail(new SqlError({
|
|
75
|
+
reason: new ConnectionError({
|
|
76
|
+
cause: new Error("Connection timed out"),
|
|
77
|
+
message: "PgClient: Connection timed out",
|
|
78
|
+
operation: "connect"
|
|
79
|
+
})
|
|
78
80
|
}))
|
|
79
81
|
}));
|
|
80
82
|
return pool;
|
|
81
83
|
})
|
|
82
84
|
});
|
|
85
|
+
/**
|
|
86
|
+
* @category constructors
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
*/
|
|
89
|
+
export const makeClient = options => fromClient({
|
|
90
|
+
...options,
|
|
91
|
+
acquire: Effect.gen(function* () {
|
|
92
|
+
const client = new Pg.Client({
|
|
93
|
+
connectionString: options.url ? Redacted.value(options.url) : undefined,
|
|
94
|
+
user: options.username,
|
|
95
|
+
host: options.host,
|
|
96
|
+
database: options.database,
|
|
97
|
+
password: options.password ? Redacted.value(options.password) : undefined,
|
|
98
|
+
ssl: options.ssl,
|
|
99
|
+
port: options.port,
|
|
100
|
+
...(options.stream ? {
|
|
101
|
+
stream: options.stream
|
|
102
|
+
} : {}),
|
|
103
|
+
application_name: options.applicationName ?? "@effect/sql-pg",
|
|
104
|
+
types: options.types
|
|
105
|
+
});
|
|
106
|
+
yield* Effect.acquireRelease(Effect.tryPromise({
|
|
107
|
+
try: () => client.query("SELECT 1"),
|
|
108
|
+
catch: cause => new SqlError({
|
|
109
|
+
reason: classifyError(cause, "PgClient: Failed to connect", "connect")
|
|
110
|
+
})
|
|
111
|
+
}), () => Effect.promise(() => client.end()).pipe(Effect.timeoutOption(1000)), {
|
|
112
|
+
interruptible: true
|
|
113
|
+
}).pipe(Effect.timeoutOrElse({
|
|
114
|
+
duration: options.connectTimeout ?? Duration.seconds(5),
|
|
115
|
+
orElse: () => Effect.fail(new SqlError({
|
|
116
|
+
reason: new ConnectionError({
|
|
117
|
+
cause: new Error("Connection timed out"),
|
|
118
|
+
message: "PgClient: Connection timed out",
|
|
119
|
+
operation: "connect"
|
|
120
|
+
})
|
|
121
|
+
}))
|
|
122
|
+
}));
|
|
123
|
+
return client;
|
|
124
|
+
}),
|
|
125
|
+
acquireForStream: options.acquireForStream ?? false
|
|
126
|
+
});
|
|
83
127
|
/**
|
|
84
128
|
* @category constructors
|
|
85
129
|
* @since 1.0.0
|
|
86
130
|
*/
|
|
87
131
|
export const fromPool = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
88
|
-
const compiler = makeCompiler(options.transformQueryNames, options.transformJson);
|
|
89
|
-
const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames, options.transformJson).array : undefined;
|
|
90
132
|
const pool = yield* options.acquire;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
const makeConection = client => new ConnectionImpl(function runWithClient(f) {
|
|
134
|
+
if (client !== undefined) {
|
|
135
|
+
return Effect.callback(resume => {
|
|
136
|
+
f(client, resume);
|
|
137
|
+
return makeCancel(pool, client);
|
|
138
|
+
});
|
|
95
139
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
140
|
+
return Effect.callback(resume => {
|
|
141
|
+
let done = false;
|
|
142
|
+
let cancel = undefined;
|
|
143
|
+
let client = undefined;
|
|
144
|
+
function onError(cause) {
|
|
145
|
+
cleanup(cause);
|
|
146
|
+
resume(Effect.fail(new SqlError({
|
|
147
|
+
reason: classifyError(cause, "Connection error", "acquireConnection")
|
|
148
|
+
})));
|
|
102
149
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
150
|
+
function cleanup(cause) {
|
|
151
|
+
if (!done) client?.release(cause);
|
|
152
|
+
done = true;
|
|
153
|
+
client?.off("error", onError);
|
|
154
|
+
}
|
|
155
|
+
pool.connect((cause, client_) => {
|
|
156
|
+
if (cause) {
|
|
157
|
+
return resume(Effect.fail(new SqlError({
|
|
158
|
+
reason: classifyError(cause, "Failed to acquire connection", "acquireConnection")
|
|
112
159
|
})));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
done = true;
|
|
117
|
-
client?.off("error", onError);
|
|
118
|
-
}
|
|
119
|
-
pool.connect((cause, client_) => {
|
|
120
|
-
if (cause) {
|
|
121
|
-
return resume(Effect.fail(new SqlError({
|
|
122
|
-
cause,
|
|
123
|
-
message: "Failed to acquire connection"
|
|
124
|
-
})));
|
|
125
|
-
} else if (!client_) {
|
|
126
|
-
return resume(Effect.fail(new SqlError({
|
|
160
|
+
} else if (!client_) {
|
|
161
|
+
return resume(Effect.fail(new SqlError({
|
|
162
|
+
reason: new ConnectionError({
|
|
127
163
|
message: "Failed to acquire connection",
|
|
128
|
-
cause: new Error("No client returned")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return Effect.suspend(() => {
|
|
143
|
-
if (!cancel) {
|
|
144
|
-
cleanup();
|
|
145
|
-
return Effect.void;
|
|
146
|
-
}
|
|
147
|
-
return Effect.ensuring(cancel, Effect.sync(cleanup));
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
run(query, params) {
|
|
152
|
-
return this.runWithClient((client, resume) => {
|
|
153
|
-
client.query(query, params, (err, result) => {
|
|
154
|
-
if (err) {
|
|
155
|
-
resume(Effect.fail(new SqlError({
|
|
156
|
-
cause: err,
|
|
157
|
-
message: "Failed to execute statement"
|
|
158
|
-
})));
|
|
159
|
-
} else {
|
|
160
|
-
// Multi-statement queries return an array of results
|
|
161
|
-
resume(Effect.succeed(Array.isArray(result) ? result.map(r => r.rows ?? []) : result.rows ?? []));
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
execute(sql, params, transformRows) {
|
|
167
|
-
return transformRows ? Effect.map(this.run(sql, params), transformRows) : this.run(sql, params);
|
|
168
|
-
}
|
|
169
|
-
executeRaw(sql, params) {
|
|
170
|
-
return this.runWithClient((client, resume) => {
|
|
171
|
-
client.query(sql, params, (err, result) => {
|
|
172
|
-
if (err) {
|
|
173
|
-
resume(Effect.fail(new SqlError({
|
|
174
|
-
cause: err,
|
|
175
|
-
message: "Failed to execute statement"
|
|
176
|
-
})));
|
|
177
|
-
} else {
|
|
178
|
-
resume(Effect.succeed(result));
|
|
179
|
-
}
|
|
164
|
+
cause: new Error("No client returned"),
|
|
165
|
+
operation: "acquireConnection"
|
|
166
|
+
})
|
|
167
|
+
})));
|
|
168
|
+
} else if (done) {
|
|
169
|
+
client_.release();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
client = client_;
|
|
173
|
+
client.once("error", onError);
|
|
174
|
+
cancel = makeCancel(pool, client);
|
|
175
|
+
f(client, eff => {
|
|
176
|
+
cleanup();
|
|
177
|
+
resume(eff);
|
|
180
178
|
});
|
|
181
179
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
client.query({
|
|
189
|
-
text: sql,
|
|
190
|
-
rowMode: "array",
|
|
191
|
-
values: params
|
|
192
|
-
}, (err, result) => {
|
|
193
|
-
if (err) {
|
|
194
|
-
resume(Effect.fail(new SqlError({
|
|
195
|
-
cause: err,
|
|
196
|
-
message: "Failed to execute statement"
|
|
197
|
-
})));
|
|
198
|
-
} else {
|
|
199
|
-
resume(Effect.succeed(result.rows));
|
|
200
|
-
}
|
|
201
|
-
});
|
|
180
|
+
return Effect.suspend(() => {
|
|
181
|
+
if (!cancel) {
|
|
182
|
+
cleanup();
|
|
183
|
+
return Effect.void;
|
|
184
|
+
}
|
|
185
|
+
return Effect.ensuring(cancel, Effect.sync(cleanup));
|
|
202
186
|
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return this.execute(sql, params, transformRows);
|
|
206
|
-
}
|
|
207
|
-
executeStream(sql, params, transformRows) {
|
|
208
|
-
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
|
209
|
-
const self = this;
|
|
210
|
-
return Stream.fromChannel(Channel.fromTransform(Effect.fnUntraced(function* (_, scope) {
|
|
211
|
-
const client = self.pg ?? (yield* Scope.provide(reserveRaw, scope));
|
|
212
|
-
yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()));
|
|
213
|
-
const cursor = client.query(new Cursor(sql, params));
|
|
214
|
-
// @effect-diagnostics-next-line returnEffectInGen:off
|
|
215
|
-
return Effect.callback(resume => {
|
|
216
|
-
cursor.read(128, (err, rows) => {
|
|
217
|
-
if (err) {
|
|
218
|
-
resume(Effect.fail(new SqlError({
|
|
219
|
-
cause: err,
|
|
220
|
-
message: "Failed to execute statement"
|
|
221
|
-
})));
|
|
222
|
-
} else if (Arr.isArrayNonEmpty(rows)) {
|
|
223
|
-
resume(Effect.succeed(transformRows ? transformRows(rows) : rows));
|
|
224
|
-
} else {
|
|
225
|
-
resume(Cause.done());
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
})));
|
|
230
|
-
}
|
|
231
|
-
}
|
|
187
|
+
});
|
|
188
|
+
}, client ? Effect.succeed(client) : reserveRaw);
|
|
232
189
|
const reserveRaw = Effect.callback(resume => {
|
|
233
190
|
const fiber = Fiber.getCurrent();
|
|
234
|
-
const scope =
|
|
191
|
+
const scope = Context.getUnsafe(fiber.context, Scope.Scope);
|
|
235
192
|
let cause = undefined;
|
|
193
|
+
function onError(cause_) {
|
|
194
|
+
cause = cause_;
|
|
195
|
+
}
|
|
236
196
|
pool.connect((err, client, release) => {
|
|
237
197
|
if (err) {
|
|
238
|
-
resume(Effect.fail(new SqlError({
|
|
239
|
-
|
|
240
|
-
|
|
198
|
+
return resume(Effect.fail(new SqlError({
|
|
199
|
+
reason: classifyError(err, "Failed to acquire connection for transaction", "acquireConnection")
|
|
200
|
+
})));
|
|
201
|
+
} else if (!client) {
|
|
202
|
+
return resume(Effect.fail(new SqlError({
|
|
203
|
+
reason: new ConnectionError({
|
|
204
|
+
message: "Failed to acquire connection for transaction",
|
|
205
|
+
cause: new Error("No client returned"),
|
|
206
|
+
operation: "acquireConnection"
|
|
207
|
+
})
|
|
241
208
|
})));
|
|
242
|
-
} else {
|
|
243
|
-
resume(Effect.as(Scope.addFinalizer(scope, Effect.sync(() => {
|
|
244
|
-
client.off("error", onError);
|
|
245
|
-
release(cause);
|
|
246
|
-
})), client));
|
|
247
|
-
}
|
|
248
|
-
function onError(cause_) {
|
|
249
|
-
cause = cause_;
|
|
250
209
|
}
|
|
251
210
|
client.on("error", onError);
|
|
211
|
+
resume(Effect.as(Scope.addFinalizer(scope, Effect.sync(() => {
|
|
212
|
+
client.off("error", onError);
|
|
213
|
+
release(cause);
|
|
214
|
+
})), client));
|
|
252
215
|
});
|
|
253
216
|
});
|
|
254
|
-
const reserve = Effect.map(reserveRaw,
|
|
255
|
-
const
|
|
256
|
-
|
|
217
|
+
const reserve = Effect.map(reserveRaw, makeConection);
|
|
218
|
+
const onListenClientError = _ => {};
|
|
219
|
+
const listenAcquirer = yield* RcRef.make({
|
|
220
|
+
acquire: Effect.acquireRelease(Effect.tryPromise({
|
|
221
|
+
try: async () => {
|
|
222
|
+
const client = new Pg.Client(pool.options);
|
|
223
|
+
await client.connect();
|
|
224
|
+
client.on("error", onListenClientError);
|
|
225
|
+
return client;
|
|
226
|
+
},
|
|
227
|
+
catch: cause => new SqlError({
|
|
228
|
+
reason: classifyError(cause, "Failed to acquire connection for listen", "acquireConnection")
|
|
229
|
+
})
|
|
230
|
+
}), client => Effect.promise(() => {
|
|
231
|
+
client.off("error", onListenClientError);
|
|
232
|
+
return client.end();
|
|
233
|
+
}).pipe(Effect.timeoutOption(1000)), {
|
|
234
|
+
interruptible: true
|
|
235
|
+
})
|
|
257
236
|
});
|
|
258
237
|
let config = {
|
|
259
238
|
url: pool.options.connectionString ? Redacted.make(pool.options.connectionString) : undefined,
|
|
@@ -273,7 +252,7 @@ export const fromPool = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
|
273
252
|
config = {
|
|
274
253
|
...config,
|
|
275
254
|
host: config.host ?? parsed.host ?? undefined,
|
|
276
|
-
port: config.port ?? (parsed.port ? Number.parse(parsed.port) : undefined),
|
|
255
|
+
port: config.port ?? (parsed.port ? Option.getOrUndefined(Number.parse(parsed.port)) : undefined),
|
|
277
256
|
username: config.username ?? parsed.user ?? undefined,
|
|
278
257
|
password: config.password ?? (parsed.password ? Redacted.make(parsed.password) : undefined),
|
|
279
258
|
database: config.database ?? parsed.database ?? undefined
|
|
@@ -282,18 +261,80 @@ export const fromPool = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
|
282
261
|
//
|
|
283
262
|
}
|
|
284
263
|
}
|
|
285
|
-
return
|
|
286
|
-
acquirer: Effect.succeed(
|
|
264
|
+
return yield* makeWith({
|
|
265
|
+
acquirer: Effect.succeed(makeConection()),
|
|
287
266
|
transactionAcquirer: reserve,
|
|
267
|
+
listenAcquirer: RcRef.get(listenAcquirer),
|
|
268
|
+
config,
|
|
269
|
+
spanAttributes: options.spanAttributes,
|
|
270
|
+
transformResultNames: options.transformResultNames,
|
|
271
|
+
transformQueryNames: options.transformQueryNames,
|
|
272
|
+
transformJson: options.transformJson
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
/**
|
|
276
|
+
* @category constructors
|
|
277
|
+
* @since 1.0.0
|
|
278
|
+
*/
|
|
279
|
+
export const fromClient = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
280
|
+
function onError() {}
|
|
281
|
+
const acquireWithErrorHandler = options.acquire.pipe(Effect.tap(client => {
|
|
282
|
+
client.on("error", onError);
|
|
283
|
+
return Effect.addFinalizer(() => {
|
|
284
|
+
client.off("error", onError);
|
|
285
|
+
return Effect.void;
|
|
286
|
+
});
|
|
287
|
+
}));
|
|
288
|
+
const client = yield* acquireWithErrorHandler;
|
|
289
|
+
const semaphore = Semaphore.makeUnsafe(1);
|
|
290
|
+
let streamClient = options.acquireForStream ? acquireWithErrorHandler : Effect.acquireRelease(Effect.as(semaphore.take(1), client), () => semaphore.release(1));
|
|
291
|
+
const makeConection = client => new ConnectionImpl(function runWithClient(f) {
|
|
292
|
+
return Effect.callback(resume => {
|
|
293
|
+
f(client, resume);
|
|
294
|
+
});
|
|
295
|
+
}, streamClient);
|
|
296
|
+
const connection = makeConection(client);
|
|
297
|
+
const acquirer = semaphore.withPermit(Effect.succeed(connection));
|
|
298
|
+
const config = {
|
|
299
|
+
...options,
|
|
300
|
+
host: client.host,
|
|
301
|
+
port: client.port,
|
|
302
|
+
database: client.database,
|
|
303
|
+
username: client.user,
|
|
304
|
+
password: typeof client.password === "string" ? Redacted.make(client.password) : undefined,
|
|
305
|
+
ssl: client.ssl
|
|
306
|
+
};
|
|
307
|
+
return yield* makeWith({
|
|
308
|
+
acquirer,
|
|
309
|
+
transactionAcquirer: acquirer,
|
|
310
|
+
listenAcquirer: streamClient,
|
|
311
|
+
config,
|
|
312
|
+
spanAttributes: options.spanAttributes,
|
|
313
|
+
transformResultNames: options.transformResultNames,
|
|
314
|
+
transformQueryNames: options.transformQueryNames,
|
|
315
|
+
transformJson: options.transformJson
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
/**
|
|
319
|
+
* @category constructors
|
|
320
|
+
* @since 1.0.0
|
|
321
|
+
*/
|
|
322
|
+
export const makeWith = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
323
|
+
const compiler = makeCompiler(options.transformQueryNames, options.transformJson);
|
|
324
|
+
const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames, options.transformJson).array : undefined;
|
|
325
|
+
const config = options.config;
|
|
326
|
+
return Object.assign(yield* Client.make({
|
|
327
|
+
acquirer: options.acquirer,
|
|
328
|
+
transactionAcquirer: options.transactionAcquirer,
|
|
288
329
|
compiler,
|
|
289
330
|
spanAttributes: [...(options.spanAttributes ? Object.entries(options.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]],
|
|
290
331
|
transformRows
|
|
291
332
|
}), {
|
|
292
333
|
[TypeId]: TypeId,
|
|
293
|
-
config,
|
|
334
|
+
config: options.config,
|
|
294
335
|
json: _ => Statement.fragment([PgJson(_)]),
|
|
295
336
|
listen: channel => Stream.callback(Effect.fnUntraced(function* (queue) {
|
|
296
|
-
const client = yield*
|
|
337
|
+
const client = yield* options.listenAcquirer;
|
|
297
338
|
function onNotification(msg) {
|
|
298
339
|
if (msg.channel === channel && msg.payload) {
|
|
299
340
|
Queue.offerUnsafe(queue, msg.payload);
|
|
@@ -306,26 +347,98 @@ export const fromPool = /*#__PURE__*/Effect.fnUntraced(function* (options) {
|
|
|
306
347
|
yield* Effect.tryPromise({
|
|
307
348
|
try: () => client.query(`LISTEN ${Pg.escapeIdentifier(channel)}`),
|
|
308
349
|
catch: cause => new SqlError({
|
|
309
|
-
cause,
|
|
310
|
-
message: "Failed to listen"
|
|
350
|
+
reason: classifyError(cause, "Failed to listen", "listen")
|
|
311
351
|
})
|
|
312
352
|
});
|
|
313
353
|
client.on("notification", onNotification);
|
|
314
354
|
})),
|
|
315
|
-
notify: (channel, payload) => Effect.
|
|
316
|
-
|
|
355
|
+
notify: (channel, payload) => Effect.asVoid(Effect.scoped(Effect.flatMap(options.acquirer, conn => conn.executeRaw(`SELECT pg_notify($1, $2)`, [channel, payload]))))
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
class ConnectionImpl {
|
|
359
|
+
constructor(runWithClient, reserve) {
|
|
360
|
+
this.runWithClient = runWithClient;
|
|
361
|
+
this.reserve = reserve;
|
|
362
|
+
}
|
|
363
|
+
runWithClient;
|
|
364
|
+
reserve;
|
|
365
|
+
run(query, params) {
|
|
366
|
+
return this.runWithClient((client, resume) => {
|
|
367
|
+
client.query(query, params, (err, result) => {
|
|
317
368
|
if (err) {
|
|
318
369
|
resume(Effect.fail(new SqlError({
|
|
319
|
-
|
|
320
|
-
message: "Failed to notify"
|
|
370
|
+
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
321
371
|
})));
|
|
322
372
|
} else {
|
|
323
|
-
|
|
373
|
+
// Multi-statement queries return an array of results
|
|
374
|
+
resume(Effect.succeed(Array.isArray(result) ? result.map(r => r.rows ?? []) : result.rows ?? []));
|
|
324
375
|
}
|
|
325
376
|
});
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
execute(sql, params, transformRows) {
|
|
380
|
+
return transformRows ? Effect.map(this.run(sql, params), transformRows) : this.run(sql, params);
|
|
381
|
+
}
|
|
382
|
+
executeRaw(sql, params) {
|
|
383
|
+
return this.runWithClient((client, resume) => {
|
|
384
|
+
client.query(sql, params, (err, result) => {
|
|
385
|
+
if (err) {
|
|
386
|
+
resume(Effect.fail(new SqlError({
|
|
387
|
+
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
388
|
+
})));
|
|
389
|
+
} else {
|
|
390
|
+
resume(Effect.succeed(result));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
executeWithoutTransform(sql, params) {
|
|
396
|
+
return this.run(sql, params);
|
|
397
|
+
}
|
|
398
|
+
executeValues(sql, params) {
|
|
399
|
+
return this.runWithClient((client, resume) => {
|
|
400
|
+
client.query({
|
|
401
|
+
text: sql,
|
|
402
|
+
rowMode: "array",
|
|
403
|
+
values: params
|
|
404
|
+
}, (err, result) => {
|
|
405
|
+
if (err) {
|
|
406
|
+
resume(Effect.fail(new SqlError({
|
|
407
|
+
reason: classifyError(err, "Failed to execute statement", "execute")
|
|
408
|
+
})));
|
|
409
|
+
} else {
|
|
410
|
+
resume(Effect.succeed(result.rows));
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
executeUnprepared(sql, params, transformRows) {
|
|
416
|
+
return this.execute(sql, params, transformRows);
|
|
417
|
+
}
|
|
418
|
+
executeStream(sql, params, transformRows) {
|
|
419
|
+
// oxlint-disable-next-line @typescript-eslint/no-this-alias
|
|
420
|
+
const self = this;
|
|
421
|
+
return Stream.fromChannel(Channel.fromTransform(Effect.fnUntraced(function* (_, scope) {
|
|
422
|
+
const client = yield* Scope.provide(self.reserve, scope);
|
|
423
|
+
yield* Scope.addFinalizer(scope, Effect.promise(() => cursor.close()));
|
|
424
|
+
const cursor = client.query(new Cursor(sql, params));
|
|
425
|
+
// @effect-diagnostics-next-line returnEffectInGen:off
|
|
426
|
+
return Effect.callback(resume => {
|
|
427
|
+
cursor.read(128, (err, rows) => {
|
|
428
|
+
if (err) {
|
|
429
|
+
resume(Effect.fail(new SqlError({
|
|
430
|
+
reason: classifyError(err, "Failed to execute statement", "stream")
|
|
431
|
+
})));
|
|
432
|
+
} else if (Arr.isArrayNonEmpty(rows)) {
|
|
433
|
+
resume(Effect.succeed(transformRows ? transformRows(rows) : rows));
|
|
434
|
+
} else {
|
|
435
|
+
resume(Cause.done());
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
})));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
329
442
|
const cancelEffects = /*#__PURE__*/new WeakMap();
|
|
330
443
|
const makeCancel = (pool, client) => {
|
|
331
444
|
if (cancelEffects.has(client)) {
|
|
@@ -347,17 +460,17 @@ const makeCancel = (pool, client) => {
|
|
|
347
460
|
* @category layers
|
|
348
461
|
* @since 1.0.0
|
|
349
462
|
*/
|
|
350
|
-
export const
|
|
463
|
+
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));
|
|
351
464
|
/**
|
|
352
465
|
* @category layers
|
|
353
466
|
* @since 1.0.0
|
|
354
467
|
*/
|
|
355
|
-
export const
|
|
468
|
+
export const layerConfig = config => layerFrom(Effect.flatMap(Config.unwrap(config).asEffect(), make));
|
|
356
469
|
/**
|
|
357
470
|
* @category layers
|
|
358
471
|
* @since 1.0.0
|
|
359
472
|
*/
|
|
360
|
-
export const
|
|
473
|
+
export const layer = config => layerFrom(make(config));
|
|
361
474
|
/**
|
|
362
475
|
* @category constructor
|
|
363
476
|
* @since 1.0.0
|
|
@@ -391,4 +504,53 @@ const escape = /*#__PURE__*/Statement.defaultEscape("\"");
|
|
|
391
504
|
* @since 1.0.0
|
|
392
505
|
*/
|
|
393
506
|
const PgJson = /*#__PURE__*/Statement.custom("PgJson");
|
|
507
|
+
const ATTR_DB_SYSTEM_NAME = "db.system.name";
|
|
508
|
+
const ATTR_DB_NAMESPACE = "db.namespace";
|
|
509
|
+
const ATTR_SERVER_ADDRESS = "server.address";
|
|
510
|
+
const ATTR_SERVER_PORT = "server.port";
|
|
511
|
+
const pgCodeFromCause = cause => {
|
|
512
|
+
if (typeof cause !== "object" || cause === null || !("code" in cause)) {
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
const code = cause.code;
|
|
516
|
+
return typeof code === "string" ? code : undefined;
|
|
517
|
+
};
|
|
518
|
+
const classifyError = (cause, message, operation) => {
|
|
519
|
+
const props = {
|
|
520
|
+
cause,
|
|
521
|
+
message,
|
|
522
|
+
operation
|
|
523
|
+
};
|
|
524
|
+
const code = pgCodeFromCause(cause);
|
|
525
|
+
if (code !== undefined) {
|
|
526
|
+
if (code.startsWith("08")) {
|
|
527
|
+
return new ConnectionError(props);
|
|
528
|
+
}
|
|
529
|
+
if (code.startsWith("28")) {
|
|
530
|
+
return new AuthenticationError(props);
|
|
531
|
+
}
|
|
532
|
+
if (code === "42501") {
|
|
533
|
+
return new AuthorizationError(props);
|
|
534
|
+
}
|
|
535
|
+
if (code.startsWith("42")) {
|
|
536
|
+
return new SqlSyntaxError(props);
|
|
537
|
+
}
|
|
538
|
+
if (code.startsWith("23")) {
|
|
539
|
+
return new ConstraintError(props);
|
|
540
|
+
}
|
|
541
|
+
if (code === "40P01") {
|
|
542
|
+
return new DeadlockError(props);
|
|
543
|
+
}
|
|
544
|
+
if (code === "40001") {
|
|
545
|
+
return new SerializationError(props);
|
|
546
|
+
}
|
|
547
|
+
if (code === "55P03") {
|
|
548
|
+
return new LockTimeoutError(props);
|
|
549
|
+
}
|
|
550
|
+
if (code === "57014") {
|
|
551
|
+
return new StatementTimeoutError(props);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return new UnknownError(props);
|
|
555
|
+
};
|
|
394
556
|
//# sourceMappingURL=PgClient.js.map
|