@kequtech/tenon-orm 0.2.2 → 0.3.1
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/CHANGELOG.md +46 -42
- package/README.md +4 -4
- package/dist/affected-rows.js +6 -6
- package/dist/baseline.js +4 -4
- package/dist/client.js +1 -1
- package/dist/connection.d.ts +15 -22
- package/dist/connection.js +122 -25
- package/dist/database.js +10 -17
- package/dist/driver/types.d.ts +1 -1
- package/dist/driver/types.js +11 -11
- package/dist/generate.js +7 -7
- package/dist/generation/catalog.d.ts +2 -2
- package/dist/generation/catalog.js +4 -4
- package/dist/generation/schema-summary.d.ts +3 -3
- package/dist/generation/schema-summary.js +11 -11
- package/dist/generation/table-types.js +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/inspection/catalog.d.ts +4 -3
- package/dist/inspection/catalog.js +13 -10
- package/dist/inspection/connection.d.ts +2 -2
- package/dist/inspection/connection.js +22 -22
- package/dist/inspection/protocol.d.ts +7 -0
- package/dist/inspection/protocol.js +45 -0
- package/dist/inspection/result.d.ts +1 -1
- package/dist/inspection/result.js +6 -6
- package/dist/inspection/types.d.ts +2 -2
- package/dist/migrations/connection.d.ts +3 -2
- package/dist/migrations/connection.js +18 -20
- package/dist/migrations/history.d.ts +4 -4
- package/dist/migrations/history.js +5 -5
- package/dist/migrations.js +7 -7
- package/dist/query-options.js +4 -4
- package/dist/query.js +2 -2
- package/dist/transaction.d.ts +2 -2
- package/dist/transaction.js +19 -14
- package/dist/types.d.ts +2 -2
- package/docs/adoption-guide.md +40 -27
- package/docs/agent-guide.md +55 -48
- package/docs/human-guide.md +25 -20
- package/docs/mutations.md +13 -10
- package/docs/query-inspection.md +12 -14
- package/docs/reference.md +304 -131
- package/package.json +3 -2
- package/dist/driver/array.d.ts +0 -5
- package/dist/driver/array.js +0 -70
- package/dist/driver/lifecycle.d.ts +0 -15
- package/dist/driver/lifecycle.js +0 -57
- package/dist/driver/listener.d.ts +0 -3
- package/dist/driver/listener.js +0 -63
- package/dist/driver/session.d.ts +0 -8
- package/dist/driver/session.js +0 -92
package/dist/driver/array.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const initialization = new WeakMap();
|
|
2
|
-
/** Preserve driver scalar parsers and serializers; replace only lossy array parsing. */
|
|
3
|
-
export function prepareArrays(sql) {
|
|
4
|
-
const existing = initialization.get(sql);
|
|
5
|
-
if (existing)
|
|
6
|
-
return existing;
|
|
7
|
-
const ready = (async () => {
|
|
8
|
-
const customArrays = new Set(Object.values(sql.options.types).flatMap((type) => type.from));
|
|
9
|
-
const arrays = await sql `
|
|
10
|
-
SELECT t.typarray AS oid, t.oid AS element, t.typdelim AS delimiter
|
|
11
|
-
FROM pg_catalog.pg_type t WHERE t.typarray <> 0`;
|
|
12
|
-
for (const type of arrays) {
|
|
13
|
-
if (customArrays.has(type.oid))
|
|
14
|
-
continue;
|
|
15
|
-
const scalar = sql.options.parsers[type.element] ?? ((value) => value);
|
|
16
|
-
sql.options.parsers[type.oid] = (value) => parseArray(value, scalar, type.delimiter);
|
|
17
|
-
}
|
|
18
|
-
})();
|
|
19
|
-
initialization.set(sql, ready);
|
|
20
|
-
void ready.catch(() => initialization.delete(sql));
|
|
21
|
-
return ready;
|
|
22
|
-
}
|
|
23
|
-
/** PostgreSQL text arrays: only unquoted NULL is null; quoted values use the scalar parser. */
|
|
24
|
-
export function parseArray(text, parse, delimiter = ',') {
|
|
25
|
-
let position = text.startsWith('[') ? text.indexOf('=') + 1 : 0;
|
|
26
|
-
function array() {
|
|
27
|
-
if (text[position++] !== '{')
|
|
28
|
-
throw new Error('Invalid PostgreSQL array.');
|
|
29
|
-
const values = [];
|
|
30
|
-
while (position < text.length && text[position] !== '}') {
|
|
31
|
-
if (text[position] === '{')
|
|
32
|
-
values.push(array());
|
|
33
|
-
else {
|
|
34
|
-
let value = '';
|
|
35
|
-
const quoted = text[position] === '"';
|
|
36
|
-
if (quoted)
|
|
37
|
-
position++;
|
|
38
|
-
while (position < text.length) {
|
|
39
|
-
const char = text[position];
|
|
40
|
-
if (char === '\\') {
|
|
41
|
-
position++;
|
|
42
|
-
value += text[position++] ?? '';
|
|
43
|
-
}
|
|
44
|
-
else if (quoted && char === '"') {
|
|
45
|
-
position++;
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
else if (!quoted && (char === delimiter || char === '}'))
|
|
49
|
-
break;
|
|
50
|
-
else {
|
|
51
|
-
value += char;
|
|
52
|
-
position++;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
values.push(!quoted && value === 'NULL' ? null : parse(value));
|
|
56
|
-
}
|
|
57
|
-
if (text[position] === delimiter)
|
|
58
|
-
position++;
|
|
59
|
-
else if (text[position] !== '}')
|
|
60
|
-
throw new Error('Invalid PostgreSQL array separator.');
|
|
61
|
-
}
|
|
62
|
-
if (text[position++] !== '}')
|
|
63
|
-
throw new Error('Unterminated PostgreSQL array.');
|
|
64
|
-
return values;
|
|
65
|
-
}
|
|
66
|
-
const result = array();
|
|
67
|
-
if (position !== text.length)
|
|
68
|
-
throw new Error('Unexpected PostgreSQL array suffix.');
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type postgres from 'postgres';
|
|
2
|
-
export interface Watch {
|
|
3
|
-
failure?: Error;
|
|
4
|
-
check(): void;
|
|
5
|
-
closed: Promise<never>;
|
|
6
|
-
reject(error: Error): void;
|
|
7
|
-
}
|
|
8
|
-
interface State {
|
|
9
|
-
acquiring: Set<(error: Error) => void>;
|
|
10
|
-
active: Map<number, Watch>;
|
|
11
|
-
capture(marker: string, watch: Watch): () => void;
|
|
12
|
-
}
|
|
13
|
-
/** Observe the driver's public connection callbacks; never reuse a reconnected transaction. */
|
|
14
|
-
export declare function observeConnections(sql: postgres.Sql): State;
|
|
15
|
-
export {};
|
package/dist/driver/lifecycle.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const states = new WeakMap();
|
|
2
|
-
function disconnected() {
|
|
3
|
-
return Object.assign(new Error('The reserved PostgreSQL connection closed.'), {
|
|
4
|
-
code: 'CONNECTION_CLOSED',
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
/** Observe the driver's public connection callbacks; never reuse a reconnected transaction. */
|
|
8
|
-
export function observeConnections(sql) {
|
|
9
|
-
const previous = states.get(sql);
|
|
10
|
-
if (previous)
|
|
11
|
-
return previous;
|
|
12
|
-
const markers = new Map();
|
|
13
|
-
let debug = sql.options.debug;
|
|
14
|
-
// Capture the bootstrap connection ID, then restore debug before application work.
|
|
15
|
-
// Leaving it enabled would make the driver include SQL and values in enumerable errors.
|
|
16
|
-
const captureDebug = (id, text, values, types) => {
|
|
17
|
-
const watch = markers.get(text);
|
|
18
|
-
if (watch) {
|
|
19
|
-
state.active.set(id, watch);
|
|
20
|
-
markers.delete(text);
|
|
21
|
-
}
|
|
22
|
-
if (!markers.size)
|
|
23
|
-
sql.options.debug = debug;
|
|
24
|
-
if (typeof debug === 'function')
|
|
25
|
-
debug(id, text, values, types);
|
|
26
|
-
};
|
|
27
|
-
const state = {
|
|
28
|
-
acquiring: new Set(),
|
|
29
|
-
active: new Map(),
|
|
30
|
-
capture(marker, watch) {
|
|
31
|
-
if (!markers.size) {
|
|
32
|
-
debug = sql.options.debug;
|
|
33
|
-
sql.options.debug = captureDebug;
|
|
34
|
-
}
|
|
35
|
-
markers.set(marker, watch);
|
|
36
|
-
return () => {
|
|
37
|
-
markers.delete(marker);
|
|
38
|
-
if (!markers.size)
|
|
39
|
-
sql.options.debug = debug;
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
const onclose = sql.options.onclose;
|
|
44
|
-
sql.options.onclose = (id) => {
|
|
45
|
-
const error = disconnected();
|
|
46
|
-
const watch = state.active.get(id);
|
|
47
|
-
if (watch) {
|
|
48
|
-
watch.failure ??= error;
|
|
49
|
-
watch.reject(watch.failure);
|
|
50
|
-
}
|
|
51
|
-
for (const reject of state.acquiring)
|
|
52
|
-
reject(error);
|
|
53
|
-
onclose?.(id);
|
|
54
|
-
};
|
|
55
|
-
states.set(sql, state);
|
|
56
|
-
return state;
|
|
57
|
-
}
|
package/dist/driver/listener.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { createConnection } from 'node:net';
|
|
2
|
-
function closedError() {
|
|
3
|
-
return Object.assign(new Error('The owned PostgreSQL listener is closed.'), {
|
|
4
|
-
code: 'CONNECTION_ENDED',
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
/** Prevent an owned LISTEN client's queued reconnect from reopening it after shutdown. */
|
|
8
|
-
export function guardListenerShutdown(sql) {
|
|
9
|
-
const options = sql.options;
|
|
10
|
-
const listen = sql.listen;
|
|
11
|
-
const sockets = new Set();
|
|
12
|
-
let closed = false;
|
|
13
|
-
let hostIndex = 0;
|
|
14
|
-
function track(socket) {
|
|
15
|
-
// TLS adoption removes raw socket listeners; prune those closed transports on reconnect.
|
|
16
|
-
for (const previous of sockets)
|
|
17
|
-
if (previous.destroyed)
|
|
18
|
-
sockets.delete(previous);
|
|
19
|
-
sockets.add(socket);
|
|
20
|
-
socket.once('close', () => sockets.delete(socket));
|
|
21
|
-
if (closed) {
|
|
22
|
-
socket.destroy();
|
|
23
|
-
throw closedError();
|
|
24
|
-
}
|
|
25
|
-
return socket;
|
|
26
|
-
}
|
|
27
|
-
sql.listen = (...args) => {
|
|
28
|
-
if (closed)
|
|
29
|
-
return Promise.reject(closedError());
|
|
30
|
-
const previous = options.socket;
|
|
31
|
-
// Postgres.js synchronously clones these options for its dedicated max:1 listener.
|
|
32
|
-
// Restore immediately so ordinary queries retain their native transport configuration.
|
|
33
|
-
options.socket = (settings) => {
|
|
34
|
-
if (closed)
|
|
35
|
-
throw closedError();
|
|
36
|
-
if (previous)
|
|
37
|
-
return Promise.resolve(previous(settings)).then(track);
|
|
38
|
-
const host = settings.host[hostIndex];
|
|
39
|
-
const port = settings.port[hostIndex];
|
|
40
|
-
const socket = createConnection(settings.path ? { path: settings.path } : { host, port });
|
|
41
|
-
if (!settings.path) {
|
|
42
|
-
hostIndex = (hostIndex + 1) % settings.port.length;
|
|
43
|
-
// The driver's TLS negotiation uses this metadata for SNI and verification.
|
|
44
|
-
Object.assign(socket, { host, port });
|
|
45
|
-
}
|
|
46
|
-
// Node buffers startup writes while connecting; the driver retains its connect timeout,
|
|
47
|
-
// authentication, TLS negotiation and reconnect handling.
|
|
48
|
-
return track(socket);
|
|
49
|
-
};
|
|
50
|
-
try {
|
|
51
|
-
return listen(...args);
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
options.socket = previous;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
return () => {
|
|
58
|
-
closed = true;
|
|
59
|
-
for (const socket of sockets)
|
|
60
|
-
socket.destroy();
|
|
61
|
-
sockets.clear();
|
|
62
|
-
};
|
|
63
|
-
}
|
package/dist/driver/session.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type postgres from 'postgres';
|
|
2
|
-
import type { SqlConnection } from '../connection.js';
|
|
3
|
-
/** Pin a healthy session for transactions and advisory locks, with explicit failure cleanup. */
|
|
4
|
-
export declare function reserveConnection(sql: postgres.Sql, initial?: 'SELECT 1' | 'BEGIN'): Promise<{
|
|
5
|
-
sql: SqlConnection;
|
|
6
|
-
check: () => void;
|
|
7
|
-
release(discard?: boolean): Promise<void>;
|
|
8
|
-
}>;
|
package/dist/driver/session.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { prepareArrays } from './array.js';
|
|
2
|
-
import { observeConnections } from './lifecycle.js';
|
|
3
|
-
let nextLease = 0;
|
|
4
|
-
/** Pin a healthy session for transactions and advisory locks, with explicit failure cleanup. */
|
|
5
|
-
export async function reserveConnection(sql, initial = 'SELECT 1') {
|
|
6
|
-
await prepareArrays(sql);
|
|
7
|
-
const state = observeConnections(sql);
|
|
8
|
-
// Warm a connection before reserve(): 3.4.9 can strand a cold reserve after reconnect.
|
|
9
|
-
await sql `SELECT 1`;
|
|
10
|
-
let abandoned = false;
|
|
11
|
-
let rejectAcquisition;
|
|
12
|
-
const closed = new Promise((_, reject) => {
|
|
13
|
-
rejectAcquisition = reject;
|
|
14
|
-
});
|
|
15
|
-
state.acquiring.add(rejectAcquisition);
|
|
16
|
-
const pending = sql.reserve();
|
|
17
|
-
void pending.then((client) => {
|
|
18
|
-
if (abandoned)
|
|
19
|
-
client.release();
|
|
20
|
-
}, () => { });
|
|
21
|
-
let client;
|
|
22
|
-
try {
|
|
23
|
-
client = await Promise.race([pending, closed]);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
abandoned = true;
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
state.acquiring.delete(rejectAcquisition);
|
|
31
|
-
}
|
|
32
|
-
let rejectClosed;
|
|
33
|
-
const closedSession = new Promise((_, reject) => {
|
|
34
|
-
rejectClosed = reject;
|
|
35
|
-
});
|
|
36
|
-
void closedSession.catch(() => { });
|
|
37
|
-
const watch = {
|
|
38
|
-
closed: closedSession,
|
|
39
|
-
reject: rejectClosed,
|
|
40
|
-
check() {
|
|
41
|
-
if (watch.failure)
|
|
42
|
-
throw watch.failure;
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
const safe = {
|
|
46
|
-
async unsafe(text, input = [], options) {
|
|
47
|
-
watch.check();
|
|
48
|
-
// Driver dispatch runs in the next microtask, before another socket event.
|
|
49
|
-
const query = client.unsafe(text, input, options).execute();
|
|
50
|
-
return Promise.race([query, watch.closed]);
|
|
51
|
-
},
|
|
52
|
-
array: client.array,
|
|
53
|
-
typed: client.typed,
|
|
54
|
-
};
|
|
55
|
-
const connection = {
|
|
56
|
-
sql: safe,
|
|
57
|
-
check: watch.check,
|
|
58
|
-
async release(discard = false) {
|
|
59
|
-
for (const [id, active] of state.active)
|
|
60
|
-
if (active === watch)
|
|
61
|
-
state.active.delete(id);
|
|
62
|
-
if (watch.failure)
|
|
63
|
-
return; // The driver already removed the closed session.
|
|
64
|
-
if (discard) {
|
|
65
|
-
// Failed recovery must not return an unknown transaction/lock state to the pool.
|
|
66
|
-
try {
|
|
67
|
-
await client.unsafe('SELECT pg_terminate_backend(pg_backend_pid())').execute();
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
/* The backend closes itself. */
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
client.release();
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
// Capture the connection on BEGIN itself when opening a transaction.
|
|
78
|
-
const marker = `${initial} /* tenon_session_${++nextLease} */`;
|
|
79
|
-
const stopCapture = state.capture(marker, watch);
|
|
80
|
-
try {
|
|
81
|
-
await safe.unsafe(marker, [], { simple: false, prepare: false });
|
|
82
|
-
watch.check();
|
|
83
|
-
return connection;
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
await connection.release(true);
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
stopCapture();
|
|
91
|
-
}
|
|
92
|
-
}
|