{"version":3,"file":"session-model.mjs","names":[],"sources":["../../../src/internal/models/session-model.ts"],"sourcesContent":["import type { TokenDigest } from \"@yielded/auth/Schema\";\nimport type {\n  AuthenticationRequirement,\n  SecurityRevision,\n  SessionId,\n  AuthenticationFlowId,\n  PendingAuthenticationRecord,\n  StatefulSessionRecord,\n} from \"@yielded/auth/Sessions\";\nimport type * as DateTime from \"effect/DateTime\";\nimport type * as Effect from \"effect/Effect\";\n\nimport type { TableModel as Table, SqlExpression } from \"../table-model\";\nimport type { PersistenceMappingError, SubjectIdCodec } from \"./common\";\nimport type { ProofClock } from \"./proof-model\";\n\ntype ColumnKey<T extends Table> = T[\"column\"];\n\nexport interface SessionIdCodec<NativeId> {\n  readonly toNative: (id: SessionId) => Effect.Effect<NativeId, PersistenceMappingError>;\n  readonly toSession: (id: NativeId) => Effect.Effect<SessionId, PersistenceMappingError>;\n  readonly equals: (left: NativeId, right: NativeId) => boolean;\n}\n\nexport interface SessionSubjectTables<Subject extends Table, NativeId> {\n  readonly subject: {\n    readonly table: Subject[\"table\"];\n    readonly id: ColumnKey<Subject>;\n    readonly status: ColumnKey<Subject>;\n    readonly securityRevision: ColumnKey<Subject>;\n    readonly activeStatusValue: unknown;\n    readonly isActiveStatus: (value: unknown) => boolean;\n    readonly decodeRequirement: (\n      row: Subject[\"select\"],\n    ) => Effect.Effect<AuthenticationRequirement, PersistenceMappingError>;\n    /** Required for D1 authority writes. Declare every mutable subject input of\n     * decodeRequirement; [] explicitly declares a constant requirement. */\n    readonly requirementColumns?: ReadonlyArray<ColumnKey<Subject>>;\n    readonly nextSecurityRevision?: (\n      current: SecurityRevision,\n    ) => Effect.Effect<SecurityRevision, PersistenceMappingError>;\n    readonly nextSecurityRevisionSync?: (current: SecurityRevision) => SecurityRevision;\n  };\n  readonly subjectId: SubjectIdCodec<NativeId>;\n}\n\nexport interface SessionAuthorityTables<\n  Subject extends Table,\n  Credential extends Table,\n  NativeId,\n> extends SessionSubjectTables<Subject, NativeId> {\n  readonly credential: {\n    readonly table: Credential[\"table\"];\n    readonly subjectId: ColumnKey<Credential>;\n    readonly credentialId: ColumnKey<Credential>;\n    readonly revision: ColumnKey<Credential>;\n    /** Omitted status means every row is an active credential. */\n    readonly status?: ColumnKey<Credential>;\n    readonly activeStatusValue?: unknown;\n    readonly isActiveStatus?: (value: unknown) => boolean;\n  };\n}\n\nexport interface SessionExecution<Expression extends SqlExpression = SqlExpression> {\n  /** Exact session module scope for pending rows and revocation tombstones. */\n  readonly moduleId: string;\n  readonly clock: ProofClock<Expression>;\n  /** D1 mappings must use the authoritative primary database. */\n  readonly d1?: { readonly primary: true };\n  /** Match only the declared unique keys, never generic SQL failures. */\n  readonly isConstraintConflict: (cause: unknown) => boolean;\n}\n\nexport interface StatefulSessionTables<\n  Claims,\n  Session extends Table,\n  NativeSubjectId,\n  NativeSessionId,\n> {\n  readonly session: {\n    readonly table: Session[\"table\"];\n    readonly sessionId: ColumnKey<Session>;\n    readonly subjectId: ColumnKey<Session>;\n    readonly digest: ColumnKey<Session>;\n    readonly securityRevision: ColumnKey<Session>;\n    readonly issuedAt: ColumnKey<Session>;\n    readonly expiresAt: ColumnKey<Session>;\n    readonly absoluteExpiresAt: ColumnKey<Session>;\n    readonly encodeInstant: (instant: DateTime.Utc) => unknown;\n    readonly encodeInsert: (\n      record: StatefulSessionRecord<Claims>,\n      ids: { readonly subjectId: NativeSubjectId; readonly sessionId: NativeSessionId },\n    ) => Session[\"insert\"];\n    /** Replace prepared metadata and retain exact private provenance. Digest binds the immutable source metadata. */\n    readonly encodeRotation: (record: StatefulSessionRecord<Claims>) => Partial<Session[\"insert\"]>;\n    readonly decode: (\n      row: Session[\"select\"],\n    ) => Effect.Effect<StatefulSessionRecord<Claims>, PersistenceMappingError>;\n    readonly allocateId?: Effect.Effect<NativeSessionId, PersistenceMappingError>;\n    readonly allocateIdSync?: () => NativeSessionId;\n  };\n  readonly sessionId: SessionIdCodec<NativeSessionId>;\n}\n\nexport interface SessionPendingInsert<NativeId> {\n  readonly moduleId: string;\n  readonly kind: \"Login\" | \"StepUp\";\n  readonly digest: TokenDigest;\n  readonly version: SecurityRevision;\n  readonly flowId: AuthenticationFlowId;\n  readonly subjectId: NativeId;\n  readonly bindingDigest: TokenDigest;\n  readonly snapshot: string;\n  readonly expiresAt: DateTime.Utc;\n  readonly attemptLimit: number;\n}\n\n/** One physical table and one SQL owner for Login and StepUp. The adapter sets\n * every guard column and binds kind before decoding, charging or consuming. */\nexport interface SessionPendingTables<Pending extends Table, NativeSubjectId> {\n  readonly pending: {\n    readonly table: Pending[\"table\"];\n    readonly moduleId: ColumnKey<Pending>;\n    readonly kind: ColumnKey<Pending>;\n    readonly digest: ColumnKey<Pending>;\n    readonly version: ColumnKey<Pending>;\n    readonly flowId: ColumnKey<Pending>;\n    readonly subjectId: ColumnKey<Pending>;\n    readonly bindingDigest: ColumnKey<Pending>;\n    readonly snapshot: ColumnKey<Pending>;\n    readonly expiresAt: ColumnKey<Pending>;\n    readonly attemptLimit: ColumnKey<Pending>;\n    readonly failedAttempts: ColumnKey<Pending>;\n    readonly consumed: ColumnKey<Pending>;\n    readonly encodeInstant: (instant: DateTime.Utc) => unknown;\n    readonly encodeInsert: (input: SessionPendingInsert<NativeSubjectId>) => Pending[\"insert\"];\n    readonly allocateVersion?: Effect.Effect<SecurityRevision, PersistenceMappingError>;\n    readonly allocateVersionSync?: () => SecurityRevision;\n  };\n}\n\nexport interface PendingAuthenticationTables<\n  Claims,\n  Pending extends Table,\n  NativeSubjectId,\n> extends SessionPendingTables<Pending, NativeSubjectId> {\n  /** Canonical JSON of the full record; the consumer Schema owns Claims encoding. */\n  readonly login: {\n    readonly encode: (\n      record: PendingAuthenticationRecord<Claims>,\n    ) => Effect.Effect<string, PersistenceMappingError>;\n    readonly decode: (\n      snapshot: unknown,\n    ) => Effect.Effect<PendingAuthenticationRecord<Claims>, PersistenceMappingError>;\n  };\n}\n\nexport interface SignedSessionValidityTables<\n  Tombstone extends Table,\n  NativeSubjectId,\n  NativeSessionId,\n> {\n  readonly tombstone: {\n    readonly table: Tombstone[\"table\"];\n    readonly moduleId: ColumnKey<Tombstone>;\n    readonly subjectId: ColumnKey<Tombstone>;\n    readonly sessionId: ColumnKey<Tombstone>;\n    readonly absoluteExpiresAt: ColumnKey<Tombstone>;\n    readonly encodeInstant: (instant: DateTime.Utc) => unknown;\n    readonly decodeInstant: (\n      native: unknown,\n    ) => Effect.Effect<DateTime.Utc, PersistenceMappingError>;\n    readonly encodeInsert: (input: {\n      readonly moduleId: string;\n      readonly subjectId: NativeSubjectId;\n      readonly sessionId: NativeSessionId;\n      readonly absoluteExpiresAt: DateTime.Utc;\n    }) => Tombstone[\"insert\"];\n  };\n  readonly sessionId: SessionIdCodec<NativeSessionId>;\n}\n\nexport const requiredSessionConstraints = { sessionDigest: \"unique(session.digest)\" } as const;\n\nexport const requiredPendingAuthenticationConstraints = {\n  pendingDigest: \"unique(pending.digest)\",\n} as const;\n\nexport const requiredStatefulPendingConstraints = {\n  ...requiredSessionConstraints,\n  ...requiredPendingAuthenticationConstraints,\n} as const;\n\nexport const requiredSignedValidityConstraints = {\n  tombstoneOwner: \"unique(tombstone.moduleId,tombstone.subjectId,tombstone.sessionId)\",\n} as const;\n\nexport type RequiredSessionConstraints = typeof requiredSessionConstraints;\n\nexport type RequiredPendingAuthenticationConstraints =\n  typeof requiredPendingAuthenticationConstraints;\n\nexport type RequiredStatefulPendingConstraints = typeof requiredStatefulPendingConstraints;\nexport type RequiredSignedValidityConstraints = typeof requiredSignedValidityConstraints;\n\nexport type AuthenticationAuthorityMapping<\n  Claims,\n  Subject extends Table,\n  Credential extends Table,\n  Pending extends Table,\n  NativeSubjectId,\n  Expression extends SqlExpression = SqlExpression,\n> = SessionAuthorityTables<Subject, Credential, NativeSubjectId> &\n  SessionExecution<Expression> &\n  (\n    | { readonly pending?: undefined }\n    | {\n        readonly pending: PendingAuthenticationTables<Claims, Pending, NativeSubjectId>;\n        readonly constraints: RequiredPendingAuthenticationConstraints;\n      }\n  );\n\nexport type StatefulSessionMapping<\n  Claims,\n  Subject extends Table,\n  Credential extends Table,\n  Session extends Table,\n  Pending extends Table,\n  NativeSubjectId,\n  NativeSessionId,\n  Expression extends SqlExpression = SqlExpression,\n> = SessionAuthorityTables<Subject, Credential, NativeSubjectId> &\n  StatefulSessionTables<Claims, Session, NativeSubjectId, NativeSessionId> &\n  SessionExecution<Expression> &\n  (\n    | { readonly pending?: undefined; readonly constraints: RequiredSessionConstraints }\n    | {\n        readonly pending: PendingAuthenticationTables<Claims, Pending, NativeSubjectId>;\n        readonly constraints: RequiredStatefulPendingConstraints;\n      }\n  );\n\nexport type PendingAuthenticationMapping<\n  Claims,\n  Subject extends Table,\n  Credential extends Table,\n  Pending extends Table,\n  NativeSubjectId,\n  Expression extends SqlExpression = SqlExpression,\n> = SessionAuthorityTables<Subject, Credential, NativeSubjectId> &\n  PendingAuthenticationTables<Claims, Pending, NativeSubjectId> &\n  SessionExecution<Expression> & { readonly constraints: RequiredPendingAuthenticationConstraints };\n\nexport type SignedSessionValidityMapping<\n  Subject extends Table,\n  Tombstone extends Table,\n  NativeSubjectId,\n  NativeSessionId,\n  Expression extends SqlExpression = SqlExpression,\n> = SessionSubjectTables<Subject, NativeSubjectId> &\n  SignedSessionValidityTables<Tombstone, NativeSubjectId, NativeSessionId> &\n  SessionExecution<Expression> & { readonly constraints: RequiredSignedValidityConstraints };\n\n/** Maintenance uses the same mapped rows and one total per-call deletion limit. */\nexport type SessionCleanupMapping<\n  Pending extends Table,\n  Tombstone extends Table,\n  NativeSubjectId,\n  NativeSessionId,\n  Expression extends SqlExpression = SqlExpression,\n> = Pick<SessionExecution<Expression>, \"moduleId\" | \"clock\" | \"d1\"> & {\n  readonly pending?: SessionPendingTables<Pending, NativeSubjectId>[\"pending\"];\n  readonly tombstone?: SignedSessionValidityTables<\n    Tombstone,\n    NativeSubjectId,\n    NativeSessionId\n  >[\"tombstone\"];\n};\n"],"mappings":";AAsLA,MAAa,6BAA6B,EAAE,eAAe,yBAAyB;AAEpF,MAAa,2CAA2C,EACtD,eAAe,yBACjB;AAEA,MAAa,qCAAqC;CAChD,GAAG;CACH,GAAG;AACL;AAEA,MAAa,oCAAoC,EAC/C,gBAAgB,qEAClB"}