@yielded/auth-persistence 0.1.0-beta.10 → 0.1.0-beta.12

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 (85) hide show
  1. package/README.md +7 -7
  2. package/dist/Adapter.d.mts +2 -2
  3. package/dist/Adapter.mjs +4 -4
  4. package/dist/index.d.mts +1 -2
  5. package/dist/index.mjs +1 -2
  6. package/dist/internal/PasswordPreparedPostconditions.d.mts +1 -1
  7. package/dist/internal/configuration.d.mts +2 -2
  8. package/dist/internal/configuration.mjs.map +1 -1
  9. package/dist/internal/email-kernel.d.mts +6 -6
  10. package/dist/internal/email-kernel.mjs +8 -6
  11. package/dist/internal/email-kernel.mjs.map +1 -1
  12. package/dist/internal/models/email-model.d.mts +1 -1
  13. package/dist/internal/oauth-server.d.mts +1 -1
  14. package/dist/internal/oauth-server.mjs +2 -2
  15. package/dist/internal/oauth-server.mjs.map +1 -1
  16. package/dist/internal/passkey/state.mjs +3 -2
  17. package/dist/internal/passkey/state.mjs.map +1 -1
  18. package/dist/internal/passkey/target.d.mts +1 -1
  19. package/dist/internal/passkey/target.mjs +21 -12
  20. package/dist/internal/passkey/target.mjs.map +1 -1
  21. package/dist/internal/passkey/write-state.mjs +3 -2
  22. package/dist/internal/passkey/write-state.mjs.map +1 -1
  23. package/dist/internal/passkey/write-target.mjs +6 -6
  24. package/dist/internal/passkey/write-target.mjs.map +1 -1
  25. package/dist/internal/passkey-kernel.d.mts +13 -16
  26. package/dist/internal/passkeys.mjs +9 -8
  27. package/dist/internal/passkeys.mjs.map +1 -1
  28. package/dist/internal/password-kernel.d.mts +4 -4
  29. package/dist/internal/password-kernel.mjs +3 -2
  30. package/dist/internal/password-kernel.mjs.map +1 -1
  31. package/dist/internal/persistence.d.mts +4 -3
  32. package/dist/internal/persistence.mjs +23 -27
  33. package/dist/internal/persistence.mjs.map +1 -1
  34. package/dist/internal/proof-kernel.d.mts +3 -3
  35. package/dist/internal/proof-kernel.mjs +2 -1
  36. package/dist/internal/proof-kernel.mjs.map +1 -1
  37. package/dist/internal/query-operations.d.mts +1 -1
  38. package/dist/internal/registration.mjs +6 -1
  39. package/dist/internal/registration.mjs.map +1 -1
  40. package/dist/internal/session-kernel.d.mts +20 -10
  41. package/dist/internal/session-kernel.mjs +155 -147
  42. package/dist/internal/session-kernel.mjs.map +1 -1
  43. package/dist/internal/sql-persistence.mjs +3 -4
  44. package/dist/internal/sql-persistence.mjs.map +1 -1
  45. package/dist/internal/sql-query.mjs +4 -2
  46. package/dist/internal/sql-query.mjs.map +1 -1
  47. package/dist/internal/standalone.d.mts +2 -4
  48. package/dist/internal/standalone.mjs +2 -2
  49. package/dist/internal/standalone.mjs.map +1 -1
  50. package/dist/internal/storage-mapping.mjs +2 -2
  51. package/dist/internal/storage-validation.mjs +4 -2
  52. package/dist/internal/storage-validation.mjs.map +1 -1
  53. package/dist/internal/transaction-execution-kernel.d.mts +8 -9
  54. package/dist/internal/transaction-execution-kernel.mjs +9 -7
  55. package/dist/internal/transaction-execution-kernel.mjs.map +1 -1
  56. package/dist/internal/transaction-kernel.d.mts +9 -5
  57. package/dist/internal/transaction-kernel.mjs +5 -2
  58. package/dist/internal/transaction-kernel.mjs.map +1 -1
  59. package/package.json +1 -1
  60. package/src/Adapter.ts +6 -1
  61. package/src/index.ts +0 -1
  62. package/src/internal/configuration.ts +1 -1
  63. package/src/internal/email-kernel.ts +31 -36
  64. package/src/internal/oauth-server.ts +2 -2
  65. package/src/internal/passkey/state.ts +3 -2
  66. package/src/internal/passkey/target.ts +37 -44
  67. package/src/internal/passkey/write-state.ts +3 -2
  68. package/src/internal/passkey/write-target.ts +5 -15
  69. package/src/internal/passkeys.ts +5 -10
  70. package/src/internal/password-kernel.ts +3 -3
  71. package/src/internal/persistence.ts +78 -69
  72. package/src/internal/proof-kernel.ts +3 -3
  73. package/src/internal/query-operations.ts +1 -1
  74. package/src/internal/registration.ts +22 -7
  75. package/src/internal/session-kernel.ts +1123 -1140
  76. package/src/internal/sql-persistence.ts +2 -2
  77. package/src/internal/sql-query.ts +6 -4
  78. package/src/internal/standalone.ts +6 -8
  79. package/src/internal/storage-validation.ts +20 -16
  80. package/src/internal/transaction-execution-kernel.ts +20 -18
  81. package/src/internal/transaction-kernel.ts +10 -4
  82. package/dist/internal/oauth-app.d.mts +0 -11
  83. package/dist/internal/oauth-app.mjs +0 -57
  84. package/dist/internal/oauth-app.mjs.map +0 -1
  85. package/src/internal/oauth-app.ts +0 -82
@@ -34,7 +34,7 @@ import {
34
34
  type SecurityRevision,
35
35
  } from "@yielded/auth/Sessions";
36
36
  import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
37
- import type * as SqlError from "effect/unstable/sql/SqlError";
37
+ import type * as SqlError from "effect/sql/SqlError";
38
38
 
39
39
  import { PersistenceMappingError } from "./mapping-error";
40
40
  import {
@@ -1121,10 +1121,10 @@ export const makePasswordKernel = <
1121
1121
  });
1122
1122
 
1123
1123
  const makeSqlPasswordPersistence = Effect.fn("makeSqlPasswordPersistence")(function* (
1124
- database: Database,
1125
1124
  mapping: Mapping,
1126
1125
  configuration: PasswordSqlConfiguration,
1127
- ): Effect.fn.Return<PasswordPersistence["Service"], never, LifecycleHooks> {
1126
+ ): Effect.fn.Return<PasswordPersistence["Service"], never, LifecycleHooks | CurrentPasswordSql> {
1127
+ const database = yield* CurrentPasswordSql;
1128
1128
  const hooks = yield* LifecycleHooks;
1129
1129
 
1130
1130
  return PasswordPersistence.of({
@@ -8,7 +8,7 @@ import { PhoneAdmission, PhoneSignInTargets, PhoneOtpUnavailable } from "@yielde
8
8
  import { ProofPersistence, ProofUnavailable } from "@yielded/auth/Proofs";
9
9
  import { AuthenticationAuthority, SessionUnavailable } from "@yielded/auth/Sessions";
10
10
  import { Context, Effect, Layer, Schema } from "effect";
11
- import { SqlClient } from "effect/unstable/sql";
11
+ import { SqlClient } from "effect/sql";
12
12
 
13
13
  import {
14
14
  PersistenceConfigurationError,
@@ -27,14 +27,23 @@ import {
27
27
  type PasskeyRequirement,
28
28
  type MappingInput,
29
29
  } from "./configuration";
30
- import { makeEmailKernel, type EmailSqlDatabase } from "./email-kernel";
30
+ import { makeEmailKernel, CurrentEmailSql, type EmailSqlDatabase } from "./email-kernel";
31
31
  import { PersistenceMappingError } from "./mapping-error";
32
- import { makePasswordKernel, type PasswordSqlDatabase } from "./password-kernel";
32
+ import {
33
+ makePasswordKernel,
34
+ CurrentPasswordSql,
35
+ type PasswordSqlDatabase,
36
+ } from "./password-kernel";
33
37
  import { makePhoneKernel, CurrentPhoneTransaction } from "./phone-kernel";
34
- import { makeProofKernel, type ProofSqlDatabase, type ProofSqlQuery } from "./proof-kernel";
38
+ import {
39
+ makeProofKernel,
40
+ CurrentProofSql,
41
+ type ProofSqlDatabase,
42
+ type ProofSqlQuery,
43
+ } from "./proof-kernel";
35
44
  import type { QueryOperations } from "./query-operations";
36
- import { makeRegistrationAuthority, type CreateSubject } from "./registration";
37
- import { makeSessionKernel, type SessionSqlDatabase } from "./session-kernel";
45
+ import { makeRegistrationAuthority } from "./registration";
46
+ import { makeSessionKernel, CurrentSessionSql, type SessionSqlDatabase } from "./session-kernel";
38
47
  import { requireStandalone } from "./standalone";
39
48
  import { makeMappings } from "./storage-mapping";
40
49
  import {
@@ -45,7 +54,11 @@ import {
45
54
  } from "./storage-tables";
46
55
  import { validateStorage } from "./storage-validation";
47
56
  import { makeTransactionExecutionKernel } from "./transaction-execution-kernel";
48
- import { makeTransactionKernel, type TransactionNativeDatabase } from "./transaction-kernel";
57
+ import {
58
+ makeTransactionKernel,
59
+ NativeDatabase,
60
+ type TransactionNativeDatabase,
61
+ } from "./transaction-kernel";
49
62
 
50
63
  export interface Backend<T extends object, R> {
51
64
  readonly makeTable: (definition: StorageTable) => T;
@@ -53,7 +66,6 @@ export interface Backend<T extends object, R> {
53
66
  readonly acquire: Effect.Effect<object, PersistenceConfigurationError, R | SqlClient.SqlClient>;
54
67
  readonly operations: QueryOperations;
55
68
  readonly passkeys: (input: {
56
- readonly database: object;
57
69
  readonly storage: MappingInput;
58
70
  readonly namespace: string;
59
71
  readonly dialect: "pg" | "sqlite";
@@ -62,7 +74,7 @@ export interface Backend<T extends object, R> {
62
74
  }) => Effect.Effect<
63
75
  Context.Context<never>,
64
76
  PersistenceConfigurationError,
65
- PasskeyConfig | LifecycleHooks | SqlClient.SqlClient
77
+ PasskeyConfig | LifecycleHooks | SqlClient.SqlClient | NativeDatabase | CurrentProofSql
66
78
  >;
67
79
  }
68
80
 
@@ -165,14 +177,6 @@ export const createPersistence = <T extends object, R>(
165
177
  `@yielded/auth-persistence/${auth.namespace}/Provisioning`,
166
178
  );
167
179
 
168
- // Capability metadata controls the conditional requirement, and core has
169
- // already decoded each registration with its strategy's Schema.
170
- const provisioners = management
171
- ? ProvisioningKey.pipe(
172
- Effect.map((value) => value as Readonly<Record<string, CreateSubject>>),
173
- )
174
- : Effect.succeed<Readonly<Record<string, CreateSubject>>>({});
175
-
176
180
  const layout = <N, Instant>(
177
181
  options: {
178
182
  readonly subjects: SubjectOptions<T, N>;
@@ -317,14 +321,15 @@ export const createPersistence = <T extends object, R>(
317
321
  if (columns[name] === undefined)
318
322
  return yield* configError(`Missing ${role}.${name} column`);
319
323
  }
320
- yield* validateStorage(client, dialect, description, storageTables[role].unique);
324
+ yield* validateStorage(dialect, description, storageTables[role].unique);
321
325
  }
322
- yield* validateStorage(client, dialect, backend.describe(storage.subjects.table as T), [
326
+ yield* validateStorage(dialect, backend.describe(storage.subjects.table as T), [
323
327
  [storage.subjects.id],
324
328
  ]);
325
329
  const mappings = makeMappings(storage);
326
330
 
327
- const standalone = <E>(error: () => E) => requireStandalone(error, client);
331
+ const standalone = <E>(error: () => E) =>
332
+ requireStandalone(error, client.transactionService);
328
333
 
329
334
  // Backend validation owns the foreign query-builder shape, never the decoded rows.
330
335
  const native = database as ProofSqlDatabase &
@@ -341,17 +346,16 @@ export const createPersistence = <T extends object, R>(
341
346
 
342
347
  const sessionMapping = mappings.sessions(auth.claims);
343
348
 
344
- const sessionServices = yield* sessionKernel.makeSqlStatefulSessions(
345
- native,
346
- sessionMapping,
347
- options,
348
- );
349
+ const sessionServices = yield* sessionKernel
350
+ .makeSqlStatefulSessions(sessionMapping, options)
351
+ .pipe(Effect.provideService(CurrentSessionSql, native));
349
352
 
350
- const authority = yield* sessionKernel.makeSqlAuthenticationAuthority<C["Type"]>(
351
- native,
352
- { ...mappings.authority(), isConstraintConflict: () => false },
353
- options,
354
- );
353
+ const authority = yield* sessionKernel
354
+ .makeSqlAuthenticationAuthority<C["Type"]>(
355
+ { ...mappings.authority(), isConstraintConflict: () => false },
356
+ options,
357
+ )
358
+ .pipe(Effect.provideService(CurrentSessionSql, native));
355
359
 
356
360
  let context: Context.Context<never> = Context.make(AuthenticationAuthority, authority).pipe(
357
361
  Context.add(
@@ -377,47 +381,40 @@ export const createPersistence = <T extends object, R>(
377
381
  context = Context.add(
378
382
  context,
379
383
  ProofPersistence,
380
- yield* proofKernel.makeSqlProofPersistence(
381
- native,
382
- proofConfiguration.mapping,
383
- proofConfiguration.configuration,
384
- ),
384
+ yield* proofKernel
385
+ .makeSqlProofPersistence(proofConfiguration.mapping, proofConfiguration.configuration)
386
+ .pipe(Effect.provideService(CurrentProofSql, native)),
385
387
  );
386
388
  }
387
389
 
388
390
  if (password) {
389
- const persistence = yield* passwordKernel.makeSqlPasswordPersistence(
390
- native,
391
- mappings.passwords(),
392
- {
391
+ const persistence = yield* passwordKernel
392
+ .makeSqlPasswordPersistence(mappings.passwords(), {
393
393
  mode: "interactive",
394
394
  locking: dialect === "pg",
395
395
  standaloneGuard: standalone(() => PasswordUnavailable.make({})),
396
396
  insertIfAbsent: (query) => query.onConflictDoNothing(),
397
397
  proof: proofConfiguration,
398
- },
399
- );
398
+ })
399
+ .pipe(Effect.provideService(CurrentPasswordSql, native));
400
400
 
401
401
  context = Context.add(context, PasswordPersistence, persistence);
402
402
  }
403
- const creators = yield* provisioners;
404
-
405
403
  for (const [name, strategy] of Object.entries(auth.strategies)) {
406
404
  if (strategy.persistence?.kind !== "password" || !strategy.persistence.management)
407
405
  continue;
408
406
  const key = strategy.RegistrationAuthority;
409
- const create = creators[name];
410
407
 
411
- if (key === undefined || create === undefined)
408
+ if (key === undefined)
412
409
  return yield* configError(`Missing subject provisioning for ${name}`);
413
410
 
414
411
  const registration = yield* makeRegistrationAuthority(
415
- native,
416
412
  mappings,
417
413
  backend.operations,
418
414
  standalone(() => PasswordUnavailable.make({})),
419
- create,
420
- );
415
+ ProvisioningKey,
416
+ name,
417
+ ).pipe(Effect.provideService(CurrentPasswordSql, native));
421
418
 
422
419
  context = Context.add(context, key, registration);
423
420
  }
@@ -425,12 +422,14 @@ export const createPersistence = <T extends object, R>(
425
422
  context = Context.add(
426
423
  context,
427
424
  EmailAddressPersistence,
428
- yield* emailKernel.makeSqlEmailAddressPersistence(native, mappings.emails(), {
429
- mode: "interactive",
430
- locking: dialect === "pg",
431
- standaloneGuard: standalone(() => EmailUnavailable.make({})),
432
- proof: proofConfiguration,
433
- }),
425
+ yield* emailKernel
426
+ .makeSqlEmailAddressPersistence(mappings.emails(), {
427
+ mode: "interactive",
428
+ locking: dialect === "pg",
429
+ standaloneGuard: standalone(() => EmailUnavailable.make({})),
430
+ proof: proofConfiguration,
431
+ })
432
+ .pipe(Effect.provideService(CurrentEmailSql, native)),
434
433
  );
435
434
  }
436
435
  if (phone) {
@@ -486,18 +485,19 @@ export const createPersistence = <T extends object, R>(
486
485
  encodeInstant: storage.encodeInstant,
487
486
  });
488
487
 
489
- const execution = executionKernel.makeTransactionExecution(
490
- CurrentPhoneTransaction,
491
- native,
492
- {
493
- mode: "interactive",
494
- dialect,
495
- locking: dialect === "pg",
496
- standaloneGuard: () => standalone(() => PhoneOtpUnavailable.make({})),
497
- },
498
- () => PhoneOtpUnavailable.make({}),
499
- randomId,
500
- );
488
+ const execution = yield* executionKernel
489
+ .makeTransactionExecution(
490
+ CurrentPhoneTransaction,
491
+ {
492
+ mode: "interactive",
493
+ dialect,
494
+ locking: dialect === "pg",
495
+ standaloneGuard: () => standalone(() => PhoneOtpUnavailable.make({})),
496
+ },
497
+ () => PhoneOtpUnavailable.make({}),
498
+ randomId,
499
+ )
500
+ .pipe(Effect.provideService(NativeDatabase, native));
501
501
 
502
502
  const run = <Value, E, Requirements>(
503
503
  work: Effect.Effect<Value, E, Requirements>,
@@ -538,7 +538,6 @@ export const createPersistence = <T extends object, R>(
538
538
  if (passkeys.length > 0) {
539
539
  // Auth capability metadata determines whether PasskeyConfig is required.
540
540
  const services = backend.passkeys({
541
- database,
542
541
  storage,
543
542
  namespace: auth.namespace,
544
543
  dialect,
@@ -549,10 +548,20 @@ export const createPersistence = <T extends object, R>(
549
548
  }) as Effect.Effect<
550
549
  Context.Context<never>,
551
550
  PersistenceConfigurationError,
552
- PasskeyRequirement<A> | LifecycleHooks | SqlClient.SqlClient
551
+ | PasskeyRequirement<A>
552
+ | LifecycleHooks
553
+ | SqlClient.SqlClient
554
+ | NativeDatabase
555
+ | CurrentProofSql
553
556
  >;
554
557
 
555
- context = Context.merge(context, yield* services);
558
+ context = Context.merge(
559
+ context,
560
+ yield* services.pipe(
561
+ Effect.provideService(NativeDatabase, native),
562
+ Effect.provideService(CurrentProofSql, native),
563
+ ),
564
+ );
556
565
  }
557
566
 
558
567
  // The checked capability metadata above determines exactly these service keys.
@@ -22,7 +22,7 @@ import {
22
22
  ProofUnavailable,
23
23
  } from "@yielded/auth/Proofs";
24
24
  import { Cause, Context, DateTime, Effect, Option, Schema } from "effect";
25
- import type * as SqlError from "effect/unstable/sql/SqlError";
25
+ import type * as SqlError from "effect/sql/SqlError";
26
26
 
27
27
  import { PersistenceMappingError } from "./mapping-error";
28
28
  import {
@@ -659,10 +659,10 @@ export const makeProofKernel = <
659
659
  };
660
660
 
661
661
  const makeSqlProofPersistence = Effect.fn("makeSqlProofPersistence")(function* (
662
- database: Database,
663
662
  mapping: Mapping,
664
663
  configuration: ProofSqlConfiguration,
665
- ): Effect.fn.Return<ProofPersistence["Service"], never, LifecycleHooks> {
664
+ ): Effect.fn.Return<ProofPersistence["Service"], never, LifecycleHooks | CurrentProofSql> {
665
+ const database = yield* CurrentProofSql;
666
666
  const hooks = yield* LifecycleHooks;
667
667
 
668
668
  return ProofPersistence.of({
@@ -1,4 +1,4 @@
1
- import type { Statement } from "effect/unstable/sql/Statement";
1
+ import type { Statement } from "effect/sql/Statement";
2
2
 
3
3
  /** Adapter-owned table metadata. No driver or ORM type enters the shared models. */
4
4
  export interface TableModel {
@@ -5,16 +5,17 @@ import { PasswordUnavailable } from "@yielded/auth/Password";
5
5
  import { reportPersistenceFailure } from "@yielded/auth/Persistence";
6
6
  import type { SubjectId } from "@yielded/auth/Schema";
7
7
  import { SecurityRevision } from "@yielded/auth/Sessions";
8
- import { Effect, Schema } from "effect";
8
+ import { type Context, Effect, Schema } from "effect";
9
9
 
10
- import type { PasswordSqlDatabase } from "./password-kernel";
10
+ import { PersistenceConfigurationError } from "./configuration";
11
+ import { CurrentPasswordSql } from "./password-kernel";
11
12
  import type { QueryOperations } from "./query-operations";
12
13
  import type { PasswordRegistrationAuthority } from "./registration-contract";
13
14
  import type { makeMappings } from "./storage-mapping";
14
15
 
15
16
  class IdentifierTaken extends Schema.TaggedError<IdentifierTaken>()("IdentifierTaken", {}) {}
16
17
 
17
- export type CreateSubject = (input: {
18
+ type CreateSubject = (input: {
18
19
  readonly identifier: LoginIdentifier;
19
20
  readonly registration: unknown;
20
21
  }) => Effect.Effect<SubjectId, PasswordUnavailable>;
@@ -23,14 +24,28 @@ export type CreateSubject = (input: {
23
24
  * creates the unverified identifier and password in one SQL transaction. Replays
24
25
  * always suppress; a public request ID never recovers a private password intent.
25
26
  */
26
- export const makeRegistrationAuthority = Effect.fn("makeRegistrationAuthority")(function* (
27
- database: PasswordSqlDatabase,
27
+ export const makeRegistrationAuthority = Effect.fn("makeRegistrationAuthority")(function* <R>(
28
28
  mappings: ReturnType<typeof makeMappings>,
29
29
  operations: QueryOperations,
30
30
  standalone: Effect.Effect<void, PasswordUnavailable>,
31
- createSubject: CreateSubject,
32
- ): Effect.fn.Return<PasswordRegistrationAuthority<unknown>, never, LifecycleHooks> {
31
+ provisioning: Context.Key<R, object>,
32
+ strategy: string,
33
+ ): Effect.fn.Return<
34
+ PasswordRegistrationAuthority<unknown>,
35
+ PersistenceConfigurationError,
36
+ LifecycleHooks | CurrentPasswordSql | R
37
+ > {
38
+ const database = yield* CurrentPasswordSql;
33
39
  const hooks = yield* LifecycleHooks;
40
+ // Core has already decoded registration with the selected strategy's Schema;
41
+ // the dynamic strategy table erases only that heterogeneous callback signature.
42
+ const creators = (yield* provisioning) as Readonly<Record<string, CreateSubject>>;
43
+ const createSubject = creators[strategy];
44
+
45
+ if (createSubject === undefined)
46
+ return yield* PersistenceConfigurationError.make({
47
+ reason: `Missing subject provisioning for ${strategy}`,
48
+ });
34
49
  const { and, eq, column } = operations;
35
50
  const mapping = mappings.passwords();
36
51
  const receipts = mappings.table("passwordRegistrations");