kern-db 0.0.0-stage → 0.2.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.
@@ -0,0 +1,177 @@
1
+ import { a as toDriverError, n as QueryError, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
2
+ // src/drivers/node-postgres.ts
3
+ /** Parsers of a query that keep every value as its text, whatever parsers the client has. */
4
+ const textTypes = { getTypeParser: () => (value) => value };
5
+ /**
6
+ * Returns why the driver cannot read the values of a client as text, if it cannot.
7
+ *
8
+ * `pg` sets `native` and `binary` on a client, though its types omit them.
9
+ */
10
+ function getUnsupportedClientReason(client) {
11
+ if (client.native) return "nodePostgresDriver requires the JavaScript client: create the pool with pg, not pg.native";
12
+ if (client.binary) return "nodePostgresDriver requires text results: disable binary in the options of the pool and in pg.defaults";
13
+ }
14
+ /** Listener of the `error` event of a pool or a client, for a connection whose loss needs no handling at the event; an `EventEmitter` throws an `error` event without a listener, which terminates the process. */
15
+ function ignoreConnectionError() {}
16
+ /** Transaction status that `getTransactionStatus` of `pg` gives for a connection outside a transaction block. */
17
+ const idleStatus = "I";
18
+ /** Query that runs no statement, also in a failed transaction, and completes after the statements sent before it. */
19
+ const emptyQuery = { text: "" };
20
+ /**
21
+ * Tells whether a client is outside a transaction block after its last statement.
22
+ *
23
+ * `pg` rejects a statement at the ErrorResponse of PostgreSQL, before the ReadyForQuery that reports the status of the transaction, so after a statement that PostgreSQL rejected, which `rejected` tells, an empty query waits for that report.
24
+ * It returns `false` when the empty query fails, since the status cannot be trusted then.
25
+ */
26
+ async function isIdle(client, rejected) {
27
+ if (rejected) try {
28
+ await client.query(emptyQuery);
29
+ } catch {
30
+ return false;
31
+ }
32
+ return client.getTransactionStatus() === idleStatus;
33
+ }
34
+ /**
35
+ * Classifies an error of a statement of `pg`, which gives the severity only in the language of the server.
36
+ *
37
+ * With translated messages, `toDriverError` tells an error that ends the session by its SQLSTATE.
38
+ */
39
+ function toNodePostgresError(error) {
40
+ return toDriverError(error, error ?? {});
41
+ }
42
+ async function execute(client, statement) {
43
+ const config = {
44
+ text: statement.text,
45
+ values: statement.parameters,
46
+ rowMode: "array",
47
+ types: textTypes,
48
+ queryMode: "extended"
49
+ };
50
+ let result;
51
+ try {
52
+ result = await client.query(config);
53
+ } catch (error) {
54
+ throw toNodePostgresError(error);
55
+ }
56
+ const { fields } = result;
57
+ const columns = [];
58
+ for (let index = 0; index < fields.length; index++) columns.push(fields[index].name);
59
+ return {
60
+ command: result.command,
61
+ count: result.rowCount ?? 0,
62
+ columns,
63
+ rows: result.rows
64
+ };
65
+ }
66
+ async function executeScript(client, text) {
67
+ try {
68
+ await client.query({
69
+ text,
70
+ rowMode: "array",
71
+ types: textTypes
72
+ });
73
+ } catch (error) {
74
+ throw toNodePostgresError(error);
75
+ }
76
+ }
77
+ /**
78
+ * Creates a kern-db driver over a node-postgres pool; the pool keeps managing its connections.
79
+ *
80
+ * Closing the driver ends the supplied pool.
81
+ */
82
+ function nodePostgresDriver(pool) {
83
+ let checkouts = 0;
84
+ let drained;
85
+ let closing;
86
+ pool.on("error", ignoreConnectionError);
87
+ pool.on("acquire", (client) => client.on("error", ignoreConnectionError));
88
+ pool.on("release", (_error, client) => client.off("error", ignoreConnectionError));
89
+ function endCheckout() {
90
+ if (--checkouts === 0) drained?.resolve();
91
+ }
92
+ /** Gets a client from the pool for `operation`, which names it in the error once closing starts. */
93
+ async function checkOut(operation) {
94
+ if (closing) throw new ConnectionError(`Cannot ${operation}: the driver is closed`);
95
+ checkouts++;
96
+ try {
97
+ const client = await pool.connect();
98
+ const unsupported = getUnsupportedClientReason(client);
99
+ if (unsupported) {
100
+ client.release();
101
+ throw new ConnectionError(unsupported);
102
+ }
103
+ return client;
104
+ } catch (error) {
105
+ endCheckout();
106
+ throw toDriverError(error);
107
+ }
108
+ }
109
+ /** Returns a client to the pool, which closes it with `destroy` or when its connection is lost. */
110
+ function checkIn(client, destroy) {
111
+ client.release(destroy);
112
+ endCheckout();
113
+ }
114
+ /**
115
+ * Runs a statement or a `script` on a client of its own, unlike `pool.query`, which closes the connection of any failed statement.
116
+ *
117
+ * `pg` returns a client to the pool whatever its transaction, so the driver closes a connection that is not idle after it, and rejects a statement or script that leaves a transaction open.
118
+ */
119
+ async function runOnPool(operation, script, run) {
120
+ const client = await checkOut(operation);
121
+ let result;
122
+ try {
123
+ result = await run(client);
124
+ } catch (error) {
125
+ checkIn(client, !(error instanceof QueryError && (!script || await isIdle(client, true))));
126
+ throw error;
127
+ }
128
+ if (client.getTransactionStatus() !== idleStatus) {
129
+ checkIn(client, true);
130
+ throw new ConnectionError(`Cannot ${operation} that leaves a transaction open on a pooled connection: the driver closed the connection to roll the transaction back; use $transaction of a Db or transaction of the database instead`);
131
+ }
132
+ checkIn(client, false);
133
+ return result;
134
+ }
135
+ async function endPool() {
136
+ if (checkouts > 0) {
137
+ drained = Promise.withResolvers();
138
+ await drained.promise;
139
+ }
140
+ await pool.end();
141
+ }
142
+ return {
143
+ execute: (statement) => runOnPool("execute a statement", false, (client) => execute(client, statement)),
144
+ executeScript: (text) => runOnPool("execute a script", true, (client) => executeScript(client, text)),
145
+ async acquire() {
146
+ const client = await checkOut("acquire a connection");
147
+ let last = Promise.resolve();
148
+ let broken = false;
149
+ let rejected = false;
150
+ const onCompleted = () => {
151
+ rejected = false;
152
+ };
153
+ const onFailed = (error) => {
154
+ if (error instanceof QueryError) rejected = true;
155
+ else broken = true;
156
+ };
157
+ function enqueue(run) {
158
+ const result = last.then(run);
159
+ last = result.then(onCompleted, onFailed);
160
+ return result;
161
+ }
162
+ return {
163
+ execute: (statement) => enqueue(() => execute(client, statement)),
164
+ executeScript: (text) => enqueue(() => executeScript(client, text)),
165
+ async release() {
166
+ await last;
167
+ checkIn(client, broken || !await isIdle(client, rejected));
168
+ }
169
+ };
170
+ },
171
+ close() {
172
+ closing ??= endPool();
173
+ return closing;
174
+ }
175
+ };
176
+ }
177
+ export { nodePostgresDriver };
@@ -0,0 +1,9 @@
1
+ import { n as Driver } from "./driver-CSfbXOBJ.mjs";
2
+ import { Sql } from "postgres";
3
+ /**
4
+ * Creates a kern-db driver over a postgres.js client; the client keeps managing its pool.
5
+ *
6
+ * It throws a `TypeError` for a client that transforms the column names or rows of results, such as with `postgres.camel`.
7
+ * Closing the driver ends the supplied client.
8
+ */
9
+ export declare function postgresJsDriver(sql: Sql): Driver;
@@ -0,0 +1,86 @@
1
+ import { a as toDriverError, t as ConnectionError } from "./driver-error-26zcdHeU.mjs";
2
+ // src/drivers/postgres-js.ts
3
+ /** OID of the PostgreSQL `unknown` pseudo-type; postgres.js sends such parameters as they are, and PostgreSQL infers their types. */
4
+ const unknownType = 705;
5
+ /** Classifies an error of a statement of postgres.js, which gives the severity in English as `severity`, and the constraint as `constraint_name`. */
6
+ function toPostgresJsError(error) {
7
+ const fields = error ?? {};
8
+ return toDriverError(error, {
9
+ severity: fields.severity,
10
+ code: fields.code,
11
+ constraint: fields.constraint_name
12
+ });
13
+ }
14
+ /**
15
+ * Creates a kern-db driver over a postgres.js client; the client keeps managing its pool.
16
+ *
17
+ * It throws a `TypeError` for a client that transforms the column names or rows of results, such as with `postgres.camel`.
18
+ * Closing the driver ends the supplied client.
19
+ */
20
+ function postgresJsDriver(sql) {
21
+ const { column, row } = sql.options.transform;
22
+ if (column.from !== void 0 || row.from !== void 0) throw new TypeError("Cannot create a driver of a postgres.js client that transforms the column names or rows of results, such as with postgres.camel: kern-db reads results as PostgreSQL returns them");
23
+ let closing;
24
+ async function execute(client, statement) {
25
+ const parameters = statement.parameters.map((value) => sql.typed(value, unknownType));
26
+ let rows;
27
+ try {
28
+ rows = await client.unsafe(statement.text, parameters, { simple: false }).raw();
29
+ } catch (error) {
30
+ throw toPostgresJsError(error);
31
+ }
32
+ const { columns } = rows;
33
+ const names = [];
34
+ for (let index = 0; index < columns.length; index++) names.push(columns[index].name);
35
+ return {
36
+ command: rows.command,
37
+ count: rows.count ?? 0,
38
+ columns: names,
39
+ rows: rows.map((row) => row.map((value) => value === null ? null : value.toString("utf8")))
40
+ };
41
+ }
42
+ async function executeScript(client, text) {
43
+ try {
44
+ await client.unsafe(text).simple().raw();
45
+ } catch (error) {
46
+ throw toPostgresJsError(error);
47
+ }
48
+ }
49
+ /**
50
+ * Throws once closing starts.
51
+ *
52
+ * `sql.end()` ends the pool only after a few microtasks, and the pool still queues and runs a statement sent before then.
53
+ */
54
+ function assertOpen(operation) {
55
+ if (closing) throw new ConnectionError(`Cannot ${operation}: the driver is closed`);
56
+ }
57
+ return {
58
+ async execute(statement) {
59
+ assertOpen("execute a statement");
60
+ return execute(sql, statement);
61
+ },
62
+ async executeScript(text) {
63
+ assertOpen("execute a script");
64
+ return executeScript(sql, text);
65
+ },
66
+ async acquire() {
67
+ assertOpen("acquire a connection");
68
+ let connection;
69
+ try {
70
+ connection = await sql.reserve();
71
+ } catch (error) {
72
+ throw toDriverError(error);
73
+ }
74
+ return {
75
+ execute: (statement) => execute(connection, statement),
76
+ executeScript: (text) => executeScript(connection, text),
77
+ release: () => connection.release()
78
+ };
79
+ },
80
+ close() {
81
+ closing ??= sql.end();
82
+ return closing;
83
+ }
84
+ };
85
+ }
86
+ export { postgresJsDriver };