@truto/ginger 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +597 -0
  3. package/dist/adapters/bun-sqlite.d.ts +37 -0
  4. package/dist/adapters/bun-sqlite.d.ts.map +1 -0
  5. package/dist/adapters/bun-sqlite.js +136 -0
  6. package/dist/adapters/bun-sqlite.js.map +1 -0
  7. package/dist/adapters/durable-object.d.ts +40 -0
  8. package/dist/adapters/durable-object.d.ts.map +1 -0
  9. package/dist/adapters/durable-object.js +142 -0
  10. package/dist/adapters/durable-object.js.map +1 -0
  11. package/dist/adapters/index.d.ts +5 -0
  12. package/dist/adapters/index.d.ts.map +1 -0
  13. package/dist/adapters/index.js +3 -0
  14. package/dist/adapters/index.js.map +1 -0
  15. package/dist/crypto.d.ts +40 -0
  16. package/dist/crypto.d.ts.map +1 -0
  17. package/dist/crypto.js +148 -0
  18. package/dist/crypto.js.map +1 -0
  19. package/dist/errors.d.ts +64 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +90 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/example.d.ts +119 -0
  24. package/dist/example.d.ts.map +1 -0
  25. package/dist/example.js +297 -0
  26. package/dist/example.js.map +1 -0
  27. package/dist/index.d.ts +54 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +62 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/pagination.d.ts +31 -0
  32. package/dist/pagination.d.ts.map +1 -0
  33. package/dist/pagination.js +173 -0
  34. package/dist/pagination.js.map +1 -0
  35. package/dist/service.d.ts +81 -0
  36. package/dist/service.d.ts.map +1 -0
  37. package/dist/service.js +615 -0
  38. package/dist/service.js.map +1 -0
  39. package/dist/sql-builder.d.ts +48 -0
  40. package/dist/sql-builder.d.ts.map +1 -0
  41. package/dist/sql-builder.js +230 -0
  42. package/dist/sql-builder.js.map +1 -0
  43. package/dist/types.d.ts +266 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +2 -0
  46. package/dist/types.js.map +1 -0
  47. package/package.json +94 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,597 @@
1
+ # Ginger
2
+
3
+ A type-safe SQLite data access layer that works with **Cloudflare D1**, **Bun SQLite**, and **Durable Object SqlStorage**. Comes with cursor-based pagination, declarative joins, AES-256-GCM field encryption, and a Feathers.js-inspired hook system.
4
+
5
+ Built with TypeScript and [Zod v4](https://zod.dev/) for complete type safety. All dynamic SQL is generated via [@truto/sqlite-builder](https://github.com/trutohq/truto-sqlite-builder) — no raw string concatenation, ever.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add ginger
11
+ ```
12
+
13
+ Peer dependencies:
14
+
15
+ ```bash
16
+ bun add zod @truto/sqlite-builder
17
+ ```
18
+
19
+ ## Features
20
+
21
+ - **Fully type-safe** — Zod schemas drive runtime validation and static types
22
+ - **Cursor pagination** — opaque base64 cursors with `next` / `prev` support
23
+ - **Declarative joins** — `one` and `many` joins with conditional `include`
24
+ - **Field encryption** — AES-256-GCM via Web Crypto, stored as `kid:iv:cipher`
25
+ - **Hook system** — `before` / `after` / `error` hooks per method, inspired by Feathers.js
26
+ - **Dependency injection** — pass other services via `deps` for cross-service logic
27
+ - **SQL injection protection** — every query is parameterised through `@truto/sqlite-builder`
28
+ - **Custom error hierarchy** — `NotFoundError`, `ValidationError`, `AuthError`, `EncryptionError`, etc.
29
+
30
+ ## Quick example
31
+
32
+ A complete `users` service with an encrypted `apiKey`, a join to `teams`, a custom `withMembership` method, and a hook that enforces tenant filtering via `auth.user`.
33
+
34
+ ```typescript
35
+ import {
36
+ createService,
37
+ Service,
38
+ z,
39
+ type AuthContext,
40
+ type Database,
41
+ type JoinDef,
42
+ type SecretFieldDef,
43
+ } from 'ginger'
44
+
45
+ // ── Schemas ──────────────────────────────────────────────────────────
46
+
47
+ const UserRow = z.object({
48
+ id: z.number(),
49
+ name: z.string(),
50
+ email: z.string(),
51
+ tenant_id: z.string(),
52
+ created_at: z.string(),
53
+ updated_at: z.string().nullable(),
54
+ })
55
+
56
+ const CreateUser = z.object({
57
+ name: z.string().min(1).max(255),
58
+ email: z.string().email(),
59
+ apiKey: z.string().min(32),
60
+ tenant_id: z.string(),
61
+ })
62
+
63
+ const UpdateUser = z.object({
64
+ name: z.string().min(1).max(255).optional(),
65
+ email: z.string().email().optional(),
66
+ })
67
+
68
+ const TeamRow = z.object({
69
+ id: z.number(),
70
+ name: z.string(),
71
+ description: z.string(),
72
+ })
73
+
74
+ // ── Joins ────────────────────────────────────────────────────────────
75
+
76
+ const userJoins = {
77
+ teams: {
78
+ kind: 'many',
79
+ localPk: 'id',
80
+ through: {
81
+ table: 'user_teams',
82
+ from: 'user_id',
83
+ to: 'team_id',
84
+ },
85
+ remote: {
86
+ table: 'teams',
87
+ pk: 'id',
88
+ select: ['id', 'name', 'description'],
89
+ },
90
+ schema: TeamRow,
91
+ },
92
+ } satisfies Record<string, JoinDef>
93
+
94
+ // ── Secrets ──────────────────────────────────────────────────────────
95
+
96
+ const userSecrets = [
97
+ {
98
+ logicalName: 'apiKey',
99
+ columnName: 'api_key_encrypted',
100
+ keyId: 'user-secrets',
101
+ },
102
+ ] as const satisfies readonly SecretFieldDef[]
103
+
104
+ // ── Service ──────────────────────────────────────────────────────────
105
+
106
+ class UsersService extends Service<
107
+ typeof UserRow,
108
+ typeof CreateUser,
109
+ typeof UpdateUser,
110
+ typeof userJoins,
111
+ typeof userSecrets
112
+ > {
113
+ /** Fetch a user together with their team memberships */
114
+ async withMembership(id: number, auth: AuthContext) {
115
+ return this.get(id, {
116
+ auth,
117
+ include: { teams: true },
118
+ })
119
+ }
120
+ }
121
+
122
+ // ── Factory ──────────────────────────────────────────────────────────
123
+
124
+ function createUsersService(
125
+ db: Database,
126
+ encryptionKeys: Record<string, string>,
127
+ ) {
128
+ return new UsersService({
129
+ table: 'users',
130
+ db: db as any,
131
+ rowSchema: UserRow,
132
+ createSchema: CreateUser,
133
+ updateSchema: UpdateUser,
134
+ joins: userJoins,
135
+ secrets: userSecrets,
136
+ encryptionKeys,
137
+ hooks: {
138
+ list: {
139
+ before: async (ctx: any) => {
140
+ if (!ctx.auth.user?.tenantId) throw new Error('Missing tenant')
141
+ ctx.params.where = {
142
+ ...ctx.params.where,
143
+ tenant_id: ctx.auth.user.tenantId,
144
+ }
145
+ },
146
+ },
147
+ create: {
148
+ before: async (ctx: any) => {
149
+ if (!ctx.auth.user?.tenantId) throw new Error('Missing tenant')
150
+ ctx.data.tenant_id = ctx.auth.user.tenantId
151
+ },
152
+ },
153
+ },
154
+ })
155
+ }
156
+
157
+ // ── Worker entry point ───────────────────────────────────────────────
158
+
159
+ export default {
160
+ async fetch(request: Request, env: any): Promise<Response> {
161
+ const usersService = createUsersService(env.DB, {
162
+ default: env.ENCRYPTION_KEY,
163
+ 'user-secrets': env.ENCRYPTION_KEY,
164
+ })
165
+
166
+ const auth = {
167
+ user: { id: 'usr_1', tenantId: 'tnt_1', roles: ['admin'] },
168
+ }
169
+
170
+ // Create (apiKey is encrypted transparently)
171
+ const user = await usersService.create(
172
+ {
173
+ name: 'Jane Doe',
174
+ email: 'jane@example.com',
175
+ apiKey: 'sk_ex_abcdef1234567890abcdef1234567890',
176
+ tenant_id: 'tnt_1',
177
+ },
178
+ { auth },
179
+ )
180
+
181
+ // List with pagination + joins
182
+ const page = await usersService.list({
183
+ auth,
184
+ limit: 20,
185
+ include: { teams: true },
186
+ orderBy: [{ column: 'created_at', direction: 'desc' }],
187
+ })
188
+
189
+ // Get with decrypted secrets
190
+ const full = await usersService.get(user.id, {
191
+ auth,
192
+ includeSecrets: true,
193
+ })
194
+
195
+ // Custom method
196
+ const withTeams = await usersService.withMembership(user.id, auth)
197
+
198
+ return Response.json({ user, page, full, withTeams })
199
+ },
200
+ }
201
+ ```
202
+
203
+ ## Core concepts
204
+
205
+ ### Database adapters
206
+
207
+ Ginger works with any SQLite database that satisfies the `Database` interface. Three adapters are provided out of the box:
208
+
209
+ **Cloudflare D1** — pass the binding directly, no adapter needed:
210
+
211
+ ```typescript
212
+ import { createService } from 'ginger'
213
+
214
+ const service = createService({
215
+ table: 'users',
216
+ db: env.DB, // D1 binding satisfies Database natively
217
+ // ...
218
+ })
219
+ ```
220
+
221
+ **Bun SQLite** — wrap with `fromBunSqlite`:
222
+
223
+ ```typescript
224
+ import { Database } from 'bun:sqlite'
225
+ import { createService, fromBunSqlite } from 'ginger'
226
+
227
+ const bunDb = new Database('myapp.sqlite')
228
+ const service = createService({
229
+ table: 'users',
230
+ db: fromBunSqlite(bunDb),
231
+ // ...
232
+ })
233
+ ```
234
+
235
+ **Durable Object SqlStorage** — wrap with `fromDurableObjectStorage`:
236
+
237
+ ```typescript
238
+ import { DurableObject } from 'cloudflare:workers'
239
+ import { createService, fromDurableObjectStorage } from 'ginger'
240
+
241
+ export class MyDO extends DurableObject {
242
+ service = createService({
243
+ table: 'users',
244
+ db: fromDurableObjectStorage(this.ctx.storage.sql),
245
+ // ...
246
+ })
247
+ }
248
+ ```
249
+
250
+ ### Service configuration
251
+
252
+ ```typescript
253
+ import { createService, z } from 'ginger'
254
+
255
+ const service = createService({
256
+ table: 'users',
257
+ db, // Database instance (D1, fromBunSqlite, or fromDurableObjectStorage)
258
+ rowSchema: UserRow, // canonical decoded row
259
+ createSchema: CreateUser, // POST body schema
260
+ updateSchema: UpdateUser, // PATCH body schema (partial)
261
+ joins: userJoins, // declarative join map
262
+ secrets: userSecrets, // secret field definitions
263
+ hooks: {
264
+ /* ... */
265
+ }, // before/after/error hooks
266
+ deps: { teams: teamsService }, // other services
267
+ primaryKey: 'id', // default "id"
268
+ defaultOrderBy: { column: 'created_at', direction: 'desc' },
269
+ keyProvider: customProvider, // or pass encryptionKeys: { ... }
270
+ })
271
+ ```
272
+
273
+ ### CRUD methods
274
+
275
+ Every service gets these methods out of the box:
276
+
277
+ | Method | Description |
278
+ | ----------------------------- | ---------------------------------------------------------------- |
279
+ | `list(params)` | Cursor-based paginated list with filtering, ordering, and joins |
280
+ | `get(id, opts)` | Single record by ID with optional `include` and `includeSecrets` |
281
+ | `create(data, opts)` | Validates with `createSchema`, returns decoded row |
282
+ | `update(id, data, opts)` | Partial update, merges with existing row |
283
+ | `delete(id, opts)` | Hard delete |
284
+ | `count(params)` | Count rows matching a typed `where` clause |
285
+ | `query(sql, opts, ...params)` | Low-level escape hatch returning decoded rows |
286
+
287
+ All methods accept an `auth` object:
288
+
289
+ ```typescript
290
+ interface AuthContext {
291
+ user?: {
292
+ id: string
293
+ roles: string[]
294
+ [k: string]: unknown
295
+ }
296
+ }
297
+ ```
298
+
299
+ ### Pagination
300
+
301
+ Opaque cursor tokens (base64-encoded JSON) with `next` / `prev` support:
302
+
303
+ ```typescript
304
+ const page1 = await service.list({
305
+ auth,
306
+ limit: 20,
307
+ orderBy: [{ column: 'created_at', direction: 'desc' }],
308
+ })
309
+
310
+ // page1.result — array of rows
311
+ // page1.nextCursor — pass to next call for the next page
312
+ // page1.prevCursor — pass to next call for the previous page
313
+
314
+ const page2 = await service.list({
315
+ auth,
316
+ cursor: page1.nextCursor,
317
+ limit: 20,
318
+ })
319
+ ```
320
+
321
+ ### Joins
322
+
323
+ Define type-safe joins with conditional inclusion. The return type of `get` / `list` changes based on which joins are included:
324
+
325
+ ```typescript
326
+ const joins = {
327
+ profile: {
328
+ kind: 'one' as const,
329
+ localPk: 'id',
330
+ remote: {
331
+ table: 'profiles',
332
+ pk: 'user_id',
333
+ select: ['bio', 'avatar'],
334
+ },
335
+ schema: ProfileSchema,
336
+ },
337
+ teams: {
338
+ kind: 'many' as const,
339
+ localPk: 'id',
340
+ through: {
341
+ table: 'user_teams',
342
+ from: 'user_id',
343
+ to: 'team_id',
344
+ },
345
+ remote: {
346
+ table: 'teams',
347
+ pk: 'id',
348
+ select: ['id', 'name'],
349
+ },
350
+ where: 'teams.active = 1',
351
+ schema: TeamSchema,
352
+ },
353
+ }
354
+
355
+ const user = await service.get(id, {
356
+ auth,
357
+ include: { profile: true, teams: true },
358
+ })
359
+ // user.profile → ProfileRow | null
360
+ // user.teams → TeamRow[]
361
+ ```
362
+
363
+ ### Field encryption
364
+
365
+ Sensitive fields are encrypted with **AES-256-GCM** via Web Crypto and stored as `kid:iv:cipher` (base64 segments):
366
+
367
+ ```typescript
368
+ const secrets = [
369
+ {
370
+ logicalName: 'apiKey', // field in your schema
371
+ columnName: 'api_key_enc', // column in the DB
372
+ keyId: 'api-keys', // key identifier
373
+ },
374
+ ] as const
375
+
376
+ // Provide keys directly
377
+ const service = createService({
378
+ // ...
379
+ secrets,
380
+ encryptionKeys: {
381
+ default: env.ENCRYPTION_KEY,
382
+ 'api-keys': env.API_KEY_ENCRYPTION_KEY,
383
+ },
384
+ })
385
+
386
+ // Or provide a custom KeyProvider
387
+ const service = createService({
388
+ // ...
389
+ secrets,
390
+ keyProvider: {
391
+ async getKey(keyId: string): Promise<CryptoKey> {
392
+ // your custom key retrieval logic
393
+ },
394
+ },
395
+ })
396
+ ```
397
+
398
+ Generate a key:
399
+
400
+ ```typescript
401
+ import { generateSecretKey } from 'ginger'
402
+
403
+ const key = await generateSecretKey()
404
+ // → base64-encoded 256-bit key
405
+ ```
406
+
407
+ Encryption is injected automatically via hooks:
408
+
409
+ - `before.create` / `before.update` — encrypt logical fields → ciphertext column
410
+ - `after.get` / `after.list` (when `includeSecrets: true`) — decrypt back
411
+
412
+ ### Hooks
413
+
414
+ Feathers.js-inspired hooks with `before` / `after` / `error` phases:
415
+
416
+ ```typescript
417
+ const service = createService({
418
+ // ...
419
+ hooks: {
420
+ list: {
421
+ before: [authHook, tenantFilterHook],
422
+ after: [auditLogHook],
423
+ error: [errorReportingHook],
424
+ },
425
+ create: {
426
+ before: async (ctx) => {
427
+ ctx.data.createdBy = ctx.auth.user?.id
428
+ },
429
+ after: async (ctx) => {
430
+ await sendWelcomeEmail(ctx.result.email)
431
+ },
432
+ },
433
+ },
434
+ })
435
+ ```
436
+
437
+ Hooks receive a context object:
438
+
439
+ ```typescript
440
+ interface BaseCtx {
441
+ auth: AuthContext
442
+ db: Database
443
+ deps: ServiceDeps
444
+ method: MethodName
445
+ params?: unknown
446
+ data?: unknown
447
+ result?: unknown
448
+ }
449
+ ```
450
+
451
+ Hooks run sequentially in registration order. If a `before` or `after` hook throws, control jumps to the `error` chain.
452
+
453
+ ### Custom methods
454
+
455
+ Extend `Service` to add arbitrary async methods that can leverage all built-in functionality:
456
+
457
+ ```typescript
458
+ class UsersService extends Service</* ... */> {
459
+ async findByEmail(email: string, auth: AuthContext) {
460
+ const results = await this.query(
461
+ 'SELECT * FROM users WHERE email = ?',
462
+ { auth },
463
+ email,
464
+ )
465
+ return results[0] ?? null
466
+ }
467
+
468
+ async deactivate(id: number, auth: AuthContext) {
469
+ return this.update(id, { active: false }, { auth })
470
+ }
471
+ }
472
+ ```
473
+
474
+ ### Dependency injection
475
+
476
+ Pass other services via `deps` — they're available on `this.deps` and in every hook context:
477
+
478
+ ```typescript
479
+ const teamsService = createService({
480
+ /* ... */
481
+ })
482
+
483
+ const usersService = createService({
484
+ // ...
485
+ deps: { teams: teamsService },
486
+ hooks: {
487
+ delete: {
488
+ after: async (ctx) => {
489
+ // Clean up team memberships when a user is deleted
490
+ await ctx.deps.teams.query(
491
+ 'DELETE FROM user_teams WHERE user_id = ?',
492
+ { auth: ctx.auth },
493
+ ctx.params.id,
494
+ )
495
+ },
496
+ },
497
+ },
498
+ })
499
+ ```
500
+
501
+ ### Error handling
502
+
503
+ All errors extend `ServiceError` with structured `code` and `statusCode`:
504
+
505
+ ```typescript
506
+ import {
507
+ ServiceError,
508
+ NotFoundError,
509
+ ValidationError,
510
+ AuthError,
511
+ DatabaseError,
512
+ EncryptionError,
513
+ HookError,
514
+ CursorError,
515
+ } from 'ginger'
516
+
517
+ try {
518
+ await service.get(id, { auth })
519
+ } catch (error) {
520
+ if (error instanceof NotFoundError) {
521
+ return new Response('Not found', { status: 404 })
522
+ }
523
+ if (error instanceof ValidationError) {
524
+ return new Response(error.message, { status: 400 })
525
+ }
526
+ }
527
+ ```
528
+
529
+ | Error class | Code | Status |
530
+ | ----------------- | ------------------ | ------ |
531
+ | `NotFoundError` | `NOT_FOUND` | 404 |
532
+ | `ValidationError` | `VALIDATION_ERROR` | 400 |
533
+ | `AuthError` | `AUTH_ERROR` | 403 |
534
+ | `DatabaseError` | `DATABASE_ERROR` | 500 |
535
+ | `EncryptionError` | `ENCRYPTION_ERROR` | 500 |
536
+ | `HookError` | `HOOK_ERROR` | 500 |
537
+ | `CursorError` | `CURSOR_ERROR` | 400 |
538
+
539
+ ## SQL schema example
540
+
541
+ ```sql
542
+ CREATE TABLE users (
543
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
544
+ name TEXT NOT NULL,
545
+ email TEXT NOT NULL UNIQUE,
546
+ api_key_encrypted TEXT,
547
+ tenant_id TEXT NOT NULL,
548
+ created_at TEXT NOT NULL,
549
+ updated_at TEXT
550
+ );
551
+
552
+ CREATE TABLE teams (
553
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
554
+ name TEXT NOT NULL,
555
+ description TEXT,
556
+ active INTEGER DEFAULT 1
557
+ );
558
+
559
+ CREATE TABLE user_teams (
560
+ user_id INTEGER NOT NULL,
561
+ team_id INTEGER NOT NULL,
562
+ PRIMARY KEY (user_id, team_id),
563
+ FOREIGN KEY (user_id) REFERENCES users(id),
564
+ FOREIGN KEY (team_id) REFERENCES teams(id)
565
+ );
566
+
567
+ CREATE TABLE profiles (
568
+ user_id INTEGER PRIMARY KEY,
569
+ bio TEXT,
570
+ avatar TEXT,
571
+ FOREIGN KEY (user_id) REFERENCES users(id)
572
+ );
573
+ ```
574
+
575
+ ## Requirements
576
+
577
+ - Any runtime with Web Crypto (Cloudflare Workers, Bun, Node 20+)
578
+ - A supported SQLite backend: Cloudflare D1, `bun:sqlite`, or Durable Object `SqlStorage`
579
+ - TypeScript 5.0+
580
+ - Zod 3.25+ (v4)
581
+ - @truto/sqlite-builder 1.0+
582
+
583
+ ## Development
584
+
585
+ ```bash
586
+ bun install # Install dependencies
587
+ bun test # Run tests
588
+ bun run dev # Run tests in watch mode
589
+ bun run build # Build the library
590
+ bun run typecheck # TypeScript type checking
591
+ bun run lint # ESLint
592
+ bun run format # Prettier
593
+ ```
594
+
595
+ ## License
596
+
597
+ MIT — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,37 @@
1
+ import type { Database } from '../types.js';
2
+ /**
3
+ * Structural interface matching Bun's `bun:sqlite` Statement.
4
+ * No runtime import of `bun:sqlite` is needed — TypeScript's structural
5
+ * typing lets the real Bun `Database` satisfy this automatically.
6
+ */
7
+ export interface BunSqliteStatement {
8
+ get(...params: unknown[]): unknown;
9
+ all(...params: unknown[]): unknown[];
10
+ run(...params: unknown[]): {
11
+ changes: number;
12
+ lastInsertRowid: number | bigint;
13
+ };
14
+ }
15
+ /**
16
+ * Structural interface matching Bun's `bun:sqlite` Database.
17
+ */
18
+ export interface BunSqliteDatabase {
19
+ prepare(query: string): BunSqliteStatement;
20
+ exec(query: string): void;
21
+ }
22
+ /**
23
+ * Wrap a Bun `Database` (from `bun:sqlite`) so it satisfies the
24
+ * generic {@link Database} interface used by Ginger services.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { Database } from 'bun:sqlite'
29
+ * import { createService, fromBunSqlite } from 'ginger'
30
+ *
31
+ * const bunDb = new Database(':memory:')
32
+ * const db = fromBunSqlite(bunDb)
33
+ * const service = createService({ db, ... })
34
+ * ```
35
+ */
36
+ export declare function fromBunSqlite(bunDb: BunSqliteDatabase): Database;
37
+ //# 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":"AAAA,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAClC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;IACpC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG;QACzB,OAAO,EAAE,MAAM,CAAA;QACf,eAAe,EAAE,MAAM,GAAG,MAAM,CAAA;KACjC,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAC1C,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,QAAQ,CA4HhE"}