effect 4.0.0-beta.28 → 4.0.0-beta.29

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.
Files changed (44) hide show
  1. package/dist/Brand.d.ts +1 -1
  2. package/dist/Brand.d.ts.map +1 -1
  3. package/dist/Brand.js +1 -1
  4. package/dist/Brand.js.map +1 -1
  5. package/dist/Function.d.ts +1 -9
  6. package/dist/Function.d.ts.map +1 -1
  7. package/dist/Function.js +2 -10
  8. package/dist/Function.js.map +1 -1
  9. package/dist/Newtype.d.ts +291 -0
  10. package/dist/Newtype.d.ts.map +1 -0
  11. package/dist/Newtype.js +161 -0
  12. package/dist/Newtype.js.map +1 -0
  13. package/dist/Schema.d.ts +1 -1
  14. package/dist/Schema.d.ts.map +1 -1
  15. package/dist/Schema.js.map +1 -1
  16. package/dist/SchemaAST.d.ts.map +1 -1
  17. package/dist/SchemaAST.js +1 -1
  18. package/dist/SchemaAST.js.map +1 -1
  19. package/dist/index.d.ts +69 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +69 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/unstable/cli/Prompt.js +4 -8
  24. package/dist/unstable/cli/Prompt.js.map +1 -1
  25. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  26. package/dist/unstable/http/HttpClient.js +3 -7
  27. package/dist/unstable/http/HttpClient.js.map +1 -1
  28. package/dist/unstable/reactivity/Atom.d.ts +7 -2
  29. package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
  30. package/dist/unstable/reactivity/Atom.js +31 -7
  31. package/dist/unstable/reactivity/Atom.js.map +1 -1
  32. package/dist/unstable/reactivity/AtomRegistry.js +26 -2
  33. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/Brand.ts +1 -1
  36. package/src/Function.ts +2 -10
  37. package/src/Newtype.ts +308 -0
  38. package/src/Schema.ts +1 -1
  39. package/src/SchemaAST.ts +1 -4
  40. package/src/index.ts +70 -3
  41. package/src/unstable/cli/Prompt.ts +4 -9
  42. package/src/unstable/http/HttpClient.ts +5 -6
  43. package/src/unstable/reactivity/Atom.ts +45 -12
  44. package/src/unstable/reactivity/AtomRegistry.ts +30 -2
package/src/Newtype.ts ADDED
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Lightweight wrapper types that prevent accidental mixing of structurally
3
+ * identical values (e.g. `UserId` vs `OrderId`, both `string` at runtime).
4
+ *
5
+ * **Mental model**
6
+ *
7
+ * - **Newtype** — a compile-time wrapper around a **carrier** type (the
8
+ * underlying primitive or object). At runtime the value is unchanged; the
9
+ * tag exists only in the type system.
10
+ * - **Key** — a unique string literal that distinguishes one newtype from
11
+ * another (e.g. `"Label"`, `"UserId"`).
12
+ * - **Carrier** — the underlying type the newtype wraps (e.g. `string`,
13
+ * `number`).
14
+ * - **Iso** — a lossless two-way conversion between a newtype and its carrier,
15
+ * created with {@link makeIso}. Use `iso.set(carrier)` to wrap and
16
+ * `iso.get(newtype)` to unwrap.
17
+ *
18
+ * **Common tasks**
19
+ *
20
+ * - Define a newtype → declare an `interface` extending
21
+ * `Newtype.Newtype<Key, Carrier>`
22
+ * - Wrap / unwrap values → {@link makeIso} (returns an `Optic.Iso`)
23
+ * - Unwrap only → {@link value}
24
+ * - Lift an `Equivalence` → {@link makeEquivalence}
25
+ * - Lift an `Order` → {@link makeOrder}
26
+ * - Lift a `Combiner` → {@link makeCombiner}
27
+ * - Lift a `Reducer` → {@link makeReducer}
28
+ *
29
+ * **Gotchas**
30
+ *
31
+ * - Newtypes are **purely compile-time**. There is zero runtime overhead;
32
+ * `value` and `makeIso` use identity casts.
33
+ * - Two newtypes sharing the same key string will be assignable to each other.
34
+ * Choose unique key strings.
35
+ * - A newtype value is **not** assignable to its carrier type without
36
+ * explicitly unwrapping via {@link value} or an iso.
37
+ *
38
+ * **Quickstart**
39
+ *
40
+ * **Example** (defining and using a newtype)
41
+ *
42
+ * ```ts
43
+ * import { Newtype } from "effect"
44
+ *
45
+ * // 1. Define a newtype
46
+ * interface Label extends Newtype.Newtype<"Label", string> {}
47
+ *
48
+ * // 2. Create an iso for wrapping/unwrapping
49
+ * const labelIso = Newtype.makeIso<Label>()
50
+ *
51
+ * // 3. Wrap a raw string
52
+ * const myLabel: Label = labelIso.set("hello")
53
+ *
54
+ * // 4. Unwrap back to string
55
+ * const raw: string = labelIso.get(myLabel) // "hello"
56
+ * ```
57
+ *
58
+ * **See also**
59
+ *
60
+ * - {@link Newtype} (the tagged interface)
61
+ * - {@link makeIso} (wrap and unwrap)
62
+ * - {@link value} (unwrap only)
63
+ *
64
+ * @since 4.0.0
65
+ */
66
+ import type * as Combiner from "./Combiner.ts"
67
+ import type * as Equivalence from "./Equivalence.ts"
68
+ import { cast } from "./Function.ts"
69
+ import * as Optic from "./Optic.ts"
70
+ import type * as Order from "./Order.ts"
71
+ import type * as Reducer from "./Reducer.ts"
72
+
73
+ const TypeId = "~effect/Newtype"
74
+
75
+ /**
76
+ * A tagged interface that wraps a carrier type under a unique key, preventing
77
+ * accidental interchange of structurally identical values.
78
+ *
79
+ * - Define your newtype as an `interface` extending
80
+ * `Newtype<"MyKey", CarrierType>`.
81
+ * - The tag is compile-time only; no runtime wrapper is allocated.
82
+ * - Use {@link makeIso} to create a two-way conversion, or {@link value} to
83
+ * unwrap.
84
+ *
85
+ * **Example** (defining a newtype)
86
+ *
87
+ * ```ts
88
+ * import { Newtype } from "effect"
89
+ *
90
+ * interface UserId extends Newtype.Newtype<"UserId", number> {}
91
+ * interface OrderId extends Newtype.Newtype<"OrderId", number> {}
92
+ *
93
+ * // UserId and OrderId are not assignable to each other
94
+ * // even though both wrap `number`.
95
+ * ```
96
+ *
97
+ * @see {@link makeIso} — create an iso to wrap and unwrap
98
+ * @see {@link value} — unwrap a newtype value
99
+ *
100
+ * @since 4.0.0
101
+ */
102
+ export interface Newtype<in out Key extends string, out Carrier> {
103
+ readonly [TypeId]: {
104
+ readonly key: Key
105
+ readonly carrier: Carrier
106
+ }
107
+ }
108
+
109
+ /**
110
+ * @since 4.0.0
111
+ */
112
+ export declare namespace Newtype {
113
+ /**
114
+ * A type that matches any `Newtype`, useful as a generic constraint:
115
+ * `<N extends Newtype.Any>`.
116
+ *
117
+ * @see {@link Newtype} — the base tagged interface
118
+ *
119
+ * @since 4.0.0
120
+ */
121
+ export type Any = Newtype<any, unknown>
122
+
123
+ /**
124
+ * Extracts the key literal type from a newtype.
125
+ *
126
+ * - Useful in generic code that needs to inspect or constrain the key.
127
+ *
128
+ * @since 4.0.0
129
+ */
130
+ export type Key<N extends Any> = N extends Newtype<infer Key, unknown> ? Key : never
131
+
132
+ /**
133
+ * Extracts the carrier (underlying) type from a newtype.
134
+ *
135
+ * - Useful when you need to refer to the wrapped type in generic utilities.
136
+ *
137
+ * @since 4.0.0
138
+ */
139
+ export type Carrier<N extends Any> = N extends Newtype<infer _Key, infer Carrier> ? Carrier : never
140
+ }
141
+
142
+ /**
143
+ * Unwraps a newtype value, returning the underlying carrier value.
144
+ *
145
+ * - Use when you only need to read the inner value and do not need to wrap new
146
+ * values.
147
+ * - For both wrapping and unwrapping, prefer {@link makeIso}.
148
+ * - Zero runtime cost: this is an identity cast.
149
+ *
150
+ * **Example** (unwrapping a newtype)
151
+ *
152
+ * ```ts
153
+ * import { Newtype } from "effect"
154
+ *
155
+ * interface Label extends Newtype.Newtype<"Label", string> {}
156
+ *
157
+ * const iso = Newtype.makeIso<Label>()
158
+ * const label = iso.set("hello")
159
+ *
160
+ * const raw: string = Newtype.value(label) // "hello"
161
+ * ```
162
+ *
163
+ * @see {@link makeIso} — two-way conversion (wrap and unwrap)
164
+ *
165
+ * @since 4.0.0
166
+ */
167
+ export const value: <N extends Newtype.Any>(newtype: N) => Newtype.Carrier<N> = cast
168
+
169
+ /**
170
+ * Creates an `Optic.Iso` for a newtype, providing both wrapping (`set`) and
171
+ * unwrapping (`get`).
172
+ *
173
+ * - Use this as the primary way to construct and deconstruct newtype values.
174
+ * - The returned iso composes with other optics via the standard `Optic` API.
175
+ * - Zero runtime cost: both directions are identity casts.
176
+ *
177
+ * **Example** (wrapping and unwrapping with an iso)
178
+ *
179
+ * ```ts
180
+ * import { Newtype } from "effect"
181
+ *
182
+ * interface Label extends Newtype.Newtype<"Label", string> {}
183
+ *
184
+ * const labelIso = Newtype.makeIso<Label>()
185
+ *
186
+ * const label: Label = labelIso.set("world")
187
+ * const str: string = labelIso.get(label) // "world"
188
+ * ```
189
+ *
190
+ * @see {@link value} — unwrap only
191
+ *
192
+ * @since 4.0.0
193
+ */
194
+ export function makeIso<N extends Newtype.Any>(): Optic.Iso<N, Newtype.Carrier<N>> {
195
+ return Optic.makeIso(value, cast)
196
+ }
197
+
198
+ /**
199
+ * Lifts an `Equivalence` for the carrier type into an `Equivalence` for the
200
+ * newtype.
201
+ *
202
+ * - Use when you need to compare two newtype values for equality.
203
+ * - The returned equivalence delegates to the provided carrier equivalence.
204
+ * - Zero runtime cost beyond the underlying equivalence check.
205
+ *
206
+ * **Example** (comparing newtypes)
207
+ *
208
+ * ```ts
209
+ * import { Newtype, Equivalence } from "effect"
210
+ *
211
+ * interface Label extends Newtype.Newtype<"Label", string> {}
212
+ *
213
+ * const eq = Newtype.makeEquivalence<Label>(Equivalence.String)
214
+ * const iso = Newtype.makeIso<Label>()
215
+ *
216
+ * eq(iso.set("a"), iso.set("a")) // true
217
+ * eq(iso.set("a"), iso.set("b")) // false
218
+ * ```
219
+ *
220
+ * @see {@link makeOrder} — lift an `Order` for the carrier
221
+ *
222
+ * @since 4.0.0
223
+ */
224
+ export const makeEquivalence: <N extends Newtype.Any>(
225
+ equivalence: Equivalence.Equivalence<Newtype.Carrier<N>>
226
+ ) => Equivalence.Equivalence<N> = cast
227
+
228
+ /**
229
+ * Lifts an `Order` for the carrier type into an `Order` for the newtype.
230
+ *
231
+ * - Use when you need to sort or compare newtype values.
232
+ * - The returned order delegates to the provided carrier order.
233
+ *
234
+ * **Example** (ordering newtypes)
235
+ *
236
+ * ```ts
237
+ * import { Newtype, Order } from "effect"
238
+ *
239
+ * interface Score extends Newtype.Newtype<"Score", number> {}
240
+ *
241
+ * const ord = Newtype.makeOrder<Score>(Order.Number)
242
+ * const iso = Newtype.makeIso<Score>()
243
+ *
244
+ * ord(iso.set(1), iso.set(2)) // -1
245
+ * ```
246
+ *
247
+ * @see {@link makeEquivalence} — lift an `Equivalence` for the carrier
248
+ *
249
+ * @since 4.0.0
250
+ */
251
+ export const makeOrder: <N extends Newtype.Any>(order: Order.Order<Newtype.Carrier<N>>) => Order.Order<N> = cast
252
+
253
+ /**
254
+ * Lifts a `Combiner` for the carrier type into a `Combiner` for the newtype.
255
+ *
256
+ * - Use when you need to combine (e.g. concatenate, add) newtype values.
257
+ * - The returned combiner delegates to the provided carrier combiner.
258
+ *
259
+ * **Example** (combining newtypes)
260
+ *
261
+ * ```ts
262
+ * import { Newtype, Combiner } from "effect"
263
+ *
264
+ * interface Amount extends Newtype.Newtype<"Amount", number> {}
265
+ *
266
+ * const sum = Combiner.make<number>((a, b) => a + b)
267
+ * const combiner = Newtype.makeCombiner<Amount>(sum)
268
+ * const iso = Newtype.makeIso<Amount>()
269
+ *
270
+ * const total = combiner.combine(iso.set(10), iso.set(20))
271
+ * Newtype.value(total) // 30
272
+ * ```
273
+ *
274
+ * @see {@link makeReducer} — lift a `Reducer` for the carrier
275
+ *
276
+ * @since 4.0.0
277
+ */
278
+ export const makeCombiner: <N extends Newtype.Any>(
279
+ combiner: Combiner.Combiner<Newtype.Carrier<N>>
280
+ ) => Combiner.Combiner<N> = cast
281
+
282
+ /**
283
+ * Lifts a `Reducer` for the carrier type into a `Reducer` for the newtype.
284
+ *
285
+ * - Use when you need to fold/reduce over a collection of newtype values.
286
+ * - The returned reducer delegates to the provided carrier reducer.
287
+ *
288
+ * **Example** (reducing newtypes)
289
+ *
290
+ * ```ts
291
+ * import { Newtype, Reducer } from "effect"
292
+ *
293
+ * interface Score extends Newtype.Newtype<"Score", number> {}
294
+ *
295
+ * const sum = Reducer.make<number>((a, b) => a + b, 0)
296
+ * const reducer = Newtype.makeReducer<Score>(sum)
297
+ * const iso = Newtype.makeIso<Score>()
298
+ *
299
+ * const total = reducer.combineAll([iso.set(1), iso.set(2), iso.set(3)])
300
+ * Newtype.value(total) // 6
301
+ * ```
302
+ *
303
+ * @see {@link makeCombiner} — lift a `Combiner` for the carrier
304
+ *
305
+ * @since 4.0.0
306
+ */
307
+ export const makeReducer: <N extends Newtype.Any>(reducer: Reducer.Reducer<Newtype.Carrier<N>>) => Reducer.Reducer<N> =
308
+ cast
package/src/Schema.ts CHANGED
@@ -3590,7 +3590,7 @@ const getUUIDRegExp = (version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8): globalThis.RegE
3590
3590
  * @category String checks
3591
3591
  * @since 4.0.0
3592
3592
  */
3593
- export function isUUID(version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | undefined, annotations?: Annotations.Filter) {
3593
+ export function isUUID(version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, annotations?: Annotations.Filter) {
3594
3594
  const regExp = getUUIDRegExp(version)
3595
3595
  return isPattern(
3596
3596
  regExp,
package/src/SchemaAST.ts CHANGED
@@ -3206,10 +3206,7 @@ export const bigIntString = appendChecks(string, [isStringBigInt()])
3206
3206
 
3207
3207
  const bigIntToString = new Link(
3208
3208
  bigIntString,
3209
- new Transformation.Transformation(
3210
- Getter.transform(globalThis.BigInt),
3211
- Getter.String()
3212
- )
3209
+ Transformation.bigintFromString
3213
3210
  )
3214
3211
 
3215
3212
  const REGEXP_PATTERN = "Symbol\\((.*)\\)"
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ export {
10
10
  /**
11
11
  * @since 2.0.0
12
12
  */
13
- coerceUnsafe,
13
+ cast,
14
14
  /**
15
15
  * @since 2.0.0
16
16
  */
@@ -958,12 +958,12 @@ export * as Duration from "./Duration.ts"
958
958
  * ```ts
959
959
  * import { Data, Effect } from "effect"
960
960
  *
961
- * class DivisionByZeroError extends Data.TaggedError("DivisionByZeroError")<{}> {}
961
+ * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
962
962
  *
963
963
  * // Effect that may fail
964
964
  * const divide = (a: number, b: number) =>
965
965
  * b === 0
966
- * ? Effect.fail(new DivisionByZeroError())
966
+ * ? Effect.fail(new DiscountRateError())
967
967
  * : Effect.succeed(a / b)
968
968
  *
969
969
  * // Error handling
@@ -2314,6 +2314,73 @@ export * as MutableList from "./MutableList.ts"
2314
2314
  */
2315
2315
  export * as MutableRef from "./MutableRef.ts"
2316
2316
 
2317
+ /**
2318
+ * Lightweight wrapper types that prevent accidental mixing of structurally
2319
+ * identical values (e.g. `UserId` vs `OrderId`, both `string` at runtime).
2320
+ *
2321
+ * **Mental model**
2322
+ *
2323
+ * - **Newtype** — a compile-time wrapper around a **carrier** type (the
2324
+ * underlying primitive or object). At runtime the value is unchanged; the
2325
+ * tag exists only in the type system.
2326
+ * - **Key** — a unique string literal that distinguishes one newtype from
2327
+ * another (e.g. `"Label"`, `"UserId"`).
2328
+ * - **Carrier** — the underlying type the newtype wraps (e.g. `string`,
2329
+ * `number`).
2330
+ * - **Iso** — a lossless two-way conversion between a newtype and its carrier,
2331
+ * created with {@link makeIso}. Use `iso.set(carrier)` to wrap and
2332
+ * `iso.get(newtype)` to unwrap.
2333
+ *
2334
+ * **Common tasks**
2335
+ *
2336
+ * - Define a newtype → declare an `interface` extending
2337
+ * `Newtype.Newtype<Key, Carrier>`
2338
+ * - Wrap / unwrap values → {@link makeIso} (returns an `Optic.Iso`)
2339
+ * - Unwrap only → {@link value}
2340
+ * - Lift an `Equivalence` → {@link makeEquivalence}
2341
+ * - Lift an `Order` → {@link makeOrder}
2342
+ * - Lift a `Combiner` → {@link makeCombiner}
2343
+ * - Lift a `Reducer` → {@link makeReducer}
2344
+ *
2345
+ * **Gotchas**
2346
+ *
2347
+ * - Newtypes are **purely compile-time**. There is zero runtime overhead;
2348
+ * `value` and `makeIso` use identity casts.
2349
+ * - Two newtypes sharing the same key string will be assignable to each other.
2350
+ * Choose unique key strings.
2351
+ * - A newtype value is **not** assignable to its carrier type without
2352
+ * explicitly unwrapping via {@link value} or an iso.
2353
+ *
2354
+ * **Quickstart**
2355
+ *
2356
+ * **Example** (defining and using a newtype)
2357
+ *
2358
+ * ```ts
2359
+ * import { Newtype } from "effect"
2360
+ *
2361
+ * // 1. Define a newtype
2362
+ * interface Label extends Newtype.Newtype<"Label", string> {}
2363
+ *
2364
+ * // 2. Create an iso for wrapping/unwrapping
2365
+ * const labelIso = Newtype.makeIso<Label>()
2366
+ *
2367
+ * // 3. Wrap a raw string
2368
+ * const myLabel: Label = labelIso.set("hello")
2369
+ *
2370
+ * // 4. Unwrap back to string
2371
+ * const raw: string = labelIso.get(myLabel) // "hello"
2372
+ * ```
2373
+ *
2374
+ * **See also**
2375
+ *
2376
+ * - {@link Newtype} (the tagged interface)
2377
+ * - {@link makeIso} (wrap and unwrap)
2378
+ * - {@link value} (unwrap only)
2379
+ *
2380
+ * @since 4.0.0
2381
+ */
2382
+ export * as Newtype from "./Newtype.ts"
2383
+
2317
2384
  /**
2318
2385
  * @since 2.0.0
2319
2386
  *
@@ -3108,10 +3108,6 @@ interface TextState {
3108
3108
  readonly error: string | undefined
3109
3109
  }
3110
3110
 
3111
- const getValue = (state: TextState, options: TextOptionsReq): string => {
3112
- return state.value.length > 0 ? state.value : options.default
3113
- }
3114
-
3115
3111
  const renderClearScreen = Effect.fnUntraced(function*(state: TextState, options: TextOptionsReq) {
3116
3112
  const terminal = yield* Terminal.Terminal
3117
3113
  const columns = yield* terminal.columns
@@ -3132,7 +3128,7 @@ const renderTextInput = (
3132
3128
  submitted: boolean,
3133
3129
  renderOptions?: RenderOptions | undefined
3134
3130
  ) => {
3135
- const text = getValue(nextState, options)
3131
+ const text = nextState.value
3136
3132
  if (renderOptions?.plain === true) {
3137
3133
  switch (options.type) {
3138
3134
  case "hidden": {
@@ -3269,11 +3265,10 @@ const processTab = (state: TextState, options: TextOptionsReq) => {
3269
3265
  if (state.value === options.default) {
3270
3266
  return Effect.succeed(Action.Beep())
3271
3267
  }
3272
- const value = getValue(state, options)
3273
- const cursor = value.length
3268
+ const value = state.value.length === 0 ? options.default : state.value
3274
3269
  return Effect.succeed(
3275
3270
  Action.NextFrame({
3276
- state: { ...state, value, cursor, error: undefined }
3271
+ state: { ...state, value, cursor: value.length, error: undefined }
3277
3272
  })
3278
3273
  )
3279
3274
  }
@@ -3317,7 +3312,7 @@ const handleTextProcess = (options: TextOptionsReq) => {
3317
3312
  }
3318
3313
  case "enter":
3319
3314
  case "return": {
3320
- const value = getValue(state, options)
3315
+ const value = state.value
3321
3316
  return Effect.match(options.validate(value), {
3322
3317
  onFailure: (error) =>
3323
3318
  Action.NextFrame({
@@ -6,7 +6,6 @@ import * as Cause from "../../Cause.ts"
6
6
  import { Clock } from "../../Clock.ts"
7
7
  import * as Duration from "../../Duration.ts"
8
8
  import * as Effect from "../../Effect.ts"
9
- import * as Exit from "../../Exit.ts"
10
9
  import * as Fiber from "../../Fiber.ts"
11
10
  import { constant, constFalse, constTrue, dual, flow, identity } from "../../Function.ts"
12
11
  import * as Inspectable from "../../Inspectable.ts"
@@ -1735,12 +1734,12 @@ class InterruptibleResponse implements HttpClientResponse.HttpClientResponse {
1735
1734
  get stream() {
1736
1735
  return Stream.suspend(() => {
1737
1736
  responseRegistry.unregister(this.original)
1738
- return Stream.onExit(this.original.stream, (exit) => {
1739
- if (Exit.hasInterrupts(exit)) {
1737
+ return Stream.ensuring(
1738
+ this.original.stream,
1739
+ Effect.sync(() => {
1740
1740
  this.controller.abort()
1741
- }
1742
- return Effect.void
1743
- })
1741
+ })
1742
+ )
1744
1743
  })
1745
1744
  }
1746
1745
 
@@ -25,7 +25,7 @@ import * as Scope from "../../Scope.ts"
25
25
  import * as ServiceMap from "../../ServiceMap.ts"
26
26
  import * as Stream from "../../Stream.ts"
27
27
  import * as SubscriptionRef from "../../SubscriptionRef.ts"
28
- import type { NoInfer } from "../../Types.ts"
28
+ import type { Mutable, NoInfer } from "../../Types.ts"
29
29
  import * as KeyValueStore from "../persistence/KeyValueStore.ts"
30
30
  import * as AsyncResult from "./AsyncResult.ts"
31
31
  import { AtomRegistry } from "./AtomRegistry.ts"
@@ -56,6 +56,7 @@ export interface Atom<A> extends Pipeable, Inspectable.Inspectable {
56
56
  readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
57
57
  readonly label?: readonly [name: string, stack: string]
58
58
  readonly idleTTL?: number
59
+ readonly initialValueTarget?: Atom<A>
59
60
  }
60
61
 
61
62
  /**
@@ -792,7 +793,7 @@ export const context: (options: {
792
793
  }))
793
794
  get.subscribe(atom, (value) => get.setSelf(value))
794
795
  return get.once(atom)
795
- }) as any as A
796
+ }, { initialValueTarget: atom }) as any as A
796
797
  return factory
797
798
  }
798
799
 
@@ -1600,16 +1601,35 @@ export const transform: {
1600
1601
  * @since 4.0.0
1601
1602
  * @category combinators
1602
1603
  */
1603
- <R extends Atom<any>, B>(f: (get: Context, atom: R) => B): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1604
+ <R extends Atom<any>, B>(
1605
+ f: (get: Context, atom: R) => B,
1606
+ options?: {
1607
+ readonly initialValueTarget?: Atom<B> | undefined
1608
+ }
1609
+ ): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1604
1610
  /**
1605
1611
  * @since 4.0.0
1606
1612
  * @category combinators
1607
1613
  */
1608
- <R extends Atom<any>, B>(self: R, f: (get: Context, atom: R) => B): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1614
+ <R extends Atom<any>, B>(
1615
+ self: R,
1616
+ f: (get: Context, atom: R) => B,
1617
+ options?: {
1618
+ readonly initialValueTarget?: Atom<B> | undefined
1619
+ }
1620
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1609
1621
  } = dual(
1610
- 2,
1611
- (<A, B>(self: Atom<A>, f: (get: Context, atom: Atom<A>) => B): Atom<B> =>
1612
- removeTtl(
1622
+ (args) => isAtom(args[0]),
1623
+ (<A, B>(
1624
+ self: Atom<A>,
1625
+ f: (get: Context, atom: Atom<A>, options?: {
1626
+ readonly initialValueTarget?: Atom<B> | undefined
1627
+ }) => B,
1628
+ options?: {
1629
+ readonly initialValueTarget?: Atom<B> | undefined
1630
+ }
1631
+ ): Atom<B> => {
1632
+ const atom = removeTtl(
1613
1633
  isWritable(self)
1614
1634
  ? writable(
1615
1635
  (get) => f(get, self),
@@ -1626,9 +1646,22 @@ export const transform: {
1626
1646
  refresh(self)
1627
1647
  }
1628
1648
  )
1629
- )) as any
1649
+ )
1650
+ if (options?.initialValueTarget) {
1651
+ ;(atom as Mutable<Atom<B>>).initialValueTarget = getInitialValueTarget(options.initialValueTarget)
1652
+ }
1653
+ return atom
1654
+ }) as any
1630
1655
  )
1631
1656
 
1657
+ const getInitialValueTarget = <A>(atom: Atom<A>): Atom<A> => {
1658
+ let target = atom
1659
+ while (target.initialValueTarget) {
1660
+ target = target.initialValueTarget
1661
+ }
1662
+ return target
1663
+ }
1664
+
1632
1665
  /**
1633
1666
  * @since 4.0.0
1634
1667
  * @category combinators
@@ -1712,7 +1745,7 @@ export const debounce: {
1712
1745
  timeout = setTimeout(update, millis) as any
1713
1746
  })
1714
1747
  return value
1715
- })
1748
+ }, { initialValueTarget: self })
1716
1749
  }
1717
1750
  )
1718
1751
 
@@ -1748,7 +1781,7 @@ export const withRefresh: {
1748
1781
  const handle = setTimeout(() => get.refresh(self), millis) as any
1749
1782
  get.addFinalizer(() => clearTimeout(handle))
1750
1783
  return get(self)
1751
- })
1784
+ }, { initialValueTarget: self })
1752
1785
  }
1753
1786
  )
1754
1787
 
@@ -1849,7 +1882,7 @@ export const swr: {
1849
1882
  get.refresh(self)
1850
1883
  }
1851
1884
  return current
1852
- })
1885
+ }, { initialValueTarget: self })
1853
1886
  }
1854
1887
  ) as any
1855
1888
 
@@ -2088,7 +2121,7 @@ export const makeRefreshOnSignal = <_>(signal: Atom<_>) => <A extends Atom<any>>
2088
2121
  get.subscribe(signal, (_) => get.refresh(self))
2089
2122
  get.subscribe(self, (value) => get.setSelf(value))
2090
2123
  return get.once(self)
2091
- }) as any
2124
+ }, { initialValueTarget: self }) as any
2092
2125
 
2093
2126
  /**
2094
2127
  * @since 4.0.0
@@ -317,7 +317,11 @@ class RegistryImpl implements AtomRegistry {
317
317
  }
318
318
  if (initialValues !== undefined) {
319
319
  for (const [atom, value] of initialValues) {
320
- this.ensureNode(atom).setValue(value)
320
+ let target = atom
321
+ while (target.initialValueTarget) {
322
+ target = target.initialValueTarget
323
+ }
324
+ this.ensureNode(target).setInitialValue(value)
321
325
  }
322
326
  }
323
327
  }
@@ -558,6 +562,7 @@ class NodeImpl<A> {
558
562
  state: NodeState = NodeState.uninitialized
559
563
  lifetime: Lifetime<A> | undefined
560
564
  writeContext: WriteContextImpl<A>
565
+ preserveInitialValueOnBuild = false
561
566
 
562
567
  parents: Array<NodeImpl<any>> = []
563
568
  previousParents: Array<NodeImpl<any>> | undefined
@@ -588,7 +593,12 @@ class NodeImpl<A> {
588
593
  this.lifetime = makeLifetime(this)
589
594
  const value = this.atom.read(this.lifetime)
590
595
  if ((this.state & NodeFlags.waitingForValue) !== 0) {
591
- this.setValue(value)
596
+ if (this.preserveInitialValueOnBuild) {
597
+ this.preserveInitialValueOnBuild = false
598
+ this.state = NodeState.valid
599
+ } else {
600
+ this.setValue(value)
601
+ }
592
602
  }
593
603
 
594
604
  if (this.previousParents) {
@@ -613,6 +623,24 @@ class NodeImpl<A> {
613
623
  return Option.some(this._value)
614
624
  }
615
625
 
626
+ setInitialValue(value: A): void {
627
+ if ((this.state & NodeFlags.initialized) === 0) {
628
+ this.preserveInitialValueOnBuild = true
629
+ this.state = NodeState.stale
630
+ this._value = value
631
+
632
+ if (batchState.phase === BatchPhase.collect) {
633
+ batchState.notify.add(this)
634
+ } else {
635
+ this.notify()
636
+ }
637
+
638
+ return
639
+ }
640
+
641
+ this.setValue(value)
642
+ }
643
+
616
644
  setValue(value: A): void {
617
645
  if ((this.state & NodeFlags.initialized) === 0) {
618
646
  this.state = NodeState.valid