@coffre/db 0.14.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/change-records-_fKvahvs.js +55 -0
- package/dist/connect.d.ts +22 -10
- package/dist/connect.js +74 -12
- package/dist/database-CVteLhp1.d.ts +410 -0
- package/dist/{database-BadNeeN-.js → database-D55ENPX-.js} +46 -25
- package/dist/dialect.d.ts +4 -3
- package/dist/dialect.js +14 -5
- package/dist/grants.d.ts +4 -4
- package/dist/grants.js +4 -4
- package/dist/hyperdrive.d.ts +48 -23
- package/dist/hyperdrive.js +168 -91
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/log-DaD7fyUq.d.ts +722 -0
- package/dist/log.d.ts +2 -596
- package/dist/log.js +224 -10
- package/dist/migrate.d.ts +43 -3
- package/dist/migrate.js +149 -29
- package/dist/migrations/postgres/0021_prune_vault_releases.sql +19 -0
- package/dist/migrations/postgres/0022_log_015.sql +672 -0
- package/dist/migrations/postgres/0023_change_path.sql +153 -0
- package/dist/migrations/postgres/0024_use_015.sql +556 -0
- package/dist/migrations/postgres/0025_app_change.sql +221 -0
- package/dist/migrations/postgres/0026_issue_lock.sql +106 -0
- package/dist/migrations/postgres/0027_retention.sql +118 -0
- package/dist/migrations/postgres/0028_use_stalls.sql +594 -0
- package/dist/migrations/postgres/0029_use_objects.sql +887 -0
- package/dist/migrations/postgres/0030_unarchive_restores.sql +259 -0
- package/dist/migrations/postgres/0031_app_read.sql +65 -0
- package/dist/migrations/postgres/0032_secrets_live_index.sql +4 -0
- package/dist/migrations/postgres/0033_use_generations.sql +582 -0
- package/dist/migrations/postgres/0034_vault_credentials.sql +153 -0
- package/dist/migrations/postgres/0035_first_days.sql +3 -0
- package/dist/migrations/postgres/0036_vault_issues.sql +91 -0
- package/dist/migrations/postgres/0037_vault_bindings.sql +271 -0
- package/dist/migrations/postgres/0040_binding_origin.sql +159 -0
- package/dist/migrations/postgres/0041_cutover.sql +132 -0
- package/dist/migrations/postgres/0042_sign_in_index.sql +7 -0
- package/dist/migrations/postgres/0043_issue_revoke_index.sql +4 -0
- package/dist/migrations/postgres/0044_use_by_name.sql +705 -0
- package/dist/migrations/postgres/0045_decide_first.sql +878 -0
- package/dist/migrations/postgres/0046_way_in_uses.sql +1149 -0
- package/dist/migrations/postgres/0047_cutover_finish.sql +268 -0
- package/dist/migrations/postgres/0048_use_batch.sql +59 -0
- package/dist/migrations/postgres/0049_reads.sql +171 -0
- package/dist/migrations/postgres/meta/0021_snapshot.json +3334 -0
- package/dist/migrations/postgres/meta/0022_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0023_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0024_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0025_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0026_snapshot.json +3831 -0
- package/dist/migrations/postgres/meta/0027_snapshot.json +3853 -0
- package/dist/migrations/postgres/meta/0028_snapshot.json +3853 -0
- package/dist/migrations/postgres/meta/0029_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0030_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0031_snapshot.json +3859 -0
- package/dist/migrations/postgres/meta/0032_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0033_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0034_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0035_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0036_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0037_snapshot.json +3875 -0
- package/dist/migrations/postgres/meta/0038_snapshot.json +3881 -0
- package/dist/migrations/postgres/meta/0039_snapshot.json +3881 -0
- package/dist/migrations/postgres/meta/0040_snapshot.json +3931 -0
- package/dist/migrations/postgres/meta/0041_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0042_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0043_snapshot.json +3981 -0
- package/dist/migrations/postgres/meta/0044_snapshot.json +3991 -0
- package/dist/migrations/postgres/meta/0045_snapshot.json +3997 -0
- package/dist/migrations/postgres/meta/0046_snapshot.json +3997 -0
- package/dist/migrations/postgres/meta/0047_snapshot.json +4234 -0
- package/dist/migrations/postgres/meta/_journal.json +189 -0
- package/dist/migrations/sqlite/0018_log_015.sql +84 -0
- package/dist/migrations/sqlite/0019_retention.sql +2 -0
- package/dist/migrations/sqlite/0020_ended_references.sql +1 -0
- package/dist/migrations/sqlite/0021_secrets_live_index.sql +1 -0
- package/dist/migrations/sqlite/0022_binding_origin.sql +1 -0
- package/dist/migrations/sqlite/0023_sign_in_index.sql +2 -0
- package/dist/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
- package/dist/migrations/sqlite/0025_way_in_uses.sql +42 -0
- package/dist/migrations/sqlite/0026_cutover_finish.sql +11 -0
- package/dist/migrations/sqlite/0027_reads.sql +38 -0
- package/dist/migrations/sqlite/meta/0018_snapshot.json +3529 -0
- package/dist/migrations/sqlite/meta/0019_snapshot.json +3538 -0
- package/dist/migrations/sqlite/meta/0020_snapshot.json +3545 -0
- package/dist/migrations/sqlite/meta/0021_snapshot.json +3553 -0
- package/dist/migrations/sqlite/meta/0022_snapshot.json +3560 -0
- package/dist/migrations/sqlite/meta/0023_snapshot.json +3579 -0
- package/dist/migrations/sqlite/meta/0024_snapshot.json +3598 -0
- package/dist/migrations/sqlite/meta/0025_snapshot.json +3609 -0
- package/dist/migrations/sqlite/meta/0026_snapshot.json +3616 -0
- package/dist/migrations/sqlite/meta/0027_snapshot.json +3834 -0
- package/dist/migrations/sqlite/meta/_journal.json +70 -0
- package/dist/platform-Y3gICK6n.d.ts +58 -0
- package/dist/portable.d.ts +2 -2
- package/dist/postgres-BI3xTnyw.js +1718 -0
- package/dist/postgres-platform-node.d.ts +14 -0
- package/dist/postgres-platform-node.js +21 -0
- package/dist/postgres-platform-workerd.d.ts +61 -0
- package/dist/postgres-platform-workerd.js +163 -0
- package/dist/{schema-z8GcN_Xu.d.ts → schema-DgoPqqsI.d.ts} +1329 -9
- package/dist/schema-sqlite.d.ts +1308 -8
- package/dist/schema-sqlite.js +156 -20
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +261 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +229 -22
- package/package.json +13 -5
- package/src/active-subject.ts +9 -0
- package/src/change-records.ts +38 -0
- package/src/connect.ts +67 -13
- package/src/database.ts +67 -51
- package/src/dialect.ts +17 -8
- package/src/grants.ts +3 -3
- package/src/hyperdrive.ts +181 -104
- package/src/log.ts +244 -11
- package/src/migrate.ts +157 -30
- package/src/migrations/postgres/0021_prune_vault_releases.sql +19 -0
- package/src/migrations/postgres/0022_log_015.sql +672 -0
- package/src/migrations/postgres/0023_change_path.sql +153 -0
- package/src/migrations/postgres/0024_use_015.sql +556 -0
- package/src/migrations/postgres/0025_app_change.sql +221 -0
- package/src/migrations/postgres/0026_issue_lock.sql +106 -0
- package/src/migrations/postgres/0027_retention.sql +118 -0
- package/src/migrations/postgres/0028_use_stalls.sql +594 -0
- package/src/migrations/postgres/0029_use_objects.sql +887 -0
- package/src/migrations/postgres/0030_unarchive_restores.sql +259 -0
- package/src/migrations/postgres/0031_app_read.sql +65 -0
- package/src/migrations/postgres/0032_secrets_live_index.sql +4 -0
- package/src/migrations/postgres/0033_use_generations.sql +582 -0
- package/src/migrations/postgres/0034_vault_credentials.sql +153 -0
- package/src/migrations/postgres/0035_first_days.sql +3 -0
- package/src/migrations/postgres/0036_vault_issues.sql +91 -0
- package/src/migrations/postgres/0037_vault_bindings.sql +271 -0
- package/src/migrations/postgres/0040_binding_origin.sql +159 -0
- package/src/migrations/postgres/0041_cutover.sql +132 -0
- package/src/migrations/postgres/0042_sign_in_index.sql +7 -0
- package/src/migrations/postgres/0043_issue_revoke_index.sql +4 -0
- package/src/migrations/postgres/0044_use_by_name.sql +705 -0
- package/src/migrations/postgres/0045_decide_first.sql +878 -0
- package/src/migrations/postgres/0046_way_in_uses.sql +1149 -0
- package/src/migrations/postgres/0047_cutover_finish.sql +268 -0
- package/src/migrations/postgres/0048_use_batch.sql +59 -0
- package/src/migrations/postgres/0049_reads.sql +171 -0
- package/src/migrations/postgres/meta/0021_snapshot.json +3334 -0
- package/src/migrations/postgres/meta/0022_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0023_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0024_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0025_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0026_snapshot.json +3831 -0
- package/src/migrations/postgres/meta/0027_snapshot.json +3853 -0
- package/src/migrations/postgres/meta/0028_snapshot.json +3853 -0
- package/src/migrations/postgres/meta/0029_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0030_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0031_snapshot.json +3859 -0
- package/src/migrations/postgres/meta/0032_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0033_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0034_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0035_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0036_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0037_snapshot.json +3875 -0
- package/src/migrations/postgres/meta/0038_snapshot.json +3881 -0
- package/src/migrations/postgres/meta/0039_snapshot.json +3881 -0
- package/src/migrations/postgres/meta/0040_snapshot.json +3931 -0
- package/src/migrations/postgres/meta/0041_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0042_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0043_snapshot.json +3981 -0
- package/src/migrations/postgres/meta/0044_snapshot.json +3991 -0
- package/src/migrations/postgres/meta/0045_snapshot.json +3997 -0
- package/src/migrations/postgres/meta/0046_snapshot.json +3997 -0
- package/src/migrations/postgres/meta/0047_snapshot.json +4234 -0
- package/src/migrations/postgres/meta/_journal.json +189 -0
- package/src/migrations/sqlite/0018_log_015.sql +84 -0
- package/src/migrations/sqlite/0019_retention.sql +2 -0
- package/src/migrations/sqlite/0020_ended_references.sql +1 -0
- package/src/migrations/sqlite/0021_secrets_live_index.sql +1 -0
- package/src/migrations/sqlite/0022_binding_origin.sql +1 -0
- package/src/migrations/sqlite/0023_sign_in_index.sql +2 -0
- package/src/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
- package/src/migrations/sqlite/0025_way_in_uses.sql +42 -0
- package/src/migrations/sqlite/0026_cutover_finish.sql +11 -0
- package/src/migrations/sqlite/0027_reads.sql +38 -0
- package/src/migrations/sqlite/meta/0018_snapshot.json +3529 -0
- package/src/migrations/sqlite/meta/0019_snapshot.json +3538 -0
- package/src/migrations/sqlite/meta/0020_snapshot.json +3545 -0
- package/src/migrations/sqlite/meta/0021_snapshot.json +3553 -0
- package/src/migrations/sqlite/meta/0022_snapshot.json +3560 -0
- package/src/migrations/sqlite/meta/0023_snapshot.json +3579 -0
- package/src/migrations/sqlite/meta/0024_snapshot.json +3598 -0
- package/src/migrations/sqlite/meta/0025_snapshot.json +3609 -0
- package/src/migrations/sqlite/meta/0026_snapshot.json +3616 -0
- package/src/migrations/sqlite/meta/0027_snapshot.json +3834 -0
- package/src/migrations/sqlite/meta/_journal.json +70 -0
- package/src/postgres-js/README.md +183 -0
- package/src/postgres-js/UNLICENSE +24 -0
- package/src/postgres-js/bytes.ts +89 -0
- package/src/postgres-js/connection.ts +882 -0
- package/src/postgres-js/errors.ts +84 -0
- package/src/postgres-js/index.ts +516 -0
- package/src/postgres-js/platform.node.ts +29 -0
- package/src/postgres-js/platform.ts +60 -0
- package/src/postgres-js/platform.workerd.ts +225 -0
- package/src/postgres-js/query.ts +123 -0
- package/src/postgres-js/queue.ts +42 -0
- package/src/postgres-js/result.ts +46 -0
- package/src/postgres-js/types.ts +52 -0
- package/src/postgres.ts +375 -26
- package/src/schema-version.ts +2 -2
- package/src/schema.sqlite.ts +200 -9
- package/src/schema.ts +294 -8
- package/src/verify-runtime.ts +18 -29
- package/LICENSE +0 -21
- package/dist/database-CFhTnnzM.d.ts +0 -73
- package/dist/postgres-Bmar8y0q.js +0 -29
- package/dist/relations-BfR3JjT1.js +0 -38
- package/src/relations.ts +0 -40
package/src/postgres.ts
CHANGED
|
@@ -1,35 +1,384 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import { checkServerIdentity, type ConnectionOptions } from 'node:tls';
|
|
2
4
|
|
|
3
|
-
import {
|
|
4
|
-
import type { ClientConfig } from 'pg';
|
|
5
|
+
import postgres, { type Options, type Sql } from './postgres-js/index.ts';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A client of coffre's postgres.js (`./postgres-js`): statements (`unsafe`),
|
|
9
|
+
* transactions (`begin`), a connection held (`reserve`), and closing it.
|
|
10
|
+
*/
|
|
11
|
+
export type PostgresClient = Sql;
|
|
12
|
+
|
|
13
|
+
/** What a client may be opened with beside its URL. */
|
|
14
|
+
export type ClientOptions = Pick<Options, 'max' | 'idle_timeout' | 'connect_timeout' | 'max_lifetime' | 'onnotice' | 'debug' | 'onclose'> & {
|
|
15
|
+
/** The stream to speak Postgres on, made for each connection, rather than a TCP socket: a tunnel's. */
|
|
16
|
+
socket?: Options['socket'];
|
|
17
|
+
/** Postgres's `application_name` for the connection. */
|
|
18
|
+
application_name?: string;
|
|
19
|
+
/** Run-time parameters sent when the connection starts, `statement_timeout` and the like. */
|
|
20
|
+
connection?: Record<string, string | number | boolean>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A client for `url`, as coffre opens every one: the driver's defaults (10
|
|
25
|
+
* connections, every statement unnamed, as node-postgres sends them), but
|
|
26
|
+
*
|
|
27
|
+
* - notices dropped rather than logged;
|
|
28
|
+
* - no connection closed for its age (`max_lifetime`), as node-postgres
|
|
29
|
+
* closed none: a backup's session holds its snapshot while pg_dump reads.
|
|
30
|
+
*
|
|
31
|
+
* The driver reads no array type from `pg_type` (postgres.js's
|
|
32
|
+
* `fetch_types`, a round trip more per connection), and would answer one as
|
|
33
|
+
* its text: Postgres's own are taken as known instead (`ARRAYS`). Arrays and
|
|
34
|
+
* dates go as node-postgres sends them (`asPgSends`).
|
|
35
|
+
*/
|
|
36
|
+
export function postgresClient(url: string, options: ClientOptions = {}): PostgresClient {
|
|
37
|
+
const { application_name, connection, ...rest } = options;
|
|
38
|
+
const target = postgresOptions(url);
|
|
39
|
+
return asPgSends(arrayTypes(postgres({
|
|
40
|
+
onnotice: () => {},
|
|
41
|
+
max_lifetime: null,
|
|
42
|
+
// The URL's own (its TLS, its sslnegotiation) come after, and the caller's last.
|
|
43
|
+
...target,
|
|
44
|
+
...rest,
|
|
45
|
+
connection: {
|
|
46
|
+
...target.connection,
|
|
47
|
+
...(application_name === undefined ? {} : { application_name }),
|
|
48
|
+
...connection,
|
|
49
|
+
},
|
|
50
|
+
})));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A statement asked of a session that lost its connection, and with it its transaction, its locks and its settings. */
|
|
54
|
+
export class SessionLost extends Error {
|
|
55
|
+
constructor() {
|
|
56
|
+
super('the database session was lost: its connection closed, and with it its transaction, locks and settings');
|
|
57
|
+
this.name = 'SessionLost';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** What is asked of a session: each fails once it is lost. */
|
|
62
|
+
const ASKED = new Set<PropertyKey>(['unsafe', 'begin', 'reserve']);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One connection to `url`, kept as a session, as node-postgres's Client
|
|
66
|
+
* kept one: what a statement sets (SET, BEGIN, a session's advisory lock)
|
|
67
|
+
* holds for every statement after. A client of postgres.js would dial a new
|
|
68
|
+
* connection for the next statement once its own closed, which holds none of
|
|
69
|
+
* it: a migration going on without its lock, a probe meant to be rolled back
|
|
70
|
+
* committed, a backup read outside its snapshot. A session does not: once
|
|
71
|
+
* its connection has closed, by the server, the network or `end`, every
|
|
72
|
+
* statement after fails, `SessionLost`.
|
|
73
|
+
*/
|
|
74
|
+
export function postgresSession(url: string, options: Omit<ClientOptions, 'max'> = {}): PostgresClient {
|
|
75
|
+
let lost = false;
|
|
76
|
+
// `onclose`, if given, hears the session's connection close.
|
|
77
|
+
const client = postgresClient(url, { ...options, max: 1, onclose: (id) => ((lost = true), options.onclose?.(id)) });
|
|
78
|
+
/** A statement refused: rejected, and still a statement, whose `values()` and `execute()` Drizzle and callers ask for. */
|
|
79
|
+
const refused = () => {
|
|
80
|
+
const answer = Promise.reject(new SessionLost());
|
|
81
|
+
answer.catch(() => {});
|
|
82
|
+
return Object.assign(answer, { values: () => answer, execute: () => answer });
|
|
83
|
+
};
|
|
84
|
+
return new Proxy(client, {
|
|
85
|
+
get(target, key) {
|
|
86
|
+
const value: unknown = Reflect.get(target, key, target);
|
|
87
|
+
if (typeof value !== 'function' || !ASKED.has(key)) return value;
|
|
88
|
+
return (...args: unknown[]) => (lost ? refused() : (value as (...args: unknown[]) => unknown).apply(target, args));
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `client`, sending a statement's values as node-postgres sends them: an
|
|
95
|
+
* array as Postgres's text for one (`{"a","b"}`, bytes as `\\x…`), a date
|
|
96
|
+
* as its ISO text and a plain object as its JSON, each for the server to
|
|
97
|
+
* type, with the statement, in one round trip; `undefined` as NULL. Its
|
|
98
|
+
* transactions', savepoints' and reserved connections' alike. The driver
|
|
99
|
+
* would type an array by its first element, the element's type rather than
|
|
100
|
+
* the array's, and so bind `[1, 2]` as one integer and mistype a `bytea[]`;
|
|
101
|
+
* and it would ask the server first how to send an array of text.
|
|
102
|
+
*/
|
|
103
|
+
function asPgSends<T extends object>(client: T): T {
|
|
104
|
+
return new Proxy(client, {
|
|
105
|
+
get(target, key) {
|
|
106
|
+
const value: unknown = Reflect.get(target, key, target);
|
|
107
|
+
if (key === 'unsafe') {
|
|
108
|
+
return (text: string, values?: unknown[], ...rest: unknown[]) =>
|
|
109
|
+
(value as (...args: unknown[]) => unknown).call(target, text, Array.isArray(values) ? values.map(sent) : values, ...rest);
|
|
110
|
+
}
|
|
111
|
+
if (key === 'group') {
|
|
112
|
+
return (statements: { query: string; parameters?: unknown[]; arrays?: boolean }[]) =>
|
|
113
|
+
(value as (...args: unknown[]) => unknown).call(target, statements.map((statement) => ({ ...statement, parameters: statement.parameters?.map(sent) })));
|
|
114
|
+
}
|
|
115
|
+
if (key === 'begin' || key === 'savepoint' || key === 'reserve') {
|
|
116
|
+
return (...args: unknown[]) => {
|
|
117
|
+
const work = args.at(-1);
|
|
118
|
+
if (typeof work !== 'function') return (value as (...args: unknown[]) => Promise<object>).apply(target, args).then(asPgSends);
|
|
119
|
+
return (value as (...args: unknown[]) => unknown).apply(target, [...args.slice(0, -1), (tx: object) => work(asPgSends(tx))]);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A value as node-postgres sends it: an array, a date or a plain object (as
|
|
129
|
+
* its JSON) as text, bytes of any view as bytes, `undefined` as NULL. What
|
|
130
|
+
* else there is goes as it is.
|
|
131
|
+
*/
|
|
132
|
+
function sent(value: unknown): unknown {
|
|
133
|
+
if (value === undefined) return null;
|
|
134
|
+
if (Array.isArray(value)) return arrayText(value);
|
|
135
|
+
if (value instanceof Date) return value.toISOString();
|
|
136
|
+
const bytes = bytesOf(value);
|
|
137
|
+
if (bytes !== null) return bytes;
|
|
138
|
+
return plain(value) ? JSON.stringify(value) : value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** An object made as `{…}` is: no class of its own. */
|
|
142
|
+
function plain(value: unknown): value is object {
|
|
143
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
144
|
+
const prototype: unknown = Object.getPrototypeOf(value);
|
|
145
|
+
return prototype === Object.prototype || prototype === null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Bytes, from any view of them or their buffer, as a Uint8Array; null for anything else. */
|
|
149
|
+
function bytesOf(value: unknown): Uint8Array | null {
|
|
150
|
+
if (value instanceof Uint8Array) return value;
|
|
151
|
+
if (ArrayBuffer.isView(value)) return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
152
|
+
if (value instanceof ArrayBuffer) return Buffer.from(value);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** An array as Postgres's text for one, as node-postgres writes it: elements quoted, bytes as hex, nulls as NULL. */
|
|
157
|
+
function arrayText(values: unknown[]): string {
|
|
158
|
+
return `{${values.map((value) => {
|
|
159
|
+
if (value === null || value === undefined) return 'NULL';
|
|
160
|
+
if (Array.isArray(value)) return arrayText(value);
|
|
161
|
+
const bytes = bytesOf(value);
|
|
162
|
+
if (bytes !== null) return `\\\\x${Buffer.from(bytes).toString('hex')}`;
|
|
163
|
+
const text = value instanceof Date ? value.toISOString() : typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
164
|
+
return `"${text.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
165
|
+
}).join(',')}}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Postgres's built-in types and their arrays, element and array oid, as
|
|
170
|
+
* `fetch_types` would read them from `pg_type` at each connection: those a
|
|
171
|
+
* hand-written statement may send or answer, as node-postgres knew them. An
|
|
172
|
+
* array of any other type (ranges, geometry, `macaddr8`, a type of one's
|
|
173
|
+
* own) is answered as Postgres's text for it: add it here to read it.
|
|
174
|
+
*/
|
|
175
|
+
const ARRAYS = [
|
|
176
|
+
[16, 1000], [17, 1001], [18, 1002], [19, 1003], [20, 1016], [21, 1005], [23, 1007], [25, 1009], [26, 1028],
|
|
177
|
+
[114, 199], [650, 651], [700, 1021], [701, 1022], [829, 1040], [869, 1041], [1042, 1014], [1043, 1015],
|
|
178
|
+
[1082, 1182], [1083, 1183], [1114, 1115], [1184, 1185], [1186, 1187], [1266, 1270], [1700, 1231], [2950, 2951],
|
|
179
|
+
[3802, 3807],
|
|
180
|
+
] as const;
|
|
181
|
+
|
|
182
|
+
/** `client`, which reads Postgres's arrays as node-postgres reads them, each element by its type's parser. */
|
|
183
|
+
function arrayTypes(client: PostgresClient): PostgresClient {
|
|
184
|
+
const parsers = client.options.parsers;
|
|
185
|
+
for (const [element, array] of ARRAYS) {
|
|
186
|
+
const parse = parsers[element];
|
|
187
|
+
parsers[array] = (text: string) => arrayOf(text, parse);
|
|
188
|
+
}
|
|
189
|
+
return client;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Postgres's text for an array, `{a,"b,c",NULL,{1,2}}`, as its values: a
|
|
194
|
+
* quoted element is text (`"NULL"` included), an unquoted NULL is null, and
|
|
195
|
+
* explicit bounds (`[0:1]={…}`) are read past.
|
|
196
|
+
*/
|
|
197
|
+
export function arrayOf(text: string, parse?: (text: string) => unknown): unknown[] {
|
|
198
|
+
let at = text.startsWith('[') ? text.indexOf('=') + 1 : 0;
|
|
199
|
+
const list = (): unknown[] => {
|
|
200
|
+
const values: unknown[] = [];
|
|
201
|
+
at += 1;
|
|
202
|
+
if (text[at] === '}') {
|
|
203
|
+
at += 1;
|
|
204
|
+
return values;
|
|
205
|
+
}
|
|
206
|
+
for (;;) {
|
|
207
|
+
if (text[at] === '{') {
|
|
208
|
+
values.push(list());
|
|
209
|
+
} else if (text[at] === '"') {
|
|
210
|
+
let element = '';
|
|
211
|
+
for (at += 1; text[at] !== '"'; at += 1) element += text[text[at] === '\\' ? ++at : at];
|
|
212
|
+
at += 1;
|
|
213
|
+
values.push(parse ? parse(element) : element);
|
|
214
|
+
} else {
|
|
215
|
+
const start = at;
|
|
216
|
+
while (text[at] !== ',' && text[at] !== '}') at += 1;
|
|
217
|
+
const element = text.slice(start, at);
|
|
218
|
+
values.push(element === 'NULL' ? null : parse ? parse(element) : element);
|
|
219
|
+
}
|
|
220
|
+
if (text[at++] === '}') return values;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
return list();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The Postgres types Drizzle maps from their text itself: dates and times,
|
|
228
|
+
* and arrays of them and of numerics, as Drizzle's own drivers set them up,
|
|
229
|
+
* postgres.js's (`construct`) and node-postgres's alike.
|
|
230
|
+
*/
|
|
231
|
+
const TEXT_TYPES = [1184, 1082, 1083, 1114, 1182, 1185, 1115, 1231];
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* `client`, set up as Drizzle's own postgres.js driver sets one up: dates
|
|
235
|
+
* and times come as Postgres's text, which each column maps, and JSON goes
|
|
236
|
+
* as Drizzle writes it. For every statement of the client, `unsafe`'s
|
|
237
|
+
* included: a client made a database (`createDatabase`) answers a raw
|
|
238
|
+
* `now()` as text, not a Date.
|
|
239
|
+
*/
|
|
240
|
+
export function textTypes(client: PostgresClient): PostgresClient {
|
|
241
|
+
const identity = (value: unknown) => value;
|
|
242
|
+
for (const type of TEXT_TYPES) {
|
|
243
|
+
client.options.parsers[type] = identity;
|
|
244
|
+
client.options.serializers[type] = identity;
|
|
245
|
+
}
|
|
246
|
+
client.options.serializers[114] = identity;
|
|
247
|
+
client.options.serializers[3802] = identity;
|
|
248
|
+
return client;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The connection `url` names, as node-postgres and its pg-connection-string
|
|
253
|
+
* read one. The driver reads no URL: it is given the parts. (Upstream
|
|
254
|
+
* postgres.js read URLs otherwise: it sent every query parameter it did not
|
|
255
|
+
* know to the server as a setting, which refused the connection over one it
|
|
256
|
+
* did not know either, `sslrootcert` or `uselibpqcompat`; its
|
|
257
|
+
* `sslmode=require` skipped the certificate check, where coffre's URLs, as
|
|
258
|
+
* pg reads them, verify; and it checked an IP address against `localhost`.)
|
|
259
|
+
*
|
|
260
|
+
* TLS is:
|
|
261
|
+
*
|
|
262
|
+
* no sslmode, sslmode=disable none
|
|
263
|
+
* sslmode=prefer, require, verify-ca, the certificate chain and the host verified
|
|
264
|
+
* verify-full; ssl=true or 1
|
|
265
|
+
* sslmode=no-verify TLS, nothing verified
|
|
266
|
+
* uselibpqcompat=true libpq's modes: prefer and require verify nothing
|
|
267
|
+
* (require with a sslrootcert, the chain), verify-ca
|
|
268
|
+
* the chain, verify-full the chain and the host
|
|
269
|
+
* sslrootcert=system Node's trusted CAs; only with verify-full or no sslmode
|
|
270
|
+
* sslrootcert, sslcert, sslkey (files) read from disk, as pg does
|
|
271
|
+
*
|
|
272
|
+
* The host is verified against the URL's own, an IP address included.
|
|
273
|
+
*/
|
|
274
|
+
export function postgresOptions(url: string): Partial<Options> & { connection: Record<string, string> } {
|
|
275
|
+
// A URL may name no host, its socket's directory in the query (`postgresql://u@/db?host=/var/run/postgresql`), which a
|
|
276
|
+
// URL parser refuses: as pg-connection-string does, it is read with a host in its place, and the host is the query's.
|
|
277
|
+
const hostless = /^[a-z]+:\/\/([^/?#@]*@)?\//i.test(url);
|
|
278
|
+
// Several hosts, which libpq tries in turn: node-postgres read them as one name, and so does this, but says so.
|
|
279
|
+
if (/^[a-z]+:\/\/([^/?#@]*@)?[^/?#]*,/i.test(url)) throw new Error('a Postgres URL names one host: several, as libpq takes them, are not supported');
|
|
280
|
+
let read: { query: Map<string, string>; host: string; port: number; user: string; pass: string; database: string | undefined };
|
|
9
281
|
try {
|
|
10
|
-
parsed = new URL(url);
|
|
282
|
+
const parsed = new URL(hostless ? url.replace(/^([a-z]+:\/\/([^/?#@]*@)?)/i, '$1localhost') : url);
|
|
283
|
+
// A parameter given empty is no parameter, as pg-connection-string reads one.
|
|
284
|
+
const query = new Map([...parsed.searchParams].filter(([, value]) => value !== ''));
|
|
285
|
+
const port = query.get('port') ?? (parsed.port || '5432');
|
|
286
|
+
if (!/^\d{1,5}$/.test(port) || Number(port) === 0 || Number(port) > 65535) throw new Error('no port');
|
|
287
|
+
read = {
|
|
288
|
+
query,
|
|
289
|
+
host: query.get('host') ?? decodeURIComponent(parsed.hostname.replace(/^\[(.*)\]$/, '$1')),
|
|
290
|
+
port: Number(port),
|
|
291
|
+
// As pg-connection-string reads them, a `user` or `password` parameter in the query wins.
|
|
292
|
+
user: query.get('user') ?? decodeURIComponent(parsed.username),
|
|
293
|
+
pass: query.get('password') ?? decodeURIComponent(parsed.password),
|
|
294
|
+
database: decodeURIComponent(parsed.pathname.slice(1)) || undefined,
|
|
295
|
+
};
|
|
11
296
|
} catch {
|
|
12
|
-
// A parse error must not print the URL's password.
|
|
297
|
+
// A parse error, a malformed escape or port included, must not print the URL's password.
|
|
13
298
|
throw new Error('invalid Postgres URL');
|
|
14
299
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
300
|
+
const { query, host, port, user, pass, database } = read;
|
|
301
|
+
// libpq would take the operating system's user; coffre reads nothing of where it runs, so the URL names one.
|
|
302
|
+
if (user === '') throw new Error('a Postgres URL names its user: postgresql://<user>@<host>/<database>');
|
|
303
|
+
if (host.includes(',')) throw new Error('a Postgres URL names one host: several, as libpq takes them, are not supported');
|
|
304
|
+
// node-postgres cancelled a statement past it, from the client: refused rather than ignored, the server's bound named.
|
|
305
|
+
if (query.has('query_timeout')) throw new Error("a Postgres URL's query_timeout is not supported: statement_timeout bounds a statement in the server");
|
|
306
|
+
// libpq's bound on opening a connection, in seconds, as the driver takes it: 0 for none.
|
|
307
|
+
const connectTimeout = query.get('connect_timeout');
|
|
308
|
+
if (connectTimeout !== undefined && !/^\d+$/.test(connectTimeout)) throw new Error('invalid Postgres URL');
|
|
309
|
+
// What libpq refuses, or the driver cannot do, is refused rather than read as something else.
|
|
310
|
+
const mode = query.get('sslmode');
|
|
311
|
+
if (mode !== undefined && !['disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full', 'no-verify'].includes(mode)) {
|
|
312
|
+
throw new Error(`a Postgres URL's sslmode is one of libpq's, not ${mode}`);
|
|
313
|
+
}
|
|
314
|
+
if (!['postgres', 'direct', undefined].includes(query.get('sslnegotiation'))) throw new Error("a Postgres URL's sslnegotiation is postgres or direct");
|
|
315
|
+
if (query.get('channel_binding') === 'require') throw new Error("a Postgres URL's channel_binding=require is not supported: SCRAM goes without channel binding");
|
|
316
|
+
// What node-postgres sent the server from the query, as it starts the connection: the rest it read itself, or not.
|
|
317
|
+
const connection: Record<string, string> = {};
|
|
318
|
+
for (const name of ['application_name', 'fallback_application_name', 'options', 'statement_timeout', 'lock_timeout', 'idle_in_transaction_session_timeout'] as const) {
|
|
319
|
+
const value = query.get(name);
|
|
320
|
+
if (value !== undefined) connection[name] = value;
|
|
321
|
+
}
|
|
322
|
+
const sslnegotiation = query.get('sslnegotiation') === 'direct' ? 'direct' : undefined;
|
|
323
|
+
return {
|
|
324
|
+
...(host.startsWith('/') ? { path: `${host}/.s.PGSQL.${port}` } : { host }),
|
|
325
|
+
port,
|
|
326
|
+
user,
|
|
327
|
+
pass,
|
|
328
|
+
database,
|
|
329
|
+
ssl: tlsFor(query, host, sslnegotiation !== undefined),
|
|
330
|
+
...(sslnegotiation === undefined ? {} : { sslnegotiation }),
|
|
331
|
+
...(connectTimeout === undefined ? {} : { connect_timeout: Number(connectTimeout) }),
|
|
332
|
+
connection,
|
|
333
|
+
} as Partial<Options> & { connection: Record<string, string> };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function tlsFor(query: Map<string, string>, host: string, direct: boolean): false | ConnectionOptions {
|
|
337
|
+
const mode = query.get('sslmode');
|
|
338
|
+
let rootcert = query.get('sslrootcert');
|
|
339
|
+
if (rootcert === 'system') {
|
|
340
|
+
if (mode !== undefined && mode !== 'verify-full') throw new Error('sslrootcert=system requires sslmode=verify-full');
|
|
341
|
+
if (!host || host.startsWith('/')) throw new Error('sslrootcert=system requires a Postgres hostname');
|
|
342
|
+
// Node's default trusted roots: no CA of our own.
|
|
343
|
+
rootcert = undefined;
|
|
344
|
+
}
|
|
345
|
+
const files = { cert: query.get('sslcert'), key: query.get('sslkey'), ca: rootcert };
|
|
346
|
+
const flag = query.get('ssl');
|
|
347
|
+
const wanted = mode !== undefined || Object.values(files).some((file) => file !== undefined) || query.get('sslrootcert') === 'system'
|
|
348
|
+
|| flag === 'true' || flag === '1' || direct;
|
|
349
|
+
if (!wanted || mode === 'disable') return false;
|
|
350
|
+
|
|
351
|
+
const tls: ConnectionOptions = {};
|
|
352
|
+
for (const [name, file] of Object.entries(files)) {
|
|
353
|
+
if (file !== undefined) tls[name as keyof typeof files] = readFileSync(file).toString();
|
|
354
|
+
}
|
|
355
|
+
// What opens an encrypted sslkey.
|
|
356
|
+
const passphrase = query.get('sslpassword');
|
|
357
|
+
if (passphrase !== undefined) tls.passphrase = passphrase;
|
|
358
|
+
const libpq = query.get('uselibpqcompat') === 'true';
|
|
359
|
+
const verify = !libpq
|
|
360
|
+
? mode === 'no-verify' ? 'none' : 'full'
|
|
361
|
+
: mode === 'prefer' || (mode === 'require' && tls.ca === undefined) ? 'none' : mode === 'require' || mode === 'verify-ca' ? 'chain' : 'full';
|
|
362
|
+
if (libpq && mode === 'verify-ca' && tls.ca === undefined) {
|
|
363
|
+
throw new Error('sslmode=verify-ca requires a CA in sslrootcert: with a public CA, it lets anyone with a certificate from it pass as the database. Use sslmode=verify-full');
|
|
364
|
+
}
|
|
365
|
+
if (verify === 'none') return { ...tls, rejectUnauthorized: false };
|
|
366
|
+
return {
|
|
367
|
+
...tls,
|
|
31
368
|
rejectUnauthorized: true,
|
|
32
|
-
|
|
369
|
+
// The driver names an IP address to TLS as nothing, which Node checks as localhost: the URL's host is checked instead.
|
|
370
|
+
checkServerIdentity: verify === 'chain' ? () => undefined : (_hostname, certificate) => checkServerIdentity(host, certificate),
|
|
371
|
+
...(isIP(host) === 0 ? { servername: host } : {}),
|
|
33
372
|
};
|
|
34
|
-
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** `name` as a Postgres identifier, quoted, as node-postgres's `escapeIdentifier` writes one. */
|
|
376
|
+
export function quoteIdentifier(name: string): string {
|
|
377
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** `text` as a Postgres string literal, as node-postgres's `escapeLiteral` writes one: `E'…'` when it holds a backslash. */
|
|
381
|
+
export function quoteLiteral(text: string): string {
|
|
382
|
+
const quoted = `'${text.replaceAll("'", "''").replaceAll('\\', '\\\\')}'`;
|
|
383
|
+
return text.includes('\\') ? ` E${quoted}` : quoted;
|
|
35
384
|
}
|
package/src/schema-version.ts
CHANGED
|
@@ -30,8 +30,8 @@ export async function migrated(db: Queryable): Promise<boolean> {
|
|
|
30
30
|
|
|
31
31
|
async function hasLedger(db: Queryable): Promise<boolean> {
|
|
32
32
|
if (engineOf(db) === 'postgres') {
|
|
33
|
-
const
|
|
34
|
-
return
|
|
33
|
+
const [ledger] = await db.execute<{ present: boolean }>(sql`SELECT to_regclass('drizzle.__drizzle_migrations') IS NOT NULL AS present`);
|
|
34
|
+
return ledger?.present === true;
|
|
35
35
|
}
|
|
36
36
|
const rows = await (db as unknown as { all<R>(query: SQL): Promise<R[]> }).all<{ present: number }>(
|
|
37
37
|
sql`SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = '__drizzle_migrations'`,
|