@yielded/auth-persistence-drizzle 0.1.0-beta.18 → 0.1.0-beta.19

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 (130) hide show
  1. package/README.md +8 -1
  2. package/dist/D1.d.mts +8 -8
  3. package/dist/Libsql.d.mts +44 -44
  4. package/dist/Mysql2.d.mts +44 -44
  5. package/dist/Pglite.d.mts +44 -44
  6. package/dist/Postgres.d.mts +44 -44
  7. package/dist/SqliteBun.d.mts +44 -44
  8. package/dist/SqliteDo.d.mts +112 -134
  9. package/dist/SqliteDo.mjs +30 -45
  10. package/dist/SqliteDo.mjs.map +1 -1
  11. package/dist/SqliteNode.d.mts +38 -38
  12. package/dist/SqliteWasm.d.mts +38 -38
  13. package/dist/drizzle/crypto.mjs +11 -0
  14. package/dist/drizzle/crypto.mjs.map +1 -0
  15. package/dist/drizzle/d1-identity.mjs +2 -2
  16. package/dist/drizzle/d1-identity.mjs.map +1 -1
  17. package/dist/drizzle/d1-oauth.d.mts +11 -11
  18. package/dist/drizzle/d1-oauth.mjs.map +1 -1
  19. package/dist/drizzle/d1-passkey.d.mts +6 -6
  20. package/dist/drizzle/d1-passkey.mjs.map +1 -1
  21. package/dist/drizzle/d1-passwords.d.mts +1 -1
  22. package/dist/drizzle/d1-phone.d.mts +3 -3
  23. package/dist/drizzle/d1-phone.mjs.map +1 -1
  24. package/dist/drizzle/d1-proofs.d.mts +1 -1
  25. package/dist/drizzle/d1-sessions.d.mts +1 -1
  26. package/dist/drizzle/d1-totp.d.mts +3 -3
  27. package/dist/drizzle/d1-totp.mjs.map +1 -1
  28. package/dist/drizzle/mysql-identity.mjs +2 -2
  29. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  30. package/dist/drizzle/oauth-accounts.mjs +9 -8
  31. package/dist/drizzle/oauth-accounts.mjs.map +1 -1
  32. package/dist/drizzle/oauth-connected-collection.mjs +1 -1
  33. package/dist/drizzle/oauth-connected-collection.mjs.map +1 -1
  34. package/dist/drizzle/oauth-connected-custody.mjs +5 -4
  35. package/dist/drizzle/oauth-connected-custody.mjs.map +1 -1
  36. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  37. package/dist/drizzle/oauth-connected-flow.mjs +8 -7
  38. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -1
  39. package/dist/drizzle/oauth-connected-management.mjs +3 -2
  40. package/dist/drizzle/oauth-connected-management.mjs.map +1 -1
  41. package/dist/drizzle/oauth-connected-reference.mjs +8 -4
  42. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  43. package/dist/drizzle/oauth-connected-settlement.mjs +4 -4
  44. package/dist/drizzle/oauth-connected-settlement.mjs.map +1 -1
  45. package/dist/drizzle/oauth-connected-sign-in.mjs +11 -10
  46. package/dist/drizzle/oauth-connected-sign-in.mjs.map +1 -1
  47. package/dist/drizzle/oauth-connected-state.d.mts +13 -13
  48. package/dist/drizzle/oauth-connected-state.mjs +23 -20
  49. package/dist/drizzle/oauth-connected-state.mjs.map +1 -1
  50. package/dist/drizzle/oauth-connected-target.d.mts +7 -7
  51. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  52. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  53. package/dist/drizzle/oauth-flow.mjs +1 -1
  54. package/dist/drizzle/oauth-flow.mjs.map +1 -1
  55. package/dist/drizzle/oauth-model.d.mts +4 -4
  56. package/dist/drizzle/oauth-model.mjs.map +1 -1
  57. package/dist/drizzle/oauth-registration.mjs +2 -2
  58. package/dist/drizzle/oauth-registration.mjs.map +1 -1
  59. package/dist/drizzle/oauth-state.mjs +11 -12
  60. package/dist/drizzle/oauth-state.mjs.map +1 -1
  61. package/dist/drizzle/oauth-target.d.mts +4 -4
  62. package/dist/drizzle/oauth-target.mjs.map +1 -1
  63. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  64. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  65. package/dist/drizzle/passkey-write-target.d.mts +1 -1
  66. package/dist/drizzle/password-prepared-state.mjs +1 -1
  67. package/dist/drizzle/pg-identity.mjs +2 -2
  68. package/dist/drizzle/pg-identity.mjs.map +1 -1
  69. package/dist/drizzle/phone-model.d.mts +2 -2
  70. package/dist/drizzle/phone-model.mjs +3 -3
  71. package/dist/drizzle/phone-model.mjs.map +1 -1
  72. package/dist/drizzle/phone-target.d.mts +1 -1
  73. package/dist/drizzle/phone-target.mjs +2 -2
  74. package/dist/drizzle/phone-target.mjs.map +1 -1
  75. package/dist/drizzle/sqlite-do-database.d.mts +29 -0
  76. package/dist/drizzle/sqlite-do-database.mjs +45 -0
  77. package/dist/drizzle/sqlite-do-database.mjs.map +1 -0
  78. package/dist/drizzle/sqlite-emails.mjs.map +1 -1
  79. package/dist/drizzle/sqlite-identity.mjs +2 -2
  80. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  81. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  82. package/dist/drizzle/sqlite-proofs.mjs.map +1 -1
  83. package/dist/drizzle/sqlite-sessions.mjs.map +1 -1
  84. package/dist/drizzle/storage-validation.mjs +17 -9
  85. package/dist/drizzle/storage-validation.mjs.map +1 -1
  86. package/dist/drizzle/totp-state.mjs +8 -8
  87. package/dist/drizzle/totp-state.mjs.map +1 -1
  88. package/dist/drizzle/totp-target.d.mts +1 -1
  89. package/dist/drizzle/totp-target.mjs +2 -2
  90. package/dist/drizzle/totp-target.mjs.map +1 -1
  91. package/package.json +1 -1
  92. package/src/SqliteDo.ts +63 -70
  93. package/src/drizzle/crypto.ts +14 -0
  94. package/src/drizzle/d1-identity.ts +2 -2
  95. package/src/drizzle/d1-oauth.ts +14 -5
  96. package/src/drizzle/d1-passkey.ts +5 -1
  97. package/src/drizzle/d1-phone.ts +2 -1
  98. package/src/drizzle/d1-totp.ts +6 -2
  99. package/src/drizzle/mysql-identity.ts +2 -2
  100. package/src/drizzle/oauth-accounts.ts +14 -13
  101. package/src/drizzle/oauth-connected-collection.ts +1 -1
  102. package/src/drizzle/oauth-connected-custody.ts +8 -6
  103. package/src/drizzle/oauth-connected-drivers.ts +9 -3
  104. package/src/drizzle/oauth-connected-flow.ts +6 -6
  105. package/src/drizzle/oauth-connected-management.ts +1 -1
  106. package/src/drizzle/oauth-connected-reference.ts +14 -9
  107. package/src/drizzle/oauth-connected-settlement.ts +4 -4
  108. package/src/drizzle/oauth-connected-sign-in.ts +11 -10
  109. package/src/drizzle/oauth-connected-state.ts +40 -25
  110. package/src/drizzle/oauth-connected-target.ts +9 -5
  111. package/src/drizzle/oauth-drivers.ts +13 -5
  112. package/src/drizzle/oauth-flow.ts +1 -1
  113. package/src/drizzle/oauth-model.ts +4 -4
  114. package/src/drizzle/oauth-registration.ts +2 -2
  115. package/src/drizzle/oauth-state.ts +17 -15
  116. package/src/drizzle/oauth-target.ts +11 -3
  117. package/src/drizzle/passkey-drivers.ts +7 -3
  118. package/src/drizzle/passkey-write-drivers.ts +11 -2
  119. package/src/drizzle/pg-identity.ts +2 -2
  120. package/src/drizzle/phone-model.ts +3 -3
  121. package/src/drizzle/phone-target.ts +11 -4
  122. package/src/drizzle/sqlite-do-database.ts +87 -0
  123. package/src/drizzle/sqlite-emails.ts +1 -1
  124. package/src/drizzle/sqlite-identity.ts +2 -2
  125. package/src/drizzle/sqlite-passwords.ts +1 -1
  126. package/src/drizzle/sqlite-proofs.ts +2 -2
  127. package/src/drizzle/sqlite-sessions.ts +2 -2
  128. package/src/drizzle/storage-validation.ts +15 -6
  129. package/src/drizzle/totp-state.ts +8 -8
  130. package/src/drizzle/totp-target.ts +11 -5
@@ -28,7 +28,7 @@ import {
28
28
  import type { SecurityRevision } from "@yielded/auth/Sessions";
29
29
  /* oxlint-disable no-explicit-any -- concrete driver entry points restore table/database generics. */
30
30
  import { eq, sql } from "drizzle-orm";
31
- import { type Context, Effect, Layer } from "effect";
31
+ import { type Crypto, type Context, Effect, Layer } from "effect";
32
32
  import type { Statement } from "effect/sql/Statement";
33
33
 
34
34
  import type { PersistenceMappingError } from "./model";
@@ -91,7 +91,11 @@ export const sqlClientOAuthStandaloneGuard = (
91
91
  export const makeOAuthExecution = Effect.fnUntraced(function* (
92
92
  configuration: OAuthTargetConfiguration,
93
93
  mapping: unknown,
94
- ): Effect.fn.Return<OAuthExecution, OAuthUnavailable, LifecycleHooks | NativeDatabase> {
94
+ ): Effect.fn.Return<
95
+ OAuthExecution,
96
+ OAuthUnavailable,
97
+ LifecycleHooks | NativeDatabase | Crypto.Crypto
98
+ > {
95
99
  const hooks = yield* LifecycleHooks;
96
100
 
97
101
  yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
@@ -435,7 +439,11 @@ export const coordinateOAuthOwner = <Services, Transaction, A, E, R>(
435
439
  services: Services,
436
440
  append: (statement: Statement<any>) => void,
437
441
  ) => Effect.Effect<A, E, R>,
438
- ): Effect.Effect<A, OAuthCoordinatorError<E>, R | LifecycleHooks | NativeDatabase> => {
442
+ ): Effect.Effect<
443
+ A,
444
+ OAuthCoordinatorError<E>,
445
+ R | Crypto.Crypto | LifecycleHooks | NativeDatabase
446
+ > => {
439
447
  const captured = captureOAuthMapping(mapping);
440
448
 
441
449
  return Effect.flatMap(LifecycleHooks, (hooks) =>
@@ -2,7 +2,7 @@ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
2
  import type { LifecycleHooks } from "@yielded/auth/Hooks";
3
3
  import { PasskeyPersistence } from "@yielded/auth/Passkey";
4
4
  import type { Table } from "drizzle-orm";
5
- import { Effect, Context } from "effect";
5
+ import { type Crypto, Effect, Context } from "effect";
6
6
 
7
7
  import { nativeDatabase } from "./native-database";
8
8
  import type {
@@ -84,6 +84,7 @@ export const makePasskeyTarget = <
84
84
  A,
85
85
  PasskeyCoordinatorError<E> | DatabaseError,
86
86
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence>)
87
+ | Crypto.Crypto
87
88
  | LifecycleHooks
88
89
  | DatabaseRequirements
89
90
  | RSetup
@@ -130,6 +131,7 @@ export const makePasskeyTarget = <
130
131
  A,
131
132
  PasskeyCoordinatorError<E> | DatabaseError,
132
133
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence | TxId>)
134
+ | Crypto.Crypto
133
135
  | LifecycleHooks
134
136
  | DatabaseRequirements
135
137
  | RSetup
@@ -175,7 +177,7 @@ export const makePasskeyTarget = <
175
177
  ): Effect.Effect<
176
178
  A,
177
179
  PasskeyCoordinatorError<E> | DatabaseError,
178
- Exclude<R, PasskeyPersistence> | LifecycleHooks | DatabaseRequirements | RSetup
180
+ Exclude<R, PasskeyPersistence> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements | RSetup
179
181
  > {
180
182
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
181
183
  coordinateTargetPasskey(
@@ -221,6 +223,7 @@ export const makePasskeyTarget = <
221
223
  A,
222
224
  PasskeyCoordinatorError<E> | DatabaseError,
223
225
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence>)
226
+ | Crypto.Crypto
224
227
  | LifecycleHooks
225
228
  | DatabaseRequirements
226
229
  | RSetup
@@ -255,6 +258,7 @@ export const makePasskeyTarget = <
255
258
  A,
256
259
  PasskeyCoordinatorError<E> | DatabaseError,
257
260
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence | TxId>)
261
+ | Crypto.Crypto
258
262
  | LifecycleHooks
259
263
  | DatabaseRequirements
260
264
  | RSetup
@@ -288,7 +292,7 @@ export const makePasskeyTarget = <
288
292
  ): Effect.Effect<
289
293
  A,
290
294
  PasskeyCoordinatorError<E> | DatabaseError,
291
- Exclude<R, PasskeyPersistence> | LifecycleHooks | DatabaseRequirements | RSetup
295
+ Exclude<R, PasskeyPersistence> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements | RSetup
292
296
  > {
293
297
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
294
298
  coordinateTargetPasskeyRegistration(
@@ -2,7 +2,7 @@ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
2
  import type { LifecycleHooks } from "@yielded/auth/Hooks";
3
3
  import { PasskeyManagementPersistence, PasskeyPersistence } from "@yielded/auth/Passkey";
4
4
  import type { Table } from "drizzle-orm";
5
- import { Context, Effect } from "effect";
5
+ import { type Crypto, Context, Effect } from "effect";
6
6
 
7
7
  import { nativeDatabase } from "./native-database";
8
8
  import type { PasskeyMappingSource } from "./passkey-model";
@@ -73,6 +73,7 @@ export const makePasskeyWriteTarget = <
73
73
  | (Synchronous extends true
74
74
  ? never
75
75
  : Exclude<R, PasskeyPersistence | PasskeyManagementPersistence>)
76
+ | Crypto.Crypto
76
77
  | LifecycleHooks
77
78
  | RSetup
78
79
  | DatabaseRequirements
@@ -120,6 +121,7 @@ export const makePasskeyWriteTarget = <
120
121
  | (Synchronous extends true
121
122
  ? never
122
123
  : Exclude<R, PasskeyPersistence | PasskeyManagementPersistence | TxId>)
124
+ | Crypto.Crypto
123
125
  | LifecycleHooks
124
126
  | RSetup
125
127
  | DatabaseRequirements
@@ -159,6 +161,7 @@ export const makePasskeyWriteTarget = <
159
161
  A,
160
162
  PasskeyCoordinatorError<E> | DatabaseError,
161
163
  | Exclude<R, PasskeyPersistence | PasskeyManagementPersistence>
164
+ | Crypto.Crypto
162
165
  | LifecycleHooks
163
166
  | RSetup
164
167
  | DatabaseRequirements
@@ -250,6 +253,7 @@ export const makePasskeyWriteTarget = <
250
253
  A,
251
254
  PasskeyCoordinatorError<E> | DatabaseError,
252
255
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence | AuthorityId>)
256
+ | Crypto.Crypto
253
257
  | LifecycleHooks
254
258
  | RSetup
255
259
  | DatabaseRequirements
@@ -297,6 +301,7 @@ export const makePasskeyWriteTarget = <
297
301
  A,
298
302
  PasskeyCoordinatorError<E> | DatabaseError,
299
303
  | (Synchronous extends true ? never : Exclude<R, PasskeyPersistence | AuthorityId | TxId>)
304
+ | Crypto.Crypto
300
305
  | LifecycleHooks
301
306
  | RSetup
302
307
  | DatabaseRequirements
@@ -339,7 +344,11 @@ export const makePasskeyWriteTarget = <
339
344
  ): Effect.Effect<
340
345
  A,
341
346
  PasskeyCoordinatorError<E> | DatabaseError,
342
- Exclude<R, PasskeyPersistence | AuthorityId> | LifecycleHooks | RSetup | DatabaseRequirements
347
+ | Exclude<R, PasskeyPersistence | AuthorityId>
348
+ | Crypto.Crypto
349
+ | LifecycleHooks
350
+ | RSetup
351
+ | DatabaseRequirements
343
352
  > {
344
353
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
345
354
  coordinateTargetPasskeyRegistrationWriter(
@@ -1,4 +1,4 @@
1
- import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
+ import { withStorageValidation, NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
2
  import {
3
3
  ExternalIdentityMutation,
4
4
  IdentityConflict,
@@ -307,4 +307,4 @@ export const makePgIdentityServices = Effect.fnUntraced(function* <
307
307
  ...(yield* makePgSubjectProvisioningServices(mapping)),
308
308
  ...(yield* makePgExternalIdentityServices(mapping)),
309
309
  };
310
- });
310
+ }, withStorageValidation);
@@ -1,4 +1,4 @@
1
- import { cryptoLayer, hooksLayer } from "@yielded/auth/Persistence";
1
+ import { hooksLayer } from "@yielded/auth/Persistence";
2
2
  import {
3
3
  type PhoneAdmissionPolicy,
4
4
  type PhoneLifecyclePolicy,
@@ -175,7 +175,7 @@ export interface PhonePersistenceServices {
175
175
  readonly phoneSignInTargets: PhoneSignInTargets["Service"];
176
176
  }
177
177
 
178
- /** Bundle phone storage, admission, and lookup with overridable crypto and hook defaults. */
178
+ /** Bundle phone storage, admission, and lookup with empty hook defaults. */
179
179
  export const phonePersistenceLayer = <E, R>(
180
180
  services: Effect.Effect<PhonePersistenceServices, E, R>,
181
181
  ) =>
@@ -187,4 +187,4 @@ export const phonePersistenceLayer = <E, R>(
187
187
  Layer.succeed(PhoneSignInTargets, value.phoneSignInTargets),
188
188
  ),
189
189
  ),
190
- ).pipe(Layer.provide([cryptoLayer, hooksLayer]));
190
+ ).pipe(Layer.provide(hooksLayer));
@@ -1,4 +1,3 @@
1
- import { randomId } from "@yielded/auth-crypto";
2
1
  import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
3
2
  import { LifecycleHooks } from "@yielded/auth/Hooks";
4
3
  import {
@@ -16,9 +15,10 @@ import {
16
15
  import { ProofBinding } from "@yielded/auth/Proofs";
17
16
  import { sql, type Table } from "drizzle-orm";
18
17
  /* oxlint-disable no-explicit-any -- shared native implementation; driver entrypoints retain exact database and table types. */
19
- import { Context, Effect, Schema } from "effect";
18
+ import { type Crypto, Context, Effect, Schema } from "effect";
20
19
  import type { Statement } from "effect/sql/Statement";
21
20
 
21
+ import { randomId } from "./crypto";
22
22
  import { CurrentD1PlanningDatabase } from "./d1-planning";
23
23
  import { compileD1ProofCompletionPlan } from "./d1-proofs";
24
24
  import type { PersistenceMappingError } from "./model";
@@ -309,7 +309,11 @@ export const coordinateTargetPhone = <
309
309
  services: PhonePersistenceServices,
310
310
  append: (statement: Statement<any>) => void,
311
311
  ) => Effect.Effect<A, E, R>,
312
- ): Effect.Effect<A, PhoneCoordinatorError<E>, R | RSetup | LifecycleHooks | NativeDatabase> =>
312
+ ): Effect.Effect<
313
+ A,
314
+ PhoneCoordinatorError<E>,
315
+ R | RSetup | Crypto.Crypto | LifecycleHooks | NativeDatabase
316
+ > =>
313
317
  Effect.gen(function* () {
314
318
  const original = yield* Effect.isEffect(source) ? source : Effect.succeed(source);
315
319
 
@@ -342,7 +346,7 @@ type TransactionOf<D> = D extends { readonly transaction: (...args: any[]) => an
342
346
  ? Parameters<Parameters<D["transaction"]>[0]>[0]
343
347
  : never;
344
348
 
345
- /** Concrete driver wrappers select transaction mode; cryptography always precedes these owners. */
349
+ /** Concrete driver wrappers select transaction mode. */
346
350
  export const makePhoneTarget = <
347
351
  DatabaseId,
348
352
  D extends { readonly transaction: any },
@@ -383,6 +387,7 @@ export const makePhoneTarget = <
383
387
  | (Synchronous extends true
384
388
  ? never
385
389
  : Exclude<R, PhonePersistence | PhoneAdmission | PhoneSignInTargets>)
390
+ | Crypto.Crypto
386
391
  | LifecycleHooks
387
392
  | RSetup
388
393
  | DatabaseRequirements
@@ -421,6 +426,7 @@ export const makePhoneTarget = <
421
426
  | (Synchronous extends true
422
427
  ? never
423
428
  : Exclude<R, PhonePersistence | PhoneAdmission | PhoneSignInTargets | TxId>)
429
+ | Crypto.Crypto
424
430
  | LifecycleHooks
425
431
  | RSetup
426
432
  | DatabaseRequirements
@@ -451,6 +457,7 @@ export const makePhoneTarget = <
451
457
  A,
452
458
  PhoneCoordinatorError<E> | DatabaseError,
453
459
  | Exclude<R, PhonePersistence | PhoneAdmission | PhoneSignInTargets>
460
+ | Crypto.Crypto
454
461
  | LifecycleHooks
455
462
  | RSetup
456
463
  | DatabaseRequirements
@@ -0,0 +1,87 @@
1
+ import type { SqliteClient } from "@effect/sql-sqlite-do/SqliteClient";
2
+ import { PersistenceConfigurationError } from "@yielded/auth-persistence/Adapter";
3
+ import type { AnyRelations } from "drizzle-orm";
4
+ import { EffectTransactionRollbackError } from "drizzle-orm/effect-core";
5
+ import type { EffectSQLiteDoDatabase } from "drizzle-orm/effect-sqlite-do";
6
+ import { Effect } from "effect";
7
+ import type { SqlError } from "effect/sql/SqlError";
8
+
9
+ type NativeDatabase<T extends AnyRelations> = EffectSQLiteDoDatabase<T> & {
10
+ readonly $client: SqliteClient;
11
+ };
12
+
13
+ type Queries<T extends AnyRelations> = Pick<
14
+ NativeDatabase<T>,
15
+ | "query"
16
+ | "$cache"
17
+ | "$with"
18
+ | "$count"
19
+ | "with"
20
+ | "select"
21
+ | "selectDistinct"
22
+ | "insert"
23
+ | "update"
24
+ | "delete"
25
+ | "run"
26
+ | "all"
27
+ | "get"
28
+ | "values"
29
+ >;
30
+
31
+ /** Drizzle queries with transactions owned by the captured Effect SQL client. */
32
+ export type DatabaseValue<T extends AnyRelations = AnyRelations> = Queries<T> & {
33
+ readonly $client: SqliteClient;
34
+ readonly transaction: <A, E, R>(
35
+ body: (transaction: Transaction<T>) => Effect.Effect<A, E, R>,
36
+ ) => Effect.Effect<A, E | SqlError, R>;
37
+ };
38
+
39
+ /** An asynchronous Durable Object transaction, including nested rollback. */
40
+ export type Transaction<T extends AnyRelations = AnyRelations> = DatabaseValue<T> & {
41
+ readonly rollback: () => EffectTransactionRollbackError;
42
+ };
43
+
44
+ /**
45
+ * Keep the application's Drizzle query configuration and capture its SQL client.
46
+ * Effect SQL owns storage.transaction, so cryptography may suspend before commit.
47
+ * Raw Drizzle transactionSync callbacks are not supported outer owners.
48
+ */
49
+ export const makeDatabase = Effect.fnUntraced(function* <T extends AnyRelations>(
50
+ database: NativeDatabase<T>,
51
+ ): Effect.fn.Return<DatabaseValue<T>, PersistenceConfigurationError> {
52
+ const client = database.$client;
53
+
54
+ if (client.config.storage === undefined)
55
+ return yield* PersistenceConfigurationError.make({
56
+ reason: "SqliteDo requires a SQL client configured with Durable Object storage",
57
+ });
58
+
59
+ const queries: Queries<T> = {
60
+ query: database.query,
61
+ $cache: database.$cache,
62
+ $with: database.$with.bind(database),
63
+ $count: database.$count.bind(database),
64
+ with: database.with.bind(database),
65
+ select: database.select.bind(database),
66
+ selectDistinct: database.selectDistinct.bind(database),
67
+ insert: database.insert.bind(database),
68
+ update: database.update.bind(database),
69
+ delete: database.delete.bind(database),
70
+ run: database.run.bind(database),
71
+ all: database.all.bind(database),
72
+ get: database.get.bind(database),
73
+ values: database.values.bind(database),
74
+ };
75
+
76
+ const transaction: DatabaseValue<T>["transaction"] = (body) =>
77
+ client.withTransaction(Effect.suspend(() => body(makeTransaction())));
78
+
79
+ const makeTransaction = (): Transaction<T> => ({
80
+ ...queries,
81
+ $client: client,
82
+ transaction,
83
+ rollback: () => new EffectTransactionRollbackError(),
84
+ });
85
+
86
+ return { ...queries, $client: client, transaction };
87
+ });
@@ -5,7 +5,6 @@ import type { LifecycleHooks, HookConfigurationError } from "@yielded/auth/Hooks
5
5
  import type { AnyRelations } from "drizzle-orm";
6
6
  import type { EffectLibsqlDatabase } from "drizzle-orm/effect-libsql";
7
7
  import type { EffectSQLiteBunDatabase } from "drizzle-orm/effect-sqlite-bun";
8
- import type { EffectSQLiteDoDatabase as DODatabase } from "drizzle-orm/effect-sqlite-do";
9
8
  import type { EffectSQLiteNodeDatabase as NodeDatabase } from "drizzle-orm/effect-sqlite-node";
10
9
  import type { EffectSQLiteWasmDatabase as WasmDatabase } from "drizzle-orm/effect-sqlite-wasm";
11
10
  import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
@@ -31,6 +30,7 @@ import {
31
30
  import { nativeDatabase } from "./native-database";
32
31
  import type { ProofPersistenceMapping } from "./proof-model";
33
32
  import type { ProofSqlQuery } from "./proof-sql";
33
+ import type { DatabaseValue as DODatabase } from "./sqlite-do-database";
34
34
  import {
35
35
  type TransactionService,
36
36
  acquireTransactionService,
@@ -1,4 +1,4 @@
1
- import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
+ import { withStorageValidation, NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
2
  import {
3
3
  ExternalIdentityMutation,
4
4
  type ExternalIdentity,
@@ -326,4 +326,4 @@ export const makeSqliteIdentityServices = Effect.fnUntraced(function* <
326
326
  ...(yield* makeSqliteSubjectProvisioningServices(mapping, mode)),
327
327
  ...(yield* makeSqliteExternalIdentityServices(mapping)),
328
328
  };
329
- });
329
+ }, withStorageValidation);
@@ -5,7 +5,6 @@ import { type PasswordUnavailable, PasswordPersistence } from "@yielded/auth/Pas
5
5
  import type { AnyRelations } from "drizzle-orm";
6
6
  import type { EffectLibsqlDatabase } from "drizzle-orm/effect-libsql";
7
7
  import type { EffectSQLiteBunDatabase } from "drizzle-orm/effect-sqlite-bun";
8
- import type { EffectSQLiteDoDatabase as DODatabase } from "drizzle-orm/effect-sqlite-do";
9
8
  import type { EffectSQLiteNodeDatabase as NodeDatabase } from "drizzle-orm/effect-sqlite-node";
10
9
  import type { EffectSQLiteWasmDatabase as WasmDatabase } from "drizzle-orm/effect-sqlite-wasm";
11
10
  import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
@@ -26,6 +25,7 @@ import {
26
25
  } from "./password-target";
27
26
  import type { ProofPersistenceMapping } from "./proof-model";
28
27
  import type { ProofSqlQuery } from "./proof-sql";
28
+ import type { DatabaseValue as DODatabase } from "./sqlite-do-database";
29
29
  import {
30
30
  type TransactionService,
31
31
  acquireTransactionService,
@@ -4,7 +4,6 @@ import { type ProofUnavailable, ProofPersistence } from "@yielded/auth/Proofs";
4
4
  import type { AnyRelations } from "drizzle-orm";
5
5
  import type { EffectLibsqlDatabase } from "drizzle-orm/effect-libsql";
6
6
  import type { EffectSQLiteBunDatabase } from "drizzle-orm/effect-sqlite-bun";
7
- import type { EffectSQLiteDoDatabase } from "drizzle-orm/effect-sqlite-do";
8
7
  import type { EffectSQLiteNodeDatabase } from "drizzle-orm/effect-sqlite-node";
9
8
  import type { EffectSQLiteWasmDatabase } from "drizzle-orm/effect-sqlite-wasm";
10
9
  import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
@@ -19,6 +18,7 @@ import {
19
18
  makeTargetProofPersistenceServices,
20
19
  type ProofTargetConfiguration,
21
20
  } from "./proof-target";
21
+ import type { DatabaseValue } from "./sqlite-do-database";
22
22
  import {
23
23
  type TransactionService,
24
24
  acquireTransactionService,
@@ -29,7 +29,7 @@ import type { SuppliedService } from "./SuppliedService";
29
29
  type Database =
30
30
  | EffectLibsqlDatabase<AnyRelations>
31
31
  | EffectSQLiteBunDatabase<AnyRelations>
32
- | EffectSQLiteDoDatabase<AnyRelations>
32
+ | DatabaseValue<AnyRelations>
33
33
  | EffectSQLiteNodeDatabase<AnyRelations>
34
34
  | EffectSQLiteWasmDatabase<AnyRelations>;
35
35
  type TransactionOf<D extends Database> = Parameters<Parameters<D["transaction"]>[0]>[0];
@@ -12,7 +12,6 @@ import {
12
12
  import type { AnyRelations } from "drizzle-orm";
13
13
  import type { EffectLibsqlDatabase } from "drizzle-orm/effect-libsql";
14
14
  import type { EffectSQLiteBunDatabase } from "drizzle-orm/effect-sqlite-bun";
15
- import type { EffectSQLiteDoDatabase } from "drizzle-orm/effect-sqlite-do";
16
15
  import type { EffectSQLiteNodeDatabase } from "drizzle-orm/effect-sqlite-node";
17
16
  import type { EffectSQLiteWasmDatabase } from "drizzle-orm/effect-sqlite-wasm";
18
17
  import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
@@ -40,6 +39,7 @@ import {
40
39
  makeTargetSessionStepUpServices,
41
40
  coordinateTargetSessionStepUp,
42
41
  } from "./session-target";
42
+ import type { DatabaseValue } from "./sqlite-do-database";
43
43
  import {
44
44
  type TransactionService,
45
45
  acquireTransactionService,
@@ -50,7 +50,7 @@ import type { SuppliedService } from "./SuppliedService";
50
50
  type Database =
51
51
  | EffectLibsqlDatabase<AnyRelations>
52
52
  | EffectSQLiteBunDatabase<AnyRelations>
53
- | EffectSQLiteDoDatabase<AnyRelations>
53
+ | DatabaseValue<AnyRelations>
54
54
  | EffectSQLiteNodeDatabase<AnyRelations>
55
55
  | EffectSQLiteWasmDatabase<AnyRelations>;
56
56
  type TransactionOf<D extends Database> = Parameters<Parameters<D["transaction"]>[0]>[0];
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  NativeDatabase,
3
3
  PersistenceMappingError,
4
- validateStorage,
4
+ validateStorageBatch,
5
+ type StorageValidation,
5
6
  } from "@yielded/auth-persistence/Adapter";
6
7
  import { getTableColumns, getTableName, is, Table } from "drizzle-orm";
7
8
  import { getTableConfig as getMysqlTableConfig, MySqlTable } from "drizzle-orm/mysql-core";
@@ -243,6 +244,8 @@ export const validateDrizzleStorage = Effect.fnUntraced(
243
244
 
244
245
  const { $client: client } = yield* NativeDatabase;
245
246
 
247
+ const batches = new Map<"pg" | "mysql" | "sqlite", StorageValidation[]>();
248
+
246
249
  for (const plan of physical) {
247
250
  const pg = is(plan.table, PgTable);
248
251
  const mysql = is(plan.table, MySqlTable);
@@ -255,15 +258,21 @@ export const validateDrizzleStorage = Effect.fnUntraced(
255
258
 
256
259
  const columns = getTableColumns(plan.table);
257
260
 
258
- yield* validateStorage(
259
- pg ? "pg" : mysql ? "mysql" : "sqlite",
260
- {
261
+ const dialect = pg ? "pg" : mysql ? "mysql" : "sqlite";
262
+ const batch = batches.get(dialect) ?? [];
263
+
264
+ batch.push({
265
+ table: {
261
266
  name: getTableName(plan.table),
262
267
  ...(schema === undefined ? {} : { schema }),
263
268
  columns: Object.fromEntries(plan.keys.map((key) => [key, { name: columns[key]!.name }])),
264
269
  },
265
- [plan.keys],
266
- ).pipe(
270
+ required: [plan.keys],
271
+ });
272
+ batches.set(dialect, batch);
273
+ }
274
+ for (const [dialect, requirements] of batches) {
275
+ yield* validateStorageBatch(dialect, requirements).pipe(
267
276
  Effect.provideService(SqlClient, client),
268
277
  Effect.mapError(() =>
269
278
  PersistenceMappingError.make({
@@ -1,4 +1,3 @@
1
- import { randomId, digest } from "@yielded/auth-crypto";
2
1
  import { AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
3
2
  import {
4
3
  TotpUnavailable,
@@ -11,6 +10,7 @@ import {
11
10
  import { sql, type SQL } from "drizzle-orm";
12
11
  import { Context, Effect, Schema } from "effect";
13
12
 
13
+ import { randomId, digest } from "./crypto";
14
14
  import { both, makeTransactionRows, type TransactionOwner } from "./transaction-owner";
15
15
  export const unavailable = () => TotpUnavailable.make({});
16
16
 
@@ -59,7 +59,7 @@ export const captureTotp = Effect.fn("TotpNative.capture")(function* (
59
59
  )
60
60
  return undefined;
61
61
 
62
- const scope = scopeFor(mapping.moduleId, subjectId),
62
+ const scope = yield* scopeFor(mapping.moduleId, subjectId),
63
63
  found = yield* owner.read(factor.table, equal(factor.table, { [factor.scope]: scope }), {
64
64
  limit: 1,
65
65
  }),
@@ -140,7 +140,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
140
140
  const guards: SQL[] = [];
141
141
  const credentialGuards: { credentialId: string; condition: SQL }[] = [];
142
142
 
143
- const commandScope = digest(
143
+ const commandScope = yield* digest(
144
144
  `effect-auth/totp/command/v1/${mapping.moduleId.length}:${mapping.moduleId}/${input.subjectId.length}:${input.subjectId}/${input.commandId}`,
145
145
  );
146
146
 
@@ -413,7 +413,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
413
413
  secret: null,
414
414
  pending: null,
415
415
  recoveryDigests: [],
416
- revision: randomId(),
416
+ revision: yield* randomId,
417
417
  };
418
418
  semantic = true;
419
419
  }
@@ -428,7 +428,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
428
428
  secret: null,
429
429
  pending: null,
430
430
  recoveryDigests: [],
431
- revision: randomId(),
431
+ revision: yield* randomId,
432
432
  };
433
433
  else {
434
434
  if (new Set(action.recoveryDigests).size !== 10) return reject;
@@ -436,7 +436,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
436
436
  }
437
437
  }
438
438
  }
439
- next = { ...next, version: randomId() };
439
+ next = { ...next, version: yield* randomId };
440
440
 
441
441
  const subject = mapping.subject,
442
442
  factor = mapping.factor,
@@ -445,7 +445,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
445
445
  const subjectKey = { [subject.id]: nativeId };
446
446
 
447
447
  if (semantic) {
448
- const revision = randomId(),
448
+ const revision = yield* randomId,
449
449
  updates = {
450
450
  [subject.securityRevision]: revision,
451
451
  [subject.factorEnabled]: subject.encodeEnabled(next.secret !== null),
@@ -500,7 +500,7 @@ export const mutateTotp = Effect.fn("TotpNative.mutate")(function* (
500
500
  const values = factor.encodeInsert({
501
501
  scope: commandScope,
502
502
  state: "totp-command/v1",
503
- version: randomId(),
503
+ version: yield* randomId,
504
504
  });
505
505
 
506
506
  invariant(values[factor.scope] === commandScope && values[factor.state] === "totp-command/v1");
@@ -1,4 +1,3 @@
1
- import { randomId } from "@yielded/auth-crypto";
2
1
  import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
3
2
  import { LifecycleHooks } from "@yielded/auth/Hooks";
4
3
  import {
@@ -10,9 +9,10 @@ import {
10
9
  } from "@yielded/auth/Totp";
11
10
  import type { Table } from "drizzle-orm";
12
11
  /* oxlint-disable no-explicit-any -- shared native implementation; driver entrypoints retain exact database and table types. */
13
- import { Context, Effect, Schema } from "effect";
12
+ import { type Crypto, Context, Effect, Schema } from "effect";
14
13
  import type { Statement } from "effect/sql/Statement";
15
14
 
15
+ import { randomId } from "./crypto";
16
16
  import type { PersistenceMappingError } from "./model";
17
17
  import { nativeDatabase } from "./native-database";
18
18
  import { validateDrizzleStorage } from "./storage-validation";
@@ -164,7 +164,11 @@ export const coordinateTargetTotp = <M, A, E, R, RSetup = never>(
164
164
  services: TotpPersistenceServices,
165
165
  append: (statement: Statement<any>) => void,
166
166
  ) => Effect.Effect<A, E, R>,
167
- ): Effect.Effect<A, TotpCoordinatorError<E>, R | RSetup | LifecycleHooks | NativeDatabase> =>
167
+ ): Effect.Effect<
168
+ A,
169
+ TotpCoordinatorError<E>,
170
+ R | RSetup | Crypto.Crypto | LifecycleHooks | NativeDatabase
171
+ > =>
168
172
  Effect.gen(function* () {
169
173
  const original = yield* Effect.isEffect(source) ? source : Effect.succeed(source);
170
174
 
@@ -196,7 +200,7 @@ type TransactionOf<D> = D extends { readonly transaction: (...args: any[]) => an
196
200
  ? Parameters<Parameters<D["transaction"]>[0]>[0]
197
201
  : never;
198
202
 
199
- /** Concrete driver wrappers select transaction mode; cryptography always precedes these owners. */
203
+ /** Concrete driver wrappers select transaction mode. */
200
204
  export const makeTotpTarget = <
201
205
  DatabaseId,
202
206
  D extends { readonly transaction: any },
@@ -230,6 +234,7 @@ export const makeTotpTarget = <
230
234
  A,
231
235
  TotpCoordinatorError<E> | DatabaseError,
232
236
  | (Synchronous extends true ? never : Exclude<R, TotpPersistence>)
237
+ | Crypto.Crypto
233
238
  | LifecycleHooks
234
239
  | RSetup
235
240
  | DatabaseRequirements
@@ -259,6 +264,7 @@ export const makeTotpTarget = <
259
264
  A,
260
265
  TotpCoordinatorError<E> | DatabaseError,
261
266
  | (Synchronous extends true ? never : Exclude<R, TotpPersistence | TxId>)
267
+ | Crypto.Crypto
262
268
  | LifecycleHooks
263
269
  | RSetup
264
270
  | DatabaseRequirements
@@ -287,7 +293,7 @@ export const makeTotpTarget = <
287
293
  ): Effect.Effect<
288
294
  A,
289
295
  TotpCoordinatorError<E> | DatabaseError,
290
- Exclude<R, TotpPersistence> | LifecycleHooks | RSetup | DatabaseRequirements
296
+ Exclude<R, TotpPersistence> | Crypto.Crypto | LifecycleHooks | RSetup | DatabaseRequirements
291
297
  > {
292
298
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
293
299
  coordinateTargetTotp(