@gobing-ai/ts-db 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/README.md +326 -0
- package/dist/adapter.d.ts +86 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +18 -0
- package/dist/adapters/bun-sqlite.d.ts +40 -0
- package/dist/adapters/bun-sqlite.d.ts.map +1 -0
- package/dist/adapters/bun-sqlite.js +70 -0
- package/dist/adapters/d1.d.ts +48 -0
- package/dist/adapters/d1.d.ts.map +1 -0
- package/dist/adapters/d1.js +45 -0
- package/dist/base-dao.d.ts +27 -0
- package/dist/base-dao.d.ts.map +1 -0
- package/dist/base-dao.js +34 -0
- package/dist/embedded-migrations.d.ts +15 -0
- package/dist/embedded-migrations.d.ts.map +1 -0
- package/dist/embedded-migrations.js +25 -0
- package/dist/entity-dao.d.ts +143 -0
- package/dist/entity-dao.d.ts.map +1 -0
- package/dist/entity-dao.js +218 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +9 -0
- package/dist/migrate.d.ts +38 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +131 -0
- package/dist/queue-job-dao.d.ts +95 -0
- package/dist/queue-job-dao.d.ts.map +1 -0
- package/dist/queue-job-dao.js +211 -0
- package/dist/schema/common.d.ts +87 -0
- package/dist/schema/common.d.ts.map +1 -0
- package/dist/schema/common.js +76 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/queue-jobs.d.ts +225 -0
- package/dist/schema/queue-jobs.d.ts.map +1 -0
- package/dist/schema/queue-jobs.js +18 -0
- package/dist/span-context.d.ts +2 -0
- package/dist/span-context.d.ts.map +1 -0
- package/dist/span-context.js +0 -0
- package/package.json +47 -0
- package/src/adapter.ts +109 -0
- package/src/adapters/bun-sqlite.ts +108 -0
- package/src/adapters/d1.ts +76 -0
- package/src/base-dao.ts +37 -0
- package/src/embedded-migrations.ts +32 -0
- package/src/entity-dao.ts +290 -0
- package/src/index.ts +19 -0
- package/src/migrate.ts +163 -0
- package/src/queue-job-dao.ts +317 -0
- package/src/schema/common.ts +94 -0
- package/src/schema/index.ts +2 -0
- package/src/schema/queue-jobs.ts +23 -0
- package/src/span-context.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# @gobing-ai/ts-db
|
|
2
|
+
|
|
3
|
+
Database abstraction layer — adapter pattern with Drizzle ORM, generic CRUD DAOs, job queue persistence, and migration tooling. Supports Bun SQLite (in-memory and file-based) and Cloudflare D1.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`ts-db` provides a typed database layer decoupled from any specific storage engine. The `DbAdapter` interface abstracts Bun SQLite and Cloudflare D1 behind a common API, while `BaseDao`, `EntityDao`, and `QueueJobDao` provide progressively richer data access patterns.
|
|
8
|
+
|
|
9
|
+
| Component | Purpose |
|
|
10
|
+
|-----------|---------|
|
|
11
|
+
| `DbAdapter` | Unified interface across Bun SQLite and D1 |
|
|
12
|
+
| `BunSqliteAdapter` | Bun SQLite implementation with statement caching and WAL pragmas |
|
|
13
|
+
| `D1Adapter` | Cloudflare D1 implementation (no `@cloudflare/workers-types` dependency) |
|
|
14
|
+
| `BaseDao` | Transaction + timestamp utilities for all DAOs |
|
|
15
|
+
| `EntityDao` | Generic CRUD with soft delete, pagination, and `count()` |
|
|
16
|
+
| `QueueJobDao` | Job queue persistence — `enqueue`, `claimReady`, `markCompleted`, `failExpiredJobs` |
|
|
17
|
+
| `applyMigrations` | Drizzle migration runner (file-based + embedded fallback) |
|
|
18
|
+
| `schema` | Reusable Drizzle column helpers + `queue_jobs` table definition |
|
|
19
|
+
| `SpanContext` | Re-exported from `@gobing-ai/ts-runtime` for telemetry |
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
classDiagram
|
|
25
|
+
class DbAdapter {
|
|
26
|
+
<<interface>>
|
|
27
|
+
+getDb() DbClient
|
|
28
|
+
+exec(sql) void
|
|
29
|
+
+run(sql, ...params) void
|
|
30
|
+
+queryFirst(sql, ...params) T?
|
|
31
|
+
+queryAll(sql, ...params) T[]
|
|
32
|
+
+close() void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class BunSqliteAdapter {
|
|
36
|
+
-Database sqlite
|
|
37
|
+
-drizzleDb
|
|
38
|
+
-stmtCache
|
|
39
|
+
+getDrizzleDb()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class D1Adapter {
|
|
43
|
+
-binding
|
|
44
|
+
-drizzleDb
|
|
45
|
+
+getBinding()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class BaseDao {
|
|
49
|
+
<<abstract>>
|
|
50
|
+
#db
|
|
51
|
+
+now() number
|
|
52
|
+
+withTransaction(fn) T
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class EntityDao {
|
|
56
|
+
+create(data) TSelect
|
|
57
|
+
+findById(id) TSelect?
|
|
58
|
+
+findAll() TSelect[]
|
|
59
|
+
+update(id, data) TSelect?
|
|
60
|
+
+delete(id, soft?) TSelect?
|
|
61
|
+
+findBy(column, value) TSelect?
|
|
62
|
+
+findAllBy(column, value) TSelect[]
|
|
63
|
+
+list(opts) TSelect[]
|
|
64
|
+
+count(where?) number
|
|
65
|
+
#hasSoftDelete boolean
|
|
66
|
+
#activeCondition SQL?
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class QueueJobDao {
|
|
70
|
+
+enqueue(type, payload, opts?) string
|
|
71
|
+
+enqueueBatch(jobs) string[]
|
|
72
|
+
+claimReady(batchSize) QueueJobRecord[]
|
|
73
|
+
+markProcessing(ids) void
|
|
74
|
+
+markCompleted(id) void
|
|
75
|
+
+markFailed(id, attempts, error) void
|
|
76
|
+
+markForRetry(id, attempts, error, nextRetryAt) void
|
|
77
|
+
+resetStuckJobs(timeout) number
|
|
78
|
+
+failExpiredJobs() number
|
|
79
|
+
+getStats() QueueStats
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class ColumnHelpers {
|
|
83
|
+
+standardColumns
|
|
84
|
+
+standardColumnsWithSoftDelete
|
|
85
|
+
+appendOnlyColumns
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class QueueJobsTable {
|
|
89
|
+
+queueJobs
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class MigrationRunner {
|
|
93
|
+
+applyMigrations(adapter, opts?) void
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class EmbeddedMigrations {
|
|
97
|
+
+embeddedMigrations EmbeddedMigration[]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
DbAdapter <|.. BunSqliteAdapter : implements
|
|
101
|
+
DbAdapter <|.. D1Adapter : implements
|
|
102
|
+
BaseDao <|-- EntityDao : extends
|
|
103
|
+
EntityDao <|-- QueueJobDao : extends
|
|
104
|
+
QueueJobDao --> QueueJobsTable : "uses"
|
|
105
|
+
MigrationRunner --> EmbeddedMigrations : "uses"
|
|
106
|
+
MigrationRunner --> BunSqliteAdapter : "requires"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## How It Works
|
|
110
|
+
|
|
111
|
+
### Adapter pattern
|
|
112
|
+
|
|
113
|
+
`createDbAdapter()` selects the correct implementation based on driver config:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { createDbAdapter } from '@gobing-ai/ts-db';
|
|
117
|
+
|
|
118
|
+
// Bun SQLite (in-memory)
|
|
119
|
+
const adapter = await createDbAdapter({ driver: 'bun-sqlite', url: ':memory:' });
|
|
120
|
+
|
|
121
|
+
// Bun SQLite (file-based with pragmas)
|
|
122
|
+
const adapter = await createDbAdapter({
|
|
123
|
+
driver: 'bun-sqlite',
|
|
124
|
+
url: './data/app.db',
|
|
125
|
+
pragmas: { journalMode: 'PRAGMA journal_mode = WAL' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Cloudflare D1
|
|
129
|
+
const adapter = await createDbAdapter({ driver: 'd1', binding: env.DB });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
All adapters implement the same `DbAdapter` interface:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
await adapter.exec('CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)');
|
|
136
|
+
await adapter.run('INSERT INTO users VALUES (?, ?)', 'u1', 'Alice');
|
|
137
|
+
const user = await adapter.queryFirst<{ name: string }>('SELECT name FROM users WHERE id = ?', 'u1');
|
|
138
|
+
const all = await adapter.queryAll<{ name: string }>('SELECT name FROM users');
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### EntityDao — CRUD with soft delete
|
|
142
|
+
|
|
143
|
+
Define a Drizzle table, extend `EntityDao`, get full CRUD for free:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
147
|
+
import { EntityDao, standardColumns } from '@gobing-ai/ts-db';
|
|
148
|
+
|
|
149
|
+
const users = sqliteTable('users', {
|
|
150
|
+
id: text('id').primaryKey(),
|
|
151
|
+
name: text('name').notNull(),
|
|
152
|
+
email: text('email').notNull(),
|
|
153
|
+
...standardColumns,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
class UsersDao extends EntityDao<typeof users, typeof users.id> {
|
|
157
|
+
constructor(db: DbClient) {
|
|
158
|
+
super(db, users, users.id, 'users');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async findByEmail(email: string) {
|
|
162
|
+
return this.findBy(users.email, email);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Usage
|
|
167
|
+
const dao = new UsersDao(adapter.getDb());
|
|
168
|
+
const user = await dao.create({ id: 'u1', name: 'Alice', email: 'a@test.com' });
|
|
169
|
+
const found = await dao.findById('u1');
|
|
170
|
+
const updated = await dao.update('u1', { name: 'Alice Updated' });
|
|
171
|
+
const page = await dao.list({ limit: 20, offset: 0 });
|
|
172
|
+
const total = await dao.count();
|
|
173
|
+
await dao.delete('u1'); // soft delete if table has `inUsed` column
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Soft delete** is automatic for tables with an `inUsed` column (from `standardColumnsWithSoftDelete`). Call `findById(id, true)` to include soft-deleted records.
|
|
177
|
+
|
|
178
|
+
### QueueJobDao — job queue persistence
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { QueueJobDao } from '@gobing-ai/ts-db';
|
|
182
|
+
|
|
183
|
+
const queue = new QueueJobDao(adapter.getDb());
|
|
184
|
+
|
|
185
|
+
// Enqueue
|
|
186
|
+
const jobId = await queue.enqueue('send-email', { to: 'user@test.com' }, { maxRetries: 5 });
|
|
187
|
+
|
|
188
|
+
// Consumer: claim ready jobs atomically
|
|
189
|
+
const jobs = await queue.claimReady(10);
|
|
190
|
+
|
|
191
|
+
for (const job of jobs) {
|
|
192
|
+
try {
|
|
193
|
+
await processJob(job);
|
|
194
|
+
await queue.markCompleted(job.id);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (job.attempts >= job.maxRetries) {
|
|
197
|
+
await queue.markFailed(job.id, job.attempts + 1, String(error));
|
|
198
|
+
} else {
|
|
199
|
+
const retryAt = Date.now() + Math.pow(2, job.attempts) * 1000;
|
|
200
|
+
await queue.markForRetry(job.id, job.attempts + 1, String(error), retryAt);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Maintenance
|
|
206
|
+
await queue.resetStuckJobs(30_000); // reset stuck after 30s
|
|
207
|
+
await queue.failExpiredJobs(); // fail expired TTL jobs
|
|
208
|
+
|
|
209
|
+
const stats = await queue.getStats();
|
|
210
|
+
// → { pending: 5, processing: 2, completed: 100, failed: 3 }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Migrations
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { BunSqliteAdapter, applyMigrations } from '@gobing-ai/ts-db';
|
|
217
|
+
|
|
218
|
+
const adapter = new BunSqliteAdapter({ databaseUrl: './data/app.db' });
|
|
219
|
+
|
|
220
|
+
// Applies pending migrations from drizzle/ folder (file-based)
|
|
221
|
+
// Falls back to embedded SQL if no folder exists (compiled binaries, CF Workers)
|
|
222
|
+
await applyMigrations(adapter);
|
|
223
|
+
|
|
224
|
+
// Safe to call on every startup — already-applied migrations are skipped
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Schema helpers
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
231
|
+
import { standardColumns, standardColumnsWithSoftDelete, queueJobs } from '@gobing-ai/ts-db';
|
|
232
|
+
|
|
233
|
+
// Standard columns (createdAt, updatedAt)
|
|
234
|
+
const docs = sqliteTable('docs', {
|
|
235
|
+
id: text('id').primaryKey(),
|
|
236
|
+
title: text('title').notNull(),
|
|
237
|
+
...standardColumns,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// With soft delete (adds inUsed column)
|
|
241
|
+
const projects = sqliteTable('projects', {
|
|
242
|
+
id: text('id').primaryKey(),
|
|
243
|
+
name: text('name').notNull(),
|
|
244
|
+
...standardColumnsWithSoftDelete,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// queue_jobs table is pre-built for use with QueueJobDao
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Usage
|
|
251
|
+
|
|
252
|
+
### Install
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
bun add @gobing-ai/ts-db drizzle-orm
|
|
256
|
+
bun add -D drizzle-kit
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Define your schema
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
// src/schema.ts
|
|
263
|
+
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
264
|
+
import { standardColumns } from '@gobing-ai/ts-db';
|
|
265
|
+
|
|
266
|
+
export const todos = sqliteTable('todos', {
|
|
267
|
+
id: text('id').primaryKey(),
|
|
268
|
+
title: text('title').notNull(),
|
|
269
|
+
done: text('done').notNull().default('0'),
|
|
270
|
+
...standardColumns,
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Create a DAO
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
// src/todos-dao.ts
|
|
278
|
+
import type { DbClient } from '@gobing-ai/ts-db';
|
|
279
|
+
import { EntityDao } from '@gobing-ai/ts-db';
|
|
280
|
+
import { todos } from './schema';
|
|
281
|
+
|
|
282
|
+
export class TodosDao extends EntityDao<typeof todos, typeof todos.id> {
|
|
283
|
+
constructor(db: DbClient) {
|
|
284
|
+
super(db, todos, todos.id, 'todos');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async findPending() {
|
|
288
|
+
return this.findAllBy(todos.done, '0');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async markDone(id: string) {
|
|
292
|
+
return this.update(id, { done: '1' });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Wire it up
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// src/index.ts
|
|
301
|
+
import { createDbAdapter, applyMigrations } from '@gobing-ai/ts-db';
|
|
302
|
+
import { TodosDao } from './todos-dao';
|
|
303
|
+
|
|
304
|
+
const adapter = await createDbAdapter({ driver: 'bun-sqlite', url: ':memory:' });
|
|
305
|
+
await applyMigrations(adapter);
|
|
306
|
+
|
|
307
|
+
const todos = new TodosDao(adapter.getDb());
|
|
308
|
+
|
|
309
|
+
await todos.create({ id: '1', title: 'Learn ts-db' });
|
|
310
|
+
await todos.create({ id: '2', title: 'Build something' });
|
|
311
|
+
|
|
312
|
+
const pending = await todos.findPending();
|
|
313
|
+
// → [{ id: '1', ... }, { id: '2', ... }]
|
|
314
|
+
|
|
315
|
+
await todos.markDone('1');
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Running with Bun
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# Generate migrations
|
|
322
|
+
bun drizzle-kit generate
|
|
323
|
+
|
|
324
|
+
# Apply at startup
|
|
325
|
+
bun run src/index.ts
|
|
326
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal D1 binding interface — avoids depending on @cloudflare/workers-types.
|
|
3
|
+
*/
|
|
4
|
+
interface D1Binding {
|
|
5
|
+
prepare(sql: string): unknown;
|
|
6
|
+
exec(sql: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generic database table descriptor carrying select and insert type info.
|
|
10
|
+
*/
|
|
11
|
+
export interface DbTable<TSelect, TInsert = TSelect> {
|
|
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
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Database adapter providing a unified client, raw SQL exec, and lifecycle management.
|
|
56
|
+
*/
|
|
57
|
+
export interface DbAdapter {
|
|
58
|
+
getDb(): DbClient;
|
|
59
|
+
exec(sql: string): Promise<void>;
|
|
60
|
+
/** Parameterized write (INSERT/UPDATE/DELETE) that returns no rows. */
|
|
61
|
+
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
62
|
+
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
63
|
+
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
64
|
+
close(): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Discriminated-union config for creating a database adapter (bun-sqlite or D1).
|
|
68
|
+
*/
|
|
69
|
+
export type DbAdapterConfig = {
|
|
70
|
+
driver: 'bun-sqlite';
|
|
71
|
+
url?: string;
|
|
72
|
+
pragmas?: {
|
|
73
|
+
journalMode?: string;
|
|
74
|
+
synchronous?: string;
|
|
75
|
+
foreignKeys?: string;
|
|
76
|
+
};
|
|
77
|
+
} | {
|
|
78
|
+
driver: 'd1';
|
|
79
|
+
binding: D1Binding;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Factory: creates the correct {@link DbAdapter} implementation based on driver config.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createDbAdapter(config: DbAdapterConfig): Promise<DbAdapter>;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory: creates the correct {@link DbAdapter} implementation based on driver config.
|
|
3
|
+
*/
|
|
4
|
+
export async function createDbAdapter(config) {
|
|
5
|
+
switch (config.driver) {
|
|
6
|
+
case 'bun-sqlite': {
|
|
7
|
+
const { BunSqliteAdapter } = await import('./adapters/bun-sqlite.js');
|
|
8
|
+
return new BunSqliteAdapter({
|
|
9
|
+
...(config.url ? { databaseUrl: config.url } : {}),
|
|
10
|
+
...(config.pragmas ? { pragmas: config.pragmas } : {}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
case 'd1': {
|
|
14
|
+
const { D1Adapter } = await import('./adapters/d1.js');
|
|
15
|
+
return new D1Adapter(config.binding);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
2
|
+
import type { DbAdapter, DbClient } from '../adapter';
|
|
3
|
+
import * as schema from '../schema/index';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the bun:sqlite adapter (path, pragmas).
|
|
6
|
+
*/
|
|
7
|
+
export interface BunSqliteOptions {
|
|
8
|
+
/** Database path or ":memory:". Default: ".spur/spur.db" */
|
|
9
|
+
databaseUrl?: string;
|
|
10
|
+
/** SQLite pragmas. All have sensible defaults. */
|
|
11
|
+
pragmas?: {
|
|
12
|
+
journalMode?: string;
|
|
13
|
+
synchronous?: string;
|
|
14
|
+
foreignKeys?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Bun SQLite database adapter backed by `bun:sqlite`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class BunSqliteAdapter implements DbAdapter {
|
|
21
|
+
private sqlite;
|
|
22
|
+
private drizzleDb;
|
|
23
|
+
/**
|
|
24
|
+
* Compiled-statement cache keyed by SQL text. `bun:sqlite` statements are
|
|
25
|
+
* reusable across calls with different params, so caching collapses the
|
|
26
|
+
* per-call `prepare()` recompile that dominated bulk write loops.
|
|
27
|
+
*/
|
|
28
|
+
private readonly stmtCache;
|
|
29
|
+
private getStatement;
|
|
30
|
+
constructor(options?: BunSqliteOptions);
|
|
31
|
+
getDb(): DbClient;
|
|
32
|
+
/** Returns the underlying drizzle instance for migration operations. */
|
|
33
|
+
getDrizzleDb(): BunSQLiteDatabase<typeof schema>;
|
|
34
|
+
exec(sql: string): Promise<void>;
|
|
35
|
+
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
36
|
+
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
37
|
+
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
38
|
+
close(): void;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=bun-sqlite.d.ts.map
|
|
@@ -0,0 +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,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,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,KAAK,IAAI,QAAQ;IAIjB,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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
4
|
+
import * as schema from '../schema/index.js';
|
|
5
|
+
const DEFAULT_PRAGMAS = {
|
|
6
|
+
journalMode: 'PRAGMA journal_mode = WAL',
|
|
7
|
+
synchronous: 'PRAGMA synchronous = NORMAL',
|
|
8
|
+
foreignKeys: 'PRAGMA foreign_keys = ON',
|
|
9
|
+
};
|
|
10
|
+
const DEFAULT_DB_PATH = '.spur/spur.db';
|
|
11
|
+
/**
|
|
12
|
+
* Bun SQLite database adapter backed by `bun:sqlite`.
|
|
13
|
+
*/
|
|
14
|
+
export class BunSqliteAdapter {
|
|
15
|
+
sqlite;
|
|
16
|
+
drizzleDb;
|
|
17
|
+
/**
|
|
18
|
+
* Compiled-statement cache keyed by SQL text. `bun:sqlite` statements are
|
|
19
|
+
* reusable across calls with different params, so caching collapses the
|
|
20
|
+
* per-call `prepare()` recompile that dominated bulk write loops.
|
|
21
|
+
*/
|
|
22
|
+
stmtCache = new Map();
|
|
23
|
+
getStatement(sql) {
|
|
24
|
+
const cached = this.stmtCache.get(sql);
|
|
25
|
+
if (cached !== undefined) {
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
const stmt = this.sqlite.prepare(sql);
|
|
29
|
+
this.stmtCache.set(sql, stmt);
|
|
30
|
+
return stmt;
|
|
31
|
+
}
|
|
32
|
+
constructor(options) {
|
|
33
|
+
let dbPath = options?.databaseUrl ?? DEFAULT_DB_PATH;
|
|
34
|
+
const pragmas = { ...DEFAULT_PRAGMAS, ...options?.pragmas };
|
|
35
|
+
// Resolve relative paths
|
|
36
|
+
if (dbPath !== ':memory:' && !isAbsolute(dbPath)) {
|
|
37
|
+
dbPath = resolve(dbPath);
|
|
38
|
+
}
|
|
39
|
+
this.sqlite = new Database(dbPath, { create: true });
|
|
40
|
+
this.sqlite.run(pragmas.journalMode);
|
|
41
|
+
this.sqlite.run(pragmas.synchronous);
|
|
42
|
+
this.sqlite.run(pragmas.foreignKeys);
|
|
43
|
+
this.drizzleDb = drizzle({ client: this.sqlite, schema });
|
|
44
|
+
}
|
|
45
|
+
getDb() {
|
|
46
|
+
return this.drizzleDb;
|
|
47
|
+
}
|
|
48
|
+
/** Returns the underlying drizzle instance for migration operations. */
|
|
49
|
+
getDrizzleDb() {
|
|
50
|
+
return this.drizzleDb;
|
|
51
|
+
}
|
|
52
|
+
async exec(sql) {
|
|
53
|
+
this.sqlite.prepare(sql).run();
|
|
54
|
+
}
|
|
55
|
+
async run(sql, ...params) {
|
|
56
|
+
const stmt = this.getStatement(sql);
|
|
57
|
+
stmt.run(...params);
|
|
58
|
+
}
|
|
59
|
+
async queryFirst(sql, ...params) {
|
|
60
|
+
const stmt = this.getStatement(sql);
|
|
61
|
+
return stmt.get(...params);
|
|
62
|
+
}
|
|
63
|
+
async queryAll(sql, ...params) {
|
|
64
|
+
const stmt = this.getStatement(sql);
|
|
65
|
+
return stmt.all(...params) ?? [];
|
|
66
|
+
}
|
|
67
|
+
close() {
|
|
68
|
+
this.sqlite.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DbAdapter, DbClient } from '../adapter';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal D1 binding interface — avoids depending on @cloudflare/workers-types.
|
|
4
|
+
*/
|
|
5
|
+
export interface D1Binding {
|
|
6
|
+
prepare(sql: string): {
|
|
7
|
+
bind(...params: unknown[]): D1BoundStatement;
|
|
8
|
+
first?<T>(): Promise<T | null>;
|
|
9
|
+
run?(): Promise<{
|
|
10
|
+
results: unknown[];
|
|
11
|
+
success: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
exec(sql: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
interface D1BoundStatement {
|
|
17
|
+
all<T>(): Promise<{
|
|
18
|
+
results: T[];
|
|
19
|
+
success: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
run(): Promise<{
|
|
22
|
+
results: unknown[];
|
|
23
|
+
success: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
raw<T>(): Promise<T[]>;
|
|
26
|
+
first?<T>(): Promise<T | null>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Cloudflare D1 database adapter.
|
|
30
|
+
*
|
|
31
|
+
* Accepts a D1 binding object matching the Cloudflare Workers D1Database
|
|
32
|
+
* interface shape. No ambient @cloudflare/workers-types dependency required.
|
|
33
|
+
*/
|
|
34
|
+
export declare class D1Adapter implements DbAdapter {
|
|
35
|
+
private binding;
|
|
36
|
+
private drizzleDb;
|
|
37
|
+
constructor(binding: D1Binding);
|
|
38
|
+
getDb(): DbClient;
|
|
39
|
+
/** Returns the non-mutating binding for advanced direct D1 calls. */
|
|
40
|
+
getBinding(): D1Binding;
|
|
41
|
+
exec(sql: string): Promise<void>;
|
|
42
|
+
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
43
|
+
queryFirst<T>(sql: string, ...params: unknown[]): Promise<T | undefined>;
|
|
44
|
+
queryAll<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
45
|
+
close(): void;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=d1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/adapters/d1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtD;;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,KAAK,IAAI,QAAQ;IAIjB,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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
import * as schema from '../schema/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare D1 database adapter.
|
|
5
|
+
*
|
|
6
|
+
* Accepts a D1 binding object matching the Cloudflare Workers D1Database
|
|
7
|
+
* interface shape. No ambient @cloudflare/workers-types dependency required.
|
|
8
|
+
*/
|
|
9
|
+
export class D1Adapter {
|
|
10
|
+
binding;
|
|
11
|
+
drizzleDb;
|
|
12
|
+
constructor(binding) {
|
|
13
|
+
this.binding = binding;
|
|
14
|
+
this.drizzleDb = drizzle(this.binding, { schema });
|
|
15
|
+
}
|
|
16
|
+
getDb() {
|
|
17
|
+
return this.drizzleDb;
|
|
18
|
+
}
|
|
19
|
+
/** Returns the non-mutating binding for advanced direct D1 calls. */
|
|
20
|
+
getBinding() {
|
|
21
|
+
return this.binding;
|
|
22
|
+
}
|
|
23
|
+
async exec(sql) {
|
|
24
|
+
await this.binding.exec(sql);
|
|
25
|
+
}
|
|
26
|
+
async run(sql, ...params) {
|
|
27
|
+
const stmt = this.binding.prepare(sql);
|
|
28
|
+
const bound = params.length > 0 ? stmt.bind(...params) : stmt;
|
|
29
|
+
await bound.run();
|
|
30
|
+
}
|
|
31
|
+
async queryFirst(sql, ...params) {
|
|
32
|
+
const stmt = this.binding.prepare(sql);
|
|
33
|
+
const bound = params.length > 0 ? stmt.bind(...params) : stmt;
|
|
34
|
+
return ((await bound.first()) ?? undefined);
|
|
35
|
+
}
|
|
36
|
+
async queryAll(sql, ...params) {
|
|
37
|
+
const stmt = this.binding.prepare(sql);
|
|
38
|
+
const bound = stmt.bind(...params);
|
|
39
|
+
const result = await bound.all();
|
|
40
|
+
return result.results ?? [];
|
|
41
|
+
}
|
|
42
|
+
close() {
|
|
43
|
+
// D1 bindings are managed by the Workers runtime -- no-op
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DbClient } from './adapter';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base DAO providing transaction and timestamp utilities to all entity DAOs.
|
|
4
|
+
*/
|
|
5
|
+
export declare abstract class BaseDao {
|
|
6
|
+
protected readonly db: DbClient;
|
|
7
|
+
/**
|
|
8
|
+
* DB transaction utility for subclasses.
|
|
9
|
+
*
|
|
10
|
+
* Constructor is `protected` — instantiate through concrete DAO subclasses,
|
|
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);
|
|
15
|
+
protected now(): number;
|
|
16
|
+
/**
|
|
17
|
+
* Execute a function within a database transaction.
|
|
18
|
+
*
|
|
19
|
+
* Works uniformly on both D1 (async) and bun:sqlite (sync wrapped in promise).
|
|
20
|
+
* The callback receives a transaction-scoped DbClient.
|
|
21
|
+
*
|
|
22
|
+
* @param fn - Function to execute within the transaction.
|
|
23
|
+
* @returns The return value of `fn`.
|
|
24
|
+
*/
|
|
25
|
+
protected withTransaction<T>(fn: (tx: DbClient) => Promise<T>): Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=base-dao.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-dao.d.ts","sourceRoot":"","sources":["../src/base-dao.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C;;GAEG;AACH,8BAAsB,OAAO;IAQH,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ;IAPrD;;;;;;OAMG;IACH,SAAS,aAAgC,EAAE,EAAE,QAAQ;IAErD,SAAS,CAAC,GAAG,IAAI,MAAM;IAIvB;;;;;;;;OAQG;cACa,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQnF"}
|