{"version":3,"file":"native-mutation.mjs","names":[],"sources":["../../../src/internal/oauth/native-mutation.ts"],"sourcesContent":["import type { OAuthAccountRevision, OAuthExternalIdentity } from \"@yielded/auth/OAuth\";\nimport * as Cause from \"effect/Cause\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport * as SqlClient from \"effect/sql/SqlClient\";\nimport * as SqlError from \"effect/sql/SqlError\";\nimport type * as Statement from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"../d1-planning\";\nimport { PersistenceMappingError } from \"../mapping-error\";\nimport type { OAuthOwnershipTable } from \"../models/oauth-model\";\nimport type { NativeSqlTables } from \"../native-sql-table\";\nimport { exactSqlText, executeSqlChange } from \"../sql-change\";\nimport {\n  appendSqlBatchStatement,\n  registerSqlBatchPostcondition,\n  registerSqlPostcondition,\n} from \"../sql-commit\";\nimport type { TableModel } from \"../table-model\";\nimport { makeOAuthNativeAuthority, type OAuthNativeReadMapping } from \"./native-state\";\nimport { invalidState, oauthIdentityKey } from \"./state\";\n\nexport type OAuthNativeMutationMapping = Pick<\n  OAuthNativeReadMapping,\n  \"subject\" | \"authority\" | \"subjectId\" | \"clock\"\n> & {\n  readonly ownership: OAuthOwnershipTable<TableModel, unknown>;\n};\n\n/** Shared semantic predicates for native transactions and fixed D1 statements. */\nexport const makeOAuthNativeMutation = Effect.fnUntraced(function* (\n  tables: NativeSqlTables,\n  mapping: OAuthNativeMutationMapping,\n  batch: boolean,\n) {\n  const authority = yield* makeOAuthNativeAuthority(tables, mapping);\n  const sql = (yield* SqlClient.SqlClient).withoutTransforms();\n  const ownership = tables(mapping.ownership.table);\n  const o = mapping.ownership;\n  const s = mapping.subject;\n  const a = mapping.authority;\n\n  const exact = (key: string, value: unknown) =>\n    exactSqlText(sql, ownership.column(key), ownership.value(key, value));\n\n  const assert = Effect.fnUntraced(function* (condition: Statement.Fragment) {\n    if (batch) yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n    else {\n      const rows = yield* sql`select 1 where ${condition}`;\n\n      if (!(rows.length === 1)) return yield* invalidState();\n    }\n  });\n\n  const change = Effect.fnUntraced(function* (statement: Statement.Fragment, expected = 1) {\n    if (batch) {\n      yield* appendSqlBatchStatement(sql`${statement}`);\n      yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = ${expected}`));\n\n      return expected;\n    }\n\n    return yield* executeSqlChange(sql, statement);\n  });\n\n  const finish = (name: string, condition: Statement.Fragment) =>\n    batch\n      ? registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) })\n      : registerSqlPostcondition({\n          name,\n          check: Effect.gen(function* () {\n            const rows = yield* sql`select 1 where ${condition}`;\n\n            if (!(rows.length === 1))\n              return yield* PersistenceMappingError.make({\n                operation: \"postcondition\",\n                cause: invalidState(),\n              });\n          }),\n        });\n\n  const subjectCondition = (\n    nativeId: unknown,\n    revision: Pick<OAuthAccountRevision, \"securityRevision\">,\n  ) => {\n    const subject = authority.subject;\n\n    return sql.and([\n      sql`${subject.column(s.id)} = ${subject.value(s.id, nativeId)}`,\n      exactSqlText(\n        sql,\n        subject.column(s.securityRevision),\n        subject.value(s.securityRevision, revision.securityRevision),\n      ),\n      tables.expression(s.activeCondition),\n    ]);\n  };\n\n  const factorCondition = (nativeId: unknown, revision: OAuthAccountRevision) => {\n    const factors = authority.authority;\n    const factorOwner = sql`${factors.column(a.subjectId)} = ${factors.value(a.subjectId, nativeId)}`;\n    const active = tables.expression(a.activeCondition);\n\n    return sql.and([\n      sql`(select count(*) from ${factors.name} where ${factorOwner} and ${active}) = ${revision.credentials.length}`,\n      ...revision.credentials.map(\n        (item) => sql`exists(select 1 from ${factors.name} where ${factorOwner} and ${active}\n        and ${exactSqlText(sql, factors.column(a.credentialId), factors.value(a.credentialId, item.credentialId))}\n        and ${exactSqlText(sql, factors.column(a.revision), factors.value(a.revision, item.revision))})`,\n      ),\n    ]);\n  };\n\n  const authorityCondition = (nativeId: unknown, revision: OAuthAccountRevision) =>\n    sql.and([\n      sql`exists(select 1 from ${authority.subject.name} where ${subjectCondition(nativeId, revision)})`,\n      factorCondition(nativeId, revision),\n    ]);\n\n  const ownerCondition = (identityKey: string, nativeId: unknown) =>\n    sql.and([\n      exact(o.identityKey, identityKey),\n      sql`${ownership.column(o.subjectId)} = ${ownership.value(o.subjectId, nativeId)}`,\n    ]);\n\n  const insertUnique = (statement: Statement.Fragment) =>\n    change(statement).pipe((effect) =>\n      Effect.catchCauseIf(\n        effect,\n        (cause) =>\n          cause.reasons.length > 0 &&\n          cause.reasons.every(\n            (reason) =>\n              Cause.isFailReason(reason) &&\n              Schema.is(SqlError.SqlError)(reason.error) &&\n              reason.error.reason._tag === \"UniqueViolation\",\n          ),\n        () => Effect.succeed(0),\n      ),\n    );\n\n  const ensureOwnership = Effect.fnUntraced(function* (\n    identity: typeof OAuthExternalIdentity.Type,\n    nativeId: unknown,\n  ) {\n    const identityKey = yield* oauthIdentityKey(identity);\n\n    // An existing-owner mapping may deliberately reject creation in encodeInsert.\n    // Inspect ownership after the caller's subject lock before invoking that policy.\n    const rows =\n      yield* sql`select ${ownership.fields(\"identity_\")} from ${ownership.name} where ${exact(o.identityKey, identityKey)}`;\n\n    if (!(rows.length <= 1)) return yield* invalidState();\n    const row = rows[0] === undefined ? undefined : ownership.decode(rows[0], \"identity_\");\n\n    if (row !== undefined) {\n      if (\n        !(\n          row[o.provider] === identity.provider &&\n          row[o.issuer] === identity.issuer &&\n          row[o.externalSubject] === identity.subject\n        )\n      )\n        return yield* invalidState();\n      if (!mapping.subjectId.equals(o.decodeSubjectId(row), nativeId)) return undefined;\n      if (batch)\n        yield* assert(\n          sql`exists(select 1 from ${ownership.name} where ${ownerCondition(identityKey, nativeId)})`,\n        );\n\n      return identityKey;\n    }\n\n    const statement = sql`${ownership.insert({\n      ...o.encodeInsert({ identity, identityKey, subjectId: nativeId }),\n      [o.identityKey]: identityKey,\n      [o.provider]: identity.provider,\n      [o.issuer]: identity.issuer,\n      [o.externalSubject]: identity.subject,\n      [o.subjectId]: nativeId,\n    })} ${sql.onDialectOrElse({ mysql: () => sql``, orElse: () => sql`on conflict do nothing` })}`;\n\n    if (batch) {\n      yield* change(statement);\n\n      return identityKey;\n    }\n\n    const inserted = yield* insertUnique(statement);\n\n    if (inserted === 1) return identityKey;\n\n    const raced =\n      yield* sql`select ${ownership.fields(\"identity_\")} from ${ownership.name} where ${exact(o.identityKey, identityKey)}`;\n\n    if (!(raced.length === 1)) return yield* invalidState();\n    const committed = ownership.decode(raced[0]!, \"identity_\");\n\n    if (\n      !(\n        committed[o.provider] === identity.provider &&\n        committed[o.issuer] === identity.issuer &&\n        committed[o.externalSubject] === identity.subject\n      )\n    )\n      return yield* invalidState();\n\n    return mapping.subjectId.equals(o.decodeSubjectId(committed), nativeId)\n      ? identityKey\n      : undefined;\n  });\n\n  const execute = (statement: Statement.Fragment) =>\n    batch\n      ? appendSqlBatchStatement(sql`${statement}`)\n      : executeSqlChange(sql, statement).pipe(Effect.asVoid);\n\n  const releaseOwnership = (\n    identityKey: string,\n    nativeId: unknown,\n    referenced: Statement.Fragment,\n  ) =>\n    execute(\n      sql`delete from ${ownership.name} where ${ownerCondition(identityKey, nativeId)} and not (${referenced})`,\n    );\n\n  return {\n    ...authority,\n    sql,\n    ownership,\n    assert,\n    change,\n    finish,\n    authorityCondition,\n    subjectCondition,\n    factorCondition,\n    ownerCondition,\n    ensureOwnership,\n    insertUnique,\n    releaseOwnership,\n    execute,\n  };\n});\n"],"mappings":"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"}