import * as SqlClient from "effect/unstable/sql/SqlClient";import * as SqlSchema from "effect/unstable/sql/SqlSchema";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import { toPersistenceDecodeError, toPersistenceSqlError, type AuthPairingLinkRepositoryError,} from "../Errors.ts";import { AuthPairingLinkRecord, AuthPairingLinkRepository, type AuthPairingLinkRepositoryShape, ConsumeAuthPairingLinkInput, CreateAuthPairingLinkInput, GetAuthPairingLinkByCredentialInput, ListActiveAuthPairingLinksInput, RevokeAuthPairingLinkInput,} from "../Services/AuthPairingLinks.ts"; function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) { return (cause: unknown): AuthPairingLinkRepositoryError => Schema.isSchemaError(cause) ? toPersistenceDecodeError(decodeOperation)(cause) : toPersistenceSqlError(sqlOperation)(cause);} const makeAuthPairingLinkRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const createPairingLinkRow = SqlSchema.void({ Request: CreateAuthPairingLinkInput, execute: (input) => sql` INSERT INTO auth_pairing_links ( id, credential, method, scopes, subject, label, created_at, expires_at, consumed_at, revoked_at ) VALUES ( ${input.id}, ${input.credential}, ${input.method}, ${JSON.stringify(input.scopes)}, ${input.subject}, ${input.label}, ${input.createdAt}, ${input.expiresAt}, NULL, NULL ) `, }); const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({ Request: ConsumeAuthPairingLinkInput, Result: AuthPairingLinkRecord, execute: ({ credential, consumedAt, now }) => sql` UPDATE auth_pairing_links SET consumed_at = ${consumedAt} WHERE credential = ${credential} AND revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} RETURNING id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" `, }); const listActivePairingLinkRows = SqlSchema.findAll({ Request: ListActiveAuthPairingLinksInput, Result: AuthPairingLinkRecord, execute: ({ now }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} ORDER BY created_at DESC, id DESC `, }); const revokePairingLinkRow = SqlSchema.findAll({ Request: RevokeAuthPairingLinkInput, Result: Schema.Struct({ id: Schema.String }), execute: ({ id, revokedAt }) => sql` UPDATE auth_pairing_links SET revoked_at = ${revokedAt} WHERE id = ${id} AND revoked_at IS NULL AND consumed_at IS NULL RETURNING id AS "id" `, }); const getPairingLinkRowByCredential = SqlSchema.findOneOption({ Request: GetAuthPairingLinkByCredentialInput, Result: AuthPairingLinkRecord, execute: ({ credential }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE credential = ${credential} `, }); const create: AuthPairingLinkRepositoryShape["create"] = (input) => createPairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", ), ), ); const consumeAvailable: AuthPairingLinkRepositoryShape["consumeAvailable"] = (input) => consumeAvailablePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow", ), ), ); const listActive: AuthPairingLinkRepositoryShape["listActive"] = (input) => listActivePairingLinkRows(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows", ), ), ); const revoke: AuthPairingLinkRepositoryShape["revoke"] = (input) => revokePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", ), ), Effect.map((rows) => rows.length > 0), ); const getByCredential: AuthPairingLinkRepositoryShape["getByCredential"] = (input) => getPairingLinkRowByCredential(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow", ), ), ); return { create, consumeAvailable, listActive, revoke, getByCredential, } satisfies AuthPairingLinkRepositoryShape;}); export const AuthPairingLinkRepositoryLive = Layer.effect( AuthPairingLinkRepository, makeAuthPairingLinkRepository,);
import * as SqlClient from "effect/unstable/sql/SqlClient";
import * as SqlSchema from "effect/unstable/sql/SqlSchema";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import {
toPersistenceDecodeError,
toPersistenceSqlError,
type AuthPairingLinkRepositoryError,
} from "../Errors.ts";
import {
AuthPairingLinkRecord,
AuthPairingLinkRepository,
type AuthPairingLinkRepositoryShape,
ConsumeAuthPairingLinkInput,
CreateAuthPairingLinkInput,
GetAuthPairingLinkByCredentialInput,
ListActiveAuthPairingLinksInput,
RevokeAuthPairingLinkInput,
} from "../Services/AuthPairingLinks.ts";
function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) {
return (cause: unknown): AuthPairingLinkRepositoryError =>
Schema.isSchemaError(cause)
? toPersistenceDecodeError(decodeOperation)(cause)
: toPersistenceSqlError(sqlOperation)(cause);
}
const makeAuthPairingLinkRepository = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
const createPairingLinkRow = SqlSchema.void({
Request: CreateAuthPairingLinkInput,
execute: (input) =>
sql`
INSERT INTO auth_pairing_links (
id,
credential,
method,
scopes,
subject,
label,
created_at,
expires_at,
consumed_at,
revoked_at
)
VALUES (
${input.id},
${input.credential},
${input.method},
${JSON.stringify(input.scopes)},
${input.subject},
${input.label},
${input.createdAt},
${input.expiresAt},
NULL,
NULL
)
`,
});
const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
Request: ConsumeAuthPairingLinkInput,
Result: AuthPairingLinkRecord,
execute: ({ credential, consumedAt, now }) =>
sql`
UPDATE auth_pairing_links
SET consumed_at = ${consumedAt}
WHERE credential = ${credential}
AND revoked_at IS NULL
AND consumed_at IS NULL
AND expires_at > ${now}
RETURNING
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
`,
});
const listActivePairingLinkRows = SqlSchema.findAll({
Request: ListActiveAuthPairingLinksInput,
Result: AuthPairingLinkRecord,
execute: ({ now }) =>
sql`
SELECT
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
FROM auth_pairing_links
WHERE revoked_at IS NULL
AND consumed_at IS NULL
AND expires_at > ${now}
ORDER BY created_at DESC, id DESC
`,
});
const revokePairingLinkRow = SqlSchema.findAll({
Request: RevokeAuthPairingLinkInput,
Result: Schema.Struct({ id: Schema.String }),
execute: ({ id, revokedAt }) =>
sql`
UPDATE auth_pairing_links
SET revoked_at = ${revokedAt}
WHERE id = ${id}
AND revoked_at IS NULL
AND consumed_at IS NULL
RETURNING id AS "id"
`,
});
const getPairingLinkRowByCredential = SqlSchema.findOneOption({
Request: GetAuthPairingLinkByCredentialInput,
Result: AuthPairingLinkRecord,
execute: ({ credential }) =>
sql`
SELECT
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
FROM auth_pairing_links
WHERE credential = ${credential}
`,
});
const create: AuthPairingLinkRepositoryShape["create"] = (input) =>
createPairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.create:query",
"AuthPairingLinkRepository.create:encodeRequest",
),
),
);
const consumeAvailable: AuthPairingLinkRepositoryShape["consumeAvailable"] = (input) =>
consumeAvailablePairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.consumeAvailable:query",
"AuthPairingLinkRepository.consumeAvailable:decodeRow",
),
),
);
const listActive: AuthPairingLinkRepositoryShape["listActive"] = (input) =>
listActivePairingLinkRows(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.listActive:query",
"AuthPairingLinkRepository.listActive:decodeRows",
),
),
);
const revoke: AuthPairingLinkRepositoryShape["revoke"] = (input) =>
revokePairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.revoke:query",
"AuthPairingLinkRepository.revoke:decodeRows",
),
),
Effect.map((rows) => rows.length > 0),
);
const getByCredential: AuthPairingLinkRepositoryShape["getByCredential"] = (input) =>
getPairingLinkRowByCredential(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.getByCredential:query",
"AuthPairingLinkRepository.getByCredential:decodeRow",
),
),
);
return {
create,
consumeAvailable,
listActive,
revoke,
getByCredential,
} satisfies AuthPairingLinkRepositoryShape;
});
export const AuthPairingLinkRepositoryLive = Layer.effect(
AuthPairingLinkRepository,
makeAuthPairingLinkRepository,
);