@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,816 @@
|
|
|
1
|
+
//#region src/emitter.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Synchronous fan-out event bus. Handlers run in the order they subscribed.
|
|
4
|
+
* Handlers added during emit don't fire for the current emission; handlers
|
|
5
|
+
* removed during emit are skipped from that point in the iteration.
|
|
6
|
+
*
|
|
7
|
+
* `Emitter<void>` exposes `emit()` (no argument); other shapes expose
|
|
8
|
+
* `emit(value: T)`.
|
|
9
|
+
*
|
|
10
|
+
* Spec §7, §20.6.
|
|
11
|
+
*/
|
|
12
|
+
type Emitter<T> = {
|
|
13
|
+
emit: [T] extends [void] ? () => void : (value: T) => void; /** Subscribe to every emission. Returns the unsubscribe function. */
|
|
14
|
+
on(handler: (value: T) => void): () => void; /** Subscribe to the next emission only. Auto-unsubscribes after firing. */
|
|
15
|
+
once(handler: (value: T) => void): () => void; /** Tear down the emitter. Subsequent `emit` / `on` / `once` are no-ops. */
|
|
16
|
+
dispose(): void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Create a standalone emitter. Handlers persist until explicitly unsubscribed
|
|
20
|
+
* (or the emitter is disposed). Use this for emitters that live outside any
|
|
21
|
+
* single controller — typically in deps. Use `ctx.emitter()` for emitters that
|
|
22
|
+
* should auto-clean with a controller.
|
|
23
|
+
*/
|
|
24
|
+
declare function createEmitter<T = void>(): Emitter<T>;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/errors.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* Context passed to a root's `onError` handler. `kind` identifies where in
|
|
29
|
+
* the controller's surface the throw originated; `controllerPath` is the
|
|
30
|
+
* path from root to the controller that owned the failing code; `queryKey`
|
|
31
|
+
* is set for `cache` kinds. Spec §12, §20.9.
|
|
32
|
+
*
|
|
33
|
+
* `'plugin'` is used for exceptions raised by `QueryClientPlugin` callbacks
|
|
34
|
+
* (`@kontsedal/olas-cross-tab` and friends); SPEC §13.2.
|
|
35
|
+
*/
|
|
36
|
+
type ErrorContext = {
|
|
37
|
+
kind: 'effect' | 'cache' | 'mutation' | 'emitter' | 'construction' | 'plugin';
|
|
38
|
+
controllerPath: readonly string[];
|
|
39
|
+
queryKey?: readonly unknown[];
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/signals/types.d.ts
|
|
43
|
+
/**
|
|
44
|
+
* Read-only reactive value. Reading `.value` inside a tracking scope
|
|
45
|
+
* (`computed` / `effect`) registers a dependency; `peek()` reads without
|
|
46
|
+
* tracking; `subscribe(handler)` fires `handler` immediately with the current
|
|
47
|
+
* value and on every change until the returned unsubscribe is called.
|
|
48
|
+
*/
|
|
49
|
+
type ReadSignal<T> = {
|
|
50
|
+
readonly value: T; /** Read the current value without registering a dependency. */
|
|
51
|
+
peek(): T;
|
|
52
|
+
/**
|
|
53
|
+
* Subscribe to value changes. The handler is called synchronously with the
|
|
54
|
+
* current value on subscribe and on every change thereafter. Returns the
|
|
55
|
+
* unsubscribe function.
|
|
56
|
+
*/
|
|
57
|
+
subscribe(handler: (value: T) => void): () => void;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Writable reactive value. `value` is assignable; `set(value)` is the
|
|
61
|
+
* functional equivalent; `update(fn)` reads (peek) and writes the result of
|
|
62
|
+
* `fn(previous)`.
|
|
63
|
+
*/
|
|
64
|
+
type Signal<T> = ReadSignal<T> & {
|
|
65
|
+
value: T;
|
|
66
|
+
set(value: T): void;
|
|
67
|
+
update(fn: (prev: T) => T): void;
|
|
68
|
+
};
|
|
69
|
+
/** A read-only derived signal — alias of `ReadSignal<T>`. */
|
|
70
|
+
type Computed<T> = ReadSignal<T>;
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/forms/types.d.ts
|
|
73
|
+
type Validator<T> = (value: T, signal: AbortSignal) => string | null | Promise<string | null>;
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/forms/form-types.d.ts
|
|
76
|
+
type FormSchema = {
|
|
77
|
+
[key: string]: Field<any> | Form<any> | FieldArray<any>;
|
|
78
|
+
};
|
|
79
|
+
type FormValue<S extends FormSchema> = { [K in keyof S]: S[K] extends Field<infer T> ? T : S[K] extends Form<infer SS> ? FormValue<SS> : S[K] extends FieldArray<infer I> ? FieldArrayValue<I> : never };
|
|
80
|
+
type FormErrors<S extends FormSchema> = { [K in keyof S]?: S[K] extends Field<any> ? string[] | undefined : S[K] extends Form<infer SS> ? FormErrors<SS> : S[K] extends FieldArray<infer I> ? Array<FieldArrayItemErrors<I> | undefined> : never };
|
|
81
|
+
type FieldArrayValue<I> = I extends Field<infer T> ? T[] : I extends Form<infer S> ? FormValue<S>[] : never;
|
|
82
|
+
type FieldArrayItemErrors<I> = I extends Field<any> ? string[] : I extends Form<infer S> ? FormErrors<S> : never;
|
|
83
|
+
type ItemInitial<I> = I extends Field<infer T> ? T : I extends Form<infer S> ? DeepPartial<FormValue<S>> : never;
|
|
84
|
+
type DeepPartial<T> = T extends object ? T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
|
85
|
+
type FormValidator<S extends FormSchema> = Validator<FormValue<S>>;
|
|
86
|
+
type FieldArrayValidator<I> = Validator<FieldArrayValue<I>>;
|
|
87
|
+
type FormOptions<S extends FormSchema> = {
|
|
88
|
+
initial?: (() => DeepPartial<FormValue<S>> | undefined) | DeepPartial<FormValue<S>>;
|
|
89
|
+
validators?: FormValidator<S>[];
|
|
90
|
+
};
|
|
91
|
+
type FieldArrayOptions<I> = {
|
|
92
|
+
initial?: Array<ItemInitial<I>>;
|
|
93
|
+
validators?: FieldArrayValidator<I>[];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* A nested form. Created via `ctx.form(schema, options?)`. `value` aggregates
|
|
97
|
+
* every leaf into the structurally-typed `FormValue<S>`; `errors` mirrors that
|
|
98
|
+
* shape with `string[] | undefined`. `flatErrors` is a flattened view useful
|
|
99
|
+
* for rendering a single error summary. Spec §8, §20.7.
|
|
100
|
+
*
|
|
101
|
+
* IMPORTANT: `Form.value` is a `ReadSignal<FormValue<S>>` while `Field.value`
|
|
102
|
+
* is `T` directly — different shapes. See `.wiki/pitfalls/field-value-shape.md`.
|
|
103
|
+
*/
|
|
104
|
+
type Form<S extends FormSchema> = {
|
|
105
|
+
readonly fields: { [K in keyof S]: S[K] };
|
|
106
|
+
readonly value: ReadSignal<FormValue<S>>;
|
|
107
|
+
readonly errors: ReadSignal<FormErrors<S>>;
|
|
108
|
+
readonly topLevelErrors: ReadSignal<string[]>;
|
|
109
|
+
readonly flatErrors: ReadSignal<Array<{
|
|
110
|
+
path: string;
|
|
111
|
+
errors: string[];
|
|
112
|
+
}>>;
|
|
113
|
+
readonly isValid: ReadSignal<boolean>;
|
|
114
|
+
readonly isDirty: ReadSignal<boolean>;
|
|
115
|
+
readonly touched: ReadSignal<boolean>;
|
|
116
|
+
readonly isValidating: ReadSignal<boolean>; /** Deep-merge a partial value into the form, batched. */
|
|
117
|
+
set(partial: DeepPartial<FormValue<S>>): void;
|
|
118
|
+
/**
|
|
119
|
+
* Re-seat the form's leaves from `partial` as their new initials —
|
|
120
|
+
* each leaf calls `setAsInitial(value)`, so `isDirty` stays false and a
|
|
121
|
+
* subsequent `reset()` returns *here*. Internal-ish but exported for
|
|
122
|
+
* `Form`-traversal code (nested-form initial application).
|
|
123
|
+
*/
|
|
124
|
+
resetWithInitial(partial: DeepPartial<FormValue<S>>): void; /** Reset every leaf to its initial value. */
|
|
125
|
+
reset(): void; /** Mark every leaf as touched (so error messages appear). */
|
|
126
|
+
markAllTouched(): void; /** Re-run every leaf's validators. Resolves with true if all leaves are valid. */
|
|
127
|
+
validate(): Promise<boolean>; /** Idempotent. Called by the owning controller's dispose. */
|
|
128
|
+
dispose(): void;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* A dynamically-sized list of `Field` or `Form` items. Created via
|
|
132
|
+
* `ctx.fieldArray(itemFactory, options?)`. The factory is invoked per
|
|
133
|
+
* insertion. Spec §8, §20.7.
|
|
134
|
+
*/
|
|
135
|
+
type FieldArray<I extends Field<any> | Form<any>> = {
|
|
136
|
+
readonly items: ReadSignal<ReadonlyArray<I>>;
|
|
137
|
+
readonly value: ReadSignal<FieldArrayValue<I>>;
|
|
138
|
+
readonly errors: ReadSignal<Array<FieldArrayItemErrors<I> | undefined>>;
|
|
139
|
+
readonly topLevelErrors: ReadSignal<string[]>;
|
|
140
|
+
readonly isValid: ReadSignal<boolean>;
|
|
141
|
+
readonly isDirty: ReadSignal<boolean>;
|
|
142
|
+
readonly touched: ReadSignal<boolean>;
|
|
143
|
+
readonly isValidating: ReadSignal<boolean>;
|
|
144
|
+
readonly size: ReadSignal<number>;
|
|
145
|
+
add(initial?: ItemInitial<I>): void;
|
|
146
|
+
insert(index: number, initial?: ItemInitial<I>): void;
|
|
147
|
+
remove(index: number): void;
|
|
148
|
+
move(from: number, to: number): void;
|
|
149
|
+
at(index: number): I | undefined;
|
|
150
|
+
clear(): void;
|
|
151
|
+
reset(): void;
|
|
152
|
+
markAllTouched(): void;
|
|
153
|
+
validate(): Promise<boolean>;
|
|
154
|
+
dispose(): void;
|
|
155
|
+
};
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/query/types.d.ts
|
|
158
|
+
/** Lifecycle phase of an async resource. */
|
|
159
|
+
type AsyncStatus = 'idle' | 'pending' | 'success' | 'error';
|
|
160
|
+
/**
|
|
161
|
+
* The eight reactive signals + three actions a subscriber sees for any async
|
|
162
|
+
* resource (`LocalCache<T>` or a `Query` subscription). Spec §20.4.
|
|
163
|
+
*
|
|
164
|
+
* - `data` / `error` / `status` — current outcome.
|
|
165
|
+
* - `isLoading` — true only on the first pending fetch (no `data` yet).
|
|
166
|
+
* - `isFetching` — true on any pending fetch.
|
|
167
|
+
* - `isStale` — true when `staleTime` has elapsed since `lastUpdatedAt`.
|
|
168
|
+
* - `lastUpdatedAt` — epoch ms of last success.
|
|
169
|
+
* - `hasPendingMutations` — at least one mutation has a snapshot on this entry.
|
|
170
|
+
*
|
|
171
|
+
* Actions:
|
|
172
|
+
* - `refetch()` — force a fetch; resolves with the result.
|
|
173
|
+
* - `reset()` — clear `error` + `status` without re-fetching.
|
|
174
|
+
* - `firstValue()` — resolves on the first success after subscribe.
|
|
175
|
+
*/
|
|
176
|
+
type AsyncState<T> = {
|
|
177
|
+
data: ReadSignal<T | undefined>;
|
|
178
|
+
error: ReadSignal<unknown | undefined>;
|
|
179
|
+
status: ReadSignal<AsyncStatus>;
|
|
180
|
+
isLoading: ReadSignal<boolean>;
|
|
181
|
+
isFetching: ReadSignal<boolean>;
|
|
182
|
+
isStale: ReadSignal<boolean>;
|
|
183
|
+
lastUpdatedAt: ReadSignal<number | undefined>;
|
|
184
|
+
hasPendingMutations: ReadSignal<boolean>;
|
|
185
|
+
refetch: () => Promise<T>;
|
|
186
|
+
reset: () => void;
|
|
187
|
+
firstValue: () => Promise<T>;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Returned by `query.setData(...)` or `localCache.setData(...)`. Used by
|
|
191
|
+
* `mutation.onMutate` for optimistic-update rollback (spec §6.4).
|
|
192
|
+
*
|
|
193
|
+
* - `rollback()` restores the previous data state (and clears the
|
|
194
|
+
* "pending mutation" flag on the entry if no other snapshots are live).
|
|
195
|
+
* - `finalize()` commits the snapshot as the new truth — no rollback,
|
|
196
|
+
* `hasPendingMutations` clears once all live snapshots on the entry
|
|
197
|
+
* are finalized or rolled back. The mutation runner calls this on
|
|
198
|
+
* success; user code rarely needs to.
|
|
199
|
+
*
|
|
200
|
+
* Both are idempotent and mutually exclusive (calling one disables the
|
|
201
|
+
* other). Safe to call after the owning entry has been disposed.
|
|
202
|
+
*/
|
|
203
|
+
type Snapshot = {
|
|
204
|
+
rollback: () => void;
|
|
205
|
+
finalize: () => void;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* A cache owned by one controller — no sharing across the tree. Returned by
|
|
209
|
+
* `ctx.cache(fetcher, options?)`. Disposed automatically with the controller.
|
|
210
|
+
*/
|
|
211
|
+
type LocalCache<T> = AsyncState<T> & {
|
|
212
|
+
/** Mark stale and trigger an immediate refetch. */invalidate(): void; /** Patch the current data. Returns a `Snapshot` for rollback. */
|
|
213
|
+
setData(updater: (prev: T | undefined) => T): Snapshot; /** Idempotent — also called when the owning controller disposes. */
|
|
214
|
+
dispose(): void;
|
|
215
|
+
};
|
|
216
|
+
/** One entry inside a `DehydratedState`. */
|
|
217
|
+
type DehydratedEntry = {
|
|
218
|
+
key: readonly unknown[];
|
|
219
|
+
data: unknown;
|
|
220
|
+
lastUpdatedAt: number;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* SSR-serializable snapshot of a root's `QueryClient`. Produced by
|
|
224
|
+
* `root.dehydrate()` on the server; consumed by
|
|
225
|
+
* `createRoot(def, { hydrate: state })` on the client. Spec §15, §20.9.
|
|
226
|
+
*/
|
|
227
|
+
type DehydratedState = {
|
|
228
|
+
version: 1;
|
|
229
|
+
entries: DehydratedEntry[];
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Retry policy for queries and mutations. A number is a max-attempt count
|
|
233
|
+
* (default backoff). A function decides per-attempt (return `true` to retry).
|
|
234
|
+
*/
|
|
235
|
+
type RetryPolicy = number | ((attempt: number, error: unknown) => boolean);
|
|
236
|
+
/** Backoff in ms. A number is constant delay; a function computes per-attempt. */
|
|
237
|
+
type RetryDelay = number | ((attempt: number) => number);
|
|
238
|
+
/**
|
|
239
|
+
* Per-fetch context: the `AbortSignal` to honor + the root's `deps`. Passed
|
|
240
|
+
* as the first argument to every `QuerySpec.fetcher` invocation so module-
|
|
241
|
+
* level queries can reach their dependencies without resorting to globals.
|
|
242
|
+
*/
|
|
243
|
+
type FetchCtx = {
|
|
244
|
+
signal: AbortSignal;
|
|
245
|
+
deps: AmbientDeps;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Configuration passed to `defineQuery({ ... })`. The `Args` tuple is what
|
|
249
|
+
* callers pass as cache keys and to the fetcher. Spec §20.4.
|
|
250
|
+
*
|
|
251
|
+
* The fetcher's first argument is a `FetchCtx` (signal + deps); positional
|
|
252
|
+
* cache args come after. This shape lets module-scoped queries read
|
|
253
|
+
* `ctx.deps.api` etc. — no `setApiForQuery(api)` module-level capture needed.
|
|
254
|
+
*/
|
|
255
|
+
type QuerySpec<Args extends unknown[], T> = {
|
|
256
|
+
key: (...args: Args) => unknown[];
|
|
257
|
+
fetcher: (ctx: FetchCtx, ...args: Args) => Promise<T>;
|
|
258
|
+
staleTime?: number;
|
|
259
|
+
gcTime?: number;
|
|
260
|
+
refetchInterval?: number;
|
|
261
|
+
refetchOnWindowFocus?: boolean;
|
|
262
|
+
refetchOnReconnect?: boolean;
|
|
263
|
+
keepPreviousData?: boolean;
|
|
264
|
+
retry?: RetryPolicy;
|
|
265
|
+
retryDelay?: RetryDelay;
|
|
266
|
+
/**
|
|
267
|
+
* Stable identifier used by `QueryClientPlugin`s (e.g. `@kontsedal/olas-cross-tab`)
|
|
268
|
+
* to locate the same query across tabs / processes / persistence layers.
|
|
269
|
+
* REQUIRED for queries with `crossTab: true`. SPEC §13.2.
|
|
270
|
+
*
|
|
271
|
+
* Don't auto-derive from `fetcher.name` or argument hashing — both are
|
|
272
|
+
* fragile under minification.
|
|
273
|
+
*/
|
|
274
|
+
queryId?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Opt this query into cross-tab cache sync (`@kontsedal/olas-cross-tab`). No effect
|
|
277
|
+
* without a `queryId` and without a plugin installed. SPEC §13.2.
|
|
278
|
+
*/
|
|
279
|
+
crossTab?: boolean;
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* A module-scoped shared query handle. Bind a subscriber via
|
|
283
|
+
* `ctx.use(query, () => [...args])`. The same `Query` value can be used by
|
|
284
|
+
* many controllers across many roots — each root has its own cache.
|
|
285
|
+
*/
|
|
286
|
+
type Query<Args extends unknown[], T> = {
|
|
287
|
+
readonly __olas: 'query'; /** Mark a specific keyed entry stale + trigger refetch if any subscribers. */
|
|
288
|
+
invalidate(...args: Args): void; /** Mark every keyed entry stale + trigger refetch on all subscribers. */
|
|
289
|
+
invalidateAll(): void; /** Patch the current data for a specific key. Returns a `Snapshot` for rollback. */
|
|
290
|
+
setData(...args: [...Args, updater: (prev: T | undefined) => T]): Snapshot; /** Eagerly fetch into the cache without subscribing. */
|
|
291
|
+
prefetch(...args: Args): Promise<T>;
|
|
292
|
+
};
|
|
293
|
+
/** What `ctx.use(query, ...)` returns. Alias of `AsyncState<T>`. */
|
|
294
|
+
type QuerySubscription<T> = AsyncState<T>;
|
|
295
|
+
/**
|
|
296
|
+
* Options passed to `ctx.use(query, opts)` to control the subscription
|
|
297
|
+
* (reactive key, enabled-gating). The `key` thunk reads signals — re-evaluating
|
|
298
|
+
* when they change re-keys the subscription.
|
|
299
|
+
*/
|
|
300
|
+
type UseOptions<Args extends readonly unknown[]> = {
|
|
301
|
+
key?: () => Args;
|
|
302
|
+
enabled?: () => boolean;
|
|
303
|
+
};
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/query/infinite.d.ts
|
|
306
|
+
/**
|
|
307
|
+
* Configuration for `defineInfiniteQuery({ ... })`. Spec §5.7, §20.4.
|
|
308
|
+
*
|
|
309
|
+
* - `getNextPageParam(lastPage, allPages)` returns the param for the next
|
|
310
|
+
* page, or `null` when there's no more.
|
|
311
|
+
* - `getPreviousPageParam` (optional) enables bidirectional infinite lists.
|
|
312
|
+
* - `itemsOf(page)` (optional) flattens pages into items for the
|
|
313
|
+
* `subscription.flat` convenience signal.
|
|
314
|
+
*/
|
|
315
|
+
type InfiniteFetchCtx<PageParam> = {
|
|
316
|
+
pageParam: PageParam;
|
|
317
|
+
signal: AbortSignal;
|
|
318
|
+
deps: AmbientDeps;
|
|
319
|
+
};
|
|
320
|
+
type InfiniteQuerySpec<Args extends unknown[], PageParam, TPage, TItem = TPage> = {
|
|
321
|
+
key: (...args: Args) => unknown[];
|
|
322
|
+
/**
|
|
323
|
+
* Fetcher receives an `InfiniteFetchCtx` (pageParam + signal + deps) as
|
|
324
|
+
* the first arg and positional cache args after. See `FetchCtx` for the
|
|
325
|
+
* regular-query analogue.
|
|
326
|
+
*/
|
|
327
|
+
fetcher: (ctx: InfiniteFetchCtx<PageParam>, ...args: Args) => Promise<TPage>;
|
|
328
|
+
initialPageParam: PageParam;
|
|
329
|
+
getNextPageParam: (lastPage: TPage, allPages: TPage[]) => PageParam | null;
|
|
330
|
+
getPreviousPageParam?: (firstPage: TPage, allPages: TPage[]) => PageParam | null;
|
|
331
|
+
itemsOf?: (page: TPage) => TItem[];
|
|
332
|
+
staleTime?: number;
|
|
333
|
+
gcTime?: number;
|
|
334
|
+
refetchInterval?: number;
|
|
335
|
+
keepPreviousData?: boolean;
|
|
336
|
+
retry?: RetryPolicy;
|
|
337
|
+
retryDelay?: RetryDelay;
|
|
338
|
+
/**
|
|
339
|
+
* Stable identifier used by `QueryClientPlugin`s (`@kontsedal/olas-cross-tab`,
|
|
340
|
+
* etc.). Infinite queries do NOT propagate cross-tab in v1 — the
|
|
341
|
+
* page-array payload is too heavy to be a safe default — but the field is
|
|
342
|
+
* accepted for forward compatibility. SPEC §13.2.
|
|
343
|
+
*/
|
|
344
|
+
queryId?: string;
|
|
345
|
+
/**
|
|
346
|
+
* Opt into cross-tab sync. No effect for infinite queries in v1 (see
|
|
347
|
+
* `queryId` doc above).
|
|
348
|
+
*/
|
|
349
|
+
crossTab?: boolean;
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* Module-scoped handle for a paginated query. Mirrors `Query<Args, TPage[]>`
|
|
353
|
+
* with paginated `setData` semantics.
|
|
354
|
+
*/
|
|
355
|
+
type InfiniteQuery<Args extends unknown[], TPage, _TItem> = {
|
|
356
|
+
readonly __olas: 'infiniteQuery';
|
|
357
|
+
invalidate(...args: Args): void;
|
|
358
|
+
invalidateAll(): void;
|
|
359
|
+
setData(...args: [...Args, updater: (prev: TPage[] | undefined) => TPage[]]): {
|
|
360
|
+
rollback: () => void;
|
|
361
|
+
};
|
|
362
|
+
prefetch(...args: Args): Promise<TPage>;
|
|
363
|
+
};
|
|
364
|
+
/**
|
|
365
|
+
* What `ctx.use(infiniteQuery, ...)` returns. Extends `AsyncState<TPage[]>`
|
|
366
|
+
* with paginated controls: `fetchNextPage` / `fetchPreviousPage`,
|
|
367
|
+
* `hasNextPage` / `hasPreviousPage`, and per-direction `isFetching` signals.
|
|
368
|
+
*
|
|
369
|
+
* `flat` is a convenience: present when the query spec provides `itemsOf` —
|
|
370
|
+
* otherwise it's an empty array.
|
|
371
|
+
*/
|
|
372
|
+
type InfiniteQuerySubscription<TPage, TItem> = AsyncState<TPage[]> & {
|
|
373
|
+
pages: ReadSignal<TPage[]>;
|
|
374
|
+
flat: ReadSignal<TItem[]>;
|
|
375
|
+
hasNextPage: ReadSignal<boolean>;
|
|
376
|
+
hasPreviousPage: ReadSignal<boolean>;
|
|
377
|
+
isFetchingNextPage: ReadSignal<boolean>;
|
|
378
|
+
isFetchingPreviousPage: ReadSignal<boolean>;
|
|
379
|
+
fetchNextPage: () => Promise<void>;
|
|
380
|
+
fetchPreviousPage: () => Promise<void>;
|
|
381
|
+
};
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/devtools.d.ts
|
|
384
|
+
/**
|
|
385
|
+
* Discriminated union of devtools events emitted by a root via
|
|
386
|
+
* `root.__debug.subscribe(handler)`. Spec §20.9. Adding new variants is
|
|
387
|
+
* non-breaking — consumers `switch` on `type` and ignore unknowns.
|
|
388
|
+
*/
|
|
389
|
+
type DebugEvent = {
|
|
390
|
+
type: 'controller:constructed';
|
|
391
|
+
path: readonly string[];
|
|
392
|
+
props: unknown;
|
|
393
|
+
} | {
|
|
394
|
+
type: 'controller:suspended';
|
|
395
|
+
path: readonly string[];
|
|
396
|
+
} | {
|
|
397
|
+
type: 'controller:resumed';
|
|
398
|
+
path: readonly string[];
|
|
399
|
+
} | {
|
|
400
|
+
type: 'controller:disposed';
|
|
401
|
+
path: readonly string[];
|
|
402
|
+
} | {
|
|
403
|
+
type: 'cache:subscribed';
|
|
404
|
+
queryKey: readonly unknown[];
|
|
405
|
+
subscriberPath: readonly string[];
|
|
406
|
+
} | {
|
|
407
|
+
type: 'cache:fetch-start';
|
|
408
|
+
queryKey: readonly unknown[];
|
|
409
|
+
} | {
|
|
410
|
+
type: 'cache:fetch-success';
|
|
411
|
+
queryKey: readonly unknown[];
|
|
412
|
+
durationMs: number;
|
|
413
|
+
} | {
|
|
414
|
+
type: 'cache:fetch-error';
|
|
415
|
+
queryKey: readonly unknown[];
|
|
416
|
+
error: unknown;
|
|
417
|
+
durationMs: number;
|
|
418
|
+
} | {
|
|
419
|
+
type: 'cache:invalidated';
|
|
420
|
+
queryKey: readonly unknown[];
|
|
421
|
+
} | {
|
|
422
|
+
type: 'cache:gc';
|
|
423
|
+
queryKey: readonly unknown[];
|
|
424
|
+
} | {
|
|
425
|
+
type: 'mutation:run';
|
|
426
|
+
path: readonly string[];
|
|
427
|
+
name?: string;
|
|
428
|
+
vars: unknown;
|
|
429
|
+
} | {
|
|
430
|
+
type: 'mutation:success';
|
|
431
|
+
path: readonly string[];
|
|
432
|
+
name?: string;
|
|
433
|
+
result: unknown;
|
|
434
|
+
} | {
|
|
435
|
+
type: 'mutation:error';
|
|
436
|
+
path: readonly string[];
|
|
437
|
+
name?: string;
|
|
438
|
+
error: unknown;
|
|
439
|
+
} | {
|
|
440
|
+
type: 'mutation:rollback';
|
|
441
|
+
path: readonly string[];
|
|
442
|
+
name?: string;
|
|
443
|
+
} | {
|
|
444
|
+
type: 'field:validated';
|
|
445
|
+
path: readonly string[];
|
|
446
|
+
field: string;
|
|
447
|
+
valid: boolean;
|
|
448
|
+
errors: string[];
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Snapshot of one live cache entry — produced by `root.__debug.queryEntries()`
|
|
452
|
+
* so devtools panels can show *current data*, not just past fetch events.
|
|
453
|
+
*/
|
|
454
|
+
type DebugCacheEntry = {
|
|
455
|
+
key: readonly unknown[];
|
|
456
|
+
status: 'idle' | 'pending' | 'success' | 'error';
|
|
457
|
+
data: unknown;
|
|
458
|
+
error: unknown;
|
|
459
|
+
lastUpdatedAt: number | undefined;
|
|
460
|
+
isStale: boolean;
|
|
461
|
+
isFetching: boolean;
|
|
462
|
+
hasPendingMutations: boolean;
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* The shape of `root.__debug`. Subscribe to receive every `DebugEvent` until
|
|
466
|
+
* the returned unsubscribe is called.
|
|
467
|
+
*
|
|
468
|
+
* The bus replays a snapshot of the *live controller tree* to every new
|
|
469
|
+
* subscriber synchronously inside `subscribe(...)` — so a panel that mounts
|
|
470
|
+
* after `createRoot()` sees the existing tree immediately, not just future
|
|
471
|
+
* events. Event types other than `controller:*` are not buffered.
|
|
472
|
+
*
|
|
473
|
+
* `queryEntries()` returns a fresh inspector snapshot — current state of
|
|
474
|
+
* every cached entry. Useful for "what's in the cache right now?" views.
|
|
475
|
+
*/
|
|
476
|
+
type DebugBus = {
|
|
477
|
+
subscribe(handler: (event: DebugEvent) => void): () => void;
|
|
478
|
+
queryEntries(): DebugCacheEntry[];
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/query/mutation.d.ts
|
|
482
|
+
/**
|
|
483
|
+
* How concurrent calls to `mutation.run(...)` interact:
|
|
484
|
+
* - `parallel` (default): every call runs concurrently.
|
|
485
|
+
* - `latest-wins`: a new call aborts any in-flight previous call (`AbortSignal` fires).
|
|
486
|
+
* - `serial`: calls queue and run one at a time in order.
|
|
487
|
+
*
|
|
488
|
+
* Spec §6.3.
|
|
489
|
+
*/
|
|
490
|
+
type MutationConcurrency = 'parallel' | 'latest-wins' | 'serial';
|
|
491
|
+
/**
|
|
492
|
+
* The configuration object passed to `ctx.mutation(spec)`. See spec §20.5 for
|
|
493
|
+
* the full lifecycle semantics. `onMutate` may return a `Snapshot` (from
|
|
494
|
+
* `query.setData(...)`) to enable automatic rollback on error.
|
|
495
|
+
*/
|
|
496
|
+
type MutationSpec<V, R> = {
|
|
497
|
+
/**
|
|
498
|
+
* A short human-readable name. Surfaces in the devtools mutation log so the
|
|
499
|
+
* user sees `moveCard` instead of just the controller path. Strongly
|
|
500
|
+
* recommended in app code; cosmetic only — no runtime semantics depend on it.
|
|
501
|
+
*/
|
|
502
|
+
name?: string; /** The actual write. Receives the user-supplied vars and an `AbortSignal`. */
|
|
503
|
+
mutate: (vars: V, signal: AbortSignal) => Promise<R>;
|
|
504
|
+
/**
|
|
505
|
+
* Runs before `mutate`. Return a `Snapshot` from `query.setData(...)` to
|
|
506
|
+
* apply an optimistic update; the snapshot is rolled back on error.
|
|
507
|
+
*/
|
|
508
|
+
onMutate?: (vars: V) => Snapshot | void;
|
|
509
|
+
onSuccess?: (result: R, vars: V) => void;
|
|
510
|
+
onError?: (err: unknown, vars: V, snapshot: Snapshot | undefined) => void;
|
|
511
|
+
onSettled?: (result: R | undefined, err: unknown | undefined, vars: V) => void;
|
|
512
|
+
concurrency?: MutationConcurrency;
|
|
513
|
+
retry?: RetryPolicy;
|
|
514
|
+
retryDelay?: RetryDelay;
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* A running mutation. Created via `ctx.mutation(spec)` — the controller owns
|
|
518
|
+
* its lifetime. Each `run(vars)` returns a Promise; the four signals reflect
|
|
519
|
+
* the last-resolved run for UI binding.
|
|
520
|
+
*
|
|
521
|
+
* Spec §6, §20.5.
|
|
522
|
+
*/
|
|
523
|
+
/**
|
|
524
|
+
* Call signature for `mutation.run`:
|
|
525
|
+
* - When `V` is `void` → no args. (`mutation.run()`)
|
|
526
|
+
* - When `V` was not constrained (default-inferred as `unknown`) → optional
|
|
527
|
+
* arg. Lets `ctx.mutation({ mutate: async () => 1 })` call `run()` *or*
|
|
528
|
+
* `run(anything)` without a type error.
|
|
529
|
+
* - Otherwise → arg required. (`mutation.run(vars)`)
|
|
530
|
+
*
|
|
531
|
+
* Defined as a variadic-tuple conditional so consumers see the right shape
|
|
532
|
+
* without writing `run(undefined as unknown as void)`.
|
|
533
|
+
*/
|
|
534
|
+
type MutationRun<V, R> = (...args: unknown extends V ? [V?] : [V] extends [void] ? [] : [V]) => Promise<R>;
|
|
535
|
+
type Mutation<V, R> = {
|
|
536
|
+
/** Trigger a run. Returns a Promise that resolves with the mutate result. */run: MutationRun<V, R>;
|
|
537
|
+
data: ReadSignal<R | undefined>;
|
|
538
|
+
error: ReadSignal<unknown | undefined>;
|
|
539
|
+
isPending: ReadSignal<boolean>;
|
|
540
|
+
lastVariables: ReadSignal<V | undefined>; /** Clear `data` / `error` / `lastVariables` without aborting in-flight runs. */
|
|
541
|
+
reset(): void; /** Abort in-flight runs and tear down. Idempotent. Called by the parent controller's dispose. */
|
|
542
|
+
dispose(): void;
|
|
543
|
+
};
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/query/plugin.d.ts
|
|
546
|
+
/**
|
|
547
|
+
* `QueryClientPlugin` — a slot for layered cache concerns (cross-tab sync,
|
|
548
|
+
* server-push patches, persistence-like layers) that need to observe
|
|
549
|
+
* `setData` / `invalidate` / `gc` and apply remote writes back into the
|
|
550
|
+
* cache without re-triggering their own outbound side-effects.
|
|
551
|
+
*
|
|
552
|
+
* Plugins are installed via `RootOptions.plugins[]`; lifecycle is bound to
|
|
553
|
+
* the root's `QueryClient` (`init` is called once after construction;
|
|
554
|
+
* `dispose` is called from `QueryClient.dispose`).
|
|
555
|
+
*
|
|
556
|
+
* SPEC §13.2.
|
|
557
|
+
*/
|
|
558
|
+
/**
|
|
559
|
+
* Surface the plugin gets at `init` time. Used to push remote-originated
|
|
560
|
+
* cache writes through the normal `setData`/`invalidate` paths without
|
|
561
|
+
* triggering the plugin's own outbound hooks for those writes (the inbound
|
|
562
|
+
* writes are marked `isRemote: true` and rebroadcast must be skipped).
|
|
563
|
+
*
|
|
564
|
+
* `subscribedKeys(queryId)` walks the per-root entry registry for the
|
|
565
|
+
* matching query and returns every bound entry's `keyArgs`. Cross-tab
|
|
566
|
+
* plugins use this to scope outbound traffic (e.g. only echo invalidations
|
|
567
|
+
* for queries the local tab actually has entries for).
|
|
568
|
+
*/
|
|
569
|
+
type QueryClientPluginApi = {
|
|
570
|
+
/**
|
|
571
|
+
* Apply a remote snapshot. The plugin's own `onSetData` IS fired for the
|
|
572
|
+
* resulting cache write, but the event carries `isRemote: true` — plugins
|
|
573
|
+
* MUST skip rebroadcast in that case.
|
|
574
|
+
*/
|
|
575
|
+
applyRemoteSetData(queryId: string, keyArgs: readonly unknown[], data: unknown): void;
|
|
576
|
+
applyRemoteInvalidate(queryId: string, keyArgs: readonly unknown[]): void;
|
|
577
|
+
/**
|
|
578
|
+
* Snapshot of currently bound entry keys for a query (by `queryId`). Empty
|
|
579
|
+
* array when the query isn't registered, has no client entries, or the
|
|
580
|
+
* `queryId` doesn't match any registered query.
|
|
581
|
+
*/
|
|
582
|
+
subscribedKeys(queryId: string): readonly (readonly unknown[])[];
|
|
583
|
+
};
|
|
584
|
+
type SetDataEvent = {
|
|
585
|
+
queryId: string;
|
|
586
|
+
keyArgs: readonly unknown[];
|
|
587
|
+
data: unknown;
|
|
588
|
+
/**
|
|
589
|
+
* `'data'` for regular queries, `'infinite'` for paginated queries.
|
|
590
|
+
* Cross-tab plugins skip `'infinite'` in v1 — page-array payloads are
|
|
591
|
+
* too heavy to be a safe default.
|
|
592
|
+
*/
|
|
593
|
+
kind: 'data' | 'infinite';
|
|
594
|
+
/**
|
|
595
|
+
* True iff this write originated from `applyRemoteSetData`. Plugins MUST
|
|
596
|
+
* skip rebroadcast in that case — otherwise the message would echo back.
|
|
597
|
+
*/
|
|
598
|
+
isRemote: boolean;
|
|
599
|
+
};
|
|
600
|
+
type InvalidateEvent = {
|
|
601
|
+
queryId: string;
|
|
602
|
+
keyArgs: readonly unknown[];
|
|
603
|
+
kind: 'data' | 'infinite';
|
|
604
|
+
isRemote: boolean;
|
|
605
|
+
};
|
|
606
|
+
type GcEvent = {
|
|
607
|
+
queryId: string;
|
|
608
|
+
keyArgs: readonly unknown[];
|
|
609
|
+
kind: 'data' | 'infinite';
|
|
610
|
+
};
|
|
611
|
+
/**
|
|
612
|
+
* Plugin contract. Every hook is optional. Hooks are wrapped in try/catch
|
|
613
|
+
* by `QueryClient`; thrown exceptions are routed through the root's
|
|
614
|
+
* `onError` handler with `kind: 'plugin'`.
|
|
615
|
+
*/
|
|
616
|
+
type QueryClientPlugin = {
|
|
617
|
+
/**
|
|
618
|
+
* Called once after the `QueryClient` is constructed. Use it to wire up
|
|
619
|
+
* transport listeners and capture the `QueryClientPluginApi`.
|
|
620
|
+
*/
|
|
621
|
+
init?(api: QueryClientPluginApi): void;
|
|
622
|
+
onSetData?(event: SetDataEvent): void;
|
|
623
|
+
onInvalidate?(event: InvalidateEvent): void;
|
|
624
|
+
onGc?(event: GcEvent): void; /** Called from `QueryClient.dispose`. Tear down transports / listeners here. */
|
|
625
|
+
dispose?(): void;
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* Shape of values stored in the `queryId → Query` registry. Either a
|
|
629
|
+
* regular `Query` or an `InfiniteQuery`, both branded by `__olas`.
|
|
630
|
+
*/
|
|
631
|
+
type RegisteredQuery = {
|
|
632
|
+
readonly __olas: 'query' | 'infiniteQuery';
|
|
633
|
+
readonly __spec: {
|
|
634
|
+
queryId?: string;
|
|
635
|
+
crossTab?: boolean;
|
|
636
|
+
};
|
|
637
|
+
};
|
|
638
|
+
/**
|
|
639
|
+
* Look up a query by its declared `queryId`. Returns `undefined` when no
|
|
640
|
+
* query with that id has been defined yet (e.g. the module isn't imported
|
|
641
|
+
* in the receiving tab).
|
|
642
|
+
*/
|
|
643
|
+
declare function lookupRegisteredQuery(queryId: string): RegisteredQuery | undefined;
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/scope.d.ts
|
|
646
|
+
/**
|
|
647
|
+
* Typed cross-tree data slot. Provided by an ancestor via `ctx.provide(scope, value)`
|
|
648
|
+
* and consumed anywhere in its subtree via `ctx.inject(scope)`. Defined at module
|
|
649
|
+
* scope so the identity is stable across calls. See spec §10.3.
|
|
650
|
+
*/
|
|
651
|
+
type Scope<T> = {
|
|
652
|
+
readonly __olas: 'scope'; /** Per-scope identity; matches across `provide` / `inject`. */
|
|
653
|
+
readonly __id: symbol; /** Optional human-readable name (used in error messages). */
|
|
654
|
+
readonly name?: string; /** Default value used when no provider exists; `undefined` if none was set. */
|
|
655
|
+
readonly default?: T; /** True iff `defineScope` was called with a `default` (even `default: undefined`). */
|
|
656
|
+
readonly hasDefault: boolean;
|
|
657
|
+
readonly __t?: T;
|
|
658
|
+
};
|
|
659
|
+
type ScopeOptions<T> = {
|
|
660
|
+
default?: T;
|
|
661
|
+
name?: string;
|
|
662
|
+
};
|
|
663
|
+
/**
|
|
664
|
+
* Create a scope. The returned value is the typed handle passed to
|
|
665
|
+
* `ctx.provide(scope, value)` and `ctx.inject(scope)`. Identity is keyed by
|
|
666
|
+
* an internal symbol so two `defineScope()` calls — even with identical
|
|
667
|
+
* options — yield distinct scopes.
|
|
668
|
+
*/
|
|
669
|
+
declare function defineScope<T>(options?: ScopeOptions<T>): Scope<T>;
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/controller/types.d.ts
|
|
672
|
+
/**
|
|
673
|
+
* App-wide deps available on every controller's `ctx.deps`.
|
|
674
|
+
*
|
|
675
|
+
* Default shape carries an index signature so untyped reads compile (as
|
|
676
|
+
* `unknown`). Users augment this interface in their app to add typed services:
|
|
677
|
+
*
|
|
678
|
+
* ```ts
|
|
679
|
+
* declare module '@kontsedal/olas-core' {
|
|
680
|
+
* interface AmbientDeps {
|
|
681
|
+
* api: ApiClient
|
|
682
|
+
* session: SessionStore
|
|
683
|
+
* }
|
|
684
|
+
* }
|
|
685
|
+
* ```
|
|
686
|
+
*/
|
|
687
|
+
interface AmbientDeps {
|
|
688
|
+
[key: string]: unknown;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* A reactive form field. Extends `ReadSignal<T>` for the current value, plus
|
|
692
|
+
* five signals for state (errors / isValid / isDirty / touched / isValidating)
|
|
693
|
+
* and four methods (`set`, `reset`, `markTouched`, `revalidate`). Created via
|
|
694
|
+
* `ctx.field(initial, validators?)`. Spec §8, §20.7.
|
|
695
|
+
*/
|
|
696
|
+
type Field<T> = ReadSignal<T> & {
|
|
697
|
+
errors: ReadSignal<string[]>;
|
|
698
|
+
isValid: ReadSignal<boolean>;
|
|
699
|
+
isDirty: ReadSignal<boolean>;
|
|
700
|
+
touched: ReadSignal<boolean>;
|
|
701
|
+
isValidating: ReadSignal<boolean>;
|
|
702
|
+
set(value: T): void;
|
|
703
|
+
/**
|
|
704
|
+
* Reseat the field as if this value had been its constructor `initial`:
|
|
705
|
+
* writes the value, re-anchors `reset()`'s target, leaves `isDirty` false.
|
|
706
|
+
* `Form` uses this when applying its own `initial` (constructor + reset),
|
|
707
|
+
* so a form populated from server data isn't born dirty. Useful for any
|
|
708
|
+
* "load this value as the new baseline" pattern.
|
|
709
|
+
*/
|
|
710
|
+
setAsInitial(value: T): void;
|
|
711
|
+
reset(): void;
|
|
712
|
+
markTouched(): void;
|
|
713
|
+
revalidate(): Promise<boolean>; /** Idempotent. Called by the owning controller's dispose. */
|
|
714
|
+
dispose(): void;
|
|
715
|
+
};
|
|
716
|
+
/**
|
|
717
|
+
* The handle returned by `defineController(...)`. Pass it to `createRoot(...)`
|
|
718
|
+
* or `ctx.child(...)` to instantiate. Phantom types preserve `Props` / `Api`
|
|
719
|
+
* for inference via `CtrlProps<C>` / `CtrlApi<C>`.
|
|
720
|
+
*/
|
|
721
|
+
type ControllerDef<Props, Api> = {
|
|
722
|
+
readonly __olas: 'controller';
|
|
723
|
+
readonly __types?: {
|
|
724
|
+
props: Props;
|
|
725
|
+
api: Api;
|
|
726
|
+
};
|
|
727
|
+
};
|
|
728
|
+
/** Extract a controller's Props type. */
|
|
729
|
+
type CtrlProps<C> = C extends ControllerDef<infer P, unknown> ? P : never;
|
|
730
|
+
/** Extract a controller's Api type. */
|
|
731
|
+
type CtrlApi<C> = C extends ControllerDef<unknown, infer A> ? A : never;
|
|
732
|
+
/**
|
|
733
|
+
* `ctx` is the lifecycle-bound surface every controller factory receives.
|
|
734
|
+
* Every primitive constructed through `ctx` is owned by the controller and
|
|
735
|
+
* disposed when the controller disposes.
|
|
736
|
+
*
|
|
737
|
+
* Phase 3 surface — caches, mutations, forms, collections, scopes, etc.
|
|
738
|
+
* land in later phases.
|
|
739
|
+
*/
|
|
740
|
+
type Ctx<TDeps = AmbientDeps> = {
|
|
741
|
+
cache<T>(fetcher: (signal: AbortSignal) => Promise<T>, options?: {
|
|
742
|
+
key?: () => readonly unknown[];
|
|
743
|
+
staleTime?: number;
|
|
744
|
+
keepPreviousData?: boolean;
|
|
745
|
+
initialData?: T | undefined;
|
|
746
|
+
}): LocalCache<T>;
|
|
747
|
+
use<Args extends unknown[], T>(source: Query<Args, T>, keyOrOptions?: (() => Args) | UseOptions<Args>): QuerySubscription<T>;
|
|
748
|
+
use<Args extends unknown[], TPage, TItem>(source: InfiniteQuery<Args, TPage, TItem>, keyOrOptions?: (() => Args) | UseOptions<Args>): InfiniteQuerySubscription<TPage, TItem>;
|
|
749
|
+
mutation<V, R>(spec: MutationSpec<V, R>): Mutation<V, R>;
|
|
750
|
+
emitter<T = void>(): Emitter<T>;
|
|
751
|
+
field<T>(initial: T, validators?: ReadonlyArray<Validator<T>>): Field<T>;
|
|
752
|
+
form<S extends FormSchema>(schema: S, options?: FormOptions<S>): Form<S>;
|
|
753
|
+
fieldArray<I extends Field<any> | Form<any>>(itemFactory: (initial?: ItemInitial<I>) => I, options?: FieldArrayOptions<I>): FieldArray<I>;
|
|
754
|
+
child<Props, Api>(def: ControllerDef<Props, Api>, props: Props, options?: {
|
|
755
|
+
deps?: Partial<TDeps>;
|
|
756
|
+
}): Api;
|
|
757
|
+
/**
|
|
758
|
+
* Like `child(...)` but additionally returns a `dispose()` handle so the
|
|
759
|
+
* parent can tear down this specific sub-tree early — e.g. when the user
|
|
760
|
+
* closes a details panel. The child is still disposed automatically when
|
|
761
|
+
* the parent disposes; `dispose()` is idempotent and only earlies the
|
|
762
|
+
* teardown. Useful for "openable" sub-controllers whose lifetime is driven
|
|
763
|
+
* by a user gesture rather than the parent's lifetime alone.
|
|
764
|
+
*/
|
|
765
|
+
attach<Props, Api>(def: ControllerDef<Props, Api>, props: Props, options?: {
|
|
766
|
+
deps?: Partial<TDeps>;
|
|
767
|
+
}): {
|
|
768
|
+
api: Api;
|
|
769
|
+
dispose: () => void;
|
|
770
|
+
};
|
|
771
|
+
effect(fn: () => void | (() => void)): void;
|
|
772
|
+
on<T>(emitter: Emitter<T>, handler: (value: T) => void): void;
|
|
773
|
+
provide<T>(scope: Scope<T>, value: T): void;
|
|
774
|
+
inject<T>(scope: Scope<T>): T;
|
|
775
|
+
onDispose(fn: () => void): void;
|
|
776
|
+
onSuspend(fn: () => void): void;
|
|
777
|
+
onResume(fn: () => void): void;
|
|
778
|
+
readonly deps: TDeps;
|
|
779
|
+
};
|
|
780
|
+
/**
|
|
781
|
+
* Configuration passed to `createRoot(def, options)`. `deps` is required and
|
|
782
|
+
* available everywhere as `ctx.deps`. `onError` receives errors from effects,
|
|
783
|
+
* mutations, caches, emitter handlers, and construction. `hydrate` replays a
|
|
784
|
+
* `DehydratedState` produced on the server. Spec §20.8.
|
|
785
|
+
*/
|
|
786
|
+
type RootOptions<TDeps> = {
|
|
787
|
+
deps: TDeps;
|
|
788
|
+
onError?: (err: unknown, context: ErrorContext) => void;
|
|
789
|
+
hydrate?: DehydratedState; /** Default for queries that don't set `refetchOnWindowFocus` on their spec (§5.9). */
|
|
790
|
+
refetchOnWindowFocus?: boolean; /** Default for queries that don't set `refetchOnReconnect` on their spec (§5.9). */
|
|
791
|
+
refetchOnReconnect?: boolean;
|
|
792
|
+
/**
|
|
793
|
+
* `QueryClientPlugin`s — cross-tab sync, server-push patches, etc.
|
|
794
|
+
* Installed when the root's `QueryClient` is constructed; disposed when
|
|
795
|
+
* the root disposes. SPEC §13.2.
|
|
796
|
+
*/
|
|
797
|
+
plugins?: QueryClientPlugin[];
|
|
798
|
+
};
|
|
799
|
+
/**
|
|
800
|
+
* The root's public surface: the controller's `Api` plus lifecycle controls
|
|
801
|
+
* (`dispose`, `suspend`, `resume`), SSR (`dehydrate`, `waitForIdle`), and
|
|
802
|
+
* devtools (`__debug`). Spec §20.8.
|
|
803
|
+
*/
|
|
804
|
+
type Root<Api> = Api & {
|
|
805
|
+
dispose(): void;
|
|
806
|
+
suspend(options?: {
|
|
807
|
+
maxIdle?: number;
|
|
808
|
+
}): void;
|
|
809
|
+
resume(): void;
|
|
810
|
+
dehydrate(): DehydratedState;
|
|
811
|
+
waitForIdle(): Promise<void>;
|
|
812
|
+
readonly __debug: DebugBus;
|
|
813
|
+
};
|
|
814
|
+
//#endregion
|
|
815
|
+
export { Validator as $, DehydratedEntry as A, DeepPartial as B, DebugCacheEntry as C, InfiniteQuerySubscription as D, InfiniteQuerySpec as E, QuerySubscription as F, FieldArrayValue as G, FieldArrayItemErrors as H, RetryDelay as I, FormOptions as J, Form as K, RetryPolicy as L, LocalCache as M, Query as N, AsyncState as O, QuerySpec as P, ItemInitial as Q, Snapshot as R, DebugBus as S, InfiniteQuery as T, FieldArrayOptions as U, FieldArray as V, FieldArrayValidator as W, FormValidator as X, FormSchema as Y, FormValue as Z, SetDataEvent as _, Ctx as a, createEmitter as at, MutationConcurrency as b, RootOptions as c, defineScope as d, Computed as et, GcEvent as f, RegisteredQuery as g, QueryClientPluginApi as h, CtrlProps as i, Emitter as it, DehydratedState as j, AsyncStatus as k, Scope as l, QueryClientPlugin as m, ControllerDef as n, Signal as nt, Field as o, InvalidateEvent as p, FormErrors as q, CtrlApi as r, ErrorContext as rt, Root as s, AmbientDeps as t, ReadSignal as tt, ScopeOptions as u, lookupRegisteredQuery as v, DebugEvent as w, MutationSpec as x, Mutation as y, UseOptions as z };
|
|
816
|
+
//# sourceMappingURL=types-CAMgqCMz.d.mts.map
|