@beignet/provider-drizzle-turso 0.0.3 → 0.0.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @beignet/provider-drizzle-turso
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 8bcb31f: Mark package READMEs with Beignet's experimental alpha status and 0.0.x stability expectations.
8
+ - d137044: Declare `@beignet/core` as a peer dependency with a lockstep version range in
9
+ every integration and provider package instead of a regular `"*"` dependency.
10
+ Installs now always resolve a single shared copy of core, so `instanceof`
11
+ checks such as `isContractError` and upload error identity keep working, and
12
+ mixed Beignet versions fail loudly at install time instead of at runtime.
13
+
14
+ If your package manager does not install peer dependencies automatically, add
15
+ `@beignet/core` to your app alongside these packages. `@beignet/nuqs` now also
16
+ declares `@beignet/react-query` as a peer dependency, and
17
+ `@beignet/provider-storage-s3` now expects you to install
18
+ `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` yourself, matching
19
+ how other providers treat their SDKs.
20
+
21
+ - 780955c: Add Drizzle/Turso-backed durable idempotency storage with setup SQL, root and transaction-client ports, and Unit of Work rollback coverage for idempotency and outbox writes.
22
+ - 89390fe: Harden the Drizzle/Turso outbox and idempotency adapters and widen database URL validation:
23
+
24
+ - `markDelivered(...)` and `markFailed(...)` now trust the driver-reported `rowsAffected` instead of re-reading the row after the update. The re-read raced with legitimate re-claims (a concurrent worker could re-claim a message immediately after `markFailed` returned it to `pending`) and threw spurious `OutboxClaimError`s. The verification read only runs as a fallback when the driver does not report `rowsAffected`.
25
+ - Idempotency `complete(...)` and `fail(...)` no longer silently no-op when no in-progress reservation matches the key and fingerprint. They now throw the new `DrizzleTursoIdempotencyMutationError`, surfacing workflow bugs such as double completion, missing reservations, and fingerprint mismatches. Like `reserve(...)`, a missing `rowsAffected` is treated as success.
26
+ - `TURSO_DB_URL` now accepts every URL scheme `@libsql/client` accepts: `libsql://` and `file:` plus `http://`, `https://`, `ws://`, and `wss://` for local or self-hosted sqld instances.
27
+
28
+ - 69b8c35: Replace the README's broken multiple-databases example. The provider reads one connection from `TURSO_DB_URL`, so registering it twice cannot target two databases; the docs now explain the limitation and show wiring a second database through an app-owned typed provider with its own env prefix.
29
+ - 1a79090: Emit Node-compatible ESM: all relative imports in published packages now carry explicit .js extensions, fixing ERR_MODULE_NOT_FOUND when running the CLI or importing package dist files under plain Node.
30
+ - 44f1192: Move first-party provider diagnostics to package-owned `beignet.provider`
31
+ manifest metadata and have doctor read installed provider package manifests.
32
+ - 2aa77ca: Add static provider metadata and provider wiring diagnostics for generated apps.
33
+ - aa5425c: Clarify Postgres guidance in the provider README with public-facing database adapter language.
34
+ - 03b1743: Document durable workflow provider conventions for retries, backoff, dead-letter state, and unsupported provider semantics.
35
+ - 69b8c35: Require patched dependency versions for security advisories: drizzle-orm
36
+ `^0.45.2` (SQL injection via improperly escaped identifiers, GHSA advisory
37
+ patched in 0.45.2) and nodemailer `^8.0.5` (SMTP command injection fixes).
38
+ Generated apps now scaffold with drizzle-orm `^0.45.2` and drizzle-kit
39
+ `^0.31.10`.
40
+ - 8063d38: Rename the contract front door to `defineContract`/`defineContractGroup`, rename operational commands to tasks (`@beignet/core/tasks`, `defineTasks`, `runTask`, `beignet task run`, `beignet make task`, `server/tasks.ts`, `features/<feature>/tasks/`, `paths.tasks`), and standardize context binding: context-free declarations stay top-level (`defineEvent`), while context-bound definitions come from per-capability factories (`createListeners`, `createJobs`, `createSchedules`, `createNotifications`, `createTasks`) called once in `lib/`. Top-level context-generic `defineListener`, `defineJob`, `defineSchedule`, and `defineNotification` are removed.
41
+
3
42
  ## 0.0.3
4
43
 
5
44
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @beignet/provider-drizzle-turso
2
2
 
3
+ > [!CAUTION]
4
+ > Beignet is experimental alpha software. The `0.0.x` package line is for early
5
+ > evaluation, and APIs may change between releases while the framework settles.
6
+
3
7
  Drizzle ORM + Turso/libSQL provider for Beignet applications.
4
8
 
5
9
  The provider installs a typed database port backed by Drizzle ORM and Turso's
@@ -12,11 +16,27 @@ and migration workflow.
12
16
  - Full TypeScript inference from your Drizzle schema.
13
17
  - Works with Turso Cloud or local libSQL.
14
18
  - Keeps runtime provider wiring separate from Drizzle CLI configuration.
19
+ - Provides a Unit of Work helper that can build transaction-scoped repositories,
20
+ audit logs, outbox recorders, and other app-owned ports from one Drizzle
21
+ transaction client.
22
+
23
+ ## Current scope
24
+
25
+ This package is Beignet's first-party Drizzle provider for libSQL/Turso. It is
26
+ also the reference SQL shape for Beignet apps: keep schema and migrations
27
+ app-owned, accept both a root Drizzle database and transaction client in
28
+ repository factories, and create transaction-scoped app ports inside Unit of
29
+ Work.
30
+
31
+ Beignet does not currently ship a first-party Postgres Drizzle provider. Use
32
+ the same app-owned port pattern with Drizzle's Postgres driver when your app
33
+ needs Postgres. Contracts, use cases, policies, and routes can keep depending
34
+ on ports while the app's infra adapter chooses the database driver.
15
35
 
16
36
  ## Install
17
37
 
18
38
  ```bash
19
- bun add @beignet/provider-drizzle-turso drizzle-orm @libsql/client
39
+ bun add @beignet/provider-drizzle-turso @beignet/core drizzle-orm @libsql/client
20
40
  ```
21
41
 
22
42
  ## Setup
@@ -101,7 +121,7 @@ import { providers } from "./providers";
101
121
  export const server = await createNextServer({
102
122
  ports: appPorts,
103
123
  providers,
104
- createContext: ({ ports }) => ({ ports }),
124
+ context: ({ ports }) => ({ ports }),
105
125
  routes,
106
126
  });
107
127
  ```
@@ -113,26 +133,66 @@ infrastructure, then wire the repository into `ctx.ports`.
113
133
 
114
134
  ```ts
115
135
  // infra/todos/drizzle-todo-repository.ts
116
- import { count, desc } from "drizzle-orm";
117
- import { offsetPageResult } from "@beignet/core/pagination";
136
+ import { Buffer } from "node:buffer";
137
+ import { and, desc, eq, lt, or } from "drizzle-orm";
138
+ import { cursorPageResult } from "@beignet/core/pagination";
118
139
  import type { DrizzleTursoDatabase } from "@beignet/provider-drizzle-turso";
119
140
  import type { TodoRepository } from "@/features/todos/ports";
120
141
  import * as schema from "@/infra/db/schema";
121
142
 
143
+ type TodoCursor = {
144
+ sortBy: "createdAt";
145
+ sortDirection: "desc";
146
+ sortValue: string;
147
+ id: string;
148
+ };
149
+
150
+ function encodeTodoCursor(row: typeof schema.todos.$inferSelect): string {
151
+ return Buffer.from(
152
+ JSON.stringify({
153
+ sortBy: "createdAt",
154
+ sortDirection: "desc",
155
+ sortValue: row.createdAt,
156
+ id: row.id,
157
+ }),
158
+ ).toString("base64url");
159
+ }
160
+
161
+ function decodeTodoCursor(cursor: string): TodoCursor {
162
+ return JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
163
+ }
164
+
122
165
  export function createTodoRepository(
123
166
  db: DrizzleTursoDatabase<typeof schema>,
124
167
  ): TodoRepository {
125
168
  return {
126
169
  async list(input) {
170
+ const cursor = input.page.cursor
171
+ ? decodeTodoCursor(input.page.cursor)
172
+ : null;
127
173
  const rows = await db
128
174
  .select()
129
175
  .from(schema.todos)
130
- .orderBy(desc(schema.todos.createdAt))
131
- .limit(input.limit)
132
- .offset(input.offset);
133
- const [{ total }] = await db.select({ total: count() }).from(schema.todos);
134
-
135
- return offsetPageResult(rows, input, total);
176
+ .where(
177
+ cursor
178
+ ? or(
179
+ lt(schema.todos.createdAt, cursor.sortValue),
180
+ and(
181
+ eq(schema.todos.createdAt, cursor.sortValue),
182
+ lt(schema.todos.id, cursor.id),
183
+ ),
184
+ )
185
+ : undefined,
186
+ )
187
+ .orderBy(desc(schema.todos.createdAt), desc(schema.todos.id))
188
+ .limit(input.page.limit + 1);
189
+ const pageRows = rows.slice(0, input.page.limit);
190
+ const nextCursor =
191
+ rows.length > input.page.limit && pageRows.length > 0
192
+ ? encodeTodoCursor(pageRows[pageRows.length - 1])
193
+ : null;
194
+
195
+ return cursorPageResult(pageRows, input.page, nextCursor);
136
196
  },
137
197
  };
138
198
  }
@@ -153,14 +213,63 @@ export function createRepositories(db: DrizzleTursoDatabase<typeof schema>) {
153
213
  }
154
214
  ```
155
215
 
216
+ Wire repositories into app ports with a typed app-owned database provider.
217
+ The curried `createProvider<Requires, Context, ServiceInput>()` form types the
218
+ required `db` port, the app context, and `createServiceContext` without casts.
219
+ Register it after `createDrizzleTursoProvider`, which installs the `db` port it
220
+ requires:
221
+
222
+ ```ts
223
+ // infra/db/provider.ts
224
+ import { createProvider } from "@beignet/core/providers";
225
+ import type { DbPort } from "@beignet/provider-drizzle-turso";
226
+ import type { AppContext } from "@/app-context";
227
+ import type { AppPorts } from "@/ports";
228
+ import type { AppServiceContextInput } from "@/server";
229
+ import { createRepositories } from "./repositories";
230
+ import type * as schema from "./schema";
231
+
232
+ export const appDatabaseProvider = createProvider<
233
+ { db: DbPort<typeof schema> },
234
+ AppContext,
235
+ AppServiceContextInput
236
+ >()({
237
+ name: "app-database",
238
+ async setup({ ports }) {
239
+ const providedPorts: Pick<AppPorts, "todos"> = createRepositories(
240
+ ports.db.db,
241
+ );
242
+
243
+ return { ports: providedPorts };
244
+ },
245
+ });
246
+ ```
247
+
248
+ Type `ctx.ports` with `InferProviderPorts` so provider-contributed ports,
249
+ including the provider-owned `db` port, stay typed in app code:
250
+
251
+ ```ts
252
+ // app-context.ts
253
+ import type { InferProviderPorts } from "@beignet/core/providers";
254
+ import type { AppPorts } from "@/ports";
255
+ import type { providers } from "@/server/providers";
256
+
257
+ export type AppRuntimePorts = AppPorts & InferProviderPorts<typeof providers>;
258
+
259
+ export type AppContext = {
260
+ requestId: string;
261
+ ports: AppRuntimePorts;
262
+ };
263
+ ```
264
+
156
265
  ```ts
157
266
  // features/todos/use-cases/list-todos.ts
158
- import { normalizeOffsetPage } from "@beignet/core/pagination";
267
+ import { normalizeCursorPage } from "@beignet/core/pagination";
159
268
 
160
269
  export const listTodos = useCase.query("todos.list").run(async ({ ctx }) => {
161
- const page = normalizeOffsetPage({}, { defaultLimit: 20, maxLimit: 100 });
270
+ const page = normalizeCursorPage({}, { defaultLimit: 20, maxLimit: 100 });
162
271
 
163
- return ctx.ports.todos.list(page);
272
+ return ctx.ports.todos.list({ page });
164
273
  });
165
274
  ```
166
275
 
@@ -233,28 +342,24 @@ export function createDrizzleTodoRepository(
233
342
  ```
234
343
 
235
344
  ```ts
236
- // server/index.ts
345
+ // infra/db/provider.ts
237
346
  import { createDrizzleTursoUnitOfWork } from "@beignet/provider-drizzle-turso";
238
- import { createRepositories } from "@/infra/db/repositories";
239
-
240
- createContext: async ({ ports }) => {
241
- const repositories = createRepositories(ports.db.db);
242
-
243
- return {
244
- ports: {
245
- ...ports,
246
- ...repositories,
247
- uow: createDrizzleTursoUnitOfWork({
248
- db: ports.db.db,
249
- eventBus: ports.eventBus,
250
- createTransactionPorts: (tx, events) => ({
251
- ...createRepositories(tx),
252
- events,
253
- }),
254
- }),
255
- },
256
- };
347
+ import { createRepositories } from "./repositories";
348
+
349
+ // Inside the app database provider's setup({ ports }):
350
+ const providedPorts: Pick<AppPorts, "todos" | "uow"> = {
351
+ ...createRepositories(ports.db.db),
352
+ uow: createDrizzleTursoUnitOfWork({
353
+ db: ports.db.db,
354
+ eventBus: ports.eventBus,
355
+ createTransactionPorts: (tx, events) => ({
356
+ ...createRepositories(tx),
357
+ events,
358
+ }),
359
+ }),
257
360
  };
361
+
362
+ return { ports: providedPorts };
258
363
  ```
259
364
 
260
365
  Inside a use case, call transaction-scoped repositories through `tx` and record
@@ -284,15 +389,22 @@ import {
284
389
  drainOutbox,
285
390
  } from "@beignet/core/outbox";
286
391
  import {
392
+ createDrizzleTursoIdempotencyPort,
393
+ createDrizzleTursoIdempotencySetupStatements,
287
394
  createDrizzleTursoOutboxPort,
288
395
  createDrizzleTursoOutboxSetupStatements,
289
396
  createDrizzleTursoUnitOfWork,
290
397
  } from "@beignet/provider-drizzle-turso";
291
398
  ```
292
399
 
293
- Add Beignet's outbox table to your app-owned migration or bootstrap flow:
400
+ Add Beignet's outbox and idempotency tables to your app-owned migration or
401
+ bootstrap flow:
294
402
 
295
403
  ```ts
404
+ for (const statement of createDrizzleTursoIdempotencySetupStatements()) {
405
+ await client.execute(statement);
406
+ }
407
+
296
408
  for (const statement of createDrizzleTursoOutboxSetupStatements()) {
297
409
  await client.execute(statement);
298
410
  }
@@ -304,18 +416,70 @@ Then expose transaction-scoped outbox-backed event recording:
304
416
  uow: createDrizzleTursoUnitOfWork({
305
417
  db: ports.db.db,
306
418
  createTransactionPorts: (tx) => {
419
+ const idempotency = createDrizzleTursoIdempotencyPort(tx);
307
420
  const outbox = createDrizzleTursoOutboxPort(tx);
308
421
 
309
422
  return {
310
423
  ...createRepositories(tx),
311
424
  events: createOutboxEventRecorder(outbox),
425
+ idempotency,
312
426
  outbox,
313
427
  };
314
428
  },
315
429
  });
316
430
  ```
317
431
 
318
- Drain from a worker, cron route, or scheduled task:
432
+ Any port that must commit with the business write belongs in
433
+ `createTransactionPorts`: repositories, audit logs, outbox-backed events/jobs,
434
+ feature history repositories, and durable idempotency records. Root ports are
435
+ still useful for reads and background work, but they do not participate in the
436
+ current transaction unless you rebuild them from `tx`.
437
+
438
+ Use `createDrizzleTursoIdempotencyPort(...)` at the root app port for ordinary
439
+ retry-safe commands, and rebuild it from `tx` when the idempotency reservation,
440
+ business write, audit row, outbox records, and replay result must commit
441
+ together.
442
+
443
+ ## Durable idempotency
444
+
445
+ Use `createDrizzleTursoIdempotencyPort(...)` when `runIdempotently(...)` needs
446
+ durable storage instead of the in-memory test adapter:
447
+
448
+ ```ts
449
+ import { runIdempotently } from "@beignet/core/idempotency";
450
+ import {
451
+ createDrizzleTursoIdempotencyPort,
452
+ createDrizzleTursoIdempotencySetupStatements,
453
+ } from "@beignet/provider-drizzle-turso";
454
+
455
+ for (const statement of createDrizzleTursoIdempotencySetupStatements()) {
456
+ await client.execute(statement);
457
+ }
458
+
459
+ const idempotency = createDrizzleTursoIdempotencyPort(db);
460
+
461
+ await runIdempotently(idempotency, {
462
+ namespace: "posts.create",
463
+ key: input.idempotencyKey,
464
+ scope: { actorId: ctx.actor.id, tenantId: ctx.tenant.id },
465
+ fingerprint,
466
+ run: () => ctx.ports.posts.create(input),
467
+ });
468
+ ```
469
+
470
+ The default table is `idempotency_records`; pass `tableName` to both setup and
471
+ port creation if your app uses a different table name. Expired records are
472
+ removed during reservation, failed in-progress reservations are released, and
473
+ completed matching records replay their stored result.
474
+
475
+ `complete(...)` and `fail(...)` must match an in-progress reservation with the
476
+ provided fingerprint. When the database reports that no row matched, the port
477
+ throws `DrizzleTursoIdempotencyMutationError` instead of silently doing
478
+ nothing, so workflow bugs such as double completion, a missing reservation, or
479
+ a mismatched fingerprint surface immediately. The error exposes `action`,
480
+ `namespace`, `key`, and `scopeKey` for logging and debugging.
481
+
482
+ Drain from a worker, cron route, or schedule:
319
483
 
320
484
  ```ts
321
485
  await drainOutbox({
@@ -329,15 +493,31 @@ await drainOutbox({
329
493
  The default table is `outbox_messages`; pass `tableName` to both setup and port
330
494
  creation if your app uses a different table name.
331
495
 
496
+ The adapter preserves Beignet's durable workflow semantics in SQL:
497
+
498
+ - `attempts` increments when a worker claims a message.
499
+ - `max_attempts` stores the maximum total attempts for the message.
500
+ - `available_at` stores retry timing and backoff decisions.
501
+ - `locked_until` and `claim_token` provide a lease so workers can compete.
502
+ - `deadLettered` is the terminal state for messages that should no longer be
503
+ retried automatically.
504
+
505
+ `drainOutbox(...)` owns retry classification and backoff computation. The
506
+ Drizzle/Turso adapter stores the resulting state and enforces claim ownership.
507
+
332
508
  ## Configuration
333
509
 
334
510
  The provider reads configuration from environment variables with the `TURSO_` prefix:
335
511
 
336
512
  | Variable | Required | Description |
337
513
  |----------|----------|-------------|
338
- | `TURSO_DB_URL` | Yes | Turso/libSQL database URL (e.g., `libsql://your-db.turso.io` or `file:local.db`) |
514
+ | `TURSO_DB_URL` | Yes | Turso/libSQL database URL (e.g., `libsql://your-db.turso.io`, `file:local.db`, or `http://127.0.0.1:8080` for local sqld) |
339
515
  | `TURSO_DB_AUTH_TOKEN` | No | Turso auth token (required for cloud databases, optional for local) |
340
516
 
517
+ `TURSO_DB_URL` accepts every URL scheme `@libsql/client` accepts: `libsql://`
518
+ and `file:` plus `http://`, `https://`, `ws://`, and `wss://` for local or
519
+ self-hosted sqld instances.
520
+
341
521
  ### Example `.env`
342
522
 
343
523
  ```env
@@ -347,30 +527,76 @@ TURSO_DB_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI...
347
527
 
348
528
  # For local development
349
529
  TURSO_DB_URL=file:local.db
530
+
531
+ # For a local sqld instance
532
+ TURSO_DB_URL=http://127.0.0.1:8080
350
533
  ```
351
534
 
352
535
  ## Advanced usage
353
536
 
354
537
  ### Multiple databases
355
538
 
356
- You can create multiple database providers with different port names:
539
+ This provider reads one connection from the `TURSO_DB_URL` and
540
+ `TURSO_DB_AUTH_TOKEN` environment variables. Registering it twice, even with
541
+ different `portName` values, reads the same env vars, so both ports would
542
+ connect to the same database. Use `portName` only to rename the single
543
+ provider-owned port.
544
+
545
+ To connect a second database, wire it through an app-owned provider that loads
546
+ its own connection config, following the same typed custom provider pattern
547
+ shown above:
357
548
 
358
549
  ```ts
359
- import * as mainSchema from "@/infra/db/schema";
360
- import * as analyticsSchema from "@/infra/db/analytics-schema";
361
- import { createDrizzleTursoProvider } from "@beignet/provider-drizzle-turso";
550
+ // infra/db/analytics-provider.ts
551
+ import { createClient } from "@libsql/client";
552
+ import { drizzle } from "drizzle-orm/libsql";
553
+ import { createProvider } from "@beignet/core/providers";
554
+ import type { DbPort } from "@beignet/provider-drizzle-turso";
555
+ import { z } from "zod";
556
+ import * as analyticsSchema from "./analytics-schema";
362
557
 
363
- export const mainDbProvider = createDrizzleTursoProvider({
364
- schema: mainSchema,
365
- portName: "db", // default
558
+ const AnalyticsDbConfigSchema = z.object({
559
+ DB_URL: z.string().min(1),
560
+ DB_AUTH_TOKEN: z.string().optional(),
366
561
  });
367
562
 
368
- export const analyticsDbProvider = createDrizzleTursoProvider({
369
- schema: analyticsSchema,
370
- portName: "analyticsDb",
563
+ export const analyticsDbProvider = createProvider({
564
+ name: "analytics-db",
565
+
566
+ config: {
567
+ schema: AnalyticsDbConfigSchema,
568
+ envPrefix: "ANALYTICS_",
569
+ },
570
+
571
+ async setup({ config }) {
572
+ if (!config) {
573
+ throw new Error(
574
+ "[analyticsDbProvider] Missing config. Set ANALYTICS_DB_URL (and optional ANALYTICS_DB_AUTH_TOKEN).",
575
+ );
576
+ }
577
+
578
+ const client = createClient({
579
+ url: config.DB_URL,
580
+ authToken: config.DB_AUTH_TOKEN,
581
+ });
582
+
583
+ const analyticsDb: DbPort<typeof analyticsSchema> = {
584
+ db: drizzle(client, { schema: analyticsSchema }),
585
+ client,
586
+ };
587
+
588
+ return {
589
+ ports: { analyticsDb },
590
+ async stop() {
591
+ await client.close();
592
+ },
593
+ };
594
+ },
371
595
  });
596
+ ```
372
597
 
373
- // Then in your ports type:
598
+ ```ts
599
+ // ports/index.ts
374
600
  export type AppPorts = {
375
601
  db: DbPort<typeof mainSchema>;
376
602
  analyticsDb: DbPort<typeof analyticsSchema>;
@@ -490,6 +716,22 @@ Returns SQL setup statements for the app-owned outbox table and indexes. Run
490
716
  these through your migration/bootstrap flow or translate them into your normal
491
717
  Drizzle migrations.
492
718
 
719
+ ### `createDrizzleTursoIdempotencyPort<TSchema>(db, options?)`
720
+
721
+ Factory function to create a SQL-backed `IdempotencyPort` from a root Drizzle
722
+ database or transaction client.
723
+
724
+ **Parameters:**
725
+ - `db` (required): A `DrizzleTursoDatabase<TSchema>` root database or transaction
726
+ - `options.tableName` (optional): Idempotency table name, defaults to `"idempotency_records"`
727
+ - `options.now` (optional): Test clock
728
+
729
+ ### `createDrizzleTursoIdempotencySetupStatements(options?)`
730
+
731
+ Returns SQL setup statements for the app-owned idempotency table and indexes.
732
+ Run these through your migration/bootstrap flow or translate them into your
733
+ normal Drizzle migrations.
734
+
493
735
  ## License
494
736
 
495
737
  MIT
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  * const todos = createDrizzleTodoRepository(ctx.ports.db.db);
26
26
  * ```
27
27
  */
28
+ import { type IdempotencyPort } from "@beignet/core/idempotency";
28
29
  import { type OutboxPort } from "@beignet/core/outbox";
29
30
  import { type BufferedDomainEventRecorder, type EventBusPort, type UnitOfWorkPort } from "@beignet/core/ports";
30
31
  import { type Client } from "@libsql/client";
@@ -120,6 +121,56 @@ export declare function createDrizzleTursoOutboxSetupStatements(options?: Drizzl
120
121
  * for eligible messages.
121
122
  */
122
123
  export declare function createDrizzleTursoOutboxPort<TSchema extends Record<string, unknown>>(db: DrizzleTursoDatabase<TSchema>, adapterOptions?: DrizzleTursoOutboxOptions): OutboxPort;
124
+ /**
125
+ * Options for a Drizzle/Turso-backed idempotency port.
126
+ */
127
+ export interface DrizzleTursoIdempotencyOptions {
128
+ /**
129
+ * Table that stores idempotency records.
130
+ *
131
+ * Default: "idempotency_records".
132
+ */
133
+ tableName?: string;
134
+ /**
135
+ * Clock used by tests and deterministic environments.
136
+ */
137
+ now?: () => Date;
138
+ }
139
+ /**
140
+ * Error thrown when `complete(...)` or `fail(...)` does not match an
141
+ * in-progress idempotency reservation with the provided fingerprint.
142
+ *
143
+ * This usually signals a workflow bug: the reservation was never made, was
144
+ * already completed or released, expired and was cleaned up, or the caller
145
+ * passed a different fingerprint than the one used to reserve.
146
+ */
147
+ export declare class DrizzleTursoIdempotencyMutationError extends Error {
148
+ readonly action: "complete" | "fail";
149
+ readonly namespace: string;
150
+ readonly key: string;
151
+ readonly scopeKey: string;
152
+ constructor(args: {
153
+ action: "complete" | "fail";
154
+ namespace: string;
155
+ key: string;
156
+ scopeKey: string;
157
+ });
158
+ }
159
+ /**
160
+ * Create idempotent SQL statements for the Drizzle/Turso idempotency table.
161
+ *
162
+ * Run these during migrations or local setup before using
163
+ * `createDrizzleTursoIdempotencyPort(...)`.
164
+ */
165
+ export declare function createDrizzleTursoIdempotencySetupStatements(options?: DrizzleTursoIdempotencyOptions): readonly string[];
166
+ /**
167
+ * Create a Beignet idempotency port backed by a Drizzle/Turso database.
168
+ *
169
+ * The port accepts both the root Drizzle database and transaction clients, so
170
+ * applications can expose root idempotency for ordinary retry-safe commands and
171
+ * transaction-scoped idempotency from Unit of Work.
172
+ */
173
+ export declare function createDrizzleTursoIdempotencyPort<TSchema extends Record<string, unknown>>(db: DrizzleTursoDatabase<TSchema>, adapterOptions?: DrizzleTursoIdempotencyOptions): IdempotencyPort;
123
174
  /**
124
175
  * Configuration schema for the Drizzle Turso provider.
125
176
  * Validates environment variables with TURSO_ prefix.
@@ -145,7 +196,9 @@ export interface DrizzleTursoProviderOptions<TSchema extends Record<string, unkn
145
196
  schema: TSchema;
146
197
  /**
147
198
  * Optional port name. Defaults to "db".
148
- * If you want multiple databases, you can override this.
199
+ * Renames the contributed port. The provider always reads its connection
200
+ * from `TURSO_DB_URL`; for a second database, wire an app-owned provider
201
+ * with its own env prefix instead.
149
202
  */
150
203
  portName?: PortName;
151
204
  }
@@ -173,6 +226,6 @@ export interface DrizzleTursoProviderOptions<TSchema extends Record<string, unkn
173
226
  export declare function createDrizzleTursoProvider<TSchema extends Record<string, unknown>, PortName extends string = "db">(options: DrizzleTursoProviderOptions<TSchema, PortName>): import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
174
227
  DB_URL: z.ZodString;
175
228
  DB_AUTH_TOKEN: z.ZodOptional<z.ZodString>;
176
- }, z.core.$strip>, Record<PortName, DbPort<TSchema>>>;
229
+ }, z.core.$strip>, Record<PortName, DbPort<TSchema>>, unknown, void>;
177
230
  export {};
178
231
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}