@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bohdan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @kontsedal/olas-core
2
+
3
+ The core of Olas — UI-framework-agnostic. Signals, controllers, queries, mutations, forms, scopes, SSR, and the devtools event bus. No React / Vue / Svelte imports anywhere.
4
+
5
+ This package is the only place that touches `@preact/signals-core` (peer dep). Everything else is plain TypeScript.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @kontsedal/olas-core @preact/signals-core
11
+ ```
12
+
13
+ ## What's in the box
14
+
15
+ | Concern | API |
16
+ |---|---|
17
+ | Reactive primitives | `signal`, `computed`, `effect`, `batch`, `untracked` |
18
+ | Time-based signals | `debounced`, `throttled` |
19
+ | Controllers | `defineController`, `createRoot`, `Ctx` |
20
+ | Async data — shared | `defineQuery`, `defineInfiniteQuery`, `ctx.use` |
21
+ | Async data — local | `ctx.cache` |
22
+ | Mutations | `ctx.mutation` with `parallel` / `latest-wins` / `serial` modes |
23
+ | Forms | `ctx.field`, `ctx.form`, `ctx.fieldArray`, stdlib validators |
24
+ | Cross-tree data | `defineScope`, `ctx.provide`, `ctx.inject` |
25
+ | Events | `createEmitter`, `ctx.emitter`, `ctx.on` |
26
+ | Lifecycle | `ctx.effect`, `ctx.child`, `ctx.attach`, `ctx.onDispose`, `ctx.onSuspend`, `ctx.onResume` |
27
+ | SSR | `root.dehydrate()`, `createRoot(def, { hydrate })`, `root.waitForIdle()` |
28
+ | Devtools | `root.__debug.subscribe(handler)` (events documented in `DebugEvent`) |
29
+ | Errors | `RootOptions.onError`, `ErrorContext`, `isAbortError` |
30
+
31
+ Full reference with signatures and examples: [`../../API.md`](../../API.md).
32
+
33
+ ## 30-second example
34
+
35
+ ```ts
36
+ import { createRoot, defineController, signal } from '@kontsedal/olas-core'
37
+
38
+ const counter = defineController(() => {
39
+ const count = signal(0)
40
+ return { count, increment: () => count.update((n) => n + 1) }
41
+ })
42
+
43
+ const root = createRoot(counter, { deps: {} })
44
+ root.increment()
45
+ console.log(root.count.value) // 1
46
+ root.dispose()
47
+ ```
48
+
49
+ ## Sub-paths
50
+
51
+ - `@kontsedal/olas-core` — the main entry.
52
+ - `@kontsedal/olas-core/testing` — `createTestController`, `fakeField`, `fakeAsyncState`. Test-only helpers; the sub-path makes "you imported testing utilities into production code" loud and grep-able.
53
+
54
+ ```ts
55
+ import { createTestController, fakeField, fakeAsyncState } from '@kontsedal/olas-core/testing'
56
+ ```
57
+
58
+ ## Further reading
59
+
60
+ - [`../../API.md`](../../API.md) — every export, signature, example.
61
+ - [`../../README.md`](../../README.md) — guided tour.
62
+ - [`../../SPEC.md`](../../SPEC.md) — authoritative design.
63
+ - [`../../.wiki/modules/`](../../.wiki/modules/) — per-module pages (signals, controller, query, forms, …).
64
+ - [`../../.wiki/pitfalls/`](../../.wiki/pitfalls/) — known footguns.
package/dist/index.cjs ADDED
@@ -0,0 +1,363 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_root = require("./root-Bazp5_Ik.cjs");
3
+ //#region src/signals/readonly.ts
4
+ /**
5
+ * Project a Signal (or any object with a reactive `value` + `peek` + `subscribe`)
6
+ * as a `ReadSignal`. The returned object does not expose `set` / `update` /
7
+ * settable `value`, so it can be returned from APIs without callers mutating it.
8
+ *
9
+ * Internal helper — not exported from the package's public surface.
10
+ */
11
+ function readOnly(source) {
12
+ return {
13
+ get value() {
14
+ return source.value;
15
+ },
16
+ peek() {
17
+ return source.peek();
18
+ },
19
+ subscribe(handler) {
20
+ return source.subscribe(handler);
21
+ }
22
+ };
23
+ }
24
+ //#endregion
25
+ //#region src/forms/validators.ts
26
+ const isEmpty = (value) => {
27
+ if (value === void 0 || value === null) return true;
28
+ if (typeof value === "string") return value.length === 0;
29
+ if (Array.isArray(value)) return value.length === 0;
30
+ return false;
31
+ };
32
+ /** Reject empty values (undefined, null, empty string, empty array). */
33
+ const required = (message = "Required") => (value) => isEmpty(value) ? message : null;
34
+ /** Reject strings / arrays shorter than `n`. Allows null/undefined (use with `required` to forbid). */
35
+ const minLength = (n, message) => (value) => {
36
+ if (value == null) return null;
37
+ if (value.length >= n) return null;
38
+ return message ?? `Must be at least ${n} characters`;
39
+ };
40
+ /** Reject strings / arrays longer than `n`. */
41
+ const maxLength = (n, message) => (value) => {
42
+ if (value == null) return null;
43
+ if (value.length <= n) return null;
44
+ return message ?? `Must be no more than ${n} characters`;
45
+ };
46
+ /** Reject numbers less than `n`. */
47
+ const min = (n, message) => (value) => {
48
+ if (value == null) return null;
49
+ if (value >= n) return null;
50
+ return message ?? `Must be at least ${n}`;
51
+ };
52
+ /** Reject numbers greater than `n`. */
53
+ const max = (n, message) => (value) => {
54
+ if (value == null) return null;
55
+ if (value <= n) return null;
56
+ return message ?? `Must be no more than ${n}`;
57
+ };
58
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
59
+ /** Reject strings that don't look like an email. Empty / null pass (use with `required` to forbid). */
60
+ const email = (message = "Invalid email address") => (value) => {
61
+ if (value == null || value === "") return null;
62
+ return EMAIL_RE.test(value) ? null : message;
63
+ };
64
+ /** Reject strings that don't match the supplied `RegExp`. */
65
+ const pattern = (re, message = "Invalid format") => (value) => {
66
+ if (value == null || value === "") return null;
67
+ return re.test(value) ? null : message;
68
+ };
69
+ //#endregion
70
+ //#region src/query/define.ts
71
+ function registerQueryId(spec, query) {
72
+ if (spec.queryId != null) require_root.registerQueryById(spec.queryId, query);
73
+ else if (spec.crossTab === true) {}
74
+ }
75
+ /**
76
+ * Define a keyed, shared query. The returned Query value lives at module
77
+ * scope; per-root QueryClients bind their own entry registries to it.
78
+ */
79
+ function defineQuery(spec) {
80
+ const clients = /* @__PURE__ */ new Set();
81
+ const query = {
82
+ __olas: "query",
83
+ __spec: spec,
84
+ __clients: clients,
85
+ invalidate(...args) {
86
+ for (const client of clients) client.invalidate(query, args);
87
+ },
88
+ invalidateAll() {
89
+ for (const client of clients) client.invalidateAll(query);
90
+ },
91
+ setData(...rest) {
92
+ const updater = rest[rest.length - 1];
93
+ const keyArgs = rest.slice(0, -1);
94
+ const childSnapshots = [];
95
+ for (const client of clients) childSnapshots.push(client.setData(query, keyArgs, updater));
96
+ return {
97
+ rollback: () => {
98
+ for (const s of childSnapshots) s.rollback();
99
+ },
100
+ finalize: () => {
101
+ for (const s of childSnapshots) s.finalize();
102
+ }
103
+ };
104
+ },
105
+ prefetch(...args) {
106
+ const [first] = clients;
107
+ if (!first) return Promise.reject(/* @__PURE__ */ new Error("[olas] prefetch called before any root has subscribed"));
108
+ return first.prefetch(query, args);
109
+ }
110
+ };
111
+ registerQueryId(spec, query);
112
+ return query;
113
+ }
114
+ /**
115
+ * Define a paginated query (chat-style "load more", infinite scrolling). Pages
116
+ * are kept in order and concatenated via `getNextPageParam` /
117
+ * `getPreviousPageParam`. The returned handle is module-scoped — bind
118
+ * subscribers via `ctx.use(infiniteQuery, () => [...args])`. Spec §5.7,
119
+ * §20.4.
120
+ */
121
+ function defineInfiniteQuery(spec) {
122
+ const clients = /* @__PURE__ */ new Set();
123
+ const query = {
124
+ __olas: "infiniteQuery",
125
+ __spec: spec,
126
+ __clients: clients,
127
+ invalidate(...args) {
128
+ for (const client of clients) client.invalidateInfinite(query, args);
129
+ },
130
+ invalidateAll() {
131
+ for (const client of clients) client.invalidateAllInfinite(query);
132
+ },
133
+ setData(...rest) {
134
+ const updater = rest[rest.length - 1];
135
+ const keyArgs = rest.slice(0, -1);
136
+ const childSnapshots = [];
137
+ for (const client of clients) childSnapshots.push(client.setInfiniteData(query, keyArgs, updater));
138
+ return {
139
+ rollback: () => {
140
+ for (const s of childSnapshots) s.rollback();
141
+ },
142
+ finalize: () => {
143
+ for (const s of childSnapshots) s.finalize();
144
+ }
145
+ };
146
+ },
147
+ prefetch(...args) {
148
+ const [first] = clients;
149
+ if (!first) return Promise.reject(/* @__PURE__ */ new Error("[olas] prefetch called before any root has subscribed"));
150
+ return first.prefetchInfinite(query, args);
151
+ }
152
+ };
153
+ registerQueryId(spec, query);
154
+ return query;
155
+ }
156
+ //#endregion
157
+ //#region src/scope.ts
158
+ /**
159
+ * Create a scope. The returned value is the typed handle passed to
160
+ * `ctx.provide(scope, value)` and `ctx.inject(scope)`. Identity is keyed by
161
+ * an internal symbol so two `defineScope()` calls — even with identical
162
+ * options — yield distinct scopes.
163
+ */
164
+ function defineScope(options) {
165
+ const hasDefault = options !== void 0 && "default" in options;
166
+ const name = options?.name;
167
+ return {
168
+ __olas: "scope",
169
+ __id: Symbol(name ?? "scope"),
170
+ hasDefault,
171
+ ...name !== void 0 ? { name } : {},
172
+ ...hasDefault ? { default: options?.default } : {}
173
+ };
174
+ }
175
+ //#endregion
176
+ //#region src/selection.ts
177
+ /**
178
+ * Create a `Selection<T>`. Optional `initial` seeds the selected set.
179
+ *
180
+ * `handleClick` encapsulates the standard click semantics:
181
+ * - plain click → select only `id` (anchor moves to `id`)
182
+ * - meta-click → toggle `id` (anchor moves to `id` on add)
183
+ * - shift-click → range from anchor to `id` along `ordered` (anchor sticks,
184
+ * so subsequent shift-clicks extend from the same origin)
185
+ *
186
+ * Spec §16.5 / §17.5.
187
+ */
188
+ function selection(options) {
189
+ const ids = require_root.signal(new Set(options?.initial));
190
+ let anchor = options?.initial?.length ? options.initial[options.initial.length - 1] ?? null : null;
191
+ let preShiftSelection = null;
192
+ const size = require_root.computed(() => ids.value.size);
193
+ const isSelected = (id) => require_root.computed(() => ids.value.has(id));
194
+ const select = (id) => {
195
+ const prev = ids.peek();
196
+ if (!prev.has(id)) {
197
+ const next = new Set(prev);
198
+ next.add(id);
199
+ ids.set(next);
200
+ }
201
+ anchor = id;
202
+ };
203
+ const deselect = (id) => {
204
+ const prev = ids.peek();
205
+ if (!prev.has(id)) return;
206
+ const next = new Set(prev);
207
+ next.delete(id);
208
+ ids.set(next);
209
+ };
210
+ const toggle = (id) => {
211
+ const prev = ids.peek();
212
+ const next = new Set(prev);
213
+ if (prev.has(id)) next.delete(id);
214
+ else {
215
+ next.add(id);
216
+ anchor = id;
217
+ }
218
+ ids.set(next);
219
+ };
220
+ const clear = () => {
221
+ if (ids.peek().size === 0) {
222
+ anchor = null;
223
+ return;
224
+ }
225
+ ids.set(/* @__PURE__ */ new Set());
226
+ anchor = null;
227
+ };
228
+ const selectAll = (incoming) => {
229
+ ids.set(new Set(incoming));
230
+ anchor = incoming.length > 0 ? incoming[incoming.length - 1] ?? null : null;
231
+ };
232
+ const handleClick = (id, mods, ordered) => {
233
+ if (mods.shift && anchor !== null) {
234
+ const anchorIdx = ordered.indexOf(anchor);
235
+ const targetIdx = ordered.indexOf(id);
236
+ if (anchorIdx === -1 || targetIdx === -1) {
237
+ ids.set(new Set([id]));
238
+ anchor = id;
239
+ preShiftSelection = null;
240
+ return;
241
+ }
242
+ if (preShiftSelection === null) preShiftSelection = ids.peek();
243
+ const [lo, hi] = anchorIdx < targetIdx ? [anchorIdx, targetIdx] : [targetIdx, anchorIdx];
244
+ const next = new Set(preShiftSelection);
245
+ for (const k of ordered.slice(lo, hi + 1)) next.add(k);
246
+ ids.set(next);
247
+ return;
248
+ }
249
+ preShiftSelection = null;
250
+ if (mods.meta) {
251
+ toggle(id);
252
+ return;
253
+ }
254
+ ids.set(new Set([id]));
255
+ anchor = id;
256
+ };
257
+ return {
258
+ selectedIds: readOnly(ids),
259
+ size,
260
+ isSelected,
261
+ select,
262
+ deselect,
263
+ toggle,
264
+ clear,
265
+ selectAll,
266
+ handleClick
267
+ };
268
+ }
269
+ //#endregion
270
+ //#region src/timing/debounced.ts
271
+ /**
272
+ * Lag a signal by `ms`. The returned signal updates only after the source has
273
+ * been unchanged for `ms`. Each new write resets the timer.
274
+ *
275
+ * No lifecycle — the internal effect runs for the lifetime of the program.
276
+ * Use inside a controller closure so it dies with the closure.
277
+ */
278
+ function debounced(source, ms) {
279
+ const out = require_root.signal(source.peek());
280
+ let timer = null;
281
+ let initial = true;
282
+ require_root.effect(() => {
283
+ const value = source.value;
284
+ if (initial) {
285
+ initial = false;
286
+ return;
287
+ }
288
+ if (timer != null) clearTimeout(timer);
289
+ timer = setTimeout(() => {
290
+ out.set(value);
291
+ timer = null;
292
+ }, ms);
293
+ });
294
+ return out;
295
+ }
296
+ //#endregion
297
+ //#region src/timing/throttled.ts
298
+ /**
299
+ * Rate-limit a signal so it emits at most once per `ms` (leading + trailing).
300
+ * The first change passes through immediately. Subsequent changes within the
301
+ * window are coalesced; the latest value is emitted when the window expires.
302
+ *
303
+ * No lifecycle — see debounced() note.
304
+ */
305
+ function throttled(source, ms) {
306
+ const out = require_root.signal(source.peek());
307
+ let lastEmit = Number.NEGATIVE_INFINITY;
308
+ let trailingTimer = null;
309
+ let trailingValue = source.peek();
310
+ let initial = true;
311
+ require_root.effect(() => {
312
+ const value = source.value;
313
+ if (initial) {
314
+ initial = false;
315
+ return;
316
+ }
317
+ const now = Date.now();
318
+ const elapsed = now - lastEmit;
319
+ if (elapsed >= ms) {
320
+ out.set(value);
321
+ lastEmit = now;
322
+ if (trailingTimer != null) {
323
+ clearTimeout(trailingTimer);
324
+ trailingTimer = null;
325
+ }
326
+ } else {
327
+ trailingValue = value;
328
+ if (trailingTimer == null) trailingTimer = setTimeout(() => {
329
+ out.set(trailingValue);
330
+ lastEmit = Date.now();
331
+ trailingTimer = null;
332
+ }, ms - elapsed);
333
+ }
334
+ });
335
+ return out;
336
+ }
337
+ //#endregion
338
+ exports.batch = require_root.batch;
339
+ exports.computed = require_root.computed;
340
+ exports.createEmitter = require_root.createEmitter;
341
+ exports.createRoot = require_root.createRoot;
342
+ exports.debounced = debounced;
343
+ exports.debouncedValidator = require_root.debouncedValidator;
344
+ exports.defineController = require_root.defineController;
345
+ exports.defineInfiniteQuery = defineInfiniteQuery;
346
+ exports.defineQuery = defineQuery;
347
+ exports.defineScope = defineScope;
348
+ exports.effect = require_root.effect;
349
+ exports.email = email;
350
+ exports.isAbortError = require_root.isAbortError;
351
+ exports.lookupRegisteredQuery = require_root.lookupRegisteredQuery;
352
+ exports.max = max;
353
+ exports.maxLength = maxLength;
354
+ exports.min = min;
355
+ exports.minLength = minLength;
356
+ exports.pattern = pattern;
357
+ exports.required = required;
358
+ exports.selection = selection;
359
+ exports.signal = require_root.signal;
360
+ exports.throttled = throttled;
361
+ exports.untracked = require_root.untracked;
362
+
363
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["signal","computed","signal","signal"],"sources":["../src/signals/readonly.ts","../src/forms/validators.ts","../src/query/define.ts","../src/scope.ts","../src/selection.ts","../src/timing/debounced.ts","../src/timing/throttled.ts"],"sourcesContent":["import type { ReadSignal } from './types'\n\n/**\n * Project a Signal (or any object with a reactive `value` + `peek` + `subscribe`)\n * as a `ReadSignal`. The returned object does not expose `set` / `update` /\n * settable `value`, so it can be returned from APIs without callers mutating it.\n *\n * Internal helper — not exported from the package's public surface.\n */\nexport function readOnly<T>(source: ReadSignal<T>): ReadSignal<T> {\n return {\n get value() {\n return source.value\n },\n peek() {\n return source.peek()\n },\n subscribe(handler) {\n return source.subscribe(handler)\n },\n }\n}\n","import type { Validator } from './types'\n\nconst isEmpty = (value: unknown): boolean => {\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.length === 0\n if (Array.isArray(value)) return value.length === 0\n return false\n}\n\n/** Reject empty values (undefined, null, empty string, empty array). */\nexport const required =\n <T>(message = 'Required'): Validator<T> =>\n (value) =>\n isEmpty(value) ? message : null\n\n/** Reject strings / arrays shorter than `n`. Allows null/undefined (use with `required` to forbid). */\nexport const minLength =\n (n: number, message?: string): Validator<string | readonly unknown[]> =>\n (value) => {\n if (value == null) return null\n if (value.length >= n) return null\n return message ?? `Must be at least ${n} characters`\n }\n\n/** Reject strings / arrays longer than `n`. */\nexport const maxLength =\n (n: number, message?: string): Validator<string | readonly unknown[]> =>\n (value) => {\n if (value == null) return null\n if (value.length <= n) return null\n return message ?? `Must be no more than ${n} characters`\n }\n\n/** Reject numbers less than `n`. */\nexport const min =\n (n: number, message?: string): Validator<number> =>\n (value) => {\n if (value == null) return null\n if (value >= n) return null\n return message ?? `Must be at least ${n}`\n }\n\n/** Reject numbers greater than `n`. */\nexport const max =\n (n: number, message?: string): Validator<number> =>\n (value) => {\n if (value == null) return null\n if (value <= n) return null\n return message ?? `Must be no more than ${n}`\n }\n\n// RFC-5322-light. Pragmatic, not exhaustive — production forms should\n// rely on server-side validation for definitive answers.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/** Reject strings that don't look like an email. Empty / null pass (use with `required` to forbid). */\nexport const email =\n (message = 'Invalid email address'): Validator<string> =>\n (value) => {\n if (value == null || value === '') return null\n return EMAIL_RE.test(value) ? null : message\n }\n\n/** Reject strings that don't match the supplied `RegExp`. */\nexport const pattern =\n (re: RegExp, message = 'Invalid format'): Validator<string> =>\n (value) => {\n if (value == null || value === '') return null\n return re.test(value) ? null : message\n }\n","import type { QueryClient } from './client'\nimport type { InfiniteQuery, InfiniteQuerySpec } from './infinite'\nimport { type RegisteredQuery, registerQueryById } from './plugin'\nimport type { Query, QuerySpec, Snapshot } from './types'\n\ntype QueryInternal<Args extends unknown[], T> = Query<Args, T> & {\n readonly __spec: QuerySpec<Args, T>\n __clients: Set<QueryClient>\n}\n\nconst warnedMissingId = new WeakSet<object>()\n\nfunction registerQueryId(spec: { queryId?: string; crossTab?: boolean }, query: object): void {\n if (spec.queryId != null) {\n registerQueryById(spec.queryId, query as RegisteredQuery)\n } else if (spec.crossTab === true) {\n // Plugins can't route a message without a `queryId`. Warn once per\n // offending spec — repeated warnings on every render would be noisy.\n if (__DEV__ && !warnedMissingId.has(spec as object)) {\n warnedMissingId.add(spec as object)\n console.warn(\n '[olas] defineQuery({ crossTab: true }) requires a stable `queryId`. ' +\n 'Add `queryId: \"<unique-string>\"` to the spec. Cross-tab sync is disabled for this query.',\n )\n }\n }\n}\n\n/**\n * Define a keyed, shared query. The returned Query value lives at module\n * scope; per-root QueryClients bind their own entry registries to it.\n */\nexport function defineQuery<Args extends unknown[], T>(spec: QuerySpec<Args, T>): Query<Args, T> {\n const clients = new Set<QueryClient>()\n const query = {\n __olas: 'query' as const,\n __spec: spec,\n __clients: clients,\n\n invalidate(...args: Args): void {\n for (const client of clients) {\n client.invalidate(query as Query<Args, T>, args)\n }\n },\n\n invalidateAll(): void {\n for (const client of clients) {\n client.invalidateAll(query as Query<Args, T>)\n }\n },\n\n setData(...rest: [...Args, updater: (prev: T | undefined) => T]): Snapshot {\n const updater = rest[rest.length - 1] as (prev: T | undefined) => T\n const keyArgs = rest.slice(0, -1) as unknown as Args\n const childSnapshots: Snapshot[] = []\n for (const client of clients) {\n childSnapshots.push(client.setData(query as Query<Args, T>, keyArgs, updater))\n }\n return {\n rollback: () => {\n for (const s of childSnapshots) s.rollback()\n },\n finalize: () => {\n for (const s of childSnapshots) s.finalize()\n },\n }\n },\n\n prefetch(...args: Args): Promise<T> {\n // Single-client common case; if none, throw.\n const [first] = clients\n if (!first) {\n return Promise.reject(new Error('[olas] prefetch called before any root has subscribed'))\n }\n return first.prefetch(query as Query<Args, T>, args)\n },\n } satisfies QueryInternal<Args, T>\n\n registerQueryId(spec, query)\n return query as Query<Args, T>\n}\n\ntype InfiniteQueryInternal<Args extends unknown[], TPage, TItem> = InfiniteQuery<\n Args,\n TPage,\n TItem\n> & {\n readonly __spec: InfiniteQuerySpec<Args, any, TPage, TItem>\n __clients: Set<QueryClient>\n}\n\n/**\n * Define a paginated query (chat-style \"load more\", infinite scrolling). Pages\n * are kept in order and concatenated via `getNextPageParam` /\n * `getPreviousPageParam`. The returned handle is module-scoped — bind\n * subscribers via `ctx.use(infiniteQuery, () => [...args])`. Spec §5.7,\n * §20.4.\n */\nexport function defineInfiniteQuery<Args extends unknown[], PageParam, TPage, TItem = TPage>(\n spec: InfiniteQuerySpec<Args, PageParam, TPage, TItem>,\n): InfiniteQuery<Args, TPage, TItem> {\n const clients = new Set<QueryClient>()\n const query = {\n __olas: 'infiniteQuery' as const,\n __spec: spec,\n __clients: clients,\n\n invalidate(...args: Args): void {\n for (const client of clients) {\n client.invalidateInfinite(query as InfiniteQuery<Args, TPage, TItem>, args)\n }\n },\n\n invalidateAll(): void {\n for (const client of clients) {\n client.invalidateAllInfinite(query as InfiniteQuery<Args, TPage, TItem>)\n }\n },\n\n setData(...rest: [...Args, updater: (prev: TPage[] | undefined) => TPage[]]): Snapshot {\n const updater = rest[rest.length - 1] as (prev: TPage[] | undefined) => TPage[]\n const keyArgs = rest.slice(0, -1) as unknown as Args\n const childSnapshots: Snapshot[] = []\n for (const client of clients) {\n childSnapshots.push(\n client.setInfiniteData<Args, TPage>(\n query as InfiniteQuery<Args, TPage, TItem>,\n keyArgs,\n updater,\n ),\n )\n }\n return {\n rollback: () => {\n for (const s of childSnapshots) s.rollback()\n },\n finalize: () => {\n for (const s of childSnapshots) s.finalize()\n },\n }\n },\n\n prefetch(...args: Args): Promise<TPage> {\n const [first] = clients\n if (!first) {\n return Promise.reject(new Error('[olas] prefetch called before any root has subscribed'))\n }\n return first.prefetchInfinite(query as InfiniteQuery<Args, TPage, TItem>, args)\n },\n } satisfies InfiniteQueryInternal<Args, TPage, TItem>\n\n registerQueryId(spec, query)\n return query as InfiniteQuery<Args, TPage, TItem>\n}\n","/**\n * Typed cross-tree data slot. Provided by an ancestor via `ctx.provide(scope, value)`\n * and consumed anywhere in its subtree via `ctx.inject(scope)`. Defined at module\n * scope so the identity is stable across calls. See spec §10.3.\n */\nexport type Scope<T> = {\n readonly __olas: 'scope'\n /** Per-scope identity; matches across `provide` / `inject`. */\n readonly __id: symbol\n /** Optional human-readable name (used in error messages). */\n readonly name?: string\n /** Default value used when no provider exists; `undefined` if none was set. */\n readonly default?: T\n /** True iff `defineScope` was called with a `default` (even `default: undefined`). */\n readonly hasDefault: boolean\n // Phantom for inference — typed `T` is preserved through the scope's lifetime.\n readonly __t?: T\n}\n\nexport type ScopeOptions<T> = {\n default?: T\n name?: string\n}\n\n/**\n * Create a scope. The returned value is the typed handle passed to\n * `ctx.provide(scope, value)` and `ctx.inject(scope)`. Identity is keyed by\n * an internal symbol so two `defineScope()` calls — even with identical\n * options — yield distinct scopes.\n */\nexport function defineScope<T>(options?: ScopeOptions<T>): Scope<T> {\n const hasDefault = options !== undefined && 'default' in options\n const name = options?.name\n const scope: Scope<T> = {\n __olas: 'scope',\n __id: Symbol(name ?? 'scope'),\n hasDefault,\n ...(name !== undefined ? { name } : {}),\n ...(hasDefault ? { default: options?.default as T } : {}),\n }\n return scope\n}\n","import { computed, signal } from './signals'\nimport { readOnly } from './signals/readonly'\nimport type { ReadSignal } from './signals/types'\n\n/**\n * Multi-select state for tables / lists with bulk actions (spec §17.5).\n *\n * Plain function — not bound to `ctx`. Place it in a controller's closure so\n * it dies with the closure. The phantom `T` parameter brands the selection by\n * item type; IDs are always strings.\n */\n// biome-ignore lint/correctness/noUnusedVariables: phantom branding param (spec §17.5)\nexport type Selection<T = unknown> = {\n selectedIds: ReadSignal<ReadonlySet<string>>\n size: ReadSignal<number>\n isSelected(id: string): ReadSignal<boolean>\n\n select(id: string): void\n deselect(id: string): void\n toggle(id: string): void\n clear(): void\n selectAll(ids: readonly string[]): void\n\n handleClick(\n id: string,\n mods: { shift?: boolean; meta?: boolean },\n ordered: readonly string[],\n ): void\n}\n\n/**\n * Create a `Selection<T>`. Optional `initial` seeds the selected set.\n *\n * `handleClick` encapsulates the standard click semantics:\n * - plain click → select only `id` (anchor moves to `id`)\n * - meta-click → toggle `id` (anchor moves to `id` on add)\n * - shift-click → range from anchor to `id` along `ordered` (anchor sticks,\n * so subsequent shift-clicks extend from the same origin)\n *\n * Spec §16.5 / §17.5.\n */\nexport function selection<T = unknown>(options?: { initial?: readonly string[] }): Selection<T> {\n const ids = signal<ReadonlySet<string>>(new Set(options?.initial))\n let anchor: string | null = options?.initial?.length\n ? (options.initial[options.initial.length - 1] ?? null)\n : null\n // Snapshot of the selection just before the first shift-click of a run.\n // Subsequent shift-clicks re-compute the range against this snapshot so the\n // user can shrink or grow the range. Reset on any non-shift click.\n let preShiftSelection: ReadonlySet<string> | null = null\n\n const size = computed(() => ids.value.size)\n\n const isSelected = (id: string): ReadSignal<boolean> => computed(() => ids.value.has(id))\n\n const select = (id: string): void => {\n const prev = ids.peek()\n if (!prev.has(id)) {\n const next = new Set(prev)\n next.add(id)\n ids.set(next)\n }\n anchor = id\n }\n\n const deselect = (id: string): void => {\n const prev = ids.peek()\n if (!prev.has(id)) return\n const next = new Set(prev)\n next.delete(id)\n ids.set(next)\n }\n\n const toggle = (id: string): void => {\n const prev = ids.peek()\n const next = new Set(prev)\n if (prev.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n anchor = id\n }\n ids.set(next)\n }\n\n const clear = (): void => {\n if (ids.peek().size === 0) {\n anchor = null\n return\n }\n ids.set(new Set())\n anchor = null\n }\n\n const selectAll = (incoming: readonly string[]): void => {\n ids.set(new Set(incoming))\n anchor = incoming.length > 0 ? (incoming[incoming.length - 1] ?? null) : null\n }\n\n const handleClick = (\n id: string,\n mods: { shift?: boolean; meta?: boolean },\n ordered: readonly string[],\n ): void => {\n if (mods.shift && anchor !== null) {\n const anchorIdx = ordered.indexOf(anchor)\n const targetIdx = ordered.indexOf(id)\n if (anchorIdx === -1 || targetIdx === -1) {\n // anchor or target not visible — fall back to plain select\n ids.set(new Set([id]))\n anchor = id\n preShiftSelection = null\n return\n }\n if (preShiftSelection === null) {\n preShiftSelection = ids.peek()\n }\n const [lo, hi] = anchorIdx < targetIdx ? [anchorIdx, targetIdx] : [targetIdx, anchorIdx]\n const next = new Set(preShiftSelection)\n for (const k of ordered.slice(lo, hi + 1)) next.add(k)\n ids.set(next)\n // Anchor stays — subsequent shift-clicks extend from the same origin.\n return\n }\n // Any non-shift click ends the shift run.\n preShiftSelection = null\n if (mods.meta) {\n toggle(id)\n return\n }\n ids.set(new Set([id]))\n anchor = id\n }\n\n return {\n selectedIds: readOnly(ids),\n size,\n isSelected,\n select,\n deselect,\n toggle,\n clear,\n selectAll,\n handleClick,\n }\n}\n","import { effect, signal } from '../signals'\nimport type { ReadSignal } from '../signals/types'\n\n/**\n * Lag a signal by `ms`. The returned signal updates only after the source has\n * been unchanged for `ms`. Each new write resets the timer.\n *\n * No lifecycle — the internal effect runs for the lifetime of the program.\n * Use inside a controller closure so it dies with the closure.\n */\nexport function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {\n const out = signal<T>(source.peek())\n let timer: ReturnType<typeof setTimeout> | null = null\n let initial = true\n\n effect(() => {\n const value = source.value\n if (initial) {\n // The first effect run reads the source for tracking; we already\n // initialized `out` to the same value, so skip scheduling.\n initial = false\n return\n }\n if (timer != null) clearTimeout(timer)\n timer = setTimeout(() => {\n out.set(value)\n timer = null\n }, ms)\n })\n\n return out\n}\n","import { effect, signal } from '../signals'\nimport type { ReadSignal } from '../signals/types'\n\n/**\n * Rate-limit a signal so it emits at most once per `ms` (leading + trailing).\n * The first change passes through immediately. Subsequent changes within the\n * window are coalesced; the latest value is emitted when the window expires.\n *\n * No lifecycle — see debounced() note.\n */\nexport function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {\n const out = signal<T>(source.peek())\n let lastEmit = Number.NEGATIVE_INFINITY\n let trailingTimer: ReturnType<typeof setTimeout> | null = null\n let trailingValue: T = source.peek()\n let initial = true\n\n effect(() => {\n const value = source.value\n if (initial) {\n initial = false\n return\n }\n const now = Date.now()\n const elapsed = now - lastEmit\n if (elapsed >= ms) {\n out.set(value)\n lastEmit = now\n if (trailingTimer != null) {\n clearTimeout(trailingTimer)\n trailingTimer = null\n }\n } else {\n trailingValue = value\n if (trailingTimer == null) {\n trailingTimer = setTimeout(() => {\n out.set(trailingValue)\n lastEmit = Date.now()\n trailingTimer = null\n }, ms - elapsed)\n }\n }\n })\n\n return out\n}\n"],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ import { $ as Validator, A as DehydratedEntry, B as DeepPartial, C as DebugCacheEntry, D as InfiniteQuerySubscription, E as InfiniteQuerySpec, F as QuerySubscription, G as FieldArrayValue, H as FieldArrayItemErrors, I as RetryDelay, J as FormOptions, K as Form, L as RetryPolicy, M as LocalCache, N as Query, O as AsyncState, P as QuerySpec, Q as ItemInitial, R as Snapshot, S as DebugBus, T as InfiniteQuery, U as FieldArrayOptions, V as FieldArray, W as FieldArrayValidator, X as FormValidator, Y as FormSchema, Z as FormValue, _ as SetDataEvent, a as Ctx, at as createEmitter, b as MutationConcurrency, c as RootOptions, d as defineScope, et as Computed, f as GcEvent, g as RegisteredQuery, h as QueryClientPluginApi, i as CtrlProps, it as Emitter, j as DehydratedState, k as AsyncStatus, l as Scope, m as QueryClientPlugin, n as ControllerDef, nt as Signal, o as Field, p as InvalidateEvent, q as FormErrors, r as CtrlApi, rt as ErrorContext, s as Root, t as AmbientDeps, tt as ReadSignal, u as ScopeOptions, v as lookupRegisteredQuery, w as DebugEvent, x as MutationSpec, y as Mutation, z as UseOptions } from "./types-emq_lZd7.cjs";
2
+
3
+ //#region src/signals/runtime.d.ts
4
+ /**
5
+ * Create a writable `Signal<T>`. Reads track the current auto-tracking scope
6
+ * (effect / computed); writes notify all subscribers (deduped via `Object.is`).
7
+ *
8
+ * Spec §20.1. For a single-pass non-tracked read use `signal.peek()`.
9
+ */
10
+ declare function signal<T>(initial: T): Signal<T>;
11
+ /**
12
+ * Create a `Computed<T>` — a read-only derived signal. The provided `fn` is
13
+ * re-evaluated whenever a signal it read during its last run changes; the
14
+ * resulting value is cached until then.
15
+ *
16
+ * Spec §20.1. The graph is glitch-free: a `computed` re-runs at most once per
17
+ * batched-write cycle.
18
+ */
19
+ declare function computed<T>(fn: () => T): Computed<T>;
20
+ /**
21
+ * Run `fn` immediately and again whenever any signal it reads changes. If
22
+ * `fn` returns a function, that function is called as a cleanup before the
23
+ * next re-run and on dispose.
24
+ *
25
+ * Returns a `dispose` function. Inside a controller use `ctx.effect(...)`
26
+ * instead — that variant is auto-disposed with the controller.
27
+ */
28
+ declare function effect(fn: () => void | (() => void)): () => void;
29
+ /**
30
+ * Batch synchronous signal writes so subscribers see one notification at the
31
+ * end of the batch rather than one per write. Returns whatever `fn` returns.
32
+ */
33
+ declare function batch<T>(fn: () => T): T;
34
+ /**
35
+ * Run `fn` with auto-tracking suppressed — signals read inside don't become
36
+ * dependencies of the surrounding `computed` / `effect`. Useful for "read
37
+ * these signals once to log them" or for snapshotting state inside an effect
38
+ * without subscribing to it. For a single-signal peek, prefer `signal.peek()`.
39
+ */
40
+ declare function untracked<T>(fn: () => T): T;
41
+ //#endregion
42
+ //#region src/controller/define.d.ts
43
+ /** Optional configuration for `defineController`. */
44
+ type DefineControllerOptions = {
45
+ /**
46
+ * A short, human-readable name for this controller — used in the devtools
47
+ * tree, `controller:*` events, and error contexts (e.g. `["root","board[0]"]`).
48
+ *
49
+ * When omitted, the runtime falls back to `factory.name` (the JS-inferred
50
+ * function name) or `"anonymous"` for arrow-function factories defined
51
+ * inline. Naming is strongly recommended in app code.
52
+ */
53
+ name?: string;
54
+ };
55
+ /**
56
+ * Create a controller definition. The factory is stored on the returned object
57
+ * and invoked during `createRoot` / `ctx.child` to build instances.
58
+ *
59
+ * `Props` defaults to `void` so a factory written as `(ctx) => ...` is typed
60
+ * as `ControllerDef<void, Api>` — the form `createRoot` requires.
61
+ */
62
+ declare function defineController<Props = void, Api = unknown>(factory: (ctx: Ctx, props: Props) => Api, options?: DefineControllerOptions): ControllerDef<Props, Api>;
63
+ //#endregion
64
+ //#region src/controller/root.d.ts
65
+ /**
66
+ * Construct a root controller. Root factories take no props — startup config
67
+ * goes in `deps`.
68
+ */
69
+ declare function createRoot<Api, TDeps extends Record<string, unknown> = AmbientDeps>(def: ControllerDef<void, Api>, options: RootOptions<TDeps>): Root<Api>;
70
+ //#endregion
71
+ //#region src/forms/validators.d.ts
72
+ /** Reject empty values (undefined, null, empty string, empty array). */
73
+ declare const required: <T>(message?: string) => Validator<T>;
74
+ /** Reject strings / arrays shorter than `n`. Allows null/undefined (use with `required` to forbid). */
75
+ declare const minLength: (n: number, message?: string) => Validator<string | readonly unknown[]>;
76
+ /** Reject strings / arrays longer than `n`. */
77
+ declare const maxLength: (n: number, message?: string) => Validator<string | readonly unknown[]>;
78
+ /** Reject numbers less than `n`. */
79
+ declare const min: (n: number, message?: string) => Validator<number>;
80
+ /** Reject numbers greater than `n`. */
81
+ declare const max: (n: number, message?: string) => Validator<number>;
82
+ /** Reject strings that don't look like an email. Empty / null pass (use with `required` to forbid). */
83
+ declare const email: (message?: string) => Validator<string>;
84
+ /** Reject strings that don't match the supplied `RegExp`. */
85
+ declare const pattern: (re: RegExp, message?: string) => Validator<string>;
86
+ //#endregion
87
+ //#region src/forms/field.d.ts
88
+ /**
89
+ * Wrap an async validator with a debounce. The debounce timer resets on every
90
+ * value change. While debouncing or the request is in flight, the field's
91
+ * `isValidating` is true and `isValid` is false (treat-as-invalid-until-proven-valid).
92
+ */
93
+ declare function debouncedValidator<T>(fn: (value: T, signal: AbortSignal) => Promise<string | null>, ms: number): Validator<T>;
94
+ //#endregion
95
+ //#region src/query/define.d.ts
96
+ /**
97
+ * Define a keyed, shared query. The returned Query value lives at module
98
+ * scope; per-root QueryClients bind their own entry registries to it.
99
+ */
100
+ declare function defineQuery<Args extends unknown[], T>(spec: QuerySpec<Args, T>): Query<Args, T>;
101
+ /**
102
+ * Define a paginated query (chat-style "load more", infinite scrolling). Pages
103
+ * are kept in order and concatenated via `getNextPageParam` /
104
+ * `getPreviousPageParam`. The returned handle is module-scoped — bind
105
+ * subscribers via `ctx.use(infiniteQuery, () => [...args])`. Spec §5.7,
106
+ * §20.4.
107
+ */
108
+ declare function defineInfiniteQuery<Args extends unknown[], PageParam, TPage, TItem = TPage>(spec: InfiniteQuerySpec<Args, PageParam, TPage, TItem>): InfiniteQuery<Args, TPage, TItem>;
109
+ //#endregion
110
+ //#region src/selection.d.ts
111
+ /**
112
+ * Multi-select state for tables / lists with bulk actions (spec §17.5).
113
+ *
114
+ * Plain function — not bound to `ctx`. Place it in a controller's closure so
115
+ * it dies with the closure. The phantom `T` parameter brands the selection by
116
+ * item type; IDs are always strings.
117
+ */
118
+ type Selection<T = unknown> = {
119
+ selectedIds: ReadSignal<ReadonlySet<string>>;
120
+ size: ReadSignal<number>;
121
+ isSelected(id: string): ReadSignal<boolean>;
122
+ select(id: string): void;
123
+ deselect(id: string): void;
124
+ toggle(id: string): void;
125
+ clear(): void;
126
+ selectAll(ids: readonly string[]): void;
127
+ handleClick(id: string, mods: {
128
+ shift?: boolean;
129
+ meta?: boolean;
130
+ }, ordered: readonly string[]): void;
131
+ };
132
+ /**
133
+ * Create a `Selection<T>`. Optional `initial` seeds the selected set.
134
+ *
135
+ * `handleClick` encapsulates the standard click semantics:
136
+ * - plain click → select only `id` (anchor moves to `id`)
137
+ * - meta-click → toggle `id` (anchor moves to `id` on add)
138
+ * - shift-click → range from anchor to `id` along `ordered` (anchor sticks,
139
+ * so subsequent shift-clicks extend from the same origin)
140
+ *
141
+ * Spec §16.5 / §17.5.
142
+ */
143
+ declare function selection<T = unknown>(options?: {
144
+ initial?: readonly string[];
145
+ }): Selection<T>;
146
+ //#endregion
147
+ //#region src/timing/debounced.d.ts
148
+ /**
149
+ * Lag a signal by `ms`. The returned signal updates only after the source has
150
+ * been unchanged for `ms`. Each new write resets the timer.
151
+ *
152
+ * No lifecycle — the internal effect runs for the lifetime of the program.
153
+ * Use inside a controller closure so it dies with the closure.
154
+ */
155
+ declare function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
156
+ //#endregion
157
+ //#region src/timing/throttled.d.ts
158
+ /**
159
+ * Rate-limit a signal so it emits at most once per `ms` (leading + trailing).
160
+ * The first change passes through immediately. Subsequent changes within the
161
+ * window are coalesced; the latest value is emitted when the window expires.
162
+ *
163
+ * No lifecycle — see debounced() note.
164
+ */
165
+ declare function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
166
+ //#endregion
167
+ //#region src/utils.d.ts
168
+ /**
169
+ * True iff `err` is an AbortError. Used to filter superseded latest-wins
170
+ * mutations and aborted fetches from genuine failures.
171
+ *
172
+ * Spec: §20.12 — checks `err instanceof DOMException && err.name === 'AbortError'`.
173
+ * Node 17+ exposes a global DOMException, so this works server-side too.
174
+ */
175
+ declare function isAbortError(err: unknown): boolean;
176
+ //#endregion
177
+ export { type AmbientDeps, type AsyncState, type AsyncStatus, type Computed, type ControllerDef, type CtrlApi, type CtrlProps, type Ctx, type DebugBus, type DebugCacheEntry, type DebugEvent, type DeepPartial, type DehydratedEntry, type DehydratedState, type Emitter, type ErrorContext, type Field, type FieldArray, type FieldArrayItemErrors, type FieldArrayOptions, type FieldArrayValidator, type FieldArrayValue, type Form, type FormErrors, type FormOptions, type FormSchema, type FormValidator, type FormValue, type GcEvent, type InfiniteQuery, type InfiniteQuerySpec, type InfiniteQuerySubscription, type InvalidateEvent, type ItemInitial, type LocalCache, type Mutation, type MutationConcurrency, type MutationSpec, type Query, type QueryClientPlugin, type QueryClientPluginApi, type QuerySpec, type QuerySubscription, type ReadSignal, type RegisteredQuery, type RetryDelay, type RetryPolicy, type Root, type RootOptions, type Scope, type ScopeOptions, type Selection, type SetDataEvent, type Signal, type Snapshot, type UseOptions, type Validator, batch, computed, createEmitter, createRoot, debounced, debouncedValidator, defineController, defineInfiniteQuery, defineQuery, defineScope, effect, email, isAbortError, lookupRegisteredQuery, max, maxLength, min, minLength, pattern, required, selection, signal, throttled, untracked };
178
+ //# sourceMappingURL=index.d.cts.map