@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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/index.cjs +363 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +178 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +178 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.mjs +339 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/root-BImHnGj1.mjs +3270 -0
  12. package/dist/root-BImHnGj1.mjs.map +1 -0
  13. package/dist/root-Bazp5_Ik.cjs +3347 -0
  14. package/dist/root-Bazp5_Ik.cjs.map +1 -0
  15. package/dist/testing.cjs +81 -0
  16. package/dist/testing.cjs.map +1 -0
  17. package/dist/testing.d.cts +56 -0
  18. package/dist/testing.d.cts.map +1 -0
  19. package/dist/testing.d.mts +56 -0
  20. package/dist/testing.d.mts.map +1 -0
  21. package/dist/testing.mjs +78 -0
  22. package/dist/testing.mjs.map +1 -0
  23. package/dist/types-CAMgqCMz.d.mts +816 -0
  24. package/dist/types-CAMgqCMz.d.mts.map +1 -0
  25. package/dist/types-emq_lZd7.d.cts +816 -0
  26. package/dist/types-emq_lZd7.d.cts.map +1 -0
  27. package/package.json +47 -0
  28. package/src/__dev__.d.ts +8 -0
  29. package/src/controller/define.ts +50 -0
  30. package/src/controller/index.ts +12 -0
  31. package/src/controller/instance.ts +499 -0
  32. package/src/controller/root.ts +160 -0
  33. package/src/controller/types.ts +195 -0
  34. package/src/devtools.ts +0 -0
  35. package/src/emitter.ts +79 -0
  36. package/src/errors.ts +49 -0
  37. package/src/forms/field.ts +303 -0
  38. package/src/forms/form-types.ts +130 -0
  39. package/src/forms/form.ts +640 -0
  40. package/src/forms/index.ts +2 -0
  41. package/src/forms/types.ts +1 -0
  42. package/src/forms/validators.ts +70 -0
  43. package/src/index.ts +89 -0
  44. package/src/query/client.ts +934 -0
  45. package/src/query/define.ts +154 -0
  46. package/src/query/entry.ts +322 -0
  47. package/src/query/focus-online.ts +73 -0
  48. package/src/query/index.ts +3 -0
  49. package/src/query/infinite.ts +462 -0
  50. package/src/query/keys.ts +33 -0
  51. package/src/query/local.ts +113 -0
  52. package/src/query/mutation.ts +384 -0
  53. package/src/query/plugin.ts +135 -0
  54. package/src/query/types.ts +168 -0
  55. package/src/query/use.ts +321 -0
  56. package/src/scope.ts +42 -0
  57. package/src/selection.ts +146 -0
  58. package/src/signals/index.ts +3 -0
  59. package/src/signals/readonly.ts +22 -0
  60. package/src/signals/runtime.ts +115 -0
  61. package/src/signals/types.ts +31 -0
  62. package/src/testing.ts +142 -0
  63. package/src/timing/debounced.ts +32 -0
  64. package/src/timing/index.ts +2 -0
  65. package/src/timing/throttled.ts +46 -0
  66. 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-emq_lZd7.d.cts.map