@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 CHANGED
@@ -1,88 +1,96 @@
1
1
  # @vibeorm/adapter-pg
2
2
 
3
- `node-postgres` adapter for VibeORM. Use VibeORM with the `pg` driver on Bun or Node.js.
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
- ## Installation
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
- const db = VibeClient({
18
- adapter: pgAdapter({ connectionString: "postgres://user:pass@localhost:5432/mydb" }),
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
- ### Using an existing pool
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: "postgres://..." });
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
- ```ts
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
- ### Statement timeout
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
- Set a default timeout for all queries. Any query exceeding this duration is cancelled by PostgreSQL (SQLSTATE `57014`), surfaced as a `VibeTransientError` with code `STATEMENT_TIMEOUT`.
50
+ ## Runtime support
59
51
 
60
- ```ts
61
- const adapter = pgAdapter({
62
- connectionString: "postgres://...",
63
- statementTimeout: 30000, // 30 seconds
64
- });
65
- ```
52
+ | Runtime | Supported |
53
+ |---|---|
54
+ | Bun ≥ 1.2 | yes |
55
+ | Node (with `pg` installed) | yes |
66
56
 
67
- Passed as `statement_timeout` in the pg Pool config, so every connection inherits it automatically with zero per-query overhead.
57
+ `pg` is a pure-JavaScript driver, so nothing native has to build. Peer range: `pg >= 8`.
68
58
 
69
- Transaction-level `timeout` (via `db.$transaction(fn, { timeout })`) overrides this default for that transaction using `SET LOCAL statement_timeout`.
59
+ ## Capabilities
70
60
 
71
- > **Note:** `statementTimeout` and `connectionTimeout` are only available when creating a new pool. When you pass an existing `pool`, you manage these settings yourself via your pool config.
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
- ### Connection timeout
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
- Limit how long pool checkout (acquiring a connection) can take:
65
+ ## Parameter serialization: two paths
76
66
 
77
- ```ts
78
- const adapter = pgAdapter({
79
- connectionString: "postgres://...",
80
- connectionTimeout: 5000, // 5 seconds
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
- Mapped to pg Pool's `connectionTimeoutMillis` option. Throws when the pool is exhausted and no connection becomes available within the limit.
88
+ A `VibeError` raised deeper inside the ORM passes through unchanged.
85
89
 
86
- ## License
90
+ ## Links
87
91
 
88
- [MIT](../../LICENSE)
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
@@ -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"}
@@ -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