/* eslint-disable @typescript-eslint/no-explicit-any */ import * as RedisClient from "@effect-ts-app/infra/redis-client" import { NotFoundError } from "../../errors.js" import { memFilter } from "./Memory.js" import type { Filter, FilterJoinSelect, PersistenceModelType, StorageConfig, Store, StoreConfig } from "./service.js" import { StoreMaker } from "./service.js" import { codeFilterJoinSelect, makeETag, makeUpdateETag } from "./utils.js" export function makeRedisStore({ prefix }: StorageConfig) { return Effect.gen(function*($) { const redis = yield* $(RedisClient.RedisClient.access) return { make: , Id2 extends Id>( name: string, existing?: Effect>, _config?: StoreConfig ) => Effect.gen(function*($) { const updateETag = makeUpdateETag(name) // Very naive implementation of course. const key = `${prefix}${name}` const current = yield* $(RedisClient.get(key).orDie.provideService(RedisClient.RedisClient, redis)) if (!current.isSome()) { const m = yield* $(existing ?? Effect(ROMap.empty)) yield* $( RedisClient.set(key, JSON.stringify({ data: [...m.values()].map(e => makeETag(e)) })) .orDie .provideService(RedisClient.RedisClient, redis) ) } const get = RedisClient.get(key) .flatMap(x => x.encaseInEffect(() => new NotFoundError("data", ""))) .orDie .map(x => JSON.parse(x) as { data: readonly PM[] }) .map(_ => _.data) .provideService(RedisClient.RedisClient, redis) const set = (i: ROMap) => RedisClient.set(key, JSON.stringify({ data: [...i.values()] })).orDie const sem = Semaphore.unsafeMake(1) const withPermit = sem.withPermits(1) const asMap = get.map(x => ROMap.make(x.map(x => [x.id, x] as const))) const all = get.map(Chunk.fromIterable) const batchSet = (items: NonEmptyReadonlyArray) => items .forEachEffect(e => s.find(e.id).flatMap(current => updateETag(e, current))) .tap(items => asMap .map(m => { const mut = ROMap.toMutable(m) items.forEach(e => mut.set(e.id, e)) return ROMap.fromMutable(mut) }) .flatMap(set) ) .map(_ => _.toReadonlyArray() as NonEmptyReadonlyArray) .apply(withPermit) .provideService(RedisClient.RedisClient, redis) const s: Store = { all, filter: (filter: Filter, cursor?: { skip?: number; limit?: number }) => all.map(memFilter(filter, cursor)), filterJoinSelect: (filter: FilterJoinSelect) => all.map(c => c.flatMap(codeFilterJoinSelect(filter))), find: id => asMap.map(ROMap.lookup(id)), set: e => s .find(e.id) .flatMap(current => updateETag(e, current)) .tap(e => asMap.map(ROMap.insert(e.id, e)).flatMap(set)) .apply(withPermit) .provideService(RedisClient.RedisClient, redis), batchSet, bulkSet: batchSet, remove: (e: PM) => asMap.map(ROMap.remove(e.id)).flatMap(set) .apply(withPermit) .provideService( RedisClient.RedisClient, redis ) } return s }) } }) } export function RedisStoreLive(config: Config) { return config.config.flatMap(makeRedisStore).toLayer(StoreMaker) }