@kontsedal/olas-core 0.0.1-rc.1 → 0.0.2

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 (45) hide show
  1. package/dist/index.cjs +40 -10
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +32 -11
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +32 -11
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +40 -11
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{root-BImHnGj1.mjs → root-De-6KWIZ.mjs} +750 -149
  10. package/dist/root-De-6KWIZ.mjs.map +1 -0
  11. package/dist/{root-Bazp5_Ik.cjs → root-XKEsSmcd.cjs} +755 -148
  12. package/dist/root-XKEsSmcd.cjs.map +1 -0
  13. package/dist/testing.cjs +1 -1
  14. package/dist/testing.d.cts +1 -1
  15. package/dist/testing.d.mts +1 -1
  16. package/dist/testing.mjs +1 -1
  17. package/dist/{types-CAMgqCMz.d.mts → types-C-zV1JZA.d.mts} +215 -13
  18. package/dist/types-C-zV1JZA.d.mts.map +1 -0
  19. package/dist/{types-emq_lZd7.d.cts → types-DKfpkm17.d.cts} +215 -13
  20. package/dist/types-DKfpkm17.d.cts.map +1 -0
  21. package/package.json +1 -1
  22. package/src/controller/index.ts +6 -0
  23. package/src/controller/instance.ts +432 -18
  24. package/src/controller/root.ts +9 -1
  25. package/src/controller/types.ts +148 -7
  26. package/src/emitter.ts +34 -3
  27. package/src/forms/field.ts +73 -8
  28. package/src/forms/form-types.ts +16 -0
  29. package/src/forms/form.ts +218 -26
  30. package/src/index.ts +12 -1
  31. package/src/query/client.ts +161 -6
  32. package/src/query/define.ts +14 -0
  33. package/src/query/entry.ts +64 -42
  34. package/src/query/infinite.ts +77 -55
  35. package/src/query/mutation.ts +11 -21
  36. package/src/query/plugin.ts +50 -0
  37. package/src/query/use.ts +80 -3
  38. package/src/signals/readonly.ts +3 -3
  39. package/src/timing/debounced.ts +24 -4
  40. package/src/timing/throttled.ts +22 -3
  41. package/src/utils.ts +32 -4
  42. package/dist/root-BImHnGj1.mjs.map +0 -1
  43. package/dist/root-Bazp5_Ik.cjs.map +0 -1
  44. package/dist/types-CAMgqCMz.d.mts.map +0 -1
  45. package/dist/types-emq_lZd7.d.cts.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_root = require("./root-Bazp5_Ik.cjs");
2
+ const require_root = require("./root-XKEsSmcd.cjs");
3
3
  //#region src/signals/readonly.ts
4
4
  /**
5
5
  * Project a Signal (or any object with a reactive `value` + `peek` + `subscribe`)
@@ -9,7 +9,7 @@ const require_root = require("./root-Bazp5_Ik.cjs");
9
9
  * Internal helper — not exported from the package's public surface.
10
10
  */
11
11
  function readOnly(source) {
12
- return {
12
+ return Object.freeze({
13
13
  get value() {
14
14
  return source.value;
15
15
  },
@@ -19,7 +19,7 @@ function readOnly(source) {
19
19
  subscribe(handler) {
20
20
  return source.subscribe(handler);
21
21
  }
22
- };
22
+ });
23
23
  }
24
24
  //#endregion
25
25
  //#region src/forms/validators.ts
@@ -272,14 +272,17 @@ function selection(options) {
272
272
  * Lag a signal by `ms`. The returned signal updates only after the source has
273
273
  * been unchanged for `ms`. Each new write resets the timer.
274
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.
275
+ * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a
276
+ * lifecycle when the signal aborts the effect disposes, the pending timer
277
+ * clears, and the subscriber chain on `source` drops. Without `signal`, the
278
+ * effect lives as long as `source` does; pass a signal whenever the source
279
+ * outlives the consumer.
277
280
  */
278
- function debounced(source, ms) {
281
+ function debounced(source, ms, options) {
279
282
  const out = require_root.signal(source.peek());
280
283
  let timer = null;
281
284
  let initial = true;
282
- require_root.effect(() => {
285
+ const dispose = require_root.effect(() => {
283
286
  const value = source.value;
284
287
  if (initial) {
285
288
  initial = false;
@@ -291,6 +294,18 @@ function debounced(source, ms) {
291
294
  timer = null;
292
295
  }, ms);
293
296
  });
297
+ const sig = options?.signal;
298
+ if (sig) {
299
+ const stop = () => {
300
+ if (timer != null) {
301
+ clearTimeout(timer);
302
+ timer = null;
303
+ }
304
+ dispose();
305
+ };
306
+ if (sig.aborted) stop();
307
+ else sig.addEventListener("abort", stop, { once: true });
308
+ }
294
309
  return out;
295
310
  }
296
311
  //#endregion
@@ -300,15 +315,17 @@ function debounced(source, ms) {
300
315
  * The first change passes through immediately. Subsequent changes within the
301
316
  * window are coalesced; the latest value is emitted when the window expires.
302
317
  *
303
- * No lifecycle — see debounced() note.
318
+ * Pass `options.signal` to tie the internal effect to a lifecycle — when the
319
+ * signal aborts the effect disposes and any pending trailing timer clears.
320
+ * Without `signal`, the effect lives as long as `source` does.
304
321
  */
305
- function throttled(source, ms) {
322
+ function throttled(source, ms, options) {
306
323
  const out = require_root.signal(source.peek());
307
324
  let lastEmit = Number.NEGATIVE_INFINITY;
308
325
  let trailingTimer = null;
309
326
  let trailingValue = source.peek();
310
327
  let initial = true;
311
- require_root.effect(() => {
328
+ const dispose = require_root.effect(() => {
312
329
  const value = source.value;
313
330
  if (initial) {
314
331
  initial = false;
@@ -332,6 +349,18 @@ function throttled(source, ms) {
332
349
  }, ms - elapsed);
333
350
  }
334
351
  });
352
+ const sig = options?.signal;
353
+ if (sig) {
354
+ const stop = () => {
355
+ if (trailingTimer != null) {
356
+ clearTimeout(trailingTimer);
357
+ trailingTimer = null;
358
+ }
359
+ dispose();
360
+ };
361
+ if (sig.aborted) stop();
362
+ else sig.addEventListener("abort", stop, { once: true });
363
+ }
335
364
  return out;
336
365
  }
337
366
  //#endregion
@@ -357,6 +386,7 @@ exports.pattern = pattern;
357
386
  exports.required = required;
358
387
  exports.selection = selection;
359
388
  exports.signal = require_root.signal;
389
+ exports.stableHash = require_root.stableHash;
360
390
  exports.throttled = throttled;
361
391
  exports.untracked = require_root.untracked;
362
392
 
@@ -1 +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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"}
1
+ {"version":3,"file":"index.cjs","names":["signal","computed","signal","effect","signal","effect"],"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 Object.freeze({\n get value() {\n return source.value\n },\n peek() {\n return source.peek()\n },\n subscribe(handler: (value: T) => void) {\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 if (__DEV__ && clients.size > 1) {\n // eslint-disable-next-line no-console\n console.warn(\n '[olas] query.prefetch() is ambiguous when multiple roots are registered; ' +\n 'using an arbitrary root. Call `root.prefetch(query, args)` (or per-root) to be explicit.',\n )\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 if (__DEV__ && clients.size > 1) {\n // eslint-disable-next-line no-console\n console.warn(\n '[olas] infiniteQuery.prefetch() is ambiguous when multiple roots are registered; ' +\n 'using an arbitrary root. Call `root.prefetch(query, args)` (or per-root) to be explicit.',\n )\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 * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a\n * lifecycle — when the signal aborts the effect disposes, the pending timer\n * clears, and the subscriber chain on `source` drops. Without `signal`, the\n * effect lives as long as `source` does; pass a signal whenever the source\n * outlives the consumer.\n */\nexport function debounced<T>(\n source: ReadSignal<T>,\n ms: number,\n options?: { signal?: AbortSignal },\n): ReadSignal<T> {\n const out = signal<T>(source.peek())\n let timer: ReturnType<typeof setTimeout> | null = null\n let initial = true\n\n const dispose = 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 const sig = options?.signal\n if (sig) {\n const stop = () => {\n if (timer != null) {\n clearTimeout(timer)\n timer = null\n }\n dispose()\n }\n if (sig.aborted) stop()\n else sig.addEventListener('abort', stop, { once: true })\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 * Pass `options.signal` to tie the internal effect to a lifecycle — when the\n * signal aborts the effect disposes and any pending trailing timer clears.\n * Without `signal`, the effect lives as long as `source` does.\n */\nexport function throttled<T>(\n source: ReadSignal<T>,\n ms: number,\n options?: { signal?: AbortSignal },\n): 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 const dispose = 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 const sig = options?.signal\n if (sig) {\n const stop = () => {\n if (trailingTimer != null) {\n clearTimeout(trailingTimer)\n trailingTimer = null\n }\n dispose()\n }\n if (sig.aborted) stop()\n else sig.addEventListener('abort', stop, { once: true })\n }\n\n return out\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,SAAY,QAAsC;CAChE,OAAO,OAAO,OAAO;EACnB,IAAI,QAAQ;GACV,OAAO,OAAO;EAChB;EACA,OAAO;GACL,OAAO,OAAO,KAAK;EACrB;EACA,UAAU,SAA6B;GACrC,OAAO,OAAO,UAAU,OAAO;EACjC;CACF,CAAC;AACH;;;ACnBA,MAAM,WAAW,UAA4B;CAC3C,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,WAAW;CACvD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,WAAW;CAClD,OAAO;AACT;;AAGA,MAAa,YACP,UAAU,gBACb,UACC,QAAQ,KAAK,IAAI,UAAU;;AAG/B,MAAa,aACV,GAAW,aACX,UAAU;CACT,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,MAAM,UAAU,GAAG,OAAO;CAC9B,OAAO,WAAW,oBAAoB,EAAE;AAC1C;;AAGF,MAAa,aACV,GAAW,aACX,UAAU;CACT,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,MAAM,UAAU,GAAG,OAAO;CAC9B,OAAO,WAAW,wBAAwB,EAAE;AAC9C;;AAGF,MAAa,OACV,GAAW,aACX,UAAU;CACT,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,SAAS,GAAG,OAAO;CACvB,OAAO,WAAW,oBAAoB;AACxC;;AAGF,MAAa,OACV,GAAW,aACX,UAAU;CACT,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,SAAS,GAAG,OAAO;CACvB,OAAO,WAAW,wBAAwB;AAC5C;AAIF,MAAM,WAAW;;AAGjB,MAAa,SACV,UAAU,6BACV,UAAU;CACT,IAAI,SAAS,QAAQ,UAAU,IAAI,OAAO;CAC1C,OAAO,SAAS,KAAK,KAAK,IAAI,OAAO;AACvC;;AAGF,MAAa,WACV,IAAY,UAAU,sBACtB,UAAU;CACT,IAAI,SAAS,QAAQ,UAAU,IAAI,OAAO;CAC1C,OAAO,GAAG,KAAK,KAAK,IAAI,OAAO;AACjC;;;ACzDF,SAAS,gBAAgB,MAAgD,OAAqB;CAC5F,IAAI,KAAK,WAAW,MAClB,aAAA,kBAAkB,KAAK,SAAS,KAAwB;MACnD,IAAI,KAAK,aAAa,MAAM,CAUnC;AACF;;;;;AAMA,SAAgB,YAAuC,MAA0C;CAC/F,MAAM,0BAAU,IAAI,IAAiB;CACrC,MAAM,QAAQ;EACZ,QAAQ;EACR,QAAQ;EACR,WAAW;EAEX,WAAW,GAAG,MAAkB;GAC9B,KAAK,MAAM,UAAU,SACnB,OAAO,WAAW,OAAyB,IAAI;EAEnD;EAEA,gBAAsB;GACpB,KAAK,MAAM,UAAU,SACnB,OAAO,cAAc,KAAuB;EAEhD;EAEA,QAAQ,GAAG,MAAgE;GACzE,MAAM,UAAU,KAAK,KAAK,SAAS;GACnC,MAAM,UAAU,KAAK,MAAM,GAAG,EAAE;GAChC,MAAM,iBAA6B,CAAC;GACpC,KAAK,MAAM,UAAU,SACnB,eAAe,KAAK,OAAO,QAAQ,OAAyB,SAAS,OAAO,CAAC;GAE/E,OAAO;IACL,gBAAgB;KACd,KAAK,MAAM,KAAK,gBAAgB,EAAE,SAAS;IAC7C;IACA,gBAAgB;KACd,KAAK,MAAM,KAAK,gBAAgB,EAAE,SAAS;IAC7C;GACF;EACF;EAEA,SAAS,GAAG,MAAwB;GAElC,MAAM,CAAC,SAAS;GAChB,IAAI,CAAC,OACH,OAAO,QAAQ,uBAAO,IAAI,MAAM,uDAAuD,CAAC;GAS1F,OAAO,MAAM,SAAS,OAAyB,IAAI;EACrD;CACF;CAEA,gBAAgB,MAAM,KAAK;CAC3B,OAAO;AACT;;;;;;;;AAkBA,SAAgB,oBACd,MACmC;CACnC,MAAM,0BAAU,IAAI,IAAiB;CACrC,MAAM,QAAQ;EACZ,QAAQ;EACR,QAAQ;EACR,WAAW;EAEX,WAAW,GAAG,MAAkB;GAC9B,KAAK,MAAM,UAAU,SACnB,OAAO,mBAAmB,OAA4C,IAAI;EAE9E;EAEA,gBAAsB;GACpB,KAAK,MAAM,UAAU,SACnB,OAAO,sBAAsB,KAA0C;EAE3E;EAEA,QAAQ,GAAG,MAA4E;GACrF,MAAM,UAAU,KAAK,KAAK,SAAS;GACnC,MAAM,UAAU,KAAK,MAAM,GAAG,EAAE;GAChC,MAAM,iBAA6B,CAAC;GACpC,KAAK,MAAM,UAAU,SACnB,eAAe,KACb,OAAO,gBACL,OACA,SACA,OACF,CACF;GAEF,OAAO;IACL,gBAAgB;KACd,KAAK,MAAM,KAAK,gBAAgB,EAAE,SAAS;IAC7C;IACA,gBAAgB;KACd,KAAK,MAAM,KAAK,gBAAgB,EAAE,SAAS;IAC7C;GACF;EACF;EAEA,SAAS,GAAG,MAA4B;GACtC,MAAM,CAAC,SAAS;GAChB,IAAI,CAAC,OACH,OAAO,QAAQ,uBAAO,IAAI,MAAM,uDAAuD,CAAC;GAS1F,OAAO,MAAM,iBAAiB,OAA4C,IAAI;EAChF;CACF;CAEA,gBAAgB,MAAM,KAAK;CAC3B,OAAO;AACT;;;;;;;;;ACzIA,SAAgB,YAAe,SAAqC;CAClE,MAAM,aAAa,YAAY,KAAA,KAAa,aAAa;CACzD,MAAM,OAAO,SAAS;CAQtB,OAAO;EANL,QAAQ;EACR,MAAM,OAAO,QAAQ,OAAO;EAC5B;EACA,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EACrC,GAAI,aAAa,EAAE,SAAS,SAAS,QAAa,IAAI,CAAC;CAE9C;AACb;;;;;;;;;;;;;;ACAA,SAAgB,UAAuB,SAAyD;CAC9F,MAAM,MAAMA,aAAAA,OAA4B,IAAI,IAAI,SAAS,OAAO,CAAC;CACjE,IAAI,SAAwB,SAAS,SAAS,SACzC,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,MAAM,OAChD;CAIJ,IAAI,oBAAgD;CAEpD,MAAM,OAAOC,aAAAA,eAAe,IAAI,MAAM,IAAI;CAE1C,MAAM,cAAc,OAAoCA,aAAAA,eAAe,IAAI,MAAM,IAAI,EAAE,CAAC;CAExF,MAAM,UAAU,OAAqB;EACnC,MAAM,OAAO,IAAI,KAAK;EACtB,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG;GACjB,MAAM,OAAO,IAAI,IAAI,IAAI;GACzB,KAAK,IAAI,EAAE;GACX,IAAI,IAAI,IAAI;EACd;EACA,SAAS;CACX;CAEA,MAAM,YAAY,OAAqB;EACrC,MAAM,OAAO,IAAI,KAAK;EACtB,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG;EACnB,MAAM,OAAO,IAAI,IAAI,IAAI;EACzB,KAAK,OAAO,EAAE;EACd,IAAI,IAAI,IAAI;CACd;CAEA,MAAM,UAAU,OAAqB;EACnC,MAAM,OAAO,IAAI,KAAK;EACtB,MAAM,OAAO,IAAI,IAAI,IAAI;EACzB,IAAI,KAAK,IAAI,EAAE,GACb,KAAK,OAAO,EAAE;OACT;GACL,KAAK,IAAI,EAAE;GACX,SAAS;EACX;EACA,IAAI,IAAI,IAAI;CACd;CAEA,MAAM,cAAoB;EACxB,IAAI,IAAI,KAAK,EAAE,SAAS,GAAG;GACzB,SAAS;GACT;EACF;EACA,IAAI,oBAAI,IAAI,IAAI,CAAC;EACjB,SAAS;CACX;CAEA,MAAM,aAAa,aAAsC;EACvD,IAAI,IAAI,IAAI,IAAI,QAAQ,CAAC;EACzB,SAAS,SAAS,SAAS,IAAK,SAAS,SAAS,SAAS,MAAM,OAAQ;CAC3E;CAEA,MAAM,eACJ,IACA,MACA,YACS;EACT,IAAI,KAAK,SAAS,WAAW,MAAM;GACjC,MAAM,YAAY,QAAQ,QAAQ,MAAM;GACxC,MAAM,YAAY,QAAQ,QAAQ,EAAE;GACpC,IAAI,cAAc,MAAM,cAAc,IAAI;IAExC,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IACrB,SAAS;IACT,oBAAoB;IACpB;GACF;GACA,IAAI,sBAAsB,MACxB,oBAAoB,IAAI,KAAK;GAE/B,MAAM,CAAC,IAAI,MAAM,YAAY,YAAY,CAAC,WAAW,SAAS,IAAI,CAAC,WAAW,SAAS;GACvF,MAAM,OAAO,IAAI,IAAI,iBAAiB;GACtC,KAAK,MAAM,KAAK,QAAQ,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC;GACrD,IAAI,IAAI,IAAI;GAEZ;EACF;EAEA,oBAAoB;EACpB,IAAI,KAAK,MAAM;GACb,OAAO,EAAE;GACT;EACF;EACA,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;EACrB,SAAS;CACX;CAEA,OAAO;EACL,aAAa,SAAS,GAAG;EACzB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;;;;;;;ACpIA,SAAgB,UACd,QACA,IACA,SACe;CACf,MAAM,MAAMC,aAAAA,OAAU,OAAO,KAAK,CAAC;CACnC,IAAI,QAA8C;CAClD,IAAI,UAAU;CAEd,MAAM,UAAUC,aAAAA,aAAa;EAC3B,MAAM,QAAQ,OAAO;EACrB,IAAI,SAAS;GAGX,UAAU;GACV;EACF;EACA,IAAI,SAAS,MAAM,aAAa,KAAK;EACrC,QAAQ,iBAAiB;GACvB,IAAI,IAAI,KAAK;GACb,QAAQ;EACV,GAAG,EAAE;CACP,CAAC;CAED,MAAM,MAAM,SAAS;CACrB,IAAI,KAAK;EACP,MAAM,aAAa;GACjB,IAAI,SAAS,MAAM;IACjB,aAAa,KAAK;IAClB,QAAQ;GACV;GACA,QAAQ;EACV;EACA,IAAI,IAAI,SAAS,KAAK;OACjB,IAAI,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;CACzD;CAEA,OAAO;AACT;;;;;;;;;;;;ACvCA,SAAgB,UACd,QACA,IACA,SACe;CACf,MAAM,MAAMC,aAAAA,OAAU,OAAO,KAAK,CAAC;CACnC,IAAI,WAAW,OAAO;CACtB,IAAI,gBAAsD;CAC1D,IAAI,gBAAmB,OAAO,KAAK;CACnC,IAAI,UAAU;CAEd,MAAM,UAAUC,aAAAA,aAAa;EAC3B,MAAM,QAAQ,OAAO;EACrB,IAAI,SAAS;GACX,UAAU;GACV;EACF;EACA,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,UAAU,MAAM;EACtB,IAAI,WAAW,IAAI;GACjB,IAAI,IAAI,KAAK;GACb,WAAW;GACX,IAAI,iBAAiB,MAAM;IACzB,aAAa,aAAa;IAC1B,gBAAgB;GAClB;EACF,OAAO;GACL,gBAAgB;GAChB,IAAI,iBAAiB,MACnB,gBAAgB,iBAAiB;IAC/B,IAAI,IAAI,aAAa;IACrB,WAAW,KAAK,IAAI;IACpB,gBAAgB;GAClB,GAAG,KAAK,OAAO;EAEnB;CACF,CAAC;CAED,MAAM,MAAM,SAAS;CACrB,IAAI,KAAK;EACP,MAAM,aAAa;GACjB,IAAI,iBAAiB,MAAM;IACzB,aAAa,aAAa;IAC1B,gBAAgB;GAClB;GACA,QAAQ;EACV;EACA,IAAI,IAAI,SAAS,KAAK;OACjB,IAAI,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;CACzD;CAEA,OAAO;AACT"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
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";
1
+ import { $ as FormErrors, A as DebugEvent, B as QuerySpec, C as SetDataEvent, D as MutationSpec, E as MutationConcurrency, F as AsyncStatus, G as UseOptions, H as RetryDelay, I as DehydratedEntry, J as FieldArrayItemErrors, K as DeepPartial, L as DehydratedState, M as InfiniteQuerySpec, N as InfiniteQuerySubscription, O as DebugBus, P as AsyncState, Q as Form, R as LocalCache, S as RegisteredQuery, T as Mutation, U as RetryPolicy, V as QuerySubscription, W as Snapshot, X as FieldArrayValidator, Y as FieldArrayOptions, Z as FieldArrayValue, _ as defineScope, a as CollectionFactoryResult, at as Validator, b as QueryClientPlugin, c as CtrlApi, ct as Signal, d as Field, dt as EmitterErrorReporter, et as FormOptions, f as LazyChild, ft as createEmitter, g as ScopeOptions, h as Scope, i as CollectionFactoryOptions, it as ItemInitial, j as InfiniteQuery, k as DebugCacheEntry, l as CtrlProps, lt as ErrorContext, m as RootOptions, n as Collection, nt as FormValidator, o as CollectionHomogeneousOptions, ot as Computed, p as Root, q as FieldArray, r as CollectionFactoryApi, rt as FormValue, s as ControllerDef, st as ReadSignal, t as AmbientDeps, tt as FormSchema, u as Ctx, ut as Emitter, v as GcEvent, w as lookupRegisteredQuery, x as QueryClientPluginApi, y as InvalidateEvent, z as Query } from "./types-DKfpkm17.cjs";
2
2
 
3
3
  //#region src/signals/runtime.d.ts
4
4
  /**
@@ -107,6 +107,17 @@ declare function defineQuery<Args extends unknown[], T>(spec: QuerySpec<Args, T>
107
107
  */
108
108
  declare function defineInfiniteQuery<Args extends unknown[], PageParam, TPage, TItem = TPage>(spec: InfiniteQuerySpec<Args, PageParam, TPage, TItem>): InfiniteQuery<Args, TPage, TItem>;
109
109
  //#endregion
110
+ //#region src/query/keys.d.ts
111
+ /**
112
+ * Stable string hash of a key tuple. Two equal-by-content args produce the
113
+ * same string regardless of property iteration order. Handles primitives,
114
+ * arrays, plain objects, Date.
115
+ *
116
+ * Functions and symbols throw — keys must be serializable so distinct
117
+ * subscribers can share entries.
118
+ */
119
+ declare function stableHash(args: readonly unknown[]): string;
120
+ //#endregion
110
121
  //#region src/selection.d.ts
111
122
  /**
112
123
  * Multi-select state for tables / lists with bulk actions (spec §17.5).
@@ -149,10 +160,15 @@ declare function selection<T = unknown>(options?: {
149
160
  * Lag a signal by `ms`. The returned signal updates only after the source has
150
161
  * been unchanged for `ms`. Each new write resets the timer.
151
162
  *
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.
163
+ * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a
164
+ * lifecycle when the signal aborts the effect disposes, the pending timer
165
+ * clears, and the subscriber chain on `source` drops. Without `signal`, the
166
+ * effect lives as long as `source` does; pass a signal whenever the source
167
+ * outlives the consumer.
154
168
  */
155
- declare function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
169
+ declare function debounced<T>(source: ReadSignal<T>, ms: number, options?: {
170
+ signal?: AbortSignal;
171
+ }): ReadSignal<T>;
156
172
  //#endregion
157
173
  //#region src/timing/throttled.d.ts
158
174
  /**
@@ -160,19 +176,24 @@ declare function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
160
176
  * The first change passes through immediately. Subsequent changes within the
161
177
  * window are coalesced; the latest value is emitted when the window expires.
162
178
  *
163
- * No lifecycle — see debounced() note.
179
+ * Pass `options.signal` to tie the internal effect to a lifecycle — when the
180
+ * signal aborts the effect disposes and any pending trailing timer clears.
181
+ * Without `signal`, the effect lives as long as `source` does.
164
182
  */
165
- declare function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
183
+ declare function throttled<T>(source: ReadSignal<T>, ms: number, options?: {
184
+ signal?: AbortSignal;
185
+ }): ReadSignal<T>;
166
186
  //#endregion
167
187
  //#region src/utils.d.ts
168
188
  /**
169
- * True iff `err` is an AbortError. Used to filter superseded latest-wins
170
- * mutations and aborted fetches from genuine failures.
189
+ * True iff `err` looks like an AbortError. Matches the standard `DOMException`
190
+ * shape thrown by `AbortController` AND any object whose `name === 'AbortError'`
191
+ * — that covers axios / msw / user-thrown plain Errors that signal abort.
171
192
  *
172
- * Spec: §20.12 checks `err instanceof DOMException && err.name === 'AbortError'`.
173
- * Node 17+ exposes a global DOMException, so this works server-side too.
193
+ * Spec: §20.12. Node 17+ exposes a global DOMException, so the instanceof
194
+ * branch works server-side; the name-based branch is the portable fallback.
174
195
  */
175
196
  declare function isAbortError(err: unknown): boolean;
176
197
  //#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 };
198
+ export { type AmbientDeps, type AsyncState, type AsyncStatus, type Collection, type CollectionFactoryApi, type CollectionFactoryOptions, type CollectionFactoryResult, type CollectionHomogeneousOptions, 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 EmitterErrorReporter, 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 LazyChild, 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, stableHash, throttled, untracked };
178
199
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/signals/runtime.ts","../src/controller/define.ts","../src/controller/root.ts","../src/forms/validators.ts","../src/forms/field.ts","../src/query/define.ts","../src/selection.ts","../src/timing/debounced.ts","../src/timing/throttled.ts","../src/utils.ts"],"mappings":";;;;;AAsEA;;;;iBAAgB,MAAA,GAAA,CAAU,OAAA,EAAS,CAAA,GAAI,MAAA,CAAO,CAAA;;;;;;;;;iBAY9B,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,QAAA,CAAS,CAAA;AAZJ;AAY/C;;;;;;;AAZ+C,iBAwB/B,MAAA,CAAO,EAA6B;;;;;iBAQpC,KAAA,GAAA,CAAS,EAAA,QAAU,CAAA,GAAI,CAAC;;AApBY;AAYpD;;;;iBAkBgB,SAAA,GAAA,CAAa,EAAA,QAAU,CAAA,GAAI,CAAC;;;;KCxGhC,uBAAA;ED8DI;;;;;;;;ECrDd,IAAI;AAAA;;;;;ADqDyC;AAY/C;;iBCvDgB,gBAAA,6BAAA,CACd,OAAA,GAAU,GAAA,EAAK,GAAA,EAAK,KAAA,EAAO,KAAA,KAAU,GAAA,EACrC,OAAA,GAAU,uBAAA,GACT,aAAA,CAAc,KAAA,EAAO,GAAA;;;;;;;iBC4HR,UAAA,oBAA8B,MAAA,oBAA0B,WAAA,CAAA,CACtE,GAAA,EAAK,aAAA,OAAoB,GAAA,GACzB,OAAA,EAAS,WAAA,CAAY,KAAA,IACpB,IAAA,CAAK,GAAA;;;;cCnJK,QAAA,MACP,OAAA,cAAuB,SAAS,CAAC,CAAA;AH2DvC;AAAA,cGtDa,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAY7B,KAAA,GACV,OAAA,cAAoC,SAAS;;cAOnC,OAAA,GACV,EAAA,EAAI,MAAA,EAAQ,OAAA,cAA6B,SAAS;;;;;;;;iBCwNrC,kBAAA,GAAA,CACd,EAAA,GAAK,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,WAAA,KAAgB,OAAA,iBACvC,EAAA,WACC,SAAA,CAAU,CAAA;;;;AJtNb;;;iBKtCgB,WAAA,2BAAA,CAAuC,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,CAAA,IAAK,KAAA,CAAM,IAAA,EAAM,CAAA;;;;;;;;iBAkE9E,mBAAA,mDAAsE,KAAA,CAAA,CACpF,IAAA,EAAM,iBAAA,CAAkB,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,KAAA,IAC/C,aAAA,CAAc,IAAA,EAAM,KAAA,EAAO,KAAA;;;;;AL9B9B;;;;;KM1DY,SAAA;EACV,WAAA,EAAa,UAAA,CAAW,WAAA;EACxB,IAAA,EAAM,UAAA;EACN,UAAA,CAAW,EAAA,WAAa,UAAA;EAExB,MAAA,CAAO,EAAA;EACP,QAAA,CAAS,EAAA;EACT,MAAA,CAAO,EAAA;EACP,KAAA;EACA,SAAA,CAAU,GAAA;EAEV,WAAA,CACE,EAAA,UACA,IAAA;IAAQ,KAAA;IAAiB,IAAA;EAAA,GACzB,OAAA;AAAA;;;;;;;;;;;ANwDgD;iBMzCpC,SAAA,aAAA,CAAuB,OAAA;EAAY,OAAA;AAAA,IAAgC,SAAS,CAAC,CAAA;;;;;AN6B7F;;;;;iBO5DgB,SAAA,GAAA,CAAa,MAAA,EAAQ,UAAA,CAAW,CAAA,GAAI,EAAA,WAAa,UAAA,CAAW,CAAA;;;;;AP4D5E;;;;;iBQ5DgB,SAAA,GAAA,CAAa,MAAA,EAAQ,UAAA,CAAW,CAAA,GAAI,EAAA,WAAa,UAAA,CAAW,CAAA;;;;;;AR4D5E;;;;iBS/DgB,YAAA,CAAa,GAAY"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/signals/runtime.ts","../src/controller/define.ts","../src/controller/root.ts","../src/forms/validators.ts","../src/forms/field.ts","../src/query/define.ts","../src/query/keys.ts","../src/selection.ts","../src/timing/debounced.ts","../src/timing/throttled.ts","../src/utils.ts"],"mappings":";;;;;AAsEA;;;;iBAAgB,MAAA,GAAA,CAAU,OAAA,EAAS,CAAA,GAAI,MAAA,CAAO,CAAA;;;;;;;;;iBAY9B,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,QAAA,CAAS,CAAA;AAZJ;AAY/C;;;;;;;AAZ+C,iBAwB/B,MAAA,CAAO,EAA6B;;;;;iBAQpC,KAAA,GAAA,CAAS,EAAA,QAAU,CAAA,GAAI,CAAC;;AApBY;AAYpD;;;;iBAkBgB,SAAA,GAAA,CAAa,EAAA,QAAU,CAAA,GAAI,CAAC;;;;KCxGhC,uBAAA;ED8DI;;;;;;;;ECrDd,IAAI;AAAA;;;;;ADqDyC;AAY/C;;iBCvDgB,gBAAA,6BAAA,CACd,OAAA,GAAU,GAAA,EAAK,GAAA,EAAK,KAAA,EAAO,KAAA,KAAU,GAAA,EACrC,OAAA,GAAU,uBAAA,GACT,aAAA,CAAc,KAAA,EAAO,GAAA;;;;;;;iBCoIR,UAAA,oBAA8B,MAAA,oBAA0B,WAAA,CAAA,CACtE,GAAA,EAAK,aAAA,OAAoB,GAAA,GACzB,OAAA,EAAS,WAAA,CAAY,KAAA,IACpB,IAAA,CAAK,GAAA;;;;cC3JK,QAAA,MACP,OAAA,cAAuB,SAAS,CAAC,CAAA;AH2DvC;AAAA,cGtDa,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAY7B,KAAA,GACV,OAAA,cAAoC,SAAS;;cAOnC,OAAA,GACV,EAAA,EAAI,MAAA,EAAQ,OAAA,cAA6B,SAAS;;;;;;;;iBCyRrC,kBAAA,GAAA,CACd,EAAA,GAAK,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,WAAA,KAAgB,OAAA,iBACvC,EAAA,WACC,SAAA,CAAU,CAAA;;;;AJvRb;;;iBKtCgB,WAAA,2BAAA,CAAuC,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,CAAA,IAAK,KAAA,CAAM,IAAA,EAAM,CAAA;;;;;;;;iBAyE9E,mBAAA,mDAAsE,KAAA,CAAA,CACpF,IAAA,EAAM,iBAAA,CAAkB,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,KAAA,IAC/C,aAAA,CAAc,IAAA,EAAM,KAAA,EAAO,KAAA;;;;;;ALrC9B;;;;;iBM9DgB,UAAA,CAAW,IAAwB;;;;;AN8DnD;;;;;KO1DY,SAAA;EACV,WAAA,EAAa,UAAA,CAAW,WAAA;EACxB,IAAA,EAAM,UAAA;EACN,UAAA,CAAW,EAAA,WAAa,UAAA;EAExB,MAAA,CAAO,EAAA;EACP,QAAA,CAAS,EAAA;EACT,MAAA,CAAO,EAAA;EACP,KAAA;EACA,SAAA,CAAU,GAAA;EAEV,WAAA,CACE,EAAA,UACA,IAAA;IAAQ,KAAA;IAAiB,IAAA;EAAA,GACzB,OAAA;AAAA;;;;;;;;;;;APwDgD;iBOzCpC,SAAA,aAAA,CAAuB,OAAA;EAAY,OAAA;AAAA,IAAgC,SAAS,CAAC,CAAA;;;;;AP6B7F;;;;;;;;iBQzDgB,SAAA,GAAA,CACd,MAAA,EAAQ,UAAA,CAAW,CAAA,GACnB,EAAA,UACA,OAAA;EAAY,MAAA,GAAS,WAAA;AAAA,IACpB,UAAA,CAAW,CAAA;;;;;ARqDd;;;;;;;iBS1DgB,SAAA,GAAA,CACd,MAAA,EAAQ,UAAA,CAAW,CAAA,GACnB,EAAA,UACA,OAAA;EAAY,MAAA,GAAS,WAAA;AAAA,IACpB,UAAA,CAAW,CAAA;;;;;;ATsDd;;;;;iBU9DgB,YAAA,CAAa,GAAY"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
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-CAMgqCMz.mjs";
1
+ import { $ as FormErrors, A as DebugEvent, B as QuerySpec, C as SetDataEvent, D as MutationSpec, E as MutationConcurrency, F as AsyncStatus, G as UseOptions, H as RetryDelay, I as DehydratedEntry, J as FieldArrayItemErrors, K as DeepPartial, L as DehydratedState, M as InfiniteQuerySpec, N as InfiniteQuerySubscription, O as DebugBus, P as AsyncState, Q as Form, R as LocalCache, S as RegisteredQuery, T as Mutation, U as RetryPolicy, V as QuerySubscription, W as Snapshot, X as FieldArrayValidator, Y as FieldArrayOptions, Z as FieldArrayValue, _ as defineScope, a as CollectionFactoryResult, at as Validator, b as QueryClientPlugin, c as CtrlApi, ct as Signal, d as Field, dt as EmitterErrorReporter, et as FormOptions, f as LazyChild, ft as createEmitter, g as ScopeOptions, h as Scope, i as CollectionFactoryOptions, it as ItemInitial, j as InfiniteQuery, k as DebugCacheEntry, l as CtrlProps, lt as ErrorContext, m as RootOptions, n as Collection, nt as FormValidator, o as CollectionHomogeneousOptions, ot as Computed, p as Root, q as FieldArray, r as CollectionFactoryApi, rt as FormValue, s as ControllerDef, st as ReadSignal, t as AmbientDeps, tt as FormSchema, u as Ctx, ut as Emitter, v as GcEvent, w as lookupRegisteredQuery, x as QueryClientPluginApi, y as InvalidateEvent, z as Query } from "./types-C-zV1JZA.mjs";
2
2
 
3
3
  //#region src/signals/runtime.d.ts
4
4
  /**
@@ -107,6 +107,17 @@ declare function defineQuery<Args extends unknown[], T>(spec: QuerySpec<Args, T>
107
107
  */
108
108
  declare function defineInfiniteQuery<Args extends unknown[], PageParam, TPage, TItem = TPage>(spec: InfiniteQuerySpec<Args, PageParam, TPage, TItem>): InfiniteQuery<Args, TPage, TItem>;
109
109
  //#endregion
110
+ //#region src/query/keys.d.ts
111
+ /**
112
+ * Stable string hash of a key tuple. Two equal-by-content args produce the
113
+ * same string regardless of property iteration order. Handles primitives,
114
+ * arrays, plain objects, Date.
115
+ *
116
+ * Functions and symbols throw — keys must be serializable so distinct
117
+ * subscribers can share entries.
118
+ */
119
+ declare function stableHash(args: readonly unknown[]): string;
120
+ //#endregion
110
121
  //#region src/selection.d.ts
111
122
  /**
112
123
  * Multi-select state for tables / lists with bulk actions (spec §17.5).
@@ -149,10 +160,15 @@ declare function selection<T = unknown>(options?: {
149
160
  * Lag a signal by `ms`. The returned signal updates only after the source has
150
161
  * been unchanged for `ms`. Each new write resets the timer.
151
162
  *
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.
163
+ * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a
164
+ * lifecycle when the signal aborts the effect disposes, the pending timer
165
+ * clears, and the subscriber chain on `source` drops. Without `signal`, the
166
+ * effect lives as long as `source` does; pass a signal whenever the source
167
+ * outlives the consumer.
154
168
  */
155
- declare function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
169
+ declare function debounced<T>(source: ReadSignal<T>, ms: number, options?: {
170
+ signal?: AbortSignal;
171
+ }): ReadSignal<T>;
156
172
  //#endregion
157
173
  //#region src/timing/throttled.d.ts
158
174
  /**
@@ -160,19 +176,24 @@ declare function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
160
176
  * The first change passes through immediately. Subsequent changes within the
161
177
  * window are coalesced; the latest value is emitted when the window expires.
162
178
  *
163
- * No lifecycle — see debounced() note.
179
+ * Pass `options.signal` to tie the internal effect to a lifecycle — when the
180
+ * signal aborts the effect disposes and any pending trailing timer clears.
181
+ * Without `signal`, the effect lives as long as `source` does.
164
182
  */
165
- declare function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T>;
183
+ declare function throttled<T>(source: ReadSignal<T>, ms: number, options?: {
184
+ signal?: AbortSignal;
185
+ }): ReadSignal<T>;
166
186
  //#endregion
167
187
  //#region src/utils.d.ts
168
188
  /**
169
- * True iff `err` is an AbortError. Used to filter superseded latest-wins
170
- * mutations and aborted fetches from genuine failures.
189
+ * True iff `err` looks like an AbortError. Matches the standard `DOMException`
190
+ * shape thrown by `AbortController` AND any object whose `name === 'AbortError'`
191
+ * — that covers axios / msw / user-thrown plain Errors that signal abort.
171
192
  *
172
- * Spec: §20.12 checks `err instanceof DOMException && err.name === 'AbortError'`.
173
- * Node 17+ exposes a global DOMException, so this works server-side too.
193
+ * Spec: §20.12. Node 17+ exposes a global DOMException, so the instanceof
194
+ * branch works server-side; the name-based branch is the portable fallback.
174
195
  */
175
196
  declare function isAbortError(err: unknown): boolean;
176
197
  //#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 };
198
+ export { type AmbientDeps, type AsyncState, type AsyncStatus, type Collection, type CollectionFactoryApi, type CollectionFactoryOptions, type CollectionFactoryResult, type CollectionHomogeneousOptions, 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 EmitterErrorReporter, 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 LazyChild, 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, stableHash, throttled, untracked };
178
199
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/signals/runtime.ts","../src/controller/define.ts","../src/controller/root.ts","../src/forms/validators.ts","../src/forms/field.ts","../src/query/define.ts","../src/selection.ts","../src/timing/debounced.ts","../src/timing/throttled.ts","../src/utils.ts"],"mappings":";;;;;AAsEA;;;;iBAAgB,MAAA,GAAA,CAAU,OAAA,EAAS,CAAA,GAAI,MAAA,CAAO,CAAA;;;;;;;;;iBAY9B,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,QAAA,CAAS,CAAA;AAZJ;AAY/C;;;;;;;AAZ+C,iBAwB/B,MAAA,CAAO,EAA6B;;;;;iBAQpC,KAAA,GAAA,CAAS,EAAA,QAAU,CAAA,GAAI,CAAC;;AApBY;AAYpD;;;;iBAkBgB,SAAA,GAAA,CAAa,EAAA,QAAU,CAAA,GAAI,CAAC;;;;KCxGhC,uBAAA;ED8DI;;;;;;;;ECrDd,IAAI;AAAA;;;;;ADqDyC;AAY/C;;iBCvDgB,gBAAA,6BAAA,CACd,OAAA,GAAU,GAAA,EAAK,GAAA,EAAK,KAAA,EAAO,KAAA,KAAU,GAAA,EACrC,OAAA,GAAU,uBAAA,GACT,aAAA,CAAc,KAAA,EAAO,GAAA;;;;;;;iBC4HR,UAAA,oBAA8B,MAAA,oBAA0B,WAAA,CAAA,CACtE,GAAA,EAAK,aAAA,OAAoB,GAAA,GACzB,OAAA,EAAS,WAAA,CAAY,KAAA,IACpB,IAAA,CAAK,GAAA;;;;cCnJK,QAAA,MACP,OAAA,cAAuB,SAAS,CAAC,CAAA;AH2DvC;AAAA,cGtDa,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAY7B,KAAA,GACV,OAAA,cAAoC,SAAS;;cAOnC,OAAA,GACV,EAAA,EAAI,MAAA,EAAQ,OAAA,cAA6B,SAAS;;;;;;;;iBCwNrC,kBAAA,GAAA,CACd,EAAA,GAAK,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,WAAA,KAAgB,OAAA,iBACvC,EAAA,WACC,SAAA,CAAU,CAAA;;;;AJtNb;;;iBKtCgB,WAAA,2BAAA,CAAuC,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,CAAA,IAAK,KAAA,CAAM,IAAA,EAAM,CAAA;;;;;;;;iBAkE9E,mBAAA,mDAAsE,KAAA,CAAA,CACpF,IAAA,EAAM,iBAAA,CAAkB,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,KAAA,IAC/C,aAAA,CAAc,IAAA,EAAM,KAAA,EAAO,KAAA;;;;;AL9B9B;;;;;KM1DY,SAAA;EACV,WAAA,EAAa,UAAA,CAAW,WAAA;EACxB,IAAA,EAAM,UAAA;EACN,UAAA,CAAW,EAAA,WAAa,UAAA;EAExB,MAAA,CAAO,EAAA;EACP,QAAA,CAAS,EAAA;EACT,MAAA,CAAO,EAAA;EACP,KAAA;EACA,SAAA,CAAU,GAAA;EAEV,WAAA,CACE,EAAA,UACA,IAAA;IAAQ,KAAA;IAAiB,IAAA;EAAA,GACzB,OAAA;AAAA;;;;;;;;;;;ANwDgD;iBMzCpC,SAAA,aAAA,CAAuB,OAAA;EAAY,OAAA;AAAA,IAAgC,SAAS,CAAC,CAAA;;;;;AN6B7F;;;;;iBO5DgB,SAAA,GAAA,CAAa,MAAA,EAAQ,UAAA,CAAW,CAAA,GAAI,EAAA,WAAa,UAAA,CAAW,CAAA;;;;;AP4D5E;;;;;iBQ5DgB,SAAA,GAAA,CAAa,MAAA,EAAQ,UAAA,CAAW,CAAA,GAAI,EAAA,WAAa,UAAA,CAAW,CAAA;;;;;;AR4D5E;;;;iBS/DgB,YAAA,CAAa,GAAY"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/signals/runtime.ts","../src/controller/define.ts","../src/controller/root.ts","../src/forms/validators.ts","../src/forms/field.ts","../src/query/define.ts","../src/query/keys.ts","../src/selection.ts","../src/timing/debounced.ts","../src/timing/throttled.ts","../src/utils.ts"],"mappings":";;;;;AAsEA;;;;iBAAgB,MAAA,GAAA,CAAU,OAAA,EAAS,CAAA,GAAI,MAAA,CAAO,CAAA;;;;;;;;;iBAY9B,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,QAAA,CAAS,CAAA;AAZJ;AAY/C;;;;;;;AAZ+C,iBAwB/B,MAAA,CAAO,EAA6B;;;;;iBAQpC,KAAA,GAAA,CAAS,EAAA,QAAU,CAAA,GAAI,CAAC;;AApBY;AAYpD;;;;iBAkBgB,SAAA,GAAA,CAAa,EAAA,QAAU,CAAA,GAAI,CAAC;;;;KCxGhC,uBAAA;ED8DI;;;;;;;;ECrDd,IAAI;AAAA;;;;;ADqDyC;AAY/C;;iBCvDgB,gBAAA,6BAAA,CACd,OAAA,GAAU,GAAA,EAAK,GAAA,EAAK,KAAA,EAAO,KAAA,KAAU,GAAA,EACrC,OAAA,GAAU,uBAAA,GACT,aAAA,CAAc,KAAA,EAAO,GAAA;;;;;;;iBCoIR,UAAA,oBAA8B,MAAA,oBAA0B,WAAA,CAAA,CACtE,GAAA,EAAK,aAAA,OAAoB,GAAA,GACzB,OAAA,EAAS,WAAA,CAAY,KAAA,IACpB,IAAA,CAAK,GAAA;;;;cC3JK,QAAA,MACP,OAAA,cAAuB,SAAS,CAAC,CAAA;AH2DvC;AAAA,cGtDa,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,SAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAQ7B,GAAA,GACV,CAAA,UAAW,OAAA,cAAmB,SAAS;;cAY7B,KAAA,GACV,OAAA,cAAoC,SAAS;;cAOnC,OAAA,GACV,EAAA,EAAI,MAAA,EAAQ,OAAA,cAA6B,SAAS;;;;;;;;iBCyRrC,kBAAA,GAAA,CACd,EAAA,GAAK,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,WAAA,KAAgB,OAAA,iBACvC,EAAA,WACC,SAAA,CAAU,CAAA;;;;AJvRb;;;iBKtCgB,WAAA,2BAAA,CAAuC,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,CAAA,IAAK,KAAA,CAAM,IAAA,EAAM,CAAA;;;;;;;;iBAyE9E,mBAAA,mDAAsE,KAAA,CAAA,CACpF,IAAA,EAAM,iBAAA,CAAkB,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,KAAA,IAC/C,aAAA,CAAc,IAAA,EAAM,KAAA,EAAO,KAAA;;;;;;ALrC9B;;;;;iBM9DgB,UAAA,CAAW,IAAwB;;;;;AN8DnD;;;;;KO1DY,SAAA;EACV,WAAA,EAAa,UAAA,CAAW,WAAA;EACxB,IAAA,EAAM,UAAA;EACN,UAAA,CAAW,EAAA,WAAa,UAAA;EAExB,MAAA,CAAO,EAAA;EACP,QAAA,CAAS,EAAA;EACT,MAAA,CAAO,EAAA;EACP,KAAA;EACA,SAAA,CAAU,GAAA;EAEV,WAAA,CACE,EAAA,UACA,IAAA;IAAQ,KAAA;IAAiB,IAAA;EAAA,GACzB,OAAA;AAAA;;;;;;;;;;;APwDgD;iBOzCpC,SAAA,aAAA,CAAuB,OAAA;EAAY,OAAA;AAAA,IAAgC,SAAS,CAAC,CAAA;;;;;AP6B7F;;;;;;;;iBQzDgB,SAAA,GAAA,CACd,MAAA,EAAQ,UAAA,CAAW,CAAA,GACnB,EAAA,UACA,OAAA;EAAY,MAAA,GAAS,WAAA;AAAA,IACpB,UAAA,CAAW,CAAA;;;;;ARqDd;;;;;;;iBS1DgB,SAAA,GAAA,CACd,MAAA,EAAQ,UAAA,CAAW,CAAA,GACnB,EAAA,UACA,OAAA;EAAY,MAAA,GAAS,WAAA;AAAA,IACpB,UAAA,CAAW,CAAA;;;;;;ATsDd;;;;;iBU9DgB,YAAA,CAAa,GAAY"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as lookupRegisteredQuery, c as batch, d as signal, f as untracked, i as createEmitter, l as computed, o as registerQueryById, p as defineController, r as debouncedValidator, s as isAbortError, t as createRoot, u as effect } from "./root-BImHnGj1.mjs";
1
+ import { a as lookupRegisteredQuery, c as isAbortError, d as effect, f as signal, i as createEmitter, l as batch, m as defineController, o as registerQueryById, p as untracked, r as debouncedValidator, s as stableHash, t as createRoot, u as computed } from "./root-De-6KWIZ.mjs";
2
2
  //#region src/signals/readonly.ts
3
3
  /**
4
4
  * Project a Signal (or any object with a reactive `value` + `peek` + `subscribe`)
@@ -8,7 +8,7 @@ import { a as lookupRegisteredQuery, c as batch, d as signal, f as untracked, i
8
8
  * Internal helper — not exported from the package's public surface.
9
9
  */
10
10
  function readOnly(source) {
11
- return {
11
+ return Object.freeze({
12
12
  get value() {
13
13
  return source.value;
14
14
  },
@@ -18,7 +18,7 @@ function readOnly(source) {
18
18
  subscribe(handler) {
19
19
  return source.subscribe(handler);
20
20
  }
21
- };
21
+ });
22
22
  }
23
23
  //#endregion
24
24
  //#region src/forms/validators.ts
@@ -271,14 +271,17 @@ function selection(options) {
271
271
  * Lag a signal by `ms`. The returned signal updates only after the source has
272
272
  * been unchanged for `ms`. Each new write resets the timer.
273
273
  *
274
- * No lifecycle the internal effect runs for the lifetime of the program.
275
- * Use inside a controller closure so it dies with the closure.
274
+ * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a
275
+ * lifecycle when the signal aborts the effect disposes, the pending timer
276
+ * clears, and the subscriber chain on `source` drops. Without `signal`, the
277
+ * effect lives as long as `source` does; pass a signal whenever the source
278
+ * outlives the consumer.
276
279
  */
277
- function debounced(source, ms) {
280
+ function debounced(source, ms, options) {
278
281
  const out = signal(source.peek());
279
282
  let timer = null;
280
283
  let initial = true;
281
- effect(() => {
284
+ const dispose = effect(() => {
282
285
  const value = source.value;
283
286
  if (initial) {
284
287
  initial = false;
@@ -290,6 +293,18 @@ function debounced(source, ms) {
290
293
  timer = null;
291
294
  }, ms);
292
295
  });
296
+ const sig = options?.signal;
297
+ if (sig) {
298
+ const stop = () => {
299
+ if (timer != null) {
300
+ clearTimeout(timer);
301
+ timer = null;
302
+ }
303
+ dispose();
304
+ };
305
+ if (sig.aborted) stop();
306
+ else sig.addEventListener("abort", stop, { once: true });
307
+ }
293
308
  return out;
294
309
  }
295
310
  //#endregion
@@ -299,15 +314,17 @@ function debounced(source, ms) {
299
314
  * The first change passes through immediately. Subsequent changes within the
300
315
  * window are coalesced; the latest value is emitted when the window expires.
301
316
  *
302
- * No lifecycle — see debounced() note.
317
+ * Pass `options.signal` to tie the internal effect to a lifecycle — when the
318
+ * signal aborts the effect disposes and any pending trailing timer clears.
319
+ * Without `signal`, the effect lives as long as `source` does.
303
320
  */
304
- function throttled(source, ms) {
321
+ function throttled(source, ms, options) {
305
322
  const out = signal(source.peek());
306
323
  let lastEmit = Number.NEGATIVE_INFINITY;
307
324
  let trailingTimer = null;
308
325
  let trailingValue = source.peek();
309
326
  let initial = true;
310
- effect(() => {
327
+ const dispose = effect(() => {
311
328
  const value = source.value;
312
329
  if (initial) {
313
330
  initial = false;
@@ -331,9 +348,21 @@ function throttled(source, ms) {
331
348
  }, ms - elapsed);
332
349
  }
333
350
  });
351
+ const sig = options?.signal;
352
+ if (sig) {
353
+ const stop = () => {
354
+ if (trailingTimer != null) {
355
+ clearTimeout(trailingTimer);
356
+ trailingTimer = null;
357
+ }
358
+ dispose();
359
+ };
360
+ if (sig.aborted) stop();
361
+ else sig.addEventListener("abort", stop, { once: true });
362
+ }
334
363
  return out;
335
364
  }
336
365
  //#endregion
337
- export { batch, computed, createEmitter, createRoot, debounced, debouncedValidator, defineController, defineInfiniteQuery, defineQuery, defineScope, effect, email, isAbortError, lookupRegisteredQuery, max, maxLength, min, minLength, pattern, required, selection, signal, throttled, untracked };
366
+ export { batch, computed, createEmitter, createRoot, debounced, debouncedValidator, defineController, defineInfiniteQuery, defineQuery, defineScope, effect, email, isAbortError, lookupRegisteredQuery, max, maxLength, min, minLength, pattern, required, selection, signal, stableHash, throttled, untracked };
338
367
 
339
368
  //# sourceMappingURL=index.mjs.map