@anabranch/db-postgres 0.1.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 +113 -0
- package/esm/anabranch/index.d.ts +44 -0
- package/esm/anabranch/index.d.ts.map +1 -0
- package/esm/anabranch/index.js +41 -0
- package/esm/anabranch/streams/channel.d.ts +15 -0
- package/esm/anabranch/streams/channel.d.ts.map +1 -0
- package/esm/anabranch/streams/channel.js +122 -0
- package/esm/anabranch/streams/source.d.ts +68 -0
- package/esm/anabranch/streams/source.d.ts.map +1 -0
- package/esm/anabranch/streams/source.js +72 -0
- package/esm/anabranch/streams/stream.d.ts +431 -0
- package/esm/anabranch/streams/stream.d.ts.map +1 -0
- package/esm/anabranch/streams/stream.js +625 -0
- package/esm/anabranch/streams/task.d.ts +117 -0
- package/esm/anabranch/streams/task.d.ts.map +1 -0
- package/esm/anabranch/streams/task.js +419 -0
- package/esm/anabranch/streams/util.d.ts +33 -0
- package/esm/anabranch/streams/util.d.ts.map +1 -0
- package/esm/anabranch/streams/util.js +18 -0
- package/esm/db/adapter.d.ts +52 -0
- package/esm/db/adapter.d.ts.map +1 -0
- package/esm/db/adapter.js +1 -0
- package/esm/db/db.d.ts +73 -0
- package/esm/db/db.d.ts.map +1 -0
- package/esm/db/db.js +194 -0
- package/esm/db/errors.d.ts +57 -0
- package/esm/db/errors.d.ts.map +1 -0
- package/esm/db/errors.js +82 -0
- package/esm/db/index.d.ts +66 -0
- package/esm/db/index.d.ts.map +1 -0
- package/esm/db/index.js +64 -0
- package/esm/db/sqlite.d.ts +4 -0
- package/esm/db/sqlite.d.ts.map +1 -0
- package/esm/db/sqlite.js +24 -0
- package/esm/db-postgres/index.d.ts +17 -0
- package/esm/db-postgres/index.d.ts.map +1 -0
- package/esm/db-postgres/index.js +64 -0
- package/esm/package.json +3 -0
- package/package.json +30 -0
package/esm/db/db.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Promisable, Source, Task } from "../anabranch/index.js";
|
|
2
|
+
import type { DBAdapter, DBConnector, DBTransactionAdapter } from "./adapter.js";
|
|
3
|
+
import { ConnectionFailed, ConstraintViolation, QueryFailed, TransactionFailed } from "./errors.js";
|
|
4
|
+
/** Database transaction with Task semantics. */
|
|
5
|
+
export declare class DBTransaction {
|
|
6
|
+
private readonly adapter;
|
|
7
|
+
constructor(adapter: DBTransactionAdapter);
|
|
8
|
+
query<T>(sql: string, params?: unknown[]): Task<T[], QueryFailed>;
|
|
9
|
+
execute(sql: string, params?: unknown[]): Task<number, QueryFailed>;
|
|
10
|
+
commit(): Task<void, TransactionFailed>;
|
|
11
|
+
rollback(): Task<void, TransactionFailed>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Database wrapper with Task/Stream semantics.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // With a connector (recommended for production)
|
|
19
|
+
* const result = await DB.withConnection(myConnector, (db) =>
|
|
20
|
+
* db.query("SELECT * FROM users")
|
|
21
|
+
* ).run();
|
|
22
|
+
*
|
|
23
|
+
* // With a bare adapter (for testing or custom lifecycle)
|
|
24
|
+
* const db = new DB(adapter);
|
|
25
|
+
* const users = await db.query("SELECT * FROM users").run();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class DB {
|
|
29
|
+
private readonly adapter;
|
|
30
|
+
constructor(adapter: DBAdapter);
|
|
31
|
+
/**
|
|
32
|
+
* Execute operations with a connection acquired from the connector.
|
|
33
|
+
* The connection is automatically released after the operation completes,
|
|
34
|
+
* whether successful or failed.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const result = await DB.withConnection(postgresConnector, (db) =>
|
|
39
|
+
* db.withTransaction(async (tx) => {
|
|
40
|
+
* await tx.execute("INSERT INTO orders (user_id) VALUES (?)", [userId]).run();
|
|
41
|
+
* return tx.query("SELECT last_insert_rowid()").run();
|
|
42
|
+
* })
|
|
43
|
+
* ).run();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
static withConnection<R, E>(connector: DBConnector, fn: (db: DB) => Task<R, E>): Task<R, E | ConnectionFailed>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a SELECT query and return rows.
|
|
49
|
+
* @example
|
|
50
|
+
* const users = await db.query("SELECT * FROM users").run();
|
|
51
|
+
*/
|
|
52
|
+
query<T>(sql: string, params?: unknown[]): Task<T[], QueryFailed | ConstraintViolation>;
|
|
53
|
+
/**
|
|
54
|
+
* Execute INSERT/UPDATE/DELETE and return affected row count.
|
|
55
|
+
* @example
|
|
56
|
+
* const affected = await db.execute("DELETE FROM users WHERE id = ?", [1]).run();
|
|
57
|
+
*/
|
|
58
|
+
execute(sql: string, params?: unknown[]): Task<number, QueryFailed | ConstraintViolation>;
|
|
59
|
+
/**
|
|
60
|
+
* Stream rows from a SELECT query for memory-efficient processing.
|
|
61
|
+
*
|
|
62
|
+
* If the adapter supports cursor-based streaming (via the optional stream method),
|
|
63
|
+
* rows are yielded one at a time. Otherwise, the full result set is buffered
|
|
64
|
+
* in memory before streaming.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* const { successes, errors } = await db.stream("SELECT * FROM users").partition();
|
|
68
|
+
*/
|
|
69
|
+
stream<T>(sql: string, params?: unknown[]): Source<T, QueryFailed>;
|
|
70
|
+
withTransaction<R>(fn: (tx: DBTransaction) => Promisable<R>): Task<R, TransactionFailed | QueryFailed | ConstraintViolation>;
|
|
71
|
+
private transaction;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,oBAAoB,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,gDAAgD;AAChD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAE1D,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE,EAAE,WAAW,CAAC;IAgBjE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;IAgBnE,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAYvC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC;CAW1C;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,EAAE;IACD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,SAAS;IAE/C;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,EACxB,SAAS,EAAE,WAAW,EACtB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GACzB,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,CAAC;IAahC;;;;OAIG;IACH,KAAK,CAAC,CAAC,EACL,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,IAAI,CAAC,CAAC,EAAE,EAAE,WAAW,GAAG,mBAAmB,CAAC;IAgB/C;;;;OAIG;IACH,OAAO,CACL,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,CAAC;IAgBlD;;;;;;;;;OASG;IACH,MAAM,CAAC,CAAC,EACN,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC;IAqBzB,eAAe,CAAC,CAAC,EACf,EAAE,EAAE,CAAC,EAAE,EAAE,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,GACvC,IAAI,CAAC,CAAC,EAAE,iBAAiB,GAAG,WAAW,GAAG,mBAAmB,CAAC;IAkBjE,OAAO,CAAC,WAAW;CAepB"}
|
package/esm/db/db.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Source, Task } from "../anabranch/index.js";
|
|
2
|
+
import { ConnectionFailed, ConstraintViolation, QueryFailed, TransactionFailed, } from "./errors.js";
|
|
3
|
+
/** Database transaction with Task semantics. */
|
|
4
|
+
export class DBTransaction {
|
|
5
|
+
constructor(adapter) {
|
|
6
|
+
Object.defineProperty(this, "adapter", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: adapter
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
query(sql, params) {
|
|
14
|
+
return Task.of(async () => {
|
|
15
|
+
try {
|
|
16
|
+
return await this.adapter.query(sql, params);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof Error && error.message.includes("constraint")) {
|
|
20
|
+
throw new ConstraintViolation(sql, error.message);
|
|
21
|
+
}
|
|
22
|
+
throw new QueryFailed(sql, error instanceof Error ? error.message : String(error));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
execute(sql, params) {
|
|
27
|
+
return Task.of(async () => {
|
|
28
|
+
try {
|
|
29
|
+
return await this.adapter.execute(sql, params);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof Error && error.message.includes("constraint")) {
|
|
33
|
+
throw new ConstraintViolation(sql, error.message);
|
|
34
|
+
}
|
|
35
|
+
throw new QueryFailed(sql, error instanceof Error ? error.message : String(error));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
commit() {
|
|
40
|
+
return Task.of(async () => {
|
|
41
|
+
try {
|
|
42
|
+
await this.adapter.commit();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new TransactionFailed(error instanceof Error ? error.message : String(error));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
rollback() {
|
|
50
|
+
return Task.of(async () => {
|
|
51
|
+
try {
|
|
52
|
+
await this.adapter.rollback();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new TransactionFailed(error instanceof Error ? error.message : String(error));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Database wrapper with Task/Stream semantics.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* // With a connector (recommended for production)
|
|
66
|
+
* const result = await DB.withConnection(myConnector, (db) =>
|
|
67
|
+
* db.query("SELECT * FROM users")
|
|
68
|
+
* ).run();
|
|
69
|
+
*
|
|
70
|
+
* // With a bare adapter (for testing or custom lifecycle)
|
|
71
|
+
* const db = new DB(adapter);
|
|
72
|
+
* const users = await db.query("SELECT * FROM users").run();
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class DB {
|
|
76
|
+
constructor(adapter) {
|
|
77
|
+
Object.defineProperty(this, "adapter", {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
writable: true,
|
|
81
|
+
value: adapter
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Execute operations with a connection acquired from the connector.
|
|
86
|
+
* The connection is automatically released after the operation completes,
|
|
87
|
+
* whether successful or failed.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* const result = await DB.withConnection(postgresConnector, (db) =>
|
|
92
|
+
* db.withTransaction(async (tx) => {
|
|
93
|
+
* await tx.execute("INSERT INTO orders (user_id) VALUES (?)", [userId]).run();
|
|
94
|
+
* return tx.query("SELECT last_insert_rowid()").run();
|
|
95
|
+
* })
|
|
96
|
+
* ).run();
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
static withConnection(connector, fn) {
|
|
100
|
+
return Task.acquireRelease({
|
|
101
|
+
acquire: (signal) => connector.connect(signal).catch((error) => {
|
|
102
|
+
throw new ConnectionFailed(error instanceof Error ? error.message : String(error));
|
|
103
|
+
}),
|
|
104
|
+
release: (adapter) => adapter.close(),
|
|
105
|
+
use: (adapter) => fn(new DB(adapter)),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Execute a SELECT query and return rows.
|
|
110
|
+
* @example
|
|
111
|
+
* const users = await db.query("SELECT * FROM users").run();
|
|
112
|
+
*/
|
|
113
|
+
query(sql, params) {
|
|
114
|
+
return Task.of(async () => {
|
|
115
|
+
try {
|
|
116
|
+
return await this.adapter.query(sql, params);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof Error && error.message.includes("constraint")) {
|
|
120
|
+
throw new ConstraintViolation(sql, error.message);
|
|
121
|
+
}
|
|
122
|
+
throw new QueryFailed(sql, error instanceof Error ? error.message : String(error));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Execute INSERT/UPDATE/DELETE and return affected row count.
|
|
128
|
+
* @example
|
|
129
|
+
* const affected = await db.execute("DELETE FROM users WHERE id = ?", [1]).run();
|
|
130
|
+
*/
|
|
131
|
+
execute(sql, params) {
|
|
132
|
+
return Task.of(async () => {
|
|
133
|
+
try {
|
|
134
|
+
return await this.adapter.execute(sql, params);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof Error && error.message.includes("constraint")) {
|
|
138
|
+
throw new ConstraintViolation(sql, error.message);
|
|
139
|
+
}
|
|
140
|
+
throw new QueryFailed(sql, error instanceof Error ? error.message : String(error));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Stream rows from a SELECT query for memory-efficient processing.
|
|
146
|
+
*
|
|
147
|
+
* If the adapter supports cursor-based streaming (via the optional stream method),
|
|
148
|
+
* rows are yielded one at a time. Otherwise, the full result set is buffered
|
|
149
|
+
* in memory before streaming.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const { successes, errors } = await db.stream("SELECT * FROM users").partition();
|
|
153
|
+
*/
|
|
154
|
+
stream(sql, params) {
|
|
155
|
+
const adapter = this.adapter;
|
|
156
|
+
return Source.from(async function* () {
|
|
157
|
+
try {
|
|
158
|
+
if (adapter.stream) {
|
|
159
|
+
yield* adapter.stream(sql, params);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const results = await adapter.query(sql, params);
|
|
163
|
+
for (const row of results) {
|
|
164
|
+
yield row;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw new QueryFailed(sql, error instanceof Error ? error.message : String(error));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
withTransaction(fn) {
|
|
174
|
+
return Task.acquireRelease({
|
|
175
|
+
acquire: () => this.transaction().run(),
|
|
176
|
+
release: (tx) => tx.rollback().run().catch(() => { }),
|
|
177
|
+
use: (tx) => Task.of(() => {
|
|
178
|
+
const result = fn(tx);
|
|
179
|
+
return result instanceof Promise ? result : Promise.resolve(result);
|
|
180
|
+
}).flatMap((result) => tx.commit().map(() => result)),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
transaction() {
|
|
184
|
+
return Task.of(async () => {
|
|
185
|
+
await this.adapter.execute("BEGIN");
|
|
186
|
+
return new DBTransaction({
|
|
187
|
+
query: (sql, params) => this.adapter.query(sql, params),
|
|
188
|
+
execute: (sql, params) => this.adapter.execute(sql, params),
|
|
189
|
+
commit: () => this.adapter.execute("COMMIT").then(() => { }),
|
|
190
|
+
rollback: () => this.adapter.execute("ROLLBACK").then(() => { }),
|
|
191
|
+
});
|
|
192
|
+
}).mapErr((error) => new TransactionFailed(error instanceof Error ? error.message : String(error)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for database operations.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { DB, InMemoryDriver, DBError } from "@anabranch/db";
|
|
7
|
+
*
|
|
8
|
+
* const db = await DB.connect(InMemoryDriver.connect()).run();
|
|
9
|
+
* const result = await db.query("SELECT * FROM users").run();
|
|
10
|
+
*
|
|
11
|
+
* if (result.type === "error") {
|
|
12
|
+
* const err = result.error;
|
|
13
|
+
* if (err instanceof DBError) {
|
|
14
|
+
* console.error(`${err.kind}: ${err.message}`);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class DBError extends Error {
|
|
20
|
+
readonly kind: string;
|
|
21
|
+
readonly sql: string | undefined;
|
|
22
|
+
constructor(kind: string, sql: string | undefined, message: string);
|
|
23
|
+
}
|
|
24
|
+
/** Failed to establish a database connection. */
|
|
25
|
+
export declare class ConnectionFailed extends DBError {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
/** Query execution failed. */
|
|
29
|
+
export declare class QueryFailed extends DBError {
|
|
30
|
+
constructor(sql: string, message: string);
|
|
31
|
+
}
|
|
32
|
+
/** Constraint violation (e.g., unique, foreign key). */
|
|
33
|
+
export declare class ConstraintViolation extends DBError {
|
|
34
|
+
constructor(sql: string, message: string);
|
|
35
|
+
}
|
|
36
|
+
/** Transaction failed. */
|
|
37
|
+
export declare class TransactionFailed extends DBError {
|
|
38
|
+
constructor(message: string);
|
|
39
|
+
}
|
|
40
|
+
/** Failed to close the database connection. */
|
|
41
|
+
export declare class CloseError extends DBError {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
/** Serialization failure (concurrent modification detected). */
|
|
45
|
+
export declare class SerializationFailure extends DBError {
|
|
46
|
+
constructor(message?: string);
|
|
47
|
+
}
|
|
48
|
+
/** Registry of error constructors for `instanceof` checks. */
|
|
49
|
+
export declare const DBErrors: {
|
|
50
|
+
readonly ConnectionFailed: typeof ConnectionFailed;
|
|
51
|
+
readonly QueryFailed: typeof QueryFailed;
|
|
52
|
+
readonly ConstraintViolation: typeof ConstraintViolation;
|
|
53
|
+
readonly TransactionFailed: typeof TransactionFailed;
|
|
54
|
+
readonly CloseError: typeof CloseError;
|
|
55
|
+
readonly SerializationFailure: typeof SerializationFailure;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/db/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAErB,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM;CAKnE;AAED,iDAAiD;AACjD,qBAAa,gBAAiB,SAAQ,OAAO;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED,8BAA8B;AAC9B,qBAAa,WAAY,SAAQ,OAAO;gBAC1B,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAGzC;AAED,wDAAwD;AACxD,qBAAa,mBAAoB,SAAQ,OAAO;gBAClC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAGzC;AAED,0BAA0B;AAC1B,qBAAa,iBAAkB,SAAQ,OAAO;gBAChC,OAAO,EAAE,MAAM;CAG5B;AAED,+CAA+C;AAC/C,qBAAa,UAAW,SAAQ,OAAO;gBACzB,OAAO,EAAE,MAAM;CAG5B;AAED,gEAAgE;AAChE,qBAAa,oBAAqB,SAAQ,OAAO;gBACnC,OAAO,GAAE,MAAgC;CAGtD;AAED,8DAA8D;AAC9D,eAAO,MAAM,QAAQ;;;;;;;CAOX,CAAC"}
|
package/esm/db/errors.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error types for database operations.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { DB, InMemoryDriver, DBError } from "@anabranch/db";
|
|
7
|
+
*
|
|
8
|
+
* const db = await DB.connect(InMemoryDriver.connect()).run();
|
|
9
|
+
* const result = await db.query("SELECT * FROM users").run();
|
|
10
|
+
*
|
|
11
|
+
* if (result.type === "error") {
|
|
12
|
+
* const err = result.error;
|
|
13
|
+
* if (err instanceof DBError) {
|
|
14
|
+
* console.error(`${err.kind}: ${err.message}`);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class DBError extends Error {
|
|
20
|
+
constructor(kind, sql, message) {
|
|
21
|
+
super(message);
|
|
22
|
+
Object.defineProperty(this, "kind", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: void 0
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "sql", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
34
|
+
this.kind = kind;
|
|
35
|
+
this.sql = sql;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Failed to establish a database connection. */
|
|
39
|
+
export class ConnectionFailed extends DBError {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super("ConnectionFailed", undefined, message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Query execution failed. */
|
|
45
|
+
export class QueryFailed extends DBError {
|
|
46
|
+
constructor(sql, message) {
|
|
47
|
+
super("QueryFailed", sql, message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Constraint violation (e.g., unique, foreign key). */
|
|
51
|
+
export class ConstraintViolation extends DBError {
|
|
52
|
+
constructor(sql, message) {
|
|
53
|
+
super("ConstraintViolation", sql, message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Transaction failed. */
|
|
57
|
+
export class TransactionFailed extends DBError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super("TransactionFailed", undefined, message);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Failed to close the database connection. */
|
|
63
|
+
export class CloseError extends DBError {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super("CloseError", undefined, message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Serialization failure (concurrent modification detected). */
|
|
69
|
+
export class SerializationFailure extends DBError {
|
|
70
|
+
constructor(message = "serialization failure") {
|
|
71
|
+
super("SerializationFailure", undefined, message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Registry of error constructors for `instanceof` checks. */
|
|
75
|
+
export const DBErrors = {
|
|
76
|
+
ConnectionFailed,
|
|
77
|
+
QueryFailed,
|
|
78
|
+
ConstraintViolation,
|
|
79
|
+
TransactionFailed,
|
|
80
|
+
CloseError,
|
|
81
|
+
SerializationFailure,
|
|
82
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @abranch/db
|
|
3
|
+
*
|
|
4
|
+
* Database primitives with Task/Stream semantics for error-tolerant async operations.
|
|
5
|
+
*
|
|
6
|
+
* ## Adapters vs Connectors
|
|
7
|
+
*
|
|
8
|
+
* A **DBConnector** produces connected **DBAdapter** instances. Use connectors for
|
|
9
|
+
* production code to properly manage connection lifecycles:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { DB, createInMemory } from "@anabranch/db";
|
|
13
|
+
*
|
|
14
|
+
* // Idiomatic usage with connector (recommended)
|
|
15
|
+
* const result = await DB.withConnection(createInMemory(), (db) =>
|
|
16
|
+
* db.query("SELECT * FROM users")
|
|
17
|
+
* ).run();
|
|
18
|
+
*
|
|
19
|
+
* // Bare adapter for testing or custom lifecycle management
|
|
20
|
+
* const adapter = await createInMemory().connect();
|
|
21
|
+
* const db = new DB(adapter);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Usage
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { DB, createInMemory } from "@anabranch/db";
|
|
28
|
+
*
|
|
29
|
+
* // Using withConnection for automatic lifecycle management
|
|
30
|
+
* await DB.withConnection(createInMemory(), async (db) => {
|
|
31
|
+
* await db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)").run();
|
|
32
|
+
* await db.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]).run();
|
|
33
|
+
* return db.query("SELECT * FROM users").run();
|
|
34
|
+
* }).run();
|
|
35
|
+
*
|
|
36
|
+
* // Stream large result sets
|
|
37
|
+
* const { successes, errors } = await db.stream("SELECT * FROM users")
|
|
38
|
+
* .map(u => processUser(u))
|
|
39
|
+
* .partition();
|
|
40
|
+
*
|
|
41
|
+
* // Transactions with automatic rollback on error
|
|
42
|
+
* const result = await DB.withConnection(createInMemory(), (db) =>
|
|
43
|
+
* db.withTransaction(async (tx) => {
|
|
44
|
+
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Bob"]).run();
|
|
45
|
+
* return tx.query("SELECT last_insert_rowid()").run();
|
|
46
|
+
* })
|
|
47
|
+
* ).run();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ## Error Types
|
|
51
|
+
*
|
|
52
|
+
* The following error types are exported for adapter implementors:
|
|
53
|
+
*
|
|
54
|
+
* - {@link ConnectionFailed} - Throw from connector's `connect()` on failure
|
|
55
|
+
* - {@link CloseError} - Throw from adapter's `close()` on failure
|
|
56
|
+
* - {@link QueryFailed} - Thrown for query execution errors
|
|
57
|
+
* - {@link ConstraintViolation} - Thrown for constraint violations (e.g., UNIQUE, FOREIGN KEY)
|
|
58
|
+
* - {@link TransactionFailed} - Thrown for transaction errors
|
|
59
|
+
*
|
|
60
|
+
* @module
|
|
61
|
+
*/
|
|
62
|
+
export { DB, DBTransaction } from "./db.js";
|
|
63
|
+
export type { DBAdapter, DBConnector, DBTransactionAdapter, } from "./adapter.js";
|
|
64
|
+
export * from "./errors.js";
|
|
65
|
+
export { createInMemory } from "./sqlite.js";
|
|
66
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EACV,SAAS,EACT,WAAW,EACX,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/esm/db/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @abranch/db
|
|
3
|
+
*
|
|
4
|
+
* Database primitives with Task/Stream semantics for error-tolerant async operations.
|
|
5
|
+
*
|
|
6
|
+
* ## Adapters vs Connectors
|
|
7
|
+
*
|
|
8
|
+
* A **DBConnector** produces connected **DBAdapter** instances. Use connectors for
|
|
9
|
+
* production code to properly manage connection lifecycles:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { DB, createInMemory } from "@anabranch/db";
|
|
13
|
+
*
|
|
14
|
+
* // Idiomatic usage with connector (recommended)
|
|
15
|
+
* const result = await DB.withConnection(createInMemory(), (db) =>
|
|
16
|
+
* db.query("SELECT * FROM users")
|
|
17
|
+
* ).run();
|
|
18
|
+
*
|
|
19
|
+
* // Bare adapter for testing or custom lifecycle management
|
|
20
|
+
* const adapter = await createInMemory().connect();
|
|
21
|
+
* const db = new DB(adapter);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Usage
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { DB, createInMemory } from "@anabranch/db";
|
|
28
|
+
*
|
|
29
|
+
* // Using withConnection for automatic lifecycle management
|
|
30
|
+
* await DB.withConnection(createInMemory(), async (db) => {
|
|
31
|
+
* await db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)").run();
|
|
32
|
+
* await db.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]).run();
|
|
33
|
+
* return db.query("SELECT * FROM users").run();
|
|
34
|
+
* }).run();
|
|
35
|
+
*
|
|
36
|
+
* // Stream large result sets
|
|
37
|
+
* const { successes, errors } = await db.stream("SELECT * FROM users")
|
|
38
|
+
* .map(u => processUser(u))
|
|
39
|
+
* .partition();
|
|
40
|
+
*
|
|
41
|
+
* // Transactions with automatic rollback on error
|
|
42
|
+
* const result = await DB.withConnection(createInMemory(), (db) =>
|
|
43
|
+
* db.withTransaction(async (tx) => {
|
|
44
|
+
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Bob"]).run();
|
|
45
|
+
* return tx.query("SELECT last_insert_rowid()").run();
|
|
46
|
+
* })
|
|
47
|
+
* ).run();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ## Error Types
|
|
51
|
+
*
|
|
52
|
+
* The following error types are exported for adapter implementors:
|
|
53
|
+
*
|
|
54
|
+
* - {@link ConnectionFailed} - Throw from connector's `connect()` on failure
|
|
55
|
+
* - {@link CloseError} - Throw from adapter's `close()` on failure
|
|
56
|
+
* - {@link QueryFailed} - Thrown for query execution errors
|
|
57
|
+
* - {@link ConstraintViolation} - Thrown for constraint violations (e.g., UNIQUE, FOREIGN KEY)
|
|
58
|
+
* - {@link TransactionFailed} - Thrown for transaction errors
|
|
59
|
+
*
|
|
60
|
+
* @module
|
|
61
|
+
*/
|
|
62
|
+
export { DB, DBTransaction } from "./db.js";
|
|
63
|
+
export * from "./errors.js";
|
|
64
|
+
export { createInMemory } from "./sqlite.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/db/sqlite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,WAAW,CAyB5C"}
|
package/esm/db/sqlite.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
/** Creates an in-memory SQLite connector for testing. */
|
|
3
|
+
export function createInMemory() {
|
|
4
|
+
return {
|
|
5
|
+
connect: () => {
|
|
6
|
+
const db = new DatabaseSync(":memory:");
|
|
7
|
+
return Promise.resolve({
|
|
8
|
+
query: (sql, params) => {
|
|
9
|
+
const stmt = db.prepare(sql);
|
|
10
|
+
return Promise.resolve(stmt.all(...(params ?? [])));
|
|
11
|
+
},
|
|
12
|
+
execute: (sql, params) => {
|
|
13
|
+
const stmt = db.prepare(sql);
|
|
14
|
+
const { changes } = stmt.run(...(params ?? []));
|
|
15
|
+
return Promise.resolve(Number(changes));
|
|
16
|
+
},
|
|
17
|
+
close: () => {
|
|
18
|
+
db.close();
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DBConnector } from "../db/index.js";
|
|
2
|
+
export interface PostgresOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
user?: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
database?: string;
|
|
8
|
+
connectionString?: string;
|
|
9
|
+
max?: number;
|
|
10
|
+
idleTimeoutMillis?: number;
|
|
11
|
+
connectionTimeoutMillis?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PostgresConnector extends DBConnector {
|
|
14
|
+
end(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createPostgres(options?: PostgresOptions): PostgresConnector;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db-postgres/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAK7D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAuBD,wBAAgB,cAAc,CAC5B,OAAO,GAAE,eAAoB,GAC5B,iBAAiB,CA4CnB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import Cursor from "pg-cursor";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
const { Pool } = pg;
|
|
5
|
+
function toPoolConfig(options) {
|
|
6
|
+
if (options.connectionString) {
|
|
7
|
+
return {
|
|
8
|
+
connectionString: options.connectionString,
|
|
9
|
+
max: options.max,
|
|
10
|
+
idleTimeoutMillis: options.idleTimeoutMillis,
|
|
11
|
+
connectionTimeoutMillis: options.connectionTimeoutMillis,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
host: options.host ?? process.env.PGHOST ?? "localhost",
|
|
16
|
+
port: options.port ?? parseInt(process.env.PGPORT ?? "5432"),
|
|
17
|
+
user: options.user ?? process.env.PGUSER ?? "postgres",
|
|
18
|
+
password: options.password ?? process.env.PGPASSWORD ?? "",
|
|
19
|
+
database: options.database ?? process.env.PGDATABASE ?? "postgres",
|
|
20
|
+
max: options.max,
|
|
21
|
+
idleTimeoutMillis: options.idleTimeoutMillis,
|
|
22
|
+
connectionTimeoutMillis: options.connectionTimeoutMillis,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function createPostgres(options = {}) {
|
|
26
|
+
const pool = new Pool(toPoolConfig(options));
|
|
27
|
+
return {
|
|
28
|
+
async connect(signal) {
|
|
29
|
+
const client = await pool.connect();
|
|
30
|
+
const onAbort = () => client.release(true);
|
|
31
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
32
|
+
return {
|
|
33
|
+
query: (sql, params) => client
|
|
34
|
+
.query(sql, params)
|
|
35
|
+
.then((r) => r.rows),
|
|
36
|
+
execute: (sql, params) => client
|
|
37
|
+
.query(sql, params)
|
|
38
|
+
.then((r) => Number(r.rowCount ?? 0)),
|
|
39
|
+
close: () => {
|
|
40
|
+
signal?.removeEventListener("abort", onAbort);
|
|
41
|
+
client.release();
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
},
|
|
44
|
+
stream: async function* (sql, params) {
|
|
45
|
+
const cursor = client.query(new Cursor(sql, params));
|
|
46
|
+
try {
|
|
47
|
+
while (true) {
|
|
48
|
+
const rows = await cursor.read(100);
|
|
49
|
+
if (rows.length === 0)
|
|
50
|
+
break;
|
|
51
|
+
yield* rows;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await cursor.close();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
end() {
|
|
61
|
+
return pool.end();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
package/esm/package.json
ADDED