@solidjs/signals 2.0.0-beta.31 → 2.0.0-beta.33

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/dist/prod/map.js CHANGED
@@ -37,7 +37,7 @@ function mapArray(t, s, i) {
37
37
  const o = computed(updateKeyedMap.bind(h));
38
38
  // Untracked reads inside the internal owner resolve via _parentComputed; routing
39
39
  // them through node lets store-proxy lookups see pending writes (not stale _value).
40
- h.wt.Tt = o;
40
+ h.wt.dt = o;
41
41
  o.T &= ~CONFIG_AUTO_DISPOSE;
42
42
  return accessor(o);
43
43
  }
@@ -235,18 +235,18 @@ function updateKeyedMap() {
235
235
  wt: createOwner(),
236
236
  jt: 0,
237
237
  Jt: 0,
238
- Lt: t,
238
+ Vt: t,
239
239
  Kt: e,
240
240
  Ut: [],
241
241
  xt: [],
242
- Vt: i?.from,
242
+ Xt: i?.from,
243
243
  Ht: i?.fallback
244
244
  };
245
245
  const n = computed(updateRepeat.bind(r));
246
246
  // Same as mapArray: untracked reads inside the internal owner resolve via
247
247
  // _parentComputed, so async reads in row callbacks register with the node
248
248
  // (pending tracking + post-settle retry) instead of vanishing.
249
- r.wt.Tt = n;
249
+ r.wt.dt = n;
250
250
  n.T &= ~CONFIG_AUTO_DISPOSE;
251
251
  return accessor(n);
252
252
  }
@@ -259,8 +259,8 @@ function updateKeyedMap() {
259
259
  // for the post-settle retry. The overlap math also subsumes the previous
260
260
  // disjoint-window/front-clear/end-clear/shift special cases.
261
261
  function updateRepeat() {
262
- const t = this.Lt();
263
- const s = this.Vt?.() || 0;
262
+ const t = this.Vt();
263
+ const s = this.Xt?.() || 0;
264
264
  runWithOwner(this.wt, () => {
265
265
  if (t === 0) {
266
266
  if (this.jt !== 0) {
@@ -72,38 +72,6 @@ function createSignal(e, t) {
72
72
  return [ accessor(n), setSignal.bind(null, n) ];
73
73
  }
74
74
 
75
- /**
76
- * Creates a readonly derived reactive memoized signal.
77
- *
78
- * ```typescript
79
- * const value = createMemo<T>(compute, options?: MemoOptions<T>);
80
- * ```
81
- * @param compute a function that receives its previous value and returns a new value used to react on a computation
82
- * @param options `MemoOptions` -- id, name, equals, unobserved, lazy
83
- *
84
- * @example
85
- * ```ts
86
- * const [first, setFirst] = createSignal("Ada");
87
- * const [last, setLast] = createSignal("Lovelace");
88
- *
89
- * const fullName = createMemo(() => `${first()} ${last()}`);
90
- *
91
- * fullName(); // "Ada Lovelace"
92
- * ```
93
- *
94
- * @example
95
- * ```ts
96
- * // Async memo — reads surface as pending inside <Loading>
97
- * const user = createMemo(async () => {
98
- * const res = await fetch(`/users/${id()}`);
99
- * return res.json();
100
- * });
101
- * ```
102
- *
103
- * @description https://docs.solidjs.com/reference/basic-reactivity/create-memo
104
- */
105
- // NoInfer keeps the previous-value parameter from influencing T inference, so
106
- // the memo/effect result type is still driven by the compute return type.
107
75
  function createMemo(e, t) {
108
76
  return accessor(computed(e, t));
109
77
  }
@@ -129,7 +97,7 @@ function createEffect(e, t, n) {
129
97
  * ```
130
98
  * @param compute a function that receives its previous value and returns a new value used to react on a computation
131
99
  * @param effectFn a function that receives the new value and is used to perform side effects
132
- * @param options `EffectOptions` -- name, defer, schedule
100
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
133
101
  *
134
102
  * @example
135
103
  * ```ts
@@ -277,7 +245,7 @@ function createEffect(e, t, n) {
277
245
  // route through the inherited queue.
278
246
  const r = getOwner();
279
247
  const o = new MicrotaskQueue;
280
- o.ve = r.C;
248
+ o.ke = r.C;
281
249
  // notify() forwards up the normal chain
282
250
  r.C = o;
283
251
  // A user effect rather than a bare computed: computeds are pull-based and
@@ -16,7 +16,7 @@ import { runProjectionComputed } from "./projection.js";
16
16
 
17
17
  import { $TARGET, STORE_FIREWALL, storeSetter, STORE_OPTIMISTIC_OVERRIDE, STORE_NODE, STORE_OPTIMISTIC_OWNERS, getOverlayLayer, STORE_VALUE, $DELETED, isWrappable, wrap, visibleNodeValue, $TRACK, notifySelf, STORE_WRAP, createStoreProxy, storeTraps, STORE_LOOKUP, STORE_SHALLOW, markRawIngest, STORE_OPTIMISTIC } from "./store.js";
18
18
 
19
- function createOptimisticStore(e, t, r) {
19
+ function createOptimisticStore(e, t, i) {
20
20
  // Register clear function with scheduler; store nodes marked
21
21
  // STORE_OPTIMISTIC take the engine's write path, so install it before any
22
22
  // node can be created.
@@ -41,13 +41,13 @@ function createOptimisticStore(e, t, r) {
41
41
  return e(t);
42
42
  };
43
43
  }
44
- const i = typeof e === "function";
44
+ const r = typeof e === "function";
45
45
  // Plain form: the second slot carries options.
46
- if (!i && r === undefined) r = t;
47
- const o = i ? t : e;
48
- const n = i ? e : undefined;
46
+ if (!r && i === undefined) i = t;
47
+ const o = r ? t : e;
48
+ const n = r ? e : undefined;
49
49
  // Create optimistic projection store
50
- const {store: c} = createOptimisticProjectionInternal(n, o, r);
50
+ const {store: c} = createOptimisticProjectionInternal(n, o, i);
51
51
  return [ c, e => storeSetter(c, e) ];
52
52
  }
53
53
 
@@ -56,8 +56,8 @@ function createOptimisticStore(e, t, r) {
56
56
  // scheduler's flush tail carries only a size-guarded hook call. The
57
57
  // completing transition scopes each clear to its own layer keys (#2899).
58
58
  function clearOptimisticStores(e, t) {
59
- for (const r of e) {
60
- const e = r[$TARGET];
59
+ for (const i of e) {
60
+ const e = i[$TARGET];
61
61
  if (e?.[STORE_OPTIMISTIC_OVERRIDE]) clearOptimisticOverride(e, t);
62
62
  }
63
63
  e.clear();
@@ -75,9 +75,9 @@ function clearOptimisticStores(e, t) {
75
75
  * fresh authoritative data) consumes everything — the correction supersedes
76
76
  * every tentative layer.
77
77
  */ function clearOptimisticOverride(e, t) {
78
- const r = e[STORE_OPTIMISTIC_OVERRIDE];
79
- if (!r) return;
80
- const i = e[STORE_NODE];
78
+ const i = e[STORE_OPTIMISTIC_OVERRIDE];
79
+ if (!i) return;
80
+ const r = e[STORE_NODE];
81
81
  const o = e[STORE_OPTIMISTIC_OWNERS];
82
82
  const n = t !== undefined;
83
83
  let c = false;
@@ -87,7 +87,7 @@ function clearOptimisticStores(e, t) {
87
87
  const O = projectionWriteActive;
88
88
  setProjectionWriteActive(true);
89
89
  try {
90
- for (const O of Reflect.ownKeys(r)) {
90
+ for (const O of Reflect.ownKeys(i)) {
91
91
  if (n) {
92
92
  let e = o?.[O] ?? null;
93
93
  // Resolve merge chains (entangled actions settle as one); path-compress
@@ -104,21 +104,21 @@ function clearOptimisticStores(e, t) {
104
104
  }
105
105
  }
106
106
  }
107
- delete r[O];
107
+ delete i[O];
108
108
  if (o) delete o[O];
109
109
  c = true;
110
- const T = i?.[O];
110
+ const T = r?.[O];
111
111
  if (T) {
112
112
  // Clear lane association so effects go to regular queue
113
- T.Me = undefined;
113
+ T.Ke = undefined;
114
114
  // Re-read from base — this key left the optimistic layer above, so the
115
115
  // overlay resolves to STORE_OVERRIDE or STORE_VALUE.
116
116
  const t = getOverlayLayer(e, O);
117
- const r = t ? t[O] : e[STORE_VALUE][O];
118
- const i = r === $DELETED ? undefined : r;
119
- const o = isWrappable(i) ? wrap(i, e) : i;
117
+ const i = t ? t[O] : e[STORE_VALUE][O];
118
+ const r = i === $DELETED ? undefined : i;
119
+ const o = isWrappable(r) ? wrap(r, e) : r;
120
120
  const n = visibleNodeValue(T);
121
- T.Ae = NOT_PENDING;
121
+ T._e = NOT_PENDING;
122
122
  T.sn = null;
123
123
  T.De = NOT_PENDING;
124
124
  T.Ue = o;
@@ -135,8 +135,8 @@ function clearOptimisticStores(e, t) {
135
135
  e[STORE_OPTIMISTIC_OWNERS] = undefined;
136
136
  }
137
137
  // Notify $TRACK
138
- if (c && i?.[$TRACK]) {
139
- i[$TRACK].Me = undefined;
138
+ if (c && r?.[$TRACK]) {
139
+ r[$TRACK].Ke = undefined;
140
140
  notifySelf(e);
141
141
  }
142
142
  } finally {
@@ -144,10 +144,10 @@ function clearOptimisticStores(e, t) {
144
144
  }
145
145
  }
146
146
 
147
- function createOptimisticProjectionInternal(e, t, r) {
148
- let i;
147
+ function createOptimisticProjectionInternal(e, t, i) {
148
+ let r;
149
149
  const o = new WeakMap;
150
- const n = !!r?.shallow;
150
+ const n = !!i?.shallow;
151
151
  const wrapper = e => {
152
152
  e[STORE_WRAP] = wrapProjection;
153
153
  e[STORE_LOOKUP] = o;
@@ -159,7 +159,7 @@ function createOptimisticProjectionInternal(e, t, r) {
159
159
  // Mark as optimistic store
160
160
  Object.defineProperty(e, STORE_FIREWALL, {
161
161
  get() {
162
- return i;
162
+ return r;
163
163
  },
164
164
  configurable: true
165
165
  });
@@ -193,19 +193,24 @@ function createOptimisticProjectionInternal(e, t, r) {
193
193
  setProjectionWriteActive(t);
194
194
  }
195
195
  };
196
- i = computed(() => {
196
+ // seedLoadingValue: born-committed firewall, same as createProjection.
197
+ let t;
198
+ if (i?.seedLoadingValue) t = {
199
+ loadingValue: undefined
200
+ };
201
+ r = computed(() => {
197
202
  setProjectionWriteActive(true);
198
203
  try {
199
- runProjectionComputed(c, e, r?.key === undefined ? "id" : r.key, wrapCommit, clearProjectionOverride);
204
+ runProjectionComputed(c, e, i?.key === undefined ? "id" : i.key, wrapCommit, clearProjectionOverride);
200
205
  } finally {
201
206
  setProjectionWriteActive(false);
202
207
  }
203
- }, undefined);
204
- i.T &= ~CONFIG_AUTO_DISPOSE;
208
+ }, t);
209
+ r.T &= ~CONFIG_AUTO_DISPOSE;
205
210
  }
206
211
  return {
207
212
  store: c,
208
- node: i
213
+ node: r
209
214
  };
210
215
  }
211
216
 
@@ -14,7 +14,7 @@ import { CONFIG_AUTO_DISPOSE } from "../core/constants.js";
14
14
 
15
15
  import { reconcileState } from "./reconcile.js";
16
16
 
17
- import { storeSetter, $TARGET, STORE_WRAP, createStoreProxy, storeTraps, setWriteOverride, STORE_LOOKUP, STORE_SHALLOW, markRawIngest, STORE_VALUE, STORE_FIREWALL } from "./store.js";
17
+ import { $TARGET, STORE_VALUE, storeSetter, STORE_WRAP, createStoreProxy, storeTraps, setWriteOverride, STORE_LOOKUP, STORE_SHALLOW, markRawIngest, STORE_FIREWALL } from "./store.js";
18
18
 
19
19
  function createProjectionInternal(e, t, r) {
20
20
  let o;
@@ -44,10 +44,18 @@ function createProjectionInternal(e, t, r) {
44
44
  return t;
45
45
  };
46
46
  const c = wrapProjection(t);
47
+ // seedLoadingValue: the firewall is born committed (the seed is commit #0);
48
+ // the internal handleAsync serves it during the derive's first flight. The
49
+ // node's own value channel is void, so the loading value itself is
50
+ // `undefined` — presence of the key is what flips the mode.
51
+ let s;
52
+ if (r?.seedLoadingValue) s = {
53
+ loadingValue: undefined
54
+ };
47
55
  o = computed(() => {
48
56
  if (!o) o = getOwner();
49
57
  runProjectionComputed(c, e, r?.key === undefined ? "id" : r.key);
50
- }, undefined);
58
+ }, s);
51
59
  o.T &= ~CONFIG_AUTO_DISPOSE;
52
60
  return {
53
61
  store: c,
@@ -122,16 +130,43 @@ function createProjectionInternal(e, t, r) {
122
130
  const i = getOwner();
123
131
  let c = false;
124
132
  let s;
125
- const u = new Proxy(e, createWriteTraps(() => !c || i.Te === s, n));
126
- storeSetter(u, n => {
127
- s = t(n);
133
+ // Open loading window (seedLoadingValue): the observable store IS commit #0
134
+ // for the whole first flight, so the derive works a detached shadow of the
135
+ // seed — draft writes (pre-await, or between yields) land on the shadow and
136
+ // cannot tear through to readers (#2988; store reads resolve from the live
137
+ // backing, and the born-committed firewall removed the status gate that hid
138
+ // windowless drafts). Every commit point — sync return, each yield, the
139
+ // async landing — reconciles the shadow through the normal commit path, so
140
+ // a fully-sync derive still lands immediately (commit #0 superseded before
141
+ // any observer runs, same as a sync answer superseding loadingValue). The
142
+ // JSON round-trip matches the server's frozen-seed copy (seedLock): a
143
+ // loading-window seed is renderable data by contract. Optimistic note:
144
+ // onDraftWrite (override clearing) shifts from write-time to commit-time
145
+ // for the shadow run — an invisible draft write must not clobber a visible
146
+ // optimistic override mid-window.
147
+ const u = i.Ee ? JSON.parse(JSON.stringify(e[$TARGET][STORE_VALUE])) : null;
148
+ const a = new Proxy(e, createWriteTraps(() => !c || i.Te === s, n));
149
+ storeSetter(a, n => {
150
+ s = t(u ?? n);
128
151
  c = true;
129
152
  const commit = t => {
153
+ // Shadow run: a void/self return is the mutation form — the shadow
154
+ // carries the writes and is what commits. Commit a detached snapshot,
155
+ // never the shadow itself: reconcile adopts a new root value by
156
+ // identity, and handing it the live shadow would fuse the draft to the
157
+ // observable store — later shadow writes would mutate the backing
158
+ // silently and the next yield would diff the shadow against itself.
159
+ if (u && (t === undefined || t === u)) t = JSON.parse(JSON.stringify(u));
130
160
  if (t === n || t === undefined) return;
131
161
  const write = () => storeSetter(e, e => reconcileState(t, e, r, true));
132
162
  o ? o(write) : write();
133
163
  };
134
- commit(handleAsync(i, s, commit));
164
+ const a = handleAsync(i, s, commit);
165
+ // A still-open window after handleAsync means the return was the
166
+ // commit-#0 fall-through, not a landing — real landings arrive through
167
+ // the setter. A closed one is a genuine sync landing (windowless nodes
168
+ // were never open); commit it.
169
+ if (!i.Ee) commit(a);
135
170
  });
136
171
  return i;
137
172
  }
@@ -291,7 +291,7 @@ function ownEnumerableKeysPlain(e) {
291
291
  * The value a store leaf's backing signal currently shows to readers: active
292
292
  * override, else held pending value, else committed value.
293
293
  */ function visibleNodeValue(e) {
294
- return e.Ae !== undefined && e.Ae !== NOT_PENDING ? unwrapOverride(e.Ae) : e.De !== NOT_PENDING ? e.De : e.Ue;
294
+ return e._e !== undefined && e._e !== NOT_PENDING ? unwrapOverride(e._e) : e.De !== NOT_PENDING ? e.De : e.Ue;
295
295
  }
296
296
 
297
297
  function hasOwnStoreProperty(e, t) {
@@ -342,11 +342,11 @@ function getNode(e, t, r, n, o = isEqual, i) {
342
342
  }
343
343
  }, e[STORE_FIREWALL]);
344
344
  if (e[STORE_OPTIMISTIC]) {
345
- O.Ae = NOT_PENDING;
345
+ O._e = NOT_PENDING;
346
346
  }
347
347
  if (i && r in i) {
348
348
  const e = i[r];
349
- O.xe = e === undefined ? NO_SNAPSHOT : e;
349
+ O.Le = e === undefined ? NO_SNAPSHOT : e;
350
350
  snapshotSources?.add(O);
351
351
  }
352
352
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
@@ -464,11 +464,11 @@ o) {
464
464
  // Callers guard on `pendingCheckActive`, which only flips inside
465
465
  // isPending() — the verdict layer is loaded and its hook installed.
466
466
  const r = e[STORE_NODE]?.[$AFFECTS];
467
- if (r?.t) GlobalQueue.Gt(r);
467
+ if (r?.t) GlobalQueue.Lt(r);
468
468
  if (affectsScopes.size) {
469
469
  const n = e[STORE_VALUE];
470
470
  for (const [e, o] of affectsScopes) {
471
- if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.Gt(e);
471
+ if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.Lt(e);
472
472
  }
473
473
  }
474
474
  }
@@ -610,8 +610,8 @@ function getPropertyDescriptor(e, t, r) {
610
610
  function prepareStoreWrite(e, t, r) {
611
611
  if (e[STORE_OPTIMISTIC]) {
612
612
  const t = e[STORE_FIREWALL];
613
- if (t?.Ie) {
614
- globalQueue.initTransition(t.Ie);
613
+ if (t?.Ne) {
614
+ globalQueue.initTransition(t.Ne);
615
615
  }
616
616
  }
617
617
  const n = e[STORE_VALUE];
@@ -1,6 +1,15 @@
1
+ import { NotReadyError } from "./error.js";
1
2
  import { type OptimisticLane } from "./lanes.js";
2
3
  import type { Computed, Link } from "./types.js";
3
4
  export declare function addPendingSource(el: Computed<any>, source: Computed<any>): boolean;
5
+ /**
6
+ * A loading-window node hit an unready source (sync throw in recompute, or a
7
+ * NotReadyError-rejected flight): register for the source's settle — the
8
+ * settlePendingSource walk runs off `_pendingSources` + `_blocked` alone —
9
+ * with NO read-visible pending status, no downstream propagation, no
10
+ * transition, no lane registration. Commit #0 keeps serving.
11
+ */
12
+ export declare function parkLoadingWindow(el: Computed<any>, e: NotReadyError): void;
4
13
  export declare function setPendingError(el: Computed<any>, source?: Computed<any>, error?: any): void;
5
14
  export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
6
15
  export declare function releaseSettledDependents(el: Computed<any>): void;
@@ -30,6 +30,16 @@ export interface NodeOptions<T> {
30
30
  unobserved?: () => void;
31
31
  lazy?: boolean;
32
32
  sync?: boolean;
33
+ /**
34
+ * Commit #0. When present (checked with `in`, so an explicit `undefined`
35
+ * counts), the node is born committed with this value instead of
36
+ * STATUS_UNINITIALIZED: reads serve it everywhere, nothing suspends to
37
+ * Loading boundaries, transitions are never held, and the window is
38
+ * verdict-quiet (`isPending` stays false — commit #0 answers the question
39
+ * by declaration; first-load affordances live in the value itself). After
40
+ * the first real answer lands, normal refetch/pending semantics apply.
41
+ */
42
+ loadingValue?: T;
33
43
  }
34
44
  export interface RawSignal<T> {
35
45
  _subs: Link | null;
@@ -116,6 +126,20 @@ export interface Computed<T> extends RawSignal<T>, Owner {
116
126
  * (`clearStatus`). Meaningless while not STATUS_PENDING.
117
127
  */
118
128
  _reask: boolean;
129
+ /**
130
+ * True while a `loadingValue` node's first real answer hasn't landed: the
131
+ * node was born committed (commit #0 = the loading value) and `handleAsync`
132
+ * serves that committed value instead of throwing NotReadyError, so first
133
+ * flights never suspend readers, trip boundaries, or hold transitions.
134
+ * The window is verdict-quiet: `isPending` stays false, because commit #0
135
+ * answers the question by declaration (first-load affordances belong to
136
+ * the value channel). Cleared by the first value landing on any path (sync
137
+ * return, sync-resolved promise, first iterator yield, async settle); a
138
+ * real error leaves it set — errors answer reads but don't enter the value
139
+ * lineage, so a retry serves the loading value again. Once cleared, normal
140
+ * pending/refetch semantics apply forever.
141
+ */
142
+ _loading: boolean;
119
143
  }
120
144
  export interface Root extends Owner {
121
145
  _root: true;
@@ -111,6 +111,28 @@ export interface EffectOptions extends BaseEffectOptions {
111
111
  * stored as-is and never awaited.
112
112
  */
113
113
  sync?: boolean;
114
+ /**
115
+ * Advanced (integration tier). When true, the effect is invisible to the
116
+ * hydration id scheme: it inherits its parent's id instead of consuming a
117
+ * child slot, and during hydration its compute runs live instead of
118
+ * adopting the serialized server value (its first run is not frozen to
119
+ * the server's decision).
120
+ *
121
+ * For **client-only effects created while hydrating** — effects with no
122
+ * server-rendered counterpart (a router wiring link state, scroll
123
+ * restoration, etc.). An id-consuming node the server never created would
124
+ * shift every later sibling's hydration id, making serialized lookups and
125
+ * template claims after it miss. `transparent` is also the supported
126
+ * alternative to branching on hydration state
127
+ * (`if (hydrating) createEffect(...)`), which freezes whatever the first
128
+ * run decided: create the effect unconditionally and let it observe live
129
+ * state instead.
130
+ *
131
+ * SSR ignores this option (a server-side effect always allocates its id
132
+ * slot), so only mark effects the server does not create. Outside
133
+ * hydration it is a no-op.
134
+ */
135
+ transparent?: boolean;
114
136
  }
115
137
  /** Options for plain signals created with `createSignal(value)` or `createOptimistic(value)`. */
116
138
  export interface SignalOptions<T> {
@@ -138,7 +160,14 @@ export interface MemoOptions<T> {
138
160
  id?: string;
139
161
  /** Debug name (dev mode only) */
140
162
  name?: string;
141
- /** When true, the owner is invisible to the ID scheme -- inherits parent ID and doesn't consume a childCount slot */
163
+ /**
164
+ * Advanced (integration tier). When true, the memo is invisible to the
165
+ * hydration id scheme: it inherits its parent's id instead of consuming a
166
+ * child slot, and during hydration it computes live instead of adopting
167
+ * the serialized server value. For client-only memos with no
168
+ * server-rendered counterpart — see {@link EffectOptions.transparent} for
169
+ * the full semantics. No-op outside hydration.
170
+ */
142
171
  transparent?: boolean;
143
172
  /**
144
173
  * Custom equality function, or `false` to always notify subscribers.
@@ -168,6 +197,30 @@ export interface MemoOptions<T> {
168
197
  * stored as-is and never awaited.
169
198
  */
170
199
  sync?: boolean;
200
+ /**
201
+ * Commit #0: a committed value the memo is born with, shown until the
202
+ * compute's first real answer lands. While that first answer is in flight
203
+ * the memo reads as a settled value everywhere — nothing suspends to a
204
+ * `<Loading>` boundary, no transition is held (first-flight work is
205
+ * loading-class, like a boundary fallback), and `isPending(memo)` stays
206
+ * **false**: commit #0 answers the question by declaration, so first-load
207
+ * affordances are driven from the value itself (a `null` placeholder, a
208
+ * `skeleton: true` field, etc.). Once the first answer lands, the loading
209
+ * value leaves the lineage forever: refetches use normal pending semantics
210
+ * (stale value shown, `isPending` true, boundaries/transitions coordinate)
211
+ * — the canonical guard is `data.skeleton || isPending(data)`, whose two
212
+ * terms cover the two disjoint states.
213
+ *
214
+ * Typed strictly as `T`: to use `null`/`undefined` as the placeholder,
215
+ * declare it in the memo's type (e.g. `createMemo<User | null>(...)`), so
216
+ * every consumer sees the nullable window honestly. If the placeholder is
217
+ * shaped data standing in for real data, encode its provenance in the data
218
+ * (e.g. a `skeleton: true` field) rather than letting it impersonate truth.
219
+ *
220
+ * The loading value is also the compute's first `prev`, so `prev`-based
221
+ * memos fold from it.
222
+ */
223
+ loadingValue?: T;
171
224
  }
172
225
  export type NoInfer<T extends any> = [T][T extends any ? 0 : never];
173
226
  /**
@@ -216,7 +269,7 @@ export declare function createSignal<T>(fn: ComputeFunction<T>, options?: Signal
216
269
  * const value = createMemo<T>(compute, options?: MemoOptions<T>);
217
270
  * ```
218
271
  * @param compute a function that receives its previous value and returns a new value used to react on a computation
219
- * @param options `MemoOptions` -- id, name, equals, unobserved, lazy
272
+ * @param options `MemoOptions` -- id, name, equals, unobserved, lazy, transparent
220
273
  *
221
274
  * @example
222
275
  * ```ts
@@ -239,6 +292,9 @@ export declare function createSignal<T>(fn: ComputeFunction<T>, options?: Signal
239
292
  *
240
293
  * @description https://docs.solidjs.com/reference/basic-reactivity/create-memo
241
294
  */
295
+ export declare function createMemo<T>(compute: ComputeFunction<NoInfer<T>, T>, options: MemoOptions<T> & {
296
+ loadingValue: T;
297
+ }): SourceAccessor<T>;
242
298
  export declare function createMemo<T>(compute: ComputeFunction<undefined | NoInfer<T>, T>, options?: MemoOptions<T>): SourceAccessor<T>;
243
299
  /**
244
300
  * Creates a reactive effect with **separate compute and effect phases**.
@@ -280,7 +336,7 @@ export declare function createMemo<T>(compute: ComputeFunction<undefined | NoInf
280
336
  * ```
281
337
  * @param compute a function that receives its previous value and returns a new value used to react on a computation
282
338
  * @param effectFn a function that receives the new value and is used to perform side effects (return a cleanup function), or an `EffectBundle` with `effect` and `error` handlers
283
- * @param options `EffectOptions` -- name, defer, schedule
339
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
284
340
  *
285
341
  * @example
286
342
  * ```ts
@@ -334,7 +390,7 @@ export declare function createEffect<T>(compute: ComputeFunction<undefined | NoI
334
390
  * ```
335
391
  * @param compute a function that receives its previous value and returns a new value used to react on a computation
336
392
  * @param effectFn a function that receives the new value and is used to perform side effects
337
- * @param options `EffectOptions` -- name, defer, schedule
393
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
338
394
  *
339
395
  * @example
340
396
  * ```ts
@@ -33,6 +33,20 @@ export interface ProjectionOptions extends StoreOptions {
33
33
  key?: string | ((item: NonNullable<any>) => any) | null;
34
34
  /** Single-layer store: root keys reactive, values raw records replaced by reference */
35
35
  shallow?: boolean;
36
+ /**
37
+ * Treat the seed as commit #0: the store is born committed with the seed's
38
+ * contents, shown until the derive's first real answer lands. While that
39
+ * first answer is in flight, reads serve the seed everywhere — nothing
40
+ * suspends to a `<Loading>` boundary, no transition is held, and
41
+ * `isPending` stays false (the seed answers by declaration; first-load
42
+ * affordances belong to the data, e.g. a `skeleton: true` field in the
43
+ * seed). Once the first answer lands (reconciled into the seed), refetches
44
+ * use normal pending semantics with `isPending` true.
45
+ *
46
+ * The store equivalent of `MemoOptions.loadingValue`; the seed already
47
+ * carries the placeholder shape, so this is just the opt-in.
48
+ */
49
+ seedLoadingValue?: boolean;
36
50
  }
37
51
  export type NoFn<T> = T extends Function ? never : T;
38
52
  type DataNode = Signal<any>;
@@ -1,6 +1,15 @@
1
+ import { NotReadyError } from "./error.cjs";
1
2
  import { type OptimisticLane } from "./lanes.cjs";
2
3
  import type { Computed, Link } from "./types.cjs";
3
4
  export declare function addPendingSource(el: Computed<any>, source: Computed<any>): boolean;
5
+ /**
6
+ * A loading-window node hit an unready source (sync throw in recompute, or a
7
+ * NotReadyError-rejected flight): register for the source's settle — the
8
+ * settlePendingSource walk runs off `_pendingSources` + `_blocked` alone —
9
+ * with NO read-visible pending status, no downstream propagation, no
10
+ * transition, no lane registration. Commit #0 keeps serving.
11
+ */
12
+ export declare function parkLoadingWindow(el: Computed<any>, e: NotReadyError): void;
4
13
  export declare function setPendingError(el: Computed<any>, source?: Computed<any>, error?: any): void;
5
14
  export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
6
15
  export declare function releaseSettledDependents(el: Computed<any>): void;
@@ -30,6 +30,16 @@ export interface NodeOptions<T> {
30
30
  unobserved?: () => void;
31
31
  lazy?: boolean;
32
32
  sync?: boolean;
33
+ /**
34
+ * Commit #0. When present (checked with `in`, so an explicit `undefined`
35
+ * counts), the node is born committed with this value instead of
36
+ * STATUS_UNINITIALIZED: reads serve it everywhere, nothing suspends to
37
+ * Loading boundaries, transitions are never held, and the window is
38
+ * verdict-quiet (`isPending` stays false — commit #0 answers the question
39
+ * by declaration; first-load affordances live in the value itself). After
40
+ * the first real answer lands, normal refetch/pending semantics apply.
41
+ */
42
+ loadingValue?: T;
33
43
  }
34
44
  export interface RawSignal<T> {
35
45
  _subs: Link | null;
@@ -116,6 +126,20 @@ export interface Computed<T> extends RawSignal<T>, Owner {
116
126
  * (`clearStatus`). Meaningless while not STATUS_PENDING.
117
127
  */
118
128
  _reask: boolean;
129
+ /**
130
+ * True while a `loadingValue` node's first real answer hasn't landed: the
131
+ * node was born committed (commit #0 = the loading value) and `handleAsync`
132
+ * serves that committed value instead of throwing NotReadyError, so first
133
+ * flights never suspend readers, trip boundaries, or hold transitions.
134
+ * The window is verdict-quiet: `isPending` stays false, because commit #0
135
+ * answers the question by declaration (first-load affordances belong to
136
+ * the value channel). Cleared by the first value landing on any path (sync
137
+ * return, sync-resolved promise, first iterator yield, async settle); a
138
+ * real error leaves it set — errors answer reads but don't enter the value
139
+ * lineage, so a retry serves the loading value again. Once cleared, normal
140
+ * pending/refetch semantics apply forever.
141
+ */
142
+ _loading: boolean;
119
143
  }
120
144
  export interface Root extends Owner {
121
145
  _root: true;