state.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. export * as State from "./state"
  2. import { Effect, Scope, Semaphore } from "effect"
  3. import type { Draft, Objectish } from "immer"
  4. export type Transform<Editor> = (editor: Editor) => void
  5. export type MakeEditor<State extends Objectish, Editor> = (draft: Draft<State>) => Editor
  6. export interface Options<State extends Objectish, Editor> {
  7. readonly initial: () => State
  8. readonly editor: MakeEditor<State, Editor>
  9. /** Completes every committed edit; reason identifies exceptional update origins. */
  10. readonly finalize?: (editor: Editor, reason?: string) => Effect.Effect<void>
  11. }
  12. export interface Interface<State extends Objectish, Editor> {
  13. readonly get: () => State
  14. readonly transform: () => Effect.Effect<(transform: Transform<Editor>) => Effect.Effect<void>, never, Scope.Scope>
  15. readonly update: (update: (editor: Editor) => Effect.Effect<void>, reason?: string) => Effect.Effect<void>
  16. }
  17. export function create<State extends Objectish, Editor>(options: Options<State, Editor>): Interface<State, Editor> {
  18. let state = options.initial()
  19. let transforms: { update: Transform<Editor> }[] = []
  20. const semaphore = Semaphore.makeUnsafe(1)
  21. const commit = Effect.fn("State.commit")(function* (next: State, reason?: string) {
  22. const api = options.editor(next as Draft<State>)
  23. if (options.finalize) yield* options.finalize(api, reason)
  24. state = next
  25. })
  26. const rebuild = Effect.fn("State.rebuild")(function* () {
  27. const next = options.initial()
  28. const api = options.editor(next as Draft<State>)
  29. for (const transform of transforms)
  30. yield* Effect.sync(() => transform.update(api)).pipe(Effect.withSpan("State.rebuild.update", {}))
  31. yield* commit(next)
  32. }, semaphore.withPermit)
  33. return {
  34. get: () => state,
  35. transform: Effect.fn("State.transform")(function* () {
  36. const transform = { update: (_editor: Editor) => {} }
  37. transforms = [...transforms, transform]
  38. const scope = yield* Scope.Scope
  39. yield* Scope.addFinalizer(
  40. scope,
  41. Effect.sync(() => {
  42. transforms = transforms.filter((item) => item !== transform)
  43. }).pipe(Effect.andThen(rebuild())),
  44. )
  45. return Effect.fnUntraced(function* (update: Transform<Editor>) {
  46. transform.update = update
  47. yield* rebuild()
  48. })
  49. }),
  50. update: Effect.fn("State.update")(function* (update, reason) {
  51. const api = options.editor(state as Draft<State>)
  52. yield* update(api)
  53. if (options.finalize) yield* options.finalize(api, reason)
  54. }, semaphore.withPermit),
  55. }
  56. }