@vibeorm/adapter-pg 1.2.0 → 2.0.0-alpha.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/README.md +62 -54
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +366 -0
- package/dist/index.js.map +15 -0
- package/dist/params.d.ts +40 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/savepoints.d.ts +22 -0
- package/dist/savepoints.d.ts.map +1 -0
- package/dist/statement-cache.d.ts +24 -0
- package/dist/statement-cache.d.ts.map +1 -0
- package/dist/transaction-sql.d.ts +62 -0
- package/dist/transaction-sql.d.ts.map +1 -0
- package/package.json +35 -19
- package/src/index.ts +0 -461
package/README.md
CHANGED
|
@@ -1,88 +1,96 @@
|
|
|
1
1
|
# @vibeorm/adapter-pg
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Part of **[VibeORM](https://github.com/vibeorm/vibeorm)** — a type-safe TypeScript ORM for Bun and Node. Prisma-schema or TypeScript-DSL input, a canonical schema IR, a generated client, and a dialect-aware SQL layer over PostgreSQL, PGlite, SQLite and MySQL.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This adapter runs VibeORM on [node-postgres](https://node-postgres.com/) (`pg`) against a PostgreSQL server. It works on both Bun and Node, and `pg` is a peer dependency you install yourself.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bun add @vibeorm/adapter-pg pg
|
|
8
|
+
bun add @vibeorm/adapter-pg@alpha pg
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
> **Pre-release.** `2.0.0-alpha.x`, published under the `alpha` dist-tag. `npm i vibeorm` still resolves to the stable v1 line.
|
|
12
|
+
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
13
15
|
```ts
|
|
16
|
+
import { VibeClient } from "./generated/vibeorm/index.js";
|
|
14
17
|
import { pgAdapter } from "@vibeorm/adapter-pg";
|
|
15
|
-
import { VibeClient } from "./generated/vibeorm/index.ts";
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
// connectionString defaults to process.env.DATABASE_URL
|
|
20
|
+
const db = VibeClient({ adapter: pgAdapter({ connectionString: process.env.DATABASE_URL, max: 20 }) });
|
|
21
|
+
|
|
22
|
+
const users = await db.user.findMany({ where: { email: { contains: "@example.com" } }, take: 10 });
|
|
23
|
+
|
|
24
|
+
await db.$disconnect();
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
To reuse a pool you already own, pass it in. The adapter never ends a pool it did not create, so `disconnect()` is a no-op in that mode:
|
|
23
28
|
|
|
24
29
|
```ts
|
|
25
30
|
import pg from "pg";
|
|
26
|
-
import { pgAdapter } from "@vibeorm/adapter-pg";
|
|
27
31
|
|
|
28
|
-
const pool = new pg.Pool({ connectionString:
|
|
29
|
-
|
|
30
|
-
const adapter = pgAdapter({ pool });
|
|
32
|
+
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
33
|
+
const db = VibeClient({ adapter: pgAdapter({ pool }) });
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
When you pass an existing pool, the adapter will **not** call `pool.end()` on disconnect — you manage the pool lifecycle.
|
|
34
|
-
|
|
35
36
|
## Options
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
// Create a new pool
|
|
39
|
-
type PgAdapterOptions = {
|
|
40
|
-
connectionString?: string; // Connection string (defaults to DATABASE_URL env)
|
|
41
|
-
max?: number; // Pool size (default: 10)
|
|
42
|
-
statementTimeout?: number; // Default query timeout in ms (default: none)
|
|
43
|
-
connectionTimeout?: number; // Max time to acquire a connection in ms (default: none)
|
|
44
|
-
preparedStatements?: boolean; // Enable prepared statement caching (default: false)
|
|
45
|
-
stmtCacheMax?: number; // Max cached prepared statements (default: 1000)
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Or use an existing pool (BYOP — you manage pool config)
|
|
49
|
-
type PgAdapterOptions = {
|
|
50
|
-
pool: PgPool; // Existing pg.Pool instance
|
|
51
|
-
preparedStatements?: boolean;
|
|
52
|
-
stmtCacheMax?: number;
|
|
53
|
-
};
|
|
54
|
-
```
|
|
38
|
+
`pgAdapter(options?: PgAdapterOptions)`:
|
|
55
39
|
|
|
56
|
-
|
|
40
|
+
| Option | Type | Default | Meaning |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `connectionString` | `string` | `process.env.DATABASE_URL` | PostgreSQL connection URL |
|
|
43
|
+
| `pool` | `PgPoolLike` | — | An existing `pg.Pool` to use instead of creating one; it is never ended by the adapter |
|
|
44
|
+
| `max` | `number` | `10` | Maximum pooled connections; ignored when `pool` is given |
|
|
45
|
+
| `statementTimeout` | `number` (ms) | — | Pool-wide `statement_timeout`; a per-transaction `timeout` overrides it |
|
|
46
|
+
| `connectionTimeout` | `number` (ms) | — | Wait for a pool checkout before failing (pg's `connectionTimeoutMillis`) |
|
|
47
|
+
| `preparedStatements` | `boolean` | `false` | Send ORM-path queries as named prepared statements. Saves planning time, but PostgreSQL may fall back to a generic plan after ~5 executions — leave off unless profiling says otherwise |
|
|
48
|
+
| `stmtCacheMax` | `number` | `1000` | Maximum entries in the prepared-statement name cache (LRU) |
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
## Runtime support
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
```
|
|
52
|
+
| Runtime | Supported |
|
|
53
|
+
|---|---|
|
|
54
|
+
| Bun ≥ 1.2 | yes |
|
|
55
|
+
| Node (with `pg` installed) | yes |
|
|
66
56
|
|
|
67
|
-
|
|
57
|
+
`pg` is a pure-JavaScript driver, so nothing native has to build. Peer range: `pg >= 8`.
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
## Capabilities
|
|
70
60
|
|
|
71
|
-
|
|
61
|
+
This is a postgres-dialect adapter, so the full capability set is available: `RETURNING`, scalar array columns, native enums, `ILIKE`, `DISTINCT ON`, partial indexes, transactional DDL, `createManyAndReturn`, a real JSON column type, native `NULLS FIRST`/`NULLS LAST`, and the `"join"` relation strategy (`LEFT JOIN LATERAL` plus JSON aggregation). The generated capability table lives in [docs/dialects.md](https://github.com/vibeorm/vibeorm/blob/master/docs/dialects.md).
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
Transactions run on one checked-out client; nested `transaction()` calls become savepoints on that same client. Raw transaction control (`BEGIN` / `COMMIT` / `ROLLBACK` sent through the raw path, as `@vibeorm/migrate` does for transactional DDL) pins one client for the duration, so a raw transaction never splits across connections. Concurrent raw transactions on a single adapter are unsupported.
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
## Parameter serialization: two paths
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
`pg` turns a JS array into a PostgreSQL array literal natively, which is what a raw query wants but not what the ORM path can assume. The adapter therefore keeps two helpers:
|
|
68
|
+
|
|
69
|
+
- `serializeParams` (ORM path): plain objects **and** plain arrays are JSON-stringified, since at that point they can only be `json`/`jsonb` values. Scalar-list values are tagged by `formatArrayParam` with `PgArrayParam` and unwrapped, reaching the driver as the untouched array.
|
|
70
|
+
- `serializeParamsRaw` (raw path, `$queryRaw*` / `$executeRaw*` / DDL): plain JS arrays pass straight through, so `$queryRawUnsafe('… WHERE "id" = ANY($1)', [1, 2, 3])` matches rows. Binding an array *into* a `jsonb` column from a raw query means calling `JSON.stringify` yourself.
|
|
71
|
+
|
|
72
|
+
## Error mapping
|
|
73
|
+
|
|
74
|
+
Every driver failure becomes a `VibeError` with the driver error as `cause` and `meta` carrying `sqlstate` plus `constraint` / `table` / `column` when the server reported them. Table (`PG_SQLSTATE_ERROR_CODES`, frozen and mirrored by tests):
|
|
75
|
+
|
|
76
|
+
| SQLSTATE | Condition | VibeError code | `meta.reason` |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `23505` | unique_violation | `VIBE_UNIQUE_VIOLATION` | |
|
|
79
|
+
| `23503` | foreign_key_violation | `VIBE_FK_VIOLATION` | |
|
|
80
|
+
| `23502` | not_null_violation | `VIBE_VALIDATION` | |
|
|
81
|
+
| `23514` | check_violation | `VIBE_VALIDATION` | |
|
|
82
|
+
| `22P02` | invalid_text_representation | `VIBE_VALIDATION` | |
|
|
83
|
+
| `40001` | serialization_failure | `VIBE_TRANSACTION` | |
|
|
84
|
+
| `40P01` | deadlock_detected | `VIBE_TRANSACTION` | |
|
|
85
|
+
| `57014` | query_canceled (statement_timeout) | `VIBE_TRANSACTION` | `"timeout"` |
|
|
86
|
+
| anything else, including client-side failures such as `ECONNREFUSED` | | `VIBE_ADAPTER` | |
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
A `VibeError` raised deeper inside the ORM passes through unchanged.
|
|
85
89
|
|
|
86
|
-
##
|
|
90
|
+
## Links
|
|
87
91
|
|
|
88
|
-
[
|
|
92
|
+
- [Documentation](https://github.com/vibeorm/vibeorm/tree/master/docs)
|
|
93
|
+
- [Getting started](https://github.com/vibeorm/vibeorm/blob/master/docs/getting-started.md)
|
|
94
|
+
- [Dialect capability tables](https://github.com/vibeorm/vibeorm/blob/master/docs/dialects.md)
|
|
95
|
+
- [Repository and issues](https://github.com/vibeorm/vibeorm)
|
|
96
|
+
- MIT licensed
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driver-error mapping for node-postgres (`pg`).
|
|
3
|
+
*
|
|
4
|
+
* Constitution rule 5: a raw driver error never escapes an adapter. Every
|
|
5
|
+
* failure becomes a `VibeError` with a stable code, the driver error attached
|
|
6
|
+
* as `cause`, and the SQLSTATE (plus constraint/table/column when the server
|
|
7
|
+
* reported them) in `meta`.
|
|
8
|
+
*/
|
|
9
|
+
import { VibeError, type VibeErrorCode } from "@vibeorm/schema";
|
|
10
|
+
/**
|
|
11
|
+
* PostgreSQL SQLSTATE → stable VibeError code. Frozen and mirrored by tests;
|
|
12
|
+
* anything absent from this table maps to `VIBE_ADAPTER`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PG_SQLSTATE_ERROR_CODES: Readonly<Record<string, VibeErrorCode>>;
|
|
15
|
+
/** SQLSTATEs that additionally explain themselves through `meta.reason`. */
|
|
16
|
+
export declare const PG_SQLSTATE_REASONS: Readonly<Record<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Extract the SQLSTATE from a pg driver error. `pg` puts the server's SQLSTATE
|
|
19
|
+
* on `error.code`; client-side failures (`ECONNREFUSED`, `ETIMEDOUT`, …) use
|
|
20
|
+
* the same field for a non-SQLSTATE string, which this rejects.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pgErrorSqlstate(params: {
|
|
23
|
+
error: unknown;
|
|
24
|
+
}): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Map any error thrown by `pg` onto a `VibeError`.
|
|
27
|
+
*
|
|
28
|
+
* A `VibeError` passes through unchanged, so an error raised deeper inside the
|
|
29
|
+
* ORM (or by a nested adapter call) is never re-wrapped.
|
|
30
|
+
*/
|
|
31
|
+
export declare function mapPgDriverError(params: {
|
|
32
|
+
error: unknown;
|
|
33
|
+
}): VibeError;
|
|
34
|
+
/** Run a driver call, mapping anything it throws onto a `VibeError`. */
|
|
35
|
+
export declare function runMapped<T>(fn: () => Promise<T>): Promise<T>;
|
|
36
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAqB1E,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE/D,CAAC;AAYH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAK9E;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CA0BtE;AAED,wEAAwE;AACxE,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMnE"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibeorm/adapter-pg — node-postgres (`pg`) adapter for VibeORM v2.
|
|
3
|
+
*
|
|
4
|
+
* Runs on Bun and Node. Owns pooling, optional named prepared statements,
|
|
5
|
+
* transactions (nested → savepoints on the same client) and driver-error
|
|
6
|
+
* mapping. Only `VibeError` ever escapes.
|
|
7
|
+
*/
|
|
8
|
+
import type { DatabaseAdapter } from "@vibeorm/runtime";
|
|
9
|
+
/** The single `pg` query form this adapter uses (config object, never overloads). */
|
|
10
|
+
export type PgQueryConfig = {
|
|
11
|
+
readonly text: string;
|
|
12
|
+
readonly values?: unknown[];
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
};
|
|
15
|
+
/** The parts of a `pg` result this adapter reads. */
|
|
16
|
+
export type PgQueryResultLike = {
|
|
17
|
+
readonly rows: Record<string, unknown>[];
|
|
18
|
+
readonly rowCount: number | null;
|
|
19
|
+
};
|
|
20
|
+
/** The parts of a `pg` PoolClient this adapter uses. */
|
|
21
|
+
export type PgPoolClientLike = {
|
|
22
|
+
query(config: PgQueryConfig): Promise<PgQueryResultLike>;
|
|
23
|
+
release(): void;
|
|
24
|
+
};
|
|
25
|
+
/** The parts of a `pg` Pool this adapter uses — satisfied by a real `pg.Pool`. */
|
|
26
|
+
export type PgPoolLike = {
|
|
27
|
+
connect(): Promise<PgPoolClientLike>;
|
|
28
|
+
query(config: PgQueryConfig): Promise<PgQueryResultLike>;
|
|
29
|
+
end(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Options for {@link pgAdapter}. Either point it at a connection string (the
|
|
33
|
+
* adapter then owns the pool it creates) or hand it a pool you already own.
|
|
34
|
+
*/
|
|
35
|
+
export type PgAdapterOptions = {
|
|
36
|
+
/** PostgreSQL connection URL. Defaults to `process.env.DATABASE_URL`. */
|
|
37
|
+
readonly connectionString?: string;
|
|
38
|
+
/**
|
|
39
|
+
* An existing `pg.Pool` to use instead of creating one. The adapter never
|
|
40
|
+
* ends a pool it did not create, so `disconnect()` is a no-op in this mode.
|
|
41
|
+
*/
|
|
42
|
+
readonly pool?: PgPoolLike;
|
|
43
|
+
/** Maximum pooled connections (default 10). Ignored when `pool` is given. */
|
|
44
|
+
readonly max?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Pool-wide `statement_timeout` in milliseconds. Every connection inherits
|
|
47
|
+
* it; a per-transaction `timeout` overrides it for that transaction.
|
|
48
|
+
*/
|
|
49
|
+
readonly statementTimeout?: number;
|
|
50
|
+
/** Milliseconds to wait for a pool checkout before failing (pg's `connectionTimeoutMillis`). */
|
|
51
|
+
readonly connectionTimeout?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Send ORM-path queries as named prepared statements (default false).
|
|
54
|
+
* Named statements save planning time but let PostgreSQL fall back to a
|
|
55
|
+
* generic plan after ~5 executions, which can pick worse indexes for range
|
|
56
|
+
* filters — leave off unless profiling says otherwise.
|
|
57
|
+
*/
|
|
58
|
+
readonly preparedStatements?: boolean;
|
|
59
|
+
/** Maximum entries in the prepared-statement name cache (default 1000, LRU). */
|
|
60
|
+
readonly stmtCacheMax?: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Create a VibeORM adapter backed by node-postgres.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const adapter = pgAdapter({ connectionString: process.env.DATABASE_URL, max: 20 });
|
|
68
|
+
* await adapter.connect();
|
|
69
|
+
* const rows = await adapter.execute({ text: 'SELECT * FROM "User" WHERE "id" = $1', values: [1] });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example Reusing a pool you already own
|
|
73
|
+
* ```ts
|
|
74
|
+
* import pg from "pg";
|
|
75
|
+
* const pool = new pg.Pool({ connectionString });
|
|
76
|
+
* const adapter = pgAdapter({ pool });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function pgAdapter(options?: PgAdapterOptions): DatabaseAdapter;
|
|
80
|
+
export { PG_SQLSTATE_ERROR_CODES, PG_SQLSTATE_REASONS, mapPgDriverError, pgErrorSqlstate } from "./errors.ts";
|
|
81
|
+
export { PgArrayParam, serializeParams, serializeParamsRaw } from "./params.ts";
|
|
82
|
+
export { createSavepointCounter, nextSavepointName } from "./savepoints.ts";
|
|
83
|
+
export type { SavepointCounter } from "./savepoints.ts";
|
|
84
|
+
export { createStatementNameCache } from "./statement-cache.ts";
|
|
85
|
+
export type { StatementNameCache } from "./statement-cache.ts";
|
|
86
|
+
export { ISOLATION_LEVEL_SQL, beginStatement, classifyRawTransactionControl, refuseNestedTransactionOptions, statementTimeoutStatement, } from "./transaction-sql.ts";
|
|
87
|
+
export type { RawTransactionControl } from "./transaction-sql.ts";
|
|
88
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAiD,MAAM,kBAAkB,CAAC;AAgBvG,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,qDAAqD;AACrD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzD,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF,kFAAkF;AAClF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrC,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB,CAAC;AAOF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,gGAAgG;IAChG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAcF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAyOrE;AAID,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,6BAA6B,EAC7B,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/adapter-pg/src/index.ts
|
|
3
|
+
import pg from "pg";
|
|
4
|
+
|
|
5
|
+
// packages/adapter-pg/src/errors.ts
|
|
6
|
+
import { VibeError } from "@vibeorm/schema";
|
|
7
|
+
var PG_SQLSTATE_ERROR_CODES = Object.freeze({
|
|
8
|
+
"23505": "VIBE_UNIQUE_VIOLATION",
|
|
9
|
+
"23503": "VIBE_FK_VIOLATION",
|
|
10
|
+
"23502": "VIBE_VALIDATION",
|
|
11
|
+
"23514": "VIBE_VALIDATION",
|
|
12
|
+
"22P02": "VIBE_VALIDATION",
|
|
13
|
+
"40001": "VIBE_TRANSACTION",
|
|
14
|
+
"40P01": "VIBE_TRANSACTION",
|
|
15
|
+
"57014": "VIBE_TRANSACTION"
|
|
16
|
+
});
|
|
17
|
+
var PG_SQLSTATE_REASONS = Object.freeze({
|
|
18
|
+
"57014": "timeout"
|
|
19
|
+
});
|
|
20
|
+
var SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
|
|
21
|
+
function readStringField(params) {
|
|
22
|
+
const value = params.source[params.key];
|
|
23
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
24
|
+
}
|
|
25
|
+
function pgErrorSqlstate(params) {
|
|
26
|
+
const { error } = params;
|
|
27
|
+
if (error === null || typeof error !== "object")
|
|
28
|
+
return;
|
|
29
|
+
const code = readStringField({ source: error, key: "code" });
|
|
30
|
+
return code !== undefined && SQLSTATE_PATTERN.test(code) ? code : undefined;
|
|
31
|
+
}
|
|
32
|
+
function mapPgDriverError(params) {
|
|
33
|
+
const { error } = params;
|
|
34
|
+
if (error instanceof VibeError)
|
|
35
|
+
return error;
|
|
36
|
+
const source = error !== null && typeof error === "object" ? error : {};
|
|
37
|
+
const sqlstate = pgErrorSqlstate({ error });
|
|
38
|
+
const code = (sqlstate === undefined ? undefined : PG_SQLSTATE_ERROR_CODES[sqlstate]) ?? "VIBE_ADAPTER";
|
|
39
|
+
const meta = {};
|
|
40
|
+
if (sqlstate !== undefined)
|
|
41
|
+
meta.sqlstate = sqlstate;
|
|
42
|
+
const constraint = readStringField({ source, key: "constraint" });
|
|
43
|
+
if (constraint !== undefined)
|
|
44
|
+
meta.constraint = constraint;
|
|
45
|
+
const table = readStringField({ source, key: "table" });
|
|
46
|
+
if (table !== undefined)
|
|
47
|
+
meta.table = table;
|
|
48
|
+
const column = readStringField({ source, key: "column" });
|
|
49
|
+
if (column !== undefined)
|
|
50
|
+
meta.column = column;
|
|
51
|
+
const reason = sqlstate === undefined ? undefined : PG_SQLSTATE_REASONS[sqlstate];
|
|
52
|
+
if (reason !== undefined)
|
|
53
|
+
meta.reason = reason;
|
|
54
|
+
const message = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
|
|
55
|
+
return new VibeError({ code, message, meta, cause: error });
|
|
56
|
+
}
|
|
57
|
+
async function runMapped(fn) {
|
|
58
|
+
try {
|
|
59
|
+
return await fn();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw mapPgDriverError({ error });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// packages/adapter-pg/src/params.ts
|
|
66
|
+
class PgArrayParam {
|
|
67
|
+
values;
|
|
68
|
+
constructor(values) {
|
|
69
|
+
this.values = values;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function isDriverNativeValue(value) {
|
|
73
|
+
return value === null || value === undefined || typeof value !== "object" || value instanceof Date || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
|
74
|
+
}
|
|
75
|
+
function serializeParams(params) {
|
|
76
|
+
return params.values.map((value) => {
|
|
77
|
+
if (value instanceof PgArrayParam)
|
|
78
|
+
return value.values;
|
|
79
|
+
if (isDriverNativeValue(value))
|
|
80
|
+
return value;
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function serializeParamsRaw(params) {
|
|
85
|
+
return params.values.map((value) => {
|
|
86
|
+
if (value instanceof PgArrayParam)
|
|
87
|
+
return value.values;
|
|
88
|
+
if (Array.isArray(value))
|
|
89
|
+
return value;
|
|
90
|
+
if (isDriverNativeValue(value))
|
|
91
|
+
return value;
|
|
92
|
+
return JSON.stringify(value);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// packages/adapter-pg/src/savepoints.ts
|
|
97
|
+
function createSavepointCounter() {
|
|
98
|
+
return { n: 0 };
|
|
99
|
+
}
|
|
100
|
+
function nextSavepointName(params) {
|
|
101
|
+
return `vibeorm_sp_${params.counter.n++}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// packages/adapter-pg/src/statement-cache.ts
|
|
105
|
+
function createStatementNameCache(params) {
|
|
106
|
+
const { max } = params;
|
|
107
|
+
const names = new Map;
|
|
108
|
+
let counter = 0;
|
|
109
|
+
return {
|
|
110
|
+
getName(nameParams) {
|
|
111
|
+
const { text } = nameParams;
|
|
112
|
+
const existing = names.get(text);
|
|
113
|
+
if (existing !== undefined) {
|
|
114
|
+
names.delete(text);
|
|
115
|
+
names.set(text, existing);
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
const name = `vibeorm_${counter++}`;
|
|
119
|
+
if (names.size >= max) {
|
|
120
|
+
const oldest = names.keys().next().value;
|
|
121
|
+
if (oldest !== undefined)
|
|
122
|
+
names.delete(oldest);
|
|
123
|
+
}
|
|
124
|
+
names.set(text, name);
|
|
125
|
+
return name;
|
|
126
|
+
},
|
|
127
|
+
size() {
|
|
128
|
+
return names.size;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// packages/adapter-pg/src/transaction-sql.ts
|
|
134
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
135
|
+
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
136
|
+
ReadCommitted: "READ COMMITTED",
|
|
137
|
+
RepeatableRead: "REPEATABLE READ",
|
|
138
|
+
Serializable: "SERIALIZABLE"
|
|
139
|
+
});
|
|
140
|
+
function beginStatement(params) {
|
|
141
|
+
const { isolationLevel } = params;
|
|
142
|
+
return isolationLevel === undefined ? "BEGIN" : `BEGIN ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`;
|
|
143
|
+
}
|
|
144
|
+
function statementTimeoutStatement(params) {
|
|
145
|
+
const { timeout } = params;
|
|
146
|
+
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
147
|
+
throw new VibeError2({
|
|
148
|
+
code: "VIBE_VALIDATION",
|
|
149
|
+
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
150
|
+
meta: { timeout }
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return `SET LOCAL statement_timeout = ${timeout}`;
|
|
154
|
+
}
|
|
155
|
+
var RAW_OPEN_PATTERN = /^\s*(?:begin|start\s+transaction)\b[^;]*;?\s*$/i;
|
|
156
|
+
var RAW_CLOSE_PATTERN = /^\s*(?:commit|end|rollback|abort)(?:\s+(?:transaction|work))?\s*;?\s*$/i;
|
|
157
|
+
function classifyRawTransactionControl(params) {
|
|
158
|
+
if (RAW_OPEN_PATTERN.test(params.text))
|
|
159
|
+
return "open";
|
|
160
|
+
if (RAW_CLOSE_PATTERN.test(params.text))
|
|
161
|
+
return "close";
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function refuseNestedTransactionOptions(params) {
|
|
165
|
+
const { options, provider } = params;
|
|
166
|
+
if (options === undefined)
|
|
167
|
+
return;
|
|
168
|
+
const passed = [];
|
|
169
|
+
if (options.isolationLevel !== undefined)
|
|
170
|
+
passed.push("isolationLevel");
|
|
171
|
+
if (options.timeout !== undefined)
|
|
172
|
+
passed.push("timeout");
|
|
173
|
+
if (passed.length === 0)
|
|
174
|
+
return;
|
|
175
|
+
const meta = { provider, nested: true };
|
|
176
|
+
if (options.isolationLevel !== undefined)
|
|
177
|
+
meta.isolationLevel = options.isolationLevel;
|
|
178
|
+
if (options.timeout !== undefined)
|
|
179
|
+
meta.timeout = options.timeout;
|
|
180
|
+
throw new VibeError2({
|
|
181
|
+
code: "VIBE_VALIDATION",
|
|
182
|
+
message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins, and a nested timeout is not enforceable; pass options on the top-level $transaction`,
|
|
183
|
+
meta
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// packages/adapter-pg/src/index.ts
|
|
188
|
+
var DIALECT = "postgres";
|
|
189
|
+
var PROVIDER = "postgresql";
|
|
190
|
+
var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
191
|
+
function pgAdapter(options) {
|
|
192
|
+
const usePrepared = options?.preparedStatements ?? false;
|
|
193
|
+
const statementNames = createStatementNameCache({ max: options?.stmtCacheMax ?? 1000 });
|
|
194
|
+
let pool = null;
|
|
195
|
+
let ownsPool = false;
|
|
196
|
+
let rawSession = null;
|
|
197
|
+
function getPool() {
|
|
198
|
+
if (pool !== null)
|
|
199
|
+
return pool;
|
|
200
|
+
if (options?.pool !== undefined) {
|
|
201
|
+
pool = options.pool;
|
|
202
|
+
ownsPool = false;
|
|
203
|
+
return pool;
|
|
204
|
+
}
|
|
205
|
+
const config = {
|
|
206
|
+
connectionString: options?.connectionString ?? process.env.DATABASE_URL,
|
|
207
|
+
max: options?.max ?? 10
|
|
208
|
+
};
|
|
209
|
+
if (options?.statementTimeout !== undefined) {
|
|
210
|
+
config.statement_timeout = options.statementTimeout;
|
|
211
|
+
}
|
|
212
|
+
if (options?.connectionTimeout !== undefined) {
|
|
213
|
+
config.connectionTimeoutMillis = options.connectionTimeout;
|
|
214
|
+
}
|
|
215
|
+
const PoolCtor = pg.Pool;
|
|
216
|
+
pool = new PoolCtor(config);
|
|
217
|
+
ownsPool = true;
|
|
218
|
+
return pool;
|
|
219
|
+
}
|
|
220
|
+
function buildQueryConfig(params) {
|
|
221
|
+
const values = serializeParams({ values: params.values });
|
|
222
|
+
return usePrepared ? { name: statementNames.getName({ text: params.text }), text: params.text, values } : { text: params.text, values };
|
|
223
|
+
}
|
|
224
|
+
async function runExecute(params) {
|
|
225
|
+
const config = buildQueryConfig({ text: params.text, values: params.values });
|
|
226
|
+
const result = await runMapped(() => params.queryable.query(config));
|
|
227
|
+
return result.rows;
|
|
228
|
+
}
|
|
229
|
+
async function runExecuteUnsafe(params) {
|
|
230
|
+
const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
|
|
231
|
+
const result = await runMapped(() => params.queryable.query({ text: params.text, values }));
|
|
232
|
+
return { rows: result.rows, affectedRows: result.rowCount ?? 0 };
|
|
233
|
+
}
|
|
234
|
+
function createClientAdapter(params) {
|
|
235
|
+
const { client, savepointCounter } = params;
|
|
236
|
+
return {
|
|
237
|
+
dialect: DIALECT,
|
|
238
|
+
provider: PROVIDER,
|
|
239
|
+
wire: WIRE,
|
|
240
|
+
async execute(execParams) {
|
|
241
|
+
return runExecute({ queryable: client, ...execParams });
|
|
242
|
+
},
|
|
243
|
+
async executeUnsafe(execParams) {
|
|
244
|
+
return runExecuteUnsafe({ queryable: client, ...execParams });
|
|
245
|
+
},
|
|
246
|
+
async transaction(fn, nestedOptions) {
|
|
247
|
+
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
248
|
+
const savepoint = nextSavepointName({ counter: savepointCounter });
|
|
249
|
+
await runMapped(() => client.query({ text: `SAVEPOINT ${savepoint}` }));
|
|
250
|
+
try {
|
|
251
|
+
const result = await fn(createClientAdapter({ client, savepointCounter }));
|
|
252
|
+
await runMapped(() => client.query({ text: `RELEASE SAVEPOINT ${savepoint}` }));
|
|
253
|
+
return result;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
try {
|
|
256
|
+
await client.query({ text: `ROLLBACK TO SAVEPOINT ${savepoint}` });
|
|
257
|
+
} catch {}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
async connect() {},
|
|
262
|
+
async disconnect() {},
|
|
263
|
+
formatArrayParam(values) {
|
|
264
|
+
return new PgArrayParam(values);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
dialect: DIALECT,
|
|
270
|
+
provider: PROVIDER,
|
|
271
|
+
wire: WIRE,
|
|
272
|
+
async execute(params) {
|
|
273
|
+
return runExecute({ queryable: getPool(), ...params });
|
|
274
|
+
},
|
|
275
|
+
async executeUnsafe(params) {
|
|
276
|
+
const control = classifyRawTransactionControl({ text: params.text });
|
|
277
|
+
if (rawSession !== null) {
|
|
278
|
+
if (control === "close") {
|
|
279
|
+
const session = rawSession;
|
|
280
|
+
rawSession = null;
|
|
281
|
+
try {
|
|
282
|
+
return await runExecuteUnsafe({ queryable: session, ...params });
|
|
283
|
+
} finally {
|
|
284
|
+
session.release();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return runExecuteUnsafe({ queryable: rawSession, ...params });
|
|
288
|
+
}
|
|
289
|
+
if (control === "open") {
|
|
290
|
+
const client = await runMapped(() => getPool().connect());
|
|
291
|
+
try {
|
|
292
|
+
const result = await runExecuteUnsafe({ queryable: client, ...params });
|
|
293
|
+
rawSession = client;
|
|
294
|
+
return result;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
client.release();
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return runExecuteUnsafe({ queryable: getPool(), ...params });
|
|
301
|
+
},
|
|
302
|
+
async transaction(fn, transactionOptions) {
|
|
303
|
+
const client = await runMapped(() => getPool().connect());
|
|
304
|
+
const savepointCounter = createSavepointCounter();
|
|
305
|
+
try {
|
|
306
|
+
await runMapped(() => client.query({ text: beginStatement({ isolationLevel: transactionOptions?.isolationLevel }) }));
|
|
307
|
+
if (transactionOptions?.timeout !== undefined) {
|
|
308
|
+
const text = statementTimeoutStatement({ timeout: transactionOptions.timeout });
|
|
309
|
+
await runMapped(() => client.query({ text }));
|
|
310
|
+
}
|
|
311
|
+
const result = await fn(createClientAdapter({ client, savepointCounter }));
|
|
312
|
+
await runMapped(() => client.query({ text: "COMMIT" }));
|
|
313
|
+
return result;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
try {
|
|
316
|
+
await client.query({ text: "ROLLBACK" });
|
|
317
|
+
} catch {}
|
|
318
|
+
throw error;
|
|
319
|
+
} finally {
|
|
320
|
+
client.release();
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
async connect() {
|
|
324
|
+
const client = await runMapped(() => getPool().connect());
|
|
325
|
+
client.release();
|
|
326
|
+
},
|
|
327
|
+
async disconnect() {
|
|
328
|
+
if (rawSession !== null) {
|
|
329
|
+
const session = rawSession;
|
|
330
|
+
rawSession = null;
|
|
331
|
+
try {
|
|
332
|
+
await session.query({ text: "ROLLBACK" });
|
|
333
|
+
} catch {}
|
|
334
|
+
session.release();
|
|
335
|
+
}
|
|
336
|
+
if (pool !== null && ownsPool) {
|
|
337
|
+
const closing = pool;
|
|
338
|
+
pool = null;
|
|
339
|
+
await runMapped(() => closing.end());
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
formatArrayParam(values) {
|
|
343
|
+
return new PgArrayParam(values);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
export {
|
|
348
|
+
statementTimeoutStatement,
|
|
349
|
+
serializeParamsRaw,
|
|
350
|
+
serializeParams,
|
|
351
|
+
refuseNestedTransactionOptions,
|
|
352
|
+
pgErrorSqlstate,
|
|
353
|
+
pgAdapter,
|
|
354
|
+
nextSavepointName,
|
|
355
|
+
mapPgDriverError,
|
|
356
|
+
createStatementNameCache,
|
|
357
|
+
createSavepointCounter,
|
|
358
|
+
classifyRawTransactionControl,
|
|
359
|
+
beginStatement,
|
|
360
|
+
PgArrayParam,
|
|
361
|
+
PG_SQLSTATE_REASONS,
|
|
362
|
+
PG_SQLSTATE_ERROR_CODES,
|
|
363
|
+
ISOLATION_LEVEL_SQL
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
//# debugId=7F872087B9FB07A764756E2164756E21
|