Add effect because I coldn't resist
This commit is contained in:
111
packages/frontend/src/cache.ts
Normal file
111
packages/frontend/src/cache.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type * as Db from "backend/database";
|
||||
import { AttachmentId, PieceId, UserId } from "common";
|
||||
import { Cache, Duration, Effect, Option, pipe } from "effect";
|
||||
import { client, mapResponse } from "./client";
|
||||
|
||||
export interface User {
|
||||
readonly userId: UserId;
|
||||
readonly username: string;
|
||||
readonly admin: boolean;
|
||||
}
|
||||
|
||||
export interface SystemInformation {
|
||||
readonly createdBy: Option.Option<User>;
|
||||
readonly createdAt: string;
|
||||
readonly modifiedBy: Option.Option<User>;
|
||||
readonly modifiedAt: Option.Option<string>;
|
||||
}
|
||||
|
||||
export interface Attachment extends SystemInformation {
|
||||
readonly attachmentId: AttachmentId;
|
||||
readonly pieceId: PieceId;
|
||||
readonly sha256: string;
|
||||
readonly filename: string;
|
||||
readonly mediaType: string;
|
||||
}
|
||||
|
||||
export interface Piece extends SystemInformation {
|
||||
readonly pieceId: PieceId;
|
||||
readonly name: string;
|
||||
readonly composer: Option.Option<string>;
|
||||
readonly lyricist: Option.Option<string>;
|
||||
readonly arranger: Option.Option<string>;
|
||||
readonly attachments: readonly Attachment[];
|
||||
}
|
||||
|
||||
interface DbSystemInformation {
|
||||
readonly createdBy: UserId | null;
|
||||
readonly createdAt: string;
|
||||
readonly modifiedBy: UserId | null;
|
||||
readonly modifiedAt: string | null;
|
||||
}
|
||||
|
||||
export const denormalizeSystemInformation = <T extends DbSystemInformation>({
|
||||
createdBy,
|
||||
modifiedBy,
|
||||
modifiedAt,
|
||||
...rest
|
||||
}: T) => pipe(
|
||||
Effect.all({
|
||||
createdBy: pipe(
|
||||
createdBy,
|
||||
Effect.fromNullable,
|
||||
Effect.flatMap((userId) => Effect.uninterruptible(userCache.get(userId))),
|
||||
Effect.optionFromOptional,
|
||||
),
|
||||
modifiedBy: pipe(
|
||||
modifiedBy,
|
||||
Effect.fromNullable,
|
||||
Effect.flatMap((userId) => Effect.uninterruptible(userCache.get(userId))),
|
||||
Effect.optionFromOptional,
|
||||
),
|
||||
}, { concurrency: "unbounded" }),
|
||||
Effect.map((si) => Object.freeze({
|
||||
...rest,
|
||||
...si,
|
||||
modifiedAt: Option.fromNullable(modifiedAt),
|
||||
})),
|
||||
);
|
||||
|
||||
export const denormalizePiece = ({
|
||||
composer,
|
||||
lyricist,
|
||||
arranger,
|
||||
attachments,
|
||||
...rest
|
||||
}: Db.Piece & { attachments: (Omit<Db.Attachment, "sha256"> & { sha256: string })[] }) => pipe(
|
||||
Effect.all({
|
||||
attachments: Effect.all(attachments.map(denormalizeSystemInformation), { concurrency: "unbounded" }),
|
||||
}, { concurrency: "unbounded" }),
|
||||
Effect.map((piece) => Object.freeze({
|
||||
...rest,
|
||||
...piece,
|
||||
composer: Option.fromNullable(composer),
|
||||
lyricist: Option.fromNullable(lyricist),
|
||||
arranger: Option.fromNullable(arranger),
|
||||
})),
|
||||
Effect.flatMap(denormalizeSystemInformation),
|
||||
);
|
||||
|
||||
export const userLookup = (userId: UserId) => pipe(
|
||||
Effect.promise((signal) => client.user({ userId }).get({ fetch: { signal } })),
|
||||
Effect.flatMap(mapResponse),
|
||||
);
|
||||
|
||||
export const pieceLookup = (pieceId: PieceId) => pipe(
|
||||
Effect.promise((signal) => client.piece({ pieceId }).get({ fetch: { signal } })),
|
||||
Effect.flatMap(mapResponse),
|
||||
Effect.flatMap(denormalizePiece),
|
||||
);
|
||||
|
||||
export const userCache = Effect.runSync(Cache.make({
|
||||
capacity: Infinity,
|
||||
timeToLive: Duration.infinity,
|
||||
lookup: userLookup,
|
||||
}));
|
||||
|
||||
export const pieceCache = Effect.runSync(Cache.make({
|
||||
capacity: Infinity,
|
||||
timeToLive: Infinity,
|
||||
lookup: pieceLookup,
|
||||
}));
|
||||
Reference in New Issue
Block a user