@yielded/auth-persistence-drizzle 0.1.0-beta.17 → 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 (142) hide show
  1. package/README.md +8 -1
  2. package/dist/D1.d.mts +120 -120
  3. package/dist/Libsql.d.mts +154 -154
  4. package/dist/Mysql2.d.mts +166 -166
  5. package/dist/Pglite.d.mts +166 -166
  6. package/dist/Postgres.d.mts +152 -152
  7. package/dist/SqliteBun.d.mts +168 -168
  8. package/dist/SqliteDo.d.mts +237 -259
  9. package/dist/SqliteDo.mjs +30 -45
  10. package/dist/SqliteDo.mjs.map +1 -1
  11. package/dist/SqliteNode.d.mts +154 -154
  12. package/dist/SqliteWasm.d.mts +163 -163
  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 +16 -16
  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-password-prepared.mjs +3 -3
  22. package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
  23. package/dist/drizzle/d1-passwords.d.mts +1 -1
  24. package/dist/drizzle/d1-passwords.mjs +1 -1
  25. package/dist/drizzle/d1-passwords.mjs.map +1 -1
  26. package/dist/drizzle/d1-phone.d.mts +3 -3
  27. package/dist/drizzle/d1-phone.mjs.map +1 -1
  28. package/dist/drizzle/d1-proofs.d.mts +1 -1
  29. package/dist/drizzle/d1-sessions.d.mts +1 -1
  30. package/dist/drizzle/d1-sessions.mjs +4 -3
  31. package/dist/drizzle/d1-sessions.mjs.map +1 -1
  32. package/dist/drizzle/d1-totp.d.mts +3 -3
  33. package/dist/drizzle/d1-totp.mjs.map +1 -1
  34. package/dist/drizzle/mysql-identity.mjs +2 -2
  35. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  36. package/dist/drizzle/oauth-accounts.mjs +9 -8
  37. package/dist/drizzle/oauth-accounts.mjs.map +1 -1
  38. package/dist/drizzle/oauth-connected-collection.mjs +1 -1
  39. package/dist/drizzle/oauth-connected-collection.mjs.map +1 -1
  40. package/dist/drizzle/oauth-connected-custody.mjs +5 -4
  41. package/dist/drizzle/oauth-connected-custody.mjs.map +1 -1
  42. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  43. package/dist/drizzle/oauth-connected-flow.mjs +8 -7
  44. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -1
  45. package/dist/drizzle/oauth-connected-management.mjs +3 -2
  46. package/dist/drizzle/oauth-connected-management.mjs.map +1 -1
  47. package/dist/drizzle/oauth-connected-reference.mjs +8 -4
  48. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  49. package/dist/drizzle/oauth-connected-settlement.mjs +4 -4
  50. package/dist/drizzle/oauth-connected-settlement.mjs.map +1 -1
  51. package/dist/drizzle/oauth-connected-sign-in.mjs +11 -10
  52. package/dist/drizzle/oauth-connected-sign-in.mjs.map +1 -1
  53. package/dist/drizzle/oauth-connected-state.d.mts +265 -265
  54. package/dist/drizzle/oauth-connected-state.mjs +23 -20
  55. package/dist/drizzle/oauth-connected-state.mjs.map +1 -1
  56. package/dist/drizzle/oauth-connected-target.d.mts +105 -105
  57. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  58. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  59. package/dist/drizzle/oauth-flow.mjs +1 -1
  60. package/dist/drizzle/oauth-flow.mjs.map +1 -1
  61. package/dist/drizzle/oauth-model.d.mts +4 -4
  62. package/dist/drizzle/oauth-model.mjs.map +1 -1
  63. package/dist/drizzle/oauth-registration.mjs +2 -2
  64. package/dist/drizzle/oauth-registration.mjs.map +1 -1
  65. package/dist/drizzle/oauth-state.mjs +11 -12
  66. package/dist/drizzle/oauth-state.mjs.map +1 -1
  67. package/dist/drizzle/oauth-target.d.mts +4 -4
  68. package/dist/drizzle/oauth-target.mjs.map +1 -1
  69. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  70. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  71. package/dist/drizzle/passkey-write-target.d.mts +1 -1
  72. package/dist/drizzle/password-prepared-sql.mjs +1 -1
  73. package/dist/drizzle/password-prepared-sql.mjs.map +1 -1
  74. package/dist/drizzle/password-prepared-state.mjs +1 -1
  75. package/dist/drizzle/pg-identity.mjs +2 -2
  76. package/dist/drizzle/pg-identity.mjs.map +1 -1
  77. package/dist/drizzle/phone-model.d.mts +2 -2
  78. package/dist/drizzle/phone-model.mjs +3 -3
  79. package/dist/drizzle/phone-model.mjs.map +1 -1
  80. package/dist/drizzle/phone-target.d.mts +1 -1
  81. package/dist/drizzle/phone-target.mjs +2 -2
  82. package/dist/drizzle/phone-target.mjs.map +1 -1
  83. package/dist/drizzle/sqlite-do-database.d.mts +29 -0
  84. package/dist/drizzle/sqlite-do-database.mjs +45 -0
  85. package/dist/drizzle/sqlite-do-database.mjs.map +1 -0
  86. package/dist/drizzle/sqlite-emails.mjs.map +1 -1
  87. package/dist/drizzle/sqlite-identity.mjs +2 -2
  88. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  89. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  90. package/dist/drizzle/sqlite-proofs.mjs.map +1 -1
  91. package/dist/drizzle/sqlite-sessions.mjs.map +1 -1
  92. package/dist/drizzle/storage-validation.mjs +17 -9
  93. package/dist/drizzle/storage-validation.mjs.map +1 -1
  94. package/dist/drizzle/totp-state.mjs +8 -8
  95. package/dist/drizzle/totp-state.mjs.map +1 -1
  96. package/dist/drizzle/totp-target.d.mts +1 -1
  97. package/dist/drizzle/totp-target.mjs +2 -2
  98. package/dist/drizzle/totp-target.mjs.map +1 -1
  99. package/package.json +1 -1
  100. package/src/SqliteDo.ts +63 -70
  101. package/src/drizzle/crypto.ts +14 -0
  102. package/src/drizzle/d1-identity.ts +2 -2
  103. package/src/drizzle/d1-oauth.ts +14 -5
  104. package/src/drizzle/d1-passkey.ts +5 -1
  105. package/src/drizzle/d1-password-prepared.ts +11 -5
  106. package/src/drizzle/d1-passwords.ts +1 -1
  107. package/src/drizzle/d1-phone.ts +2 -1
  108. package/src/drizzle/d1-sessions.ts +8 -2
  109. package/src/drizzle/d1-totp.ts +6 -2
  110. package/src/drizzle/mysql-identity.ts +2 -2
  111. package/src/drizzle/oauth-accounts.ts +14 -13
  112. package/src/drizzle/oauth-connected-collection.ts +1 -1
  113. package/src/drizzle/oauth-connected-custody.ts +8 -6
  114. package/src/drizzle/oauth-connected-drivers.ts +9 -3
  115. package/src/drizzle/oauth-connected-flow.ts +6 -6
  116. package/src/drizzle/oauth-connected-management.ts +1 -1
  117. package/src/drizzle/oauth-connected-reference.ts +14 -9
  118. package/src/drizzle/oauth-connected-settlement.ts +4 -4
  119. package/src/drizzle/oauth-connected-sign-in.ts +11 -10
  120. package/src/drizzle/oauth-connected-state.ts +40 -25
  121. package/src/drizzle/oauth-connected-target.ts +9 -5
  122. package/src/drizzle/oauth-drivers.ts +13 -5
  123. package/src/drizzle/oauth-flow.ts +1 -1
  124. package/src/drizzle/oauth-model.ts +4 -4
  125. package/src/drizzle/oauth-registration.ts +2 -2
  126. package/src/drizzle/oauth-state.ts +17 -15
  127. package/src/drizzle/oauth-target.ts +11 -3
  128. package/src/drizzle/passkey-drivers.ts +7 -3
  129. package/src/drizzle/passkey-write-drivers.ts +11 -2
  130. package/src/drizzle/password-prepared-sql.ts +1 -1
  131. package/src/drizzle/pg-identity.ts +2 -2
  132. package/src/drizzle/phone-model.ts +3 -3
  133. package/src/drizzle/phone-target.ts +11 -4
  134. package/src/drizzle/sqlite-do-database.ts +87 -0
  135. package/src/drizzle/sqlite-emails.ts +1 -1
  136. package/src/drizzle/sqlite-identity.ts +2 -2
  137. package/src/drizzle/sqlite-passwords.ts +1 -1
  138. package/src/drizzle/sqlite-proofs.ts +2 -2
  139. package/src/drizzle/sqlite-sessions.ts +2 -2
  140. package/src/drizzle/storage-validation.ts +15 -6
  141. package/src/drizzle/totp-state.ts +8 -8
  142. package/src/drizzle/totp-target.ts +11 -5
@@ -6,7 +6,7 @@ import {
6
6
  OAuthSignInPersistence,
7
7
  } from "@yielded/auth/OAuth";
8
8
  import type { Table } from "drizzle-orm";
9
- import { Effect, Context } from "effect";
9
+ import { type Crypto, Effect, Context } from "effect";
10
10
 
11
11
  import { nativeDatabase } from "./native-database";
12
12
  import { makeOAuthConnectedTarget } from "./oauth-connected-drivers";
@@ -70,6 +70,7 @@ export const makeOAuthTarget = <
70
70
  A,
71
71
  OAuthCoordinatorError<E> | DatabaseError,
72
72
  | (Synchronous extends true ? never : Exclude<R, OAuthSignInPersistence>)
73
+ | Crypto.Crypto
73
74
  | LifecycleHooks
74
75
  | DatabaseRequirements
75
76
  >;
@@ -103,6 +104,7 @@ export const makeOAuthTarget = <
103
104
  A,
104
105
  OAuthCoordinatorError<E> | DatabaseError,
105
106
  | (Synchronous extends true ? never : Exclude<R, OAuthSignInPersistence | TxId>)
107
+ | Crypto.Crypto
106
108
  | LifecycleHooks
107
109
  | DatabaseRequirements
108
110
  >;
@@ -131,7 +133,7 @@ export const makeOAuthTarget = <
131
133
  ): Effect.Effect<
132
134
  A,
133
135
  OAuthCoordinatorError<E> | DatabaseError,
134
- Exclude<R, OAuthSignInPersistence> | LifecycleHooks | DatabaseRequirements
136
+ Exclude<R, OAuthSignInPersistence> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements
135
137
  > {
136
138
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
137
139
  coordinateTargetOAuthSignIn<
@@ -183,6 +185,7 @@ export const makeOAuthTarget = <
183
185
  A,
184
186
  OAuthCoordinatorError<E> | DatabaseError,
185
187
  | (Synchronous extends true ? never : Exclude<R, OAuthRegistrationIntents>)
188
+ | Crypto.Crypto
186
189
  | LifecycleHooks
187
190
  | DatabaseRequirements
188
191
  >;
@@ -218,6 +221,7 @@ export const makeOAuthTarget = <
218
221
  A,
219
222
  OAuthCoordinatorError<E> | DatabaseError,
220
223
  | (Synchronous extends true ? never : Exclude<R, OAuthRegistrationIntents | TxId>)
224
+ | Crypto.Crypto
221
225
  | LifecycleHooks
222
226
  | DatabaseRequirements
223
227
  >;
@@ -248,7 +252,7 @@ export const makeOAuthTarget = <
248
252
  ): Effect.Effect<
249
253
  A,
250
254
  OAuthCoordinatorError<E> | DatabaseError,
251
- Exclude<R, OAuthRegistrationIntents> | LifecycleHooks | DatabaseRequirements
255
+ Exclude<R, OAuthRegistrationIntents> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements
252
256
  > {
253
257
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
254
258
  coordinateTargetOAuthRegistrationIntents<
@@ -304,6 +308,7 @@ export const makeOAuthTarget = <
304
308
  A,
305
309
  OAuthCoordinatorError<E> | DatabaseError,
306
310
  | (Synchronous extends true ? never : Exclude<R, OAuthAccountsPersistence>)
311
+ | Crypto.Crypto
307
312
  | LifecycleHooks
308
313
  | DatabaseRequirements
309
314
  >;
@@ -339,6 +344,7 @@ export const makeOAuthTarget = <
339
344
  A,
340
345
  OAuthCoordinatorError<E> | DatabaseError,
341
346
  | (Synchronous extends true ? never : Exclude<R, OAuthAccountsPersistence | TxId>)
347
+ | Crypto.Crypto
342
348
  | LifecycleHooks
343
349
  | DatabaseRequirements
344
350
  >;
@@ -369,7 +375,7 @@ export const makeOAuthTarget = <
369
375
  ): Effect.Effect<
370
376
  A,
371
377
  OAuthCoordinatorError<E> | DatabaseError,
372
- Exclude<R, OAuthAccountsPersistence> | LifecycleHooks | DatabaseRequirements
378
+ Exclude<R, OAuthAccountsPersistence> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements
373
379
  > {
374
380
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
375
381
  coordinateTargetOAuthAccounts<
@@ -428,6 +434,7 @@ export const makeOAuthTarget = <
428
434
  A,
429
435
  OAuthCoordinatorError<E> | DatabaseError,
430
436
  | (Synchronous extends true ? never : Exclude<R, TargetId>)
437
+ | Crypto.Crypto
431
438
  | LifecycleHooks
432
439
  | DatabaseRequirements
433
440
  >;
@@ -462,6 +469,7 @@ export const makeOAuthTarget = <
462
469
  A,
463
470
  OAuthCoordinatorError<E> | DatabaseError,
464
471
  | (Synchronous extends true ? never : Exclude<R, TargetId | TxId>)
472
+ | Crypto.Crypto
465
473
  | LifecycleHooks
466
474
  | DatabaseRequirements
467
475
  >;
@@ -495,7 +503,7 @@ export const makeOAuthTarget = <
495
503
  ): Effect.Effect<
496
504
  A,
497
505
  OAuthCoordinatorError<E> | DatabaseError,
498
- Exclude<R, TargetId> | LifecycleHooks | DatabaseRequirements
506
+ Exclude<R, TargetId> | Crypto.Crypto | LifecycleHooks | DatabaseRequirements
499
507
  > {
500
508
  return Effect.flatMap(nativeDatabase(acquire), (database) =>
501
509
  coordinateTargetOAuthRegistration<
@@ -301,7 +301,7 @@ export const currentSubject = (mapping: any, nativeId: unknown) =>
301
301
  export const discoverOwned = (mapping: any, identity: OAuthVerifiedExternalIdentity["identity"]) =>
302
302
  Effect.flatMap(CurrentOAuthTransaction, (owner) =>
303
303
  Effect.gen(function* () {
304
- const key = oauthIdentityKey(identity),
304
+ const key = yield* oauthIdentityKey(identity),
305
305
  o = mapping.ownership;
306
306
 
307
307
  const where = both(eq(col(o.table, o.identityKey), key), o.ownedCondition);
@@ -8,7 +8,7 @@ import type {
8
8
  import type { AuthenticationFactor } from "@yielded/auth/Operations";
9
9
  import type { AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
10
10
  import type { InferInsertModel, InferSelectModel, SQL, Table } from "drizzle-orm";
11
- import type { Effect } from "effect";
11
+ import type { Crypto, Effect, PlatformError } from "effect";
12
12
 
13
13
  import type { PersistenceMappingError, SubjectIdCodec } from "./model";
14
14
 
@@ -506,8 +506,8 @@ export const oauthCleanupTable = <T extends Table, N>(
506
506
  Object.freeze({ table: input.table, subjectId: input.subjectId, condition: input.condition });
507
507
 
508
508
  /** A stable application/reference lock acquired before an ownership target.
509
- * Missing rows retain ownership. Conditions are synchronous and use detached
510
- * native IDs; every writer of the guarded reference follows the same order. */
509
+ * Missing rows retain ownership. Conditions use detached native IDs; every
510
+ * writer of the guarded reference follows the same order. */
511
511
  export interface OAuthReferenceGuardTable<T extends Table, N> {
512
512
  readonly table: T;
513
513
  readonly orderBy: Column<T>;
@@ -515,7 +515,7 @@ export interface OAuthReferenceGuardTable<T extends Table, N> {
515
515
  readonly identity: typeof OAuthExternalIdentity.Type;
516
516
  readonly identityKey: string;
517
517
  readonly subjectId: N;
518
- }) => SQL;
518
+ }) => SQL | Effect.Effect<SQL, PlatformError.PlatformError, Crypto.Crypto>;
519
519
  }
520
520
 
521
521
  export interface OAuthReferenceGuardDescriptor<N> {
@@ -40,7 +40,7 @@ export const readTuple = (ownership: any, identity: typeof OAuthExternalIdentity
40
40
  Effect.flatMap(CurrentOAuthTransaction, (owner) =>
41
41
  Effect.gen(function* () {
42
42
  const t = ownership.tuple,
43
- key = oauthIdentityKey(identity);
43
+ key = yield* oauthIdentityKey(identity);
44
44
 
45
45
  const where = { [t.identityKey]: key };
46
46
  const observed = yield* owner.read(t.table, equal(t.table, where), { limit: 1 });
@@ -259,7 +259,7 @@ export const inspectIntent = (
259
259
  row[i.reference] === intent.reference &&
260
260
  row[i.flowId] === c.flowId &&
261
261
  row[i.claimId] === intent.claimId &&
262
- row[i.identityKey] === oauthIdentityKey(intent.identity) &&
262
+ row[i.identityKey] === (yield* oauthIdentityKey(intent.identity)) &&
263
263
  mapping.clock.decodeInstant(row[i.expiresAt]) === intent.expiresAtMillis &&
264
264
  mapping.clock.decodeInstant(row[i.retentionUntil]) === intent.retentionUntilMillis,
265
265
  );
@@ -1,7 +1,3 @@
1
- // oxlint-disable-next-line import/extensions -- Noble exposes only its explicit .js subpath.
2
- import { sha256 } from "@noble/hashes/sha2.js";
3
- // oxlint-disable-next-line import/extensions -- Noble exposes only its explicit .js subpath.
4
- import { randomBytes } from "@noble/hashes/utils.js";
5
1
  import {
6
2
  type OAuthActionAuthorization,
7
3
  type OAuthAccountRevision,
@@ -10,9 +6,12 @@ import {
10
6
  snapshotOAuthSync,
11
7
  } from "@yielded/auth/OAuth";
12
8
  import type { AuthenticationRequirement } from "@yielded/auth/Sessions";
13
- import { DateTime, Effect, Schema } from "effect";
9
+ import { Crypto, DateTime, Effect, Schema } from "effect";
14
10
  import { Base64Url } from "effect/encoding";
15
11
 
12
+ import { digest, randomId } from "./crypto";
13
+ export { digest } from "./crypto";
14
+
16
15
  const encoder = new TextEncoder();
17
16
  const decoder = new TextDecoder("utf-8", { fatal: true });
18
17
 
@@ -22,8 +21,7 @@ export const invariant: (value: unknown) => asserts value = (value) => {
22
21
  if (!value) throw unavailable();
23
22
  };
24
23
 
25
- export const nonce = () => Base64Url.encode(randomBytes(32));
26
- export const digest = (value: string) => Base64Url.encode(sha256(encoder.encode(value)));
24
+ export const nonce = randomId;
27
25
 
28
26
  /** Capture configuration/callback references while preserving Drizzle table, SQL
29
27
  * and Effect objects. Never freeze or mutate the consumer's original graph. */
@@ -57,7 +55,9 @@ export const captureOAuthMapping = <A>(input: A): A => {
57
55
 
58
56
  /** Versioned length-delimited UTF-8 tuple; the module is deliberately excluded.
59
57
  * Reject ill-formed UTF-16 rather than letting UTF-8 replacement alias identities. */
60
- export const oauthIdentityKey = (input: typeof OAuthExternalIdentity.Type): string => {
58
+ export const oauthIdentityKey = Effect.fnUntraced(function* (
59
+ input: typeof OAuthExternalIdentity.Type,
60
+ ) {
61
61
  const identity = snapshotOAuthSync(OAuthExternalIdentity, input);
62
62
 
63
63
  const fields = [
@@ -85,8 +85,10 @@ export const oauthIdentityKey = (input: typeof OAuthExternalIdentity.Type): stri
85
85
  offset += 4 + value.length;
86
86
  }
87
87
 
88
- return "v1:" + Base64Url.encode(sha256(packed));
89
- };
88
+ const crypto = yield* Crypto.Crypto;
89
+
90
+ return "v1:" + Base64Url.encode(yield* crypto.digest("SHA-256", packed));
91
+ });
90
92
 
91
93
  export const sameIdentity = (
92
94
  left: typeof OAuthExternalIdentity.Type,
@@ -148,7 +150,7 @@ export const satisfies = (
148
150
  );
149
151
  };
150
152
 
151
- export const validAction = (
153
+ export const validAction = Effect.fnUntraced(function* (
152
154
  authorization: OAuthActionAuthorization,
153
155
  expected: {
154
156
  readonly moduleId: string;
@@ -161,12 +163,12 @@ export const validAction = (
161
163
  currentRequirement: AuthenticationRequirement,
162
164
  now: number,
163
165
  maximumAgeMillis: number,
164
- ) => {
166
+ ) {
165
167
  const challenge = authorization.challenge;
166
168
  const evidence = authorization.evidence;
167
- const intentDigest = digest(expected.intent);
169
+ const intentDigest = yield* digest(expected.intent);
168
170
 
169
- const binding = digest(
171
+ const binding = yield* digest(
170
172
  // oxlint-disable-next-line no-restricted-properties -- Fixed private action fingerprint format shared with the core verifier.
171
173
  JSON.stringify([
172
174
  "effect-auth/oauth-action/v1",
@@ -213,4 +215,4 @@ export const validAction = (
213
215
 
214
216
  return satisfies(fresh, requirement);
215
217
  });
216
- };
218
+ });
@@ -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(
@@ -752,7 +752,7 @@ export const makeSqlPasswordPreparedPersistence = Effect.fn("makeSqlPasswordPrep
752
752
  nativeNow = p.encodeInstant(now);
753
753
 
754
754
  const scope = identifierScope({
755
- credential: authority.snapshot,
755
+ ...(authority.snapshot === undefined ? {} : { credential: authority.snapshot }),
756
756
  revision: authority.revision,
757
757
  });
758
758
 
@@ -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];