import { assert, describe, it } from "@effect/vitest" import type { Layer } from "effect" import { Context, Data, Effect, Exit, Result, Schema } from "effect" import { Persistable, PersistedCache, Persistence } from "effect/unstable/persistence" class User extends Schema.Class("User")({ id: Schema.Number, name: Schema.String }) {} class TTLRequest extends Persistable.Class<{ payload: { id: number } }>()("TTLRequest", { primaryKey: (req) => `TTLRequest:${req.id}`, success: User, error: Schema.String }) {} export class TransientError extends Data.TaggedError("TransientError") {} class LookupService extends Context.Service()("LookupService") {} export const suite = (storeId: string, layer: Layer.Layer) => describe(`PersistedCache (${storeId})`, { timeout: 30_000 }, () => { it.effect("smoke test", () => Effect.gen(function*() { const persistence = yield* Persistence.Persistence const store = yield* persistence.make({ storeId: "users" }) let invocations = 0 let cache = yield* PersistedCache.make((req: TTLRequest) => Effect.sync(() => { invocations++ return new User({ id: req.id, name: "John" }) }), { storeId: "users", timeToLive: () => 5000 }) const user = yield* cache.get(new TTLRequest({ id: 1 })) assert.deepStrictEqual(user, new User({ id: 1, name: "John" })) assert.deepStrictEqual( yield* store.get(new TTLRequest({ id: 1 })), Exit.succeed(new User({ id: 1, name: "John" })) ) assert.strictEqual(invocations, 1) assert.deepStrictEqual(yield* cache.get(new TTLRequest({ id: 1 })), new User({ id: 1, name: "John" })) assert.strictEqual(invocations, 1) cache = yield* PersistedCache.make((req: TTLRequest) => Effect.sync(() => { invocations++ return new User({ id: req.id, name: "John" }) }), { storeId: "users", timeToLive: (_req, _exit) => 5000 }) assert.deepStrictEqual(yield* cache.get(new TTLRequest({ id: 1 })), new User({ id: 1, name: "John" })) assert.strictEqual(invocations, 1) yield* store.setMany([ [new TTLRequest({ id: 1 }), Exit.fail("Not found")], [new TTLRequest({ id: 2 }), Exit.succeed(new User({ id: 2, name: "Jane" }))] ]) const results = yield* store.getMany([new TTLRequest({ id: 1 }), new TTLRequest({ id: 2 })]) assert.deepStrictEqual(results, [ Exit.fail("Not found"), Exit.succeed(new User({ id: 2, name: "Jane" })) ]) }).pipe( Effect.provide(layer), Effect.catchFilter((e) => e instanceof TransientError ? Result.succeed(e) : Result.fail(e), () => Effect.void), flakyTest )) it.effect("requireServicesAt: 'lookup' requires lookup services at get-time", () => Effect.gen(function*() { const cache = yield* PersistedCache.make( (req: TTLRequest) => Effect.map(LookupService, (service) => new User({ id: req.id, name: service.value })), { storeId: `${storeId}-require-services-at`, timeToLive: () => 5000, requireServicesAt: "lookup" } ) const result1 = yield* cache.get(new TTLRequest({ id: 1 })).pipe( Effect.provideService(LookupService, LookupService.of({ value: "first" })) ) const result2 = yield* cache.get(new TTLRequest({ id: 2 })).pipe( Effect.provideService(LookupService, LookupService.of({ value: "second" })) ) const result3 = yield* cache.get(new TTLRequest({ id: 1 })).pipe( Effect.provideService(LookupService, LookupService.of({ value: "third" })) ) assert.deepStrictEqual(result1, new User({ id: 1, name: "first" })) assert.deepStrictEqual(result2, new User({ id: 2, name: "second" })) assert.deepStrictEqual(result3, new User({ id: 1, name: "first" })) }).pipe( Effect.provide(layer), Effect.catchFilter((e) => e instanceof TransientError ? Result.succeed(e) : Result.fail(e), () => Effect.void), flakyTest )) }) const flakyTest = (effect: Effect.Effect) => effect.pipe( Effect.timeoutOrElse({ duration: "10 seconds", orElse: () => Effect.void }) )