@effect-atom/atom 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Atom/package.json +6 -0
- package/AtomRef/package.json +6 -0
- package/Hydration/package.json +6 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/Registry/package.json +6 -0
- package/Result/package.json +6 -0
- package/dist/cjs/Atom.js +1079 -0
- package/dist/cjs/Atom.js.map +1 -0
- package/dist/cjs/AtomRef.js +261 -0
- package/dist/cjs/AtomRef.js.map +1 -0
- package/dist/cjs/Hydration.js +100 -0
- package/dist/cjs/Hydration.js.map +1 -0
- package/dist/cjs/Registry.js +128 -0
- package/dist/cjs/Registry.js.map +1 -0
- package/dist/cjs/Result.js +454 -0
- package/dist/cjs/Result.js.map +1 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/registry.js +701 -0
- package/dist/cjs/internal/registry.js.map +1 -0
- package/dist/cjs/internal/runtime.js +92 -0
- package/dist/cjs/internal/runtime.js.map +1 -0
- package/dist/dts/Atom.d.ts +597 -0
- package/dist/dts/Atom.d.ts.map +1 -0
- package/dist/dts/AtomRef.d.ts +55 -0
- package/dist/dts/AtomRef.d.ts.map +1 -0
- package/dist/dts/Hydration.d.ts +27 -0
- package/dist/dts/Hydration.d.ts.map +1 -0
- package/dist/dts/Registry.d.ts +115 -0
- package/dist/dts/Registry.d.ts.map +1 -0
- package/dist/dts/Result.d.ts +351 -0
- package/dist/dts/Result.d.ts.map +1 -0
- package/dist/dts/index.d.ts +21 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/registry.d.ts +2 -0
- package/dist/dts/internal/registry.d.ts.map +1 -0
- package/dist/dts/internal/runtime.d.ts +2 -0
- package/dist/dts/internal/runtime.d.ts.map +1 -0
- package/dist/esm/Atom.js +1029 -0
- package/dist/esm/Atom.js.map +1 -0
- package/dist/esm/AtomRef.js +232 -0
- package/dist/esm/AtomRef.js.map +1 -0
- package/dist/esm/Hydration.js +71 -0
- package/dist/esm/Hydration.js.map +1 -0
- package/dist/esm/Registry.js +98 -0
- package/dist/esm/Registry.js.map +1 -0
- package/dist/esm/Result.js +403 -0
- package/dist/esm/Result.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/registry.js +672 -0
- package/dist/esm/internal/registry.js.map +1 -0
- package/dist/esm/internal/runtime.js +64 -0
- package/dist/esm/internal/runtime.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +72 -0
- package/src/Atom.ts +1865 -0
- package/src/AtomRef.ts +282 -0
- package/src/Hydration.ts +98 -0
- package/src/Registry.ts +204 -0
- package/src/Result.ts +767 -0
- package/src/index.ts +24 -0
- package/src/internal/registry.ts +810 -0
- package/src/internal/runtime.ts +63 -0
package/src/Atom.ts
ADDED
|
@@ -0,0 +1,1865 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
5
|
+
import * as KeyValueStore from "@effect/platform/KeyValueStore"
|
|
6
|
+
import * as Arr from "effect/Array"
|
|
7
|
+
import { NoSuchElementException } from "effect/Cause"
|
|
8
|
+
import * as Cause from "effect/Cause"
|
|
9
|
+
import * as Channel from "effect/Channel"
|
|
10
|
+
import * as Chunk from "effect/Chunk"
|
|
11
|
+
import * as EffectContext from "effect/Context"
|
|
12
|
+
import * as Duration from "effect/Duration"
|
|
13
|
+
import * as Effect from "effect/Effect"
|
|
14
|
+
import * as Either from "effect/Either"
|
|
15
|
+
import * as Exit from "effect/Exit"
|
|
16
|
+
import * as Fiber from "effect/Fiber"
|
|
17
|
+
import * as FiberRef from "effect/FiberRef"
|
|
18
|
+
import type { LazyArg } from "effect/Function"
|
|
19
|
+
import { constant, constVoid, dual, pipe } from "effect/Function"
|
|
20
|
+
import { globalValue } from "effect/GlobalValue"
|
|
21
|
+
import * as Inspectable from "effect/Inspectable"
|
|
22
|
+
import * as Layer from "effect/Layer"
|
|
23
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
24
|
+
import * as Option from "effect/Option"
|
|
25
|
+
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
|
26
|
+
import * as Runtime from "effect/Runtime"
|
|
27
|
+
import * as Schema from "effect/Schema"
|
|
28
|
+
import * as Scope from "effect/Scope"
|
|
29
|
+
import * as Stream from "effect/Stream"
|
|
30
|
+
import * as Subscribable from "effect/Subscribable"
|
|
31
|
+
import * as SubscriptionRef from "effect/SubscriptionRef"
|
|
32
|
+
import type { NoInfer } from "effect/Types"
|
|
33
|
+
import * as internalRegistry from "./internal/registry.js"
|
|
34
|
+
import { runCallbackSync } from "./internal/runtime.js"
|
|
35
|
+
import * as Registry from "./Registry.js"
|
|
36
|
+
import { AtomRegistry as AtomRegistry } from "./Registry.js"
|
|
37
|
+
import * as Result from "./Result.js"
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @since 1.0.0
|
|
41
|
+
* @category type ids
|
|
42
|
+
*/
|
|
43
|
+
export const TypeId: TypeId = "~effect-atom/atom/Atom"
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @since 1.0.0
|
|
47
|
+
* @category type ids
|
|
48
|
+
*/
|
|
49
|
+
export type TypeId = "~effect-atom/atom/Atom"
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @since 1.0.0
|
|
53
|
+
* @category models
|
|
54
|
+
*/
|
|
55
|
+
export interface Atom<A> extends Pipeable, Inspectable.Inspectable {
|
|
56
|
+
readonly [TypeId]: TypeId
|
|
57
|
+
readonly keepAlive: boolean
|
|
58
|
+
readonly lazy: boolean
|
|
59
|
+
readonly read: (get: Context) => A
|
|
60
|
+
readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
|
|
61
|
+
readonly label?: readonly [name: string, stack: string]
|
|
62
|
+
readonly idleTTL?: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @since 1.0.0
|
|
67
|
+
*/
|
|
68
|
+
export type Type<T extends Atom<any>> = T extends Atom<infer A> ? A : never
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @since 1.0.0
|
|
72
|
+
*/
|
|
73
|
+
export type Success<T extends Atom<any>> = T extends Atom<Result.Result<infer A, infer _>> ? A : never
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @since 1.0.0
|
|
77
|
+
*/
|
|
78
|
+
export type PullSuccess<T extends Atom<any>> = T extends Atom<PullResult<infer A, infer _>> ? A : never
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
*/
|
|
83
|
+
export type Failure<T extends Atom<any>> = T extends Atom<Result.Result<infer _, infer E>> ? E : never
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @since 1.0.0
|
|
87
|
+
*/
|
|
88
|
+
export type WithoutSerializable<T extends Atom<any>> = T extends Writable<infer R, infer W> ? Writable<R, W>
|
|
89
|
+
: Atom<Type<T>>
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @since 1.0.0
|
|
93
|
+
* @category type ids
|
|
94
|
+
*/
|
|
95
|
+
export const WritableTypeId: WritableTypeId = "~effect-atom/atom/Atom/Writable"
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @since 1.0.0
|
|
99
|
+
* @category type ids
|
|
100
|
+
*/
|
|
101
|
+
export type WritableTypeId = "~effect-atom/atom/Atom/Writable"
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @since 1.0.0
|
|
105
|
+
* @category models
|
|
106
|
+
*/
|
|
107
|
+
export interface Writable<R, W = R> extends Atom<R> {
|
|
108
|
+
readonly [WritableTypeId]: WritableTypeId
|
|
109
|
+
readonly write: (ctx: WriteContext<R>, value: W) => void
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @since 1.0.0
|
|
114
|
+
* @category context
|
|
115
|
+
*/
|
|
116
|
+
export interface Context {
|
|
117
|
+
<A>(atom: Atom<A>): A
|
|
118
|
+
readonly get: <A>(atom: Atom<A>) => A
|
|
119
|
+
readonly result: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
|
|
120
|
+
readonly suspendOnWaiting?: boolean | undefined
|
|
121
|
+
}) => Effect.Effect<A, E>
|
|
122
|
+
readonly resultOnce: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
|
|
123
|
+
readonly suspendOnWaiting?: boolean | undefined
|
|
124
|
+
}) => Effect.Effect<A, E>
|
|
125
|
+
readonly once: <A>(atom: Atom<A>) => A
|
|
126
|
+
readonly addFinalizer: (f: () => void) => void
|
|
127
|
+
readonly mount: <A>(atom: Atom<A>) => void
|
|
128
|
+
readonly refresh: <A>(atom: Atom<A>) => void
|
|
129
|
+
readonly refreshSelf: () => void
|
|
130
|
+
readonly self: <A>() => Option.Option<A>
|
|
131
|
+
readonly setSelf: <A>(a: A) => void
|
|
132
|
+
readonly set: <R, W>(atom: Writable<R, W>, value: W) => void
|
|
133
|
+
readonly some: <A>(atom: Atom<Option.Option<A>>) => Effect.Effect<A>
|
|
134
|
+
readonly someOnce: <A>(atom: Atom<Option.Option<A>>) => Effect.Effect<A>
|
|
135
|
+
readonly stream: <A>(atom: Atom<A>, options?: {
|
|
136
|
+
readonly withoutInitialValue?: boolean
|
|
137
|
+
readonly bufferSize?: number
|
|
138
|
+
}) => Stream.Stream<A>
|
|
139
|
+
readonly streamResult: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
|
|
140
|
+
readonly withoutInitialValue?: boolean
|
|
141
|
+
readonly bufferSize?: number
|
|
142
|
+
}) => Stream.Stream<A, E>
|
|
143
|
+
readonly subscribe: <A>(atom: Atom<A>, f: (_: A) => void, options?: {
|
|
144
|
+
readonly immediate?: boolean
|
|
145
|
+
}) => void
|
|
146
|
+
readonly registry: Registry.Registry
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
* @category context
|
|
152
|
+
*/
|
|
153
|
+
export interface WriteContext<A> {
|
|
154
|
+
readonly get: <A>(atom: Atom<A>) => A
|
|
155
|
+
readonly refreshSelf: () => void
|
|
156
|
+
readonly setSelf: (a: A) => void
|
|
157
|
+
readonly set: <R, W>(atom: Writable<R, W>, value: W) => void
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const AtomProto = {
|
|
161
|
+
[TypeId]: TypeId,
|
|
162
|
+
pipe() {
|
|
163
|
+
return pipeArguments(this, arguments)
|
|
164
|
+
},
|
|
165
|
+
toJSON(this: Atom<any>) {
|
|
166
|
+
return {
|
|
167
|
+
_id: "Atom",
|
|
168
|
+
keepAlive: this.keepAlive,
|
|
169
|
+
lazy: this.lazy,
|
|
170
|
+
label: this.label
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
toString() {
|
|
174
|
+
return Inspectable.format(this)
|
|
175
|
+
},
|
|
176
|
+
[Inspectable.NodeInspectSymbol](this: Atom<any>) {
|
|
177
|
+
return this.toJSON()
|
|
178
|
+
}
|
|
179
|
+
} as const
|
|
180
|
+
|
|
181
|
+
const RuntimeProto = {
|
|
182
|
+
...AtomProto,
|
|
183
|
+
atom(this: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) {
|
|
184
|
+
const read = makeRead(arg, options)
|
|
185
|
+
return readable((get) => {
|
|
186
|
+
const previous = get.self<Result.Result<any, any>>()
|
|
187
|
+
const runtimeResult = get(this)
|
|
188
|
+
if (runtimeResult._tag !== "Success") {
|
|
189
|
+
return Result.replacePrevious(runtimeResult, previous)
|
|
190
|
+
}
|
|
191
|
+
return read(get, runtimeResult.value)
|
|
192
|
+
})
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
fn(this: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) {
|
|
196
|
+
if (arguments.length === 0) {
|
|
197
|
+
return (arg: any, options?: { readonly initialValue?: unknown }) => makeFnRuntime(this, arg, options)
|
|
198
|
+
}
|
|
199
|
+
return makeFnRuntime(this, arg, options)
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
pull(this: AtomRuntime<any, any>, arg: any, options?: {
|
|
203
|
+
readonly disableAccumulation?: boolean
|
|
204
|
+
readonly initialValue?: ReadonlyArray<any>
|
|
205
|
+
}) {
|
|
206
|
+
const pullSignal = state(0)
|
|
207
|
+
const pullAtom = readable((get) => {
|
|
208
|
+
const previous = get.self<Result.Result<any, any>>()
|
|
209
|
+
const runtimeResult = get(this)
|
|
210
|
+
if (runtimeResult._tag !== "Success") {
|
|
211
|
+
return Result.replacePrevious(runtimeResult, previous)
|
|
212
|
+
}
|
|
213
|
+
return makeEffect(
|
|
214
|
+
get,
|
|
215
|
+
makeStreamPullEffect(get, pullSignal, arg, options),
|
|
216
|
+
Result.initial(true),
|
|
217
|
+
runtimeResult.value
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
return makeStreamPull(pullSignal, pullAtom)
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
subscriptionRef(this: AtomRuntime<any, any>, ref: any) {
|
|
224
|
+
return makeSubRef(
|
|
225
|
+
readable((get) => {
|
|
226
|
+
const previous = get.self<Result.Result<any, any>>()
|
|
227
|
+
const runtimeResult = get(this)
|
|
228
|
+
if (runtimeResult._tag !== "Success") {
|
|
229
|
+
return Result.replacePrevious(runtimeResult, previous)
|
|
230
|
+
}
|
|
231
|
+
const value = typeof ref === "function" ? ref(get) : ref
|
|
232
|
+
return SubscriptionRef.SubscriptionRefTypeId in value
|
|
233
|
+
? value
|
|
234
|
+
: makeEffect(get, value, Result.initial(true), runtimeResult.value)
|
|
235
|
+
}),
|
|
236
|
+
(get, ref) => {
|
|
237
|
+
const runtime = Result.getOrThrow(get(this))
|
|
238
|
+
return readSubscribable(get, ref, runtime)
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
subscribable(this: AtomRuntime<any, any>, arg: any) {
|
|
244
|
+
return makeSubscribable(
|
|
245
|
+
readable((get) => {
|
|
246
|
+
const previous = get.self<Result.Result<any, any>>()
|
|
247
|
+
const runtimeResult = get(this)
|
|
248
|
+
if (runtimeResult._tag !== "Success") {
|
|
249
|
+
return Result.replacePrevious(runtimeResult, previous)
|
|
250
|
+
}
|
|
251
|
+
const value = typeof arg === "function" ? arg(get) : arg
|
|
252
|
+
return Subscribable.isSubscribable(value) ?
|
|
253
|
+
value as Subscribable.Subscribable<any, any>
|
|
254
|
+
: makeEffect(get, value, Result.initial(true), runtimeResult.value)
|
|
255
|
+
}),
|
|
256
|
+
(get, ref) => {
|
|
257
|
+
const runtime = Result.getOrThrow(get(this))
|
|
258
|
+
return readSubscribable(get, ref, runtime)
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const makeFnRuntime = (self: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) => {
|
|
265
|
+
const [read, write, argAtom] = makeResultFn(arg, options)
|
|
266
|
+
return writable((get) => {
|
|
267
|
+
get.get(argAtom)
|
|
268
|
+
const previous = get.self<Result.Result<any, any>>()
|
|
269
|
+
const runtimeResult = get.get(self)
|
|
270
|
+
if (runtimeResult._tag !== "Success") {
|
|
271
|
+
return Result.replacePrevious(runtimeResult, previous)
|
|
272
|
+
}
|
|
273
|
+
return read(get, runtimeResult.value)
|
|
274
|
+
}, write)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const WritableProto = {
|
|
278
|
+
...AtomProto,
|
|
279
|
+
[WritableTypeId]: WritableTypeId
|
|
280
|
+
} as const
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @since 1.0.0
|
|
284
|
+
* @category refinements
|
|
285
|
+
*/
|
|
286
|
+
export const isWritable = <R, W>(atom: Atom<R>): atom is Writable<R, W> => WritableTypeId in atom
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @since 1.0.0
|
|
290
|
+
* @category constructors
|
|
291
|
+
*/
|
|
292
|
+
export const readable = <A>(
|
|
293
|
+
read: (get: Context) => A,
|
|
294
|
+
refresh?: (f: <A>(atom: Atom<A>) => void) => void
|
|
295
|
+
): Atom<A> => {
|
|
296
|
+
const self = Object.create(AtomProto)
|
|
297
|
+
self.keepAlive = false
|
|
298
|
+
self.lazy = true
|
|
299
|
+
self.read = read
|
|
300
|
+
self.refresh = refresh
|
|
301
|
+
return self
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @since 1.0.0
|
|
306
|
+
* @category constructors
|
|
307
|
+
*/
|
|
308
|
+
export const writable = <R, W>(
|
|
309
|
+
read: (get: Context) => R,
|
|
310
|
+
write: (ctx: WriteContext<R>, value: W) => void,
|
|
311
|
+
refresh?: (f: <A>(atom: Atom<A>) => void) => void
|
|
312
|
+
): Writable<R, W> => {
|
|
313
|
+
const self = Object.create(WritableProto)
|
|
314
|
+
self.keepAlive = false
|
|
315
|
+
self.lazy = true
|
|
316
|
+
self.read = read
|
|
317
|
+
self.write = write
|
|
318
|
+
self.refresh = refresh
|
|
319
|
+
return self
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function constSetSelf<A>(ctx: WriteContext<A>, value: A) {
|
|
323
|
+
ctx.setSelf(value)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// -----------------------------------------------------------------------------
|
|
327
|
+
// constructors
|
|
328
|
+
// -----------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @since 1.0.0
|
|
332
|
+
* @category constructors
|
|
333
|
+
*/
|
|
334
|
+
export const make: {
|
|
335
|
+
<A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
336
|
+
readonly initialValue?: A
|
|
337
|
+
}): Atom<Result.Result<A, E>>
|
|
338
|
+
<A, E>(effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
339
|
+
readonly initialValue?: A
|
|
340
|
+
}): Atom<Result.Result<A, E>>
|
|
341
|
+
<A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
342
|
+
readonly initialValue?: A
|
|
343
|
+
}): Atom<Result.Result<A, E>>
|
|
344
|
+
<A, E>(stream: Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
345
|
+
readonly initialValue?: A
|
|
346
|
+
}): Atom<Result.Result<A, E>>
|
|
347
|
+
<A>(create: (get: Context) => A): Atom<A>
|
|
348
|
+
<A>(initialValue: A): Writable<A>
|
|
349
|
+
} = (arg: any, options?: { readonly initialValue?: unknown }) => {
|
|
350
|
+
const readOrAtom = makeRead(arg, options)
|
|
351
|
+
if (TypeId in readOrAtom) {
|
|
352
|
+
return readOrAtom as any
|
|
353
|
+
}
|
|
354
|
+
return readable(readOrAtom)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// -----------------------------------------------------------------------------
|
|
358
|
+
// constructors - effect
|
|
359
|
+
// -----------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
const isDataType = (u: object): u is Option.Option<unknown> | Either.Either<unknown, unknown> =>
|
|
362
|
+
Option.TypeId in u ||
|
|
363
|
+
Either.TypeId in u
|
|
364
|
+
|
|
365
|
+
const makeRead: {
|
|
366
|
+
<A, E>(effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
367
|
+
readonly initialValue?: A
|
|
368
|
+
}): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
|
|
369
|
+
<A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
370
|
+
readonly initialValue?: A
|
|
371
|
+
}): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
|
|
372
|
+
<A, E>(stream: Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
373
|
+
readonly initialValue?: A
|
|
374
|
+
}): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
|
|
375
|
+
<A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
376
|
+
readonly initialValue?: A
|
|
377
|
+
}): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
|
|
378
|
+
<A>(create: (get: Context) => A): (get: Context, runtime?: Runtime.Runtime<any>) => A
|
|
379
|
+
<A>(initialValue: A): Writable<A>
|
|
380
|
+
} = <A, E>(
|
|
381
|
+
arg:
|
|
382
|
+
| Effect.Effect<A, E, Scope.Scope | AtomRegistry>
|
|
383
|
+
| ((get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>)
|
|
384
|
+
| Stream.Stream<A, E, AtomRegistry>
|
|
385
|
+
| ((get: Context) => Stream.Stream<A, E, AtomRegistry>)
|
|
386
|
+
| ((get: Context) => A)
|
|
387
|
+
| A,
|
|
388
|
+
options?: { readonly initialValue?: unknown }
|
|
389
|
+
) => {
|
|
390
|
+
if (typeof arg === "function") {
|
|
391
|
+
const create = arg as (get: Context) => any
|
|
392
|
+
return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
|
|
393
|
+
const value = create(get)
|
|
394
|
+
if (typeof value === "object" && value !== null) {
|
|
395
|
+
if (isDataType(value)) {
|
|
396
|
+
return value
|
|
397
|
+
} else if (Effect.EffectTypeId in value) {
|
|
398
|
+
return effect(get, value as any, options, providedRuntime)
|
|
399
|
+
} else if (Stream.StreamTypeId in value) {
|
|
400
|
+
return stream(get, value, options, providedRuntime)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return value
|
|
404
|
+
}
|
|
405
|
+
} else if (typeof arg === "object" && arg !== null) {
|
|
406
|
+
if (isDataType(arg)) {
|
|
407
|
+
return state(arg)
|
|
408
|
+
} else if (Effect.EffectTypeId in arg) {
|
|
409
|
+
return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
|
|
410
|
+
return effect(get, arg, options, providedRuntime)
|
|
411
|
+
}
|
|
412
|
+
} else if (Stream.StreamTypeId in arg) {
|
|
413
|
+
return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
|
|
414
|
+
return stream(get, arg, options, providedRuntime)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return state(arg) as any
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const state = <A>(
|
|
423
|
+
initialValue: A
|
|
424
|
+
): Writable<A> =>
|
|
425
|
+
writable(function(_get) {
|
|
426
|
+
return initialValue
|
|
427
|
+
}, constSetSelf)
|
|
428
|
+
|
|
429
|
+
const effect = <A, E>(
|
|
430
|
+
get: Context,
|
|
431
|
+
effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
|
|
432
|
+
options?: { readonly initialValue?: A; readonly uninterruptible?: boolean },
|
|
433
|
+
runtime?: Runtime.Runtime<any>
|
|
434
|
+
): Result.Result<A, E> => {
|
|
435
|
+
const initialValue = options?.initialValue !== undefined
|
|
436
|
+
? Result.success<A, E>(options.initialValue)
|
|
437
|
+
: Result.initial<A, E>()
|
|
438
|
+
return makeEffect(get, effect, initialValue, runtime, options?.uninterruptible)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function makeEffect<A, E>(
|
|
442
|
+
ctx: Context,
|
|
443
|
+
effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
|
|
444
|
+
initialValue: Result.Result<A, E>,
|
|
445
|
+
runtime = Runtime.defaultRuntime,
|
|
446
|
+
uninterruptible = false
|
|
447
|
+
): Result.Result<A, E> {
|
|
448
|
+
const previous = ctx.self<Result.Result<A, E>>()
|
|
449
|
+
|
|
450
|
+
const scope = Effect.runSync(Scope.make())
|
|
451
|
+
ctx.addFinalizer(() => {
|
|
452
|
+
Effect.runFork(Scope.close(scope, Exit.void))
|
|
453
|
+
})
|
|
454
|
+
const contextMap = new Map(runtime.context.unsafeMap)
|
|
455
|
+
contextMap.set(Scope.Scope.key, scope)
|
|
456
|
+
contextMap.set(AtomRegistry.key, ctx.registry)
|
|
457
|
+
const scopedRuntime = Runtime.make({
|
|
458
|
+
context: EffectContext.unsafeMake(contextMap),
|
|
459
|
+
fiberRefs: runtime.fiberRefs,
|
|
460
|
+
runtimeFlags: runtime.runtimeFlags
|
|
461
|
+
})
|
|
462
|
+
let syncResult: Result.Result<A, E> | undefined
|
|
463
|
+
let isAsync = false
|
|
464
|
+
const cancel = runCallbackSync(scopedRuntime)(
|
|
465
|
+
effect,
|
|
466
|
+
function(exit) {
|
|
467
|
+
syncResult = Result.fromExitWithPrevious(exit, previous)
|
|
468
|
+
if (isAsync) ctx.setSelf(syncResult)
|
|
469
|
+
},
|
|
470
|
+
uninterruptible
|
|
471
|
+
)
|
|
472
|
+
isAsync = true
|
|
473
|
+
if (cancel !== undefined) {
|
|
474
|
+
ctx.addFinalizer(cancel)
|
|
475
|
+
}
|
|
476
|
+
if (syncResult !== undefined) {
|
|
477
|
+
return syncResult
|
|
478
|
+
} else if (previous._tag === "Some") {
|
|
479
|
+
return Result.waitingFrom(previous)
|
|
480
|
+
}
|
|
481
|
+
return Result.waiting(initialValue)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// -----------------------------------------------------------------------------
|
|
485
|
+
// context
|
|
486
|
+
// -----------------------------------------------------------------------------
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* @since 1.0.0
|
|
490
|
+
* @category models
|
|
491
|
+
*/
|
|
492
|
+
export interface AtomRuntime<R, ER> extends Atom<Result.Result<Runtime.Runtime<R>, ER>> {
|
|
493
|
+
readonly layer: Atom<Layer.Layer<R, ER>>
|
|
494
|
+
|
|
495
|
+
readonly atom: {
|
|
496
|
+
<A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | R | AtomRegistry>, options?: {
|
|
497
|
+
readonly initialValue?: A
|
|
498
|
+
}): Atom<Result.Result<A, E | ER>>
|
|
499
|
+
<A, E>(effect: Effect.Effect<A, E, Scope.Scope | R>, options?: {
|
|
500
|
+
readonly initialValue?: A
|
|
501
|
+
}): Atom<Result.Result<A, E | ER>>
|
|
502
|
+
<A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
|
|
503
|
+
readonly initialValue?: A
|
|
504
|
+
}): Atom<Result.Result<A, E | ER>>
|
|
505
|
+
<A, E>(stream: Stream.Stream<A, E, AtomRegistry | R>, options?: {
|
|
506
|
+
readonly initialValue?: A
|
|
507
|
+
}): Atom<Result.Result<A, E | ER>>
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
readonly fn: {
|
|
511
|
+
<Arg>(): {
|
|
512
|
+
<E, A>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry | R>, options?: {
|
|
513
|
+
readonly initialValue?: A
|
|
514
|
+
}): AtomResultFn<Arg, A, E | ER>
|
|
515
|
+
<E, A>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
|
|
516
|
+
readonly initialValue?: A
|
|
517
|
+
}): AtomResultFn<Arg, A, E | ER | NoSuchElementException>
|
|
518
|
+
}
|
|
519
|
+
<E, A, Arg = void>(
|
|
520
|
+
fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry | R>,
|
|
521
|
+
options?: {
|
|
522
|
+
readonly initialValue?: A
|
|
523
|
+
}
|
|
524
|
+
): AtomResultFn<Arg, A, E | ER>
|
|
525
|
+
<E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
|
|
526
|
+
readonly initialValue?: A
|
|
527
|
+
}): AtomResultFn<Arg, A, E | ER | NoSuchElementException>
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
readonly pull: <A, E>(
|
|
531
|
+
create: ((get: Context) => Stream.Stream<A, E, R | AtomRegistry>) | Stream.Stream<A, E, R | AtomRegistry>,
|
|
532
|
+
options?: {
|
|
533
|
+
readonly disableAccumulation?: boolean
|
|
534
|
+
readonly initialValue?: ReadonlyArray<A>
|
|
535
|
+
}
|
|
536
|
+
) => Writable<PullResult<A, E | ER>, void>
|
|
537
|
+
|
|
538
|
+
readonly subscriptionRef: <A, E>(
|
|
539
|
+
create:
|
|
540
|
+
| Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, R | AtomRegistry>
|
|
541
|
+
| ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, R | AtomRegistry>)
|
|
542
|
+
) => Writable<Result.Result<A, E>, A>
|
|
543
|
+
|
|
544
|
+
readonly subscribable: <A, E, E1 = never>(
|
|
545
|
+
create:
|
|
546
|
+
| Effect.Effect<Subscribable.Subscribable<A, E, R>, E1, R | AtomRegistry>
|
|
547
|
+
| ((get: Context) => Effect.Effect<Subscribable.Subscribable<A, E, R>, E1, R | AtomRegistry>)
|
|
548
|
+
) => Atom<Result.Result<A, E | E1>>
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @since 1.0.0
|
|
553
|
+
* @category models
|
|
554
|
+
*/
|
|
555
|
+
export interface RuntimeFactory {
|
|
556
|
+
<R, E>(
|
|
557
|
+
create: Layer.Layer<R, E, AtomRegistry> | ((get: Context) => Layer.Layer<R, E, AtomRegistry>)
|
|
558
|
+
): AtomRuntime<R, E>
|
|
559
|
+
readonly memoMap: Layer.MemoMap
|
|
560
|
+
readonly addGlobalLayer: <A, E>(layer: Layer.Layer<A, E, AtomRegistry>) => void
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* @since 1.0.0
|
|
565
|
+
* @category constructors
|
|
566
|
+
*/
|
|
567
|
+
export const context: (options: {
|
|
568
|
+
readonly memoMap: Layer.MemoMap
|
|
569
|
+
}) => RuntimeFactory = (options) => {
|
|
570
|
+
let globalLayer: Layer.Layer<any, any, AtomRegistry> | undefined
|
|
571
|
+
function factory<E, R>(
|
|
572
|
+
create: Layer.Layer<R, E, AtomRegistry> | ((get: Context) => Layer.Layer<R, E, AtomRegistry>)
|
|
573
|
+
): AtomRuntime<R, E> {
|
|
574
|
+
const self = Object.create(RuntimeProto)
|
|
575
|
+
self.keepAlive = false
|
|
576
|
+
self.lazy = true
|
|
577
|
+
self.refresh = undefined
|
|
578
|
+
|
|
579
|
+
const layerAtom = keepAlive(
|
|
580
|
+
typeof create === "function"
|
|
581
|
+
? readable((get) => globalLayer ? Layer.provideMerge(create(get), globalLayer) : create(get))
|
|
582
|
+
: readable(() => globalLayer ? Layer.provideMerge(create, globalLayer) : create)
|
|
583
|
+
)
|
|
584
|
+
self.layer = layerAtom
|
|
585
|
+
|
|
586
|
+
self.read = function read(get: Context) {
|
|
587
|
+
const layer = get(layerAtom)
|
|
588
|
+
const build = Effect.flatMap(
|
|
589
|
+
Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(layer, options.memoMap, scope)),
|
|
590
|
+
(context) => Effect.provide(Effect.runtime<R>(), context)
|
|
591
|
+
)
|
|
592
|
+
return effect(get, build, { uninterruptible: true })
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return self
|
|
596
|
+
}
|
|
597
|
+
factory.memoMap = options.memoMap
|
|
598
|
+
factory.addGlobalLayer = (layer: Layer.Layer<any, any, AtomRegistry>) => {
|
|
599
|
+
if (globalLayer === undefined) {
|
|
600
|
+
globalLayer = layer
|
|
601
|
+
} else {
|
|
602
|
+
globalLayer = Layer.provideMerge(globalLayer, layer)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return factory
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* @since 1.0.0
|
|
610
|
+
* @category context
|
|
611
|
+
*/
|
|
612
|
+
export const defaultMemoMap: Layer.MemoMap = globalValue(
|
|
613
|
+
"@effect-atom/atom/Atom/defaultMemoMap",
|
|
614
|
+
() => Effect.runSync(Layer.makeMemoMap)
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* @since 1.0.0
|
|
619
|
+
* @category context
|
|
620
|
+
*/
|
|
621
|
+
export const runtime: RuntimeFactory = globalValue(
|
|
622
|
+
"@effect-atom/atom/Atom/defaultContext",
|
|
623
|
+
() => context({ memoMap: defaultMemoMap })
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
// -----------------------------------------------------------------------------
|
|
627
|
+
// constructors - stream
|
|
628
|
+
// -----------------------------------------------------------------------------
|
|
629
|
+
|
|
630
|
+
const stream = <A, E>(
|
|
631
|
+
get: Context,
|
|
632
|
+
stream: Stream.Stream<A, E, AtomRegistry>,
|
|
633
|
+
options?: { readonly initialValue?: A },
|
|
634
|
+
runtime?: Runtime.Runtime<any>
|
|
635
|
+
): Result.Result<A, E | NoSuchElementException> => {
|
|
636
|
+
const initialValue = options?.initialValue !== undefined
|
|
637
|
+
? Result.success<A, E>(options.initialValue)
|
|
638
|
+
: Result.initial<A, E>()
|
|
639
|
+
return makeStream(get, stream, initialValue, runtime)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function makeStream<A, E>(
|
|
643
|
+
ctx: Context,
|
|
644
|
+
stream: Stream.Stream<A, E, AtomRegistry>,
|
|
645
|
+
initialValue: Result.Result<A, E | NoSuchElementException>,
|
|
646
|
+
runtime = Runtime.defaultRuntime
|
|
647
|
+
): Result.Result<A, E | NoSuchElementException> {
|
|
648
|
+
const previous = ctx.self<Result.Result<A, E | NoSuchElementException>>()
|
|
649
|
+
|
|
650
|
+
const writer: Channel.Channel<never, Chunk.Chunk<A>, never, E> = Channel.readWithCause({
|
|
651
|
+
onInput(input: Chunk.Chunk<A>) {
|
|
652
|
+
return Channel.suspend(() => {
|
|
653
|
+
const last = Chunk.last(input)
|
|
654
|
+
if (last._tag === "Some") {
|
|
655
|
+
ctx.setSelf(Result.success(last.value, {
|
|
656
|
+
waiting: true
|
|
657
|
+
}))
|
|
658
|
+
}
|
|
659
|
+
return writer
|
|
660
|
+
})
|
|
661
|
+
},
|
|
662
|
+
onFailure(cause: Cause.Cause<E>) {
|
|
663
|
+
return Channel.sync(() => {
|
|
664
|
+
ctx.setSelf(Result.failureWithPrevious(cause, { previous }))
|
|
665
|
+
})
|
|
666
|
+
},
|
|
667
|
+
onDone(_done: unknown) {
|
|
668
|
+
return Channel.sync(() => {
|
|
669
|
+
pipe(
|
|
670
|
+
ctx.self<Result.Result<A, E | NoSuchElementException>>(),
|
|
671
|
+
Option.flatMap(Result.value),
|
|
672
|
+
Option.match({
|
|
673
|
+
onNone: () => ctx.setSelf(Result.failWithPrevious(new NoSuchElementException(), { previous })),
|
|
674
|
+
onSome: (a) => ctx.setSelf(Result.success(a))
|
|
675
|
+
})
|
|
676
|
+
)
|
|
677
|
+
})
|
|
678
|
+
}
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
const registryRuntime = Runtime.make({
|
|
682
|
+
context: EffectContext.add(runtime.context, AtomRegistry, ctx.registry),
|
|
683
|
+
fiberRefs: runtime.fiberRefs,
|
|
684
|
+
runtimeFlags: runtime.runtimeFlags
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
const cancel = runCallbackSync(registryRuntime)(
|
|
688
|
+
Channel.runDrain(Channel.pipeTo(Stream.toChannel(stream), writer)),
|
|
689
|
+
constVoid
|
|
690
|
+
)
|
|
691
|
+
if (cancel !== undefined) {
|
|
692
|
+
ctx.addFinalizer(cancel)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (previous._tag === "Some") {
|
|
696
|
+
return Result.waitingFrom(previous)
|
|
697
|
+
}
|
|
698
|
+
return Result.waiting(initialValue)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// -----------------------------------------------------------------------------
|
|
702
|
+
// constructors - subscription ref
|
|
703
|
+
// -----------------------------------------------------------------------------
|
|
704
|
+
|
|
705
|
+
/** @internal */
|
|
706
|
+
const readSubscribable = (
|
|
707
|
+
get: Context,
|
|
708
|
+
sub:
|
|
709
|
+
| Subscribable.Subscribable<any, any>
|
|
710
|
+
| Result.Result<Subscribable.Subscribable<any, any>, any>,
|
|
711
|
+
runtime = Runtime.defaultRuntime
|
|
712
|
+
) => {
|
|
713
|
+
if (Subscribable.TypeId in sub) {
|
|
714
|
+
get.addFinalizer(
|
|
715
|
+
sub.changes.pipe(
|
|
716
|
+
Stream.runForEach((value) => {
|
|
717
|
+
get.setSelf(value)
|
|
718
|
+
return Effect.void
|
|
719
|
+
}),
|
|
720
|
+
Runtime.runCallback(runtime)
|
|
721
|
+
)
|
|
722
|
+
)
|
|
723
|
+
return Runtime.runSync(runtime)(sub.get)
|
|
724
|
+
} else if (sub._tag !== "Success") {
|
|
725
|
+
return sub
|
|
726
|
+
}
|
|
727
|
+
return makeStream(get, sub.value.changes, Result.initial(true), runtime)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const makeSubRef = (
|
|
731
|
+
refAtom: Atom<SubscriptionRef.SubscriptionRef<any> | Result.Result<SubscriptionRef.SubscriptionRef<any>, any>>,
|
|
732
|
+
read: (
|
|
733
|
+
get: Context,
|
|
734
|
+
ref: SubscriptionRef.SubscriptionRef<any> | Result.Success<SubscriptionRef.SubscriptionRef<any>, any>
|
|
735
|
+
) => any
|
|
736
|
+
) => {
|
|
737
|
+
function write(ctx: WriteContext<SubscriptionRef.SubscriptionRef<any>>, value: any) {
|
|
738
|
+
const ref = ctx.get(refAtom)
|
|
739
|
+
if (SubscriptionRef.SubscriptionRefTypeId in ref) {
|
|
740
|
+
Effect.runSync(SubscriptionRef.set(ref, value))
|
|
741
|
+
} else if (Result.isSuccess(ref)) {
|
|
742
|
+
Effect.runSync(SubscriptionRef.set(ref.value, value))
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return writable((get) => {
|
|
746
|
+
const ref = get(refAtom)
|
|
747
|
+
if (SubscriptionRef.SubscriptionRefTypeId in ref) {
|
|
748
|
+
return read(get, ref)
|
|
749
|
+
} else if (Result.isSuccess(ref)) {
|
|
750
|
+
return read(get, ref)
|
|
751
|
+
}
|
|
752
|
+
return ref
|
|
753
|
+
}, write)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* @since 1.0.0
|
|
758
|
+
* @category constructors
|
|
759
|
+
*/
|
|
760
|
+
export const subscriptionRef: {
|
|
761
|
+
<A>(ref: SubscriptionRef.SubscriptionRef<A> | ((get: Context) => SubscriptionRef.SubscriptionRef<A>)): Writable<A>
|
|
762
|
+
<A, E>(
|
|
763
|
+
effect:
|
|
764
|
+
| Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, Scope.Scope | AtomRegistry>
|
|
765
|
+
| ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, Scope.Scope | AtomRegistry>)
|
|
766
|
+
): Writable<Result.Result<A, E>, A>
|
|
767
|
+
} = (
|
|
768
|
+
ref:
|
|
769
|
+
| SubscriptionRef.SubscriptionRef<any>
|
|
770
|
+
| ((get: Context) => SubscriptionRef.SubscriptionRef<any>)
|
|
771
|
+
| Effect.Effect<SubscriptionRef.SubscriptionRef<any>, any, Scope.Scope | AtomRegistry>
|
|
772
|
+
| ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<any>, any, Scope.Scope | AtomRegistry>)
|
|
773
|
+
) =>
|
|
774
|
+
makeSubRef(
|
|
775
|
+
readable((get) => {
|
|
776
|
+
const value = typeof ref === "function" ? ref(get) : ref
|
|
777
|
+
return SubscriptionRef.SubscriptionRefTypeId in value
|
|
778
|
+
? value
|
|
779
|
+
: makeEffect(get, value, Result.initial(true))
|
|
780
|
+
}),
|
|
781
|
+
readSubscribable
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
// -----------------------------------------------------------------------------
|
|
785
|
+
// constructors - subscribable
|
|
786
|
+
// -----------------------------------------------------------------------------
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @since 1.0.0
|
|
790
|
+
* @category constructors
|
|
791
|
+
*/
|
|
792
|
+
export const subscribable: {
|
|
793
|
+
<A, E>(
|
|
794
|
+
ref: Subscribable.Subscribable<A, E> | ((get: Context) => Subscribable.Subscribable<A, E>)
|
|
795
|
+
): Atom<A>
|
|
796
|
+
<A, E, E1>(
|
|
797
|
+
effect:
|
|
798
|
+
| Effect.Effect<Subscribable.Subscribable<A, E1>, E, Scope.Scope | AtomRegistry>
|
|
799
|
+
| ((get: Context) => Effect.Effect<Subscribable.Subscribable<A, E1>, E, Scope.Scope | AtomRegistry>)
|
|
800
|
+
): Atom<Result.Result<A, E | E1>>
|
|
801
|
+
} = (
|
|
802
|
+
ref:
|
|
803
|
+
| Subscribable.Subscribable<any, any>
|
|
804
|
+
| ((get: Context) => Subscribable.Subscribable<any, any>)
|
|
805
|
+
| Effect.Effect<Subscribable.Subscribable<any, any>, any, Scope.Scope | AtomRegistry>
|
|
806
|
+
| ((get: Context) => Effect.Effect<Subscribable.Subscribable<any, any>, any, Scope.Scope | AtomRegistry>)
|
|
807
|
+
) =>
|
|
808
|
+
makeSubscribable(
|
|
809
|
+
readable((get) => {
|
|
810
|
+
const value = typeof ref === "function" ? ref(get) : ref
|
|
811
|
+
return Subscribable.isSubscribable(value)
|
|
812
|
+
? value
|
|
813
|
+
: makeEffect(get, value, Result.initial(true))
|
|
814
|
+
}),
|
|
815
|
+
readSubscribable
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
const makeSubscribable = (
|
|
819
|
+
subAtom: Atom<Subscribable.Subscribable<any, any> | Result.Result<Subscribable.Subscribable<any, any>, any>>,
|
|
820
|
+
read: (
|
|
821
|
+
get: Context,
|
|
822
|
+
sub: Subscribable.Subscribable<any, any> | Result.Success<Subscribable.Subscribable<any, any>, any>
|
|
823
|
+
) => any
|
|
824
|
+
) =>
|
|
825
|
+
readable((get) => {
|
|
826
|
+
const sub = get(subAtom)
|
|
827
|
+
if (Subscribable.isSubscribable(sub)) {
|
|
828
|
+
return read(get, sub)
|
|
829
|
+
} else if (Result.isSuccess(sub)) {
|
|
830
|
+
return read(get, sub)
|
|
831
|
+
}
|
|
832
|
+
return sub
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
// -----------------------------------------------------------------------------
|
|
836
|
+
// constructors - functions
|
|
837
|
+
// -----------------------------------------------------------------------------
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* @since 1.0.0
|
|
841
|
+
* @category models
|
|
842
|
+
*/
|
|
843
|
+
export interface FnContext extends Omit<Context, "get" | "once" | "resultOnce" | "someOnce" | "refreshSelf"> {
|
|
844
|
+
<A>(atom: Atom<A>): A
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* @since 1.0.0
|
|
849
|
+
* @category constructors
|
|
850
|
+
*/
|
|
851
|
+
export const fnSync: {
|
|
852
|
+
<Arg>(): {
|
|
853
|
+
<A>(
|
|
854
|
+
f: (arg: Arg, get: FnContext) => A
|
|
855
|
+
): Writable<Option.Option<A>, Arg>
|
|
856
|
+
<A>(
|
|
857
|
+
f: (arg: Arg, get: FnContext) => A,
|
|
858
|
+
options: { readonly initialValue: A }
|
|
859
|
+
): Writable<A, Arg>
|
|
860
|
+
}
|
|
861
|
+
<A, Arg = void>(
|
|
862
|
+
f: (arg: Arg, get: FnContext) => A
|
|
863
|
+
): Writable<Option.Option<A>, Arg>
|
|
864
|
+
<A, Arg = void>(
|
|
865
|
+
f: (arg: Arg, get: FnContext) => A,
|
|
866
|
+
options: { readonly initialValue: A }
|
|
867
|
+
): Writable<A, Arg>
|
|
868
|
+
} = function(...args: ReadonlyArray<any>) {
|
|
869
|
+
if (args.length === 0) {
|
|
870
|
+
return makeFnSync
|
|
871
|
+
}
|
|
872
|
+
return makeFnSync(...args as [any, any]) as any
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const makeFnSync = <Arg, A>(f: (arg: Arg, get: FnContext) => A, options?: {
|
|
876
|
+
readonly initialValue?: A
|
|
877
|
+
}): Writable<Option.Option<A> | A, Arg> => {
|
|
878
|
+
const argAtom = state<[number, Arg]>([0, undefined as any])
|
|
879
|
+
const hasInitialValue = options?.initialValue !== undefined
|
|
880
|
+
return writable(function(get) {
|
|
881
|
+
;(get as any).isFn = true
|
|
882
|
+
const [counter, arg] = get.get(argAtom)
|
|
883
|
+
if (counter === 0) {
|
|
884
|
+
return hasInitialValue ? options.initialValue : Option.none()
|
|
885
|
+
}
|
|
886
|
+
return hasInitialValue ? f(arg, get) : Option.some(f(arg, get))
|
|
887
|
+
}, function(ctx, arg) {
|
|
888
|
+
batch(() => {
|
|
889
|
+
ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg as Arg])
|
|
890
|
+
ctx.refreshSelf()
|
|
891
|
+
})
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* @since 1.0.0
|
|
897
|
+
* @category models
|
|
898
|
+
*/
|
|
899
|
+
export interface AtomResultFn<Arg, A, E = never> extends Writable<Result.Result<A, E>, Arg | Reset> {}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* @since 1.0.0
|
|
903
|
+
* @category symbols
|
|
904
|
+
*/
|
|
905
|
+
export const Reset = Symbol.for("@effect-atom/atom/Atom/Reset")
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* @since 1.0.0
|
|
909
|
+
* @category symbols
|
|
910
|
+
*/
|
|
911
|
+
export type Reset = typeof Reset
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* @since 1.0.0
|
|
915
|
+
* @category constructors
|
|
916
|
+
*/
|
|
917
|
+
export const fn: {
|
|
918
|
+
<Arg>(): <E, A>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
919
|
+
readonly initialValue?: A
|
|
920
|
+
}) => AtomResultFn<Arg, A, E>
|
|
921
|
+
<E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
|
|
922
|
+
readonly initialValue?: A
|
|
923
|
+
}): AtomResultFn<Arg, A, E>
|
|
924
|
+
<Arg>(): <E, A>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
925
|
+
readonly initialValue?: A
|
|
926
|
+
}) => AtomResultFn<Arg, A, E | NoSuchElementException>
|
|
927
|
+
<E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry>, options?: {
|
|
928
|
+
readonly initialValue?: A
|
|
929
|
+
}): AtomResultFn<Arg, A, E | NoSuchElementException>
|
|
930
|
+
} = function(...args: ReadonlyArray<any>) {
|
|
931
|
+
if (args.length === 0) {
|
|
932
|
+
return makeFn
|
|
933
|
+
}
|
|
934
|
+
return makeFn(...args as [any, any]) as any
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const makeFn = <Arg, E, A>(
|
|
938
|
+
f: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry> | Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
|
|
939
|
+
options?: {
|
|
940
|
+
readonly initialValue?: A
|
|
941
|
+
}
|
|
942
|
+
): AtomResultFn<Arg, A, E | NoSuchElementException> => {
|
|
943
|
+
const [read, write] = makeResultFn(f, options)
|
|
944
|
+
return writable(read, write) as any
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function makeResultFn<Arg, E, A>(
|
|
948
|
+
f: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry> | Stream.Stream<A, E, AtomRegistry>,
|
|
949
|
+
options?: { readonly initialValue?: A }
|
|
950
|
+
) {
|
|
951
|
+
const argAtom = state<[number, Arg]>([0, undefined as any])
|
|
952
|
+
const initialValue = options?.initialValue !== undefined
|
|
953
|
+
? Result.success<A, E>(options.initialValue)
|
|
954
|
+
: Result.initial<A, E>()
|
|
955
|
+
|
|
956
|
+
function read(get: Context, runtime?: Runtime.Runtime<any>): Result.Result<A, E | NoSuchElementException> {
|
|
957
|
+
;(get as any).isFn = true
|
|
958
|
+
const [counter, arg] = get.get(argAtom)
|
|
959
|
+
if (counter === 0) {
|
|
960
|
+
return initialValue
|
|
961
|
+
}
|
|
962
|
+
const value = f(arg, get)
|
|
963
|
+
if (Effect.EffectTypeId in value) {
|
|
964
|
+
return makeEffect(get, value, initialValue, runtime)
|
|
965
|
+
}
|
|
966
|
+
return makeStream(get, value, initialValue, runtime)
|
|
967
|
+
}
|
|
968
|
+
function write(ctx: WriteContext<Result.Result<A, E | NoSuchElementException>>, arg: Arg | Reset) {
|
|
969
|
+
batch(() => {
|
|
970
|
+
if (arg === Reset) {
|
|
971
|
+
ctx.set(argAtom, [0, undefined as any])
|
|
972
|
+
} else {
|
|
973
|
+
ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg])
|
|
974
|
+
}
|
|
975
|
+
ctx.refreshSelf()
|
|
976
|
+
})
|
|
977
|
+
}
|
|
978
|
+
return [read, write, argAtom] as const
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* @since 1.0.0
|
|
983
|
+
* @category models
|
|
984
|
+
*/
|
|
985
|
+
export type PullResult<A, E = never> = Result.Result<{
|
|
986
|
+
readonly done: boolean
|
|
987
|
+
readonly items: Arr.NonEmptyArray<A>
|
|
988
|
+
}, E | Cause.NoSuchElementException>
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* @since 1.0.0
|
|
992
|
+
* @category constructors
|
|
993
|
+
*/
|
|
994
|
+
export const pull = <A, E>(
|
|
995
|
+
create: ((get: Context) => Stream.Stream<A, E, AtomRegistry>) | Stream.Stream<A, E, AtomRegistry>,
|
|
996
|
+
options?: {
|
|
997
|
+
readonly disableAccumulation?: boolean
|
|
998
|
+
}
|
|
999
|
+
): Writable<PullResult<A, E>, void> => {
|
|
1000
|
+
const pullSignal = state(0)
|
|
1001
|
+
const pullAtom = readable(
|
|
1002
|
+
makeRead(function(get) {
|
|
1003
|
+
return makeStreamPullEffect(get, pullSignal, create, options)
|
|
1004
|
+
})
|
|
1005
|
+
)
|
|
1006
|
+
return makeStreamPull(pullSignal, pullAtom)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const makeStreamPullEffect = <A, E>(
|
|
1010
|
+
get: Context,
|
|
1011
|
+
pullSignal: Atom<number>,
|
|
1012
|
+
create: Stream.Stream<A, E, AtomRegistry> | ((get: Context) => Stream.Stream<A, E, AtomRegistry>),
|
|
1013
|
+
options?: {
|
|
1014
|
+
readonly disableAccumulation?: boolean
|
|
1015
|
+
}
|
|
1016
|
+
): Effect.Effect<
|
|
1017
|
+
{ readonly done: boolean; readonly items: Arr.NonEmptyArray<A> },
|
|
1018
|
+
E | Cause.NoSuchElementException,
|
|
1019
|
+
Scope.Scope | AtomRegistry
|
|
1020
|
+
> =>
|
|
1021
|
+
Effect.flatMap(
|
|
1022
|
+
Channel.toPull(
|
|
1023
|
+
Stream.toChannel(typeof create === "function" ? create(get) : create)
|
|
1024
|
+
),
|
|
1025
|
+
(pullChunk) => {
|
|
1026
|
+
const semaphore = Effect.unsafeMakeSemaphore(1)
|
|
1027
|
+
const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
|
|
1028
|
+
const context = fiber.currentContext as EffectContext.Context<AtomRegistry | Scope.Scope>
|
|
1029
|
+
let acc = Chunk.empty<A>()
|
|
1030
|
+
const pull: Effect.Effect<
|
|
1031
|
+
{
|
|
1032
|
+
done: boolean
|
|
1033
|
+
items: Arr.NonEmptyArray<A>
|
|
1034
|
+
},
|
|
1035
|
+
NoSuchElementException | E,
|
|
1036
|
+
Registry.AtomRegistry
|
|
1037
|
+
> = Effect.flatMap(
|
|
1038
|
+
Effect.locally(
|
|
1039
|
+
Effect.suspend(() => pullChunk),
|
|
1040
|
+
FiberRef.currentContext,
|
|
1041
|
+
context
|
|
1042
|
+
),
|
|
1043
|
+
Either.match({
|
|
1044
|
+
onLeft: (): Effect.Effect<
|
|
1045
|
+
{ done: boolean; items: Arr.NonEmptyArray<A> },
|
|
1046
|
+
NoSuchElementException
|
|
1047
|
+
> => {
|
|
1048
|
+
const items = Chunk.toReadonlyArray(acc) as Array<A>
|
|
1049
|
+
if (!Arr.isNonEmptyArray(items)) {
|
|
1050
|
+
return Effect.fail(new Cause.NoSuchElementException(`Atom.pull: no items`))
|
|
1051
|
+
}
|
|
1052
|
+
return Effect.succeed({ done: true, items })
|
|
1053
|
+
},
|
|
1054
|
+
onRight(chunk) {
|
|
1055
|
+
let items: Chunk.Chunk<A>
|
|
1056
|
+
if (options?.disableAccumulation) {
|
|
1057
|
+
items = chunk
|
|
1058
|
+
} else {
|
|
1059
|
+
items = Chunk.appendAll(acc, chunk)
|
|
1060
|
+
acc = items
|
|
1061
|
+
}
|
|
1062
|
+
const arr = Chunk.toReadonlyArray(items) as Array<A>
|
|
1063
|
+
if (!Arr.isNonEmptyArray(arr)) {
|
|
1064
|
+
return pull
|
|
1065
|
+
}
|
|
1066
|
+
return Effect.succeed({ done: false, items: arr })
|
|
1067
|
+
}
|
|
1068
|
+
})
|
|
1069
|
+
)
|
|
1070
|
+
const pullWithSemaphore = semaphore.withPermits(1)(pull)
|
|
1071
|
+
|
|
1072
|
+
const runCallback = runCallbackSync(Runtime.make({
|
|
1073
|
+
context,
|
|
1074
|
+
fiberRefs: fiber.getFiberRefs(),
|
|
1075
|
+
runtimeFlags: Runtime.defaultRuntime.runtimeFlags
|
|
1076
|
+
}))
|
|
1077
|
+
const cancels = new Set<() => void>()
|
|
1078
|
+
get.addFinalizer(() => {
|
|
1079
|
+
for (const cancel of cancels) cancel()
|
|
1080
|
+
})
|
|
1081
|
+
get.once(pullSignal)
|
|
1082
|
+
get.subscribe(pullSignal, () => {
|
|
1083
|
+
get.setSelf(Result.waitingFrom(get.self<PullResult<A, E>>()))
|
|
1084
|
+
let cancel: (() => void) | undefined
|
|
1085
|
+
// eslint-disable-next-line prefer-const
|
|
1086
|
+
cancel = runCallback(pullWithSemaphore, (exit) => {
|
|
1087
|
+
if (cancel) cancels.delete(cancel)
|
|
1088
|
+
const result = Result.fromExitWithPrevious(exit, get.self())
|
|
1089
|
+
const pending = cancels.size > 0
|
|
1090
|
+
get.setSelf(pending ? Result.waiting(result) : result)
|
|
1091
|
+
})
|
|
1092
|
+
if (cancel) cancels.add(cancel)
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
return pull
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
const makeStreamPull = <A, E>(
|
|
1100
|
+
pullSignal: Writable<number>,
|
|
1101
|
+
pullAtom: Atom<PullResult<A, E>>
|
|
1102
|
+
) =>
|
|
1103
|
+
writable(pullAtom.read, function(ctx, _) {
|
|
1104
|
+
ctx.set(pullSignal, ctx.get(pullSignal) + 1)
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* @since 1.0.0
|
|
1109
|
+
* @category constructors
|
|
1110
|
+
*/
|
|
1111
|
+
export const family = typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined" ?
|
|
1112
|
+
<Arg, T extends object>(
|
|
1113
|
+
f: (arg: Arg) => T
|
|
1114
|
+
): (arg: Arg) => T => {
|
|
1115
|
+
const atoms = MutableHashMap.empty<Arg, T>()
|
|
1116
|
+
return function(arg) {
|
|
1117
|
+
const atomEntry = MutableHashMap.get(atoms, arg)
|
|
1118
|
+
if (atomEntry._tag === "Some") {
|
|
1119
|
+
return atomEntry.value
|
|
1120
|
+
}
|
|
1121
|
+
const newAtom = f(arg)
|
|
1122
|
+
MutableHashMap.set(atoms, arg, newAtom)
|
|
1123
|
+
return newAtom
|
|
1124
|
+
}
|
|
1125
|
+
} :
|
|
1126
|
+
<Arg, T extends object>(
|
|
1127
|
+
f: (arg: Arg) => T
|
|
1128
|
+
): (arg: Arg) => T => {
|
|
1129
|
+
const atoms = MutableHashMap.empty<Arg, WeakRef<T>>()
|
|
1130
|
+
const registry = new FinalizationRegistry<Arg>((arg) => {
|
|
1131
|
+
MutableHashMap.remove(atoms, arg)
|
|
1132
|
+
})
|
|
1133
|
+
return function(arg) {
|
|
1134
|
+
const atomEntry = MutableHashMap.get(atoms, arg).pipe(
|
|
1135
|
+
Option.flatMapNullable((ref) => ref.deref())
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
if (atomEntry._tag === "Some") {
|
|
1139
|
+
return atomEntry.value
|
|
1140
|
+
}
|
|
1141
|
+
const newAtom = f(arg)
|
|
1142
|
+
MutableHashMap.set(atoms, arg, new WeakRef(newAtom))
|
|
1143
|
+
registry.register(newAtom, arg)
|
|
1144
|
+
return newAtom
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* @since 1.0.0
|
|
1150
|
+
* @category combinators
|
|
1151
|
+
*/
|
|
1152
|
+
export const withFallback: {
|
|
1153
|
+
<E2, A2>(
|
|
1154
|
+
fallback: Atom<Result.Result<A2, E2>>
|
|
1155
|
+
): <R extends Atom<Result.Result<any, any>>>(
|
|
1156
|
+
self: R
|
|
1157
|
+
) => [R] extends [Writable<infer _, infer RW>]
|
|
1158
|
+
? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
|
|
1159
|
+
: Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>>
|
|
1160
|
+
<R extends Atom<Result.Result<any, any>>, A2, E2>(
|
|
1161
|
+
self: R,
|
|
1162
|
+
fallback: Atom<Result.Result<A2, E2>>
|
|
1163
|
+
): [R] extends [Writable<infer _, infer RW>]
|
|
1164
|
+
? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
|
|
1165
|
+
: Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>>
|
|
1166
|
+
} = dual(2, <R extends Atom<Result.Result<any, any>>, A2, E2>(
|
|
1167
|
+
self: R,
|
|
1168
|
+
fallback: Atom<Result.Result<A2, E2>>
|
|
1169
|
+
): [R] extends [Writable<infer _, infer RW>]
|
|
1170
|
+
? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
|
|
1171
|
+
: Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>> =>
|
|
1172
|
+
{
|
|
1173
|
+
function withFallback(get: Context) {
|
|
1174
|
+
const result = get(self)
|
|
1175
|
+
if (result._tag === "Initial") {
|
|
1176
|
+
return Result.waiting(get(fallback))
|
|
1177
|
+
}
|
|
1178
|
+
return result
|
|
1179
|
+
}
|
|
1180
|
+
return isWritable(self)
|
|
1181
|
+
? writable(
|
|
1182
|
+
withFallback,
|
|
1183
|
+
self.write,
|
|
1184
|
+
self.refresh ?? function(refresh) {
|
|
1185
|
+
refresh(self)
|
|
1186
|
+
}
|
|
1187
|
+
) as any
|
|
1188
|
+
: readable(
|
|
1189
|
+
withFallback,
|
|
1190
|
+
self.refresh ?? function(refresh) {
|
|
1191
|
+
refresh(self)
|
|
1192
|
+
}
|
|
1193
|
+
) as any
|
|
1194
|
+
})
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* @since 1.0.0
|
|
1198
|
+
* @category combinators
|
|
1199
|
+
*/
|
|
1200
|
+
export const keepAlive = <A extends Atom<any>>(self: A): A =>
|
|
1201
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1202
|
+
...self,
|
|
1203
|
+
keepAlive: true
|
|
1204
|
+
})
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Reverts the `keepAlive` behavior of a reactive value, allowing it to be
|
|
1208
|
+
* disposed of when not in use.
|
|
1209
|
+
*
|
|
1210
|
+
* Note that Atom's have this behavior by default.
|
|
1211
|
+
*
|
|
1212
|
+
* @since 1.0.0
|
|
1213
|
+
* @category combinators
|
|
1214
|
+
*/
|
|
1215
|
+
export const autoDispose = <A extends Atom<any>>(self: A): A =>
|
|
1216
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1217
|
+
...self,
|
|
1218
|
+
keepAlive: false
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* @since 1.0.0
|
|
1223
|
+
* @category combinators
|
|
1224
|
+
*/
|
|
1225
|
+
export const setLazy: {
|
|
1226
|
+
(lazy: boolean): <A extends Atom<any>>(self: A) => A
|
|
1227
|
+
<A extends Atom<any>>(self: A, lazy: boolean): A
|
|
1228
|
+
} = dual(2, <A extends Atom<any>>(self: A, lazy: boolean) =>
|
|
1229
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1230
|
+
...self,
|
|
1231
|
+
lazy
|
|
1232
|
+
}))
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* @since 1.0.0
|
|
1236
|
+
* @category combinators
|
|
1237
|
+
*/
|
|
1238
|
+
export const withLabel: {
|
|
1239
|
+
(name: string): <A extends Atom<any>>(self: A) => A
|
|
1240
|
+
<A extends Atom<any>>(self: A, name: string): A
|
|
1241
|
+
} = dual<
|
|
1242
|
+
(name: string) => <A extends Atom<any>>(self: A) => A,
|
|
1243
|
+
<A extends Atom<any>>(self: A, name: string) => A
|
|
1244
|
+
>(2, (self, name) =>
|
|
1245
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1246
|
+
...self,
|
|
1247
|
+
label: [name, new Error().stack?.split("\n")[5] ?? ""]
|
|
1248
|
+
}))
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* @since 1.0.0
|
|
1252
|
+
* @category combinators
|
|
1253
|
+
*/
|
|
1254
|
+
export const setIdleTTL: {
|
|
1255
|
+
(duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => A
|
|
1256
|
+
<A extends Atom<any>>(self: A, duration: Duration.DurationInput): A
|
|
1257
|
+
} = dual<
|
|
1258
|
+
(duration: Duration.DurationInput) => <A extends Atom<any>>(self: A) => A,
|
|
1259
|
+
<A extends Atom<any>>(self: A, duration: Duration.DurationInput) => A
|
|
1260
|
+
>(2, (self, duration) =>
|
|
1261
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1262
|
+
...self,
|
|
1263
|
+
keepAlive: false,
|
|
1264
|
+
idleTTL: Duration.toMillis(duration)
|
|
1265
|
+
}))
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* @since 1.0.0
|
|
1269
|
+
* @category combinators
|
|
1270
|
+
*/
|
|
1271
|
+
export const initialValue: {
|
|
1272
|
+
<A>(initialValue: A): (self: Atom<A>) => readonly [Atom<A>, A]
|
|
1273
|
+
<A>(self: Atom<A>, initialValue: A): readonly [Atom<A>, A]
|
|
1274
|
+
} = dual<
|
|
1275
|
+
<A>(initialValue: A) => (self: Atom<A>) => readonly [Atom<A>, A],
|
|
1276
|
+
<A>(self: Atom<A>, initialValue: A) => readonly [Atom<A>, A]
|
|
1277
|
+
>(2, (self, initialValue) => [self, initialValue])
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* @since 1.0.0
|
|
1281
|
+
* @category combinators
|
|
1282
|
+
*/
|
|
1283
|
+
export const transform: {
|
|
1284
|
+
<R extends Atom<any>, B>(
|
|
1285
|
+
f: (get: Context) => B
|
|
1286
|
+
): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
|
|
1287
|
+
<R extends Atom<any>, B>(
|
|
1288
|
+
self: R,
|
|
1289
|
+
f: (get: Context) => B
|
|
1290
|
+
): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
|
|
1291
|
+
} = dual(
|
|
1292
|
+
2,
|
|
1293
|
+
(<A, B>(self: Atom<A>, f: (get: Context) => B): Atom<B> =>
|
|
1294
|
+
isWritable(self)
|
|
1295
|
+
? writable(
|
|
1296
|
+
f,
|
|
1297
|
+
function(ctx, value) {
|
|
1298
|
+
ctx.set(self, value)
|
|
1299
|
+
},
|
|
1300
|
+
self.refresh ?? function(refresh) {
|
|
1301
|
+
refresh(self)
|
|
1302
|
+
}
|
|
1303
|
+
)
|
|
1304
|
+
: readable(
|
|
1305
|
+
f,
|
|
1306
|
+
self.refresh ?? function(refresh) {
|
|
1307
|
+
refresh(self)
|
|
1308
|
+
}
|
|
1309
|
+
)) as any
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* @since 1.0.0
|
|
1314
|
+
* @category combinators
|
|
1315
|
+
*/
|
|
1316
|
+
export const map: {
|
|
1317
|
+
<R extends Atom<any>, B>(
|
|
1318
|
+
f: (_: Type<R>) => B
|
|
1319
|
+
): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
|
|
1320
|
+
<R extends Atom<any>, B>(
|
|
1321
|
+
self: R,
|
|
1322
|
+
f: (_: Type<R>) => B
|
|
1323
|
+
): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
|
|
1324
|
+
} = dual(
|
|
1325
|
+
2,
|
|
1326
|
+
<A, B>(self: Atom<A>, f: (_: A) => B): Atom<B> => transform(self, (get) => f(get(self)))
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* @since 1.0.0
|
|
1331
|
+
* @category combinators
|
|
1332
|
+
*/
|
|
1333
|
+
export const mapResult: {
|
|
1334
|
+
<R extends Atom<Result.Result<any, any>>, B>(
|
|
1335
|
+
f: (_: Result.Result.Success<Type<R>>) => B
|
|
1336
|
+
): (
|
|
1337
|
+
self: R
|
|
1338
|
+
) => [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
|
|
1339
|
+
: Atom<Result.Result<B, Result.Result.Failure<Type<R>>>>
|
|
1340
|
+
<R extends Atom<Result.Result<any, any>>, B>(
|
|
1341
|
+
self: R,
|
|
1342
|
+
f: (_: Result.Result.Success<Type<R>>) => B
|
|
1343
|
+
): [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
|
|
1344
|
+
: Atom<Result.Result<B, Result.Result.Failure<Type<R>>>>
|
|
1345
|
+
} = dual(2, <R extends Atom<Result.Result<any, any>>, B>(
|
|
1346
|
+
self: R,
|
|
1347
|
+
f: (_: Result.Result.Success<Type<R>>) => B
|
|
1348
|
+
): [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
|
|
1349
|
+
: Atom<Result.Result<B, Result.Result.Failure<Type<R>>>> => map(self, Result.map(f)))
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* @since 1.0.0
|
|
1353
|
+
* @category combinators
|
|
1354
|
+
*/
|
|
1355
|
+
export const debounce: {
|
|
1356
|
+
(duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => WithoutSerializable<A>
|
|
1357
|
+
<A extends Atom<any>>(self: A, duration: Duration.DurationInput): WithoutSerializable<A>
|
|
1358
|
+
} = dual(
|
|
1359
|
+
2,
|
|
1360
|
+
<A>(self: Atom<A>, duration: Duration.DurationInput): Atom<A> => {
|
|
1361
|
+
const millis = Duration.toMillis(duration)
|
|
1362
|
+
return transform(self, function(get) {
|
|
1363
|
+
let timeout: number | undefined
|
|
1364
|
+
let value = get.once(self)
|
|
1365
|
+
function update() {
|
|
1366
|
+
timeout = undefined
|
|
1367
|
+
get.setSelf(value)
|
|
1368
|
+
}
|
|
1369
|
+
get.addFinalizer(function() {
|
|
1370
|
+
if (timeout) clearTimeout(timeout)
|
|
1371
|
+
})
|
|
1372
|
+
get.subscribe(self, function(val) {
|
|
1373
|
+
value = val
|
|
1374
|
+
if (timeout) clearTimeout(timeout)
|
|
1375
|
+
timeout = setTimeout(update, millis)
|
|
1376
|
+
})
|
|
1377
|
+
return value
|
|
1378
|
+
})
|
|
1379
|
+
}
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* @since 1.0.0
|
|
1384
|
+
* @category Optimistic
|
|
1385
|
+
*/
|
|
1386
|
+
export const optimistic = <A>(
|
|
1387
|
+
self: Atom<A>
|
|
1388
|
+
): Writable<A, Atom<Result.Result<A, unknown>>> => {
|
|
1389
|
+
let counter = 0
|
|
1390
|
+
const writeAtom = state(
|
|
1391
|
+
[
|
|
1392
|
+
counter,
|
|
1393
|
+
undefined as any as Atom<Result.Result<A, unknown>>
|
|
1394
|
+
] as const
|
|
1395
|
+
)
|
|
1396
|
+
return writable(
|
|
1397
|
+
(get) => {
|
|
1398
|
+
let lastValue = get.once(self)
|
|
1399
|
+
get.subscribe(self, (value) => {
|
|
1400
|
+
lastValue = value
|
|
1401
|
+
if (!Result.isResult(value)) {
|
|
1402
|
+
return get.setSelf(value)
|
|
1403
|
+
}
|
|
1404
|
+
const current = Option.getOrUndefined(get.self<Result.Result<any, any>>())!
|
|
1405
|
+
if (Result.isSuccess(current) && Result.isSuccess(value)) {
|
|
1406
|
+
if (value.timestamp >= current.timestamp) {
|
|
1407
|
+
get.setSelf(value)
|
|
1408
|
+
}
|
|
1409
|
+
} else {
|
|
1410
|
+
get.setSelf(value)
|
|
1411
|
+
}
|
|
1412
|
+
})
|
|
1413
|
+
const transitions = new Set<Atom<Result.Result<A, unknown>>>()
|
|
1414
|
+
const cancels = new Set<() => void>()
|
|
1415
|
+
get.subscribe(writeAtom, ([, atom]) => {
|
|
1416
|
+
if (transitions.has(atom)) return
|
|
1417
|
+
transitions.add(atom)
|
|
1418
|
+
let cancel: (() => void) | undefined
|
|
1419
|
+
// eslint-disable-next-line prefer-const
|
|
1420
|
+
cancel = get.registry.subscribe(atom, (result) => {
|
|
1421
|
+
if (Result.isSuccess(result) && result.waiting) {
|
|
1422
|
+
return get.setSelf(result.value)
|
|
1423
|
+
}
|
|
1424
|
+
transitions.delete(atom)
|
|
1425
|
+
if (cancel) {
|
|
1426
|
+
cancels.delete(cancel)
|
|
1427
|
+
cancel()
|
|
1428
|
+
}
|
|
1429
|
+
if (transitions.size === 0) {
|
|
1430
|
+
if (Result.isFailure(result)) {
|
|
1431
|
+
get.setSelf(lastValue)
|
|
1432
|
+
}
|
|
1433
|
+
get.refresh(self)
|
|
1434
|
+
}
|
|
1435
|
+
}, { immediate: true })
|
|
1436
|
+
if (transitions.has(atom)) {
|
|
1437
|
+
cancels.add(cancel)
|
|
1438
|
+
} else {
|
|
1439
|
+
cancel()
|
|
1440
|
+
}
|
|
1441
|
+
})
|
|
1442
|
+
get.addFinalizer(() => {
|
|
1443
|
+
for (const cancel of cancels) cancel()
|
|
1444
|
+
transitions.clear()
|
|
1445
|
+
cancels.clear()
|
|
1446
|
+
})
|
|
1447
|
+
return lastValue
|
|
1448
|
+
},
|
|
1449
|
+
(ctx, atom) => ctx.set(writeAtom, [++counter, atom]),
|
|
1450
|
+
(refresh) => refresh(self)
|
|
1451
|
+
)
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* @since 1.0.0
|
|
1456
|
+
* @category Optimistic
|
|
1457
|
+
*/
|
|
1458
|
+
export const optimisticFn: {
|
|
1459
|
+
<A, W, XA, XE, OW = void>(
|
|
1460
|
+
options: {
|
|
1461
|
+
readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
|
|
1462
|
+
readonly fn:
|
|
1463
|
+
| AtomResultFn<OW, XA, XE>
|
|
1464
|
+
| ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
|
|
1465
|
+
}
|
|
1466
|
+
): (
|
|
1467
|
+
self: Writable<A, Atom<Result.Result<W, unknown>>>
|
|
1468
|
+
) => AtomResultFn<OW, XA, XE>
|
|
1469
|
+
<A, W, XA, XE, OW = void>(
|
|
1470
|
+
self: Writable<A, Atom<Result.Result<W, unknown>>>,
|
|
1471
|
+
options: {
|
|
1472
|
+
readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
|
|
1473
|
+
readonly fn:
|
|
1474
|
+
| AtomResultFn<OW, XA, XE>
|
|
1475
|
+
| ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
|
|
1476
|
+
}
|
|
1477
|
+
): AtomResultFn<OW, XA, XE>
|
|
1478
|
+
} = dual(2, <A, W, XA, XE, OW = void>(
|
|
1479
|
+
self: Writable<A, Atom<Result.Result<W, unknown>>>,
|
|
1480
|
+
options: {
|
|
1481
|
+
readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
|
|
1482
|
+
readonly fn:
|
|
1483
|
+
| AtomResultFn<OW, XA, XE>
|
|
1484
|
+
| ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
|
|
1485
|
+
}
|
|
1486
|
+
): AtomResultFn<OW, XA, XE> => {
|
|
1487
|
+
const transition = state<Result.Result<W, unknown>>(Result.initial())
|
|
1488
|
+
return fn((arg: OW, get) => {
|
|
1489
|
+
let value = options.reducer(get(self), arg)
|
|
1490
|
+
if (Result.isResult(value) && !value.waiting) {
|
|
1491
|
+
value = Result.waiting(value)
|
|
1492
|
+
}
|
|
1493
|
+
get.set(transition, Result.success(value, { waiting: true }))
|
|
1494
|
+
get.set(self, transition)
|
|
1495
|
+
const fn = typeof options.fn === "function"
|
|
1496
|
+
? autoDispose(options.fn((value) =>
|
|
1497
|
+
get.set(
|
|
1498
|
+
transition,
|
|
1499
|
+
Result.success(Result.isResult(value) ? Result.waiting(value) : value, { waiting: true })
|
|
1500
|
+
)
|
|
1501
|
+
))
|
|
1502
|
+
: options.fn
|
|
1503
|
+
get.set(fn, arg)
|
|
1504
|
+
return Effect.onExit(get.result(fn, { suspendOnWaiting: true }), (exit) => {
|
|
1505
|
+
get.set(transition, Result.fromExit(Exit.as(exit, value)))
|
|
1506
|
+
return Effect.void
|
|
1507
|
+
})
|
|
1508
|
+
})
|
|
1509
|
+
})
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* @since 1.0.0
|
|
1513
|
+
* @category batching
|
|
1514
|
+
*/
|
|
1515
|
+
export const batch: (f: () => void) => void = internalRegistry.batch
|
|
1516
|
+
|
|
1517
|
+
// -----------------------------------------------------------------------------
|
|
1518
|
+
// Focus
|
|
1519
|
+
// -----------------------------------------------------------------------------
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* @since 1.0.0
|
|
1523
|
+
* @category Focus
|
|
1524
|
+
*/
|
|
1525
|
+
export const windowFocusSignal: Atom<number> = readable((get) => {
|
|
1526
|
+
let count = 0
|
|
1527
|
+
function update() {
|
|
1528
|
+
if (document.visibilityState === "visible") {
|
|
1529
|
+
get.setSelf(++count)
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
window.addEventListener("visibilitychange", update)
|
|
1533
|
+
get.addFinalizer(() => {
|
|
1534
|
+
window.removeEventListener("visibilitychange", update)
|
|
1535
|
+
})
|
|
1536
|
+
return count
|
|
1537
|
+
})
|
|
1538
|
+
|
|
1539
|
+
/**
|
|
1540
|
+
* @since 1.0.0
|
|
1541
|
+
* @category Focus
|
|
1542
|
+
*/
|
|
1543
|
+
export const makeRefreshOnSignal = <_>(signal: Atom<_>) => <A extends Atom<any>>(self: A): WithoutSerializable<A> =>
|
|
1544
|
+
transform(self, (get) => {
|
|
1545
|
+
get.subscribe(signal, (_) => get.refresh(self))
|
|
1546
|
+
get.subscribe(self, (value) => get.setSelf(value))
|
|
1547
|
+
return get.once(self)
|
|
1548
|
+
}) as any
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* @since 1.0.0
|
|
1552
|
+
* @category Focus
|
|
1553
|
+
*/
|
|
1554
|
+
export const refreshOnWindowFocus: <A extends Atom<any>>(self: A) => WithoutSerializable<A> = makeRefreshOnSignal(
|
|
1555
|
+
windowFocusSignal
|
|
1556
|
+
)
|
|
1557
|
+
|
|
1558
|
+
// -----------------------------------------------------------------------------
|
|
1559
|
+
// KeyValueStore
|
|
1560
|
+
// -----------------------------------------------------------------------------
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* @since 1.0.0
|
|
1564
|
+
* @category KeyValueStore
|
|
1565
|
+
*/
|
|
1566
|
+
export const kvs = <A>(options: {
|
|
1567
|
+
readonly runtime: AtomRuntime<KeyValueStore.KeyValueStore, any>
|
|
1568
|
+
readonly key: string
|
|
1569
|
+
readonly schema: Schema.Schema<A, any>
|
|
1570
|
+
readonly defaultValue: LazyArg<A>
|
|
1571
|
+
}): Writable<A> => {
|
|
1572
|
+
const setAtom = options.runtime.fn(
|
|
1573
|
+
Effect.fnUntraced(function*(value: A) {
|
|
1574
|
+
const store = (yield* KeyValueStore.KeyValueStore).forSchema(
|
|
1575
|
+
options.schema
|
|
1576
|
+
)
|
|
1577
|
+
yield* store.set(options.key, value)
|
|
1578
|
+
})
|
|
1579
|
+
)
|
|
1580
|
+
const resultAtom = options.runtime.atom(
|
|
1581
|
+
Effect.flatMap(
|
|
1582
|
+
KeyValueStore.KeyValueStore,
|
|
1583
|
+
(store) => Effect.flatten(store.forSchema(options.schema).get(options.key))
|
|
1584
|
+
)
|
|
1585
|
+
)
|
|
1586
|
+
return writable(
|
|
1587
|
+
(get) => {
|
|
1588
|
+
get.mount(setAtom)
|
|
1589
|
+
return Result.getOrElse(get(resultAtom), options.defaultValue)
|
|
1590
|
+
},
|
|
1591
|
+
(ctx, value: A) => {
|
|
1592
|
+
ctx.set(setAtom, value as any)
|
|
1593
|
+
ctx.setSelf(value)
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// -----------------------------------------------------------------------------
|
|
1599
|
+
// URL search params
|
|
1600
|
+
// -----------------------------------------------------------------------------
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Create an Atom that reads and writes a URL search parameter.
|
|
1604
|
+
*
|
|
1605
|
+
* Note: If you pass a schema, it has to be synchronous and have no context.
|
|
1606
|
+
*
|
|
1607
|
+
* @since 1.0.0
|
|
1608
|
+
* @category URL search params
|
|
1609
|
+
*/
|
|
1610
|
+
export const searchParam = <A = never, I extends string = never>(name: string, options?: {
|
|
1611
|
+
readonly schema?: Schema.Schema<A, I>
|
|
1612
|
+
}): Writable<[A] extends [never] ? string : Option.Option<A>> => {
|
|
1613
|
+
const decode = options?.schema && Schema.decodeEither(options.schema)
|
|
1614
|
+
const encode = options?.schema && Schema.encodeEither(options.schema)
|
|
1615
|
+
return writable(
|
|
1616
|
+
(get) => {
|
|
1617
|
+
const handleUpdate = () => {
|
|
1618
|
+
if (searchParamState.updating) return
|
|
1619
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
1620
|
+
const newValue = searchParams.get(name) || ""
|
|
1621
|
+
if (decode) {
|
|
1622
|
+
get.setSelf(Either.getRight(decode(newValue as I)))
|
|
1623
|
+
} else if (newValue !== Option.getOrUndefined(get.self())) {
|
|
1624
|
+
get.setSelf(newValue)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
window.addEventListener("popstate", handleUpdate)
|
|
1628
|
+
window.addEventListener("pushstate", handleUpdate)
|
|
1629
|
+
get.addFinalizer(() => {
|
|
1630
|
+
window.removeEventListener("popstate", handleUpdate)
|
|
1631
|
+
window.removeEventListener("pushstate", handleUpdate)
|
|
1632
|
+
})
|
|
1633
|
+
const value = new URLSearchParams(window.location.search).get(name) || ""
|
|
1634
|
+
return decode ? Either.getRight(decode(value as I)) : value as any
|
|
1635
|
+
},
|
|
1636
|
+
(ctx, value: any) => {
|
|
1637
|
+
if (encode) {
|
|
1638
|
+
const encoded = Option.flatMap(value, (v) => Either.getRight(encode(v as A)))
|
|
1639
|
+
searchParamState.updates.set(name, Option.getOrElse(encoded, () => ""))
|
|
1640
|
+
value = Option.zipRight(encoded, value)
|
|
1641
|
+
} else {
|
|
1642
|
+
searchParamState.updates.set(name, value)
|
|
1643
|
+
}
|
|
1644
|
+
ctx.setSelf(value)
|
|
1645
|
+
if (searchParamState.timeout) {
|
|
1646
|
+
clearTimeout(searchParamState.timeout)
|
|
1647
|
+
}
|
|
1648
|
+
searchParamState.timeout = setTimeout(updateSearchParams, 500)
|
|
1649
|
+
}
|
|
1650
|
+
)
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const searchParamState = {
|
|
1654
|
+
timeout: undefined as number | undefined,
|
|
1655
|
+
updates: new Map<string, string>(),
|
|
1656
|
+
updating: false
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function updateSearchParams() {
|
|
1660
|
+
searchParamState.timeout = undefined
|
|
1661
|
+
searchParamState.updating = true
|
|
1662
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
1663
|
+
for (const [key, value] of searchParamState.updates.entries()) {
|
|
1664
|
+
if (value.length > 0) {
|
|
1665
|
+
searchParams.set(key, value)
|
|
1666
|
+
} else {
|
|
1667
|
+
searchParams.delete(key)
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
searchParamState.updates.clear()
|
|
1671
|
+
const newUrl = `${window.location.pathname}?${searchParams.toString()}`
|
|
1672
|
+
window.history.pushState({}, "", newUrl)
|
|
1673
|
+
searchParamState.updating = false
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// -----------------------------------------------------------------------------
|
|
1677
|
+
// conversions
|
|
1678
|
+
// -----------------------------------------------------------------------------
|
|
1679
|
+
|
|
1680
|
+
/**
|
|
1681
|
+
* @since 1.0.0
|
|
1682
|
+
* @category Conversions
|
|
1683
|
+
*/
|
|
1684
|
+
export const toStream = <A>(self: Atom<A>): Stream.Stream<A, never, AtomRegistry> =>
|
|
1685
|
+
Stream.unwrap(Effect.map(AtomRegistry, Registry.toStream(self)))
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* @since 1.0.0
|
|
1689
|
+
* @category Conversions
|
|
1690
|
+
*/
|
|
1691
|
+
export const toStreamResult = <A, E>(self: Atom<Result.Result<A, E>>): Stream.Stream<A, E, AtomRegistry> =>
|
|
1692
|
+
Stream.unwrap(Effect.map(AtomRegistry, Registry.toStreamResult(self)))
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* @since 1.0.0
|
|
1696
|
+
* @category Conversions
|
|
1697
|
+
*/
|
|
1698
|
+
export const get = <A>(self: Atom<A>): Effect.Effect<A, never, AtomRegistry> =>
|
|
1699
|
+
Effect.map(AtomRegistry, (_) => _.get(self))
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* @since 1.0.0
|
|
1703
|
+
* @category Conversions
|
|
1704
|
+
*/
|
|
1705
|
+
export const modify: {
|
|
1706
|
+
<R, W, A>(
|
|
1707
|
+
f: (_: R) => [returnValue: A, nextValue: W]
|
|
1708
|
+
): (self: Writable<R, W>) => Effect.Effect<A, never, AtomRegistry>
|
|
1709
|
+
<R, W, A>(self: Writable<R, W>, f: (_: R) => [returnValue: A, nextValue: W]): Effect.Effect<A, never, AtomRegistry>
|
|
1710
|
+
} = dual(
|
|
1711
|
+
2,
|
|
1712
|
+
<R, W, A>(self: Writable<R, W>, f: (_: R) => [returnValue: A, nextValue: W]): Effect.Effect<A, never, AtomRegistry> =>
|
|
1713
|
+
Effect.map(AtomRegistry, (_) => _.modify(self, f))
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* @since 1.0.0
|
|
1718
|
+
* @category Conversions
|
|
1719
|
+
*/
|
|
1720
|
+
export const set: {
|
|
1721
|
+
<W>(value: W): <R>(self: Writable<R, W>) => Effect.Effect<void, never, AtomRegistry>
|
|
1722
|
+
<R, W>(self: Writable<R, W>, value: W): Effect.Effect<void, never, AtomRegistry>
|
|
1723
|
+
} = dual(
|
|
1724
|
+
2,
|
|
1725
|
+
<R, W>(self: Writable<R, W>, value: W): Effect.Effect<void, never, AtomRegistry> =>
|
|
1726
|
+
Effect.map(AtomRegistry, (_) => _.set(self, value))
|
|
1727
|
+
)
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* @since 1.0.0
|
|
1731
|
+
* @category Conversions
|
|
1732
|
+
*/
|
|
1733
|
+
export const update: {
|
|
1734
|
+
<R, W>(f: (_: R) => W): (self: Writable<R, W>) => Effect.Effect<void, never, AtomRegistry>
|
|
1735
|
+
<R, W>(self: Writable<R, W>, f: (_: R) => W): Effect.Effect<void, never, AtomRegistry>
|
|
1736
|
+
} = dual(
|
|
1737
|
+
2,
|
|
1738
|
+
<R, W>(self: Writable<R, W>, f: (_: R) => W): Effect.Effect<void, never, AtomRegistry> =>
|
|
1739
|
+
Effect.map(AtomRegistry, (_) => _.update(self, f))
|
|
1740
|
+
)
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* @since 1.0.0
|
|
1744
|
+
* @category Conversions
|
|
1745
|
+
*/
|
|
1746
|
+
export const getResult = <A, E>(
|
|
1747
|
+
self: Atom<Result.Result<A, E>>,
|
|
1748
|
+
options?: { readonly suspendOnWaiting?: boolean | undefined }
|
|
1749
|
+
): Effect.Effect<A, E, AtomRegistry> => Effect.flatMap(AtomRegistry, Registry.getResult(self, options))
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* @since 1.0.0
|
|
1753
|
+
* @category Conversions
|
|
1754
|
+
*/
|
|
1755
|
+
export const refresh = <A>(self: Atom<A>): Effect.Effect<void, never, AtomRegistry> =>
|
|
1756
|
+
Effect.map(AtomRegistry, (_) => _.refresh(self))
|
|
1757
|
+
|
|
1758
|
+
// -----------------------------------------------------------------------------
|
|
1759
|
+
// Serializable
|
|
1760
|
+
// -----------------------------------------------------------------------------
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* @since 1.0.0
|
|
1764
|
+
* @category Serializable
|
|
1765
|
+
*/
|
|
1766
|
+
export const SerializableTypeId: SerializableTypeId = "~effect-atom/atom/Atom/Serializable"
|
|
1767
|
+
|
|
1768
|
+
/**
|
|
1769
|
+
* @since 1.0.0
|
|
1770
|
+
* @category Serializable
|
|
1771
|
+
*/
|
|
1772
|
+
export type SerializableTypeId = "~effect-atom/atom/Atom/Serializable"
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* @since 1.0.0
|
|
1776
|
+
* @category Serializable
|
|
1777
|
+
*/
|
|
1778
|
+
export interface Serializable {
|
|
1779
|
+
readonly [SerializableTypeId]: {
|
|
1780
|
+
readonly key: string
|
|
1781
|
+
readonly encode: (value: unknown) => unknown
|
|
1782
|
+
readonly decode: (value: unknown) => unknown
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* @since 1.0.0
|
|
1788
|
+
* @category Serializable
|
|
1789
|
+
*/
|
|
1790
|
+
export const isSerializable = (self: Atom<any>): self is Atom<any> & Serializable => SerializableTypeId in self
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* @since 1.0.0
|
|
1794
|
+
* @category combinators
|
|
1795
|
+
*/
|
|
1796
|
+
export const serializable: {
|
|
1797
|
+
<R extends Atom<any>, I>(options: {
|
|
1798
|
+
readonly key: string
|
|
1799
|
+
readonly schema: Schema.Schema<Type<R>, I>
|
|
1800
|
+
}): (self: R) => R & Serializable
|
|
1801
|
+
<R extends Atom<any>, I>(self: R, options: {
|
|
1802
|
+
readonly key: string
|
|
1803
|
+
readonly schema: Schema.Schema<Type<R>, I>
|
|
1804
|
+
}): R & Serializable
|
|
1805
|
+
} = dual(2, <R extends Atom<any>, A, I>(self: R, options: {
|
|
1806
|
+
readonly key: string
|
|
1807
|
+
readonly schema: Schema.Schema<A, I>
|
|
1808
|
+
}): R & Serializable =>
|
|
1809
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1810
|
+
...self,
|
|
1811
|
+
label: self.label ?? [options.key, new Error().stack?.split("\n")[5] ?? ""],
|
|
1812
|
+
[SerializableTypeId]: {
|
|
1813
|
+
key: options.key,
|
|
1814
|
+
encode: Schema.encodeSync(options.schema),
|
|
1815
|
+
decode: Schema.decodeSync(options.schema)
|
|
1816
|
+
}
|
|
1817
|
+
}))
|
|
1818
|
+
|
|
1819
|
+
/**
|
|
1820
|
+
* @since 1.0.0
|
|
1821
|
+
* @category ServerValue
|
|
1822
|
+
*/
|
|
1823
|
+
export const ServerValueTypeId = "~effect-atom/atom/Atom/ServerValue" as const
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Overrides the value of an Atom when read on the server.
|
|
1827
|
+
*
|
|
1828
|
+
* @since 1.0.0
|
|
1829
|
+
* @category ServerValue
|
|
1830
|
+
*/
|
|
1831
|
+
export const withServerValue: {
|
|
1832
|
+
<A extends Atom<any>>(read: (get: <A>(atom: Atom<A>) => A) => Type<A>): (self: A) => A
|
|
1833
|
+
<A extends Atom<any>>(self: A, read: (get: <A>(atom: Atom<A>) => A) => Type<A>): A
|
|
1834
|
+
} = dual(
|
|
1835
|
+
2,
|
|
1836
|
+
<A extends Atom<any>>(self: A, read: (get: <A>(atom: Atom<A>) => A) => Type<A>): A =>
|
|
1837
|
+
Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
1838
|
+
...self,
|
|
1839
|
+
[ServerValueTypeId]: read
|
|
1840
|
+
})
|
|
1841
|
+
)
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Sets the Atom's server value to `Result.initial(true)`.
|
|
1845
|
+
*
|
|
1846
|
+
* @since 1.0.0
|
|
1847
|
+
* @category ServerValue
|
|
1848
|
+
*/
|
|
1849
|
+
export const withServerValueInitial = <A extends Atom<Result.Result<any, any>>>(self: A): A =>
|
|
1850
|
+
withServerValue(self, constant(Result.initial(true)) as any)
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* @since 1.0.0
|
|
1854
|
+
* @category ServerValue
|
|
1855
|
+
*/
|
|
1856
|
+
export const getServerValue: {
|
|
1857
|
+
(registry: Registry.Registry): <A>(self: Atom<A>) => A
|
|
1858
|
+
<A>(self: Atom<A>, registry: Registry.Registry): A
|
|
1859
|
+
} = dual(
|
|
1860
|
+
2,
|
|
1861
|
+
<A>(self: Atom<A>, registry: Registry.Registry): A =>
|
|
1862
|
+
ServerValueTypeId in self
|
|
1863
|
+
? (self as any)[ServerValueTypeId]((atom: Atom<any>) => registry.get(atom))
|
|
1864
|
+
: registry.get(self)
|
|
1865
|
+
)
|