| 59 | } |
| 60 | |
| 61 | export function create<State, DraftApi>(options: Options<State, DraftApi>): Interface<State, DraftApi> { |
| 62 | let state = options.initial() |
| 63 | let transforms: { run: TransformCallback<DraftApi> }[] = [] |
| 64 | const semaphore = Semaphore.makeUnsafe(1) |
| 65 | |
| 66 | const commit = Effect.fn("State.commit")(function* (next: State) { |
| 67 | const api = options.draft(next) |
| 68 | if (options.finalize) yield* options.finalize(api) |
| 69 | state = next |
| 70 | }) |
| 71 | |
| 72 | const apply = (transform: TransformCallback<DraftApi>, draft: DraftApi) => |
| 73 | Effect.suspend(() => { |
| 74 | const result = transform(draft) |
| 75 | return Effect.isEffect(result) ? Effect.asVoid(result).pipe(Effect.orDie) : Effect.void |
| 76 | }) |
| 77 | |
| 78 | const materialize = Effect.fnUntraced(function* () { |
| 79 | const next = options.initial() |
| 80 | const api = options.draft(next) |
| 81 | for (const transform of transforms) yield* apply(transform.run, api).pipe(Effect.withSpan("State.reload.update")) |
| 82 | yield* commit(next) |
| 83 | }) |
| 84 | |
| 85 | const reload = () => semaphore.withPermit(materialize()) |
| 86 | |
| 87 | const result: Interface<State, DraftApi> = { |
| 88 | get: () => state, |
| 89 | transform: Effect.fn("State.transform")(function* (update) { |
| 90 | const scope = yield* Scope.Scope |
| 91 | return yield* Effect.uninterruptible( |
| 92 | Effect.gen(function* () { |
| 93 | const transform = { run: update } |
| 94 | let active = true |
| 95 | const dispose = Effect.uninterruptible( |
| 96 | semaphore.withPermit( |
| 97 | Effect.suspend(() => { |
| 98 | if (!active) return Effect.void |
| 99 | active = false |
| 100 | transforms = transforms.filter((item) => item !== transform) |
| 101 | return Effect.gen(function* () { |
| 102 | const batch = yield* CurrentBatch |
| 103 | if (batch) { |
| 104 | batch.add(reload) |
| 105 | return |
| 106 | } |
| 107 | yield* materialize() |
| 108 | }) |
| 109 | }), |
| 110 | ), |
| 111 | ) |
| 112 | yield* semaphore.withPermit( |
| 113 | Effect.sync(() => { |
| 114 | transforms = [...transforms, transform] |
| 115 | }), |
| 116 | ) |
| 117 | yield* Scope.addFinalizer(scope, dispose) |
| 118 | const batch = yield* CurrentBatch |