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

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 +38 -38
  4. package/dist/Mysql2.d.mts +51 -51
  5. package/dist/Pglite.d.mts +51 -51
  6. package/dist/Postgres.d.mts +38 -38
  7. package/dist/SqliteBun.d.mts +51 -51
  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 +44 -44
  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 +9 -9
  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
@@ -45,7 +45,7 @@ export const pending = Effect.fn("oauthConnected.pending")(function* (
45
45
  row[f.flowId] === c.flowId &&
46
46
  row[f.commandId] === c.commandId &&
47
47
  mapping.subjectId.equals(native, row[f.subjectId]) &&
48
- row[f.clientKey] === S.clientKey(S.configuration(flow)) &&
48
+ row[f.clientKey] === (yield* S.clientKey(S.configuration(flow))) &&
49
49
  row[f.stateDigest] === c.stateDigest &&
50
50
  mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
51
51
  mapping.clock.decodeInstant(row[f.retentionUntil]) === flow.retentionUntilMillis,
@@ -238,7 +238,7 @@ export const inspectPrepared = Effect.fn("oauthConnected.inspectPrepared")(funct
238
238
  row[f.flowId] === c.flowId &&
239
239
  row[f.commandId] === c.commandId &&
240
240
  mapping.subjectId.equals(found.nativeId, row[f.subjectId]) &&
241
- row[f.clientKey] === S.clientKey(S.configuration(flow)) &&
241
+ row[f.clientKey] === (yield* S.clientKey(S.configuration(flow))) &&
242
242
  row[f.stateDigest] === c.stateDigest &&
243
243
  mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
244
244
  mapping.clock.decodeInstant(row[f.retentionUntil]) === flow.retentionUntilMillis,
@@ -356,7 +356,7 @@ export const claim = Effect.fn("oauthConnected.claim")(function* (
356
356
  {
357
357
  [f.state]: "Claimed",
358
358
  [f.claimId]: claimed.claimId,
359
- [f.claimDigest]: digest(S.claimStorage.encode(claimed)),
359
+ [f.claimDigest]: yield* digest(S.claimStorage.encode(claimed)),
360
360
  [f.claimOrder]: mapping.order.encode(order),
361
361
  [f.claimedAt]: mapping.clock.encodeInstant(now),
362
362
  [f.claimExpiresAt]: mapping.clock.encodeInstant(expires),
@@ -392,7 +392,7 @@ export const exact = Effect.fn("oauthConnected.exactClaim")(function* (
392
392
  row === undefined ||
393
393
  row[f.work] !== "Unresolved" ||
394
394
  row[f.claimId] !== claim.claimId ||
395
- row[f.claimDigest] !== digest(S.claimStorage.encode(claim))
395
+ row[f.claimDigest] !== (yield* digest(S.claimStorage.encode(claim)))
396
396
  )
397
397
  return undefined;
398
398
  const native = yield* mapping.subjectId.toNative(c.revision.subjectId);
@@ -403,7 +403,7 @@ export const exact = Effect.fn("oauthConnected.exactClaim")(function* (
403
403
  row[f.stateDigest] === c.stateDigest &&
404
404
  mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
405
405
  mapping.clock.decodeInstant(row[f.retentionUntil]) === claim.flow.retentionUntilMillis &&
406
- row[f.clientKey] === S.clientKey(S.configuration(claim.flow)) &&
406
+ row[f.clientKey] === (yield* S.clientKey(S.configuration(claim.flow))) &&
407
407
  S.nativeOrder(mapping, row[f.claimOrder]) === S.orderNumber(claim.order) &&
408
408
  mapping.clock.decodeInstant(row[f.claimedAt]) === claim.claimedAtMillis &&
409
409
  mapping.clock.decodeInstant(row[f.claimExpiresAt]) === claim.claimExpiresAtMillis,
@@ -448,7 +448,7 @@ export const inspectTuple = Effect.fn("oauthConnected.inspectTuple")(function* (
448
448
  const owner = yield* CurrentOAuthTransaction;
449
449
 
450
450
  const t = mapping.ownership.tuple,
451
- key = oauthIdentityKey(identity);
451
+ key = yield* oauthIdentityKey(identity);
452
452
 
453
453
  const read = yield* owner.read(t.table, equal(t.table, { [t.identityKey]: key }), { limit: 1 });
454
454
  const row = read.rows[0];
@@ -341,7 +341,7 @@ export const disconnect = Effect.fn("oauthConnected.disconnect")(function* (
341
341
  [d.grantId]: token.grantId,
342
342
  [d.intent]: S.commandStorage.encode({
343
343
  context: token,
344
- originalDigest: digest(S.disconnectStorage.encode(input.grant)),
344
+ originalDigest: yield* digest(S.disconnectStorage.encode(input.grant)),
345
345
  ...(input.revocation === undefined
346
346
  ? {}
347
347
  : { revocationJobId: input.revocation.context.jobId }),
@@ -1,6 +1,7 @@
1
1
  import { eq, is, sql, type SQL, type Table } from "drizzle-orm";
2
2
  import { MySqlTable } from "drizzle-orm/mysql-core/table";
3
3
  import { PgTable } from "drizzle-orm/pg-core/table";
4
+ import { Effect } from "effect";
4
5
 
5
6
  import type { OAuthConnectedMapping } from "./oauth-connected-model";
6
7
  import { scopeKey, type Mapping } from "./oauth-connected-state";
@@ -52,15 +53,19 @@ export const oauthConnectedOwnershipReferences = <
52
53
  table: c.table,
53
54
  orderBy: c.clientKey,
54
55
  condition: (input) =>
55
- both(
56
- equal(c.table, {
57
- [c.clientKey]: scopeKey(input.identity.provider, input.identity.issuer),
58
- [c.counter]: mapping.order.encode(0),
59
- }),
60
- exactText(c.table, c.provider, input.identity.provider),
61
- exactText(c.table, c.issuer, input.identity.issuer),
62
- exactText(c.table, c.clientRegistrationId, ""),
63
- ),
56
+ Effect.gen(function* () {
57
+ const key = yield* scopeKey(input.identity.provider, input.identity.issuer);
58
+
59
+ return both(
60
+ equal(c.table, {
61
+ [c.clientKey]: key,
62
+ [c.counter]: mapping.order.encode(0),
63
+ }),
64
+ exactText(c.table, c.provider, input.identity.provider),
65
+ exactText(c.table, c.issuer, input.identity.issuer),
66
+ exactText(c.table, c.clientRegistrationId, ""),
67
+ );
68
+ }),
64
69
  }),
65
70
  ],
66
71
  connectedReference: (input) => connectedReferenceCondition(mapping, input.identityKey),
@@ -46,9 +46,9 @@ export const activateGrant = Effect.fn("oauthConnected.activateGrant")(function*
46
46
  const owner = yield* CurrentOAuthTransaction;
47
47
  const token = grant.context;
48
48
  const g = mapping.grant;
49
- const identityKey = oauthIdentityKey(token.identity);
50
- const clientKey = S.clientKey(token.configuration);
51
- const cohortKey = S.cohortKey(clientKey, identityKey);
49
+ const identityKey = yield* oauthIdentityKey(token.identity);
50
+ const clientKey = yield* S.clientKey(token.configuration);
51
+ const cohortKey = yield* S.cohortKey(clientKey, identityKey);
52
52
 
53
53
  const values = {
54
54
  ...g.encodeInsert({
@@ -143,7 +143,7 @@ export const settle = Effect.fn("oauthConnected.settle")(function* (
143
143
  token.grantId === c.grantId &&
144
144
  token.identity.provider === c.provider &&
145
145
  token.identity.issuer === c.issuer &&
146
- S.clientKey(token.configuration) === cl.id &&
146
+ (yield* S.clientKey(token.configuration)) === cl.id &&
147
147
  S.tokenContextStorage.encode({
148
148
  ...token,
149
149
  configuration: S.configuration(input.claim.flow),
@@ -83,7 +83,7 @@ export const claimSignIn = Effect.fn("oauthConnected.claimSignIn")(function* (
83
83
  [f.stateDigest]: c.stateDigest,
84
84
  [f.snapshot]: null,
85
85
  [f.claimId]: claim.claimId,
86
- [f.claimDigest]: digest(reservationStorage.encode(reservation)),
86
+ [f.claimDigest]: yield* digest(reservationStorage.encode(reservation)),
87
87
  [f.claimOrder]: mapping.order.encode(order),
88
88
  [f.claimedAt]: mapping.clock.encodeInstant(claim.claimedAtMillis),
89
89
  [f.claimExpiresAt]: mapping.clock.encodeInstant(claim.claimExpiresAtMillis),
@@ -116,13 +116,13 @@ const exact = Effect.fn("oauthConnected.exactSignIn")(function* (
116
116
  row === undefined ||
117
117
  row[f.work] !== "Unresolved" ||
118
118
  row[f.claimId] !== reservation.claim.claimId ||
119
- row[f.claimDigest] !== digest(reservationStorage.encode(reservation))
119
+ row[f.claimDigest] !== (yield* digest(reservationStorage.encode(reservation)))
120
120
  )
121
121
  return undefined;
122
122
  invariant(
123
123
  row[f.subjectId] === null &&
124
124
  row[f.commandId] === c.commandId &&
125
- row[f.clientKey] === S.clientKey(reservation.configuration) &&
125
+ row[f.clientKey] === (yield* S.clientKey(reservation.configuration)) &&
126
126
  S.nativeOrder(mapping, row[f.claimOrder]) === S.orderNumber(reservation.order),
127
127
  );
128
128
  const now = yield* owner.now(mapping.clock);
@@ -164,7 +164,7 @@ const credential = Effect.fn("oauthConnected.signInCredential")(function* (
164
164
  [c.moduleId]: context.moduleId,
165
165
  [c.credentialId]: input.credentialId,
166
166
  [c.subjectId]: found.nativeId,
167
- [c.identityKey]: oauthIdentityKey(input.identity),
167
+ [c.identityKey]: yield* oauthIdentityKey(input.identity),
168
168
  [c.credentialRevision]: input.credentialRevision,
169
169
  }),
170
170
  c.activeCondition,
@@ -242,18 +242,19 @@ const slot = Effect.fn("oauthConnected.signInSlot")(function* (
242
242
 
243
243
  /** Orders belong to one client registration. Moving a profile to another client
244
244
  * requires a newer profile generation; retired clients cannot replace it later. */
245
- const canReplace = (
245
+ const canReplace = Effect.fnUntraced(function* (
246
246
  reservation: M.OAuthSignInAccessClaim,
247
247
  previous: M.OAuthConnectedTokenContext,
248
- ) => {
248
+ ) {
249
249
  const generation = reservation.configuration.profile.generation;
250
250
  const oldGeneration = previous.configuration.profile.generation;
251
251
 
252
- return S.clientKey(reservation.configuration) === S.clientKey(previous.configuration)
252
+ return (yield* S.clientKey(reservation.configuration)) ===
253
+ (yield* S.clientKey(previous.configuration))
253
254
  ? generation >= oldGeneration &&
254
255
  S.orderNumber(reservation.order) > S.orderNumber(previous.exchangeOrder)
255
256
  : generation > oldGeneration;
256
- };
257
+ });
257
258
 
258
259
  export const inspectSignIn = Effect.fn("oauthConnected.inspectSignIn")(function* (
259
260
  mapping: S.Mapping,
@@ -293,7 +294,7 @@ export const inspectSignIn = Effect.fn("oauthConnected.inspectSignIn")(function*
293
294
  co.blocked ||
294
295
  S.orderNumber(r.order) <= co.cutoff ||
295
296
  previous?.row[mapping.grant.refreshWork] === "Unresolved" ||
296
- (previous !== undefined && !canReplace(r, previous.context));
297
+ (previous !== undefined && !(yield* canReplace(r, previous.context)));
297
298
 
298
299
  return snapshotOAuthSync(M.OAuthSignInAccessInspection, {
299
300
  _tag: quarantine ? "Quarantine" : "Target",
@@ -377,7 +378,7 @@ export const settleSignIn = Effect.fn("oauthConnected.settleSignIn")(function* (
377
378
  F.sameTarget(out.previous, previous.context) &&
378
379
  sameIdentity(previous.context.identity, token.identity) &&
379
380
  previous.row[mapping.grant.refreshWork] !== "Unresolved" &&
380
- canReplace(r, previous.context);
381
+ (yield* canReplace(r, previous.context));
381
382
 
382
383
  const previousSafe = yield* C.noFormerOwner(mapping, tuple.key, cl.id, native);
383
384
 
@@ -107,11 +107,11 @@ export const nativeOrder = (mapping: Authority, value: unknown) =>
107
107
  const encoder = new TextEncoder();
108
108
  const decoder = new TextDecoder("utf-8", { fatal: true });
109
109
 
110
- const key = (domain: string, fields: ReadonlyArray<string>) => {
110
+ const key = Effect.fnUntraced(function* (domain: string, fields: ReadonlyArray<string>) {
111
111
  for (const value of fields) invariant(decoder.decode(encoder.encode(value)) === value);
112
112
 
113
- return "v1:" + digest(strings.encode([domain, ...fields]));
114
- };
113
+ return "v1:" + (yield* digest(strings.encode([domain, ...fields])));
114
+ });
115
115
 
116
116
  export const clientKey = (configuration: M.OAuthConnectedConfiguration) =>
117
117
  key("effect-auth/oauth-connected-client/v1", [
@@ -124,7 +124,9 @@ export const cohortKey = (client: string, identity: string) =>
124
124
  key("effect-auth/oauth-connected-cohort/v1", [client, identity]);
125
125
 
126
126
  export const initialGeneration = (cohort: string) =>
127
- M.OAuthConnectedTarget.fields.cohortGeneration.make("initial:" + digest(cohort));
127
+ digest(cohort).pipe(
128
+ Effect.map((value) => M.OAuthConnectedTarget.fields.cohortGeneration.make("initial:" + value)),
129
+ );
128
130
 
129
131
  export const configuration = (flow: M.OAuthConnectedPendingFlow): M.OAuthConnectedConfiguration =>
130
132
  snapshotOAuthSync(M.OAuthConnectedConfiguration, flow.context);
@@ -254,9 +256,9 @@ export const action = Effect.fn("oauthConnected.action")(function* (
254
256
  ? "connected-disconnect"
255
257
  : "connected-complete";
256
258
 
257
- const intentDigest = digest(expected.intent);
259
+ const intentDigest = yield* digest(expected.intent);
258
260
 
259
- const binding = digest(
261
+ const binding = yield* digest(
260
262
  strings.encode([
261
263
  "effect-auth/oauth-connected-action/v1",
262
264
  expected.moduleId,
@@ -364,7 +366,9 @@ export const useAuthority = Effect.fn("oauthConnected.useAuthority")(function* (
364
366
  });
365
367
 
366
368
  export const scopeKey = (provider: string, issuer: string) =>
367
- "scope:" + key("effect-auth/oauth-connected-provider-issuer/v1", [provider, issuer]);
369
+ key("effect-auth/oauth-connected-provider-issuer/v1", [provider, issuer]).pipe(
370
+ Effect.map((value) => "scope:" + value),
371
+ );
368
372
 
369
373
  export interface ScopeAnchor {
370
374
  readonly id: string;
@@ -412,7 +416,7 @@ export const scope = Effect.fn("oauthConnected.scope")(function* (
412
416
  const owner = yield* CurrentOAuthTransaction;
413
417
 
414
418
  const c = mapping.client,
415
- id = scopeKey(config.provider, config.issuer),
419
+ id = yield* scopeKey(config.provider, config.issuer),
416
420
  cache = yield* anchors(scopeCache, c.table);
417
421
 
418
422
  const existing = cache.get(id);
@@ -497,7 +501,7 @@ export const readScope = Effect.fn("oauthConnected.readScope")(function* (
497
501
  const owner = yield* CurrentOAuthTransaction;
498
502
 
499
503
  const c = mapping.client,
500
- id = scopeKey(provider, issuer),
504
+ id = yield* scopeKey(provider, issuer),
501
505
  cache = yield* anchors(scopeCache, c.table),
502
506
  cached = cache.get(id);
503
507
 
@@ -549,12 +553,12 @@ export const lockClientById = Effect.fn("oauthConnected.lockClientById")(functio
549
553
  row !== undefined &&
550
554
  row[c.clientRegistrationId] !== "" &&
551
555
  id ===
552
- key("effect-auth/oauth-connected-client/v1", [
556
+ (yield* key("effect-auth/oauth-connected-client/v1", [
553
557
  row[c.provider],
554
558
  row[c.issuer],
555
559
  row[c.clientRegistrationId],
556
- ]) &&
557
- scope.id === scopeKey(row[c.provider], row[c.issuer]),
560
+ ])) &&
561
+ scope.id === (yield* scopeKey(row[c.provider], row[c.issuer])),
558
562
  );
559
563
  const found = { id, row, counter: nativeOrder(mapping, row[c.counter]), scope };
560
564
 
@@ -563,10 +567,15 @@ export const lockClientById = Effect.fn("oauthConnected.lockClientById")(functio
563
567
  return found;
564
568
  });
565
569
 
566
- export const heldScope = (mapping: Authority, provider: string, issuer: string) =>
567
- Effect.map(anchors(scopeCache, mapping.client.table), (cache) =>
568
- cache.get(scopeKey(provider, issuer)),
569
- );
570
+ export const heldScope = Effect.fnUntraced(function* (
571
+ mapping: Authority,
572
+ provider: string,
573
+ issuer: string,
574
+ ) {
575
+ const cache = yield* anchors(scopeCache, mapping.client.table);
576
+
577
+ return cache.get(yield* scopeKey(provider, issuer));
578
+ });
570
579
 
571
580
  /** Cleanup obtains every scope then every client before entering any tuple. */
572
581
  export const prelockClients = Effect.fn("oauthConnected.prelockClients")(function* (
@@ -583,7 +592,7 @@ export const prelockClients = Effect.fn("oauthConnected.prelockClients")(functio
583
592
  const scopes = new Map<string, { provider: string; issuer: string; required: boolean }>();
584
593
 
585
594
  for (const value of releaseScopes)
586
- scopes.set(scopeKey(value.provider, value.issuer), { ...value, required: false });
595
+ scopes.set(yield* scopeKey(value.provider, value.issuer), { ...value, required: false });
587
596
  for (const id of unique) {
588
597
  const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), {
589
598
  limit: 1,
@@ -593,7 +602,7 @@ export const prelockClients = Effect.fn("oauthConnected.prelockClients")(functio
593
602
  row = read.rows[0];
594
603
 
595
604
  invariant(row !== undefined);
596
- scopes.set(scopeKey(row[c.provider], row[c.issuer]), {
605
+ scopes.set(yield* scopeKey(row[c.provider], row[c.issuer]), {
597
606
  provider: row[c.provider],
598
607
  issuer: row[c.issuer],
599
608
  required: true,
@@ -629,7 +638,7 @@ export const client = Effect.fn("oauthConnected.client")(function* (
629
638
  const owner = yield* CurrentOAuthTransaction;
630
639
 
631
640
  const c = mapping.client,
632
- id = clientKey(config),
641
+ id = yield* clientKey(config),
633
642
  where = { [c.clientKey]: id };
634
643
 
635
644
  const heldScope = yield* scope(mapping, config, create);
@@ -723,7 +732,7 @@ export const cohort = Effect.fn("oauthConnected.cohort")(function* (
723
732
  const owner = yield* CurrentOAuthTransaction;
724
733
 
725
734
  const c = mapping.cohort,
726
- id = cohortKey(clientId, identityId),
735
+ id = yield* cohortKey(clientId, identityId),
727
736
  where = { [c.cohortKey]: id };
728
737
 
729
738
  let read = yield* owner.read(c.table, equal(c.table, where), { limit: 1 });
@@ -755,7 +764,7 @@ export const cohort = Effect.fn("oauthConnected.cohort")(function* (
755
764
  ...where,
756
765
  [c.clientKey]: clientId,
757
766
  [c.identityKey]: identityId,
758
- [c.generation]: initialGeneration(id),
767
+ [c.generation]: yield* initialGeneration(id),
759
768
  [c.cutoff]: mapping.order.encode(0),
760
769
  [c.state]: "Open",
761
770
  [c.version]: owner.marker,
@@ -769,7 +778,13 @@ export const cohort = Effect.fn("oauthConnected.cohort")(function* (
769
778
  const row = read.rows[0];
770
779
 
771
780
  if (row === undefined)
772
- return { id, row: undefined, generation: initialGeneration(id), cutoff: 0, blocked: false };
781
+ return {
782
+ id,
783
+ row: undefined,
784
+ generation: yield* initialGeneration(id),
785
+ cutoff: 0,
786
+ blocked: false,
787
+ };
773
788
  invariant(
774
789
  row[c.clientKey] === clientId &&
775
790
  row[c.identityKey] === identityId &&
@@ -816,9 +831,9 @@ export const readGrant = Effect.fn("oauthConnected.readGrant")(function* (
816
831
  context.moduleId === moduleId &&
817
832
  context.grantId === grantId &&
818
833
  mapping.subjectId.equals(native, row[g.subjectId]) &&
819
- row[g.identityKey] === oauthIdentityKey(context.identity) &&
820
- row[g.clientKey] === clientKey(context.configuration) &&
821
- row[g.cohortKey] === cohortKey(row[g.clientKey], row[g.identityKey]) &&
834
+ row[g.identityKey] === (yield* oauthIdentityKey(context.identity)) &&
835
+ row[g.clientKey] === (yield* clientKey(context.configuration)) &&
836
+ row[g.cohortKey] === (yield* cohortKey(row[g.clientKey], row[g.identityKey])) &&
822
837
  row[g.profileKey] === context.configuration.profile.key &&
823
838
  row[g.grantVersion] === context.grantVersion &&
824
839
  row[g.tokenVersion] === context.tokenVersion &&
@@ -8,7 +8,7 @@ import {
8
8
  type PrepareOAuthCommit,
9
9
  } from "@yielded/auth/OAuth";
10
10
  /* oxlint-disable no-explicit-any -- concrete adapters retain the native database, table and ID types. */
11
- import { Effect, Layer, Schema } from "effect";
11
+ import { type Crypto, Effect, Layer, type PlatformError, Schema } from "effect";
12
12
  import type { Statement } from "effect/sql/Statement";
13
13
 
14
14
  import type { PersistenceMappingError } from "./model";
@@ -94,8 +94,10 @@ const mutation =
94
94
  input: Input,
95
95
  ) => Effect.Effect<
96
96
  Value,
97
- import("@yielded/auth/OAuth").OAuthUnavailable | PersistenceMappingError,
98
- CurrentOAuthTransaction
97
+ | import("@yielded/auth/OAuth").OAuthUnavailable
98
+ | PersistenceMappingError
99
+ | PlatformError.PlatformError,
100
+ CurrentOAuthTransaction | Crypto.Crypto
99
101
  >,
100
102
  schema: Schema.Codec<Value, Encoded, never, never>,
101
103
  ) =>
@@ -111,8 +113,10 @@ const read =
111
113
  input: Input,
112
114
  ) => Effect.Effect<
113
115
  Value,
114
- import("@yielded/auth/OAuth").OAuthUnavailable | PersistenceMappingError,
115
- CurrentOAuthTransaction
116
+ | import("@yielded/auth/OAuth").OAuthUnavailable
117
+ | PersistenceMappingError
118
+ | PlatformError.PlatformError,
119
+ CurrentOAuthTransaction | Crypto.Crypto
116
120
  >,
117
121
  ) =>
118
122
  (input: Input) =>
@@ -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
+ });