@zmdb/postgres 1.0.0-beta.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/LICENSE +674 -0
- package/README.md +111 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/driver.d.ts +27 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +266 -0
- package/dist/driver.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +8 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +489 -0
- package/dist/introspect.js.map +1 -0
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +452 -0
- package/dist/migrations.js.map +1 -0
- package/dist/outbox.d.ts +6 -0
- package/dist/outbox.d.ts.map +1 -0
- package/dist/outbox.js +33 -0
- package/dist/outbox.js.map +1 -0
- package/package.json +59 -0
- package/src/constants.ts +14 -0
- package/src/driver.ts +331 -0
- package/src/index.ts +105 -0
- package/src/introspect.ts +645 -0
- package/src/migrations.ts +576 -0
- package/src/outbox.ts +42 -0
package/src/driver.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { type ExecuteOptions, type SelectedDriver, type TransactionalDriver } from '@zmdb/orm';
|
|
2
|
+
import { type CompiledQuery, type SqlDialect } from '@zmdb/sql';
|
|
3
|
+
|
|
4
|
+
export interface PgQueryable {
|
|
5
|
+
query(text: string, params?: readonly unknown[]): Promise<{ rows: Record<string, unknown>[] }>;
|
|
6
|
+
query(config: {
|
|
7
|
+
readonly name?: string;
|
|
8
|
+
readonly queryMode?: 'extended';
|
|
9
|
+
readonly text: string;
|
|
10
|
+
readonly values?: readonly unknown[];
|
|
11
|
+
}): Promise<{ rows: Record<string, unknown>[] }>;
|
|
12
|
+
connect?(): Promise<PgConnection>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PgConnection extends PgQueryable {
|
|
16
|
+
release?(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PgOptions {
|
|
20
|
+
readonly prepared?: boolean;
|
|
21
|
+
readonly maxCacheSize?: number;
|
|
22
|
+
/** A queryable guaranteed to use a connection other than the running backend. */
|
|
23
|
+
readonly cancelVia?: PgQueryable;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PgPoolClient extends PgConnection {
|
|
27
|
+
release(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface PgPoolQueryable extends PgQueryable {
|
|
31
|
+
readonly totalCount: number;
|
|
32
|
+
readonly idleCount: number;
|
|
33
|
+
connect(): Promise<PgPoolClient>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PreparedState {
|
|
37
|
+
readonly names: Map<string, string>;
|
|
38
|
+
sequence: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function postgresFamilyDriver<Name extends string>(
|
|
42
|
+
dialect: SqlDialect<Name>,
|
|
43
|
+
client: PgQueryable,
|
|
44
|
+
options?: PgOptions,
|
|
45
|
+
): TransactionalDriver<Name> {
|
|
46
|
+
return createPostgresDriver(dialect, client, options, false, new WeakMap());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createPostgresDriver<Name extends string>(
|
|
50
|
+
dialect: SqlDialect<Name>,
|
|
51
|
+
client: PgQueryable,
|
|
52
|
+
options: PgOptions | undefined,
|
|
53
|
+
pinned: boolean,
|
|
54
|
+
preparedStates: WeakMap<PgQueryable, PreparedState>,
|
|
55
|
+
): TransactionalDriver<Name> {
|
|
56
|
+
const prepared = options?.prepared ?? false;
|
|
57
|
+
const maxCacheSize = options?.maxCacheSize ?? 1000;
|
|
58
|
+
if (!Number.isSafeInteger(maxCacheSize) || maxCacheSize < 0) {
|
|
59
|
+
throw new RangeError('maxCacheSize must be a non-negative safe integer');
|
|
60
|
+
}
|
|
61
|
+
let cursorSequence = 0;
|
|
62
|
+
|
|
63
|
+
const stateFor = (target: PgQueryable): PreparedState => {
|
|
64
|
+
const current = preparedStates.get(target);
|
|
65
|
+
if (current !== undefined) return current;
|
|
66
|
+
const created = { names: new Map<string, string>(), sequence: 0 };
|
|
67
|
+
preparedStates.set(target, created);
|
|
68
|
+
return created;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const preparedName = async (target: PgQueryable, text: string): Promise<string> => {
|
|
72
|
+
const preparedState = stateFor(target);
|
|
73
|
+
const cached = maxCacheSize > 0 ? preparedState.names.get(text) : undefined;
|
|
74
|
+
if (cached !== undefined) {
|
|
75
|
+
preparedState.names.delete(text);
|
|
76
|
+
preparedState.names.set(text, cached);
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const name = `zmdb_${(preparedState.sequence++).toString(36)}`;
|
|
81
|
+
if (maxCacheSize > 0) {
|
|
82
|
+
if (preparedState.names.size >= maxCacheSize) {
|
|
83
|
+
const oldestSql = preparedState.names.keys().next().value;
|
|
84
|
+
if (oldestSql !== undefined) {
|
|
85
|
+
const oldestName = preparedState.names.get(oldestSql);
|
|
86
|
+
preparedState.names.delete(oldestSql);
|
|
87
|
+
if (oldestName !== undefined) await target.query(`DEALLOCATE ${oldestName}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
preparedState.names.set(text, name);
|
|
91
|
+
}
|
|
92
|
+
return name;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const executeOn = async (target: PgQueryable, query: CompiledQuery): Promise<readonly Record<string, unknown>[]> => {
|
|
96
|
+
const result = !prepared
|
|
97
|
+
? await target.query(query.text, query.parameters)
|
|
98
|
+
: maxCacheSize === 0
|
|
99
|
+
? await target.query({
|
|
100
|
+
queryMode: 'extended',
|
|
101
|
+
text: query.text,
|
|
102
|
+
values: query.parameters,
|
|
103
|
+
})
|
|
104
|
+
: await target.query({
|
|
105
|
+
name: await preparedName(target, query.text),
|
|
106
|
+
text: query.text,
|
|
107
|
+
values: query.parameters,
|
|
108
|
+
});
|
|
109
|
+
return result.rows;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const driver: TransactionalDriver<Name> = {
|
|
113
|
+
dialect,
|
|
114
|
+
async execute(query, executeOptions) {
|
|
115
|
+
const signal = executeOptions?.signal;
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
|
|
118
|
+
const cancelVia = options?.cancelVia;
|
|
119
|
+
if (signal === undefined || cancelVia === undefined) {
|
|
120
|
+
const ownsPreparedConnection = prepared && !pinned && isPool(client);
|
|
121
|
+
const target = ownsPreparedConnection ? await client.connect() : client;
|
|
122
|
+
try {
|
|
123
|
+
const rows = await executeOn(target, query);
|
|
124
|
+
signal?.throwIfAborted();
|
|
125
|
+
return rows;
|
|
126
|
+
} finally {
|
|
127
|
+
if (ownsPreparedConnection) release(target);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const ownsConnection = !pinned && isPool(client);
|
|
132
|
+
const connection = ownsConnection ? await client.connect() : client;
|
|
133
|
+
try {
|
|
134
|
+
signal.throwIfAborted();
|
|
135
|
+
const pid = await backendPid(connection);
|
|
136
|
+
signal.throwIfAborted();
|
|
137
|
+
const removeAbort = forwardAbort(signal, pid, cancelVia);
|
|
138
|
+
try {
|
|
139
|
+
const rows = await executeOn(connection, query);
|
|
140
|
+
signal.throwIfAborted();
|
|
141
|
+
return rows;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (signal.aborted) signal.throwIfAborted();
|
|
144
|
+
throw error;
|
|
145
|
+
} finally {
|
|
146
|
+
removeAbort();
|
|
147
|
+
}
|
|
148
|
+
} finally {
|
|
149
|
+
if (ownsConnection) release(connection);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
...(pinned || isPool(client)
|
|
153
|
+
? {
|
|
154
|
+
stream(query: CompiledQuery, executeOptions?: ExecuteOptions): AsyncIterable<Record<string, unknown>> {
|
|
155
|
+
return streamPostgres(
|
|
156
|
+
client,
|
|
157
|
+
query,
|
|
158
|
+
executeOptions,
|
|
159
|
+
options?.cancelVia,
|
|
160
|
+
pinned,
|
|
161
|
+
`zmdb_${(cursorSequence++).toString(36)}`,
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
: {}),
|
|
166
|
+
async transaction<Result>(run: (transaction: SelectedDriver<Name>) => Promise<Result>): Promise<Result> {
|
|
167
|
+
if (!isPool(client)) {
|
|
168
|
+
return runTransaction(dialect, client, options, preparedStates, run);
|
|
169
|
+
}
|
|
170
|
+
const connection = await client.connect();
|
|
171
|
+
try {
|
|
172
|
+
return await runTransaction(dialect, connection, options, preparedStates, run);
|
|
173
|
+
} finally {
|
|
174
|
+
connection.release();
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
return driver;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function runTransaction<Name extends string, Result>(
|
|
182
|
+
dialect: SqlDialect<Name>,
|
|
183
|
+
connection: PgQueryable,
|
|
184
|
+
options: PgOptions | undefined,
|
|
185
|
+
preparedStates: WeakMap<PgQueryable, PreparedState>,
|
|
186
|
+
run: (transaction: SelectedDriver<Name>) => Promise<Result>,
|
|
187
|
+
): Promise<Result> {
|
|
188
|
+
const transactionDriver = createPostgresDriver(dialect, connection, options, true, preparedStates);
|
|
189
|
+
await connection.query('BEGIN');
|
|
190
|
+
try {
|
|
191
|
+
const result = await run(transactionDriver);
|
|
192
|
+
await connection.query('COMMIT');
|
|
193
|
+
return result;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
await connection.query('ROLLBACK');
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function backendPid(connection: PgQueryable): Promise<number> {
|
|
201
|
+
const result = await connection.query('SELECT pg_backend_pid() AS pid');
|
|
202
|
+
const pid = result.rows[0]?.['pid'];
|
|
203
|
+
if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) {
|
|
204
|
+
throw new Error('postgresDriver could not read a valid pg_backend_pid()');
|
|
205
|
+
}
|
|
206
|
+
return pid;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function forwardAbort(signal: AbortSignal, pid: number, cancelVia: PgQueryable): () => void {
|
|
210
|
+
let sent = false;
|
|
211
|
+
const cancel = (): void => {
|
|
212
|
+
if (sent) return;
|
|
213
|
+
sent = true;
|
|
214
|
+
void cancelVia.query('SELECT pg_cancel_backend($1)', [pid]).catch(() => {});
|
|
215
|
+
};
|
|
216
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
217
|
+
if (signal.aborted) cancel();
|
|
218
|
+
return () => signal.removeEventListener('abort', cancel);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function streamPostgres(
|
|
222
|
+
client: PgQueryable,
|
|
223
|
+
query: CompiledQuery,
|
|
224
|
+
options: ExecuteOptions | undefined,
|
|
225
|
+
cancelVia: PgQueryable | undefined,
|
|
226
|
+
pinned: boolean,
|
|
227
|
+
cursorName: string,
|
|
228
|
+
): AsyncIterable<Record<string, unknown>> {
|
|
229
|
+
const batchSize = options?.batchSize ?? 100;
|
|
230
|
+
if (!Number.isSafeInteger(batchSize) || batchSize <= 0) {
|
|
231
|
+
throw new RangeError('batchSize must be a positive safe integer');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
[Symbol.asyncIterator](): AsyncIterator<Record<string, unknown>, void, unknown> {
|
|
236
|
+
let cleanupFailure: unknown;
|
|
237
|
+
const generator = (async function* (): AsyncGenerator<Record<string, unknown>, void, unknown> {
|
|
238
|
+
const signal = options?.signal;
|
|
239
|
+
signal?.throwIfAborted();
|
|
240
|
+
const ownsConnection = !pinned && isPool(client);
|
|
241
|
+
const connection = ownsConnection ? await client.connect() : client;
|
|
242
|
+
let transactionOpen = false;
|
|
243
|
+
let cursorOpen = false;
|
|
244
|
+
let bodyFailure: unknown;
|
|
245
|
+
let removeAbort = (): void => {};
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
signal?.throwIfAborted();
|
|
249
|
+
const pid = await backendPid(connection);
|
|
250
|
+
if (signal !== undefined && cancelVia !== undefined) {
|
|
251
|
+
removeAbort = forwardAbort(signal, pid, cancelVia);
|
|
252
|
+
}
|
|
253
|
+
signal?.throwIfAborted();
|
|
254
|
+
|
|
255
|
+
if (ownsConnection) {
|
|
256
|
+
await connection.query('BEGIN');
|
|
257
|
+
transactionOpen = true;
|
|
258
|
+
}
|
|
259
|
+
await connection.query({
|
|
260
|
+
text: `DECLARE "${cursorName}" NO SCROLL CURSOR FOR ${query.text}`,
|
|
261
|
+
values: query.parameters,
|
|
262
|
+
});
|
|
263
|
+
cursorOpen = true;
|
|
264
|
+
|
|
265
|
+
while (true) {
|
|
266
|
+
signal?.throwIfAborted();
|
|
267
|
+
const fetched = await connection.query(`FETCH FORWARD ${batchSize} FROM "${cursorName}"`);
|
|
268
|
+
signal?.throwIfAborted();
|
|
269
|
+
if (fetched.rows.length === 0) break;
|
|
270
|
+
for (const row of fetched.rows) {
|
|
271
|
+
signal?.throwIfAborted();
|
|
272
|
+
yield row;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
bodyFailure = signal?.aborted === true ? signal.reason : error;
|
|
277
|
+
} finally {
|
|
278
|
+
removeAbort();
|
|
279
|
+
if (cursorOpen) {
|
|
280
|
+
try {
|
|
281
|
+
await connection.query(`CLOSE "${cursorName}"`);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
cleanupFailure ??= error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (transactionOpen) {
|
|
287
|
+
try {
|
|
288
|
+
await connection.query(bodyFailure === undefined && cleanupFailure === undefined ? 'COMMIT' : 'ROLLBACK');
|
|
289
|
+
} catch (error) {
|
|
290
|
+
cleanupFailure ??= error;
|
|
291
|
+
try {
|
|
292
|
+
await connection.query('ROLLBACK');
|
|
293
|
+
} catch (rollbackError) {
|
|
294
|
+
cleanupFailure ??= rollbackError;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (ownsConnection) release(connection);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (bodyFailure !== undefined) throw bodyFailure;
|
|
302
|
+
if (cleanupFailure !== undefined) throw cleanupFailure;
|
|
303
|
+
})();
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
next: () => generator.next(),
|
|
307
|
+
async return() {
|
|
308
|
+
const result = await generator.return(undefined);
|
|
309
|
+
if (cleanupFailure !== undefined) throw cleanupFailure;
|
|
310
|
+
return result;
|
|
311
|
+
},
|
|
312
|
+
throw: error => generator.throw(error),
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function release(connection: PgQueryable): void {
|
|
319
|
+
const method = Reflect.get(connection, 'release');
|
|
320
|
+
if (typeof method === 'function') Reflect.apply(method, connection, []);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isPool(client: PgQueryable): client is PgPoolQueryable {
|
|
324
|
+
return (
|
|
325
|
+
typeof client.connect === 'function' &&
|
|
326
|
+
'totalCount' in client &&
|
|
327
|
+
typeof client.totalCount === 'number' &&
|
|
328
|
+
'idleCount' in client &&
|
|
329
|
+
typeof client.idleCount === 'number'
|
|
330
|
+
);
|
|
331
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type DatabaseVertical, type TransactionalDriver } from '@zmdb/orm';
|
|
2
|
+
import {
|
|
3
|
+
defineSqlDialect,
|
|
4
|
+
type DatabaseCapabilities,
|
|
5
|
+
type PaginationTail,
|
|
6
|
+
type ResolvedDialectTraits,
|
|
7
|
+
type SqlDialect,
|
|
8
|
+
} from '@zmdb/sql';
|
|
9
|
+
|
|
10
|
+
import { POSTGRES_TYPES } from './constants.js';
|
|
11
|
+
import { postgresFamilyDriver, type PgConnection, type PgOptions, type PgQueryable } from './driver.js';
|
|
12
|
+
import { postgresFamilyIntrospector, type PostgresCatalogOverrides } from './introspect.js';
|
|
13
|
+
import { postgresFamilyMigrations, type PostgresMigrationOptions } from './migrations.js';
|
|
14
|
+
|
|
15
|
+
export type { PgConnection, PgOptions, PgQueryable, PostgresCatalogOverrides, PostgresMigrationOptions };
|
|
16
|
+
export { postgresFamilyDriver, postgresFamilyIntrospector, postgresFamilyMigrations };
|
|
17
|
+
export {
|
|
18
|
+
POSTGRES_OUTBOX_TABLE,
|
|
19
|
+
postgresOutboxMigration,
|
|
20
|
+
postgresOutboxPendingIndexDdl,
|
|
21
|
+
postgresOutboxTableDdl,
|
|
22
|
+
} from './outbox.js';
|
|
23
|
+
|
|
24
|
+
const UNMAPPED_OPERATOR_TOKEN = /^(?!.*--)[A-Za-z@<>=!~*&|?-]{1,4}$/;
|
|
25
|
+
const POSTGRES_QUOTE: readonly [open: string, close: string] = ['"', '"'];
|
|
26
|
+
Object.freeze(POSTGRES_QUOTE);
|
|
27
|
+
|
|
28
|
+
const traits: ResolvedDialectTraits = Object.freeze({
|
|
29
|
+
placeholder: 'numbered',
|
|
30
|
+
quote: POSTGRES_QUOTE,
|
|
31
|
+
paginate: ({ limit, offset }: PaginationTail) => {
|
|
32
|
+
let text = '';
|
|
33
|
+
if (limit !== undefined) text += ` LIMIT ${limit}`;
|
|
34
|
+
if (offset !== undefined) text += ` OFFSET ${offset}`;
|
|
35
|
+
return text;
|
|
36
|
+
},
|
|
37
|
+
paginationRequiresOrder: false,
|
|
38
|
+
rowValueIn: true,
|
|
39
|
+
returning: Object.freeze({
|
|
40
|
+
insert: 'suffix',
|
|
41
|
+
upsert: 'suffix',
|
|
42
|
+
update: 'suffix',
|
|
43
|
+
delete: 'suffix',
|
|
44
|
+
}),
|
|
45
|
+
upsert: 'onConflict',
|
|
46
|
+
fts: 'tsvector',
|
|
47
|
+
concat: 'operator',
|
|
48
|
+
booleanNot: 'not',
|
|
49
|
+
types: POSTGRES_TYPES,
|
|
50
|
+
paramLimit: 60_000,
|
|
51
|
+
retryableCodes: Object.freeze(['40001', '40P01']),
|
|
52
|
+
acceptsOperator: (operator: string) =>
|
|
53
|
+
operator === '#>' || operator === '#>>' || UNMAPPED_OPERATOR_TOKEN.test(operator),
|
|
54
|
+
functions: true,
|
|
55
|
+
procedures: true,
|
|
56
|
+
tableFunctions: true,
|
|
57
|
+
vectorDistance: true,
|
|
58
|
+
spatialPredicates: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const capabilities: DatabaseCapabilities = Object.freeze({
|
|
62
|
+
returning: Object.freeze({
|
|
63
|
+
insert: true,
|
|
64
|
+
upsert: true,
|
|
65
|
+
update: true,
|
|
66
|
+
delete: true,
|
|
67
|
+
}),
|
|
68
|
+
transactionalDdl: true,
|
|
69
|
+
schemas: true,
|
|
70
|
+
sequences: true,
|
|
71
|
+
generatedColumns: true,
|
|
72
|
+
partialIndexes: true,
|
|
73
|
+
foreignKeys: true,
|
|
74
|
+
rowLevelSecurity: true,
|
|
75
|
+
streaming: true,
|
|
76
|
+
cancellation: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const postgresIntrospector = postgresFamilyIntrospector('postgres');
|
|
80
|
+
|
|
81
|
+
export const postgres: SqlDialect<'postgres'> = defineSqlDialect({
|
|
82
|
+
name: 'postgres',
|
|
83
|
+
family: 'postgres',
|
|
84
|
+
telemetrySystem: 'postgresql',
|
|
85
|
+
traits,
|
|
86
|
+
capabilities,
|
|
87
|
+
migrations: postgresFamilyMigrations('postgres'),
|
|
88
|
+
introspector: postgresIntrospector,
|
|
89
|
+
outbox: Object.freeze({
|
|
90
|
+
createTable: 'CREATE TABLE',
|
|
91
|
+
pendingIndex: 'filtered',
|
|
92
|
+
epochLiteral: "'1970-01-01T00:00:00.000Z'",
|
|
93
|
+
createdAtDefault: 'CURRENT_TIMESTAMP',
|
|
94
|
+
boundedTextType: () => 'TEXT',
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export function postgresDriver(client: PgQueryable, options?: PgOptions): TransactionalDriver<'postgres'> {
|
|
99
|
+
return postgresFamilyDriver(postgres, client, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const postgresVertical: DatabaseVertical<'postgres', PgQueryable, PgOptions> = Object.freeze({
|
|
103
|
+
dialect: postgres,
|
|
104
|
+
driver: postgresDriver,
|
|
105
|
+
});
|