@vibeorm/adapter-pg 2.4.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +13 -9
- package/dist/batch.d.ts +152 -0
- package/dist/index.d.ts +29 -7
- package/dist/index.js +493 -95
- package/dist/index.js.map +7 -6
- package/dist/transaction-sql.d.ts +22 -1
- package/package.json +6 -4
- package/dist/errors.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/pool-config.d.ts.map +0 -1
- package/dist/statement-cache.d.ts.map +0 -1
- package/dist/transaction-sql.d.ts.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VibeORM contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -16,31 +16,35 @@ bun add @vibeorm/adapter-pg pg
|
|
|
16
16
|
import { VibeClient } from "./generated/vibeorm/index.js";
|
|
17
17
|
import { pgAdapter } from "@vibeorm/adapter-pg";
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
19
|
+
// The adapter reads no environment variable: pass the address you mean.
|
|
20
|
+
const connectionString = process.env.DATABASE_URL;
|
|
21
|
+
if (!connectionString) throw new Error("DATABASE_URL is not set");
|
|
22
|
+
const db = VibeClient({ adapter: pgAdapter({ connectionString, max: 20 }) });
|
|
21
23
|
|
|
22
24
|
const users = await db.user.findMany({ where: { email: { contains: "@example.com" } }, take: 10 });
|
|
23
25
|
|
|
24
26
|
await db.$disconnect();
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
`disconnect()` ends the adapter for good: work that already started finishes, the pool it created is ended once, and every later call refuses with `VIBE_ADAPTER_CLOSED`. Create a new adapter to connect again.
|
|
30
|
+
|
|
31
|
+
To reuse a pool you already own, pass it in. The adapter never ends a pool it did not create: in that mode `disconnect()` only ends the adapter, and the pool stays usable by its owner and by other adapters:
|
|
28
32
|
|
|
29
33
|
```ts
|
|
30
34
|
import pg from "pg";
|
|
31
35
|
|
|
32
|
-
const pool = new pg.Pool({ connectionString
|
|
36
|
+
const pool = new pg.Pool({ connectionString });
|
|
33
37
|
const db = VibeClient({ adapter: pgAdapter({ pool }) });
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
## Options
|
|
37
41
|
|
|
38
|
-
`pgAdapter(options
|
|
42
|
+
`pgAdapter(options: PgAdapterOptions)` — `connectionString` or `pool` is required. Without either, the factory throws `VIBE_CONFIG` (`meta.reason: "missing-address"`) before any pool or connection exists. The adapter never reads `DATABASE_URL`. Give a complete URL: node-postgres fills parts a URL leaves out (host, user, database, …) from the `PG*` environment variables.
|
|
39
43
|
|
|
40
44
|
| Option | Type | Default | Meaning |
|
|
41
45
|
|---|---|---|---|
|
|
42
|
-
| `connectionString` | `string` | `
|
|
43
|
-
| `pool` | `PgPoolLike` | — | An existing `pg.Pool` to use instead of creating one; it is never ended by the adapter |
|
|
46
|
+
| `connectionString` | `string` | — (required without `pool`) | PostgreSQL connection URL |
|
|
47
|
+
| `pool` | `PgPoolLike` | — (required without `connectionString`) | An existing `pg.Pool` to use instead of creating one; it is never ended by the adapter, and it wins when both are given |
|
|
44
48
|
| `max` | `number` | `10` | Maximum pooled connections; ignored when `pool` is given |
|
|
45
49
|
| `statementTimeout` | `number` (ms) | — | Pool-wide `statement_timeout`; a per-transaction `timeout` overrides it |
|
|
46
50
|
| `connectionTimeout` | `number` (ms) | — | Wait for a pool checkout before failing (pg's `connectionTimeoutMillis`) |
|
|
@@ -71,7 +75,7 @@ Why it stays opt-in: named statements live on the **backend** connection. Behind
|
|
|
71
75
|
|
|
72
76
|
The adapter surfaces the failure as `VIBE_ADAPTER` with `meta.sqlstate: "0A000"` and the driver error as `cause`. It does not retry the statement, replay a transaction callback, or issue `DEALLOCATE`. Inside a transaction, the error leaves PostgreSQL in its aborted state until rollback. SQLSTATE `0A000` also covers other unsupported features, so it is not a safe generic retry signal.
|
|
73
77
|
|
|
74
|
-
For an incompatible schema deployment, stop admitting work on the old pool, let active transactions settle, apply the migration, then recycle the connections before resuming traffic. If the adapter owns its pool, await `disconnect()` after draining and
|
|
78
|
+
For an incompatible schema deployment, stop admitting work on the old pool, let active transactions settle, apply the migration, then recycle the connections before resuming traffic. If the adapter owns its pool, await `disconnect()` after draining and create a new adapter (a disconnected adapter does not reconnect). If you supplied `pool`, its owner must await `pool.end()` and supply a new pool to a new adapter. Disconnecting or replacing only the adapter leaves an injected pool's retained statements intact. Never run `DEALLOCATE ALL` behind node-postgres' remembered statement-name registry.
|
|
75
79
|
|
|
76
80
|
The timings above are historical measurements, not a schema-recovery or current performance guarantee. Prepared statements remain opt-in; applications without a coordinated connection lifecycle should keep the default `preparedStatements: false`.
|
|
77
81
|
|
|
@@ -99,7 +103,7 @@ A raw script that produces multiple PostgreSQL results returns only the **final
|
|
|
99
103
|
- `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.
|
|
100
104
|
- `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.
|
|
101
105
|
|
|
102
|
-
A string bound to a `::jsonb` slot on the raw path is sent as text and parsed by the server, so pre-encoded JSON text stores the document here
|
|
106
|
+
A string bound to a `::jsonb` slot on the raw path is sent as text and parsed by the server, so pre-encoded JSON text stores the document here, and on `@vibeorm/adapter-bun` with its default unnamed statements. With adapter-bun's `preparedStatements: true` the same statement stores a jsonb *string*, because bun:sql then JSON-encodes whatever is bound to a jsonb slot. Spell the slot `::text::jsonb` when a raw statement must run on both drivers; that is what the ORM path renders for `Json` fields (pinned live in `tests/raw-jsonb.live.test.ts` on both adapters).
|
|
103
107
|
|
|
104
108
|
On both paths a `Date` is spelled as UTC ISO text (`2026-03-08T19:38:08.538Z`) rather than node-postgres' host-local form, and the result side attaches per-query parsers (never `pg.types` globally) that read `timestamp without time zone`, `date` and their arrays as **UTC** — pg-types reads them as host-local time. Together they give zone-less columns (Prisma's `@db.Timestamp(n)` / `@db.Date`) the UTC wall-clock contract shared with bun:sql, PGlite and Prisma, whatever the process `TZ` is; `timestamptz` is unaffected. See `docs/dialect-notes.md` "DateTime without time zone on postgres".
|
|
105
109
|
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-Sync batch for node-postgres (round-trip EPIC 6, pipelining).
|
|
3
|
+
*
|
|
4
|
+
* Several statements go to the server as Parse / Bind / Describe / Execute
|
|
5
|
+
* each, followed by ONE Sync — one wait instead of one per statement. It is a
|
|
6
|
+
* `Submittable` (the extension point `client.query(object)` documents and
|
|
7
|
+
* pg-cursor uses) running on an ordinary, NON-pipeline client: the batch is one
|
|
8
|
+
* query object, so the client's own queue, ReadyForQuery handling and status
|
|
9
|
+
* bookkeeping stay node-postgres'.
|
|
10
|
+
*
|
|
11
|
+
* PostgreSQL's extended protocol skips every message after an error until the
|
|
12
|
+
* Sync (as libpq pipeline mode does), so a failing step leaves every later step
|
|
13
|
+
* UNRUN — a failed `BEGIN` can never let a later write run in autocommit, and
|
|
14
|
+
* no later step answers 25P02 (spike-report §1.3, "pg one-Sync batch").
|
|
15
|
+
*
|
|
16
|
+
* Only API that node-postgres exposes is used: `connection.parse / bind /
|
|
17
|
+
* describe / execute / sync` (declared on `Connection` in @types/pg), the
|
|
18
|
+
* message handlers of the Submittable contract, and the driver's own
|
|
19
|
+
* `utils.prepareValue` (a property of the `pg` module object, feature-detected)
|
|
20
|
+
* so parameters serialize exactly as `client.query` serializes them. Rows are
|
|
21
|
+
* decoded HERE, with the adapter's query-local type parsers — never through
|
|
22
|
+
* node-postgres' internal `lib/result.js`.
|
|
23
|
+
*/
|
|
24
|
+
/** One statement of a batch. Values are the adapter's already-serialized params. */
|
|
25
|
+
export type BatchStep = {
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly values?: readonly unknown[];
|
|
28
|
+
};
|
|
29
|
+
/** A step's decoded result, shaped like the node-postgres result the adapter reads. */
|
|
30
|
+
export type BatchStepResult = {
|
|
31
|
+
readonly rows: Record<string, unknown>[];
|
|
32
|
+
readonly rowCount: number | null;
|
|
33
|
+
readonly command?: string;
|
|
34
|
+
};
|
|
35
|
+
/** Per-step outcome after the batch's ReadyForQuery. */
|
|
36
|
+
export type BatchStepOutcome = {
|
|
37
|
+
readonly status: "ok";
|
|
38
|
+
readonly result: BatchStepResult;
|
|
39
|
+
} | {
|
|
40
|
+
readonly status: "failed";
|
|
41
|
+
readonly error: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
readonly status: "skipped";
|
|
44
|
+
};
|
|
45
|
+
/** What a batch reports once it settled. */
|
|
46
|
+
export type BatchOutcome = {
|
|
47
|
+
/** One entry per step, in order. */
|
|
48
|
+
readonly steps: readonly BatchStepOutcome[];
|
|
49
|
+
/**
|
|
50
|
+
* A failure WITHOUT a server answer — the socket broke, or the client's
|
|
51
|
+
* `query_timeout` fired while the batch was still on the wire. What the
|
|
52
|
+
* server did with the batch is unknown; `steps` lists only what was seen.
|
|
53
|
+
*/
|
|
54
|
+
readonly transportError?: unknown;
|
|
55
|
+
};
|
|
56
|
+
/** The query-local type parser surface (the adapter's `EXACT_TYPES`). */
|
|
57
|
+
export type BatchTypeParsers = {
|
|
58
|
+
getTypeParser(oid: number, format?: "text" | "binary"): (value: string) => unknown;
|
|
59
|
+
};
|
|
60
|
+
type RowDescriptionField = {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly dataTypeID: number;
|
|
63
|
+
readonly format?: "text" | "binary";
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Whether `client` can run a one-Sync batch: a node-postgres client whose
|
|
67
|
+
* connection exposes the protocol writers, NOT in pipeline mode (pipeline mode
|
|
68
|
+
* sends a Sync per query and was rejected by the spike), with the driver's
|
|
69
|
+
* value serializer available. Test doubles and foreign clients answer false
|
|
70
|
+
* and keep today's one-statement-per-wait path.
|
|
71
|
+
*/
|
|
72
|
+
export declare function supportsBatch(params: {
|
|
73
|
+
client: unknown;
|
|
74
|
+
}): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Serialize a step's parameters with the driver's own `prepareValue`. Runs
|
|
77
|
+
* BEFORE anything is written, so a value the driver cannot serialize refuses
|
|
78
|
+
* the whole batch with nothing sent (node-postgres' own path closes and syncs
|
|
79
|
+
* instead; the caller-visible error is the same driver error).
|
|
80
|
+
*/
|
|
81
|
+
export declare function prepareBatchValues(params: {
|
|
82
|
+
values: readonly unknown[];
|
|
83
|
+
}): unknown[];
|
|
84
|
+
/**
|
|
85
|
+
* The Submittable. Construct with steps whose values are already prepared
|
|
86
|
+
* ({@link prepareBatchValues}); pass to `client.query(batch)`; await
|
|
87
|
+
* {@link PgBatch.done}.
|
|
88
|
+
*/
|
|
89
|
+
export declare class PgBatch {
|
|
90
|
+
/** Settles once: at the batch's ReadyForQuery, at its first server error, or at a transport failure. */
|
|
91
|
+
readonly done: Promise<BatchOutcome>;
|
|
92
|
+
/**
|
|
93
|
+
* Set by node-postgres when the client has a `query_timeout`: its wrapper
|
|
94
|
+
* clears the read-timeout timer. Called once when the batch settles, so a
|
|
95
|
+
* timer never outlives the batch (fix lane FX-A, RV5 MI-6).
|
|
96
|
+
*/
|
|
97
|
+
callback: ((error: unknown, result?: unknown) => void) | undefined;
|
|
98
|
+
private readonly steps;
|
|
99
|
+
private readonly types;
|
|
100
|
+
private readonly outcomes;
|
|
101
|
+
private parsers;
|
|
102
|
+
private fields;
|
|
103
|
+
private rows;
|
|
104
|
+
/** The first type-parser failure of the current step; its remaining rows are skipped. */
|
|
105
|
+
private rowError;
|
|
106
|
+
private settled;
|
|
107
|
+
private resolve;
|
|
108
|
+
constructor(params: {
|
|
109
|
+
steps: readonly BatchStep[];
|
|
110
|
+
types: BatchTypeParsers;
|
|
111
|
+
});
|
|
112
|
+
/** Called by node-postgres when the batch reaches the head of the client's queue. */
|
|
113
|
+
submit(connection: unknown): void;
|
|
114
|
+
handleRowDescription(message: {
|
|
115
|
+
fields: readonly RowDescriptionField[];
|
|
116
|
+
}): void;
|
|
117
|
+
handleDataRow(message: {
|
|
118
|
+
fields: readonly (string | null)[];
|
|
119
|
+
}): void;
|
|
120
|
+
handleCommandComplete(message: {
|
|
121
|
+
text?: string;
|
|
122
|
+
command?: string;
|
|
123
|
+
}): void;
|
|
124
|
+
handleEmptyQuery(): void;
|
|
125
|
+
handleError(error: unknown): void;
|
|
126
|
+
handleReadyForQuery(): void;
|
|
127
|
+
handlePortalSuspended(): void;
|
|
128
|
+
handleCopyInResponse(connection: unknown): void;
|
|
129
|
+
handleCopyData(): void;
|
|
130
|
+
private finish;
|
|
131
|
+
}
|
|
132
|
+
/** Run `steps` as one batch on `client` and wait for it to settle. */
|
|
133
|
+
export declare function runBatch(params: {
|
|
134
|
+
/** A checked-out client that passed {@link supportsBatch}. */
|
|
135
|
+
client: {
|
|
136
|
+
query: (...args: never[]) => unknown;
|
|
137
|
+
};
|
|
138
|
+
steps: readonly BatchStep[];
|
|
139
|
+
types: BatchTypeParsers;
|
|
140
|
+
}): Promise<BatchOutcome>;
|
|
141
|
+
/**
|
|
142
|
+
* The first failure of a settled batch: the transport error, or the first
|
|
143
|
+
* failed step with its index. `undefined` when every step succeeded.
|
|
144
|
+
*/
|
|
145
|
+
export declare function firstBatchFailure(params: {
|
|
146
|
+
outcome: BatchOutcome;
|
|
147
|
+
}): {
|
|
148
|
+
readonly index: number;
|
|
149
|
+
readonly error: unknown;
|
|
150
|
+
} | undefined;
|
|
151
|
+
export {};
|
|
152
|
+
//# sourceMappingURL=batch.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -63,15 +63,22 @@ export type PgPoolLike = {
|
|
|
63
63
|
on?(event: "error", listener: (error: unknown, client: PgPoolClientLike) => void): void;
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* Every {@link pgAdapter} option. {@link PgAdapterOptions} adds the rule that
|
|
67
|
+
* one of `connectionString` and `pool` is present.
|
|
68
68
|
*/
|
|
69
|
-
export type
|
|
70
|
-
/**
|
|
69
|
+
export type PgAdapterSettings = {
|
|
70
|
+
/**
|
|
71
|
+
* PostgreSQL connection URL. The adapter never reads `DATABASE_URL` (or any
|
|
72
|
+
* other environment variable) itself: pass the URL you mean. Give a complete
|
|
73
|
+
* URL — node-postgres fills parts the URL omits (host, user, database, …)
|
|
74
|
+
* from the `PG*` environment variables.
|
|
75
|
+
*/
|
|
71
76
|
readonly connectionString?: string;
|
|
72
77
|
/**
|
|
73
78
|
* An existing `pg.Pool` to use instead of creating one. The adapter never
|
|
74
|
-
* ends a pool it did not create
|
|
79
|
+
* ends a pool it did not create: `disconnect()` only ends this adapter (and
|
|
80
|
+
* rolls back and releases its own raw session); the pool stays usable.
|
|
81
|
+
* Wins over `connectionString` when both are given.
|
|
75
82
|
*/
|
|
76
83
|
readonly pool?: PgPoolLike;
|
|
77
84
|
/** Maximum pooled connections (default 10). Ignored when `pool` is given. */
|
|
@@ -130,12 +137,27 @@ export type PgAdapterOptions = {
|
|
|
130
137
|
*/
|
|
131
138
|
readonly planCacheMode?: PlanCacheMode;
|
|
132
139
|
};
|
|
140
|
+
/**
|
|
141
|
+
* Options for {@link pgAdapter}: an explicit address is required. Either point
|
|
142
|
+
* it at a connection string (the adapter then owns the pool it creates) or hand
|
|
143
|
+
* it a pool you already own. Without either, the factory throws `VIBE_CONFIG`
|
|
144
|
+
* before any pool or connection exists.
|
|
145
|
+
*/
|
|
146
|
+
export type PgAdapterOptions = PgAdapterSettings & ({
|
|
147
|
+
readonly connectionString: string;
|
|
148
|
+
} | {
|
|
149
|
+
readonly pool: PgPoolLike;
|
|
150
|
+
});
|
|
133
151
|
/**
|
|
134
152
|
* Create a VibeORM adapter backed by node-postgres.
|
|
135
153
|
*
|
|
154
|
+
* Requires an explicit address (`connectionString` or `pool`); see
|
|
155
|
+
* {@link PgAdapterOptions}. Construction opens nothing — the pool is created
|
|
156
|
+
* on first use.
|
|
157
|
+
*
|
|
136
158
|
* @example
|
|
137
159
|
* ```ts
|
|
138
|
-
* const adapter = pgAdapter({ connectionString:
|
|
160
|
+
* const adapter = pgAdapter({ connectionString: "postgresql://app:secret@db.internal:5432/app", max: 20 });
|
|
139
161
|
* await adapter.connect();
|
|
140
162
|
* const rows = await adapter.execute({ text: 'SELECT * FROM "User" WHERE "id" = $1', values: [1] });
|
|
141
163
|
* ```
|
|
@@ -147,7 +169,7 @@ export type PgAdapterOptions = {
|
|
|
147
169
|
* const adapter = pgAdapter({ pool });
|
|
148
170
|
* ```
|
|
149
171
|
*/
|
|
150
|
-
export declare function pgAdapter(options
|
|
172
|
+
export declare function pgAdapter(options: PgAdapterOptions): DatabaseAdapter;
|
|
151
173
|
export { PG_SQLSTATE_ERROR_CODES, PG_SQLSTATE_REASONS, mapPgDriverError, pgErrorSqlstate } from "./errors.ts";
|
|
152
174
|
export { PgArrayParam, serializeParams, serializeParamsRaw } from "./params.ts";
|
|
153
175
|
export { createSavepointCounter, nextSavepointName } from "@vibeorm/runtime/adapter-kit";
|