@gobing-ai/ts-db 0.1.8 → 0.2.2
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 +44 -21
- package/dist/adapter.d.ts +26 -46
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.d.ts +3 -2
- package/dist/adapters/bun-sqlite.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.js +2 -1
- package/dist/adapters/d1.d.ts +7 -2
- package/dist/adapters/d1.d.ts.map +1 -1
- package/dist/adapters/d1.js +6 -1
- package/dist/base-dao.d.ts +36 -16
- package/dist/base-dao.d.ts.map +1 -1
- package/dist/base-dao.js +46 -20
- package/dist/define-table.d.ts +40 -0
- package/dist/define-table.d.ts.map +1 -0
- package/dist/define-table.js +36 -0
- package/dist/entity-dao.d.ts +99 -96
- package/dist/entity-dao.d.ts.map +1 -1
- package/dist/entity-dao.js +145 -170
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/query-spec.d.ts +57 -0
- package/dist/query-spec.d.ts.map +1 -0
- package/dist/query-spec.js +40 -0
- package/dist/queue-job-dao.d.ts +2 -2
- package/dist/queue-job-dao.d.ts.map +1 -1
- package/dist/queue-job-dao.js +3 -3
- package/package.json +17 -5
- package/src/adapter.ts +27 -54
- package/src/adapters/bun-sqlite.ts +4 -3
- package/src/adapters/d1.ts +9 -3
- package/src/base-dao.ts +62 -20
- package/src/define-table.ts +66 -0
- package/src/entity-dao.ts +258 -199
- package/src/index.ts +23 -3
- package/src/query-spec.ts +84 -0
- package/src/queue-job-dao.ts +4 -4
package/README.md
CHANGED
|
@@ -1,30 +1,52 @@
|
|
|
1
1
|
# @gobing-ai/ts-db
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A **drizzle-free database facade**: typed DAOs over Bun SQLite / Cloudflare D1, a small predicate query spec, single-source-of-truth tables, and migration tooling. Drizzle ORM powers it internally but never appears in your application code — so the storage layer is swappable without touching call sites.
|
|
4
|
+
|
|
5
|
+
> **v0.2.0 is a breaking redesign.** The public `DbClient` interface and `adapter.getDb()` are removed; DAOs now take a `DbAdapter`; `where`/`orderBy` use a ts-db predicate spec instead of drizzle expressions. See [Migrating from 0.1.x](#migrating-from-01x).
|
|
4
6
|
|
|
5
7
|
## Overview
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Application code imports only `@gobing-ai/ts-db` — never `drizzle-orm`. drizzle is an internal implementation detail, which keeps the storage engine swappable and the query surface small and auditable. Two tiers, your choice:
|
|
10
|
+
|
|
11
|
+
- **Structured tier** (`EntityDao`) — typed `create`/`createMany`/`upsert`/`findById`/`findBy`/`update`/`delete`/`list`/`listByCursor`/`count`, filtered by a small predicate spec (`{ col, op, value }`).
|
|
12
|
+
- **Raw tier** (`BaseDao`) — `query`/`one`/`tx` for table-agnostic access; ETL/analytics/reporting DAOs extend this directly.
|
|
13
|
+
- **String-SQL escape** (`adapter.exec`/`run`/`queryFirst`/`queryAll`) — for DDL and dynamic identifiers only.
|
|
8
14
|
|
|
9
15
|
| Component | Purpose |
|
|
10
16
|
|-----------|---------|
|
|
11
|
-
| `DbAdapter` |
|
|
17
|
+
| `createDbAdapter` / `DbAdapter` | Construction + lifecycle + string-SQL escape; exposes an internal typed db to the DAO layer only |
|
|
12
18
|
| `BunSqliteAdapter` | Bun SQLite implementation with statement caching and WAL pragmas |
|
|
13
19
|
| `D1Adapter` | Cloudflare D1 implementation (no `@cloudflare/workers-types` dependency) |
|
|
14
|
-
| `BaseDao` |
|
|
15
|
-
| `EntityDao` |
|
|
20
|
+
| `BaseDao` | Raw tier — `query`/`one`/`tx`, drizzle-free signatures |
|
|
21
|
+
| `EntityDao` | Structured CRUD — predicate filters, soft delete, RETURNING, batch, upsert, cursor pagination, composite PK |
|
|
22
|
+
| `defineTable` | Single source of truth — one table → drizzle table + derived zod insert/select schemas (optional peers) |
|
|
23
|
+
| `Predicate` / `ListSpec` / `OrderTerm` | The drizzle-free query vocabulary |
|
|
16
24
|
| `QueueJobDao` | Job queue persistence — `enqueue`, `claimReady`, `markCompleted`, `failExpiredJobs` |
|
|
17
25
|
| `applyMigrations` | Drizzle migration runner (file-based + embedded fallback) |
|
|
18
|
-
| `schema`
|
|
26
|
+
| `schema` helpers | `standardColumns`, `appendOnlyColumns`, soft-delete columns |
|
|
19
27
|
| `SpanContext` | Re-exported from `@gobing-ai/ts-runtime` for telemetry |
|
|
20
28
|
|
|
29
|
+
### Optional peers (validation)
|
|
30
|
+
|
|
31
|
+
`defineTable`'s `insertSchema`/`selectSchema` and DAO validation require the **optional** peers `zod` and `drizzle-zod`. Install them only if you use validation; the DAOs and queries work without them.
|
|
32
|
+
|
|
33
|
+
### Migrating from 0.1.x
|
|
34
|
+
|
|
35
|
+
- `adapter.getDb()` → `adapter.db` (internal typed db; rarely needed directly).
|
|
36
|
+
- `new SomeDao(adapter.getDb())` → `new SomeDao(adapter)` — DAOs now take the adapter, not a db handle.
|
|
37
|
+
- `EntityDao` PK arg accepts an array: `super(adapter, table, [table.id], 'name')` (enables composite PKs).
|
|
38
|
+
- `BaseDao.withTransaction` → `tx`.
|
|
39
|
+
- `list({ where: eq(col, v) })` → `list({ where: { col, op: 'eq', value: v } })`.
|
|
40
|
+
- `count(eq(col, v))` → `count({ col, op: 'eq', value: v })`.
|
|
41
|
+
- `create`/`update` use `RETURNING`, so returned rows include DB-defaulted columns.
|
|
42
|
+
|
|
21
43
|
## Architecture
|
|
22
44
|
|
|
23
45
|
```mermaid
|
|
24
46
|
classDiagram
|
|
25
47
|
class DbAdapter {
|
|
26
48
|
<<interface>>
|
|
27
|
-
+
|
|
49
|
+
+db InternalDb (internal)
|
|
28
50
|
+exec(sql) void
|
|
29
51
|
+run(sql, ...params) void
|
|
30
52
|
+queryFirst(sql, ...params) T?
|
|
@@ -49,21 +71,22 @@ classDiagram
|
|
|
49
71
|
<<abstract>>
|
|
50
72
|
#db
|
|
51
73
|
+now() number
|
|
52
|
-
+
|
|
74
|
+
+tx(fn) T
|
|
75
|
+
+query(table, spec) T[]
|
|
76
|
+
+one(table, where) T?
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
class EntityDao {
|
|
56
80
|
+create(data) TSelect
|
|
81
|
+
+createMany(rows) TSelect[]
|
|
82
|
+
+upsert(data, conflict) TSelect
|
|
57
83
|
+findById(id) TSelect?
|
|
58
|
-
+findAll() TSelect[]
|
|
59
84
|
+update(id, data) TSelect?
|
|
60
85
|
+delete(id, soft?) TSelect?
|
|
61
86
|
+findBy(column, value) TSelect?
|
|
62
|
-
+
|
|
63
|
-
+
|
|
87
|
+
+list(spec) TSelect[]
|
|
88
|
+
+listByCursor(spec) Page
|
|
64
89
|
+count(where?) number
|
|
65
|
-
#hasSoftDelete boolean
|
|
66
|
-
#activeCondition SQL?
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
class QueueJobDao {
|
|
@@ -154,8 +177,8 @@ const users = sqliteTable('users', {
|
|
|
154
177
|
});
|
|
155
178
|
|
|
156
179
|
class UsersDao extends EntityDao<typeof users, typeof users.id> {
|
|
157
|
-
constructor(
|
|
158
|
-
super(
|
|
180
|
+
constructor(adapter: DbAdapter) {
|
|
181
|
+
super(adapter, users, [users.id], 'users');
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
async findByEmail(email: string) {
|
|
@@ -164,7 +187,7 @@ class UsersDao extends EntityDao<typeof users, typeof users.id> {
|
|
|
164
187
|
}
|
|
165
188
|
|
|
166
189
|
// Usage
|
|
167
|
-
const dao = new UsersDao(adapter
|
|
190
|
+
const dao = new UsersDao(adapter);
|
|
168
191
|
const user = await dao.create({ id: 'u1', name: 'Alice', email: 'a@test.com' });
|
|
169
192
|
const found = await dao.findById('u1');
|
|
170
193
|
const updated = await dao.update('u1', { name: 'Alice Updated' });
|
|
@@ -180,7 +203,7 @@ await dao.delete('u1'); // soft delete if table has `inUsed` column
|
|
|
180
203
|
```ts
|
|
181
204
|
import { QueueJobDao } from '@gobing-ai/ts-db';
|
|
182
205
|
|
|
183
|
-
const queue = new QueueJobDao(adapter
|
|
206
|
+
const queue = new QueueJobDao(adapter);
|
|
184
207
|
|
|
185
208
|
// Enqueue
|
|
186
209
|
const jobId = await queue.enqueue('send-email', { to: 'user@test.com' }, { maxRetries: 5 });
|
|
@@ -275,13 +298,13 @@ export const todos = sqliteTable('todos', {
|
|
|
275
298
|
|
|
276
299
|
```ts
|
|
277
300
|
// src/todos-dao.ts
|
|
278
|
-
import type {
|
|
301
|
+
import type { DbAdapter } from '@gobing-ai/ts-db';
|
|
279
302
|
import { EntityDao } from '@gobing-ai/ts-db';
|
|
280
303
|
import { todos } from './schema';
|
|
281
304
|
|
|
282
305
|
export class TodosDao extends EntityDao<typeof todos, typeof todos.id> {
|
|
283
|
-
constructor(
|
|
284
|
-
super(
|
|
306
|
+
constructor(adapter: DbAdapter) {
|
|
307
|
+
super(adapter, todos, [todos.id], 'todos');
|
|
285
308
|
}
|
|
286
309
|
|
|
287
310
|
async findPending() {
|
|
@@ -304,7 +327,7 @@ import { TodosDao } from './todos-dao';
|
|
|
304
327
|
const adapter = await createDbAdapter({ driver: 'bun-sqlite', url: ':memory:' });
|
|
305
328
|
await applyMigrations(adapter);
|
|
306
329
|
|
|
307
|
-
const todos = new TodosDao(adapter
|
|
330
|
+
const todos = new TodosDao(adapter);
|
|
308
331
|
|
|
309
332
|
await todos.create({ id: '1', title: 'Learn ts-db' });
|
|
310
333
|
await todos.create({ id: '2', title: 'Build something' });
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
2
|
+
import type { DrizzleD1Database } from 'drizzle-orm/d1';
|
|
1
3
|
/**
|
|
2
4
|
* Minimal D1 binding interface — avoids depending on @cloudflare/workers-types.
|
|
3
5
|
*/
|
|
@@ -6,61 +8,39 @@ interface D1Binding {
|
|
|
6
8
|
exec(sql: string): Promise<void>;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* Internal typed drizzle database handle.
|
|
12
|
+
*
|
|
13
|
+
* This is the REAL drizzle database (bun:sqlite or D1 flavour), fully typed.
|
|
14
|
+
* It is `@internal` — ts-db's DAO base classes use it to build queries, but it
|
|
15
|
+
* is never part of the public API. Consumers depend only on the ts-db facade
|
|
16
|
+
* (DAOs, the predicate spec), never on drizzle types directly. (G1)
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
10
19
|
*/
|
|
11
|
-
export
|
|
12
|
-
readonly $inferSelect: TSelect;
|
|
13
|
-
readonly $inferInsert: TInsert;
|
|
14
|
-
}
|
|
15
|
-
type DbInsertBuilder<TTable extends DbTable<unknown, unknown>> = {
|
|
16
|
-
values(values: TTable['$inferInsert'] | TTable['$inferInsert'][]): PromiseLike<unknown>;
|
|
17
|
-
};
|
|
18
|
-
interface DbSelectWhereResult<TTable extends DbTable<unknown, unknown>> extends PromiseLike<TTable['$inferSelect'][]> {
|
|
19
|
-
limit(value: number): DbSelectWhereResult<TTable>;
|
|
20
|
-
offset(value: number): DbSelectWhereResult<TTable>;
|
|
21
|
-
orderBy(column: unknown): DbSelectWhereResult<TTable>;
|
|
22
|
-
}
|
|
23
|
-
type DbSelectFromResult<TTable extends DbTable<unknown, unknown>> = DbSelectWhereResult<TTable> & {
|
|
24
|
-
where(condition: unknown): DbSelectWhereResult<TTable>;
|
|
25
|
-
};
|
|
26
|
-
type DbSelectBuilder = {
|
|
27
|
-
from<TTable extends DbTable<unknown, unknown>>(table: TTable): DbSelectFromResult<TTable>;
|
|
28
|
-
};
|
|
29
|
-
type DbProjectionSelectBuilder<TProjection> = {
|
|
30
|
-
from(table: DbTable<unknown, unknown>): PromiseLike<TProjection[]> & {
|
|
31
|
-
where(condition: unknown): PromiseLike<TProjection[]>;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
interface DbUpdateResult {
|
|
35
|
-
changes: number;
|
|
36
|
-
}
|
|
37
|
-
interface DbUpdateBuilder<TTable extends DbTable<unknown, unknown>> {
|
|
38
|
-
set(values: Partial<TTable['$inferInsert']>): {
|
|
39
|
-
where(condition: unknown): PromiseLike<DbUpdateResult>;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Abstract database client with insert/select/update/delete query builders.
|
|
44
|
-
*/
|
|
45
|
-
export interface DbClient {
|
|
46
|
-
insert<TTable extends DbTable<unknown, unknown>>(table: TTable): DbInsertBuilder<TTable>;
|
|
47
|
-
select(): DbSelectBuilder;
|
|
48
|
-
select<TProjection>(projection: Record<string, unknown>): DbProjectionSelectBuilder<TProjection>;
|
|
49
|
-
update<TTable extends DbTable<unknown, unknown>>(table: TTable): DbUpdateBuilder<TTable>;
|
|
50
|
-
delete<TTable extends DbTable<unknown, unknown>>(table: TTable): {
|
|
51
|
-
where(condition: unknown): PromiseLike<DbUpdateResult>;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
20
|
+
export type InternalDb = BunSQLiteDatabase<Record<string, unknown>> | DrizzleD1Database<Record<string, unknown>>;
|
|
54
21
|
/**
|
|
55
|
-
* Database adapter
|
|
22
|
+
* Database adapter: construction, lifecycle, the internal typed drizzle db, and a
|
|
23
|
+
* raw string-SQL escape for DDL / dynamic identifiers.
|
|
24
|
+
*
|
|
25
|
+
* The internal drizzle db (`db`) is exposed only to ts-db's own DAO layer; the
|
|
26
|
+
* string-SQL methods are the sole raw escape, intended for DDL and dynamic
|
|
27
|
+
* identifiers and gated to DAO files by a consumer-side lint rule.
|
|
56
28
|
*/
|
|
57
29
|
export interface DbAdapter {
|
|
58
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The internal typed drizzle database. ts-db DAO layer only — not public API.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
readonly db: InternalDb;
|
|
35
|
+
/** Run a raw SQL statement with no parameters (DDL). */
|
|
59
36
|
exec(sql: string): Promise<void>;
|
|
60
37
|
/** Parameterized write (INSERT/UPDATE/DELETE) that returns no rows. */
|
|
61
38
|
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
39
|
+
/** Parameterized read returning the first row, or undefined. */
|
|
62
40
|
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
41
|
+
/** Parameterized read returning all rows. */
|
|
63
42
|
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
43
|
+
/** Close the underlying connection. */
|
|
64
44
|
close(): void;
|
|
65
45
|
}
|
|
66
46
|
/**
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { type BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
2
|
-
import type { DbAdapter,
|
|
2
|
+
import type { DbAdapter, InternalDb } from '../adapter';
|
|
3
3
|
import * as schema from '../schema/index';
|
|
4
4
|
/**
|
|
5
5
|
* Configuration options for the bun:sqlite adapter (path, pragmas).
|
|
@@ -28,7 +28,8 @@ export declare class BunSqliteAdapter implements DbAdapter {
|
|
|
28
28
|
private readonly stmtCache;
|
|
29
29
|
private getStatement;
|
|
30
30
|
constructor(options?: BunSqliteOptions);
|
|
31
|
-
|
|
31
|
+
/** The internal typed drizzle database (ts-db DAO layer + migrations only). */
|
|
32
|
+
get db(): InternalDb;
|
|
32
33
|
/** Returns the underlying drizzle instance for migration operations. */
|
|
33
34
|
getDrizzleDb(): BunSQLiteDatabase<typeof schema>;
|
|
34
35
|
exec(sql: string): Promise<void>;
|
|
@@ -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,
|
|
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;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAS1C;;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;IAKlE,KAAK,IAAI,IAAI;CAGhB"}
|
|
@@ -42,7 +42,8 @@ export class BunSqliteAdapter {
|
|
|
42
42
|
this.sqlite.run(pragmas.foreignKeys);
|
|
43
43
|
this.drizzleDb = drizzle({ client: this.sqlite, schema });
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
/** The internal typed drizzle database (ts-db DAO layer + migrations only). */
|
|
46
|
+
get db() {
|
|
46
47
|
return this.drizzleDb;
|
|
47
48
|
}
|
|
48
49
|
/** Returns the underlying drizzle instance for migration operations. */
|
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type DrizzleD1Database } from 'drizzle-orm/d1';
|
|
2
|
+
import type { DbAdapter, InternalDb } from '../adapter';
|
|
3
|
+
import * as schema from '../schema/index';
|
|
2
4
|
/**
|
|
3
5
|
* Minimal D1 binding interface — avoids depending on @cloudflare/workers-types.
|
|
4
6
|
*/
|
|
@@ -35,7 +37,10 @@ export declare class D1Adapter implements DbAdapter {
|
|
|
35
37
|
private binding;
|
|
36
38
|
private drizzleDb;
|
|
37
39
|
constructor(binding: D1Binding);
|
|
38
|
-
|
|
40
|
+
/** The internal typed drizzle database (ts-db DAO layer only). */
|
|
41
|
+
get db(): InternalDb;
|
|
42
|
+
/** Returns the underlying drizzle instance for migration operations. */
|
|
43
|
+
getDrizzleDb(): DrizzleD1Database<typeof schema>;
|
|
39
44
|
/** Returns the non-mutating binding for advanced direct D1 calls. */
|
|
40
45
|
getBinding(): D1Binding;
|
|
41
46
|
exec(sql: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/adapters/d1.ts"],"names":[],"mappings":"
|
|
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;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAE1C;;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;CACpC;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;IAOlE,KAAK,IAAI,IAAI;CAGhB"}
|
package/dist/adapters/d1.js
CHANGED
|
@@ -13,7 +13,12 @@ export class D1Adapter {
|
|
|
13
13
|
this.binding = binding;
|
|
14
14
|
this.drizzleDb = drizzle(this.binding, { schema });
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
/** The internal typed drizzle database (ts-db DAO layer only). */
|
|
17
|
+
get db() {
|
|
18
|
+
return this.drizzleDb;
|
|
19
|
+
}
|
|
20
|
+
/** Returns the underlying drizzle instance for migration operations. */
|
|
21
|
+
getDrizzleDb() {
|
|
17
22
|
return this.drizzleDb;
|
|
18
23
|
}
|
|
19
24
|
/** Returns the non-mutating binding for advanced direct D1 calls. */
|
package/dist/base-dao.d.ts
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import type { DbAdapter, InternalDb } from './adapter';
|
|
3
|
+
import { type ListSpec, type Predicate } from './query-spec';
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* A transaction-scoped handle passed to {@link BaseDao.tx} callbacks.
|
|
6
|
+
*
|
|
7
|
+
* Drizzle-free by design — exposes the same internal db shape DAOs use, so a
|
|
8
|
+
* transactional block can run the same structured/raw operations. (G3)
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export type TxHandle = InternalDb;
|
|
13
|
+
/**
|
|
14
|
+
* Abstract base DAO — the RAW tier of the ts-db facade.
|
|
15
|
+
*
|
|
16
|
+
* Owns the adapter and provides generic, table-agnostic data access:
|
|
17
|
+
* transactions and parameterized queries expressed through the ts-db predicate
|
|
18
|
+
* spec (never drizzle's `sql` tag). ETL / analytics / reporting DAOs extend this
|
|
19
|
+
* directly; {@link EntityDao} extends it to add typed CRUD over a single table.
|
|
20
|
+
*
|
|
21
|
+
* All signatures are ts-db's own vocabulary — drizzle never leaks to consumers. (G1)
|
|
4
22
|
*/
|
|
5
23
|
export declare abstract class BaseDao {
|
|
6
|
-
protected readonly
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* not BaseDao directly. Tests must declare an explicit public constructor
|
|
12
|
-
* that calls `super(db)` to expose the protected constructor publicly.
|
|
13
|
-
*/
|
|
14
|
-
protected constructor(db: DbClient);
|
|
24
|
+
protected readonly adapter: DbAdapter;
|
|
25
|
+
protected constructor(adapter: DbAdapter);
|
|
26
|
+
/** The internal typed drizzle db. Subclasses use it to build queries. @internal */
|
|
27
|
+
protected get db(): InternalDb;
|
|
28
|
+
/** Current timestamp in milliseconds (audit columns, etc.). */
|
|
15
29
|
protected now(): number;
|
|
16
30
|
/**
|
|
17
31
|
* Execute a function within a database transaction.
|
|
18
32
|
*
|
|
19
|
-
* Works uniformly on
|
|
20
|
-
* The callback receives a transaction-scoped
|
|
33
|
+
* Works uniformly on bun:sqlite (sync, wrapped in a promise) and D1 (async).
|
|
34
|
+
* The callback receives a transaction-scoped db handle.
|
|
35
|
+
*/
|
|
36
|
+
protected tx<T>(fn: (tx: TxHandle) => Promise<T>): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Run a SELECT against a table, filtered/ordered/paged by a {@link ListSpec}.
|
|
21
39
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
40
|
+
* The raw-tier read primitive: drizzle-free input, typed rows out. Subclasses
|
|
41
|
+
* pass their table; `EntityDao` builds on this for `list`.
|
|
24
42
|
*/
|
|
25
|
-
protected
|
|
43
|
+
protected query<T>(table: SQLiteTable, spec?: ListSpec): Promise<T[]>;
|
|
44
|
+
/** Run a SELECT and return the first matching row, or undefined. */
|
|
45
|
+
protected one<T>(table: SQLiteTable, where: Predicate): Promise<T | undefined>;
|
|
26
46
|
}
|
|
27
47
|
//# sourceMappingURL=base-dao.d.ts.map
|
package/dist/base-dao.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dao.d.ts","sourceRoot":"","sources":["../src/base-dao.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"base-dao.d.ts","sourceRoot":"","sources":["../src/base-dao.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAoC,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE/F;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC;AAElC;;;;;;;;;GASG;AACH,8BAAsB,OAAO;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS;IAA3D,SAAS,aAAgC,OAAO,EAAE,SAAS;IAE3D,mFAAmF;IACnF,SAAS,KAAK,EAAE,IAAI,UAAU,CAE7B;IAED,+DAA+D;IAC/D,SAAS,CAAC,GAAG,IAAI,MAAM;IAIvB;;;;;OAKG;cACa,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAMnE;;;;;OAKG;cACa,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,QAAa,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAkB/E,oEAAoE;cACpD,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CAIvF"}
|
package/dist/base-dao.js
CHANGED
|
@@ -1,34 +1,60 @@
|
|
|
1
|
+
import { compileOrderBy, compilePredicate } from './query-spec.js';
|
|
1
2
|
/**
|
|
2
|
-
* Abstract base DAO
|
|
3
|
+
* Abstract base DAO — the RAW tier of the ts-db facade.
|
|
4
|
+
*
|
|
5
|
+
* Owns the adapter and provides generic, table-agnostic data access:
|
|
6
|
+
* transactions and parameterized queries expressed through the ts-db predicate
|
|
7
|
+
* spec (never drizzle's `sql` tag). ETL / analytics / reporting DAOs extend this
|
|
8
|
+
* directly; {@link EntityDao} extends it to add typed CRUD over a single table.
|
|
9
|
+
*
|
|
10
|
+
* All signatures are ts-db's own vocabulary — drizzle never leaks to consumers. (G1)
|
|
3
11
|
*/
|
|
4
12
|
export class BaseDao {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
constructor(db) {
|
|
14
|
-
this.db = db;
|
|
13
|
+
adapter;
|
|
14
|
+
constructor(adapter) {
|
|
15
|
+
this.adapter = adapter;
|
|
16
|
+
}
|
|
17
|
+
/** The internal typed drizzle db. Subclasses use it to build queries. @internal */
|
|
18
|
+
get db() {
|
|
19
|
+
return this.adapter.db;
|
|
15
20
|
}
|
|
21
|
+
/** Current timestamp in milliseconds (audit columns, etc.). */
|
|
16
22
|
now() {
|
|
17
23
|
return Date.now();
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
26
|
* Execute a function within a database transaction.
|
|
21
27
|
*
|
|
22
|
-
* Works uniformly on
|
|
23
|
-
* The callback receives a transaction-scoped
|
|
28
|
+
* Works uniformly on bun:sqlite (sync, wrapped in a promise) and D1 (async).
|
|
29
|
+
* The callback receives a transaction-scoped db handle.
|
|
30
|
+
*/
|
|
31
|
+
async tx(fn) {
|
|
32
|
+
return this.db.transaction((tx) => fn(tx));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a SELECT against a table, filtered/ordered/paged by a {@link ListSpec}.
|
|
24
36
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
37
|
+
* The raw-tier read primitive: drizzle-free input, typed rows out. Subclasses
|
|
38
|
+
* pass their table; `EntityDao` builds on this for `list`.
|
|
27
39
|
*/
|
|
28
|
-
async
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
40
|
+
async query(table, spec = {}) {
|
|
41
|
+
const condition = spec.where ? compilePredicate(spec.where) : undefined;
|
|
42
|
+
const order = spec.orderBy ? compileOrderBy(spec.orderBy) : [];
|
|
43
|
+
// drizzle's fluent builder is internal here; the public input is the spec.
|
|
44
|
+
let q = this.db.select().from(table);
|
|
45
|
+
if (condition)
|
|
46
|
+
q = q.where(condition);
|
|
47
|
+
if (order.length > 0)
|
|
48
|
+
q = q.orderBy(...order);
|
|
49
|
+
if (spec.limit !== undefined)
|
|
50
|
+
q = q.limit(spec.limit);
|
|
51
|
+
if (spec.offset !== undefined)
|
|
52
|
+
q = q.offset(spec.offset);
|
|
53
|
+
return (await q) ?? [];
|
|
54
|
+
}
|
|
55
|
+
/** Run a SELECT and return the first matching row, or undefined. */
|
|
56
|
+
async one(table, where) {
|
|
57
|
+
const rows = await this.query(table, { where, limit: 1 });
|
|
58
|
+
return rows[0];
|
|
33
59
|
}
|
|
34
60
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type SQLiteColumnBuilderBase, sqliteTable } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import type { ZodType } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* A table definition bundled with its drizzle-zod validation schemas.
|
|
5
|
+
*
|
|
6
|
+
* The single source of truth (G2): one table authored once yields the drizzle
|
|
7
|
+
* table (for queries/migrations) plus insert/select zod schemas (for boundary
|
|
8
|
+
* validation), with no parallel re-authoring.
|
|
9
|
+
*
|
|
10
|
+
* The zod schemas are derived lazily — only materialised the first time they are
|
|
11
|
+
* read — so a consumer that only needs the table pays nothing extra.
|
|
12
|
+
*
|
|
13
|
+
* `defineTable`, `insertSchema`, and `selectSchema` require the optional peers
|
|
14
|
+
* `zod` and `drizzle-zod`. Consumers that never validate need not install them
|
|
15
|
+
* and simply use `createDbAdapter` + raw `sqliteTable` + the column helpers.
|
|
16
|
+
*/
|
|
17
|
+
export interface DefinedTable<TTable> {
|
|
18
|
+
/** The underlying drizzle table — pass to DAOs, migrations, queries. */
|
|
19
|
+
readonly table: TTable;
|
|
20
|
+
/** Zod schema validating a row for insertion. */
|
|
21
|
+
readonly insertSchema: ZodType;
|
|
22
|
+
/** Zod schema validating a selected row. */
|
|
23
|
+
readonly selectSchema: ZodType;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Define a SQLite table and derive its validation schemas in one place (G2).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* export const users = defineTable('users', {
|
|
31
|
+
* id: text('id').primaryKey(),
|
|
32
|
+
* email: text('email').notNull().unique(),
|
|
33
|
+
* ...standardColumns,
|
|
34
|
+
* });
|
|
35
|
+
* users.table // drizzle table for DAOs/migrations
|
|
36
|
+
* users.insertSchema // zod schema derived from the table
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function defineTable<TName extends string, TColumns extends Record<string, SQLiteColumnBuilderBase>>(name: TName, columns: TColumns): DefinedTable<ReturnType<typeof sqliteTable<TName, TColumns>>>;
|
|
40
|
+
//# sourceMappingURL=define-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-table.d.ts","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM;IAChC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,EACtG,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,QAAQ,GAClB,YAAY,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAqB/D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sqliteTable } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
|
3
|
+
/**
|
|
4
|
+
* Define a SQLite table and derive its validation schemas in one place (G2).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* export const users = defineTable('users', {
|
|
9
|
+
* id: text('id').primaryKey(),
|
|
10
|
+
* email: text('email').notNull().unique(),
|
|
11
|
+
* ...standardColumns,
|
|
12
|
+
* });
|
|
13
|
+
* users.table // drizzle table for DAOs/migrations
|
|
14
|
+
* users.insertSchema // zod schema derived from the table
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function defineTable(name, columns) {
|
|
18
|
+
const table = sqliteTable(name, columns);
|
|
19
|
+
let insert;
|
|
20
|
+
let select;
|
|
21
|
+
return {
|
|
22
|
+
table,
|
|
23
|
+
get insertSchema() {
|
|
24
|
+
if (insert === undefined) {
|
|
25
|
+
insert = createInsertSchema(table);
|
|
26
|
+
}
|
|
27
|
+
return insert;
|
|
28
|
+
},
|
|
29
|
+
get selectSchema() {
|
|
30
|
+
if (select === undefined) {
|
|
31
|
+
select = createSelectSchema(table);
|
|
32
|
+
}
|
|
33
|
+
return select;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|