@kontsedal/olas-core 0.0.1-rc.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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/index.cjs +363 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +178 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +178 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +339 -0
- package/dist/index.mjs.map +1 -0
- package/dist/root-BImHnGj1.mjs +3270 -0
- package/dist/root-BImHnGj1.mjs.map +1 -0
- package/dist/root-Bazp5_Ik.cjs +3347 -0
- package/dist/root-Bazp5_Ik.cjs.map +1 -0
- package/dist/testing.cjs +81 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +56 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +56 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +78 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-CAMgqCMz.d.mts +816 -0
- package/dist/types-CAMgqCMz.d.mts.map +1 -0
- package/dist/types-emq_lZd7.d.cts +816 -0
- package/dist/types-emq_lZd7.d.cts.map +1 -0
- package/package.json +47 -0
- package/src/__dev__.d.ts +8 -0
- package/src/controller/define.ts +50 -0
- package/src/controller/index.ts +12 -0
- package/src/controller/instance.ts +499 -0
- package/src/controller/root.ts +160 -0
- package/src/controller/types.ts +195 -0
- package/src/devtools.ts +0 -0
- package/src/emitter.ts +79 -0
- package/src/errors.ts +49 -0
- package/src/forms/field.ts +303 -0
- package/src/forms/form-types.ts +130 -0
- package/src/forms/form.ts +640 -0
- package/src/forms/index.ts +2 -0
- package/src/forms/types.ts +1 -0
- package/src/forms/validators.ts +70 -0
- package/src/index.ts +89 -0
- package/src/query/client.ts +934 -0
- package/src/query/define.ts +154 -0
- package/src/query/entry.ts +322 -0
- package/src/query/focus-online.ts +73 -0
- package/src/query/index.ts +3 -0
- package/src/query/infinite.ts +462 -0
- package/src/query/keys.ts +33 -0
- package/src/query/local.ts +113 -0
- package/src/query/mutation.ts +384 -0
- package/src/query/plugin.ts +135 -0
- package/src/query/types.ts +168 -0
- package/src/query/use.ts +321 -0
- package/src/scope.ts +42 -0
- package/src/selection.ts +146 -0
- package/src/signals/index.ts +3 -0
- package/src/signals/readonly.ts +22 -0
- package/src/signals/runtime.ts +115 -0
- package/src/signals/types.ts +31 -0
- package/src/testing.ts +142 -0
- package/src/timing/debounced.ts +32 -0
- package/src/timing/index.ts +2 -0
- package/src/timing/throttled.ts +46 -0
- package/src/utils.ts +13 -0
|
@@ -0,0 +1,3270 @@
|
|
|
1
|
+
import { batch, computed, effect, signal, untracked } from "@preact/signals-core";
|
|
2
|
+
//#region src/controller/define.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create a controller definition. The factory is stored on the returned object
|
|
5
|
+
* and invoked during `createRoot` / `ctx.child` to build instances.
|
|
6
|
+
*
|
|
7
|
+
* `Props` defaults to `void` so a factory written as `(ctx) => ...` is typed
|
|
8
|
+
* as `ControllerDef<void, Api>` — the form `createRoot` requires.
|
|
9
|
+
*/
|
|
10
|
+
function defineController(factory, options) {
|
|
11
|
+
return {
|
|
12
|
+
__olas: "controller",
|
|
13
|
+
__factory: factory,
|
|
14
|
+
...options?.name !== void 0 ? { __name: options.name } : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Internal — extracts the factory from a ControllerDef. */
|
|
18
|
+
function getFactory(def) {
|
|
19
|
+
return def.__factory;
|
|
20
|
+
}
|
|
21
|
+
/** Internal — extracts the explicit `name` option from a ControllerDef, if any. */
|
|
22
|
+
function getName(def) {
|
|
23
|
+
return def.__name;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/devtools.ts
|
|
27
|
+
function pathKey(path) {
|
|
28
|
+
return path.join("\0");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Per-root devtools event multiplexer. Emit is a no-op when no one is
|
|
32
|
+
* subscribed (one Set size check), so leaving the bus in production has
|
|
33
|
+
* effectively zero cost until a consumer attaches.
|
|
34
|
+
*
|
|
35
|
+
* Internal — exposed to consumers via `root.__debug`.
|
|
36
|
+
*
|
|
37
|
+
* Tracks a snapshot of every live controller (constructed but not yet
|
|
38
|
+
* disposed) and replays construction events to new subscribers, so the
|
|
39
|
+
* devtools tree populates on mount instead of staying blank until the
|
|
40
|
+
* next event.
|
|
41
|
+
*/
|
|
42
|
+
var DevtoolsEmitter = class {
|
|
43
|
+
handlers = /* @__PURE__ */ new Set();
|
|
44
|
+
/** Path → entry for every live (constructed, not disposed) controller. */
|
|
45
|
+
liveControllers = /* @__PURE__ */ new Map();
|
|
46
|
+
subscribe(handler) {
|
|
47
|
+
for (const entry of this.liveControllers.values()) try {
|
|
48
|
+
handler({
|
|
49
|
+
type: "controller:constructed",
|
|
50
|
+
path: entry.path,
|
|
51
|
+
props: entry.props
|
|
52
|
+
});
|
|
53
|
+
if (entry.state === "suspended") handler({
|
|
54
|
+
type: "controller:suspended",
|
|
55
|
+
path: entry.path
|
|
56
|
+
});
|
|
57
|
+
} catch {}
|
|
58
|
+
this.handlers.add(handler);
|
|
59
|
+
return () => {
|
|
60
|
+
this.handlers.delete(handler);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
emit(event) {
|
|
64
|
+
this.recordLifecycle(event);
|
|
65
|
+
if (this.handlers.size === 0) return;
|
|
66
|
+
const snapshot = Array.from(this.handlers);
|
|
67
|
+
for (const handler of snapshot) try {
|
|
68
|
+
handler(event);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
get hasSubscribers() {
|
|
72
|
+
return this.handlers.size > 0;
|
|
73
|
+
}
|
|
74
|
+
recordLifecycle(event) {
|
|
75
|
+
if (event.type === "controller:constructed") {
|
|
76
|
+
this.liveControllers.set(pathKey(event.path), {
|
|
77
|
+
path: event.path,
|
|
78
|
+
props: event.props,
|
|
79
|
+
state: "active"
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (event.type === "controller:suspended") {
|
|
84
|
+
const key = pathKey(event.path);
|
|
85
|
+
const cur = this.liveControllers.get(key);
|
|
86
|
+
if (cur !== void 0) cur.state = "suspended";
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (event.type === "controller:resumed") {
|
|
90
|
+
const key = pathKey(event.path);
|
|
91
|
+
const cur = this.liveControllers.get(key);
|
|
92
|
+
if (cur !== void 0) cur.state = "active";
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (event.type === "controller:disposed") {
|
|
96
|
+
this.liveControllers.delete(pathKey(event.path));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/errors.ts
|
|
103
|
+
const defaultHandler = (err, context) => {
|
|
104
|
+
console.error("[olas]", context, err);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Dispatch an error to a user-provided handler, falling back to console.error.
|
|
108
|
+
* The handler itself is wrapped — if it throws, the throw is swallowed and
|
|
109
|
+
* logged so an `onError` bug never tears down the tree.
|
|
110
|
+
*
|
|
111
|
+
* Internal — used by the controller container and query client.
|
|
112
|
+
*/
|
|
113
|
+
function dispatchError(handler, err, context) {
|
|
114
|
+
const fn = handler ?? defaultHandler;
|
|
115
|
+
try {
|
|
116
|
+
fn(err, context);
|
|
117
|
+
} catch (handlerErr) {
|
|
118
|
+
try {
|
|
119
|
+
console.error("[olas] onError handler threw:", handlerErr);
|
|
120
|
+
console.error("[olas] original error:", err, context);
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/signals/runtime.ts
|
|
126
|
+
var SignalImpl = class {
|
|
127
|
+
inner;
|
|
128
|
+
constructor(initial) {
|
|
129
|
+
this.inner = signal(initial);
|
|
130
|
+
}
|
|
131
|
+
get value() {
|
|
132
|
+
return this.inner.value;
|
|
133
|
+
}
|
|
134
|
+
set value(next) {
|
|
135
|
+
this.inner.value = next;
|
|
136
|
+
}
|
|
137
|
+
peek() {
|
|
138
|
+
return this.inner.peek();
|
|
139
|
+
}
|
|
140
|
+
subscribe(handler) {
|
|
141
|
+
return this.inner.subscribe(handler);
|
|
142
|
+
}
|
|
143
|
+
set(value) {
|
|
144
|
+
this.inner.value = value;
|
|
145
|
+
}
|
|
146
|
+
update(fn) {
|
|
147
|
+
this.inner.value = fn(this.inner.peek());
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var ComputedImpl = class {
|
|
151
|
+
inner;
|
|
152
|
+
constructor(fn) {
|
|
153
|
+
this.inner = computed(fn);
|
|
154
|
+
}
|
|
155
|
+
get value() {
|
|
156
|
+
return this.inner.value;
|
|
157
|
+
}
|
|
158
|
+
peek() {
|
|
159
|
+
return this.inner.peek();
|
|
160
|
+
}
|
|
161
|
+
subscribe(handler) {
|
|
162
|
+
return this.inner.subscribe(handler);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Create a writable `Signal<T>`. Reads track the current auto-tracking scope
|
|
167
|
+
* (effect / computed); writes notify all subscribers (deduped via `Object.is`).
|
|
168
|
+
*
|
|
169
|
+
* Spec §20.1. For a single-pass non-tracked read use `signal.peek()`.
|
|
170
|
+
*/
|
|
171
|
+
function signal$1(initial) {
|
|
172
|
+
return new SignalImpl(initial);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a `Computed<T>` — a read-only derived signal. The provided `fn` is
|
|
176
|
+
* re-evaluated whenever a signal it read during its last run changes; the
|
|
177
|
+
* resulting value is cached until then.
|
|
178
|
+
*
|
|
179
|
+
* Spec §20.1. The graph is glitch-free: a `computed` re-runs at most once per
|
|
180
|
+
* batched-write cycle.
|
|
181
|
+
*/
|
|
182
|
+
function computed$1(fn) {
|
|
183
|
+
return new ComputedImpl(fn);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Run `fn` immediately and again whenever any signal it reads changes. If
|
|
187
|
+
* `fn` returns a function, that function is called as a cleanup before the
|
|
188
|
+
* next re-run and on dispose.
|
|
189
|
+
*
|
|
190
|
+
* Returns a `dispose` function. Inside a controller use `ctx.effect(...)`
|
|
191
|
+
* instead — that variant is auto-disposed with the controller.
|
|
192
|
+
*/
|
|
193
|
+
function effect$1(fn) {
|
|
194
|
+
return effect(fn);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Batch synchronous signal writes so subscribers see one notification at the
|
|
198
|
+
* end of the batch rather than one per write. Returns whatever `fn` returns.
|
|
199
|
+
*/
|
|
200
|
+
function batch$1(fn) {
|
|
201
|
+
return batch(fn);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Run `fn` with auto-tracking suppressed — signals read inside don't become
|
|
205
|
+
* dependencies of the surrounding `computed` / `effect`. Useful for "read
|
|
206
|
+
* these signals once to log them" or for snapshotting state inside an effect
|
|
207
|
+
* without subscribing to it. For a single-signal peek, prefer `signal.peek()`.
|
|
208
|
+
*/
|
|
209
|
+
function untracked$1(fn) {
|
|
210
|
+
return untracked(fn);
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/utils.ts
|
|
214
|
+
/**
|
|
215
|
+
* True iff `err` is an AbortError. Used to filter superseded latest-wins
|
|
216
|
+
* mutations and aborted fetches from genuine failures.
|
|
217
|
+
*
|
|
218
|
+
* Spec: §20.12 — checks `err instanceof DOMException && err.name === 'AbortError'`.
|
|
219
|
+
* Node 17+ exposes a global DOMException, so this works server-side too.
|
|
220
|
+
*/
|
|
221
|
+
function isAbortError(err) {
|
|
222
|
+
if (typeof DOMException !== "undefined" && err instanceof DOMException) return err.name === "AbortError";
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/query/entry.ts
|
|
227
|
+
/**
|
|
228
|
+
* One cache entry's state machine. Owns the AsyncState signals, race
|
|
229
|
+
* protection, retry loop, optimistic-update snapshot stack.
|
|
230
|
+
*
|
|
231
|
+
* Internal — not exported from the public surface.
|
|
232
|
+
*/
|
|
233
|
+
var Entry = class {
|
|
234
|
+
data;
|
|
235
|
+
error = signal$1(void 0);
|
|
236
|
+
status;
|
|
237
|
+
isLoading = signal$1(false);
|
|
238
|
+
isFetching = signal$1(false);
|
|
239
|
+
lastUpdatedAt;
|
|
240
|
+
hasPendingMutations = signal$1(false);
|
|
241
|
+
isStale = signal$1(true);
|
|
242
|
+
fetcherProvider;
|
|
243
|
+
staleTime;
|
|
244
|
+
retry;
|
|
245
|
+
retryDelay;
|
|
246
|
+
currentFetchId = 0;
|
|
247
|
+
currentAbort = null;
|
|
248
|
+
staleTimer = null;
|
|
249
|
+
snapshots = [];
|
|
250
|
+
nextSnapshotId = 0;
|
|
251
|
+
disposed = false;
|
|
252
|
+
events;
|
|
253
|
+
fetchStartTime = 0;
|
|
254
|
+
constructor(options) {
|
|
255
|
+
this.fetcherProvider = options.fetcher;
|
|
256
|
+
this.staleTime = options.staleTime ?? 0;
|
|
257
|
+
this.retry = options.retry ?? 0;
|
|
258
|
+
this.retryDelay = options.retryDelay ?? 1e3;
|
|
259
|
+
this.events = options.events ?? {};
|
|
260
|
+
this.data = signal$1(options.initialData);
|
|
261
|
+
if (options.initialData !== void 0) {
|
|
262
|
+
this.status = signal$1("success");
|
|
263
|
+
this.scheduleStaleness();
|
|
264
|
+
this.isStale.set(this.staleTime === 0);
|
|
265
|
+
} else this.status = signal$1("idle");
|
|
266
|
+
this.lastUpdatedAt = signal$1(options.initialUpdatedAt);
|
|
267
|
+
}
|
|
268
|
+
startFetch() {
|
|
269
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
|
|
270
|
+
const myId = ++this.currentFetchId;
|
|
271
|
+
this.currentAbort?.abort();
|
|
272
|
+
const abort = new AbortController();
|
|
273
|
+
this.currentAbort = abort;
|
|
274
|
+
const previouslyHadData = this.data.peek() !== void 0;
|
|
275
|
+
batch$1(() => {
|
|
276
|
+
this.status.set("pending");
|
|
277
|
+
this.isFetching.set(true);
|
|
278
|
+
this.isLoading.set(!previouslyHadData);
|
|
279
|
+
});
|
|
280
|
+
this.fetchStartTime = Date.now();
|
|
281
|
+
try {
|
|
282
|
+
this.events.onFetchStart?.();
|
|
283
|
+
} catch {}
|
|
284
|
+
return this.runWithRetry(myId, abort);
|
|
285
|
+
}
|
|
286
|
+
async runWithRetry(myId, abort) {
|
|
287
|
+
let attempt = 0;
|
|
288
|
+
while (true) {
|
|
289
|
+
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
290
|
+
try {
|
|
291
|
+
const result = await this.fetcherProvider()(abort.signal);
|
|
292
|
+
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
293
|
+
return this.applySuccess(result);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
|
|
296
|
+
if (!this.shouldRetry(attempt, err)) return this.applyFailure(err);
|
|
297
|
+
await abortableSleep$2(this.computeDelay(attempt), abort.signal);
|
|
298
|
+
attempt += 1;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
shouldRetry(attempt, err) {
|
|
303
|
+
const retry = this.retry;
|
|
304
|
+
if (retry === 0) return false;
|
|
305
|
+
if (typeof retry === "number") return attempt < retry;
|
|
306
|
+
return retry(attempt, err);
|
|
307
|
+
}
|
|
308
|
+
computeDelay(attempt) {
|
|
309
|
+
const d = this.retryDelay;
|
|
310
|
+
return typeof d === "function" ? d(attempt) : d;
|
|
311
|
+
}
|
|
312
|
+
applySuccess(result) {
|
|
313
|
+
batch$1(() => {
|
|
314
|
+
this.data.set(result);
|
|
315
|
+
this.error.set(void 0);
|
|
316
|
+
this.status.set("success");
|
|
317
|
+
this.isLoading.set(false);
|
|
318
|
+
this.isFetching.set(false);
|
|
319
|
+
this.lastUpdatedAt.set(Date.now());
|
|
320
|
+
this.isStale.set(this.staleTime === 0);
|
|
321
|
+
});
|
|
322
|
+
if (this.staleTime > 0) this.scheduleStaleness();
|
|
323
|
+
try {
|
|
324
|
+
this.events.onFetchSuccess?.(Date.now() - this.fetchStartTime);
|
|
325
|
+
} catch {}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
applyFailure(err) {
|
|
329
|
+
batch$1(() => {
|
|
330
|
+
this.error.set(err);
|
|
331
|
+
this.status.set("error");
|
|
332
|
+
this.isLoading.set(false);
|
|
333
|
+
this.isFetching.set(false);
|
|
334
|
+
});
|
|
335
|
+
try {
|
|
336
|
+
this.events.onFetchError?.(Date.now() - this.fetchStartTime, err);
|
|
337
|
+
} catch {}
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
scheduleStaleness() {
|
|
341
|
+
if (this.staleTimer != null) clearTimeout(this.staleTimer);
|
|
342
|
+
if (this.staleTime > 0) this.staleTimer = setTimeout(() => {
|
|
343
|
+
this.staleTimer = null;
|
|
344
|
+
if (!this.disposed) this.isStale.set(true);
|
|
345
|
+
}, this.staleTime);
|
|
346
|
+
}
|
|
347
|
+
refetch() {
|
|
348
|
+
return this.startFetch();
|
|
349
|
+
}
|
|
350
|
+
invalidate() {
|
|
351
|
+
if (this.staleTimer != null) {
|
|
352
|
+
clearTimeout(this.staleTimer);
|
|
353
|
+
this.staleTimer = null;
|
|
354
|
+
}
|
|
355
|
+
this.isStale.set(true);
|
|
356
|
+
return this.startFetch();
|
|
357
|
+
}
|
|
358
|
+
reset() {
|
|
359
|
+
if (this.disposed) return;
|
|
360
|
+
batch$1(() => {
|
|
361
|
+
this.error.set(void 0);
|
|
362
|
+
this.status.set(this.data.peek() !== void 0 ? "success" : "idle");
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
setData(updater) {
|
|
366
|
+
if (this.disposed) return {
|
|
367
|
+
rollback: () => {},
|
|
368
|
+
finalize: () => {}
|
|
369
|
+
};
|
|
370
|
+
const prev = this.data.peek();
|
|
371
|
+
const next = updater(prev);
|
|
372
|
+
const id = this.nextSnapshotId++;
|
|
373
|
+
const record = {
|
|
374
|
+
id,
|
|
375
|
+
prev,
|
|
376
|
+
live: true
|
|
377
|
+
};
|
|
378
|
+
this.snapshots.push(record);
|
|
379
|
+
batch$1(() => {
|
|
380
|
+
this.data.set(next);
|
|
381
|
+
if (this.status.peek() === "idle" || this.status.peek() === "pending") this.status.set("success");
|
|
382
|
+
this.lastUpdatedAt.set(Date.now());
|
|
383
|
+
this.hasPendingMutations.set(true);
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
rollback: () => {
|
|
387
|
+
if (!record.live || this.disposed) return;
|
|
388
|
+
record.live = false;
|
|
389
|
+
batch$1(() => {
|
|
390
|
+
this.data.set(record.prev);
|
|
391
|
+
this.snapshots = this.snapshots.filter((s) => s.id !== id);
|
|
392
|
+
const anyLive = this.snapshots.some((s) => s.live);
|
|
393
|
+
this.hasPendingMutations.set(anyLive);
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
finalize: () => {
|
|
397
|
+
if (!record.live || this.disposed) return;
|
|
398
|
+
record.live = false;
|
|
399
|
+
this.snapshots = this.snapshots.filter((s) => s.id !== id);
|
|
400
|
+
if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
finalizeSnapshot(snapshot) {
|
|
405
|
+
const id = snapshotIds.get(snapshot);
|
|
406
|
+
if (id === void 0) return;
|
|
407
|
+
const record = this.snapshots.find((s) => s.live && s.id === id);
|
|
408
|
+
if (!record) return;
|
|
409
|
+
record.live = false;
|
|
410
|
+
this.snapshots = this.snapshots.filter((s) => s !== record);
|
|
411
|
+
if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
|
|
412
|
+
}
|
|
413
|
+
firstValue() {
|
|
414
|
+
if (this.status.peek() === "success") return Promise.resolve(this.data.peek());
|
|
415
|
+
if (this.status.peek() === "error") return Promise.reject(this.error.peek());
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
const unsub = this.status.subscribe((s) => {
|
|
418
|
+
if (s === "success") {
|
|
419
|
+
unsub();
|
|
420
|
+
resolve(this.data.peek());
|
|
421
|
+
} else if (s === "error") {
|
|
422
|
+
unsub();
|
|
423
|
+
reject(this.error.peek());
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* True iff data is older than `staleTime` (or no data has been fetched yet).
|
|
430
|
+
* Used by the query client to decide whether to refetch on subscribe.
|
|
431
|
+
*/
|
|
432
|
+
isStaleNow() {
|
|
433
|
+
const last = this.lastUpdatedAt.peek();
|
|
434
|
+
if (last === void 0) return true;
|
|
435
|
+
return Date.now() - last >= this.staleTime;
|
|
436
|
+
}
|
|
437
|
+
dispose() {
|
|
438
|
+
if (this.disposed) return;
|
|
439
|
+
this.disposed = true;
|
|
440
|
+
if (this.staleTimer != null) {
|
|
441
|
+
clearTimeout(this.staleTimer);
|
|
442
|
+
this.staleTimer = null;
|
|
443
|
+
}
|
|
444
|
+
this.currentAbort?.abort();
|
|
445
|
+
this.currentAbort = null;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const snapshotIds = /* @__PURE__ */ new WeakMap();
|
|
449
|
+
function abortableSleep$2(ms, signal) {
|
|
450
|
+
return new Promise((resolve, reject) => {
|
|
451
|
+
if (signal.aborted) {
|
|
452
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const timer = setTimeout(() => {
|
|
456
|
+
signal.removeEventListener("abort", onAbort);
|
|
457
|
+
resolve();
|
|
458
|
+
}, ms);
|
|
459
|
+
const onAbort = () => {
|
|
460
|
+
clearTimeout(timer);
|
|
461
|
+
signal.removeEventListener("abort", onAbort);
|
|
462
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
463
|
+
};
|
|
464
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/query/focus-online.ts
|
|
469
|
+
const focusSubs = /* @__PURE__ */ new Set();
|
|
470
|
+
const onlineSubs = /* @__PURE__ */ new Set();
|
|
471
|
+
let focusInstalled = false;
|
|
472
|
+
let onlineInstalled = false;
|
|
473
|
+
function fireFocus() {
|
|
474
|
+
for (const fn of focusSubs) try {
|
|
475
|
+
fn();
|
|
476
|
+
} catch {}
|
|
477
|
+
}
|
|
478
|
+
function fireOnline() {
|
|
479
|
+
for (const fn of onlineSubs) try {
|
|
480
|
+
fn();
|
|
481
|
+
} catch {}
|
|
482
|
+
}
|
|
483
|
+
function ensureFocusInstalled() {
|
|
484
|
+
if (focusInstalled) return;
|
|
485
|
+
if (typeof window === "undefined") return;
|
|
486
|
+
window.addEventListener("focus", fireFocus);
|
|
487
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", () => {
|
|
488
|
+
if (document.visibilityState === "visible") fireFocus();
|
|
489
|
+
});
|
|
490
|
+
focusInstalled = true;
|
|
491
|
+
}
|
|
492
|
+
function ensureOnlineInstalled() {
|
|
493
|
+
if (onlineInstalled) return;
|
|
494
|
+
if (typeof window === "undefined") return;
|
|
495
|
+
window.addEventListener("online", fireOnline);
|
|
496
|
+
onlineInstalled = true;
|
|
497
|
+
}
|
|
498
|
+
function subscribeWindowFocus(fn) {
|
|
499
|
+
ensureFocusInstalled();
|
|
500
|
+
focusSubs.add(fn);
|
|
501
|
+
return () => {
|
|
502
|
+
focusSubs.delete(fn);
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function subscribeReconnect(fn) {
|
|
506
|
+
ensureOnlineInstalled();
|
|
507
|
+
onlineSubs.add(fn);
|
|
508
|
+
return () => {
|
|
509
|
+
onlineSubs.delete(fn);
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/query/infinite.ts
|
|
514
|
+
/**
|
|
515
|
+
* Holds an array of pages plus their pageParams. Supports fetchNextPage /
|
|
516
|
+
* fetchPreviousPage / invalidate (drops all pages). Race-protected.
|
|
517
|
+
*
|
|
518
|
+
* Internal.
|
|
519
|
+
*/
|
|
520
|
+
var InfiniteEntry = class {
|
|
521
|
+
pages = signal$1([]);
|
|
522
|
+
pageParams;
|
|
523
|
+
data;
|
|
524
|
+
error = signal$1(void 0);
|
|
525
|
+
status = signal$1("idle");
|
|
526
|
+
isLoading = signal$1(false);
|
|
527
|
+
isFetching = signal$1(false);
|
|
528
|
+
isStale = signal$1(true);
|
|
529
|
+
lastUpdatedAt = signal$1(void 0);
|
|
530
|
+
hasPendingMutations = signal$1(false);
|
|
531
|
+
isFetchingNextPage = signal$1(false);
|
|
532
|
+
isFetchingPreviousPage = signal$1(false);
|
|
533
|
+
hasNextPage;
|
|
534
|
+
hasPreviousPage;
|
|
535
|
+
flat;
|
|
536
|
+
currentFetchId = 0;
|
|
537
|
+
currentAbort = null;
|
|
538
|
+
staleTimer = null;
|
|
539
|
+
snapshots = [];
|
|
540
|
+
nextSnapshotId = 0;
|
|
541
|
+
disposed = false;
|
|
542
|
+
fetcher;
|
|
543
|
+
initialPageParam;
|
|
544
|
+
getNextPageParam;
|
|
545
|
+
getPreviousPageParam;
|
|
546
|
+
staleTime;
|
|
547
|
+
retry;
|
|
548
|
+
retryDelay;
|
|
549
|
+
itemsOf;
|
|
550
|
+
constructor(opts) {
|
|
551
|
+
this.fetcher = opts.fetcher;
|
|
552
|
+
this.initialPageParam = opts.initialPageParam;
|
|
553
|
+
this.getNextPageParam = opts.getNextPageParam;
|
|
554
|
+
this.getPreviousPageParam = opts.getPreviousPageParam;
|
|
555
|
+
this.itemsOf = opts.itemsOf;
|
|
556
|
+
this.staleTime = opts.staleTime ?? 0;
|
|
557
|
+
this.retry = opts.retry ?? 0;
|
|
558
|
+
this.retryDelay = opts.retryDelay ?? 1e3;
|
|
559
|
+
this.pageParams = signal$1([]);
|
|
560
|
+
this.data = computed$1(() => {
|
|
561
|
+
const ps = this.pages.value;
|
|
562
|
+
return ps.length === 0 ? void 0 : ps;
|
|
563
|
+
});
|
|
564
|
+
this.flat = computed$1(() => {
|
|
565
|
+
const ps = this.pages.value;
|
|
566
|
+
if (!this.itemsOf) return ps;
|
|
567
|
+
const out = [];
|
|
568
|
+
for (const p of ps) for (const item of this.itemsOf(p)) out.push(item);
|
|
569
|
+
return out;
|
|
570
|
+
});
|
|
571
|
+
this.hasNextPage = computed$1(() => {
|
|
572
|
+
const ps = this.pages.value;
|
|
573
|
+
if (ps.length === 0) return false;
|
|
574
|
+
return this.getNextPageParam(ps[ps.length - 1], ps) !== null;
|
|
575
|
+
});
|
|
576
|
+
this.hasPreviousPage = computed$1(() => {
|
|
577
|
+
const ps = this.pages.value;
|
|
578
|
+
if (ps.length === 0) return false;
|
|
579
|
+
const fn = this.getPreviousPageParam;
|
|
580
|
+
if (!fn) return false;
|
|
581
|
+
return fn(ps[0], ps) !== null;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
/** Initial / refetch — drops all pages and fetches starting from initialPageParam. */
|
|
585
|
+
startFetch() {
|
|
586
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
|
|
587
|
+
const myId = ++this.currentFetchId;
|
|
588
|
+
this.currentAbort?.abort();
|
|
589
|
+
const abort = new AbortController();
|
|
590
|
+
this.currentAbort = abort;
|
|
591
|
+
const previouslyHadPages = this.pages.peek().length > 0;
|
|
592
|
+
batch$1(() => {
|
|
593
|
+
this.status.set("pending");
|
|
594
|
+
this.isFetching.set(true);
|
|
595
|
+
this.isLoading.set(!previouslyHadPages);
|
|
596
|
+
});
|
|
597
|
+
return this.runFetch(myId, abort.signal, this.initialPageParam, (page, param) => {
|
|
598
|
+
if (myId !== this.currentFetchId || this.disposed) return;
|
|
599
|
+
batch$1(() => {
|
|
600
|
+
this.pages.set([page]);
|
|
601
|
+
this.pageParams.set([param]);
|
|
602
|
+
this.error.set(void 0);
|
|
603
|
+
this.status.set("success");
|
|
604
|
+
this.isLoading.set(false);
|
|
605
|
+
this.isFetching.set(false);
|
|
606
|
+
this.lastUpdatedAt.set(Date.now());
|
|
607
|
+
this.isStale.set(this.staleTime === 0);
|
|
608
|
+
});
|
|
609
|
+
if (this.staleTime > 0) this.scheduleStaleness();
|
|
610
|
+
}, "initial");
|
|
611
|
+
}
|
|
612
|
+
fetchNextPage() {
|
|
613
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
|
|
614
|
+
if (this.isFetchingNextPage.peek()) return Promise.resolve();
|
|
615
|
+
const ps = this.pages.peek();
|
|
616
|
+
if (ps.length === 0) return this.startFetch().then(() => {});
|
|
617
|
+
const nextParam = this.getNextPageParam(ps[ps.length - 1], ps);
|
|
618
|
+
if (nextParam === null) return Promise.resolve();
|
|
619
|
+
const myId = ++this.currentFetchId;
|
|
620
|
+
const abort = new AbortController();
|
|
621
|
+
this.currentAbort?.abort();
|
|
622
|
+
this.currentAbort = abort;
|
|
623
|
+
batch$1(() => {
|
|
624
|
+
this.isFetchingNextPage.set(true);
|
|
625
|
+
this.isFetching.set(true);
|
|
626
|
+
});
|
|
627
|
+
return this.runFetch(myId, abort.signal, nextParam, (page, param) => {
|
|
628
|
+
if (myId !== this.currentFetchId || this.disposed) return;
|
|
629
|
+
batch$1(() => {
|
|
630
|
+
this.pages.set([...this.pages.peek(), page]);
|
|
631
|
+
this.pageParams.set([...this.pageParams.peek(), param]);
|
|
632
|
+
this.isFetchingNextPage.set(false);
|
|
633
|
+
this.isFetching.set(false);
|
|
634
|
+
this.lastUpdatedAt.set(Date.now());
|
|
635
|
+
});
|
|
636
|
+
}, "next").then(() => {});
|
|
637
|
+
}
|
|
638
|
+
fetchPreviousPage() {
|
|
639
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
|
|
640
|
+
if (this.isFetchingPreviousPage.peek()) return Promise.resolve();
|
|
641
|
+
if (!this.getPreviousPageParam) return Promise.resolve();
|
|
642
|
+
const ps = this.pages.peek();
|
|
643
|
+
if (ps.length === 0) return this.startFetch().then(() => {});
|
|
644
|
+
const prevParam = this.getPreviousPageParam(ps[0], ps);
|
|
645
|
+
if (prevParam === null) return Promise.resolve();
|
|
646
|
+
const myId = ++this.currentFetchId;
|
|
647
|
+
const abort = new AbortController();
|
|
648
|
+
this.currentAbort?.abort();
|
|
649
|
+
this.currentAbort = abort;
|
|
650
|
+
batch$1(() => {
|
|
651
|
+
this.isFetchingPreviousPage.set(true);
|
|
652
|
+
this.isFetching.set(true);
|
|
653
|
+
});
|
|
654
|
+
return this.runFetch(myId, abort.signal, prevParam, (page, param) => {
|
|
655
|
+
if (myId !== this.currentFetchId || this.disposed) return;
|
|
656
|
+
batch$1(() => {
|
|
657
|
+
this.pages.set([page, ...this.pages.peek()]);
|
|
658
|
+
this.pageParams.set([param, ...this.pageParams.peek()]);
|
|
659
|
+
this.isFetchingPreviousPage.set(false);
|
|
660
|
+
this.isFetching.set(false);
|
|
661
|
+
this.lastUpdatedAt.set(Date.now());
|
|
662
|
+
});
|
|
663
|
+
}, "prev").then(() => {});
|
|
664
|
+
}
|
|
665
|
+
async runFetch(myId, signal, pageParam, onSuccess, direction) {
|
|
666
|
+
let attempt = 0;
|
|
667
|
+
while (true) {
|
|
668
|
+
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
669
|
+
try {
|
|
670
|
+
const page = await this.fetcher({
|
|
671
|
+
pageParam,
|
|
672
|
+
signal
|
|
673
|
+
});
|
|
674
|
+
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
675
|
+
onSuccess(page, pageParam);
|
|
676
|
+
return page;
|
|
677
|
+
} catch (err) {
|
|
678
|
+
if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
|
|
679
|
+
if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
|
|
680
|
+
batch$1(() => {
|
|
681
|
+
this.error.set(err);
|
|
682
|
+
this.status.set("error");
|
|
683
|
+
this.isLoading.set(false);
|
|
684
|
+
this.isFetching.set(false);
|
|
685
|
+
if (direction === "next") this.isFetchingNextPage.set(false);
|
|
686
|
+
if (direction === "prev") this.isFetchingPreviousPage.set(false);
|
|
687
|
+
});
|
|
688
|
+
throw err;
|
|
689
|
+
}
|
|
690
|
+
await abortableSleep$1(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
|
|
691
|
+
attempt += 1;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
refetch() {
|
|
696
|
+
return this.startFetch();
|
|
697
|
+
}
|
|
698
|
+
invalidate() {
|
|
699
|
+
if (this.staleTimer != null) {
|
|
700
|
+
clearTimeout(this.staleTimer);
|
|
701
|
+
this.staleTimer = null;
|
|
702
|
+
}
|
|
703
|
+
this.isStale.set(true);
|
|
704
|
+
return this.startFetch();
|
|
705
|
+
}
|
|
706
|
+
reset() {
|
|
707
|
+
if (this.disposed) return;
|
|
708
|
+
batch$1(() => {
|
|
709
|
+
this.error.set(void 0);
|
|
710
|
+
this.status.set(this.pages.peek().length > 0 ? "success" : "idle");
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
setData(updater) {
|
|
714
|
+
if (this.disposed) return {
|
|
715
|
+
rollback: () => {},
|
|
716
|
+
finalize: () => {}
|
|
717
|
+
};
|
|
718
|
+
const prev = this.pages.peek();
|
|
719
|
+
const next = updater(prev.length === 0 ? void 0 : prev);
|
|
720
|
+
const id = this.nextSnapshotId++;
|
|
721
|
+
const record = {
|
|
722
|
+
id,
|
|
723
|
+
prev,
|
|
724
|
+
live: true
|
|
725
|
+
};
|
|
726
|
+
this.snapshots.push(record);
|
|
727
|
+
batch$1(() => {
|
|
728
|
+
this.pages.set(next);
|
|
729
|
+
if (this.status.peek() === "idle" || this.status.peek() === "pending") this.status.set("success");
|
|
730
|
+
this.lastUpdatedAt.set(Date.now());
|
|
731
|
+
this.hasPendingMutations.set(true);
|
|
732
|
+
});
|
|
733
|
+
return {
|
|
734
|
+
rollback: () => {
|
|
735
|
+
if (!record.live || this.disposed) return;
|
|
736
|
+
record.live = false;
|
|
737
|
+
batch$1(() => {
|
|
738
|
+
this.pages.set(record.prev);
|
|
739
|
+
this.snapshots = this.snapshots.filter((s) => s.id !== id);
|
|
740
|
+
this.hasPendingMutations.set(this.snapshots.some((s) => s.live));
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
finalize: () => {
|
|
744
|
+
if (!record.live || this.disposed) return;
|
|
745
|
+
record.live = false;
|
|
746
|
+
this.snapshots = this.snapshots.filter((s) => s.id !== id);
|
|
747
|
+
if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
firstValue() {
|
|
752
|
+
if (this.status.peek() === "success") return Promise.resolve(this.pages.peek());
|
|
753
|
+
if (this.status.peek() === "error") return Promise.reject(this.error.peek());
|
|
754
|
+
return new Promise((resolve, reject) => {
|
|
755
|
+
const unsub = this.status.subscribe((s) => {
|
|
756
|
+
if (s === "success") {
|
|
757
|
+
unsub();
|
|
758
|
+
resolve(this.pages.peek());
|
|
759
|
+
} else if (s === "error") {
|
|
760
|
+
unsub();
|
|
761
|
+
reject(this.error.peek());
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
isStaleNow() {
|
|
767
|
+
const last = this.lastUpdatedAt.peek();
|
|
768
|
+
if (last === void 0) return true;
|
|
769
|
+
return Date.now() - last >= this.staleTime;
|
|
770
|
+
}
|
|
771
|
+
scheduleStaleness() {
|
|
772
|
+
if (this.staleTimer != null) clearTimeout(this.staleTimer);
|
|
773
|
+
if (this.staleTime > 0) this.staleTimer = setTimeout(() => {
|
|
774
|
+
this.staleTimer = null;
|
|
775
|
+
if (!this.disposed) this.isStale.set(true);
|
|
776
|
+
}, this.staleTime);
|
|
777
|
+
}
|
|
778
|
+
dispose() {
|
|
779
|
+
if (this.disposed) return;
|
|
780
|
+
this.disposed = true;
|
|
781
|
+
if (this.staleTimer != null) {
|
|
782
|
+
clearTimeout(this.staleTimer);
|
|
783
|
+
this.staleTimer = null;
|
|
784
|
+
}
|
|
785
|
+
this.currentAbort?.abort();
|
|
786
|
+
this.currentAbort = null;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
function abortableSleep$1(ms, signal) {
|
|
790
|
+
return new Promise((resolve, reject) => {
|
|
791
|
+
if (signal.aborted) {
|
|
792
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const timer = setTimeout(() => {
|
|
796
|
+
signal.removeEventListener("abort", onAbort);
|
|
797
|
+
resolve();
|
|
798
|
+
}, ms);
|
|
799
|
+
const onAbort = () => {
|
|
800
|
+
clearTimeout(timer);
|
|
801
|
+
signal.removeEventListener("abort", onAbort);
|
|
802
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
803
|
+
};
|
|
804
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
//#endregion
|
|
808
|
+
//#region src/query/keys.ts
|
|
809
|
+
/**
|
|
810
|
+
* Stable string hash of a key tuple. Two equal-by-content args produce the
|
|
811
|
+
* same string regardless of property iteration order. Handles primitives,
|
|
812
|
+
* arrays, plain objects, Date.
|
|
813
|
+
*
|
|
814
|
+
* Functions and symbols throw — keys must be serializable so distinct
|
|
815
|
+
* subscribers can share entries.
|
|
816
|
+
*/
|
|
817
|
+
function stableHash(args) {
|
|
818
|
+
return JSON.stringify(args, replacer);
|
|
819
|
+
}
|
|
820
|
+
const replacer = (_key, value) => {
|
|
821
|
+
if (typeof value === "function") throw new Error("[olas] query keys cannot contain functions");
|
|
822
|
+
if (typeof value === "symbol") throw new Error("[olas] query keys cannot contain symbols");
|
|
823
|
+
if (value === void 0) return "__undefined__";
|
|
824
|
+
if (value instanceof Date) return { __date: value.toISOString() };
|
|
825
|
+
if (value instanceof Map || value instanceof Set) throw new Error("[olas] query keys cannot contain Map/Set — use arrays/objects");
|
|
826
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
827
|
+
const sorted = {};
|
|
828
|
+
for (const k of Object.keys(value).sort()) sorted[k] = value[k];
|
|
829
|
+
return sorted;
|
|
830
|
+
}
|
|
831
|
+
return value;
|
|
832
|
+
};
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/query/plugin.ts
|
|
835
|
+
const queryRegistry = /* @__PURE__ */ new Map();
|
|
836
|
+
/**
|
|
837
|
+
* Register a query by its `queryId`. Internal — called from `defineQuery` /
|
|
838
|
+
* `defineInfiniteQuery`. Replaces any previous registration with the same
|
|
839
|
+
* id (matches Olas's "full root rebuild" HMR story; a mid-flight remote
|
|
840
|
+
* message routed against the old `Query` simply misses).
|
|
841
|
+
*/
|
|
842
|
+
function registerQueryById(queryId, query) {
|
|
843
|
+
queryRegistry.set(queryId, query);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Look up a query by its declared `queryId`. Returns `undefined` when no
|
|
847
|
+
* query with that id has been defined yet (e.g. the module isn't imported
|
|
848
|
+
* in the receiving tab).
|
|
849
|
+
*/
|
|
850
|
+
function lookupRegisteredQuery(queryId) {
|
|
851
|
+
return queryRegistry.get(queryId);
|
|
852
|
+
}
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/query/client.ts
|
|
855
|
+
const DEFAULT_GC_TIME = 5 * 6e4;
|
|
856
|
+
var ClientEntry = class {
|
|
857
|
+
entry;
|
|
858
|
+
/** The result of `spec.key(...args)` — used for hashing/identity. */
|
|
859
|
+
keyArgs;
|
|
860
|
+
/** The original args the consumer passed — what the fetcher receives. */
|
|
861
|
+
callArgs;
|
|
862
|
+
client;
|
|
863
|
+
query;
|
|
864
|
+
subscriberCount = 0;
|
|
865
|
+
gcTimer = null;
|
|
866
|
+
intervalTimer = null;
|
|
867
|
+
unsubFocus = null;
|
|
868
|
+
unsubOnline = null;
|
|
869
|
+
gcTime;
|
|
870
|
+
refetchInterval;
|
|
871
|
+
refetchOnWindowFocus;
|
|
872
|
+
refetchOnReconnect;
|
|
873
|
+
constructor(client, query, callArgs, keyArgs, spec, hydrated) {
|
|
874
|
+
this.client = client;
|
|
875
|
+
this.query = query;
|
|
876
|
+
this.callArgs = callArgs;
|
|
877
|
+
this.keyArgs = keyArgs;
|
|
878
|
+
this.gcTime = spec.gcTime ?? DEFAULT_GC_TIME;
|
|
879
|
+
this.refetchInterval = spec.refetchInterval;
|
|
880
|
+
this.refetchOnWindowFocus = spec.refetchOnWindowFocus ?? client.refetchOnWindowFocus;
|
|
881
|
+
this.refetchOnReconnect = spec.refetchOnReconnect ?? client.refetchOnReconnect;
|
|
882
|
+
const fetcherFn = spec.fetcher;
|
|
883
|
+
const deps = client.deps;
|
|
884
|
+
client.devtools;
|
|
885
|
+
this.keyArgs;
|
|
886
|
+
this.entry = new Entry({
|
|
887
|
+
fetcher: () => (signal) => fetcherFn({
|
|
888
|
+
signal,
|
|
889
|
+
deps
|
|
890
|
+
}, ...callArgs),
|
|
891
|
+
staleTime: spec.staleTime,
|
|
892
|
+
retry: spec.retry,
|
|
893
|
+
retryDelay: spec.retryDelay,
|
|
894
|
+
initialData: hydrated?.data,
|
|
895
|
+
initialUpdatedAt: hydrated?.lastUpdatedAt,
|
|
896
|
+
events: void 0
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
acquire() {
|
|
900
|
+
this.subscriberCount += 1;
|
|
901
|
+
if (this.gcTimer != null) {
|
|
902
|
+
clearTimeout(this.gcTimer);
|
|
903
|
+
this.gcTimer = null;
|
|
904
|
+
}
|
|
905
|
+
if (this.subscriberCount === 1) {
|
|
906
|
+
if (this.refetchInterval != null) this.startIntervalTimer();
|
|
907
|
+
if (this.refetchOnWindowFocus) this.unsubFocus = subscribeWindowFocus(() => this.triggerEventRefetch());
|
|
908
|
+
if (this.refetchOnReconnect) this.unsubOnline = subscribeReconnect(() => this.triggerEventRefetch());
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
release() {
|
|
912
|
+
this.subscriberCount -= 1;
|
|
913
|
+
if (this.subscriberCount <= 0) {
|
|
914
|
+
this.stopIntervalTimer();
|
|
915
|
+
this.stopEventSubscriptions();
|
|
916
|
+
if (this.gcTime === 0) this.client.dropEntry(this);
|
|
917
|
+
else this.gcTimer = setTimeout(() => {
|
|
918
|
+
this.gcTimer = null;
|
|
919
|
+
this.client.dropEntry(this);
|
|
920
|
+
}, this.gcTime);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
hasSubscribers() {
|
|
924
|
+
return this.subscriberCount > 0;
|
|
925
|
+
}
|
|
926
|
+
startIntervalTimer() {
|
|
927
|
+
if (this.refetchInterval == null) return;
|
|
928
|
+
if (this.intervalTimer != null) return;
|
|
929
|
+
this.intervalTimer = setInterval(() => {
|
|
930
|
+
this.entry.startFetch().catch(() => {});
|
|
931
|
+
}, this.refetchInterval);
|
|
932
|
+
}
|
|
933
|
+
stopIntervalTimer() {
|
|
934
|
+
if (this.intervalTimer != null) {
|
|
935
|
+
clearInterval(this.intervalTimer);
|
|
936
|
+
this.intervalTimer = null;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
stopEventSubscriptions() {
|
|
940
|
+
if (this.unsubFocus != null) {
|
|
941
|
+
this.unsubFocus();
|
|
942
|
+
this.unsubFocus = null;
|
|
943
|
+
}
|
|
944
|
+
if (this.unsubOnline != null) {
|
|
945
|
+
this.unsubOnline();
|
|
946
|
+
this.unsubOnline = null;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Schedule a gc timer for an entry that was just created via a non-subscribing
|
|
951
|
+
* path (`prefetch`, `setData`, `invalidate`). Without this, those entries
|
|
952
|
+
* never trigger `release()` and would live until root dispose. Called by
|
|
953
|
+
* `QueryClient.bindEntry` right after creating a fresh entry; `acquire()`
|
|
954
|
+
* (e.g., from a subscriber that arrives shortly after a prefetch) clears it.
|
|
955
|
+
* No-op if the entry already has subscribers or a gc timer pending.
|
|
956
|
+
*/
|
|
957
|
+
scheduleGcIfOrphan() {
|
|
958
|
+
if (this.subscriberCount > 0 || this.gcTimer != null) return;
|
|
959
|
+
if (this.gcTime === 0) {
|
|
960
|
+
queueMicrotask(() => {
|
|
961
|
+
if (this.subscriberCount === 0 && this.gcTimer == null) this.client.dropEntry(this);
|
|
962
|
+
});
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
this.gcTimer = setTimeout(() => {
|
|
966
|
+
this.gcTimer = null;
|
|
967
|
+
this.client.dropEntry(this);
|
|
968
|
+
}, this.gcTime);
|
|
969
|
+
}
|
|
970
|
+
/** Refetch on focus / reconnect, but only if the data is actually stale. */
|
|
971
|
+
triggerEventRefetch() {
|
|
972
|
+
if (!this.entry.isStaleNow()) return;
|
|
973
|
+
this.entry.startFetch().catch(() => {});
|
|
974
|
+
}
|
|
975
|
+
dispose() {
|
|
976
|
+
if (this.gcTimer != null) {
|
|
977
|
+
clearTimeout(this.gcTimer);
|
|
978
|
+
this.gcTimer = null;
|
|
979
|
+
}
|
|
980
|
+
this.stopIntervalTimer();
|
|
981
|
+
this.stopEventSubscriptions();
|
|
982
|
+
this.entry.dispose();
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
var InfiniteClientEntry = class {
|
|
986
|
+
entry;
|
|
987
|
+
keyArgs;
|
|
988
|
+
callArgs;
|
|
989
|
+
client;
|
|
990
|
+
query;
|
|
991
|
+
subscriberCount = 0;
|
|
992
|
+
gcTimer = null;
|
|
993
|
+
intervalTimer = null;
|
|
994
|
+
gcTime;
|
|
995
|
+
refetchInterval;
|
|
996
|
+
constructor(client, query, callArgs, keyArgs, spec) {
|
|
997
|
+
this.client = client;
|
|
998
|
+
this.query = query;
|
|
999
|
+
this.callArgs = callArgs;
|
|
1000
|
+
this.keyArgs = keyArgs;
|
|
1001
|
+
this.gcTime = spec.gcTime ?? DEFAULT_GC_TIME;
|
|
1002
|
+
this.refetchInterval = spec.refetchInterval;
|
|
1003
|
+
const fetcherFn = spec.fetcher;
|
|
1004
|
+
const deps = client.deps;
|
|
1005
|
+
this.entry = new InfiniteEntry({
|
|
1006
|
+
fetcher: ({ pageParam, signal }) => fetcherFn({
|
|
1007
|
+
pageParam,
|
|
1008
|
+
signal,
|
|
1009
|
+
deps
|
|
1010
|
+
}, ...callArgs),
|
|
1011
|
+
initialPageParam: spec.initialPageParam,
|
|
1012
|
+
getNextPageParam: spec.getNextPageParam,
|
|
1013
|
+
getPreviousPageParam: spec.getPreviousPageParam,
|
|
1014
|
+
itemsOf: spec.itemsOf,
|
|
1015
|
+
staleTime: spec.staleTime,
|
|
1016
|
+
retry: spec.retry,
|
|
1017
|
+
retryDelay: spec.retryDelay
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
acquire() {
|
|
1021
|
+
this.subscriberCount += 1;
|
|
1022
|
+
if (this.gcTimer != null) {
|
|
1023
|
+
clearTimeout(this.gcTimer);
|
|
1024
|
+
this.gcTimer = null;
|
|
1025
|
+
}
|
|
1026
|
+
if (this.subscriberCount === 1 && this.refetchInterval != null) this.startIntervalTimer();
|
|
1027
|
+
}
|
|
1028
|
+
release() {
|
|
1029
|
+
this.subscriberCount -= 1;
|
|
1030
|
+
if (this.subscriberCount <= 0) {
|
|
1031
|
+
this.stopIntervalTimer();
|
|
1032
|
+
if (this.gcTime === 0) this.client.dropInfiniteEntry(this);
|
|
1033
|
+
else this.gcTimer = setTimeout(() => {
|
|
1034
|
+
this.gcTimer = null;
|
|
1035
|
+
this.client.dropInfiniteEntry(this);
|
|
1036
|
+
}, this.gcTime);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
startIntervalTimer() {
|
|
1040
|
+
if (this.refetchInterval == null || this.intervalTimer != null) return;
|
|
1041
|
+
this.intervalTimer = setInterval(() => {
|
|
1042
|
+
this.entry.startFetch().catch(() => {});
|
|
1043
|
+
}, this.refetchInterval);
|
|
1044
|
+
}
|
|
1045
|
+
stopIntervalTimer() {
|
|
1046
|
+
if (this.intervalTimer != null) {
|
|
1047
|
+
clearInterval(this.intervalTimer);
|
|
1048
|
+
this.intervalTimer = null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/** See `ClientEntry.scheduleGcIfOrphan`. */
|
|
1052
|
+
scheduleGcIfOrphan() {
|
|
1053
|
+
if (this.subscriberCount > 0 || this.gcTimer != null) return;
|
|
1054
|
+
if (this.gcTime === 0) {
|
|
1055
|
+
queueMicrotask(() => {
|
|
1056
|
+
if (this.subscriberCount === 0 && this.gcTimer == null) this.client.dropInfiniteEntry(this);
|
|
1057
|
+
});
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
this.gcTimer = setTimeout(() => {
|
|
1061
|
+
this.gcTimer = null;
|
|
1062
|
+
this.client.dropInfiniteEntry(this);
|
|
1063
|
+
}, this.gcTime);
|
|
1064
|
+
}
|
|
1065
|
+
dispose() {
|
|
1066
|
+
if (this.gcTimer != null) {
|
|
1067
|
+
clearTimeout(this.gcTimer);
|
|
1068
|
+
this.gcTimer = null;
|
|
1069
|
+
}
|
|
1070
|
+
this.stopIntervalTimer();
|
|
1071
|
+
this.entry.dispose();
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
/**
|
|
1075
|
+
* Per-root entry registry. Owns the keyed `Map<hash, ClientEntry>` per query,
|
|
1076
|
+
* GC timers, refetch-interval timers. Subscribers are routed in/out via
|
|
1077
|
+
* `acquire` / `release`.
|
|
1078
|
+
*/
|
|
1079
|
+
var QueryClient = class {
|
|
1080
|
+
maps = /* @__PURE__ */ new Map();
|
|
1081
|
+
infiniteMaps = /* @__PURE__ */ new Map();
|
|
1082
|
+
touchedQueries = /* @__PURE__ */ new Set();
|
|
1083
|
+
touchedInfiniteQueries = /* @__PURE__ */ new Set();
|
|
1084
|
+
hydratedData = /* @__PURE__ */ new Map();
|
|
1085
|
+
/** Mutations inflight across the whole root — used by `waitForIdle`. */
|
|
1086
|
+
mutationsInflight$ = signal$1(0);
|
|
1087
|
+
onError;
|
|
1088
|
+
disposed = false;
|
|
1089
|
+
/** Devtools bus, if any — passed by `createRoot`. Used to emit cache events. */
|
|
1090
|
+
devtools;
|
|
1091
|
+
/** Root-level deps; passed to every `QuerySpec.fetcher` via `FetchCtx`. */
|
|
1092
|
+
deps;
|
|
1093
|
+
/** Root-wide defaults for refetch triggers; per-query spec overrides win. Spec §5.9. */
|
|
1094
|
+
refetchOnWindowFocus;
|
|
1095
|
+
refetchOnReconnect;
|
|
1096
|
+
/**
|
|
1097
|
+
* Installed plugins. Fired on every `setData` / `invalidate` / `gc` so
|
|
1098
|
+
* cross-tab / persistence-like layers can observe and react. SPEC §13.2.
|
|
1099
|
+
*/
|
|
1100
|
+
plugins;
|
|
1101
|
+
/**
|
|
1102
|
+
* Flipped to `true` while a remote-originated write (via
|
|
1103
|
+
* `applyRemoteSetData` / `applyRemoteInvalidate`) is being applied. The
|
|
1104
|
+
* resulting plugin events carry `isRemote: true` so plugins know to skip
|
|
1105
|
+
* rebroadcast.
|
|
1106
|
+
*/
|
|
1107
|
+
applyingRemote = false;
|
|
1108
|
+
constructor(opts) {
|
|
1109
|
+
this.onError = opts?.onError;
|
|
1110
|
+
this.devtools = opts?.devtools;
|
|
1111
|
+
this.deps = opts?.deps ?? {};
|
|
1112
|
+
this.refetchOnWindowFocus = opts?.refetchOnWindowFocus ?? false;
|
|
1113
|
+
this.refetchOnReconnect = opts?.refetchOnReconnect ?? false;
|
|
1114
|
+
this.plugins = opts?.plugins ?? [];
|
|
1115
|
+
if (opts?.hydrate) this.hydrate(opts.hydrate);
|
|
1116
|
+
const api = this.makePluginApi();
|
|
1117
|
+
for (const plugin of this.plugins) this.callPlugin(() => plugin.init?.(api));
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Build the `QueryClientPluginApi` view that plugins receive at `init`
|
|
1121
|
+
* time. Closes over `this`; safe to hand out — plugins call back through
|
|
1122
|
+
* these methods to push remote-originated writes into the local cache.
|
|
1123
|
+
*/
|
|
1124
|
+
makePluginApi() {
|
|
1125
|
+
const self = this;
|
|
1126
|
+
return {
|
|
1127
|
+
applyRemoteSetData(queryId, keyArgs, data) {
|
|
1128
|
+
self.applyRemoteSetData(queryId, keyArgs, data);
|
|
1129
|
+
},
|
|
1130
|
+
applyRemoteInvalidate(queryId, keyArgs) {
|
|
1131
|
+
self.applyRemoteInvalidate(queryId, keyArgs);
|
|
1132
|
+
},
|
|
1133
|
+
subscribedKeys(queryId) {
|
|
1134
|
+
return self.subscribedKeysFor(queryId);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
/** Invoke a plugin callback; route exceptions through `onError`. */
|
|
1139
|
+
callPlugin(fn) {
|
|
1140
|
+
try {
|
|
1141
|
+
fn();
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
dispatchError(this.onError, err, {
|
|
1144
|
+
kind: "plugin",
|
|
1145
|
+
controllerPath: []
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
emitSetData(query, keyArgs, data, kind) {
|
|
1150
|
+
if (this.plugins.length === 0) return;
|
|
1151
|
+
const queryId = query.__spec.queryId;
|
|
1152
|
+
if (queryId == null) return;
|
|
1153
|
+
const event = {
|
|
1154
|
+
queryId,
|
|
1155
|
+
keyArgs,
|
|
1156
|
+
data,
|
|
1157
|
+
kind,
|
|
1158
|
+
isRemote: this.applyingRemote
|
|
1159
|
+
};
|
|
1160
|
+
for (const plugin of this.plugins) if (plugin.onSetData) {
|
|
1161
|
+
const cb = plugin.onSetData;
|
|
1162
|
+
this.callPlugin(() => cb.call(plugin, event));
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
emitInvalidate(query, keyArgs, kind) {
|
|
1166
|
+
if (this.plugins.length === 0) return;
|
|
1167
|
+
const queryId = query.__spec.queryId;
|
|
1168
|
+
if (queryId == null) return;
|
|
1169
|
+
const event = {
|
|
1170
|
+
queryId,
|
|
1171
|
+
keyArgs,
|
|
1172
|
+
kind,
|
|
1173
|
+
isRemote: this.applyingRemote
|
|
1174
|
+
};
|
|
1175
|
+
for (const plugin of this.plugins) if (plugin.onInvalidate) {
|
|
1176
|
+
const cb = plugin.onInvalidate;
|
|
1177
|
+
this.callPlugin(() => cb.call(plugin, event));
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
emitGc(query, keyArgs, kind) {
|
|
1181
|
+
if (this.plugins.length === 0) return;
|
|
1182
|
+
const queryId = query.__spec.queryId;
|
|
1183
|
+
if (queryId == null) return;
|
|
1184
|
+
const event = {
|
|
1185
|
+
queryId,
|
|
1186
|
+
keyArgs,
|
|
1187
|
+
kind
|
|
1188
|
+
};
|
|
1189
|
+
for (const plugin of this.plugins) if (plugin.onGc) {
|
|
1190
|
+
const cb = plugin.onGc;
|
|
1191
|
+
this.callPlugin(() => cb.call(plugin, event));
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
/** Resolve `queryId → live entry-map keys`. Empty array when unknown. */
|
|
1195
|
+
subscribedKeysFor(queryId) {
|
|
1196
|
+
const query = lookupRegisteredQuery(queryId);
|
|
1197
|
+
if (!query) return [];
|
|
1198
|
+
const out = [];
|
|
1199
|
+
if (query.__olas === "query") {
|
|
1200
|
+
const map = this.maps.get(query);
|
|
1201
|
+
if (map) for (const ce of map.values()) out.push(ce.keyArgs);
|
|
1202
|
+
} else {
|
|
1203
|
+
const map = this.infiniteMaps.get(query);
|
|
1204
|
+
if (map) for (const ce of map.values()) out.push(ce.keyArgs);
|
|
1205
|
+
}
|
|
1206
|
+
return out;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Apply a remote-originated `setData` for the query identified by
|
|
1210
|
+
* `queryId`, scoped to the entry already keyed by `keyArgs` in this
|
|
1211
|
+
* client. Goes through the underlying `Entry.setData` so subscribers see
|
|
1212
|
+
* the write; plugin `onSetData` fires with `isRemote: true`.
|
|
1213
|
+
*
|
|
1214
|
+
* Drops silently when:
|
|
1215
|
+
* - No query with that id is registered (the receiving tab hasn't
|
|
1216
|
+
* imported the module that defined it).
|
|
1217
|
+
* - The registered query is an infinite query (cross-tab infinite sync
|
|
1218
|
+
* is deferred — see `plugin.ts` `SetDataEvent.kind`).
|
|
1219
|
+
* - No local entry exists for that key (the receiving tab isn't
|
|
1220
|
+
* subscribed; nothing useful to write to without callArgs for a
|
|
1221
|
+
* future refetch).
|
|
1222
|
+
*/
|
|
1223
|
+
applyRemoteSetData(queryId, keyArgs, data) {
|
|
1224
|
+
const query = lookupRegisteredQuery(queryId);
|
|
1225
|
+
if (!query) return;
|
|
1226
|
+
if (query.__olas !== "query") return;
|
|
1227
|
+
const internal = query;
|
|
1228
|
+
const map = this.maps.get(internal);
|
|
1229
|
+
if (!map) return;
|
|
1230
|
+
const hash = stableHash(keyArgs);
|
|
1231
|
+
const entry = map.get(hash);
|
|
1232
|
+
if (!entry) return;
|
|
1233
|
+
this.applyingRemote = true;
|
|
1234
|
+
try {
|
|
1235
|
+
entry.entry.setData(() => data);
|
|
1236
|
+
this.emitSetData(internal, entry.keyArgs, data, "data");
|
|
1237
|
+
} finally {
|
|
1238
|
+
this.applyingRemote = false;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
applyRemoteInvalidate(queryId, keyArgs) {
|
|
1242
|
+
const query = lookupRegisteredQuery(queryId);
|
|
1243
|
+
if (!query) return;
|
|
1244
|
+
if (query.__olas !== "query") return;
|
|
1245
|
+
const internal = query;
|
|
1246
|
+
const map = this.maps.get(internal);
|
|
1247
|
+
if (!map) return;
|
|
1248
|
+
const hash = stableHash(keyArgs);
|
|
1249
|
+
const entry = map.get(hash);
|
|
1250
|
+
if (!entry) return;
|
|
1251
|
+
this.applyingRemote = true;
|
|
1252
|
+
try {
|
|
1253
|
+
entry.entry.invalidate().catch((err) => {
|
|
1254
|
+
dispatchError(this.onError, err, {
|
|
1255
|
+
kind: "cache",
|
|
1256
|
+
controllerPath: [],
|
|
1257
|
+
queryKey: entry.keyArgs
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
this.emitInvalidate(internal, entry.keyArgs, "data");
|
|
1261
|
+
} finally {
|
|
1262
|
+
this.applyingRemote = false;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
hydrate(state) {
|
|
1266
|
+
if (state.version !== 1) return;
|
|
1267
|
+
for (const entry of state.entries) {
|
|
1268
|
+
const hash = stableHash(entry.key);
|
|
1269
|
+
this.hydratedData.set(hash, {
|
|
1270
|
+
data: entry.data,
|
|
1271
|
+
lastUpdatedAt: entry.lastUpdatedAt
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Snapshot every live cache entry (regular + infinite) as a flat list of
|
|
1277
|
+
* `DebugCacheEntry`. Exposed via `root.__debug.queryEntries()` for the
|
|
1278
|
+
* devtools cache inspector — shows current data and state, not past
|
|
1279
|
+
* fetch events. Spec §20.9.
|
|
1280
|
+
*/
|
|
1281
|
+
queryEntriesSnapshot() {
|
|
1282
|
+
const out = [];
|
|
1283
|
+
for (const map of this.maps.values()) for (const ce of map.values()) out.push({
|
|
1284
|
+
key: ce.keyArgs,
|
|
1285
|
+
status: ce.entry.status.peek(),
|
|
1286
|
+
data: ce.entry.data.peek(),
|
|
1287
|
+
error: ce.entry.error.peek(),
|
|
1288
|
+
lastUpdatedAt: ce.entry.lastUpdatedAt.peek(),
|
|
1289
|
+
isStale: ce.entry.isStale.peek(),
|
|
1290
|
+
isFetching: ce.entry.isFetching.peek(),
|
|
1291
|
+
hasPendingMutations: ce.entry.hasPendingMutations.peek()
|
|
1292
|
+
});
|
|
1293
|
+
for (const map of this.infiniteMaps.values()) for (const ce of map.values()) out.push({
|
|
1294
|
+
key: ce.keyArgs,
|
|
1295
|
+
status: ce.entry.status.peek(),
|
|
1296
|
+
data: ce.entry.pages.peek(),
|
|
1297
|
+
error: ce.entry.error.peek(),
|
|
1298
|
+
lastUpdatedAt: ce.entry.lastUpdatedAt.peek(),
|
|
1299
|
+
isStale: ce.entry.isStale.peek(),
|
|
1300
|
+
isFetching: ce.entry.isFetching.peek(),
|
|
1301
|
+
hasPendingMutations: ce.entry.hasPendingMutations.peek()
|
|
1302
|
+
});
|
|
1303
|
+
return out;
|
|
1304
|
+
}
|
|
1305
|
+
dehydrate() {
|
|
1306
|
+
const entries = [];
|
|
1307
|
+
for (const map of this.maps.values()) for (const ce of map.values()) if (ce.entry.status.peek() === "success") entries.push({
|
|
1308
|
+
key: ce.keyArgs,
|
|
1309
|
+
data: ce.entry.data.peek(),
|
|
1310
|
+
lastUpdatedAt: ce.entry.lastUpdatedAt.peek() ?? Date.now()
|
|
1311
|
+
});
|
|
1312
|
+
return {
|
|
1313
|
+
version: 1,
|
|
1314
|
+
entries
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
async waitForIdle() {
|
|
1318
|
+
for (let safety = 0; safety < 100; safety++) {
|
|
1319
|
+
const tasks = [];
|
|
1320
|
+
for (const map of this.maps.values()) for (const ce of map.values()) if (ce.entry.isFetching.peek()) tasks.push(waitUntilFalse(ce.entry.isFetching));
|
|
1321
|
+
for (const map of this.infiniteMaps.values()) for (const ce of map.values()) if (ce.entry.isFetching.peek()) tasks.push(waitUntilFalse(ce.entry.isFetching));
|
|
1322
|
+
if (this.mutationsInflight$.peek() > 0) tasks.push(new Promise((resolve) => {
|
|
1323
|
+
const unsub = this.mutationsInflight$.subscribe((v) => {
|
|
1324
|
+
if (v === 0) {
|
|
1325
|
+
unsub();
|
|
1326
|
+
resolve();
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
}));
|
|
1330
|
+
if (tasks.length === 0) return;
|
|
1331
|
+
await Promise.all(tasks);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
bindEntry(query, args) {
|
|
1335
|
+
const internal = query;
|
|
1336
|
+
let map = this.maps.get(internal);
|
|
1337
|
+
if (!map) {
|
|
1338
|
+
map = /* @__PURE__ */ new Map();
|
|
1339
|
+
this.maps.set(internal, map);
|
|
1340
|
+
this.touchedQueries.add(internal);
|
|
1341
|
+
internal.__clients.add(this);
|
|
1342
|
+
}
|
|
1343
|
+
const keyArgs = internal.__spec.key(...args);
|
|
1344
|
+
const hash = stableHash(keyArgs);
|
|
1345
|
+
let entry = map.get(hash);
|
|
1346
|
+
if (!entry) {
|
|
1347
|
+
const hydrated = this.hydratedData.get(hash);
|
|
1348
|
+
if (hydrated) this.hydratedData.delete(hash);
|
|
1349
|
+
entry = new ClientEntry(this, internal, args, keyArgs, internal.__spec, hydrated);
|
|
1350
|
+
map.set(hash, entry);
|
|
1351
|
+
entry.scheduleGcIfOrphan();
|
|
1352
|
+
}
|
|
1353
|
+
return entry;
|
|
1354
|
+
}
|
|
1355
|
+
dropEntry(entry) {
|
|
1356
|
+
const map = this.maps.get(entry.query);
|
|
1357
|
+
if (!map) return;
|
|
1358
|
+
const hash = stableHash(entry.keyArgs);
|
|
1359
|
+
if (map.get(hash) !== entry) return;
|
|
1360
|
+
map.delete(hash);
|
|
1361
|
+
entry.dispose();
|
|
1362
|
+
if (map.size === 0) this.maps.delete(entry.query);
|
|
1363
|
+
this.emitGc(entry.query, entry.keyArgs, "data");
|
|
1364
|
+
}
|
|
1365
|
+
invalidate(query, args) {
|
|
1366
|
+
const internal = query;
|
|
1367
|
+
const map = this.maps.get(internal);
|
|
1368
|
+
if (!map) return;
|
|
1369
|
+
const keyArgs = internal.__spec.key(...args);
|
|
1370
|
+
const hash = stableHash(keyArgs);
|
|
1371
|
+
const entry = map.get(hash);
|
|
1372
|
+
if (!entry) return;
|
|
1373
|
+
entry.entry.invalidate().catch((err) => {
|
|
1374
|
+
dispatchError(this.onError, err, {
|
|
1375
|
+
kind: "cache",
|
|
1376
|
+
controllerPath: [],
|
|
1377
|
+
queryKey: keyArgs
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
this.emitInvalidate(internal, keyArgs, "data");
|
|
1381
|
+
}
|
|
1382
|
+
invalidateAll(query) {
|
|
1383
|
+
const internal = query;
|
|
1384
|
+
const map = this.maps.get(internal);
|
|
1385
|
+
if (!map) return;
|
|
1386
|
+
for (const [hash, entry] of map) {
|
|
1387
|
+
entry.entry.invalidate().catch((err) => {
|
|
1388
|
+
dispatchError(this.onError, err, {
|
|
1389
|
+
kind: "cache",
|
|
1390
|
+
controllerPath: [],
|
|
1391
|
+
queryKey: entry.keyArgs
|
|
1392
|
+
});
|
|
1393
|
+
});
|
|
1394
|
+
this.emitInvalidate(internal, entry.keyArgs, "data");
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
setData(query, args, updater) {
|
|
1398
|
+
const entry = this.bindEntry(query, args);
|
|
1399
|
+
const snapshot = entry.entry.setData(updater);
|
|
1400
|
+
this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data");
|
|
1401
|
+
return snapshot;
|
|
1402
|
+
}
|
|
1403
|
+
bindInfiniteEntry(query, args) {
|
|
1404
|
+
const internal = query;
|
|
1405
|
+
let map = this.infiniteMaps.get(internal);
|
|
1406
|
+
if (!map) {
|
|
1407
|
+
map = /* @__PURE__ */ new Map();
|
|
1408
|
+
this.infiniteMaps.set(internal, map);
|
|
1409
|
+
this.touchedInfiniteQueries.add(internal);
|
|
1410
|
+
internal.__clients.add(this);
|
|
1411
|
+
}
|
|
1412
|
+
const keyArgs = internal.__spec.key(...args);
|
|
1413
|
+
const hash = stableHash(keyArgs);
|
|
1414
|
+
let entry = map.get(hash);
|
|
1415
|
+
if (!entry) {
|
|
1416
|
+
entry = new InfiniteClientEntry(this, internal, args, keyArgs, internal.__spec);
|
|
1417
|
+
map.set(hash, entry);
|
|
1418
|
+
entry.scheduleGcIfOrphan();
|
|
1419
|
+
}
|
|
1420
|
+
return entry;
|
|
1421
|
+
}
|
|
1422
|
+
dropInfiniteEntry(entry) {
|
|
1423
|
+
const map = this.infiniteMaps.get(entry.query);
|
|
1424
|
+
if (!map) return;
|
|
1425
|
+
const hash = stableHash(entry.keyArgs);
|
|
1426
|
+
if (map.get(hash) !== entry) return;
|
|
1427
|
+
map.delete(hash);
|
|
1428
|
+
entry.dispose();
|
|
1429
|
+
if (map.size === 0) this.infiniteMaps.delete(entry.query);
|
|
1430
|
+
this.emitGc(entry.query, entry.keyArgs, "infinite");
|
|
1431
|
+
}
|
|
1432
|
+
invalidateInfinite(query, args) {
|
|
1433
|
+
const internal = query;
|
|
1434
|
+
const map = this.infiniteMaps.get(internal);
|
|
1435
|
+
if (!map) return;
|
|
1436
|
+
const keyArgs = internal.__spec.key(...args);
|
|
1437
|
+
const hash = stableHash(keyArgs);
|
|
1438
|
+
const entry = map.get(hash);
|
|
1439
|
+
if (!entry) return;
|
|
1440
|
+
entry.entry.invalidate().catch((err) => {
|
|
1441
|
+
dispatchError(this.onError, err, {
|
|
1442
|
+
kind: "cache",
|
|
1443
|
+
controllerPath: [],
|
|
1444
|
+
queryKey: entry.keyArgs
|
|
1445
|
+
});
|
|
1446
|
+
});
|
|
1447
|
+
this.emitInvalidate(internal, keyArgs, "infinite");
|
|
1448
|
+
}
|
|
1449
|
+
invalidateAllInfinite(query) {
|
|
1450
|
+
const internal = query;
|
|
1451
|
+
const map = this.infiniteMaps.get(internal);
|
|
1452
|
+
if (!map) return;
|
|
1453
|
+
for (const entry of map.values()) {
|
|
1454
|
+
entry.entry.invalidate().catch((err) => {
|
|
1455
|
+
dispatchError(this.onError, err, {
|
|
1456
|
+
kind: "cache",
|
|
1457
|
+
controllerPath: [],
|
|
1458
|
+
queryKey: entry.keyArgs
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
1461
|
+
this.emitInvalidate(internal, entry.keyArgs, "infinite");
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
setInfiniteData(query, args, updater) {
|
|
1465
|
+
const entry = this.bindInfiniteEntry(query, args);
|
|
1466
|
+
const snapshot = entry.entry.setData(updater);
|
|
1467
|
+
this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite");
|
|
1468
|
+
return snapshot;
|
|
1469
|
+
}
|
|
1470
|
+
prefetchInfinite(query, args) {
|
|
1471
|
+
const entry = this.bindInfiniteEntry(query, args);
|
|
1472
|
+
entry.acquire();
|
|
1473
|
+
return (async () => {
|
|
1474
|
+
if (entry.entry.status.peek() === "success" && !entry.entry.isStaleNow()) return entry.entry.pages.peek()[0];
|
|
1475
|
+
return entry.entry.startFetch();
|
|
1476
|
+
})().finally(() => entry.release());
|
|
1477
|
+
}
|
|
1478
|
+
prefetch(query, args) {
|
|
1479
|
+
const entry = this.bindEntry(query, args);
|
|
1480
|
+
entry.acquire();
|
|
1481
|
+
return (async () => {
|
|
1482
|
+
if (entry.entry.status.peek() === "success" && !entry.entry.isStaleNow()) return entry.entry.data.peek();
|
|
1483
|
+
if (entry.entry.isFetching.peek()) return entry.entry.firstValue();
|
|
1484
|
+
return entry.entry.startFetch();
|
|
1485
|
+
})().finally(() => entry.release());
|
|
1486
|
+
}
|
|
1487
|
+
inflightCount() {
|
|
1488
|
+
let count = 0;
|
|
1489
|
+
for (const [, map] of this.maps) for (const [, entry] of map) if (entry.entry.isFetching.peek()) count++;
|
|
1490
|
+
return count;
|
|
1491
|
+
}
|
|
1492
|
+
dispose() {
|
|
1493
|
+
if (this.disposed) return;
|
|
1494
|
+
this.disposed = true;
|
|
1495
|
+
for (const map of this.maps.values()) for (const entry of map.values()) entry.dispose();
|
|
1496
|
+
this.maps.clear();
|
|
1497
|
+
for (const map of this.infiniteMaps.values()) for (const entry of map.values()) entry.dispose();
|
|
1498
|
+
this.infiniteMaps.clear();
|
|
1499
|
+
for (const q of this.touchedQueries) q.__clients.delete(this);
|
|
1500
|
+
this.touchedQueries.clear();
|
|
1501
|
+
for (const q of this.touchedInfiniteQueries) q.__clients.delete(this);
|
|
1502
|
+
this.touchedInfiniteQueries.clear();
|
|
1503
|
+
this.hydratedData.clear();
|
|
1504
|
+
for (const plugin of this.plugins) if (plugin.dispose) {
|
|
1505
|
+
const cb = plugin.dispose;
|
|
1506
|
+
this.callPlugin(() => cb.call(plugin));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
function waitUntilFalse(sig) {
|
|
1511
|
+
if (!sig.peek()) return Promise.resolve();
|
|
1512
|
+
return new Promise((resolve) => {
|
|
1513
|
+
const unsub = sig.subscribe((v) => {
|
|
1514
|
+
if (!v) {
|
|
1515
|
+
unsub();
|
|
1516
|
+
resolve();
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/emitter.ts
|
|
1523
|
+
var EmitterImpl = class {
|
|
1524
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1525
|
+
disposed = false;
|
|
1526
|
+
emit(value) {
|
|
1527
|
+
if (this.disposed) return;
|
|
1528
|
+
const snapshot = Array.from(this.handlers);
|
|
1529
|
+
for (const handler of snapshot) handler(value);
|
|
1530
|
+
}
|
|
1531
|
+
on(handler) {
|
|
1532
|
+
if (this.disposed) return () => {};
|
|
1533
|
+
const wrapped = handler;
|
|
1534
|
+
this.handlers.add(wrapped);
|
|
1535
|
+
return () => {
|
|
1536
|
+
this.handlers.delete(wrapped);
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
once(handler) {
|
|
1540
|
+
if (this.disposed) return () => {};
|
|
1541
|
+
const wrapped = (value) => {
|
|
1542
|
+
this.handlers.delete(wrapped);
|
|
1543
|
+
handler(value);
|
|
1544
|
+
};
|
|
1545
|
+
this.handlers.add(wrapped);
|
|
1546
|
+
return () => {
|
|
1547
|
+
this.handlers.delete(wrapped);
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
dispose() {
|
|
1551
|
+
if (this.disposed) return;
|
|
1552
|
+
this.disposed = true;
|
|
1553
|
+
this.handlers.clear();
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
/**
|
|
1557
|
+
* Create a standalone emitter. Handlers persist until explicitly unsubscribed
|
|
1558
|
+
* (or the emitter is disposed). Use this for emitters that live outside any
|
|
1559
|
+
* single controller — typically in deps. Use `ctx.emitter()` for emitters that
|
|
1560
|
+
* should auto-clean with a controller.
|
|
1561
|
+
*/
|
|
1562
|
+
function createEmitter() {
|
|
1563
|
+
const impl = new EmitterImpl();
|
|
1564
|
+
return {
|
|
1565
|
+
emit: ((value) => impl.emit(value)),
|
|
1566
|
+
on: (handler) => impl.on(handler),
|
|
1567
|
+
once: (handler) => impl.once(handler),
|
|
1568
|
+
dispose: () => impl.dispose()
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region src/forms/field.ts
|
|
1573
|
+
var FieldImpl = class {
|
|
1574
|
+
value$;
|
|
1575
|
+
errors$;
|
|
1576
|
+
touched$;
|
|
1577
|
+
dirty$;
|
|
1578
|
+
validating$;
|
|
1579
|
+
isValid$;
|
|
1580
|
+
revalidateTrigger$;
|
|
1581
|
+
validators;
|
|
1582
|
+
/** The value `reset()` returns to. Mutated by `setAsInitial()` so a form
|
|
1583
|
+
* initialized from server data resets to *that* data, not the empty seed. */
|
|
1584
|
+
initial;
|
|
1585
|
+
validatorDispose = null;
|
|
1586
|
+
currentAbort = null;
|
|
1587
|
+
runId = 0;
|
|
1588
|
+
disposed = false;
|
|
1589
|
+
devtoolsOwner = null;
|
|
1590
|
+
constructor(initial, validators = []) {
|
|
1591
|
+
this.initial = initial;
|
|
1592
|
+
this.validators = validators;
|
|
1593
|
+
this.value$ = signal$1(initial);
|
|
1594
|
+
this.errors$ = signal$1([]);
|
|
1595
|
+
this.touched$ = signal$1(false);
|
|
1596
|
+
this.dirty$ = signal$1(false);
|
|
1597
|
+
this.validating$ = signal$1(false);
|
|
1598
|
+
this.revalidateTrigger$ = signal$1(0);
|
|
1599
|
+
this.isValid$ = computed$1(() => this.errors$.value.length === 0 && !this.validating$.value);
|
|
1600
|
+
if (validators.length > 0) this.validatorDispose = effect$1(() => {
|
|
1601
|
+
this.runValidators();
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
get value() {
|
|
1605
|
+
return this.value$.value;
|
|
1606
|
+
}
|
|
1607
|
+
peek() {
|
|
1608
|
+
return this.value$.peek();
|
|
1609
|
+
}
|
|
1610
|
+
subscribe(handler) {
|
|
1611
|
+
return this.value$.subscribe(handler);
|
|
1612
|
+
}
|
|
1613
|
+
get errors() {
|
|
1614
|
+
return this.errors$;
|
|
1615
|
+
}
|
|
1616
|
+
get isValid() {
|
|
1617
|
+
return this.isValid$;
|
|
1618
|
+
}
|
|
1619
|
+
get isDirty() {
|
|
1620
|
+
return this.dirty$;
|
|
1621
|
+
}
|
|
1622
|
+
get touched() {
|
|
1623
|
+
return this.touched$;
|
|
1624
|
+
}
|
|
1625
|
+
get isValidating() {
|
|
1626
|
+
return this.validating$;
|
|
1627
|
+
}
|
|
1628
|
+
set(value) {
|
|
1629
|
+
if (this.disposed) return;
|
|
1630
|
+
this.value$.set(value);
|
|
1631
|
+
this.dirty$.set(true);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Reseat the field as if this value had been its constructor `initial`.
|
|
1635
|
+
* Sets the value, re-anchors `reset()`'s target, and does NOT mark dirty.
|
|
1636
|
+
* Used by `Form` when applying its own `initial` (in the constructor and
|
|
1637
|
+
* on `reset()`), so server-loaded forms don't start dirty. Internal-ish —
|
|
1638
|
+
* exposed for `Form`'s use, not for user code that just wants to write.
|
|
1639
|
+
*/
|
|
1640
|
+
setAsInitial(value) {
|
|
1641
|
+
if (this.disposed) return;
|
|
1642
|
+
this.initial = value;
|
|
1643
|
+
batch$1(() => {
|
|
1644
|
+
this.value$.set(value);
|
|
1645
|
+
this.dirty$.set(false);
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
reset() {
|
|
1649
|
+
if (this.disposed) return;
|
|
1650
|
+
this.currentAbort?.abort();
|
|
1651
|
+
this.currentAbort = null;
|
|
1652
|
+
batch$1(() => {
|
|
1653
|
+
this.value$.set(this.initial);
|
|
1654
|
+
this.dirty$.set(false);
|
|
1655
|
+
this.touched$.set(false);
|
|
1656
|
+
this.errors$.set([]);
|
|
1657
|
+
this.validating$.set(false);
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
markTouched() {
|
|
1661
|
+
if (this.disposed) return;
|
|
1662
|
+
this.touched$.set(true);
|
|
1663
|
+
}
|
|
1664
|
+
async revalidate() {
|
|
1665
|
+
if (this.disposed) return this.isValid$.peek();
|
|
1666
|
+
this.revalidateTrigger$.update((n) => n + 1);
|
|
1667
|
+
await this.waitUntilSettled();
|
|
1668
|
+
return this.isValid$.peek();
|
|
1669
|
+
}
|
|
1670
|
+
dispose() {
|
|
1671
|
+
if (this.disposed) return;
|
|
1672
|
+
this.disposed = true;
|
|
1673
|
+
this.validatorDispose?.();
|
|
1674
|
+
this.validatorDispose = null;
|
|
1675
|
+
this.currentAbort?.abort();
|
|
1676
|
+
this.currentAbort = null;
|
|
1677
|
+
this.devtoolsOwner = null;
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Bind this field to a devtools owner. Each subsequent validation pass
|
|
1681
|
+
* publishes a `field:validated` event with the supplied path + name.
|
|
1682
|
+
* Idempotent — calling again replaces the owner. Internal: called by
|
|
1683
|
+
* `createForm` / `createFieldArray` so the form's keys reach the panel.
|
|
1684
|
+
*/
|
|
1685
|
+
bindDevtoolsOwner(owner) {
|
|
1686
|
+
this.devtoolsOwner = owner;
|
|
1687
|
+
}
|
|
1688
|
+
emitValidated(valid, errors) {}
|
|
1689
|
+
async waitUntilSettled() {
|
|
1690
|
+
if (!this.validating$.peek()) return;
|
|
1691
|
+
await new Promise((resolve) => {
|
|
1692
|
+
const unsub = this.validating$.subscribe((v) => {
|
|
1693
|
+
if (!v) {
|
|
1694
|
+
unsub();
|
|
1695
|
+
resolve();
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
runValidators() {
|
|
1701
|
+
if (this.disposed) return;
|
|
1702
|
+
const value = this.value$.value;
|
|
1703
|
+
this.revalidateTrigger$.value;
|
|
1704
|
+
this.currentAbort?.abort();
|
|
1705
|
+
const abort = new AbortController();
|
|
1706
|
+
this.currentAbort = abort;
|
|
1707
|
+
const myId = ++this.runId;
|
|
1708
|
+
const syncErrors = [];
|
|
1709
|
+
const asyncPromises = [];
|
|
1710
|
+
for (const validator of this.validators) {
|
|
1711
|
+
const result = validator(value, abort.signal);
|
|
1712
|
+
if (result instanceof Promise) asyncPromises.push(result);
|
|
1713
|
+
else if (result != null) syncErrors.push(result);
|
|
1714
|
+
}
|
|
1715
|
+
if (syncErrors.length > 0) {
|
|
1716
|
+
batch$1(() => {
|
|
1717
|
+
this.errors$.set(syncErrors);
|
|
1718
|
+
this.validating$.set(false);
|
|
1719
|
+
});
|
|
1720
|
+
this.emitValidated(false, syncErrors);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
if (asyncPromises.length === 0) {
|
|
1724
|
+
batch$1(() => {
|
|
1725
|
+
this.errors$.set([]);
|
|
1726
|
+
this.validating$.set(false);
|
|
1727
|
+
});
|
|
1728
|
+
this.emitValidated(true, []);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
batch$1(() => {
|
|
1732
|
+
this.errors$.set([]);
|
|
1733
|
+
this.validating$.set(true);
|
|
1734
|
+
});
|
|
1735
|
+
Promise.allSettled(asyncPromises).then((results) => {
|
|
1736
|
+
if (myId !== this.runId || this.disposed) return;
|
|
1737
|
+
const asyncErrors = [];
|
|
1738
|
+
for (const r of results) if (r.status === "fulfilled") {
|
|
1739
|
+
if (r.value != null) asyncErrors.push(r.value);
|
|
1740
|
+
} else if (!isAbortError(r.reason)) {
|
|
1741
|
+
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
1742
|
+
asyncErrors.push(msg);
|
|
1743
|
+
}
|
|
1744
|
+
batch$1(() => {
|
|
1745
|
+
this.errors$.set(asyncErrors);
|
|
1746
|
+
this.validating$.set(false);
|
|
1747
|
+
});
|
|
1748
|
+
this.emitValidated(asyncErrors.length === 0, asyncErrors);
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
/**
|
|
1753
|
+
* Internal — type guard / accessor for the binding hook. Avoids exposing
|
|
1754
|
+
* `bindDevtoolsOwner` on the public `Field<T>` type while letting `createForm`
|
|
1755
|
+
* call it via a structural check.
|
|
1756
|
+
*/
|
|
1757
|
+
function bindFieldDevtoolsOwner(field, owner) {
|
|
1758
|
+
const impl = field;
|
|
1759
|
+
if (typeof impl.bindDevtoolsOwner === "function") impl.bindDevtoolsOwner(owner);
|
|
1760
|
+
}
|
|
1761
|
+
function createField(initial, validators) {
|
|
1762
|
+
return new FieldImpl(initial, validators);
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Wrap an async validator with a debounce. The debounce timer resets on every
|
|
1766
|
+
* value change. While debouncing or the request is in flight, the field's
|
|
1767
|
+
* `isValidating` is true and `isValid` is false (treat-as-invalid-until-proven-valid).
|
|
1768
|
+
*/
|
|
1769
|
+
function debouncedValidator(fn, ms) {
|
|
1770
|
+
return (value, signal) => new Promise((resolve, reject) => {
|
|
1771
|
+
if (signal.aborted) {
|
|
1772
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const timer = setTimeout(() => {
|
|
1776
|
+
signal.removeEventListener("abort", onAbort);
|
|
1777
|
+
fn(value, signal).then(resolve, reject);
|
|
1778
|
+
}, ms);
|
|
1779
|
+
const onAbort = () => {
|
|
1780
|
+
clearTimeout(timer);
|
|
1781
|
+
signal.removeEventListener("abort", onAbort);
|
|
1782
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1783
|
+
};
|
|
1784
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
//#endregion
|
|
1788
|
+
//#region src/forms/form.ts
|
|
1789
|
+
const FORM_BRAND = Symbol.for("olas.form");
|
|
1790
|
+
const FIELD_ARRAY_BRAND = Symbol.for("olas.fieldArray");
|
|
1791
|
+
const isForm = (x) => typeof x === "object" && x !== null && x[FORM_BRAND] === true;
|
|
1792
|
+
const isFieldArray = (x) => typeof x === "object" && x !== null && x[FIELD_ARRAY_BRAND] === true;
|
|
1793
|
+
var FormImpl = class {
|
|
1794
|
+
[FORM_BRAND] = true;
|
|
1795
|
+
fields;
|
|
1796
|
+
value;
|
|
1797
|
+
errors;
|
|
1798
|
+
isValid;
|
|
1799
|
+
isDirty;
|
|
1800
|
+
touched;
|
|
1801
|
+
isValidating;
|
|
1802
|
+
flatErrors;
|
|
1803
|
+
topLevelErrors$ = signal$1([]);
|
|
1804
|
+
topLevelErrors = this.topLevelErrors$;
|
|
1805
|
+
topLevelValidating$ = signal$1(false);
|
|
1806
|
+
validators;
|
|
1807
|
+
options;
|
|
1808
|
+
validatorDispose = null;
|
|
1809
|
+
currentValidatorRun = 0;
|
|
1810
|
+
currentValidatorAbort = null;
|
|
1811
|
+
disposed = false;
|
|
1812
|
+
constructor(schema, options) {
|
|
1813
|
+
this.fields = schema;
|
|
1814
|
+
this.options = options;
|
|
1815
|
+
this.validators = options?.validators ?? [];
|
|
1816
|
+
if (options?.initial !== void 0) {
|
|
1817
|
+
const ini = typeof options.initial === "function" ? options.initial() : options.initial;
|
|
1818
|
+
if (ini !== void 0) this.applyPartial(ini, true);
|
|
1819
|
+
}
|
|
1820
|
+
this.value = computed$1(() => this.computeValue());
|
|
1821
|
+
this.errors = computed$1(() => this.computeErrors());
|
|
1822
|
+
this.isDirty = computed$1(() => this.computeBool("isDirty"));
|
|
1823
|
+
this.touched = computed$1(() => this.computeBool("touched"));
|
|
1824
|
+
this.isValidating = computed$1(() => {
|
|
1825
|
+
if (this.topLevelValidating$.value) return true;
|
|
1826
|
+
for (const child of Object.values(this.fields)) if (child.isValidating.value) return true;
|
|
1827
|
+
return false;
|
|
1828
|
+
});
|
|
1829
|
+
this.isValid = computed$1(() => {
|
|
1830
|
+
if (this.topLevelErrors$.value.length > 0) return false;
|
|
1831
|
+
if (this.isValidating.value) return false;
|
|
1832
|
+
for (const child of Object.values(this.fields)) if (!child.isValid.value) return false;
|
|
1833
|
+
return true;
|
|
1834
|
+
});
|
|
1835
|
+
this.flatErrors = computed$1(() => this.computeFlatErrors());
|
|
1836
|
+
if (this.validators.length > 0) this.validatorDispose = effect$1(() => this.runTopLevelValidators());
|
|
1837
|
+
}
|
|
1838
|
+
computeValue() {
|
|
1839
|
+
const out = {};
|
|
1840
|
+
for (const [k, child] of Object.entries(this.fields)) if (isForm(child) || isFieldArray(child)) out[k] = child.value.value;
|
|
1841
|
+
else out[k] = child.value;
|
|
1842
|
+
return out;
|
|
1843
|
+
}
|
|
1844
|
+
computeErrors() {
|
|
1845
|
+
const out = {};
|
|
1846
|
+
for (const [k, child] of Object.entries(this.fields)) if (isForm(child)) out[k] = child.errors.value;
|
|
1847
|
+
else if (isFieldArray(child)) out[k] = child.errors.value;
|
|
1848
|
+
else {
|
|
1849
|
+
const errs = child.errors.value;
|
|
1850
|
+
out[k] = errs.length > 0 ? errs : void 0;
|
|
1851
|
+
}
|
|
1852
|
+
return out;
|
|
1853
|
+
}
|
|
1854
|
+
computeBool(key) {
|
|
1855
|
+
for (const child of Object.values(this.fields)) if (child[key]?.value) return true;
|
|
1856
|
+
return false;
|
|
1857
|
+
}
|
|
1858
|
+
computeFlatErrors() {
|
|
1859
|
+
const out = [];
|
|
1860
|
+
const tle = this.topLevelErrors$.value;
|
|
1861
|
+
if (tle.length > 0) out.push({
|
|
1862
|
+
path: "",
|
|
1863
|
+
errors: tle
|
|
1864
|
+
});
|
|
1865
|
+
walkErrors(this.fields, "", out);
|
|
1866
|
+
return out;
|
|
1867
|
+
}
|
|
1868
|
+
set(partial) {
|
|
1869
|
+
if (this.disposed) return;
|
|
1870
|
+
batch$1(() => this.applyPartial(partial, false));
|
|
1871
|
+
}
|
|
1872
|
+
applyPartial(partial, asInitial) {
|
|
1873
|
+
for (const [k, val] of Object.entries(partial)) {
|
|
1874
|
+
const child = this.fields[k];
|
|
1875
|
+
if (!child) continue;
|
|
1876
|
+
if (isForm(child)) if (asInitial) child.resetWithInitial(val);
|
|
1877
|
+
else child.set(val);
|
|
1878
|
+
else if (isFieldArray(child)) {
|
|
1879
|
+
const arr = child;
|
|
1880
|
+
arr.clear();
|
|
1881
|
+
for (const itemVal of val) arr.add(itemVal);
|
|
1882
|
+
} else {
|
|
1883
|
+
const f = child;
|
|
1884
|
+
if (asInitial) f.setAsInitial(val);
|
|
1885
|
+
else f.set(val);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
/** Internal: re-seat this form's leaves from `partial` as their new initial. */
|
|
1890
|
+
resetWithInitial(partial) {
|
|
1891
|
+
if (this.disposed) return;
|
|
1892
|
+
batch$1(() => this.applyPartial(partial, true));
|
|
1893
|
+
}
|
|
1894
|
+
reset() {
|
|
1895
|
+
if (this.disposed) return;
|
|
1896
|
+
batch$1(() => {
|
|
1897
|
+
for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) child.reset();
|
|
1898
|
+
else child.reset();
|
|
1899
|
+
this.topLevelErrors$.set([]);
|
|
1900
|
+
});
|
|
1901
|
+
if (this.options?.initial !== void 0) {
|
|
1902
|
+
const ini = typeof this.options.initial === "function" ? this.options.initial() : this.options.initial;
|
|
1903
|
+
if (ini !== void 0) this.applyPartial(ini, true);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
markAllTouched() {
|
|
1907
|
+
if (this.disposed) return;
|
|
1908
|
+
for (const child of Object.values(this.fields)) if (isForm(child)) child.markAllTouched();
|
|
1909
|
+
else if (isFieldArray(child)) child.markAllTouched();
|
|
1910
|
+
else child.markTouched();
|
|
1911
|
+
}
|
|
1912
|
+
async validate() {
|
|
1913
|
+
if (this.disposed) return this.isValid.peek();
|
|
1914
|
+
const tasks = [];
|
|
1915
|
+
for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) tasks.push(child.validate());
|
|
1916
|
+
else tasks.push(child.revalidate());
|
|
1917
|
+
await Promise.all(tasks);
|
|
1918
|
+
if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
|
|
1919
|
+
const unsub = this.topLevelValidating$.subscribe((v) => {
|
|
1920
|
+
if (!v) {
|
|
1921
|
+
unsub();
|
|
1922
|
+
resolve();
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
});
|
|
1926
|
+
return this.isValid.peek();
|
|
1927
|
+
}
|
|
1928
|
+
dispose() {
|
|
1929
|
+
if (this.disposed) return;
|
|
1930
|
+
this.disposed = true;
|
|
1931
|
+
this.validatorDispose?.();
|
|
1932
|
+
this.currentValidatorAbort?.abort();
|
|
1933
|
+
for (const child of Object.values(this.fields)) child.dispose?.();
|
|
1934
|
+
}
|
|
1935
|
+
runTopLevelValidators() {
|
|
1936
|
+
if (this.disposed) return;
|
|
1937
|
+
const value = this.value.value;
|
|
1938
|
+
this.currentValidatorAbort?.abort();
|
|
1939
|
+
const abort = new AbortController();
|
|
1940
|
+
this.currentValidatorAbort = abort;
|
|
1941
|
+
const myId = ++this.currentValidatorRun;
|
|
1942
|
+
const syncErrors = [];
|
|
1943
|
+
const asyncPromises = [];
|
|
1944
|
+
for (const v of this.validators) {
|
|
1945
|
+
const r = v(value, abort.signal);
|
|
1946
|
+
if (r instanceof Promise) asyncPromises.push(r);
|
|
1947
|
+
else if (r != null) syncErrors.push(r);
|
|
1948
|
+
}
|
|
1949
|
+
if (syncErrors.length > 0) {
|
|
1950
|
+
batch$1(() => {
|
|
1951
|
+
this.topLevelErrors$.set(syncErrors);
|
|
1952
|
+
this.topLevelValidating$.set(false);
|
|
1953
|
+
});
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (asyncPromises.length === 0) {
|
|
1957
|
+
batch$1(() => {
|
|
1958
|
+
this.topLevelErrors$.set([]);
|
|
1959
|
+
this.topLevelValidating$.set(false);
|
|
1960
|
+
});
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
batch$1(() => {
|
|
1964
|
+
this.topLevelErrors$.set([]);
|
|
1965
|
+
this.topLevelValidating$.set(true);
|
|
1966
|
+
});
|
|
1967
|
+
Promise.allSettled(asyncPromises).then((results) => {
|
|
1968
|
+
if (myId !== this.currentValidatorRun || this.disposed) return;
|
|
1969
|
+
const errs = [];
|
|
1970
|
+
for (const r of results) if (r.status === "fulfilled" && r.value != null) errs.push(r.value);
|
|
1971
|
+
batch$1(() => {
|
|
1972
|
+
this.topLevelErrors$.set(errs);
|
|
1973
|
+
this.topLevelValidating$.set(false);
|
|
1974
|
+
});
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
function walkErrors(fields, prefix, out) {
|
|
1979
|
+
for (const [k, child] of Object.entries(fields)) {
|
|
1980
|
+
const path = prefix ? `${prefix}.${k}` : k;
|
|
1981
|
+
if (isForm(child)) {
|
|
1982
|
+
const tle = child.topLevelErrors.value;
|
|
1983
|
+
if (tle.length > 0) out.push({
|
|
1984
|
+
path,
|
|
1985
|
+
errors: tle
|
|
1986
|
+
});
|
|
1987
|
+
walkErrors(child.fields, path, out);
|
|
1988
|
+
} else if (isFieldArray(child)) {
|
|
1989
|
+
const tle = child.topLevelErrors.value;
|
|
1990
|
+
if (tle.length > 0) out.push({
|
|
1991
|
+
path,
|
|
1992
|
+
errors: tle
|
|
1993
|
+
});
|
|
1994
|
+
child.items.value.forEach((item, idx) => {
|
|
1995
|
+
const itemPath = `${path}[${idx}]`;
|
|
1996
|
+
if (isForm(item)) {
|
|
1997
|
+
const itle = item.topLevelErrors.value;
|
|
1998
|
+
if (itle.length > 0) out.push({
|
|
1999
|
+
path: itemPath,
|
|
2000
|
+
errors: itle
|
|
2001
|
+
});
|
|
2002
|
+
walkErrors(item.fields, itemPath, out);
|
|
2003
|
+
} else {
|
|
2004
|
+
const errs = item.errors.value;
|
|
2005
|
+
if (errs.length > 0) out.push({
|
|
2006
|
+
path: itemPath,
|
|
2007
|
+
errors: errs
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
} else {
|
|
2012
|
+
const errs = child.errors.value;
|
|
2013
|
+
if (errs.length > 0) out.push({
|
|
2014
|
+
path,
|
|
2015
|
+
errors: errs
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
var FieldArrayImpl = class {
|
|
2021
|
+
[FIELD_ARRAY_BRAND] = true;
|
|
2022
|
+
items;
|
|
2023
|
+
value;
|
|
2024
|
+
errors;
|
|
2025
|
+
size;
|
|
2026
|
+
isValid;
|
|
2027
|
+
isDirty;
|
|
2028
|
+
touched;
|
|
2029
|
+
isValidating;
|
|
2030
|
+
items$;
|
|
2031
|
+
topLevelErrors$ = signal$1([]);
|
|
2032
|
+
topLevelErrors = this.topLevelErrors$;
|
|
2033
|
+
topLevelValidating$ = signal$1(false);
|
|
2034
|
+
itemFactory;
|
|
2035
|
+
initialItems = [];
|
|
2036
|
+
validators;
|
|
2037
|
+
currentValidatorRun = 0;
|
|
2038
|
+
currentValidatorAbort = null;
|
|
2039
|
+
validatorDispose = null;
|
|
2040
|
+
disposed = false;
|
|
2041
|
+
constructor(itemFactory, options) {
|
|
2042
|
+
this.itemFactory = itemFactory;
|
|
2043
|
+
this.validators = options?.validators ?? [];
|
|
2044
|
+
this.items$ = signal$1([]);
|
|
2045
|
+
if (options?.initial) {
|
|
2046
|
+
this.initialItems = options.initial;
|
|
2047
|
+
for (const ini of options.initial) this.items$.peek().push(itemFactory(ini));
|
|
2048
|
+
this.items$.set([...this.items$.peek()]);
|
|
2049
|
+
}
|
|
2050
|
+
this.items = this.items$;
|
|
2051
|
+
this.size = computed$1(() => this.items$.value.length);
|
|
2052
|
+
this.value = computed$1(() => this.items$.value.map((item) => {
|
|
2053
|
+
if (isForm(item)) return item.value.value;
|
|
2054
|
+
return item.value;
|
|
2055
|
+
}));
|
|
2056
|
+
this.errors = computed$1(() => this.items$.value.map((item) => {
|
|
2057
|
+
if (isForm(item)) return item.errors.value;
|
|
2058
|
+
const errs = item.errors.value;
|
|
2059
|
+
return errs.length > 0 ? errs : void 0;
|
|
2060
|
+
}));
|
|
2061
|
+
this.isDirty = computed$1(() => {
|
|
2062
|
+
for (const item of this.items$.value) if (item.isDirty.value) return true;
|
|
2063
|
+
return false;
|
|
2064
|
+
});
|
|
2065
|
+
this.touched = computed$1(() => {
|
|
2066
|
+
for (const item of this.items$.value) if (item.touched.value) return true;
|
|
2067
|
+
return false;
|
|
2068
|
+
});
|
|
2069
|
+
this.isValidating = computed$1(() => {
|
|
2070
|
+
if (this.topLevelValidating$.value) return true;
|
|
2071
|
+
for (const item of this.items$.value) if (item.isValidating.value) return true;
|
|
2072
|
+
return false;
|
|
2073
|
+
});
|
|
2074
|
+
this.isValid = computed$1(() => {
|
|
2075
|
+
if (this.topLevelErrors$.value.length > 0) return false;
|
|
2076
|
+
if (this.isValidating.value) return false;
|
|
2077
|
+
for (const item of this.items$.value) if (!item.isValid.value) return false;
|
|
2078
|
+
return true;
|
|
2079
|
+
});
|
|
2080
|
+
if (this.validators.length > 0) this.validatorDispose = effect$1(() => this.runTopLevelValidators());
|
|
2081
|
+
}
|
|
2082
|
+
at(index) {
|
|
2083
|
+
return this.items$.peek()[index];
|
|
2084
|
+
}
|
|
2085
|
+
add(initial) {
|
|
2086
|
+
if (this.disposed) return;
|
|
2087
|
+
const item = this.itemFactory(initial);
|
|
2088
|
+
this.items$.set([...this.items$.peek(), item]);
|
|
2089
|
+
}
|
|
2090
|
+
insert(index, initial) {
|
|
2091
|
+
if (this.disposed) return;
|
|
2092
|
+
const item = this.itemFactory(initial);
|
|
2093
|
+
const next = [...this.items$.peek()];
|
|
2094
|
+
next.splice(index, 0, item);
|
|
2095
|
+
this.items$.set(next);
|
|
2096
|
+
}
|
|
2097
|
+
remove(index) {
|
|
2098
|
+
if (this.disposed) return;
|
|
2099
|
+
const next = [...this.items$.peek()];
|
|
2100
|
+
const [removed] = next.splice(index, 1);
|
|
2101
|
+
if (removed) removed.dispose?.();
|
|
2102
|
+
this.items$.set(next);
|
|
2103
|
+
}
|
|
2104
|
+
move(from, to) {
|
|
2105
|
+
if (this.disposed) return;
|
|
2106
|
+
const next = [...this.items$.peek()];
|
|
2107
|
+
const [item] = next.splice(from, 1);
|
|
2108
|
+
if (item) next.splice(to, 0, item);
|
|
2109
|
+
this.items$.set(next);
|
|
2110
|
+
}
|
|
2111
|
+
clear() {
|
|
2112
|
+
if (this.disposed) return;
|
|
2113
|
+
for (const item of this.items$.peek()) item.dispose?.();
|
|
2114
|
+
this.items$.set([]);
|
|
2115
|
+
}
|
|
2116
|
+
reset() {
|
|
2117
|
+
if (this.disposed) return;
|
|
2118
|
+
batch$1(() => {
|
|
2119
|
+
this.clear();
|
|
2120
|
+
for (const ini of this.initialItems) this.add(ini);
|
|
2121
|
+
this.topLevelErrors$.set([]);
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
markAllTouched() {
|
|
2125
|
+
for (const item of this.items$.peek()) if (isForm(item)) item.markAllTouched();
|
|
2126
|
+
else item.markTouched();
|
|
2127
|
+
}
|
|
2128
|
+
async validate() {
|
|
2129
|
+
if (this.disposed) return this.isValid.peek();
|
|
2130
|
+
const tasks = [];
|
|
2131
|
+
for (const item of this.items$.peek()) if (isForm(item)) tasks.push(item.validate());
|
|
2132
|
+
else tasks.push(item.revalidate());
|
|
2133
|
+
await Promise.all(tasks);
|
|
2134
|
+
if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
|
|
2135
|
+
const unsub = this.topLevelValidating$.subscribe((v) => {
|
|
2136
|
+
if (!v) {
|
|
2137
|
+
unsub();
|
|
2138
|
+
resolve();
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
return this.isValid.peek();
|
|
2143
|
+
}
|
|
2144
|
+
dispose() {
|
|
2145
|
+
if (this.disposed) return;
|
|
2146
|
+
this.disposed = true;
|
|
2147
|
+
this.validatorDispose?.();
|
|
2148
|
+
this.currentValidatorAbort?.abort();
|
|
2149
|
+
for (const item of this.items$.peek()) item.dispose?.();
|
|
2150
|
+
}
|
|
2151
|
+
runTopLevelValidators() {
|
|
2152
|
+
if (this.disposed) return;
|
|
2153
|
+
const value = this.value.value;
|
|
2154
|
+
this.currentValidatorAbort?.abort();
|
|
2155
|
+
const abort = new AbortController();
|
|
2156
|
+
this.currentValidatorAbort = abort;
|
|
2157
|
+
const myId = ++this.currentValidatorRun;
|
|
2158
|
+
const syncErrors = [];
|
|
2159
|
+
const asyncPromises = [];
|
|
2160
|
+
for (const v of this.validators) {
|
|
2161
|
+
const r = v(value, abort.signal);
|
|
2162
|
+
if (r instanceof Promise) asyncPromises.push(r);
|
|
2163
|
+
else if (r != null) syncErrors.push(r);
|
|
2164
|
+
}
|
|
2165
|
+
if (syncErrors.length > 0) {
|
|
2166
|
+
batch$1(() => {
|
|
2167
|
+
this.topLevelErrors$.set(syncErrors);
|
|
2168
|
+
this.topLevelValidating$.set(false);
|
|
2169
|
+
});
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
if (asyncPromises.length === 0) {
|
|
2173
|
+
batch$1(() => {
|
|
2174
|
+
this.topLevelErrors$.set([]);
|
|
2175
|
+
this.topLevelValidating$.set(false);
|
|
2176
|
+
});
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
batch$1(() => {
|
|
2180
|
+
this.topLevelErrors$.set([]);
|
|
2181
|
+
this.topLevelValidating$.set(true);
|
|
2182
|
+
});
|
|
2183
|
+
Promise.allSettled(asyncPromises).then((results) => {
|
|
2184
|
+
if (myId !== this.currentValidatorRun || this.disposed) return;
|
|
2185
|
+
const errs = [];
|
|
2186
|
+
for (const r of results) if (r.status === "fulfilled" && r.value != null) errs.push(r.value);
|
|
2187
|
+
batch$1(() => {
|
|
2188
|
+
this.topLevelErrors$.set(errs);
|
|
2189
|
+
this.topLevelValidating$.set(false);
|
|
2190
|
+
});
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
function createForm(schema, options) {
|
|
2195
|
+
return new FormImpl(schema, options);
|
|
2196
|
+
}
|
|
2197
|
+
function createFieldArray(itemFactory, options) {
|
|
2198
|
+
return new FieldArrayImpl(itemFactory, options);
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Recursively wire every leaf `Field` in a form / field-array tree to a
|
|
2202
|
+
* devtools emitter. Returns a single disposer that tears down every standalone
|
|
2203
|
+
* `effect()` registered along the way (used for FieldArray watching), so the
|
|
2204
|
+
* caller — `ctx.form` / `ctx.fieldArray` in the controller — can register one
|
|
2205
|
+
* cleanup entry and have the whole subtree's reactive work die with the
|
|
2206
|
+
* controller. Spec §20.9.
|
|
2207
|
+
*/
|
|
2208
|
+
function bindTreeToDevtools(node, prefix, controllerPath, emitter) {
|
|
2209
|
+
const disposers = [];
|
|
2210
|
+
bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers);
|
|
2211
|
+
return () => {
|
|
2212
|
+
for (const d of disposers) try {
|
|
2213
|
+
d();
|
|
2214
|
+
} catch {}
|
|
2215
|
+
disposers.length = 0;
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers) {
|
|
2219
|
+
if (isForm(node)) {
|
|
2220
|
+
for (const [key, child] of Object.entries(node.fields)) bindTreeToDevtoolsInto(child, prefix === "" ? key : `${prefix}.${key}`, controllerPath, emitter, disposers);
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
if (isFieldArray(node)) {
|
|
2224
|
+
const arr = node;
|
|
2225
|
+
const stop = effect$1(() => {
|
|
2226
|
+
arr.items.value.forEach((item, idx) => {
|
|
2227
|
+
bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, disposers);
|
|
2228
|
+
});
|
|
2229
|
+
});
|
|
2230
|
+
disposers.push(stop);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
bindFieldDevtoolsOwner(node, {
|
|
2234
|
+
controllerPath,
|
|
2235
|
+
fieldName: prefix,
|
|
2236
|
+
emitter
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
//#endregion
|
|
2240
|
+
//#region src/query/local.ts
|
|
2241
|
+
var LocalCacheImpl = class {
|
|
2242
|
+
entry;
|
|
2243
|
+
keyEffectDispose = null;
|
|
2244
|
+
disposed = false;
|
|
2245
|
+
keepPreviousData;
|
|
2246
|
+
lastSucceededFor = null;
|
|
2247
|
+
constructor(fetcher, options) {
|
|
2248
|
+
this.keepPreviousData = options.keepPreviousData ?? false;
|
|
2249
|
+
this.entry = new Entry({
|
|
2250
|
+
fetcher: () => fetcher,
|
|
2251
|
+
staleTime: options.staleTime ?? 0,
|
|
2252
|
+
initialData: options.initialData
|
|
2253
|
+
});
|
|
2254
|
+
if (options.key) {
|
|
2255
|
+
const keyFn = options.key;
|
|
2256
|
+
this.keyEffectDispose = effect$1(() => {
|
|
2257
|
+
const keyArgs = keyFn();
|
|
2258
|
+
untracked$1(() => {
|
|
2259
|
+
if (!this.keepPreviousData) {
|
|
2260
|
+
if (this.lastSucceededFor != null && !arraysEqual(this.lastSucceededFor, keyArgs)) this.entry.data.set(void 0);
|
|
2261
|
+
}
|
|
2262
|
+
this.entry.startFetch().then(() => {
|
|
2263
|
+
this.lastSucceededFor = [...keyArgs];
|
|
2264
|
+
}, () => {});
|
|
2265
|
+
});
|
|
2266
|
+
});
|
|
2267
|
+
} else this.entry.startFetch().catch(() => {});
|
|
2268
|
+
}
|
|
2269
|
+
get data() {
|
|
2270
|
+
return this.entry.data;
|
|
2271
|
+
}
|
|
2272
|
+
get error() {
|
|
2273
|
+
return this.entry.error;
|
|
2274
|
+
}
|
|
2275
|
+
get status() {
|
|
2276
|
+
return this.entry.status;
|
|
2277
|
+
}
|
|
2278
|
+
get isLoading() {
|
|
2279
|
+
return this.entry.isLoading;
|
|
2280
|
+
}
|
|
2281
|
+
get isFetching() {
|
|
2282
|
+
return this.entry.isFetching;
|
|
2283
|
+
}
|
|
2284
|
+
get isStale() {
|
|
2285
|
+
return this.entry.isStale;
|
|
2286
|
+
}
|
|
2287
|
+
get lastUpdatedAt() {
|
|
2288
|
+
return this.entry.lastUpdatedAt;
|
|
2289
|
+
}
|
|
2290
|
+
get hasPendingMutations() {
|
|
2291
|
+
return this.entry.hasPendingMutations;
|
|
2292
|
+
}
|
|
2293
|
+
refetch = () => this.entry.refetch();
|
|
2294
|
+
reset = () => this.entry.reset();
|
|
2295
|
+
firstValue = () => this.entry.firstValue();
|
|
2296
|
+
invalidate = () => {
|
|
2297
|
+
this.entry.invalidate().catch(() => {});
|
|
2298
|
+
};
|
|
2299
|
+
setData = (updater) => this.entry.setData(updater);
|
|
2300
|
+
dispose() {
|
|
2301
|
+
if (this.disposed) return;
|
|
2302
|
+
this.disposed = true;
|
|
2303
|
+
this.keyEffectDispose?.();
|
|
2304
|
+
this.keyEffectDispose = null;
|
|
2305
|
+
this.entry.dispose();
|
|
2306
|
+
}
|
|
2307
|
+
};
|
|
2308
|
+
function createLocalCache(fetcher, options) {
|
|
2309
|
+
return new LocalCacheImpl(fetcher, options ?? {});
|
|
2310
|
+
}
|
|
2311
|
+
function arraysEqual(a, b) {
|
|
2312
|
+
if (a.length !== b.length) return false;
|
|
2313
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
2314
|
+
return true;
|
|
2315
|
+
}
|
|
2316
|
+
//#endregion
|
|
2317
|
+
//#region src/query/mutation.ts
|
|
2318
|
+
var MutationImpl = class {
|
|
2319
|
+
spec;
|
|
2320
|
+
onError;
|
|
2321
|
+
controllerPath;
|
|
2322
|
+
inflightCounter;
|
|
2323
|
+
devtools;
|
|
2324
|
+
data = signal$1(void 0);
|
|
2325
|
+
error = signal$1(void 0);
|
|
2326
|
+
isPending = signal$1(false);
|
|
2327
|
+
lastVariables = signal$1(void 0);
|
|
2328
|
+
inflight = /* @__PURE__ */ new Set();
|
|
2329
|
+
serialQueue = [];
|
|
2330
|
+
serialActive = false;
|
|
2331
|
+
disposed = false;
|
|
2332
|
+
constructor(spec, onError, controllerPath, inflightCounter, devtools) {
|
|
2333
|
+
this.spec = spec;
|
|
2334
|
+
this.onError = onError;
|
|
2335
|
+
this.controllerPath = controllerPath;
|
|
2336
|
+
this.inflightCounter = inflightCounter;
|
|
2337
|
+
this.devtools = devtools;
|
|
2338
|
+
}
|
|
2339
|
+
emit(event) {}
|
|
2340
|
+
run = ((vars = void 0) => {
|
|
2341
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Mutation disposed"));
|
|
2342
|
+
switch (this.spec.concurrency ?? "parallel") {
|
|
2343
|
+
case "parallel": return this.executeRun(vars);
|
|
2344
|
+
case "latest-wins":
|
|
2345
|
+
for (const handle of this.inflight) {
|
|
2346
|
+
handle.abort.abort();
|
|
2347
|
+
handle.snapshot?.rollback();
|
|
2348
|
+
handle.snapshot = void 0;
|
|
2349
|
+
}
|
|
2350
|
+
return this.executeRun(vars);
|
|
2351
|
+
case "serial": return this.enqueueSerial(vars);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
enqueueSerial(vars) {
|
|
2355
|
+
if (this.serialActive) return new Promise((resolve, reject) => {
|
|
2356
|
+
this.serialQueue.push({
|
|
2357
|
+
vars,
|
|
2358
|
+
resolve,
|
|
2359
|
+
reject
|
|
2360
|
+
});
|
|
2361
|
+
});
|
|
2362
|
+
this.serialActive = true;
|
|
2363
|
+
return this.executeRun(vars).finally(() => this.advanceSerialQueue());
|
|
2364
|
+
}
|
|
2365
|
+
advanceSerialQueue() {
|
|
2366
|
+
const next = this.serialQueue.shift();
|
|
2367
|
+
if (!next) {
|
|
2368
|
+
this.serialActive = false;
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
this.executeRun(next.vars).then((result) => {
|
|
2372
|
+
next.resolve(result);
|
|
2373
|
+
this.advanceSerialQueue();
|
|
2374
|
+
}, (err) => {
|
|
2375
|
+
next.reject(err);
|
|
2376
|
+
this.advanceSerialQueue();
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
async executeRun(vars) {
|
|
2380
|
+
const abort = new AbortController();
|
|
2381
|
+
let snapshot;
|
|
2382
|
+
try {
|
|
2383
|
+
const raw = this.spec.onMutate?.(vars) ?? void 0;
|
|
2384
|
+
snapshot = raw === void 0 ? void 0 : this.wrapSnapshot(raw);
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
dispatchError(this.onError, err, {
|
|
2387
|
+
kind: "mutation",
|
|
2388
|
+
controllerPath: this.controllerPath
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
const handle = {
|
|
2392
|
+
abort,
|
|
2393
|
+
snapshot
|
|
2394
|
+
};
|
|
2395
|
+
this.inflight.add(handle);
|
|
2396
|
+
this.inflightCounter?.update((n) => n + 1);
|
|
2397
|
+
batch$1(() => {
|
|
2398
|
+
this.isPending.set(true);
|
|
2399
|
+
this.lastVariables.set(vars);
|
|
2400
|
+
});
|
|
2401
|
+
try {
|
|
2402
|
+
const result = await raceAbort(this.runWithRetry(vars, abort.signal), abort.signal);
|
|
2403
|
+
if (abort.signal.aborted || this.disposed) {
|
|
2404
|
+
snapshot?.rollback();
|
|
2405
|
+
throw new DOMException("Superseded", "AbortError");
|
|
2406
|
+
}
|
|
2407
|
+
batch$1(() => {
|
|
2408
|
+
this.data.set(result);
|
|
2409
|
+
this.error.set(void 0);
|
|
2410
|
+
});
|
|
2411
|
+
this.safeCall(() => this.spec.onSuccess?.(result, vars), "mutation");
|
|
2412
|
+
snapshot?.finalize();
|
|
2413
|
+
this.safeCall(() => this.spec.onSettled?.(result, void 0, vars), "mutation");
|
|
2414
|
+
return result;
|
|
2415
|
+
} catch (err) {
|
|
2416
|
+
if (isAbortError(err) || abort.signal.aborted) {
|
|
2417
|
+
snapshot?.rollback();
|
|
2418
|
+
throw err;
|
|
2419
|
+
}
|
|
2420
|
+
this.error.set(err);
|
|
2421
|
+
this.safeCall(() => this.spec.onError?.(err, vars, snapshot), "mutation");
|
|
2422
|
+
snapshot?.rollback();
|
|
2423
|
+
this.safeCall(() => this.spec.onSettled?.(void 0, err, vars), "mutation");
|
|
2424
|
+
throw err;
|
|
2425
|
+
} finally {
|
|
2426
|
+
this.inflight.delete(handle);
|
|
2427
|
+
this.inflightCounter?.update((n) => Math.max(0, n - 1));
|
|
2428
|
+
if (this.inflight.size === 0) this.isPending.set(false);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
wrapSnapshot(raw) {
|
|
2432
|
+
let consumed = false;
|
|
2433
|
+
return {
|
|
2434
|
+
rollback: () => {
|
|
2435
|
+
if (consumed) return;
|
|
2436
|
+
consumed = true;
|
|
2437
|
+
raw.rollback();
|
|
2438
|
+
},
|
|
2439
|
+
finalize: () => {
|
|
2440
|
+
if (consumed) return;
|
|
2441
|
+
consumed = true;
|
|
2442
|
+
raw.finalize();
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
async runWithRetry(vars, signal) {
|
|
2447
|
+
const retry = this.spec.retry ?? 0;
|
|
2448
|
+
const retryDelay = this.spec.retryDelay ?? 1e3;
|
|
2449
|
+
let attempt = 0;
|
|
2450
|
+
while (true) try {
|
|
2451
|
+
return await this.spec.mutate(vars, signal);
|
|
2452
|
+
} catch (err) {
|
|
2453
|
+
if (signal.aborted || isAbortError(err)) throw err;
|
|
2454
|
+
if (!(typeof retry === "number" ? attempt < retry : retry(attempt, err))) throw err;
|
|
2455
|
+
await abortableSleep(typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay, signal);
|
|
2456
|
+
attempt += 1;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
safeCall(fn, kind) {
|
|
2460
|
+
try {
|
|
2461
|
+
fn();
|
|
2462
|
+
} catch (err) {
|
|
2463
|
+
dispatchError(this.onError, err, {
|
|
2464
|
+
kind,
|
|
2465
|
+
controllerPath: this.controllerPath
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
reset() {
|
|
2470
|
+
if (this.disposed) return;
|
|
2471
|
+
for (const handle of this.inflight) handle.abort.abort();
|
|
2472
|
+
this.serialQueue.length = 0;
|
|
2473
|
+
batch$1(() => {
|
|
2474
|
+
this.data.set(void 0);
|
|
2475
|
+
this.error.set(void 0);
|
|
2476
|
+
this.lastVariables.set(void 0);
|
|
2477
|
+
this.isPending.set(false);
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
dispose() {
|
|
2481
|
+
if (this.disposed) return;
|
|
2482
|
+
this.disposed = true;
|
|
2483
|
+
for (const handle of this.inflight) handle.abort.abort();
|
|
2484
|
+
for (const queued of this.serialQueue) queued.reject(new DOMException("Disposed", "AbortError"));
|
|
2485
|
+
this.serialQueue.length = 0;
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
function createMutation(spec, onError, controllerPath, inflightCounter, devtools) {
|
|
2489
|
+
return new MutationImpl(spec, onError, controllerPath, inflightCounter, devtools);
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Race a promise against an AbortSignal. If the signal fires before the
|
|
2493
|
+
* promise settles, the returned promise rejects with AbortError — regardless
|
|
2494
|
+
* of whether the underlying promise ever resolves. Protects against
|
|
2495
|
+
* misbehaving mutate fns that ignore their signal.
|
|
2496
|
+
*/
|
|
2497
|
+
function raceAbort(promise, signal) {
|
|
2498
|
+
if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
|
|
2499
|
+
return new Promise((resolve, reject) => {
|
|
2500
|
+
let settled = false;
|
|
2501
|
+
const onAbort = () => {
|
|
2502
|
+
if (settled) return;
|
|
2503
|
+
settled = true;
|
|
2504
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2505
|
+
};
|
|
2506
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2507
|
+
promise.then((v) => {
|
|
2508
|
+
if (settled) return;
|
|
2509
|
+
settled = true;
|
|
2510
|
+
signal.removeEventListener("abort", onAbort);
|
|
2511
|
+
resolve(v);
|
|
2512
|
+
}, (e) => {
|
|
2513
|
+
if (settled) return;
|
|
2514
|
+
settled = true;
|
|
2515
|
+
signal.removeEventListener("abort", onAbort);
|
|
2516
|
+
reject(e);
|
|
2517
|
+
});
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
function abortableSleep(ms, signal) {
|
|
2521
|
+
return new Promise((resolve, reject) => {
|
|
2522
|
+
if (signal.aborted) {
|
|
2523
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const timer = setTimeout(() => {
|
|
2527
|
+
signal.removeEventListener("abort", onAbort);
|
|
2528
|
+
resolve();
|
|
2529
|
+
}, ms);
|
|
2530
|
+
const onAbort = () => {
|
|
2531
|
+
clearTimeout(timer);
|
|
2532
|
+
signal.removeEventListener("abort", onAbort);
|
|
2533
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2534
|
+
};
|
|
2535
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
//#endregion
|
|
2539
|
+
//#region src/query/use.ts
|
|
2540
|
+
var SubscriptionImpl = class {
|
|
2541
|
+
keepPreviousData;
|
|
2542
|
+
current$ = signal$1(null);
|
|
2543
|
+
previousData$ = signal$1(void 0);
|
|
2544
|
+
data;
|
|
2545
|
+
error;
|
|
2546
|
+
status;
|
|
2547
|
+
isLoading;
|
|
2548
|
+
isFetching;
|
|
2549
|
+
isStale;
|
|
2550
|
+
lastUpdatedAt;
|
|
2551
|
+
hasPendingMutations;
|
|
2552
|
+
constructor(keepPreviousData) {
|
|
2553
|
+
this.keepPreviousData = keepPreviousData;
|
|
2554
|
+
this.data = computed$1(() => {
|
|
2555
|
+
const curData = this.current$.value?.entry.data.value;
|
|
2556
|
+
if (curData !== void 0) return curData;
|
|
2557
|
+
if (keepPreviousData) return this.previousData$.value;
|
|
2558
|
+
});
|
|
2559
|
+
this.error = computed$1(() => this.current$.value?.entry.error.value);
|
|
2560
|
+
this.status = computed$1(() => this.current$.value?.entry.status.value ?? "idle");
|
|
2561
|
+
this.isLoading = computed$1(() => {
|
|
2562
|
+
const cur = this.current$.value;
|
|
2563
|
+
if (!cur) return false;
|
|
2564
|
+
if (keepPreviousData && this.previousData$.value !== void 0) return false;
|
|
2565
|
+
return cur.entry.isLoading.value;
|
|
2566
|
+
});
|
|
2567
|
+
this.isFetching = computed$1(() => this.current$.value?.entry.isFetching.value ?? false);
|
|
2568
|
+
this.isStale = computed$1(() => this.current$.value?.entry.isStale.value ?? true);
|
|
2569
|
+
this.lastUpdatedAt = computed$1(() => this.current$.value?.entry.lastUpdatedAt.value);
|
|
2570
|
+
this.hasPendingMutations = computed$1(() => this.current$.value?.entry.hasPendingMutations.value ?? false);
|
|
2571
|
+
}
|
|
2572
|
+
attach(entry) {
|
|
2573
|
+
const prev = this.current$.peek();
|
|
2574
|
+
if (prev === entry) return;
|
|
2575
|
+
if (prev && this.keepPreviousData) {
|
|
2576
|
+
const prevData = prev.entry.data.peek();
|
|
2577
|
+
if (prevData !== void 0) this.previousData$.set(prevData);
|
|
2578
|
+
}
|
|
2579
|
+
this.current$.set(entry);
|
|
2580
|
+
}
|
|
2581
|
+
detach() {
|
|
2582
|
+
this.current$.set(null);
|
|
2583
|
+
}
|
|
2584
|
+
refetch = () => {
|
|
2585
|
+
const cur = this.current$.peek();
|
|
2586
|
+
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2587
|
+
return cur.entry.refetch();
|
|
2588
|
+
};
|
|
2589
|
+
reset = () => {
|
|
2590
|
+
this.current$.peek()?.entry.reset();
|
|
2591
|
+
};
|
|
2592
|
+
firstValue = () => {
|
|
2593
|
+
const cur = this.current$.peek();
|
|
2594
|
+
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2595
|
+
return cur.entry.firstValue();
|
|
2596
|
+
};
|
|
2597
|
+
};
|
|
2598
|
+
/**
|
|
2599
|
+
* Build a subscription + the effect that keeps it bound to the right entry.
|
|
2600
|
+
* The controller container wires the disposer into the lifecycle.
|
|
2601
|
+
*/
|
|
2602
|
+
function createUse(client, query, keyOrOptions) {
|
|
2603
|
+
const keepPreviousData = query.__spec.keepPreviousData ?? false;
|
|
2604
|
+
const keyFn = typeof keyOrOptions === "function" ? keyOrOptions : keyOrOptions?.key;
|
|
2605
|
+
const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
|
|
2606
|
+
const sub = new SubscriptionImpl(keepPreviousData);
|
|
2607
|
+
let currentEntry = null;
|
|
2608
|
+
const effectDispose = effect$1(() => {
|
|
2609
|
+
if (!(enabledFn ? enabledFn() : true)) {
|
|
2610
|
+
untracked$1(() => {
|
|
2611
|
+
if (currentEntry) {
|
|
2612
|
+
currentEntry.release();
|
|
2613
|
+
currentEntry = null;
|
|
2614
|
+
}
|
|
2615
|
+
sub.detach();
|
|
2616
|
+
});
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const args = keyFn ? keyFn() : [];
|
|
2620
|
+
untracked$1(() => {
|
|
2621
|
+
const entry = client.bindEntry(query, args);
|
|
2622
|
+
if (currentEntry === entry) return;
|
|
2623
|
+
if (currentEntry) currentEntry.release();
|
|
2624
|
+
entry.acquire();
|
|
2625
|
+
currentEntry = entry;
|
|
2626
|
+
sub.attach(entry);
|
|
2627
|
+
const status = entry.entry.status.peek();
|
|
2628
|
+
if (!entry.entry.isFetching.peek() && (status === "idle" || entry.entry.isStaleNow() || status === "error")) entry.entry.startFetch().catch(() => {});
|
|
2629
|
+
});
|
|
2630
|
+
});
|
|
2631
|
+
const dispose = () => {
|
|
2632
|
+
effectDispose();
|
|
2633
|
+
if (currentEntry) {
|
|
2634
|
+
currentEntry.release();
|
|
2635
|
+
currentEntry = null;
|
|
2636
|
+
}
|
|
2637
|
+
sub.detach();
|
|
2638
|
+
};
|
|
2639
|
+
return {
|
|
2640
|
+
subscription: sub,
|
|
2641
|
+
dispose
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
var InfiniteSubscriptionImpl = class {
|
|
2645
|
+
keepPreviousData;
|
|
2646
|
+
current$ = signal$1(null);
|
|
2647
|
+
previousPages$ = signal$1(void 0);
|
|
2648
|
+
data;
|
|
2649
|
+
pages;
|
|
2650
|
+
flat;
|
|
2651
|
+
error;
|
|
2652
|
+
status;
|
|
2653
|
+
isLoading;
|
|
2654
|
+
isFetching;
|
|
2655
|
+
isStale;
|
|
2656
|
+
lastUpdatedAt;
|
|
2657
|
+
hasPendingMutations;
|
|
2658
|
+
hasNextPage;
|
|
2659
|
+
hasPreviousPage;
|
|
2660
|
+
isFetchingNextPage;
|
|
2661
|
+
isFetchingPreviousPage;
|
|
2662
|
+
constructor(keepPreviousData) {
|
|
2663
|
+
this.keepPreviousData = keepPreviousData;
|
|
2664
|
+
this.pages = computed$1(() => {
|
|
2665
|
+
const ps = this.current$.value?.entry.pages.value;
|
|
2666
|
+
if (ps && ps.length > 0) return ps;
|
|
2667
|
+
if (keepPreviousData) return this.previousPages$.value ?? [];
|
|
2668
|
+
return ps ?? [];
|
|
2669
|
+
});
|
|
2670
|
+
this.data = computed$1(() => {
|
|
2671
|
+
const ps = this.current$.value?.entry.pages.value;
|
|
2672
|
+
if (ps && ps.length > 0) return ps;
|
|
2673
|
+
if (keepPreviousData) {
|
|
2674
|
+
const prev = this.previousPages$.value;
|
|
2675
|
+
if (prev && prev.length > 0) return prev;
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
this.flat = computed$1(() => this.current$.value?.entry.flat.value ?? []);
|
|
2679
|
+
this.error = computed$1(() => this.current$.value?.entry.error.value);
|
|
2680
|
+
this.status = computed$1(() => this.current$.value?.entry.status.value ?? "idle");
|
|
2681
|
+
this.isLoading = computed$1(() => {
|
|
2682
|
+
const cur = this.current$.value;
|
|
2683
|
+
if (!cur) return false;
|
|
2684
|
+
if (keepPreviousData) {
|
|
2685
|
+
const prev = this.previousPages$.value;
|
|
2686
|
+
if (prev && prev.length > 0) return false;
|
|
2687
|
+
}
|
|
2688
|
+
return cur.entry.isLoading.value;
|
|
2689
|
+
});
|
|
2690
|
+
this.isFetching = computed$1(() => this.current$.value?.entry.isFetching.value ?? false);
|
|
2691
|
+
this.isStale = computed$1(() => this.current$.value?.entry.isStale.value ?? true);
|
|
2692
|
+
this.lastUpdatedAt = computed$1(() => this.current$.value?.entry.lastUpdatedAt.value);
|
|
2693
|
+
this.hasPendingMutations = computed$1(() => this.current$.value?.entry.hasPendingMutations.value ?? false);
|
|
2694
|
+
this.hasNextPage = computed$1(() => this.current$.value?.entry.hasNextPage.value ?? false);
|
|
2695
|
+
this.hasPreviousPage = computed$1(() => this.current$.value?.entry.hasPreviousPage.value ?? false);
|
|
2696
|
+
this.isFetchingNextPage = computed$1(() => this.current$.value?.entry.isFetchingNextPage.value ?? false);
|
|
2697
|
+
this.isFetchingPreviousPage = computed$1(() => this.current$.value?.entry.isFetchingPreviousPage.value ?? false);
|
|
2698
|
+
}
|
|
2699
|
+
attach(entry) {
|
|
2700
|
+
const prev = this.current$.peek();
|
|
2701
|
+
if (prev === entry) return;
|
|
2702
|
+
if (prev && this.keepPreviousData) {
|
|
2703
|
+
const prevPages = prev.entry.pages.peek();
|
|
2704
|
+
if (prevPages.length > 0) this.previousPages$.set(prevPages);
|
|
2705
|
+
}
|
|
2706
|
+
this.current$.set(entry);
|
|
2707
|
+
}
|
|
2708
|
+
detach() {
|
|
2709
|
+
this.current$.set(null);
|
|
2710
|
+
}
|
|
2711
|
+
refetch = () => {
|
|
2712
|
+
const cur = this.current$.peek();
|
|
2713
|
+
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2714
|
+
return cur.entry.refetch().then(() => cur.entry.pages.peek());
|
|
2715
|
+
};
|
|
2716
|
+
reset = () => {
|
|
2717
|
+
this.current$.peek()?.entry.reset();
|
|
2718
|
+
};
|
|
2719
|
+
firstValue = () => {
|
|
2720
|
+
const cur = this.current$.peek();
|
|
2721
|
+
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2722
|
+
return cur.entry.firstValue();
|
|
2723
|
+
};
|
|
2724
|
+
fetchNextPage = () => {
|
|
2725
|
+
const cur = this.current$.peek();
|
|
2726
|
+
if (!cur) return Promise.resolve();
|
|
2727
|
+
return cur.entry.fetchNextPage();
|
|
2728
|
+
};
|
|
2729
|
+
fetchPreviousPage = () => {
|
|
2730
|
+
const cur = this.current$.peek();
|
|
2731
|
+
if (!cur) return Promise.resolve();
|
|
2732
|
+
return cur.entry.fetchPreviousPage();
|
|
2733
|
+
};
|
|
2734
|
+
};
|
|
2735
|
+
function createInfiniteUse(client, query, keyOrOptions) {
|
|
2736
|
+
const keepPreviousData = query.__spec.keepPreviousData ?? false;
|
|
2737
|
+
const keyFn = typeof keyOrOptions === "function" ? keyOrOptions : keyOrOptions?.key;
|
|
2738
|
+
const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
|
|
2739
|
+
const sub = new InfiniteSubscriptionImpl(keepPreviousData);
|
|
2740
|
+
let currentEntry = null;
|
|
2741
|
+
const effectDispose = effect$1(() => {
|
|
2742
|
+
if (!(enabledFn ? enabledFn() : true)) {
|
|
2743
|
+
untracked$1(() => {
|
|
2744
|
+
if (currentEntry) {
|
|
2745
|
+
currentEntry.release();
|
|
2746
|
+
currentEntry = null;
|
|
2747
|
+
}
|
|
2748
|
+
sub.detach();
|
|
2749
|
+
});
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
const args = keyFn ? keyFn() : [];
|
|
2753
|
+
untracked$1(() => {
|
|
2754
|
+
const entry = client.bindInfiniteEntry(query, args);
|
|
2755
|
+
if (currentEntry === entry) return;
|
|
2756
|
+
if (currentEntry) currentEntry.release();
|
|
2757
|
+
entry.acquire();
|
|
2758
|
+
currentEntry = entry;
|
|
2759
|
+
sub.attach(entry);
|
|
2760
|
+
const status = entry.entry.status.peek();
|
|
2761
|
+
if (!entry.entry.isFetching.peek() && (status === "idle" || entry.entry.isStaleNow() || status === "error")) entry.entry.startFetch().catch(() => {});
|
|
2762
|
+
});
|
|
2763
|
+
});
|
|
2764
|
+
const dispose = () => {
|
|
2765
|
+
effectDispose();
|
|
2766
|
+
if (currentEntry) {
|
|
2767
|
+
currentEntry.release();
|
|
2768
|
+
currentEntry = null;
|
|
2769
|
+
}
|
|
2770
|
+
sub.detach();
|
|
2771
|
+
};
|
|
2772
|
+
return {
|
|
2773
|
+
subscription: sub,
|
|
2774
|
+
dispose
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
//#endregion
|
|
2778
|
+
//#region src/controller/instance.ts
|
|
2779
|
+
var ControllerInstance = class ControllerInstance {
|
|
2780
|
+
path;
|
|
2781
|
+
deps;
|
|
2782
|
+
state = "constructing";
|
|
2783
|
+
entries = [];
|
|
2784
|
+
rootShared;
|
|
2785
|
+
parent;
|
|
2786
|
+
childCounter = 0;
|
|
2787
|
+
/** Scope values provided on this instance, keyed by `Scope.__id`. */
|
|
2788
|
+
scopes = null;
|
|
2789
|
+
constructor(parent, rootShared, pathSegment, deps) {
|
|
2790
|
+
this.parent = parent;
|
|
2791
|
+
this.rootShared = rootShared;
|
|
2792
|
+
this.path = parent ? [...parent.path, pathSegment] : [pathSegment];
|
|
2793
|
+
this.deps = deps;
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Run the factory and produce an api. On throw, the partially-constructed
|
|
2797
|
+
* state is rolled back (entries disposed in reverse) and the error is rethrown.
|
|
2798
|
+
*/
|
|
2799
|
+
construct(factory, props) {
|
|
2800
|
+
const ctx = this.buildCtx();
|
|
2801
|
+
let api;
|
|
2802
|
+
try {
|
|
2803
|
+
api = factory(ctx, props);
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
this.rollbackPartialConstruction();
|
|
2806
|
+
throw err;
|
|
2807
|
+
}
|
|
2808
|
+
this.state = "active";
|
|
2809
|
+
return api;
|
|
2810
|
+
}
|
|
2811
|
+
rollbackPartialConstruction() {
|
|
2812
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2813
|
+
const entry = this.entries[i];
|
|
2814
|
+
if (!entry) continue;
|
|
2815
|
+
try {
|
|
2816
|
+
this.disposeEntry(entry);
|
|
2817
|
+
} catch {}
|
|
2818
|
+
}
|
|
2819
|
+
this.entries.length = 0;
|
|
2820
|
+
this.state = "disposed";
|
|
2821
|
+
}
|
|
2822
|
+
dispose() {
|
|
2823
|
+
if (this.state === "disposed") return;
|
|
2824
|
+
this.state;
|
|
2825
|
+
this.state = "disposed";
|
|
2826
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2827
|
+
const entry = this.entries[i];
|
|
2828
|
+
if (!entry) continue;
|
|
2829
|
+
try {
|
|
2830
|
+
this.disposeEntry(entry);
|
|
2831
|
+
} catch (err) {
|
|
2832
|
+
dispatchError(this.rootShared.onError, err, {
|
|
2833
|
+
kind: "effect",
|
|
2834
|
+
controllerPath: this.path
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
this.entries.length = 0;
|
|
2839
|
+
this.scopes = null;
|
|
2840
|
+
}
|
|
2841
|
+
disposeEntry(entry) {
|
|
2842
|
+
switch (entry.kind) {
|
|
2843
|
+
case "effect":
|
|
2844
|
+
entry.dispose?.();
|
|
2845
|
+
entry.dispose = null;
|
|
2846
|
+
break;
|
|
2847
|
+
case "cleanup":
|
|
2848
|
+
entry.dispose();
|
|
2849
|
+
break;
|
|
2850
|
+
case "child":
|
|
2851
|
+
entry.instance.dispose();
|
|
2852
|
+
break;
|
|
2853
|
+
case "subscription":
|
|
2854
|
+
entry.unsubscribe();
|
|
2855
|
+
break;
|
|
2856
|
+
case "onDispose":
|
|
2857
|
+
entry.fn();
|
|
2858
|
+
break;
|
|
2859
|
+
case "onSuspend":
|
|
2860
|
+
case "onResume": break;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
suspend() {
|
|
2864
|
+
if (this.state !== "active") return;
|
|
2865
|
+
this.state = "suspended";
|
|
2866
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2867
|
+
const entry = this.entries[i];
|
|
2868
|
+
if (!entry) continue;
|
|
2869
|
+
try {
|
|
2870
|
+
switch (entry.kind) {
|
|
2871
|
+
case "effect":
|
|
2872
|
+
entry.dispose?.();
|
|
2873
|
+
entry.dispose = null;
|
|
2874
|
+
break;
|
|
2875
|
+
case "child":
|
|
2876
|
+
entry.instance.suspend();
|
|
2877
|
+
break;
|
|
2878
|
+
case "onSuspend":
|
|
2879
|
+
entry.fn();
|
|
2880
|
+
break;
|
|
2881
|
+
default: break;
|
|
2882
|
+
}
|
|
2883
|
+
} catch (err) {
|
|
2884
|
+
dispatchError(this.rootShared.onError, err, {
|
|
2885
|
+
kind: "effect",
|
|
2886
|
+
controllerPath: this.path
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
resume() {
|
|
2892
|
+
if (this.state !== "suspended") return;
|
|
2893
|
+
this.state = "active";
|
|
2894
|
+
for (const entry of this.entries) try {
|
|
2895
|
+
switch (entry.kind) {
|
|
2896
|
+
case "effect":
|
|
2897
|
+
entry.dispose = effect$1(entry.factory);
|
|
2898
|
+
break;
|
|
2899
|
+
case "child":
|
|
2900
|
+
entry.instance.resume();
|
|
2901
|
+
break;
|
|
2902
|
+
case "onResume":
|
|
2903
|
+
entry.fn();
|
|
2904
|
+
break;
|
|
2905
|
+
default: break;
|
|
2906
|
+
}
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
dispatchError(this.rootShared.onError, err, {
|
|
2909
|
+
kind: "effect",
|
|
2910
|
+
controllerPath: this.path
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
buildCtx() {
|
|
2915
|
+
const self = this;
|
|
2916
|
+
return {
|
|
2917
|
+
get deps() {
|
|
2918
|
+
return self.deps;
|
|
2919
|
+
},
|
|
2920
|
+
effect(fn) {
|
|
2921
|
+
if (self.isTerminal()) return;
|
|
2922
|
+
const entry = {
|
|
2923
|
+
kind: "effect",
|
|
2924
|
+
factory: () => fn(),
|
|
2925
|
+
dispose: null
|
|
2926
|
+
};
|
|
2927
|
+
const wrapped = () => {
|
|
2928
|
+
try {
|
|
2929
|
+
return fn();
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
dispatchError(self.rootShared.onError, err, {
|
|
2932
|
+
kind: "effect",
|
|
2933
|
+
controllerPath: self.path
|
|
2934
|
+
});
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
};
|
|
2938
|
+
entry.factory = wrapped;
|
|
2939
|
+
entry.dispose = effect$1(wrapped);
|
|
2940
|
+
self.entries.push(entry);
|
|
2941
|
+
},
|
|
2942
|
+
cache(fetcher, options) {
|
|
2943
|
+
const cache = createLocalCache(fetcher, options);
|
|
2944
|
+
self.entries.push({
|
|
2945
|
+
kind: "cleanup",
|
|
2946
|
+
dispose: () => cache.dispose()
|
|
2947
|
+
});
|
|
2948
|
+
return cache;
|
|
2949
|
+
},
|
|
2950
|
+
use(query, keyOrOptions) {
|
|
2951
|
+
if (query.__olas === "infiniteQuery") {
|
|
2952
|
+
const { subscription, dispose: d } = createInfiniteUse(self.rootShared.queryClient, query, keyOrOptions);
|
|
2953
|
+
self.entries.push({
|
|
2954
|
+
kind: "cleanup",
|
|
2955
|
+
dispose: d
|
|
2956
|
+
});
|
|
2957
|
+
return subscription;
|
|
2958
|
+
}
|
|
2959
|
+
const { subscription, dispose: d } = createUse(self.rootShared.queryClient, query, keyOrOptions);
|
|
2960
|
+
self.entries.push({
|
|
2961
|
+
kind: "cleanup",
|
|
2962
|
+
dispose: d
|
|
2963
|
+
});
|
|
2964
|
+
return subscription;
|
|
2965
|
+
},
|
|
2966
|
+
mutation(spec) {
|
|
2967
|
+
const m = createMutation(spec, self.rootShared.onError, self.path, self.rootShared.queryClient.mutationsInflight$, self.rootShared.devtools);
|
|
2968
|
+
self.entries.push({
|
|
2969
|
+
kind: "cleanup",
|
|
2970
|
+
dispose: () => m.dispose()
|
|
2971
|
+
});
|
|
2972
|
+
return m;
|
|
2973
|
+
},
|
|
2974
|
+
emitter() {
|
|
2975
|
+
const e = createEmitter();
|
|
2976
|
+
self.entries.push({
|
|
2977
|
+
kind: "cleanup",
|
|
2978
|
+
dispose: () => e.dispose()
|
|
2979
|
+
});
|
|
2980
|
+
return e;
|
|
2981
|
+
},
|
|
2982
|
+
field(initial, validators) {
|
|
2983
|
+
const f = createField(initial, validators);
|
|
2984
|
+
self.entries.push({
|
|
2985
|
+
kind: "cleanup",
|
|
2986
|
+
dispose: () => f.dispose()
|
|
2987
|
+
});
|
|
2988
|
+
bindFieldDevtoolsOwner(f, {
|
|
2989
|
+
controllerPath: self.path,
|
|
2990
|
+
fieldName: "(field)",
|
|
2991
|
+
emitter: self.rootShared.devtools
|
|
2992
|
+
});
|
|
2993
|
+
return f;
|
|
2994
|
+
},
|
|
2995
|
+
form(schema, options) {
|
|
2996
|
+
const f = createForm(schema, options);
|
|
2997
|
+
self.entries.push({
|
|
2998
|
+
kind: "cleanup",
|
|
2999
|
+
dispose: () => f.dispose()
|
|
3000
|
+
});
|
|
3001
|
+
const stop = bindTreeToDevtools(f, "", self.path, self.rootShared.devtools);
|
|
3002
|
+
self.entries.push({
|
|
3003
|
+
kind: "cleanup",
|
|
3004
|
+
dispose: stop
|
|
3005
|
+
});
|
|
3006
|
+
return f;
|
|
3007
|
+
},
|
|
3008
|
+
fieldArray(itemFactory, options) {
|
|
3009
|
+
const fa = createFieldArray(itemFactory, options);
|
|
3010
|
+
self.entries.push({
|
|
3011
|
+
kind: "cleanup",
|
|
3012
|
+
dispose: () => fa.dispose()
|
|
3013
|
+
});
|
|
3014
|
+
const stop = bindTreeToDevtools(fa, "", self.path, self.rootShared.devtools);
|
|
3015
|
+
self.entries.push({
|
|
3016
|
+
kind: "cleanup",
|
|
3017
|
+
dispose: stop
|
|
3018
|
+
});
|
|
3019
|
+
return fa;
|
|
3020
|
+
},
|
|
3021
|
+
provide(scope, value) {
|
|
3022
|
+
if (self.scopes === null) self.scopes = /* @__PURE__ */ new Map();
|
|
3023
|
+
self.scopes.set(scope.__id, value);
|
|
3024
|
+
},
|
|
3025
|
+
inject(scope) {
|
|
3026
|
+
let node = self;
|
|
3027
|
+
while (node !== null) {
|
|
3028
|
+
const map = node.scopes;
|
|
3029
|
+
if (map?.has(scope.__id)) return map.get(scope.__id);
|
|
3030
|
+
node = node.parent;
|
|
3031
|
+
}
|
|
3032
|
+
if (scope.hasDefault) return scope.default;
|
|
3033
|
+
const label = scope.name ?? scope.__id.description ?? "unnamed";
|
|
3034
|
+
throw new Error(`[olas] ctx.inject(): no provider for scope '${label}' and no default. Provide it on an ancestor via ctx.provide(${label}, ...) or pass a default to defineScope.`);
|
|
3035
|
+
},
|
|
3036
|
+
on(emitter, handler) {
|
|
3037
|
+
const wrapped = (value) => {
|
|
3038
|
+
try {
|
|
3039
|
+
handler(value);
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3042
|
+
kind: "emitter",
|
|
3043
|
+
controllerPath: self.path
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
const unsubscribe = emitter.on(wrapped);
|
|
3048
|
+
self.entries.push({
|
|
3049
|
+
kind: "subscription",
|
|
3050
|
+
unsubscribe
|
|
3051
|
+
});
|
|
3052
|
+
},
|
|
3053
|
+
child(def, props, options) {
|
|
3054
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3055
|
+
const override = options?.deps;
|
|
3056
|
+
const childDeps = override !== void 0 ? {
|
|
3057
|
+
...self.deps,
|
|
3058
|
+
...override
|
|
3059
|
+
} : self.deps;
|
|
3060
|
+
const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3061
|
+
const api = childInstance.construct(getFactory(def), props);
|
|
3062
|
+
self.entries.push({
|
|
3063
|
+
kind: "child",
|
|
3064
|
+
instance: childInstance
|
|
3065
|
+
});
|
|
3066
|
+
return api;
|
|
3067
|
+
},
|
|
3068
|
+
attach(def, props, options) {
|
|
3069
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3070
|
+
const override = options?.deps;
|
|
3071
|
+
const childDeps = override !== void 0 ? {
|
|
3072
|
+
...self.deps,
|
|
3073
|
+
...override
|
|
3074
|
+
} : self.deps;
|
|
3075
|
+
const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3076
|
+
const api = childInstance.construct(getFactory(def), props);
|
|
3077
|
+
const entry = {
|
|
3078
|
+
kind: "child",
|
|
3079
|
+
instance: childInstance
|
|
3080
|
+
};
|
|
3081
|
+
self.entries.push(entry);
|
|
3082
|
+
let disposed = false;
|
|
3083
|
+
return {
|
|
3084
|
+
api,
|
|
3085
|
+
dispose: () => {
|
|
3086
|
+
if (disposed) return;
|
|
3087
|
+
disposed = true;
|
|
3088
|
+
const idx = self.entries.indexOf(entry);
|
|
3089
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3090
|
+
try {
|
|
3091
|
+
childInstance.dispose();
|
|
3092
|
+
} catch (err) {
|
|
3093
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3094
|
+
kind: "effect",
|
|
3095
|
+
controllerPath: self.path
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
},
|
|
3101
|
+
onDispose(fn) {
|
|
3102
|
+
self.entries.push({
|
|
3103
|
+
kind: "onDispose",
|
|
3104
|
+
fn: () => {
|
|
3105
|
+
try {
|
|
3106
|
+
fn();
|
|
3107
|
+
} catch (err) {
|
|
3108
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3109
|
+
kind: "effect",
|
|
3110
|
+
controllerPath: self.path
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
});
|
|
3115
|
+
},
|
|
3116
|
+
onSuspend(fn) {
|
|
3117
|
+
self.entries.push({
|
|
3118
|
+
kind: "onSuspend",
|
|
3119
|
+
fn: () => {
|
|
3120
|
+
try {
|
|
3121
|
+
fn();
|
|
3122
|
+
} catch (err) {
|
|
3123
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3124
|
+
kind: "effect",
|
|
3125
|
+
controllerPath: self.path
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
});
|
|
3130
|
+
},
|
|
3131
|
+
onResume(fn) {
|
|
3132
|
+
self.entries.push({
|
|
3133
|
+
kind: "onResume",
|
|
3134
|
+
fn: () => {
|
|
3135
|
+
try {
|
|
3136
|
+
fn();
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3139
|
+
kind: "effect",
|
|
3140
|
+
controllerPath: self.path
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
isTerminal() {
|
|
3149
|
+
return this.state === "disposed";
|
|
3150
|
+
}
|
|
3151
|
+
makeChildSegment(factory, explicitName) {
|
|
3152
|
+
const idx = this.childCounter++;
|
|
3153
|
+
const base = explicitName ?? factory.name ?? "";
|
|
3154
|
+
return `${base !== "" ? base : "anonymous"}[${idx}]`;
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
//#endregion
|
|
3158
|
+
//#region src/controller/root.ts
|
|
3159
|
+
const ROOT_METHODS = [
|
|
3160
|
+
"dispose",
|
|
3161
|
+
"suspend",
|
|
3162
|
+
"resume",
|
|
3163
|
+
"dehydrate",
|
|
3164
|
+
"waitForIdle",
|
|
3165
|
+
"__debug"
|
|
3166
|
+
];
|
|
3167
|
+
/**
|
|
3168
|
+
* Construct a root controller.
|
|
3169
|
+
*
|
|
3170
|
+
* Internal: this is the shared engine. The public `createRoot` (props-less)
|
|
3171
|
+
* and `createTestController` (props-allowing) both call through here.
|
|
3172
|
+
*/
|
|
3173
|
+
function createRootWithProps(def, props, options) {
|
|
3174
|
+
const devtools = new DevtoolsEmitter();
|
|
3175
|
+
const queryClient = new QueryClient({
|
|
3176
|
+
onError: options.onError,
|
|
3177
|
+
hydrate: options.hydrate,
|
|
3178
|
+
devtools,
|
|
3179
|
+
deps: options.deps,
|
|
3180
|
+
refetchOnWindowFocus: options.refetchOnWindowFocus,
|
|
3181
|
+
refetchOnReconnect: options.refetchOnReconnect,
|
|
3182
|
+
plugins: options.plugins
|
|
3183
|
+
});
|
|
3184
|
+
const instance = new ControllerInstance(null, {
|
|
3185
|
+
devtools,
|
|
3186
|
+
onError: options.onError,
|
|
3187
|
+
queryClient
|
|
3188
|
+
}, "root", options.deps);
|
|
3189
|
+
const api = instance.construct(getFactory(def), props);
|
|
3190
|
+
if (typeof api !== "object" || api === null) return attachRootControls({ value: api }, instance, devtools, queryClient);
|
|
3191
|
+
return attachRootControls(api, instance, devtools, queryClient);
|
|
3192
|
+
}
|
|
3193
|
+
function attachRootControls(api, instance, devtools, queryClient) {
|
|
3194
|
+
let suspendTimer = null;
|
|
3195
|
+
const dispose = () => {
|
|
3196
|
+
if (suspendTimer != null) {
|
|
3197
|
+
clearTimeout(suspendTimer);
|
|
3198
|
+
suspendTimer = null;
|
|
3199
|
+
}
|
|
3200
|
+
instance.dispose();
|
|
3201
|
+
queryClient.dispose();
|
|
3202
|
+
};
|
|
3203
|
+
const suspend = (opts) => {
|
|
3204
|
+
instance.suspend();
|
|
3205
|
+
if (suspendTimer != null) {
|
|
3206
|
+
clearTimeout(suspendTimer);
|
|
3207
|
+
suspendTimer = null;
|
|
3208
|
+
}
|
|
3209
|
+
const maxIdle = opts?.maxIdle;
|
|
3210
|
+
if (maxIdle != null && maxIdle !== Number.POSITIVE_INFINITY) suspendTimer = setTimeout(() => {
|
|
3211
|
+
suspendTimer = null;
|
|
3212
|
+
dispose();
|
|
3213
|
+
}, maxIdle);
|
|
3214
|
+
};
|
|
3215
|
+
const resume = () => {
|
|
3216
|
+
if (suspendTimer != null) {
|
|
3217
|
+
clearTimeout(suspendTimer);
|
|
3218
|
+
suspendTimer = null;
|
|
3219
|
+
}
|
|
3220
|
+
instance.resume();
|
|
3221
|
+
};
|
|
3222
|
+
const debug = {
|
|
3223
|
+
subscribe: (handler) => devtools.subscribe(handler),
|
|
3224
|
+
queryEntries: () => queryClient.queryEntriesSnapshot()
|
|
3225
|
+
};
|
|
3226
|
+
const target = api;
|
|
3227
|
+
for (const method of ROOT_METHODS) if (Object.hasOwn(target, method)) throw new Error(`[olas] Root controller api defines '${method}' which conflicts with the root controls.`);
|
|
3228
|
+
Object.defineProperty(target, "dispose", {
|
|
3229
|
+
value: dispose,
|
|
3230
|
+
enumerable: false,
|
|
3231
|
+
configurable: true
|
|
3232
|
+
});
|
|
3233
|
+
Object.defineProperty(target, "suspend", {
|
|
3234
|
+
value: suspend,
|
|
3235
|
+
enumerable: false,
|
|
3236
|
+
configurable: true
|
|
3237
|
+
});
|
|
3238
|
+
Object.defineProperty(target, "resume", {
|
|
3239
|
+
value: resume,
|
|
3240
|
+
enumerable: false,
|
|
3241
|
+
configurable: true
|
|
3242
|
+
});
|
|
3243
|
+
Object.defineProperty(target, "__debug", {
|
|
3244
|
+
value: debug,
|
|
3245
|
+
enumerable: false,
|
|
3246
|
+
configurable: true
|
|
3247
|
+
});
|
|
3248
|
+
Object.defineProperty(target, "dehydrate", {
|
|
3249
|
+
value: () => queryClient.dehydrate(),
|
|
3250
|
+
enumerable: false,
|
|
3251
|
+
configurable: true
|
|
3252
|
+
});
|
|
3253
|
+
Object.defineProperty(target, "waitForIdle", {
|
|
3254
|
+
value: () => queryClient.waitForIdle(),
|
|
3255
|
+
enumerable: false,
|
|
3256
|
+
configurable: true
|
|
3257
|
+
});
|
|
3258
|
+
return api;
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Construct a root controller. Root factories take no props — startup config
|
|
3262
|
+
* goes in `deps`.
|
|
3263
|
+
*/
|
|
3264
|
+
function createRoot(def, options) {
|
|
3265
|
+
return createRootWithProps(def, void 0, options);
|
|
3266
|
+
}
|
|
3267
|
+
//#endregion
|
|
3268
|
+
export { lookupRegisteredQuery as a, batch$1 as c, signal$1 as d, untracked$1 as f, createEmitter as i, computed$1 as l, createRootWithProps as n, registerQueryById as o, defineController as p, debouncedValidator as r, isAbortError as s, createRoot as t, effect$1 as u };
|
|
3269
|
+
|
|
3270
|
+
//# sourceMappingURL=root-BImHnGj1.mjs.map
|