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