@gobing-ai/ts-db 0.4.6 → 0.4.8
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 +9 -1
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.d.ts +2 -1
- package/dist/adapters/bun-sqlite.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.js +10 -0
- package/dist/adapters/d1.d.ts +10 -1
- package/dist/adapters/d1.d.ts.map +1 -1
- package/dist/adapters/d1.js +22 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +20 -0
- package/src/adapters/bun-sqlite.ts +11 -1
- package/src/adapters/d1.ts +32 -1
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Application code imports only `@gobing-ai/ts-db` — never `drizzle-orm`. drizzl
|
|
|
14
14
|
|
|
15
15
|
| Component | Purpose |
|
|
16
16
|
|-----------|---------|
|
|
17
|
-
| `createDbAdapter` / `DbAdapter` | Construction + lifecycle + string-SQL escape; exposes an internal typed db to the DAO layer only |
|
|
17
|
+
| `createDbAdapter` / `DbAdapter` | Construction + lifecycle + string-SQL escape + atomic batch; exposes an internal typed db to the DAO layer only |
|
|
18
18
|
| `BunSqliteAdapter` | Bun SQLite implementation with statement caching and WAL pragmas (`@gobing-ai/ts-db/bun-sqlite`) |
|
|
19
19
|
| `D1Adapter` | Cloudflare D1 implementation (no `@cloudflare/workers-types` dependency) |
|
|
20
20
|
| `BaseDao` | Raw tier — `query`/`one`/`tx`, drizzle-free signatures |
|
|
@@ -52,6 +52,7 @@ classDiagram
|
|
|
52
52
|
+run(sql, ...params) void
|
|
53
53
|
+queryFirst(sql, ...params) T?
|
|
54
54
|
+queryAll(sql, ...params) T[]
|
|
55
|
+
+batch(operations) void
|
|
55
56
|
+close() void
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -177,6 +178,13 @@ const user = await adapter.queryFirst<{ name: string }>('SELECT name FROM users
|
|
|
177
178
|
const all = await adapter.queryAll<{ name: string }>('SELECT name FROM users');
|
|
178
179
|
```
|
|
179
180
|
|
|
181
|
+
```ts
|
|
182
|
+
// Atomic multi-statement batch — all succeed or all roll back (ADR-020)
|
|
183
|
+
await adapter.batch([
|
|
184
|
+
{ sql: 'INSERT INTO users VALUES (?, ?)', params: ['u2', 'Bob'] },
|
|
185
|
+
{ sql: 'UPDATE counters SET count = count + 1 WHERE name = ?', params: ['users'] },
|
|
186
|
+
]);
|
|
187
|
+
```
|
|
180
188
|
### EntityDao — CRUD with soft delete
|
|
181
189
|
|
|
182
190
|
Define a Drizzle table, extend `EntityDao`, get full CRUD for free:
|
package/dist/adapter.d.ts
CHANGED
|
@@ -40,9 +40,28 @@ export interface DbAdapter {
|
|
|
40
40
|
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
41
41
|
/** Parameterized read returning all rows. */
|
|
42
42
|
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Execute a batch of parameterized writes atomically — either all commit or
|
|
45
|
+
* none do. Used by adapters that need to persist multiple related rows in a
|
|
46
|
+
* single transaction (e.g. workflow transition + state snapshot + phase write).
|
|
47
|
+
* Each entry is a `{ sql, params }` tuple applied in order. Adapters must
|
|
48
|
+
* uphold all-or-nothing semantics for multi-statement batches or **throw**;
|
|
49
|
+
* silently degrading to non-atomic sequential writes is not allowed. A
|
|
50
|
+
* single-statement batch may execute directly (trivially atomic).
|
|
51
|
+
*/
|
|
52
|
+
batch(operations: readonly DbBatchOp[]): Promise<void>;
|
|
43
53
|
/** Close the underlying connection. */
|
|
44
54
|
close(): void;
|
|
45
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* A single write operation inside a {@link DbAdapter.batch} call.
|
|
58
|
+
*/
|
|
59
|
+
export interface DbBatchOp {
|
|
60
|
+
/** SQL statement (INSERT/UPDATE/DELETE). */
|
|
61
|
+
sql: string;
|
|
62
|
+
/** Bind parameters for the statement. */
|
|
63
|
+
params: readonly unknown[];
|
|
64
|
+
}
|
|
46
65
|
/**
|
|
47
66
|
* Discriminated-union config for creating a database adapter (bun-sqlite or D1).
|
|
48
67
|
*/
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;GAEG;AACH,UAAU,SAAS;IACf,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjH;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,wDAAwD;IACxD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,uEAAuE;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,gEAAgE;IAChE,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzE,6CAA6C;IAC7C,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7D,uCAAuC;IACvC,KAAK,IAAI,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACrB;IACI,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACL,GACD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3C;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAcjF"}
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;GAEG;AACH,UAAU,SAAS;IACf,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjH;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,wDAAwD;IACxD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,uEAAuE;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,gEAAgE;IAChE,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzE,6CAA6C;IAC7C,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7D;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,uCAAuC;IACvC,KAAK,IAAI,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACrB;IACI,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACL,GACD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC;AAE3C;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAcjF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
2
|
-
import type { DbAdapter, InternalDb } from '../adapter';
|
|
2
|
+
import type { DbAdapter, DbBatchOp, InternalDb } from '../adapter';
|
|
3
3
|
import * as schema from '../schema/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* Configuration options for the bun:sqlite adapter (path, pragmas).
|
|
@@ -36,6 +36,7 @@ export declare class BunSqliteAdapter implements DbAdapter {
|
|
|
36
36
|
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
37
37
|
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
38
38
|
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
39
|
+
batch(operations: readonly DbBatchOp[]): Promise<void>;
|
|
39
40
|
close(): void;
|
|
40
41
|
}
|
|
41
42
|
//# sourceMappingURL=bun-sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bun-sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/bun-sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAAW,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"bun-sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/bun-sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAAW,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAS5C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,OAAO,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACL;AAUD;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAC9C,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,SAAS,CAAmC;IACpD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IAEpE,OAAO,CAAC,YAAY;gBAUR,OAAO,CAAC,EAAE,gBAAgB;IAkBtC,+EAA+E;IAC/E,IAAI,EAAE,IAAI,UAAU,CAEnB;IAED,wEAAwE;IACxE,YAAY,IAAI,iBAAiB,CAAC,OAAO,MAAM,CAAC;IAI1C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKxE,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAK5D,KAAK,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5D,KAAK,IAAI,IAAI;CAGhB"}
|
|
@@ -65,6 +65,16 @@ export class BunSqliteAdapter {
|
|
|
65
65
|
const stmt = this.getStatement(sql);
|
|
66
66
|
return stmt.all(...params) ?? [];
|
|
67
67
|
}
|
|
68
|
+
async batch(operations) {
|
|
69
|
+
if (operations.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
this.sqlite.transaction(() => {
|
|
72
|
+
for (const op of operations) {
|
|
73
|
+
const stmt = this.getStatement(op.sql);
|
|
74
|
+
stmt.run(...op.params);
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
68
78
|
close() {
|
|
69
79
|
this.sqlite.close();
|
|
70
80
|
}
|
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DrizzleD1Database } from 'drizzle-orm/d1';
|
|
2
|
-
import type { DbAdapter, InternalDb } from '../adapter';
|
|
2
|
+
import type { DbAdapter, DbBatchOp, InternalDb } from '../adapter';
|
|
3
3
|
import * as schema from '../schema/runtime';
|
|
4
4
|
/**
|
|
5
5
|
* Minimal D1 binding interface — avoids depending on @cloudflare/workers-types.
|
|
@@ -14,6 +14,14 @@ export interface D1Binding {
|
|
|
14
14
|
}>;
|
|
15
15
|
};
|
|
16
16
|
exec(sql: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Run multiple statements as a single atomic batch (D1's native `batch()`
|
|
19
|
+
* API). Optional for single-statement test doubles only: when absent,
|
|
20
|
+
* {@link D1Adapter.batch} runs a lone statement directly (trivially atomic)
|
|
21
|
+
* and **throws** for multi-statement batches rather than silently degrading
|
|
22
|
+
* atomicity. Real D1 bindings always provide `batch()`.
|
|
23
|
+
*/
|
|
24
|
+
batch?(statements: D1BoundStatement[]): Promise<unknown[]>;
|
|
17
25
|
}
|
|
18
26
|
interface D1BoundStatement {
|
|
19
27
|
all<T>(): Promise<{
|
|
@@ -47,6 +55,7 @@ export declare class D1Adapter implements DbAdapter {
|
|
|
47
55
|
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
48
56
|
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
49
57
|
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
58
|
+
batch(operations: readonly DbBatchOp[]): Promise<void>;
|
|
50
59
|
close(): void;
|
|
51
60
|
}
|
|
52
61
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/adapters/d1.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAW,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/adapters/d1.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAW,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAClB,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC;QAC7C,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/B,GAAG,CAAC,IAAI,OAAO,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;KAC7D,CAAC;IACF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,CAAC,UAAU,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CAC9D;AAED,UAAU,gBAAgB;IACtB,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtD,GAAG,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACzD,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACvB,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,SAAS;IACvC,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,SAAS,CAAmC;gBAExC,OAAO,EAAE,SAAS;IAK9B,kEAAkE;IAClE,IAAI,EAAE,IAAI,UAAU,CAEnB;IAED,wEAAwE;IACxE,YAAY,IAAI,iBAAiB,CAAC,OAAO,MAAM,CAAC;IAIhD,qEAAqE;IACrE,UAAU,IAAI,SAAS;IAIjB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAQxE,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAO5D,KAAK,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB5D,KAAK,IAAI,IAAI;CAGhB"}
|
package/dist/adapters/d1.js
CHANGED
|
@@ -44,6 +44,28 @@ export class D1Adapter {
|
|
|
44
44
|
const result = await bound.all();
|
|
45
45
|
return result.results ?? [];
|
|
46
46
|
}
|
|
47
|
+
async batch(operations) {
|
|
48
|
+
if (operations.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
// A single statement is atomic on its own, so a binding without native
|
|
51
|
+
// batch() may run it directly. Multiple statements without native batch()
|
|
52
|
+
// cannot be made atomic — fail loud rather than silently break the
|
|
53
|
+
// all-or-nothing contract (real D1 bindings always provide batch()).
|
|
54
|
+
if (this.binding.batch === undefined && operations.length > 1) {
|
|
55
|
+
throw new Error(`D1 binding has no batch() — cannot execute ${operations.length} statements atomically; ` +
|
|
56
|
+
'provide a binding with native batch() support');
|
|
57
|
+
}
|
|
58
|
+
const boundStmts = operations.map((op) => {
|
|
59
|
+
const stmt = this.binding.prepare(op.sql);
|
|
60
|
+
return op.params.length > 0 ? stmt.bind(...op.params) : stmt.bind();
|
|
61
|
+
});
|
|
62
|
+
if (this.binding.batch !== undefined) {
|
|
63
|
+
await this.binding.batch(boundStmts);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
await boundStmts[0]?.run();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
47
69
|
close() {
|
|
48
70
|
// D1 bindings are managed by the Workers runtime -- no-op
|
|
49
71
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type InternalDb } from './adapter';
|
|
1
|
+
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type DbBatchOp, type InternalDb } from './adapter';
|
|
2
2
|
export { D1Adapter } from './adapters/d1';
|
|
3
3
|
export { BaseDao, type TxHandle } from './base-dao';
|
|
4
4
|
export { type EmbeddedMigration, embeddedMigrations } from './embedded-migrations';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACnH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,kBAAkB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EACH,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACpF,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-db",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "@gobing-ai/ts-db — a drizzle-free database facade: typed DAOs over Bun SQLite / Cloudflare D1, a small predicate query spec, single-source-of-truth tables, and migrations. Drizzle stays an internal detail.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-db-v<version> && git push --tags' && exit 1"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
70
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
69
|
+
"@gobing-ai/ts-runtime": "^0.4.8",
|
|
70
|
+
"@gobing-ai/ts-utils": "^0.4.8"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"drizzle-orm": ">=0.38.0",
|
package/src/adapter.ts
CHANGED
|
@@ -43,10 +43,30 @@ export interface DbAdapter {
|
|
|
43
43
|
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
44
44
|
/** Parameterized read returning all rows. */
|
|
45
45
|
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Execute a batch of parameterized writes atomically — either all commit or
|
|
48
|
+
* none do. Used by adapters that need to persist multiple related rows in a
|
|
49
|
+
* single transaction (e.g. workflow transition + state snapshot + phase write).
|
|
50
|
+
* Each entry is a `{ sql, params }` tuple applied in order. Adapters must
|
|
51
|
+
* uphold all-or-nothing semantics for multi-statement batches or **throw**;
|
|
52
|
+
* silently degrading to non-atomic sequential writes is not allowed. A
|
|
53
|
+
* single-statement batch may execute directly (trivially atomic).
|
|
54
|
+
*/
|
|
55
|
+
batch(operations: readonly DbBatchOp[]): Promise<void>;
|
|
46
56
|
/** Close the underlying connection. */
|
|
47
57
|
close(): void;
|
|
48
58
|
}
|
|
49
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A single write operation inside a {@link DbAdapter.batch} call.
|
|
62
|
+
*/
|
|
63
|
+
export interface DbBatchOp {
|
|
64
|
+
/** SQL statement (INSERT/UPDATE/DELETE). */
|
|
65
|
+
sql: string;
|
|
66
|
+
/** Bind parameters for the statement. */
|
|
67
|
+
params: readonly unknown[];
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
/**
|
|
51
71
|
* Discriminated-union config for creating a database adapter (bun-sqlite or D1).
|
|
52
72
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isAbsolutePath, resolvePath } from '@gobing-ai/ts-runtime';
|
|
2
2
|
import { Database } from '@gobing-ai/ts-runtime/bun-sqlite';
|
|
3
3
|
import { type BunSQLiteDatabase, drizzle } from 'drizzle-orm/bun-sqlite';
|
|
4
|
-
import type { DbAdapter, InternalDb } from '../adapter';
|
|
4
|
+
import type { DbAdapter, DbBatchOp, InternalDb } from '../adapter';
|
|
5
5
|
import * as schema from '../schema/runtime';
|
|
6
6
|
|
|
7
7
|
type SqliteStatementLike = {
|
|
@@ -103,6 +103,16 @@ export class BunSqliteAdapter implements DbAdapter {
|
|
|
103
103
|
return ((stmt as unknown as { all: (...p: unknown[]) => T[] }).all(...params) as T[]) ?? [];
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
async batch(operations: readonly DbBatchOp[]): Promise<void> {
|
|
107
|
+
if (operations.length === 0) return;
|
|
108
|
+
this.sqlite.transaction(() => {
|
|
109
|
+
for (const op of operations) {
|
|
110
|
+
const stmt = this.getStatement(op.sql);
|
|
111
|
+
stmt.run(...op.params);
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
}
|
|
115
|
+
|
|
106
116
|
close(): void {
|
|
107
117
|
this.sqlite.close();
|
|
108
118
|
}
|
package/src/adapters/d1.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DrizzleD1Database, drizzle } from 'drizzle-orm/d1';
|
|
2
|
-
import type { DbAdapter, InternalDb } from '../adapter';
|
|
2
|
+
import type { DbAdapter, DbBatchOp, InternalDb } from '../adapter';
|
|
3
3
|
import * as schema from '../schema/runtime';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -12,6 +12,14 @@ export interface D1Binding {
|
|
|
12
12
|
run?(): Promise<{ results: unknown[]; success: boolean }>;
|
|
13
13
|
};
|
|
14
14
|
exec(sql: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Run multiple statements as a single atomic batch (D1's native `batch()`
|
|
17
|
+
* API). Optional for single-statement test doubles only: when absent,
|
|
18
|
+
* {@link D1Adapter.batch} runs a lone statement directly (trivially atomic)
|
|
19
|
+
* and **throws** for multi-statement batches rather than silently degrading
|
|
20
|
+
* atomicity. Real D1 bindings always provide `batch()`.
|
|
21
|
+
*/
|
|
22
|
+
batch?(statements: D1BoundStatement[]): Promise<unknown[]>;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
interface D1BoundStatement {
|
|
@@ -76,6 +84,29 @@ export class D1Adapter implements DbAdapter {
|
|
|
76
84
|
return result.results ?? [];
|
|
77
85
|
}
|
|
78
86
|
|
|
87
|
+
async batch(operations: readonly DbBatchOp[]): Promise<void> {
|
|
88
|
+
if (operations.length === 0) return;
|
|
89
|
+
// A single statement is atomic on its own, so a binding without native
|
|
90
|
+
// batch() may run it directly. Multiple statements without native batch()
|
|
91
|
+
// cannot be made atomic — fail loud rather than silently break the
|
|
92
|
+
// all-or-nothing contract (real D1 bindings always provide batch()).
|
|
93
|
+
if (this.binding.batch === undefined && operations.length > 1) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`D1 binding has no batch() — cannot execute ${operations.length} statements atomically; ` +
|
|
96
|
+
'provide a binding with native batch() support',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const boundStmts: D1BoundStatement[] = operations.map((op) => {
|
|
100
|
+
const stmt = this.binding.prepare(op.sql);
|
|
101
|
+
return op.params.length > 0 ? stmt.bind(...op.params) : stmt.bind();
|
|
102
|
+
});
|
|
103
|
+
if (this.binding.batch !== undefined) {
|
|
104
|
+
await this.binding.batch(boundStmts);
|
|
105
|
+
} else {
|
|
106
|
+
await boundStmts[0]?.run();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
close(): void {
|
|
80
111
|
// D1 bindings are managed by the Workers runtime -- no-op
|
|
81
112
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type InternalDb } from './adapter';
|
|
1
|
+
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type DbBatchOp, type InternalDb } from './adapter';
|
|
2
2
|
export { D1Adapter } from './adapters/d1';
|
|
3
3
|
export { BaseDao, type TxHandle } from './base-dao';
|
|
4
4
|
|