|
| 1 | +/** |
| 2 | + * @since 1.0.0 |
| 3 | + */ |
| 4 | +/* eslint-disable @typescript-eslint/no-empty-object-type */ |
| 5 | +import * as Result from "@effect-rx/rx/Result" |
| 6 | +import * as Rx from "@effect-rx/rx/Rx" |
| 7 | +import type { CreateStoreOptions, LiveQueryDef, LiveStoreSchema, OtelOptions, Store } from "@livestore/livestore" |
| 8 | +import { createStore, provideOtel } from "@livestore/livestore" |
| 9 | +import * as Context from "effect/Context" |
| 10 | +import * as Effect from "effect/Effect" |
| 11 | +import { constUndefined } from "effect/Function" |
| 12 | +import * as Layer from "effect/Layer" |
| 13 | +import type { Option } from "effect/Option" |
| 14 | + |
| 15 | +/** |
| 16 | + * @since 1.0.0 |
| 17 | + * @category Store |
| 18 | + */ |
| 19 | +export interface StoreService { |
| 20 | + readonly _: unique symbol |
| 21 | +} |
| 22 | + |
| 23 | +/** |
| 24 | + * @since 1.0.0 |
| 25 | + * @category Constructors |
| 26 | + */ |
| 27 | +export const make = <S extends LiveStoreSchema, Context = {}>( |
| 28 | + options: CreateStoreOptions<S, Context> & { |
| 29 | + readonly otelOptions?: Partial<OtelOptions> | undefined |
| 30 | + } |
| 31 | +): { |
| 32 | + readonly runtimeRx: Rx.RxRuntime<StoreService, never> |
| 33 | + readonly storeRx: Rx.Rx<Result.Result<Store<S, Context>>> |
| 34 | + readonly storeRxUnsafe: Rx.Rx<Store<S, Context>> |
| 35 | + readonly makeQueryRx: <A>(query: LiveQueryDef<A>) => Rx.Rx<Result.Result<A>> |
| 36 | + readonly makeQueryRxUnsafe: <A>(query: LiveQueryDef<A>) => Rx.Rx<A> |
| 37 | + readonly commitRx: Rx.Writable<Option<void>, {}> |
| 38 | +} => { |
| 39 | + const StoreService = Context.GenericTag<StoreService, Store<S, Context>>("@effect-rx/rx-livestore/StoreService") |
| 40 | + const runtimeRx = Rx.runtime(Layer.scoped( |
| 41 | + StoreService, |
| 42 | + createStore(options).pipe( |
| 43 | + provideOtel({ |
| 44 | + parentSpanContext: options?.otelOptions?.rootSpanContext, |
| 45 | + otelTracer: options?.otelOptions?.tracer |
| 46 | + }), |
| 47 | + Effect.orDie |
| 48 | + ) |
| 49 | + )) |
| 50 | + const storeRx = runtimeRx.rx(StoreService) |
| 51 | + const storeRxUnsafe = Rx.readable((get) => { |
| 52 | + const result = get(storeRx) |
| 53 | + return Result.getOrElse(result, constUndefined) as Store<S, Context> |
| 54 | + }) |
| 55 | + const makeQueryRx = <A>(query: LiveQueryDef<A>) => |
| 56 | + Rx.readable((get) => { |
| 57 | + const store = get(storeRx) |
| 58 | + return Result.map(store, (store) => { |
| 59 | + const result = store.query(query) |
| 60 | + get.addFinalizer( |
| 61 | + store.subscribe(query, { |
| 62 | + onUpdate(value) { |
| 63 | + get.setSelf(Result.success(value)) |
| 64 | + } |
| 65 | + }) |
| 66 | + ) |
| 67 | + return result |
| 68 | + }) |
| 69 | + }) |
| 70 | + const makeQueryRxUnsafe = <A>(query: LiveQueryDef<A>) => |
| 71 | + Rx.readable((get) => { |
| 72 | + const store = get(storeRxUnsafe) |
| 73 | + get.addFinalizer( |
| 74 | + store.subscribe(query, { |
| 75 | + onUpdate(value) { |
| 76 | + get.setSelf(Result.success(value)) |
| 77 | + } |
| 78 | + }) |
| 79 | + ) |
| 80 | + return store.query(query) |
| 81 | + }) |
| 82 | + const commitRx = Rx.fnSync((event: {}, get) => { |
| 83 | + get(storeRxUnsafe)?.commit(event) |
| 84 | + }) |
| 85 | + return { |
| 86 | + runtimeRx, |
| 87 | + storeRx, |
| 88 | + storeRxUnsafe, |
| 89 | + makeQueryRx, |
| 90 | + makeQueryRxUnsafe, |
| 91 | + commitRx |
| 92 | + } as const |
| 93 | +} |
0 commit comments