105 lines
3.8 KiB
TypeScript
105 lines
3.8 KiB
TypeScript
import { assert, describe, it } from "@effect/vitest"
|
|
import { Effect, Layer, Ref, Schema } from "effect"
|
|
import * as EventGroup from "effect/unstable/eventlog/EventGroup"
|
|
import * as EventJournal from "effect/unstable/eventlog/EventJournal"
|
|
import * as EventLog from "effect/unstable/eventlog/EventLog"
|
|
import * as EventLogEncryption from "effect/unstable/eventlog/EventLogEncryption"
|
|
|
|
const UserPayload = Schema.Struct({
|
|
id: Schema.String
|
|
})
|
|
|
|
const UserGroup = EventGroup.empty.add({
|
|
tag: "UserCreated",
|
|
primaryKey: (payload) => payload.id,
|
|
payload: UserPayload
|
|
})
|
|
|
|
const schema = EventLog.schema(UserGroup)
|
|
|
|
const handlerLayer = (handled: Ref.Ref<ReadonlyArray<string>>) =>
|
|
EventLog.group(
|
|
UserGroup,
|
|
(handlers) =>
|
|
handlers.handle("UserCreated", ({ payload }) => Ref.update(handled, (values) => [...values, payload.id]))
|
|
).pipe(
|
|
Layer.provide(EventLog.layerRegistry)
|
|
)
|
|
|
|
const logLayer = (handled: Ref.Ref<ReadonlyArray<string>>) =>
|
|
EventLog.layer(schema, handlerLayer(handled)).pipe(
|
|
Layer.provide(EventJournal.layerMemory),
|
|
Layer.provide(
|
|
Layer.effect(EventLog.Identity, EventLog.makeIdentity).pipe(
|
|
Layer.provide(EventLogEncryption.layerSubtle)
|
|
)
|
|
)
|
|
)
|
|
|
|
describe("EventLog", () => {
|
|
it.effect("writes a typed event, commits the entry, and runs its handler", () =>
|
|
Effect.gen(function*() {
|
|
const handled = yield* Ref.make<ReadonlyArray<string>>([])
|
|
return yield* Effect.gen(function*() {
|
|
const log = yield* EventLog.EventLog
|
|
yield* log.write({
|
|
schema,
|
|
event: "UserCreated",
|
|
payload: { id: "user-1" }
|
|
})
|
|
const entries = yield* log.entries
|
|
const seen = yield* Ref.get(handled)
|
|
assert.strictEqual(entries.length, 1)
|
|
assert.strictEqual(entries[0].event, "UserCreated")
|
|
assert.deepStrictEqual(seen, ["user-1"])
|
|
}).pipe(Effect.provide(logLayer(handled)))
|
|
}))
|
|
|
|
it.effect("encrypts and decrypts entries", () =>
|
|
Effect.gen(function*() {
|
|
const encryption = yield* EventLogEncryption.EventLogEncryption
|
|
const identity = yield* encryption.generateIdentity
|
|
const entry = new EventJournal.Entry({
|
|
id: EventJournal.makeEntryIdUnsafe(),
|
|
event: "UserCreated",
|
|
primaryKey: "user-1",
|
|
payload: new Uint8Array([1, 2, 3])
|
|
}, { disableChecks: true })
|
|
const encrypted = yield* encryption.encrypt(identity, [entry])
|
|
const decrypted = yield* encryption.decrypt(identity, [{
|
|
sequence: 0,
|
|
iv: encrypted.iv,
|
|
entryId: entry.id,
|
|
encryptedEntry: encrypted.encryptedEntries[0]
|
|
}])
|
|
assert.strictEqual(decrypted.length, 1)
|
|
assert.strictEqual(decrypted[0].entry.idString, entry.idString)
|
|
}).pipe(Effect.provide(EventLogEncryption.layerSubtle)))
|
|
|
|
it.effect("publishes local journal changes through a scoped subscription", () =>
|
|
Effect.gen(function*() {
|
|
const remoteId = EventJournal.makeRemoteIdUnsafe()
|
|
const journal = yield* EventJournal.EventJournal
|
|
const entry = new EventJournal.Entry({
|
|
id: EventJournal.makeEntryIdUnsafe(),
|
|
event: "UserCreated",
|
|
primaryKey: "user-1",
|
|
payload: new Uint8Array([1])
|
|
}, { disableChecks: true })
|
|
|
|
const first = yield* journal.writeFromRemote({
|
|
remoteId,
|
|
entries: [new EventJournal.RemoteEntry({ remoteSequence: 1, entry })],
|
|
effect: () => Effect.void
|
|
})
|
|
const second = yield* journal.writeFromRemote({
|
|
remoteId,
|
|
entries: [new EventJournal.RemoteEntry({ remoteSequence: 2, entry })],
|
|
effect: () => Effect.void
|
|
})
|
|
|
|
assert.deepStrictEqual(first.duplicateEntries, [])
|
|
assert.deepStrictEqual(second.duplicateEntries.map((_) => _.idString), [entry.idString])
|
|
}).pipe(Effect.provide(EventJournal.layerMemory)))
|
|
})
|