From 58824c2443638b2b376093023419a36653c3b9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Thu, 8 Oct 2026 08:47:25 -0300 Subject: [PATCH 01/18] test(angular-pacer): define adapter rework contracts Replace implementation-focused tests with per-utility Angular regression coverage for deferred inputs, reactive options, selected state, operations, pacing, cleanup, and stability. Add the Angular testing setup and dependencies, require Angular 20+, and use the core Store dependency. These tests target the replacement APIs introduced by the following implementation commit. Validation of the complete change: pnpm test:pr passes. --- packages/angular-pacer/package.json | 18 +- packages/angular-pacer/test-setup.ts | 16 + .../async-batcher/injectAsyncBatcher.test.ts | 269 +++++++ .../injectAsyncDebouncer.test.ts | 266 +++++++ .../injectAsyncQueuedItems.test.ts | 40 + .../async-queuer/injectAsyncQueuer.test.ts | 226 ++++++ .../injectAsyncRateLimiter.test.ts | 249 +++++++ .../injectAsyncThrottler.test.ts | 266 +++++++ .../tests/batcher/injectBatcher.test.ts | 192 +++++ .../debouncer/injectDebouncedComputed.test.ts | 67 ++ .../debouncer/injectDebouncedSignal.test.ts | 112 +++ .../tests/debouncer/injectDebouncer.test.ts | 254 +++++++ .../tests/helper-options.test.ts | 158 ---- packages/angular-pacer/tests/options.test.ts | 101 --- .../provider/providePacerOptions.test.ts | 182 +++++ .../angular-pacer/tests/queued-value.test.ts | 201 ----- .../tests/queuer/injectQueuedComputed.test.ts | 79 ++ .../tests/queuer/injectQueuedItems.test.ts | 83 +++ .../tests/queuer/injectQueuedSignal.test.ts | 75 ++ .../tests/queuer/injectQueuer.test.ts | 218 ++++++ .../injectRateLimitedComputed.test.ts | 62 ++ .../injectRateLimitedSignal.test.ts | 76 ++ .../rate-limiter/injectRateLimiter.test.ts | 213 ++++++ .../tests/reactive-options.test.ts | 699 ------------------ .../throttler/injectThrottledComputed.test.ts | 66 ++ .../throttler/injectThrottledSignal.test.ts | 93 +++ .../tests/throttler/injectThrottler.test.ts | 239 ++++++ .../utils/injectPendingTasksLifecycle.test.ts | 49 ++ .../tests/utils/injectTestStability.ts | 20 + packages/angular-pacer/tsconfig.json | 5 +- packages/angular-pacer/tsconfig.spec.json | 17 + packages/angular-pacer/vitest.config.ts | 11 +- pnpm-lock.yaml | 683 ++++++++++++++++- 33 files changed, 4114 insertions(+), 1191 deletions(-) create mode 100644 packages/angular-pacer/test-setup.ts create mode 100644 packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts create mode 100644 packages/angular-pacer/tests/async-debouncer/injectAsyncDebouncer.test.ts create mode 100644 packages/angular-pacer/tests/async-queuer/injectAsyncQueuedItems.test.ts create mode 100644 packages/angular-pacer/tests/async-queuer/injectAsyncQueuer.test.ts create mode 100644 packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts create mode 100644 packages/angular-pacer/tests/async-throttler/injectAsyncThrottler.test.ts create mode 100644 packages/angular-pacer/tests/batcher/injectBatcher.test.ts create mode 100644 packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts create mode 100644 packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts create mode 100644 packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts delete mode 100644 packages/angular-pacer/tests/helper-options.test.ts delete mode 100644 packages/angular-pacer/tests/options.test.ts create mode 100644 packages/angular-pacer/tests/provider/providePacerOptions.test.ts delete mode 100644 packages/angular-pacer/tests/queued-value.test.ts create mode 100644 packages/angular-pacer/tests/queuer/injectQueuedComputed.test.ts create mode 100644 packages/angular-pacer/tests/queuer/injectQueuedItems.test.ts create mode 100644 packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts create mode 100644 packages/angular-pacer/tests/queuer/injectQueuer.test.ts create mode 100644 packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts create mode 100644 packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts create mode 100644 packages/angular-pacer/tests/rate-limiter/injectRateLimiter.test.ts delete mode 100644 packages/angular-pacer/tests/reactive-options.test.ts create mode 100644 packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts create mode 100644 packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts create mode 100644 packages/angular-pacer/tests/throttler/injectThrottler.test.ts create mode 100644 packages/angular-pacer/tests/utils/injectPendingTasksLifecycle.test.ts create mode 100644 packages/angular-pacer/tests/utils/injectTestStability.ts create mode 100644 packages/angular-pacer/tsconfig.spec.json diff --git a/packages/angular-pacer/package.json b/packages/angular-pacer/package.json index aa11d7cc0..605809882 100644 --- a/packages/angular-pacer/package.json +++ b/packages/angular-pacer/package.json @@ -27,7 +27,7 @@ "clean": "premove ./build ./dist", "lint:fix": "eslint ./src --fix", "test:eslint": "eslint ./src", - "test:lib": "vitest --passWithNoTests", + "test:lib": "vitest run", "test:lib:dev": "pnpm test:lib --watch", "test:types": "tsc", "build": "tsdown", @@ -61,16 +61,24 @@ "dist" ], "dependencies": { - "@tanstack/angular-store": "^0.11.2", - "@tanstack/pacer": "workspace:*" + "@tanstack/pacer": "workspace:*", + "@tanstack/store": "^0.11.2" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^2.8.0", + "@angular/build": "^22.2.1", + "@angular/common": "^22.2.1", "@angular/compiler": "^22.2.1", + "@angular/compiler-cli": "^22.2.1", "@angular/core": "^22.2.1", "@angular/platform-browser": "^22.2.1", - "@types/node": "^26.6.4" + "@angular/router": "^22.2.1", + "@testing-library/angular": "^19.5.0", + "@testing-library/dom": "^10.4.1", + "@types/node": "^26.6.4", + "jsdom": "^30.1.1" }, "peerDependencies": { - "@angular/core": ">=17.0.0" + "@angular/core": ">=20.0.0" } } diff --git a/packages/angular-pacer/test-setup.ts b/packages/angular-pacer/test-setup.ts new file mode 100644 index 000000000..1d57a4890 --- /dev/null +++ b/packages/angular-pacer/test-setup.ts @@ -0,0 +1,16 @@ +import '@testing-library/jest-dom/vitest' +import '@angular/compiler' +import { TestBed, getTestBed } from '@angular/core/testing' +import { + BrowserTestingModule, + platformBrowserTesting, +} from '@angular/platform-browser/testing' + +import { afterEach, vi } from 'vitest' + +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()) + +afterEach(() => { + TestBed.resetTestingModule() + vi.useRealTimers() +}) diff --git a/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts b/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts new file mode 100644 index 000000000..314411e23 --- /dev/null +++ b/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts @@ -0,0 +1,269 @@ +/// + +import { Component, computed, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { AsyncBatcher } from '@tanstack/pacer/async-batcher' +import { render } from '@testing-library/angular' +import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectAsyncBatcher } from '../../src/async-batcher/injectAsyncBatcher' +import { injectTestStability } from '../utils/injectTestStability' + +describe('selected state', () => { + it('returns empty state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher(async () => {}), + ) + expect(utility.state()).toEqual({}) + expectTypeOf(utility.state()).toEqualTypeOf>() + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + + TestBed.runInInjectionContext(() => { + // @ts-expect-error A caller-selected state type requires a selector. + injectAsyncBatcher(async () => {}) + }) + }) + + it('infers selected fields and updates them after execution', async () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher( + async (_items: Array) => {}, + { maxSize: 1 }, + (state) => ({ count: state.executionCount }), + ), + ) + TestBed.tick() + expectTypeOf(utility.state()).toEqualTypeOf>() + expect(utility.state()).toEqual({ count: 0 }) + await utility.addItem(1) + expect(utility.state()).toEqual({ count: 1 }) + }) +}) + +describe('stability', () => { + it('awaits automatic execution triggered by async batch capacity, including result callbacks', async () => { + const stability = injectTestStability() + + const { promise, resolve } = Promise.withResolvers() + let applied = '' + const batcher = TestBed.runInInjectionContext(() => + injectAsyncBatcher((_items: Array) => promise, { + maxSize: 1, + onSuccess: (value) => { + applied = value + }, + }), + ) + + const execution = batcher.addItem('job') + const stabilityPromise = stability.whenStable() + + try { + expect(stability.isStable).toBe(false) + } finally { + resolve('applied') + await execution + await stabilityPromise + } + expect(applied).toBe('applied') + expect(stability.isStable).toBe(true) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup policy without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectAsyncBatcher( + async () => {}, + () => ({ + wait: Infinity, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(latestCleanup).toHaveBeenCalledTimes(1) + expect(oldCleanup).not.toHaveBeenCalled() + }) + + it('passes the core batcher with its queued items to custom cleanup', async () => { + const cleanup = vi.fn<(core: AsyncBatcher) => void>() + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher(async (_items: Array) => {}, { + wait: Infinity, + onUnmount: cleanup, + }), + ) + TestBed.tick() + await utility.addItem('queued') + TestBed.resetTestingModule() + expect(cleanup).toHaveBeenCalledOnce() + const core = cleanup.mock.calls[0]![0] + expect(core).toBeInstanceOf(AsyncBatcher) + expect(core.peekAllItems()).toEqual(['queued']) + }) + + it('cancels a scheduled batch on destruction after options change', async () => { + vi.useFakeTimers() + const wait = signal(10) + const process = vi.fn(async (_items: Array) => {}) + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher(process, () => ({ wait: wait() })), + ) + TestBed.tick() + const pending = utility.addItem('job') + wait.set(20) + TestBed.tick() + TestBed.resetTestingModule() + await vi.advanceTimersByTimeAsync(20) + await pending + expect(process).not.toHaveBeenCalled() + }) +}) + +describe('options', () => { + beforeEach(() => vi.useFakeTimers()) + + it('updates factory options without losing queued items and uses the latest cleanup', async () => { + const first = vi.fn() + const latest = vi.fn() + const maxSize = signal(3) + const process = vi.fn(async (_items: Array) => {}) + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher(process, () => ({ + wait: Infinity, + maxSize: maxSize(), + onUnmount: maxSize() === 3 ? first : latest, + })), + ) + TestBed.tick() + await utility.addItem('first') + expect(process).not.toHaveBeenCalled() + maxSize.set(2) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first']) + await utility.addItem('second') + expect(process).toHaveBeenCalledExactlyOnceWith(['first', 'second']) + expect(first).not.toHaveBeenCalled() + expect(latest).not.toHaveBeenCalled() + TestBed.resetTestingModule() + expect(first).not.toHaveBeenCalled() + expect(latest).toHaveBeenCalledOnce() + expect(latest.mock.calls[0]![0]).toBeInstanceOf(AsyncBatcher) + }) + + it('uses changed options for an operation before the options effect runs', async () => { + const maxSize = signal(3) + const process = vi.fn(async (_items: Array) => {}) + const utility = TestBed.runInInjectionContext(() => + injectAsyncBatcher(process, () => ({ + wait: Infinity, + maxSize: maxSize(), + })), + ) + TestBed.tick() + await utility.addItem('first') + maxSize.set(2) + await utility.addItem('second') + expect(process).toHaveBeenCalledExactlyOnceWith(['first', 'second']) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + maxSize = input.required() + process = vi.fn(async (_items: Array) => {}) + utility = injectAsyncBatcher( + this.process, + () => ({ + wait: Infinity, + maxSize: this.maxSize(), + }), + (state) => state.size, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers required options during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + + fixture.componentRef.setInput('maxSize', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + + await component.utility.addItem('first') + fixture.detectChanges() + expect(component.derived()).toBe(1) + }) + + it('uses bound and changed input options before effects run', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const { utility, process } = fixture.componentInstance + + fixture.componentRef.setInput('maxSize', 3) + await utility.addItem('first') + expect(process).not.toHaveBeenCalled() + + fixture.componentRef.setInput('maxSize', 2) + await utility.addItem('second') + expect(process).toHaveBeenCalledExactlyOnceWith(['first', 'second']) + }) + + @Component({ template: '' }) + class RequiredSelector { + multiplier = input.required() + utility = injectAsyncBatcher( + async (_items: Array) => {}, + { wait: Infinity }, + (state) => state.size * this.multiplier(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + + fixture.componentRef.setInput('multiplier', 2) + await component.utility.addItem('first') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + + fixture.componentRef.setInput('multiplier', 3) + expect(component.derived()).toBe(3) + }) + + it('destroys the ref before required options are bound without evaluating them', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) +}) diff --git a/packages/angular-pacer/tests/async-debouncer/injectAsyncDebouncer.test.ts b/packages/angular-pacer/tests/async-debouncer/injectAsyncDebouncer.test.ts new file mode 100644 index 000000000..b4f30babc --- /dev/null +++ b/packages/angular-pacer/tests/async-debouncer/injectAsyncDebouncer.test.ts @@ -0,0 +1,266 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectAsyncDebouncer } from '../../src/async-debouncer/injectAsyncDebouncer' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer( + process, + { wait: 0, leading: true, trailing: false }, + (state) => ({ count: state.maybeExecuteCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer(async (item: string) => item, { + wait: 0, + leading: true, + trailing: false, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer( + async (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.maybeExecuteCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectAsyncDebouncer( + async (item: string) => item, + () => ({ + ...{ wait: 0, leading: true, trailing: false }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(AsyncDebouncer) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectAsyncDebouncer( + async (item: string) => item, + () => ({ wait: this.value(), leading: true, trailing: false }), + (state) => state.maybeExecuteCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectAsyncDebouncer( + async (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.maybeExecuteCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer( + process, + () => ({ wait: 0, leading: true, trailing: false, enabled: enabled() }), + (state) => state.maybeExecuteCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn(async (_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectAsyncDebouncer(process, { + ...{ wait: 0, leading: true, trailing: false }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('uses changed wait inputs before effects run and cancels scheduled work on destruction', async () => { + vi.useFakeTimers() + const process = vi.fn(async (item: string) => item) + @Component({ template: '' }) + class Host { + wait = input.required() + utility = injectAsyncDebouncer(process, () => ({ + wait: this.wait(), + leading: false, + trailing: true, + })) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.componentRef.setInput('wait', 10) + fixture.detectChanges() + fixture.componentRef.setInput('wait', 20) + const execution = fixture.componentInstance.utility.maybeExecute('first') + await vi.advanceTimersByTimeAsync(10) + expect(process).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(10) + await execution + expect(process).toHaveBeenCalledExactlyOnceWith('first') + const canceled = fixture.componentInstance.utility.maybeExecute('canceled') + fixture.destroy() + await vi.advanceTimersByTimeAsync(20) + await canceled + expect(process).toHaveBeenCalledOnce() +}) + +it('holds stability until the asynchronous result and success callback are applied', async () => { + const stability = injectTestStability() + const { promise, resolve } = Promise.withResolvers() + let applied = '' + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer((_item: string) => promise, { + ...{ wait: 0, leading: true, trailing: false }, + onSuccess: (value) => { + applied = value + }, + }), + ) + TestBed.tick() + const execution = utility.maybeExecute('job') + TestBed.tick() + const stable = stability.whenStable() + try { + expect(stability.isStable).toBe(false) + } finally { + resolve('applied') + await execution + await stable + } + expect(applied).toBe('applied') + expect(stability.isStable).toBe(true) +}) + +it('preserves awaited failures without reporting them to Angular globally', async () => { + const error = new Error('failed') + const utility = TestBed.runInInjectionContext(() => + injectAsyncDebouncer( + async (_item: string) => { + throw error + }, + { wait: 0, leading: true, trailing: false }, + (state) => state.errorCount, + ), + ) + TestBed.tick() + await expect(utility.maybeExecute('job')).rejects.toBe(error) + expect(utility.state()).toBe(1) + TestBed.tick() + await injectTestStability().whenStable() +}) diff --git a/packages/angular-pacer/tests/async-queuer/injectAsyncQueuedItems.test.ts b/packages/angular-pacer/tests/async-queuer/injectAsyncQueuedItems.test.ts new file mode 100644 index 000000000..c1ff4333f --- /dev/null +++ b/packages/angular-pacer/tests/async-queuer/injectAsyncQueuedItems.test.ts @@ -0,0 +1,40 @@ +import { isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' + +import { expect, expectTypeOf, it } from 'vitest' +import { injectAsyncQueuerItems } from '../../src/async-queuer/injectAsyncQueuedItems' + +it('returns an Angular signal with the queuer as an attribute', () => { + const value = TestBed.runInInjectionContext(() => + injectAsyncQueuerItems(async (_item: string) => {}, { started: false }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf>() + expect(value).toHaveProperty('queuer') + TestBed.tick() + expect(value.queuer.addItem('queued')).toBe(true) + expect(value()).toEqual(['queued']) +}) + +it('keeps items and selected state separate and forwards the addItem shortcut', () => { + const factor = signal(2) + const value = TestBed.runInInjectionContext(() => + injectAsyncQueuerItems( + async (_item: string) => {}, + { started: false }, + (state) => ({ count: state.size * factor() }), + ), + ) + TestBed.tick() + expectTypeOf(value.queuer.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + expect(value.addItem).toBe(value.queuer.addItem) + expect(value.addItem('back')).toBe(true) + expect(value.addItem('front', 'front')).toBe(true) + expect(value()).toEqual(['front', 'back']) + expect(value.queuer.state()).toEqual({ count: 4 }) + factor.set(3) + expect(value.queuer.state()).toEqual({ count: 6 }) + expect(value()).toEqual(['front', 'back']) +}) diff --git a/packages/angular-pacer/tests/async-queuer/injectAsyncQueuer.test.ts b/packages/angular-pacer/tests/async-queuer/injectAsyncQueuer.test.ts new file mode 100644 index 000000000..b4c569604 --- /dev/null +++ b/packages/angular-pacer/tests/async-queuer/injectAsyncQueuer.test.ts @@ -0,0 +1,226 @@ +/// + +import { Component, computed, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { AsyncQueuer } from '@tanstack/pacer/async-queuer' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectAsyncQueuer } from '../../src/async-queuer/injectAsyncQueuer' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncQueuer( + process, + { started: false, wait: 0, maxSize: 3 }, + (state) => ({ count: state.executionCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + utility.addItem('job') + await utility.execute() + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncQueuer(async (item: string) => item, { + started: false, + wait: 0, + maxSize: 3, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectAsyncQueuer( + async (item: string) => item, + { started: false, wait: 0, maxSize: 3 }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + utility.addItem('job') + await utility.execute() + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectAsyncQueuer( + async (item: string) => item, + () => ({ + ...{ started: false, wait: 0, maxSize: 3 }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(AsyncQueuer) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectAsyncQueuer( + async (item: string) => item, + () => ({ started: false, maxSize: this.value() }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectAsyncQueuer( + async (item: string) => item, + { started: false, wait: 0, maxSize: 3 }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + component.utility.addItem('job') + await component.utility.execute() + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +it('preserves initial items across options updates and uses changed capacity before effects run', async () => { + const maxSize = signal(1) + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncQueuer(process, () => ({ + started: false, + initialItems: ['first'], + maxSize: maxSize(), + })), + ) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first']) + expect(utility.addItem('rejected')).toBe(false) + maxSize.set(2) + expect(utility.addItem('second')).toBe(true) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first', 'second']) + await utility.flush() + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('loads initial items lazily and processes them automatically', async () => { + const process = vi.fn(async (item: string) => item) + const stability = injectTestStability() + TestBed.runInInjectionContext(() => + injectAsyncQueuer(process, { initialItems: ['first', 'second'], wait: 0 }), + ) + expect(process).not.toHaveBeenCalled() + TestBed.tick() + await stability.whenStable() + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('starts restored queued items whose pendingTick has no live timer', async () => { + const process = vi.fn(async (item: string) => item) + const stability = injectTestStability() + TestBed.runInInjectionContext(() => + injectAsyncQueuer(process, { + initialState: { items: ['job'], isRunning: true, pendingTick: true }, + wait: 0, + }), + ) + TestBed.tick() + await stability.whenStable() + expect(process).toHaveBeenCalledExactlyOnceWith('job') +}) + +it('holds stability until the asynchronous result and success callback are applied', async () => { + const stability = injectTestStability() + const { promise, resolve } = Promise.withResolvers() + let applied = '' + const utility = TestBed.runInInjectionContext(() => + injectAsyncQueuer((_item: string) => promise, { + ...{ started: true, wait: 0 }, + onSuccess: (value) => { + applied = value + }, + }), + ) + TestBed.tick() + utility.addItem('job') + TestBed.tick() + const stable = stability.whenStable() + try { + expect(stability.isStable).toBe(false) + } finally { + resolve('applied') + + await stable + } + expect(applied).toBe('applied') + expect(stability.isStable).toBe(true) +}) diff --git a/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts b/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts new file mode 100644 index 000000000..188593943 --- /dev/null +++ b/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts @@ -0,0 +1,249 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectAsyncRateLimiter } from '../../src/async-rate-limiter/injectAsyncRateLimiter' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter(process, { limit: 2, window: 10 }, (state) => ({ + count: state.maybeExecuteCount, + })), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter(async (item: string) => item, { + limit: 2, + window: 10, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter( + async (item: string) => item, + { limit: 2, window: 10 }, + (state) => state.maybeExecuteCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectAsyncRateLimiter( + async (item: string) => item, + () => ({ ...{ limit: 2, window: 10 }, onUnmount: policy() }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(AsyncRateLimiter) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectAsyncRateLimiter( + async (item: string) => item, + () => ({ limit: this.value(), window: 10 }), + (state) => state.maybeExecuteCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectAsyncRateLimiter( + async (item: string) => item, + { limit: 2, window: 10 }, + (state) => state.maybeExecuteCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter( + process, + () => ({ limit: 2, window: 10, enabled: enabled() }), + (state) => state.successCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn(async (_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectAsyncRateLimiter(process, { + ...{ limit: 2, window: 10 }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('updates the limit without resetting executions already in the window', async () => { + vi.useFakeTimers() + const limit = signal(1) + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter(process, () => ({ limit: limit(), window: 1000 })), + ) + TestBed.tick() + await utility.maybeExecute('first') + await utility.maybeExecute('rejected') + expect(process).toHaveBeenCalledOnce() + limit.set(2) + TestBed.tick() + await utility.maybeExecute('second') + await utility.maybeExecute('also rejected') + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('holds stability until the asynchronous result and success callback are applied', async () => { + const stability = injectTestStability() + const { promise, resolve } = Promise.withResolvers() + let applied = '' + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter((_item: string) => promise, { + ...{ limit: 2, window: 10 }, + onSuccess: (value) => { + applied = value + }, + }), + ) + TestBed.tick() + const execution = utility.maybeExecute('job') + TestBed.tick() + const stable = stability.whenStable() + try { + expect(stability.isStable).toBe(false) + } finally { + resolve('applied') + await execution + await stable + } + expect(applied).toBe('applied') + expect(stability.isStable).toBe(true) +}) + +it('preserves awaited failures without reporting them to Angular globally', async () => { + const error = new Error('failed') + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter( + async (_item: string) => { + throw error + }, + { limit: 2, window: 10 }, + (state) => state.errorCount, + ), + ) + TestBed.tick() + await expect(utility.maybeExecute('job')).rejects.toBe(error) + expect(utility.state()).toBe(1) + TestBed.tick() + await injectTestStability().whenStable() +}) diff --git a/packages/angular-pacer/tests/async-throttler/injectAsyncThrottler.test.ts b/packages/angular-pacer/tests/async-throttler/injectAsyncThrottler.test.ts new file mode 100644 index 000000000..570d3c445 --- /dev/null +++ b/packages/angular-pacer/tests/async-throttler/injectAsyncThrottler.test.ts @@ -0,0 +1,266 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { AsyncThrottler } from '@tanstack/pacer/async-throttler' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectAsyncThrottler } from '../../src/async-throttler/injectAsyncThrottler' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler( + process, + { wait: 0, leading: true, trailing: false }, + (state) => ({ count: state.maybeExecuteCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler(async (item: string) => item, { + wait: 0, + leading: true, + trailing: false, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler( + async (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.maybeExecuteCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectAsyncThrottler( + async (item: string) => item, + () => ({ + ...{ wait: 0, leading: true, trailing: false }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(AsyncThrottler) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectAsyncThrottler( + async (item: string) => item, + () => ({ wait: this.value(), leading: true, trailing: false }), + (state) => state.maybeExecuteCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectAsyncThrottler( + async (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.maybeExecuteCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn(async (item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler( + process, + () => ({ wait: 0, leading: true, trailing: false, enabled: enabled() }), + (state) => state.maybeExecuteCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn(async (_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectAsyncThrottler(process, { + ...{ wait: 0, leading: true, trailing: false }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('uses changed wait inputs before effects run and cancels scheduled work on destruction', async () => { + vi.useFakeTimers() + const process = vi.fn(async (item: string) => item) + @Component({ template: '' }) + class Host { + wait = input.required() + utility = injectAsyncThrottler(process, () => ({ + wait: this.wait(), + leading: false, + trailing: true, + })) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.componentRef.setInput('wait', 10) + fixture.detectChanges() + fixture.componentRef.setInput('wait', 20) + const execution = fixture.componentInstance.utility.maybeExecute('first') + await vi.advanceTimersByTimeAsync(10) + expect(process).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(10) + await execution + expect(process).toHaveBeenCalledExactlyOnceWith('first') + const canceled = fixture.componentInstance.utility.maybeExecute('canceled') + fixture.destroy() + await vi.advanceTimersByTimeAsync(20) + await canceled + expect(process).toHaveBeenCalledOnce() +}) + +it('holds stability until the asynchronous result and success callback are applied', async () => { + const stability = injectTestStability() + const { promise, resolve } = Promise.withResolvers() + let applied = '' + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler((_item: string) => promise, { + ...{ wait: 0, leading: true, trailing: false }, + onSuccess: (value) => { + applied = value + }, + }), + ) + TestBed.tick() + const execution = utility.maybeExecute('job') + TestBed.tick() + const stable = stability.whenStable() + try { + expect(stability.isStable).toBe(false) + } finally { + resolve('applied') + await execution + await stable + } + expect(applied).toBe('applied') + expect(stability.isStable).toBe(true) +}) + +it('preserves awaited failures without reporting them to Angular globally', async () => { + const error = new Error('failed') + const utility = TestBed.runInInjectionContext(() => + injectAsyncThrottler( + async (_item: string) => { + throw error + }, + { wait: 0, leading: true, trailing: false }, + (state) => state.errorCount, + ), + ) + TestBed.tick() + await expect(utility.maybeExecute('job')).rejects.toBe(error) + expect(utility.state()).toBe(1) + TestBed.tick() + await injectTestStability().whenStable() +}) diff --git a/packages/angular-pacer/tests/batcher/injectBatcher.test.ts b/packages/angular-pacer/tests/batcher/injectBatcher.test.ts new file mode 100644 index 000000000..0d0c8b541 --- /dev/null +++ b/packages/angular-pacer/tests/batcher/injectBatcher.test.ts @@ -0,0 +1,192 @@ +/// + +import { Component, computed, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { Batcher } from '@tanstack/pacer/batcher' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectBatcher } from '../../src/batcher/injectBatcher' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn((item: Array) => item) + const utility = TestBed.runInInjectionContext(() => + injectBatcher(process, { wait: Infinity, maxSize: 3 }, (state) => ({ + count: state.executionCount, + })), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + utility.addItem('job') + utility.flush() + expect(process).toHaveBeenCalledExactlyOnceWith(['job']) + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectBatcher((item: Array) => item, { + wait: Infinity, + maxSize: 3, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectBatcher( + (item: Array) => item, + { wait: Infinity, maxSize: 3 }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + utility.addItem('job') + utility.flush() + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectBatcher( + (item: Array) => item, + () => ({ ...{ wait: Infinity, maxSize: 3 }, onUnmount: policy() }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(Batcher) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectBatcher( + (item: Array) => item, + () => ({ wait: Infinity, maxSize: this.value() }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectBatcher( + (item: Array) => item, + { wait: Infinity, maxSize: 3 }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + component.utility.addItem('job') + component.utility.flush() + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +it('preserves queued items across factory updates and applies changed capacity before effects run', () => { + const maxSize = signal(3) + const process = vi.fn((item: Array) => item) + const utility = TestBed.runInInjectionContext(() => + injectBatcher(process, () => ({ wait: Infinity, maxSize: maxSize() })), + ) + TestBed.tick() + utility.addItem('first') + maxSize.set(2) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first']) + utility.addItem('second') + expect(process).toHaveBeenCalledExactlyOnceWith(['first', 'second']) +}) + +it('cancels scheduled batches on destruction', async () => { + vi.useFakeTimers() + const process = vi.fn((item: Array) => item) + const utility = TestBed.runInInjectionContext(() => + injectBatcher(process, { wait: 10 }), + ) + TestBed.tick() + utility.addItem('job') + TestBed.resetTestingModule() + await vi.advanceTimersByTimeAsync(10) + expect(process).not.toHaveBeenCalled() +}) + +it('holds stability until scheduled synchronous work finishes', async () => { + vi.useFakeTimers() + const stability = injectTestStability() + const process = vi.fn((item: Array) => item) + const utility = TestBed.runInInjectionContext(() => + injectBatcher(process, { wait: 10 }), + ) + TestBed.tick() + utility.addItem('job') + TestBed.tick() + expect(stability.isStable).toBe(false) + await vi.advanceTimersByTimeAsync(10) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + expect(process).toHaveBeenCalledTimes(1) +}) diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts new file mode 100644 index 000000000..0eacdf67f --- /dev/null +++ b/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts @@ -0,0 +1,67 @@ +import { isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectDebouncedComputed } from '../../src/debouncer/injectDebouncedComputed' + +it('returns an Angular signal with the debouncer as an attribute', () => { + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectDebouncedComputed(source, { wait: 100 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe('initial') + expect(value).toHaveProperty('debouncer') + TestBed.tick() + source.set('updated') + TestBed.tick() + expect(value()).toBe('initial') + value.debouncer.flush() + expect(value()).toBe('updated') +}) + +it('debounces source changes until the delay after the latest value', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectDebouncedComputed(source, { wait: 100 }), + ) + TestBed.tick() + expect(value()).toBe('initial') + + source.set('first') + TestBed.tick() + expect(value()).toBe('initial') + vi.advanceTimersByTime(60) + expect(value()).toBe('initial') + + source.set('latest') + TestBed.tick() + vi.advanceTimersByTime(40) + expect(value()).toBe('initial') + vi.advanceTimersByTime(59) + expect(value()).toBe('initial') + vi.advanceTimersByTime(1) + expect(value()).toBe('latest') +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectDebouncedComputed(source, { wait: 100 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.debouncer.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + value.debouncer.flush() + const before = value.debouncer.state().count + source.set('updated') + TestBed.tick() + value.debouncer.flush() + expect(value()).toBe('updated') + expect(value.debouncer.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts new file mode 100644 index 000000000..d50509d98 --- /dev/null +++ b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts @@ -0,0 +1,112 @@ +import { isSignal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectDebouncedSignal } from '../../src/debouncer/injectDebouncedSignal' + +it('returns an Angular signal with the debouncer as an attribute', () => { + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(0, { wait: 0 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe(0) + expect(value).toHaveProperty('debouncer') + TestBed.tick() + const callback = vi.fn() + value.debouncer.maybeExecute(callback) + expect(callback).not.toHaveBeenCalled() + value.debouncer.flush() + expect(callback).toHaveBeenCalledOnce() +}) + +it('debounces set calls until the delay after the latest value', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal('initial', { wait: 100 }), + ) + TestBed.tick() + + value.set('first') + expect(value()).toBe('initial') + vi.advanceTimersByTime(60) + expect(value()).toBe('initial') + + value.set('latest') + vi.advanceTimersByTime(40) + expect(value()).toBe('initial') + vi.advanceTimersByTime(59) + expect(value()).toBe('initial') + vi.advanceTimersByTime(1) + expect(value()).toBe('latest') +}) + +it('debounces update calls and applies only the latest callback to the current value', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(10, { wait: 100 }), + ) + const first = vi.fn((previous: number) => previous + 1) + const latest = vi.fn((previous: number) => previous * 2) + TestBed.tick() + + value.update(first) + vi.advanceTimersByTime(60) + value.update(latest) + expect(value()).toBe(10) + expect(first).not.toHaveBeenCalled() + expect(latest).not.toHaveBeenCalled() + + vi.advanceTimersByTime(99) + expect(value()).toBe(10) + expect(latest).not.toHaveBeenCalled() + vi.advanceTimersByTime(1) + expect(value()).toBe(20) + expect(first).not.toHaveBeenCalled() + expect(latest).toHaveBeenCalledExactlyOnceWith(10) +}) + +it('supports set followed by update using the current committed value', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(0, { wait: 100 }), + ) + const update = vi.fn((previous: number) => previous + 5) + TestBed.tick() + + value.set(10) + vi.advanceTimersByTime(99) + expect(value()).toBe(0) + vi.advanceTimersByTime(1) + expect(value()).toBe(10) + + value.update(update) + vi.advanceTimersByTime(99) + expect(value()).toBe(10) + expect(update).not.toHaveBeenCalled() + vi.advanceTimersByTime(1) + expect(update).toHaveBeenCalledExactlyOnceWith(10) + expect(value()).toBe(15) + + value.set(2) + vi.advanceTimersByTime(100) + expect(value()).toBe(2) +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal('initial', { wait: 100 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.debouncer.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + value.debouncer.flush() + const before = value.debouncer.state().count + value.set('updated') + value.debouncer.flush() + expect(value()).toBe('updated') + expect(value.debouncer.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts new file mode 100644 index 000000000..464f2acc1 --- /dev/null +++ b/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts @@ -0,0 +1,254 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { Debouncer } from '@tanstack/pacer/debouncer' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectDebouncer } from '../../src/debouncer/injectDebouncer' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callable selected state and updates its result', () => { + const utility = TestBed.runInInjectionContext(() => + injectDebouncer( + (_item: string) => {}, + { wait: 0, leading: true, trailing: false }, + (state) => (offset: number) => state.executionCount + offset, + ), + ) + TestBed.tick() + expectTypeOf(utility.state()).toEqualTypeOf<(offset: number) => number>() + expect(utility.state()(10)).toBe(10) + utility.maybeExecute('job') + expect(utility.state()(10)).toBe(11) + }) + + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectDebouncer( + process, + { wait: 0, leading: true, trailing: false }, + (state) => ({ count: state.executionCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectDebouncer((item: string) => item, { + wait: 0, + leading: true, + trailing: false, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectDebouncer( + (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectDebouncer( + (item: string) => item, + () => ({ + ...{ wait: 0, leading: true, trailing: false }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(Debouncer) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectDebouncer( + (item: string) => item, + () => ({ wait: this.value(), leading: true, trailing: false }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectDebouncer( + (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectDebouncer( + process, + () => ({ wait: 0, leading: true, trailing: false, enabled: enabled() }), + (state) => state.executionCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn((_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectDebouncer(process, { + ...{ wait: 0, leading: true, trailing: false }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('uses changed wait inputs before effects run and cancels scheduled work on destruction', async () => { + vi.useFakeTimers() + const process = vi.fn((item: string) => item) + @Component({ template: '' }) + class Host { + wait = input.required() + utility = injectDebouncer(process, () => ({ + wait: this.wait(), + leading: false, + trailing: true, + })) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.componentRef.setInput('wait', 10) + fixture.detectChanges() + fixture.componentRef.setInput('wait', 20) + const execution = fixture.componentInstance.utility.maybeExecute('first') + await vi.advanceTimersByTimeAsync(10) + expect(process).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(10) + await execution + expect(process).toHaveBeenCalledExactlyOnceWith('first') + const canceled = fixture.componentInstance.utility.maybeExecute('canceled') + fixture.destroy() + await vi.advanceTimersByTimeAsync(20) + await canceled + expect(process).toHaveBeenCalledOnce() +}) + +it('holds stability until scheduled synchronous work finishes', async () => { + vi.useFakeTimers() + const stability = injectTestStability() + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectDebouncer(process, { wait: 10, leading: false, trailing: true }), + ) + TestBed.tick() + utility.maybeExecute('job') + TestBed.tick() + expect(stability.isStable).toBe(false) + await vi.advanceTimersByTimeAsync(10) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + expect(process).toHaveBeenCalledTimes(1) +}) diff --git a/packages/angular-pacer/tests/helper-options.test.ts b/packages/angular-pacer/tests/helper-options.test.ts deleted file mode 100644 index db02d9e2f..000000000 --- a/packages/angular-pacer/tests/helper-options.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import '@angular/compiler' -import { Component, Input, input, signal } from '@angular/core' -import { TestBed } from '@angular/core/testing' -import { - BrowserTestingModule, - platformBrowserTesting, -} from '@angular/platform-browser/testing' -import { afterEach, beforeAll, beforeEach, expect, it, vi } from 'vitest' -import { injectDebouncedValue } from '../src/debouncer/injectDebouncedValue' -import { injectRateLimitedValue } from '../src/rate-limiter/injectRateLimitedValue' -import { injectThrottledValue } from '../src/throttler/injectThrottledValue' -import { injectQueuedValue } from '../src/queuer/injectQueuedValue' -import { injectQueuedSignal } from '../src/queuer/injectQueuedSignal' -import { injectAsyncQueuedSignal } from '../src/async-queuer/injectAsyncQueuedSignal' -import { injectDebouncer } from '../src/debouncer/injectDebouncer' -import { injectDebouncedSignal } from '../src/debouncer/injectDebouncedSignal' -import { injectThrottledSignal } from '../src/throttler/injectThrottledSignal' -import { injectRateLimitedSignal } from '../src/rate-limiter/injectRateLimitedSignal' - -beforeAll(() => - TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting()), -) -beforeEach(() => vi.useFakeTimers()) -afterEach(() => { - TestBed.resetTestingModule() - vi.useRealTimers() -}) - -it.each(['factory', 'getters'] as const)( - 'accepts %s options in each value helper with an explicit initial value', - (mode) => { - const wait = signal(100), - source = signal('source') - const options = { - get wait() { - return wait() - }, - limit: 1, - get window() { - return wait() - }, - started: false, - } - const helpers = TestBed.runInInjectionContext(() => - mode === 'factory' - ? [ - injectDebouncedValue(source, 'initial', () => options, undefined) - .debouncer, - injectThrottledValue(source, 'initial', () => options, undefined) - .throttler, - injectRateLimitedValue(source, 'initial', () => options, undefined) - .rateLimiter, - injectQueuedValue(source, 'initial', () => options, undefined) - .queuer, - ] - : [ - injectDebouncedValue(source, 'initial', options).debouncer, - injectThrottledValue(source, 'initial', options).throttler, - injectRateLimitedValue(source, 'initial', options).rateLimiter, - injectQueuedValue(source, 'initial', options).queuer, - ], - ) - TestBed.tick() - for (const helper of helpers) - expect(helper.options).toMatchObject( - 'window' in helper.options ? { window: 100 } : { wait: 100 }, - ) - wait.set(200) - TestBed.tick() - for (const helper of helpers) - expect(helper.options).toMatchObject( - 'window' in helper.options ? { window: 200 } : { wait: 200 }, - ) - }, -) - -it('keeps factory options and selector arguments distinct', () => { - const source = signal('value') - const helper = TestBed.runInInjectionContext(() => - injectDebouncedValue( - source, - () => ({ wait: 100 }), - (state) => ({ pending: state.isPending }), - ), - ) - TestBed.tick() - expect(helper.debouncer.options.wait).toBe(100) - expect(helper.debouncer.state().pending).toBe(true) - vi.advanceTimersByTime(100) - expect(helper()).toBe('value') -}) - -it.each(['factory', 'getters'] as const)( - 'defers %s in callback and queue-signal helpers until required inputs are bound', - (mode) => { - const callback = vi.fn() - const createOptions = (readWait: () => number) => { - const options = { - get wait() { - return readWait() - }, - started: false, - } - return mode === 'factory' ? () => options : options - } - class Helpers { - wait = input.required() - options = createOptions(this.wait) - debounced = injectDebouncer(callback, this.options) - queued = injectQueuedSignal(callback, this.options) - asyncQueued = injectAsyncQueuedSignal( - (value: string) => Promise.resolve(callback(value)), - this.options, - ) - } - Input({ required: true, isSignal: true } as Parameters[0])( - Helpers.prototype, - 'wait', - ) - Component({ standalone: true, template: '' })(Helpers) - const fixture = TestBed.createComponent(Helpers) - fixture.componentRef.setInput('wait', 100) - fixture.detectChanges() - fixture.componentInstance.debounced.maybeExecute('debounced') - fixture.componentInstance.queued.addItem('queued') - fixture.componentInstance.asyncQueued.addItem('asyncQueued') - expect(fixture.componentInstance.queued.queuer.options.wait).toBe(100) - expect(fixture.componentInstance.asyncQueued.queuer.options.wait).toBe(100) - vi.advanceTimersByTime(100) - expect(callback).toHaveBeenCalledExactlyOnceWith('debounced') - }, -) - -it('updates getter options in each managed signal helper', () => { - const wait = signal(100) - const options = { - get wait() { - return wait() - }, - get window() { - return wait() - }, - limit: 1, - } - const helpers = TestBed.runInInjectionContext(() => [ - injectDebouncedSignal('initial', options).debouncer, - injectThrottledSignal('initial', options).throttler, - injectRateLimitedSignal('initial', options).rateLimiter, - ]) - TestBed.tick() - wait.set(200) - TestBed.tick() - for (const helper of helpers) { - expect(helper.options).toMatchObject( - 'window' in helper.options ? { window: 200 } : { wait: 200 }, - ) - } -}) diff --git a/packages/angular-pacer/tests/options.test.ts b/packages/angular-pacer/tests/options.test.ts deleted file mode 100644 index fc2b9c9ad..000000000 --- a/packages/angular-pacer/tests/options.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { expectTypeOf, it } from 'vitest' -import { signal } from '@angular/core' -import { injectDebouncedValue } from '../src/debouncer/injectDebouncedValue' -import { injectRateLimitedValue } from '../src/rate-limiter/injectRateLimitedValue' -import { injectThrottledValue } from '../src/throttler/injectThrottledValue' -import { injectQueuedValue } from '../src/queuer/injectQueuedValue' -import { injectBatcher } from '../src/batcher/injectBatcher' -import { injectAsyncBatcher } from '../src/async-batcher/injectAsyncBatcher' -import { providePacerOptions } from '../src/provider/pacer-provider' -import type { AngularDebouncer } from '../src/debouncer/injectDebouncer' -import type { Debouncer } from '@tanstack/pacer/debouncer' -import type { Batcher } from '@tanstack/pacer/batcher' -import type { AsyncBatcher } from '@tanstack/pacer/async-batcher' - -function checkOptionOverloads() { - providePacerOptions({ - asyncBatcher: { onUnmount: (utility) => utility.state() }, - asyncDebouncer: { onUnmount: (utility) => utility.state() }, - asyncQueuer: { onUnmount: (utility) => utility.state() }, - asyncRateLimiter: { onUnmount: (utility) => utility.state() }, - asyncThrottler: { onUnmount: (utility) => utility.state() }, - batcher: { onUnmount: (utility) => utility.state() }, - debouncer: { - onUnmount: (utility) => { - expectTypeOf(utility).toEqualTypeOf>() - }, - onExecute: (_args, utility) => { - expectTypeOf(utility).toEqualTypeOf>() - }, - }, - queuer: { onUnmount: (utility) => utility.state() }, - rateLimiter: { onUnmount: (utility) => utility.state() }, - throttler: { onUnmount: (utility) => utility.state() }, - }) - const source = signal('value') - injectDebouncedValue(source, { wait: 100 }) - injectDebouncedValue( - source, - () => ({ wait: 100 }), - (state) => state.isPending, - ) - injectDebouncedValue(source, 'initial', { wait: 100 }) - injectDebouncedValue(source, 'initial', () => ({ wait: 100 }), undefined) - injectThrottledValue(source, 'initial', () => ({ wait: 100 }), undefined) - injectRateLimitedValue( - source, - 'initial', - () => ({ limit: 1, window: 100 }), - undefined, - ) - injectQueuedValue(source, 'initial', () => ({ wait: 100 }), undefined) - expectTypeOf(injectQueuedValue(source)()).toEqualTypeOf() - const objectSource = signal({ label: 'source' }) - const objectInitial = { label: 'fallback' } - const functionInitial = () => 'fallback' - const functionSource = signal(() => 'source') - injectQueuedValue(objectSource, objectInitial, {}) - injectQueuedValue(objectSource, objectInitial, undefined, undefined) - injectQueuedValue( - objectSource, - objectInitial, - () => ({ wait: 100 }), - undefined, - ) - injectQueuedValue(functionSource, functionInitial, {}) - injectQueuedValue(functionSource, functionInitial, undefined, undefined) - injectQueuedValue(source, 'fallback') - injectQueuedValue(source, 'fallback', undefined) - injectQueuedValue(source, { wait: 100 }, undefined) - // @ts-expect-error Object fallbacks require explicit options to distinguish them from options-only calls. - injectQueuedValue(objectSource, objectInitial) - // @ts-expect-error Undefined third arguments are reserved for options plus an undefined selector. - injectQueuedValue(objectSource, objectInitial, undefined) - // @ts-expect-error Function fallbacks require explicit options to distinguish them from factories. - injectQueuedValue(functionSource, functionInitial) - // @ts-expect-error Undefined options for a function fallback require a fourth argument. - injectQueuedValue(functionSource, functionInitial, undefined) - // @ts-expect-error A fourth argument distinguishes a factory from a selector. - injectDebouncedValue(source, 'initial', () => ({ wait: 100 })) - // @ts-expect-error A fourth argument distinguishes a factory from a selector. - injectThrottledValue(source, 'initial', () => ({ wait: 100 })) - // @ts-expect-error A fourth argument distinguishes a factory from a selector. - injectRateLimitedValue(source, 'initial', () => ({ limit: 1, window: 100 })) - // @ts-expect-error A fourth argument distinguishes a factory from a selector. - injectQueuedValue(source, 'initial', () => ({ wait: 100 })) - - const batcher = injectBatcher((_items: Array) => {}, { wait: 100 }) - expectTypeOf(batcher.options.getShouldExecute).toEqualTypeOf< - Batcher['options']['getShouldExecute'] - >() - const asyncBatcher = injectAsyncBatcher(async (_items: Array) => {}, { - wait: 100, - }) - expectTypeOf(asyncBatcher.options.asyncRetryerOptions).toEqualTypeOf< - AsyncBatcher['options']['asyncRetryerOptions'] - >() -} - -it('supports the documented options overloads', () => { - expectTypeOf(checkOptionOverloads).toBeFunction() -}) diff --git a/packages/angular-pacer/tests/provider/providePacerOptions.test.ts b/packages/angular-pacer/tests/provider/providePacerOptions.test.ts new file mode 100644 index 000000000..9a6a36ce9 --- /dev/null +++ b/packages/angular-pacer/tests/provider/providePacerOptions.test.ts @@ -0,0 +1,182 @@ +import { Component, computed, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { Debouncer } from '@tanstack/pacer/debouncer' +import { render } from '@testing-library/angular' +import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectDebouncer } from '../../src/debouncer/injectDebouncer' +import { providePacerOptions } from '../../src/provider/providePacerOptions' + +it('types provider cleanup callbacks as core instances', () => { + TestBed.configureTestingModule({ + providers: [ + providePacerOptions({ + debouncer: { + onUnmount: (core) => { + expectTypeOf(core).toEqualTypeOf>() + }, + }, + }), + ], + }) + TestBed.runInInjectionContext(() => injectDebouncer(() => {}, { wait: 100 })) + TestBed.tick() +}) + +describe('provider defaults', () => { + beforeEach(() => vi.useFakeTimers()) + + it('uses provider fallbacks when a factory removes its local override', () => { + TestBed.configureTestingModule({ + providers: [ + providePacerOptions({ debouncer: { leading: true, trailing: false } }), + ], + }) + const wait = signal(100) + const process = vi.fn() + const utility = TestBed.runInInjectionContext(() => + injectDebouncer(process, () => ({ + wait: wait(), + ...(wait() === 100 ? { leading: false } : {}), + })), + ) + TestBed.tick() + utility.maybeExecute('suppressed') + expect(process).not.toHaveBeenCalled() + wait.set(200) + utility.maybeExecute('allowed') + expect(process).toHaveBeenCalledExactlyOnceWith('allowed') + }) + + for (const source of ['fields', 'utility'] as const) { + it(`tracks reactive provider ${source} defaults with static options`, () => { + const leading = signal(true) + const defaults = + source === 'fields' + ? { + debouncer: { + get leading() { + return leading() + }, + trailing: false, + }, + } + : { + get debouncer() { + return { leading: leading(), trailing: false } + }, + } + TestBed.configureTestingModule({ + providers: [providePacerOptions(defaults)], + }) + const process = vi.fn() + const utility = TestBed.runInInjectionContext(() => + injectDebouncer(process, { wait: 100 }), + ) + TestBed.tick() + utility.maybeExecute('first') + expect(process).toHaveBeenCalledExactlyOnceWith('first') + utility.cancel() + leading.set(false) + TestBed.tick() + utility.maybeExecute('suppressed') + expect(process).toHaveBeenCalledOnce() + }) + + it(`defers provider ${source} getters until required inputs are bound`, async () => { + let required!: () => boolean + const defaults = + source === 'fields' + ? { + debouncer: { + get leading() { + return required() + }, + trailing: false, + }, + } + : { + get debouncer() { + return { leading: required(), trailing: false } + }, + } + TestBed.configureTestingModule({ + providers: [providePacerOptions(defaults)], + }) + @Component({ template: '' }) + class RequiredDefaults { + leading = input.required() + process = vi.fn() + utility: ReturnType void, number>> + derived + valueOnInit = -1 + + constructor() { + required = this.leading + this.utility = injectDebouncer( + this.process, + { wait: 100 }, + (state) => state.executionCount, + ) + this.derived = computed(this.utility.state) + } + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + const { fixture } = await render(RequiredDefaults, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + fixture.componentRef.setInput('leading', true) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + component.utility.maybeExecute() + expect(component.process).toHaveBeenCalledOnce() + component.utility.cancel() + fixture.componentRef.setInput('leading', false) + component.utility.maybeExecute() + expect(component.process).toHaveBeenCalledOnce() + }) + } + + it.each([false, true])( + 'uses provider cleanup fallback unless explicitly cleared: %s', + (clear) => { + const providerCleanup = vi.fn() + const localCleanup = vi.fn() + const process = vi.fn() + const phase = signal<'local' | 'fallback' | 'clear'>('local') + TestBed.configureTestingModule({ + providers: [ + providePacerOptions({ debouncer: { onUnmount: providerCleanup } }), + ], + }) + const utility = TestBed.runInInjectionContext(() => + injectDebouncer(process, () => ({ + wait: 100, + ...(phase() === 'local' ? { onUnmount: localCleanup } : {}), + ...(phase() === 'clear' ? { onUnmount: undefined } : {}), + })), + ) + TestBed.tick() + phase.set('fallback') + TestBed.tick() + if (clear) { + phase.set('clear') + TestBed.tick() + utility.maybeExecute() + } + TestBed.resetTestingModule() + expect(localCleanup).not.toHaveBeenCalled() + if (clear) { + expect(providerCleanup).not.toHaveBeenCalled() + vi.advanceTimersByTime(100) + expect(process).not.toHaveBeenCalled() + } else { + expect(providerCleanup).toHaveBeenCalledOnce() + expect(providerCleanup.mock.calls[0]![0]).toBeInstanceOf(Debouncer) + } + }, + ) +}) diff --git a/packages/angular-pacer/tests/queued-value.test.ts b/packages/angular-pacer/tests/queued-value.test.ts deleted file mode 100644 index b3d6dc7b2..000000000 --- a/packages/angular-pacer/tests/queued-value.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -import '@angular/compiler' -import { Component, Input, input, isSignal, signal } from '@angular/core' -import { TestBed } from '@angular/core/testing' -import { - BrowserTestingModule, - platformBrowserTesting, -} from '@angular/platform-browser/testing' -import { afterEach, beforeAll, beforeEach, expect, it, vi } from 'vitest' -import { injectQueuedValue } from '../src/queuer/injectQueuedValue' - -beforeAll(() => - TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting()), -) -beforeEach(() => vi.useFakeTimers()) -afterEach(() => { - TestBed.resetTestingModule() - vi.useRealTimers() -}) - -it('returns the processed scalar while retaining pending queue items', () => { - const source = signal('initial') - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue(source, { started: false }), - ) - TestBed.tick() - expect(isSignal(queued)).toBe(true) - expect(queued()).toBe('initial') - source.set('next') - TestBed.tick() - expect(queued()).toBe('initial') - expect(queued.queuer.peekAllItems()).toEqual(['initial', 'next']) - queued.queuer.flush() - expect(queued()).toBe('next') -}) - -it('constructs before required source inputs are bound', () => { - class RequiredSource { - source = input.required() - queued = injectQueuedValue(this.source, { started: false }) - } - Input({ required: true, isSignal: true } as Parameters[0])( - RequiredSource.prototype, - 'source', - ) - Component({ standalone: true, template: '' })(RequiredSource) - const fixture = TestBed.createComponent(RequiredSource) - expect(() => fixture.componentInstance.queued()).toThrow(/NG0950/) - fixture.componentRef.setInput('source', 'bound') - fixture.detectChanges() - expect(fixture.componentInstance.queued()).toBe('bound') -}) - -it('recognizes a two-argument primitive initial value without reading the source', () => { - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue(input.required(), 'fallback'), - ) - expect(queued()).toBe('fallback') -}) - -it.each([0, false, '', null, 0n, Symbol('initial')])( - 'preserves the primitive fallback %s', - (initial) => { - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue(input.required(), initial), - ) - expect(queued()).toBe(initial) - }, -) - -it('supports object and function initial values with explicit options', () => { - const initialObject = { label: 'fallback' } - const initialFunction = vi.fn(() => 'fallback') - const [objectValue, functionValue] = TestBed.runInInjectionContext(() => [ - injectQueuedValue( - input.required(), - initialObject, - {}, - ), - injectQueuedValue( - input.required(), - initialFunction, - {}, - ), - ]) - expect(objectValue()).toBe(initialObject) - expect(functionValue()).toBe(initialFunction) - expect(initialFunction).not.toHaveBeenCalled() -}) - -it('distinguishes options with an undefined selector from explicit initial values', () => { - const source = signal({ label: 'source' }) - const fallback = { label: 'fallback' } - const [optionsOnly, explicitFallback, explicitUndefined] = - TestBed.runInInjectionContext(() => [ - injectQueuedValue(source, { started: false }, undefined), - injectQueuedValue(source, fallback, undefined, undefined), - injectQueuedValue( - signal('source'), - undefined, - {}, - undefined, - ), - ]) - expect(optionsOnly()).toBe(source()) - expect(optionsOnly.queuer.options.started).toBe(false) - expect(explicitFallback()).toBe(fallback) - expect(explicitUndefined()).toBeUndefined() -}) - -it('preserves selectors and processes manual values in queue order', () => { - const source = signal('source') - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue( - source, - 'fallback', - { started: false, wait: 100 }, - (state) => ({ items: state.items, size: state.size }), - ), - ) - expect(queued()).toBe('fallback') - TestBed.tick() - queued.addItem('manual') - expect(queued.queuer.state().size).toBe(2) - queued.queuer.start() - expect(queued()).toBe('source') - vi.advanceTimersByTime(99) - expect(queued()).toBe('source') - vi.advanceTimersByTime(1) - expect(queued()).toBe('manual') - expect(queued.queuer.state().items).toEqual([]) -}) - -it('observes only source changes when enqueuing values', () => { - const source = signal('initial'), - wait = signal(100) - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue(source, () => ({ wait: wait(), started: false })), - ) - TestBed.tick() - expect(queued.queuer.peekAllItems()).toEqual(['initial']) - wait.set(200) - TestBed.tick() - queued.queuer.execute() - TestBed.tick() - expect(queued.queuer.peekAllItems()).toEqual([]) - source.set('intermediate') - source.set('latest') - TestBed.tick() - expect(queued.queuer.peekAllItems()).toEqual(['latest']) - expect(queued()).toBe('initial') - queued.queuer.execute() - expect(queued()).toBe('latest') -}) - -it('supports required source and option inputs across the supported forms', () => { - class RequiredInputs { - source = input.required() - wait = input.required() - defaultOptions = injectQueuedValue(this.source) - factoryOptions = injectQueuedValue(this.source, () => ({ - wait: this.wait(), - started: false, - })) - explicitFactory = injectQueuedValue( - this.source, - 'fallback', - () => ({ wait: this.wait(), started: false }), - undefined, - ) - } - for (const property of ['source', 'wait']) { - Input({ required: true, isSignal: true } as Parameters[0])( - RequiredInputs.prototype, - property, - ) - } - Component({ standalone: true, template: '' })(RequiredInputs) - const fixture = TestBed.createComponent(RequiredInputs) - expect(fixture.componentInstance.explicitFactory()).toBe('fallback') - fixture.componentRef.setInput('source', 'bound') - fixture.componentRef.setInput('wait', 100) - fixture.detectChanges() - expect(fixture.componentInstance.defaultOptions()).toBe('bound') - expect(fixture.componentInstance.factoryOptions()).toBe('bound') - expect(fixture.componentInstance.explicitFactory()).toBe('fallback') - fixture.componentInstance.explicitFactory.queuer.flush() - expect(fixture.componentInstance.explicitFactory()).toBe('bound') -}) - -it('stops pending work when its injection context is destroyed', () => { - const processed = vi.fn() - const queued = TestBed.runInInjectionContext(() => - injectQueuedValue(signal('source'), { wait: 100, onExecute: processed }), - ) - TestBed.tick() - queued.addItem('later') - TestBed.resetTestingModule() - vi.advanceTimersByTime(100) - expect(queued()).toBe('source') - expect(processed).toHaveBeenCalledOnce() -}) diff --git a/packages/angular-pacer/tests/queuer/injectQueuedComputed.test.ts b/packages/angular-pacer/tests/queuer/injectQueuedComputed.test.ts new file mode 100644 index 000000000..540d2229e --- /dev/null +++ b/packages/angular-pacer/tests/queuer/injectQueuedComputed.test.ts @@ -0,0 +1,79 @@ +import { isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectQueuedComputed } from '../../src/queuer/injectQueuedComputed' + +it('returns a scalar Angular signal and queues initial and observed source values in order', () => { + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectQueuedComputed(source, { started: false }, (state) => ({ + size: state.size, + })), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expectTypeOf(value.queuer.state()).toEqualTypeOf>() + expect(value()).toBe('initial') + TestBed.tick() + source.set('first') + TestBed.tick() + source.set('second') + TestBed.tick() + expect(value()).toBe('initial') + expect(value.queuer.peekAllItems()).toEqual(['initial', 'first', 'second']) + expect(value.queuer.state()).toEqual({ size: 3 }) + value.queuer.execute() + value.queuer.execute() + expect(value()).toBe('first') + value.queuer.execute() + expect(value()).toBe('second') +}) + +it('processes queued source changes at the configured interval after resuming', () => { + vi.useFakeTimers() + const source = signal(0) + const value = TestBed.runInInjectionContext(() => + injectQueuedComputed(source, { started: false, wait: 100 }), + ) + TestBed.tick() + source.set(1) + TestBed.tick() + source.set(2) + TestBed.tick() + value.queuer.start() + expect(value()).toBe(0) + vi.advanceTimersByTime(99) + expect(value()).toBe(0) + vi.advanceTimersByTime(1) + expect(value()).toBe(1) + vi.advanceTimersByTime(100) + expect(value()).toBe(2) +}) + +it('observes accessor results and coalesces source writes before the effect runs', () => { + const source = signal(1) + const value = TestBed.runInInjectionContext(() => + injectQueuedComputed(() => source() * 2, { started: false }), + ) + TestBed.tick() + source.set(2) + source.set(3) + TestBed.tick() + expect(value.queuer.peekAllItems()).toEqual([2, 6]) + value.queuer.flush() + expect(value()).toBe(6) +}) + +it('applies explicitly enqueued values without changing the source', () => { + const source = signal('source') + const value = TestBed.runInInjectionContext(() => + injectQueuedComputed(source, { started: false }), + ) + TestBed.tick() + value.queuer.clear() + value.queuer.addItem('manual') + value.queuer.execute() + expect(value()).toBe('manual') + expect(source()).toBe('source') + expect(value.queuer.state()).toEqual({}) +}) diff --git a/packages/angular-pacer/tests/queuer/injectQueuedItems.test.ts b/packages/angular-pacer/tests/queuer/injectQueuedItems.test.ts new file mode 100644 index 000000000..fc7f2a27a --- /dev/null +++ b/packages/angular-pacer/tests/queuer/injectQueuedItems.test.ts @@ -0,0 +1,83 @@ +import { Component, computed, input, isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { render } from '@testing-library/angular' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectQueuerItems } from '../../src/queuer/injectQueuedItems' + +it('returns an Angular signal with a callable queuer attribute', () => { + const value = TestBed.runInInjectionContext(() => + injectQueuerItems((_item: string) => {}, { started: false }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf>() + expect(value).toHaveProperty('queuer') + TestBed.tick() + expect(value.queuer.addItem('queued')).toBe(true) + expect(value()).toEqual(['queued']) +}) + +it('reflects pending items and processes them in queue order', () => { + const process = vi.fn((_item: string) => {}) + const value = TestBed.runInInjectionContext(() => + injectQueuerItems(process, { started: false }), + ) + TestBed.tick() + value.queuer.addItem('first') + value.queuer.addItem('second') + expect(value()).toEqual(['first', 'second']) + value.queuer.execute() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(value()).toEqual(['second']) + value.queuer.execute() + expect(process).toHaveBeenLastCalledWith('second') + expect(value()).toEqual([]) +}) + +it('defers required options during setup and allows reading items in ngOnInit', async () => { + @Component({ template: '' }) + class Host { + wait = input.required() + items = injectQueuerItems( + (_item: string) => {}, + () => ({ + wait: this.wait(), + started: false, + }), + ) + derived = computed(this.items) + itemsOnInit: ReadonlyArray | undefined + + ngOnInit() { + this.itemsOnInit = this.items() + } + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.componentRef.setInput('wait', 100) + fixture.detectChanges() + expect(fixture.componentInstance.itemsOnInit).toEqual([]) + fixture.componentInstance.items.queuer.addItem('queued') + expect(fixture.componentInstance.derived()).toEqual(['queued']) +}) + +it('keeps items and selected state separate and forwards the addItem shortcut', () => { + const factor = signal(2) + const value = TestBed.runInInjectionContext(() => + injectQueuerItems( + (_item: string) => {}, + { started: false }, + (state) => ({ count: state.size * factor() }), + ), + ) + TestBed.tick() + expectTypeOf(value.queuer.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + expect(value.addItem).toBe(value.queuer.addItem) + expect(value.addItem('back')).toBe(true) + expect(value.addItem('front', 'front')).toBe(true) + expect(value()).toEqual(['front', 'back']) + expect(value.queuer.state()).toEqual({ count: 4 }) + factor.set(3) + expect(value.queuer.state()).toEqual({ count: 6 }) + expect(value()).toEqual(['front', 'back']) +}) diff --git a/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts b/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts new file mode 100644 index 000000000..dbbabe460 --- /dev/null +++ b/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts @@ -0,0 +1,75 @@ +import { isSignal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectQueuedSignal } from '../../src/queuer/injectQueuedSignal' + +it('returns an editable Angular signal with selected queue state', () => { + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(0, { started: false }, (state) => state.size), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expectTypeOf(value.queuer.state()).toEqualTypeOf() + TestBed.tick() + expect(value.queuer.state()).toBe(0) + value.set(1) + expect(value()).toBe(0) + expect(value.queuer.state()).toBe(1) + value.queuer.execute() + expect(value()).toBe(1) +}) + +it('processes every set and update in FIFO order against the committed value', () => { + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(0, { started: false }), + ) + const firstUpdate = vi.fn((previous: number) => previous + 2) + const secondUpdate = vi.fn((previous: number) => previous * 2) + TestBed.tick() + value.set(1) + value.update(firstUpdate) + value.update(secondUpdate) + expect(value()).toBe(0) + expect(firstUpdate).not.toHaveBeenCalled() + expect(secondUpdate).not.toHaveBeenCalled() + value.queuer.execute() + expect(value()).toBe(1) + value.queuer.execute() + expect(value()).toBe(3) + expect(firstUpdate).toHaveBeenCalledExactlyOnceWith(1) + value.queuer.execute() + expect(value()).toBe(6) + expect(secondUpdate).toHaveBeenCalledExactlyOnceWith(3) +}) + +it('paces accepted writes rather than replacing them with the latest value', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(0, { wait: 100 }), + ) + TestBed.tick() + value.set(1) + value.set(2) + value.update((previous) => previous + 3) + expect(value()).toBe(1) + vi.advanceTimersByTime(99) + expect(value()).toBe(1) + vi.advanceTimersByTime(1) + expect(value()).toBe(2) + vi.advanceTimersByTime(100) + expect(value()).toBe(5) +}) + +it('does not apply cleared writes or evaluate their updaters', () => { + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(0, { started: false }), + ) + const update = vi.fn((previous: number) => previous + 1) + TestBed.tick() + value.set(10) + value.update(update) + value.queuer.clear() + value.queuer.flush() + expect(value()).toBe(0) + expect(update).not.toHaveBeenCalled() +}) diff --git a/packages/angular-pacer/tests/queuer/injectQueuer.test.ts b/packages/angular-pacer/tests/queuer/injectQueuer.test.ts new file mode 100644 index 000000000..a8f0bb6c1 --- /dev/null +++ b/packages/angular-pacer/tests/queuer/injectQueuer.test.ts @@ -0,0 +1,218 @@ +/// + +import { Component, computed, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { Queuer } from '@tanstack/pacer/queuer' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectQueuer } from '../../src/queuer/injectQueuer' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectQueuer( + process, + { started: false, wait: 0, maxSize: 3 }, + (state) => ({ count: state.executionCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + utility.addItem('job') + await utility.execute() + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectQueuer((item: string) => item, { + started: false, + wait: 0, + maxSize: 3, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectQueuer( + (item: string) => item, + { started: false, wait: 0, maxSize: 3 }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + utility.addItem('job') + await utility.execute() + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectQueuer( + (item: string) => item, + () => ({ + ...{ started: false, wait: 0, maxSize: 3 }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(Queuer) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectQueuer( + (item: string) => item, + () => ({ started: false, maxSize: this.value() }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectQueuer( + (item: string) => item, + { started: false, wait: 0, maxSize: 3 }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + component.utility.addItem('job') + await component.utility.execute() + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +it('preserves initial items across options updates and uses changed capacity before effects run', async () => { + const maxSize = signal(1) + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectQueuer(process, () => ({ + started: false, + initialItems: ['first'], + maxSize: maxSize(), + })), + ) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first']) + expect(utility.addItem('rejected')).toBe(false) + maxSize.set(2) + expect(utility.addItem('second')).toBe(true) + TestBed.tick() + expect(utility.peekAllItems()).toEqual(['first', 'second']) + await utility.flush() + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('loads initial items lazily and processes them automatically', async () => { + const process = vi.fn((item: string) => item) + + TestBed.runInInjectionContext(() => + injectQueuer(process, { initialItems: ['first', 'second'], wait: 0 }), + ) + expect(process).not.toHaveBeenCalled() + TestBed.tick() + + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('starts restored queued items whose pendingTick has no live timer', async () => { + const process = vi.fn((item: string) => item) + const stability = injectTestStability() + TestBed.runInInjectionContext(() => + injectQueuer(process, { + initialState: { items: ['job'], isRunning: true, pendingTick: true }, + wait: 0, + }), + ) + TestBed.tick() + await stability.whenStable() + expect(process).toHaveBeenCalledExactlyOnceWith('job') +}) + +it('holds stability until scheduled synchronous work finishes', async () => { + vi.useFakeTimers() + const stability = injectTestStability() + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectQueuer(process, { wait: 10, started: true }), + ) + TestBed.tick() + utility.addItem('first') + utility.addItem('second') + TestBed.tick() + expect(stability.isStable).toBe(false) + await vi.advanceTimersByTimeAsync(20) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + expect(process).toHaveBeenCalledTimes(2) +}) diff --git a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts new file mode 100644 index 000000000..b49bd2b74 --- /dev/null +++ b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts @@ -0,0 +1,62 @@ +import { isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectRateLimitedComputed } from '../../src/rate-limiter/injectRateLimitedComputed' + +it('returns an Angular signal with a callable rateLimiter attribute', () => { + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectRateLimitedComputed(source, { limit: 1, window: 0 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe('initial') + expect(value).toHaveProperty('rateLimiter') + TestBed.tick() + value.rateLimiter.reset() + source.set('updated') + TestBed.tick() + expect(value()).toBe('updated') +}) + +it('limits source updates and discards rejected values instead of replaying them', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectRateLimitedComputed(source, { limit: 2, window: 100 }), + ) + TestBed.tick() + source.set('accepted') + TestBed.tick() + expect(value()).toBe('accepted') + source.set('rejected') + TestBed.tick() + expect(value()).toBe('accepted') + vi.advanceTimersByTime(99) + expect(value()).toBe('accepted') + vi.advanceTimersByTime(2) + TestBed.tick() + expect(value()).toBe('accepted') + source.set('next window') + TestBed.tick() + expect(value()).toBe('next window') +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectRateLimitedComputed(source, { limit: 5, window: 1000 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.rateLimiter.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + const before = value.rateLimiter.state().count + source.set('updated') + TestBed.tick() + expect(value()).toBe('updated') + expect(value.rateLimiter.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts new file mode 100644 index 000000000..9f268cb4f --- /dev/null +++ b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts @@ -0,0 +1,76 @@ +import { isSignal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectRateLimitedSignal } from '../../src/rate-limiter/injectRateLimitedSignal' + +it('returns an Angular signal with a callable rateLimiter attribute', () => { + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal(0, { limit: 1, window: 0 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe(0) + expect(value).toHaveProperty('rateLimiter') + const callback = vi.fn() + value.rateLimiter.maybeExecute(callback) + expect(callback).toHaveBeenCalledOnce() +}) + +it('accepts set calls up to the limit, rejects excess calls, and accepts again after the window', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal('initial', { limit: 2, window: 100 }), + ) + TestBed.tick() + value.set('first') + expect(value()).toBe('first') + value.set('second') + expect(value()).toBe('second') + value.set('rejected') + expect(value()).toBe('second') + vi.advanceTimersByTime(99) + value.set('still rejected') + expect(value()).toBe('second') + vi.advanceTimersByTime(2) + expect(value()).toBe('second') + value.set('next window') + expect(value()).toBe('next window') +}) + +it('shares the limit between set and update and runs accepted updaters with the current value', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal(0, { limit: 2, window: 100 }), + ) + const update = vi.fn((previous: number) => previous + 5) + const rejected = vi.fn((previous: number) => previous * 2) + TestBed.tick() + value.set(10) + value.update(update) + expect(update).toHaveBeenCalledExactlyOnceWith(10) + expect(value()).toBe(15) + value.update(rejected) + expect(rejected).not.toHaveBeenCalled() + expect(value()).toBe(15) + vi.advanceTimersByTime(101) + value.update(update) + expect(update).toHaveBeenLastCalledWith(15) + expect(value()).toBe(20) +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal('initial', { limit: 5, window: 1000 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.rateLimiter.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + const before = value.rateLimiter.state().count + value.set('updated') + expect(value()).toBe('updated') + expect(value.rateLimiter.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/rate-limiter/injectRateLimiter.test.ts b/packages/angular-pacer/tests/rate-limiter/injectRateLimiter.test.ts new file mode 100644 index 000000000..6a2baf171 --- /dev/null +++ b/packages/angular-pacer/tests/rate-limiter/injectRateLimiter.test.ts @@ -0,0 +1,213 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { RateLimiter } from '@tanstack/pacer/rate-limiter' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectRateLimiter } from '../../src/rate-limiter/injectRateLimiter' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter(process, { limit: 2, window: 10 }, (state) => ({ + count: state.executionCount, + })), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter((item: string) => item, { limit: 2, window: 10 }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter( + (item: string) => item, + { limit: 2, window: 10 }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectRateLimiter( + (item: string) => item, + () => ({ ...{ limit: 2, window: 10 }, onUnmount: policy() }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(RateLimiter) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectRateLimiter( + (item: string) => item, + () => ({ limit: this.value(), window: 10 }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectRateLimiter( + (item: string) => item, + { limit: 2, window: 10 }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter( + process, + () => ({ limit: 2, window: 10, enabled: enabled() }), + (state) => state.executionCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn((_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectRateLimiter(process, { + ...{ limit: 2, window: 10 }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('updates the limit without resetting executions already in the window', async () => { + vi.useFakeTimers() + const limit = signal(1) + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter(process, () => ({ limit: limit(), window: 1000 })), + ) + TestBed.tick() + await utility.maybeExecute('first') + await utility.maybeExecute('rejected') + expect(process).toHaveBeenCalledOnce() + limit.set(2) + TestBed.tick() + await utility.maybeExecute('second') + await utility.maybeExecute('also rejected') + expect(process.mock.calls).toEqual([['first'], ['second']]) +}) + +it('does not hold stability for a rate-limit window after synchronous work finishes', async () => { + const stability = injectTestStability() + const utility = TestBed.runInInjectionContext(() => + injectRateLimiter((_item: string) => {}, { limit: 1, window: 60000 }), + ) + TestBed.tick() + expect(utility.maybeExecute('job')).toBe(true) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) +}) diff --git a/packages/angular-pacer/tests/reactive-options.test.ts b/packages/angular-pacer/tests/reactive-options.test.ts deleted file mode 100644 index 90c20b990..000000000 --- a/packages/angular-pacer/tests/reactive-options.test.ts +++ /dev/null @@ -1,699 +0,0 @@ -import '@angular/compiler' -import { Debouncer } from '@tanstack/pacer/debouncer' -import { Component, Input, effect, input, signal } from '@angular/core' -import { TestBed } from '@angular/core/testing' -import { - BrowserTestingModule, - platformBrowserTesting, -} from '@angular/platform-browser/testing' -import { afterEach, beforeAll, beforeEach, expect, it, vi } from 'vitest' -import { providePacerOptions } from '../src/provider/pacer-provider' -import { injectAsyncThrottler } from '../src/async-throttler/injectAsyncThrottler' -import { injectAsyncRateLimiter } from '../src/async-rate-limiter/injectAsyncRateLimiter' -import { injectAsyncQueuer } from '../src/async-queuer/injectAsyncQueuer' -import { injectAsyncDebouncer } from '../src/async-debouncer/injectAsyncDebouncer' -import { injectAsyncBatcher } from '../src/async-batcher/injectAsyncBatcher' -import { injectThrottler } from '../src/throttler/injectThrottler' -import { injectQueuer } from '../src/queuer/injectQueuer' -import { injectDebouncer } from '../src/debouncer/injectDebouncer' -import { injectBatcher } from '../src/batcher/injectBatcher' -import { injectRateLimiter } from '../src/rate-limiter/injectRateLimiter' - -beforeAll(() => - TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting()), -) -afterEach(() => TestBed.resetTestingModule()) - -it('updates options without replacing the rate limiter or its store', () => { - const limit = signal(1) - const limiter = TestBed.runInInjectionContext(() => - injectRateLimiter( - () => {}, - () => ({ limit: limit(), window: 1000 }), - ), - ) - TestBed.tick() - const store = limiter.store - expect(limiter.maybeExecute()).toBe(true) - expect(limiter.maybeExecute()).toBe(false) - limit.set(2) - TestBed.tick() - expect(limiter.store).toBe(store) - expect(limiter.options.limit).toBe(2) - expect(limiter.maybeExecute()).toBe(true) -}) - -it('reads required options inputs after Angular binds them', () => { - class RequiredOptions { - limit = input.required() - limiter = injectRateLimiter( - () => {}, - () => ({ limit: this.limit(), window: 1000 }), - ) - } - // Supply the signal metadata normally emitted by Angular's compiler for this JIT fixture. - Input({ required: true, isSignal: true } as Parameters[0])( - RequiredOptions.prototype, - 'limit', - ) - Component({ standalone: true, template: '' })(RequiredOptions) - const fixture = TestBed.createComponent(RequiredOptions) - expect(() => fixture.componentInstance.limiter.options).toThrow(/NG0950/) - fixture.componentRef.setInput('limit', 2) - fixture.detectChanges() - expect(fixture.componentInstance.limiter.options.limit).toBe(2) - fixture.componentRef.setInput('limit', 3) - fixture.detectChanges() - expect(fixture.componentInstance.limiter.options.limit).toBe(3) -}) - -beforeEach(() => vi.useFakeTimers()) -afterEach(() => vi.useRealTimers()) - -type TestOptions = { - wait: number - limit: number - window: number - maxSize: number - onUnmount?: () => void -} - -type OptionsFactory = TestOptions | (() => TestOptions) - -const cases = [ - { - name: 'Batcher', - create: (options: OptionsFactory) => - injectBatcher((_value: Array) => {}, options), - }, - { - name: 'Debouncer', - create: (options: OptionsFactory) => - injectDebouncer((_value: string) => {}, options), - }, - { - name: 'Queuer', - create: (options: OptionsFactory) => - injectQueuer((_value: string) => {}, options), - }, - { - name: 'RateLimiter', - create: (options: OptionsFactory) => - injectRateLimiter((_value: string) => {}, options), - }, - { - name: 'Throttler', - create: (options: OptionsFactory) => - injectThrottler((_value: string) => {}, options), - }, - { - name: 'AsyncBatcher', - create: (options: OptionsFactory) => - injectAsyncBatcher(async (_value: Array) => {}, options), - }, - { - name: 'AsyncDebouncer', - create: (options: OptionsFactory) => - injectAsyncDebouncer(async (_value: string) => {}, options), - }, - { - name: 'AsyncQueuer', - create: (options: OptionsFactory) => - injectAsyncQueuer(async (_value: string) => {}, options), - }, - { - name: 'AsyncRateLimiter', - create: (options: OptionsFactory) => - injectAsyncRateLimiter(async (_value: string) => {}, options), - }, - { - name: 'AsyncThrottler', - create: (options: OptionsFactory) => - injectAsyncThrottler(async (_value: string) => {}, options), - }, -] - -for (const entry of cases) { - it(`${entry.name} updates factory options and uses the latest cleanup callback`, () => { - const first = vi.fn(), - latest = vi.fn(), - value = signal(1) - const utility = TestBed.runInInjectionContext(() => - entry.create(() => ({ - wait: value(), - limit: value(), - window: 1000, - maxSize: value(), - onUnmount: value() === 1 ? first : latest, - })), - ) - TestBed.tick() - const initial = utility, - store = utility.store - expect(utility.options).toMatchObject({ wait: 1, limit: 1, maxSize: 1 }) - value.set(2) - TestBed.tick() - expect(utility).toBe(initial) - expect(utility.store).toBe(store) - expect(utility.options).toMatchObject({ wait: 2, limit: 2, maxSize: 2 }) - expect(first).not.toHaveBeenCalled() - TestBed.resetTestingModule() - expect(first).not.toHaveBeenCalled() - expect(latest).toHaveBeenCalledOnce() - expect(latest).toHaveBeenCalledWith(utility) - }) -} - -it('does not evaluate a factory when its component is destroyed before initialization', () => { - const options = vi.fn(() => ({ wait: 100 })) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(() => {}, options), - ) - expect(Object.getPrototypeOf(utility)).toBe(Object.prototype) - expect(utility).not.toBeInstanceOf(Debouncer) - expect(options).not.toHaveBeenCalled() - TestBed.resetTestingModule() - expect(options).not.toHaveBeenCalled() - expect(() => utility.options).toThrow( - 'after its injection context is destroyed', - ) -}) - -it('keeps bound methods and property writes working on the stable facade', () => { - const callback = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectRateLimiter(callback, () => ({ limit: 1, window: 1000 })), - ) - TestBed.tick() - const execute = utility.maybeExecute.bind(utility) - utility.key = 'new-key' - utility.options = { ...utility.options, limit: 2 } - expect(utility.key).toBe('new-key') - expect(execute()).toBe(true) - expect(execute()).toBe(true) - expect(execute()).toBe(false) - expect(Object.keys(utility)).toContain('store') - expect('maybeExecute' in utility).toBe(true) - expect(callback).toHaveBeenCalledTimes(2) -}) - -it('keeps object options eager and cancels pending work on destroy', () => { - const callback = vi.fn() - const options = { wait: 100 } - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(callback, options), - ) - expect(utility.options.wait).toBe(100) - expect(Object.getPrototypeOf(utility)).toBe(Object.prototype) - expect(utility).not.toBeInstanceOf(Debouncer) - utility.maybeExecute() - TestBed.resetTestingModule() - vi.advanceTimersByTime(100) - expect(callback).not.toHaveBeenCalled() -}) - -it('uses default cleanup when an accessor removes its onUnmount callback', () => { - const callback = vi.fn(), - cleanup = vi.fn(), - custom = signal(true) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(callback, () => ({ - wait: 100, - onUnmount: custom() ? cleanup : undefined, - })), - ) - TestBed.tick() - utility.maybeExecute() - custom.set(false) - TestBed.tick() - TestBed.resetTestingModule() - vi.advanceTimersByTime(100) - expect(cleanup).not.toHaveBeenCalled() - expect(callback).not.toHaveBeenCalled() -}) - -it('preserves pending work and initial queue items across options updates', () => { - const wait = signal(100), - callback = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectQueuer(callback, () => ({ - wait: wait(), - started: false, - initialItems: ['first'], - })), - ) - TestBed.tick() - expect(utility.peekAllItems()).toEqual(['first']) - utility.addItem('second') - wait.set(200) - TestBed.tick() - expect(utility.peekAllItems()).toEqual(['first', 'second']) - utility.flush() - expect(callback.mock.calls.map(([value]) => value)).toEqual([ - 'first', - 'second', - ]) -}) - -it('merges provider defaults on each factory update', () => { - TestBed.configureTestingModule({ - providers: [ - providePacerOptions({ debouncer: { leading: true, trailing: false } }), - ], - }) - const wait = signal(100) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer( - () => {}, - () => ({ wait: wait(), ...(wait() === 100 ? { leading: false } : {}) }), - ), - ) - TestBed.tick() - expect(utility.options).toMatchObject({ - wait: 100, - leading: false, - trailing: false, - }) - wait.set(200) - TestBed.tick() - expect(utility.options).toMatchObject({ - wait: 200, - leading: true, - trailing: false, - }) -}) - -it('applies initial state once and updates execution callbacks', () => { - const version = signal(1) - const first = vi.fn() - const latest = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectRateLimiter( - () => {}, - () => ({ - limit: 10, - window: 1000, - initialState: { executionCount: version() * 5 }, - onExecute: version() === 1 ? first : latest, - }), - ), - ) - TestBed.tick() - expect(utility.store.state.executionCount).toBe(5) - utility.maybeExecute() - expect(first).toHaveBeenCalledOnce() - version.set(2) - TestBed.tick() - expect(utility.store.state.executionCount).toBe(6) - utility.maybeExecute() - expect(utility.store.state.executionCount).toBe(7) - expect(first).toHaveBeenCalledOnce() - expect(latest).toHaveBeenCalledOnce() -}) - -for (const entry of cases) { - it(`${entry.name} tracks getter options without replacing its store`, () => { - const first = vi.fn(), - latest = vi.fn(), - value = signal(1) - const options = { - get wait() { - return value() - }, - get limit() { - return value() - }, - window: 1000, - get maxSize() { - return value() - }, - get onUnmount() { - return value() === 1 ? first : latest - }, - } - const utility = TestBed.runInInjectionContext(() => entry.create(options)) - TestBed.tick() - const store = utility.store - expect(utility.options).toMatchObject({ wait: 1, limit: 1, maxSize: 1 }) - value.set(2) - TestBed.tick() - expect(utility.store).toBe(store) - expect(utility.options).toMatchObject({ wait: 2, limit: 2, maxSize: 2 }) - expect(first).not.toHaveBeenCalled() - expect(latest).not.toHaveBeenCalled() - TestBed.resetTestingModule() - expect(first).not.toHaveBeenCalled() - expect(latest).toHaveBeenCalledExactlyOnceWith(utility) - }) -} - -for (const source of ['fields', 'utility'] as const) { - it(`tracks provider defaults ${source} getters with plain options`, () => { - const leading = signal(true) - const read = vi.fn(() => leading()) - const defaults = - source === 'fields' - ? { - debouncer: { - get leading() { - return read() - }, - trailing: false, - }, - } - : { - get debouncer() { - return { leading: read(), trailing: false } - }, - } - TestBed.configureTestingModule({ - providers: [providePacerOptions(defaults)], - }) - const callback = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(callback, { wait: 100 }), - ) - expect(read).not.toHaveBeenCalled() - TestBed.tick() - expect(utility.options.leading).toBe(true) - utility.maybeExecute() - expect(callback).toHaveBeenCalledOnce() - leading.set(false) - TestBed.tick() - expect(utility.options.leading).toBe(false) - }) -} - -it('defers getter reads until required inputs are bound and recovers after an early read', () => { - class RequiredOptions { - limit = input.required() - limiter: ReturnType - constructor() { - const limit = this.limit - this.limiter = injectRateLimiter(() => {}, { - get limit() { - return limit() - }, - window: 1000, - }) - } - } - Input({ required: true, isSignal: true } as Parameters[0])( - RequiredOptions.prototype, - 'limit', - ) - Component({ standalone: true, template: '' })(RequiredOptions) - const fixture = TestBed.createComponent(RequiredOptions) - expect(() => fixture.componentInstance.limiter.options).toThrow(/NG0950/) - fixture.componentRef.setInput('limit', 2) - fixture.detectChanges() - expect(fixture.componentInstance.limiter.options.limit).toBe(2) - fixture.componentRef.setInput('limit', 3) - fixture.detectChanges() - expect(fixture.componentInstance.limiter.options.limit).toBe(3) -}) - -for (const entry of cases) { - it(`${entry.name} passes its public instance to a manually updated cleanup`, () => { - const cleanup = vi.fn() - const utility = TestBed.runInInjectionContext(() => - entry.create(() => ({ - wait: 100, - limit: 1, - window: 1000, - maxSize: 10, - })), - ) - TestBed.tick() - utility.setOptions({ onUnmount: cleanup }) - TestBed.resetTestingModule() - expect(cleanup).toHaveBeenCalledOnce() - expect(cleanup.mock.calls[0]?.[0]).toBe(utility) - }) -} - -it('retains omitted options, applies provider fallbacks, and clears explicit undefined', () => { - const cleanup = vi.fn(), - execute = vi.fn(), - phase = signal(0) - TestBed.configureTestingModule({ - providers: [providePacerOptions({ debouncer: { leading: false } })], - }) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer( - () => {}, - () => ({ - wait: 100, - ...(phase() === 0 - ? { leading: true, onExecute: execute, onUnmount: cleanup } - : {}), - ...(phase() === 2 - ? { onExecute: undefined, onUnmount: undefined } - : {}), - }), - ), - ) - TestBed.tick() - expect(utility.options.leading).toBe(true) - phase.set(1) - TestBed.tick() - expect(utility.options.leading).toBe(false) - expect(utility.options.onExecute).toBe(execute) - expect(utility.options.onUnmount).toBe(cleanup) - phase.set(2) - TestBed.tick() - expect(utility.options.onExecute).toBeUndefined() - expect(utility.options.onUnmount).toBeUndefined() - TestBed.resetTestingModule() - expect(cleanup).not.toHaveBeenCalled() -}) - -it('does not evaluate getters when destroyed before initialization', () => { - const read = vi.fn(() => 100) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(() => {}, { - get wait() { - return read() - }, - }), - ) - expect(read).not.toHaveBeenCalled() - TestBed.resetTestingModule() - expect(read).not.toHaveBeenCalled() - expect(() => utility.options).toThrow( - 'after its injection context is destroyed', - ) - expect(read).not.toHaveBeenCalled() -}) - -for (const source of ['fields', 'utility'] as const) { - it(`defers provider ${source} getters until required inputs are bound`, () => { - let required!: () => boolean - const defaults = - source === 'fields' - ? { - debouncer: { - get leading() { - return required() - }, - }, - } - : { - get debouncer() { - return { leading: required() } - }, - } - TestBed.configureTestingModule({ - providers: [providePacerOptions(defaults)], - }) - class RequiredDefaults { - leading = input.required() - utility: ReturnType - constructor() { - required = this.leading - this.utility = injectDebouncer(() => {}, { wait: 100 }) - } - } - Input({ required: true, isSignal: true } as Parameters[0])( - RequiredDefaults.prototype, - 'leading', - ) - Component({ standalone: true, template: '' })(RequiredDefaults) - const fixture = TestBed.createComponent(RequiredDefaults) - fixture.componentRef.setInput('leading', true) - fixture.detectChanges() - expect(fixture.componentInstance.utility.options.leading).toBe(true) - fixture.componentRef.setInput('leading', false) - fixture.detectChanges() - expect(fixture.componentInstance.utility.options.leading).toBe(false) - }) -} - -it('tracks option reads without subscribing to function-valued core reads', () => { - const wait = signal(100), - incidental = signal(true) - const enabled = vi.fn(() => incidental()), - onExecute = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(() => {}, { - get wait() { - return wait() - }, - enabled, - onExecute, - }), - ) - TestBed.tick() - expect(utility.options.enabled).toBe(enabled) - expect(utility.options.onExecute).toBe(onExecute) - expect(onExecute).not.toHaveBeenCalled() - const calls = enabled.mock.calls.length - incidental.set(false) - TestBed.tick() - expect(enabled).toHaveBeenCalledTimes(calls) - wait.set(200) - TestBed.tick() - expect(enabled.mock.calls.length).toBeGreaterThan(calls) - expect(utility.options.wait).toBe(200) -}) - -it('constructs data-only options eagerly and outside the caller tracking context', () => { - const incidental = signal(true), - calls = vi.fn() - let utility!: ReturnType - TestBed.runInInjectionContext(() => - effect(() => { - calls() - utility = TestBed.runInInjectionContext(() => - injectDebouncer(() => {}, { - wait: 100, - enabled: () => incidental(), - }), - ) - expect(utility.options.wait).toBe(100) - }), - ) - TestBed.tick() - incidental.set(false) - TestBed.tick() - expect(calls).toHaveBeenCalledOnce() -}) - -it('cancels pending work when an enabled getter becomes false after the effect runs', () => { - const enabled = signal(true), - callback = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(callback, { - wait: 100, - get enabled() { - return enabled() - }, - }), - ) - TestBed.tick() - utility.maybeExecute() - enabled.set(false) - expect(utility.store.state.isPending).toBe(true) - TestBed.tick() - expect(utility.store.state.isPending).toBe(false) - vi.advanceTimersByTime(100) - expect(callback).not.toHaveBeenCalled() -}) - -it('consumes queue initialization options once when getters update', () => { - const version = signal(1), - callback = vi.fn() - const utility = TestBed.runInInjectionContext(() => - injectQueuer(callback, { - get started() { - return version() > 1 - }, - get initialItems() { - return [`item-${version()}`] - }, - get initialState() { - return { executionCount: version() } - }, - get wait() { - return version() * 100 - }, - }), - ) - TestBed.tick() - const store = utility.store - expect(utility.peekAllItems()).toEqual(['item-1']) - version.set(2) - TestBed.tick() - expect(utility.store).toBe(store) - expect(utility.peekAllItems()).toEqual(['item-1']) - expect(utility.store.state).toMatchObject({ - isRunning: false, - executionCount: 1, - }) - expect(callback).not.toHaveBeenCalled() -}) - -it('types manual option updates with the selected adapter state', () => { - const utility = TestBed.runInInjectionContext(() => - injectDebouncer( - () => {}, - { - get wait() { - return 100 - }, - }, - (state) => ({ pending: state.isPending }), - ), - ) - const cleanup = vi.fn((received: typeof utility) => { - expect(received).toBe(utility) - expect(received.state().pending).toBe(false) - }) - utility.setOptions({ onUnmount: cleanup }) - expect(utility.options.onUnmount).toBe(cleanup) - TestBed.resetTestingModule() - expect(cleanup).toHaveBeenCalledOnce() -}) - -it.each([false, true])( - 'uses provider cleanup fallback unless explicitly cleared: %s', - (clear) => { - const providerCleanup = vi.fn(), - localCleanup = vi.fn(), - callback = vi.fn() - const phase = signal<'local' | 'fallback' | 'clear'>('local') - TestBed.configureTestingModule({ - providers: [ - providePacerOptions({ debouncer: { onUnmount: providerCleanup } }), - ], - }) - const utility = TestBed.runInInjectionContext(() => - injectDebouncer(callback, () => ({ - wait: 100, - ...(phase() === 'local' ? { onUnmount: localCleanup } : {}), - ...(phase() === 'clear' ? { onUnmount: undefined } : {}), - })), - ) - TestBed.tick() - expect(utility.options.onUnmount).toBe(localCleanup) - phase.set('fallback') - TestBed.tick() - expect(utility.options.onUnmount).toBe(providerCleanup) - if (clear) { - phase.set('clear') - TestBed.tick() - expect(utility.options.onUnmount).toBeUndefined() - utility.maybeExecute() - } - TestBed.resetTestingModule() - expect(localCleanup).not.toHaveBeenCalled() - if (clear) { - expect(providerCleanup).not.toHaveBeenCalled() - vi.advanceTimersByTime(100) - expect(callback).not.toHaveBeenCalled() - } else { - expect(providerCleanup).toHaveBeenCalledOnce() - expect(providerCleanup.mock.calls[0]?.[0]).toBe(utility) - } - }, -) diff --git a/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts b/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts new file mode 100644 index 000000000..6365dc7a0 --- /dev/null +++ b/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts @@ -0,0 +1,66 @@ +import { isSignal, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectThrottledComputed } from '../../src/throttler/injectThrottledComputed' + +it('returns an Angular signal with a callable throttler attribute', () => { + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectThrottledComputed(source, { wait: 100 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe('initial') + expect(value).toHaveProperty('throttler') + TestBed.tick() + source.set('updated') + TestBed.tick() + expect(value()).toBe('initial') + value.throttler.flush() + expect(value()).toBe('updated') +}) + +it('applies the latest source change at the original throttle deadline', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectThrottledComputed(source, { wait: 100 }), + ) + TestBed.tick() + vi.advanceTimersByTime(20) + source.set('first trailing') + TestBed.tick() + vi.advanceTimersByTime(40) + source.set('latest trailing') + TestBed.tick() + expect(value()).toBe('initial') + vi.advanceTimersByTime(39) + expect(value()).toBe('initial') + vi.advanceTimersByTime(1) + expect(value()).toBe('latest trailing') + vi.advanceTimersByTime(100) + source.set('next leading') + TestBed.tick() + expect(value()).toBe('next leading') +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectThrottledComputed(source, { wait: 100 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.throttler.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + value.throttler.flush() + const before = value.throttler.state().count + source.set('updated') + TestBed.tick() + value.throttler.flush() + expect(value()).toBe('updated') + expect(value.throttler.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts b/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts new file mode 100644 index 000000000..d9ebd3d0a --- /dev/null +++ b/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts @@ -0,0 +1,93 @@ +import { isSignal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, expectTypeOf, it, vi } from 'vitest' +import { injectThrottledSignal } from '../../src/throttler/injectThrottledSignal' + +it('returns an Angular signal with a callable throttler attribute', () => { + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal(0, { wait: 0 }), + ) + expect(isSignal(value)).toBe(true) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe(0) + expect(value).toHaveProperty('throttler') + const callback = vi.fn() + value.throttler.maybeExecute(callback) + expect(callback).toHaveBeenCalledOnce() +}) + +it('applies leading set immediately and the latest trailing set at the original deadline', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal('initial', { wait: 100 }), + ) + TestBed.tick() + value.set('leading') + expect(value()).toBe('leading') + vi.advanceTimersByTime(20) + value.set('first trailing') + vi.advanceTimersByTime(40) + value.set('latest trailing') + expect(value()).toBe('leading') + vi.advanceTimersByTime(39) + expect(value()).toBe('leading') + vi.advanceTimersByTime(1) + expect(value()).toBe('latest trailing') +}) + +it('supports leading set and trailing update with only the latest updater applied', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal(0, { wait: 100 }), + ) + const first = vi.fn((previous: number) => previous + 1) + const latest = vi.fn((previous: number) => previous + 5) + TestBed.tick() + value.set(10) + value.update(first) + vi.advanceTimersByTime(60) + value.update(latest) + vi.advanceTimersByTime(39) + expect(value()).toBe(10) + expect(first).not.toHaveBeenCalled() + expect(latest).not.toHaveBeenCalled() + vi.advanceTimersByTime(1) + expect(value()).toBe(15) + expect(first).not.toHaveBeenCalled() + expect(latest).toHaveBeenCalledExactlyOnceWith(10) + vi.advanceTimersByTime(100) + value.set(2) + expect(value()).toBe(2) +}) + +it('defers the first write when leading is disabled', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal(0, { wait: 100, leading: false }), + ) + TestBed.tick() + value.set(10) + vi.advanceTimersByTime(99) + expect(value()).toBe(0) + vi.advanceTimersByTime(1) + expect(value()).toBe(10) +}) + +it('passes selected state through to the attached utility', () => { + vi.useFakeTimers() + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal('initial', { wait: 100 }, (state) => ({ + count: state.executionCount, + })), + ) + expectTypeOf(value.throttler.state()).toEqualTypeOf< + Readonly<{ count: number }> + >() + TestBed.tick() + value.throttler.flush() + const before = value.throttler.state().count + value.set('updated') + value.throttler.flush() + expect(value()).toBe('updated') + expect(value.throttler.state().count).toBe(before + 1) +}) diff --git a/packages/angular-pacer/tests/throttler/injectThrottler.test.ts b/packages/angular-pacer/tests/throttler/injectThrottler.test.ts new file mode 100644 index 000000000..ac59db4b6 --- /dev/null +++ b/packages/angular-pacer/tests/throttler/injectThrottler.test.ts @@ -0,0 +1,239 @@ +/// + +import { Component, computed, effect, input, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { Throttler } from '@tanstack/pacer/throttler' +import { render } from '@testing-library/angular' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import { injectThrottler } from '../../src/throttler/injectThrottler' +import { injectTestStability } from '../utils/injectTestStability' + +describe('operations and selected state', () => { + it('preserves callback arguments and exposes the selected state', async () => { + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectThrottler( + process, + { wait: 0, leading: true, trailing: false }, + (state) => ({ count: state.executionCount }), + ), + ) + TestBed.tick() + expect(utility.state()).toEqual({ count: 0 }) + await utility.maybeExecute('job') + expect(process).toHaveBeenCalledExactlyOnceWith('job') + expect(utility.state()).toEqual({ count: 1 }) + expectTypeOf(utility.state()).toEqualTypeOf>() + }) + + it('returns empty selected state when no selector is supplied', () => { + const utility = TestBed.runInInjectionContext(() => + injectThrottler((item: string) => item, { + wait: 0, + leading: true, + trailing: false, + }), + ) + expect(utility.state()).toEqual({}) + // @ts-expect-error An omitted selector cannot promise selected fields. + utility.state().count + }) + + it('tracks selector dependencies and observes changes between an early read and connection', async () => { + const factor = signal(2) + const utility = TestBed.runInInjectionContext(() => + injectThrottler( + (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.executionCount * factor(), + ), + ) + expect(utility.state()).toBe(0) + await utility.maybeExecute('job') + TestBed.tick() + expect(utility.state()).toBe(2) + factor.set(3) + expect(utility.state()).toBe(3) + }) +}) + +describe('cleanup', () => { + it('uses the latest cleanup callback without waiting for the options effect', () => { + const oldCleanup = vi.fn() + const latestCleanup = vi.fn() + const policy = signal(oldCleanup) + TestBed.runInInjectionContext(() => + injectThrottler( + (item: string) => item, + () => ({ + ...{ wait: 0, leading: true, trailing: false }, + onUnmount: policy(), + }), + ), + ) + TestBed.tick() + policy.set(latestCleanup) + TestBed.resetTestingModule() + expect(oldCleanup).not.toHaveBeenCalled() + expect(latestCleanup).toHaveBeenCalledOnce() + expect(latestCleanup.mock.calls[0]![0]).toBeInstanceOf(Throttler) + }) +}) + +describe('signal inputs', () => { + @Component({ template: '' }) + class RequiredOptions { + value = input.required() + utility = injectThrottler( + (item: string) => item, + () => ({ wait: this.value(), leading: true, trailing: false }), + (state) => state.executionCount, + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('defers input reads during construction and reads state in ngOnInit', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('value', 3) + fixture.detectChanges() + expect(component.valueOnInit).toBe(0) + expect(component.derived()).toBe(0) + }) + + it('destroys before input binding without evaluating required options', async () => { + const { fixture } = await render(RequiredOptions, { + detectChangesOnRender: false, + }) + expect(() => fixture.destroy()).not.toThrow() + }) + + @Component({ template: '' }) + class RequiredSelector { + factor = input.required() + utility = injectThrottler( + (item: string) => item, + { wait: 0, leading: true, trailing: false }, + (state) => state.executionCount * this.factor(), + ) + derived = computed(this.utility.state) + valueOnInit = -1 + + ngOnInit() { + this.valueOnInit = this.utility.state() + } + } + + it('reads required selector inputs in ngOnInit and tracks later changes', async () => { + const { fixture } = await render(RequiredSelector, { + detectChangesOnRender: false, + }) + const component = fixture.componentInstance + expect(component.valueOnInit).toBe(-1) + fixture.componentRef.setInput('factor', 2) + await component.utility.maybeExecute('job') + fixture.detectChanges() + expect(component.valueOnInit).toBe(2) + expect(component.derived()).toBe(2) + fixture.componentRef.setInput('factor', 3) + expect(component.derived()).toBe(3) + }) +}) + +describe('reactive options', () => { + it('applies changed enabled options before an operation without resetting history', async () => { + const enabled = signal(true) + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectThrottler( + process, + () => ({ wait: 0, leading: true, trailing: false, enabled: enabled() }), + (state) => state.executionCount, + ), + ) + TestBed.tick() + await utility.maybeExecute('first') + enabled.set(false) + await utility.maybeExecute('second') + TestBed.tick() + expect(process).toHaveBeenCalledExactlyOnceWith('first') + expect(utility.state()).toBe(1) + }) + + it('keeps callback and option-function reads out of the consumer effect', async () => { + const incidental = signal(0) + const process = vi.fn((_item: string) => incidental()) + let runs = 0 + TestBed.runInInjectionContext(() => { + const utility = injectThrottler(process, { + ...{ wait: 0, leading: true, trailing: false }, + enabled: () => incidental() >= 0, + }) + effect(() => { + runs++ + void utility.maybeExecute('job') + }) + }) + TestBed.tick() + await Promise.resolve() + incidental.set(1) + TestBed.tick() + expect(runs).toBe(1) + expect(process).toHaveBeenCalledOnce() + }) +}) + +it('uses changed wait inputs before effects run and cancels scheduled work on destruction', async () => { + vi.useFakeTimers() + const process = vi.fn((item: string) => item) + @Component({ template: '' }) + class Host { + wait = input.required() + utility = injectThrottler(process, () => ({ + wait: this.wait(), + leading: false, + trailing: true, + })) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.componentRef.setInput('wait', 10) + fixture.detectChanges() + fixture.componentRef.setInput('wait', 20) + const execution = fixture.componentInstance.utility.maybeExecute('first') + await vi.advanceTimersByTimeAsync(10) + expect(process).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(10) + await execution + expect(process).toHaveBeenCalledExactlyOnceWith('first') + const canceled = fixture.componentInstance.utility.maybeExecute('canceled') + fixture.destroy() + await vi.advanceTimersByTimeAsync(20) + await canceled + expect(process).toHaveBeenCalledOnce() +}) + +it('holds stability until scheduled synchronous work finishes', async () => { + vi.useFakeTimers() + const stability = injectTestStability() + const process = vi.fn((item: string) => item) + const utility = TestBed.runInInjectionContext(() => + injectThrottler(process, { wait: 10, leading: false, trailing: true }), + ) + TestBed.tick() + utility.maybeExecute('job') + TestBed.tick() + expect(stability.isStable).toBe(false) + await vi.advanceTimersByTimeAsync(10) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + expect(process).toHaveBeenCalledTimes(1) +}) diff --git a/packages/angular-pacer/tests/utils/injectPendingTasksLifecycle.test.ts b/packages/angular-pacer/tests/utils/injectPendingTasksLifecycle.test.ts new file mode 100644 index 000000000..a756e2203 --- /dev/null +++ b/packages/angular-pacer/tests/utils/injectPendingTasksLifecycle.test.ts @@ -0,0 +1,49 @@ +import { + EnvironmentInjector, + createEnvironmentInjector, + signal, +} from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, it } from 'vitest' +import { injectPendingTasksLifecycle } from '../../src/utils/injectPendingTasksLifecycle' +import { injectTestStability } from './injectTestStability' + +it('holds stability only while the source reports pending work', async () => { + const pending = signal(false) + const stability = injectTestStability() + TestBed.runInInjectionContext(() => injectPendingTasksLifecycle(pending)) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + + pending.set(true) + TestBed.tick() + expect(stability.isStable).toBe(false) + pending.set(false) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) + + pending.set(true) + TestBed.tick() + expect(stability.isStable).toBe(false) + pending.set(false) + TestBed.tick() + await stability.whenStable() + expect(stability.isStable).toBe(true) +}) + +it('releases an active task when its injection context is destroyed', async () => { + const pending = signal(true) + const stability = injectTestStability() + const owner = createEnvironmentInjector( + [], + TestBed.inject(EnvironmentInjector), + ) + owner.runInContext(() => injectPendingTasksLifecycle(pending)) + TestBed.tick() + expect(stability.isStable).toBe(false) + owner.destroy() + await stability.whenStable() + expect(stability.isStable).toBe(true) +}) diff --git a/packages/angular-pacer/tests/utils/injectTestStability.ts b/packages/angular-pacer/tests/utils/injectTestStability.ts new file mode 100644 index 000000000..daacbbd5e --- /dev/null +++ b/packages/angular-pacer/tests/utils/injectTestStability.ts @@ -0,0 +1,20 @@ +import { ApplicationRef, DestroyRef } from '@angular/core' +import { TestBed } from '@angular/core/testing' + +export function injectTestStability() { + const app = TestBed.inject(ApplicationRef) + let stable = true + const subscription = app.isStable.subscribe((value) => { + stable = value + }) + TestBed.inject(DestroyRef).onDestroy(() => { + subscription.unsubscribe() + }) + + return { + whenStable: () => app.whenStable(), + get isStable() { + return stable + }, + } +} diff --git a/packages/angular-pacer/tsconfig.json b/packages/angular-pacer/tsconfig.json index 66baec5ea..35f56f234 100644 --- a/packages/angular-pacer/tsconfig.json +++ b/packages/angular-pacer/tsconfig.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "jsx": "preserve" + "jsx": "preserve", + "experimentalDecorators": true }, - "include": ["src", "vitest.config.ts", "tests"], + "include": ["src", "vitest.config.ts", "tests", "test-setup.ts"], "exclude": ["eslint.config.js"] } diff --git a/packages/angular-pacer/tsconfig.spec.json b/packages/angular-pacer/tsconfig.spec.json new file mode 100644 index 000000000..1f3f4c754 --- /dev/null +++ b/packages/angular-pacer/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true, + "types": ["vitest/globals", "node"], + "noEmit": false, + "declaration": false, + "useDefineForClassFields": false, + }, + "angularCompilerOptions": { + "strictTemplates": true, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + }, + "include": ["tests/**/*.ts", "test-setup.ts", "src/**/*.ts"], + "exclude": [], +} diff --git a/packages/angular-pacer/vitest.config.ts b/packages/angular-pacer/vitest.config.ts index 48bf74651..33bfdbf7f 100644 --- a/packages/angular-pacer/vitest.config.ts +++ b/packages/angular-pacer/vitest.config.ts @@ -1,13 +1,22 @@ import { fileURLToPath } from 'node:url' +import angular from '@analogjs/vite-plugin-angular' import { defineConfig } from 'vitest/config' import packageJson from './package.json' with { type: 'json' } export default defineConfig({ + plugins: [ + angular({ + tsconfig: fileURLToPath(new URL('./tsconfig.spec.json', import.meta.url)), + jit: false, + }), + ], test: { name: packageJson.name, dir: fileURLToPath(new URL('./tests', import.meta.url)), + setupFiles: [fileURLToPath(new URL('./test-setup.ts', import.meta.url))], watch: false, - environment: 'happy-dom', + environment: 'jsdom', globals: true, + restoreMocks: true, }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fd4f0e97..9ed17885c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10483,25 +10483,49 @@ importers: packages/angular-pacer: dependencies: - '@tanstack/angular-store': - specifier: ^0.11.2 - version: 0.11.2(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) '@tanstack/pacer': specifier: workspace:* version: link:../pacer + '@tanstack/store': + specifier: ^0.11.2 + version: 0.11.2 devDependencies: + '@analogjs/vite-plugin-angular': + specifier: ^2.8.0 + version: 2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) + '@angular/build': + specifier: ^22.2.1 + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) + '@angular/common': + specifier: ^22.2.1 + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 + '@angular/compiler-cli': + specifier: ^22.2.1 + version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) '@angular/core': specifier: ^22.2.1 version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/router': + specifier: ^22.2.1 + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + '@testing-library/angular': + specifier: ^19.5.0 + version: 19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 '@types/node': specifier: ^26.6.4 version: 26.6.4 + jsdom: + specifier: ^30.1.1 + version: 30.1.2(@noble/hashes@2.4.0) packages/angular-pacer-devtools: dependencies: @@ -10917,6 +10941,20 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@analogjs/vite-plugin-angular@2.8.0': + resolution: {integrity: sha512-4tDpjPERxmEBgIUxFwBVjbQOB0++1b1LoturpcXIvFuhcpBSVTJEKr+QYMAYMyeljEAUk07eo3zrIkzoyqlz8w==} + peerDependencies: + '@angular-devkit/build-angular': ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 + '@angular/build': ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@angular-devkit/build-angular': + optional: true + '@angular/build': + optional: true + vite: + optional: true + '@angular-devkit/architect@0.2202.1': resolution: {integrity: sha512-WGRAxFj+R9LC8HuK3DnDgxHo/XrgfklNQeNgjq/phTEVq5ZdWxc8kDoPA4uQz4vF+HY2eycAAtOPfMvj2TBEiQ==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -11084,6 +11122,10 @@ packages: resolution: {integrity: sha512-C9duntabagkBZ1LebM7FKmphR4Q1pBclLxVbZETQV0akkFjV0ooFxo8FlvAQyKj9F6l8rEnmidgcTlpyxFYizg==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + '@asamuzakjp/css-color@7.1.3': + resolution: {integrity: sha512-1t1U8Cm3RBl9vh3RAZhjkPaduzLSvQJRToRrxpGR/eDRW99mOREleuu+mWfiFa795bUzjwht7NgphCKTs2ianQ==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + '@asamuzakjp/dom-selector@8.3.2': resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} engines: {node: ^22.13.0 || >=24.0.0} @@ -11092,6 +11134,10 @@ packages: resolution: {integrity: sha512-NT4s3yZLjovPpliRpTvdsdzyPjqRqiCZj9MxnarBihbaY5MbAG7DWAJcrLlhmC7xKTNCXsTELcqB8YsqpLnUFQ==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + '@asamuzakjp/dom-selector@9.2.4': + resolution: {integrity: sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -11951,6 +11997,10 @@ packages: resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} engines: {node: '>=20.19.0'} + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} + engines: {node: '>=20.19.0'} + '@csstools/css-calc@2.1.4': resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} engines: {node: '>=18'} @@ -11965,6 +12015,13 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + '@csstools/css-color-parser@3.1.0': resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} @@ -11979,6 +12036,13 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-color-parser@4.2.6': + resolution: {integrity: sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + '@csstools/css-parser-algorithms@3.0.5': resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} @@ -11991,6 +12055,12 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.2 + '@csstools/css-syntax-patches-for-csstree@1.1.14': resolution: {integrity: sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==} peerDependencies: @@ -11999,6 +12069,14 @@ packages: css-tree: optional: true + '@csstools/css-syntax-patches-for-csstree@1.1.15': + resolution: {integrity: sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} @@ -12007,6 +12085,10 @@ packages: resolution: {integrity: sha512-bPlN9S9O1A0euCpEWE4qnvB5YDuyYVsUTrxSgmAM1Is0j4tICHoVyOVAXfWMP/kS9ZrjvyIXWV2PmomiAXXqOw==} engines: {node: '>=20.19.0'} + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} + engines: {node: '>=20.19.0'} + '@ember-data/rfc395-data@0.0.4': resolution: {integrity: sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==} @@ -12494,6 +12576,15 @@ packages: '@noble/hashes': optional: true + '@exodus/bytes@1.16.0': + resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@faker-js/faker@10.6.0': resolution: {integrity: sha512-3RQHgEtvL1Frl/d1cSreo7qhJ3Gk1OdNUai/CtZ8G+wYeRQnJih3s9xJ9/kgYekPQRdwgh0HXRPqMlzWGwivIQ==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} @@ -13082,6 +13173,12 @@ packages: '@one-ini/wasm@0.2.1': resolution: {integrity: sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==} + '@oxc-parser/binding-android-arm-eabi@0.121.0': + resolution: {integrity: sha512-n07FQcySwOlzap424/PLMtOkbS7xOu8nsJduKL8P3COGHKgKoDYXwoAHCbChfgFpHnviehrLWIPX0lKGtbEk/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm-eabi@0.130.0': resolution: {integrity: sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13094,6 +13191,12 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm64@0.121.0': + resolution: {integrity: sha512-/Dd1xIXboYAicw+twT2utxPD7bL8qh7d3ej0qvaYIMj3/EgIrGR+tSnjCUkiCT6g6uTC0neSS4JY8LxhdSU/sA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-android-arm64@0.130.0': resolution: {integrity: sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13106,6 +13209,12 @@ packages: cpu: [arm64] os: [android] + '@oxc-parser/binding-darwin-arm64@0.121.0': + resolution: {integrity: sha512-A0jNEvv7QMtCO1yk205t3DWU9sWUjQ2KNF0hSVO5W9R9r/R1BIvzG01UQAfmtC0dQm7sCrs5puixurKSfr2bRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.130.0': resolution: {integrity: sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13118,6 +13227,12 @@ packages: cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.121.0': + resolution: {integrity: sha512-SsHzipdxTKUs3I9EOAPmnIimEeJOemqRlRDOp9LIj+96wtxZejF51gNibmoGq8KoqbT1ssAI5po/E3J+vEtXGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.130.0': resolution: {integrity: sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13130,6 +13245,12 @@ packages: cpu: [x64] os: [darwin] + '@oxc-parser/binding-freebsd-x64@0.121.0': + resolution: {integrity: sha512-v1APOTkCp+RWOIDAHRoaeW/UoaHF15a60E8eUL6kUQXh+i4K7PBwq2Wi7jm8p0ymID5/m/oC1w3W31Z/+r7HQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.130.0': resolution: {integrity: sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13142,6 +13263,12 @@ packages: cpu: [x64] os: [freebsd] + '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': + resolution: {integrity: sha512-PmqPQuqHZyFVWA4ycr0eu4VnTMmq9laOHZd+8R359w6kzuNZPvmmunmNJ8ybkm769A0nCoVp3TJ6dUz7B3FYIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': resolution: {integrity: sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13154,6 +13281,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': + resolution: {integrity: sha512-vF24htj+MOH+Q7y9A8NuC6pUZu8t/C2Fr/kDOi2OcNf28oogr2xadBPXAbml802E8wRAVfbta6YLDQTearz+jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': resolution: {integrity: sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13166,6 +13299,13 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm64-gnu@0.121.0': + resolution: {integrity: sha512-wjH8cIG2Lu/3d64iZpbYr73hREMgKAfu7fqpXjgM2S16y2zhTfDIp8EQjxO8vlDtKP5Rc7waZW72lh8nZtWrpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.130.0': resolution: {integrity: sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13180,6 +13320,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-musl@0.121.0': + resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.130.0': resolution: {integrity: sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13194,6 +13341,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': + resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': resolution: {integrity: sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13208,6 +13362,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': + resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': resolution: {integrity: sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13222,6 +13383,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-musl@0.121.0': + resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.130.0': resolution: {integrity: sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13236,6 +13404,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-s390x-gnu@0.121.0': + resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.130.0': resolution: {integrity: sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13250,6 +13425,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.121.0': + resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.130.0': resolution: {integrity: sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13264,6 +13446,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-musl@0.121.0': + resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.130.0': resolution: {integrity: sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13278,6 +13467,12 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-openharmony-arm64@0.121.0': + resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.130.0': resolution: {integrity: sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13290,11 +13485,22 @@ packages: cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-wasm32-wasi@0.121.0': + resolution: {integrity: sha512-5TFISkPTymKvsmIlKasPVTPuWxzCcrT8pM+p77+mtQbIZDd1UC8zww4CJcRI46kolmgrEX6QpKO8AvWMVZ+ifw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.130.0': resolution: {integrity: sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.121.0': + resolution: {integrity: sha512-V0pxh4mql4XTt3aiEtRNUeBAUFOw5jzZNxPABLaOKAWrVzSr9+XUaB095lY7jqMf5t8vkfh8NManGB28zanYKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.130.0': resolution: {integrity: sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13307,6 +13513,12 @@ packages: cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.121.0': + resolution: {integrity: sha512-4Ob1qvYMPnlF2N9rdmKdkQFdrq16QVcQwBsO8yiPZXof0fHKFF+LmQV501XFbi7lHyrKm8rlJRfQ/M8bZZPVLw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.130.0': resolution: {integrity: sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13319,6 +13531,12 @@ packages: cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.121.0': + resolution: {integrity: sha512-BOp1KCzdboB1tPqoCPXgntgFs0jjeSyOXHzgxVFR7B/qfr3F8r4YDacHkTOUNXtDgM8YwKnkf3rE5gwALYX7NA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.130.0': resolution: {integrity: sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13331,6 +13549,9 @@ packages: cpu: [x64] os: [win32] + '@oxc-project/types@0.121.0': + resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==} + '@oxc-project/types@0.130.0': resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} @@ -14470,12 +14691,6 @@ packages: peerDependencies: '@angular/core': '>=21.0.0' - '@tanstack/angular-store@0.11.2': - resolution: {integrity: sha512-KdyoiKicx807HbcH98Ses9hRM7tHrn+sq6ZEZsA3FIGF20/bCnOqVxRVef+m14OGgck+9cUlEKgkAH/SKW3sVQ==} - peerDependencies: - '@angular/common': '>=19.0.0' - '@angular/core': '>=19.0.0' - '@tanstack/devtools-client@0.0.8': resolution: {integrity: sha512-cG3iZkGWCwN330bLBKa8+9r4Of2AXNoz2zUqcsy/4XsD3105ghVBx78cGyvJj9fSclNomPxoqAnDGXXhg1WLvA==} engines: {node: '>=18'} @@ -14664,6 +14879,13 @@ packages: '@vue/composition-api': optional: true + '@testing-library/angular@19.5.0': + resolution: {integrity: sha512-wGSWTsibHaaQ0zMMlvqD1OEn+u3J2lu5BLFs7ULcUdy+kdqAFlDulexHFMpugdcJK1sTPu7yfJtI+cHf5si61w==} + peerDependencies: + '@angular/core': '>= 21.0.0' + '@angular/router': '>= 21.0.0' + '@testing-library/dom': ^10.0.0 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -16224,6 +16446,10 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-urls@8.0.0: + resolution: {integrity: sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==} + engines: {node: ^22.14.0 || >=24.0.0} + dataloader@2.2.3: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} @@ -17472,6 +17698,15 @@ packages: canvas: optional: true + jsdom@30.1.2: + resolution: {integrity: sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -18040,6 +18275,10 @@ packages: ordered-binary@1.6.1: resolution: {integrity: sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==} + oxc-parser@0.121.0: + resolution: {integrity: sha512-ek9o58+SCv6AV7nchiAcUJy1DNE2CC5WRdBcO0mF+W4oRjNQfPO7b3pLjTHSFECpHkKGOZSQxx3hk8viIL5YCg==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.130.0: resolution: {integrity: sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19264,6 +19503,10 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + tr46@7.0.0: + resolution: {integrity: sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==} + engines: {node: ^22.14.0 || >=24.0.0} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -19414,6 +19657,10 @@ packages: resolution: {integrity: sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w==} engines: {node: '>=22.19.0'} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} + engines: {node: '>=22.19.0'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -19799,6 +20046,10 @@ packages: resolution: {integrity: sha512-TEZA+Zqxin7Jjsm2cjRohCmen5awh+hT6Zi3VZdqZlNRk7zvOI/9WpBFg/DWlA56bWnzwm6DuB8NS0EsxQH9uQ==} engines: {node: ^22.14.0 || >=24.0.0} + whatwg-url@17.2.0: + resolution: {integrity: sha512-IhLIHNcBrzIixC5XXsbr4DxdjajyA5vQ5RwGZMtrUp+GbtE5Nrsge9QKau7jXrC0aTXLYGV0xiW7Tb4YS2aomw==} + engines: {node: ^22.14.0 || >=24.0.0} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -19973,6 +20224,19 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@analogjs/vite-plugin-angular@2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))': + dependencies: + magic-string: 0.30.21 + obug: 2.2.1 + oxc-parser: 0.121.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + tinyglobby: 0.2.17 + optionalDependencies: + '@angular/build': 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) + vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + '@angular-devkit/architect@0.2202.1(chokidar@5.0.0)': dependencies: '@angular-devkit/core': 22.2.1(chokidar@5.0.0) @@ -20107,6 +20371,59 @@ snapshots: - tsx - yaml + '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.2202.1(chokidar@5.0.0) + '@angular/compiler': 22.2.1 + '@angular/compiler-cli': 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) + '@babel/core': 8.0.5 + '@inquirer/confirm': 6.3.2(@types/node@26.6.4) + '@parcel/watcher': 2.6.0 + '@vitejs/plugin-basic-ssl': 2.3.0(vite@8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) + beasties: 0.5.4 + browserslist: 4.29.3 + chokidar: 5.0.0 + esbuild: 0.28.2 + https-proxy-agent: 9.1.0(supports-color@8.1.1) + jsonc-parser: 3.3.1 + listr2: 11.1.0 + magic-string: 1.4.1 + mrmime: 2.0.1 + oxc-parser: 0.150.0 + parse5-html-rewriting-stream: 8.0.1 + picomatch: 4.0.7 + piscina: 5.3.2 + rolldown: 1.2.8 + sass: 1.104.1 + sass-embedded: 1.104.1 + semver: 7.8.5 + source-map-support: 0.5.21 + tinyglobby: 0.2.17 + tslib: 2.8.1 + typescript: 6.0.3 + vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) + xxhash-wasm: 1.1.0 + optionalDependencies: + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + istanbul-lib-instrument: 6.0.3(supports-color@8.1.1) + lmdb: 3.5.6 + postcss: 8.5.28 + rollup: 4.60.1 + vitest: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) + transitivePeerDependencies: + - '@types/node' + - '@vitejs/devtools' + - jiti + - kerberos + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@angular/cli@22.2.1(@types/node@26.6.4)(chokidar@5.0.0)': dependencies: '@angular-devkit/architect': 0.2202.1(chokidar@5.0.0) @@ -20205,10 +20522,10 @@ snapshots: '@asamuzakjp/css-color@6.0.5': dependencies: - '@csstools/css-calc': 3.4.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1) - '@csstools/css-color-parser': 4.2.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) - '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 lru-cache: 11.5.3 optional: true @@ -20220,6 +20537,14 @@ snapshots: '@csstools/css-tokenizer': 4.0.1 lru-cache: 11.5.3 + '@asamuzakjp/css-color@7.1.3': + dependencies: + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + lru-cache: 11.5.3 + '@asamuzakjp/dom-selector@8.3.2': dependencies: bidi-js: 1.1.0 @@ -20235,6 +20560,13 @@ snapshots: is-potential-custom-element-name: 1.0.1 lru-cache: 11.5.3 + '@asamuzakjp/dom-selector@9.2.4': + dependencies: + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -20298,6 +20630,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + optional: true + '@babel/core@8.0.1': dependencies: '@babel/code-frame': 8.0.6 @@ -20475,6 +20828,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + optional: true + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: '@babel/core': 7.29.0(supports-color@7.2.0) @@ -20493,6 +20854,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + optional: true + '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.29.0 @@ -21238,6 +21609,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.8(supports-color@8.1.1)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + optional: true + '@babel/traverse@8.0.6': dependencies: '@babel/code-frame': 8.0.6 @@ -21427,6 +21811,8 @@ snapshots: '@csstools/color-helpers@6.1.1': {} + '@csstools/color-helpers@6.1.2': {} + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) @@ -21437,6 +21823,11 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/color-helpers': 5.1.0 @@ -21451,6 +21842,13 @@ snapshots: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-color-parser@4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-tokenizer': 3.0.4 @@ -21459,14 +21857,24 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-tokenizer': 4.0.2 + '@csstools/css-syntax-patches-for-csstree@1.1.14(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 + '@csstools/css-syntax-patches-for-csstree@1.1.15(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + '@csstools/css-tokenizer@3.0.4': {} '@csstools/css-tokenizer@4.0.1': {} + '@csstools/css-tokenizer@4.0.2': {} + '@ember-data/rfc395-data@0.0.4': {} '@ember/app-tsconfig@2.1.0': {} @@ -21929,6 +22337,10 @@ snapshots: optionalDependencies: '@noble/hashes': 2.4.0 + '@exodus/bytes@1.16.0(@noble/hashes@2.4.0)': + optionalDependencies: + '@noble/hashes': 2.4.0 + '@faker-js/faker@10.6.0': {} '@floating-ui/core@1.8.0': @@ -22522,102 +22934,158 @@ snapshots: '@one-ini/wasm@0.2.1': {} + '@oxc-parser/binding-android-arm-eabi@0.121.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.130.0': optional: true '@oxc-parser/binding-android-arm-eabi@0.150.0': optional: true + '@oxc-parser/binding-android-arm64@0.121.0': + optional: true + '@oxc-parser/binding-android-arm64@0.130.0': optional: true '@oxc-parser/binding-android-arm64@0.150.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.121.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.130.0': optional: true '@oxc-parser/binding-darwin-arm64@0.150.0': optional: true + '@oxc-parser/binding-darwin-x64@0.121.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.130.0': optional: true '@oxc-parser/binding-darwin-x64@0.150.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.121.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.130.0': optional: true '@oxc-parser/binding-freebsd-x64@0.150.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.130.0': optional: true '@oxc-parser/binding-linux-arm-gnueabihf@0.150.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.130.0': optional: true '@oxc-parser/binding-linux-arm-musleabihf@0.150.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.130.0': optional: true '@oxc-parser/binding-linux-arm64-gnu@0.150.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.130.0': optional: true '@oxc-parser/binding-linux-arm64-musl@0.150.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.130.0': optional: true '@oxc-parser/binding-linux-ppc64-gnu@0.150.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.130.0': optional: true '@oxc-parser/binding-linux-riscv64-gnu@0.150.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.130.0': optional: true '@oxc-parser/binding-linux-riscv64-musl@0.150.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.130.0': optional: true '@oxc-parser/binding-linux-s390x-gnu@0.150.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.130.0': optional: true '@oxc-parser/binding-linux-x64-gnu@0.150.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.130.0': optional: true '@oxc-parser/binding-linux-x64-musl@0.150.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.121.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.130.0': optional: true '@oxc-parser/binding-openharmony-arm64@0.150.0': optional: true + '@oxc-parser/binding-wasm32-wasi@0.121.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@napi-rs/wasm-runtime': 1.2.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + '@oxc-parser/binding-wasm32-wasi@0.130.0': dependencies: '@emnapi/core': 1.10.0 @@ -22625,24 +23093,35 @@ snapshots: '@napi-rs/wasm-runtime': 1.2.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.130.0': optional: true '@oxc-parser/binding-win32-arm64-msvc@0.150.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.130.0': optional: true '@oxc-parser/binding-win32-ia32-msvc@0.150.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.130.0': optional: true '@oxc-parser/binding-win32-x64-msvc@0.150.0': optional: true + '@oxc-project/types@0.121.0': {} + '@oxc-project/types@0.130.0': {} '@oxc-project/types@0.149.0': {} @@ -23386,12 +23865,6 @@ snapshots: - solid-js - utf-8-validate - '@tanstack/angular-store@0.11.2(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))': - dependencies: - '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@tanstack/store': 0.11.2 - '@tanstack/devtools-client@0.0.8': dependencies: '@tanstack/devtools-event-client': 0.5.0 @@ -23654,6 +24127,13 @@ snapshots: vue: 3.5.43(typescript@6.0.3) vue-demi: 0.14.10(vue@3.5.43(typescript@6.0.3)) + '@testing-library/angular@19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1)': + dependencies: + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/router': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + '@testing-library/dom': 10.4.1 + tslib: 2.8.1 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 @@ -24240,6 +24720,10 @@ snapshots: dependencies: vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0) + '@vitejs/plugin-basic-ssl@2.3.0(vite@8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))': + dependencies: + vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) + '@vitejs/plugin-react@6.1.1(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 @@ -24269,6 +24753,16 @@ snapshots: optionalDependencies: vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0) + '@vitest/mocker@5.0.3(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.2 + optionalDependencies: + vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) + optional: true + '@vitest/spy@5.0.3': {} '@volar/kit@2.4.28(typescript@5.9.3)': @@ -25290,6 +25784,13 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + data-urls@8.0.0(@noble/hashes@2.4.0): + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.2(@noble/hashes@2.4.0) + transitivePeerDependencies: + - '@noble/hashes' + dataloader@2.2.3: {} dayjs@1.11.21: {} @@ -25308,6 +25809,12 @@ snapshots: optionalDependencies: supports-color: 7.2.0 + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + decimal.js@10.6.0: {} decorator-transforms@2.4.0(@babel/core@7.29.7(supports-color@7.2.0)): @@ -26474,7 +26981,7 @@ snapshots: html-encoding-sniffer@6.0.0(@noble/hashes@2.4.0): dependencies: - '@exodus/bytes': 1.15.1(@noble/hashes@2.4.0) + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) transitivePeerDependencies: - '@noble/hashes' optional: true @@ -26543,6 +27050,15 @@ snapshots: - kerberos - supports-color + https-proxy-agent@9.1.0(supports-color@8.1.1): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@8.1.1) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + httpxy@0.5.5: {} human-id@4.2.1: {} @@ -26739,6 +27255,17 @@ snapshots: - supports-color optional: true + istanbul-lib-instrument@6.0.3(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/parser': 7.29.9 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + optional: true + istextorbinary@2.1.0: dependencies: binaryextensions: 2.3.0 @@ -26814,8 +27341,8 @@ snapshots: '@asamuzakjp/css-color': 6.0.5 '@asamuzakjp/dom-selector': 8.3.2 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.14(css-tree@3.2.1) - '@exodus/bytes': 1.15.1(@noble/hashes@2.4.0) + '@csstools/css-syntax-patches-for-csstree': 1.1.15(css-tree@3.2.1) + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) css-tree: 3.2.1 data-urls: 7.0.0(@noble/hashes@2.4.0) decimal.js: 10.6.0 @@ -26826,11 +27353,11 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.2 - undici: 8.11.0 + undici: 8.11.2 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 17.1.2(@noble/hashes@2.4.0) + whatwg-url: 17.2.0(@noble/hashes@2.4.0) xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -26861,6 +27388,31 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + jsdom@30.1.2(@noble/hashes@2.4.0): + dependencies: + '@asamuzakjp/css-color': 7.1.3 + '@asamuzakjp/dom-selector': 9.2.4 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.15(css-tree@3.2.1) + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + css-tree: 3.2.1 + data-urls: 8.0.0(@noble/hashes@2.4.0) + decimal.js: 10.6.0 + html-encoding-sniffer: 7.0.0(@noble/hashes@2.4.0) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + parse5: 8.0.1 + saxes: 6.0.0 + tough-cookie: 6.0.2 + undici: 8.11.2 + w3c-xmlserializer: 6.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.2.0(@noble/hashes@2.4.0) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-schema-traverse@0.4.1: {} @@ -27563,6 +28115,34 @@ snapshots: ordered-binary@1.6.1: optional: true + oxc-parser@0.121.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2): + dependencies: + '@oxc-project/types': 0.121.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.121.0 + '@oxc-parser/binding-android-arm64': 0.121.0 + '@oxc-parser/binding-darwin-arm64': 0.121.0 + '@oxc-parser/binding-darwin-x64': 0.121.0 + '@oxc-parser/binding-freebsd-x64': 0.121.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.121.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.121.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.121.0 + '@oxc-parser/binding-linux-arm64-musl': 0.121.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.121.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.121.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.121.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.121.0 + '@oxc-parser/binding-linux-x64-gnu': 0.121.0 + '@oxc-parser/binding-linux-x64-musl': 0.121.0 + '@oxc-parser/binding-openharmony-arm64': 0.121.0 + '@oxc-parser/binding-wasm32-wasi': 0.121.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + '@oxc-parser/binding-win32-arm64-msvc': 0.121.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.121.0 + '@oxc-parser/binding-win32-x64-msvc': 0.121.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + oxc-parser@0.130.0: dependencies: '@oxc-project/types': 0.130.0 @@ -28986,6 +29566,10 @@ snapshots: dependencies: punycode: 2.3.1 + tr46@7.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} tree-sync@1.4.0(supports-color@7.2.0): @@ -29121,6 +29705,8 @@ snapshots: undici@8.11.0: {} + undici@8.11.2: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -29255,6 +29841,23 @@ snapshots: terser: 5.51.2 yaml: 2.9.0 + vite@8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.12 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.4 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + sass: 1.104.1 + sass-embedded: 1.104.1 + terser: 5.51.2 + yaml: 2.9.1 + vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 @@ -29387,6 +29990,30 @@ snapshots: transitivePeerDependencies: - msw + vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 6.1.4 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 26.6.4 + happy-dom: 20.14.5 + jsdom: 30.1.2(@noble/hashes@2.4.0) + transitivePeerDependencies: + - msw + optional: true + volar-service-html@0.0.71(@volar/language-service@2.4.28(typescript@5.9.3)): dependencies: vscode-html-languageservice: 5.6.2 @@ -29567,6 +30194,14 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@17.2.0(@noble/hashes@2.4.0): + dependencies: + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + tr46: 7.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 From 98046efa84a44f7b3e15865350d04c1793fb3de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Thu, 8 Oct 2026 08:47:49 -0300 Subject: [PATCH 02/18] feat(angular-pacer): rebuild the signal adapters Use lazy cores, explicit stable operations, reactive options, shared Store selection, effect-owned cleanup, and Angular pending-task integration. Add idiomatic computed, editable signal, and queued-items APIs while preserving core feature exports and callable selected state. Document breaking API changes and migration steps, refresh the guides and reference, and include the release changeset. Validation: pnpm test:pr passes. --- .changeset/angular-adapter-rework.md | 11 + ANGULAR-BREAKING-CHANGES.md | 110 ++++++ .../angular/guides/async-batching.md | 6 +- .../angular/guides/async-debouncing.md | 16 +- .../framework/angular/guides/async-queuing.md | 21 +- .../angular/guides/async-rate-limiting.md | 16 +- .../angular/guides/async-retrying.md | 14 +- .../angular/guides/async-throttling.md | 18 +- docs/framework/angular/guides/batching.md | 16 +- docs/framework/angular/guides/debouncing.md | 30 +- docs/framework/angular/guides/queuing.md | 68 ++-- .../framework/angular/guides/rate-limiting.md | 18 +- docs/framework/angular/guides/throttling.md | 20 +- .../reference/functions/injectAsyncBatcher.md | 136 ++++---- .../functions/injectAsyncDebouncer.md | 154 ++++----- .../functions/injectAsyncQueuedSignal.md | 78 ----- .../reference/functions/injectAsyncQueuer.md | 138 ++++---- .../functions/injectAsyncQueuerItems.md | 103 ++++++ .../functions/injectAsyncRateLimiter.md | 131 ++++--- .../functions/injectAsyncThrottler.md | 135 ++++---- .../reference/functions/injectBatcher.md | 129 ++++--- .../functions/injectDebouncedComputed.md | 99 ++++++ .../functions/injectDebouncedSignal.md | 117 ++++--- .../functions/injectDebouncedValue.md | 326 ------------------ .../reference/functions/injectDebouncer.md | 147 ++++---- .../reference/functions/injectPacerOptions.md | 19 + .../functions/injectQueuedComputed.md | 97 ++++++ .../reference/functions/injectQueuedSignal.md | 105 +++--- .../reference/functions/injectQueuedValue.md | 304 ---------------- .../reference/functions/injectQueuer.md | 140 ++++---- .../reference/functions/injectQueuerItems.md | 103 ++++++ .../functions/injectRateLimitedComputed.md | 99 ++++++ .../functions/injectRateLimitedSignal.md | 119 ++++--- .../functions/injectRateLimitedValue.md | 248 ------------- .../reference/functions/injectRateLimiter.md | 157 ++++----- .../functions/injectThrottledComputed.md | 99 ++++++ .../functions/injectThrottledSignal.md | 116 ++++--- .../functions/injectThrottledValue.md | 317 ----------------- .../reference/functions/injectThrottler.md | 145 ++++---- .../functions/providePacerOptions.md | 36 +- docs/framework/angular/reference/index.md | 33 +- .../interfaces/AngularAsyncBatcher.md | 72 +--- .../interfaces/AngularAsyncBatcherOptions.md | 18 +- .../interfaces/AngularAsyncDebouncer.md | 60 +--- .../AngularAsyncDebouncerOptions.md | 22 +- .../interfaces/AngularAsyncQueuer.md | 60 +--- .../interfaces/AngularAsyncQueuerItems.md | 104 ++++++ .../interfaces/AngularAsyncQueuerOptions.md | 22 +- .../interfaces/AngularAsyncRateLimiter.md | 60 +--- .../AngularAsyncRateLimiterOptions.md | 21 +- .../interfaces/AngularAsyncThrottler.md | 60 +--- .../AngularAsyncThrottlerOptions.md | 22 +- .../reference/interfaces/AngularBatcher.md | 60 +--- .../interfaces/AngularBatcherOptions.md | 21 +- .../reference/interfaces/AngularDebouncer.md | 62 +--- .../interfaces/AngularDebouncerComputed.md | 65 ++++ .../interfaces/AngularDebouncerOptions.md | 23 +- .../interfaces/AngularDebouncerSignal.md | 109 ++++++ .../reference/interfaces/AngularQueuer.md | 60 +--- .../interfaces/AngularQueuerComputed.md | 63 ++++ .../interfaces/AngularQueuerItems.md | 105 ++++++ .../interfaces/AngularQueuerOptions.md | 21 +- .../interfaces/AngularQueuerSignal.md | 107 ++++++ .../interfaces/AngularRateLimiter.md | 60 +--- .../interfaces/AngularRateLimiterComputed.md | 65 ++++ .../interfaces/AngularRateLimiterOptions.md | 20 +- .../interfaces/AngularRateLimiterSignal.md | 109 ++++++ .../reference/interfaces/AngularThrottler.md | 60 +--- .../interfaces/AngularThrottlerComputed.md | 65 ++++ .../interfaces/AngularThrottlerOptions.md | 21 +- .../interfaces/AngularThrottlerSignal.md | 109 ++++++ .../reference/interfaces/AsyncQueuedSignal.md | 76 ---- .../reference/interfaces/DebouncedSignal.md | 47 --- .../interfaces/PacerProviderOptions.md | 129 +++++++ .../reference/interfaces/QueuedSignal.md | 77 ----- .../reference/interfaces/RateLimitedSignal.md | 47 --- .../reference/interfaces/ThrottledSignal.md | 47 --- .../type-aliases/AngularPacerOptions.md | 18 - .../type-aliases/QueuedValueSignal.md | 37 -- .../src/async-batcher/injectAsyncBatcher.ts | 250 +++++++------- .../async-debouncer/injectAsyncDebouncer.ts | 258 +++++++------- .../angular-pacer/src/async-queuer/index.ts | 2 +- .../async-queuer/injectAsyncQueuedItems.ts | 81 +++++ .../async-queuer/injectAsyncQueuedSignal.ts | 76 ---- .../src/async-queuer/injectAsyncQueuer.ts | 249 ++++++------- .../injectAsyncRateLimiter.ts | 223 ++++++------ .../async-throttler/injectAsyncThrottler.ts | 237 +++++++------ .../src/batcher/injectBatcher.ts | 226 ++++++------ packages/angular-pacer/src/debouncer/index.ts | 2 +- .../src/debouncer/injectDebouncedComputed.ts | 76 ++++ .../src/debouncer/injectDebouncedSignal.ts | 151 ++++---- .../src/debouncer/injectDebouncedValue.ts | 137 -------- .../src/debouncer/injectDebouncer.ts | 243 +++++++------ packages/angular-pacer/src/index.ts | 44 +-- packages/angular-pacer/src/provider/index.ts | 3 +- .../src/provider/pacer-context.ts | 42 --- .../src/provider/pacer-provider.ts | 38 -- .../src/provider/providePacerOptions.ts | 81 +++++ packages/angular-pacer/src/queuer/index.ts | 5 +- .../src/queuer/injectQueuedComputed.ts | 68 ++++ .../src/queuer/injectQueuedItems.ts | 78 +++++ .../src/queuer/injectQueuedSignal.ts | 111 +++--- .../src/queuer/injectQueuedValue.ts | 162 --------- .../angular-pacer/src/queuer/injectQueuer.ts | 231 +++++++------ .../angular-pacer/src/rate-limiter/index.ts | 2 +- .../rate-limiter/injectRateLimitedComputed.ts | 75 ++++ .../rate-limiter/injectRateLimitedSignal.ts | 139 ++++---- .../rate-limiter/injectRateLimitedValue.ts | 110 ------ .../src/rate-limiter/injectRateLimiter.ts | 236 ++++++------- packages/angular-pacer/src/throttler/index.ts | 2 +- .../src/throttler/injectThrottledComputed.ts | 76 ++++ .../src/throttler/injectThrottledSignal.ts | 150 ++++---- .../src/throttler/injectThrottledValue.ts | 133 ------- .../src/throttler/injectThrottler.ts | 237 ++++++------- packages/angular-pacer/src/types/index.ts | 3 - .../src/utils/injectExternalStore.ts | 89 +++++ .../src/utils/injectForwardMethods.ts | 37 ++ .../angular-pacer/src/utils/injectLazy.ts | 8 + .../src/utils/injectOutsideZone.ts | 7 + .../src/utils/injectPendingTasksLifecycle.ts | 16 + .../src/utils/injectReactiveOptions.ts | 109 ------ .../angular-pacer/src/utils/injectSelector.ts | 34 ++ .../angular-pacer/src/utils/maybeAccessor.ts | 13 + .../src/utils/readonlySelected.ts | 4 + packages/angular-pacer/tsconfig.docs.json | 3 +- packages/angular-pacer/tsdown.config.ts | 2 +- 126 files changed, 5416 insertions(+), 5541 deletions(-) create mode 100644 .changeset/angular-adapter-rework.md create mode 100644 ANGULAR-BREAKING-CHANGES.md delete mode 100644 docs/framework/angular/reference/functions/injectAsyncQueuedSignal.md create mode 100644 docs/framework/angular/reference/functions/injectAsyncQueuerItems.md create mode 100644 docs/framework/angular/reference/functions/injectDebouncedComputed.md delete mode 100644 docs/framework/angular/reference/functions/injectDebouncedValue.md create mode 100644 docs/framework/angular/reference/functions/injectPacerOptions.md create mode 100644 docs/framework/angular/reference/functions/injectQueuedComputed.md delete mode 100644 docs/framework/angular/reference/functions/injectQueuedValue.md create mode 100644 docs/framework/angular/reference/functions/injectQueuerItems.md create mode 100644 docs/framework/angular/reference/functions/injectRateLimitedComputed.md delete mode 100644 docs/framework/angular/reference/functions/injectRateLimitedValue.md create mode 100644 docs/framework/angular/reference/functions/injectThrottledComputed.md delete mode 100644 docs/framework/angular/reference/functions/injectThrottledValue.md create mode 100644 docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md create mode 100644 docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md create mode 100644 docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md create mode 100644 docs/framework/angular/reference/interfaces/AngularQueuerComputed.md create mode 100644 docs/framework/angular/reference/interfaces/AngularQueuerItems.md create mode 100644 docs/framework/angular/reference/interfaces/AngularQueuerSignal.md create mode 100644 docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md create mode 100644 docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md create mode 100644 docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md create mode 100644 docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md delete mode 100644 docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md delete mode 100644 docs/framework/angular/reference/interfaces/DebouncedSignal.md create mode 100644 docs/framework/angular/reference/interfaces/PacerProviderOptions.md delete mode 100644 docs/framework/angular/reference/interfaces/QueuedSignal.md delete mode 100644 docs/framework/angular/reference/interfaces/RateLimitedSignal.md delete mode 100644 docs/framework/angular/reference/interfaces/ThrottledSignal.md delete mode 100644 docs/framework/angular/reference/type-aliases/AngularPacerOptions.md delete mode 100644 docs/framework/angular/reference/type-aliases/QueuedValueSignal.md create mode 100644 packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts delete mode 100644 packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts create mode 100644 packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts delete mode 100644 packages/angular-pacer/src/debouncer/injectDebouncedValue.ts delete mode 100644 packages/angular-pacer/src/provider/pacer-context.ts delete mode 100644 packages/angular-pacer/src/provider/pacer-provider.ts create mode 100644 packages/angular-pacer/src/provider/providePacerOptions.ts create mode 100644 packages/angular-pacer/src/queuer/injectQueuedComputed.ts create mode 100644 packages/angular-pacer/src/queuer/injectQueuedItems.ts delete mode 100644 packages/angular-pacer/src/queuer/injectQueuedValue.ts create mode 100644 packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts delete mode 100644 packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts create mode 100644 packages/angular-pacer/src/throttler/injectThrottledComputed.ts delete mode 100644 packages/angular-pacer/src/throttler/injectThrottledValue.ts create mode 100644 packages/angular-pacer/src/utils/injectExternalStore.ts create mode 100644 packages/angular-pacer/src/utils/injectForwardMethods.ts create mode 100644 packages/angular-pacer/src/utils/injectLazy.ts create mode 100644 packages/angular-pacer/src/utils/injectOutsideZone.ts create mode 100644 packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts delete mode 100644 packages/angular-pacer/src/utils/injectReactiveOptions.ts create mode 100644 packages/angular-pacer/src/utils/injectSelector.ts create mode 100644 packages/angular-pacer/src/utils/maybeAccessor.ts create mode 100644 packages/angular-pacer/src/utils/readonlySelected.ts diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md new file mode 100644 index 000000000..ad16e8fa6 --- /dev/null +++ b/.changeset/angular-adapter-rework.md @@ -0,0 +1,11 @@ +--- +'@tanstack/angular-pacer': minor +--- + +Rebuild Angular utility adapters with lazy construction, readonly selected +state, explicit methods, reactive options, and effect-owned cleanup. Replace the +Angular Store dependency with core Store integration and cover the adapters with +per-utility Angular behavior tests. + +Support Angular 20 and up, including all Angular LTS versions, using stable public +signal, effect, and pending-task APIs. diff --git a/ANGULAR-BREAKING-CHANGES.md b/ANGULAR-BREAKING-CHANGES.md new file mode 100644 index 000000000..57cb8bab2 --- /dev/null +++ b/ANGULAR-BREAKING-CHANGES.md @@ -0,0 +1,110 @@ +# Angular adapter breaking changes + +Changes from the previous Angular adapter on upstream main. + +## Angular version + +Angular 20 or newer is required. Angular 17–19 are no longer supported. + +## API renames + +| Previous API | Replacement | +| ------------------------------------ | --------------------------- | +| `injectDebouncedValue` | `injectDebouncedComputed` | +| `injectThrottledValue` | `injectThrottledComputed` | +| `injectRateLimitedValue` | `injectRateLimitedComputed` | +| `injectQueuedValue` | `injectQueuedComputed` | +| `injectQueuedSignal` (pending items) | `injectQueuerItems` | +| `injectAsyncQueuedSignal` | `injectAsyncQueuerItems` | + +`injectQueuedSignal` now creates an editable scalar signal. Old calls that pass +an item-processing callback must migrate to `injectQueuerItems`. + +## Utility refs + +Refs expose their selected `state()` signal and explicit methods. They no longer +expose the full core instance: `fn`, `key`, `options`, `store`, and `setOptions` +are removed. Read state through a selector and change options through an options +factory: + +```ts +const wait = signal(100) +const debouncer = injectDebouncer( + callback, + () => ({ wait: wait() }), + (state) => ({ + isPending: state.isPending, + }), +) +wait.set(200) // Replaces debouncer.setOptions({ wait: 200 }). +``` + +`onUnmount` now receives the core Pacer instance rather than the Angular ref. +Read `core.store.state` inside that callback instead of `ref.state()`. + +A caller-selected state type requires a selector argument. Calls that specify +`TSelected` without providing a selector no longer compile. + +## Editable signal writes + +For debounced, throttled, and rate-limited signals, replace updater calls to +`set` with `update`: + +```ts +value.set((previous) => previous + 1) // Previous API. +value.update((previous) => previous + 1) // Current API. +``` + +These helpers now queue callbacks internally. Their attached utility accepts a +callback in `maybeExecute`, and item-dependent options and selected fields such +as `lastArgs` contain that callback rather than the replacement value. +Use the signal's `set` and `update` methods for value writes. + +## Computed helpers + +Computed helpers take `(source, options, selector?)`. Overloads accepting a +separate initial value are removed; the first read returns the source value. +They return readonly signals with an attached utility rather than the editable +signal refs returned by the previous debounced, throttled, and rate-limited value +helpers. Write to the source instead of calling `set` on the computed result. + +For queued computed values, replace `value.addItem(item)` with +`value.queuer.addItem(item)`. The shortcut remains on the items helpers. + +## Pending items + +Items helpers return `ReadonlyArray` rather than mutable arrays. +Without a selector, their attached `queuer.state()` now returns `{}`, rather +than `{ items }`. Read waiting items from the items signal, or explicitly select +additional state: + +```ts +const items = injectQueuerItems(processItem, { started: false }, (state) => ({ + size: state.size, +})) +items.addItem('job') +items() // Waiting items. +items.queuer.state().size // Selected state. +``` + +Selectors no longer have to include an `items` field. + +## Public types and imports + +| Previous type | Replacement | +| --------------------------------- | ----------------------------------------- | +| `DebouncedSignal` | `AngularDebouncerSignal` | +| `ThrottledSignal` | `AngularThrottlerSignal` | +| `RateLimitedSignal` | `AngularRateLimiterSignal` | +| `QueuedSignal` | `AngularQueuerItems` | +| `AsyncQueuedSignal` | `AngularAsyncQueuerItems` | +| `QueuedValueSignal` | `AngularQueuerComputed` | +| `AngularPacerOptions` | Use `T \| (() => T)` in application types | + +Computed helpers use their corresponding `AngularDebouncerComputed`, +`AngularThrottlerComputed`, and `AngularRateLimiterComputed` types. + +`Angular*Options` types no longer take a `TSelected` generic argument; selection +belongs to the utility ref. `PACER_OPTIONS` is no longer exported from the +provider entry point. Use `providePacerOptions` to configure defaults and +`injectPacerOptions` to read them. diff --git a/docs/framework/angular/guides/async-batching.md b/docs/framework/angular/guides/async-batching.md index 8ad7466d9..6a2accea7 100644 --- a/docs/framework/angular/guides/async-batching.md +++ b/docs/framework/angular/guides/async-batching.md @@ -167,7 +167,7 @@ The adapter cancels the pending wait timer and aborts active work when its owner ## Configuration and reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const batcher = injectAsyncBatcher( @@ -187,9 +187,9 @@ console.log( ) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. -`wait` may be a number or a function that receives the batcher instance. `setOptions()` merges new options, and `asyncBatcherOptions()` creates reusable, type-checked option objects. +`wait` may be a number or a function that receives the batcher instance. `asyncBatcherOptions()` creates reusable, type-checked option objects. Do not use `started` to pause a batcher. It is currently a no-op, so every `addItem()` call evaluates the configured triggers. diff --git a/docs/framework/angular/guides/async-debouncing.md b/docs/framework/angular/guides/async-debouncing.md index 0b4fcaeee..34ac63072 100644 --- a/docs/framework/angular/guides/async-debouncing.md +++ b/docs/framework/angular/guides/async-debouncing.md @@ -123,13 +123,15 @@ search.reset() ## Configuration -`wait` and `enabled` may be values or functions that receive the debouncer instance. `setOptions()` merges new options into the current configuration. +`wait` and `enabled` may be values or functions that receive the debouncer instance. Use an options function for reactive configuration. ```ts -search.setOptions({ - enabled: (debouncer) => debouncer.store.state.errorCount < 3, - wait: (debouncer) => (debouncer.store.state.successCount === 0 ? 200 : 500), -}) +import { signal } from '@angular/core' + +const wait = signal(500) +const search = injectAsyncDebouncer(searchApi, () => ({ wait: wait() })) + +wait.set(250) ``` Changing `wait` does not reschedule an existing timeout. The new value applies when later work is scheduled. @@ -142,7 +144,7 @@ The adapter cancels pending work and aborts active work when its owner is destro ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const debouncer = injectAsyncDebouncer( @@ -162,7 +164,7 @@ console.log( ) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers and active executions are not restored. diff --git a/docs/framework/angular/guides/async-queuing.md b/docs/framework/angular/guides/async-queuing.md index efdfe9a11..784351c08 100644 --- a/docs/framework/angular/guides/async-queuing.md +++ b/docs/framework/angular/guides/async-queuing.md @@ -26,26 +26,19 @@ The queue controls start order. With concurrency greater than `1`, completion or ## Choose an API -- `injectAsyncQueuedSignal` for reactive pending items +- `injectAsyncQueuerItems` for reactive pending items - `injectAsyncQueuer` for concurrency, ordering, and lifecycle control ## Angular example ```ts -import { injectAsyncQueuedSignal } from '@tanstack/angular-pacer' +import { injectAsyncQueuerItems } from '@tanstack/angular-pacer' export class UploadComponent { - readonly queued = injectAsyncQueuedSignal( - uploadFile, - { concurrency: 2 }, - (state) => ({ - items: state.items, - activeItems: state.activeItems, - }), - ) + readonly queued = injectAsyncQueuerItems(uploadFile, { concurrency: 2 }) add() { - this.queued.addItem(nextFile()) + this.queued.queuer.addItem(nextFile()) } } ``` @@ -184,7 +177,7 @@ The adapter stops automatic processing and aborts active work when its owner is ## Configuration and reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const queue = injectAsyncQueuer(processJob, { concurrency: 2 }, (state) => ({ @@ -196,9 +189,9 @@ const queue = injectAsyncQueuer(processJob, { concurrency: 2 }, (state) => ({ console.log(queue.state().size, queue.state().activeItems, queue.state().status) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. -`concurrency` and `wait` may be values or functions that receive the queue instance. `setOptions()` merges new options, and `asyncQueuerOptions()` creates reusable, type-checked option objects. +`concurrency` and `wait` may be values or functions that receive the queue instance. `asyncQueuerOptions()` creates reusable, type-checked option objects. `initialState` can restore selected queue state that your app has persisted. If it includes `items`, they take precedence over `initialItems`; `initialState.isRunning` likewise takes precedence over `started`. Restore only durable fields. Pending timers and active executions are not restored. diff --git a/docs/framework/angular/guides/async-rate-limiting.md b/docs/framework/angular/guides/async-rate-limiting.md index ed0d4cbf1..a6ddc5351 100644 --- a/docs/framework/angular/guides/async-rate-limiting.md +++ b/docs/framework/angular/guides/async-rate-limiting.md @@ -132,13 +132,15 @@ Resetting restores capacity immediately. Only do this when starting a genuinely ## Configuration -`enabled`, `limit`, and `window` may be values or functions that receive the limiter instance. `setOptions()` merges new options into the current configuration. +`enabled`, `limit`, and `window` may be values or functions that receive the limiter instance. Use an options function for reactive configuration. ```ts -limiter.setOptions({ - enabled: (limiter) => limiter.store.state.errorCount < 3, - limit: (limiter) => (limiter.store.state.rejectionCount > 10 ? 2 : 5), -}) +import { signal } from '@angular/core' + +const limit = signal(5) +const limiter = injectAsyncRateLimiter(sendRequest, () => ({ limit: limit(), window: 60_000 })) + +limit.set(10) ``` Changing `limit`, `window`, or `windowType` does not erase existing execution history. Call `reset()` if a new configuration should begin with a fresh window. A disabled limiter does not execute the function or consume capacity; its calls resolve with `undefined`. @@ -151,7 +153,7 @@ The adapter aborts active work when its owner is destroyed. Providing `onUnmount ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const limiter = injectAsyncRateLimiter( @@ -171,7 +173,7 @@ console.log( ) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers and active executions are not restored. diff --git a/docs/framework/angular/guides/async-retrying.md b/docs/framework/angular/guides/async-retrying.md index ccffdb6d0..6280ca34d 100644 --- a/docs/framework/angular/guides/async-retrying.md +++ b/docs/framework/angular/guides/async-retrying.md @@ -8,7 +8,13 @@ Retrying runs an async operation again after it fails. It can make transient fai > [!NOTE] > `AsyncRetryer` is an alpha API and may change before 1.0. Its current design also supports the retry behavior inside Pacer's other async utilities. -Retrying is the exception among these framework guides: TanStack Pacer does not provide an Angular-specific retry primitive. The adapter re-exports the public `asyncRetry` function and `AsyncRetryer` class, so this guide uses those APIs and connects long-lived instances to the Angular lifecycle. +Retrying is the exception among these framework guides: TanStack Pacer does not provide an Angular-specific retry primitive. Use `asyncRetry` and `AsyncRetryer` from `@tanstack/pacer` and connect long-lived instances to the Angular lifecycle. + +Install the core package when using these APIs directly: + +```sh +npm install @tanstack/pacer +``` If TanStack Query already owns the request, use its retry support so one system controls request state and cancellation. @@ -41,7 +47,7 @@ Also consider whether repeating the operation is idempotent. Reads are commonly `asyncRetry` creates one retryer and returns its bound execution function: ```ts -import { asyncRetry } from '@tanstack/angular-pacer' +import { asyncRetry } from '@tanstack/pacer' const loadUserWithRetry = asyncRetry(loadUser, { maxAttempts: 3, @@ -62,7 +68,7 @@ The defaults are three total attempts, exponential backoff from 1000 millisecond The returned function can be reused sequentially. It owns one `AsyncRetryer`, so starting a new call while an earlier call is active aborts the earlier retry flow. When calls may overlap, create a retryer per call or use another utility that manages concurrency: ```ts -import { AsyncRetryer } from '@tanstack/angular-pacer' +import { AsyncRetryer } from '@tanstack/pacer' async function loadOneUser(id: string) { const retryer = new AsyncRetryer(loadUser, { maxAttempts: 3 }) @@ -74,7 +80,7 @@ async function loadOneUser(id: string) { ```ts import { DestroyRef, inject } from '@angular/core' -import { AsyncRetryer } from '@tanstack/angular-pacer' +import { AsyncRetryer } from '@tanstack/pacer' export class UserComponent { private readonly destroyRef = inject(DestroyRef) diff --git a/docs/framework/angular/guides/async-throttling.md b/docs/framework/angular/guides/async-throttling.md index 14f807cd9..28bd3923c 100644 --- a/docs/framework/angular/guides/async-throttling.md +++ b/docs/framework/angular/guides/async-throttling.md @@ -126,16 +126,18 @@ saver.reset() ## Configuration -`wait` and `enabled` may be values or functions that receive the throttler instance. `setOptions()` merges new options into the existing configuration. +`wait` and `enabled` may be values or functions that receive the throttler instance. Use an options function for reactive configuration. ```ts -saver.setOptions({ - enabled: (throttler) => throttler.store.state.errorCount < 3, - wait: (throttler) => (throttler.store.state.successCount < 10 ? 500 : 1000), -}) +import { signal } from '@angular/core' + +const wait = signal(1000) +const saver = injectAsyncThrottler(savePositionToServer, () => ({ wait: wait() })) + +wait.set(250) ``` -A changed `wait` value does not reschedule existing trailing work. It applies to later scheduling and executions. Disabling the throttler through `setOptions()` cancels pending trailing work. +A changed `wait` value does not reschedule existing trailing work. It applies to later scheduling and executions. Disabling the throttler cancels pending trailing work. Use `asyncThrottlerOptions()` to define reusable, type-checked option objects. @@ -145,7 +147,7 @@ The adapter cancels pending work and aborts active work when its owner is destro ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const throttler = injectAsyncThrottler( @@ -165,7 +167,7 @@ console.log( ) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers and active executions are not restored. diff --git a/docs/framework/angular/guides/batching.md b/docs/framework/angular/guides/batching.md index 88a2d6116..719fd2b56 100644 --- a/docs/framework/angular/guides/batching.md +++ b/docs/framework/angular/guides/batching.md @@ -148,13 +148,15 @@ batcher.reset() ## Configuring and observing batches -Use `setOptions()` to update future trigger behavior: +Use an options function to read reactive configuration: ```ts -batcher.setOptions({ - maxSize: 20, - wait: 500, -}) +import { signal } from '@angular/core' + +const options = signal({ maxSize: 10, wait: 100 }) +const batcher = injectBatcher(processBatch, options) + +options.set({ maxSize: 20, wait: 500 }) ``` Changing `wait` does not reschedule an existing timer. The next `addItem()` call replaces that timer using the current value. @@ -190,7 +192,7 @@ The adapter cancels the pending wait timer while retaining collected items when ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const batcher = injectBatcher( @@ -205,7 +207,7 @@ const batcher = injectBatcher( console.log(batcher.state().size, batcher.state().isPending) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers are not restored. diff --git a/docs/framework/angular/guides/debouncing.md b/docs/framework/angular/guides/debouncing.md index 7c902fc65..4e2c8dae6 100644 --- a/docs/framework/angular/guides/debouncing.md +++ b/docs/framework/angular/guides/debouncing.md @@ -47,7 +47,7 @@ Choose another utility when: ## Choose an API -- `injectDebouncedSignal` or `injectDebouncedValue` for delayed signals +- `injectDebouncedSignal` or `injectDebouncedComputed` for delayed signals - `injectDebouncer` for lifecycle methods and selected state Use the instance's `maybeExecute` method for event handlers, and state or value helpers for delayed UI state. Keep the instance for cancellation, flushing, selected state, and dynamic options. @@ -135,30 +135,32 @@ debouncer.reset() ## Configuring behavior at runtime -Use `setOptions()` to change options after construction: +Use an options function to read reactive configuration: ```ts -debouncer.setOptions({ - wait: 1000, - leading: true, - trailing: false, -}) +import { signal } from '@angular/core' + +const options = signal({ wait: 500, leading: false, trailing: true }) +const debouncer = injectDebouncer(saveDraft, options) + +options.set({ wait: 1000, leading: true, trailing: false }) ``` A new `wait` value applies when the next call schedules a timeout. It does not reschedule a timeout that is already pending. Calling `maybeExecute()` again clears the old timeout and schedules a new one using the current options. ### Enabling and disabling -Set `enabled` to `false` to prevent execution. Disabling a debouncer through `setOptions()` also cancels its pending call. +Set `enabled` to `false` to prevent execution. Disabling a debouncer also cancels its pending call. ```ts -const debouncer = injectDebouncer(saveDraft, { +const enabled = signal(false) +const debouncer = injectDebouncer(saveDraft, () => ({ wait: 500, - enabled: false, -}) + enabled: enabled(), +})) debouncer.maybeExecute('ignored') -debouncer.setOptions({ enabled: true }) +enabled.set(true) debouncer.maybeExecute('saved') ``` @@ -191,7 +193,7 @@ The adapter cancels pending work when its owner is destroyed. Providing `onUnmou ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const debouncer = injectDebouncer(saveDraft, { wait: 500 }, (state) => ({ @@ -202,7 +204,7 @@ const debouncer = injectDebouncer(saveDraft, { wait: 500 }, (state) => ({ console.log(debouncer.state().isPending, debouncer.state().executionCount) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers are not restored. diff --git a/docs/framework/angular/guides/queuing.md b/docs/framework/angular/guides/queuing.md index 07775034e..e5183c90d 100644 --- a/docs/framework/angular/guides/queuing.md +++ b/docs/framework/angular/guides/queuing.md @@ -43,21 +43,20 @@ Choose another utility when: ## Choose an API -- `injectQueuedSignal` or `injectQueuedValue` for queue-driven signals +- `injectQueuedComputed` to queue observed source values +- `injectQueuedSignal` to queue individual `set` and `update` operations +- `injectQueuerItems` for a signal of pending queue items - `injectQueuer` for direct queue lifecycle and ordering control -Use the queued state or value API when queue contents drive the UI. Use the instance API for ordering, capacity, expiration, pause, resume, flush, and manual processing. +Use the items helper when queue contents drive the UI. Use the instance API for ordering, capacity, expiration, pause, resume, flush, and manual processing. ## Angular example ```ts -import { injectQueuedSignal } from '@tanstack/angular-pacer' +import { injectQueuerItems } from '@tanstack/angular-pacer' export class JobQueueComponent { - readonly queued = injectQueuedSignal(processJob, { wait: 500 }, (state) => ({ - items: state.items, - isRunning: state.isRunning, - })) + readonly queued = injectQueuerItems(processJob, { wait: 500 }) add() { this.queued.addItem(nextJob()) @@ -69,39 +68,42 @@ The focused snippets later in this guide use `injectQueuer` and assume they run Pass `initialItems` when work is already available at creation time. The queue applies its normal insertion and capacity rules, and automatic processing can begin immediately unless `started: false` is set. -## Queue a source signal +## Reading pending items -`injectQueuedValue` returns a `QueuedValueSignal`: `queued()` reads the most recently processed value, `queued.addItem(value)` enqueues a value, and `queued.queuer` exposes the queue controls. Its default selector exposes pending items at `queued.queuer.state().items`. +`queued()` returns the waiting items as a readonly array. Use the `addItem` shortcut to enqueue items and `queued.queuer` to control processing: ```ts -readonly source = input.required() -readonly queued = injectQueuedValue(this.source, { wait: 500 }) +const queued = injectQueuerItems(processItem, { started: false }) +queued.addItem('first') +queued.addItem('second') +console.log(queued()) // ['first', 'second'] +queued.queuer.start() ``` -Construction does not read the source. Angular's effect reads it after inputs are bound and enqueues the latest value. Each later effect run enqueues the latest observed source value. Several source writes before an effect runs are coalesced; use `queued.addItem` for every value that must enter the queue individually. +Pass a third-argument selector to expose counters or running state through `queued.queuer.state()`. The items signal still returns the waiting items. -Before processing begins, `queued()` reads the initial source value. Reading it before a required input is bound throws Angular's required-input error. Supply an explicit initial value if the output must be readable earlier: +## Queued values + +`injectQueuedComputed` returns the last processed source value. It queues the initial value and subsequent values observed by Angular effects. Multiple source writes before an effect runs are observed as one change. ```ts -const text = injectQueuedValue(source, '') -const object = injectQueuedValue(objectSource, { label: 'Loading' }, {}) -const callback = injectQueuedValue(callbackSource, () => 'Loading', {}) +const source = signal('') +const queued = injectQueuedComputed(source, { wait: 500 }) +source.set('next') +console.log(queued()) // Last processed value. ``` -Two-argument primitive initial values, including `0`, `false`, `''`, and `null`, are supported. Object and function initial values require a third options object, such as `{}`. A function in the options position is an options factory. - -Pass a fourth selector argument, which can be `undefined`, when an explicit initial value uses factory or undefined options: +`injectQueuedSignal` returns an editable signal whose accepted writes are queued individually. Each updater runs against the value at processing time: ```ts -const queued = injectQueuedValue(source, '', () => ({ wait: wait() }), undefined) -const initialObject = injectQueuedValue(objectSource, { label: 'Loading' }, undefined, undefined) +const count = injectQueuedSignal(0, { started: false }) +count.set(1) +count.update((previous) => previous + 2) +count.queuer.flush() +console.log(count()) // 3 ``` -`injectQueuedValue(source, options, undefined)` keeps the options-only meaning. An explicit `undefined` initial value requires an options object or a fourth argument. - -### Migrate from the previous return shape - -Earlier versions returned the pending-items array from `queued()`. It now returns the processed scalar value. Replace `queued().length` with `queued.queuer.state().items.length` using the default selector, or use `injectQueuedSignal` when the queue contents are the desired signal value. Custom selectors must include `items` to access that list. +Both helpers accept a selector for their attached `queuer.state()`. The editable helper queues callbacks, so its item-dependent options receive callbacks rather than the displayed value. ## Ordering items @@ -271,11 +273,15 @@ queuer.reset() ## Configuring and observing the queue -Use `setOptions()` to update future behavior. Changing `started` through `setOptions()` does not call `start()` or `stop()`. +Use an options function to read reactive configuration: ```ts -queuer.setOptions({ wait: 250, maxSize: 20 }) -queuer.start() +import { signal } from '@angular/core' + +const options = signal({ wait: 500, maxSize: 10 }) +const queuer = injectQueuer(processItem, options) + +options.set({ wait: 250, maxSize: 20 }) ``` The `wait` option may be a function that receives the queuer instance: @@ -299,7 +305,7 @@ The adapter stops automatic processing when its owner is destroyed. Providing `o ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const queuer = injectQueuer(processItem, { wait: 250 }, (state) => ({ @@ -310,7 +316,7 @@ const queuer = injectQueuer(processItem, { wait: 250 }, (state) => ({ console.log(queuer.state().size, queuer.state().isRunning) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. `initialState` can restore selected queue state that your app has persisted. If it includes `items`, they take precedence over `initialItems`; `initialState.isRunning` likewise takes precedence over `started`. Restore only durable fields. Pending timers are not restored. diff --git a/docs/framework/angular/guides/rate-limiting.md b/docs/framework/angular/guides/rate-limiting.md index b1c7bc9f0..9e07ce713 100644 --- a/docs/framework/angular/guides/rate-limiting.md +++ b/docs/framework/angular/guides/rate-limiting.md @@ -81,7 +81,7 @@ Use a sliding window when capacity should return gradually rather than all at on ## Choose an API -- `injectRateLimitedSignal` or `injectRateLimitedValue` for signals +- `injectRateLimitedSignal` or `injectRateLimitedComputed` for signals - `injectRateLimiter` for capacity helpers and selected state Call `maybeExecute()` for quota-controlled operations. Use state or value helpers for UI updates, and the instance for capacity helpers and rejection state. @@ -143,13 +143,15 @@ Both helpers use the current `limit`, `window`, `windowType`, and execution hist limiter.reset() ``` -Use `setOptions()` to update the configuration: +Use an options function to read reactive configuration: ```ts -limiter.setOptions({ - limit: 10, - window: 30_000, -}) +import { signal } from '@angular/core' + +const options = signal({ limit: 5, window: 60_000 }) +const limiter = injectRateLimiter(sendEvent, options) + +options.set({ limit: 10, window: 30_000 }) ``` Changing options does not erase existing execution history. Call `reset()` when the new configuration should begin with a fresh window. @@ -190,7 +192,7 @@ The adapter has no default operation cleanup because a synchronous limiter has n ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const limiter = injectRateLimiter( @@ -205,7 +207,7 @@ const limiter = injectRateLimiter( console.log(limiter.state().isExceeded, limiter.state().rejectionCount) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers are not restored. diff --git a/docs/framework/angular/guides/throttling.md b/docs/framework/angular/guides/throttling.md index aee22d16e..a5359f4e0 100644 --- a/docs/framework/angular/guides/throttling.md +++ b/docs/framework/angular/guides/throttling.md @@ -44,7 +44,7 @@ Choose another utility when: ## Choose an API -- `injectThrottledSignal` or `injectThrottledValue` for throttled signals +- `injectThrottledSignal` or `injectThrottledComputed` for throttled signals - `injectThrottler` for lifecycle methods and selected state Use instance methods for event handlers, and state or value helpers for rate-controlled UI state. @@ -124,13 +124,15 @@ throttler.reset() ## Configuring behavior at runtime -Use `setOptions()` to update options after construction: +Use an options function to read reactive configuration: ```ts -throttler.setOptions({ - wait: 250, - trailing: false, -}) +import { signal } from '@angular/core' + +const options = signal({ wait: 100, trailing: true }) +const throttler = injectThrottler(updateProgress, options) + +options.set({ wait: 250, trailing: false }) ``` A changed `wait` value does not reschedule an existing trailing timeout. It applies to later scheduling and executions. @@ -144,7 +146,7 @@ const throttler = injectThrottler(updateProgress, { }) ``` -Disabling a throttler through `setOptions()` cancels a pending trailing execution. +Disabling a throttler cancels a pending trailing execution. ### Observing executions @@ -166,7 +168,7 @@ The adapter cancels pending work when its owner is destroyed. Providing `onUnmou ## Reactive state -The adapter subscribes only to the state returned by the selector argument. Without a selector, the adapter state is empty. Create the utility in an Angular injection context, usually as a component or service field initializer and select only fields used by the view: +Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`: ```ts const throttler = injectThrottler(updateProgress, { wait: 100 }, (state) => ({ @@ -177,7 +179,7 @@ const throttler = injectThrottler(updateProgress, { wait: 100 }, (state) => ({ console.log(throttler.state().isPending, throttler.state().executionCount) ``` -Option functions and lifecycle callbacks receive the underlying public utility instance. The `.store.state` reads inside those callbacks in the examples above are supported. Rendering code should read the selected adapter state shown here. +Option callbacks receive the core utility instance. Read the selected `state()` signal in templates. To restore selected state that your app has persisted, pass a partial snapshot through `initialState`. It is merged with the defaults. Restore only durable fields. Pending timers are not restored. diff --git a/docs/framework/angular/reference/functions/injectAsyncBatcher.md b/docs/framework/angular/reference/functions/injectAsyncBatcher.md index 57b4af94d..4dc6944c4 100644 --- a/docs/framework/angular/reference/functions/injectAsyncBatcher.md +++ b/docs/framework/angular/reference/functions/injectAsyncBatcher.md @@ -3,95 +3,115 @@ id: injectAsyncBatcher title: injectAsyncBatcher --- +## Call Signature + ```ts -function injectAsyncBatcher( - fn, - options?, -selector?): AngularAsyncBatcher; +function injectAsyncBatcher(fn, options?): AngularAsyncBatcher; ``` -Defined in: [async-batcher/injectAsyncBatcher.ts:103](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L103) +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:91 + +Creates and manages an Angular AsyncBatcher in the current injection context. + +Collects items and processes each batch asynchronously when its size or delay threshold is reached. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. -An Angular function that creates and manages an AsyncBatcher instance. +## Cleanup -This is a lower-level function that provides direct access to the AsyncBatcher's functionality. -This allows you to integrate it with any state management solution you prefer. +The default cleanup is to cancel pending batches and abort running work. Set `onUnmount` to replace it. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### fn + +(`items`) => `Promise`\<`any`\> -The AsyncBatcher collects items and processes them in batches asynchronously with support for -promise-based processing, error handling, retry capabilities, and abort support. +The callback invoked by the core utility. -## State Management and Selector +#### options? -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +`MaybeAccessor`\<[`AngularAsyncBatcherOptions`](../interfaces/AngularAsyncBatcherOptions.md)\<`TValue`\>\> -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +Core options or a reactive factory returning them. -## Cleanup on Destroy +### Returns -By default, the function cancels any pending batch and aborts in-flight work when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +[`AngularAsyncBatcher`](../interfaces/AngularAsyncBatcher.md)\<`TValue`\> + +A ref containing stable methods and a readonly selected-state signal. + +### Example + +```ts +// In a component or service injection context. +const utility = injectAsyncBatcher( + (items: Array) => Promise.resolve(items), + () => ({ maxSize: 10, wait: 100 }), + (state) => state.isPending, +) +utility.addItem('job') +console.log(utility.state()) +``` + +## Call Signature ```ts -const batcher = injectAsyncBatcher(fn, { - maxSize: 10, - onUnmount: (b) => b.flush() -}); +function injectAsyncBatcher( + fn, + options, +selector): AngularAsyncBatcher; ``` -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:102 -## Type Parameters +Creates an Angular AsyncBatcher with a reactive selector result. -### TValue +### Type Parameters + +#### TValue `TValue` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn (`items`) => `Promise`\<`any`\> -### options? +The callback invoked by the core utility. + +#### options -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularAsyncBatcherOptions`](../interfaces/AngularAsyncBatcherOptions.md)\<`TValue`, `TSelected`\>\> = `{}` +`MaybeAccessor`\<[`AngularAsyncBatcherOptions`](../interfaces/AngularAsyncBatcherOptions.md)\<`TValue`\>\> -### selector? +Core options or a reactive options factory. + +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularAsyncBatcher`](../interfaces/AngularAsyncBatcher.md)\<`TValue`, `TSelected`\> +### Returns -## Example +[`AngularAsyncBatcher`](../interfaces/AngularAsyncBatcher.md)\<`TValue`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const batcher = injectAsyncBatcher( - async (items: Array) => { - const response = await fetch('/api/batch', { - method: 'POST', - body: JSON.stringify(items) - }); - return response.json(); - }, - { maxSize: 10, wait: 2000 } -); - -// Add items -batcher.addItem(data1); -batcher.addItem(data2); - -// Access the selected state -const { items, isExecuting } = batcher.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md index 4b30d0e55..ab25308ae 100644 --- a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md +++ b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md @@ -3,117 +3,115 @@ id: injectAsyncDebouncer title: injectAsyncDebouncer --- +## Call Signature + ```ts -function injectAsyncDebouncer( - fn, - options, -selector?): AngularAsyncDebouncer; +function injectAsyncDebouncer(fn, options): AngularAsyncDebouncer; ``` -Defined in: [async-debouncer/injectAsyncDebouncer.ts:126](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L126) +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:90 + +Creates and manages an Angular AsyncDebouncer in the current injection context. + +Waits until calls stop for the configured delay, then runs the latest asynchronous callback. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. -An Angular function that creates and manages an AsyncDebouncer instance. +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. -This is a lower-level function that provides direct access to the AsyncDebouncer's functionality. -This allows you to integrate it with any state management solution you prefer. +## Cleanup -This function provides async debouncing functionality with promise support, error handling, -retry capabilities, and abort support. +The default cleanup is to cancel pending execution and abort running work. Set `onUnmount` to replace it. + +### Type Parameters + +#### TFn + +`TFn` *extends* `AnyAsyncFunction` + +### Parameters + +#### fn + +`TFn` -The debouncer will only execute the function after the specified wait time has elapsed -since the last call. If the function is called again before the wait time expires, the -timer resets and starts waiting again. +The callback invoked by the core utility. -## State Management and Selector +#### options -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +`MaybeAccessor`\<[`AngularAsyncDebouncerOptions`](../interfaces/AngularAsyncDebouncerOptions.md)\<`TFn`\>\> -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +Core options or a reactive factory returning them. -Available state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `errorCount`: Number of function executions that have resulted in errors -- `isExecuting`: Whether the debounced function is currently executing asynchronously -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastResult`: The result from the most recent successful function execution -- `settleCount`: Number of function executions that have completed (either successfully or with errors) -- `status`: Current execution status ('disabled' | 'idle' | 'pending' | 'executing' | 'settled') -- `successCount`: Number of function executions that have completed successfully +### Returns -## Cleanup on Destroy +[`AngularAsyncDebouncer`](../interfaces/AngularAsyncDebouncer.md)\<`TFn`\> -By default, the function cancels any pending execution and aborts in-flight work when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +A ref containing stable methods and a readonly selected-state signal. + +### Example + +```ts +// In a component or service injection context. +const utility = injectAsyncDebouncer( + (query: string) => Promise.resolve(query), + () => ({ wait: 250 }), + (state) => state.isPending, +) +utility.maybeExecute('search') +console.log(utility.state()) +``` + +## Call Signature ```ts -const debouncer = injectAsyncDebouncer(fn, { - wait: 500, - onUnmount: (d) => d.flush() -}); +function injectAsyncDebouncer( + fn, + options, +selector): AngularAsyncDebouncer; ``` -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:101 -## Type Parameters +Creates an Angular AsyncDebouncer with a reactive selector result. -### TFn +### Type Parameters + +#### TFn `TFn` *extends* `AnyAsyncFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularAsyncDebouncerOptions`](../interfaces/AngularAsyncDebouncerOptions.md)\<`TFn`, `TSelected`\>\> +`MaybeAccessor`\<[`AngularAsyncDebouncerOptions`](../interfaces/AngularAsyncDebouncerOptions.md)\<`TFn`\>\> -### selector? +Core options or a reactive options factory. + +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularAsyncDebouncer`](../interfaces/AngularAsyncDebouncer.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularAsyncDebouncer`](../interfaces/AngularAsyncDebouncer.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const debouncer = injectAsyncDebouncer( - async (query: string) => { - const response = await fetch(`/api/search?q=${query}`); - return response.json(); - }, - { wait: 500 } -); - -// Opt-in to track isExecuting changes (optimized for loading states) -const debouncer = injectAsyncDebouncer( - async (query: string) => fetchSearchResults(query), - { wait: 500 }, - (state) => ({ isExecuting: state.isExecuting, isPending: state.isPending }) -); - -// In an event handler -const handleChange = async (e: Event) => { - const target = e.target as HTMLInputElement; - const result = await debouncer.maybeExecute(target.value); - console.log('Search results:', result); -}; - -// Access the selected state -const { isExecuting, errorCount } = debouncer.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuedSignal.md b/docs/framework/angular/reference/functions/injectAsyncQueuedSignal.md deleted file mode 100644 index 5a1caa717..000000000 --- a/docs/framework/angular/reference/functions/injectAsyncQueuedSignal.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -id: injectAsyncQueuedSignal -title: injectAsyncQueuedSignal ---- - -```ts -function injectAsyncQueuedSignal( - fn, - options?, -selector?): AsyncQueuedSignal; -``` - -Defined in: [async-queuer/injectAsyncQueuedSignal.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L53) - -An Angular function that creates an async queuer with managed state, combining Angular's signals with async queuing functionality. -This function provides both the current queue state and queue control methods. - -The queue state is automatically updated whenever items are added, removed, or processed in the queue. -All queue operations are reflected in the state array returned by the function. - -The function returns a callable object: -- `queued()`: Get the current queue items as an array -- `queued.addItem(...)`: Add an item to the queue -- `queued.queue`: The queuer instance with additional control methods - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` *extends* `Pick`\<`AsyncQueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`AsyncQueuerState`\<`TValue`\>, `"items"`\> - -## Parameters - -### fn - -(`value`) => `Promise`\<`any`\> - -### options? - -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`AsyncQueuerOptions`\<`TValue`\>\> = `{}` - -### selector? - -(`state`) => `TSelected` - -## Returns - -[`AsyncQueuedSignal`](../interfaces/AsyncQueuedSignal.md)\<`TValue`, `TSelected`\> - -## Example - -```ts -// Default behavior - track items -const queued = injectAsyncQueuedSignal( - async (item) => { - const response = await fetch('/api/process', { - method: 'POST', - body: JSON.stringify(item) - }); - return response.json(); - }, - { concurrency: 2, wait: 1000 } -); - -// Add items -queued.addItem(data1); - -// Access items -console.log(queued()); // [data1, ...] - -// Control the queue -queued.queuer.start(); -queued.queuer.stop(); -``` diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuer.md b/docs/framework/angular/reference/functions/injectAsyncQueuer.md index eb4ba6aec..f0783a698 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuer.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuer.md @@ -3,95 +3,115 @@ id: injectAsyncQueuer title: injectAsyncQueuer --- +## Call Signature + ```ts -function injectAsyncQueuer( - fn, - options?, -selector?): AngularAsyncQueuer; +function injectAsyncQueuer(fn, options?): AngularAsyncQueuer; ``` -Defined in: [async-queuer/injectAsyncQueuer.ts:103](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L103) +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:95 + +Creates and manages an Angular AsyncQueuer in the current injection context. + +Processes queued items asynchronously with configurable pacing and concurrency. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to stop automatic processing and abort running work. Set `onUnmount` to replace it. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters -An Angular function that creates and manages an AsyncQueuer instance. +#### fn -This is a lower-level function that provides direct access to the AsyncQueuer's functionality. -This allows you to integrate it with any state management solution you prefer. +(`item`) => `Promise`\<`any`\> -The AsyncQueuer processes items asynchronously with support for concurrent execution, -promise-based processing, error handling, retry capabilities, and abort support. +The callback invoked by the core utility. -## State Management and Selector +#### options? -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +`MaybeAccessor`\<[`AngularAsyncQueuerOptions`](../interfaces/AngularAsyncQueuerOptions.md)\<`TValue`\>\> -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +Core options or a reactive factory returning them. -## Cleanup on Destroy +### Returns -By default, the function stops the queuer and aborts in-flight work when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending items instead: +[`AngularAsyncQueuer`](../interfaces/AngularAsyncQueuer.md)\<`TValue`\> + +A ref containing stable methods and a readonly selected-state signal. + +### Example + +```ts +// In a component or service injection context. +const utility = injectAsyncQueuer( + (item: string) => Promise.resolve(item), + () => ({ wait: 100, concurrency: 2 }), + (state) => state.items, +) +utility.addItem('job') +console.log(utility.state()) +``` + +## Call Signature ```ts -const queuer = injectAsyncQueuer(fn, { - concurrency: 2, - onUnmount: (q) => q.flush() -}); +function injectAsyncQueuer( + fn, + options, +selector): AngularAsyncQueuer; ``` -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:106 -## Type Parameters +Creates an Angular AsyncQueuer with a reactive selector result. -### TValue +### Type Parameters + +#### TValue `TValue` -### TSelected +#### TSelected + +`TSelected` -`TSelected` = \{ -\} +### Parameters -## Parameters +#### fn -### fn +(`item`) => `Promise`\<`any`\> -(`value`) => `Promise`\<`any`\> +The callback invoked by the core utility. -### options? +#### options -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularAsyncQueuerOptions`](../interfaces/AngularAsyncQueuerOptions.md)\<`TValue`, `TSelected`\>\> = `{}` +`MaybeAccessor`\<[`AngularAsyncQueuerOptions`](../interfaces/AngularAsyncQueuerOptions.md)\<`TValue`\>\> -### selector? +Core options or a reactive options factory. + +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularAsyncQueuer`](../interfaces/AngularAsyncQueuer.md)\<`TValue`, `TSelected`\> +### Returns -## Example +[`AngularAsyncQueuer`](../interfaces/AngularAsyncQueuer.md)\<`TValue`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const queuer = injectAsyncQueuer( - async (item: Data) => { - const response = await fetch('/api/process', { - method: 'POST', - body: JSON.stringify(item) - }); - return response.json(); - }, - { concurrency: 2, wait: 1000 } -); - -// Add items -queuer.addItem(data1); -queuer.addItem(data2); - -// Access the selected state -const { items, isExecuting } = queuer.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md new file mode 100644 index 000000000..a5c28d2b7 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md @@ -0,0 +1,103 @@ +--- +id: injectAsyncQueuerItems +title: injectAsyncQueuerItems +--- + +## Call Signature + +```ts +function injectAsyncQueuerItems(fn, options?): AngularAsyncQueuerItems; +``` + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:51 + +Creates a readonly Angular signal of pending asynchronous queue items. + +Items appear when added and disappear when removed for processing. This signal +contains waiting items, not processing results. Use the attached `queuer` methods +to add items, start or stop processing, or inspect the queue. + +Options accept a static object or reactive factory and follow the underlying +queuer's lifecycle and provider behavior. The returned function is an Angular signal. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### fn + +(`value`) => `Promise`\<`any`\> + +The callback that processes each queued item. + +#### options? + +`MaybeAccessor`\<[`AngularAsyncQueuerOptions`](../interfaces/AngularAsyncQueuerOptions.md)\<`TValue`\>\> + +Core queue options or a reactive options factory. + +### Returns + +[`AngularAsyncQueuerItems`](../interfaces/AngularAsyncQueuerItems.md)\<`TValue`\> + +A readonly items signal with `queuer` and `addItem` attributes. + +### Example + +```ts +// In a component or service injection context. +const items = injectAsyncQueuerItems( + async (item: string) => console.log(item), + { started: false }, +) +items.addItem('job') +console.log(items()) // ['job'] +items.queuer.start() +``` + +## Call Signature + +```ts +function injectAsyncQueuerItems( + fn, + options, +selector): AngularAsyncQueuerItems; +``` + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:59 + +Creates the items signal with selected state on its attached queuer. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### fn + +(`value`) => `Promise`\<`any`\> + +#### options + +`MaybeAccessor`\<[`AngularAsyncQueuerOptions`](../interfaces/AngularAsyncQueuerOptions.md)\<`TValue`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached queuer. + +### Returns + +[`AngularAsyncQueuerItems`](../interfaces/AngularAsyncQueuerItems.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md index 5f71bf05d..668b75ce7 100644 --- a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md @@ -3,82 +3,115 @@ id: injectAsyncRateLimiter title: injectAsyncRateLimiter --- +## Call Signature + ```ts -function injectAsyncRateLimiter( - fn, - options, -selector?): AngularAsyncRateLimiter; +function injectAsyncRateLimiter(fn, options): AngularAsyncRateLimiter; ``` -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:90](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L90) +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:92 + +Creates and manages an Angular AsyncRateLimiter in the current injection context. -An Angular function that creates and manages an AsyncRateLimiter instance. +Allows asynchronous calls up to the configured limit within a fixed or sliding window. Calls beyond the limit are rejected rather than queued. -This is a lower-level function that provides direct access to the AsyncRateLimiter's functionality. -This allows you to integrate it with any state management solution you prefer. +## Options and state -This function provides async rate limiting functionality with promise support, error handling, -retry capabilities, and abort support. +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. -## State Management and Selector +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +## Cleanup -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +The default cleanup is to abort running work and reset the limiter. Set `onUnmount` to replace it. -## Cleanup on Destroy +### Type Parameters -By default, the function aborts in-flight work when the component is destroyed. -Use the `onUnmount` option to customize this. +#### TFn -## Type Parameters +`TFn` *extends* `AnyAsyncFunction` + +### Parameters + +#### fn + +`TFn` -### TFn +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularAsyncRateLimiterOptions`](../interfaces/AngularAsyncRateLimiterOptions.md)\<`TFn`\>\> + +Core options or a reactive factory returning them. + +### Returns + +[`AngularAsyncRateLimiter`](../interfaces/AngularAsyncRateLimiter.md)\<`TFn`\> + +A ref containing stable methods and a readonly selected-state signal. + +### Example + +```ts +// In a component or service injection context. +const utility = injectAsyncRateLimiter( + (query: string) => Promise.resolve(query), + () => ({ limit: 5, window: 1000 }), + (state) => state.isExecuting, +) +utility.maybeExecute('search') +console.log(utility.state()) +``` + +## Call Signature + +```ts +function injectAsyncRateLimiter( + fn, + options, +selector): AngularAsyncRateLimiter; +``` + +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:103 + +Creates an Angular AsyncRateLimiter with a reactive selector result. + +### Type Parameters + +#### TFn `TFn` *extends* `AnyAsyncFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularAsyncRateLimiterOptions`](../interfaces/AngularAsyncRateLimiterOptions.md)\<`TFn`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularAsyncRateLimiterOptions`](../interfaces/AngularAsyncRateLimiterOptions.md)\<`TFn`, `TSelected`\>\> +Core options or a reactive options factory. -### selector? +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularAsyncRateLimiter`](../interfaces/AngularAsyncRateLimiter.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularAsyncRateLimiter`](../interfaces/AngularAsyncRateLimiter.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const rateLimiter = injectAsyncRateLimiter( - async (id: string) => { - const response = await fetch(`/api/data/${id}`); - return response.json(); - }, - { limit: 5, window: 60000, windowType: 'sliding' } -); - -// In an event handler -const handleRequest = async (id: string) => { - const result = await rateLimiter.maybeExecute(id); - console.log('Result:', result); -}; -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectAsyncThrottler.md b/docs/framework/angular/reference/functions/injectAsyncThrottler.md index d68e8e05b..2bb54ddd3 100644 --- a/docs/framework/angular/reference/functions/injectAsyncThrottler.md +++ b/docs/framework/angular/reference/functions/injectAsyncThrottler.md @@ -3,96 +3,115 @@ id: injectAsyncThrottler title: injectAsyncThrottler --- +## Call Signature + ```ts -function injectAsyncThrottler( - fn, - options, -selector?): AngularAsyncThrottler; +function injectAsyncThrottler(fn, options): AngularAsyncThrottler; ``` -Defined in: [async-throttler/injectAsyncThrottler.ts:105](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L105) +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:90 + +Creates and manages an Angular AsyncThrottler in the current injection context. + +Limits asynchronous executions to the configured interval, with leading and trailing calls. Later calls replace the pending trailing arguments without restarting the interval. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to cancel pending execution and abort running work. Set `onUnmount` to replace it. + +### Type Parameters + +#### TFn + +`TFn` *extends* `AnyAsyncFunction` + +### Parameters -An Angular function that creates and manages an AsyncThrottler instance. +#### fn -This is a lower-level function that provides direct access to the AsyncThrottler's functionality. -This allows you to integrate it with any state management solution you prefer. +`TFn` + +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularAsyncThrottlerOptions`](../interfaces/AngularAsyncThrottlerOptions.md)\<`TFn`\>\> -This function provides async throttling functionality with promise support, error handling, -retry capabilities, and abort support. +Core options or a reactive factory returning them. -The throttler will execute the function at most once within the specified wait time. +### Returns -## State Management and Selector +[`AngularAsyncThrottler`](../interfaces/AngularAsyncThrottler.md)\<`TFn`\> -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +A ref containing stable methods and a readonly selected-state signal. -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +### Example -## Cleanup on Destroy +```ts +// In a component or service injection context. +const utility = injectAsyncThrottler( + (value: number) => Promise.resolve(value), + () => ({ wait: 100 }), + (state) => state.isPending, +) +utility.maybeExecute(42) +console.log(utility.state()) +``` -By default, the function cancels any pending execution and aborts in-flight work when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +## Call Signature ```ts -const throttler = injectAsyncThrottler(fn, { - wait: 1000, - onUnmount: (t) => t.flush() -}); +function injectAsyncThrottler( + fn, + options, +selector): AngularAsyncThrottler; ``` -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:101 + +Creates an Angular AsyncThrottler with a reactive selector result. -## Type Parameters +### Type Parameters -### TFn +#### TFn `TFn` *extends* `AnyAsyncFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularAsyncThrottlerOptions`](../interfaces/AngularAsyncThrottlerOptions.md)\<`TFn`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularAsyncThrottlerOptions`](../interfaces/AngularAsyncThrottlerOptions.md)\<`TFn`, `TSelected`\>\> +Core options or a reactive options factory. -### selector? +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularAsyncThrottler`](../interfaces/AngularAsyncThrottler.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularAsyncThrottler`](../interfaces/AngularAsyncThrottler.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const throttler = injectAsyncThrottler( - async (data: Data) => { - const response = await fetch('/api/update', { - method: 'POST', - body: JSON.stringify(data) - }); - return response.json(); - }, - { wait: 1000 } -); - -// In an event handler -const handleUpdate = async (data: Data) => { - const result = await throttler.maybeExecute(data); - console.log('Update result:', result); -}; -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectBatcher.md b/docs/framework/angular/reference/functions/injectBatcher.md index a07e3dda6..9c75fec06 100644 --- a/docs/framework/angular/reference/functions/injectBatcher.md +++ b/docs/framework/angular/reference/functions/injectBatcher.md @@ -3,88 +3,115 @@ id: injectBatcher title: injectBatcher --- +## Call Signature + ```ts -function injectBatcher( - fn, - options?, -selector?): AngularBatcher; +function injectBatcher(fn, options?): AngularBatcher; ``` -Defined in: [batcher/injectBatcher.ts:91](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L91) +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:84 + +Creates and manages an Angular Batcher in the current injection context. + +Collects items and processes a batch when its size or delay threshold is reached. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to cancel pending batches. Set `onUnmount` to replace it. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### fn -An Angular function that creates and manages a Batcher instance. +(`items`) => `void` + +The callback invoked by the core utility. + +#### options? + +`MaybeAccessor`\<[`AngularBatcherOptions`](../interfaces/AngularBatcherOptions.md)\<`TValue`\>\> -This is a lower-level function that provides direct access to the Batcher's functionality. -This allows you to integrate it with any state management solution you prefer. +Core options or a reactive factory returning them. -The Batcher collects items and processes them in batches based on configurable conditions: -- Maximum batch size -- Time-based batching (process after X milliseconds) -- Custom batch processing logic via getShouldExecute +### Returns -## State Management and Selector +[`AngularBatcher`](../interfaces/AngularBatcher.md)\<`TValue`\> -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +A ref containing stable methods and a readonly selected-state signal. -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +### Example -## Cleanup on Destroy +```ts +// In a component or service injection context. +const utility = injectBatcher( + (items: Array) => console.log(items), + () => ({ maxSize: 10, wait: 100 }), + (state) => state.isPending, +) +utility.addItem('job') +console.log(utility.state()) +``` -By default, the function cancels any pending batch when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +## Call Signature ```ts -const batcher = injectBatcher(fn, { - maxSize: 5, - onUnmount: (b) => b.flush() -}); +function injectBatcher( + fn, + options, +selector): AngularBatcher; ``` -## Type Parameters +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:95 -### TValue +Creates an Angular Batcher with a reactive selector result. + +### Type Parameters + +#### TValue `TValue` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn (`items`) => `void` -### options? +The callback invoked by the core utility. -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularBatcherOptions`](../interfaces/AngularBatcherOptions.md)\<`TValue`, `TSelected`\>\> = `{}` +#### options -### selector? +`MaybeAccessor`\<[`AngularBatcherOptions`](../interfaces/AngularBatcherOptions.md)\<`TValue`\>\> -(`state`) => `TSelected` +Core options or a reactive options factory. -## Returns +#### selector -[`AngularBatcher`](../interfaces/AngularBatcher.md)\<`TValue`, `TSelected`\> +(`state`) => `TSelected` -## Example +Selects the state exposed by the returned `state` signal. -```ts -// Default behavior - no reactive state subscriptions -const batcher = injectBatcher( - (items) => console.log('Processing batch:', items), - { maxSize: 5, wait: 2000 } -); +### Returns -// Add items -batcher.addItem('task1'); +[`AngularBatcher`](../interfaces/AngularBatcher.md)\<`TValue`, `TSelected`\> -// Access the selected state -const { items, isPending } = batcher.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectDebouncedComputed.md b/docs/framework/angular/reference/functions/injectDebouncedComputed.md new file mode 100644 index 000000000..0f6d4a129 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectDebouncedComputed.md @@ -0,0 +1,99 @@ +--- +id: injectDebouncedComputed +title: injectDebouncedComputed +--- + +## Call Signature + +```ts +function injectDebouncedComputed(signalToDebounce, options): AngularDebouncerComputed; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:44 + +Creates an Angular debounced view of a source signal. + +The initial source value is available on first read. Source changes are observed by an effect and applied after the debounce delay; newer changes restart the delay. + +The returned value is a real Angular signal with the underlying utility exposed +on `debouncer`. Options accept a static object or reactive factory and follow +[injectDebouncer](injectDebouncer.md) lifecycle and provider behavior. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### signalToDebounce + +() => `TValue` + +The source signal or accessor whose changes are observed. + +#### options + +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`value`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularDebouncerComputed`](../interfaces/AngularDebouncerComputed.md)\<`TValue`\> + +The value signal with a `debouncer` attribute. + +### Example + +```ts +// In a component or service injection context. +const source = signal('initial') +const value = injectDebouncedComputed(source, { wait: 250 }) +source.set('updated') +console.log(value()) +``` + +## Call Signature + +```ts +function injectDebouncedComputed( + signalToDebounce, + options, +selector): AngularDebouncerComputed; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:52 + +Creates the value signal with selected state on its attached utility ref. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### signalToDebounce + +() => `TValue` + +#### options + +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`value`) => `void`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached utility ref. + +### Returns + +[`AngularDebouncerComputed`](../interfaces/AngularDebouncerComputed.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectDebouncedSignal.md b/docs/framework/angular/reference/functions/injectDebouncedSignal.md index 7141b45e3..9bedfbbf1 100644 --- a/docs/framework/angular/reference/functions/injectDebouncedSignal.md +++ b/docs/framework/angular/reference/functions/injectDebouncedSignal.md @@ -3,86 +3,97 @@ id: injectDebouncedSignal title: injectDebouncedSignal --- +## Call Signature + ```ts -function injectDebouncedSignal( - value, - initialOptions, -selector?): DebouncedSignal; +function injectDebouncedSignal(initialValue, options): AngularDebouncerSignal; ``` -Defined in: [debouncer/injectDebouncedSignal.ts:64](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L64) +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:49 + +Creates an Angular debounced editable signal. -An Angular function that creates a debounced state signal, combining Angular's signal with debouncing functionality. -This function provides both the current debounced value and methods to update it. +The initial value is available synchronously. `set` and `update` debounce writes: a newer write restarts the delay and replaces the pending write. An updater runs against the committed value when the delay expires. -The state value is only updated after the specified wait time has elapsed since the last update attempt. -If another update is attempted before the wait time expires, the timer resets and starts waiting again. -This is useful for handling frequent state updates that should be throttled, like search input values -or window resize dimensions. +The returned value is a real Angular signal with the underlying utility exposed +on `debouncer`. Options accept a static object or reactive factory and follow +[injectDebouncer](injectDebouncer.md) lifecycle and provider behavior. -The function returns a callable object: -- `debounced()`: Get the current debounced value -- `debounced.set(...)`: Set or update the debounced value (debounced via maybeExecute) -- `debounced.debouncer`: The debouncer instance with additional control methods and state signals +### Type Parameters -## State Management and Selector +#### TValue -The function uses TanStack Store for reactive state management via the underlying debouncer instance. -The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. +`TValue` -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. Only when you provide a selector will -the reactive system track the selected state values. +### Parameters -Available debouncer state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `status`: Current execution status ('disabled' | 'idle' | 'pending') +#### initialValue -## Type Parameters +`MaybeAccessor`\<`TValue`\> -### TValue +The initial committed value. -`TValue` +#### options + +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`callback`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularDebouncerSignal`](../interfaces/AngularDebouncerSignal.md)\<`TValue`\> + +The value signal with `set`, `update`, and a `debouncer` attribute. + +### Example + +```ts +// In a component or service injection context. +const value = injectDebouncedSignal(0, { wait: 250 }) +value.set(10) +value.update(previous => previous + 1) +console.log(value()) +``` + +## Call Signature + +```ts +function injectDebouncedSignal( + initialValue, + options, +selector): AngularDebouncerSignal; +``` -### TSelected +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:59 -`TSelected` = \{ -\} +Creates the value signal with selected state on its attached utility ref. -## Parameters +### Type Parameters -### value +#### TValue `TValue` -### initialOptions +#### TSelected -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`DebouncerOptions`\<`Setter`\<`TValue`\>\>\> +`TSelected` -### selector? +### Parameters -(`state`) => `TSelected` +#### initialValue -## Returns +`MaybeAccessor`\<`TValue`\> -[`DebouncedSignal`](../interfaces/DebouncedSignal.md)\<`TValue`, `TSelected`\> +#### options -## Example +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`callback`) => `void`\>\> -```ts -const debouncedQuery = injectDebouncedSignal('', { wait: 500 }) +#### selector -// Get value -console.log(debouncedQuery()) +(`state`) => `TSelected` -// Set/update value (debounced) -debouncedQuery.set('hello') +Selects reactive state exposed on the attached utility ref. -// Access debouncer -console.log(debouncedQuery.debouncer.state().isPending) -``` +### Returns + +[`AngularDebouncerSignal`](../interfaces/AngularDebouncerSignal.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectDebouncedValue.md b/docs/framework/angular/reference/functions/injectDebouncedValue.md deleted file mode 100644 index f7e18ace1..000000000 --- a/docs/framework/angular/reference/functions/injectDebouncedValue.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -id: injectDebouncedValue -title: injectDebouncedValue ---- - -## Call Signature - -```ts -function injectDebouncedValue( - value, - initialOptions, -selector?): DebouncedSignal; -``` - -Defined in: [debouncer/injectDebouncedValue.ts:78](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L78) - -An Angular function that creates a debounced value that updates only after a specified delay. -Unlike injectDebouncedSignal, this function automatically tracks changes to the input signal -and updates the debounced value accordingly. - -The debounced value will only update after the specified wait time has elapsed since -the last change to the input value. If the input value changes again before the wait -time expires, the timer resets and starts waiting again. - -This is useful for deriving debounced values from signals that change frequently, -like search queries or form inputs, where you want to limit how often downstream effects -or calculations occur. - -The function returns a tuple containing: -- A Signal that provides the current debounced value -- The debouncer instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying debouncer instance. -The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. Only when you provide a selector will -the reactive system track the selected state values. - -Available debouncer state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialOptions - -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`DebouncerOptions`\<`Setter`\<`TValue`\>\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`DebouncedSignal`](../interfaces/DebouncedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const searchQuery = signal('') -const debounced = injectDebouncedValue(searchQuery, { - wait: 500, // Wait 500ms after last change -}) - -// Opt-in to reactive updates when pending state changes (optimized for loading indicators) -const debouncedWithPending = injectDebouncedValue( - searchQuery, - { wait: 500 }, - (state) => ({ isPending: state.isPending }), -) - -// Debounced value will update 500ms after searchQuery stops changing -effect(() => { - fetchSearchResults(debounced()) -}) - -// Access selected debouncer state via signals (only if you provided a selector) -effect(() => { - console.log('Is pending:', debouncedWithPending.state().isPending) -}) - -// Control the debouncer -debounced.cancel() // Cancel any pending updates -``` - -## Call Signature - -```ts -function injectDebouncedValue( - value, - initialValue, - initialOptions, -selector?): DebouncedSignal; -``` - -Defined in: [debouncer/injectDebouncedValue.ts:83](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L83) - -An Angular function that creates a debounced value that updates only after a specified delay. -Unlike injectDebouncedSignal, this function automatically tracks changes to the input signal -and updates the debounced value accordingly. - -The debounced value will only update after the specified wait time has elapsed since -the last change to the input value. If the input value changes again before the wait -time expires, the timer resets and starts waiting again. - -This is useful for deriving debounced values from signals that change frequently, -like search queries or form inputs, where you want to limit how often downstream effects -or calculations occur. - -The function returns a tuple containing: -- A Signal that provides the current debounced value -- The debouncer instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying debouncer instance. -The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. Only when you provide a selector will -the reactive system track the selected state values. - -Available debouncer state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -`DebouncerOptions`\<`Setter`\<`TValue`\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`DebouncedSignal`](../interfaces/DebouncedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const searchQuery = signal('') -const debounced = injectDebouncedValue(searchQuery, { - wait: 500, // Wait 500ms after last change -}) - -// Opt-in to reactive updates when pending state changes (optimized for loading indicators) -const debouncedWithPending = injectDebouncedValue( - searchQuery, - { wait: 500 }, - (state) => ({ isPending: state.isPending }), -) - -// Debounced value will update 500ms after searchQuery stops changing -effect(() => { - fetchSearchResults(debounced()) -}) - -// Access selected debouncer state via signals (only if you provided a selector) -effect(() => { - console.log('Is pending:', debouncedWithPending.state().isPending) -}) - -// Control the debouncer -debounced.cancel() // Cancel any pending updates -``` - -## Call Signature - -```ts -function injectDebouncedValue( - value, - initialValue, - initialOptions, -selector): DebouncedSignal; -``` - -Defined in: [debouncer/injectDebouncedValue.ts:89](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L89) - -An Angular function that creates a debounced value that updates only after a specified delay. -Unlike injectDebouncedSignal, this function automatically tracks changes to the input signal -and updates the debounced value accordingly. - -The debounced value will only update after the specified wait time has elapsed since -the last change to the input value. If the input value changes again before the wait -time expires, the timer resets and starts waiting again. - -This is useful for deriving debounced values from signals that change frequently, -like search queries or form inputs, where you want to limit how often downstream effects -or calculations occur. - -The function returns a tuple containing: -- A Signal that provides the current debounced value -- The debouncer instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying debouncer instance. -The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. Only when you provide a selector will -the reactive system track the selected state values. - -Available debouncer state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -() => `DebouncerOptions`\<`Setter`\<`TValue`\>\> - -#### selector - -((`state`) => `TSelected`) \| `undefined` - -### Returns - -[`DebouncedSignal`](../interfaces/DebouncedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const searchQuery = signal('') -const debounced = injectDebouncedValue(searchQuery, { - wait: 500, // Wait 500ms after last change -}) - -// Opt-in to reactive updates when pending state changes (optimized for loading indicators) -const debouncedWithPending = injectDebouncedValue( - searchQuery, - { wait: 500 }, - (state) => ({ isPending: state.isPending }), -) - -// Debounced value will update 500ms after searchQuery stops changing -effect(() => { - fetchSearchResults(debounced()) -}) - -// Access selected debouncer state via signals (only if you provided a selector) -effect(() => { - console.log('Is pending:', debouncedWithPending.state().isPending) -}) - -// Control the debouncer -debounced.cancel() // Cancel any pending updates -``` diff --git a/docs/framework/angular/reference/functions/injectDebouncer.md b/docs/framework/angular/reference/functions/injectDebouncer.md index 30262f048..6a6d0b9be 100644 --- a/docs/framework/angular/reference/functions/injectDebouncer.md +++ b/docs/framework/angular/reference/functions/injectDebouncer.md @@ -3,108 +3,115 @@ id: injectDebouncer title: injectDebouncer --- +## Call Signature + ```ts -function injectDebouncer( - fn, - options, -selector?): AngularDebouncer; +function injectDebouncer(fn, options): AngularDebouncer; ``` -Defined in: [debouncer/injectDebouncer.ts:115](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L115) +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:88 + +Creates and manages an Angular Debouncer in the current injection context. + +Waits until calls stop for the configured delay, then runs the latest callback. Each new call restarts the delay. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to cancel pending execution. Set `onUnmount` to replace it. + +### Type Parameters + +#### TFn + +`TFn` *extends* `AnyFunction` + +### Parameters -An Angular function that creates and manages a Debouncer instance. +#### fn -This is a lower-level function that provides direct access to the Debouncer's functionality. -This allows you to integrate it with any state management solution you prefer. +`TFn` + +The callback invoked by the core utility. -This function provides debouncing functionality to limit how often a function can be called, -waiting for a specified delay before executing the latest call. This is useful for handling -frequent events like window resizing, scroll events, or real-time search inputs. +#### options -The debouncer will only execute the function after the specified wait time has elapsed -since the last call. If the function is called again before the wait time expires, the -timer resets and starts waiting again. +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<`TFn`\>\> -## State Management and Selector +Core options or a reactive factory returning them. -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +### Returns -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +[`AngularDebouncer`](../interfaces/AngularDebouncer.md)\<`TFn`\> -Available state properties: -- `canLeadingExecute`: Whether the debouncer can execute on the leading edge -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the debouncer is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `status`: Current execution status ('disabled' | 'idle' | 'pending') +A ref containing stable methods and a readonly selected-state signal. -## Cleanup on Destroy +### Example + +```ts +// In a component or service injection context. +const utility = injectDebouncer( + (query: string) => console.log(query), + () => ({ wait: 250 }), + (state) => state.isPending, +) +utility.maybeExecute('search') +console.log(utility.state()) +``` -By default, the function cancels any pending execution when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +## Call Signature ```ts -const debouncer = injectDebouncer(fn, { - wait: 500, - onUnmount: (d) => d.flush() -}); +function injectDebouncer( + fn, + options, +selector): AngularDebouncer; ``` -## Type Parameters +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:99 -### TFn +Creates an Angular Debouncer with a reactive selector result. + +### Type Parameters + +#### TFn `TFn` *extends* `AnyFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<`TFn`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<`TFn`, `TSelected`\>\> +Core options or a reactive options factory. -### selector? +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularDebouncer`](../interfaces/AngularDebouncer.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularDebouncer`](../interfaces/AngularDebouncer.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const debouncer = injectDebouncer( - (query: string) => fetchSearchResults(query), - { wait: 500 } -); - -// Opt-in to track isPending changes (optimized for loading states) -const debouncer = injectDebouncer( - (query: string) => fetchSearchResults(query), - { wait: 500 }, - (state) => ({ isPending: state.isPending }) -); - -// In an event handler -const handleChange = (e: Event) => { - const target = e.target as HTMLInputElement - debouncer.maybeExecute(target.value); -}; - -// Access the selected state (will be empty object {} unless selector provided) -const { isPending } = debouncer.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectPacerOptions.md b/docs/framework/angular/reference/functions/injectPacerOptions.md new file mode 100644 index 000000000..1861103a8 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectPacerOptions.md @@ -0,0 +1,19 @@ +--- +id: injectPacerOptions +title: injectPacerOptions +--- + +```ts +function injectPacerOptions(): PacerProviderOptions; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:79 + +Reads the nearest Pacer options provider in the current injection context. +Returns an empty object when no provider is configured. + +## Returns + +[`PacerProviderOptions`](../interfaces/PacerProviderOptions.md) + +The scoped default options. diff --git a/docs/framework/angular/reference/functions/injectQueuedComputed.md b/docs/framework/angular/reference/functions/injectQueuedComputed.md new file mode 100644 index 000000000..10291218d --- /dev/null +++ b/docs/framework/angular/reference/functions/injectQueuedComputed.md @@ -0,0 +1,97 @@ +--- +id: injectQueuedComputed +title: injectQueuedComputed +--- + +## Call Signature + +```ts +function injectQueuedComputed(source, options?): AngularQueuerComputed; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:37 + +Creates an Angular queued view of a source accessor. + +The first read exposes the source value. An effect enqueues the initial value and each later value it observes; processing updates the returned value in queue order. Multiple source changes before an effect runs can collapse to the latest value. + +Options accept an object or factory and follow [injectQueuer](injectQueuer.md) behavior. +Pass a selector to expose reactive queue state on `queuer.state()`. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +() => `TValue` + +The source signal or accessor to observe. + +#### options? + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> + +Core queue options or a reactive options factory. + +### Returns + +[`AngularQueuerComputed`](../interfaces/AngularQueuerComputed.md)\<`TValue`\> + +The processed-value signal with its queuer attached. + +### Example + +```ts +// In a component or service injection context. +const source = signal('initial') +const value = injectQueuedComputed(source, { wait: 100 }) +source.set('next') +``` + +## Call Signature + +```ts +function injectQueuedComputed( + source, + options, +selector): AngularQueuerComputed; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:45 + +Creates the queued value signal with selected state on its attached queuer. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### source + +() => `TValue` + +#### options + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached queuer. + +### Returns + +[`AngularQueuerComputed`](../interfaces/AngularQueuerComputed.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectQueuedSignal.md b/docs/framework/angular/reference/functions/injectQueuedSignal.md index 72ded9034..d936fa19b 100644 --- a/docs/framework/angular/reference/functions/injectQueuedSignal.md +++ b/docs/framework/angular/reference/functions/injectQueuedSignal.md @@ -3,70 +3,95 @@ id: injectQueuedSignal title: injectQueuedSignal --- +## Call Signature + ```ts -function injectQueuedSignal( - fn, - options?, -selector?): QueuedSignal; +function injectQueuedSignal(initialValue, options?): AngularQueuerSignal; ``` -Defined in: [queuer/injectQueuedSignal.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L44) +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:42 -An Angular function that creates a queuer with managed state, combining Angular's signals with queuing functionality. -This function provides both the current queue state and queue control methods. +Creates an Angular queued editable signal. -The queue state is automatically updated whenever items are added, removed, or reordered in the queue. -All queue operations are reflected in the state array returned by the function. +Every set and update is queued and processed in order. Updaters run against the committed value when their queue item executes. -The function returns a callable object: -- `queued()`: Get the current queue items as an array -- `queued.addItem(...)`: Add an item to the queue -- `queued.queue`: The queuer instance with additional control methods +Options accept an object or factory and follow [injectQueuer](injectQueuer.md) behavior. +Pass a selector to expose reactive queue state on `queuer.state()`. -## Type Parameters +### Type Parameters -### TValue +#### TValue `TValue` -### TSelected +### Parameters -`TSelected` *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> +#### initialValue -## Parameters +`MaybeAccessor`\<`TValue`\> -### fn +The initial committed value. -(`item`) => `void` +#### options? -### options? +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<() => `void`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`QueuerOptions`\<`TValue`\>\> = `{}` +Core queue options or a reactive options factory. -### selector? +### Returns -(`state`) => `TSelected` +[`AngularQueuerSignal`](../interfaces/AngularQueuerSignal.md)\<`TValue`\> -## Returns +The processed-value signal with its queuer attached. -[`QueuedSignal`](../interfaces/QueuedSignal.md)\<`TValue`, `TSelected`\> +### Example -## Example +```ts +// In a component or service injection context. +const value = injectQueuedSignal(0, { wait: 100 }) +value.set(1) +value.update(previous => previous + 1) +``` + +## Call Signature ```ts -// Default behavior - track items -const queued = injectQueuedSignal( - (item) => console.log('Processing:', item), - { started: true, wait: 1000 } -); +function injectQueuedSignal( + initialValue, + options, +selector): AngularQueuerSignal; +``` -// Add items -queued.addItem('task1'); +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:50 -// Access items -console.log(queued()); // ['task1'] +Creates the queued value signal with selected state on its attached queuer. -// Control the queue -queued.queuer.start(); -queued.queuer.stop(); -``` +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### initialValue + +`MaybeAccessor`\<`TValue`\> + +#### options + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<() => `void`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached queuer. + +### Returns + +[`AngularQueuerSignal`](../interfaces/AngularQueuerSignal.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectQueuedValue.md b/docs/framework/angular/reference/functions/injectQueuedValue.md deleted file mode 100644 index 49be825d6..000000000 --- a/docs/framework/angular/reference/functions/injectQueuedValue.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -id: injectQueuedValue -title: injectQueuedValue ---- - -## Call Signature - -```ts -function injectQueuedValue( - value, - options?, -selector?): QueuedValueSignal; -``` - -Defined in: [queuer/injectQueuedValue.ts:48](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L48) - -An Angular function that creates a queued value that processes state changes in order with an optional delay. -This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. - -The queued value will process changes in the order they are received, with optional delays between -processing each change. This is useful for handling state updates that need to be processed -in a specific order, like animations or sequential UI updates. - -The returned signal provides the most recently processed value. Before the queue processes -an item, it provides the explicit initial value or the source's initial value. -Source reads are deferred until the signal is read or Angular runs its effect, so required -inputs can be bound before their values are read. - -Use `queued.addItem(...)` to add a value and `queued.queuer` to control the queue. -Pending items are available through `queued.queuer.state().items` with the default selector. - -Pass an options object as the third argument to supply an object or function as the initial -value. When those options are undefined or a factory, pass a fourth selector argument -(undefined is allowed) to distinguish the initial-value form from options plus a selector. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### options? - -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`, `TSelected`\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`QueuedValueSignal`](../type-aliases/QueuedValueSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -const initialValue = signal('initial') -const queued = injectQueuedValue(initialValue, { - wait: 500, - started: true, -}) - -// Add changes to the queue -queued.addItem('new value') -``` - -## Call Signature - -```ts -function injectQueuedValue( - value, - initialValue, - options?, -selector?): QueuedValueSignal; -``` - -Defined in: [queuer/injectQueuedValue.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L59) - -An Angular function that creates a queued value that processes state changes in order with an optional delay. -This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. - -The queued value will process changes in the order they are received, with optional delays between -processing each change. This is useful for handling state updates that need to be processed -in a specific order, like animations or sequential UI updates. - -The returned signal provides the most recently processed value. Before the queue processes -an item, it provides the explicit initial value or the source's initial value. -Source reads are deferred until the signal is read or Angular runs its effect, so required -inputs can be bound before their values are read. - -Use `queued.addItem(...)` to add a value and `queued.queuer` to control the queue. -Pending items are available through `queued.queuer.state().items` with the default selector. - -Pass an options object as the third argument to supply an object or function as the initial -value. When those options are undefined or a factory, pass a fourth selector argument -(undefined is allowed) to distinguish the initial-value form from options plus a selector. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`Extract`\<`TValue`, `PrimitiveInitialValue`\> - -#### options? - -[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`, `TSelected`\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`QueuedValueSignal`](../type-aliases/QueuedValueSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -const initialValue = signal('initial') -const queued = injectQueuedValue(initialValue, { - wait: 500, - started: true, -}) - -// Add changes to the queue -queued.addItem('new value') -``` - -## Call Signature - -```ts -function injectQueuedValue( - value, - initialValue, - options, -selector?): QueuedValueSignal; -``` - -Defined in: [queuer/injectQueuedValue.ts:71](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L71) - -An Angular function that creates a queued value that processes state changes in order with an optional delay. -This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. - -The queued value will process changes in the order they are received, with optional delays between -processing each change. This is useful for handling state updates that need to be processed -in a specific order, like animations or sequential UI updates. - -The returned signal provides the most recently processed value. Before the queue processes -an item, it provides the explicit initial value or the source's initial value. -Source reads are deferred until the signal is read or Angular runs its effect, so required -inputs can be bound before their values are read. - -Use `queued.addItem(...)` to add a value and `queued.queuer` to control the queue. -Pending items are available through `queued.queuer.state().items` with the default selector. - -Pass an options object as the third argument to supply an object or function as the initial -value. When those options are undefined or a factory, pass a fourth selector argument -(undefined is allowed) to distinguish the initial-value form from options plus a selector. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### options - -[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`, `TSelected`\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`QueuedValueSignal`](../type-aliases/QueuedValueSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -const initialValue = signal('initial') -const queued = injectQueuedValue(initialValue, { - wait: 500, - started: true, -}) - -// Add changes to the queue -queued.addItem('new value') -``` - -## Call Signature - -```ts -function injectQueuedValue( - value, - initialValue, - options, -selector): QueuedValueSignal; -``` - -Defined in: [queuer/injectQueuedValue.ts:83](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L83) - -An Angular function that creates a queued value that processes state changes in order with an optional delay. -This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. - -The queued value will process changes in the order they are received, with optional delays between -processing each change. This is useful for handling state updates that need to be processed -in a specific order, like animations or sequential UI updates. - -The returned signal provides the most recently processed value. Before the queue processes -an item, it provides the explicit initial value or the source's initial value. -Source reads are deferred until the signal is read or Angular runs its effect, so required -inputs can be bound before their values are read. - -Use `queued.addItem(...)` to add a value and `queued.queuer` to control the queue. -Pending items are available through `queued.queuer.state().items` with the default selector. - -Pass an options object as the third argument to supply an object or function as the initial -value. When those options are undefined or a factory, pass a fourth selector argument -(undefined is allowed) to distinguish the initial-value form from options plus a selector. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### options - - \| [`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`, `TSelected`\>\> - \| `undefined` - -#### selector - -((`state`) => `TSelected`) \| `undefined` - -### Returns - -[`QueuedValueSignal`](../type-aliases/QueuedValueSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -const initialValue = signal('initial') -const queued = injectQueuedValue(initialValue, { - wait: 500, - started: true, -}) - -// Add changes to the queue -queued.addItem('new value') -``` diff --git a/docs/framework/angular/reference/functions/injectQueuer.md b/docs/framework/angular/reference/functions/injectQueuer.md index 977cbaa98..1ecbc06f5 100644 --- a/docs/framework/angular/reference/functions/injectQueuer.md +++ b/docs/framework/angular/reference/functions/injectQueuer.md @@ -3,93 +3,115 @@ id: injectQueuer title: injectQueuer --- +## Call Signature + ```ts -function injectQueuer( - fn, - options?, -selector?): AngularQueuer; +function injectQueuer(fn, options?): AngularQueuer; ``` -Defined in: [queuer/injectQueuer.ts:96](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L96) +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:89 -An Angular function that creates and manages a Queuer instance. +Creates and manages an Angular Queuer in the current injection context. -This is a lower-level function that provides direct access to the Queuer's functionality. -This allows you to integrate it with any state management solution you prefer. +Processes queued items in order with configurable pacing, capacity, and priority. -The Queuer processes items synchronously in order, with optional delays between processing each item. -The queuer includes an internal tick mechanism that can be started and stopped, making it useful as a scheduler. +## Options and state -## State Management and Selector +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +## Cleanup -## Type Parameters +The default cleanup is to stop automatic processing. Set `onUnmount` to replace it. -### TValue - -`TValue` +### Type Parameters -### TSelected +#### TValue -`TSelected` = \{ -\} +`TValue` -## Parameters +### Parameters -### fn +#### fn (`item`) => `void` -### options? +The callback invoked by the core utility. -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`, `TSelected`\>\> = `{}` +#### options? -### selector? +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> -(`state`) => `TSelected` +Core options or a reactive factory returning them. -## Returns +### Returns -[`AngularQueuer`](../interfaces/AngularQueuer.md)\<`TValue`, `TSelected`\> +[`AngularQueuer`](../interfaces/AngularQueuer.md)\<`TValue`\> + +A ref containing stable methods and a readonly selected-state signal. -## Example +### Example ```ts -// Default behavior - no reactive state subscriptions -const queue = injectQueuer( - (item) => console.log('Processing:', item), - { started: true, wait: 1000 } -); - -// Opt-in to track queue contents changes -const queue = injectQueuer( - (item) => console.log('Processing:', item), - { started: true, wait: 1000 }, - (state) => ({ items: state.items, size: state.size }) -); - -// Add items -queue.addItem('task1'); - -// Access the selected state -const { items, isRunning } = queue.state(); +// In a component or service injection context. +const utility = injectQueuer( + (item: string) => console.log(item), + () => ({ wait: 100 }), + (state) => state.items, +) +utility.addItem('job') +console.log(utility.state()) ``` -## Cleanup on Destroy - -By default, the function stops the queuer when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending items instead: +## Call Signature ```ts -const queue = injectQueuer(fn, { - started: true, - onUnmount: (q) => q.flush() -}); +function injectQueuer( + fn, + options, +selector): AngularQueuer; ``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:100 + +Creates an Angular Queuer with a reactive selector result. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### fn + +(`item`) => `void` + +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> + +Core options or a reactive options factory. + +#### selector + +(`state`) => `TSelected` + +Selects the state exposed by the returned `state` signal. + +### Returns + +[`AngularQueuer`](../interfaces/AngularQueuer.md)\<`TValue`, `TSelected`\> + +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectQueuerItems.md b/docs/framework/angular/reference/functions/injectQueuerItems.md new file mode 100644 index 000000000..49af5f2df --- /dev/null +++ b/docs/framework/angular/reference/functions/injectQueuerItems.md @@ -0,0 +1,103 @@ +--- +id: injectQueuerItems +title: injectQueuerItems +--- + +## Call Signature + +```ts +function injectQueuerItems(fn, options?): AngularQueuerItems; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:48 + +Creates a readonly Angular signal of pending queue items. + +Items appear when added and disappear when removed for processing. This signal +contains waiting items, not processing results. Use the attached `queuer` methods +to add items, start or stop processing, or inspect the queue. + +Options accept a static object or reactive factory and follow the underlying +queuer's lifecycle and provider behavior. The returned function is an Angular signal. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### fn + +(`value`) => `void` + +The callback that processes each queued item. + +#### options? + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> + +Core queue options or a reactive options factory. + +### Returns + +[`AngularQueuerItems`](../interfaces/AngularQueuerItems.md)\<`TValue`\> + +A readonly items signal with `queuer` and `addItem` attributes. + +### Example + +```ts +// In a component or service injection context. +const items = injectQueuerItems( + (item: string) => console.log(item), + { started: false }, +) +items.addItem('job') +console.log(items()) // ['job'] +items.queuer.start() +``` + +## Call Signature + +```ts +function injectQueuerItems( + fn, + options, +selector): AngularQueuerItems; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:56 + +Creates the items signal with selected state on its attached queuer. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### fn + +(`value`) => `void` + +#### options + +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`TValue`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached queuer. + +### Returns + +[`AngularQueuerItems`](../interfaces/AngularQueuerItems.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectRateLimitedComputed.md b/docs/framework/angular/reference/functions/injectRateLimitedComputed.md new file mode 100644 index 000000000..9e4e699dc --- /dev/null +++ b/docs/framework/angular/reference/functions/injectRateLimitedComputed.md @@ -0,0 +1,99 @@ +--- +id: injectRateLimitedComputed +title: injectRateLimitedComputed +--- + +## Call Signature + +```ts +function injectRateLimitedComputed(source, options): AngularRateLimiterComputed; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:44 + +Creates an Angular ratelimited view of a source signal. + +The initial source value is available on first read. An effect passes source values to the rate limiter, including the initial value. Excess updates are discarded; the end of a window does not replay rejected values. + +The returned value is a real Angular signal with the underlying utility exposed +on `rateLimiter`. Options accept a static object or reactive factory and follow +[injectRateLimiter](injectRateLimiter.md) lifecycle and provider behavior. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +() => `TValue` + +The source signal or accessor whose changes are observed. + +#### options + +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`value`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularRateLimiterComputed`](../interfaces/AngularRateLimiterComputed.md)\<`TValue`\> + +The value signal with a `rateLimiter` attribute. + +### Example + +```ts +// In a component or service injection context. +const source = signal('initial') +const value = injectRateLimitedComputed(source, { limit: 5, window: 1000 }) +source.set('updated') +console.log(value()) +``` + +## Call Signature + +```ts +function injectRateLimitedComputed( + source, + options, +selector): AngularRateLimiterComputed; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:52 + +Creates the value signal with selected state on its attached utility ref. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### source + +() => `TValue` + +#### options + +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`value`) => `void`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached utility ref. + +### Returns + +[`AngularRateLimiterComputed`](../interfaces/AngularRateLimiterComputed.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md index 96646df00..920070d4d 100644 --- a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md +++ b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md @@ -3,80 +3,97 @@ id: injectRateLimitedSignal title: injectRateLimitedSignal --- +## Call Signature + ```ts -function injectRateLimitedSignal( - value, - initialOptions, -selector?): RateLimitedSignal; +function injectRateLimitedSignal(initialValue, options): AngularRateLimiterSignal; ``` -Defined in: [rate-limiter/injectRateLimitedSignal.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L58) +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:49 -An Angular function that creates a rate-limited state signal, combining Angular's signal with rate limiting functionality. -This function provides both the current rate-limited value and methods to update it. +Creates an Angular ratelimited editable signal. -Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks -subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out -or intelligently collapse updates. +The initial value is available synchronously. `set` and `update` share the execution limit. Accepted writes apply immediately; rejected writes and their updater callbacks are discarded, not replayed later. -The function returns a callable object: -- `rateLimited()`: Get the current rate-limited value -- `rateLimited.set(...)`: Set or update the rate-limited value (rate-limited via maybeExecute) -- `rateLimited.rateLimiter`: The rate limiter instance with additional control methods and state signals +The returned value is a real Angular signal with the underlying utility exposed +on `rateLimiter`. Options accept a static object or reactive factory and follow +[injectRateLimiter](injectRateLimiter.md) lifecycle and provider behavior. -## State Management and Selector +### Type Parameters -The function uses TanStack Store for reactive state management via the underlying rate limiter instance. -The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. +#### TValue -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +`TValue` -## Type Parameters +### Parameters -### TValue +#### initialValue -`TValue` +`MaybeAccessor`\<`TValue`\> + +The initial committed value. + +#### options + +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`callback`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularRateLimiterSignal`](../interfaces/AngularRateLimiterSignal.md)\<`TValue`\> + +The value signal with `set`, `update`, and a `rateLimiter` attribute. + +### Example + +```ts +// In a component or service injection context. +const value = injectRateLimitedSignal(0, { limit: 5, window: 1000 }) +value.set(10) +value.update(previous => previous + 1) +console.log(value()) +``` -### TSelected +## Call Signature -`TSelected` = \{ -\} +```ts +function injectRateLimitedSignal( + initialValue, + options, +selector): AngularRateLimiterSignal; +``` -## Parameters +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:59 -### value +Creates the value signal with selected state on its attached utility ref. + +### Type Parameters + +#### TValue `TValue` -### initialOptions +#### TSelected -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`RateLimiterOptions`\<`Setter`\<`TValue`\>\>\> +`TSelected` -### selector? +### Parameters -(`state`) => `TSelected` +#### initialValue -## Returns +`MaybeAccessor`\<`TValue`\> -[`RateLimitedSignal`](../interfaces/RateLimitedSignal.md)\<`TValue`, `TSelected`\> +#### options -## Example +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`callback`) => `void`\>\> -```ts -// Default behavior - no reactive state subscriptions -const rateLimited = injectRateLimitedSignal(0, { - limit: 5, - window: 60000, - windowType: 'sliding' -}); - -// Opt-in to reactive updates when limit state changes -const rateLimited = injectRateLimitedSignal( - 0, - { limit: 5, window: 60000 }, - (state) => ({ rejectionCount: state.rejectionCount }) -); -``` +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached utility ref. + +### Returns + +[`AngularRateLimiterSignal`](../interfaces/AngularRateLimiterSignal.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectRateLimitedValue.md b/docs/framework/angular/reference/functions/injectRateLimitedValue.md deleted file mode 100644 index f0ae7dbbf..000000000 --- a/docs/framework/angular/reference/functions/injectRateLimitedValue.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -id: injectRateLimitedValue -title: injectRateLimitedValue ---- - -## Call Signature - -```ts -function injectRateLimitedValue( - value, - initialOptions, -selector?): RateLimitedSignal; -``` - -Defined in: [rate-limiter/injectRateLimitedValue.ts:51](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L51) - -An Angular function that creates a rate-limited value that updates at most a certain number of times within a time window. -Unlike injectRateLimitedSignal, this function automatically tracks changes to the input signal -and updates the rate-limited value accordingly. - -The rate-limited value will update according to the configured rate limit, blocking updates -once the limit is reached until the window resets. - -The function returns a rate-limited signal object containing: -- A Signal that provides the current rate-limited value -- The rate limiter instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying rate limiter instance. -The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialOptions - -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`RateLimiterOptions`\<`Setter`\<`TValue`\>\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`RateLimitedSignal`](../interfaces/RateLimitedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const value = signal(0) -const rateLimited = injectRateLimitedValue(value, { - limit: 5, - window: 60000, - windowType: 'sliding', -}) - -// rateLimited() will update at most 5 times per 60 seconds -effect(() => { - updateUI(rateLimited()) -}) -``` - -## Call Signature - -```ts -function injectRateLimitedValue( - value, - initialValue, - initialOptions, -selector?): RateLimitedSignal; -``` - -Defined in: [rate-limiter/injectRateLimitedValue.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L56) - -An Angular function that creates a rate-limited value that updates at most a certain number of times within a time window. -Unlike injectRateLimitedSignal, this function automatically tracks changes to the input signal -and updates the rate-limited value accordingly. - -The rate-limited value will update according to the configured rate limit, blocking updates -once the limit is reached until the window resets. - -The function returns a rate-limited signal object containing: -- A Signal that provides the current rate-limited value -- The rate limiter instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying rate limiter instance. -The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -`RateLimiterOptions`\<`Setter`\<`TValue`\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`RateLimitedSignal`](../interfaces/RateLimitedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const value = signal(0) -const rateLimited = injectRateLimitedValue(value, { - limit: 5, - window: 60000, - windowType: 'sliding', -}) - -// rateLimited() will update at most 5 times per 60 seconds -effect(() => { - updateUI(rateLimited()) -}) -``` - -## Call Signature - -```ts -function injectRateLimitedValue( - value, - initialValue, - initialOptions, -selector): RateLimitedSignal; -``` - -Defined in: [rate-limiter/injectRateLimitedValue.ts:62](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L62) - -An Angular function that creates a rate-limited value that updates at most a certain number of times within a time window. -Unlike injectRateLimitedSignal, this function automatically tracks changes to the input signal -and updates the rate-limited value accordingly. - -The rate-limited value will update according to the configured rate limit, blocking updates -once the limit is reached until the window resets. - -The function returns a rate-limited signal object containing: -- A Signal that provides the current rate-limited value -- The rate limiter instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying rate limiter instance. -The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -() => `RateLimiterOptions`\<`Setter`\<`TValue`\>\> - -#### selector - -((`state`) => `TSelected`) \| `undefined` - -### Returns - -[`RateLimitedSignal`](../interfaces/RateLimitedSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const value = signal(0) -const rateLimited = injectRateLimitedValue(value, { - limit: 5, - window: 60000, - windowType: 'sliding', -}) - -// rateLimited() will update at most 5 times per 60 seconds -effect(() => { - updateUI(rateLimited()) -}) -``` diff --git a/docs/framework/angular/reference/functions/injectRateLimiter.md b/docs/framework/angular/reference/functions/injectRateLimiter.md index b2f908b88..4f1e6d0e4 100644 --- a/docs/framework/angular/reference/functions/injectRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectRateLimiter.md @@ -3,114 +3,115 @@ id: injectRateLimiter title: injectRateLimiter --- +## Call Signature + ```ts -function injectRateLimiter( - fn, - options, -selector?): AngularRateLimiter; +function injectRateLimiter(fn, options): AngularRateLimiter; ``` -Defined in: [rate-limiter/injectRateLimiter.ts:121](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L121) +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:87 + +Creates and manages an Angular RateLimiter in the current injection context. + +Allows calls up to the configured limit within a fixed or sliding window. Calls beyond the limit are rejected rather than queued. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to reset the limiter. Set `onUnmount` to replace it. + +### Type Parameters + +#### TFn + +`TFn` *extends* `AnyFunction` + +### Parameters + +#### fn -An Angular function that creates and manages a RateLimiter instance. +`TFn` -This is a lower-level function that provides direct access to the RateLimiter's functionality. -This allows you to integrate it with any state management solution you prefer. +The callback invoked by the core utility. -Rate limiting is a simple "hard limit" approach that allows executions until a maximum count is reached within -a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing, -it does not attempt to space out or collapse executions intelligently. +#### options -The rate limiter supports two types of windows: -- 'fixed': A strict window that resets after the window period. All executions within the window count - towards the limit, and the window resets completely after the period. -- 'sliding': A rolling window that allows executions as old ones expire. This provides a more - consistent rate of execution over time. +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<`TFn`\>\> -For smoother execution patterns: -- Use throttling when you want consistent spacing between executions (e.g. UI updates) -- Use debouncing when you want to collapse rapid-fire events (e.g. search input) -- Use rate limiting only when you need to enforce hard limits (e.g. API rate limits) +Core options or a reactive factory returning them. -## State Management and Selector +### Returns -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +[`AngularRateLimiter`](../interfaces/AngularRateLimiter.md)\<`TFn`\> -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +A ref containing stable methods and a readonly selected-state signal. -Available state properties: -- `executionCount`: Number of function executions that have been completed -- `executionTimes`: Array of timestamps when executions occurred for rate limiting calculations -- `rejectionCount`: Number of function executions that have been rejected due to rate limiting +### Example -## Cleanup on Destroy +```ts +// In a component or service injection context. +const utility = injectRateLimiter( + (query: string) => console.log(query), + () => ({ limit: 5, window: 1000 }), + (state) => state.executionCount, +) +utility.maybeExecute('search') +console.log(utility.state()) +``` -Use the `onUnmount` option to run a callback when the component is destroyed. +## Call Signature + +```ts +function injectRateLimiter( + fn, + options, +selector): AngularRateLimiter; +``` -## Type Parameters +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:98 -### TFn +Creates an Angular RateLimiter with a reactive selector result. + +### Type Parameters + +#### TFn `TFn` *extends* `AnyFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<`TFn`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<`TFn`, `TSelected`\>\> +Core options or a reactive options factory. -### selector? +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularRateLimiter`](../interfaces/AngularRateLimiter.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularRateLimiter`](../interfaces/AngularRateLimiter.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const rateLimiter = injectRateLimiter(apiCall, { - limit: 5, - window: 60000, - windowType: 'sliding', -}); - -// Opt-in to track execution count changes -const rateLimiter = injectRateLimiter( - apiCall, - { - limit: 5, - window: 60000, - windowType: 'sliding', - }, - (state) => ({ executionCount: state.executionCount }) -); - -// Monitor rate limit status -const handleClick = () => { - const remaining = rateLimiter.getRemainingInWindow(); - if (remaining > 0) { - rateLimiter.maybeExecute(data); - } else { - showRateLimitWarning(); - } -}; - -// Access the selected state (will be empty object {} unless selector provided) -const { executionCount, rejectionCount } = rateLimiter.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/injectThrottledComputed.md b/docs/framework/angular/reference/functions/injectThrottledComputed.md new file mode 100644 index 000000000..eba9a485e --- /dev/null +++ b/docs/framework/angular/reference/functions/injectThrottledComputed.md @@ -0,0 +1,99 @@ +--- +id: injectThrottledComputed +title: injectThrottledComputed +--- + +## Call Signature + +```ts +function injectThrottledComputed(source, options): AngularThrottlerComputed; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:44 + +Creates an Angular throttled view of a source signal. + +The initial source value is available on first read. An effect passes source values to the throttler, including the initial value. Later changes replace the pending trailing value without restarting the interval. + +The returned value is a real Angular signal with the underlying utility exposed +on `throttler`. Options accept a static object or reactive factory and follow +[injectThrottler](injectThrottler.md) lifecycle and provider behavior. + +### Type Parameters + +#### TValue + +`TValue` + +### Parameters + +#### source + +() => `TValue` + +The source signal or accessor whose changes are observed. + +#### options + +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`value`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularThrottlerComputed`](../interfaces/AngularThrottlerComputed.md)\<`TValue`\> + +The value signal with a `throttler` attribute. + +### Example + +```ts +// In a component or service injection context. +const source = signal('initial') +const value = injectThrottledComputed(source, { wait: 250 }) +source.set('updated') +console.log(value()) +``` + +## Call Signature + +```ts +function injectThrottledComputed( + source, + options, +selector): AngularThrottlerComputed; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:52 + +Creates the value signal with selected state on its attached utility ref. + +### Type Parameters + +#### TValue + +`TValue` + +#### TSelected + +`TSelected` + +### Parameters + +#### source + +() => `TValue` + +#### options + +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`value`) => `void`\>\> + +#### selector + +(`state`) => `TSelected` + +Selects reactive state exposed on the attached utility ref. + +### Returns + +[`AngularThrottlerComputed`](../interfaces/AngularThrottlerComputed.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectThrottledSignal.md b/docs/framework/angular/reference/functions/injectThrottledSignal.md index 13624ff96..09208e53d 100644 --- a/docs/framework/angular/reference/functions/injectThrottledSignal.md +++ b/docs/framework/angular/reference/functions/injectThrottledSignal.md @@ -3,85 +3,97 @@ id: injectThrottledSignal title: injectThrottledSignal --- +## Call Signature + ```ts -function injectThrottledSignal( - value, - initialOptions, -selector?): ThrottledSignal; +function injectThrottledSignal(initialValue, options): AngularThrottlerSignal; ``` -Defined in: [throttler/injectThrottledSignal.ts:63](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L63) +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:49 + +Creates an Angular throttled editable signal. -An Angular function that creates a throttled state signal, combining Angular's signal with throttling functionality. -This function provides both the current throttled value and methods to update it. +The initial value is available synchronously. `set` and `update` share a throttler. Leading writes may apply immediately; subsequent writes replace the pending trailing write without extending its deadline. An updater runs against the committed value when executed. -The state value is updated at most once within the specified wait time. -This is useful for handling frequent state updates that should be rate-limited, like scroll positions -or mouse movements. +The returned value is a real Angular signal with the underlying utility exposed +on `throttler`. Options accept a static object or reactive factory and follow +[injectThrottler](injectThrottler.md) lifecycle and provider behavior. -The function returns a callable object: -- `throttled()`: Get the current throttled value -- `throttled.set(...)`: Set or update the throttled value (throttled via maybeExecute) -- `throttled.throttler`: The throttler instance with additional control methods and state signals +### Type Parameters -## State Management and Selector +#### TValue -The function uses TanStack Store for reactive state management via the underlying throttler instance. -The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. +`TValue` -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +### Parameters -Available throttler state properties: -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the throttler is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastExecutionTime`: Timestamp of the last execution -- `nextExecutionTime`: Timestamp of the next allowed execution -- `status`: Current execution status ('disabled' | 'idle' | 'pending') +#### initialValue -## Type Parameters +`MaybeAccessor`\<`TValue`\> -### TValue +The initial committed value. -`TValue` +#### options + +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`callback`) => `void`\>\> + +Core options or a reactive options factory. + +### Returns + +[`AngularThrottlerSignal`](../interfaces/AngularThrottlerSignal.md)\<`TValue`\> + +The value signal with `set`, `update`, and a `throttler` attribute. + +### Example + +```ts +// In a component or service injection context. +const value = injectThrottledSignal(0, { wait: 250 }) +value.set(10) +value.update(previous => previous + 1) +console.log(value()) +``` + +## Call Signature + +```ts +function injectThrottledSignal( + initialValue, + options, +selector): AngularThrottlerSignal; +``` -### TSelected +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:59 -`TSelected` = \{ -\} +Creates the value signal with selected state on its attached utility ref. -## Parameters +### Type Parameters -### value +#### TValue `TValue` -### initialOptions +#### TSelected -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`ThrottlerOptions`\<`Setter`\<`TValue`\>\>\> +`TSelected` -### selector? +### Parameters -(`state`) => `TSelected` +#### initialValue -## Returns +`MaybeAccessor`\<`TValue`\> -[`ThrottledSignal`](../interfaces/ThrottledSignal.md)\<`TValue`, `TSelected`\> +#### options -## Example +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`callback`) => `void`\>\> -```ts -const throttledScrollY = injectThrottledSignal(0, { wait: 100 }) +#### selector -// Get value -console.log(throttledScrollY()) +(`state`) => `TSelected` -// Set/update value (throttled) -throttledScrollY.set(window.scrollY) +Selects reactive state exposed on the attached utility ref. -// Access throttler -console.log(throttledScrollY.throttler.state().isPending) -``` +### Returns + +[`AngularThrottlerSignal`](../interfaces/AngularThrottlerSignal.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectThrottledValue.md b/docs/framework/angular/reference/functions/injectThrottledValue.md deleted file mode 100644 index 4f46e509b..000000000 --- a/docs/framework/angular/reference/functions/injectThrottledValue.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -id: injectThrottledValue -title: injectThrottledValue ---- - -## Call Signature - -```ts -function injectThrottledValue( - value, - initialOptions, -selector?): ThrottledSignal; -``` - -Defined in: [throttler/injectThrottledValue.ts:74](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L74) - -An Angular function that creates a throttled value that updates at most once within a specified time window. -Unlike injectThrottledSignal, this function automatically tracks changes to the input signal -and updates the throttled value accordingly. - -The throttled value will update at most once within the specified wait time, regardless of -how frequently the input value changes. - -This is useful for deriving throttled values from signals that change frequently, -like scroll positions or mouse coordinates, where you want to limit how often downstream effects -or calculations occur. - -The function returns a throttled signal object containing: -- A Signal that provides the current throttled value -- The throttler instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying throttler instance. -The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -Available throttler state properties: -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the throttler is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastExecutionTime`: Timestamp of the last execution -- `nextExecutionTime`: Timestamp of the next allowed execution -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialOptions - -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<`ThrottlerOptions`\<`Setter`\<`TValue`\>\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`ThrottledSignal`](../interfaces/ThrottledSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const scrollY = signal(0) -const throttledScrollY = injectThrottledValue(scrollY, { - wait: 100, // Update at most once per 100ms -}) - -// Opt-in to reactive updates when pending state changes -const throttledScrollYWithState = injectThrottledValue( - scrollY, - { wait: 100 }, - (state) => ({ isPending: state.isPending }), -) - -// Read the throttled signal value -effect(() => { - updateUI(throttledScrollY()) -}) - -// Access throttler state via the returned object's state() signal -console.log('Is pending:', throttledScrollYWithState.state().isPending) - -// Control the throttler -throttledScrollY.cancel() // Cancel any pending updates -``` - -## Call Signature - -```ts -function injectThrottledValue( - value, - initialValue, - initialOptions, -selector?): ThrottledSignal; -``` - -Defined in: [throttler/injectThrottledValue.ts:79](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L79) - -An Angular function that creates a throttled value that updates at most once within a specified time window. -Unlike injectThrottledSignal, this function automatically tracks changes to the input signal -and updates the throttled value accordingly. - -The throttled value will update at most once within the specified wait time, regardless of -how frequently the input value changes. - -This is useful for deriving throttled values from signals that change frequently, -like scroll positions or mouse coordinates, where you want to limit how often downstream effects -or calculations occur. - -The function returns a throttled signal object containing: -- A Signal that provides the current throttled value -- The throttler instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying throttler instance. -The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -Available throttler state properties: -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the throttler is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastExecutionTime`: Timestamp of the last execution -- `nextExecutionTime`: Timestamp of the next allowed execution -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -`ThrottlerOptions`\<`Setter`\<`TValue`\>\> - -#### selector? - -(`state`) => `TSelected` - -### Returns - -[`ThrottledSignal`](../interfaces/ThrottledSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const scrollY = signal(0) -const throttledScrollY = injectThrottledValue(scrollY, { - wait: 100, // Update at most once per 100ms -}) - -// Opt-in to reactive updates when pending state changes -const throttledScrollYWithState = injectThrottledValue( - scrollY, - { wait: 100 }, - (state) => ({ isPending: state.isPending }), -) - -// Read the throttled signal value -effect(() => { - updateUI(throttledScrollY()) -}) - -// Access throttler state via the returned object's state() signal -console.log('Is pending:', throttledScrollYWithState.state().isPending) - -// Control the throttler -throttledScrollY.cancel() // Cancel any pending updates -``` - -## Call Signature - -```ts -function injectThrottledValue( - value, - initialValue, - initialOptions, -selector): ThrottledSignal; -``` - -Defined in: [throttler/injectThrottledValue.ts:85](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L85) - -An Angular function that creates a throttled value that updates at most once within a specified time window. -Unlike injectThrottledSignal, this function automatically tracks changes to the input signal -and updates the throttled value accordingly. - -The throttled value will update at most once within the specified wait time, regardless of -how frequently the input value changes. - -This is useful for deriving throttled values from signals that change frequently, -like scroll positions or mouse coordinates, where you want to limit how often downstream effects -or calculations occur. - -The function returns a throttled signal object containing: -- A Signal that provides the current throttled value -- The throttler instance with control methods - -## State Management and Selector - -The function uses TanStack Store for reactive state management via the underlying throttler instance. -The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, -optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. - -Available throttler state properties: -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the throttler is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastExecutionTime`: Timestamp of the last execution -- `nextExecutionTime`: Timestamp of the next allowed execution -- `status`: Current execution status ('disabled' | 'idle' | 'pending') - -### Type Parameters - -#### TValue - -`TValue` - -#### TSelected - -`TSelected` = \{ -\} - -### Parameters - -#### value - -`Signal`\<`TValue`\> - -#### initialValue - -`TValue` - -#### initialOptions - -() => `ThrottlerOptions`\<`Setter`\<`TValue`\>\> - -#### selector - -((`state`) => `TSelected`) \| `undefined` - -### Returns - -[`ThrottledSignal`](../interfaces/ThrottledSignal.md)\<`TValue`, `TSelected`\> - -### Example - -```ts -// Default behavior - no reactive state subscriptions -const scrollY = signal(0) -const throttledScrollY = injectThrottledValue(scrollY, { - wait: 100, // Update at most once per 100ms -}) - -// Opt-in to reactive updates when pending state changes -const throttledScrollYWithState = injectThrottledValue( - scrollY, - { wait: 100 }, - (state) => ({ isPending: state.isPending }), -) - -// Read the throttled signal value -effect(() => { - updateUI(throttledScrollY()) -}) - -// Access throttler state via the returned object's state() signal -console.log('Is pending:', throttledScrollYWithState.state().isPending) - -// Control the throttler -throttledScrollY.cancel() // Cancel any pending updates -``` diff --git a/docs/framework/angular/reference/functions/injectThrottler.md b/docs/framework/angular/reference/functions/injectThrottler.md index 3b5cbf3ae..94eeef419 100644 --- a/docs/framework/angular/reference/functions/injectThrottler.md +++ b/docs/framework/angular/reference/functions/injectThrottler.md @@ -3,106 +3,115 @@ id: injectThrottler title: injectThrottler --- +## Call Signature + ```ts -function injectThrottler( - fn, - options, -selector?): AngularThrottler; +function injectThrottler(fn, options): AngularThrottler; ``` -Defined in: [throttler/injectThrottler.ts:113](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L113) +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:88 + +Creates and manages an Angular Throttler in the current injection context. + +Limits executions to the configured interval, with leading and trailing calls. Later calls replace the pending trailing arguments without restarting the interval. + +## Options and state + +Accepts static options or an options factory. Factories are read lazily, and signal +dependencies update the existing core instance. Local options override provider defaults. +Methods apply current options before executing and run outside Angular's zone. + +Pass a selector to expose reactive core state through `state()`. Without a selector, +`state()` returns `{}`; operations remain available on the ref. + +## Cleanup + +The default cleanup is to cancel pending execution. Set `onUnmount` to replace it. + +### Type Parameters + +#### TFn + +`TFn` *extends* `AnyFunction` + +### Parameters -An Angular function that creates and manages a Throttler instance. +#### fn -This is a lower-level function that provides direct access to the Throttler's functionality. -This allows you to integrate it with any state management solution you prefer. +`TFn` + +The callback invoked by the core utility. -This function provides throttling functionality to limit how often a function can be called, -ensuring it executes at most once within a specified time window. +#### options -The throttler will execute the function immediately (if leading is enabled) and then -prevent further executions until the wait period has elapsed. +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<`TFn`\>\> -## State Management and Selector +Core options or a reactive factory returning them. -The function uses TanStack Store for state management and wraps it with Angular signals. -The `selector` parameter allows you to specify which state changes will trigger signal updates, -optimizing performance by preventing unnecessary updates when irrelevant state changes occur. +### Returns -**By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary updates and gives you -full control over when your component tracks state changes. +[`AngularThrottler`](../interfaces/AngularThrottler.md)\<`TFn`\> -Available state properties: -- `executionCount`: Number of function executions that have been completed -- `isPending`: Whether the throttler is waiting for the timeout to trigger execution -- `lastArgs`: The arguments from the most recent call to maybeExecute -- `lastExecutionTime`: Timestamp of the last execution -- `nextExecutionTime`: Timestamp of the next allowed execution -- `status`: Current execution status ('disabled' | 'idle' | 'pending') +A ref containing stable methods and a readonly selected-state signal. -## Cleanup on Destroy +### Example + +```ts +// In a component or service injection context. +const utility = injectThrottler( + (value: number) => console.log(value), + () => ({ wait: 100 }), + (state) => state.isPending, +) +utility.maybeExecute(42) +console.log(utility.state()) +``` -By default, the function cancels any pending execution when the component is destroyed. -Use the `onUnmount` option to customize this. For example, to flush pending work instead: +## Call Signature ```ts -const throttler = injectThrottler(fn, { - wait: 100, - onUnmount: (t) => t.flush() -}); +function injectThrottler( + fn, + options, +selector): AngularThrottler; ``` -## Type Parameters +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:99 -### TFn +Creates an Angular Throttler with a reactive selector result. + +### Type Parameters + +#### TFn `TFn` *extends* `AnyFunction` -### TSelected +#### TSelected -`TSelected` = \{ -\} +`TSelected` -## Parameters +### Parameters -### fn +#### fn `TFn` -### options +The callback invoked by the core utility. + +#### options + +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<`TFn`\>\> -[`AngularPacerOptions`](../type-aliases/AngularPacerOptions.md)\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<`TFn`, `TSelected`\>\> +Core options or a reactive options factory. -### selector? +#### selector (`state`) => `TSelected` -## Returns +Selects the state exposed by the returned `state` signal. -[`AngularThrottler`](../interfaces/AngularThrottler.md)\<`TFn`, `TSelected`\> +### Returns -## Example +[`AngularThrottler`](../interfaces/AngularThrottler.md)\<`TFn`, `TSelected`\> -```ts -// Default behavior - no reactive state subscriptions -const throttler = injectThrottler( - (scrollY: number) => updateScrollPosition(scrollY), - { wait: 100 } -); - -// Opt-in to track isPending changes (optimized for loading states) -const throttler = injectThrottler( - (scrollY: number) => updateScrollPosition(scrollY), - { wait: 100 }, - (state) => ({ isPending: state.isPending }) -); - -// In an event handler -window.addEventListener('scroll', () => { - throttler.maybeExecute(window.scrollY); -}); - -// Access the selected state (will be empty object {} unless selector provided) -const { isPending } = throttler.state(); -``` +The utility ref with the selected state. diff --git a/docs/framework/angular/reference/functions/providePacerOptions.md b/docs/framework/angular/reference/functions/providePacerOptions.md index ebf7e4e9a..5a6960f92 100644 --- a/docs/framework/angular/reference/functions/providePacerOptions.md +++ b/docs/framework/angular/reference/functions/providePacerOptions.md @@ -7,42 +7,34 @@ title: providePacerOptions function providePacerOptions(options): Provider; ``` -Defined in: [provider/pacer-provider.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/pacer-provider.ts#L33) +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:67 -Provides default options for all Pacer utilities in the Angular application. -Use this function when configuring your Angular application to set default options -that will be used by all Pacer utilities throughout your app. +Provides scoped defaults for Angular Pacer utilities. + +Place this provider in application, route, or component providers. The nearest +provider supplies the defaults; local utility options take precedence. A nested +provider replaces the outer defaults rather than merging scopes. ## Parameters ### options -`PacerProviderOptions` +[`PacerProviderOptions`](../interfaces/PacerProviderOptions.md) + +Partial default options grouped by utility. ## Returns `Provider` +An Angular provider for the supplied defaults. + ## Example ```ts -// In your app.config.ts (standalone) -export const appConfig: ApplicationConfig = { - providers: [ - providePacerOptions({ - debouncer: { wait: 300 }, - throttler: { wait: 100 }, - }), - ], -}; - -// Or in NgModule (module-based) -@NgModule({ +const appConfig = { providers: [ - providePacerOptions({ - debouncer: { wait: 300 }, - }), + providePacerOptions({ debouncer: { wait: 250 } }), ], -}) -export class AppModule {} +} ``` diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index ef3210c94..610b4926a 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -10,6 +10,7 @@ title: "@tanstack/angular-pacer" - [AngularAsyncDebouncer](interfaces/AngularAsyncDebouncer.md) - [AngularAsyncDebouncerOptions](interfaces/AngularAsyncDebouncerOptions.md) - [AngularAsyncQueuer](interfaces/AngularAsyncQueuer.md) +- [AngularAsyncQueuerItems](interfaces/AngularAsyncQueuerItems.md) - [AngularAsyncQueuerOptions](interfaces/AngularAsyncQueuerOptions.md) - [AngularAsyncRateLimiter](interfaces/AngularAsyncRateLimiter.md) - [AngularAsyncRateLimiterOptions](interfaces/AngularAsyncRateLimiterOptions.md) @@ -18,43 +19,45 @@ title: "@tanstack/angular-pacer" - [AngularBatcher](interfaces/AngularBatcher.md) - [AngularBatcherOptions](interfaces/AngularBatcherOptions.md) - [AngularDebouncer](interfaces/AngularDebouncer.md) +- [AngularDebouncerComputed](interfaces/AngularDebouncerComputed.md) - [AngularDebouncerOptions](interfaces/AngularDebouncerOptions.md) +- [AngularDebouncerSignal](interfaces/AngularDebouncerSignal.md) - [AngularQueuer](interfaces/AngularQueuer.md) +- [AngularQueuerComputed](interfaces/AngularQueuerComputed.md) +- [AngularQueuerItems](interfaces/AngularQueuerItems.md) - [AngularQueuerOptions](interfaces/AngularQueuerOptions.md) +- [AngularQueuerSignal](interfaces/AngularQueuerSignal.md) - [AngularRateLimiter](interfaces/AngularRateLimiter.md) +- [AngularRateLimiterComputed](interfaces/AngularRateLimiterComputed.md) - [AngularRateLimiterOptions](interfaces/AngularRateLimiterOptions.md) +- [AngularRateLimiterSignal](interfaces/AngularRateLimiterSignal.md) - [AngularThrottler](interfaces/AngularThrottler.md) +- [AngularThrottlerComputed](interfaces/AngularThrottlerComputed.md) - [AngularThrottlerOptions](interfaces/AngularThrottlerOptions.md) -- [AsyncQueuedSignal](interfaces/AsyncQueuedSignal.md) -- [DebouncedSignal](interfaces/DebouncedSignal.md) -- [QueuedSignal](interfaces/QueuedSignal.md) -- [RateLimitedSignal](interfaces/RateLimitedSignal.md) -- [ThrottledSignal](interfaces/ThrottledSignal.md) - -## Type Aliases - -- [AngularPacerOptions](type-aliases/AngularPacerOptions.md) -- [QueuedValueSignal](type-aliases/QueuedValueSignal.md) +- [AngularThrottlerSignal](interfaces/AngularThrottlerSignal.md) +- [PacerProviderOptions](interfaces/PacerProviderOptions.md) ## Functions - [injectAsyncBatcher](functions/injectAsyncBatcher.md) - [injectAsyncDebouncer](functions/injectAsyncDebouncer.md) -- [injectAsyncQueuedSignal](functions/injectAsyncQueuedSignal.md) - [injectAsyncQueuer](functions/injectAsyncQueuer.md) +- [injectAsyncQueuerItems](functions/injectAsyncQueuerItems.md) - [injectAsyncRateLimiter](functions/injectAsyncRateLimiter.md) - [injectAsyncThrottler](functions/injectAsyncThrottler.md) - [injectBatcher](functions/injectBatcher.md) +- [injectDebouncedComputed](functions/injectDebouncedComputed.md) - [injectDebouncedSignal](functions/injectDebouncedSignal.md) -- [injectDebouncedValue](functions/injectDebouncedValue.md) - [injectDebouncer](functions/injectDebouncer.md) +- [injectPacerOptions](functions/injectPacerOptions.md) +- [injectQueuedComputed](functions/injectQueuedComputed.md) - [injectQueuedSignal](functions/injectQueuedSignal.md) -- [injectQueuedValue](functions/injectQueuedValue.md) - [injectQueuer](functions/injectQueuer.md) +- [injectQueuerItems](functions/injectQueuerItems.md) +- [injectRateLimitedComputed](functions/injectRateLimitedComputed.md) - [injectRateLimitedSignal](functions/injectRateLimitedSignal.md) -- [injectRateLimitedValue](functions/injectRateLimitedValue.md) - [injectRateLimiter](functions/injectRateLimiter.md) +- [injectThrottledComputed](functions/injectThrottledComputed.md) - [injectThrottledSignal](functions/injectThrottledSignal.md) -- [injectThrottledValue](functions/injectThrottledValue.md) - [injectThrottler](functions/injectThrottler.md) - [providePacerOptions](functions/providePacerOptions.md) diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md index ad66c549b..7da6c2ca5 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md @@ -3,11 +3,14 @@ id: AngularAsyncBatcher title: AngularAsyncBatcher --- -Defined in: [async-batcher/injectAsyncBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L26) +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:47 + +An Angular AsyncBatcher ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`AsyncBatcher`\<`TValue`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`AsyncBatcher`\<`TValue`\>, `AsyncBatcherMethod`\> ## Type Parameters @@ -22,71 +25,12 @@ Defined in: [async-batcher/injectAsyncBatcher.ts:26](https://github.com/TanStack ## Properties -### options - -```ts -options: Omit>, - | "initialState" - | "key" - | "onError" - | "onSettled" - | "onSuccess" - | "onItemsChange"> & Partial>, - | "initialState" - | "key" - | "onError" - | "onSettled" - | "onSuccess" -| "onItemsChange">> & AngularAsyncBatcherOptions; -``` - -Defined in: [async-batcher/injectAsyncBatcher.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L30) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [async-batcher/injectAsyncBatcher.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L32) - -#### Parameters - -##### options - -`Partial`\<[`AngularAsyncBatcherOptions`](AngularAsyncBatcherOptions.md)\<`TValue`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [async-batcher/injectAsyncBatcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L40) - -Reactive state signal that will be updated when the async batcher state changes - -Use this instead of `batcher.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [async-batcher/injectAsyncBatcher.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L45) - -#### Deprecated +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54 -Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +Reactive state that will be updated when the batcher state changes diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md index 7968e89dc..f0e5dea07 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md @@ -3,7 +3,9 @@ id: AngularAsyncBatcherOptions title: AngularAsyncBatcherOptions --- -Defined in: [async-batcher/injectAsyncBatcher.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L14) +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:19 + +Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including core configuration and Angular cleanup. ## Extends @@ -15,11 +17,6 @@ Defined in: [async-batcher/injectAsyncBatcher.ts:14](https://github.com/TanStack `TValue` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? @@ -28,17 +25,16 @@ Defined in: [async-batcher/injectAsyncBatcher.ts:14](https://github.com/TanStack optional onUnmount?: (batcher) => void; ``` -Defined in: [async-batcher/injectAsyncBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L23) +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:26 -Optional callback invoked when the component is destroyed. Receives the batcher instance. -When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending batches and abort running work). #### Parameters ##### batcher -[`AngularAsyncBatcher`](AngularAsyncBatcher.md)\<`TValue`, `TSelected`\> +`AsyncBatcher`\<`TValue`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md index f17b55170..e2f854308 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md @@ -3,11 +3,14 @@ id: AngularAsyncDebouncer title: AngularAsyncDebouncer --- -Defined in: [async-debouncer/injectAsyncDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L27) +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:48 + +An Angular AsyncDebouncer ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`AsyncDebouncer`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`AsyncDebouncer`\<`TFn`\>, `AsyncDebouncerMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [async-debouncer/injectAsyncDebouncer.ts:27](https://github.com/TanS ## Properties -### options - -```ts -options: AsyncDebouncerOptions & AngularAsyncDebouncerOptions; -``` - -Defined in: [async-debouncer/injectAsyncDebouncer.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L31) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [async-debouncer/injectAsyncDebouncer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L33) - -#### Parameters - -##### options - -`Partial`\<[`AngularAsyncDebouncerOptions`](AngularAsyncDebouncerOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [async-debouncer/injectAsyncDebouncer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L41) - -Reactive state signal that will be updated when the async debouncer state changes - -Use this instead of `debouncer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [async-debouncer/injectAsyncDebouncer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L46) - -#### Deprecated +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:53 -Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md index 69e91f5d4..c042d238c 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md @@ -3,7 +3,9 @@ id: AngularAsyncDebouncerOptions title: AngularAsyncDebouncerOptions --- -Defined in: [async-debouncer/injectAsyncDebouncer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L15) +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:23 + +Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), including core configuration and Angular cleanup. ## Extends @@ -15,30 +17,24 @@ Defined in: [async-debouncer/injectAsyncDebouncer.ts:15](https://github.com/TanS `TFn` *extends* `AnyAsyncFunction` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (debouncer) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [async-debouncer/injectAsyncDebouncer.ts:24](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L24) +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:30 -Optional callback invoked when the component is destroyed. Receives the debouncer instance. -When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending execution and abort running work). #### Parameters -##### debouncer +##### core -[`AngularAsyncDebouncer`](AngularAsyncDebouncer.md)\<`TFn`, `TSelected`\> +`AsyncDebouncer`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md index eefab6203..def8ad4ee 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md @@ -3,11 +3,14 @@ id: AngularAsyncQueuer title: AngularAsyncQueuer --- -Defined in: [async-queuer/injectAsyncQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L26) +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:53 + +An Angular AsyncQueuer ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`AsyncQueuer`\<`TValue`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`AsyncQueuer`\<`TValue`\>, `AsyncQueuerMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [async-queuer/injectAsyncQueuer.ts:26](https://github.com/TanStack/p ## Properties -### options - -```ts -options: AsyncQueuerOptions & AngularAsyncQueuerOptions; -``` - -Defined in: [async-queuer/injectAsyncQueuer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L30) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [async-queuer/injectAsyncQueuer.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L32) - -#### Parameters - -##### options - -`Partial`\<[`AngularAsyncQueuerOptions`](AngularAsyncQueuerOptions.md)\<`TValue`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [async-queuer/injectAsyncQueuer.ts:40](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L40) - -Reactive state signal that will be updated when the async queuer state changes - -Use this instead of `queuer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [async-queuer/injectAsyncQueuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L45) - -#### Deprecated +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:58 -Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md new file mode 100644 index 000000000..e37f81ba0 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md @@ -0,0 +1,104 @@ +--- +id: AngularAsyncQueuerItems +title: AngularAsyncQueuerItems +--- + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16 + +A readonly signal of pending queue items, with the Angular queuer attached. + +## Extends + +- `Signal`\<`ReadonlyArray`\<`TValue`\>\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularAsyncQueuerItems(): readonly TValue[]; +``` + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16 + +A readonly signal of pending queue items, with the Angular queuer attached. + +## Returns + +readonly `TValue`[] + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### addItem + +```ts +addItem: (item, position, runOnItemsChange) => boolean; +``` + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:22 + +Shortcut for queuer.addItem, with the same arguments and return value. + +Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. +Items can be inserted based on priority or at the front/back depending on configuration. +`undefined` cannot be queued (it is the internal "no item" sentinel) and is always rejected. + +#### Parameters + +##### item + +`TValue` + +##### position? + +`QueuePosition` = `...` + +##### runOnItemsChange? + +`boolean` = `true` + +#### Returns + +`boolean` + +#### Example + +```ts +queuer.addItem({ value: 'task', priority: 10 }); +queuer.addItem('task2', 'front'); +``` + +*** + +### queuer + +```ts +queuer: AngularAsyncQueuer; +``` + +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:20 + +The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md index 67ca6942b..192766045 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md @@ -3,7 +3,9 @@ id: AngularAsyncQueuerOptions title: AngularAsyncQueuerOptions --- -Defined in: [async-queuer/injectAsyncQueuer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L14) +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:19 + +Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including core configuration and Angular cleanup. ## Extends @@ -15,30 +17,24 @@ Defined in: [async-queuer/injectAsyncQueuer.ts:14](https://github.com/TanStack/p `TValue` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (queuer) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [async-queuer/injectAsyncQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L23) +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:26 -Optional callback invoked when the component is destroyed. Receives the queuer instance. -When provided, replaces the default cleanup (stop + abort); use it to call flush(), stop(), add logging, etc. -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (stop automatic processing and abort running work). #### Parameters -##### queuer +##### core -[`AngularAsyncQueuer`](AngularAsyncQueuer.md)\<`TValue`, `TSelected`\> +`AsyncQueuer`\<`TValue`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md index a7e4dd835..78cab011a 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md @@ -3,11 +3,14 @@ id: AngularAsyncRateLimiter title: AngularAsyncRateLimiter --- -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L26) +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:50 + +An Angular AsyncRateLimiter ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`AsyncRateLimiter`\<`TFn`\>, `AsyncRateLimiterMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:26](https://github.com ## Properties -### options - -```ts -options: AsyncRateLimiterOptions & AngularAsyncRateLimiterOptions; -``` - -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L30) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L32) - -#### Parameters - -##### options - -`Partial`\<[`AngularAsyncRateLimiterOptions`](AngularAsyncRateLimiterOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:40](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L40) - -Reactive state signal that will be updated when the async rate limiter state changes - -Use this instead of `rateLimiter.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L45) - -#### Deprecated +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55 -Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md index 44f264ce7..4dc0cfa75 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md @@ -3,7 +3,9 @@ id: AngularAsyncRateLimiterOptions title: AngularAsyncRateLimiterOptions --- -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L15) +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:23 + +Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), including core configuration and Angular cleanup. ## Extends @@ -15,29 +17,24 @@ Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:15](https://github.com `TFn` *extends* `AnyAsyncFunction` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (rateLimiter) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [async-rate-limiter/injectAsyncRateLimiter.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L23) +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:30 -Optional callback invoked when the component is destroyed. Receives the rate limiter instance. -When provided, replaces the default cleanup (abort). +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (abort running work and reset the limiter). #### Parameters -##### rateLimiter +##### core -[`AngularAsyncRateLimiter`](AngularAsyncRateLimiter.md)\<`TFn`, `TSelected`\> +`AsyncRateLimiter`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md index ec5e02e53..35063ffe9 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md @@ -3,11 +3,14 @@ id: AngularAsyncThrottler title: AngularAsyncThrottler --- -Defined in: [async-throttler/injectAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L27) +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:48 + +An Angular AsyncThrottler ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`AsyncThrottler`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`AsyncThrottler`\<`TFn`\>, `AsyncThrottlerMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [async-throttler/injectAsyncThrottler.ts:27](https://github.com/TanS ## Properties -### options - -```ts -options: AsyncThrottlerOptions & AngularAsyncThrottlerOptions; -``` - -Defined in: [async-throttler/injectAsyncThrottler.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L31) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [async-throttler/injectAsyncThrottler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L33) - -#### Parameters - -##### options - -`Partial`\<[`AngularAsyncThrottlerOptions`](AngularAsyncThrottlerOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [async-throttler/injectAsyncThrottler.ts:41](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L41) - -Reactive state signal that will be updated when the async throttler state changes - -Use this instead of `throttler.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [async-throttler/injectAsyncThrottler.ts:46](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L46) - -#### Deprecated +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:53 -Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md index 46f938006..db95a8b13 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md @@ -3,7 +3,9 @@ id: AngularAsyncThrottlerOptions title: AngularAsyncThrottlerOptions --- -Defined in: [async-throttler/injectAsyncThrottler.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L15) +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:23 + +Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), including core configuration and Angular cleanup. ## Extends @@ -15,30 +17,24 @@ Defined in: [async-throttler/injectAsyncThrottler.ts:15](https://github.com/TanS `TFn` *extends* `AnyAsyncFunction` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (throttler) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [async-throttler/injectAsyncThrottler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L24) +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:30 -Optional callback invoked when the component is destroyed. Receives the throttler instance. -When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. -When using onUnmount with flush, guard your callbacks since the component may already be destroyed. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending execution and abort running work). #### Parameters -##### throttler +##### core -[`AngularAsyncThrottler`](AngularAsyncThrottler.md)\<`TFn`, `TSelected`\> +`AsyncThrottler`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularBatcher.md b/docs/framework/angular/reference/interfaces/AngularBatcher.md index 6c9f59e68..47dfe0268 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcher.md @@ -3,11 +3,14 @@ id: AngularBatcher title: AngularBatcher --- -Defined in: [batcher/injectBatcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L22) +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:42 + +An Angular Batcher ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`Batcher`\<`TValue`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`Batcher`\<`TValue`\>, `BatcherMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [batcher/injectBatcher.ts:22](https://github.com/TanStack/pacer/blob ## Properties -### options - -```ts -options: Omit>, "initialState" | "key" | "onItemsChange" | "onExecute"> & Partial>, "initialState" | "key" | "onItemsChange" | "onExecute">> & AngularBatcherOptions; -``` - -Defined in: [batcher/injectBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L26) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [batcher/injectBatcher.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L27) - -#### Parameters - -##### options - -`Partial`\<[`AngularBatcherOptions`](AngularBatcherOptions.md)\<`TValue`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [batcher/injectBatcher.ts:35](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L35) - -Reactive state signal that will be updated when the batcher state changes - -Use this instead of `batcher.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [batcher/injectBatcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L40) - -#### Deprecated +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:47 -Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md index 9c207f27f..c8b949d40 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md @@ -3,7 +3,9 @@ id: AngularBatcherOptions title: AngularBatcherOptions --- -Defined in: [batcher/injectBatcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L11) +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:19 + +Options for [injectBatcher](../functions/injectBatcher.md), including core configuration and Angular cleanup. ## Extends @@ -15,29 +17,24 @@ Defined in: [batcher/injectBatcher.ts:11](https://github.com/TanStack/pacer/blob `TValue` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (batcher) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [batcher/injectBatcher.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L19) +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:24 -Optional callback invoked when the component is destroyed. Receives the batcher instance. -When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending batches). #### Parameters -##### batcher +##### core -[`AngularBatcher`](AngularBatcher.md)\<`TValue`, `TSelected`\> +`Batcher`\<`TValue`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncer.md b/docs/framework/angular/reference/interfaces/AngularDebouncer.md index bd785089d..a393cfc50 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncer.md @@ -3,17 +3,20 @@ id: AngularDebouncer title: AngularDebouncer --- -Defined in: [debouncer/injectDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L26) +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:46 + +An Angular Debouncer ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`Debouncer`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`Debouncer`\<`TFn`\>, `DebouncerMethod`\> ## Type Parameters ### TFn -`TFn` *extends* `AnyFunction` +`TFn` *extends* `AnyFunction` = `AnyFunction` ### TSelected @@ -22,59 +25,12 @@ Defined in: [debouncer/injectDebouncer.ts:26](https://github.com/TanStack/pacer/ ## Properties -### options - -```ts -options: DebouncerOptions & AngularDebouncerOptions; -``` - -Defined in: [debouncer/injectDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L30) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [debouncer/injectDebouncer.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L31) - -#### Parameters - -##### options - -`Partial`\<[`AngularDebouncerOptions`](AngularDebouncerOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [debouncer/injectDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L39) - -Reactive state signal that will be updated when the debouncer state changes - -Use this instead of `debouncer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [debouncer/injectDebouncer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L44) - -#### Deprecated +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:51 -Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md b/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md new file mode 100644 index 000000000..8c2832770 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md @@ -0,0 +1,65 @@ +--- +id: AngularDebouncerComputed +title: AngularDebouncerComputed +--- + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `debouncer` attribute exposes the underlying Debouncer methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularDebouncerComputed(): TValue; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `debouncer` attribute exposes the underlying Debouncer methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### debouncer + +```ts +debouncer: AngularDebouncer<(value) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:20 + +The underlying Angular Debouncer ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md index 4071a51d2..d52f98a5c 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md @@ -3,7 +3,9 @@ id: AngularDebouncerOptions title: AngularDebouncerOptions --- -Defined in: [debouncer/injectDebouncer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L15) +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:23 + +Options for [injectDebouncer](../functions/injectDebouncer.md), including core configuration and Angular cleanup. ## Extends @@ -13,31 +15,26 @@ Defined in: [debouncer/injectDebouncer.ts:15](https://github.com/TanStack/pacer/ ### TFn -`TFn` *extends* `AnyFunction` - -### TSelected - -`TSelected` = \{ -\} +`TFn` *extends* `AnyFunction` = `AnyFunction` ## Properties ### onUnmount? ```ts -optional onUnmount?: (debouncer) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [debouncer/injectDebouncer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L23) +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:30 -Optional callback invoked when the component is destroyed. Receives the debouncer instance. -When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending execution). #### Parameters -##### debouncer +##### core -[`AngularDebouncer`](AngularDebouncer.md)\<`TFn`, `TSelected`\> +`Debouncer`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md new file mode 100644 index 000000000..404d8c977 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md @@ -0,0 +1,109 @@ +--- +id: AngularDebouncerSignal +title: AngularDebouncerSignal +--- + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `debouncer` attribute exposes the underlying Debouncer methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularDebouncerSignal(): TValue; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `debouncer` attribute exposes the underlying Debouncer methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### debouncer + +```ts +debouncer: AngularDebouncer<(callback) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:25 + +The underlying Angular Debouncer ref for controlling execution. + +*** + +### set + +```ts +set: (value) => void; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:21 + +Schedules the replacement value after the debounce delay. + +#### Parameters + +##### value + +`TValue` + +#### Returns + +`void` + +*** + +### update + +```ts +update: (updateFn) => void; +``` + +Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:23 + +Runs the updater with the current committed value when the write executes. + +#### Parameters + +##### updateFn + +(`previous`) => `TValue` + +#### Returns + +`void` diff --git a/docs/framework/angular/reference/interfaces/AngularQueuer.md b/docs/framework/angular/reference/interfaces/AngularQueuer.md index fd498a2a3..275130826 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuer.md @@ -3,11 +3,14 @@ id: AngularQueuer title: AngularQueuer --- -Defined in: [queuer/injectQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L22) +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:47 + +An Angular Queuer ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`Queuer`\<`TValue`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`Queuer`\<`TValue`\>, `QueuerMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [queuer/injectQueuer.ts:22](https://github.com/TanStack/pacer/blob/m ## Properties -### options - -```ts -options: QueuerOptions & AngularQueuerOptions; -``` - -Defined in: [queuer/injectQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L26) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [queuer/injectQueuer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L27) - -#### Parameters - -##### options - -`Partial`\<[`AngularQueuerOptions`](AngularQueuerOptions.md)\<`TValue`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [queuer/injectQueuer.ts:35](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L35) - -Reactive state signal that will be updated when the queuer state changes - -Use this instead of `queuer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [queuer/injectQueuer.ts:40](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L40) - -#### Deprecated +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:52 -Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md b/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md new file mode 100644 index 000000000..39948030c --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md @@ -0,0 +1,63 @@ +--- +id: AngularQueuerComputed +title: AngularQueuerComputed +--- + +Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9 + +A processed-value signal with its underlying queue controls. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularQueuerComputed(): TValue; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9 + +A processed-value signal with its underlying queue controls. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### queuer + +```ts +queuer: AngularQueuer; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:14 + +The underlying Angular Queuer ref and its selected state. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md new file mode 100644 index 000000000..876fb4d3c --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md @@ -0,0 +1,105 @@ +--- +id: AngularQueuerItems +title: AngularQueuerItems +--- + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:13 + +A readonly signal of pending queue items, with the Angular queuer attached. + +## Extends + +- `Signal`\<`ReadonlyArray`\<`TValue`\>\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularQueuerItems(): readonly TValue[]; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:13 + +A readonly signal of pending queue items, with the Angular queuer attached. + +## Returns + +readonly `TValue`[] + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### addItem + +```ts +addItem: (item, position, runOnItemsChange) => boolean; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:19 + +Shortcut for queuer.addItem, with the same arguments and return value. + +Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. +`undefined` cannot be queued (it is the internal "no item" sentinel) and is always rejected. +Items can be inserted based on priority or at the front/back depending on configuration. + +Returns true if the item was added, false if the queue is full. + +Example usage: +```ts +queuer.addItem('task'); +queuer.addItem('task2', 'front'); +``` + +#### Parameters + +##### item + +`TValue` + +##### position? + +`QueuePosition` = `...` + +##### runOnItemsChange? + +`boolean` = `true` + +#### Returns + +`boolean` + +*** + +### queuer + +```ts +queuer: AngularQueuer; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:17 + +The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md index 54ce16ab2..97589c57d 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md @@ -3,7 +3,9 @@ id: AngularQueuerOptions title: AngularQueuerOptions --- -Defined in: [queuer/injectQueuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L11) +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:19 + +Options for [injectQueuer](../functions/injectQueuer.md), including core configuration and Angular cleanup. ## Extends @@ -15,29 +17,24 @@ Defined in: [queuer/injectQueuer.ts:11](https://github.com/TanStack/pacer/blob/m `TValue` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (queuer) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [queuer/injectQueuer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L19) +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:24 -Optional callback invoked when the component is destroyed. Receives the queuer instance. -When provided, replaces the default cleanup (stop); use it to call flush(), stop(), add logging, etc. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (stop automatic processing). #### Parameters -##### queuer +##### core -[`AngularQueuer`](AngularQueuer.md)\<`TValue`, `TSelected`\> +`Queuer`\<`TValue`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md new file mode 100644 index 000000000..0a5866163 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md @@ -0,0 +1,107 @@ +--- +id: AngularQueuerSignal +title: AngularQueuerSignal +--- + +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10 + +A processed-value signal with its underlying queue controls. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularQueuerSignal(): TValue; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10 + +A processed-value signal with its underlying queue controls. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### queuer + +```ts +queuer: AngularQueuer<() => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:19 + +The underlying Angular Queuer ref and its selected state. + +*** + +### set + +```ts +set: (value) => void; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:15 + +Enqueues a replacement value. + +#### Parameters + +##### value + +`TValue` + +#### Returns + +`void` + +*** + +### update + +```ts +update: (updater) => void; +``` + +Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:17 + +Enqueues an updater evaluated against the committed value when processed. + +#### Parameters + +##### updater + +(`previous`) => `TValue` + +#### Returns + +`void` diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md index 703a57bb1..693c8dbde 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md @@ -3,11 +3,14 @@ id: AngularRateLimiter title: AngularRateLimiter --- -Defined in: [rate-limiter/injectRateLimiter.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L25) +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:45 + +An Angular RateLimiter ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`RateLimiter`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`RateLimiter`\<`TFn`\>, `RateLimiterMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [rate-limiter/injectRateLimiter.ts:25](https://github.com/TanStack/p ## Properties -### options - -```ts -options: RateLimiterOptions & AngularRateLimiterOptions; -``` - -Defined in: [rate-limiter/injectRateLimiter.ts:29](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L29) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [rate-limiter/injectRateLimiter.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L31) - -#### Parameters - -##### options - -`Partial`\<[`AngularRateLimiterOptions`](AngularRateLimiterOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [rate-limiter/injectRateLimiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L39) - -Reactive state signal that will be updated when the rate limiter state changes - -Use this instead of `rateLimiter.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>; -``` - -Defined in: [rate-limiter/injectRateLimiter.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L44) - -#### Deprecated +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:50 -Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md new file mode 100644 index 000000000..cae7167ca --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md @@ -0,0 +1,65 @@ +--- +id: AngularRateLimiterComputed +title: AngularRateLimiterComputed +--- + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `rateLimiter` attribute exposes the underlying RateLimiter methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularRateLimiterComputed(): TValue; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `rateLimiter` attribute exposes the underlying RateLimiter methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### rateLimiter + +```ts +rateLimiter: AngularRateLimiter<(value) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:20 + +The underlying Angular RateLimiter ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md index 1ddf48905..898277053 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md @@ -3,7 +3,9 @@ id: AngularRateLimiterOptions title: AngularRateLimiterOptions --- -Defined in: [rate-limiter/injectRateLimiter.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L15) +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:22 + +Options for [injectRateLimiter](../functions/injectRateLimiter.md), including core configuration and Angular cleanup. ## Extends @@ -15,28 +17,24 @@ Defined in: [rate-limiter/injectRateLimiter.ts:15](https://github.com/TanStack/p `TFn` *extends* `AnyFunction` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (rateLimiter) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [rate-limiter/injectRateLimiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L22) +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:29 -Optional callback invoked when the component is destroyed. Receives the rate limiter instance. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (reset the limiter). #### Parameters -##### rateLimiter +##### core -[`AngularRateLimiter`](AngularRateLimiter.md)\<`TFn`, `TSelected`\> +`RateLimiter`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md new file mode 100644 index 000000000..dd4bd3060 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md @@ -0,0 +1,109 @@ +--- +id: AngularRateLimiterSignal +title: AngularRateLimiterSignal +--- + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `rateLimiter` attribute exposes the underlying RateLimiter methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularRateLimiterSignal(): TValue; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `rateLimiter` attribute exposes the underlying RateLimiter methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### rateLimiter + +```ts +rateLimiter: AngularRateLimiter<(callback) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:25 + +The underlying Angular RateLimiter ref for controlling execution. + +*** + +### set + +```ts +set: (value) => void; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:21 + +Replaces the value immediately if the rate limit permits; rejected writes are discarded. + +#### Parameters + +##### value + +`TValue` + +#### Returns + +`void` + +*** + +### update + +```ts +update: (updateFn) => void; +``` + +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:23 + +Runs the updater with the current committed value when the write executes. + +#### Parameters + +##### updateFn + +(`previous`) => `TValue` + +#### Returns + +`void` diff --git a/docs/framework/angular/reference/interfaces/AngularThrottler.md b/docs/framework/angular/reference/interfaces/AngularThrottler.md index 025c8d226..98de4a88e 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottler.md @@ -3,11 +3,14 @@ id: AngularThrottler title: AngularThrottler --- -Defined in: [throttler/injectThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L26) +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:46 + +An Angular Throttler ref with stable core methods and readonly selected state. +Read `state()` to observe the selector result; without a selector it returns `{}`. ## Extends -- `Omit`\<`Throttler`\<`TFn`\>, `"store"` \| `"options"` \| `"setOptions"`\> +- `Pick`\<`Throttler`\<`TFn`\>, `ThrottlerMethod`\> ## Type Parameters @@ -22,59 +25,12 @@ Defined in: [throttler/injectThrottler.ts:26](https://github.com/TanStack/pacer/ ## Properties -### options - -```ts -options: ThrottlerOptions & AngularThrottlerOptions; -``` - -Defined in: [throttler/injectThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L30) - -*** - -### setOptions - -```ts -setOptions: (options) => void; -``` - -Defined in: [throttler/injectThrottler.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L31) - -#### Parameters - -##### options - -`Partial`\<[`AngularThrottlerOptions`](AngularThrottlerOptions.md)\<`TFn`, `TSelected`\>\> - -#### Returns - -`void` - -*** - ### state ```ts -readonly state: Signal>; +readonly state: Signal>; ``` -Defined in: [throttler/injectThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L39) - -Reactive state signal that will be updated when the throttler state changes - -Use this instead of `throttler.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [throttler/injectThrottler.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L44) - -#### Deprecated +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:51 -Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. -The state on the store object is not reactive in Angular signals. +The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md b/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md new file mode 100644 index 000000000..107256367 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md @@ -0,0 +1,65 @@ +--- +id: AngularThrottlerComputed +title: AngularThrottlerComputed +--- + +Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `throttler` attribute exposes the underlying Throttler methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularThrottlerComputed(): TValue; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15 + +A readonly Angular value signal with its underlying utility ref. +The `throttler` attribute exposes the underlying Throttler methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### throttler + +```ts +throttler: AngularThrottler<(value) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:20 + +The underlying Angular Throttler ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md index 634c8daf3..c98cba67a 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md @@ -3,7 +3,9 @@ id: AngularThrottlerOptions title: AngularThrottlerOptions --- -Defined in: [throttler/injectThrottler.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L15) +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:23 + +Options for [injectThrottler](../functions/injectThrottler.md), including core configuration and Angular cleanup. ## Extends @@ -15,29 +17,24 @@ Defined in: [throttler/injectThrottler.ts:15](https://github.com/TanStack/pacer/ `TFn` *extends* `AnyFunction` -### TSelected - -`TSelected` = \{ -\} - ## Properties ### onUnmount? ```ts -optional onUnmount?: (throttler) => void; +optional onUnmount?: (core) => void; ``` -Defined in: [throttler/injectThrottler.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L23) +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:30 -Optional callback invoked when the component is destroyed. Receives the throttler instance. -When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. +Called when the owning injection context is destroyed. Receives the core instance. +Providing this callback replaces the default cleanup (cancel pending execution). #### Parameters -##### throttler +##### core -[`AngularThrottler`](AngularThrottler.md)\<`TFn`, `TSelected`\> +`Throttler`\<`TFn`\> #### Returns diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md new file mode 100644 index 000000000..406965f43 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md @@ -0,0 +1,109 @@ +--- +id: AngularThrottlerSignal +title: AngularThrottlerSignal +--- + +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `throttler` attribute exposes the underlying Throttler methods. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularThrottlerSignal(): TValue; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16 + +A readonly Angular value signal with paced `set`/`update` methods. +The `throttler` attribute exposes the underlying Throttler methods. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### set + +```ts +set: (value) => void; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:21 + +Schedules the replacement value according to leading and trailing throttling options. + +#### Parameters + +##### value + +`TValue` + +#### Returns + +`void` + +*** + +### throttler + +```ts +throttler: AngularThrottler<(callback) => void, TSelected>; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:25 + +The underlying Angular Throttler ref for controlling execution. + +*** + +### update + +```ts +update: (updateFn) => void; +``` + +Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:23 + +Runs the updater with the current committed value when the write executes. + +#### Parameters + +##### updateFn + +(`previous`) => `TValue` + +#### Returns + +`void` diff --git a/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md b/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md deleted file mode 100644 index a6281e31b..000000000 --- a/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -id: AsyncQueuedSignal -title: AsyncQueuedSignal ---- - -Defined in: [async-queuer/injectAsyncQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L10) - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -AsyncQueuedSignal(): TValue[]; -``` - -Defined in: [async-queuer/injectAsyncQueuedSignal.ts:11](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L11) - -## Returns - -`TValue`[] - -## Properties - -### addItem - -```ts -addItem: (item, position?, runOnItemsChange?) => boolean; -``` - -Defined in: [async-queuer/injectAsyncQueuedSignal.ts:12](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L12) - -Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. -Items can be inserted based on priority or at the front/back depending on configuration. -`undefined` cannot be queued (it is the internal "no item" sentinel) and is always rejected. - -#### Parameters - -##### item - -`TValue` - -##### position? - -`QueuePosition` - -##### runOnItemsChange? - -`boolean` - -#### Returns - -`boolean` - -#### Example - -```ts -queuer.addItem({ value: 'task', priority: 10 }); -queuer.addItem('task2', 'front'); -``` - -*** - -### queuer - -```ts -queuer: AngularAsyncQueuer; -``` - -Defined in: [async-queuer/injectAsyncQueuedSignal.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L13) diff --git a/docs/framework/angular/reference/interfaces/DebouncedSignal.md b/docs/framework/angular/reference/interfaces/DebouncedSignal.md deleted file mode 100644 index 5bcc3b81c..000000000 --- a/docs/framework/angular/reference/interfaces/DebouncedSignal.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: DebouncedSignal -title: DebouncedSignal ---- - -Defined in: [debouncer/injectDebouncedSignal.ts:12](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L12) - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -DebouncedSignal(): TValue; -``` - -Defined in: [debouncer/injectDebouncedSignal.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L13) - -## Returns - -`TValue` - -## Properties - -### debouncer - -```ts -debouncer: AngularDebouncer, TSelected>; -``` - -Defined in: [debouncer/injectDebouncedSignal.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L15) - -*** - -### set - -```ts -set: Setter; -``` - -Defined in: [debouncer/injectDebouncedSignal.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L14) diff --git a/docs/framework/angular/reference/interfaces/PacerProviderOptions.md b/docs/framework/angular/reference/interfaces/PacerProviderOptions.md new file mode 100644 index 000000000..2b4d7af86 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/PacerProviderOptions.md @@ -0,0 +1,129 @@ +--- +id: PacerProviderOptions +title: PacerProviderOptions +--- + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:18 + +Scoped default options for synchronous and asynchronous Pacer adapters. +Local utility options override these defaults. + +## Properties + +### asyncBatcher? + +```ts +optional asyncBatcher?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:20 + +Default options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md). + +*** + +### asyncDebouncer? + +```ts +optional asyncDebouncer?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:22 + +Default options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md). + +*** + +### asyncQueuer? + +```ts +optional asyncQueuer?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:24 + +Default options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md). + +*** + +### asyncRateLimiter? + +```ts +optional asyncRateLimiter?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:26 + +Default options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md). + +*** + +### asyncThrottler? + +```ts +optional asyncThrottler?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:28 + +Default options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md). + +*** + +### batcher? + +```ts +optional batcher?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:30 + +Default options for [injectBatcher](../functions/injectBatcher.md). + +*** + +### debouncer? + +```ts +optional debouncer?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:32 + +Default options for [injectDebouncer](../functions/injectDebouncer.md). + +*** + +### queuer? + +```ts +optional queuer?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:34 + +Default options for [injectQueuer](../functions/injectQueuer.md). + +*** + +### rateLimiter? + +```ts +optional rateLimiter?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:36 + +Default options for [injectRateLimiter](../functions/injectRateLimiter.md). + +*** + +### throttler? + +```ts +optional throttler?: Partial>; +``` + +Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:38 + +Default options for [injectThrottler](../functions/injectThrottler.md). diff --git a/docs/framework/angular/reference/interfaces/QueuedSignal.md b/docs/framework/angular/reference/interfaces/QueuedSignal.md deleted file mode 100644 index 11e932054..000000000 --- a/docs/framework/angular/reference/interfaces/QueuedSignal.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: QueuedSignal -title: QueuedSignal ---- - -Defined in: [queuer/injectQueuedSignal.ts:7](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L7) - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -QueuedSignal(): TValue[]; -``` - -Defined in: [queuer/injectQueuedSignal.ts:8](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L8) - -## Returns - -`TValue`[] - -## Properties - -### addItem - -```ts -addItem: (item, position?, runOnItemsChange?) => boolean; -``` - -Defined in: [queuer/injectQueuedSignal.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L9) - -Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. -`undefined` cannot be queued (it is the internal "no item" sentinel) and is always rejected. -Items can be inserted based on priority or at the front/back depending on configuration. - -Returns true if the item was added, false if the queue is full. - -Example usage: -```ts -queuer.addItem('task'); -queuer.addItem('task2', 'front'); -``` - -#### Parameters - -##### item - -`TValue` - -##### position? - -`QueuePosition` - -##### runOnItemsChange? - -`boolean` - -#### Returns - -`boolean` - -*** - -### queuer - -```ts -queuer: AngularQueuer; -``` - -Defined in: [queuer/injectQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L10) diff --git a/docs/framework/angular/reference/interfaces/RateLimitedSignal.md b/docs/framework/angular/reference/interfaces/RateLimitedSignal.md deleted file mode 100644 index 0a4aca77c..000000000 --- a/docs/framework/angular/reference/interfaces/RateLimitedSignal.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: RateLimitedSignal -title: RateLimitedSignal ---- - -Defined in: [rate-limiter/injectRateLimitedSignal.ts:12](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L12) - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -RateLimitedSignal(): TValue; -``` - -Defined in: [rate-limiter/injectRateLimitedSignal.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L13) - -## Returns - -`TValue` - -## Properties - -### rateLimiter - -```ts -rateLimiter: AngularRateLimiter, TSelected>; -``` - -Defined in: [rate-limiter/injectRateLimitedSignal.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L15) - -*** - -### set - -```ts -set: Setter; -``` - -Defined in: [rate-limiter/injectRateLimitedSignal.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L14) diff --git a/docs/framework/angular/reference/interfaces/ThrottledSignal.md b/docs/framework/angular/reference/interfaces/ThrottledSignal.md deleted file mode 100644 index 9be65c827..000000000 --- a/docs/framework/angular/reference/interfaces/ThrottledSignal.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: ThrottledSignal -title: ThrottledSignal ---- - -Defined in: [throttler/injectThrottledSignal.ts:12](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L12) - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -ThrottledSignal(): TValue; -``` - -Defined in: [throttler/injectThrottledSignal.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L13) - -## Returns - -`TValue` - -## Properties - -### set - -```ts -set: Setter; -``` - -Defined in: [throttler/injectThrottledSignal.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L14) - -*** - -### throttler - -```ts -throttler: AngularThrottler, TSelected>; -``` - -Defined in: [throttler/injectThrottledSignal.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L15) diff --git a/docs/framework/angular/reference/type-aliases/AngularPacerOptions.md b/docs/framework/angular/reference/type-aliases/AngularPacerOptions.md deleted file mode 100644 index 03e12b8ca..000000000 --- a/docs/framework/angular/reference/type-aliases/AngularPacerOptions.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: AngularPacerOptions -title: AngularPacerOptions ---- - -```ts -type AngularPacerOptions = TOptions | (() => TOptions); -``` - -Defined in: [types/index.ts:4](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/types/index.ts#L4) - -Options with optional reactive getters, or a factory that reads reactive values. - -## Type Parameters - -### TOptions - -`TOptions` diff --git a/docs/framework/angular/reference/type-aliases/QueuedValueSignal.md b/docs/framework/angular/reference/type-aliases/QueuedValueSignal.md deleted file mode 100644 index 1621461d1..000000000 --- a/docs/framework/angular/reference/type-aliases/QueuedValueSignal.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -id: QueuedValueSignal -title: QueuedValueSignal ---- - -```ts -type QueuedValueSignal = Signal & object; -``` - -Defined in: [queuer/injectQueuedValue.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L9) - -A processed value signal with methods for adding and controlling queued values. - -## Type Declaration - -### addItem - -```ts -addItem: AngularQueuer["addItem"]; -``` - -### queuer - -```ts -queuer: AngularQueuer; -``` - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} diff --git a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts index 8a3677559..9bb5a354a 100644 --- a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts +++ b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts @@ -1,149 +1,167 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { AsyncBatcher } from '@tanstack/pacer/async-batcher' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { - AsyncBatcherOptions, - AsyncBatcherState, -} from '@tanstack/pacer/async-batcher' +import type { AsyncBatcherOptions, AsyncBatcherState } from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectAsyncBatcher}, including core configuration and Angular cleanup. + */ export interface AngularAsyncBatcherOptions< TValue, - TSelected = {}, > extends AsyncBatcherOptions { /** - * Optional callback invoked when the component is destroyed. Receives the batcher instance. - * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending batches and abort running work). */ - onUnmount?: (batcher: AngularAsyncBatcher) => void + onUnmount?: (batcher: AsyncBatcher) => void } -export interface AngularAsyncBatcher extends Omit< +const asyncBatcherMethods = [ + 'addItem', + 'flush', + 'clear', + 'abort', + 'cancel', + 'reset', + 'getAbortSignal', + 'peekAllItems', + 'peekFailedItems', +] as const satisfies ReadonlyArray>> + +type AsyncBatcherMethod = (typeof asyncBatcherMethods)[number] + +/** + * An Angular AsyncBatcher ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ +export interface AngularAsyncBatcher extends Pick< AsyncBatcher, - 'store' | 'options' | 'setOptions' + AsyncBatcherMethod > { - options: AsyncBatcher['options'] & - AngularAsyncBatcherOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the async batcher state changes - * - * Use this instead of `batcher.store.state` - */ - readonly state: Signal> /** - * @deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. + * Reactive state that will be updated when the batcher state changes */ - readonly store: Store>> + readonly state: Signal> } /** - * An Angular function that creates and manages an AsyncBatcher instance. + * Creates and manages an Angular AsyncBatcher in the current injection context. * - * This is a lower-level function that provides direct access to the AsyncBatcher's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Collects items and processes each batch asynchronously when its size or delay threshold is reached. * - * The AsyncBatcher collects items and processes them in batches asynchronously with support for - * promise-based processing, error handling, retry capabilities, and abort support. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * ## Cleanup on Destroy + * The default cleanup is to cancel pending batches and abort running work. Set `onUnmount` to replace it. * - * By default, the function cancels any pending batch and aborts in-flight work when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const batcher = injectAsyncBatcher(fn, { - * maxSize: 10, - * onUnmount: (b) => b.flush() - * }); - * ``` - * - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const batcher = injectAsyncBatcher( - * async (items: Array) => { - * const response = await fetch('/api/batch', { - * method: 'POST', - * body: JSON.stringify(items) - * }); - * return response.json(); - * }, - * { maxSize: 10, wait: 2000 } - * ); - * - * // Add items - * batcher.addItem(data1); - * batcher.addItem(data2); - * - * // Access the selected state - * const { items, isExecuting } = batcher.state(); + * // In a component or service injection context. + * const utility = injectAsyncBatcher( + * (items: Array) => Promise.resolve(items), + * () => ({ maxSize: 10, wait: 100 }), + * (state) => state.isPending, + * ) + * utility.addItem('job') + * console.log(utility.state()) * ``` */ -export function injectAsyncBatcher( +export function injectAsyncBatcher( fn: (items: Array) => Promise, - options: AngularPacerOptions< - AngularAsyncBatcherOptions - > = {}, - selector: (state: AsyncBatcherState) => TSelected = () => - ({}) as TSelected, -): AngularAsyncBatcher { - return injectReactiveOptions< - AngularAsyncBatcherOptions, - AngularAsyncBatcher - >( - options, - injectPacerOptions(), - 'asyncBatcher', - (mergedOptions, getPublicInstance) => { - const batcher = new AsyncBatcher(fn, mergedOptions) - const state = injectSelector(batcher.store, selector) - - const result = { - ...batcher, - get options() { - return batcher.options - }, - set options(value) { - batcher.options = value - }, - state, - } as AngularAsyncBatcher - - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - batcher.options as AngularAsyncBatcherOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - batcher.cancel() - batcher.abort() - } - }) - - return result + options?: MaybeAccessor>, +): AngularAsyncBatcher +/** + * Creates an Angular AsyncBatcher with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectAsyncBatcher( + fn: (items: Array) => Promise, + options: MaybeAccessor>, + selector: (state: AsyncBatcherState) => TSelected, +): AngularAsyncBatcher +export function injectAsyncBatcher( + fn: (items: Array) => Promise, + options: MaybeAccessor> = {}, + selector: (state: AsyncBatcherState) => TSelected | {} = () => ({}), +): AngularAsyncBatcher { + const baseOptions = injectPacerOptions() + + const optionsSignal = toAccessorSignal(options) + + const mergedOptions = computed(() => ({ + ...baseOptions.asyncBatcher, + ...optionsSignal(), + })) + + const asyncBatcherSignal = injectLazy( + () => new AsyncBatcher(fn, mergedOptions()), + ) + + const methods = injectForwardMethods( + asyncBatcherSignal, + asyncBatcherMethods, + (asyncBatcher) => { + asyncBatcher.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => asyncBatcherSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = asyncBatcherSignal() + + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => asyncBatcherSignal().store, + (state) => state.isPending || state.isExecuting, + ) + + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => asyncBatcherSignal().store, selector, { + compare: shallow, + }) + + return { + state, + ...methods, + } } diff --git a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts index 820d5d5b7..5fb8fe25f 100644 --- a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts +++ b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts @@ -1,173 +1,159 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { + AnyAsyncFunction, AsyncDebouncerOptions, AsyncDebouncerState, -} from '@tanstack/pacer/async-debouncer' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectAsyncDebouncer}, including core configuration and Angular cleanup. + */ export interface AngularAsyncDebouncerOptions< TFn extends AnyAsyncFunction, - TSelected = {}, > extends AsyncDebouncerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the debouncer instance. - * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending execution and abort running work). */ - onUnmount?: (debouncer: AngularAsyncDebouncer) => void + onUnmount?: (core: AsyncDebouncer) => void } +const asyncDebouncerMethods = [ + 'maybeExecute', + 'flush', + 'cancel', + 'abort', + 'reset', + 'getAbortSignal', +] as const satisfies ReadonlyArray>> + +type AsyncDebouncerMethod = (typeof asyncDebouncerMethods)[number] + +/** + * An Angular AsyncDebouncer ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularAsyncDebouncer< TFn extends AnyAsyncFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: AsyncDebouncer['options'] & - AngularAsyncDebouncerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the async debouncer state changes - * - * Use this instead of `debouncer.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> +> extends Pick, AsyncDebouncerMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages an AsyncDebouncer instance. - * - * This is a lower-level function that provides direct access to the AsyncDebouncer's functionality. - * This allows you to integrate it with any state management solution you prefer. - * - * This function provides async debouncing functionality with promise support, error handling, - * retry capabilities, and abort support. + * Creates and manages an Angular AsyncDebouncer in the current injection context. * - * The debouncer will only execute the function after the specified wait time has elapsed - * since the last call. If the function is called again before the wait time expires, the - * timer resets and starts waiting again. + * Waits until calls stop for the configured delay, then runs the latest asynchronous callback. * - * ## State Management and Selector + * ## Options and state * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * Available state properties: - * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge - * - `errorCount`: Number of function executions that have resulted in errors - * - `isExecuting`: Whether the debounced function is currently executing asynchronously - * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `lastResult`: The result from the most recent successful function execution - * - `settleCount`: Number of function executions that have completed (either successfully or with errors) - * - `status`: Current execution status ('disabled' | 'idle' | 'pending' | 'executing' | 'settled') - * - `successCount`: Number of function executions that have completed successfully + * ## Cleanup * - * ## Cleanup on Destroy - * - * By default, the function cancels any pending execution and aborts in-flight work when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const debouncer = injectAsyncDebouncer(fn, { - * wait: 500, - * onUnmount: (d) => d.flush() - * }); - * ``` + * The default cleanup is to cancel pending execution and abort running work. Set `onUnmount` to replace it. * - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const debouncer = injectAsyncDebouncer( - * async (query: string) => { - * const response = await fetch(`/api/search?q=${query}`); - * return response.json(); - * }, - * { wait: 500 } - * ); - * - * // Opt-in to track isExecuting changes (optimized for loading states) - * const debouncer = injectAsyncDebouncer( - * async (query: string) => fetchSearchResults(query), - * { wait: 500 }, - * (state) => ({ isExecuting: state.isExecuting, isPending: state.isPending }) - * ); - * - * // In an event handler - * const handleChange = async (e: Event) => { - * const target = e.target as HTMLInputElement; - * const result = await debouncer.maybeExecute(target.value); - * console.log('Search results:', result); - * }; - * - * // Access the selected state - * const { isExecuting, errorCount } = debouncer.state(); + * // In a component or service injection context. + * const utility = injectAsyncDebouncer( + * (query: string) => Promise.resolve(query), + * () => ({ wait: 250 }), + * (state) => state.isPending, + * ) + * utility.maybeExecute('search') + * console.log(utility.state()) * ``` */ -export function injectAsyncDebouncer< - TFn extends AnyAsyncFunction, - TSelected = {}, ->( +export function injectAsyncDebouncer( fn: TFn, - options: AngularPacerOptions>, - selector: (state: AsyncDebouncerState) => TSelected = () => - ({}) as TSelected, -): AngularAsyncDebouncer { - return injectReactiveOptions< - AngularAsyncDebouncerOptions, - AngularAsyncDebouncer - >( - options, - injectPacerOptions(), - 'asyncDebouncer', - (mergedOptions, getPublicInstance) => { - const debouncer = new AsyncDebouncer(fn, mergedOptions) - const state = injectSelector(debouncer.store, selector) - - const result = { - ...debouncer, - get options() { - return debouncer.options - }, - set options(value) { - debouncer.options = value - }, - state, - } as AngularAsyncDebouncer + options: MaybeAccessor>, +): AngularAsyncDebouncer +/** + * Creates an Angular AsyncDebouncer with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectAsyncDebouncer( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncDebouncerState) => TSelected, +): AngularAsyncDebouncer +export function injectAsyncDebouncer( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncDebouncerState) => TSelected | {} = () => ({}), +): AngularAsyncDebouncer { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.asyncDebouncer, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - debouncer.options as AngularAsyncDebouncerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - debouncer.cancel() - debouncer.abort() - } - }) + const asyncDebouncerSignal = injectLazy( + () => new AsyncDebouncer(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + asyncDebouncerSignal, + asyncDebouncerMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => asyncDebouncerSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = asyncDebouncerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => asyncDebouncerSignal().store, + (state) => state.isPending || state.isExecuting, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => asyncDebouncerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/async-queuer/index.ts b/packages/angular-pacer/src/async-queuer/index.ts index d2c48a3c4..324d8104c 100644 --- a/packages/angular-pacer/src/async-queuer/index.ts +++ b/packages/angular-pacer/src/async-queuer/index.ts @@ -1,4 +1,4 @@ export * from '@tanstack/pacer/async-queuer' -export * from './injectAsyncQueuedSignal' +export * from './injectAsyncQueuedItems' export * from './injectAsyncQueuer' diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts new file mode 100644 index 000000000..8e76f453e --- /dev/null +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts @@ -0,0 +1,81 @@ +import { shallow } from '@tanstack/store' +import { computed } from '@angular/core' +import { injectAsyncQueuer } from './injectAsyncQueuer' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { AsyncQueuerState } from '@tanstack/pacer/async-queuer' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { Signal } from '@angular/core' +import type { + AngularAsyncQueuer, + AngularAsyncQueuerOptions, +} from './injectAsyncQueuer' + +/** + * A readonly signal of pending queue items, with the Angular queuer attached. + */ +export interface AngularAsyncQueuerItems extends Signal< + ReadonlyArray +> { + /** The underlying utility ref for adding, processing, and inspecting items. */ + queuer: AngularAsyncQueuer + /** Shortcut for queuer.addItem, with the same arguments and return value. */ + addItem: AngularAsyncQueuer['addItem'] +} + +/** + * Creates a readonly Angular signal of pending asynchronous queue items. + * + * Items appear when added and disappear when removed for processing. This signal + * contains waiting items, not processing results. Use the attached `queuer` methods + * to add items, start or stop processing, or inspect the queue. + * + * Options accept a static object or reactive factory and follow the underlying + * queuer's lifecycle and provider behavior. The returned function is an Angular signal. + * + * @param fn The callback that processes each queued item. + * @param options Core queue options or a reactive options factory. + * @returns A readonly items signal with `queuer` and `addItem` attributes. + * + * @example + * ```ts + * // In a component or service injection context. + * const items = injectAsyncQueuerItems( + * async (item: string) => console.log(item), + * { started: false }, + * ) + * items.addItem('job') + * console.log(items()) // ['job'] + * items.queuer.start() + * ``` + */ +export function injectAsyncQueuerItems( + fn: (value: TValue) => Promise, + options?: MaybeAccessor>, +): AngularAsyncQueuerItems +/** + * Creates the items signal with selected state on its attached queuer. + * @param selector Selects reactive state exposed on the attached queuer. + */ +export function injectAsyncQueuerItems( + fn: (value: TValue) => Promise, + options: MaybeAccessor>, + selector: (state: AsyncQueuerState) => TSelected, +): AngularAsyncQueuerItems +export function injectAsyncQueuerItems( + fn: (value: TValue) => Promise, + options: MaybeAccessor> = {}, + selector?: (state: AsyncQueuerState) => TSelected, +): AngularAsyncQueuerItems { + const utility = injectAsyncQueuer(fn, options, (state) => ({ + items: state.items, + selected: selector ? selector(state) : {}, + })) + const items = computed(() => utility.state().items, { equal: shallow }) + const queuer = { + ...utility, + state: computed(() => utility.state().selected, { + equal: shallow, + }) as Signal>, + } + return Object.assign(items, { queuer, addItem: utility.addItem }) +} diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts deleted file mode 100644 index fc12be322..000000000 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { computed } from '@angular/core' -import { injectAsyncQueuer } from './injectAsyncQueuer' -import type { AngularPacerOptions } from '../types' -import type { AngularAsyncQueuer } from './injectAsyncQueuer' -import type { - AsyncQueuerOptions, - AsyncQueuerState, -} from '@tanstack/pacer/async-queuer' - -export interface AsyncQueuedSignal { - (): Array - addItem: AngularAsyncQueuer['addItem'] - queuer: AngularAsyncQueuer -} - -/** - * An Angular function that creates an async queuer with managed state, combining Angular's signals with async queuing functionality. - * This function provides both the current queue state and queue control methods. - * - * The queue state is automatically updated whenever items are added, removed, or processed in the queue. - * All queue operations are reflected in the state array returned by the function. - * - * The function returns a callable object: - * - `queued()`: Get the current queue items as an array - * - `queued.addItem(...)`: Add an item to the queue - * - `queued.queue`: The queuer instance with additional control methods - * - * @example - * ```ts - * // Default behavior - track items - * const queued = injectAsyncQueuedSignal( - * async (item) => { - * const response = await fetch('/api/process', { - * method: 'POST', - * body: JSON.stringify(item) - * }); - * return response.json(); - * }, - * { concurrency: 2, wait: 1000 } - * ); - * - * // Add items - * queued.addItem(data1); - * - * // Access items - * console.log(queued()); // [data1, ...] - * - * // Control the queue - * queued.queuer.start(); - * queued.queuer.stop(); - * ``` - */ -export function injectAsyncQueuedSignal< - TValue, - TSelected extends Pick, 'items'> = Pick< - AsyncQueuerState, - 'items' - >, ->( - fn: (value: TValue) => Promise, - options: AngularPacerOptions> = {}, - selector: (state: AsyncQueuerState) => TSelected = (state) => - ({ items: state.items }) as TSelected, -): AsyncQueuedSignal { - const queuer = injectAsyncQueuer(fn, options, selector) - - const items = computed(() => queuer.state().items as Array) - - const queued = Object.assign(items, { - addItem: (...args: Parameters) => - queuer.addItem(...args), - queuer, - }) as AsyncQueuedSignal - - return queued -} diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts index 852c04dfb..06ad44ade 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts @@ -1,149 +1,164 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { AsyncQueuer } from '@tanstack/pacer/async-queuer' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { - AsyncQueuerOptions, - AsyncQueuerState, -} from '@tanstack/pacer/async-queuer' +import type { AsyncQueuerOptions, AsyncQueuerState } from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectAsyncQueuer}, including core configuration and Angular cleanup. + */ export interface AngularAsyncQueuerOptions< TValue, - TSelected = {}, > extends AsyncQueuerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the queuer instance. - * When provided, replaces the default cleanup (stop + abort); use it to call flush(), stop(), add logging, etc. - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (stop automatic processing and abort running work). */ - onUnmount?: (queuer: AngularAsyncQueuer) => void + onUnmount?: (core: AsyncQueuer) => void } -export interface AngularAsyncQueuer extends Omit< +const asyncQueuerMethods = [ + 'addItem', + 'getNextItem', + 'execute', + 'flush', + 'flushAsBatch', + 'peekNextItem', + 'peekAllItems', + 'peekActiveItems', + 'peekPendingItems', + 'start', + 'stop', + 'clear', + 'abort', + 'reset', + 'getAbortSignal', +] as const satisfies ReadonlyArray>> + +type AsyncQueuerMethod = (typeof asyncQueuerMethods)[number] + +/** + * An Angular AsyncQueuer ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ +export interface AngularAsyncQueuer extends Pick< AsyncQueuer, - 'store' | 'options' | 'setOptions' + AsyncQueuerMethod > { - options: AsyncQueuer['options'] & - AngularAsyncQueuerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the async queuer state changes - * - * Use this instead of `queuer.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages an AsyncQueuer instance. + * Creates and manages an Angular AsyncQueuer in the current injection context. * - * This is a lower-level function that provides direct access to the AsyncQueuer's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Processes queued items asynchronously with configurable pacing and concurrency. * - * The AsyncQueuer processes items asynchronously with support for concurrent execution, - * promise-based processing, error handling, retry capabilities, and abort support. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * ## Cleanup on Destroy - * - * By default, the function stops the queuer and aborts in-flight work when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending items instead: - * - * ```ts - * const queuer = injectAsyncQueuer(fn, { - * concurrency: 2, - * onUnmount: (q) => q.flush() - * }); - * ``` + * The default cleanup is to stop automatic processing and abort running work. Set `onUnmount` to replace it. * - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const queuer = injectAsyncQueuer( - * async (item: Data) => { - * const response = await fetch('/api/process', { - * method: 'POST', - * body: JSON.stringify(item) - * }); - * return response.json(); - * }, - * { concurrency: 2, wait: 1000 } - * ); - * - * // Add items - * queuer.addItem(data1); - * queuer.addItem(data2); - * - * // Access the selected state - * const { items, isExecuting } = queuer.state(); + * // In a component or service injection context. + * const utility = injectAsyncQueuer( + * (item: string) => Promise.resolve(item), + * () => ({ wait: 100, concurrency: 2 }), + * (state) => state.items, + * ) + * utility.addItem('job') + * console.log(utility.state()) * ``` */ -export function injectAsyncQueuer( - fn: (value: TValue) => Promise, - options: AngularPacerOptions< - AngularAsyncQueuerOptions - > = {}, - selector: (state: AsyncQueuerState) => TSelected = () => - ({}) as TSelected, -): AngularAsyncQueuer { - return injectReactiveOptions< - AngularAsyncQueuerOptions, - AngularAsyncQueuer - >( - options, - injectPacerOptions(), - 'asyncQueuer', - (mergedOptions, getPublicInstance) => { - const queuer = new AsyncQueuer(fn, mergedOptions) - const state = injectSelector(queuer.store, selector) - - const result = { - ...queuer, - get options() { - return queuer.options - }, - set options(value) { - queuer.options = value - }, - state, - } as AngularAsyncQueuer +export function injectAsyncQueuer( + fn: (item: TValue) => Promise, + options?: MaybeAccessor>, +): AngularAsyncQueuer +/** + * Creates an Angular AsyncQueuer with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectAsyncQueuer( + fn: (item: TValue) => Promise, + options: MaybeAccessor>, + selector: (state: AsyncQueuerState) => TSelected, +): AngularAsyncQueuer +export function injectAsyncQueuer( + fn: (item: TValue) => Promise, + options: MaybeAccessor> = {}, + selector: (state: AsyncQueuerState) => TSelected | {} = () => ({}), +): AngularAsyncQueuer { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed(() => ({ + ...baseOptions.asyncQueuer, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - queuer.options as AngularAsyncQueuerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - queuer.stop() - queuer.abort() - } - }) + const asyncQueuerSignal = injectLazy( + () => new AsyncQueuer(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + asyncQueuerSignal, + asyncQueuerMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => asyncQueuerSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = asyncQueuerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.stop() + core.abort() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => asyncQueuerSignal().store, + (state) => state.isExecuting || (state.isRunning && state.pendingTick), + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => asyncQueuerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index a5994f776..ee6afa813 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -1,136 +1,161 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { + AnyAsyncFunction, AsyncRateLimiterOptions, AsyncRateLimiterState, -} from '@tanstack/pacer/async-rate-limiter' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectAsyncRateLimiter}, including core configuration and Angular cleanup. + */ export interface AngularAsyncRateLimiterOptions< TFn extends AnyAsyncFunction, - TSelected = {}, > extends AsyncRateLimiterOptions { /** - * Optional callback invoked when the component is destroyed. Receives the rate limiter instance. - * When provided, replaces the default cleanup (abort). + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (abort running work and reset the limiter). */ - onUnmount?: (rateLimiter: AngularAsyncRateLimiter) => void + onUnmount?: (core: AsyncRateLimiter) => void } +const asyncRateLimiterMethods = [ + 'maybeExecute', + 'getRemainingInWindow', + 'getMsUntilNextWindow', + 'abort', + 'reset', + 'getAbortSignal', +] as const satisfies ReadonlyArray< + MethodKeys> +> + +type AsyncRateLimiterMethod = (typeof asyncRateLimiterMethods)[number] + +/** + * An Angular AsyncRateLimiter ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularAsyncRateLimiter< TFn extends AnyAsyncFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: AsyncRateLimiter['options'] & - AngularAsyncRateLimiterOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the async rate limiter state changes - * - * Use this instead of `rateLimiter.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> +> extends Pick, AsyncRateLimiterMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages an AsyncRateLimiter instance. + * Creates and manages an Angular AsyncRateLimiter in the current injection context. * - * This is a lower-level function that provides direct access to the AsyncRateLimiter's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Allows asynchronous calls up to the configured limit within a fixed or sliding window. Calls beyond the limit are rejected rather than queued. * - * This function provides async rate limiting functionality with promise support, error handling, - * retry capabilities, and abort support. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * ## Cleanup on Destroy + * The default cleanup is to abort running work and reset the limiter. Set `onUnmount` to replace it. * - * By default, the function aborts in-flight work when the component is destroyed. - * Use the `onUnmount` option to customize this. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const rateLimiter = injectAsyncRateLimiter( - * async (id: string) => { - * const response = await fetch(`/api/data/${id}`); - * return response.json(); - * }, - * { limit: 5, window: 60000, windowType: 'sliding' } - * ); - * - * // In an event handler - * const handleRequest = async (id: string) => { - * const result = await rateLimiter.maybeExecute(id); - * console.log('Result:', result); - * }; + * // In a component or service injection context. + * const utility = injectAsyncRateLimiter( + * (query: string) => Promise.resolve(query), + * () => ({ limit: 5, window: 1000 }), + * (state) => state.isExecuting, + * ) + * utility.maybeExecute('search') + * console.log(utility.state()) * ``` */ -export function injectAsyncRateLimiter< - TFn extends AnyAsyncFunction, - TSelected = {}, ->( +export function injectAsyncRateLimiter( fn: TFn, - options: AngularPacerOptions>, - selector: (state: AsyncRateLimiterState) => TSelected = () => - ({}) as TSelected, -): AngularAsyncRateLimiter { - return injectReactiveOptions< - AngularAsyncRateLimiterOptions, - AngularAsyncRateLimiter - >( - options, - injectPacerOptions(), - 'asyncRateLimiter', - (mergedOptions, getPublicInstance) => { - const rateLimiter = new AsyncRateLimiter(fn, mergedOptions) - const state = injectSelector(rateLimiter.store, selector) - - const result = { - ...rateLimiter, - get options() { - return rateLimiter.options - }, - set options(value) { - rateLimiter.options = value - }, - state, - } as AngularAsyncRateLimiter + options: MaybeAccessor>, +): AngularAsyncRateLimiter +/** + * Creates an Angular AsyncRateLimiter with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectAsyncRateLimiter( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncRateLimiterState) => TSelected, +): AngularAsyncRateLimiter +export function injectAsyncRateLimiter( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncRateLimiterState) => TSelected | {} = () => ({}), +): AngularAsyncRateLimiter { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.asyncRateLimiter, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - rateLimiter.options as AngularAsyncRateLimiterOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - rateLimiter.abort() - } - }) + const asyncRateLimiterSignal = injectLazy( + () => new AsyncRateLimiter(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + asyncRateLimiterSignal, + asyncRateLimiterMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => asyncRateLimiterSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = asyncRateLimiterSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.abort() + core.reset() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => asyncRateLimiterSignal().store, + (state) => state.isExecuting, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => asyncRateLimiterSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts index 833a4b93b..230cd4c36 100644 --- a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts +++ b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts @@ -1,152 +1,159 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { AsyncThrottler } from '@tanstack/pacer/async-throttler' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { + AnyAsyncFunction, AsyncThrottlerOptions, AsyncThrottlerState, -} from '@tanstack/pacer/async-throttler' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectAsyncThrottler}, including core configuration and Angular cleanup. + */ export interface AngularAsyncThrottlerOptions< TFn extends AnyAsyncFunction, - TSelected = {}, > extends AsyncThrottlerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the throttler instance. - * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc. - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending execution and abort running work). */ - onUnmount?: (throttler: AngularAsyncThrottler) => void + onUnmount?: (core: AsyncThrottler) => void } +const asyncThrottlerMethods = [ + 'maybeExecute', + 'flush', + 'cancel', + 'abort', + 'reset', + 'getAbortSignal', +] as const satisfies ReadonlyArray>> + +type AsyncThrottlerMethod = (typeof asyncThrottlerMethods)[number] + +/** + * An Angular AsyncThrottler ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularAsyncThrottler< TFn extends AnyAsyncFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: AsyncThrottler['options'] & - AngularAsyncThrottlerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the async throttler state changes - * - * Use this instead of `throttler.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> +> extends Pick, AsyncThrottlerMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages an AsyncThrottler instance. - * - * This is a lower-level function that provides direct access to the AsyncThrottler's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Creates and manages an Angular AsyncThrottler in the current injection context. * - * This function provides async throttling functionality with promise support, error handling, - * retry capabilities, and abort support. + * Limits asynchronous executions to the configured interval, with leading and trailing calls. Later calls replace the pending trailing arguments without restarting the interval. * - * The throttler will execute the function at most once within the specified wait time. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * ## Cleanup on Destroy - * - * By default, the function cancels any pending execution and aborts in-flight work when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const throttler = injectAsyncThrottler(fn, { - * wait: 1000, - * onUnmount: (t) => t.flush() - * }); - * ``` + * The default cleanup is to cancel pending execution and abort running work. Set `onUnmount` to replace it. * - * When using onUnmount with flush, guard your callbacks since the component may already be destroyed. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const throttler = injectAsyncThrottler( - * async (data: Data) => { - * const response = await fetch('/api/update', { - * method: 'POST', - * body: JSON.stringify(data) - * }); - * return response.json(); - * }, - * { wait: 1000 } - * ); - * - * // In an event handler - * const handleUpdate = async (data: Data) => { - * const result = await throttler.maybeExecute(data); - * console.log('Update result:', result); - * }; + * // In a component or service injection context. + * const utility = injectAsyncThrottler( + * (value: number) => Promise.resolve(value), + * () => ({ wait: 100 }), + * (state) => state.isPending, + * ) + * utility.maybeExecute(42) + * console.log(utility.state()) * ``` */ -export function injectAsyncThrottler< - TFn extends AnyAsyncFunction, - TSelected = {}, ->( +export function injectAsyncThrottler( fn: TFn, - options: AngularPacerOptions>, - selector: (state: AsyncThrottlerState) => TSelected = () => - ({}) as TSelected, -): AngularAsyncThrottler { - return injectReactiveOptions< - AngularAsyncThrottlerOptions, - AngularAsyncThrottler - >( - options, - injectPacerOptions(), - 'asyncThrottler', - (mergedOptions, getPublicInstance) => { - const throttler = new AsyncThrottler(fn, mergedOptions) - const state = injectSelector(throttler.store, selector) - - const result = { - ...throttler, - get options() { - return throttler.options - }, - set options(value) { - throttler.options = value - }, - state, - } as AngularAsyncThrottler + options: MaybeAccessor>, +): AngularAsyncThrottler +/** + * Creates an Angular AsyncThrottler with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectAsyncThrottler( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncThrottlerState) => TSelected, +): AngularAsyncThrottler +export function injectAsyncThrottler( + fn: TFn, + options: MaybeAccessor>, + selector: (state: AsyncThrottlerState) => TSelected | {} = () => ({}), +): AngularAsyncThrottler { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.asyncThrottler, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - throttler.options as AngularAsyncThrottlerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - throttler.cancel() - throttler.abort() - } - }) + const asyncThrottlerSignal = injectLazy( + () => new AsyncThrottler(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + asyncThrottlerSignal, + asyncThrottlerMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => asyncThrottlerSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = asyncThrottlerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => asyncThrottlerSignal().store, + (state) => state.isPending || state.isExecuting, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => asyncThrottlerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/batcher/injectBatcher.ts b/packages/angular-pacer/src/batcher/injectBatcher.ts index d76c9b1fb..eb386c442 100644 --- a/packages/angular-pacer/src/batcher/injectBatcher.ts +++ b/packages/angular-pacer/src/batcher/injectBatcher.ts @@ -1,134 +1,152 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { Batcher } from '@tanstack/pacer/batcher' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' +import type { BatcherOptions, BatcherState } from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' -export interface AngularBatcherOptions< - TValue, - TSelected = {}, -> extends BatcherOptions { +/** + * Options for {@link injectBatcher}, including core configuration and Angular cleanup. + */ +export interface AngularBatcherOptions extends BatcherOptions { /** - * Optional callback invoked when the component is destroyed. Receives the batcher instance. - * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending batches). */ - onUnmount?: (batcher: AngularBatcher) => void + onUnmount?: (core: Batcher) => void } -export interface AngularBatcher extends Omit< +const batcherMethods = [ + 'addItem', + 'flush', + 'peekAllItems', + 'clear', + 'cancel', + 'reset', +] as const satisfies ReadonlyArray>> + +type BatcherMethod = (typeof batcherMethods)[number] + +/** + * An Angular Batcher ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ +export interface AngularBatcher extends Pick< Batcher, - 'store' | 'options' | 'setOptions' + BatcherMethod > { - options: Batcher['options'] & AngularBatcherOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the batcher state changes - * - * Use this instead of `batcher.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages a Batcher instance. + * Creates and manages an Angular Batcher in the current injection context. * - * This is a lower-level function that provides direct access to the Batcher's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Collects items and processes a batch when its size or delay threshold is reached. * - * The Batcher collects items and processes them in batches based on configurable conditions: - * - Maximum batch size - * - Time-based batching (process after X milliseconds) - * - Custom batch processing logic via getShouldExecute + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * ## Cleanup on Destroy + * The default cleanup is to cancel pending batches. Set `onUnmount` to replace it. * - * By default, the function cancels any pending batch when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const batcher = injectBatcher(fn, { - * maxSize: 5, - * onUnmount: (b) => b.flush() - * }); - * ``` + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const batcher = injectBatcher( - * (items) => console.log('Processing batch:', items), - * { maxSize: 5, wait: 2000 } - * ); - * - * // Add items - * batcher.addItem('task1'); - * - * // Access the selected state - * const { items, isPending } = batcher.state(); + * // In a component or service injection context. + * const utility = injectBatcher( + * (items: Array) => console.log(items), + * () => ({ maxSize: 10, wait: 100 }), + * (state) => state.isPending, + * ) + * utility.addItem('job') + * console.log(utility.state()) * ``` */ -export function injectBatcher( +export function injectBatcher( fn: (items: Array) => void, - options: AngularPacerOptions> = {}, - selector: (state: BatcherState) => TSelected = () => - ({}) as TSelected, -): AngularBatcher { - return injectReactiveOptions< - AngularBatcherOptions, - AngularBatcher - >( - options, - injectPacerOptions(), - 'batcher', - (mergedOptions, getPublicInstance) => { - const batcher = new Batcher(fn, mergedOptions) - const state = injectSelector(batcher.store, selector) - - const result = { - ...batcher, - get options() { - return batcher.options - }, - set options(value) { - batcher.options = value - }, - state, - } as AngularBatcher + options?: MaybeAccessor>, +): AngularBatcher +/** + * Creates an Angular Batcher with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectBatcher( + fn: (items: Array) => void, + options: MaybeAccessor>, + selector: (state: BatcherState) => TSelected, +): AngularBatcher +export function injectBatcher( + fn: (items: Array) => void, + options: MaybeAccessor> = {}, + selector: (state: BatcherState) => TSelected | {} = () => ({}), +): AngularBatcher { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed(() => ({ + ...baseOptions.batcher, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - batcher.options as AngularBatcherOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - batcher.cancel() - } - }) + const batcherSignal = injectLazy( + () => new Batcher(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + batcherSignal, + batcherMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => batcherSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = batcherSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => batcherSignal().store, + (state) => state.isPending, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => batcherSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/debouncer/index.ts b/packages/angular-pacer/src/debouncer/index.ts index 9ffaf8856..583b379ab 100644 --- a/packages/angular-pacer/src/debouncer/index.ts +++ b/packages/angular-pacer/src/debouncer/index.ts @@ -1,5 +1,5 @@ export * from '@tanstack/pacer/debouncer' +export * from './injectDebouncedComputed' export * from './injectDebouncedSignal' -export * from './injectDebouncedValue' export * from './injectDebouncer' diff --git a/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts b/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts new file mode 100644 index 000000000..62bd33790 --- /dev/null +++ b/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts @@ -0,0 +1,76 @@ +import { effect, linkedSignal, untracked } from '@angular/core' +import { injectDebouncer } from './injectDebouncer' +import type { DebouncerState } from '@tanstack/pacer/debouncer' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { + AngularDebouncer, + AngularDebouncerOptions, +} from './injectDebouncer' +import type { Signal } from '@angular/core' + +/** + * A readonly Angular value signal with its underlying utility ref. + * The `debouncer` attribute exposes the underlying Debouncer methods. + */ +export interface AngularDebouncerComputed< + TValue, + TSelected = {}, +> extends Signal { + /** The underlying Angular Debouncer ref for controlling execution. */ + debouncer: AngularDebouncer<(value: TValue) => void, TSelected> +} +/** + * Creates an Angular debounced view of a source signal. + * + * The initial source value is available on first read. Source changes are observed by an effect and applied after the debounce delay; newer changes restart the delay. + * + * The returned value is a real Angular signal with the underlying utility exposed + * on `debouncer`. Options accept a static object or reactive factory and follow + * {@link injectDebouncer} lifecycle and provider behavior. + * + * @param signalToDebounce The source signal or accessor whose changes are observed. + * @param options Core options or a reactive options factory. + * @returns The value signal with a `debouncer` attribute. + * + * @example + * ```ts + * // In a component or service injection context. + * const source = signal('initial') + * const value = injectDebouncedComputed(source, { wait: 250 }) + * source.set('updated') + * console.log(value()) + * ``` + */ +export function injectDebouncedComputed( + signalToDebounce: () => TValue, + options: MaybeAccessor void>>, +): AngularDebouncerComputed +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectDebouncedComputed( + signalToDebounce: () => TValue, + options: MaybeAccessor void>>, + selector: (state: DebouncerState<(value: TValue) => void>) => TSelected, +): AngularDebouncerComputed +export function injectDebouncedComputed( + signalToDebounce: () => TValue, + options: MaybeAccessor void>>, + selector?: (state: DebouncerState<(value: TValue) => void>) => TSelected, +): AngularDebouncerComputed { + const select = (state: DebouncerState<(value: TValue) => void>) => + selector ? selector(state) : {} + const debouncedSignal = linkedSignal(() => untracked(signalToDebounce)) + const debouncer = injectDebouncer( + (value: TValue) => debouncedSignal.set(value), + options, + select, + ) + effect(() => { + debouncer.maybeExecute(signalToDebounce()) + }) + return Object.assign(debouncedSignal.asReadonly(), { + debouncer, + }) +} diff --git a/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts b/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts index 52db513ff..5b7b13dba 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts @@ -1,95 +1,90 @@ -import { signal } from '@angular/core' +import { linkedSignal } from '@angular/core' +import { toAccessorSignal } from '../utils/maybeAccessor' import { injectDebouncer } from './injectDebouncer' -import type { AngularPacerOptions } from '../types' -import type { AngularDebouncer } from './injectDebouncer' +import type { DebouncerState } from '@tanstack/pacer/debouncer' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { - DebouncerOptions, - DebouncerState, -} from '@tanstack/pacer/debouncer' + AngularDebouncer, + AngularDebouncerOptions, +} from './injectDebouncer' +import type { Signal } from '@angular/core' -type Setter = (value: T | ((prev: T) => T)) => void - -export interface DebouncedSignal { - (): TValue - set: Setter - debouncer: AngularDebouncer, TSelected> +/** + * A readonly Angular value signal with paced `set`/`update` methods. + * The `debouncer` attribute exposes the underlying Debouncer methods. + */ +export interface AngularDebouncerSignal< + TValue, + TSelected = {}, +> extends Signal { + /** Schedules the replacement value after the debounce delay. */ + set: (value: TValue) => void + /** Runs the updater with the current committed value when the write executes. */ + update: (updateFn: (previous: TValue) => TValue) => void + /** The underlying Angular Debouncer ref for controlling execution. */ + debouncer: AngularDebouncer<(callback: () => void) => void, TSelected> } - /** - * An Angular function that creates a debounced state signal, combining Angular's signal with debouncing functionality. - * This function provides both the current debounced value and methods to update it. + * Creates an Angular debounced editable signal. * - * The state value is only updated after the specified wait time has elapsed since the last update attempt. - * If another update is attempted before the wait time expires, the timer resets and starts waiting again. - * This is useful for handling frequent state updates that should be throttled, like search input values - * or window resize dimensions. + * The initial value is available synchronously. `set` and `update` debounce writes: a newer write restarts the delay and replaces the pending write. An updater runs against the committed value when the delay expires. * - * The function returns a callable object: - * - `debounced()`: Get the current debounced value - * - `debounced.set(...)`: Set or update the debounced value (debounced via maybeExecute) - * - `debounced.debouncer`: The debouncer instance with additional control methods and state signals + * The returned value is a real Angular signal with the underlying utility exposed + * on `debouncer`. Options accept a static object or reactive factory and follow + * {@link injectDebouncer} lifecycle and provider behavior. * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying debouncer instance. - * The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. Only when you provide a selector will - * the reactive system track the selected state values. - * - * Available debouncer state properties: - * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') + * @param initialValue The initial committed value. + * @param options Core options or a reactive options factory. + * @returns The value signal with `set`, `update`, and a `debouncer` attribute. * * @example * ```ts - * const debouncedQuery = injectDebouncedSignal('', { wait: 500 }) - * - * // Get value - * console.log(debouncedQuery()) - * - * // Set/update value (debounced) - * debouncedQuery.set('hello') - * - * // Access debouncer - * console.log(debouncedQuery.debouncer.state().isPending) + * // In a component or service injection context. + * const value = injectDebouncedSignal(0, { wait: 250 }) + * value.set(10) + * value.update(previous => previous + 1) + * console.log(value()) * ``` */ -export function injectDebouncedSignal( - value: TValue, - initialOptions: AngularPacerOptions>>, - selector?: (state: DebouncerState>) => TSelected, -): DebouncedSignal { - const debouncedValue = signal(value) - +export function injectDebouncedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularDebouncerOptions<(callback: () => void) => void> + >, +): AngularDebouncerSignal +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectDebouncedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularDebouncerOptions<(callback: () => void) => void> + >, + selector: ( + state: DebouncerState<(callback: () => void) => void>, + ) => TSelected, +): AngularDebouncerSignal +export function injectDebouncedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularDebouncerOptions<(callback: () => void) => void> + >, + selector?: ( + state: DebouncerState<(callback: () => void) => void>, + ) => TSelected, +): AngularDebouncerSignal { + const debouncedSignal = linkedSignal(toAccessorSignal(initialValue)) const debouncer = injectDebouncer( - (newValue: TValue | ((prev: TValue) => TValue)) => { - if (typeof newValue === 'function') { - debouncedValue.update(newValue as (prev: TValue) => TValue) - } else { - debouncedValue.set(newValue) - } - }, - initialOptions, - selector, + (callback: () => void) => callback(), + options, + (state) => (selector ? selector(state) : {}), ) - - const set: Setter = ( - newValue: TValue | ((prev: TValue) => TValue), - ) => { - debouncer.maybeExecute(newValue) - } - - const debounced = Object.assign(() => debouncedValue(), { - set, + return Object.assign(debouncedSignal.asReadonly(), { + set: (value: TValue) => + debouncer.maybeExecute(() => debouncedSignal.set(value)), + update: (updateFn: (prev: TValue) => TValue) => + debouncer.maybeExecute(() => debouncedSignal.update(updateFn)), debouncer, - }) as DebouncedSignal - - return debounced + }) } diff --git a/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts b/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts deleted file mode 100644 index c8135d3ee..000000000 --- a/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { effect } from '@angular/core' -import { injectDebouncedSignal } from './injectDebouncedSignal' -import type { AngularPacerOptions } from '../types' -import type { DebouncedSignal } from './injectDebouncedSignal' -import type { Signal } from '@angular/core' -import type { - DebouncerOptions, - DebouncerState, -} from '@tanstack/pacer/debouncer' - -type Setter = (value: T | ((prev: T) => T)) => void - -/** - * An Angular function that creates a debounced value that updates only after a specified delay. - * Unlike injectDebouncedSignal, this function automatically tracks changes to the input signal - * and updates the debounced value accordingly. - * - * The debounced value will only update after the specified wait time has elapsed since - * the last change to the input value. If the input value changes again before the wait - * time expires, the timer resets and starts waiting again. - * - * This is useful for deriving debounced values from signals that change frequently, - * like search queries or form inputs, where you want to limit how often downstream effects - * or calculations occur. - * - * The function returns a tuple containing: - * - A Signal that provides the current debounced value - * - The debouncer instance with control methods - * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying debouncer instance. - * The `selector` parameter allows you to specify which debouncer state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. Only when you provide a selector will - * the reactive system track the selected state values. - * - * Available debouncer state properties: - * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') - * - * - * @example - * ```ts - * // Default behavior - no reactive state subscriptions - * const searchQuery = signal('') - * const debounced = injectDebouncedValue(searchQuery, { - * wait: 500, // Wait 500ms after last change - * }) - * - * // Opt-in to reactive updates when pending state changes (optimized for loading indicators) - * const debouncedWithPending = injectDebouncedValue( - * searchQuery, - * { wait: 500 }, - * (state) => ({ isPending: state.isPending }), - * ) - * - * // Debounced value will update 500ms after searchQuery stops changing - * effect(() => { - * fetchSearchResults(debounced()) - * }) - * - * // Access selected debouncer state via signals (only if you provided a selector) - * effect(() => { - * console.log('Is pending:', debouncedWithPending.state().isPending) - * }) - * - * // Control the debouncer - * debounced.cancel() // Cancel any pending updates - * ``` - */ -export function injectDebouncedValue( - value: Signal, - initialOptions: AngularPacerOptions>>, - selector?: (state: DebouncerState>) => TSelected, -): DebouncedSignal -export function injectDebouncedValue( - value: Signal, - initialValue: TValue, - initialOptions: DebouncerOptions>, - selector?: (state: DebouncerState>) => TSelected, -): DebouncedSignal -export function injectDebouncedValue( - value: Signal, - initialValue: TValue, - initialOptions: () => DebouncerOptions>, - selector: ((state: DebouncerState>) => TSelected) | undefined, -): DebouncedSignal -export function injectDebouncedValue( - value: Signal, - initialValueOrOptions: - TValue | AngularPacerOptions>>, - initialOptionsOrSelector?: - | AngularPacerOptions>> - | ((state: DebouncerState>) => TSelected), - maybeSelector?: (state: DebouncerState>) => TSelected, -): DebouncedSignal { - const hasSelector = typeof initialOptionsOrSelector === 'function' - - const hasInitialValue = - (initialOptionsOrSelector !== undefined && !hasSelector) || - arguments.length >= 4 - - const initialValue = hasInitialValue - ? (initialValueOrOptions as TValue) - : (undefined as unknown as TValue) - const initialOptions = hasInitialValue - ? (initialOptionsOrSelector as AngularPacerOptions< - DebouncerOptions> - >) - : (initialValueOrOptions as AngularPacerOptions< - DebouncerOptions> - >) - const selector = hasInitialValue - ? maybeSelector - : (initialOptionsOrSelector as - ((state: DebouncerState>) => TSelected) | undefined) - - const debounced = injectDebouncedSignal( - initialValue, - initialOptions, - selector, - ) - - effect(() => { - const latest = value() - debounced.set(latest) - }) - - return debounced -} diff --git a/packages/angular-pacer/src/debouncer/injectDebouncer.ts b/packages/angular-pacer/src/debouncer/injectDebouncer.ts index 12127b319..9574c7c2e 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncer.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncer.ts @@ -1,157 +1,156 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { Debouncer } from '@tanstack/pacer/debouncer' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyFunction } from '@tanstack/pacer/types' import type { + AnyFunction, DebouncerOptions, DebouncerState, -} from '@tanstack/pacer/debouncer' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectDebouncer}, including core configuration and Angular cleanup. + */ export interface AngularDebouncerOptions< - TFn extends AnyFunction, - TSelected = {}, + TFn extends AnyFunction = AnyFunction, > extends DebouncerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the debouncer instance. - * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending execution). */ - onUnmount?: (debouncer: AngularDebouncer) => void + onUnmount?: (core: Debouncer) => void } +const debouncerMethods = [ + 'maybeExecute', + 'flush', + 'cancel', + 'reset', +] as const satisfies ReadonlyArray>> + +type DebouncerMethod = (typeof debouncerMethods)[number] + +/** + * An Angular Debouncer ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularDebouncer< - TFn extends AnyFunction, + TFn extends AnyFunction = AnyFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: Debouncer['options'] & AngularDebouncerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the debouncer state changes - * - * Use this instead of `debouncer.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> +> extends Pick, DebouncerMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages a Debouncer instance. - * - * This is a lower-level function that provides direct access to the Debouncer's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Creates and manages an Angular Debouncer in the current injection context. * - * This function provides debouncing functionality to limit how often a function can be called, - * waiting for a specified delay before executing the latest call. This is useful for handling - * frequent events like window resizing, scroll events, or real-time search inputs. + * Waits until calls stop for the configured delay, then runs the latest callback. Each new call restarts the delay. * - * The debouncer will only execute the function after the specified wait time has elapsed - * since the last call. If the function is called again before the wait time expires, the - * timer resets and starts waiting again. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * Available state properties: - * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') + * The default cleanup is to cancel pending execution. Set `onUnmount` to replace it. * - * ## Cleanup on Destroy - * - * By default, the function cancels any pending execution when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const debouncer = injectDebouncer(fn, { - * wait: 500, - * onUnmount: (d) => d.flush() - * }); - * ``` + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const debouncer = injectDebouncer( - * (query: string) => fetchSearchResults(query), - * { wait: 500 } - * ); - * - * // Opt-in to track isPending changes (optimized for loading states) - * const debouncer = injectDebouncer( - * (query: string) => fetchSearchResults(query), - * { wait: 500 }, - * (state) => ({ isPending: state.isPending }) - * ); - * - * // In an event handler - * const handleChange = (e: Event) => { - * const target = e.target as HTMLInputElement - * debouncer.maybeExecute(target.value); - * }; - * - * // Access the selected state (will be empty object {} unless selector provided) - * const { isPending } = debouncer.state(); + * // In a component or service injection context. + * const utility = injectDebouncer( + * (query: string) => console.log(query), + * () => ({ wait: 250 }), + * (state) => state.isPending, + * ) + * utility.maybeExecute('search') + * console.log(utility.state()) * ``` */ -export function injectDebouncer( +export function injectDebouncer( fn: TFn, - options: AngularPacerOptions>, - selector: (state: DebouncerState) => TSelected = () => ({}) as TSelected, -): AngularDebouncer { - return injectReactiveOptions< - AngularDebouncerOptions, - AngularDebouncer - >( - options, - injectPacerOptions(), - 'debouncer', - (mergedOptions, getPublicInstance) => { - const debouncer = new Debouncer(fn, mergedOptions) - const state = injectSelector(debouncer.store, selector) - - const result = { - ...debouncer, - get options() { - return debouncer.options - }, - set options(value) { - debouncer.options = value - }, - state, - } as AngularDebouncer + options: MaybeAccessor>, +): AngularDebouncer +/** + * Creates an Angular Debouncer with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectDebouncer( + fn: TFn, + options: MaybeAccessor>, + selector: (state: DebouncerState) => TSelected, +): AngularDebouncer +export function injectDebouncer( + fn: TFn, + options: MaybeAccessor>, + selector: (state: DebouncerState) => TSelected | {} = () => ({}), +): AngularDebouncer { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.debouncer, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - debouncer.options as AngularDebouncerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - debouncer.cancel() - } - }) + const debouncerSignal = injectLazy( + () => new Debouncer(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + debouncerSignal, + debouncerMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => debouncerSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = debouncerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => debouncerSignal().store, + (state) => state.isPending, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => debouncerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/index.ts b/packages/angular-pacer/src/index.ts index f973792a7..5457bbd3e 100644 --- a/packages/angular-pacer/src/index.ts +++ b/packages/angular-pacer/src/index.ts @@ -1,34 +1,14 @@ +// Re-export the core Pacer APIs, matching the other framework adapters. export * from '@tanstack/pacer' -export * from './provider/pacer-provider' - -export * from './async-batcher/injectAsyncBatcher' - -export * from './async-debouncer/injectAsyncDebouncer' - -export * from './async-queuer/injectAsyncQueuedSignal' -export * from './async-queuer/injectAsyncQueuer' - -export * from './async-rate-limiter/injectAsyncRateLimiter' - -export * from './async-throttler/injectAsyncThrottler' - -export * from './batcher/injectBatcher' - -export * from './debouncer/injectDebouncedSignal' -export * from './debouncer/injectDebouncedValue' -export * from './debouncer/injectDebouncer' - -export * from './queuer/injectQueuedSignal' -export * from './queuer/injectQueuedValue' -export * from './queuer/injectQueuer' - -export * from './rate-limiter/injectRateLimitedSignal' -export * from './rate-limiter/injectRateLimitedValue' -export * from './rate-limiter/injectRateLimiter' - -export * from './throttler/injectThrottledSignal' -export * from './throttler/injectThrottledValue' -export * from './throttler/injectThrottler' - -export type { AngularPacerOptions } from './types' +export * from './async-batcher' +export * from './async-debouncer' +export * from './async-queuer' +export * from './async-rate-limiter' +export * from './async-throttler' +export * from './batcher' +export * from './debouncer' +export * from './provider' +export * from './queuer' +export * from './rate-limiter' +export * from './throttler' diff --git a/packages/angular-pacer/src/provider/index.ts b/packages/angular-pacer/src/provider/index.ts index 1faf1616a..dce1f4cd2 100644 --- a/packages/angular-pacer/src/provider/index.ts +++ b/packages/angular-pacer/src/provider/index.ts @@ -1,2 +1 @@ -export * from './pacer-context' -export * from './pacer-provider' +export * from './providePacerOptions' diff --git a/packages/angular-pacer/src/provider/pacer-context.ts b/packages/angular-pacer/src/provider/pacer-context.ts deleted file mode 100644 index d5bb5879f..000000000 --- a/packages/angular-pacer/src/provider/pacer-context.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { InjectionToken, inject } from '@angular/core' -import type { AngularAsyncBatcherOptions } from '../async-batcher/injectAsyncBatcher' -import type { AngularAsyncDebouncerOptions } from '../async-debouncer/injectAsyncDebouncer' -import type { AngularAsyncQueuerOptions } from '../async-queuer/injectAsyncQueuer' -import type { AngularAsyncRateLimiterOptions } from '../async-rate-limiter/injectAsyncRateLimiter' -import type { AngularAsyncThrottlerOptions } from '../async-throttler/injectAsyncThrottler' -import type { AngularBatcherOptions } from '../batcher/injectBatcher' -import type { AngularDebouncerOptions } from '../debouncer/injectDebouncer' -import type { AngularQueuerOptions } from '../queuer/injectQueuer' -import type { AngularRateLimiterOptions } from '../rate-limiter/injectRateLimiter' -import type { AngularThrottlerOptions } from '../throttler/injectThrottler' - -export interface PacerProviderOptions { - asyncBatcher?: Partial> - asyncDebouncer?: Partial> - asyncQueuer?: Partial> - asyncRateLimiter?: Partial> - asyncThrottler?: Partial> - batcher?: Partial> - debouncer?: Partial> - queuer?: Partial> - rateLimiter?: Partial> - throttler?: Partial> -} - -const DEFAULT_OPTIONS: PacerProviderOptions = {} - -export const PACER_OPTIONS = new InjectionToken( - 'PACER_OPTIONS', - { - providedIn: 'root', - factory: () => DEFAULT_OPTIONS, - }, -) - -export function injectPacerOptions(): PacerProviderOptions { - try { - return inject(PACER_OPTIONS, { optional: true }) ?? DEFAULT_OPTIONS - } catch { - return DEFAULT_OPTIONS - } -} diff --git a/packages/angular-pacer/src/provider/pacer-provider.ts b/packages/angular-pacer/src/provider/pacer-provider.ts deleted file mode 100644 index fe142e344..000000000 --- a/packages/angular-pacer/src/provider/pacer-provider.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { PACER_OPTIONS } from './pacer-context' -import type { Provider } from '@angular/core' -import type { PacerProviderOptions } from './pacer-context' - -/** - * Provides default options for all Pacer utilities in the Angular application. - * Use this function when configuring your Angular application to set default options - * that will be used by all Pacer utilities throughout your app. - * - * @example - * ```ts - * // In your app.config.ts (standalone) - * export const appConfig: ApplicationConfig = { - * providers: [ - * providePacerOptions({ - * debouncer: { wait: 300 }, - * throttler: { wait: 100 }, - * }), - * ], - * }; - * - * // Or in NgModule (module-based) - * @NgModule({ - * providers: [ - * providePacerOptions({ - * debouncer: { wait: 300 }, - * }), - * ], - * }) - * export class AppModule {} - * ``` - */ -export function providePacerOptions(options: PacerProviderOptions): Provider { - return { - provide: PACER_OPTIONS, - useValue: options, - } -} diff --git a/packages/angular-pacer/src/provider/providePacerOptions.ts b/packages/angular-pacer/src/provider/providePacerOptions.ts new file mode 100644 index 000000000..2eb4a16ef --- /dev/null +++ b/packages/angular-pacer/src/provider/providePacerOptions.ts @@ -0,0 +1,81 @@ +import { InjectionToken, inject } from '@angular/core' +import type { Provider } from '@angular/core' +import type { AngularAsyncBatcherOptions } from '../async-batcher/injectAsyncBatcher' +import type { AngularAsyncDebouncerOptions } from '../async-debouncer/injectAsyncDebouncer' +import type { AngularAsyncQueuerOptions } from '../async-queuer/injectAsyncQueuer' +import type { AngularAsyncRateLimiterOptions } from '../async-rate-limiter/injectAsyncRateLimiter' +import type { AngularAsyncThrottlerOptions } from '../async-throttler/injectAsyncThrottler' +import type { AngularBatcherOptions } from '../batcher/injectBatcher' +import type { AngularDebouncerOptions } from '../debouncer/injectDebouncer' +import type { AngularQueuerOptions } from '../queuer/injectQueuer' +import type { AngularRateLimiterOptions } from '../rate-limiter/injectRateLimiter' +import type { AngularThrottlerOptions } from '../throttler/injectThrottler' + +/** + * Scoped default options for synchronous and asynchronous Pacer adapters. + * Local utility options override these defaults. + */ +export interface PacerProviderOptions { + /** Default options for {@link injectAsyncBatcher}. */ + asyncBatcher?: Partial> + /** Default options for {@link injectAsyncDebouncer}. */ + asyncDebouncer?: Partial> + /** Default options for {@link injectAsyncQueuer}. */ + asyncQueuer?: Partial> + /** Default options for {@link injectAsyncRateLimiter}. */ + asyncRateLimiter?: Partial> + /** Default options for {@link injectAsyncThrottler}. */ + asyncThrottler?: Partial> + /** Default options for {@link injectBatcher}. */ + batcher?: Partial> + /** Default options for {@link injectDebouncer}. */ + debouncer?: Partial> + /** Default options for {@link injectQueuer}. */ + queuer?: Partial> + /** Default options for {@link injectRateLimiter}. */ + rateLimiter?: Partial> + /** Default options for {@link injectThrottler}. */ + throttler?: Partial> +} + +const PACER_OPTIONS = new InjectionToken( + 'TANSTACK_PACER_OPTIONS', + { + factory: () => ({}), + }, +) + +/** + * Provides scoped defaults for Angular Pacer utilities. + * + * Place this provider in application, route, or component providers. The nearest + * provider supplies the defaults; local utility options take precedence. A nested + * provider replaces the outer defaults rather than merging scopes. + * + * @param options Partial default options grouped by utility. + * @returns An Angular provider for the supplied defaults. + * + * @example + * ```ts + * const appConfig = { + * providers: [ + * providePacerOptions({ debouncer: { wait: 250 } }), + * ], + * } + * ``` + */ +export function providePacerOptions(options: PacerProviderOptions): Provider { + return { + provide: PACER_OPTIONS, + useValue: options, + } +} + +/** + * Reads the nearest Pacer options provider in the current injection context. + * Returns an empty object when no provider is configured. + * @returns The scoped default options. + */ +export function injectPacerOptions(): PacerProviderOptions { + return inject(PACER_OPTIONS) +} diff --git a/packages/angular-pacer/src/queuer/index.ts b/packages/angular-pacer/src/queuer/index.ts index f043a123f..06215ae57 100644 --- a/packages/angular-pacer/src/queuer/index.ts +++ b/packages/angular-pacer/src/queuer/index.ts @@ -1,5 +1,6 @@ export * from '@tanstack/pacer/queuer' -export * from './injectQueuedSignal' -export * from './injectQueuedValue' +export * from './injectQueuedItems' export * from './injectQueuer' +export * from './injectQueuedComputed' +export * from './injectQueuedSignal' diff --git a/packages/angular-pacer/src/queuer/injectQueuedComputed.ts b/packages/angular-pacer/src/queuer/injectQueuedComputed.ts new file mode 100644 index 000000000..945f73020 --- /dev/null +++ b/packages/angular-pacer/src/queuer/injectQueuedComputed.ts @@ -0,0 +1,68 @@ +import { computed, effect, linkedSignal, untracked } from '@angular/core' +import { injectQueuer } from './injectQueuer' +import type { Signal } from '@angular/core' +import type { QueuerState } from '@tanstack/pacer/queuer' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { AngularQueuer, AngularQueuerOptions } from './injectQueuer' + +/** A processed-value signal with its underlying queue controls. */ +export interface AngularQueuerComputed< + TValue, + TSelected = {}, +> extends Signal { + /** The underlying Angular Queuer ref and its selected state. */ + queuer: AngularQueuer +} + +/** + * Creates an Angular queued view of a source accessor. + * + * The first read exposes the source value. An effect enqueues the initial value and each later value it observes; processing updates the returned value in queue order. Multiple source changes before an effect runs can collapse to the latest value. + * + * Options accept an object or factory and follow {@link injectQueuer} behavior. + * Pass a selector to expose reactive queue state on `queuer.state()`. + * + * @param source The source signal or accessor to observe. + * @param options Core queue options or a reactive options factory. + * @returns The processed-value signal with its queuer attached. + * + * @example + * ```ts + * // In a component or service injection context. + * const source = signal('initial') + * const value = injectQueuedComputed(source, { wait: 100 }) + * source.set('next') + * ``` + */ +export function injectQueuedComputed( + source: () => TValue, + options?: MaybeAccessor>, +): AngularQueuerComputed +/** + * Creates the queued value signal with selected state on its attached queuer. + * @param selector Selects reactive state exposed on the attached queuer. + */ +export function injectQueuedComputed( + source: () => TValue, + options: MaybeAccessor>, + selector: (state: QueuerState) => TSelected, +): AngularQueuerComputed +export function injectQueuedComputed( + source: () => TValue, + options: MaybeAccessor> = {}, + selector?: (state: QueuerState) => TSelected, +): AngularQueuerComputed { + const select = (state: QueuerState) => + selector ? selector(state) : {} + const computedSource = computed(source) + const queuedSignal = linkedSignal(() => untracked(computedSource)) + const queuer = injectQueuer( + (value: TValue) => queuedSignal.set(value), + options, + select, + ) + effect(() => { + queuer.addItem(computedSource()) + }) + return Object.assign(queuedSignal.asReadonly(), { queuer }) +} diff --git a/packages/angular-pacer/src/queuer/injectQueuedItems.ts b/packages/angular-pacer/src/queuer/injectQueuedItems.ts new file mode 100644 index 000000000..f6e1e86e2 --- /dev/null +++ b/packages/angular-pacer/src/queuer/injectQueuedItems.ts @@ -0,0 +1,78 @@ +import { shallow } from '@tanstack/store' +import { computed } from '@angular/core' +import { injectQueuer } from './injectQueuer' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { QueuerState } from '@tanstack/pacer/queuer' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { Signal } from '@angular/core' +import type { AngularQueuer, AngularQueuerOptions } from './injectQueuer' + +/** + * A readonly signal of pending queue items, with the Angular queuer attached. + */ +export interface AngularQueuerItems extends Signal< + ReadonlyArray +> { + /** The underlying utility ref for adding, processing, and inspecting items. */ + queuer: AngularQueuer + /** Shortcut for queuer.addItem, with the same arguments and return value. */ + addItem: AngularQueuer['addItem'] +} + +/** + * Creates a readonly Angular signal of pending queue items. + * + * Items appear when added and disappear when removed for processing. This signal + * contains waiting items, not processing results. Use the attached `queuer` methods + * to add items, start or stop processing, or inspect the queue. + * + * Options accept a static object or reactive factory and follow the underlying + * queuer's lifecycle and provider behavior. The returned function is an Angular signal. + * + * @param fn The callback that processes each queued item. + * @param options Core queue options or a reactive options factory. + * @returns A readonly items signal with `queuer` and `addItem` attributes. + * + * @example + * ```ts + * // In a component or service injection context. + * const items = injectQueuerItems( + * (item: string) => console.log(item), + * { started: false }, + * ) + * items.addItem('job') + * console.log(items()) // ['job'] + * items.queuer.start() + * ``` + */ +export function injectQueuerItems( + fn: (value: TValue) => void, + options?: MaybeAccessor>, +): AngularQueuerItems +/** + * Creates the items signal with selected state on its attached queuer. + * @param selector Selects reactive state exposed on the attached queuer. + */ +export function injectQueuerItems( + fn: (value: TValue) => void, + options: MaybeAccessor>, + selector: (state: QueuerState) => TSelected, +): AngularQueuerItems +export function injectQueuerItems( + fn: (value: TValue) => void, + options: MaybeAccessor> = {}, + selector?: (state: QueuerState) => TSelected, +): AngularQueuerItems { + const utility = injectQueuer(fn, options, (state) => ({ + items: state.items, + selected: selector ? selector(state) : {}, + })) + const items = computed(() => utility.state().items, { equal: shallow }) + const queuer = { + ...utility, + state: computed(() => utility.state().selected, { + equal: shallow, + }) as Signal>, + } + return Object.assign(items, { queuer, addItem: utility.addItem }) +} diff --git a/packages/angular-pacer/src/queuer/injectQueuedSignal.ts b/packages/angular-pacer/src/queuer/injectQueuedSignal.ts index 0b98ca94f..f70560bbd 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedSignal.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedSignal.ts @@ -1,67 +1,72 @@ -import { computed } from '@angular/core' +import { linkedSignal } from '@angular/core' +import { toAccessorSignal } from '../utils/maybeAccessor' import { injectQueuer } from './injectQueuer' -import type { AngularPacerOptions } from '../types' -import type { AngularQueuer } from './injectQueuer' -import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' +import type { Signal } from '@angular/core' +import type { QueuerState } from '@tanstack/pacer/queuer' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { AngularQueuer, AngularQueuerOptions } from './injectQueuer' -export interface QueuedSignal { - (): Array - addItem: AngularQueuer['addItem'] - queuer: AngularQueuer +/** A processed-value signal with its underlying queue controls. */ +export interface AngularQueuerSignal< + TValue, + TSelected = {}, +> extends Signal { + /** Enqueues a replacement value. */ + set: (value: TValue) => void + /** Enqueues an updater evaluated against the committed value when processed. */ + update: (updater: (previous: TValue) => TValue) => void + /** The underlying Angular Queuer ref and its selected state. */ + queuer: AngularQueuer<() => void, TSelected> } /** - * An Angular function that creates a queuer with managed state, combining Angular's signals with queuing functionality. - * This function provides both the current queue state and queue control methods. + * Creates an Angular queued editable signal. + * + * Every set and update is queued and processed in order. Updaters run against the committed value when their queue item executes. * - * The queue state is automatically updated whenever items are added, removed, or reordered in the queue. - * All queue operations are reflected in the state array returned by the function. + * Options accept an object or factory and follow {@link injectQueuer} behavior. + * Pass a selector to expose reactive queue state on `queuer.state()`. * - * The function returns a callable object: - * - `queued()`: Get the current queue items as an array - * - `queued.addItem(...)`: Add an item to the queue - * - `queued.queue`: The queuer instance with additional control methods + * @param initialValue The initial committed value. + * @param options Core queue options or a reactive options factory. + * @returns The processed-value signal with its queuer attached. * * @example * ```ts - * // Default behavior - track items - * const queued = injectQueuedSignal( - * (item) => console.log('Processing:', item), - * { started: true, wait: 1000 } - * ); - * - * // Add items - * queued.addItem('task1'); - * - * // Access items - * console.log(queued()); // ['task1'] - * - * // Control the queue - * queued.queuer.start(); - * queued.queuer.stop(); + * // In a component or service injection context. + * const value = injectQueuedSignal(0, { wait: 100 }) + * value.set(1) + * value.update(previous => previous + 1) * ``` */ -export function injectQueuedSignal< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - fn: (item: TValue) => void, - options: AngularPacerOptions> = {}, - selector: (state: QueuerState) => TSelected = (state) => - ({ items: state.items }) as TSelected, -): QueuedSignal { - const queuer = injectQueuer(fn, options, selector) - - const items = computed(() => queuer.state().items as Array) - - const queued = Object.assign(items, { - addItem: (...args: Parameters) => - queuer.addItem(...args), +export function injectQueuedSignal( + initialValue: MaybeAccessor, + options?: MaybeAccessor void>>, +): AngularQueuerSignal +/** + * Creates the queued value signal with selected state on its attached queuer. + * @param selector Selects reactive state exposed on the attached queuer. + */ +export function injectQueuedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor void>>, + selector: (state: QueuerState<() => void>) => TSelected, +): AngularQueuerSignal +export function injectQueuedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor void>> = {}, + selector?: (state: QueuerState<() => void>) => TSelected, +): AngularQueuerSignal { + const queuedSignal = linkedSignal(toAccessorSignal(initialValue)) + const queuer = injectQueuer( + (callback: () => void) => callback(), + options, + (state) => (selector ? selector(state) : {}), + ) + return Object.assign(queuedSignal.asReadonly(), { + set: (value: TValue) => queuer.addItem(() => queuedSignal.set(value)), + update: (updater: (previous: TValue) => TValue) => + queuer.addItem(() => queuedSignal.update(updater)), queuer, - }) as QueuedSignal - - return queued + }) } diff --git a/packages/angular-pacer/src/queuer/injectQueuedValue.ts b/packages/angular-pacer/src/queuer/injectQueuedValue.ts deleted file mode 100644 index 5b45cc529..000000000 --- a/packages/angular-pacer/src/queuer/injectQueuedValue.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { computed, effect, signal, untracked } from '@angular/core' -import { injectQueuedSignal } from './injectQueuedSignal' -import type { AngularPacerOptions } from '../types' -import type { AngularQueuer, AngularQueuerOptions } from './injectQueuer' -import type { Signal } from '@angular/core' -import type { QueuerState } from '@tanstack/pacer/queuer' - -/** A processed value signal with methods for adding and controlling queued values. */ -export type QueuedValueSignal = Signal & { - addItem: AngularQueuer['addItem'] - queuer: AngularQueuer -} - -type PrimitiveInitialValue = string | number | boolean | bigint | symbol | null - -/** - * An Angular function that creates a queued value that processes state changes in order with an optional delay. - * This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. - * - * The queued value will process changes in the order they are received, with optional delays between - * processing each change. This is useful for handling state updates that need to be processed - * in a specific order, like animations or sequential UI updates. - * - * The returned signal provides the most recently processed value. Before the queue processes - * an item, it provides the explicit initial value or the source's initial value. - * Source reads are deferred until the signal is read or Angular runs its effect, so required - * inputs can be bound before their values are read. - * - * Use `queued.addItem(...)` to add a value and `queued.queuer` to control the queue. - * Pending items are available through `queued.queuer.state().items` with the default selector. - * - * Pass an options object as the third argument to supply an object or function as the initial - * value. When those options are undefined or a factory, pass a fourth selector argument - * (undefined is allowed) to distinguish the initial-value form from options plus a selector. - * - * @example - * ```ts - * const initialValue = signal('initial') - * const queued = injectQueuedValue(initialValue, { - * wait: 500, - * started: true, - * }) - * - * // Add changes to the queue - * queued.addItem('new value') - * ``` - */ -export function injectQueuedValue< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - value: Signal, - options?: AngularPacerOptions>, - selector?: (state: QueuerState) => TSelected, -): QueuedValueSignal -export function injectQueuedValue< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - value: Signal, - initialValue: Extract, - options?: AngularQueuerOptions, - selector?: (state: QueuerState) => TSelected, -): QueuedValueSignal -export function injectQueuedValue< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - value: Signal, - initialValue: TValue, - options: AngularQueuerOptions, - selector?: (state: QueuerState) => TSelected, -): QueuedValueSignal -export function injectQueuedValue< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - value: Signal, - initialValue: TValue, - options: - AngularPacerOptions> | undefined, - selector: ((state: QueuerState) => TSelected) | undefined, -): QueuedValueSignal -export function injectQueuedValue< - TValue, - TSelected extends Pick, 'items'> = Pick< - QueuerState, - 'items' - >, ->( - value: Signal, - initialValueOrOptions?: - TValue | AngularPacerOptions>, - initialOptionsOrSelector?: - | AngularPacerOptions> - | ((state: QueuerState) => TSelected), - maybeSelector?: (state: QueuerState) => TSelected, -): QueuedValueSignal { - const hasPrimitiveInitialValue = - initialValueOrOptions === null || - (initialValueOrOptions !== undefined && - typeof initialValueOrOptions !== 'object' && - typeof initialValueOrOptions !== 'function') - const hasInitialValue = - hasPrimitiveInitialValue || - (initialOptionsOrSelector !== undefined && - typeof initialOptionsOrSelector !== 'function') || - arguments.length >= 4 - const initialOptions = hasInitialValue - ? (initialOptionsOrSelector as AngularPacerOptions< - AngularQueuerOptions - >) - : (initialValueOrOptions as AngularPacerOptions< - AngularQueuerOptions - >) - const selector = hasInitialValue - ? maybeSelector - : (initialOptionsOrSelector as - ((state: QueuerState) => TSelected) | undefined) - - // Wrap values so an explicit undefined remains distinct from uninitialized state. - const processed = signal<{ value: TValue } | undefined>( - hasInitialValue ? { value: initialValueOrOptions as TValue } : undefined, - ) - const queuedValue = computed(() => { - const current = processed() - return current ? current.value : value() - }) - - const queued = injectQueuedSignal( - (item) => { - processed.set({ value: item }) - }, - initialOptions, - selector, - ) - - effect(() => { - const nextValue = value() - untracked(() => { - if (!processed()) processed.set({ value: nextValue }) - queued.addItem(nextValue) - }) - }) - - return Object.assign(queuedValue, { - addItem: queued.addItem, - queuer: queued.queuer, - }) -} diff --git a/packages/angular-pacer/src/queuer/injectQueuer.ts b/packages/angular-pacer/src/queuer/injectQueuer.ts index 2e95205a5..95cbfc504 100644 --- a/packages/angular-pacer/src/queuer/injectQueuer.ts +++ b/packages/angular-pacer/src/queuer/injectQueuer.ts @@ -1,138 +1,151 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { Queuer } from '@tanstack/pacer/queuer' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' +import type { QueuerOptions, QueuerState } from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' -export interface AngularQueuerOptions< - TValue, - TSelected = {}, -> extends QueuerOptions { +/** + * Options for {@link injectQueuer}, including core configuration and Angular cleanup. + */ +export interface AngularQueuerOptions extends QueuerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the queuer instance. - * When provided, replaces the default cleanup (stop); use it to call flush(), stop(), add logging, etc. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (stop automatic processing). */ - onUnmount?: (queuer: AngularQueuer) => void + onUnmount?: (core: Queuer) => void } -export interface AngularQueuer extends Omit< +const queuerMethods = [ + 'addItem', + 'getNextItem', + 'execute', + 'flush', + 'flushAsBatch', + 'peekNextItem', + 'peekAllItems', + 'start', + 'stop', + 'clear', + 'reset', +] as const satisfies ReadonlyArray>> + +type QueuerMethod = (typeof queuerMethods)[number] + +/** + * An Angular Queuer ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ +export interface AngularQueuer extends Pick< Queuer, - 'store' | 'options' | 'setOptions' + QueuerMethod > { - options: Queuer['options'] & AngularQueuerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the queuer state changes - * - * Use this instead of `queuer.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages a Queuer instance. - * - * This is a lower-level function that provides direct access to the Queuer's functionality. - * This allows you to integrate it with any state management solution you prefer. - * - * The Queuer processes items synchronously in order, with optional delays between processing each item. - * The queuer includes an internal tick mechanism that can be started and stopped, making it useful as a scheduler. - * - * ## State Management and Selector + * Creates and manages an Angular Queuer in the current injection context. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Processes queued items in order with configurable pacing, capacity, and priority. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Options and state * - * @example - * ```ts - * // Default behavior - no reactive state subscriptions - * const queue = injectQueuer( - * (item) => console.log('Processing:', item), - * { started: true, wait: 1000 } - * ); + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * // Opt-in to track queue contents changes - * const queue = injectQueuer( - * (item) => console.log('Processing:', item), - * { started: true, wait: 1000 }, - * (state) => ({ items: state.items, size: state.size }) - * ); + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * // Add items - * queue.addItem('task1'); - * - * // Access the selected state - * const { items, isRunning } = queue.state(); - * ``` + * ## Cleanup * - * ## Cleanup on Destroy + * The default cleanup is to stop automatic processing. Set `onUnmount` to replace it. * - * By default, the function stops the queuer when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending items instead: + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * + * @example * ```ts - * const queue = injectQueuer(fn, { - * started: true, - * onUnmount: (q) => q.flush() - * }); + * // In a component or service injection context. + * const utility = injectQueuer( + * (item: string) => console.log(item), + * () => ({ wait: 100 }), + * (state) => state.items, + * ) + * utility.addItem('job') + * console.log(utility.state()) * ``` */ -export function injectQueuer( +export function injectQueuer( fn: (item: TValue) => void, - options: AngularPacerOptions> = {}, - selector: (state: QueuerState) => TSelected = () => ({}) as TSelected, -): AngularQueuer { - return injectReactiveOptions< - AngularQueuerOptions, - AngularQueuer - >( - options, - injectPacerOptions(), - 'queuer', - (mergedOptions, getPublicInstance) => { - const queuer = new Queuer(fn, mergedOptions) - const state = injectSelector(queuer.store, selector) + options?: MaybeAccessor>, +): AngularQueuer +/** + * Creates an Angular Queuer with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectQueuer( + fn: (item: TValue) => void, + options: MaybeAccessor>, + selector: (state: QueuerState) => TSelected, +): AngularQueuer +export function injectQueuer( + fn: (item: TValue) => void, + options: MaybeAccessor> = {}, + selector: (state: QueuerState) => TSelected | {} = () => ({}), +): AngularQueuer { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed(() => ({ + ...baseOptions.queuer, + ...optionsSignal(), + })) + + const queuerSignal = injectLazy(() => new Queuer(fn, mergedOptions())) - const result = { - ...queuer, - get options() { - return queuer.options - }, - set options(value) { - queuer.options = value - }, - state, - } as AngularQueuer + const methods = injectForwardMethods(queuerSignal, queuerMethods, (core) => { + core.setOptions(mergedOptions()) + }) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - queuer.options as AngularQueuerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - queuer.stop() - } - }) + effect(() => { + const opts = mergedOptions() + untracked(() => queuerSignal().setOptions(opts)) + }) - return result - }, + effect((onCleanup) => { + const core = queuerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.stop() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => queuerSignal().store, + (state) => state.isRunning && state.pendingTick, ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => queuerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/rate-limiter/index.ts b/packages/angular-pacer/src/rate-limiter/index.ts index bf63db58b..0694edaee 100644 --- a/packages/angular-pacer/src/rate-limiter/index.ts +++ b/packages/angular-pacer/src/rate-limiter/index.ts @@ -1,5 +1,5 @@ export * from '@tanstack/pacer/rate-limiter' +export * from './injectRateLimitedComputed' export * from './injectRateLimitedSignal' -export * from './injectRateLimitedValue' export * from './injectRateLimiter' diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts new file mode 100644 index 000000000..98342117a --- /dev/null +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts @@ -0,0 +1,75 @@ +import { effect, linkedSignal, untracked } from '@angular/core' +import { injectRateLimiter } from './injectRateLimiter' +import type { RateLimiterState } from '@tanstack/pacer/rate-limiter' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { + AngularRateLimiter, + AngularRateLimiterOptions, +} from './injectRateLimiter' +import type { Signal } from '@angular/core' + +/** + * A readonly Angular value signal with its underlying utility ref. + * The `rateLimiter` attribute exposes the underlying RateLimiter methods. + */ +export interface AngularRateLimiterComputed< + TValue, + TSelected = {}, +> extends Signal { + /** The underlying Angular RateLimiter ref for controlling execution. */ + rateLimiter: AngularRateLimiter<(value: TValue) => void, TSelected> +} +/** + * Creates an Angular ratelimited view of a source signal. + * + * The initial source value is available on first read. An effect passes source values to the rate limiter, including the initial value. Excess updates are discarded; the end of a window does not replay rejected values. + * + * The returned value is a real Angular signal with the underlying utility exposed + * on `rateLimiter`. Options accept a static object or reactive factory and follow + * {@link injectRateLimiter} lifecycle and provider behavior. + * + * @param source The source signal or accessor whose changes are observed. + * @param options Core options or a reactive options factory. + * @returns The value signal with a `rateLimiter` attribute. + * + * @example + * ```ts + * // In a component or service injection context. + * const source = signal('initial') + * const value = injectRateLimitedComputed(source, { limit: 5, window: 1000 }) + * source.set('updated') + * console.log(value()) + * ``` + */ +export function injectRateLimitedComputed( + source: () => TValue, + options: MaybeAccessor void>>, +): AngularRateLimiterComputed +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectRateLimitedComputed( + source: () => TValue, + options: MaybeAccessor void>>, + selector: (state: RateLimiterState) => TSelected, +): AngularRateLimiterComputed +export function injectRateLimitedComputed( + source: () => TValue, + options: MaybeAccessor void>>, + selector?: (state: RateLimiterState) => TSelected, +): AngularRateLimiterComputed { + const select = (state: RateLimiterState) => (selector ? selector(state) : {}) + const rateLimitedSignal = linkedSignal(() => untracked(source)) + const rateLimiter = injectRateLimiter( + (value: TValue) => rateLimitedSignal.set(value), + options, + select, + ) + effect(() => { + rateLimiter.maybeExecute(source()) + }) + return Object.assign(rateLimitedSignal.asReadonly(), { + rateLimiter, + }) +} diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts index a3466c069..373d2a5bc 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts @@ -1,89 +1,86 @@ -import { signal } from '@angular/core' +import { linkedSignal } from '@angular/core' +import { toAccessorSignal } from '../utils/maybeAccessor' import { injectRateLimiter } from './injectRateLimiter' -import type { AngularPacerOptions } from '../types' -import type { AngularRateLimiter } from './injectRateLimiter' +import type { RateLimiterState } from '@tanstack/pacer/rate-limiter' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { - RateLimiterOptions, - RateLimiterState, -} from '@tanstack/pacer/rate-limiter' + AngularRateLimiter, + AngularRateLimiterOptions, +} from './injectRateLimiter' +import type { Signal } from '@angular/core' -type Setter = (value: T | ((prev: T) => T)) => void - -export interface RateLimitedSignal { - (): TValue - set: Setter - rateLimiter: AngularRateLimiter, TSelected> +/** + * A readonly Angular value signal with paced `set`/`update` methods. + * The `rateLimiter` attribute exposes the underlying RateLimiter methods. + */ +export interface AngularRateLimiterSignal< + TValue, + TSelected = {}, +> extends Signal { + /** Replaces the value immediately if the rate limit permits; rejected writes are discarded. */ + set: (value: TValue) => void + /** Runs the updater with the current committed value when the write executes. */ + update: (updateFn: (previous: TValue) => TValue) => void + /** The underlying Angular RateLimiter ref for controlling execution. */ + rateLimiter: AngularRateLimiter<(callback: () => void) => void, TSelected> } - /** - * An Angular function that creates a rate-limited state signal, combining Angular's signal with rate limiting functionality. - * This function provides both the current rate-limited value and methods to update it. + * Creates an Angular ratelimited editable signal. * - * Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks - * subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out - * or intelligently collapse updates. + * The initial value is available synchronously. `set` and `update` share the execution limit. Accepted writes apply immediately; rejected writes and their updater callbacks are discarded, not replayed later. * - * The function returns a callable object: - * - `rateLimited()`: Get the current rate-limited value - * - `rateLimited.set(...)`: Set or update the rate-limited value (rate-limited via maybeExecute) - * - `rateLimited.rateLimiter`: The rate limiter instance with additional control methods and state signals + * The returned value is a real Angular signal with the underlying utility exposed + * on `rateLimiter`. Options accept a static object or reactive factory and follow + * {@link injectRateLimiter} lifecycle and provider behavior. * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying rate limiter instance. - * The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * @param initialValue The initial committed value. + * @param options Core options or a reactive options factory. + * @returns The value signal with `set`, `update`, and a `rateLimiter` attribute. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const rateLimited = injectRateLimitedSignal(0, { - * limit: 5, - * window: 60000, - * windowType: 'sliding' - * }); - * - * // Opt-in to reactive updates when limit state changes - * const rateLimited = injectRateLimitedSignal( - * 0, - * { limit: 5, window: 60000 }, - * (state) => ({ rejectionCount: state.rejectionCount }) - * ); + * // In a component or service injection context. + * const value = injectRateLimitedSignal(0, { limit: 5, window: 1000 }) + * value.set(10) + * value.update(previous => previous + 1) + * console.log(value()) * ``` */ -export function injectRateLimitedSignal( - value: TValue, - initialOptions: AngularPacerOptions>>, +export function injectRateLimitedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularRateLimiterOptions<(callback: () => void) => void> + >, +): AngularRateLimiterSignal +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectRateLimitedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularRateLimiterOptions<(callback: () => void) => void> + >, + selector: (state: RateLimiterState) => TSelected, +): AngularRateLimiterSignal +export function injectRateLimitedSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularRateLimiterOptions<(callback: () => void) => void> + >, selector?: (state: RateLimiterState) => TSelected, -): RateLimitedSignal { - const rateLimitedValue = signal(value) - +): AngularRateLimiterSignal { + const rateLimitedSignal = linkedSignal(toAccessorSignal(initialValue)) const rateLimiter = injectRateLimiter( - (newValue: TValue | ((prev: TValue) => TValue)) => { - if (typeof newValue === 'function') { - rateLimitedValue.update(newValue as (prev: TValue) => TValue) - } else { - rateLimitedValue.set(newValue) - } - }, - initialOptions, - selector, + (callback: () => void) => callback(), + options, + (state) => (selector ? selector(state) : {}), ) - - const set: Setter = ( - newValue: TValue | ((prev: TValue) => TValue), - ) => { - rateLimiter.maybeExecute(newValue) - } - - const rateLimited = Object.assign(() => rateLimitedValue(), { - set, + return Object.assign(rateLimitedSignal.asReadonly(), { + set: (value: TValue) => + rateLimiter.maybeExecute(() => rateLimitedSignal.set(value)), + update: (updateFn: (prev: TValue) => TValue) => + rateLimiter.maybeExecute(() => rateLimitedSignal.update(updateFn)), rateLimiter, - }) as RateLimitedSignal - - return rateLimited + }) } diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts deleted file mode 100644 index 34fe8366e..000000000 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { effect } from '@angular/core' -import { injectRateLimitedSignal } from './injectRateLimitedSignal' -import type { AngularPacerOptions } from '../types' -import type { RateLimitedSignal } from './injectRateLimitedSignal' -import type { Signal } from '@angular/core' -import type { - RateLimiterOptions, - RateLimiterState, -} from '@tanstack/pacer/rate-limiter' - -type Setter = (value: T | ((prev: T) => T)) => void - -/** - * An Angular function that creates a rate-limited value that updates at most a certain number of times within a time window. - * Unlike injectRateLimitedSignal, this function automatically tracks changes to the input signal - * and updates the rate-limited value accordingly. - * - * The rate-limited value will update according to the configured rate limit, blocking updates - * once the limit is reached until the window resets. - * - * The function returns a rate-limited signal object containing: - * - A Signal that provides the current rate-limited value - * - The rate limiter instance with control methods - * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying rate limiter instance. - * The `selector` parameter allows you to specify which rate limiter state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. - * - * @example - * ```ts - * // Default behavior - no reactive state subscriptions - * const value = signal(0) - * const rateLimited = injectRateLimitedValue(value, { - * limit: 5, - * window: 60000, - * windowType: 'sliding', - * }) - * - * // rateLimited() will update at most 5 times per 60 seconds - * effect(() => { - * updateUI(rateLimited()) - * }) - * ``` - */ -export function injectRateLimitedValue( - value: Signal, - initialOptions: AngularPacerOptions>>, - selector?: (state: RateLimiterState) => TSelected, -): RateLimitedSignal -export function injectRateLimitedValue( - value: Signal, - initialValue: TValue, - initialOptions: RateLimiterOptions>, - selector?: (state: RateLimiterState) => TSelected, -): RateLimitedSignal -export function injectRateLimitedValue( - value: Signal, - initialValue: TValue, - initialOptions: () => RateLimiterOptions>, - selector: ((state: RateLimiterState) => TSelected) | undefined, -): RateLimitedSignal -export function injectRateLimitedValue( - value: Signal, - initialValueOrOptions: - TValue | AngularPacerOptions>>, - initialOptionsOrSelector?: - | AngularPacerOptions>> - | ((state: RateLimiterState) => TSelected), - maybeSelector?: (state: RateLimiterState) => TSelected, -): RateLimitedSignal { - const hasSelector = typeof initialOptionsOrSelector === 'function' - - const hasInitialValue = - (initialOptionsOrSelector !== undefined && !hasSelector) || - arguments.length >= 4 - - const initialValue = hasInitialValue - ? (initialValueOrOptions as TValue) - : (undefined as unknown as TValue) - const initialOptions = hasInitialValue - ? (initialOptionsOrSelector as AngularPacerOptions< - RateLimiterOptions> - >) - : (initialValueOrOptions as AngularPacerOptions< - RateLimiterOptions> - >) - const selector = hasInitialValue - ? maybeSelector - : (initialOptionsOrSelector as - ((state: RateLimiterState) => TSelected) | undefined) - - const rateLimited = injectRateLimitedSignal( - initialValue, - initialOptions, - selector, - ) - - effect(() => { - const latest = value() - rateLimited.set(latest) - }) - - return rateLimited -} diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index 69eb5df67..f959b5d5f 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -1,161 +1,149 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { RateLimiter } from '@tanstack/pacer/rate-limiter' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyFunction } from '@tanstack/pacer/types' import type { + AnyFunction, RateLimiterOptions, RateLimiterState, -} from '@tanstack/pacer/rate-limiter' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectRateLimiter}, including core configuration and Angular cleanup. + */ export interface AngularRateLimiterOptions< TFn extends AnyFunction, - TSelected = {}, > extends RateLimiterOptions { /** - * Optional callback invoked when the component is destroyed. Receives the rate limiter instance. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (reset the limiter). */ - onUnmount?: (rateLimiter: AngularRateLimiter) => void + onUnmount?: (core: RateLimiter) => void } +const rateLimiterMethods = [ + 'maybeExecute', + 'getRemainingInWindow', + 'getMsUntilNextWindow', + 'reset', +] as const satisfies ReadonlyArray>> + +type RateLimiterMethod = (typeof rateLimiterMethods)[number] + +/** + * An Angular RateLimiter ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularRateLimiter< TFn extends AnyFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: RateLimiter['options'] & - AngularRateLimiterOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the rate limiter state changes - * - * Use this instead of `rateLimiter.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store> +> extends Pick, RateLimiterMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages a RateLimiter instance. - * - * This is a lower-level function that provides direct access to the RateLimiter's functionality. - * This allows you to integrate it with any state management solution you prefer. - * - * Rate limiting is a simple "hard limit" approach that allows executions until a maximum count is reached within - * a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing, - * it does not attempt to space out or collapse executions intelligently. - * - * The rate limiter supports two types of windows: - * - 'fixed': A strict window that resets after the window period. All executions within the window count - * towards the limit, and the window resets completely after the period. - * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more - * consistent rate of execution over time. + * Creates and manages an Angular RateLimiter in the current injection context. * - * For smoother execution patterns: - * - Use throttling when you want consistent spacing between executions (e.g. UI updates) - * - Use debouncing when you want to collapse rapid-fire events (e.g. search input) - * - Use rate limiting only when you need to enforce hard limits (e.g. API rate limits) + * Allows calls up to the configured limit within a fixed or sliding window. Calls beyond the limit are rejected rather than queued. * - * ## State Management and Selector + * ## Options and state * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * Available state properties: - * - `executionCount`: Number of function executions that have been completed - * - `executionTimes`: Array of timestamps when executions occurred for rate limiting calculations - * - `rejectionCount`: Number of function executions that have been rejected due to rate limiting + * ## Cleanup * - * ## Cleanup on Destroy + * The default cleanup is to reset the limiter. Set `onUnmount` to replace it. * - * Use the `onUnmount` option to run a callback when the component is destroyed. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const rateLimiter = injectRateLimiter(apiCall, { - * limit: 5, - * window: 60000, - * windowType: 'sliding', - * }); - * - * // Opt-in to track execution count changes - * const rateLimiter = injectRateLimiter( - * apiCall, - * { - * limit: 5, - * window: 60000, - * windowType: 'sliding', - * }, - * (state) => ({ executionCount: state.executionCount }) - * ); - * - * // Monitor rate limit status - * const handleClick = () => { - * const remaining = rateLimiter.getRemainingInWindow(); - * if (remaining > 0) { - * rateLimiter.maybeExecute(data); - * } else { - * showRateLimitWarning(); - * } - * }; - * - * // Access the selected state (will be empty object {} unless selector provided) - * const { executionCount, rejectionCount } = rateLimiter.state(); + * // In a component or service injection context. + * const utility = injectRateLimiter( + * (query: string) => console.log(query), + * () => ({ limit: 5, window: 1000 }), + * (state) => state.executionCount, + * ) + * utility.maybeExecute('search') + * console.log(utility.state()) * ``` */ -export function injectRateLimiter( +export function injectRateLimiter( fn: TFn, - options: AngularPacerOptions>, - selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected, -): AngularRateLimiter { - return injectReactiveOptions< - AngularRateLimiterOptions, - AngularRateLimiter - >( - options, - injectPacerOptions(), - 'rateLimiter', - (mergedOptions, getPublicInstance) => { - const rateLimiter = new RateLimiter(fn, mergedOptions) - const state = injectSelector(rateLimiter.store, selector) - - const result = { - ...rateLimiter, - get options() { - return rateLimiter.options - }, - set options(value) { - rateLimiter.options = value - }, - state, - } as AngularRateLimiter + options: MaybeAccessor>, +): AngularRateLimiter +/** + * Creates an Angular RateLimiter with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectRateLimiter( + fn: TFn, + options: MaybeAccessor>, + selector: (state: RateLimiterState) => TSelected, +): AngularRateLimiter +export function injectRateLimiter( + fn: TFn, + options: MaybeAccessor>, + selector: (state: RateLimiterState) => TSelected | {} = () => ({}), +): AngularRateLimiter { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.rateLimiter, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - rateLimiter.options as AngularRateLimiterOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } - }) + const rateLimiterSignal = injectLazy( + () => new RateLimiter(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + rateLimiterSignal, + rateLimiterMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => rateLimiterSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = rateLimiterSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.reset() + } + }) + }) + + const state = injectSelector(() => rateLimiterSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/throttler/index.ts b/packages/angular-pacer/src/throttler/index.ts index 0aba74d44..27b5f551d 100644 --- a/packages/angular-pacer/src/throttler/index.ts +++ b/packages/angular-pacer/src/throttler/index.ts @@ -1,5 +1,5 @@ export * from '@tanstack/pacer/throttler' +export * from './injectThrottledComputed' export * from './injectThrottledSignal' -export * from './injectThrottledValue' export * from './injectThrottler' diff --git a/packages/angular-pacer/src/throttler/injectThrottledComputed.ts b/packages/angular-pacer/src/throttler/injectThrottledComputed.ts new file mode 100644 index 000000000..1a958c139 --- /dev/null +++ b/packages/angular-pacer/src/throttler/injectThrottledComputed.ts @@ -0,0 +1,76 @@ +import { effect, linkedSignal, untracked } from '@angular/core' +import { injectThrottler } from './injectThrottler' +import type { ThrottlerState } from '@tanstack/pacer/throttler' +import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { + AngularThrottler, + AngularThrottlerOptions, +} from './injectThrottler' +import type { Signal } from '@angular/core' + +/** + * A readonly Angular value signal with its underlying utility ref. + * The `throttler` attribute exposes the underlying Throttler methods. + */ +export interface AngularThrottlerComputed< + TValue, + TSelected = {}, +> extends Signal { + /** The underlying Angular Throttler ref for controlling execution. */ + throttler: AngularThrottler<(value: TValue) => void, TSelected> +} +/** + * Creates an Angular throttled view of a source signal. + * + * The initial source value is available on first read. An effect passes source values to the throttler, including the initial value. Later changes replace the pending trailing value without restarting the interval. + * + * The returned value is a real Angular signal with the underlying utility exposed + * on `throttler`. Options accept a static object or reactive factory and follow + * {@link injectThrottler} lifecycle and provider behavior. + * + * @param source The source signal or accessor whose changes are observed. + * @param options Core options or a reactive options factory. + * @returns The value signal with a `throttler` attribute. + * + * @example + * ```ts + * // In a component or service injection context. + * const source = signal('initial') + * const value = injectThrottledComputed(source, { wait: 250 }) + * source.set('updated') + * console.log(value()) + * ``` + */ +export function injectThrottledComputed( + source: () => TValue, + options: MaybeAccessor void>>, +): AngularThrottlerComputed +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectThrottledComputed( + source: () => TValue, + options: MaybeAccessor void>>, + selector: (state: ThrottlerState<(value: TValue) => void>) => TSelected, +): AngularThrottlerComputed +export function injectThrottledComputed( + source: () => TValue, + options: MaybeAccessor void>>, + selector?: (state: ThrottlerState<(value: TValue) => void>) => TSelected, +): AngularThrottlerComputed { + const select = (state: ThrottlerState<(value: TValue) => void>) => + selector ? selector(state) : {} + const throttledSignal = linkedSignal(() => untracked(source)) + const throttler = injectThrottler( + (value: TValue) => throttledSignal.set(value), + options, + select, + ) + effect(() => { + throttler.maybeExecute(source()) + }) + return Object.assign(throttledSignal.asReadonly(), { + throttler, + }) +} diff --git a/packages/angular-pacer/src/throttler/injectThrottledSignal.ts b/packages/angular-pacer/src/throttler/injectThrottledSignal.ts index bdec1afa4..21b211b14 100644 --- a/packages/angular-pacer/src/throttler/injectThrottledSignal.ts +++ b/packages/angular-pacer/src/throttler/injectThrottledSignal.ts @@ -1,94 +1,90 @@ -import { signal } from '@angular/core' +import { linkedSignal } from '@angular/core' +import { toAccessorSignal } from '../utils/maybeAccessor' import { injectThrottler } from './injectThrottler' -import type { AngularPacerOptions } from '../types' -import type { AngularThrottler } from './injectThrottler' +import type { ThrottlerState } from '@tanstack/pacer/throttler' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { - ThrottlerOptions, - ThrottlerState, -} from '@tanstack/pacer/throttler' + AngularThrottler, + AngularThrottlerOptions, +} from './injectThrottler' +import type { Signal } from '@angular/core' -type Setter = (value: T | ((prev: T) => T)) => void - -export interface ThrottledSignal { - (): TValue - set: Setter - throttler: AngularThrottler, TSelected> +/** + * A readonly Angular value signal with paced `set`/`update` methods. + * The `throttler` attribute exposes the underlying Throttler methods. + */ +export interface AngularThrottlerSignal< + TValue, + TSelected = {}, +> extends Signal { + /** Schedules the replacement value according to leading and trailing throttling options. */ + set: (value: TValue) => void + /** Runs the updater with the current committed value when the write executes. */ + update: (updateFn: (previous: TValue) => TValue) => void + /** The underlying Angular Throttler ref for controlling execution. */ + throttler: AngularThrottler<(callback: () => void) => void, TSelected> } - /** - * An Angular function that creates a throttled state signal, combining Angular's signal with throttling functionality. - * This function provides both the current throttled value and methods to update it. + * Creates an Angular throttled editable signal. * - * The state value is updated at most once within the specified wait time. - * This is useful for handling frequent state updates that should be rate-limited, like scroll positions - * or mouse movements. + * The initial value is available synchronously. `set` and `update` share a throttler. Leading writes may apply immediately; subsequent writes replace the pending trailing write without extending its deadline. An updater runs against the committed value when executed. * - * The function returns a callable object: - * - `throttled()`: Get the current throttled value - * - `throttled.set(...)`: Set or update the throttled value (throttled via maybeExecute) - * - `throttled.throttler`: The throttler instance with additional control methods and state signals + * The returned value is a real Angular signal with the underlying utility exposed + * on `throttler`. Options accept a static object or reactive factory and follow + * {@link injectThrottler} lifecycle and provider behavior. * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying throttler instance. - * The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. - * - * Available throttler state properties: - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the throttler is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `lastExecutionTime`: Timestamp of the last execution - * - `nextExecutionTime`: Timestamp of the next allowed execution - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') + * @param initialValue The initial committed value. + * @param options Core options or a reactive options factory. + * @returns The value signal with `set`, `update`, and a `throttler` attribute. * * @example * ```ts - * const throttledScrollY = injectThrottledSignal(0, { wait: 100 }) - * - * // Get value - * console.log(throttledScrollY()) - * - * // Set/update value (throttled) - * throttledScrollY.set(window.scrollY) - * - * // Access throttler - * console.log(throttledScrollY.throttler.state().isPending) + * // In a component or service injection context. + * const value = injectThrottledSignal(0, { wait: 250 }) + * value.set(10) + * value.update(previous => previous + 1) + * console.log(value()) * ``` */ -export function injectThrottledSignal( - value: TValue, - initialOptions: AngularPacerOptions>>, - selector?: (state: ThrottlerState>) => TSelected, -): ThrottledSignal { - const throttledValue = signal(value) - +export function injectThrottledSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularThrottlerOptions<(callback: () => void) => void> + >, +): AngularThrottlerSignal +/** + * Creates the value signal with selected state on its attached utility ref. + * @param selector Selects reactive state exposed on the attached utility ref. + */ +export function injectThrottledSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularThrottlerOptions<(callback: () => void) => void> + >, + selector: ( + state: ThrottlerState<(callback: () => void) => void>, + ) => TSelected, +): AngularThrottlerSignal +export function injectThrottledSignal( + initialValue: MaybeAccessor, + options: MaybeAccessor< + AngularThrottlerOptions<(callback: () => void) => void> + >, + selector?: ( + state: ThrottlerState<(callback: () => void) => void>, + ) => TSelected, +): AngularThrottlerSignal { + const throttledSignal = linkedSignal(toAccessorSignal(initialValue)) const throttler = injectThrottler( - (newValue: TValue | ((prev: TValue) => TValue)) => { - if (typeof newValue === 'function') { - throttledValue.update(newValue as (prev: TValue) => TValue) - } else { - throttledValue.set(newValue) - } - }, - initialOptions, - selector, + (callback: () => void) => callback(), + options, + (state) => (selector ? selector(state) : {}), ) - - const set: Setter = ( - newValue: TValue | ((prev: TValue) => TValue), - ) => { - throttler.maybeExecute(newValue) - } - - const throttled = Object.assign(() => throttledValue(), { - set, + return Object.assign(throttledSignal.asReadonly(), { + set: (value: TValue) => + throttler.maybeExecute(() => throttledSignal.set(value)), + update: (updateFn: (prev: TValue) => TValue) => + throttler.maybeExecute(() => throttledSignal.update(updateFn)), throttler, - }) as ThrottledSignal - - return throttled + }) } diff --git a/packages/angular-pacer/src/throttler/injectThrottledValue.ts b/packages/angular-pacer/src/throttler/injectThrottledValue.ts deleted file mode 100644 index c03f476ad..000000000 --- a/packages/angular-pacer/src/throttler/injectThrottledValue.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { effect } from '@angular/core' -import { injectThrottledSignal } from './injectThrottledSignal' -import type { AngularPacerOptions } from '../types' -import type { ThrottledSignal } from './injectThrottledSignal' -import type { Signal } from '@angular/core' -import type { - ThrottlerOptions, - ThrottlerState, -} from '@tanstack/pacer/throttler' - -type Setter = (value: T | ((prev: T) => T)) => void - -/** - * An Angular function that creates a throttled value that updates at most once within a specified time window. - * Unlike injectThrottledSignal, this function automatically tracks changes to the input signal - * and updates the throttled value accordingly. - * - * The throttled value will update at most once within the specified wait time, regardless of - * how frequently the input value changes. - * - * This is useful for deriving throttled values from signals that change frequently, - * like scroll positions or mouse coordinates, where you want to limit how often downstream effects - * or calculations occur. - * - * The function returns a throttled signal object containing: - * - A Signal that provides the current throttled value - * - The throttler instance with control methods - * - * ## State Management and Selector - * - * The function uses TanStack Store for reactive state management via the underlying throttler instance. - * The `selector` parameter allows you to specify which throttler state changes will trigger signal updates, - * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur. - * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. - * - * Available throttler state properties: - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the throttler is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `lastExecutionTime`: Timestamp of the last execution - * - `nextExecutionTime`: Timestamp of the next allowed execution - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') - * - * @example - * ```ts - * // Default behavior - no reactive state subscriptions - * const scrollY = signal(0) - * const throttledScrollY = injectThrottledValue(scrollY, { - * wait: 100, // Update at most once per 100ms - * }) - * - * // Opt-in to reactive updates when pending state changes - * const throttledScrollYWithState = injectThrottledValue( - * scrollY, - * { wait: 100 }, - * (state) => ({ isPending: state.isPending }), - * ) - * - * // Read the throttled signal value - * effect(() => { - * updateUI(throttledScrollY()) - * }) - * - * // Access throttler state via the returned object's state() signal - * console.log('Is pending:', throttledScrollYWithState.state().isPending) - * - * // Control the throttler - * throttledScrollY.cancel() // Cancel any pending updates - * ``` - */ -export function injectThrottledValue( - value: Signal, - initialOptions: AngularPacerOptions>>, - selector?: (state: ThrottlerState>) => TSelected, -): ThrottledSignal -export function injectThrottledValue( - value: Signal, - initialValue: TValue, - initialOptions: ThrottlerOptions>, - selector?: (state: ThrottlerState>) => TSelected, -): ThrottledSignal -export function injectThrottledValue( - value: Signal, - initialValue: TValue, - initialOptions: () => ThrottlerOptions>, - selector: ((state: ThrottlerState>) => TSelected) | undefined, -): ThrottledSignal -export function injectThrottledValue( - value: Signal, - initialValueOrOptions: - TValue | AngularPacerOptions>>, - initialOptionsOrSelector?: - | AngularPacerOptions>> - | ((state: ThrottlerState>) => TSelected), - maybeSelector?: (state: ThrottlerState>) => TSelected, -): ThrottledSignal { - const hasSelector = typeof initialOptionsOrSelector === 'function' - - const hasInitialValue = - (initialOptionsOrSelector !== undefined && !hasSelector) || - arguments.length >= 4 - - const initialValue = hasInitialValue - ? (initialValueOrOptions as TValue) - : (undefined as unknown as TValue) - const initialOptions = hasInitialValue - ? (initialOptionsOrSelector as AngularPacerOptions< - ThrottlerOptions> - >) - : (initialValueOrOptions as AngularPacerOptions< - ThrottlerOptions> - >) - const selector = hasInitialValue - ? maybeSelector - : (initialOptionsOrSelector as - ((state: ThrottlerState>) => TSelected) | undefined) - - const throttledValue = injectThrottledSignal( - initialValue, - initialOptions, - selector, - ) - - effect(() => { - const latest = value() - throttledValue.set(latest) - }) - - return throttledValue -} diff --git a/packages/angular-pacer/src/throttler/injectThrottler.ts b/packages/angular-pacer/src/throttler/injectThrottler.ts index c0c7c41c3..e80e7930b 100644 --- a/packages/angular-pacer/src/throttler/injectThrottler.ts +++ b/packages/angular-pacer/src/throttler/injectThrottler.ts @@ -1,155 +1,156 @@ -import { DestroyRef, inject } from '@angular/core' -import { injectSelector } from '@tanstack/angular-store' +import { computed, effect, untracked } from '@angular/core' import { Throttler } from '@tanstack/pacer/throttler' -import { injectReactiveOptions } from '../utils/injectReactiveOptions' -import { injectPacerOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' +import { shallow } from '@tanstack/store' +import { injectPacerOptions } from '../provider/providePacerOptions' +import { toAccessorSignal } from '../utils/maybeAccessor' +import { injectForwardMethods } from '../utils/injectForwardMethods' +import { injectLazy } from '../utils/injectLazy' +import { injectSelector } from '../utils/injectSelector' +import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' +import type { ReadonlySelected } from '../utils/readonlySelected' +import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' -import type { Store } from '@tanstack/angular-store' -import type { AnyFunction } from '@tanstack/pacer/types' import type { + AnyFunction, ThrottlerOptions, ThrottlerState, -} from '@tanstack/pacer/throttler' +} from '@tanstack/pacer' +import type { MethodKeys } from '../utils/injectForwardMethods' +/** + * Options for {@link injectThrottler}, including core configuration and Angular cleanup. + */ export interface AngularThrottlerOptions< TFn extends AnyFunction, - TSelected = {}, > extends ThrottlerOptions { /** - * Optional callback invoked when the component is destroyed. Receives the throttler instance. - * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc. + * Called when the owning injection context is destroyed. Receives the core instance. + * Providing this callback replaces the default cleanup (cancel pending execution). */ - onUnmount?: (throttler: AngularThrottler) => void + onUnmount?: (core: Throttler) => void } +const throttlerMethods = [ + 'maybeExecute', + 'flush', + 'cancel', + 'reset', +] as const satisfies ReadonlyArray>> + +type ThrottlerMethod = (typeof throttlerMethods)[number] + +/** + * An Angular Throttler ref with stable core methods and readonly selected state. + * Read `state()` to observe the selector result; without a selector it returns `{}`. + */ export interface AngularThrottler< TFn extends AnyFunction, TSelected = {}, -> extends Omit, 'store' | 'options' | 'setOptions'> { - options: Throttler['options'] & AngularThrottlerOptions - setOptions: ( - options: Partial>, - ) => void - /** - * Reactive state signal that will be updated when the throttler state changes - * - * Use this instead of `throttler.store.state` - */ - readonly state: Signal> - /** - * @deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. - * The state on the store object is not reactive in Angular signals. - */ - readonly store: Store>> +> extends Pick, ThrottlerMethod> { + /** The readonly selector result. Returns an empty object when no selector is supplied. */ + readonly state: Signal> } /** - * An Angular function that creates and manages a Throttler instance. - * - * This is a lower-level function that provides direct access to the Throttler's functionality. - * This allows you to integrate it with any state management solution you prefer. + * Creates and manages an Angular Throttler in the current injection context. * - * This function provides throttling functionality to limit how often a function can be called, - * ensuring it executes at most once within a specified time window. + * Limits executions to the configured interval, with leading and trailing calls. Later calls replace the pending trailing arguments without restarting the interval. * - * The throttler will execute the function immediately (if leading is enabled) and then - * prevent further executions until the wait period has elapsed. + * ## Options and state * - * ## State Management and Selector + * Accepts static options or an options factory. Factories are read lazily, and signal + * dependencies update the existing core instance. Local options override provider defaults. + * Methods apply current options before executing and run outside Angular's zone. * - * The function uses TanStack Store for state management and wraps it with Angular signals. - * The `selector` parameter allows you to specify which state changes will trigger signal updates, - * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. + * Pass a selector to expose reactive core state through `state()`. Without a selector, + * `state()` returns `{}`; operations remain available on the ref. * - * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary updates and gives you - * full control over when your component tracks state changes. + * ## Cleanup * - * Available state properties: - * - `executionCount`: Number of function executions that have been completed - * - `isPending`: Whether the throttler is waiting for the timeout to trigger execution - * - `lastArgs`: The arguments from the most recent call to maybeExecute - * - `lastExecutionTime`: Timestamp of the last execution - * - `nextExecutionTime`: Timestamp of the next allowed execution - * - `status`: Current execution status ('disabled' | 'idle' | 'pending') + * The default cleanup is to cancel pending execution. Set `onUnmount` to replace it. * - * ## Cleanup on Destroy - * - * By default, the function cancels any pending execution when the component is destroyed. - * Use the `onUnmount` option to customize this. For example, to flush pending work instead: - * - * ```ts - * const throttler = injectThrottler(fn, { - * wait: 100, - * onUnmount: (t) => t.flush() - * }); - * ``` + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive factory returning them. + * @returns A ref containing stable methods and a readonly selected-state signal. * * @example * ```ts - * // Default behavior - no reactive state subscriptions - * const throttler = injectThrottler( - * (scrollY: number) => updateScrollPosition(scrollY), - * { wait: 100 } - * ); - * - * // Opt-in to track isPending changes (optimized for loading states) - * const throttler = injectThrottler( - * (scrollY: number) => updateScrollPosition(scrollY), - * { wait: 100 }, - * (state) => ({ isPending: state.isPending }) - * ); - * - * // In an event handler - * window.addEventListener('scroll', () => { - * throttler.maybeExecute(window.scrollY); - * }); - * - * // Access the selected state (will be empty object {} unless selector provided) - * const { isPending } = throttler.state(); + * // In a component or service injection context. + * const utility = injectThrottler( + * (value: number) => console.log(value), + * () => ({ wait: 100 }), + * (state) => state.isPending, + * ) + * utility.maybeExecute(42) + * console.log(utility.state()) * ``` */ -export function injectThrottler( +export function injectThrottler( fn: TFn, - options: AngularPacerOptions>, - selector: (state: ThrottlerState) => TSelected = () => ({}) as TSelected, -): AngularThrottler { - return injectReactiveOptions< - AngularThrottlerOptions, - AngularThrottler - >( - options, - injectPacerOptions(), - 'throttler', - (mergedOptions, getPublicInstance) => { - const throttler = new Throttler(fn, mergedOptions) - const state = injectSelector(throttler.store, selector) - - const result = { - ...throttler, - get options() { - return throttler.options - }, - set options(value) { - throttler.options = value - }, - state, - } as AngularThrottler + options: MaybeAccessor>, +): AngularThrottler +/** + * Creates an Angular Throttler with a reactive selector result. + * @param fn The callback invoked by the core utility. + * @param options Core options or a reactive options factory. + * @param selector Selects the state exposed by the returned `state` signal. + * @returns The utility ref with the selected state. + */ +export function injectThrottler( + fn: TFn, + options: MaybeAccessor>, + selector: (state: ThrottlerState) => TSelected, +): AngularThrottler +export function injectThrottler( + fn: TFn, + options: MaybeAccessor>, + selector: (state: ThrottlerState) => TSelected | {} = () => ({}), +): AngularThrottler { + const baseOptions = injectPacerOptions() + const optionsSignal = toAccessorSignal(options) + const mergedOptions = computed>(() => ({ + ...baseOptions.throttler, + ...optionsSignal(), + })) - const destroyRef = inject(DestroyRef, { optional: true }) - destroyRef?.onDestroy(() => { - const onUnmount = ( - throttler.options as AngularThrottlerOptions - ).onUnmount - if (onUnmount) { - onUnmount(getPublicInstance()) - } else { - throttler.cancel() - } - }) + const throttlerSignal = injectLazy( + () => new Throttler(fn, mergedOptions()), + ) - return result + const methods = injectForwardMethods( + throttlerSignal, + throttlerMethods, + (core) => { + core.setOptions(mergedOptions()) }, ) + + effect(() => { + const opts = mergedOptions() + untracked(() => throttlerSignal().setOptions(opts)) + }) + + effect((onCleanup) => { + const core = throttlerSignal() + onCleanup(() => { + const opts = untracked(mergedOptions) + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + } + }) + }) + + const hasPendingTasks = injectSelector( + () => throttlerSignal().store, + (state) => state.isPending, + ) + injectPendingTasksLifecycle(hasPendingTasks) + + const state = injectSelector(() => throttlerSignal().store, selector, { + compare: shallow, + }) + + return { state, ...methods } } diff --git a/packages/angular-pacer/src/types/index.ts b/packages/angular-pacer/src/types/index.ts index 48ddfb122..bc0ca24c9 100644 --- a/packages/angular-pacer/src/types/index.ts +++ b/packages/angular-pacer/src/types/index.ts @@ -1,4 +1 @@ export * from '@tanstack/pacer/types' - -/** Options with optional reactive getters, or a factory that reads reactive values. */ -export type AngularPacerOptions = TOptions | (() => TOptions) diff --git a/packages/angular-pacer/src/utils/injectExternalStore.ts b/packages/angular-pacer/src/utils/injectExternalStore.ts new file mode 100644 index 000000000..416e18fc9 --- /dev/null +++ b/packages/angular-pacer/src/utils/injectExternalStore.ts @@ -0,0 +1,89 @@ +// Copied from Angular Query +import { + DestroyRef, + assertInInjectionContext, + computed, + effect, + inject, + signal, + untracked, +} from '@angular/core' +import { injectOutsideZone } from './injectOutsideZone' +import type { Signal, ValueEqualityFn } from '@angular/core' + +interface ExternalBinding { + /** Synchronous snapshot. Angular signals read here are tracked dependencies. */ + readonly getSnapshot: () => T + /** Omit to pause observation; changing eligibility requires a new descriptor. */ + readonly subscribe?: ((notify: () => void) => () => void) | undefined +} + +interface ExternalStoreOptions { + /** Snapshot equality, defaulting to Object.is. Does not control connection identity. */ + readonly equal?: ValueEqualityFn | undefined +} + +/** + * Creates a readonly Angular signal backed by a synchronous external store. + * + * Store access, subscription setup, and cleanup run outside NgZone so background + * timers do not hold Angular stability open. + * + * Reads evaluate snapshots without subscribing. An effect owns the subscription + * and invalidates the snapshot after setup, closing the gap between an early + * read and connection. The next read recomputes the snapshot; equality prevents + * unchanged values from propagating. Unread snapshots stay lazy. + * + * Notifications invalidate snapshots synchronously once connected. Snapshot-only + * dependencies do not reinstall the subscription. Subscription setup and cleanup + * may read the returned signal; factories and snapshot readers must not recurse. + * + * Only factory/snapshot errors are exposed on reads. Subscription and cleanup + * errors follow Angular's normal effect error handling; the helper does not store + * them or implement retries. Destruction stops observation without changing the + * cached snapshot. + */ +export function injectExternalStore( + binding: () => ExternalBinding, + options?: ExternalStoreOptions, +): Signal { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + assertInInjectionContext(injectExternalStore) + } + const owner = inject(DestroyRef) + const outsideZone = injectOutsideZone() + const revision = signal(0) + const requested = computed(() => outsideZone(binding)) + const invalidate = () => untracked(() => revision.update((n) => n + 1)) + + effect((onCleanup) => { + // The previous subscription's cleanup may have destroyed the owner. + if (owner.destroyed) return + const current = requested() + outsideZone(() => + untracked(() => { + try { + const unsubscribe = current.subscribe?.(invalidate) + if (unsubscribe) { + // subscribe() can trigger component/injector destruction before returning. + // Registering cleanup afterward misses that destruction. + if (owner.destroyed) unsubscribe() + else onCleanup(() => outsideZone(unsubscribe)) + } + } finally { + // Invalidate after subscribing so an early cached snapshot cannot miss + // changes before or during setup. Do not eagerly read the snapshot. + invalidate() + } + }), + ) + }) + + return computed( + () => { + revision() + return outsideZone(() => requested().getSnapshot()) + }, + options?.equal ? { equal: options.equal } : undefined, + ) +} diff --git a/packages/angular-pacer/src/utils/injectForwardMethods.ts b/packages/angular-pacer/src/utils/injectForwardMethods.ts new file mode 100644 index 000000000..75689caeb --- /dev/null +++ b/packages/angular-pacer/src/utils/injectForwardMethods.ts @@ -0,0 +1,37 @@ +import { untracked } from '@angular/core' +import { injectOutsideZone } from './injectOutsideZone' +import type { Signal } from '@angular/core' + +export type MethodKeys = { + [K in keyof T]-?: T[K] extends (...args: Array) => unknown ? K : never +}[keyof T] + +export function injectForwardMethods< + TSource, + TMethods extends MethodKeys, +>( + /* Source of the methods */ + source: Signal, + /* Methods key to register */ + methods: ReadonlyArray, + /* Callback to run before the method. Runs untracked and outside Angular. */ + before: (instance: TSource, method: TMethods) => void, +): Pick { + const outsideZone = injectOutsideZone() + const result = {} as Pick + + for (const key of methods) { + result[key] = ((...args: Array) => + outsideZone(() => + untracked(() => { + const instance = source() + before(instance, key) + + const method = instance[key] as (...args: Array) => unknown + return method.apply(instance, args) + }), + )) as TSource[TMethods] + } + + return result +} diff --git a/packages/angular-pacer/src/utils/injectLazy.ts b/packages/angular-pacer/src/utils/injectLazy.ts new file mode 100644 index 000000000..196dc7cf5 --- /dev/null +++ b/packages/angular-pacer/src/utils/injectLazy.ts @@ -0,0 +1,8 @@ +import { computed, untracked } from '@angular/core' +import { injectOutsideZone } from './injectOutsideZone' +import type { Signal } from '@angular/core' + +export function injectLazy(factory: () => T): Signal { + const outsideZone = injectOutsideZone() + return computed(() => outsideZone(() => untracked(factory))) +} diff --git a/packages/angular-pacer/src/utils/injectOutsideZone.ts b/packages/angular-pacer/src/utils/injectOutsideZone.ts new file mode 100644 index 000000000..5fe3cb530 --- /dev/null +++ b/packages/angular-pacer/src/utils/injectOutsideZone.ts @@ -0,0 +1,7 @@ +import { NgZone, inject } from '@angular/core' + +/** Keep background timers out of Angular's stability tracking. */ +export function injectOutsideZone() { + const ngZone = inject(NgZone) + return (fn: () => T): T => ngZone.runOutsideAngular(fn) +} diff --git a/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts b/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts new file mode 100644 index 000000000..8fc106c00 --- /dev/null +++ b/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts @@ -0,0 +1,16 @@ +import { NgZone, PendingTasks, effect, inject, untracked } from '@angular/core' + +/** Tracks pending work and releases its task when the owner is destroyed. */ +export function injectPendingTasksLifecycle( + hasPendingTasks: () => boolean, +): void { + const pendingTasks = inject(PendingTasks) + const ngZone = inject(NgZone) + + effect((onCleanup) => { + if (!hasPendingTasks()) return + const release = untracked(() => pendingTasks.add()) + // Keep the task held while entering the zone to avoid transient stability. + onCleanup(() => ngZone.run(release)) + }) +} diff --git a/packages/angular-pacer/src/utils/injectReactiveOptions.ts b/packages/angular-pacer/src/utils/injectReactiveOptions.ts deleted file mode 100644 index 2fdf8ce0b..000000000 --- a/packages/angular-pacer/src/utils/injectReactiveOptions.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - DestroyRef, - Injector, - computed, - effect, - inject, - runInInjectionContext, - untracked, -} from '@angular/core' -import type { PacerProviderOptions } from '../provider/pacer-context' -import type { AngularPacerOptions } from '../types' - -function hasEnumerableGetter(value: unknown): boolean { - if (!value || typeof value !== 'object') return false - return Reflect.ownKeys(value).some((key) => { - const descriptor = Object.getOwnPropertyDescriptor(value, key) - return descriptor?.enumerable && typeof descriptor.get === 'function' - }) -} - -function getPropertyDescriptor( - value: object, - key: PropertyKey, -): PropertyDescriptor | undefined { - let current: object | null = value - while (current) { - const descriptor = Object.getOwnPropertyDescriptor(current, key) - if (descriptor) return descriptor - current = Object.getPrototypeOf(current) as object | null - } - return undefined -} - -/** Track shallow options snapshots and defer getter reads until inputs are bound. */ -export function injectReactiveOptions< - TOptions extends object, - TInstance extends { setOptions: (options: Partial) => void }, ->( - options: AngularPacerOptions, - defaults: PacerProviderOptions, - kind: keyof PacerProviderOptions, - create: (options: TOptions, getPublicInstance: () => TInstance) => TInstance, -): TInstance { - const defaultDescriptor = getPropertyDescriptor(defaults, kind) - const reactive = - typeof options === 'function' || - hasEnumerableGetter(options) || - typeof defaultDescriptor?.get === 'function' || - hasEnumerableGetter(defaultDescriptor?.value) - const readOptions = (): TOptions => - ({ - ...defaults[kind], - ...(typeof options === 'function' ? options() : options), - }) as TOptions - - if (!reactive) { - const result: TInstance = untracked(() => - create(readOptions(), () => result), - ) - return result - } - - const injector = inject(Injector) - const destroyRef = inject(DestroyRef) - const currentOptions = computed(readOptions) - let instance: TInstance | undefined - let destroyed = false - destroyRef.onDestroy(() => { - destroyed = true - }) - - const getInstance = (): TInstance => { - if (!instance) { - if (destroyed) { - throw new Error( - 'Cannot initialize a Pacer utility after its injection context is destroyed.', - ) - } - instance = untracked(() => - runInInjectionContext(injector, () => - create(currentOptions(), () => result), - ), - ) - } - return instance - } - - const result = new Proxy({} as TInstance, { - get: (_target, property) => Reflect.get(getInstance(), property), - set: (_target, property, value) => - Reflect.set(getInstance(), property, value), - has: (_target, property) => Reflect.has(getInstance(), property), - ownKeys: () => Reflect.ownKeys(getInstance()), - getOwnPropertyDescriptor: (_target, property) => - Reflect.getOwnPropertyDescriptor(getInstance(), property), - }) - - effect(() => { - const latest = currentOptions() - untracked(() => { - if (instance) { - instance.setOptions(latest) - } else { - getInstance() - } - }) - }) - return result -} diff --git a/packages/angular-pacer/src/utils/injectSelector.ts b/packages/angular-pacer/src/utils/injectSelector.ts new file mode 100644 index 000000000..74ca729a0 --- /dev/null +++ b/packages/angular-pacer/src/utils/injectSelector.ts @@ -0,0 +1,34 @@ +import { injectExternalStore } from './injectExternalStore' +import type { ReadonlySelected } from './readonlySelected' +import type { Readable } from '@tanstack/store' +import type { Signal, ValueEqualityFn } from '@angular/core' + +export function injectSelector( + source: () => Readable, + selector?: undefined, + options?: { compare?: ValueEqualityFn<{}> }, +): Signal<{}> +export function injectSelector( + source: () => Readable, + selector: (state: NoInfer) => TSelected, + options?: { compare?: ValueEqualityFn }, +): Signal> +export function injectSelector( + source: () => Readable, + selector: (state: NoInfer) => TSelected | {} = () => ({}), + options?: { compare?: ValueEqualityFn }, +) { + return injectExternalStore( + () => { + const store = source() + return { + getSnapshot: () => selector(store.get()), + subscribe: (notify) => { + const subscription = store.subscribe(notify) + return () => subscription.unsubscribe() + }, + } + }, + { equal: options?.compare }, + ) as Signal> +} diff --git a/packages/angular-pacer/src/utils/maybeAccessor.ts b/packages/angular-pacer/src/utils/maybeAccessor.ts new file mode 100644 index 000000000..9350af326 --- /dev/null +++ b/packages/angular-pacer/src/utils/maybeAccessor.ts @@ -0,0 +1,13 @@ +import { computed } from '@angular/core' + +export type MaybeAccessor = T | (() => T) + +function isAccessor(value: MaybeAccessor): value is () => T { + return typeof value === 'function' +} + +export function toAccessorSignal(maybeAccesor: MaybeAccessor) { + return computed(() => + isAccessor(maybeAccesor) ? maybeAccesor() : maybeAccesor, + ) +} diff --git a/packages/angular-pacer/src/utils/readonlySelected.ts b/packages/angular-pacer/src/utils/readonlySelected.ts new file mode 100644 index 000000000..58fe50aaf --- /dev/null +++ b/packages/angular-pacer/src/utils/readonlySelected.ts @@ -0,0 +1,4 @@ +/** Preserves callable selector results while making other selected values readonly. */ +export type ReadonlySelected = T extends (...args: Array) => unknown + ? T + : Readonly diff --git a/packages/angular-pacer/tsconfig.docs.json b/packages/angular-pacer/tsconfig.docs.json index 20ccb038c..1871f550e 100644 --- a/packages/angular-pacer/tsconfig.docs.json +++ b/packages/angular-pacer/tsconfig.docs.json @@ -2,7 +2,8 @@ "extends": "./tsconfig.json", "compilerOptions": { "paths": { - "@tanstack/pacer": ["../pacer/src"] + "@tanstack/pacer": ["../pacer/src"], + "@tanstack/pacer/*": ["../pacer/src/*"] } }, "include": ["src"] diff --git a/packages/angular-pacer/tsdown.config.ts b/packages/angular-pacer/tsdown.config.ts index d53daf4e6..0539182e9 100644 --- a/packages/angular-pacer/tsdown.config.ts +++ b/packages/angular-pacer/tsdown.config.ts @@ -7,8 +7,8 @@ export default defineConfig({ './src/async-debouncer/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', - './src/async-retryer/index.ts', './src/async-throttler/index.ts', + './src/async-retryer/index.ts', './src/batcher/index.ts', './src/debouncer/index.ts', './src/provider/index.ts', From 0a2cc067496160588dcff9f6b4d8253c913ebde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Thu, 8 Oct 2026 08:48:04 -0300 Subject: [PATCH 03/18] docs(examples): migrate Angular examples to the reworked APIs Rename value and queue-items examples to match the public APIs, add editable queued signal and computed demonstrations, and update signal writes, selected state, unit regressions, browser assertions, and documentation navigation. Keep renamed example lockfile entries with the examples. Validation: pnpm test:pr passes; Angular example unit tests and browser regressions passed during the rework. --- docs/config.json | 56 +++++++------ .../injectAsyncQueuedSignal/src/app/app.html | 34 -------- .../injectAsyncQueuer/src/app/app.spec.ts | 1 - .../tests/e2e/smoke.spec.mts | 5 +- .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../injectAsyncQueuerItems/src/app/app.html | 24 ++++++ .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 16 ++-- .../src/app/app.ts | 20 +++-- .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 10 +-- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../src/app/app.spec.ts | 1 - .../tests/e2e/smoke.spec.mts | 5 +- .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 13 +-- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 16 +--- .../injectDebouncedComputed/src/app/app.ts | 34 ++++++++ .../src/app/inputapp.ts | 15 ++++ .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 7 +- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../injectDebouncedSignal/src/app/app.html | 11 --- .../injectDebouncedSignal/src/app/app.spec.ts | 4 +- .../injectDebouncedSignal/src/app/app.ts | 37 ++------ .../injectDebouncedValue/src/app/app.ts | 57 ------------- .../injectDebouncedValue/src/app/inputapp.ts | 27 ------ .../README.md | 0 .../angular.json | 0 .../injectQueuedComputed/eslint.config.js | 15 ++++ .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 15 ++++ .../src/app/app.css | 0 .../src/app/app.html | 12 +-- .../src/app/app.routes.ts | 3 + .../injectQueuedComputed/src/app/app.spec.ts | 55 ++++++++++++ .../injectQueuedComputed/src/app/app.ts | 30 +++++++ .../injectQueuedComputed/src/app/inputapp.ts | 22 +++++ .../src/index.html | 0 .../angular/injectQueuedComputed/src/main.ts | 5 ++ .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 54 ++++++------ .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../injectQueuedSignal/eslint.config.js | 4 +- .../injectQueuedSignal/src/app/app.config.ts | 14 ++-- .../injectQueuedSignal/src/app/app.html | 46 +++++----- .../injectQueuedSignal/src/app/app.routes.ts | 4 +- .../injectQueuedSignal/src/app/app.spec.ts | 76 +++++++++-------- .../angular/injectQueuedSignal/src/app/app.ts | 46 ++++------ .../angular/injectQueuedSignal/src/main.ts | 8 +- .../tests/e2e/smoke.spec.mts | 64 +++++++------- .../angular/injectQueuedValue/src/app/app.ts | 28 ------- .../injectQueuedValue/src/app/inputapp.ts | 26 ------ .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../injectQueuerItems/src/app/app.html | 43 ++++++++++ .../src/app/app.routes.ts | 0 .../injectQueuerItems/src/app/app.spec.ts | 53 ++++++++++++ .../angular/injectQueuerItems/src/app/app.ts | 37 ++++++++ .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 48 +++++++++++ .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 19 +---- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 12 +-- .../injectRateLimitedComputed/src/app/app.ts | 45 ++++++++++ .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 2 +- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../injectRateLimitedSignal/src/app/app.html | 20 ----- .../src/app/app.spec.ts | 12 --- .../injectRateLimitedSignal/src/app/app.ts | 60 ++++--------- .../src/app/app.spec.ts | 79 ------------------ .../injectRateLimitedValue/src/app/app.ts | 72 ---------------- .../injectRateLimiter/src/app/app.spec.ts | 1 - .../angular/injectThrottledComputed/README.md | 59 +++++++++++++ .../injectThrottledComputed/angular.json | 72 ++++++++++++++++ .../eslint.config.js | 0 .../injectThrottledComputed/package.json | 46 ++++++++++ .../public/favicon.ico | Bin 0 -> 15086 bytes .../src/app/app.config.ts | 0 .../injectThrottledComputed/src/app/app.css | 19 +++++ .../src/app/app.html | 13 +-- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 22 ++--- .../injectThrottledComputed/src/app/app.ts | 27 ++++++ .../injectThrottledComputed/src/index.html | 13 +++ .../src/main.ts | 0 .../injectThrottledComputed/src/styles.css | 1 + .../tests/e2e/smoke.spec.mts | 2 +- .../injectThrottledComputed/tsconfig.app.json | 11 +++ .../injectThrottledComputed/tsconfig.json | 33 ++++++++ .../tsconfig.spec.json | 10 +++ .../injectThrottledSignal/src/app/app.html | 11 --- .../injectThrottledSignal/src/app/app.ts | 27 +----- .../injectThrottledValue/src/app/app.ts | 51 ----------- pnpm-lock.yaml | 75 ++++++++++++++--- 141 files changed, 1077 insertions(+), 848 deletions(-) delete mode 100644 examples/angular/injectAsyncQueuedSignal/src/app/app.html rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/README.md (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/angular.json (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/eslint.config.js (100%) rename examples/angular/{injectDebouncedValue => injectAsyncQueuerItems}/package.json (94%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/public/favicon.ico (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/app/app.config.ts (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/app/app.css (100%) create mode 100644 examples/angular/injectAsyncQueuerItems/src/app/app.html rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/app/app.routes.ts (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/app/app.spec.ts (81%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/app/app.ts (73%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/index.html (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/main.ts (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/src/styles.css (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/tests/e2e/smoke.spec.mts (80%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/tsconfig.app.json (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/tsconfig.json (100%) rename examples/angular/{injectAsyncQueuedSignal => injectAsyncQueuerItems}/tsconfig.spec.json (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/README.md (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/angular.json (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/eslint.config.js (100%) rename examples/angular/{injectRateLimitedValue => injectDebouncedComputed}/package.json (94%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/public/favicon.ico (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/app/app.config.ts (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/app/app.css (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/app/app.html (65%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/app/app.routes.ts (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/app/app.spec.ts (79%) create mode 100644 examples/angular/injectDebouncedComputed/src/app/app.ts create mode 100644 examples/angular/injectDebouncedComputed/src/app/inputapp.ts rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/index.html (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/main.ts (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/src/styles.css (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/tests/e2e/smoke.spec.mts (85%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/tsconfig.app.json (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/tsconfig.json (100%) rename examples/angular/{injectDebouncedValue => injectDebouncedComputed}/tsconfig.spec.json (100%) delete mode 100644 examples/angular/injectDebouncedValue/src/app/app.ts delete mode 100644 examples/angular/injectDebouncedValue/src/app/inputapp.ts rename examples/angular/{injectQueuedValue => injectQueuedComputed}/README.md (100%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/angular.json (100%) create mode 100644 examples/angular/injectQueuedComputed/eslint.config.js rename examples/angular/{injectThrottledValue => injectQueuedComputed}/package.json (94%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/public/favicon.ico (100%) create mode 100644 examples/angular/injectQueuedComputed/src/app/app.config.ts rename examples/angular/{injectQueuedValue => injectQueuedComputed}/src/app/app.css (100%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/src/app/app.html (85%) create mode 100644 examples/angular/injectQueuedComputed/src/app/app.routes.ts create mode 100644 examples/angular/injectQueuedComputed/src/app/app.spec.ts create mode 100644 examples/angular/injectQueuedComputed/src/app/app.ts create mode 100644 examples/angular/injectQueuedComputed/src/app/inputapp.ts rename examples/angular/{injectQueuedValue => injectQueuedComputed}/src/index.html (100%) create mode 100644 examples/angular/injectQueuedComputed/src/main.ts rename examples/angular/{injectQueuedValue => injectQueuedComputed}/src/styles.css (100%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/tests/e2e/smoke.spec.mts (67%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/tsconfig.app.json (100%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/tsconfig.json (100%) rename examples/angular/{injectQueuedValue => injectQueuedComputed}/tsconfig.spec.json (100%) delete mode 100644 examples/angular/injectQueuedValue/src/app/app.ts delete mode 100644 examples/angular/injectQueuedValue/src/app/inputapp.ts rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/README.md (100%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/angular.json (100%) rename examples/angular/{injectQueuedValue => injectQueuerItems}/eslint.config.js (100%) rename examples/angular/{injectQueuedValue => injectQueuerItems}/package.json (94%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/public/favicon.ico (100%) rename examples/angular/{injectQueuedValue => injectQueuerItems}/src/app/app.config.ts (100%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/src/app/app.css (100%) create mode 100644 examples/angular/injectQueuerItems/src/app/app.html rename examples/angular/{injectQueuedValue => injectQueuerItems}/src/app/app.routes.ts (100%) create mode 100644 examples/angular/injectQueuerItems/src/app/app.spec.ts create mode 100644 examples/angular/injectQueuerItems/src/app/app.ts rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/src/index.html (100%) rename examples/angular/{injectQueuedValue => injectQueuerItems}/src/main.ts (100%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/src/styles.css (100%) create mode 100644 examples/angular/injectQueuerItems/tests/e2e/smoke.spec.mts rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/tsconfig.app.json (100%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/tsconfig.json (100%) rename examples/angular/{injectRateLimitedValue => injectQueuerItems}/tsconfig.spec.json (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/README.md (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/angular.json (100%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/eslint.config.js (100%) rename examples/angular/{injectAsyncQueuedSignal => injectRateLimitedComputed}/package.json (94%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/public/favicon.ico (100%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/src/app/app.config.ts (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/src/app/app.css (100%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/src/app/app.html (69%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/src/app/app.routes.ts (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/src/app/app.spec.ts (76%) create mode 100644 examples/angular/injectRateLimitedComputed/src/app/app.ts rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/src/index.html (100%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/src/main.ts (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/src/styles.css (100%) rename examples/angular/{injectRateLimitedValue => injectRateLimitedComputed}/tests/e2e/smoke.spec.mts (96%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/tsconfig.app.json (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/tsconfig.json (100%) rename examples/angular/{injectThrottledValue => injectRateLimitedComputed}/tsconfig.spec.json (100%) delete mode 100644 examples/angular/injectRateLimitedValue/src/app/app.spec.ts delete mode 100644 examples/angular/injectRateLimitedValue/src/app/app.ts create mode 100644 examples/angular/injectThrottledComputed/README.md create mode 100644 examples/angular/injectThrottledComputed/angular.json rename examples/angular/{injectThrottledValue => injectThrottledComputed}/eslint.config.js (100%) create mode 100644 examples/angular/injectThrottledComputed/package.json create mode 100644 examples/angular/injectThrottledComputed/public/favicon.ico rename examples/angular/{injectThrottledValue => injectThrottledComputed}/src/app/app.config.ts (100%) create mode 100644 examples/angular/injectThrottledComputed/src/app/app.css rename examples/angular/{injectThrottledValue => injectThrottledComputed}/src/app/app.html (63%) rename examples/angular/{injectThrottledValue => injectThrottledComputed}/src/app/app.routes.ts (100%) rename examples/angular/{injectQueuedValue => injectThrottledComputed}/src/app/app.spec.ts (74%) create mode 100644 examples/angular/injectThrottledComputed/src/app/app.ts create mode 100644 examples/angular/injectThrottledComputed/src/index.html rename examples/angular/{injectThrottledValue => injectThrottledComputed}/src/main.ts (100%) create mode 100644 examples/angular/injectThrottledComputed/src/styles.css rename examples/angular/{injectThrottledValue => injectThrottledComputed}/tests/e2e/smoke.spec.mts (96%) create mode 100644 examples/angular/injectThrottledComputed/tsconfig.app.json create mode 100644 examples/angular/injectThrottledComputed/tsconfig.json create mode 100644 examples/angular/injectThrottledComputed/tsconfig.spec.json delete mode 100644 examples/angular/injectThrottledValue/src/app/app.ts diff --git a/docs/config.json b/docs/config.json index 8854ebcf2..9468cc057 100644 --- a/docs/config.json +++ b/docs/config.json @@ -969,8 +969,8 @@ "to": "framework/angular/reference/functions/injectDebouncedSignal" }, { - "label": "injectDebouncedValue", - "to": "framework/angular/reference/functions/injectDebouncedValue" + "label": "injectDebouncedComputed", + "to": "framework/angular/reference/functions/injectDebouncedComputed" }, { "label": "injectAsyncDebouncer", @@ -1444,8 +1444,8 @@ "to": "framework/angular/reference/functions/injectThrottledSignal" }, { - "label": "injectThrottledValue", - "to": "framework/angular/reference/functions/injectThrottledValue" + "label": "injectThrottledComputed", + "to": "framework/angular/reference/functions/injectThrottledComputed" }, { "label": "injectAsyncThrottler", @@ -1895,8 +1895,8 @@ "to": "framework/angular/reference/functions/injectRateLimitedSignal" }, { - "label": "injectRateLimitedValue", - "to": "framework/angular/reference/functions/injectRateLimitedValue" + "label": "injectRateLimitedComputed", + "to": "framework/angular/reference/functions/injectRateLimitedComputed" }, { "label": "injectAsyncRateLimiter", @@ -2354,20 +2354,24 @@ "to": "framework/angular/reference/functions/injectQueuer" }, { - "label": "injectQueuedSignal", - "to": "framework/angular/reference/functions/injectQueuedSignal" + "label": "injectQueuerItems", + "to": "framework/angular/reference/functions/injectQueuerItems" + }, + { + "label": "injectQueuedComputed", + "to": "framework/angular/reference/functions/injectQueuedComputed" }, { - "label": "injectQueuedValue", - "to": "framework/angular/reference/functions/injectQueuedValue" + "label": "injectQueuedSignal", + "to": "framework/angular/reference/functions/injectQueuedSignal" }, { "label": "injectAsyncQueuer", "to": "framework/angular/reference/functions/injectAsyncQueuer" }, { - "label": "injectAsyncQueuedSignal", - "to": "framework/angular/reference/functions/injectAsyncQueuedSignal" + "label": "injectAsyncQueuerItems", + "to": "framework/angular/reference/functions/injectAsyncQueuerItems" } ] }, @@ -3170,8 +3174,8 @@ "to": "framework/angular/examples/injectDebouncedSignal" }, { - "label": "injectDebouncedValue", - "to": "framework/angular/examples/injectDebouncedValue" + "label": "injectDebouncedComputed", + "to": "framework/angular/examples/injectDebouncedComputed" }, { "label": "injectAsyncDebouncer", @@ -3483,8 +3487,8 @@ "to": "framework/angular/examples/injectThrottledSignal" }, { - "label": "injectThrottledValue", - "to": "framework/angular/examples/injectThrottledValue" + "label": "injectThrottledComputed", + "to": "framework/angular/examples/injectThrottledComputed" }, { "label": "injectAsyncThrottler", @@ -3788,8 +3792,8 @@ "to": "framework/angular/examples/injectRateLimitedSignal" }, { - "label": "injectRateLimitedValue", - "to": "framework/angular/examples/injectRateLimitedValue" + "label": "injectRateLimitedComputed", + "to": "framework/angular/examples/injectRateLimitedComputed" }, { "label": "injectAsyncRateLimiter", @@ -4069,20 +4073,24 @@ "to": "framework/angular/examples/injectQueuer" }, { - "label": "injectQueuedSignal", - "to": "framework/angular/examples/injectQueuedSignal" + "label": "injectQueuerItems", + "to": "framework/angular/examples/injectQueuerItems" + }, + { + "label": "injectQueuedComputed", + "to": "framework/angular/examples/injectQueuedComputed" }, { - "label": "injectQueuedValue", - "to": "framework/angular/examples/injectQueuedValue" + "label": "injectQueuedSignal", + "to": "framework/angular/examples/injectQueuedSignal" }, { "label": "injectAsyncQueuer", "to": "framework/angular/examples/injectAsyncQueuer" }, { - "label": "injectAsyncQueuedSignal", - "to": "framework/angular/examples/injectAsyncQueuedSignal" + "label": "injectAsyncQueuerItems", + "to": "framework/angular/examples/injectAsyncQueuerItems" } ] }, diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.html b/examples/angular/injectAsyncQueuedSignal/src/app/app.html deleted file mode 100644 index f816b6a47..000000000 --- a/examples/angular/injectAsyncQueuedSignal/src/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -

TanStack Pacer injectAsyncQueuedSignal Example

-

Queue Size: {{ runner.state().size }} / 25

-

- Queue Full: {{ runner.state().isFull }}, Empty: {{ runner.state().isEmpty }}, Idle: {{ - runner.state().isIdle }} -

-

Queuer Status: {{ runner.state().status }}

-

- Items Processed: {{ runner.state().successCount }}, Items Rejected: {{ - runner.state().rejectionCount }} -

-

Active Tasks: {{ runner.state().activeItems.length }}, Pending Tasks: {{ queue().length }}

- -

Queue Items

-
    - @for (item of queue(); track $index) { -
  1. {{ item }}
  2. - } -
- - - - - - -

Completed values: {{ processed().join(', ') }}

-
{{ runner.state() | json }}
diff --git a/examples/angular/injectAsyncQueuer/src/app/app.spec.ts b/examples/angular/injectAsyncQueuer/src/app/app.spec.ts index 7379b6331..469b26144 100644 --- a/examples/angular/injectAsyncQueuer/src/app/app.spec.ts +++ b/examples/angular/injectAsyncQueuer/src/app/app.spec.ts @@ -47,7 +47,6 @@ describe('example behavior', () => { app.setConcurrency('3') TestBed.tick() expect(app.queue).toBe(queue) - expect(queue.options.concurrency).toBe(3) expect(queue.state().items).toHaveLength(10) queue.start() await vi.advanceTimersByTimeAsync(350) diff --git a/examples/angular/injectAsyncQueuer/tests/e2e/smoke.spec.mts b/examples/angular/injectAsyncQueuer/tests/e2e/smoke.spec.mts index 98fb686a8..06f889b8e 100644 --- a/examples/angular/injectAsyncQueuer/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectAsyncQueuer/tests/e2e/smoke.spec.mts @@ -9,8 +9,9 @@ test.beforeEach(async ({ page, exampleUrl }) => { exact: true, }), ).toBeVisible() - // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) + // Pause after bootstrap with enough margin for the browser round trip on busy hosts. + // runFor below also advances Angular render frames. + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 10_000)) }) test('renders the stopped queue with ten seeded tasks', async ({ page }) => { diff --git a/examples/angular/injectAsyncQueuedSignal/README.md b/examples/angular/injectAsyncQueuerItems/README.md similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/README.md rename to examples/angular/injectAsyncQueuerItems/README.md diff --git a/examples/angular/injectAsyncQueuedSignal/angular.json b/examples/angular/injectAsyncQueuerItems/angular.json similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/angular.json rename to examples/angular/injectAsyncQueuerItems/angular.json diff --git a/examples/angular/injectAsyncQueuedSignal/eslint.config.js b/examples/angular/injectAsyncQueuerItems/eslint.config.js similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/eslint.config.js rename to examples/angular/injectAsyncQueuerItems/eslint.config.js diff --git a/examples/angular/injectDebouncedValue/package.json b/examples/angular/injectAsyncQueuerItems/package.json similarity index 94% rename from examples/angular/injectDebouncedValue/package.json rename to examples/angular/injectAsyncQueuerItems/package.json index 81c557e7e..3ee3bebc1 100644 --- a/examples/angular/injectDebouncedValue/package.json +++ b/examples/angular/injectAsyncQueuerItems/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-debounced-value", + "name": "@tanstack/pacer-example-angular-inject-async-queuer-items", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectAsyncQueuedSignal/public/favicon.ico b/examples/angular/injectAsyncQueuerItems/public/favicon.ico similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/public/favicon.ico rename to examples/angular/injectAsyncQueuerItems/public/favicon.ico diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.config.ts b/examples/angular/injectAsyncQueuerItems/src/app/app.config.ts similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/app/app.config.ts rename to examples/angular/injectAsyncQueuerItems/src/app/app.config.ts diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.css b/examples/angular/injectAsyncQueuerItems/src/app/app.css similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/app/app.css rename to examples/angular/injectAsyncQueuerItems/src/app/app.css diff --git a/examples/angular/injectAsyncQueuerItems/src/app/app.html b/examples/angular/injectAsyncQueuerItems/src/app/app.html new file mode 100644 index 000000000..67c16b2e4 --- /dev/null +++ b/examples/angular/injectAsyncQueuerItems/src/app/app.html @@ -0,0 +1,24 @@ +

TanStack Pacer injectAsyncQueuerItems Example

+

Pending Tasks: {{ queue().length }}

+

Items Processed: {{ processed().length }}

+ +

Queue Items

+
    + @for (item of queue(); track $index) { +
  1. {{ item }}
  2. + } +
+ + + + + + +

Completed values: {{ processed().join(', ') }}

diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.routes.ts b/examples/angular/injectAsyncQueuerItems/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/app/app.routes.ts rename to examples/angular/injectAsyncQueuerItems/src/app/app.routes.ts diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.spec.ts b/examples/angular/injectAsyncQueuerItems/src/app/app.spec.ts similarity index 81% rename from examples/angular/injectAsyncQueuedSignal/src/app/app.spec.ts rename to examples/angular/injectAsyncQueuerItems/src/app/app.spec.ts index 335bcd396..a71cbd165 100644 --- a/examples/angular/injectAsyncQueuedSignal/src/app/app.spec.ts +++ b/examples/angular/injectAsyncQueuerItems/src/app/app.spec.ts @@ -20,7 +20,7 @@ describe('App', () => { await fixture.whenStable() const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectAsyncQueuedSignal Example', + 'TanStack Pacer injectAsyncQueuerItems Example', ) }) }) @@ -43,17 +43,17 @@ describe('example behavior', () => { fixture.detectChanges() TestBed.tick() const runner = app.runner - expect(runner.state().items).toHaveLength(10) + expect(app.queue()).toHaveLength(10) app.setConcurrency('3') TestBed.tick() expect(app.runner).toBe(runner) - expect(runner.options.concurrency).toBe(3) - expect(runner.state().items).toHaveLength(10) - runner.start() + expect(app.queue()).toHaveLength(10) + app.start() await vi.advanceTimersByTimeAsync(350) - expect(runner.state().activeItems).toHaveLength(3) + expect(app.queue()).toHaveLength(7) + expect(app.processed()).toEqual([]) await vi.advanceTimersByTimeAsync(500) - expect(runner.state().successCount).toBeGreaterThanOrEqual(3) - runner.stop() + expect(app.processed().length).toBeGreaterThanOrEqual(3) + app.stop() }) }) diff --git a/examples/angular/injectAsyncQueuedSignal/src/app/app.ts b/examples/angular/injectAsyncQueuerItems/src/app/app.ts similarity index 73% rename from examples/angular/injectAsyncQueuedSignal/src/app/app.ts rename to examples/angular/injectAsyncQueuerItems/src/app/app.ts index a2e31cea9..17f5ed407 100644 --- a/examples/angular/injectAsyncQueuedSignal/src/app/app.ts +++ b/examples/angular/injectAsyncQueuerItems/src/app/app.ts @@ -1,16 +1,15 @@ import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectAsyncQueuedSignal } from '@tanstack/angular-pacer' +import { injectAsyncQueuerItems } from '@tanstack/angular-pacer' @Component({ selector: 'app-root', templateUrl: './app.html', - imports: [JsonPipe], }) export class App { readonly concurrency = signal(2) + readonly running = signal(false) readonly processed = signal>([]) - readonly queue = injectAsyncQueuedSignal( + readonly queue = injectAsyncQueuerItems( async (item: number) => { await new Promise((resolve) => setTimeout(resolve, 500)) this.processed.update((items) => [...items, item]) @@ -27,12 +26,19 @@ export class App { onError: (error, item, queuer) => console.error(`Error processing item: ${item}`, error, queuer.store.state.errorCount), }), - (state) => state, ) readonly runner = this.queue.queuer + start(): void { + this.running.set(true) + this.runner.start() + } + stop(): void { + this.running.set(false) + this.runner.stop() + } add(): void { - const items = this.runner.state().items - this.runner.addItem(items.length ? Math.max(...items) + 1 : 1) + const items = this.queue() + this.queue.addItem(items.length ? Math.max(...items) + 1 : 1) } setConcurrency(value: string): void { this.concurrency.set(Math.max(1, Number.parseInt(value) || 1)) diff --git a/examples/angular/injectAsyncQueuedSignal/src/index.html b/examples/angular/injectAsyncQueuerItems/src/index.html similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/index.html rename to examples/angular/injectAsyncQueuerItems/src/index.html diff --git a/examples/angular/injectAsyncQueuedSignal/src/main.ts b/examples/angular/injectAsyncQueuerItems/src/main.ts similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/main.ts rename to examples/angular/injectAsyncQueuerItems/src/main.ts diff --git a/examples/angular/injectAsyncQueuedSignal/src/styles.css b/examples/angular/injectAsyncQueuerItems/src/styles.css similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/src/styles.css rename to examples/angular/injectAsyncQueuerItems/src/styles.css diff --git a/examples/angular/injectAsyncQueuedSignal/tests/e2e/smoke.spec.mts b/examples/angular/injectAsyncQueuerItems/tests/e2e/smoke.spec.mts similarity index 80% rename from examples/angular/injectAsyncQueuedSignal/tests/e2e/smoke.spec.mts rename to examples/angular/injectAsyncQueuerItems/tests/e2e/smoke.spec.mts index a5bfabaeb..e6be2994a 100644 --- a/examples/angular/injectAsyncQueuedSignal/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectAsyncQueuerItems/tests/e2e/smoke.spec.mts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page, exampleUrl }) => { await page.goto(exampleUrl) await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectAsyncQueuedSignal Example', + name: 'TanStack Pacer injectAsyncQueuerItems Example', exact: true, }), ).toBeVisible() @@ -37,13 +37,11 @@ test('updates concurrency before starting and completes only the active tasks af await page.clock.runFor(32) await page.getByRole('button', { name: 'Start Processing', exact: true }).click() await page.clock.runFor(350) - await expect(page.getByText('Active Tasks: 3, Pending Tasks: 7', { exact: true })).toBeVisible() + await expect(page.getByText('Pending Tasks: 7', { exact: true })).toBeVisible() await page.getByRole('button', { name: 'Stop Processing', exact: true }).click() await page.clock.runFor(850) - await expect(page.getByText('Active Tasks: 0, Pending Tasks: 7', { exact: true })).toBeVisible() - await expect( - page.getByText('Items Processed: 3, Items Rejected: 0', { exact: true }), - ).toBeVisible() + await expect(page.getByText('Pending Tasks: 7', { exact: true })).toBeVisible() + await expect(page.getByText('Items Processed: 3', { exact: true })).toBeVisible() await expect(page.getByText('Completed values: 1, 2, 3', { exact: true })).toBeVisible() await expect(page.getByRole('listitem')).toHaveText(['4', '5', '6', '7', '8', '9', '10']) }) diff --git a/examples/angular/injectAsyncQueuedSignal/tsconfig.app.json b/examples/angular/injectAsyncQueuerItems/tsconfig.app.json similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/tsconfig.app.json rename to examples/angular/injectAsyncQueuerItems/tsconfig.app.json diff --git a/examples/angular/injectAsyncQueuedSignal/tsconfig.json b/examples/angular/injectAsyncQueuerItems/tsconfig.json similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/tsconfig.json rename to examples/angular/injectAsyncQueuerItems/tsconfig.json diff --git a/examples/angular/injectAsyncQueuedSignal/tsconfig.spec.json b/examples/angular/injectAsyncQueuerItems/tsconfig.spec.json similarity index 100% rename from examples/angular/injectAsyncQueuedSignal/tsconfig.spec.json rename to examples/angular/injectAsyncQueuerItems/tsconfig.spec.json diff --git a/examples/angular/injectAsyncRateLimiter/src/app/app.spec.ts b/examples/angular/injectAsyncRateLimiter/src/app/app.spec.ts index 456740071..475b046dd 100644 --- a/examples/angular/injectAsyncRateLimiter/src/app/app.spec.ts +++ b/examples/angular/injectAsyncRateLimiter/src/app/app.spec.ts @@ -63,7 +63,6 @@ describe('example behavior', () => { app.changeWindow('sliding') TestBed.tick() expect(app.runner).toBe(runner) - expect(runner.options.windowType).toBe('sliding') for (let index = 0; index < 4; index++) void app.onSearch(String(index)) await vi.advanceTimersByTimeAsync(500) expect(runner.state().successCount).toBe(3) diff --git a/examples/angular/injectAsyncThrottler/tests/e2e/smoke.spec.mts b/examples/angular/injectAsyncThrottler/tests/e2e/smoke.spec.mts index 9acd95ed9..8f494969d 100644 --- a/examples/angular/injectAsyncThrottler/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectAsyncThrottler/tests/e2e/smoke.spec.mts @@ -9,8 +9,9 @@ test.beforeEach(async ({ page, exampleUrl }) => { exact: true, }), ).toBeVisible() - // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) + // Pause after bootstrap with enough margin for the browser round trip on busy hosts. + // runFor below also advances Angular render frames. + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 10_000)) }) test('renders the search example with no results', async ({ page }) => { diff --git a/examples/angular/injectDebouncedValue/README.md b/examples/angular/injectDebouncedComputed/README.md similarity index 100% rename from examples/angular/injectDebouncedValue/README.md rename to examples/angular/injectDebouncedComputed/README.md diff --git a/examples/angular/injectDebouncedValue/angular.json b/examples/angular/injectDebouncedComputed/angular.json similarity index 100% rename from examples/angular/injectDebouncedValue/angular.json rename to examples/angular/injectDebouncedComputed/angular.json diff --git a/examples/angular/injectDebouncedValue/eslint.config.js b/examples/angular/injectDebouncedComputed/eslint.config.js similarity index 100% rename from examples/angular/injectDebouncedValue/eslint.config.js rename to examples/angular/injectDebouncedComputed/eslint.config.js diff --git a/examples/angular/injectRateLimitedValue/package.json b/examples/angular/injectDebouncedComputed/package.json similarity index 94% rename from examples/angular/injectRateLimitedValue/package.json rename to examples/angular/injectDebouncedComputed/package.json index 4df08dd37..b8ce35b04 100644 --- a/examples/angular/injectRateLimitedValue/package.json +++ b/examples/angular/injectDebouncedComputed/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-rate-limited-value", + "name": "@tanstack/pacer-example-angular-inject-debounced-computed", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectDebouncedValue/public/favicon.ico b/examples/angular/injectDebouncedComputed/public/favicon.ico similarity index 100% rename from examples/angular/injectDebouncedValue/public/favicon.ico rename to examples/angular/injectDebouncedComputed/public/favicon.ico diff --git a/examples/angular/injectDebouncedValue/src/app/app.config.ts b/examples/angular/injectDebouncedComputed/src/app/app.config.ts similarity index 100% rename from examples/angular/injectDebouncedValue/src/app/app.config.ts rename to examples/angular/injectDebouncedComputed/src/app/app.config.ts diff --git a/examples/angular/injectDebouncedValue/src/app/app.css b/examples/angular/injectDebouncedComputed/src/app/app.css similarity index 100% rename from examples/angular/injectDebouncedValue/src/app/app.css rename to examples/angular/injectDebouncedComputed/src/app/app.css diff --git a/examples/angular/injectDebouncedValue/src/app/app.html b/examples/angular/injectDebouncedComputed/src/app/app.html similarity index 65% rename from examples/angular/injectDebouncedValue/src/app/app.html rename to examples/angular/injectDebouncedComputed/src/app/app.html index 3495b4146..0a38d4e65 100644 --- a/examples/angular/injectDebouncedValue/src/app/app.html +++ b/examples/angular/injectDebouncedComputed/src/app/app.html @@ -1,12 +1,9 @@ -

TanStack Pacer injectDebouncedValue Example

+

TanStack Pacer injectDebouncedComputed Example

Counter

Instant Count: {{ instantCount() }}

Debounced Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Pending: {{ countRunner.state().isPending }}

-
{{ countRunner.state() | json }}

Search

@@ -21,9 +18,6 @@

Search

/>

Instant Search: {{ search() }}

Debounced Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Pending: {{ searchRunner.state().isPending }}

-
{{ searchRunner.state() | json }}

Range

@@ -41,12 +35,7 @@

Range

>Debounced Range

Debounced Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Pending: {{ rangeRunner.state().isPending }}

Values Submitted: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

-
{{ rangeRunner.state() | json }}

Debounced with a 250 ms wait.


diff --git a/examples/angular/injectDebouncedValue/src/app/app.routes.ts b/examples/angular/injectDebouncedComputed/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectDebouncedValue/src/app/app.routes.ts rename to examples/angular/injectDebouncedComputed/src/app/app.routes.ts diff --git a/examples/angular/injectDebouncedValue/src/app/app.spec.ts b/examples/angular/injectDebouncedComputed/src/app/app.spec.ts similarity index 79% rename from examples/angular/injectDebouncedValue/src/app/app.spec.ts rename to examples/angular/injectDebouncedComputed/src/app/app.spec.ts index 287668c55..1961f05e4 100644 --- a/examples/angular/injectDebouncedValue/src/app/app.spec.ts +++ b/examples/angular/injectDebouncedComputed/src/app/app.spec.ts @@ -20,7 +20,7 @@ describe('App', () => { await fixture.whenStable() const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectDebouncedValue Example', + 'TanStack Pacer injectDebouncedComputed Example', ) }) }) @@ -69,16 +69,6 @@ describe('example behavior', () => { await vi.advanceTimersByTimeAsync(1100) expect(app.controlledSearch()).toBe('abc') }) - it('counts the initial value when displaying saved executions', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - await vi.advanceTimersByTimeAsync(1100) - expect(app.instantExecutions() - app.rangeRunner.state().executionCount).toBe(0) - expect(app.reduction()).toBe(0) - }) it('cancels pending search work when the condition becomes false', async () => { vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) const fixture = TestBed.createComponent(App) @@ -89,11 +79,9 @@ describe('example behavior', () => { TestBed.tick() app.onSearch('abcd') TestBed.tick() - expect(app.searchRunner.state().isPending).toBe(true) app.onSearch('ab') TestBed.tick() - expect(app.searchRunner.state().isPending).toBe(false) await vi.advanceTimersByTimeAsync(1100) - expect(app.searchRunner.state().isPending).toBe(false) + expect(app.controlledSearch()).toBe('') }) }) diff --git a/examples/angular/injectDebouncedComputed/src/app/app.ts b/examples/angular/injectDebouncedComputed/src/app/app.ts new file mode 100644 index 000000000..8fd12ad6b --- /dev/null +++ b/examples/angular/injectDebouncedComputed/src/app/app.ts @@ -0,0 +1,34 @@ +import { Component, signal } from '@angular/core' +import { injectDebouncedComputed } from '@tanstack/angular-pacer' +import { InputApp } from './inputapp' + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + imports: [InputApp], +}) +export class App { + readonly instantCount = signal(0) + readonly search = signal('') + readonly currentValue = signal(50) + readonly instantExecutions = signal(1) + readonly controlledCount = injectDebouncedComputed(this.instantCount, () => ({ wait: 500 })) + readonly controlledSearch = injectDebouncedComputed(this.search, () => { + // Update options when the signal changes so disabling cancels pending work. + // The callback reads the current event value before the next effect runs. + this.search() + return { wait: 500, enabled: () => this.search().length > 2 } + }) + readonly controlledValue = injectDebouncedComputed(this.currentValue, () => ({ wait: 250 })) + increment(): void { + const next = this.instantCount() + 1 + this.instantCount.set(next) + } + onSearch(value: string): void { + this.search.set(value) + } + onRange(value: number): void { + this.currentValue.set(value) + this.instantExecutions.update((count) => count + 1) + } +} diff --git a/examples/angular/injectDebouncedComputed/src/app/inputapp.ts b/examples/angular/injectDebouncedComputed/src/app/inputapp.ts new file mode 100644 index 000000000..d9aab4244 --- /dev/null +++ b/examples/angular/injectDebouncedComputed/src/app/inputapp.ts @@ -0,0 +1,15 @@ +import { Component, input } from '@angular/core' +import { injectDebouncedComputed } from '@tanstack/angular-pacer' + +@Component({ + selector: 'app-input', + template: ` +

Required input

+
value: {{ value() }}
+
debounced: {{ debounced() }}
+ `, +}) +export class InputApp { + readonly value = input.required() + readonly debounced = injectDebouncedComputed(this.value, { wait: 500 }) +} diff --git a/examples/angular/injectDebouncedValue/src/index.html b/examples/angular/injectDebouncedComputed/src/index.html similarity index 100% rename from examples/angular/injectDebouncedValue/src/index.html rename to examples/angular/injectDebouncedComputed/src/index.html diff --git a/examples/angular/injectDebouncedValue/src/main.ts b/examples/angular/injectDebouncedComputed/src/main.ts similarity index 100% rename from examples/angular/injectDebouncedValue/src/main.ts rename to examples/angular/injectDebouncedComputed/src/main.ts diff --git a/examples/angular/injectDebouncedValue/src/styles.css b/examples/angular/injectDebouncedComputed/src/styles.css similarity index 100% rename from examples/angular/injectDebouncedValue/src/styles.css rename to examples/angular/injectDebouncedComputed/src/styles.css diff --git a/examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts b/examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts similarity index 85% rename from examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts rename to examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts index 5dd51b2b2..5d0c33fbe 100644 --- a/examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts @@ -5,12 +5,13 @@ test.beforeEach(async ({ page, exampleUrl }) => { await page.goto(exampleUrl) await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectDebouncedValue Example', + name: 'TanStack Pacer injectDebouncedComputed Example', exact: true, }), ).toBeVisible() - // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) + // Pause after bootstrap with enough margin for the browser round trip on busy hosts. + // runFor below also advances Angular render frames. + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 10_000)) }) test('renders the counter, search, and disabled output range', async ({ page }) => { diff --git a/examples/angular/injectDebouncedValue/tsconfig.app.json b/examples/angular/injectDebouncedComputed/tsconfig.app.json similarity index 100% rename from examples/angular/injectDebouncedValue/tsconfig.app.json rename to examples/angular/injectDebouncedComputed/tsconfig.app.json diff --git a/examples/angular/injectDebouncedValue/tsconfig.json b/examples/angular/injectDebouncedComputed/tsconfig.json similarity index 100% rename from examples/angular/injectDebouncedValue/tsconfig.json rename to examples/angular/injectDebouncedComputed/tsconfig.json diff --git a/examples/angular/injectDebouncedValue/tsconfig.spec.json b/examples/angular/injectDebouncedComputed/tsconfig.spec.json similarity index 100% rename from examples/angular/injectDebouncedValue/tsconfig.spec.json rename to examples/angular/injectDebouncedComputed/tsconfig.spec.json diff --git a/examples/angular/injectDebouncedSignal/src/app/app.html b/examples/angular/injectDebouncedSignal/src/app/app.html index 51151d1f3..09733c6a5 100644 --- a/examples/angular/injectDebouncedSignal/src/app/app.html +++ b/examples/angular/injectDebouncedSignal/src/app/app.html @@ -4,9 +4,6 @@

Counter

Instant Count: {{ instantCount() }}

Debounced Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Pending: {{ countRunner.state().isPending }}

-
{{ countRunner.state() | json }}

Search

@@ -21,9 +18,6 @@

Search

/>

Instant Search: {{ search() }}

Debounced Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Pending: {{ searchRunner.state().isPending }}

-
{{ searchRunner.state() | json }}

Range

@@ -41,11 +35,6 @@

Range

>Debounced Range

Debounced Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Pending: {{ rangeRunner.state().isPending }}

Instant Executions: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

-
{{ rangeRunner.state() | json }}

Debounced with a 250 ms wait.

diff --git a/examples/angular/injectDebouncedSignal/src/app/app.spec.ts b/examples/angular/injectDebouncedSignal/src/app/app.spec.ts index f7d4ba1eb..724bc5c5d 100644 --- a/examples/angular/injectDebouncedSignal/src/app/app.spec.ts +++ b/examples/angular/injectDebouncedSignal/src/app/app.spec.ts @@ -79,11 +79,9 @@ describe('example behavior', () => { TestBed.tick() app.onSearch('abcd') TestBed.tick() - expect(app.searchRunner.state().isPending).toBe(true) app.onSearch('ab') TestBed.tick() - expect(app.searchRunner.state().isPending).toBe(false) await vi.advanceTimersByTimeAsync(1100) - expect(app.searchRunner.state().isPending).toBe(false) + expect(app.controlledSearch()).toBe('') }) }) diff --git a/examples/angular/injectDebouncedSignal/src/app/app.ts b/examples/angular/injectDebouncedSignal/src/app/app.ts index 8d0136873..4f38abfa7 100644 --- a/examples/angular/injectDebouncedSignal/src/app/app.ts +++ b/examples/angular/injectDebouncedSignal/src/app/app.ts @@ -1,40 +1,23 @@ import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' import { injectDebouncedSignal } from '@tanstack/angular-pacer' @Component({ selector: 'app-root', templateUrl: './app.html', - imports: [JsonPipe], }) export class App { readonly instantCount = signal(0) readonly search = signal('') readonly currentValue = signal(50) readonly instantExecutions = signal(0) - readonly controlledCount = injectDebouncedSignal( - 0, - () => ({ wait: 500 }), - (state) => state, - ) - readonly countRunner = this.controlledCount.debouncer - readonly controlledSearch = injectDebouncedSignal( - '', - () => { - // Update options when the signal changes so disabling cancels pending work. - // The callback reads the current event value before the next effect runs. - this.search() - return { wait: 500, enabled: () => this.search().length > 2 } - }, - (state) => state, - ) - readonly searchRunner = this.controlledSearch.debouncer - readonly controlledValue = injectDebouncedSignal( - 50, - () => ({ wait: 250 }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.debouncer + readonly controlledCount = injectDebouncedSignal(0, () => ({ wait: 500 })) + readonly controlledSearch = injectDebouncedSignal('', () => { + // Update options when the signal changes so disabling cancels pending work. + // The callback reads the current event value before the next effect runs. + this.search() + return { wait: 500, enabled: () => this.search().length > 2 } + }) + readonly controlledValue = injectDebouncedSignal(50, () => ({ wait: 250 })) increment(): void { const next = this.instantCount() + 1 this.instantCount.set(next) @@ -49,8 +32,4 @@ export class App { this.instantExecutions.update((count) => count + 1) this.controlledValue.set(value) } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } } diff --git a/examples/angular/injectDebouncedValue/src/app/app.ts b/examples/angular/injectDebouncedValue/src/app/app.ts deleted file mode 100644 index f4121ca12..000000000 --- a/examples/angular/injectDebouncedValue/src/app/app.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectDebouncedValue } from '@tanstack/angular-pacer' -import { InputApp } from './inputapp' - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - imports: [JsonPipe, InputApp], -}) -export class App { - readonly instantCount = signal(0) - readonly search = signal('') - readonly currentValue = signal(50) - readonly instantExecutions = signal(1) - readonly controlledCount = injectDebouncedValue( - this.instantCount, - 0, - () => ({ wait: 500 }), - (state) => state, - ) - readonly countRunner = this.controlledCount.debouncer - readonly controlledSearch = injectDebouncedValue( - this.search, - '', - () => { - // Update options when the signal changes so disabling cancels pending work. - // The callback reads the current event value before the next effect runs. - this.search() - return { wait: 500, enabled: () => this.search().length > 2 } - }, - (state) => state, - ) - readonly searchRunner = this.controlledSearch.debouncer - readonly controlledValue = injectDebouncedValue( - this.currentValue, - 50, - () => ({ wait: 250 }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.debouncer - increment(): void { - const next = this.instantCount() + 1 - this.instantCount.set(next) - } - onSearch(value: string): void { - this.search.set(value) - } - onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } -} diff --git a/examples/angular/injectDebouncedValue/src/app/inputapp.ts b/examples/angular/injectDebouncedValue/src/app/inputapp.ts deleted file mode 100644 index 6539989a9..000000000 --- a/examples/angular/injectDebouncedValue/src/app/inputapp.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, input } from '@angular/core' -import { injectDebouncedValue } from '@tanstack/angular-pacer' - -@Component({ - selector: 'app-input', - standalone: true, - template: ` -

Required input

-
value: {{ value() }}
-
debounced (no initial): {{ debouncedWithoutInitial() }}
-
debounced (with initial): {{ debouncedWithInitial() }}
- `, -}) -export class InputApp { - readonly value = input.required() - - // Required inputs are unavailable during field initialization. - // This signature avoids reading the input eagerly, so the initial value is undefined. - readonly debouncedWithoutInitial = injectDebouncedValue(this.value, { - wait: 500, - }) - - // Provide an initial value to avoid an undefined first read. - readonly debouncedWithInitial = injectDebouncedValue(this.value, '', { - wait: 500, - }) -} diff --git a/examples/angular/injectQueuedValue/README.md b/examples/angular/injectQueuedComputed/README.md similarity index 100% rename from examples/angular/injectQueuedValue/README.md rename to examples/angular/injectQueuedComputed/README.md diff --git a/examples/angular/injectQueuedValue/angular.json b/examples/angular/injectQueuedComputed/angular.json similarity index 100% rename from examples/angular/injectQueuedValue/angular.json rename to examples/angular/injectQueuedComputed/angular.json diff --git a/examples/angular/injectQueuedComputed/eslint.config.js b/examples/angular/injectQueuedComputed/eslint.config.js new file mode 100644 index 000000000..66d55766f --- /dev/null +++ b/examples/angular/injectQueuedComputed/eslint.config.js @@ -0,0 +1,15 @@ +// @ts-check + +import rootConfig from '../../../eslint.config.js'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...rootConfig, + { + languageOptions: { + parserOptions: { + project: './tsconfig.app.json', + }, + }, + }, +]; diff --git a/examples/angular/injectThrottledValue/package.json b/examples/angular/injectQueuedComputed/package.json similarity index 94% rename from examples/angular/injectThrottledValue/package.json rename to examples/angular/injectQueuedComputed/package.json index 7f1d449e5..ff3aac537 100644 --- a/examples/angular/injectThrottledValue/package.json +++ b/examples/angular/injectQueuedComputed/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-throttled-value", + "name": "@tanstack/pacer-example-angular-inject-queued-computed", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectQueuedValue/public/favicon.ico b/examples/angular/injectQueuedComputed/public/favicon.ico similarity index 100% rename from examples/angular/injectQueuedValue/public/favicon.ico rename to examples/angular/injectQueuedComputed/public/favicon.ico diff --git a/examples/angular/injectQueuedComputed/src/app/app.config.ts b/examples/angular/injectQueuedComputed/src/app/app.config.ts new file mode 100644 index 000000000..7ca8b23fc --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/app/app.config.ts @@ -0,0 +1,15 @@ +import { isDevMode } from '@angular/core'; +import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider'; +import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools'; +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + ...(isDevMode() ? [provideTanStackDevtools(() => ({ plugins: [pacerDevtoolsPlugin()] }))] : []), + provideBrowserGlobalErrorListeners(), + provideRouter(routes), + ], +}; diff --git a/examples/angular/injectQueuedValue/src/app/app.css b/examples/angular/injectQueuedComputed/src/app/app.css similarity index 100% rename from examples/angular/injectQueuedValue/src/app/app.css rename to examples/angular/injectQueuedComputed/src/app/app.css diff --git a/examples/angular/injectQueuedValue/src/app/app.html b/examples/angular/injectQueuedComputed/src/app/app.html similarity index 85% rename from examples/angular/injectQueuedValue/src/app/app.html rename to examples/angular/injectQueuedComputed/src/app/app.html index e1110e7a8..0168d5e67 100644 --- a/examples/angular/injectQueuedValue/src/app/app.html +++ b/examples/angular/injectQueuedComputed/src/app/app.html @@ -1,4 +1,4 @@ -

TanStack Pacer injectQueuedValue Example

+

TanStack Pacer injectQueuedComputed Example

Queued search value

Current Value: {{ queued() }}

@@ -16,8 +16,8 @@

Queued search value

Queue Idle: {{ queuer.state().isIdle }}

Status: {{ queuer.state().status }}

Items Processed: {{ queuer.state().executionCount }}

-

Queue Items: {{ queuer.state().items.join(", ") }}

-

Queue Peek: {{ queuer.peekNextItem() ?? "None" }}

+

Queue Items: {{ queuer.state().items.join(', ') }}

+

Queue Peek: {{ queuer.peekNextItem() ?? 'None' }}

@@ -58,8 +58,8 @@

Queued range

Queue Idle: {{ rangeQueue.state().isIdle }}

Status: {{ rangeQueue.state().status }}

Items Processed: {{ rangeQueue.state().executionCount }}

-

Queue Items: {{ rangeQueue.state().items.join(", ") }}

-

Queue Peek: {{ rangeQueue.peekNextItem() ?? "None" }}

+

Queue Items: {{ rangeQueue.state().items.join(', ') }}

+

Queue Peek: {{ rangeQueue.peekNextItem() ?? 'None' }}

{{ rangeQueue.state() | json }}

diff --git a/examples/angular/injectQueuedComputed/src/app/app.routes.ts b/examples/angular/injectQueuedComputed/src/app/app.routes.ts new file mode 100644 index 000000000..25b8adc91 --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import type { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/examples/angular/injectQueuedComputed/src/app/app.spec.ts b/examples/angular/injectQueuedComputed/src/app/app.spec.ts new file mode 100644 index 000000000..3419171b2 --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/app/app.spec.ts @@ -0,0 +1,55 @@ +import { vi } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it('should render title', async () => { + const fixture = TestBed.createComponent(App); + await fixture.whenStable(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain( + 'TanStack Pacer injectQueuedComputed Example', + ); + }); +}); + +// Exercise the real adapter and component with a deterministic clock. +describe('example behavior', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [App] }).compileComponents(); + }); + afterEach(() => { + TestBed.resetTestingModule(); + vi.clearAllTimers(); + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + it('publishes a processed scalar and keeps pending values in queue state', async () => { + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + fixture.detectChanges(); + TestBed.tick(); + await vi.advanceTimersByTimeAsync(1000); + app.queuer.stop(); + app.source.set('angular'); + TestBed.tick(); + expect(app.queued()).toBe(''); + expect(app.queuer.state().items).toEqual(['angular']); + app.queuer.execute(); + expect(app.queued()).toBe('angular'); + expect(app.queuer.state().items).toEqual([]); + }); +}); diff --git a/examples/angular/injectQueuedComputed/src/app/app.ts b/examples/angular/injectQueuedComputed/src/app/app.ts new file mode 100644 index 000000000..1497570eb --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/app/app.ts @@ -0,0 +1,30 @@ +import { Component, signal } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { injectQueuedComputed } from '@tanstack/angular-pacer'; +import { InputApp } from './inputapp'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + imports: [JsonPipe, InputApp], +}) +export class App { + readonly source = signal(''); + readonly currentValue = signal(50); + readonly instantExecutions = signal(1); + readonly queued = injectQueuedComputed(this.source, { maxSize: 25, wait: 500 }, (state) => state); + readonly queuer = this.queued.queuer; + readonly rangeQueued = injectQueuedComputed( + this.currentValue, + { maxSize: 100, wait: 100 }, + (state) => state, + ); + readonly rangeQueue = this.rangeQueued.queuer; + onSearch(value: string): void { + this.source.set(value); + } + onRange(value: number): void { + this.currentValue.set(value); + this.instantExecutions.update((count) => count + 1); + } +} diff --git a/examples/angular/injectQueuedComputed/src/app/inputapp.ts b/examples/angular/injectQueuedComputed/src/app/inputapp.ts new file mode 100644 index 000000000..ffc866402 --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/app/inputapp.ts @@ -0,0 +1,22 @@ +import { Component, input, signal } from '@angular/core'; +import { injectQueuerItems } from '@tanstack/angular-pacer'; + +@Component({ + selector: 'app-input', + template: ` +

Required input

+

value: {{ value() }}

+

Value (queued): {{ processed() }}

+

Queue length: {{ queued().length }}

+ + + `, +}) +export class InputApp { + readonly value = input.required(); + readonly processed = signal(''); + readonly queued = injectQueuerItems((value: string) => this.processed.set(value), { wait: 500 }); + enqueueRandom(): void { + this.queued.queuer.addItem(Math.random().toFixed(4)); + } +} diff --git a/examples/angular/injectQueuedValue/src/index.html b/examples/angular/injectQueuedComputed/src/index.html similarity index 100% rename from examples/angular/injectQueuedValue/src/index.html rename to examples/angular/injectQueuedComputed/src/index.html diff --git a/examples/angular/injectQueuedComputed/src/main.ts b/examples/angular/injectQueuedComputed/src/main.ts new file mode 100644 index 000000000..190f3418d --- /dev/null +++ b/examples/angular/injectQueuedComputed/src/main.ts @@ -0,0 +1,5 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig).catch((err) => console.error(err)); diff --git a/examples/angular/injectQueuedValue/src/styles.css b/examples/angular/injectQueuedComputed/src/styles.css similarity index 100% rename from examples/angular/injectQueuedValue/src/styles.css rename to examples/angular/injectQueuedComputed/src/styles.css diff --git a/examples/angular/injectQueuedValue/tests/e2e/smoke.spec.mts b/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts similarity index 67% rename from examples/angular/injectQueuedValue/tests/e2e/smoke.spec.mts rename to examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts index f20ae7702..1371c514e 100644 --- a/examples/angular/injectQueuedValue/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts @@ -1,24 +1,24 @@ -import { expect, test } from '../../../../../tests/e2e/helpers/fixtures' +import { expect, test } from '../../../../../tests/e2e/helpers/fixtures'; test.beforeEach(async ({ page, exampleUrl }) => { - await page.clock.install() - await page.goto(exampleUrl) + await page.clock.install(); + await page.goto(exampleUrl); await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectQueuedValue Example', + name: 'TanStack Pacer injectQueuedComputed Example', exact: true, }), - ).toBeVisible() + ).toBeVisible(); // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) -}) + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)); +}); test('renders queued search and range values', async ({ page }) => { await expect( page.getByRole('heading', { name: 'Queued search value', exact: true }), - ).toBeVisible() - await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled() -}) + ).toBeVisible(); + await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled(); +}); test('retains pending search values while stopped and executes them in order', async ({ page }) => { const queue = page.locator('section').filter({ @@ -26,20 +26,20 @@ test('retains pending search values while stopped and executes them in order', a name: 'Queued search value', exact: true, }), - }) - await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click() - await page.clock.runFor(32) - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() - await queue.getByRole('searchbox').fill('first') - await page.clock.runFor(32) - await queue.getByRole('searchbox').fill('second') - await page.clock.runFor(32) - await queue.getByRole('button', { name: 'Start Processing', exact: true }).click() - await page.clock.runFor(32) - await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible() - await expect(queue.getByText('Queue Items: second', { exact: true })).toBeVisible() - await page.clock.runFor(550) - await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible() - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() - await expect(queue.getByText('Queue Items:', { exact: true })).toBeVisible() -}) + }); + await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click(); + await page.clock.runFor(32); + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); + await queue.getByRole('searchbox').fill('first'); + await page.clock.runFor(32); + await queue.getByRole('searchbox').fill('second'); + await page.clock.runFor(32); + await queue.getByRole('button', { name: 'Start Processing', exact: true }).click(); + await page.clock.runFor(32); + await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible(); + await expect(queue.getByText('Queue Items: second', { exact: true })).toBeVisible(); + await page.clock.runFor(550); + await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible(); + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); + await expect(queue.getByText('Queue Items:', { exact: true })).toBeVisible(); +}); diff --git a/examples/angular/injectQueuedValue/tsconfig.app.json b/examples/angular/injectQueuedComputed/tsconfig.app.json similarity index 100% rename from examples/angular/injectQueuedValue/tsconfig.app.json rename to examples/angular/injectQueuedComputed/tsconfig.app.json diff --git a/examples/angular/injectQueuedValue/tsconfig.json b/examples/angular/injectQueuedComputed/tsconfig.json similarity index 100% rename from examples/angular/injectQueuedValue/tsconfig.json rename to examples/angular/injectQueuedComputed/tsconfig.json diff --git a/examples/angular/injectQueuedValue/tsconfig.spec.json b/examples/angular/injectQueuedComputed/tsconfig.spec.json similarity index 100% rename from examples/angular/injectQueuedValue/tsconfig.spec.json rename to examples/angular/injectQueuedComputed/tsconfig.spec.json diff --git a/examples/angular/injectQueuedSignal/eslint.config.js b/examples/angular/injectQueuedSignal/eslint.config.js index 4d9c151cf..66d55766f 100644 --- a/examples/angular/injectQueuedSignal/eslint.config.js +++ b/examples/angular/injectQueuedSignal/eslint.config.js @@ -1,6 +1,6 @@ // @ts-check -import rootConfig from '../../../eslint.config.js' +import rootConfig from '../../../eslint.config.js'; /** @type {import('eslint').Linter.Config[]} */ export default [ @@ -12,4 +12,4 @@ export default [ }, }, }, -] +]; diff --git a/examples/angular/injectQueuedSignal/src/app/app.config.ts b/examples/angular/injectQueuedSignal/src/app/app.config.ts index 253f23d1a..7ca8b23fc 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.config.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.config.ts @@ -1,10 +1,10 @@ -import { isDevMode } from '@angular/core' -import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider' -import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools' -import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core' -import { provideRouter } from '@angular/router' +import { isDevMode } from '@angular/core'; +import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider'; +import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools'; +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter } from '@angular/router'; -import { routes } from './app.routes' +import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ @@ -12,4 +12,4 @@ export const appConfig: ApplicationConfig = { provideBrowserGlobalErrorListeners(), provideRouter(routes), ], -} +}; diff --git a/examples/angular/injectQueuedSignal/src/app/app.html b/examples/angular/injectQueuedSignal/src/app/app.html index 7b2ca7c5e..4611729f3 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.html +++ b/examples/angular/injectQueuedSignal/src/app/app.html @@ -1,33 +1,36 @@

TanStack Pacer injectQueuedSignal Example

-

Number queue

- -

Queue Size: {{ numberQueue.state().size }}

-

Queue Full: {{ numberQueue.state().isFull }}

-

Queue Empty: {{ numberQueue.state().isEmpty }}

-

Queue Idle: {{ numberQueue.state().isIdle }}

-

Status: {{ numberQueue.state().status }}

-

Items Processed: {{ numberQueue.state().executionCount }}

-

Queue Items: {{ queued().join(", ") }}

-

Queue Peek: {{ numberQueue.peekNextItem() ?? "None" }}

- - - - - -
{{ numberQueue.state() | json }}

Queued range

@@ -42,9 +45,9 @@

Queued range

/>

Instant Value: {{ currentValue() }}

-

Queued Value: {{ rangeValue() }}

+

Queued Value: {{ rangeQueued() }}

Values Submitted: {{ instantExecutions() }}

Pending Items: {{ rangeQueue.state().size }}

Queue Full: {{ rangeQueue.state().isFull }}

@@ -52,7 +55,4 @@

Queued range

Queue Idle: {{ rangeQueue.state().isIdle }}

Status: {{ rangeQueue.state().status }}

Items Processed: {{ rangeQueue.state().executionCount }}

-

Queue Items: {{ rangeQueued().join(", ") }}

-

Queue Peek: {{ rangeQueue.peekNextItem() ?? "None" }}

-
{{ rangeQueue.state() | json }}
diff --git a/examples/angular/injectQueuedSignal/src/app/app.routes.ts b/examples/angular/injectQueuedSignal/src/app/app.routes.ts index 9a884b113..25b8adc91 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.routes.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.routes.ts @@ -1,3 +1,3 @@ -import type { Routes } from '@angular/router' +import type { Routes } from '@angular/router'; -export const routes: Routes = [] +export const routes: Routes = []; diff --git a/examples/angular/injectQueuedSignal/src/app/app.spec.ts b/examples/angular/injectQueuedSignal/src/app/app.spec.ts index 880919bdf..0492a52ce 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.spec.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.spec.ts @@ -1,53 +1,55 @@ -import { vi } from 'vitest' -import { TestBed } from '@angular/core/testing' -import { App } from './app' +import { vi } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], - }).compileComponents() - }) + }).compileComponents(); + }); it('should create the app', () => { - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - expect(app).toBeTruthy() - }) + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); it('should render title', async () => { - const fixture = TestBed.createComponent(App) - await fixture.whenStable() - const compiled = fixture.nativeElement as HTMLElement + const fixture = TestBed.createComponent(App); + await fixture.whenStable(); + const compiled = fixture.nativeElement as HTMLElement; expect(compiled.querySelector('h1')?.textContent).toContain( 'TanStack Pacer injectQueuedSignal Example', - ) - }) -}) + ); + }); +}); // Exercise the real adapter and component with a deterministic clock. describe('example behavior', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ imports: [App] }).compileComponents() - }) + await TestBed.configureTestingModule({ imports: [App] }).compileComponents(); + }); afterEach(() => { - TestBed.resetTestingModule() - vi.clearAllTimers() - vi.useRealTimers() - vi.restoreAllMocks() - }) - it('processes a stopped preloaded queue and displays pending items', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - expect(app.numberQueue.state().items).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - expect(app.numberQueue.state().isRunning).toBe(false) - app.numberQueue.execute() - expect(app.numberQueue.state().executionCount).toBe(1) - expect(app.numberQueue.state().items).toEqual([2, 3, 4, 5, 6, 7, 8, 9, 10]) - app.numberQueue.clear() - expect(app.numberQueue.state().isEmpty).toBe(true) - }) -}) + TestBed.resetTestingModule(); + vi.clearAllTimers(); + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + it('publishes a processed scalar and keeps pending values in queue state', async () => { + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + fixture.detectChanges(); + TestBed.tick(); + await vi.advanceTimersByTimeAsync(1000); + app.queuer.stop(); + app.onSearch('angular'); + TestBed.tick(); + expect(app.queued()).toBe(''); + expect(app.queuer.state().size).toBe(1); + app.queuer.execute(); + expect(app.queued()).toBe('angular'); + expect(app.queuer.state().size).toBe(0); + }); +}); diff --git a/examples/angular/injectQueuedSignal/src/app/app.ts b/examples/angular/injectQueuedSignal/src/app/app.ts index 966b8f98b..232802495 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.ts @@ -1,41 +1,25 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectQueuedSignal } from '@tanstack/angular-pacer' +import { Component, signal } from '@angular/core'; +import { injectQueuedSignal } from '@tanstack/angular-pacer'; @Component({ selector: 'app-root', templateUrl: './app.html', - imports: [JsonPipe], }) export class App { - readonly currentValue = signal(50) - readonly rangeValue = signal(50) - readonly instantExecutions = signal(0) - readonly processed = signal>([]) - readonly queued = injectQueuedSignal( - (item: number) => this.processed.update((items) => [...items, item]), - { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, index) => index + 1), - started: false, - wait: 1000, - }, - (state) => state, - ) - readonly numberQueue = this.queued.queuer - readonly rangeQueued = injectQueuedSignal( - (item: number) => this.rangeValue.set(item), - { maxSize: 100, wait: 100 }, - (state) => state, - ) - readonly rangeQueue = this.rangeQueued.queuer - addNumber(): void { - const items = this.numberQueue.peekAllItems() - this.numberQueue.addItem(items.length ? items[items.length - 1]! + 1 : 1) + readonly source = signal(''); + readonly currentValue = signal(50); + readonly instantExecutions = signal(0); + readonly queued = injectQueuedSignal('', { maxSize: 25, wait: 500 }, (state) => state); + readonly queuer = this.queued.queuer; + readonly rangeQueued = injectQueuedSignal(50, { maxSize: 100, wait: 100 }, (state) => state); + readonly rangeQueue = this.rangeQueued.queuer; + onSearch(value: string): void { + this.source.set(value); + this.queued.set(value); } onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - this.rangeQueue.addItem(value) + this.currentValue.set(value); + this.rangeQueued.set(value); + this.instantExecutions.update((count) => count + 1); } } diff --git a/examples/angular/injectQueuedSignal/src/main.ts b/examples/angular/injectQueuedSignal/src/main.ts index 8192dca69..190f3418d 100644 --- a/examples/angular/injectQueuedSignal/src/main.ts +++ b/examples/angular/injectQueuedSignal/src/main.ts @@ -1,5 +1,5 @@ -import { bootstrapApplication } from '@angular/platform-browser' -import { appConfig } from './app/app.config' -import { App } from './app/app' +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; -bootstrapApplication(App, appConfig).catch((err) => console.error(err)) +bootstrapApplication(App, appConfig).catch((err) => console.error(err)); diff --git a/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts b/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts index 95e58ad77..3a500fe69 100644 --- a/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts @@ -1,48 +1,44 @@ -import { expect, test } from '../../../../../tests/e2e/helpers/fixtures' +import { expect, test } from '../../../../../tests/e2e/helpers/fixtures'; test.beforeEach(async ({ page, exampleUrl }) => { - await page.clock.install() - await page.goto(exampleUrl) + await page.clock.install(); + await page.goto(exampleUrl); await expect( page.getByRole('heading', { name: 'TanStack Pacer injectQueuedSignal Example', exact: true, }), - ).toBeVisible() + ).toBeVisible(); // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) -}) + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)); +}); -test('renders the ten seeded items and stopped queue', async ({ page }) => { - const queue = page.locator('section').filter({ - has: page.getByRole('heading', { name: 'Number queue', exact: true }), - }) - await expect(queue.getByText('Queue Size: 10', { exact: true })).toBeVisible() +test('renders queued search and range values', async ({ page }) => { await expect( - queue.getByText('Queue Items: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10', { - exact: true, - }), - ).toBeVisible() - await expect(queue.getByRole('button', { name: 'Start Processing', exact: true })).toBeEnabled() -}) + page.getByRole('heading', { name: 'Queued search value', exact: true }), + ).toBeVisible(); + await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled(); +}); -test('adds an item, executes the first item, then clears the remaining queue', async ({ page }) => { +test('retains pending search values while stopped and executes them in order', async ({ page }) => { const queue = page.locator('section').filter({ - has: page.getByRole('heading', { name: 'Number queue', exact: true }), - }) - await queue.getByRole('button', { name: 'Add Number', exact: true }).click() - await page.clock.runFor(32) - await expect( - queue.getByText('Queue Items: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11', { + has: page.getByRole('heading', { + name: 'Queued search value', exact: true, }), - ).toBeVisible() - await queue.getByRole('button', { name: 'Process Next', exact: true }).click() - await page.clock.runFor(32) - await expect(queue.getByText('Queue Peek: 2', { exact: true })).toBeVisible() - await expect(queue.getByText('Items Processed: 1', { exact: true })).toBeVisible() - await queue.getByRole('button', { name: 'Clear Queue', exact: true }).click() - await page.clock.runFor(32) - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() - await expect(queue.getByText('Items Processed: 1', { exact: true })).toBeVisible() -}) + }); + await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click(); + await page.clock.runFor(32); + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); + await queue.getByRole('searchbox').fill('first'); + await page.clock.runFor(32); + await queue.getByRole('searchbox').fill('second'); + await page.clock.runFor(32); + await queue.getByRole('button', { name: 'Start Processing', exact: true }).click(); + await page.clock.runFor(32); + await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible(); + await expect(queue.getByText('Queue Size: 1', { exact: true })).toBeVisible(); + await page.clock.runFor(550); + await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible(); + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); +}); diff --git a/examples/angular/injectQueuedValue/src/app/app.ts b/examples/angular/injectQueuedValue/src/app/app.ts deleted file mode 100644 index d41cfb539..000000000 --- a/examples/angular/injectQueuedValue/src/app/app.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectQueuedValue } from '@tanstack/angular-pacer' -import { InputApp } from './inputapp' - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - imports: [JsonPipe, InputApp], -}) -export class App { - readonly source = signal('') - readonly currentValue = signal(50) - readonly instantExecutions = signal(1) - readonly queued = injectQueuedValue(this.source, '', { maxSize: 25, wait: 500 }, (state) => state) - readonly queuer = this.queued.queuer - readonly rangeQueued = injectQueuedValue( - this.currentValue, - 50, - { maxSize: 100, wait: 100 }, - (state) => state, - ) - readonly rangeQueue = this.rangeQueued.queuer - onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - } -} diff --git a/examples/angular/injectQueuedValue/src/app/inputapp.ts b/examples/angular/injectQueuedValue/src/app/inputapp.ts deleted file mode 100644 index fbbdb6906..000000000 --- a/examples/angular/injectQueuedValue/src/app/inputapp.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, input } from '@angular/core' -import { injectQueuedValue } from '@tanstack/angular-pacer' - -@Component({ - selector: 'app-input', - standalone: true, - template: ` -

Required input

-

value: {{ value() }}

-

Value (queued): {{ queued() }}

-

Queue length: {{ queued.queuer.state().items.length }}

- - `, -}) -export class InputApp { - readonly value = input.required() - - protected readonly queued = injectQueuedValue(this.value, { wait: 500 }, (state) => ({ - items: state.items, - })) - - protected enqueueRandom(): void { - // You can also enqueue values directly without touching `value` - this.queued.addItem(Math.random().toFixed(4)) - } -} diff --git a/examples/angular/injectRateLimitedValue/README.md b/examples/angular/injectQueuerItems/README.md similarity index 100% rename from examples/angular/injectRateLimitedValue/README.md rename to examples/angular/injectQueuerItems/README.md diff --git a/examples/angular/injectRateLimitedValue/angular.json b/examples/angular/injectQueuerItems/angular.json similarity index 100% rename from examples/angular/injectRateLimitedValue/angular.json rename to examples/angular/injectQueuerItems/angular.json diff --git a/examples/angular/injectQueuedValue/eslint.config.js b/examples/angular/injectQueuerItems/eslint.config.js similarity index 100% rename from examples/angular/injectQueuedValue/eslint.config.js rename to examples/angular/injectQueuerItems/eslint.config.js diff --git a/examples/angular/injectQueuedValue/package.json b/examples/angular/injectQueuerItems/package.json similarity index 94% rename from examples/angular/injectQueuedValue/package.json rename to examples/angular/injectQueuerItems/package.json index 13b9e5b95..87427c986 100644 --- a/examples/angular/injectQueuedValue/package.json +++ b/examples/angular/injectQueuerItems/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-queued-value", + "name": "@tanstack/pacer-example-angular-inject-queuer-items", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectRateLimitedValue/public/favicon.ico b/examples/angular/injectQueuerItems/public/favicon.ico similarity index 100% rename from examples/angular/injectRateLimitedValue/public/favicon.ico rename to examples/angular/injectQueuerItems/public/favicon.ico diff --git a/examples/angular/injectQueuedValue/src/app/app.config.ts b/examples/angular/injectQueuerItems/src/app/app.config.ts similarity index 100% rename from examples/angular/injectQueuedValue/src/app/app.config.ts rename to examples/angular/injectQueuerItems/src/app/app.config.ts diff --git a/examples/angular/injectRateLimitedValue/src/app/app.css b/examples/angular/injectQueuerItems/src/app/app.css similarity index 100% rename from examples/angular/injectRateLimitedValue/src/app/app.css rename to examples/angular/injectQueuerItems/src/app/app.css diff --git a/examples/angular/injectQueuerItems/src/app/app.html b/examples/angular/injectQueuerItems/src/app/app.html new file mode 100644 index 000000000..3a80766f1 --- /dev/null +++ b/examples/angular/injectQueuerItems/src/app/app.html @@ -0,0 +1,43 @@ +

TanStack Pacer injectQueuerItems Example

+
+

Number queue

+ +

Queue Size: {{ queued().length }}

+

Queue Full: {{ queued().length >= 25 }}

+

Queue Empty: {{ queued().length === 0 }}

+

Items Processed: {{ processed().length }}

+

Queue Items: {{ queued().join(', ') }}

+

Queue Peek: {{ numberQueue.peekNextItem() ?? 'None' }}

+ + + + + +
+
+

Queued range

+ +

Instant Value: {{ currentValue() }}

+ +

Queued Value: {{ rangeValue() }}

+

Values Submitted: {{ instantExecutions() }}

+

Pending Items: {{ rangeQueued().length }}

+

Queue Items: {{ rangeQueued().join(', ') }}

+

Queue Peek: {{ rangeQueue.peekNextItem() ?? 'None' }}

+
diff --git a/examples/angular/injectQueuedValue/src/app/app.routes.ts b/examples/angular/injectQueuerItems/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectQueuedValue/src/app/app.routes.ts rename to examples/angular/injectQueuerItems/src/app/app.routes.ts diff --git a/examples/angular/injectQueuerItems/src/app/app.spec.ts b/examples/angular/injectQueuerItems/src/app/app.spec.ts new file mode 100644 index 000000000..fb4c7e694 --- /dev/null +++ b/examples/angular/injectQueuerItems/src/app/app.spec.ts @@ -0,0 +1,53 @@ +import { vi } from 'vitest' +import { TestBed } from '@angular/core/testing' +import { App } from './app' + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App], + }).compileComponents() + }) + + it('should create the app', () => { + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + expect(app).toBeTruthy() + }) + + it('should render title', async () => { + const fixture = TestBed.createComponent(App) + await fixture.whenStable() + const compiled = fixture.nativeElement as HTMLElement + expect(compiled.querySelector('h1')?.textContent).toContain( + 'TanStack Pacer injectQueuerItems Example', + ) + }) +}) + +// Exercise the real adapter and component with a deterministic clock. +describe('example behavior', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [App] }).compileComponents() + }) + afterEach(() => { + TestBed.resetTestingModule() + vi.clearAllTimers() + vi.useRealTimers() + vi.restoreAllMocks() + }) + it('processes a stopped preloaded queue and displays pending items', async () => { + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + fixture.detectChanges() + TestBed.tick() + expect(app.queued()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + expect(app.processed()).toEqual([]) + app.numberQueue.execute() + expect(app.processed()).toEqual([1]) + expect(app.queued()).toEqual([2, 3, 4, 5, 6, 7, 8, 9, 10]) + app.numberQueue.clear() + expect(app.queued()).toEqual([]) + }) +}) diff --git a/examples/angular/injectQueuerItems/src/app/app.ts b/examples/angular/injectQueuerItems/src/app/app.ts new file mode 100644 index 000000000..4b8f24407 --- /dev/null +++ b/examples/angular/injectQueuerItems/src/app/app.ts @@ -0,0 +1,37 @@ +import { Component, signal } from '@angular/core' +import { injectQueuerItems } from '@tanstack/angular-pacer' + +@Component({ + selector: 'app-root', + templateUrl: './app.html', +}) +export class App { + readonly currentValue = signal(50) + readonly rangeValue = signal(50) + readonly instantExecutions = signal(0) + readonly processed = signal>([]) + readonly queued = injectQueuerItems( + (item: number) => this.processed.update((items) => [...items, item]), + { + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, index) => index + 1), + started: false, + wait: 1000, + }, + ) + readonly numberQueue = this.queued.queuer + readonly rangeQueued = injectQueuerItems((item: number) => this.rangeValue.set(item), { + maxSize: 100, + wait: 100, + }) + readonly rangeQueue = this.rangeQueued.queuer + addNumber(): void { + const items = this.numberQueue.peekAllItems() + this.queued.addItem(items.length ? items[items.length - 1]! + 1 : 1) + } + onRange(value: number): void { + this.currentValue.set(value) + this.instantExecutions.update((count) => count + 1) + this.rangeQueued.addItem(value) + } +} diff --git a/examples/angular/injectRateLimitedValue/src/index.html b/examples/angular/injectQueuerItems/src/index.html similarity index 100% rename from examples/angular/injectRateLimitedValue/src/index.html rename to examples/angular/injectQueuerItems/src/index.html diff --git a/examples/angular/injectQueuedValue/src/main.ts b/examples/angular/injectQueuerItems/src/main.ts similarity index 100% rename from examples/angular/injectQueuedValue/src/main.ts rename to examples/angular/injectQueuerItems/src/main.ts diff --git a/examples/angular/injectRateLimitedValue/src/styles.css b/examples/angular/injectQueuerItems/src/styles.css similarity index 100% rename from examples/angular/injectRateLimitedValue/src/styles.css rename to examples/angular/injectQueuerItems/src/styles.css diff --git a/examples/angular/injectQueuerItems/tests/e2e/smoke.spec.mts b/examples/angular/injectQueuerItems/tests/e2e/smoke.spec.mts new file mode 100644 index 000000000..cf9d05ac6 --- /dev/null +++ b/examples/angular/injectQueuerItems/tests/e2e/smoke.spec.mts @@ -0,0 +1,48 @@ +import { expect, test } from '../../../../../tests/e2e/helpers/fixtures' + +test.beforeEach(async ({ page, exampleUrl }) => { + await page.clock.install() + await page.goto(exampleUrl) + await expect( + page.getByRole('heading', { + name: 'TanStack Pacer injectQueuerItems Example', + exact: true, + }), + ).toBeVisible() + // Let Angular bootstrap before pausing timers; runFor also advances render frames. + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) +}) + +test('renders the ten seeded items and stopped queue', async ({ page }) => { + const queue = page.locator('section').filter({ + has: page.getByRole('heading', { name: 'Number queue', exact: true }), + }) + await expect(queue.getByText('Queue Size: 10', { exact: true })).toBeVisible() + await expect( + queue.getByText('Queue Items: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10', { + exact: true, + }), + ).toBeVisible() + await expect(queue.getByRole('button', { name: 'Start Processing', exact: true })).toBeEnabled() +}) + +test('adds an item, executes the first item, then clears the remaining queue', async ({ page }) => { + const queue = page.locator('section').filter({ + has: page.getByRole('heading', { name: 'Number queue', exact: true }), + }) + await queue.getByRole('button', { name: 'Add Number', exact: true }).click() + await page.clock.runFor(32) + await expect( + queue.getByText('Queue Items: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11', { + exact: true, + }), + ).toBeVisible() + await queue.getByRole('button', { name: 'Process Next', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Queue Peek: 2', { exact: true })).toBeVisible() + await expect(queue.getByText('Items Processed: 1', { exact: true })).toBeVisible() + await queue.getByRole('button', { name: 'Clear Queue', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() + await expect(queue.getByText('Items Processed: 1', { exact: true })).toBeVisible() +}) diff --git a/examples/angular/injectRateLimitedValue/tsconfig.app.json b/examples/angular/injectQueuerItems/tsconfig.app.json similarity index 100% rename from examples/angular/injectRateLimitedValue/tsconfig.app.json rename to examples/angular/injectQueuerItems/tsconfig.app.json diff --git a/examples/angular/injectRateLimitedValue/tsconfig.json b/examples/angular/injectQueuerItems/tsconfig.json similarity index 100% rename from examples/angular/injectRateLimitedValue/tsconfig.json rename to examples/angular/injectQueuerItems/tsconfig.json diff --git a/examples/angular/injectRateLimitedValue/tsconfig.spec.json b/examples/angular/injectQueuerItems/tsconfig.spec.json similarity index 100% rename from examples/angular/injectRateLimitedValue/tsconfig.spec.json rename to examples/angular/injectQueuerItems/tsconfig.spec.json diff --git a/examples/angular/injectThrottledValue/README.md b/examples/angular/injectRateLimitedComputed/README.md similarity index 100% rename from examples/angular/injectThrottledValue/README.md rename to examples/angular/injectRateLimitedComputed/README.md diff --git a/examples/angular/injectThrottledValue/angular.json b/examples/angular/injectRateLimitedComputed/angular.json similarity index 100% rename from examples/angular/injectThrottledValue/angular.json rename to examples/angular/injectRateLimitedComputed/angular.json diff --git a/examples/angular/injectRateLimitedValue/eslint.config.js b/examples/angular/injectRateLimitedComputed/eslint.config.js similarity index 100% rename from examples/angular/injectRateLimitedValue/eslint.config.js rename to examples/angular/injectRateLimitedComputed/eslint.config.js diff --git a/examples/angular/injectAsyncQueuedSignal/package.json b/examples/angular/injectRateLimitedComputed/package.json similarity index 94% rename from examples/angular/injectAsyncQueuedSignal/package.json rename to examples/angular/injectRateLimitedComputed/package.json index 35f3f4eba..2a306a109 100644 --- a/examples/angular/injectAsyncQueuedSignal/package.json +++ b/examples/angular/injectRateLimitedComputed/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-async-queued-signal", + "name": "@tanstack/pacer-example-angular-inject-rate-limited-computed", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectThrottledValue/public/favicon.ico b/examples/angular/injectRateLimitedComputed/public/favicon.ico similarity index 100% rename from examples/angular/injectThrottledValue/public/favicon.ico rename to examples/angular/injectRateLimitedComputed/public/favicon.ico diff --git a/examples/angular/injectRateLimitedValue/src/app/app.config.ts b/examples/angular/injectRateLimitedComputed/src/app/app.config.ts similarity index 100% rename from examples/angular/injectRateLimitedValue/src/app/app.config.ts rename to examples/angular/injectRateLimitedComputed/src/app/app.config.ts diff --git a/examples/angular/injectThrottledValue/src/app/app.css b/examples/angular/injectRateLimitedComputed/src/app/app.css similarity index 100% rename from examples/angular/injectThrottledValue/src/app/app.css rename to examples/angular/injectRateLimitedComputed/src/app/app.css diff --git a/examples/angular/injectRateLimitedValue/src/app/app.html b/examples/angular/injectRateLimitedComputed/src/app/app.html similarity index 69% rename from examples/angular/injectRateLimitedValue/src/app/app.html rename to examples/angular/injectRateLimitedComputed/src/app/app.html index 0ce8d80ba..175ca4133 100644 --- a/examples/angular/injectRateLimitedValue/src/app/app.html +++ b/examples/angular/injectRateLimitedComputed/src/app/app.html @@ -1,4 +1,4 @@ -

TanStack Pacer injectRateLimitedValue Example

+

TanStack Pacer injectRateLimitedComputed Example

Counter

@@ -22,11 +22,6 @@

Counter

Instant Count: {{ instantCount() }}

Rate Limited Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Rejections: {{ countRunner.state().rejectionCount }}

-

Remaining in Window: {{ countRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ countRunner.getMsUntilNextWindow() }}

-
{{ countRunner.state() | json }}

Search

@@ -58,11 +53,6 @@

Search

/>

Instant Search: {{ search() }}

Rate Limited Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Rejections: {{ searchRunner.state().rejectionCount }}

-

Remaining in Window: {{ searchRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ searchRunner.getMsUntilNextWindow() }}

-
{{ searchRunner.state() | json }}

Range

@@ -98,13 +88,6 @@

Range

>Rate Limited Range

Rate Limited Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Rejections: {{ rangeRunner.state().rejectionCount }}

-

Remaining in Window: {{ rangeRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ rangeRunner.getMsUntilNextWindow() }}

Values Submitted: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

-
{{ rangeRunner.state() | json }}

Rate limited to 20 updates per 2 seconds.

diff --git a/examples/angular/injectRateLimitedValue/src/app/app.routes.ts b/examples/angular/injectRateLimitedComputed/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectRateLimitedValue/src/app/app.routes.ts rename to examples/angular/injectRateLimitedComputed/src/app/app.routes.ts diff --git a/examples/angular/injectThrottledValue/src/app/app.spec.ts b/examples/angular/injectRateLimitedComputed/src/app/app.spec.ts similarity index 76% rename from examples/angular/injectThrottledValue/src/app/app.spec.ts rename to examples/angular/injectRateLimitedComputed/src/app/app.spec.ts index 970c42182..6043bc843 100644 --- a/examples/angular/injectThrottledValue/src/app/app.spec.ts +++ b/examples/angular/injectRateLimitedComputed/src/app/app.spec.ts @@ -20,7 +20,7 @@ describe('App', () => { await fixture.whenStable() const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectThrottledValue Example', + 'TanStack Pacer injectRateLimitedComputed Example', ) }) }) @@ -54,14 +54,4 @@ describe('example behavior', () => { expect(app.controlledSearch()).toBe('angular') expect(app.controlledValue()).toBe(73) }) - it('counts the initial value when displaying saved executions', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - await vi.advanceTimersByTimeAsync(1100) - expect(app.instantExecutions() - app.rangeRunner.state().executionCount).toBe(0) - expect(app.reduction()).toBe(0) - }) }) diff --git a/examples/angular/injectRateLimitedComputed/src/app/app.ts b/examples/angular/injectRateLimitedComputed/src/app/app.ts new file mode 100644 index 000000000..d31ff853b --- /dev/null +++ b/examples/angular/injectRateLimitedComputed/src/app/app.ts @@ -0,0 +1,45 @@ +import { Component, signal } from '@angular/core' +import { injectRateLimitedComputed } from '@tanstack/angular-pacer' + +@Component({ + selector: 'app-root', + templateUrl: './app.html', +}) +export class App { + readonly instantCount = signal(0) + readonly search = signal('') + readonly currentValue = signal(50) + readonly instantExecutions = signal(1) + readonly countWindow = signal<'fixed' | 'sliding'>('fixed') + readonly searchWindow = signal<'fixed' | 'sliding'>('fixed') + readonly rangeWindow = signal<'fixed' | 'sliding'>('fixed') + readonly controlledCount = injectRateLimitedComputed(this.instantCount, () => ({ + limit: 5, + window: 5000, + windowType: this.countWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) + readonly controlledSearch = injectRateLimitedComputed(this.search, () => ({ + limit: 5, + window: 5000, + windowType: this.searchWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) + readonly controlledValue = injectRateLimitedComputed(this.currentValue, () => ({ + limit: 20, + window: 2000, + windowType: this.rangeWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) + increment(): void { + const next = this.instantCount() + 1 + this.instantCount.set(next) + } + onSearch(value: string): void { + this.search.set(value) + } + onRange(value: number): void { + this.currentValue.set(value) + this.instantExecutions.update((count) => count + 1) + } +} diff --git a/examples/angular/injectThrottledValue/src/index.html b/examples/angular/injectRateLimitedComputed/src/index.html similarity index 100% rename from examples/angular/injectThrottledValue/src/index.html rename to examples/angular/injectRateLimitedComputed/src/index.html diff --git a/examples/angular/injectRateLimitedValue/src/main.ts b/examples/angular/injectRateLimitedComputed/src/main.ts similarity index 100% rename from examples/angular/injectRateLimitedValue/src/main.ts rename to examples/angular/injectRateLimitedComputed/src/main.ts diff --git a/examples/angular/injectThrottledValue/src/styles.css b/examples/angular/injectRateLimitedComputed/src/styles.css similarity index 100% rename from examples/angular/injectThrottledValue/src/styles.css rename to examples/angular/injectRateLimitedComputed/src/styles.css diff --git a/examples/angular/injectRateLimitedValue/tests/e2e/smoke.spec.mts b/examples/angular/injectRateLimitedComputed/tests/e2e/smoke.spec.mts similarity index 96% rename from examples/angular/injectRateLimitedValue/tests/e2e/smoke.spec.mts rename to examples/angular/injectRateLimitedComputed/tests/e2e/smoke.spec.mts index dc03e24c9..c78f05b03 100644 --- a/examples/angular/injectRateLimitedValue/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectRateLimitedComputed/tests/e2e/smoke.spec.mts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page, exampleUrl }) => { await page.goto(exampleUrl) await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectRateLimitedValue Example', + name: 'TanStack Pacer injectRateLimitedComputed Example', exact: true, }), ).toBeVisible() diff --git a/examples/angular/injectThrottledValue/tsconfig.app.json b/examples/angular/injectRateLimitedComputed/tsconfig.app.json similarity index 100% rename from examples/angular/injectThrottledValue/tsconfig.app.json rename to examples/angular/injectRateLimitedComputed/tsconfig.app.json diff --git a/examples/angular/injectThrottledValue/tsconfig.json b/examples/angular/injectRateLimitedComputed/tsconfig.json similarity index 100% rename from examples/angular/injectThrottledValue/tsconfig.json rename to examples/angular/injectRateLimitedComputed/tsconfig.json diff --git a/examples/angular/injectThrottledValue/tsconfig.spec.json b/examples/angular/injectRateLimitedComputed/tsconfig.spec.json similarity index 100% rename from examples/angular/injectThrottledValue/tsconfig.spec.json rename to examples/angular/injectRateLimitedComputed/tsconfig.spec.json diff --git a/examples/angular/injectRateLimitedSignal/src/app/app.html b/examples/angular/injectRateLimitedSignal/src/app/app.html index 62c76defc..107ca185e 100644 --- a/examples/angular/injectRateLimitedSignal/src/app/app.html +++ b/examples/angular/injectRateLimitedSignal/src/app/app.html @@ -22,12 +22,6 @@

Counter

Instant Count: {{ instantCount() }}

Rate Limited Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Rejections: {{ countRunner.state().rejectionCount }}

-

Remaining in Window: {{ countRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ countRunner.getMsUntilNextWindow() }}

- -
{{ countRunner.state() | json }}

Search

@@ -59,12 +53,6 @@

Search

/>

Instant Search: {{ search() }}

Rate Limited Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Rejections: {{ searchRunner.state().rejectionCount }}

-

Remaining in Window: {{ searchRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ searchRunner.getMsUntilNextWindow() }}

- -
{{ searchRunner.state() | json }}

Range

@@ -100,14 +88,6 @@

Range

>Rate Limited Range

Rate Limited Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Rejections: {{ rangeRunner.state().rejectionCount }}

-

Remaining in Window: {{ rangeRunner.getRemainingInWindow() }}

-

Ms Until Next Window: {{ rangeRunner.getMsUntilNextWindow() }}

Instant Executions: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

- -
{{ rangeRunner.state() | json }}

Rate limited to 20 updates per 2 seconds.

diff --git a/examples/angular/injectRateLimitedSignal/src/app/app.spec.ts b/examples/angular/injectRateLimitedSignal/src/app/app.spec.ts index 414198a25..e24c18cd4 100644 --- a/examples/angular/injectRateLimitedSignal/src/app/app.spec.ts +++ b/examples/angular/injectRateLimitedSignal/src/app/app.spec.ts @@ -54,16 +54,4 @@ describe('example behavior', () => { expect(app.controlledSearch()).toBe('angular') expect(app.controlledValue()).toBe(73) }) - it('changes the window type on the same limiter', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - const runner = app.countRunner - app.countWindow.set('sliding') - TestBed.tick() - expect(app.countRunner).toBe(runner) - expect(runner.options.windowType).toBe('sliding') - }) }) diff --git a/examples/angular/injectRateLimitedSignal/src/app/app.ts b/examples/angular/injectRateLimitedSignal/src/app/app.ts index 5a54ea396..755fdde8c 100644 --- a/examples/angular/injectRateLimitedSignal/src/app/app.ts +++ b/examples/angular/injectRateLimitedSignal/src/app/app.ts @@ -1,11 +1,9 @@ import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' import { injectRateLimitedSignal } from '@tanstack/angular-pacer' @Component({ selector: 'app-root', templateUrl: './app.html', - imports: [JsonPipe], }) export class App { readonly instantCount = signal(0) @@ -15,42 +13,24 @@ export class App { readonly countWindow = signal<'fixed' | 'sliding'>('fixed') readonly searchWindow = signal<'fixed' | 'sliding'>('fixed') readonly rangeWindow = signal<'fixed' | 'sliding'>('fixed') - readonly controlledCount = injectRateLimitedSignal( - 0, - () => ({ - limit: 5, - window: 5000, - windowType: this.countWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly countRunner = this.controlledCount.rateLimiter - readonly controlledSearch = injectRateLimitedSignal( - '', - () => ({ - limit: 5, - window: 5000, - windowType: this.searchWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly searchRunner = this.controlledSearch.rateLimiter - readonly controlledValue = injectRateLimitedSignal( - 50, - () => ({ - limit: 20, - window: 2000, - windowType: this.rangeWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.rateLimiter + readonly controlledCount = injectRateLimitedSignal(0, () => ({ + limit: 5, + window: 5000, + windowType: this.countWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) + readonly controlledSearch = injectRateLimitedSignal('', () => ({ + limit: 5, + window: 5000, + windowType: this.searchWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) + readonly controlledValue = injectRateLimitedSignal(50, () => ({ + limit: 20, + window: 2000, + windowType: this.rangeWindow(), + onReject: (limiter) => console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), + })) increment(): void { const next = this.instantCount() + 1 this.instantCount.set(next) @@ -65,8 +45,4 @@ export class App { this.instantExecutions.update((count) => count + 1) this.controlledValue.set(value) } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } } diff --git a/examples/angular/injectRateLimitedValue/src/app/app.spec.ts b/examples/angular/injectRateLimitedValue/src/app/app.spec.ts deleted file mode 100644 index 798de6cac..000000000 --- a/examples/angular/injectRateLimitedValue/src/app/app.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { vi } from 'vitest' -import { TestBed } from '@angular/core/testing' -import { App } from './app' - -describe('App', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [App], - }).compileComponents() - }) - - it('should create the app', () => { - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - expect(app).toBeTruthy() - }) - - it('should render title', async () => { - const fixture = TestBed.createComponent(App) - await fixture.whenStable() - const compiled = fixture.nativeElement as HTMLElement - expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectRateLimitedValue Example', - ) - }) -}) - -// Exercise the real adapter and component with a deterministic clock. -describe('example behavior', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ imports: [App] }).compileComponents() - }) - afterEach(() => { - TestBed.resetTestingModule() - vi.clearAllTimers() - vi.useRealTimers() - vi.restoreAllMocks() - }) - it('runs the counter, search, and range scenarios', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - app.increment() - app.increment() - app.increment() - app.onSearch('angular') - app.onRange(73) - TestBed.tick() - await vi.advanceTimersByTimeAsync(1100) - TestBed.tick() - expect(app.controlledCount()).toBe(3) - expect(app.controlledSearch()).toBe('angular') - expect(app.controlledValue()).toBe(73) - }) - it('changes the window type on the same limiter', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - const runner = app.countRunner - app.countWindow.set('sliding') - TestBed.tick() - expect(app.countRunner).toBe(runner) - expect(runner.options.windowType).toBe('sliding') - }) - it('counts the initial value when displaying saved executions', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - await vi.advanceTimersByTimeAsync(1100) - expect(app.instantExecutions() - app.rangeRunner.state().executionCount).toBe(0) - expect(app.reduction()).toBe(0) - }) -}) diff --git a/examples/angular/injectRateLimitedValue/src/app/app.ts b/examples/angular/injectRateLimitedValue/src/app/app.ts deleted file mode 100644 index 0395e2de4..000000000 --- a/examples/angular/injectRateLimitedValue/src/app/app.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectRateLimitedValue } from '@tanstack/angular-pacer' - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - imports: [JsonPipe], -}) -export class App { - readonly instantCount = signal(0) - readonly search = signal('') - readonly currentValue = signal(50) - readonly instantExecutions = signal(1) - readonly countWindow = signal<'fixed' | 'sliding'>('fixed') - readonly searchWindow = signal<'fixed' | 'sliding'>('fixed') - readonly rangeWindow = signal<'fixed' | 'sliding'>('fixed') - readonly controlledCount = injectRateLimitedValue( - this.instantCount, - 0, - () => ({ - limit: 5, - window: 5000, - windowType: this.countWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly countRunner = this.controlledCount.rateLimiter - readonly controlledSearch = injectRateLimitedValue( - this.search, - '', - () => ({ - limit: 5, - window: 5000, - windowType: this.searchWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly searchRunner = this.controlledSearch.rateLimiter - readonly controlledValue = injectRateLimitedValue( - this.currentValue, - 50, - () => ({ - limit: 20, - window: 2000, - windowType: this.rangeWindow(), - onReject: (limiter) => - console.log('Rejected; retry in', limiter.getMsUntilNextWindow(), 'ms'), - }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.rateLimiter - increment(): void { - const next = this.instantCount() + 1 - this.instantCount.set(next) - } - onSearch(value: string): void { - this.search.set(value) - } - onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } -} diff --git a/examples/angular/injectRateLimiter/src/app/app.spec.ts b/examples/angular/injectRateLimiter/src/app/app.spec.ts index e98b1cc6f..3bebaa39f 100644 --- a/examples/angular/injectRateLimiter/src/app/app.spec.ts +++ b/examples/angular/injectRateLimiter/src/app/app.spec.ts @@ -79,6 +79,5 @@ describe('example behavior', () => { app.countWindow.set('sliding') TestBed.tick() expect(app.countRunner).toBe(runner) - expect(runner.options.windowType).toBe('sliding') }) }) diff --git a/examples/angular/injectThrottledComputed/README.md b/examples/angular/injectThrottledComputed/README.md new file mode 100644 index 000000000..bd58f6985 --- /dev/null +++ b/examples/angular/injectThrottledComputed/README.md @@ -0,0 +1,59 @@ +# Basic + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.0.5. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/angular/injectThrottledComputed/angular.json b/examples/angular/injectThrottledComputed/angular.json new file mode 100644 index 000000000..06ffc24ef --- /dev/null +++ b/examples/angular/injectThrottledComputed/angular.json @@ -0,0 +1,72 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "npm", + "analytics": false + }, + "newProjectRoot": "projects", + "projects": { + "basic": { + "projectType": "application", + "schematics": {}, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "basic:build:production" + }, + "development": { + "buildTarget": "basic:build:development" + } + }, + "defaultConfiguration": "development" + }, + "test": { + "builder": "@angular/build:unit-test" + } + } + } + } +} diff --git a/examples/angular/injectThrottledValue/eslint.config.js b/examples/angular/injectThrottledComputed/eslint.config.js similarity index 100% rename from examples/angular/injectThrottledValue/eslint.config.js rename to examples/angular/injectThrottledComputed/eslint.config.js diff --git a/examples/angular/injectThrottledComputed/package.json b/examples/angular/injectThrottledComputed/package.json new file mode 100644 index 000000000..f65c79a22 --- /dev/null +++ b/examples/angular/injectThrottledComputed/package.json @@ -0,0 +1,46 @@ +{ + "name": "@tanstack/pacer-example-angular-inject-throttled-computed", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "test:e2e": "PLAYWRIGHT_TEST_DIR=$PWD/tests/e2e playwright test --config ../../../playwright.config.ts" + }, + "prettier": { + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] + }, + "private": true, + "packageManager": "pnpm@12.6.0", + "dependencies": { + "@angular/common": "^22.2.1", + "@angular/compiler": "^22.2.1", + "@angular/core": "^22.2.1", + "@angular/forms": "^22.2.1", + "@angular/platform-browser": "^22.2.1", + "@angular/router": "^22.2.1", + "@tanstack/angular-devtools": "^0.0.12", + "@tanstack/angular-pacer": "^0.26.0", + "@tanstack/angular-pacer-devtools": "workspace:*", + "rxjs": "~7.8.2", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/build": "^22.2.1", + "@angular/cli": "^22.2.1", + "@angular/compiler-cli": "^22.2.1", + "jsdom": "^30.1.1", + "typescript": "6.0.3", + "vitest": "^5.0.3" + } +} diff --git a/examples/angular/injectThrottledComputed/public/favicon.ico b/examples/angular/injectThrottledComputed/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/examples/angular/injectThrottledValue/src/app/app.config.ts b/examples/angular/injectThrottledComputed/src/app/app.config.ts similarity index 100% rename from examples/angular/injectThrottledValue/src/app/app.config.ts rename to examples/angular/injectThrottledComputed/src/app/app.config.ts diff --git a/examples/angular/injectThrottledComputed/src/app/app.css b/examples/angular/injectThrottledComputed/src/app/app.css new file mode 100644 index 000000000..296e372b0 --- /dev/null +++ b/examples/angular/injectThrottledComputed/src/app/app.css @@ -0,0 +1,19 @@ +:host { + display: block; + padding: 2rem; + max-width: 600px; + margin: 0 auto; +} + +input { + width: 100%; + padding: 0.5rem; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 4px; +} + +p { + margin-top: 1rem; + color: #666; +} diff --git a/examples/angular/injectThrottledValue/src/app/app.html b/examples/angular/injectThrottledComputed/src/app/app.html similarity index 63% rename from examples/angular/injectThrottledValue/src/app/app.html rename to examples/angular/injectThrottledComputed/src/app/app.html index 1b33f3667..77828d9be 100644 --- a/examples/angular/injectThrottledValue/src/app/app.html +++ b/examples/angular/injectThrottledComputed/src/app/app.html @@ -1,12 +1,9 @@ -

TanStack Pacer injectThrottledValue Example

+

TanStack Pacer injectThrottledComputed Example

Counter

Instant Count: {{ instantCount() }}

Throttled Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Pending: {{ countRunner.state().isPending }}

-
{{ countRunner.state() | json }}

Search

@@ -20,9 +17,6 @@

Search

/>

Instant Search: {{ search() }}

Throttled Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Pending: {{ searchRunner.state().isPending }}

-
{{ searchRunner.state() | json }}

Range

@@ -40,11 +34,6 @@

Range

>Throttled Range

Throttled Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Pending: {{ rangeRunner.state().isPending }}

Values Submitted: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

-
{{ rangeRunner.state() | json }}

Throttled with a 250 ms wait.

diff --git a/examples/angular/injectThrottledValue/src/app/app.routes.ts b/examples/angular/injectThrottledComputed/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectThrottledValue/src/app/app.routes.ts rename to examples/angular/injectThrottledComputed/src/app/app.routes.ts diff --git a/examples/angular/injectQueuedValue/src/app/app.spec.ts b/examples/angular/injectThrottledComputed/src/app/app.spec.ts similarity index 74% rename from examples/angular/injectQueuedValue/src/app/app.spec.ts rename to examples/angular/injectThrottledComputed/src/app/app.spec.ts index 556385a67..adb254a0b 100644 --- a/examples/angular/injectQueuedValue/src/app/app.spec.ts +++ b/examples/angular/injectThrottledComputed/src/app/app.spec.ts @@ -20,7 +20,7 @@ describe('App', () => { await fixture.whenStable() const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectQueuedValue Example', + 'TanStack Pacer injectThrottledComputed Example', ) }) }) @@ -36,20 +36,22 @@ describe('example behavior', () => { vi.useRealTimers() vi.restoreAllMocks() }) - it('publishes a processed scalar and keeps pending values in queue state', async () => { + it('runs the counter, search, and range scenarios', async () => { vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) const fixture = TestBed.createComponent(App) const app = fixture.componentInstance fixture.detectChanges() TestBed.tick() - await vi.advanceTimersByTimeAsync(1000) - app.queuer.stop() - app.source.set('angular') + app.increment() + app.increment() + app.increment() + app.onSearch('angular') + app.onRange(73) TestBed.tick() - expect(app.queued()).toBe('') - expect(app.queuer.state().items).toEqual(['angular']) - app.queuer.execute() - expect(app.queued()).toBe('angular') - expect(app.queuer.state().items).toEqual([]) + await vi.advanceTimersByTimeAsync(1100) + TestBed.tick() + expect(app.controlledCount()).toBe(3) + expect(app.controlledSearch()).toBe('angular') + expect(app.controlledValue()).toBe(73) }) }) diff --git a/examples/angular/injectThrottledComputed/src/app/app.ts b/examples/angular/injectThrottledComputed/src/app/app.ts new file mode 100644 index 000000000..b8d6027b8 --- /dev/null +++ b/examples/angular/injectThrottledComputed/src/app/app.ts @@ -0,0 +1,27 @@ +import { Component, signal } from '@angular/core' +import { injectThrottledComputed } from '@tanstack/angular-pacer' + +@Component({ + selector: 'app-root', + templateUrl: './app.html', +}) +export class App { + readonly instantCount = signal(0) + readonly search = signal('') + readonly currentValue = signal(50) + readonly instantExecutions = signal(1) + readonly controlledCount = injectThrottledComputed(this.instantCount, () => ({ wait: 1000 })) + readonly controlledSearch = injectThrottledComputed(this.search, () => ({ wait: 1000 })) + readonly controlledValue = injectThrottledComputed(this.currentValue, () => ({ wait: 250 })) + increment(): void { + const next = this.instantCount() + 1 + this.instantCount.set(next) + } + onSearch(value: string): void { + this.search.set(value) + } + onRange(value: number): void { + this.currentValue.set(value) + this.instantExecutions.update((count) => count + 1) + } +} diff --git a/examples/angular/injectThrottledComputed/src/index.html b/examples/angular/injectThrottledComputed/src/index.html new file mode 100644 index 000000000..1b3f817b9 --- /dev/null +++ b/examples/angular/injectThrottledComputed/src/index.html @@ -0,0 +1,13 @@ + + + + + Basic + + + + + + + + diff --git a/examples/angular/injectThrottledValue/src/main.ts b/examples/angular/injectThrottledComputed/src/main.ts similarity index 100% rename from examples/angular/injectThrottledValue/src/main.ts rename to examples/angular/injectThrottledComputed/src/main.ts diff --git a/examples/angular/injectThrottledComputed/src/styles.css b/examples/angular/injectThrottledComputed/src/styles.css new file mode 100644 index 000000000..90d4ee007 --- /dev/null +++ b/examples/angular/injectThrottledComputed/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/examples/angular/injectThrottledValue/tests/e2e/smoke.spec.mts b/examples/angular/injectThrottledComputed/tests/e2e/smoke.spec.mts similarity index 96% rename from examples/angular/injectThrottledValue/tests/e2e/smoke.spec.mts rename to examples/angular/injectThrottledComputed/tests/e2e/smoke.spec.mts index 93138de70..fea2d72e0 100644 --- a/examples/angular/injectThrottledValue/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectThrottledComputed/tests/e2e/smoke.spec.mts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page, exampleUrl }) => { await page.goto(exampleUrl) await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectThrottledValue Example', + name: 'TanStack Pacer injectThrottledComputed Example', exact: true, }), ).toBeVisible() diff --git a/examples/angular/injectThrottledComputed/tsconfig.app.json b/examples/angular/injectThrottledComputed/tsconfig.app.json new file mode 100644 index 000000000..90631899c --- /dev/null +++ b/examples/angular/injectThrottledComputed/tsconfig.app.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": ["./src/**/*.ts"], + "exclude": ["./src/**/*.spec.ts"] +} diff --git a/examples/angular/injectThrottledComputed/tsconfig.json b/examples/angular/injectThrottledComputed/tsconfig.json new file mode 100644 index 000000000..2ab744275 --- /dev/null +++ b/examples/angular/injectThrottledComputed/tsconfig.json @@ -0,0 +1,33 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/examples/angular/injectThrottledComputed/tsconfig.spec.json b/examples/angular/injectThrottledComputed/tsconfig.spec.json new file mode 100644 index 000000000..26230b0b3 --- /dev/null +++ b/examples/angular/injectThrottledComputed/tsconfig.spec.json @@ -0,0 +1,10 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["vitest/globals"] + }, + "include": ["src/**/*.d.ts", "src/**/*.spec.ts"] +} diff --git a/examples/angular/injectThrottledSignal/src/app/app.html b/examples/angular/injectThrottledSignal/src/app/app.html index 237161101..f9fe98952 100644 --- a/examples/angular/injectThrottledSignal/src/app/app.html +++ b/examples/angular/injectThrottledSignal/src/app/app.html @@ -4,9 +4,6 @@

Counter

Instant Count: {{ instantCount() }}

Throttled Count: {{ controlledCount() }}

-

Executions: {{ countRunner.state().executionCount }}

-

Pending: {{ countRunner.state().isPending }}

-
{{ countRunner.state() | json }}

Search

@@ -20,9 +17,6 @@

Search

/>

Instant Search: {{ search() }}

Throttled Search: {{ controlledSearch() }}

-

Executions: {{ searchRunner.state().executionCount }}

-

Pending: {{ searchRunner.state().isPending }}

-
{{ searchRunner.state() | json }}

Range

@@ -40,11 +34,6 @@

Range

>Throttled Range

Throttled Value: {{ controlledValue() }}

-

Executions: {{ rangeRunner.state().executionCount }}

-

Pending: {{ rangeRunner.state().isPending }}

Instant Executions: {{ instantExecutions() }}

-

Saved Executions: {{ instantExecutions() - rangeRunner.state().executionCount }}

-

Reduction: {{ reduction() }}%

-
{{ rangeRunner.state() | json }}

Throttled with a 250 ms wait.

diff --git a/examples/angular/injectThrottledSignal/src/app/app.ts b/examples/angular/injectThrottledSignal/src/app/app.ts index 20deab468..448eeae1e 100644 --- a/examples/angular/injectThrottledSignal/src/app/app.ts +++ b/examples/angular/injectThrottledSignal/src/app/app.ts @@ -1,35 +1,18 @@ import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' import { injectThrottledSignal } from '@tanstack/angular-pacer' @Component({ selector: 'app-root', templateUrl: './app.html', - imports: [JsonPipe], }) export class App { readonly instantCount = signal(0) readonly search = signal('') readonly currentValue = signal(50) readonly instantExecutions = signal(0) - readonly controlledCount = injectThrottledSignal( - 0, - () => ({ wait: 1000 }), - (state) => state, - ) - readonly countRunner = this.controlledCount.throttler - readonly controlledSearch = injectThrottledSignal( - '', - () => ({ wait: 1000 }), - (state) => state, - ) - readonly searchRunner = this.controlledSearch.throttler - readonly controlledValue = injectThrottledSignal( - 50, - () => ({ wait: 250 }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.throttler + readonly controlledCount = injectThrottledSignal(0, () => ({ wait: 1000 })) + readonly controlledSearch = injectThrottledSignal('', () => ({ wait: 1000 })) + readonly controlledValue = injectThrottledSignal(50, () => ({ wait: 250 })) increment(): void { const next = this.instantCount() + 1 this.instantCount.set(next) @@ -44,8 +27,4 @@ export class App { this.instantExecutions.update((count) => count + 1) this.controlledValue.set(value) } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } } diff --git a/examples/angular/injectThrottledValue/src/app/app.ts b/examples/angular/injectThrottledValue/src/app/app.ts deleted file mode 100644 index 021f3973c..000000000 --- a/examples/angular/injectThrottledValue/src/app/app.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectThrottledValue } from '@tanstack/angular-pacer' - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - imports: [JsonPipe], -}) -export class App { - readonly instantCount = signal(0) - readonly search = signal('') - readonly currentValue = signal(50) - readonly instantExecutions = signal(1) - readonly controlledCount = injectThrottledValue( - this.instantCount, - 0, - () => ({ wait: 1000 }), - (state) => state, - ) - readonly countRunner = this.controlledCount.throttler - readonly controlledSearch = injectThrottledValue( - this.search, - '', - () => ({ wait: 1000 }), - (state) => state, - ) - readonly searchRunner = this.controlledSearch.throttler - readonly controlledValue = injectThrottledValue( - this.currentValue, - 50, - () => ({ wait: 250 }), - (state) => state, - ) - readonly rangeRunner = this.controlledValue.throttler - increment(): void { - const next = this.instantCount() + 1 - this.instantCount.set(next) - } - onSearch(value: string): void { - this.search.set(value) - } - onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - } - reduction(): number { - const count = this.instantExecutions() - return count ? Math.round(((count - this.rangeRunner.state().executionCount) / count) * 100) : 0 - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ed17885c..3cb3d7bc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1520,7 +1520,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectAsyncQueuedSignal: + examples/angular/injectAsyncQueuer: dependencies: '@angular/common': specifier: ^22.2.1 @@ -1575,7 +1575,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectAsyncQueuer: + examples/angular/injectAsyncQueuerItems: dependencies: '@angular/common': specifier: ^22.2.1 @@ -1795,7 +1795,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectDebouncedSignal: + examples/angular/injectDebouncedComputed: dependencies: '@angular/common': specifier: ^22.2.1 @@ -1850,7 +1850,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectDebouncedValue: + examples/angular/injectDebouncedSignal: dependencies: '@angular/common': specifier: ^22.2.1 @@ -1960,7 +1960,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectQueuedSignal: + examples/angular/injectQueuedComputed: dependencies: '@angular/common': specifier: ^22.2.1 @@ -2015,7 +2015,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectQueuedValue: + examples/angular/injectQueuedSignal: dependencies: '@angular/common': specifier: ^22.2.1 @@ -2125,7 +2125,62 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectRateLimitedSignal: + examples/angular/injectQueuerItems: + dependencies: + '@angular/common': + specifier: ^22.2.1 + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^22.2.1 + version: 22.2.1 + '@angular/core': + specifier: ^22.2.1 + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/forms': + specifier: ^22.2.1 + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + '@angular/platform-browser': + specifier: ^22.2.1 + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/router': + specifier: ^22.2.1 + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + '@tanstack/angular-devtools': + specifier: ^0.0.12 + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + '@tanstack/angular-pacer': + specifier: workspace:* + version: link:../../../packages/angular-pacer + '@tanstack/angular-pacer-devtools': + specifier: workspace:* + version: link:../../../packages/angular-pacer-devtools + rxjs: + specifier: ~7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + devDependencies: + '@angular/build': + specifier: ^22.2.1 + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + '@angular/cli': + specifier: ^22.2.1 + version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^22.2.1 + version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) + jsdom: + specifier: ^30.1.1 + version: 30.1.1(@noble/hashes@2.4.0) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + + examples/angular/injectRateLimitedComputed: dependencies: '@angular/common': specifier: ^22.2.1 @@ -2180,7 +2235,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectRateLimitedValue: + examples/angular/injectRateLimitedSignal: dependencies: '@angular/common': specifier: ^22.2.1 @@ -2290,7 +2345,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectThrottledSignal: + examples/angular/injectThrottledComputed: dependencies: '@angular/common': specifier: ^22.2.1 @@ -2345,7 +2400,7 @@ importers: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) - examples/angular/injectThrottledValue: + examples/angular/injectThrottledSignal: dependencies: '@angular/common': specifier: ^22.2.1 From 69084a0d88d2916a42167a57cd8c1836ad64fa41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Thu, 8 Oct 2026 13:54:52 -0300 Subject: [PATCH 04/18] docs(angular): restore adapter imports in the retry guide Import the re-exported retry APIs from angular-pacer and use the adapter installation instructions. --- docs/framework/angular/guides/async-retrying.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/framework/angular/guides/async-retrying.md b/docs/framework/angular/guides/async-retrying.md index 6280ca34d..581c3f439 100644 --- a/docs/framework/angular/guides/async-retrying.md +++ b/docs/framework/angular/guides/async-retrying.md @@ -8,12 +8,12 @@ Retrying runs an async operation again after it fails. It can make transient fai > [!NOTE] > `AsyncRetryer` is an alpha API and may change before 1.0. Its current design also supports the retry behavior inside Pacer's other async utilities. -Retrying is the exception among these framework guides: TanStack Pacer does not provide an Angular-specific retry primitive. Use `asyncRetry` and `AsyncRetryer` from `@tanstack/pacer` and connect long-lived instances to the Angular lifecycle. +Retrying is the exception among these framework guides: TanStack Pacer does not provide an Angular-specific retry primitive. Use `asyncRetry` and `AsyncRetryer` re-exported from `@tanstack/angular-pacer` and connect long-lived instances to the Angular lifecycle. -Install the core package when using these APIs directly: +Install the Angular adapter to use these re-exported APIs: ```sh -npm install @tanstack/pacer +npm install @tanstack/angular-pacer ``` If TanStack Query already owns the request, use its retry support so one system controls request state and cancellation. @@ -47,7 +47,7 @@ Also consider whether repeating the operation is idempotent. Reads are commonly `asyncRetry` creates one retryer and returns its bound execution function: ```ts -import { asyncRetry } from '@tanstack/pacer' +import { asyncRetry } from '@tanstack/angular-pacer' const loadUserWithRetry = asyncRetry(loadUser, { maxAttempts: 3, @@ -68,7 +68,7 @@ The defaults are three total attempts, exponential backoff from 1000 millisecond The returned function can be reused sequentially. It owns one `AsyncRetryer`, so starting a new call while an earlier call is active aborts the earlier retry flow. When calls may overlap, create a retryer per call or use another utility that manages concurrency: ```ts -import { AsyncRetryer } from '@tanstack/pacer' +import { AsyncRetryer } from '@tanstack/angular-pacer' async function loadOneUser(id: string) { const retryer = new AsyncRetryer(loadUser, { maxAttempts: 3 }) @@ -80,7 +80,7 @@ async function loadOneUser(id: string) { ```ts import { DestroyRef, inject } from '@angular/core' -import { AsyncRetryer } from '@tanstack/pacer' +import { AsyncRetryer } from '@tanstack/angular-pacer' export class UserComponent { private readonly destroyRef = inject(DestroyRef) From eacd23d667857880a8623212cbd5f646192fc3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Thu, 8 Oct 2026 13:54:52 -0300 Subject: [PATCH 05/18] refactor(angular-pacer): require exhaustive method decisions Define a boolean decision for every callable core member and derive the forwarded method lists through a shared helper. Explicitly exclude fn and setOptions while preserving the existing forwarded API. Cover omitted, newly added, and non-method keys with type regressions; regenerate reference locations. Validation: all 162 adapter tests and pnpm test:pr pass. --- .../reference/functions/injectAsyncBatcher.md | 4 +- .../functions/injectAsyncDebouncer.md | 4 +- .../reference/functions/injectAsyncQueuer.md | 4 +- .../functions/injectAsyncRateLimiter.md | 4 +- .../functions/injectAsyncThrottler.md | 4 +- .../reference/functions/injectBatcher.md | 4 +- .../reference/functions/injectDebouncer.md | 4 +- .../reference/functions/injectQueuer.md | 4 +- .../reference/functions/injectRateLimiter.md | 4 +- .../reference/functions/injectThrottler.md | 4 +- .../interfaces/AngularAsyncBatcher.md | 4 +- .../interfaces/AngularAsyncBatcherOptions.md | 4 +- .../interfaces/AngularAsyncDebouncer.md | 4 +- .../AngularAsyncDebouncerOptions.md | 4 +- .../interfaces/AngularAsyncQueuer.md | 4 +- .../interfaces/AngularAsyncQueuerOptions.md | 4 +- .../interfaces/AngularAsyncRateLimiter.md | 4 +- .../AngularAsyncRateLimiterOptions.md | 4 +- .../interfaces/AngularAsyncThrottler.md | 4 +- .../AngularAsyncThrottlerOptions.md | 4 +- .../reference/interfaces/AngularBatcher.md | 4 +- .../interfaces/AngularBatcherOptions.md | 4 +- .../reference/interfaces/AngularDebouncer.md | 4 +- .../interfaces/AngularDebouncerOptions.md | 4 +- .../reference/interfaces/AngularQueuer.md | 4 +- .../interfaces/AngularQueuerOptions.md | 4 +- .../interfaces/AngularRateLimiter.md | 4 +- .../interfaces/AngularRateLimiterOptions.md | 4 +- .../reference/interfaces/AngularThrottler.md | 4 +- .../interfaces/AngularThrottlerOptions.md | 4 +- .../src/async-batcher/injectAsyncBatcher.ts | 33 ++++++++------ .../async-debouncer/injectAsyncDebouncer.ts | 27 ++++++----- .../src/async-queuer/injectAsyncQueuer.ts | 45 +++++++++++-------- .../injectAsyncRateLimiter.ts | 29 +++++++----- .../async-throttler/injectAsyncThrottler.ts | 27 ++++++----- .../src/batcher/injectBatcher.ts | 27 ++++++----- .../src/debouncer/injectDebouncer.ts | 23 ++++++---- .../angular-pacer/src/queuer/injectQueuer.ts | 37 ++++++++------- .../src/rate-limiter/injectRateLimiter.ts | 23 ++++++---- .../src/throttler/injectThrottler.ts | 23 ++++++---- .../src/utils/injectForwardMethods.ts | 15 +++++++ .../tests/utils/injectForwardMethods.test.ts | 36 +++++++++++++++ 42 files changed, 292 insertions(+), 173 deletions(-) create mode 100644 packages/angular-pacer/tests/utils/injectForwardMethods.test.ts diff --git a/docs/framework/angular/reference/functions/injectAsyncBatcher.md b/docs/framework/angular/reference/functions/injectAsyncBatcher.md index 4dc6944c4..b9c5269a2 100644 --- a/docs/framework/angular/reference/functions/injectAsyncBatcher.md +++ b/docs/framework/angular/reference/functions/injectAsyncBatcher.md @@ -9,7 +9,7 @@ title: injectAsyncBatcher function injectAsyncBatcher(fn, options?): AngularAsyncBatcher; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:91 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:98 Creates and manages an Angular AsyncBatcher in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncBatcher( selector): AngularAsyncBatcher; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:102 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:109 Creates an Angular AsyncBatcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md index ab25308ae..f9b7b05f0 100644 --- a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md +++ b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md @@ -9,7 +9,7 @@ title: injectAsyncDebouncer function injectAsyncDebouncer(fn, options): AngularAsyncDebouncer; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:90 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:97 Creates and manages an Angular AsyncDebouncer in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncDebouncer( selector): AngularAsyncDebouncer; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:101 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:108 Creates an Angular AsyncDebouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuer.md b/docs/framework/angular/reference/functions/injectAsyncQueuer.md index f0783a698..c7ac7c979 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuer.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuer.md @@ -9,7 +9,7 @@ title: injectAsyncQueuer function injectAsyncQueuer(fn, options?): AngularAsyncQueuer; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:95 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:102 Creates and manages an Angular AsyncQueuer in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncQueuer( selector): AngularAsyncQueuer; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:106 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:113 Creates an Angular AsyncQueuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md index 668b75ce7..a4028e80a 100644 --- a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md @@ -9,7 +9,7 @@ title: injectAsyncRateLimiter function injectAsyncRateLimiter(fn, options): AngularAsyncRateLimiter; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:92 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:97 Creates and manages an Angular AsyncRateLimiter in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncRateLimiter( selector): AngularAsyncRateLimiter; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:103 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:108 Creates an Angular AsyncRateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncThrottler.md b/docs/framework/angular/reference/functions/injectAsyncThrottler.md index 2bb54ddd3..845f668e7 100644 --- a/docs/framework/angular/reference/functions/injectAsyncThrottler.md +++ b/docs/framework/angular/reference/functions/injectAsyncThrottler.md @@ -9,7 +9,7 @@ title: injectAsyncThrottler function injectAsyncThrottler(fn, options): AngularAsyncThrottler; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:90 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:97 Creates and manages an Angular AsyncThrottler in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncThrottler( selector): AngularAsyncThrottler; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:101 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:108 Creates an Angular AsyncThrottler with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectBatcher.md b/docs/framework/angular/reference/functions/injectBatcher.md index 9c75fec06..693b7243d 100644 --- a/docs/framework/angular/reference/functions/injectBatcher.md +++ b/docs/framework/angular/reference/functions/injectBatcher.md @@ -9,7 +9,7 @@ title: injectBatcher function injectBatcher(fn, options?): AngularBatcher; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:84 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:91 Creates and manages an Angular Batcher in the current injection context. @@ -76,7 +76,7 @@ function injectBatcher( selector): AngularBatcher; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:95 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:102 Creates an Angular Batcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectDebouncer.md b/docs/framework/angular/reference/functions/injectDebouncer.md index 6a6d0b9be..7ff0d5ed5 100644 --- a/docs/framework/angular/reference/functions/injectDebouncer.md +++ b/docs/framework/angular/reference/functions/injectDebouncer.md @@ -9,7 +9,7 @@ title: injectDebouncer function injectDebouncer(fn, options): AngularDebouncer; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:88 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:95 Creates and manages an Angular Debouncer in the current injection context. @@ -76,7 +76,7 @@ function injectDebouncer( selector): AngularDebouncer; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:99 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:106 Creates an Angular Debouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectQueuer.md b/docs/framework/angular/reference/functions/injectQueuer.md index 1ecbc06f5..5c9be3f4f 100644 --- a/docs/framework/angular/reference/functions/injectQueuer.md +++ b/docs/framework/angular/reference/functions/injectQueuer.md @@ -9,7 +9,7 @@ title: injectQueuer function injectQueuer(fn, options?): AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:89 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:96 Creates and manages an Angular Queuer in the current injection context. @@ -76,7 +76,7 @@ function injectQueuer( selector): AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:100 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:107 Creates an Angular Queuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectRateLimiter.md b/docs/framework/angular/reference/functions/injectRateLimiter.md index 4f1e6d0e4..4641a3b75 100644 --- a/docs/framework/angular/reference/functions/injectRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectRateLimiter.md @@ -9,7 +9,7 @@ title: injectRateLimiter function injectRateLimiter(fn, options): AngularRateLimiter; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:87 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:94 Creates and manages an Angular RateLimiter in the current injection context. @@ -76,7 +76,7 @@ function injectRateLimiter( selector): AngularRateLimiter; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:98 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:105 Creates an Angular RateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectThrottler.md b/docs/framework/angular/reference/functions/injectThrottler.md index 94eeef419..4350884b6 100644 --- a/docs/framework/angular/reference/functions/injectThrottler.md +++ b/docs/framework/angular/reference/functions/injectThrottler.md @@ -9,7 +9,7 @@ title: injectThrottler function injectThrottler(fn, options): AngularThrottler; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:88 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:95 Creates and manages an Angular Throttler in the current injection context. @@ -76,7 +76,7 @@ function injectThrottler( selector): AngularThrottler; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:99 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:106 Creates an Angular Throttler with a reactive selector result. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md index 7da6c2ca5..bfc5e7fd1 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcher title: AngularAsyncBatcher --- -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:47 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54 An Angular AsyncBatcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:61 Reactive state that will be updated when the batcher state changes diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md index f0e5dea07..5b0fd937a 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcherOptions title: AngularAsyncBatcherOptions --- -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:19 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:22 Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including optional onUnmount?: (batcher) => void; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:26 +Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:29 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md index e2f854308..9a571a4bc 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncer title: AngularAsyncDebouncer --- -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:48 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:55 An Angular AsyncDebouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:53 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:60 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md index c042d238c..9e40abc51 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncerOptions title: AngularAsyncDebouncerOptions --- -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:23 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:26 Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), includ optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:30 +Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:33 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md index def8ad4ee..5504f85fa 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuer title: AngularAsyncQueuer --- -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:53 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:60 An Angular AsyncQueuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:58 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:65 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md index 192766045..4051e2e66 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuerOptions title: AngularAsyncQueuerOptions --- -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:19 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:22 Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including co optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:26 +Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:29 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md index 78cab011a..7c0ff9a1f 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiter title: AngularAsyncRateLimiter --- -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:50 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55 An Angular AsyncRateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:60 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md index 4dc0cfa75..c5304eeb5 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiterOptions title: AngularAsyncRateLimiterOptions --- -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:23 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:26 Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), in optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:30 +Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:33 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (abort running work and reset the limiter). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md index 35063ffe9..4ce9f7c4c 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottler title: AngularAsyncThrottler --- -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:48 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:55 An Angular AsyncThrottler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:53 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:60 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md index db95a8b13..744016e0e 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottlerOptions title: AngularAsyncThrottlerOptions --- -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:23 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:26 Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), includ optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:30 +Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:33 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularBatcher.md b/docs/framework/angular/reference/interfaces/AngularBatcher.md index 47dfe0268..191301c48 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcher.md @@ -3,7 +3,7 @@ id: AngularBatcher title: AngularBatcher --- -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:42 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:49 An Angular Batcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:47 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:54 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md index c8b949d40..fce85ce41 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularBatcherOptions title: AngularBatcherOptions --- -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:19 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:22 Options for [injectBatcher](../functions/injectBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectBatcher](../functions/injectBatcher.md), including core confi optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:24 +Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:27 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches). diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncer.md b/docs/framework/angular/reference/interfaces/AngularDebouncer.md index a393cfc50..d38e75e57 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncer.md @@ -3,7 +3,7 @@ id: AngularDebouncer title: AngularDebouncer --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:46 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:53 An Angular Debouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:51 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:58 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md index d52f98a5c..72db250d0 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularDebouncerOptions title: AngularDebouncerOptions --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:23 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:26 Options for [injectDebouncer](../functions/injectDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectDebouncer](../functions/injectDebouncer.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:30 +Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:33 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/docs/framework/angular/reference/interfaces/AngularQueuer.md b/docs/framework/angular/reference/interfaces/AngularQueuer.md index 275130826..bf91e17d3 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuer.md @@ -3,7 +3,7 @@ id: AngularQueuer title: AngularQueuer --- -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:47 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:54 An Angular Queuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:52 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:59 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md index 97589c57d..0fc56fb40 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularQueuerOptions title: AngularQueuerOptions --- -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:19 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:22 Options for [injectQueuer](../functions/injectQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectQueuer](../functions/injectQueuer.md), including core configu optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:24 +Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:27 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing). diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md index 693c8dbde..0fc4a04b8 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md @@ -3,7 +3,7 @@ id: AngularRateLimiter title: AngularRateLimiter --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:45 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:52 An Angular RateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:50 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:57 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md index 898277053..9c7cc6e45 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularRateLimiterOptions title: AngularRateLimiterOptions --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:22 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:25 Options for [injectRateLimiter](../functions/injectRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectRateLimiter](../functions/injectRateLimiter.md), including co optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:29 +Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:32 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (reset the limiter). diff --git a/docs/framework/angular/reference/interfaces/AngularThrottler.md b/docs/framework/angular/reference/interfaces/AngularThrottler.md index 98de4a88e..81512a490 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottler.md @@ -3,7 +3,7 @@ id: AngularThrottler title: AngularThrottler --- -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:46 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:53 An Angular Throttler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:51 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:58 The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md index c98cba67a..e95735ea4 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularThrottlerOptions title: AngularThrottlerOptions --- -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:23 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:26 Options for [injectThrottler](../functions/injectThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectThrottler](../functions/injectThrottler.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:30 +Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:33 Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts index 9bb5a354a..6d54a7ce8 100644 --- a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts +++ b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts @@ -3,7 +3,10 @@ import { AsyncBatcher } from '@tanstack/pacer/async-batcher' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -11,7 +14,7 @@ import type { ReadonlySelected } from '../utils/readonlySelected' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { AsyncBatcherOptions, AsyncBatcherState } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncBatcher}, including core configuration and Angular cleanup. @@ -26,17 +29,21 @@ export interface AngularAsyncBatcherOptions< onUnmount?: (batcher: AsyncBatcher) => void } -const asyncBatcherMethods = [ - 'addItem', - 'flush', - 'clear', - 'abort', - 'cancel', - 'reset', - 'getAbortSignal', - 'peekAllItems', - 'peekFailedItems', -] as const satisfies ReadonlyArray>> +const asyncBatcherMethodMap = { + fn: false, + setOptions: false, + addItem: true, + flush: true, + clear: true, + abort: true, + cancel: true, + reset: true, + getAbortSignal: true, + peekAllItems: true, + peekFailedItems: true, +} satisfies MethodMap> + +const asyncBatcherMethods = methodNames(asyncBatcherMethodMap) type AsyncBatcherMethod = (typeof asyncBatcherMethods)[number] diff --git a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts index 5fb8fe25f..32a759c87 100644 --- a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts +++ b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts @@ -3,7 +3,10 @@ import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -15,7 +18,7 @@ import type { AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncDebouncer}, including core configuration and Angular cleanup. @@ -30,14 +33,18 @@ export interface AngularAsyncDebouncerOptions< onUnmount?: (core: AsyncDebouncer) => void } -const asyncDebouncerMethods = [ - 'maybeExecute', - 'flush', - 'cancel', - 'abort', - 'reset', - 'getAbortSignal', -] as const satisfies ReadonlyArray>> +const asyncDebouncerMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + flush: true, + cancel: true, + abort: true, + reset: true, + getAbortSignal: true, +} satisfies MethodMap> + +const asyncDebouncerMethods = methodNames(asyncDebouncerMethodMap) type AsyncDebouncerMethod = (typeof asyncDebouncerMethods)[number] diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts index 06ad44ade..784da724d 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts @@ -3,7 +3,10 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -11,7 +14,7 @@ import type { ReadonlySelected } from '../utils/readonlySelected' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { AsyncQueuerOptions, AsyncQueuerState } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncQueuer}, including core configuration and Angular cleanup. @@ -26,23 +29,27 @@ export interface AngularAsyncQueuerOptions< onUnmount?: (core: AsyncQueuer) => void } -const asyncQueuerMethods = [ - 'addItem', - 'getNextItem', - 'execute', - 'flush', - 'flushAsBatch', - 'peekNextItem', - 'peekAllItems', - 'peekActiveItems', - 'peekPendingItems', - 'start', - 'stop', - 'clear', - 'abort', - 'reset', - 'getAbortSignal', -] as const satisfies ReadonlyArray>> +const asyncQueuerMethodMap = { + fn: false, + setOptions: false, + addItem: true, + getNextItem: true, + execute: true, + flush: true, + flushAsBatch: true, + peekNextItem: true, + peekAllItems: true, + peekActiveItems: true, + peekPendingItems: true, + start: true, + stop: true, + clear: true, + abort: true, + reset: true, + getAbortSignal: true, +} satisfies MethodMap> + +const asyncQueuerMethods = methodNames(asyncQueuerMethodMap) type AsyncQueuerMethod = (typeof asyncQueuerMethods)[number] diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index ee6afa813..0f7d5eda8 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -3,7 +3,10 @@ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -15,7 +18,7 @@ import type { AsyncRateLimiterOptions, AsyncRateLimiterState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncRateLimiter}, including core configuration and Angular cleanup. @@ -30,16 +33,18 @@ export interface AngularAsyncRateLimiterOptions< onUnmount?: (core: AsyncRateLimiter) => void } -const asyncRateLimiterMethods = [ - 'maybeExecute', - 'getRemainingInWindow', - 'getMsUntilNextWindow', - 'abort', - 'reset', - 'getAbortSignal', -] as const satisfies ReadonlyArray< - MethodKeys> -> +const asyncRateLimiterMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + getRemainingInWindow: true, + getMsUntilNextWindow: true, + abort: true, + reset: true, + getAbortSignal: true, +} satisfies MethodMap> + +const asyncRateLimiterMethods = methodNames(asyncRateLimiterMethodMap) type AsyncRateLimiterMethod = (typeof asyncRateLimiterMethods)[number] diff --git a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts index 230cd4c36..4e68b323e 100644 --- a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts +++ b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts @@ -3,7 +3,10 @@ import { AsyncThrottler } from '@tanstack/pacer/async-throttler' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -15,7 +18,7 @@ import type { AsyncThrottlerOptions, AsyncThrottlerState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncThrottler}, including core configuration and Angular cleanup. @@ -30,14 +33,18 @@ export interface AngularAsyncThrottlerOptions< onUnmount?: (core: AsyncThrottler) => void } -const asyncThrottlerMethods = [ - 'maybeExecute', - 'flush', - 'cancel', - 'abort', - 'reset', - 'getAbortSignal', -] as const satisfies ReadonlyArray>> +const asyncThrottlerMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + flush: true, + cancel: true, + abort: true, + reset: true, + getAbortSignal: true, +} satisfies MethodMap> + +const asyncThrottlerMethods = methodNames(asyncThrottlerMethodMap) type AsyncThrottlerMethod = (typeof asyncThrottlerMethods)[number] diff --git a/packages/angular-pacer/src/batcher/injectBatcher.ts b/packages/angular-pacer/src/batcher/injectBatcher.ts index eb386c442..5adc6a63c 100644 --- a/packages/angular-pacer/src/batcher/injectBatcher.ts +++ b/packages/angular-pacer/src/batcher/injectBatcher.ts @@ -3,7 +3,10 @@ import { Batcher } from '@tanstack/pacer/batcher' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -11,7 +14,7 @@ import type { ReadonlySelected } from '../utils/readonlySelected' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { BatcherOptions, BatcherState } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectBatcher}, including core configuration and Angular cleanup. @@ -24,14 +27,18 @@ export interface AngularBatcherOptions extends BatcherOptions { onUnmount?: (core: Batcher) => void } -const batcherMethods = [ - 'addItem', - 'flush', - 'peekAllItems', - 'clear', - 'cancel', - 'reset', -] as const satisfies ReadonlyArray>> +const batcherMethodMap = { + fn: false, + setOptions: false, + addItem: true, + flush: true, + peekAllItems: true, + clear: true, + cancel: true, + reset: true, +} satisfies MethodMap> + +const batcherMethods = methodNames(batcherMethodMap) type BatcherMethod = (typeof batcherMethods)[number] diff --git a/packages/angular-pacer/src/debouncer/injectDebouncer.ts b/packages/angular-pacer/src/debouncer/injectDebouncer.ts index 9574c7c2e..6fca3ab6f 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncer.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncer.ts @@ -3,7 +3,10 @@ import { Debouncer } from '@tanstack/pacer/debouncer' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -15,7 +18,7 @@ import type { DebouncerOptions, DebouncerState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectDebouncer}, including core configuration and Angular cleanup. @@ -30,12 +33,16 @@ export interface AngularDebouncerOptions< onUnmount?: (core: Debouncer) => void } -const debouncerMethods = [ - 'maybeExecute', - 'flush', - 'cancel', - 'reset', -] as const satisfies ReadonlyArray>> +const debouncerMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + flush: true, + cancel: true, + reset: true, +} satisfies MethodMap> + +const debouncerMethods = methodNames(debouncerMethodMap) type DebouncerMethod = (typeof debouncerMethods)[number] diff --git a/packages/angular-pacer/src/queuer/injectQueuer.ts b/packages/angular-pacer/src/queuer/injectQueuer.ts index 95cbfc504..2298ec082 100644 --- a/packages/angular-pacer/src/queuer/injectQueuer.ts +++ b/packages/angular-pacer/src/queuer/injectQueuer.ts @@ -3,7 +3,10 @@ import { Queuer } from '@tanstack/pacer/queuer' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -11,7 +14,7 @@ import type { ReadonlySelected } from '../utils/readonlySelected' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { QueuerOptions, QueuerState } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectQueuer}, including core configuration and Angular cleanup. @@ -24,19 +27,23 @@ export interface AngularQueuerOptions extends QueuerOptions { onUnmount?: (core: Queuer) => void } -const queuerMethods = [ - 'addItem', - 'getNextItem', - 'execute', - 'flush', - 'flushAsBatch', - 'peekNextItem', - 'peekAllItems', - 'start', - 'stop', - 'clear', - 'reset', -] as const satisfies ReadonlyArray>> +const queuerMethodMap = { + fn: false, + setOptions: false, + addItem: true, + getNextItem: true, + execute: true, + flush: true, + flushAsBatch: true, + peekNextItem: true, + peekAllItems: true, + start: true, + stop: true, + clear: true, + reset: true, +} satisfies MethodMap> + +const queuerMethods = methodNames(queuerMethodMap) type QueuerMethod = (typeof queuerMethods)[number] diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index f959b5d5f..ab1aeca4e 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -3,7 +3,10 @@ import { RateLimiter } from '@tanstack/pacer/rate-limiter' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import type { ReadonlySelected } from '../utils/readonlySelected' @@ -14,7 +17,7 @@ import type { RateLimiterOptions, RateLimiterState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectRateLimiter}, including core configuration and Angular cleanup. @@ -29,12 +32,16 @@ export interface AngularRateLimiterOptions< onUnmount?: (core: RateLimiter) => void } -const rateLimiterMethods = [ - 'maybeExecute', - 'getRemainingInWindow', - 'getMsUntilNextWindow', - 'reset', -] as const satisfies ReadonlyArray>> +const rateLimiterMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + getRemainingInWindow: true, + getMsUntilNextWindow: true, + reset: true, +} satisfies MethodMap> + +const rateLimiterMethods = methodNames(rateLimiterMethodMap) type RateLimiterMethod = (typeof rateLimiterMethods)[number] diff --git a/packages/angular-pacer/src/throttler/injectThrottler.ts b/packages/angular-pacer/src/throttler/injectThrottler.ts index e80e7930b..fe8724513 100644 --- a/packages/angular-pacer/src/throttler/injectThrottler.ts +++ b/packages/angular-pacer/src/throttler/injectThrottler.ts @@ -3,7 +3,10 @@ import { Throttler } from '@tanstack/pacer/throttler' import { shallow } from '@tanstack/store' import { injectPacerOptions } from '../provider/providePacerOptions' import { toAccessorSignal } from '../utils/maybeAccessor' -import { injectForwardMethods } from '../utils/injectForwardMethods' +import { + injectForwardMethods, + methodNames, +} from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' @@ -15,7 +18,7 @@ import type { ThrottlerOptions, ThrottlerState, } from '@tanstack/pacer' -import type { MethodKeys } from '../utils/injectForwardMethods' +import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectThrottler}, including core configuration and Angular cleanup. @@ -30,12 +33,16 @@ export interface AngularThrottlerOptions< onUnmount?: (core: Throttler) => void } -const throttlerMethods = [ - 'maybeExecute', - 'flush', - 'cancel', - 'reset', -] as const satisfies ReadonlyArray>> +const throttlerMethodMap = { + fn: false, + setOptions: false, + maybeExecute: true, + flush: true, + cancel: true, + reset: true, +} satisfies MethodMap> + +const throttlerMethods = methodNames(throttlerMethodMap) type ThrottlerMethod = (typeof throttlerMethods)[number] diff --git a/packages/angular-pacer/src/utils/injectForwardMethods.ts b/packages/angular-pacer/src/utils/injectForwardMethods.ts index 75689caeb..ae8b9e1cf 100644 --- a/packages/angular-pacer/src/utils/injectForwardMethods.ts +++ b/packages/angular-pacer/src/utils/injectForwardMethods.ts @@ -6,6 +6,21 @@ export type MethodKeys = { [K in keyof T]-?: T[K] extends (...args: Array) => unknown ? K : never }[keyof T] +/** Every callable core member must be explicitly forwarded or excluded. */ +export type MethodMap = Record, boolean> + +type IncludedMethodKeys = { + [K in keyof T]-?: T[K] extends true ? K : never +}[keyof T] + +export function methodNames>( + methods: T, +): Array> { + return Object.keys(methods).filter((key) => methods[key]) as Array< + IncludedMethodKeys + > +} + export function injectForwardMethods< TSource, TMethods extends MethodKeys, diff --git a/packages/angular-pacer/tests/utils/injectForwardMethods.test.ts b/packages/angular-pacer/tests/utils/injectForwardMethods.test.ts new file mode 100644 index 000000000..c8dd56286 --- /dev/null +++ b/packages/angular-pacer/tests/utils/injectForwardMethods.test.ts @@ -0,0 +1,36 @@ +import { expect, expectTypeOf, it } from 'vitest' +import { methodNames } from '../../src/utils/injectForwardMethods' +import type { MethodMap } from '../../src/utils/injectForwardMethods' + +it('requires every method decision and derives only forwarded method keys', () => { + type Core = { + execute: (value: string) => string + setOptions: () => void + fn: () => void + key: string + } + const decisions = { + execute: true, + setOptions: false, + fn: false, + } satisfies MethodMap + const methods = methodNames(decisions) + expect(methods).toEqual(['execute']) + expectTypeOf(methods).toEqualTypeOf>() + + const { execute, ...missingExecute } = decisions + // @ts-expect-error Every callable core member requires a decision. + missingExecute satisfies MethodMap + + type UpdatedCore = Core & { newMethod: () => void } + // @ts-expect-error A new core method must be explicitly included or excluded. + decisions satisfies MethodMap + + const invalid: MethodMap = { + ...decisions, + // @ts-expect-error Non-method fields cannot be registered as operations. + key: true, + } + void invalid + void execute +}) From 7f90610f80ebb6c33a24889d805df807e7a22313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Fri, 9 Oct 2026 06:07:32 -0300 Subject: [PATCH 06/18] fix(angular-pacer): disambiguate callable initial values --- packages/angular-pacer/src/utils/maybeAccessor.ts | 4 +++- .../tests/debouncer/injectDebouncedSignal.test.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/angular-pacer/src/utils/maybeAccessor.ts b/packages/angular-pacer/src/utils/maybeAccessor.ts index 9350af326..1ddf9b1a7 100644 --- a/packages/angular-pacer/src/utils/maybeAccessor.ts +++ b/packages/angular-pacer/src/utils/maybeAccessor.ts @@ -1,6 +1,8 @@ import { computed } from '@angular/core' -export type MaybeAccessor = T | (() => T) +type NonCallable = T extends (...args: Array) => unknown ? never : T + +export type MaybeAccessor = NonCallable | (() => T) function isAccessor(value: MaybeAccessor): value is () => T { return typeof value === 'function' diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts index d50509d98..4b53e3bee 100644 --- a/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts +++ b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts @@ -110,3 +110,16 @@ it('passes selected state through to the attached utility', () => { expect(value()).toBe('updated') expect(value.debouncer.state().count).toBe(before + 1) }) + +it('preserves a function-valued initial value supplied through a factory', () => { + const fn = (n: number) => n + 1 + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(() => fn, { wait: 10 }), + ) + expectTypeOf(value()).toEqualTypeOf() + expect(value()).toBe(fn) + expect(value()(2)).toBe(3) + + // @ts-expect-error Function values must be wrapped to distinguish them from accessors. + void (() => injectDebouncedSignal(fn, { wait: 10 })) +}) From bd5bc57edd3c1cba5ec907e3a371e75dc13bf2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Fri, 9 Oct 2026 06:07:32 -0300 Subject: [PATCH 07/18] fix(angular-pacer): align limiter cleanup with sibling adapters --- .../functions/injectAsyncRateLimiter.md | 2 +- .../reference/functions/injectRateLimiter.md | 2 +- .../AngularAsyncRateLimiterOptions.md | 2 +- .../interfaces/AngularRateLimiterOptions.md | 2 +- .../injectAsyncRateLimiter.ts | 5 ++--- .../src/rate-limiter/injectRateLimiter.ts | 6 ++--- .../injectAsyncRateLimiter.test.ts | 22 +++++++++++++++++++ 7 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md index a4028e80a..4ca7b3b75 100644 --- a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md @@ -26,7 +26,7 @@ Pass a selector to expose reactive core state through `state()`. Without a selec ## Cleanup -The default cleanup is to abort running work and reset the limiter. Set `onUnmount` to replace it. +The default cleanup is to abort running work. Set `onUnmount` to replace it. ### Type Parameters diff --git a/docs/framework/angular/reference/functions/injectRateLimiter.md b/docs/framework/angular/reference/functions/injectRateLimiter.md index 4641a3b75..eb17ddfab 100644 --- a/docs/framework/angular/reference/functions/injectRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectRateLimiter.md @@ -26,7 +26,7 @@ Pass a selector to expose reactive core state through `state()`. Without a selec ## Cleanup -The default cleanup is to reset the limiter. Set `onUnmount` to replace it. +There is no default cleanup. Set `onUnmount` to customize cleanup. ### Type Parameters diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md index c5304eeb5..f10266704 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md @@ -28,7 +28,7 @@ optional onUnmount?: (core) => void; Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:33 Called when the owning injection context is destroyed. Receives the core instance. -Providing this callback replaces the default cleanup (abort running work and reset the limiter). +Providing this callback replaces the default cleanup (abort running work). #### Parameters diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md index 9c7cc6e45..3ce0606d2 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md @@ -28,7 +28,7 @@ optional onUnmount?: (core) => void; Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:32 Called when the owning injection context is destroyed. Receives the core instance. -Providing this callback replaces the default cleanup (reset the limiter). +There is no default cleanup; use this callback for custom teardown. #### Parameters diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index 0f7d5eda8..78a02dd2a 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -28,7 +28,7 @@ export interface AngularAsyncRateLimiterOptions< > extends AsyncRateLimiterOptions { /** * Called when the owning injection context is destroyed. Receives the core instance. - * Providing this callback replaces the default cleanup (abort running work and reset the limiter). + * Providing this callback replaces the default cleanup (abort running work). */ onUnmount?: (core: AsyncRateLimiter) => void } @@ -76,7 +76,7 @@ export interface AngularAsyncRateLimiter< * * ## Cleanup * - * The default cleanup is to abort running work and reset the limiter. Set `onUnmount` to replace it. + * The default cleanup is to abort running work. Set `onUnmount` to replace it. * * @param fn The callback invoked by the core utility. * @param options Core options or a reactive factory returning them. @@ -147,7 +147,6 @@ export function injectAsyncRateLimiter( opts.onUnmount(core) } else { core.abort() - core.reset() } }) }) diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index ab1aeca4e..eb65e7dcc 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -27,7 +27,7 @@ export interface AngularRateLimiterOptions< > extends RateLimiterOptions { /** * Called when the owning injection context is destroyed. Receives the core instance. - * Providing this callback replaces the default cleanup (reset the limiter). + * There is no default cleanup; use this callback for custom teardown. */ onUnmount?: (core: RateLimiter) => void } @@ -73,7 +73,7 @@ export interface AngularRateLimiter< * * ## Cleanup * - * The default cleanup is to reset the limiter. Set `onUnmount` to replace it. + * There is no default cleanup. Set `onUnmount` to customize cleanup. * * @param fn The callback invoked by the core utility. * @param options Core options or a reactive factory returning them. @@ -142,8 +142,6 @@ export function injectRateLimiter( const opts = untracked(mergedOptions) if (opts.onUnmount) { opts.onUnmount(core) - } else { - core.reset() } }) }) diff --git a/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts b/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts index 188593943..6a575b492 100644 --- a/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts +++ b/packages/angular-pacer/tests/async-rate-limiter/injectAsyncRateLimiter.test.ts @@ -55,6 +55,28 @@ describe('operations and selected state', () => { }) describe('cleanup', () => { + it('aborts active work on owner destruction', async () => { + vi.useFakeTimers() + const execution = Promise.withResolvers() + const utility = TestBed.runInInjectionContext(() => + injectAsyncRateLimiter(() => execution.promise, { + limit: 2, + window: 1000, + }), + ) + TestBed.tick() + const pending = utility.maybeExecute() + const abortSignal = utility.getAbortSignal() + expect(abortSignal).not.toBeNull() + expect(abortSignal?.aborted).toBe(false) + + TestBed.resetTestingModule() + + expect(abortSignal?.aborted).toBe(true) + execution.resolve('finished') + await pending + }) + it('uses the latest cleanup callback without waiting for the options effect', () => { const oldCleanup = vi.fn() const latestCleanup = vi.fn() From b09d2b26ae0dec3286135b0ba5f352f34ed956a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Fri, 9 Oct 2026 06:07:32 -0300 Subject: [PATCH 08/18] docs(angular): remove breaking changes tracking file --- ANGULAR-BREAKING-CHANGES.md | 110 ------------------------------------ 1 file changed, 110 deletions(-) delete mode 100644 ANGULAR-BREAKING-CHANGES.md diff --git a/ANGULAR-BREAKING-CHANGES.md b/ANGULAR-BREAKING-CHANGES.md deleted file mode 100644 index 57cb8bab2..000000000 --- a/ANGULAR-BREAKING-CHANGES.md +++ /dev/null @@ -1,110 +0,0 @@ -# Angular adapter breaking changes - -Changes from the previous Angular adapter on upstream main. - -## Angular version - -Angular 20 or newer is required. Angular 17–19 are no longer supported. - -## API renames - -| Previous API | Replacement | -| ------------------------------------ | --------------------------- | -| `injectDebouncedValue` | `injectDebouncedComputed` | -| `injectThrottledValue` | `injectThrottledComputed` | -| `injectRateLimitedValue` | `injectRateLimitedComputed` | -| `injectQueuedValue` | `injectQueuedComputed` | -| `injectQueuedSignal` (pending items) | `injectQueuerItems` | -| `injectAsyncQueuedSignal` | `injectAsyncQueuerItems` | - -`injectQueuedSignal` now creates an editable scalar signal. Old calls that pass -an item-processing callback must migrate to `injectQueuerItems`. - -## Utility refs - -Refs expose their selected `state()` signal and explicit methods. They no longer -expose the full core instance: `fn`, `key`, `options`, `store`, and `setOptions` -are removed. Read state through a selector and change options through an options -factory: - -```ts -const wait = signal(100) -const debouncer = injectDebouncer( - callback, - () => ({ wait: wait() }), - (state) => ({ - isPending: state.isPending, - }), -) -wait.set(200) // Replaces debouncer.setOptions({ wait: 200 }). -``` - -`onUnmount` now receives the core Pacer instance rather than the Angular ref. -Read `core.store.state` inside that callback instead of `ref.state()`. - -A caller-selected state type requires a selector argument. Calls that specify -`TSelected` without providing a selector no longer compile. - -## Editable signal writes - -For debounced, throttled, and rate-limited signals, replace updater calls to -`set` with `update`: - -```ts -value.set((previous) => previous + 1) // Previous API. -value.update((previous) => previous + 1) // Current API. -``` - -These helpers now queue callbacks internally. Their attached utility accepts a -callback in `maybeExecute`, and item-dependent options and selected fields such -as `lastArgs` contain that callback rather than the replacement value. -Use the signal's `set` and `update` methods for value writes. - -## Computed helpers - -Computed helpers take `(source, options, selector?)`. Overloads accepting a -separate initial value are removed; the first read returns the source value. -They return readonly signals with an attached utility rather than the editable -signal refs returned by the previous debounced, throttled, and rate-limited value -helpers. Write to the source instead of calling `set` on the computed result. - -For queued computed values, replace `value.addItem(item)` with -`value.queuer.addItem(item)`. The shortcut remains on the items helpers. - -## Pending items - -Items helpers return `ReadonlyArray` rather than mutable arrays. -Without a selector, their attached `queuer.state()` now returns `{}`, rather -than `{ items }`. Read waiting items from the items signal, or explicitly select -additional state: - -```ts -const items = injectQueuerItems(processItem, { started: false }, (state) => ({ - size: state.size, -})) -items.addItem('job') -items() // Waiting items. -items.queuer.state().size // Selected state. -``` - -Selectors no longer have to include an `items` field. - -## Public types and imports - -| Previous type | Replacement | -| --------------------------------- | ----------------------------------------- | -| `DebouncedSignal` | `AngularDebouncerSignal` | -| `ThrottledSignal` | `AngularThrottlerSignal` | -| `RateLimitedSignal` | `AngularRateLimiterSignal` | -| `QueuedSignal` | `AngularQueuerItems` | -| `AsyncQueuedSignal` | `AngularAsyncQueuerItems` | -| `QueuedValueSignal` | `AngularQueuerComputed` | -| `AngularPacerOptions` | Use `T \| (() => T)` in application types | - -Computed helpers use their corresponding `AngularDebouncerComputed`, -`AngularThrottlerComputed`, and `AngularRateLimiterComputed` types. - -`Angular*Options` types no longer take a `TSelected` generic argument; selection -belongs to the utility ref. `PACER_OPTIONS` is no longer exported from the -provider entry point. Use `providePacerOptions` to configure defaults and -`injectPacerOptions` to read them. From 5ac95d72df680df714b21e083d40c850e8115687 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:55:53 +0000 Subject: [PATCH 09/18] ci: apply automated fixes --- .../reference/functions/injectAsyncBatcher.md | 4 +- .../functions/injectAsyncDebouncer.md | 4 +- .../reference/functions/injectAsyncQueuer.md | 4 +- .../functions/injectAsyncQueuerItems.md | 4 +- .../functions/injectAsyncRateLimiter.md | 4 +- .../functions/injectAsyncThrottler.md | 4 +- .../reference/functions/injectBatcher.md | 4 +- .../functions/injectDebouncedComputed.md | 4 +- .../functions/injectDebouncedSignal.md | 4 +- .../reference/functions/injectDebouncer.md | 4 +- .../reference/functions/injectPacerOptions.md | 2 +- .../functions/injectQueuedComputed.md | 4 +- .../reference/functions/injectQueuedSignal.md | 4 +- .../reference/functions/injectQueuer.md | 4 +- .../reference/functions/injectQueuerItems.md | 4 +- .../functions/injectRateLimitedComputed.md | 4 +- .../functions/injectRateLimitedSignal.md | 4 +- .../reference/functions/injectRateLimiter.md | 4 +- .../functions/injectThrottledComputed.md | 4 +- .../functions/injectThrottledSignal.md | 4 +- .../reference/functions/injectThrottler.md | 4 +- .../functions/providePacerOptions.md | 2 +- .../interfaces/AngularAsyncBatcher.md | 4 +- .../interfaces/AngularAsyncBatcherOptions.md | 4 +- .../interfaces/AngularAsyncDebouncer.md | 4 +- .../AngularAsyncDebouncerOptions.md | 4 +- .../interfaces/AngularAsyncQueuer.md | 4 +- .../interfaces/AngularAsyncQueuerItems.md | 8 +- .../interfaces/AngularAsyncQueuerOptions.md | 4 +- .../interfaces/AngularAsyncRateLimiter.md | 4 +- .../AngularAsyncRateLimiterOptions.md | 4 +- .../interfaces/AngularAsyncThrottler.md | 4 +- .../AngularAsyncThrottlerOptions.md | 4 +- .../reference/interfaces/AngularBatcher.md | 4 +- .../interfaces/AngularBatcherOptions.md | 4 +- .../reference/interfaces/AngularDebouncer.md | 4 +- .../interfaces/AngularDebouncerComputed.md | 6 +- .../interfaces/AngularDebouncerOptions.md | 4 +- .../interfaces/AngularDebouncerSignal.md | 10 +-- .../reference/interfaces/AngularQueuer.md | 4 +- .../interfaces/AngularQueuerComputed.md | 6 +- .../interfaces/AngularQueuerItems.md | 8 +- .../interfaces/AngularQueuerOptions.md | 4 +- .../interfaces/AngularQueuerSignal.md | 10 +-- .../interfaces/AngularRateLimiter.md | 4 +- .../interfaces/AngularRateLimiterComputed.md | 6 +- .../interfaces/AngularRateLimiterOptions.md | 4 +- .../interfaces/AngularRateLimiterSignal.md | 10 +-- .../reference/interfaces/AngularThrottler.md | 4 +- .../interfaces/AngularThrottlerComputed.md | 6 +- .../interfaces/AngularThrottlerOptions.md | 4 +- .../interfaces/AngularThrottlerSignal.md | 10 +-- .../interfaces/PacerProviderOptions.md | 22 +++--- .../injectAsyncQueuerItems/src/app/app.html | 2 +- .../injectQueuedComputed/eslint.config.js | 4 +- .../src/app/app.config.ts | 14 ++-- .../src/app/app.routes.ts | 4 +- .../injectQueuedComputed/src/app/app.spec.ts | 76 +++++++++---------- .../injectQueuedComputed/src/app/app.ts | 28 +++---- .../injectQueuedComputed/src/app/inputapp.ts | 12 +-- .../angular/injectQueuedComputed/src/main.ts | 8 +- .../tests/e2e/smoke.spec.mts | 52 ++++++------- .../injectQueuedSignal/eslint.config.js | 4 +- .../injectQueuedSignal/src/app/app.config.ts | 14 ++-- .../injectQueuedSignal/src/app/app.routes.ts | 4 +- .../injectQueuedSignal/src/app/app.spec.ts | 76 +++++++++---------- .../angular/injectQueuedSignal/src/app/app.ts | 28 +++---- .../angular/injectQueuedSignal/src/main.ts | 8 +- .../tests/e2e/smoke.spec.mts | 50 ++++++------ packages/angular-pacer/tsconfig.spec.json | 6 +- 70 files changed, 328 insertions(+), 328 deletions(-) diff --git a/docs/framework/angular/reference/functions/injectAsyncBatcher.md b/docs/framework/angular/reference/functions/injectAsyncBatcher.md index b9c5269a2..64184ebbc 100644 --- a/docs/framework/angular/reference/functions/injectAsyncBatcher.md +++ b/docs/framework/angular/reference/functions/injectAsyncBatcher.md @@ -9,7 +9,7 @@ title: injectAsyncBatcher function injectAsyncBatcher(fn, options?): AngularAsyncBatcher; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:98 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:98](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L98) Creates and manages an Angular AsyncBatcher in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncBatcher( selector): AngularAsyncBatcher; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:109 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:109](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L109) Creates an Angular AsyncBatcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md index f9b7b05f0..ef4b0b1f2 100644 --- a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md +++ b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md @@ -9,7 +9,7 @@ title: injectAsyncDebouncer function injectAsyncDebouncer(fn, options): AngularAsyncDebouncer; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:97 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L97) Creates and manages an Angular AsyncDebouncer in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncDebouncer( selector): AngularAsyncDebouncer; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:108 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L108) Creates an Angular AsyncDebouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuer.md b/docs/framework/angular/reference/functions/injectAsyncQueuer.md index c7ac7c979..7c32f62cf 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuer.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuer.md @@ -9,7 +9,7 @@ title: injectAsyncQueuer function injectAsyncQueuer(fn, options?): AngularAsyncQueuer; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:102 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:102](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L102) Creates and manages an Angular AsyncQueuer in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncQueuer( selector): AngularAsyncQueuer; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:113 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:113](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L113) Creates an Angular AsyncQueuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md index a5c28d2b7..84995b50d 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md @@ -9,7 +9,7 @@ title: injectAsyncQueuerItems function injectAsyncQueuerItems(fn, options?): AngularAsyncQueuerItems; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:51 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:51](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L51) Creates a readonly Angular signal of pending asynchronous queue items. @@ -68,7 +68,7 @@ function injectAsyncQueuerItems( selector): AngularAsyncQueuerItems; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:59 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L59) Creates the items signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md index 4ca7b3b75..69bfdcb24 100644 --- a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md @@ -9,7 +9,7 @@ title: injectAsyncRateLimiter function injectAsyncRateLimiter(fn, options): AngularAsyncRateLimiter; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:97 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L97) Creates and manages an Angular AsyncRateLimiter in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncRateLimiter( selector): AngularAsyncRateLimiter; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:108 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L108) Creates an Angular AsyncRateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncThrottler.md b/docs/framework/angular/reference/functions/injectAsyncThrottler.md index 845f668e7..173370c0c 100644 --- a/docs/framework/angular/reference/functions/injectAsyncThrottler.md +++ b/docs/framework/angular/reference/functions/injectAsyncThrottler.md @@ -9,7 +9,7 @@ title: injectAsyncThrottler function injectAsyncThrottler(fn, options): AngularAsyncThrottler; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:97 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L97) Creates and manages an Angular AsyncThrottler in the current injection context. @@ -76,7 +76,7 @@ function injectAsyncThrottler( selector): AngularAsyncThrottler; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:108 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L108) Creates an Angular AsyncThrottler with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectBatcher.md b/docs/framework/angular/reference/functions/injectBatcher.md index 693b7243d..6451e14b9 100644 --- a/docs/framework/angular/reference/functions/injectBatcher.md +++ b/docs/framework/angular/reference/functions/injectBatcher.md @@ -9,7 +9,7 @@ title: injectBatcher function injectBatcher(fn, options?): AngularBatcher; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:91 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:91](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L91) Creates and manages an Angular Batcher in the current injection context. @@ -76,7 +76,7 @@ function injectBatcher( selector): AngularBatcher; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:102 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:102](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L102) Creates an Angular Batcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectDebouncedComputed.md b/docs/framework/angular/reference/functions/injectDebouncedComputed.md index 0f6d4a129..92014f794 100644 --- a/docs/framework/angular/reference/functions/injectDebouncedComputed.md +++ b/docs/framework/angular/reference/functions/injectDebouncedComputed.md @@ -9,7 +9,7 @@ title: injectDebouncedComputed function injectDebouncedComputed(signalToDebounce, options): AngularDebouncerComputed; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:44 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L44) Creates an Angular debounced view of a source signal. @@ -64,7 +64,7 @@ function injectDebouncedComputed( selector): AngularDebouncerComputed; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:52 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L52) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectDebouncedSignal.md b/docs/framework/angular/reference/functions/injectDebouncedSignal.md index 9bedfbbf1..734033fe7 100644 --- a/docs/framework/angular/reference/functions/injectDebouncedSignal.md +++ b/docs/framework/angular/reference/functions/injectDebouncedSignal.md @@ -9,7 +9,7 @@ title: injectDebouncedSignal function injectDebouncedSignal(initialValue, options): AngularDebouncerSignal; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:49 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L49) Creates an Angular debounced editable signal. @@ -64,7 +64,7 @@ function injectDebouncedSignal( selector): AngularDebouncerSignal; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:59 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L59) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectDebouncer.md b/docs/framework/angular/reference/functions/injectDebouncer.md index 7ff0d5ed5..ade6afdd3 100644 --- a/docs/framework/angular/reference/functions/injectDebouncer.md +++ b/docs/framework/angular/reference/functions/injectDebouncer.md @@ -9,7 +9,7 @@ title: injectDebouncer function injectDebouncer(fn, options): AngularDebouncer; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:95 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:95](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L95) Creates and manages an Angular Debouncer in the current injection context. @@ -76,7 +76,7 @@ function injectDebouncer( selector): AngularDebouncer; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:106 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:106](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L106) Creates an Angular Debouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectPacerOptions.md b/docs/framework/angular/reference/functions/injectPacerOptions.md index 1861103a8..6dce9164d 100644 --- a/docs/framework/angular/reference/functions/injectPacerOptions.md +++ b/docs/framework/angular/reference/functions/injectPacerOptions.md @@ -7,7 +7,7 @@ title: injectPacerOptions function injectPacerOptions(): PacerProviderOptions; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:79 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:79](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L79) Reads the nearest Pacer options provider in the current injection context. Returns an empty object when no provider is configured. diff --git a/docs/framework/angular/reference/functions/injectQueuedComputed.md b/docs/framework/angular/reference/functions/injectQueuedComputed.md index 10291218d..bcfdce500 100644 --- a/docs/framework/angular/reference/functions/injectQueuedComputed.md +++ b/docs/framework/angular/reference/functions/injectQueuedComputed.md @@ -9,7 +9,7 @@ title: injectQueuedComputed function injectQueuedComputed(source, options?): AngularQueuerComputed; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:37 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:37](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L37) Creates an Angular queued view of a source accessor. @@ -62,7 +62,7 @@ function injectQueuedComputed( selector): AngularQueuerComputed; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:45 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L45) Creates the queued value signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectQueuedSignal.md b/docs/framework/angular/reference/functions/injectQueuedSignal.md index d936fa19b..eaa51258e 100644 --- a/docs/framework/angular/reference/functions/injectQueuedSignal.md +++ b/docs/framework/angular/reference/functions/injectQueuedSignal.md @@ -9,7 +9,7 @@ title: injectQueuedSignal function injectQueuedSignal(initialValue, options?): AngularQueuerSignal; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:42 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:42](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L42) Creates an Angular queued editable signal. @@ -62,7 +62,7 @@ function injectQueuedSignal( selector): AngularQueuerSignal; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:50 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:50](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L50) Creates the queued value signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectQueuer.md b/docs/framework/angular/reference/functions/injectQueuer.md index 5c9be3f4f..520c7b39f 100644 --- a/docs/framework/angular/reference/functions/injectQueuer.md +++ b/docs/framework/angular/reference/functions/injectQueuer.md @@ -9,7 +9,7 @@ title: injectQueuer function injectQueuer(fn, options?): AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:96 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:96](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L96) Creates and manages an Angular Queuer in the current injection context. @@ -76,7 +76,7 @@ function injectQueuer( selector): AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:107 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L107) Creates an Angular Queuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectQueuerItems.md b/docs/framework/angular/reference/functions/injectQueuerItems.md index 49af5f2df..fec1d38ec 100644 --- a/docs/framework/angular/reference/functions/injectQueuerItems.md +++ b/docs/framework/angular/reference/functions/injectQueuerItems.md @@ -9,7 +9,7 @@ title: injectQueuerItems function injectQueuerItems(fn, options?): AngularQueuerItems; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:48 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:48](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L48) Creates a readonly Angular signal of pending queue items. @@ -68,7 +68,7 @@ function injectQueuerItems( selector): AngularQueuerItems; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:56 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L56) Creates the items signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectRateLimitedComputed.md b/docs/framework/angular/reference/functions/injectRateLimitedComputed.md index 9e4e699dc..9729b1b3e 100644 --- a/docs/framework/angular/reference/functions/injectRateLimitedComputed.md +++ b/docs/framework/angular/reference/functions/injectRateLimitedComputed.md @@ -9,7 +9,7 @@ title: injectRateLimitedComputed function injectRateLimitedComputed(source, options): AngularRateLimiterComputed; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:44 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L44) Creates an Angular ratelimited view of a source signal. @@ -64,7 +64,7 @@ function injectRateLimitedComputed( selector): AngularRateLimiterComputed; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:52 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L52) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md index 920070d4d..f963a4dd0 100644 --- a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md +++ b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md @@ -9,7 +9,7 @@ title: injectRateLimitedSignal function injectRateLimitedSignal(initialValue, options): AngularRateLimiterSignal; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:49 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L49) Creates an Angular ratelimited editable signal. @@ -64,7 +64,7 @@ function injectRateLimitedSignal( selector): AngularRateLimiterSignal; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:59 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L59) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectRateLimiter.md b/docs/framework/angular/reference/functions/injectRateLimiter.md index eb17ddfab..37f73b699 100644 --- a/docs/framework/angular/reference/functions/injectRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectRateLimiter.md @@ -9,7 +9,7 @@ title: injectRateLimiter function injectRateLimiter(fn, options): AngularRateLimiter; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:94 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:94](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L94) Creates and manages an Angular RateLimiter in the current injection context. @@ -76,7 +76,7 @@ function injectRateLimiter( selector): AngularRateLimiter; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:105 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:105](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L105) Creates an Angular RateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectThrottledComputed.md b/docs/framework/angular/reference/functions/injectThrottledComputed.md index eba9a485e..9cbc067e1 100644 --- a/docs/framework/angular/reference/functions/injectThrottledComputed.md +++ b/docs/framework/angular/reference/functions/injectThrottledComputed.md @@ -9,7 +9,7 @@ title: injectThrottledComputed function injectThrottledComputed(source, options): AngularThrottlerComputed; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:44 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L44) Creates an Angular throttled view of a source signal. @@ -64,7 +64,7 @@ function injectThrottledComputed( selector): AngularThrottlerComputed; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:52 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L52) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectThrottledSignal.md b/docs/framework/angular/reference/functions/injectThrottledSignal.md index 09208e53d..8be1687c6 100644 --- a/docs/framework/angular/reference/functions/injectThrottledSignal.md +++ b/docs/framework/angular/reference/functions/injectThrottledSignal.md @@ -9,7 +9,7 @@ title: injectThrottledSignal function injectThrottledSignal(initialValue, options): AngularThrottlerSignal; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:49 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L49) Creates an Angular throttled editable signal. @@ -64,7 +64,7 @@ function injectThrottledSignal( selector): AngularThrottlerSignal; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:59 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L59) Creates the value signal with selected state on its attached utility ref. diff --git a/docs/framework/angular/reference/functions/injectThrottler.md b/docs/framework/angular/reference/functions/injectThrottler.md index 4350884b6..c4472bed8 100644 --- a/docs/framework/angular/reference/functions/injectThrottler.md +++ b/docs/framework/angular/reference/functions/injectThrottler.md @@ -9,7 +9,7 @@ title: injectThrottler function injectThrottler(fn, options): AngularThrottler; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:95 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:95](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L95) Creates and manages an Angular Throttler in the current injection context. @@ -76,7 +76,7 @@ function injectThrottler( selector): AngularThrottler; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:106 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:106](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L106) Creates an Angular Throttler with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/providePacerOptions.md b/docs/framework/angular/reference/functions/providePacerOptions.md index 5a6960f92..923d00d12 100644 --- a/docs/framework/angular/reference/functions/providePacerOptions.md +++ b/docs/framework/angular/reference/functions/providePacerOptions.md @@ -7,7 +7,7 @@ title: providePacerOptions function providePacerOptions(options): Provider; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:67 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:67](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L67) Provides scoped defaults for Angular Pacer utilities. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md index bfc5e7fd1..a87e03b2d 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcher title: AngularAsyncBatcher --- -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L54) An Angular AsyncBatcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:61 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L61) Reactive state that will be updated when the batcher state changes diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md index 5b0fd937a..8d3d4954e 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcherOptions title: AngularAsyncBatcherOptions --- -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:22 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L22) Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including optional onUnmount?: (batcher) => void; ``` -Defined in: packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:29 +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L29) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md index 9a571a4bc..d572c9965 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncer title: AngularAsyncDebouncer --- -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:55 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L55) An Angular AsyncDebouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:60 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L60) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md index 9e40abc51..7ceaed147 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncerOptions title: AngularAsyncDebouncerOptions --- -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:26 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L26) Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), includ optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:33 +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md index 5504f85fa..69d080552 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuer title: AngularAsyncQueuer --- -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:60 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L60) An Angular AsyncQueuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:65 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:65](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L65) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md index e37f81ba0..ba2b45bca 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuerItems title: AngularAsyncQueuerItems --- -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L16) A readonly signal of pending queue items, with the Angular queuer attached. @@ -26,7 +26,7 @@ A readonly signal of pending queue items, with the Angular queuer attached. AngularAsyncQueuerItems(): readonly TValue[]; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L16) A readonly signal of pending queue items, with the Angular queuer attached. @@ -58,7 +58,7 @@ Signal.[SIGNAL] addItem: (item, position, runOnItemsChange) => boolean; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:22 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L22) Shortcut for queuer.addItem, with the same arguments and return value. @@ -99,6 +99,6 @@ queuer.addItem('task2', 'front'); queuer: AngularAsyncQueuer; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:20 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L20) The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md index 4051e2e66..9aab94801 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuerOptions title: AngularAsyncQueuerOptions --- -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:22 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L22) Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including co optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:29 +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L29) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md index 7c0ff9a1f..3604eec05 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiter title: AngularAsyncRateLimiter --- -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L55) An Angular AsyncRateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:60 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L60) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md index f10266704..0374d6786 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiterOptions title: AngularAsyncRateLimiterOptions --- -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:26 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L26) Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), in optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:33 +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md index 4ce9f7c4c..51c21b7f5 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottler title: AngularAsyncThrottler --- -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:55 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L55) An Angular AsyncThrottler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:60 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L60) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md index 744016e0e..a973f6c62 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottlerOptions title: AngularAsyncThrottlerOptions --- -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:26 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L26) Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), includ optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:33 +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularBatcher.md b/docs/framework/angular/reference/interfaces/AngularBatcher.md index 191301c48..0ec9bb23f 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcher.md @@ -3,7 +3,7 @@ id: AngularBatcher title: AngularBatcher --- -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:49 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L49) An Angular Batcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:54 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L54) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md index fce85ce41..570da3e66 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularBatcherOptions title: AngularBatcherOptions --- -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:22 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L22) Options for [injectBatcher](../functions/injectBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectBatcher](../functions/injectBatcher.md), including core confi optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/batcher/injectBatcher.ts:27 +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L27) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches). diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncer.md b/docs/framework/angular/reference/interfaces/AngularDebouncer.md index d38e75e57..dfacefdb9 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncer.md @@ -3,7 +3,7 @@ id: AngularDebouncer title: AngularDebouncer --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:53 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L53) An Angular Debouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:58 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L58) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md b/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md index 8c2832770..9c9bea96c 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md @@ -3,7 +3,7 @@ id: AngularDebouncerComputed title: AngularDebouncerComputed --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -27,7 +27,7 @@ The `debouncer` attribute exposes the underlying Debouncer methods. AngularDebouncerComputed(): TValue; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -60,6 +60,6 @@ Signal.[SIGNAL] debouncer: AngularDebouncer<(value) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:20 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L20) The underlying Angular Debouncer ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md index 72db250d0..7a2089519 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularDebouncerOptions title: AngularDebouncerOptions --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:26 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L26) Options for [injectDebouncer](../functions/injectDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectDebouncer](../functions/injectDebouncer.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncer.ts:33 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md index 404d8c977..3470ff696 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md @@ -3,7 +3,7 @@ id: AngularDebouncerSignal title: AngularDebouncerSignal --- -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -27,7 +27,7 @@ The `debouncer` attribute exposes the underlying Debouncer methods. AngularDebouncerSignal(): TValue; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -60,7 +60,7 @@ Signal.[SIGNAL] debouncer: AngularDebouncer<(callback) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:25 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L25) The underlying Angular Debouncer ref for controlling execution. @@ -72,7 +72,7 @@ The underlying Angular Debouncer ref for controlling execution. set: (value) => void; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:21 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L21) Schedules the replacement value after the debounce delay. @@ -94,7 +94,7 @@ Schedules the replacement value after the debounce delay. update: (updateFn) => void; ``` -Defined in: packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:23 +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L23) Runs the updater with the current committed value when the write executes. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuer.md b/docs/framework/angular/reference/interfaces/AngularQueuer.md index bf91e17d3..402ab2840 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuer.md @@ -3,7 +3,7 @@ id: AngularQueuer title: AngularQueuer --- -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:54 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L54) An Angular Queuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:59 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L59) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md b/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md index 39948030c..2f2fc3ba7 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md @@ -3,7 +3,7 @@ id: AngularQueuerComputed title: AngularQueuerComputed --- -Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L9) A processed-value signal with its underlying queue controls. @@ -26,7 +26,7 @@ A processed-value signal with its underlying queue controls. AngularQueuerComputed(): TValue; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L9) A processed-value signal with its underlying queue controls. @@ -58,6 +58,6 @@ Signal.[SIGNAL] queuer: AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedComputed.ts:14 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L14) The underlying Angular Queuer ref and its selected state. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md index 876fb4d3c..3647d2e04 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerItems.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md @@ -3,7 +3,7 @@ id: AngularQueuerItems title: AngularQueuerItems --- -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:13 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L13) A readonly signal of pending queue items, with the Angular queuer attached. @@ -26,7 +26,7 @@ A readonly signal of pending queue items, with the Angular queuer attached. AngularQueuerItems(): readonly TValue[]; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:13 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L13) A readonly signal of pending queue items, with the Angular queuer attached. @@ -58,7 +58,7 @@ Signal.[SIGNAL] addItem: (item, position, runOnItemsChange) => boolean; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:19 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L19) Shortcut for queuer.addItem, with the same arguments and return value. @@ -100,6 +100,6 @@ queuer.addItem('task2', 'front'); queuer: AngularQueuer; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedItems.ts:17 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:17](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L17) The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md index 0fc56fb40..9d07f47fb 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularQueuerOptions title: AngularQueuerOptions --- -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:22 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L22) Options for [injectQueuer](../functions/injectQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectQueuer](../functions/injectQueuer.md), including core configu optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuer.ts:27 +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L27) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing). diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md index 0a5866163..11f159b08 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md @@ -3,7 +3,7 @@ id: AngularQueuerSignal title: AngularQueuerSignal --- -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L10) A processed-value signal with its underlying queue controls. @@ -26,7 +26,7 @@ A processed-value signal with its underlying queue controls. AngularQueuerSignal(): TValue; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L10) A processed-value signal with its underlying queue controls. @@ -58,7 +58,7 @@ Signal.[SIGNAL] queuer: AngularQueuer<() => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:19 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L19) The underlying Angular Queuer ref and its selected state. @@ -70,7 +70,7 @@ The underlying Angular Queuer ref and its selected state. set: (value) => void; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:15 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L15) Enqueues a replacement value. @@ -92,7 +92,7 @@ Enqueues a replacement value. update: (updater) => void; ``` -Defined in: packages/angular-pacer/src/queuer/injectQueuedSignal.ts:17 +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:17](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L17) Enqueues an updater evaluated against the committed value when processed. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md index 0fc4a04b8..c2ee56482 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md @@ -3,7 +3,7 @@ id: AngularRateLimiter title: AngularRateLimiter --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:52 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L52) An Angular RateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:57 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:57](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L57) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md index cae7167ca..f64aa2355 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md @@ -3,7 +3,7 @@ id: AngularRateLimiterComputed title: AngularRateLimiterComputed --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -27,7 +27,7 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. AngularRateLimiterComputed(): TValue; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -60,6 +60,6 @@ Signal.[SIGNAL] rateLimiter: AngularRateLimiter<(value) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:20 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L20) The underlying Angular RateLimiter ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md index 3ce0606d2..5b0e864de 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularRateLimiterOptions title: AngularRateLimiterOptions --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:25 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L25) Options for [injectRateLimiter](../functions/injectRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectRateLimiter](../functions/injectRateLimiter.md), including co optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:32 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L32) Called when the owning injection context is destroyed. Receives the core instance. There is no default cleanup; use this callback for custom teardown. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md index dd4bd3060..40167c909 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md @@ -3,7 +3,7 @@ id: AngularRateLimiterSignal title: AngularRateLimiterSignal --- -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -27,7 +27,7 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. AngularRateLimiterSignal(): TValue; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -60,7 +60,7 @@ Signal.[SIGNAL] rateLimiter: AngularRateLimiter<(callback) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:25 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L25) The underlying Angular RateLimiter ref for controlling execution. @@ -72,7 +72,7 @@ The underlying Angular RateLimiter ref for controlling execution. set: (value) => void; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:21 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L21) Replaces the value immediately if the rate limit permits; rejected writes are discarded. @@ -94,7 +94,7 @@ Replaces the value immediately if the rate limit permits; rejected writes are di update: (updateFn) => void; ``` -Defined in: packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:23 +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L23) Runs the updater with the current committed value when the write executes. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottler.md b/docs/framework/angular/reference/interfaces/AngularThrottler.md index 81512a490..9f9f441b1 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottler.md @@ -3,7 +3,7 @@ id: AngularThrottler title: AngularThrottler --- -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:53 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L53) An Angular Throttler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:58 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L58) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md b/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md index 107256367..d47e19306 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md @@ -3,7 +3,7 @@ id: AngularThrottlerComputed title: AngularThrottlerComputed --- -Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `throttler` attribute exposes the underlying Throttler methods. @@ -27,7 +27,7 @@ The `throttler` attribute exposes the underlying Throttler methods. AngularThrottlerComputed(): TValue; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L15) A readonly Angular value signal with its underlying utility ref. The `throttler` attribute exposes the underlying Throttler methods. @@ -60,6 +60,6 @@ Signal.[SIGNAL] throttler: AngularThrottler<(value) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledComputed.ts:20 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L20) The underlying Angular Throttler ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md index e95735ea4..83bff4f25 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularThrottlerOptions title: AngularThrottlerOptions --- -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:26 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L26) Options for [injectThrottler](../functions/injectThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectThrottler](../functions/injectThrottler.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottler.ts:33 +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md index 406965f43..173193632 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md @@ -3,7 +3,7 @@ id: AngularThrottlerSignal title: AngularThrottlerSignal --- -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `throttler` attribute exposes the underlying Throttler methods. @@ -27,7 +27,7 @@ The `throttler` attribute exposes the underlying Throttler methods. AngularThrottlerSignal(): TValue; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L16) A readonly Angular value signal with paced `set`/`update` methods. The `throttler` attribute exposes the underlying Throttler methods. @@ -60,7 +60,7 @@ Signal.[SIGNAL] set: (value) => void; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:21 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L21) Schedules the replacement value according to leading and trailing throttling options. @@ -82,7 +82,7 @@ Schedules the replacement value according to leading and trailing throttling opt throttler: AngularThrottler<(callback) => void, TSelected>; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:25 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L25) The underlying Angular Throttler ref for controlling execution. @@ -94,7 +94,7 @@ The underlying Angular Throttler ref for controlling execution. update: (updateFn) => void; ``` -Defined in: packages/angular-pacer/src/throttler/injectThrottledSignal.ts:23 +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L23) Runs the updater with the current committed value when the write executes. diff --git a/docs/framework/angular/reference/interfaces/PacerProviderOptions.md b/docs/framework/angular/reference/interfaces/PacerProviderOptions.md index 2b4d7af86..ab9f63fef 100644 --- a/docs/framework/angular/reference/interfaces/PacerProviderOptions.md +++ b/docs/framework/angular/reference/interfaces/PacerProviderOptions.md @@ -3,7 +3,7 @@ id: PacerProviderOptions title: PacerProviderOptions --- -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:18 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:18](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L18) Scoped default options for synchronous and asynchronous Pacer adapters. Local utility options override these defaults. @@ -16,7 +16,7 @@ Local utility options override these defaults. optional asyncBatcher?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:20 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L20) Default options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md). @@ -28,7 +28,7 @@ Default options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md). optional asyncDebouncer?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:22 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L22) Default options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md). @@ -40,7 +40,7 @@ Default options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md) optional asyncQueuer?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:24 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:24](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L24) Default options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md). @@ -52,7 +52,7 @@ Default options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md). optional asyncRateLimiter?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:26 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L26) Default options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md). @@ -64,7 +64,7 @@ Default options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter optional asyncThrottler?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:28 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:28](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L28) Default options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md). @@ -76,7 +76,7 @@ Default options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md) optional batcher?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:30 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L30) Default options for [injectBatcher](../functions/injectBatcher.md). @@ -88,7 +88,7 @@ Default options for [injectBatcher](../functions/injectBatcher.md). optional debouncer?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:32 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L32) Default options for [injectDebouncer](../functions/injectDebouncer.md). @@ -100,7 +100,7 @@ Default options for [injectDebouncer](../functions/injectDebouncer.md). optional queuer?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:34 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L34) Default options for [injectQueuer](../functions/injectQueuer.md). @@ -112,7 +112,7 @@ Default options for [injectQueuer](../functions/injectQueuer.md). optional rateLimiter?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:36 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:36](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L36) Default options for [injectRateLimiter](../functions/injectRateLimiter.md). @@ -124,6 +124,6 @@ Default options for [injectRateLimiter](../functions/injectRateLimiter.md). optional throttler?: Partial>; ``` -Defined in: packages/angular-pacer/src/provider/providePacerOptions.ts:38 +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:38](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L38) Default options for [injectThrottler](../functions/injectThrottler.md). diff --git a/examples/angular/injectAsyncQueuerItems/src/app/app.html b/examples/angular/injectAsyncQueuerItems/src/app/app.html index 67c16b2e4..4c6aff38e 100644 --- a/examples/angular/injectAsyncQueuerItems/src/app/app.html +++ b/examples/angular/injectAsyncQueuerItems/src/app/app.html @@ -12,7 +12,7 @@

TanStack Pacer injectAsyncQueuerItems Example

Queue Items

    @for (item of queue(); track $index) { -
  1. {{ item }}
  2. +
  3. {{ item }}
  4. }
diff --git a/examples/angular/injectQueuedComputed/eslint.config.js b/examples/angular/injectQueuedComputed/eslint.config.js index 66d55766f..4d9c151cf 100644 --- a/examples/angular/injectQueuedComputed/eslint.config.js +++ b/examples/angular/injectQueuedComputed/eslint.config.js @@ -1,6 +1,6 @@ // @ts-check -import rootConfig from '../../../eslint.config.js'; +import rootConfig from '../../../eslint.config.js' /** @type {import('eslint').Linter.Config[]} */ export default [ @@ -12,4 +12,4 @@ export default [ }, }, }, -]; +] diff --git a/examples/angular/injectQueuedComputed/src/app/app.config.ts b/examples/angular/injectQueuedComputed/src/app/app.config.ts index 7ca8b23fc..253f23d1a 100644 --- a/examples/angular/injectQueuedComputed/src/app/app.config.ts +++ b/examples/angular/injectQueuedComputed/src/app/app.config.ts @@ -1,10 +1,10 @@ -import { isDevMode } from '@angular/core'; -import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider'; -import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools'; -import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { isDevMode } from '@angular/core' +import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider' +import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools' +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core' +import { provideRouter } from '@angular/router' -import { routes } from './app.routes'; +import { routes } from './app.routes' export const appConfig: ApplicationConfig = { providers: [ @@ -12,4 +12,4 @@ export const appConfig: ApplicationConfig = { provideBrowserGlobalErrorListeners(), provideRouter(routes), ], -}; +} diff --git a/examples/angular/injectQueuedComputed/src/app/app.routes.ts b/examples/angular/injectQueuedComputed/src/app/app.routes.ts index 25b8adc91..9a884b113 100644 --- a/examples/angular/injectQueuedComputed/src/app/app.routes.ts +++ b/examples/angular/injectQueuedComputed/src/app/app.routes.ts @@ -1,3 +1,3 @@ -import type { Routes } from '@angular/router'; +import type { Routes } from '@angular/router' -export const routes: Routes = []; +export const routes: Routes = [] diff --git a/examples/angular/injectQueuedComputed/src/app/app.spec.ts b/examples/angular/injectQueuedComputed/src/app/app.spec.ts index 3419171b2..d99f182ea 100644 --- a/examples/angular/injectQueuedComputed/src/app/app.spec.ts +++ b/examples/angular/injectQueuedComputed/src/app/app.spec.ts @@ -1,55 +1,55 @@ -import { vi } from 'vitest'; -import { TestBed } from '@angular/core/testing'; -import { App } from './app'; +import { vi } from 'vitest' +import { TestBed } from '@angular/core/testing' +import { App } from './app' describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], - }).compileComponents(); - }); + }).compileComponents() + }) it('should create the app', () => { - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + expect(app).toBeTruthy() + }) it('should render title', async () => { - const fixture = TestBed.createComponent(App); - await fixture.whenStable(); - const compiled = fixture.nativeElement as HTMLElement; + const fixture = TestBed.createComponent(App) + await fixture.whenStable() + const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( 'TanStack Pacer injectQueuedComputed Example', - ); - }); -}); + ) + }) +}) // Exercise the real adapter and component with a deterministic clock. describe('example behavior', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ imports: [App] }).compileComponents(); - }); + await TestBed.configureTestingModule({ imports: [App] }).compileComponents() + }) afterEach(() => { - TestBed.resetTestingModule(); - vi.clearAllTimers(); - vi.useRealTimers(); - vi.restoreAllMocks(); - }); + TestBed.resetTestingModule() + vi.clearAllTimers() + vi.useRealTimers() + vi.restoreAllMocks() + }) it('publishes a processed scalar and keeps pending values in queue state', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - fixture.detectChanges(); - TestBed.tick(); - await vi.advanceTimersByTimeAsync(1000); - app.queuer.stop(); - app.source.set('angular'); - TestBed.tick(); - expect(app.queued()).toBe(''); - expect(app.queuer.state().items).toEqual(['angular']); - app.queuer.execute(); - expect(app.queued()).toBe('angular'); - expect(app.queuer.state().items).toEqual([]); - }); -}); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + fixture.detectChanges() + TestBed.tick() + await vi.advanceTimersByTimeAsync(1000) + app.queuer.stop() + app.source.set('angular') + TestBed.tick() + expect(app.queued()).toBe('') + expect(app.queuer.state().items).toEqual(['angular']) + app.queuer.execute() + expect(app.queued()).toBe('angular') + expect(app.queuer.state().items).toEqual([]) + }) +}) diff --git a/examples/angular/injectQueuedComputed/src/app/app.ts b/examples/angular/injectQueuedComputed/src/app/app.ts index 1497570eb..b700f022f 100644 --- a/examples/angular/injectQueuedComputed/src/app/app.ts +++ b/examples/angular/injectQueuedComputed/src/app/app.ts @@ -1,7 +1,7 @@ -import { Component, signal } from '@angular/core'; -import { JsonPipe } from '@angular/common'; -import { injectQueuedComputed } from '@tanstack/angular-pacer'; -import { InputApp } from './inputapp'; +import { Component, signal } from '@angular/core' +import { JsonPipe } from '@angular/common' +import { injectQueuedComputed } from '@tanstack/angular-pacer' +import { InputApp } from './inputapp' @Component({ selector: 'app-root', @@ -9,22 +9,22 @@ import { InputApp } from './inputapp'; imports: [JsonPipe, InputApp], }) export class App { - readonly source = signal(''); - readonly currentValue = signal(50); - readonly instantExecutions = signal(1); - readonly queued = injectQueuedComputed(this.source, { maxSize: 25, wait: 500 }, (state) => state); - readonly queuer = this.queued.queuer; + readonly source = signal('') + readonly currentValue = signal(50) + readonly instantExecutions = signal(1) + readonly queued = injectQueuedComputed(this.source, { maxSize: 25, wait: 500 }, (state) => state) + readonly queuer = this.queued.queuer readonly rangeQueued = injectQueuedComputed( this.currentValue, { maxSize: 100, wait: 100 }, (state) => state, - ); - readonly rangeQueue = this.rangeQueued.queuer; + ) + readonly rangeQueue = this.rangeQueued.queuer onSearch(value: string): void { - this.source.set(value); + this.source.set(value) } onRange(value: number): void { - this.currentValue.set(value); - this.instantExecutions.update((count) => count + 1); + this.currentValue.set(value) + this.instantExecutions.update((count) => count + 1) } } diff --git a/examples/angular/injectQueuedComputed/src/app/inputapp.ts b/examples/angular/injectQueuedComputed/src/app/inputapp.ts index ffc866402..812358620 100644 --- a/examples/angular/injectQueuedComputed/src/app/inputapp.ts +++ b/examples/angular/injectQueuedComputed/src/app/inputapp.ts @@ -1,5 +1,5 @@ -import { Component, input, signal } from '@angular/core'; -import { injectQueuerItems } from '@tanstack/angular-pacer'; +import { Component, input, signal } from '@angular/core' +import { injectQueuerItems } from '@tanstack/angular-pacer' @Component({ selector: 'app-input', @@ -13,10 +13,10 @@ import { injectQueuerItems } from '@tanstack/angular-pacer'; `, }) export class InputApp { - readonly value = input.required(); - readonly processed = signal(''); - readonly queued = injectQueuerItems((value: string) => this.processed.set(value), { wait: 500 }); + readonly value = input.required() + readonly processed = signal('') + readonly queued = injectQueuerItems((value: string) => this.processed.set(value), { wait: 500 }) enqueueRandom(): void { - this.queued.queuer.addItem(Math.random().toFixed(4)); + this.queued.queuer.addItem(Math.random().toFixed(4)) } } diff --git a/examples/angular/injectQueuedComputed/src/main.ts b/examples/angular/injectQueuedComputed/src/main.ts index 190f3418d..8192dca69 100644 --- a/examples/angular/injectQueuedComputed/src/main.ts +++ b/examples/angular/injectQueuedComputed/src/main.ts @@ -1,5 +1,5 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { App } from './app/app'; +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' -bootstrapApplication(App, appConfig).catch((err) => console.error(err)); +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts b/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts index 1371c514e..0a16dfdaf 100644 --- a/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectQueuedComputed/tests/e2e/smoke.spec.mts @@ -1,24 +1,24 @@ -import { expect, test } from '../../../../../tests/e2e/helpers/fixtures'; +import { expect, test } from '../../../../../tests/e2e/helpers/fixtures' test.beforeEach(async ({ page, exampleUrl }) => { - await page.clock.install(); - await page.goto(exampleUrl); + await page.clock.install() + await page.goto(exampleUrl) await expect( page.getByRole('heading', { name: 'TanStack Pacer injectQueuedComputed Example', exact: true, }), - ).toBeVisible(); + ).toBeVisible() // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)); -}); + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) +}) test('renders queued search and range values', async ({ page }) => { await expect( page.getByRole('heading', { name: 'Queued search value', exact: true }), - ).toBeVisible(); - await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled(); -}); + ).toBeVisible() + await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled() +}) test('retains pending search values while stopped and executes them in order', async ({ page }) => { const queue = page.locator('section').filter({ @@ -26,20 +26,20 @@ test('retains pending search values while stopped and executes them in order', a name: 'Queued search value', exact: true, }), - }); - await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click(); - await page.clock.runFor(32); - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); - await queue.getByRole('searchbox').fill('first'); - await page.clock.runFor(32); - await queue.getByRole('searchbox').fill('second'); - await page.clock.runFor(32); - await queue.getByRole('button', { name: 'Start Processing', exact: true }).click(); - await page.clock.runFor(32); - await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible(); - await expect(queue.getByText('Queue Items: second', { exact: true })).toBeVisible(); - await page.clock.runFor(550); - await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible(); - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); - await expect(queue.getByText('Queue Items:', { exact: true })).toBeVisible(); -}); + }) + await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() + await queue.getByRole('searchbox').fill('first') + await page.clock.runFor(32) + await queue.getByRole('searchbox').fill('second') + await page.clock.runFor(32) + await queue.getByRole('button', { name: 'Start Processing', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible() + await expect(queue.getByText('Queue Items: second', { exact: true })).toBeVisible() + await page.clock.runFor(550) + await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible() + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() + await expect(queue.getByText('Queue Items:', { exact: true })).toBeVisible() +}) diff --git a/examples/angular/injectQueuedSignal/eslint.config.js b/examples/angular/injectQueuedSignal/eslint.config.js index 66d55766f..4d9c151cf 100644 --- a/examples/angular/injectQueuedSignal/eslint.config.js +++ b/examples/angular/injectQueuedSignal/eslint.config.js @@ -1,6 +1,6 @@ // @ts-check -import rootConfig from '../../../eslint.config.js'; +import rootConfig from '../../../eslint.config.js' /** @type {import('eslint').Linter.Config[]} */ export default [ @@ -12,4 +12,4 @@ export default [ }, }, }, -]; +] diff --git a/examples/angular/injectQueuedSignal/src/app/app.config.ts b/examples/angular/injectQueuedSignal/src/app/app.config.ts index 7ca8b23fc..253f23d1a 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.config.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.config.ts @@ -1,10 +1,10 @@ -import { isDevMode } from '@angular/core'; -import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider'; -import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools'; -import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { isDevMode } from '@angular/core' +import { provideTanStackDevtools } from '@tanstack/angular-devtools/provider' +import { pacerDevtoolsPlugin } from '@tanstack/angular-pacer-devtools' +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core' +import { provideRouter } from '@angular/router' -import { routes } from './app.routes'; +import { routes } from './app.routes' export const appConfig: ApplicationConfig = { providers: [ @@ -12,4 +12,4 @@ export const appConfig: ApplicationConfig = { provideBrowserGlobalErrorListeners(), provideRouter(routes), ], -}; +} diff --git a/examples/angular/injectQueuedSignal/src/app/app.routes.ts b/examples/angular/injectQueuedSignal/src/app/app.routes.ts index 25b8adc91..9a884b113 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.routes.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.routes.ts @@ -1,3 +1,3 @@ -import type { Routes } from '@angular/router'; +import type { Routes } from '@angular/router' -export const routes: Routes = []; +export const routes: Routes = [] diff --git a/examples/angular/injectQueuedSignal/src/app/app.spec.ts b/examples/angular/injectQueuedSignal/src/app/app.spec.ts index 0492a52ce..bcb7cfa74 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.spec.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.spec.ts @@ -1,55 +1,55 @@ -import { vi } from 'vitest'; -import { TestBed } from '@angular/core/testing'; -import { App } from './app'; +import { vi } from 'vitest' +import { TestBed } from '@angular/core/testing' +import { App } from './app' describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], - }).compileComponents(); - }); + }).compileComponents() + }) it('should create the app', () => { - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + expect(app).toBeTruthy() + }) it('should render title', async () => { - const fixture = TestBed.createComponent(App); - await fixture.whenStable(); - const compiled = fixture.nativeElement as HTMLElement; + const fixture = TestBed.createComponent(App) + await fixture.whenStable() + const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( 'TanStack Pacer injectQueuedSignal Example', - ); - }); -}); + ) + }) +}) // Exercise the real adapter and component with a deterministic clock. describe('example behavior', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ imports: [App] }).compileComponents(); - }); + await TestBed.configureTestingModule({ imports: [App] }).compileComponents() + }) afterEach(() => { - TestBed.resetTestingModule(); - vi.clearAllTimers(); - vi.useRealTimers(); - vi.restoreAllMocks(); - }); + TestBed.resetTestingModule() + vi.clearAllTimers() + vi.useRealTimers() + vi.restoreAllMocks() + }) it('publishes a processed scalar and keeps pending values in queue state', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - fixture.detectChanges(); - TestBed.tick(); - await vi.advanceTimersByTimeAsync(1000); - app.queuer.stop(); - app.onSearch('angular'); - TestBed.tick(); - expect(app.queued()).toBe(''); - expect(app.queuer.state().size).toBe(1); - app.queuer.execute(); - expect(app.queued()).toBe('angular'); - expect(app.queuer.state().size).toBe(0); - }); -}); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) + const fixture = TestBed.createComponent(App) + const app = fixture.componentInstance + fixture.detectChanges() + TestBed.tick() + await vi.advanceTimersByTimeAsync(1000) + app.queuer.stop() + app.onSearch('angular') + TestBed.tick() + expect(app.queued()).toBe('') + expect(app.queuer.state().size).toBe(1) + app.queuer.execute() + expect(app.queued()).toBe('angular') + expect(app.queuer.state().size).toBe(0) + }) +}) diff --git a/examples/angular/injectQueuedSignal/src/app/app.ts b/examples/angular/injectQueuedSignal/src/app/app.ts index 232802495..05fb4baa9 100644 --- a/examples/angular/injectQueuedSignal/src/app/app.ts +++ b/examples/angular/injectQueuedSignal/src/app/app.ts @@ -1,25 +1,25 @@ -import { Component, signal } from '@angular/core'; -import { injectQueuedSignal } from '@tanstack/angular-pacer'; +import { Component, signal } from '@angular/core' +import { injectQueuedSignal } from '@tanstack/angular-pacer' @Component({ selector: 'app-root', templateUrl: './app.html', }) export class App { - readonly source = signal(''); - readonly currentValue = signal(50); - readonly instantExecutions = signal(0); - readonly queued = injectQueuedSignal('', { maxSize: 25, wait: 500 }, (state) => state); - readonly queuer = this.queued.queuer; - readonly rangeQueued = injectQueuedSignal(50, { maxSize: 100, wait: 100 }, (state) => state); - readonly rangeQueue = this.rangeQueued.queuer; + readonly source = signal('') + readonly currentValue = signal(50) + readonly instantExecutions = signal(0) + readonly queued = injectQueuedSignal('', { maxSize: 25, wait: 500 }, (state) => state) + readonly queuer = this.queued.queuer + readonly rangeQueued = injectQueuedSignal(50, { maxSize: 100, wait: 100 }, (state) => state) + readonly rangeQueue = this.rangeQueued.queuer onSearch(value: string): void { - this.source.set(value); - this.queued.set(value); + this.source.set(value) + this.queued.set(value) } onRange(value: number): void { - this.currentValue.set(value); - this.rangeQueued.set(value); - this.instantExecutions.update((count) => count + 1); + this.currentValue.set(value) + this.rangeQueued.set(value) + this.instantExecutions.update((count) => count + 1) } } diff --git a/examples/angular/injectQueuedSignal/src/main.ts b/examples/angular/injectQueuedSignal/src/main.ts index 190f3418d..8192dca69 100644 --- a/examples/angular/injectQueuedSignal/src/main.ts +++ b/examples/angular/injectQueuedSignal/src/main.ts @@ -1,5 +1,5 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { App } from './app/app'; +import { bootstrapApplication } from '@angular/platform-browser' +import { appConfig } from './app/app.config' +import { App } from './app/app' -bootstrapApplication(App, appConfig).catch((err) => console.error(err)); +bootstrapApplication(App, appConfig).catch((err) => console.error(err)) diff --git a/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts b/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts index 3a500fe69..000ba49d3 100644 --- a/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectQueuedSignal/tests/e2e/smoke.spec.mts @@ -1,24 +1,24 @@ -import { expect, test } from '../../../../../tests/e2e/helpers/fixtures'; +import { expect, test } from '../../../../../tests/e2e/helpers/fixtures' test.beforeEach(async ({ page, exampleUrl }) => { - await page.clock.install(); - await page.goto(exampleUrl); + await page.clock.install() + await page.goto(exampleUrl) await expect( page.getByRole('heading', { name: 'TanStack Pacer injectQueuedSignal Example', exact: true, }), - ).toBeVisible(); + ).toBeVisible() // Let Angular bootstrap before pausing timers; runFor also advances render frames. - await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)); -}); + await page.clock.pauseAt(await page.evaluate(() => Date.now() + 100)) +}) test('renders queued search and range values', async ({ page }) => { await expect( page.getByRole('heading', { name: 'Queued search value', exact: true }), - ).toBeVisible(); - await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled(); -}); + ).toBeVisible() + await expect(page.getByRole('slider', { name: 'Queued Range', exact: true })).toBeDisabled() +}) test('retains pending search values while stopped and executes them in order', async ({ page }) => { const queue = page.locator('section').filter({ @@ -26,19 +26,19 @@ test('retains pending search values while stopped and executes them in order', a name: 'Queued search value', exact: true, }), - }); - await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click(); - await page.clock.runFor(32); - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); - await queue.getByRole('searchbox').fill('first'); - await page.clock.runFor(32); - await queue.getByRole('searchbox').fill('second'); - await page.clock.runFor(32); - await queue.getByRole('button', { name: 'Start Processing', exact: true }).click(); - await page.clock.runFor(32); - await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible(); - await expect(queue.getByText('Queue Size: 1', { exact: true })).toBeVisible(); - await page.clock.runFor(550); - await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible(); - await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible(); -}); + }) + await queue.getByRole('button', { name: 'Stop Processing', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() + await queue.getByRole('searchbox').fill('first') + await page.clock.runFor(32) + await queue.getByRole('searchbox').fill('second') + await page.clock.runFor(32) + await queue.getByRole('button', { name: 'Start Processing', exact: true }).click() + await page.clock.runFor(32) + await expect(queue.getByText('Current Value: first', { exact: true })).toBeVisible() + await expect(queue.getByText('Queue Size: 1', { exact: true })).toBeVisible() + await page.clock.runFor(550) + await expect(queue.getByText('Current Value: second', { exact: true })).toBeVisible() + await expect(queue.getByText('Queue Size: 0', { exact: true })).toBeVisible() +}) diff --git a/packages/angular-pacer/tsconfig.spec.json b/packages/angular-pacer/tsconfig.spec.json index 1f3f4c754..f173ea932 100644 --- a/packages/angular-pacer/tsconfig.spec.json +++ b/packages/angular-pacer/tsconfig.spec.json @@ -5,13 +5,13 @@ "types": ["vitest/globals", "node"], "noEmit": false, "declaration": false, - "useDefineForClassFields": false, + "useDefineForClassFields": false }, "angularCompilerOptions": { "strictTemplates": true, "strictInjectionParameters": true, - "strictInputAccessModifiers": true, + "strictInputAccessModifiers": true }, "include": ["tests/**/*.ts", "test-setup.ts", "src/**/*.ts"], - "exclude": [], + "exclude": [] } From c4f9936f361224e5c290c417918fbc82aceafe96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:03 -0300 Subject: [PATCH 10/18] fix(angular-pacer): skip initial processing for reflected values Skip processing when the initial reflected value is already visible. Preserve a source change read before the first effect, and avoid initial pending work, throttle windows, and rate-limit capacity consumption. Validation: intermediate package type checks, lint, and 166 Angular tests passed. --- .changeset/angular-adapter-rework.md | 2 ++ .../src/debouncer/injectDebouncedComputed.ts | 13 ++++++-- .../rate-limiter/injectRateLimitedComputed.ts | 15 ++++++--- .../src/throttler/injectThrottledComputed.ts | 15 ++++++--- .../debouncer/injectDebouncedComputed.test.ts | 31 ++++++++++++++++++- .../injectRateLimitedComputed.test.ts | 3 +- .../throttler/injectThrottledComputed.test.ts | 18 +++++------ 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index ad16e8fa6..f379fd054 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -9,3 +9,5 @@ per-utility Angular behavior tests. Support Angular 20 and up, including all Angular LTS versions, using stable public signal, effect, and pending-task APIs. + +Avoid processing an unchanged initial source value in debounced, throttled, and rate-limited reflected helpers, preserving stability and capacity for real changes. diff --git a/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts b/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts index 62bd33790..df32ca247 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts @@ -1,4 +1,4 @@ -import { effect, linkedSignal, untracked } from '@angular/core' +import { computed, effect, linkedSignal, untracked } from '@angular/core' import { injectDebouncer } from './injectDebouncer' import type { DebouncerState } from '@tanstack/pacer/debouncer' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -61,14 +61,21 @@ export function injectDebouncedComputed( ): AngularDebouncerComputed { const select = (state: DebouncerState<(value: TValue) => void>) => selector ? selector(state) : {} - const debouncedSignal = linkedSignal(() => untracked(signalToDebounce)) + const sourceValue = computed(signalToDebounce) + const debouncedSignal = linkedSignal(() => untracked(sourceValue)) const debouncer = injectDebouncer( (value: TValue) => debouncedSignal.set(value), options, select, ) + let initialized = false effect(() => { - debouncer.maybeExecute(signalToDebounce()) + const value = sourceValue() + if (!initialized) { + initialized = true + if (Object.is(untracked(debouncedSignal), value)) return + } + debouncer.maybeExecute(value) }) return Object.assign(debouncedSignal.asReadonly(), { debouncer, diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts index 98342117a..369f51ec9 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts @@ -1,4 +1,4 @@ -import { effect, linkedSignal, untracked } from '@angular/core' +import { computed, effect, linkedSignal, untracked } from '@angular/core' import { injectRateLimiter } from './injectRateLimiter' import type { RateLimiterState } from '@tanstack/pacer/rate-limiter' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -22,7 +22,7 @@ export interface AngularRateLimiterComputed< /** * Creates an Angular ratelimited view of a source signal. * - * The initial source value is available on first read. An effect passes source values to the rate limiter, including the initial value. Excess updates are discarded; the end of a window does not replay rejected values. + * The initial source value is available on first read. The unchanged initial value does not consume capacity. An effect passes subsequent source changes to the rate limiter. Excess updates are discarded; the end of a window does not replay rejected values. * * The returned value is a real Angular signal with the underlying utility exposed * on `rateLimiter`. Options accept a static object or reactive factory and follow @@ -60,14 +60,21 @@ export function injectRateLimitedComputed( selector?: (state: RateLimiterState) => TSelected, ): AngularRateLimiterComputed { const select = (state: RateLimiterState) => (selector ? selector(state) : {}) - const rateLimitedSignal = linkedSignal(() => untracked(source)) + const sourceValue = computed(source) + const rateLimitedSignal = linkedSignal(() => untracked(sourceValue)) const rateLimiter = injectRateLimiter( (value: TValue) => rateLimitedSignal.set(value), options, select, ) + let initialized = false effect(() => { - rateLimiter.maybeExecute(source()) + const value = sourceValue() + if (!initialized) { + initialized = true + if (Object.is(untracked(rateLimitedSignal), value)) return + } + rateLimiter.maybeExecute(value) }) return Object.assign(rateLimitedSignal.asReadonly(), { rateLimiter, diff --git a/packages/angular-pacer/src/throttler/injectThrottledComputed.ts b/packages/angular-pacer/src/throttler/injectThrottledComputed.ts index 1a958c139..751474995 100644 --- a/packages/angular-pacer/src/throttler/injectThrottledComputed.ts +++ b/packages/angular-pacer/src/throttler/injectThrottledComputed.ts @@ -1,4 +1,4 @@ -import { effect, linkedSignal, untracked } from '@angular/core' +import { computed, effect, linkedSignal, untracked } from '@angular/core' import { injectThrottler } from './injectThrottler' import type { ThrottlerState } from '@tanstack/pacer/throttler' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -22,7 +22,7 @@ export interface AngularThrottlerComputed< /** * Creates an Angular throttled view of a source signal. * - * The initial source value is available on first read. An effect passes source values to the throttler, including the initial value. Later changes replace the pending trailing value without restarting the interval. + * The initial source value is available on first read. The unchanged initial value does not enter the throttler. An effect passes subsequent source changes to the throttler; later changes replace the pending trailing value without restarting the interval. * * The returned value is a real Angular signal with the underlying utility exposed * on `throttler`. Options accept a static object or reactive factory and follow @@ -61,14 +61,21 @@ export function injectThrottledComputed( ): AngularThrottlerComputed { const select = (state: ThrottlerState<(value: TValue) => void>) => selector ? selector(state) : {} - const throttledSignal = linkedSignal(() => untracked(source)) + const sourceValue = computed(source) + const throttledSignal = linkedSignal(() => untracked(sourceValue)) const throttler = injectThrottler( (value: TValue) => throttledSignal.set(value), options, select, ) + let initialized = false effect(() => { - throttler.maybeExecute(source()) + const value = sourceValue() + if (!initialized) { + initialized = true + if (Object.is(untracked(throttledSignal), value)) return + } + throttler.maybeExecute(value) }) return Object.assign(throttledSignal.asReadonly(), { throttler, diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts index 0eacdf67f..b6aa2b31b 100644 --- a/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts +++ b/packages/angular-pacer/tests/debouncer/injectDebouncedComputed.test.ts @@ -1,4 +1,4 @@ -import { isSignal, signal } from '@angular/core' +import { ApplicationRef, isSignal, signal } from '@angular/core' import { TestBed } from '@angular/core/testing' import { expect, expectTypeOf, it, vi } from 'vitest' import { injectDebouncedComputed } from '../../src/debouncer/injectDebouncedComputed' @@ -65,3 +65,32 @@ it('passes selected state through to the attached utility', () => { expect(value()).toBe('updated') expect(value.debouncer.state().count).toBe(before + 1) }) + +it('does not hold stability for an unchanged initial value', async () => { + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectDebouncedComputed( + () => ({ message: source() }), + { wait: 1000 }, + (state) => state.isPending, + ), + ) + TestBed.tick() + expect(value()).toEqual({ message: 'initial' }) + expect(value.debouncer.state()).toBe(false) + await TestBed.inject(ApplicationRef).whenStable() +}) + +it('paces a source change observed before the first effect', () => { + vi.useFakeTimers() + const source = signal('initial') + const value = TestBed.runInInjectionContext(() => + injectDebouncedComputed(source, { wait: 100 }), + ) + expect(value()).toBe('initial') + source.set('updated') + TestBed.tick() + expect(value()).toBe('initial') + vi.advanceTimersByTime(100) + expect(value()).toBe('updated') +}) diff --git a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts index b49bd2b74..fbbcc69e2 100644 --- a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts +++ b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedComputed.test.ts @@ -13,7 +13,6 @@ it('returns an Angular signal with a callable rateLimiter attribute', () => { expect(value()).toBe('initial') expect(value).toHaveProperty('rateLimiter') TestBed.tick() - value.rateLimiter.reset() source.set('updated') TestBed.tick() expect(value()).toBe('updated') @@ -23,7 +22,7 @@ it('limits source updates and discards rejected values instead of replaying them vi.useFakeTimers() const source = signal('initial') const value = TestBed.runInInjectionContext(() => - injectRateLimitedComputed(source, { limit: 2, window: 100 }), + injectRateLimitedComputed(source, { limit: 1, window: 100 }), ) TestBed.tick() source.set('accepted') diff --git a/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts b/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts index 6365dc7a0..e7616ec17 100644 --- a/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts +++ b/packages/angular-pacer/tests/throttler/injectThrottledComputed.test.ts @@ -15,33 +15,29 @@ it('returns an Angular signal with a callable throttler attribute', () => { TestBed.tick() source.set('updated') TestBed.tick() - expect(value()).toBe('initial') + expect(value()).toBe('updated') value.throttler.flush() expect(value()).toBe('updated') }) -it('applies the latest source change at the original throttle deadline', () => { +it('keeps the leading edge available for the first real source change', () => { vi.useFakeTimers() const source = signal('initial') const value = TestBed.runInInjectionContext(() => injectThrottledComputed(source, { wait: 100 }), ) TestBed.tick() - vi.advanceTimersByTime(20) - source.set('first trailing') + source.set('first leading') TestBed.tick() + expect(value()).toBe('first leading') vi.advanceTimersByTime(40) source.set('latest trailing') TestBed.tick() - expect(value()).toBe('initial') - vi.advanceTimersByTime(39) - expect(value()).toBe('initial') + expect(value()).toBe('first leading') + vi.advanceTimersByTime(59) + expect(value()).toBe('first leading') vi.advanceTimersByTime(1) expect(value()).toBe('latest trailing') - vi.advanceTimersByTime(100) - source.set('next leading') - TestBed.tick() - expect(value()).toBe('next leading') }) it('passes selected state through to the attached utility', () => { From 3002709a27b80c0ac2a48e7242d14e19f29aa421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:28 -0300 Subject: [PATCH 11/18] fix(angular-pacer): follow WritableSignal semantics Expose editable helpers as WritableSignal, including the live asReadonly view. Keep set replacements literal, evaluate updates against the committed value at execution, and prevent unsafe function or constructor values in raw writes. Keep shared type-only definitions separate from utility-owned types. Validation: intermediate package type checks, lint, and 176 Angular tests passed. --- .changeset/angular-adapter-rework.md | 2 + .../src/async-batcher/injectAsyncBatcher.ts | 2 +- .../async-debouncer/injectAsyncDebouncer.ts | 2 +- .../async-queuer/injectAsyncQueuedItems.ts | 2 +- .../src/async-queuer/injectAsyncQueuer.ts | 2 +- .../injectAsyncRateLimiter.ts | 2 +- .../async-throttler/injectAsyncThrottler.ts | 2 +- .../src/batcher/injectBatcher.ts | 2 +- .../src/debouncer/injectDebouncedSignal.ts | 55 +++++++------ .../src/debouncer/injectDebouncer.ts | 2 +- .../src/queuer/injectQueuedItems.ts | 2 +- .../src/queuer/injectQueuedSignal.ts | 56 ++++++++----- .../angular-pacer/src/queuer/injectQueuer.ts | 2 +- .../rate-limiter/injectRateLimitedSignal.ts | 54 ++++++++----- .../src/rate-limiter/injectRateLimiter.ts | 2 +- .../src/throttler/injectThrottledSignal.ts | 56 +++++++------ .../src/throttler/injectThrottler.ts | 2 +- .../angular-pacer/src/utils/injectSelector.ts | 2 +- .../angular-pacer/src/utils/internalTypes.ts | 14 ++++ .../angular-pacer/src/utils/maybeAccessor.ts | 2 +- .../src/utils/readonlySelected.ts | 4 - .../debouncer/injectDebouncedSignal.test.ts | 76 ++++++++++++++++-- .../tests/queuer/injectQueuedSignal.test.ts | 79 +++++++++++++++++++ .../injectRateLimitedSignal.test.ts | 47 ++++++++++- .../throttler/injectThrottledSignal.test.ts | 51 +++++++++++- 25 files changed, 408 insertions(+), 114 deletions(-) create mode 100644 packages/angular-pacer/src/utils/internalTypes.ts delete mode 100644 packages/angular-pacer/src/utils/readonlySelected.ts diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index f379fd054..d5122dec9 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -11,3 +11,5 @@ Support Angular 20 and up, including all Angular LTS versions, using stable publ signal, effect, and pending-task APIs. Avoid processing an unchanged initial source value in debounced, throttled, and rate-limited reflected helpers, preserving stability and capacity for real changes. + +Expose editable helpers as Angular writable signals with paced `set` and `update`, a live `asReadonly()` view, and type-safe function-valued data. Updaters use the committed value when processed. diff --git a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts index 6d54a7ce8..56c218748 100644 --- a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts +++ b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { AsyncBatcherOptions, AsyncBatcherState } from '@tanstack/pacer' diff --git a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts index 32a759c87..c3f649f1a 100644 --- a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts +++ b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts index 8e76f453e..c30382b5f 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts @@ -1,7 +1,7 @@ import { shallow } from '@tanstack/store' import { computed } from '@angular/core' import { injectAsyncQueuer } from './injectAsyncQueuer' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { AsyncQueuerState } from '@tanstack/pacer/async-queuer' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts index 784da724d..fc16cbfe5 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { AsyncQueuerOptions, AsyncQueuerState } from '@tanstack/pacer' diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index 78a02dd2a..f4af29af5 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts index 4e68b323e..95ffe3172 100644 --- a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts +++ b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/batcher/injectBatcher.ts b/packages/angular-pacer/src/batcher/injectBatcher.ts index 5adc6a63c..c7fd89787 100644 --- a/packages/angular-pacer/src/batcher/injectBatcher.ts +++ b/packages/angular-pacer/src/batcher/injectBatcher.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { BatcherOptions, BatcherState } from '@tanstack/pacer' diff --git a/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts b/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts index 5b7b13dba..ee7fcf023 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts @@ -1,41 +1,40 @@ -import { linkedSignal } from '@angular/core' +import { linkedSignal, untracked } from '@angular/core' import { toAccessorSignal } from '../utils/maybeAccessor' import { injectDebouncer } from './injectDebouncer' import type { DebouncerState } from '@tanstack/pacer/debouncer' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { SignalWrite } from '../utils/internalTypes' import type { AngularDebouncer, AngularDebouncerOptions, } from './injectDebouncer' -import type { Signal } from '@angular/core' +import type { WritableSignal } from '@angular/core' /** - * A readonly Angular value signal with paced `set`/`update` methods. - * The `debouncer` attribute exposes the underlying Debouncer methods. + * An Angular writable signal whose `set` and `update` writes are debounced. + * New writes replace the pending write and restart the delay. Updaters receive + * the committed value when the write executes. `asReadonly()` exposes a live + * readonly view. The `debouncer` attribute controls execution. */ export interface AngularDebouncerSignal< TValue, TSelected = {}, -> extends Signal { - /** Schedules the replacement value after the debounce delay. */ - set: (value: TValue) => void - /** Runs the updater with the current committed value when the write executes. */ - update: (updateFn: (previous: TValue) => TValue) => void +> extends WritableSignal { /** The underlying Angular Debouncer ref for controlling execution. */ - debouncer: AngularDebouncer<(callback: () => void) => void, TSelected> + debouncer: AngularDebouncer<(value: SignalWrite) => void, TSelected> } /** * Creates an Angular debounced editable signal. * * The initial value is available synchronously. `set` and `update` debounce writes: a newer write restarts the delay and replaces the pending write. An updater runs against the committed value when the delay expires. * - * The returned value is a real Angular signal with the underlying utility exposed + * The returned value is a real Angular writable signal with the underlying utility exposed * on `debouncer`. Options accept a static object or reactive factory and follow * {@link injectDebouncer} lifecycle and provider behavior. * * @param initialValue The initial committed value. * @param options Core options or a reactive options factory. - * @returns The value signal with `set`, `update`, and a `debouncer` attribute. + * @returns The writable signal with `set`, `update`, `asReadonly`, and a `debouncer` attribute. * * @example * ```ts @@ -49,7 +48,7 @@ export interface AngularDebouncerSignal< export function injectDebouncedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularDebouncerOptions<(callback: () => void) => void> + AngularDebouncerOptions<(value: SignalWrite) => void> >, ): AngularDebouncerSignal /** @@ -59,32 +58,44 @@ export function injectDebouncedSignal( export function injectDebouncedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularDebouncerOptions<(callback: () => void) => void> + AngularDebouncerOptions<(value: SignalWrite) => void> >, selector: ( - state: DebouncerState<(callback: () => void) => void>, + state: DebouncerState<(value: SignalWrite) => void>, ) => TSelected, ): AngularDebouncerSignal export function injectDebouncedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularDebouncerOptions<(callback: () => void) => void> + AngularDebouncerOptions<(value: SignalWrite) => void> >, selector?: ( - state: DebouncerState<(callback: () => void) => void>, + state: DebouncerState<(value: SignalWrite) => void>, ) => TSelected, ): AngularDebouncerSignal { - const debouncedSignal = linkedSignal(toAccessorSignal(initialValue)) + const initialValueSignal = toAccessorSignal(initialValue) + const debouncedSignal = linkedSignal(() => untracked(initialValueSignal)) + const commitValue = debouncedSignal.set const debouncer = injectDebouncer( - (callback: () => void) => callback(), + (value: SignalWrite) => { + commitValue( + typeof value === 'function' + ? (value as (previous: TValue) => TValue)(debouncedSignal()) + : value, + ) + }, options, (state) => (selector ? selector(state) : {}), ) - return Object.assign(debouncedSignal.asReadonly(), { + return Object.assign(debouncedSignal, { set: (value: TValue) => - debouncer.maybeExecute(() => debouncedSignal.set(value)), + debouncer.maybeExecute( + (typeof value === 'function' + ? () => value + : value) as SignalWrite, + ), update: (updateFn: (prev: TValue) => TValue) => - debouncer.maybeExecute(() => debouncedSignal.update(updateFn)), + debouncer.maybeExecute(updateFn), debouncer, }) } diff --git a/packages/angular-pacer/src/debouncer/injectDebouncer.ts b/packages/angular-pacer/src/debouncer/injectDebouncer.ts index 6fca3ab6f..7f8e8661e 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncer.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncer.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/queuer/injectQueuedItems.ts b/packages/angular-pacer/src/queuer/injectQueuedItems.ts index f6e1e86e2..2a3bb1fa1 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedItems.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedItems.ts @@ -1,7 +1,7 @@ import { shallow } from '@tanstack/store' import { computed } from '@angular/core' import { injectQueuer } from './injectQueuer' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { QueuerState } from '@tanstack/pacer/queuer' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' diff --git a/packages/angular-pacer/src/queuer/injectQueuedSignal.ts b/packages/angular-pacer/src/queuer/injectQueuedSignal.ts index f70560bbd..ce852b66c 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedSignal.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedSignal.ts @@ -1,22 +1,24 @@ -import { linkedSignal } from '@angular/core' +import { linkedSignal, untracked } from '@angular/core' import { toAccessorSignal } from '../utils/maybeAccessor' import { injectQueuer } from './injectQueuer' -import type { Signal } from '@angular/core' +import type { WritableSignal } from '@angular/core' import type { QueuerState } from '@tanstack/pacer/queuer' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { SignalWrite } from '../utils/internalTypes' import type { AngularQueuer, AngularQueuerOptions } from './injectQueuer' -/** A processed-value signal with its underlying queue controls. */ +/** + * An Angular writable signal whose `set` and `update` writes are queued in order. + * Updaters receive the committed value when their queue item executes. + * `asReadonly()` exposes a live readonly view. The `queuer` attribute controls + * processing and exposes selected queue state. + */ export interface AngularQueuerSignal< TValue, TSelected = {}, -> extends Signal { - /** Enqueues a replacement value. */ - set: (value: TValue) => void - /** Enqueues an updater evaluated against the committed value when processed. */ - update: (updater: (previous: TValue) => TValue) => void +> extends WritableSignal { /** The underlying Angular Queuer ref and its selected state. */ - queuer: AngularQueuer<() => void, TSelected> + queuer: AngularQueuer, TSelected> } /** @@ -29,7 +31,7 @@ export interface AngularQueuerSignal< * * @param initialValue The initial committed value. * @param options Core queue options or a reactive options factory. - * @returns The processed-value signal with its queuer attached. + * @returns The writable processed-value signal with its queuer attached. * * @example * ```ts @@ -41,7 +43,7 @@ export interface AngularQueuerSignal< */ export function injectQueuedSignal( initialValue: MaybeAccessor, - options?: MaybeAccessor void>>, + options?: MaybeAccessor>>, ): AngularQueuerSignal /** * Creates the queued value signal with selected state on its attached queuer. @@ -49,24 +51,36 @@ export function injectQueuedSignal( */ export function injectQueuedSignal( initialValue: MaybeAccessor, - options: MaybeAccessor void>>, - selector: (state: QueuerState<() => void>) => TSelected, + options: MaybeAccessor>>, + selector: (state: QueuerState>) => TSelected, ): AngularQueuerSignal export function injectQueuedSignal( initialValue: MaybeAccessor, - options: MaybeAccessor void>> = {}, - selector?: (state: QueuerState<() => void>) => TSelected, + options: MaybeAccessor>> = {}, + selector?: (state: QueuerState>) => TSelected, ): AngularQueuerSignal { - const queuedSignal = linkedSignal(toAccessorSignal(initialValue)) + const initialValueSignal = toAccessorSignal(initialValue) + const queuedSignal = linkedSignal(() => untracked(initialValueSignal)) + const commitValue = queuedSignal.set const queuer = injectQueuer( - (callback: () => void) => callback(), + (value: SignalWrite) => { + commitValue( + typeof value === 'function' + ? (value as (previous: TValue) => TValue)(queuedSignal()) + : value, + ) + }, options, (state) => (selector ? selector(state) : {}), ) - return Object.assign(queuedSignal.asReadonly(), { - set: (value: TValue) => queuer.addItem(() => queuedSignal.set(value)), - update: (updater: (previous: TValue) => TValue) => - queuer.addItem(() => queuedSignal.update(updater)), + return Object.assign(queuedSignal, { + set: (value: TValue) => + queuer.addItem( + (typeof value === 'function' + ? () => value + : value) as SignalWrite, + ), + update: (updater: (previous: TValue) => TValue) => queuer.addItem(updater), queuer, }) } diff --git a/packages/angular-pacer/src/queuer/injectQueuer.ts b/packages/angular-pacer/src/queuer/injectQueuer.ts index 2298ec082..352366c27 100644 --- a/packages/angular-pacer/src/queuer/injectQueuer.ts +++ b/packages/angular-pacer/src/queuer/injectQueuer.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { QueuerOptions, QueuerState } from '@tanstack/pacer' diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts index 373d2a5bc..4385688db 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts @@ -1,41 +1,43 @@ -import { linkedSignal } from '@angular/core' +import { linkedSignal, untracked } from '@angular/core' import { toAccessorSignal } from '../utils/maybeAccessor' import { injectRateLimiter } from './injectRateLimiter' import type { RateLimiterState } from '@tanstack/pacer/rate-limiter' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { SignalWrite } from '../utils/internalTypes' import type { AngularRateLimiter, AngularRateLimiterOptions, } from './injectRateLimiter' -import type { Signal } from '@angular/core' +import type { WritableSignal } from '@angular/core' /** - * A readonly Angular value signal with paced `set`/`update` methods. - * The `rateLimiter` attribute exposes the underlying RateLimiter methods. + * An Angular writable signal whose `set` and `update` writes share a rate limit. + * Accepted writes execute immediately; rejected writes and their updaters are + * discarded. Updaters receive the committed value when executed. `asReadonly()` + * exposes a live readonly view. The `rateLimiter` attribute controls execution. */ export interface AngularRateLimiterSignal< TValue, TSelected = {}, -> extends Signal { - /** Replaces the value immediately if the rate limit permits; rejected writes are discarded. */ - set: (value: TValue) => void - /** Runs the updater with the current committed value when the write executes. */ - update: (updateFn: (previous: TValue) => TValue) => void +> extends WritableSignal { /** The underlying Angular RateLimiter ref for controlling execution. */ - rateLimiter: AngularRateLimiter<(callback: () => void) => void, TSelected> + rateLimiter: AngularRateLimiter< + (value: SignalWrite) => void, + TSelected + > } /** * Creates an Angular ratelimited editable signal. * * The initial value is available synchronously. `set` and `update` share the execution limit. Accepted writes apply immediately; rejected writes and their updater callbacks are discarded, not replayed later. * - * The returned value is a real Angular signal with the underlying utility exposed + * The returned value is a real Angular writable signal with the underlying utility exposed * on `rateLimiter`. Options accept a static object or reactive factory and follow * {@link injectRateLimiter} lifecycle and provider behavior. * * @param initialValue The initial committed value. * @param options Core options or a reactive options factory. - * @returns The value signal with `set`, `update`, and a `rateLimiter` attribute. + * @returns The writable signal with `set`, `update`, `asReadonly`, and a `rateLimiter` attribute. * * @example * ```ts @@ -49,7 +51,7 @@ export interface AngularRateLimiterSignal< export function injectRateLimitedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularRateLimiterOptions<(callback: () => void) => void> + AngularRateLimiterOptions<(value: SignalWrite) => void> >, ): AngularRateLimiterSignal /** @@ -59,28 +61,40 @@ export function injectRateLimitedSignal( export function injectRateLimitedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularRateLimiterOptions<(callback: () => void) => void> + AngularRateLimiterOptions<(value: SignalWrite) => void> >, selector: (state: RateLimiterState) => TSelected, ): AngularRateLimiterSignal export function injectRateLimitedSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularRateLimiterOptions<(callback: () => void) => void> + AngularRateLimiterOptions<(value: SignalWrite) => void> >, selector?: (state: RateLimiterState) => TSelected, ): AngularRateLimiterSignal { - const rateLimitedSignal = linkedSignal(toAccessorSignal(initialValue)) + const initialValueSignal = toAccessorSignal(initialValue) + const rateLimitedSignal = linkedSignal(() => untracked(initialValueSignal)) + const commitValue = rateLimitedSignal.set const rateLimiter = injectRateLimiter( - (callback: () => void) => callback(), + (value: SignalWrite) => { + commitValue( + typeof value === 'function' + ? (value as (previous: TValue) => TValue)(rateLimitedSignal()) + : value, + ) + }, options, (state) => (selector ? selector(state) : {}), ) - return Object.assign(rateLimitedSignal.asReadonly(), { + return Object.assign(rateLimitedSignal, { set: (value: TValue) => - rateLimiter.maybeExecute(() => rateLimitedSignal.set(value)), + rateLimiter.maybeExecute( + (typeof value === 'function' + ? () => value + : value) as SignalWrite, + ), update: (updateFn: (prev: TValue) => TValue) => - rateLimiter.maybeExecute(() => rateLimitedSignal.update(updateFn)), + rateLimiter.maybeExecute(updateFn), rateLimiter, }) } diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index eb65e7dcc..beab445c8 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -9,7 +9,7 @@ import { } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/throttler/injectThrottledSignal.ts b/packages/angular-pacer/src/throttler/injectThrottledSignal.ts index 21b211b14..437423b08 100644 --- a/packages/angular-pacer/src/throttler/injectThrottledSignal.ts +++ b/packages/angular-pacer/src/throttler/injectThrottledSignal.ts @@ -1,41 +1,41 @@ -import { linkedSignal } from '@angular/core' +import { linkedSignal, untracked } from '@angular/core' import { toAccessorSignal } from '../utils/maybeAccessor' import { injectThrottler } from './injectThrottler' import type { ThrottlerState } from '@tanstack/pacer/throttler' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { SignalWrite } from '../utils/internalTypes' import type { AngularThrottler, AngularThrottlerOptions, } from './injectThrottler' -import type { Signal } from '@angular/core' +import type { WritableSignal } from '@angular/core' /** - * A readonly Angular value signal with paced `set`/`update` methods. - * The `throttler` attribute exposes the underlying Throttler methods. + * An Angular writable signal whose `set` and `update` writes are throttled. + * Leading writes may execute immediately; later writes replace the pending + * trailing write without extending its deadline. Updaters receive the committed + * value when executed. `asReadonly()` exposes a live readonly view. + * The `throttler` attribute controls execution. */ export interface AngularThrottlerSignal< TValue, TSelected = {}, -> extends Signal { - /** Schedules the replacement value according to leading and trailing throttling options. */ - set: (value: TValue) => void - /** Runs the updater with the current committed value when the write executes. */ - update: (updateFn: (previous: TValue) => TValue) => void +> extends WritableSignal { /** The underlying Angular Throttler ref for controlling execution. */ - throttler: AngularThrottler<(callback: () => void) => void, TSelected> + throttler: AngularThrottler<(value: SignalWrite) => void, TSelected> } /** * Creates an Angular throttled editable signal. * * The initial value is available synchronously. `set` and `update` share a throttler. Leading writes may apply immediately; subsequent writes replace the pending trailing write without extending its deadline. An updater runs against the committed value when executed. * - * The returned value is a real Angular signal with the underlying utility exposed + * The returned value is a real Angular writable signal with the underlying utility exposed * on `throttler`. Options accept a static object or reactive factory and follow * {@link injectThrottler} lifecycle and provider behavior. * * @param initialValue The initial committed value. * @param options Core options or a reactive options factory. - * @returns The value signal with `set`, `update`, and a `throttler` attribute. + * @returns The writable signal with `set`, `update`, `asReadonly`, and a `throttler` attribute. * * @example * ```ts @@ -49,7 +49,7 @@ export interface AngularThrottlerSignal< export function injectThrottledSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularThrottlerOptions<(callback: () => void) => void> + AngularThrottlerOptions<(value: SignalWrite) => void> >, ): AngularThrottlerSignal /** @@ -59,32 +59,44 @@ export function injectThrottledSignal( export function injectThrottledSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularThrottlerOptions<(callback: () => void) => void> + AngularThrottlerOptions<(value: SignalWrite) => void> >, selector: ( - state: ThrottlerState<(callback: () => void) => void>, + state: ThrottlerState<(value: SignalWrite) => void>, ) => TSelected, ): AngularThrottlerSignal export function injectThrottledSignal( initialValue: MaybeAccessor, options: MaybeAccessor< - AngularThrottlerOptions<(callback: () => void) => void> + AngularThrottlerOptions<(value: SignalWrite) => void> >, selector?: ( - state: ThrottlerState<(callback: () => void) => void>, + state: ThrottlerState<(value: SignalWrite) => void>, ) => TSelected, ): AngularThrottlerSignal { - const throttledSignal = linkedSignal(toAccessorSignal(initialValue)) + const initialValueSignal = toAccessorSignal(initialValue) + const throttledSignal = linkedSignal(() => untracked(initialValueSignal)) + const commitValue = throttledSignal.set const throttler = injectThrottler( - (callback: () => void) => callback(), + (value: SignalWrite) => { + commitValue( + typeof value === 'function' + ? (value as (previous: TValue) => TValue)(throttledSignal()) + : value, + ) + }, options, (state) => (selector ? selector(state) : {}), ) - return Object.assign(throttledSignal.asReadonly(), { + return Object.assign(throttledSignal, { set: (value: TValue) => - throttler.maybeExecute(() => throttledSignal.set(value)), + throttler.maybeExecute( + (typeof value === 'function' + ? () => value + : value) as SignalWrite, + ), update: (updateFn: (prev: TValue) => TValue) => - throttler.maybeExecute(() => throttledSignal.update(updateFn)), + throttler.maybeExecute(updateFn), throttler, }) } diff --git a/packages/angular-pacer/src/throttler/injectThrottler.ts b/packages/angular-pacer/src/throttler/injectThrottler.ts index fe8724513..ffb7f598b 100644 --- a/packages/angular-pacer/src/throttler/injectThrottler.ts +++ b/packages/angular-pacer/src/throttler/injectThrottler.ts @@ -10,7 +10,7 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/readonlySelected' +import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { Signal } from '@angular/core' import type { diff --git a/packages/angular-pacer/src/utils/injectSelector.ts b/packages/angular-pacer/src/utils/injectSelector.ts index 74ca729a0..a2426139b 100644 --- a/packages/angular-pacer/src/utils/injectSelector.ts +++ b/packages/angular-pacer/src/utils/injectSelector.ts @@ -1,5 +1,5 @@ import { injectExternalStore } from './injectExternalStore' -import type { ReadonlySelected } from './readonlySelected' +import type { ReadonlySelected } from './internalTypes' import type { Readable } from '@tanstack/store' import type { Signal, ValueEqualityFn } from '@angular/core' diff --git a/packages/angular-pacer/src/utils/internalTypes.ts b/packages/angular-pacer/src/utils/internalTypes.ts new file mode 100644 index 000000000..7495865ad --- /dev/null +++ b/packages/angular-pacer/src/utils/internalTypes.ts @@ -0,0 +1,14 @@ +/** Excludes callable data from protocols that interpret functions as accessors. */ +export type NonCallable = T extends (...args: Array) => unknown + ? never + : T + +/** Editable signal utilities process literal non-callable values or updaters. */ +export type SignalWrite = + | Exclude, abstract new (...args: Array) => unknown> + | ((previous: T) => T) + +/** Preserves callable selector results while making other selected values readonly. */ +export type ReadonlySelected = T extends (...args: Array) => unknown + ? T + : Readonly diff --git a/packages/angular-pacer/src/utils/maybeAccessor.ts b/packages/angular-pacer/src/utils/maybeAccessor.ts index 1ddf9b1a7..de0b20e43 100644 --- a/packages/angular-pacer/src/utils/maybeAccessor.ts +++ b/packages/angular-pacer/src/utils/maybeAccessor.ts @@ -1,6 +1,6 @@ import { computed } from '@angular/core' -type NonCallable = T extends (...args: Array) => unknown ? never : T +import type { NonCallable } from './internalTypes' export type MaybeAccessor = NonCallable | (() => T) diff --git a/packages/angular-pacer/src/utils/readonlySelected.ts b/packages/angular-pacer/src/utils/readonlySelected.ts deleted file mode 100644 index 58fe50aaf..000000000 --- a/packages/angular-pacer/src/utils/readonlySelected.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** Preserves callable selector results while making other selected values readonly. */ -export type ReadonlySelected = T extends (...args: Array) => unknown - ? T - : Readonly diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts index 4b53e3bee..fecb2c930 100644 --- a/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts +++ b/packages/angular-pacer/tests/debouncer/injectDebouncedSignal.test.ts @@ -1,4 +1,5 @@ -import { isSignal } from '@angular/core' +import type { Signal, WritableSignal } from '@angular/core' +import { isSignal, signal } from '@angular/core' import { TestBed } from '@angular/core/testing' import { expect, expectTypeOf, it, vi } from 'vitest' import { injectDebouncedSignal } from '../../src/debouncer/injectDebouncedSignal' @@ -12,11 +13,10 @@ it('returns an Angular signal with the debouncer as an attribute', () => { expect(value()).toBe(0) expect(value).toHaveProperty('debouncer') TestBed.tick() - const callback = vi.fn() - value.debouncer.maybeExecute(callback) - expect(callback).not.toHaveBeenCalled() + value.debouncer.maybeExecute(1) + expect(value()).toBe(0) value.debouncer.flush() - expect(callback).toHaveBeenCalledOnce() + expect(value()).toBe(1) }) it('debounces set calls until the delay after the latest value', () => { @@ -119,7 +119,73 @@ it('preserves a function-valued initial value supplied through a factory', () => expectTypeOf(value()).toEqualTypeOf() expect(value()).toBe(fn) expect(value()(2)).toBe(3) + TestBed.tick() + const replacement = vi.fn((n: number) => n * 2) + value.set(replacement) + value.debouncer.flush() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() + value.update(() => fn) + value.debouncer.flush() + expect(value()).toBe(fn) + + // @ts-expect-error Raw utility functions must return a value of the signal's type. + void (() => value.debouncer.maybeExecute(() => 2)) + value.debouncer.maybeExecute(() => replacement) + value.debouncer.flush() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() // @ts-expect-error Function values must be wrapped to distinguish them from accessors. void (() => injectDebouncedSignal(fn, { wait: 10 })) }) + +it('exposes replacement values in the attached debouncer state', () => { + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal('initial', { wait: 100 }, (state) => state.lastArgs), + ) + TestBed.tick() + value.set('pending') + expect(value.debouncer.state()).toEqual(['pending']) + expect(value()).toBe('initial') + value.debouncer.flush() + expect(value()).toBe('pending') +}) + +it('reads the initial accessor once without resetting pending local writes', () => { + vi.useFakeTimers() + const initial = signal('a') + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(initial, { wait: 100 }), + ) + expect(value()).toBe('a') + TestBed.tick() + value.set('local') + initial.set('b') + TestBed.tick() + expect(value()).toBe('a') + vi.advanceTimersByTime(100) + expect(value()).toBe('local') +}) + +it('implements the writable signal contract with a live readonly view', () => { + const value = TestBed.runInInjectionContext(() => + injectDebouncedSignal(0, { wait: 0 }), + ) + expectTypeOf(value).toExtend>() + expectTypeOf(value.set).parameters.toEqualTypeOf<[number]>() + expectTypeOf(value.update).parameters.toEqualTypeOf< + [(value: number) => number] + >() + const readonlyValue = value.asReadonly() + expectTypeOf(readonlyValue).toEqualTypeOf>() + expect(isSignal(readonlyValue)).toBe(true) + expect(readonlyValue).toBe(value.asReadonly()) + expect(readonlyValue).not.toHaveProperty('set') + expect(readonlyValue).not.toHaveProperty('update') + expect(readonlyValue()).toBe(0) + TestBed.tick() + value.set(1) + value.debouncer.flush() + expect(readonlyValue()).toBe(1) +}) diff --git a/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts b/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts index dbbabe460..8c415d715 100644 --- a/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts +++ b/packages/angular-pacer/tests/queuer/injectQueuedSignal.test.ts @@ -1,3 +1,4 @@ +import type { Signal, WritableSignal } from '@angular/core' import { isSignal } from '@angular/core' import { TestBed } from '@angular/core/testing' import { expect, expectTypeOf, it, vi } from 'vitest' @@ -73,3 +74,81 @@ it('does not apply cleared writes or evaluate their updaters', () => { expect(value()).toBe(0) expect(update).not.toHaveBeenCalled() }) + +it('implements the writable signal contract with a live readonly view', () => { + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(0, { started: false }), + ) + expectTypeOf(value).toExtend>() + expectTypeOf(value.set).parameters.toEqualTypeOf<[number]>() + expectTypeOf(value.update).parameters.toEqualTypeOf< + [(value: number) => number] + >() + const readonlyValue = value.asReadonly() + expectTypeOf(readonlyValue).toEqualTypeOf>() + expect(isSignal(readonlyValue)).toBe(true) + expect(readonlyValue).toBe(value.asReadonly()) + expect(readonlyValue).not.toHaveProperty('set') + expect(readonlyValue).not.toHaveProperty('update') + expect(readonlyValue()).toBe(0) + TestBed.tick() + value.set(1) + value.queuer.execute() + expect(readonlyValue()).toBe(1) +}) + +it('preserves function values through set and requires raw writes to return them', () => { + const fn = () => 1 + const replacement = vi.fn(() => 2) + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(() => fn, { + started: false, + initialItems: [() => fn], + }), + ) + TestBed.tick() + value.queuer.execute() + expect(value()).toBe(fn) + value.set(replacement) + value.queuer.execute() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() + value.update(() => fn) + value.queuer.execute() + expect(value()).toBe(fn) + + // @ts-expect-error Raw utility functions must return a value of the signal's type. + void (() => value.queuer.addItem(() => 2)) + value.queuer.addItem(() => replacement) + value.queuer.execute() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() + + void (() => + injectQueuedSignal(() => fn, { + started: false, + // @ts-expect-error Initial queue functions must return a value of the signal's type. + initialItems: [() => 2], + })) +}) + +it('stores constructor values through set without treating them as updaters', () => { + class Initial {} + class Replacement {} + const value = TestBed.runInInjectionContext(() => + injectQueuedSignal(() => Initial, { started: false }), + ) + TestBed.tick() + value.set(Replacement) + value.queuer.execute() + expect(value()).toBe(Replacement) + + // @ts-expect-error Constructor values must be wrapped when passed to raw controls. + void (() => value.queuer.addItem(Replacement)) + void (() => + injectQueuedSignal(() => Initial, { + started: false, + // @ts-expect-error Constructor values must be wrapped in initial queue items. + initialItems: [Replacement], + })) +}) diff --git a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts index 9f268cb4f..60db0b143 100644 --- a/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts +++ b/packages/angular-pacer/tests/rate-limiter/injectRateLimitedSignal.test.ts @@ -1,3 +1,4 @@ +import type { Signal, WritableSignal } from '@angular/core' import { isSignal } from '@angular/core' import { TestBed } from '@angular/core/testing' import { expect, expectTypeOf, it, vi } from 'vitest' @@ -11,9 +12,8 @@ it('returns an Angular signal with a callable rateLimiter attribute', () => { expectTypeOf(value()).toEqualTypeOf() expect(value()).toBe(0) expect(value).toHaveProperty('rateLimiter') - const callback = vi.fn() - value.rateLimiter.maybeExecute(callback) - expect(callback).toHaveBeenCalledOnce() + value.rateLimiter.maybeExecute(1) + expect(value()).toBe(1) }) it('accepts set calls up to the limit, rejects excess calls, and accepts again after the window', () => { @@ -74,3 +74,44 @@ it('passes selected state through to the attached utility', () => { expect(value()).toBe('updated') expect(value.rateLimiter.state().count).toBe(before + 1) }) + +it('implements the writable signal contract with a live readonly view', () => { + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal(0, { limit: 2, window: 100 }), + ) + expectTypeOf(value).toExtend>() + expectTypeOf(value.set).parameters.toEqualTypeOf<[number]>() + expectTypeOf(value.update).parameters.toEqualTypeOf< + [(value: number) => number] + >() + const readonlyValue = value.asReadonly() + expectTypeOf(readonlyValue).toEqualTypeOf>() + expect(isSignal(readonlyValue)).toBe(true) + expect(readonlyValue).toBe(value.asReadonly()) + expect(readonlyValue).not.toHaveProperty('set') + expect(readonlyValue).not.toHaveProperty('update') + expect(readonlyValue()).toBe(0) + TestBed.tick() + value.set(1) + expect(readonlyValue()).toBe(1) +}) + +it('preserves function values through set and requires raw writes to return them', () => { + const fn = () => 1 + const replacement = vi.fn(() => 2) + const value = TestBed.runInInjectionContext(() => + injectRateLimitedSignal(() => fn, { limit: 5, window: 100 }), + ) + TestBed.tick() + value.set(replacement) + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() + value.update(() => fn) + expect(value()).toBe(fn) + + // @ts-expect-error Raw utility functions must return a value of the signal's type. + void (() => value.rateLimiter.maybeExecute(() => 2)) + value.rateLimiter.maybeExecute(() => replacement) + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() +}) diff --git a/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts b/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts index d9ebd3d0a..74ad8ec6b 100644 --- a/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts +++ b/packages/angular-pacer/tests/throttler/injectThrottledSignal.test.ts @@ -1,3 +1,4 @@ +import type { Signal, WritableSignal } from '@angular/core' import { isSignal } from '@angular/core' import { TestBed } from '@angular/core/testing' import { expect, expectTypeOf, it, vi } from 'vitest' @@ -11,9 +12,8 @@ it('returns an Angular signal with a callable throttler attribute', () => { expectTypeOf(value()).toEqualTypeOf() expect(value()).toBe(0) expect(value).toHaveProperty('throttler') - const callback = vi.fn() - value.throttler.maybeExecute(callback) - expect(callback).toHaveBeenCalledOnce() + value.throttler.maybeExecute(1) + expect(value()).toBe(1) }) it('applies leading set immediately and the latest trailing set at the original deadline', () => { @@ -91,3 +91,48 @@ it('passes selected state through to the attached utility', () => { expect(value()).toBe('updated') expect(value.throttler.state().count).toBe(before + 1) }) + +it('implements the writable signal contract with a live readonly view', () => { + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal(0, { wait: 0 }), + ) + expectTypeOf(value).toExtend>() + expectTypeOf(value.set).parameters.toEqualTypeOf<[number]>() + expectTypeOf(value.update).parameters.toEqualTypeOf< + [(value: number) => number] + >() + const readonlyValue = value.asReadonly() + expectTypeOf(readonlyValue).toEqualTypeOf>() + expect(isSignal(readonlyValue)).toBe(true) + expect(readonlyValue).toBe(value.asReadonly()) + expect(readonlyValue).not.toHaveProperty('set') + expect(readonlyValue).not.toHaveProperty('update') + expect(readonlyValue()).toBe(0) + TestBed.tick() + value.set(1) + value.throttler.flush() + expect(readonlyValue()).toBe(1) +}) + +it('preserves function values through set and requires raw writes to return them', () => { + const fn = () => 1 + const replacement = vi.fn(() => 2) + const value = TestBed.runInInjectionContext(() => + injectThrottledSignal(() => fn, { wait: 10 }), + ) + TestBed.tick() + value.set(replacement) + value.throttler.flush() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() + value.update(() => fn) + value.throttler.flush() + expect(value()).toBe(fn) + + // @ts-expect-error Raw utility functions must return a value of the signal's type. + void (() => value.throttler.maybeExecute(() => 2)) + value.throttler.maybeExecute(() => replacement) + value.throttler.flush() + expect(value()).toBe(replacement) + expect(replacement).not.toHaveBeenCalled() +}) From 35602a5886e0f8895b06794271150560eff5a0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:28 -0300 Subject: [PATCH 12/18] fix(angular-pacer): bind lazy instance cleanup to owner destruction Return a cached getter from injectLazy and require a disposal callback. Register disposal when construction succeeds, handle owner destruction before effects run, and avoid constructing unused instances at teardown. Replace disposal effects and preserve Angular 20.0 lifecycle compatibility. Validation: intermediate package type checks, lint, and 179 Angular tests passed. --- .changeset/angular-adapter-rework.md | 4 +- .../src/async-batcher/injectAsyncBatcher.ts | 37 ++++++++--------- .../async-debouncer/injectAsyncDebouncer.ts | 36 ++++++++--------- .../src/async-queuer/injectAsyncQueuer.ts | 36 ++++++++--------- .../injectAsyncRateLimiter.ts | 34 +++++++--------- .../async-throttler/injectAsyncThrottler.ts | 36 ++++++++--------- .../src/batcher/injectBatcher.ts | 40 ++++++++----------- .../src/debouncer/injectDebouncer.ts | 34 +++++++--------- .../angular-pacer/src/queuer/injectQueuer.ts | 36 ++++++++--------- .../src/rate-limiter/injectRateLimiter.ts | 28 ++++++------- .../src/throttler/injectThrottler.ts | 34 +++++++--------- .../src/utils/injectExternalStore.ts | 22 +++++----- .../src/utils/injectForwardMethods.ts | 7 ++-- .../angular-pacer/src/utils/injectLazy.ts | 34 +++++++++++++--- .../async-batcher/injectAsyncBatcher.test.ts | 19 +++++++++ .../tests/debouncer/injectDebouncer.test.ts | 28 +++++++++++++ 16 files changed, 249 insertions(+), 216 deletions(-) diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index d5122dec9..cdcbb3cd3 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -3,7 +3,7 @@ --- Rebuild Angular utility adapters with lazy construction, readonly selected -state, explicit methods, reactive options, and effect-owned cleanup. Replace the +state, explicit methods, reactive options, and owner-bound instance cleanup. Replace the Angular Store dependency with core Store integration and cover the adapters with per-utility Angular behavior tests. @@ -13,3 +13,5 @@ signal, effect, and pending-task APIs. Avoid processing an unchanged initial source value in debounced, throttled, and rate-limited reflected helpers, preserving stability and capacity for real changes. Expose editable helpers as Angular writable signals with paced `set` and `update`, a live `asReadonly()` view, and type-safe function-valued data. Updaters use the committed value when processed. + +Dispose lazily created instances on owner destruction, including work started before the first effect, without constructing unused instances during cleanup. diff --git a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts index 56c218748..b2e1f9e32 100644 --- a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts +++ b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts @@ -10,11 +10,11 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AsyncBatcherOptions, AsyncBatcherState } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncBatcher}, including core configuration and Angular cleanup. @@ -125,12 +125,21 @@ export function injectAsyncBatcher( ...optionsSignal(), })) - const asyncBatcherSignal = injectLazy( + const getAsyncBatcher = injectLazy( () => new AsyncBatcher(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }, ) const methods = injectForwardMethods( - asyncBatcherSignal, + getAsyncBatcher, asyncBatcherMethods, (asyncBatcher) => { asyncBatcher.setOptions(mergedOptions()) @@ -139,31 +148,17 @@ export function injectAsyncBatcher( effect(() => { const opts = mergedOptions() - untracked(() => asyncBatcherSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = asyncBatcherSignal() - - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.cancel() - core.abort() - } - }) + untracked(() => getAsyncBatcher().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => asyncBatcherSignal().store, + () => getAsyncBatcher().store, (state) => state.isPending || state.isExecuting, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => asyncBatcherSignal().store, selector, { + const state = injectSelector(() => getAsyncBatcher().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts index c3f649f1a..13cef727b 100644 --- a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts +++ b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts @@ -10,15 +10,15 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyAsyncFunction, AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncDebouncer}, including core configuration and Angular cleanup. @@ -122,12 +122,21 @@ export function injectAsyncDebouncer( ...optionsSignal(), })) - const asyncDebouncerSignal = injectLazy( + const getAsyncDebouncer = injectLazy( () => new AsyncDebouncer(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }, ) const methods = injectForwardMethods( - asyncDebouncerSignal, + getAsyncDebouncer, asyncDebouncerMethods, (core) => { core.setOptions(mergedOptions()) @@ -136,29 +145,16 @@ export function injectAsyncDebouncer( effect(() => { const opts = mergedOptions() - untracked(() => asyncDebouncerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = asyncDebouncerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.cancel() - core.abort() - } - }) + untracked(() => getAsyncDebouncer().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => asyncDebouncerSignal().store, + () => getAsyncDebouncer().store, (state) => state.isPending || state.isExecuting, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => asyncDebouncerSignal().store, selector, { + const state = injectSelector(() => getAsyncDebouncer().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts index fc16cbfe5..eaec3051e 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts @@ -10,11 +10,11 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AsyncQueuerOptions, AsyncQueuerState } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncQueuer}, including core configuration and Angular cleanup. @@ -127,12 +127,21 @@ export function injectAsyncQueuer( ...optionsSignal(), })) - const asyncQueuerSignal = injectLazy( + const getAsyncQueuer = injectLazy( () => new AsyncQueuer(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.stop() + core.abort() + } + }, ) const methods = injectForwardMethods( - asyncQueuerSignal, + getAsyncQueuer, asyncQueuerMethods, (core) => { core.setOptions(mergedOptions()) @@ -141,29 +150,16 @@ export function injectAsyncQueuer( effect(() => { const opts = mergedOptions() - untracked(() => asyncQueuerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = asyncQueuerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.stop() - core.abort() - } - }) + untracked(() => getAsyncQueuer().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => asyncQueuerSignal().store, + () => getAsyncQueuer().store, (state) => state.isExecuting || (state.isRunning && state.pendingTick), ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => asyncQueuerSignal().store, selector, { + const state = injectSelector(() => getAsyncQueuer().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index f4af29af5..89237ead5 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -10,15 +10,15 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyAsyncFunction, AsyncRateLimiterOptions, AsyncRateLimiterState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncRateLimiter}, including core configuration and Angular cleanup. @@ -122,12 +122,20 @@ export function injectAsyncRateLimiter( ...optionsSignal(), })) - const asyncRateLimiterSignal = injectLazy( + const getAsyncRateLimiter = injectLazy( () => new AsyncRateLimiter(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.abort() + } + }, ) const methods = injectForwardMethods( - asyncRateLimiterSignal, + getAsyncRateLimiter, asyncRateLimiterMethods, (core) => { core.setOptions(mergedOptions()) @@ -136,28 +144,16 @@ export function injectAsyncRateLimiter( effect(() => { const opts = mergedOptions() - untracked(() => asyncRateLimiterSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = asyncRateLimiterSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.abort() - } - }) + untracked(() => getAsyncRateLimiter().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => asyncRateLimiterSignal().store, + () => getAsyncRateLimiter().store, (state) => state.isExecuting, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => asyncRateLimiterSignal().store, selector, { + const state = injectSelector(() => getAsyncRateLimiter().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts index 95ffe3172..9c2134077 100644 --- a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts +++ b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts @@ -10,15 +10,15 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyAsyncFunction, AsyncThrottlerOptions, AsyncThrottlerState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectAsyncThrottler}, including core configuration and Angular cleanup. @@ -122,12 +122,21 @@ export function injectAsyncThrottler( ...optionsSignal(), })) - const asyncThrottlerSignal = injectLazy( + const getAsyncThrottler = injectLazy( () => new AsyncThrottler(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + core.abort() + } + }, ) const methods = injectForwardMethods( - asyncThrottlerSignal, + getAsyncThrottler, asyncThrottlerMethods, (core) => { core.setOptions(mergedOptions()) @@ -136,29 +145,16 @@ export function injectAsyncThrottler( effect(() => { const opts = mergedOptions() - untracked(() => asyncThrottlerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = asyncThrottlerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.cancel() - core.abort() - } - }) + untracked(() => getAsyncThrottler().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => asyncThrottlerSignal().store, + () => getAsyncThrottler().store, (state) => state.isPending || state.isExecuting, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => asyncThrottlerSignal().store, selector, { + const state = injectSelector(() => getAsyncThrottler().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/batcher/injectBatcher.ts b/packages/angular-pacer/src/batcher/injectBatcher.ts index c7fd89787..182b4fc6f 100644 --- a/packages/angular-pacer/src/batcher/injectBatcher.ts +++ b/packages/angular-pacer/src/batcher/injectBatcher.ts @@ -10,11 +10,11 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { BatcherOptions, BatcherState } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectBatcher}, including core configuration and Angular cleanup. @@ -116,42 +116,34 @@ export function injectBatcher( ...optionsSignal(), })) - const batcherSignal = injectLazy( + const getBatcher = injectLazy( () => new Batcher(fn, mergedOptions()), - ) - - const methods = injectForwardMethods( - batcherSignal, - batcherMethods, (core) => { - core.setOptions(mergedOptions()) - }, - ) - - effect(() => { - const opts = mergedOptions() - untracked(() => batcherSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = batcherSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) + const opts = mergedOptions() if (opts.onUnmount) { opts.onUnmount(core) } else { core.cancel() } - }) + }, + ) + + const methods = injectForwardMethods(getBatcher, batcherMethods, (core) => { + core.setOptions(mergedOptions()) + }) + + effect(() => { + const opts = mergedOptions() + untracked(() => getBatcher().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => batcherSignal().store, + () => getBatcher().store, (state) => state.isPending, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => batcherSignal().store, selector, { + const state = injectSelector(() => getBatcher().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/debouncer/injectDebouncer.ts b/packages/angular-pacer/src/debouncer/injectDebouncer.ts index 7f8e8661e..2e4647acc 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncer.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncer.ts @@ -10,15 +10,15 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyFunction, DebouncerOptions, DebouncerState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectDebouncer}, including core configuration and Angular cleanup. @@ -120,12 +120,20 @@ export function injectDebouncer( ...optionsSignal(), })) - const debouncerSignal = injectLazy( + const getDebouncer = injectLazy( () => new Debouncer(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + } + }, ) const methods = injectForwardMethods( - debouncerSignal, + getDebouncer, debouncerMethods, (core) => { core.setOptions(mergedOptions()) @@ -134,28 +142,16 @@ export function injectDebouncer( effect(() => { const opts = mergedOptions() - untracked(() => debouncerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = debouncerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.cancel() - } - }) + untracked(() => getDebouncer().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => debouncerSignal().store, + () => getDebouncer().store, (state) => state.isPending, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => debouncerSignal().store, selector, { + const state = injectSelector(() => getDebouncer().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/queuer/injectQueuer.ts b/packages/angular-pacer/src/queuer/injectQueuer.ts index 352366c27..4fa81a686 100644 --- a/packages/angular-pacer/src/queuer/injectQueuer.ts +++ b/packages/angular-pacer/src/queuer/injectQueuer.ts @@ -10,11 +10,11 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { QueuerOptions, QueuerState } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectQueuer}, including core configuration and Angular cleanup. @@ -121,36 +121,34 @@ export function injectQueuer( ...optionsSignal(), })) - const queuerSignal = injectLazy(() => new Queuer(fn, mergedOptions())) + const getQueuer = injectLazy( + () => new Queuer(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.stop() + } + }, + ) - const methods = injectForwardMethods(queuerSignal, queuerMethods, (core) => { + const methods = injectForwardMethods(getQueuer, queuerMethods, (core) => { core.setOptions(mergedOptions()) }) effect(() => { const opts = mergedOptions() - untracked(() => queuerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = queuerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.stop() - } - }) + untracked(() => getQueuer().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => queuerSignal().store, + () => getQueuer().store, (state) => state.isRunning && state.pendingTick, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => queuerSignal().store, selector, { + const state = injectSelector(() => getQueuer().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index beab445c8..e17ff2fc5 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -9,15 +9,15 @@ import { } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyFunction, RateLimiterOptions, RateLimiterState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectRateLimiter}, including core configuration and Angular cleanup. @@ -119,12 +119,18 @@ export function injectRateLimiter( ...optionsSignal(), })) - const rateLimiterSignal = injectLazy( + const getRateLimiter = injectLazy( () => new RateLimiter(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } + }, ) const methods = injectForwardMethods( - rateLimiterSignal, + getRateLimiter, rateLimiterMethods, (core) => { core.setOptions(mergedOptions()) @@ -133,20 +139,10 @@ export function injectRateLimiter( effect(() => { const opts = mergedOptions() - untracked(() => rateLimiterSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = rateLimiterSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } - }) + untracked(() => getRateLimiter().setOptions(opts)) }) - const state = injectSelector(() => rateLimiterSignal().store, selector, { + const state = injectSelector(() => getRateLimiter().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/throttler/injectThrottler.ts b/packages/angular-pacer/src/throttler/injectThrottler.ts index ffb7f598b..16967b434 100644 --- a/packages/angular-pacer/src/throttler/injectThrottler.ts +++ b/packages/angular-pacer/src/throttler/injectThrottler.ts @@ -10,15 +10,15 @@ import { import { injectLazy } from '../utils/injectLazy' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' -import type { ReadonlySelected } from '../utils/internalTypes' import type { MaybeAccessor } from '../utils/maybeAccessor' +import type { MethodMap } from '../utils/injectForwardMethods' +import type { ReadonlySelected } from '../utils/internalTypes' import type { Signal } from '@angular/core' import type { AnyFunction, ThrottlerOptions, ThrottlerState, } from '@tanstack/pacer' -import type { MethodMap } from '../utils/injectForwardMethods' /** * Options for {@link injectThrottler}, including core configuration and Angular cleanup. @@ -120,12 +120,20 @@ export function injectThrottler( ...optionsSignal(), })) - const throttlerSignal = injectLazy( + const getThrottler = injectLazy( () => new Throttler(fn, mergedOptions()), + (core) => { + const opts = mergedOptions() + if (opts.onUnmount) { + opts.onUnmount(core) + } else { + core.cancel() + } + }, ) const methods = injectForwardMethods( - throttlerSignal, + getThrottler, throttlerMethods, (core) => { core.setOptions(mergedOptions()) @@ -134,28 +142,16 @@ export function injectThrottler( effect(() => { const opts = mergedOptions() - untracked(() => throttlerSignal().setOptions(opts)) - }) - - effect((onCleanup) => { - const core = throttlerSignal() - onCleanup(() => { - const opts = untracked(mergedOptions) - if (opts.onUnmount) { - opts.onUnmount(core) - } else { - core.cancel() - } - }) + untracked(() => getThrottler().setOptions(opts)) }) const hasPendingTasks = injectSelector( - () => throttlerSignal().store, + () => getThrottler().store, (state) => state.isPending, ) injectPendingTasksLifecycle(hasPendingTasks) - const state = injectSelector(() => throttlerSignal().store, selector, { + const state = injectSelector(() => getThrottler().store, selector, { compare: shallow, }) diff --git a/packages/angular-pacer/src/utils/injectExternalStore.ts b/packages/angular-pacer/src/utils/injectExternalStore.ts index 416e18fc9..c8a254803 100644 --- a/packages/angular-pacer/src/utils/injectExternalStore.ts +++ b/packages/angular-pacer/src/utils/injectExternalStore.ts @@ -11,14 +11,14 @@ import { import { injectOutsideZone } from './injectOutsideZone' import type { Signal, ValueEqualityFn } from '@angular/core' -interface ExternalBinding { +export interface ExternalBinding { /** Synchronous snapshot. Angular signals read here are tracked dependencies. */ readonly getSnapshot: () => T /** Omit to pause observation; changing eligibility requires a new descriptor. */ readonly subscribe?: ((notify: () => void) => () => void) | undefined } -interface ExternalStoreOptions { +export interface ExternalStoreOptions { /** Snapshot equality, defaulting to Object.is. Does not control connection identity. */ readonly equal?: ValueEqualityFn | undefined } @@ -51,24 +51,28 @@ export function injectExternalStore( assertInInjectionContext(injectExternalStore) } const owner = inject(DestroyRef) - const outsideZone = injectOutsideZone() + let destroyed = false + owner.onDestroy(() => { + destroyed = true + }) + const runOutside = injectOutsideZone() const revision = signal(0) - const requested = computed(() => outsideZone(binding)) + const requested = computed(() => runOutside(binding)) const invalidate = () => untracked(() => revision.update((n) => n + 1)) effect((onCleanup) => { // The previous subscription's cleanup may have destroyed the owner. - if (owner.destroyed) return + if (destroyed) return const current = requested() - outsideZone(() => + runOutside(() => untracked(() => { try { const unsubscribe = current.subscribe?.(invalidate) if (unsubscribe) { // subscribe() can trigger component/injector destruction before returning. // Registering cleanup afterward misses that destruction. - if (owner.destroyed) unsubscribe() - else onCleanup(() => outsideZone(unsubscribe)) + if (destroyed) unsubscribe() + else onCleanup(() => runOutside(unsubscribe)) } } finally { // Invalidate after subscribing so an early cached snapshot cannot miss @@ -82,7 +86,7 @@ export function injectExternalStore( return computed( () => { revision() - return outsideZone(() => requested().getSnapshot()) + return runOutside(() => requested().getSnapshot()) }, options?.equal ? { equal: options.equal } : undefined, ) diff --git a/packages/angular-pacer/src/utils/injectForwardMethods.ts b/packages/angular-pacer/src/utils/injectForwardMethods.ts index ae8b9e1cf..613454978 100644 --- a/packages/angular-pacer/src/utils/injectForwardMethods.ts +++ b/packages/angular-pacer/src/utils/injectForwardMethods.ts @@ -1,6 +1,5 @@ import { untracked } from '@angular/core' import { injectOutsideZone } from './injectOutsideZone' -import type { Signal } from '@angular/core' export type MethodKeys = { [K in keyof T]-?: T[K] extends (...args: Array) => unknown ? K : never @@ -26,18 +25,18 @@ export function injectForwardMethods< TMethods extends MethodKeys, >( /* Source of the methods */ - source: Signal, + source: () => TSource, /* Methods key to register */ methods: ReadonlyArray, /* Callback to run before the method. Runs untracked and outside Angular. */ before: (instance: TSource, method: TMethods) => void, ): Pick { - const outsideZone = injectOutsideZone() + const runOutside = injectOutsideZone() const result = {} as Pick for (const key of methods) { result[key] = ((...args: Array) => - outsideZone(() => + runOutside(() => untracked(() => { const instance = source() before(instance, key) diff --git a/packages/angular-pacer/src/utils/injectLazy.ts b/packages/angular-pacer/src/utils/injectLazy.ts index 196dc7cf5..a13a4e7b3 100644 --- a/packages/angular-pacer/src/utils/injectLazy.ts +++ b/packages/angular-pacer/src/utils/injectLazy.ts @@ -1,8 +1,32 @@ -import { computed, untracked } from '@angular/core' +import { DestroyRef, inject, untracked } from '@angular/core' import { injectOutsideZone } from './injectOutsideZone' -import type { Signal } from '@angular/core' -export function injectLazy(factory: () => T): Signal { - const outsideZone = injectOutsideZone() - return computed(() => outsideZone(() => untracked(factory))) +/** Lazily creates one instance and disposes it with its injection context. */ +export function injectLazy( + factory: () => T, + cleanup: (instance: T) => void, +): () => T { + const owner = inject(DestroyRef) + let destroyed = false + owner.onDestroy(() => { + destroyed = true + }) + const runOutside = injectOutsideZone() + let instance: T | undefined + const dispose = (value: T) => + runOutside(() => untracked(() => cleanup(value))) + + return () => { + if (!instance) { + const created = runOutside(() => untracked(factory)) + instance = created + // The factory may synchronously destroy its own injection context. + if (destroyed) { + dispose(created) + } else { + owner.onDestroy(() => dispose(created)) + } + } + return instance + } } diff --git a/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts b/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts index 314411e23..bf9e2c98b 100644 --- a/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts +++ b/packages/angular-pacer/tests/async-batcher/injectAsyncBatcher.test.ts @@ -267,3 +267,22 @@ describe('signal inputs', () => { expect(() => fixture.destroy()).not.toThrow() }) }) + +it('aborts work started before the first effect on owner destruction', async () => { + const completion = Promise.withResolvers() + @Component({ template: '' }) + class Host { + utility = injectAsyncBatcher(() => completion.promise, { + maxSize: 1, + }) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + const pending = fixture.componentInstance.utility.addItem('job') + const abortSignal = fixture.componentInstance.utility.getAbortSignal() + expect(abortSignal).not.toBeNull() + expect(abortSignal?.aborted).toBe(false) + fixture.destroy() + expect(abortSignal?.aborted).toBe(true) + completion.resolve() + await pending +}) diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts index 464f2acc1..a4e55665c 100644 --- a/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts +++ b/packages/angular-pacer/tests/debouncer/injectDebouncer.test.ts @@ -252,3 +252,31 @@ it('holds stability until scheduled synchronous work finishes', async () => { expect(stability.isStable).toBe(true) expect(process).toHaveBeenCalledTimes(1) }) + +it('cancels work started before the first effect on owner destruction', async () => { + vi.useFakeTimers() + const process = vi.fn((item: string) => item) + @Component({ template: '' }) + class Host { + utility = injectDebouncer(process, { wait: 10 }) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + const pending = fixture.componentInstance.utility.maybeExecute('job') + fixture.destroy() + await vi.advanceTimersByTimeAsync(10) + await pending + expect(process).not.toHaveBeenCalled() +}) + +it('does not initialize unused options or invoke cleanup on destruction', async () => { + const cleanup = vi.fn() + const options = vi.fn(() => ({ wait: 10, onUnmount: cleanup })) + @Component({ template: '' }) + class Host { + utility = injectDebouncer(() => {}, options) + } + const { fixture } = await render(Host, { detectChangesOnRender: false }) + fixture.destroy() + expect(options).not.toHaveBeenCalled() + expect(cleanup).not.toHaveBeenCalled() +}) From 250baae5279ec5b331f887378a1a8eee9068ba83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:28 -0300 Subject: [PATCH 13/18] fix(angular-pacer): run processing callbacks inside NgZone Centralize NgZone compatibility in one internal module. Keep core scheduling and store integration outside the zone while running supplied processing functions inside it. Add a minimal plain-field rendering regression in zone-based Angular and the shared zone.js test dependency. Validation: intermediate package type checks, lint, and 180 Angular tests passed. --- .changeset/angular-adapter-rework.md | 2 + package.json | 3 +- .../src/async-batcher/injectAsyncBatcher.ts | 6 +- .../async-debouncer/injectAsyncDebouncer.ts | 6 +- .../src/async-queuer/injectAsyncQueuer.ts | 6 +- .../injectAsyncRateLimiter.ts | 6 +- .../async-throttler/injectAsyncThrottler.ts | 6 +- .../src/batcher/injectBatcher.ts | 6 +- .../src/debouncer/injectDebouncer.ts | 6 +- .../angular-pacer/src/queuer/injectQueuer.ts | 6 +- .../src/rate-limiter/injectRateLimiter.ts | 6 +- .../src/throttler/injectThrottler.ts | 6 +- .../src/utils/injectExternalStore.ts | 2 +- .../src/utils/injectForwardMethods.ts | 2 +- .../angular-pacer/src/utils/injectLazy.ts | 2 +- .../src/utils/injectOutsideZone.ts | 7 - .../src/utils/injectPendingTasksLifecycle.ts | 7 +- .../src/utils/zoneCompatibility.ts | 17 + .../debouncer/injectDebouncer.zone.test.ts | 35 ++ pnpm-lock.yaml | 550 +++++++++--------- 20 files changed, 382 insertions(+), 305 deletions(-) delete mode 100644 packages/angular-pacer/src/utils/injectOutsideZone.ts create mode 100644 packages/angular-pacer/src/utils/zoneCompatibility.ts create mode 100644 packages/angular-pacer/tests/debouncer/injectDebouncer.zone.test.ts diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index cdcbb3cd3..57707f2fe 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -15,3 +15,5 @@ Avoid processing an unchanged initial source value in debounced, throttled, and Expose editable helpers as Angular writable signals with paced `set` and `update`, a live `asReadonly()` view, and type-safe function-valued data. Updaters use the committed value when processed. Dispose lazily created instances on owner destruction, including work started before the first effect, without constructing unused instances during cleanup. + +Keep core scheduling outside NgZone while running the provided processing function inside NgZone, so plain component field updates render in zone-based applications. diff --git a/package.json b/package.json index bcfe39ec1..628b288fe 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,8 @@ "tsdown": "^0.23.0", "typescript": "6.0.3", "vite": "^8.3.2", - "vitest": "^5.0.3" + "vitest": "^5.0.3", + "zone.js": "~0.16.3" }, "volta": { "node": "24.21.0" diff --git a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts index b2e1f9e32..8789805c8 100644 --- a/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts +++ b/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -70,7 +71,7 @@ export interface AngularAsyncBatcher extends Pick< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -116,6 +117,7 @@ export function injectAsyncBatcher( options: MaybeAccessor> = {}, selector: (state: AsyncBatcherState) => TSelected | {} = () => ({}), ): AngularAsyncBatcher { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) @@ -126,7 +128,7 @@ export function injectAsyncBatcher( })) const getAsyncBatcher = injectLazy( - () => new AsyncBatcher(fn, mergedOptions()), + () => new AsyncBatcher(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts index 13cef727b..742af32df 100644 --- a/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts +++ b/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -69,7 +70,7 @@ export interface AngularAsyncDebouncer< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -115,6 +116,7 @@ export function injectAsyncDebouncer( options: MaybeAccessor>, selector: (state: AsyncDebouncerState) => TSelected | {} = () => ({}), ): AngularAsyncDebouncer { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -123,7 +125,7 @@ export function injectAsyncDebouncer( })) const getAsyncDebouncer = injectLazy( - () => new AsyncDebouncer(fn, mergedOptions()), + () => new AsyncDebouncer(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts index eaec3051e..c281ea109 100644 --- a/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts +++ b/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -74,7 +75,7 @@ export interface AngularAsyncQueuer extends Pick< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -120,6 +121,7 @@ export function injectAsyncQueuer( options: MaybeAccessor> = {}, selector: (state: AsyncQueuerState) => TSelected | {} = () => ({}), ): AngularAsyncQueuer { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed(() => ({ @@ -128,7 +130,7 @@ export function injectAsyncQueuer( })) const getAsyncQueuer = injectLazy( - () => new AsyncQueuer(fn, mergedOptions()), + () => new AsyncQueuer(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts index 89237ead5..69b9e9e58 100644 --- a/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts +++ b/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -69,7 +70,7 @@ export interface AngularAsyncRateLimiter< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -115,6 +116,7 @@ export function injectAsyncRateLimiter( options: MaybeAccessor>, selector: (state: AsyncRateLimiterState) => TSelected | {} = () => ({}), ): AngularAsyncRateLimiter { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -123,7 +125,7 @@ export function injectAsyncRateLimiter( })) const getAsyncRateLimiter = injectLazy( - () => new AsyncRateLimiter(fn, mergedOptions()), + () => new AsyncRateLimiter(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts index 9c2134077..6f1d29828 100644 --- a/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts +++ b/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -69,7 +70,7 @@ export interface AngularAsyncThrottler< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -115,6 +116,7 @@ export function injectAsyncThrottler( options: MaybeAccessor>, selector: (state: AsyncThrottlerState) => TSelected | {} = () => ({}), ): AngularAsyncThrottler { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -123,7 +125,7 @@ export function injectAsyncThrottler( })) const getAsyncThrottler = injectLazy( - () => new AsyncThrottler(fn, mergedOptions()), + () => new AsyncThrottler(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/batcher/injectBatcher.ts b/packages/angular-pacer/src/batcher/injectBatcher.ts index 182b4fc6f..f91632c7f 100644 --- a/packages/angular-pacer/src/batcher/injectBatcher.ts +++ b/packages/angular-pacer/src/batcher/injectBatcher.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -63,7 +64,7 @@ export interface AngularBatcher extends Pick< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -109,6 +110,7 @@ export function injectBatcher( options: MaybeAccessor> = {}, selector: (state: BatcherState) => TSelected | {} = () => ({}), ): AngularBatcher { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed(() => ({ @@ -117,7 +119,7 @@ export function injectBatcher( })) const getBatcher = injectLazy( - () => new Batcher(fn, mergedOptions()), + () => new Batcher(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/debouncer/injectDebouncer.ts b/packages/angular-pacer/src/debouncer/injectDebouncer.ts index 2e4647acc..0e190854e 100644 --- a/packages/angular-pacer/src/debouncer/injectDebouncer.ts +++ b/packages/angular-pacer/src/debouncer/injectDebouncer.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -67,7 +68,7 @@ export interface AngularDebouncer< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -113,6 +114,7 @@ export function injectDebouncer( options: MaybeAccessor>, selector: (state: DebouncerState) => TSelected | {} = () => ({}), ): AngularDebouncer { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -121,7 +123,7 @@ export function injectDebouncer( })) const getDebouncer = injectLazy( - () => new Debouncer(fn, mergedOptions()), + () => new Debouncer(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/queuer/injectQueuer.ts b/packages/angular-pacer/src/queuer/injectQueuer.ts index 4fa81a686..1085bed7b 100644 --- a/packages/angular-pacer/src/queuer/injectQueuer.ts +++ b/packages/angular-pacer/src/queuer/injectQueuer.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -68,7 +69,7 @@ export interface AngularQueuer extends Pick< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -114,6 +115,7 @@ export function injectQueuer( options: MaybeAccessor> = {}, selector: (state: QueuerState) => TSelected | {} = () => ({}), ): AngularQueuer { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed(() => ({ @@ -122,7 +124,7 @@ export function injectQueuer( })) const getQueuer = injectLazy( - () => new Queuer(fn, mergedOptions()), + () => new Queuer(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts index e17ff2fc5..ef9689960 100644 --- a/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts +++ b/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import type { MaybeAccessor } from '../utils/maybeAccessor' import type { MethodMap } from '../utils/injectForwardMethods' @@ -66,7 +67,7 @@ export interface AngularRateLimiter< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -112,6 +113,7 @@ export function injectRateLimiter( options: MaybeAccessor>, selector: (state: RateLimiterState) => TSelected | {} = () => ({}), ): AngularRateLimiter { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -120,7 +122,7 @@ export function injectRateLimiter( })) const getRateLimiter = injectLazy( - () => new RateLimiter(fn, mergedOptions()), + () => new RateLimiter(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/throttler/injectThrottler.ts b/packages/angular-pacer/src/throttler/injectThrottler.ts index 16967b434..c77ade952 100644 --- a/packages/angular-pacer/src/throttler/injectThrottler.ts +++ b/packages/angular-pacer/src/throttler/injectThrottler.ts @@ -8,6 +8,7 @@ import { methodNames, } from '../utils/injectForwardMethods' import { injectLazy } from '../utils/injectLazy' +import { injectInsideZone } from '../utils/zoneCompatibility' import { injectSelector } from '../utils/injectSelector' import { injectPendingTasksLifecycle } from '../utils/injectPendingTasksLifecycle' import type { MaybeAccessor } from '../utils/maybeAccessor' @@ -67,7 +68,7 @@ export interface AngularThrottler< * * Accepts static options or an options factory. Factories are read lazily, and signal * dependencies update the existing core instance. Local options override provider defaults. - * Methods apply current options before executing and run outside Angular's zone. + * Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. * * Pass a selector to expose reactive core state through `state()`. Without a selector, * `state()` returns `{}`; operations remain available on the ref. @@ -113,6 +114,7 @@ export function injectThrottler( options: MaybeAccessor>, selector: (state: ThrottlerState) => TSelected | {} = () => ({}), ): AngularThrottler { + const runFn = injectInsideZone(fn) const baseOptions = injectPacerOptions() const optionsSignal = toAccessorSignal(options) const mergedOptions = computed>(() => ({ @@ -121,7 +123,7 @@ export function injectThrottler( })) const getThrottler = injectLazy( - () => new Throttler(fn, mergedOptions()), + () => new Throttler(runFn, mergedOptions()), (core) => { const opts = mergedOptions() if (opts.onUnmount) { diff --git a/packages/angular-pacer/src/utils/injectExternalStore.ts b/packages/angular-pacer/src/utils/injectExternalStore.ts index c8a254803..2e189b254 100644 --- a/packages/angular-pacer/src/utils/injectExternalStore.ts +++ b/packages/angular-pacer/src/utils/injectExternalStore.ts @@ -8,7 +8,7 @@ import { signal, untracked, } from '@angular/core' -import { injectOutsideZone } from './injectOutsideZone' +import { injectOutsideZone } from './zoneCompatibility' import type { Signal, ValueEqualityFn } from '@angular/core' export interface ExternalBinding { diff --git a/packages/angular-pacer/src/utils/injectForwardMethods.ts b/packages/angular-pacer/src/utils/injectForwardMethods.ts index 613454978..201e6ae44 100644 --- a/packages/angular-pacer/src/utils/injectForwardMethods.ts +++ b/packages/angular-pacer/src/utils/injectForwardMethods.ts @@ -1,5 +1,5 @@ import { untracked } from '@angular/core' -import { injectOutsideZone } from './injectOutsideZone' +import { injectOutsideZone } from './zoneCompatibility' export type MethodKeys = { [K in keyof T]-?: T[K] extends (...args: Array) => unknown ? K : never diff --git a/packages/angular-pacer/src/utils/injectLazy.ts b/packages/angular-pacer/src/utils/injectLazy.ts index a13a4e7b3..8c8060daa 100644 --- a/packages/angular-pacer/src/utils/injectLazy.ts +++ b/packages/angular-pacer/src/utils/injectLazy.ts @@ -1,5 +1,5 @@ import { DestroyRef, inject, untracked } from '@angular/core' -import { injectOutsideZone } from './injectOutsideZone' +import { injectOutsideZone } from './zoneCompatibility' /** Lazily creates one instance and disposes it with its injection context. */ export function injectLazy( diff --git a/packages/angular-pacer/src/utils/injectOutsideZone.ts b/packages/angular-pacer/src/utils/injectOutsideZone.ts deleted file mode 100644 index 5fe3cb530..000000000 --- a/packages/angular-pacer/src/utils/injectOutsideZone.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NgZone, inject } from '@angular/core' - -/** Keep background timers out of Angular's stability tracking. */ -export function injectOutsideZone() { - const ngZone = inject(NgZone) - return (fn: () => T): T => ngZone.runOutsideAngular(fn) -} diff --git a/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts b/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts index 8fc106c00..f7ac7ea7f 100644 --- a/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts +++ b/packages/angular-pacer/src/utils/injectPendingTasksLifecycle.ts @@ -1,16 +1,17 @@ -import { NgZone, PendingTasks, effect, inject, untracked } from '@angular/core' +import { PendingTasks, effect, inject, untracked } from '@angular/core' +import { injectInsideZone } from './zoneCompatibility' /** Tracks pending work and releases its task when the owner is destroyed. */ export function injectPendingTasksLifecycle( hasPendingTasks: () => boolean, ): void { const pendingTasks = inject(PendingTasks) - const ngZone = inject(NgZone) + const releaseInZone = injectInsideZone((release: () => void) => release()) effect((onCleanup) => { if (!hasPendingTasks()) return const release = untracked(() => pendingTasks.add()) // Keep the task held while entering the zone to avoid transient stability. - onCleanup(() => ngZone.run(release)) + onCleanup(() => releaseInZone(release)) }) } diff --git a/packages/angular-pacer/src/utils/zoneCompatibility.ts b/packages/angular-pacer/src/utils/zoneCompatibility.ts new file mode 100644 index 000000000..6d85aa76d --- /dev/null +++ b/packages/angular-pacer/src/utils/zoneCompatibility.ts @@ -0,0 +1,17 @@ +import { NgZone, inject } from '@angular/core' +import type { AnyFunction } from '@tanstack/pacer' + +// Keep NgZone compatibility here so it can be removed independently of signal +// tracking, instance ownership, and PendingTasks support for zoneless apps. + +/** Wraps a processing callback to enter Angular's zone when invoked. */ +export function injectInsideZone(fn: TFn): TFn { + const ngZone = inject(NgZone) + return ((...args: Parameters) => ngZone.run(() => fn(...args))) as TFn +} + +/** Runs background scheduling outside Angular's zone. */ +export function injectOutsideZone() { + const ngZone = inject(NgZone) + return (operation: () => T): T => ngZone.runOutsideAngular(operation) +} diff --git a/packages/angular-pacer/tests/debouncer/injectDebouncer.zone.test.ts b/packages/angular-pacer/tests/debouncer/injectDebouncer.zone.test.ts new file mode 100644 index 000000000..7bcfd4aa8 --- /dev/null +++ b/packages/angular-pacer/tests/debouncer/injectDebouncer.zone.test.ts @@ -0,0 +1,35 @@ +import 'zone.js' +import { + ChangeDetectionStrategy, + Component, + NgZone, + provideZoneChangeDetection, +} from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { expect, it } from 'vitest' +import { injectDebouncer } from '../../src/debouncer/injectDebouncer' + +it('renders a plain field changed by a delayed callback in zone-based Angular', async () => { + TestBed.configureTestingModule({ providers: [provideZoneChangeDetection()] }) + @Component({ + template: '{{ message }}', + changeDetection: ChangeDetectionStrategy.Default, + }) + class Example { + message = 'initial' + inZone = false + utility = injectDebouncer( + (message: string) => { + this.inZone = NgZone.isInAngularZone() + this.message = message + }, + { wait: 1 }, + ) + } + const fixture = TestBed.createComponent(Example) + fixture.autoDetectChanges() + fixture.componentInstance.utility.maybeExecute('updated') + await fixture.whenStable() + expect(fixture.componentInstance.inZone).toBe(true) + await expect.poll(() => fixture.nativeElement.textContent).toBe('updated') +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cb3d7bc6..fe02c678d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -298,6 +298,9 @@ importers: vitest: specifier: ^5.0.3 version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + zone.js: + specifier: ~0.16.3 + version: 0.16.3 examples/alpine/asyncBatch: dependencies: @@ -1029,25 +1032,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1063,7 +1066,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1084,25 +1087,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1118,7 +1121,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1139,25 +1142,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1173,7 +1176,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1194,25 +1197,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1228,7 +1231,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1249,25 +1252,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1283,7 +1286,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1304,25 +1307,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1338,7 +1341,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1359,25 +1362,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1393,7 +1396,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1414,25 +1417,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1448,7 +1451,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1469,25 +1472,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1503,7 +1506,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1524,25 +1527,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1558,7 +1561,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1579,25 +1582,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1613,7 +1616,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1634,25 +1637,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1668,7 +1671,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1689,25 +1692,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1723,7 +1726,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1744,25 +1747,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1778,7 +1781,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1799,25 +1802,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1833,7 +1836,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1842,37 +1845,37 @@ importers: version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) jsdom: specifier: ^30.1.1 - version: 30.1.1(@noble/hashes@2.4.0) + version: 30.1.2(@noble/hashes@2.4.0) typescript: specifier: 6.0.3 version: 6.0.3 vitest: specifier: ^5.0.3 - version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) examples/angular/injectDebouncedSignal: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1888,7 +1891,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1909,25 +1912,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1943,7 +1946,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -1964,25 +1967,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -1998,7 +2001,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2007,37 +2010,37 @@ importers: version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) jsdom: specifier: ^30.1.1 - version: 30.1.1(@noble/hashes@2.4.0) + version: 30.1.2(@noble/hashes@2.4.0) typescript: specifier: 6.0.3 version: 6.0.3 vitest: specifier: ^5.0.3 - version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) examples/angular/injectQueuedSignal: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2053,7 +2056,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2074,25 +2077,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2108,7 +2111,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2129,25 +2132,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2163,7 +2166,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2184,25 +2187,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2218,7 +2221,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2227,37 +2230,37 @@ importers: version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) jsdom: specifier: ^30.1.1 - version: 30.1.1(@noble/hashes@2.4.0) + version: 30.1.2(@noble/hashes@2.4.0) typescript: specifier: 6.0.3 version: 6.0.3 vitest: specifier: ^5.0.3 - version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) examples/angular/injectRateLimitedSignal: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2273,7 +2276,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2294,25 +2297,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2328,7 +2331,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2349,25 +2352,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2383,7 +2386,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2392,37 +2395,37 @@ importers: version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) jsdom: specifier: ^30.1.1 - version: 30.1.1(@noble/hashes@2.4.0) + version: 30.1.2(@noble/hashes@2.4.0) typescript: specifier: 6.0.3 version: 6.0.3 vitest: specifier: ^5.0.3 - version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)) + version: 5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) examples/angular/injectThrottledSignal: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2438,7 +2441,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2459,25 +2462,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2493,7 +2496,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2514,25 +2517,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2548,7 +2551,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2569,25 +2572,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2603,7 +2606,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -2624,25 +2627,25 @@ importers: dependencies: '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/forms': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@tanstack/angular-devtools': specifier: ^0.0.12 - version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15) + version: 0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/angular-pacer': specifier: workspace:* version: link:../../../packages/angular-pacer @@ -2658,7 +2661,7 @@ importers: devDependencies: '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^22.2.1 version: 22.2.1(@types/node@26.6.4)(chokidar@5.0.0) @@ -10547,13 +10550,13 @@ importers: devDependencies: '@analogjs/vite-plugin-angular': specifier: ^2.8.0 - version: 2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) + version: 2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) '@angular/build': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) + version: 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) '@angular/common': specifier: ^22.2.1 - version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) + version: 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) '@angular/compiler': specifier: ^22.2.1 version: 22.2.1 @@ -10562,16 +10565,16 @@ importers: version: 22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3) '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@angular/platform-browser': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@angular/router': specifier: ^22.2.1 - version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@testing-library/angular': specifier: ^19.5.0 - version: 19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1) + version: 19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2))(@testing-library/dom@10.4.1) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -10586,14 +10589,14 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools devDependencies: '@angular/core': specifier: ^22.2.1 - version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + version: 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) packages/ember-pacer: dependencies: @@ -10728,7 +10731,7 @@ importers: version: 0.7.1(csstype@3.2.3)(solid-js@1.9.15) '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2))(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.15)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.15)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer': specifier: workspace:* version: link:../pacer @@ -10843,7 +10846,7 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.14)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.14)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools @@ -10896,7 +10899,7 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2))(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.15)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.15)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools @@ -10934,7 +10937,7 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools @@ -10975,7 +10978,7 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.7.0 - version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) + version: 0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3)) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools @@ -20270,6 +20273,9 @@ packages: zod@4.6.5: resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + zone.js@0.16.3: + resolution: {integrity: sha512-ihXL9+vhYyEhXz4TDNpHeAOZN9FVrbog0Il64OIEI28UP/n5AaI6gsccRuOHBfx+206agzyoK527bYIO0Foy6A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -20279,14 +20285,14 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@analogjs/vite-plugin-angular@2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))': - dependencies: + ? '@analogjs/vite-plugin-angular@2.8.0(@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1))(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1))' + : dependencies: magic-string: 0.30.21 obug: 2.2.1 oxc-parser: 0.121.0(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) tinyglobby: 0.2.17 optionalDependencies: - '@angular/build': 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) + '@angular/build': 22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1) vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) transitivePeerDependencies: - '@emnapi/core' @@ -20320,7 +20326,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0)': + '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.25)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.1)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.101.0)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2202.1(chokidar@5.0.0) @@ -20354,8 +20360,8 @@ snapshots: vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0) xxhash-wasm: 1.1.0 optionalDependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) istanbul-lib-instrument: 6.0.3(supports-color@7.2.0) lmdb: 3.5.6 postcss: 8.5.25 @@ -20373,7 +20379,7 @@ snapshots: - tsx - yaml - '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0)': + '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@7.2.0))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@7.2.0)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0)))(yaml@2.9.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2202.1(chokidar@5.0.0) @@ -20407,8 +20413,8 @@ snapshots: vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.0) xxhash-wasm: 1.1.0 optionalDependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) istanbul-lib-instrument: 6.0.3(supports-color@7.2.0) lmdb: 3.5.6 postcss: 8.5.28 @@ -20426,7 +20432,7 @@ snapshots: - tsx - yaml - '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1)': + '@angular/build@22.2.1(@angular/compiler-cli@22.2.1(@angular/compiler@22.2.1)(typescript@6.0.3))(@angular/compiler@22.2.1)(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(@types/node@26.6.4)(istanbul-lib-instrument@6.0.3(supports-color@8.1.1))(jiti@2.7.0)(postcss@8.5.28)(rollup@4.60.1)(supports-color@8.1.1)(terser@5.51.2)(tslib@2.8.1)(typescript@6.0.3)(vitest@5.0.3(@types/node@26.6.4)(happy-dom@20.14.5)(jsdom@30.1.2(@noble/hashes@2.4.0))(vite@8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)))(yaml@2.9.1)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2202.1(chokidar@5.0.0) @@ -20460,8 +20466,8 @@ snapshots: vite: 8.3.0(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) xxhash-wasm: 1.1.0 optionalDependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) istanbul-lib-instrument: 6.0.3(supports-color@8.1.1) lmdb: 3.5.6 postcss: 8.5.28 @@ -20499,9 +20505,9 @@ snapshots: - '@types/node' - chokidar - '@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2)': + '@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)': dependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) rxjs: 7.8.2 tslib: 2.8.1 @@ -20528,42 +20534,44 @@ snapshots: dependencies: tslib: 2.8.1 - '@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)': + '@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: '@angular/compiler': 22.1.0 + zone.js: 0.16.3 optional: true - '@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)': + '@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: '@angular/compiler': 22.2.1 + zone.js: 0.16.3 - '@angular/forms@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2)': + '@angular/forms@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 zod: 4.6.5 - '@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))': + '@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))': dependencies: - '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) tslib: 2.8.1 - '@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2)': + '@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)) + '@angular/common': 22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/platform-browser': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)) rxjs: 7.8.2 tslib: 2.8.1 @@ -23909,9 +23917,9 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/angular-devtools@0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(csstype@3.2.3)(solid-js@1.9.15)': + '@tanstack/angular-devtools@0.0.12(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(csstype@3.2.3)(solid-js@1.9.15)': dependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@tanstack/devtools': 0.15.0(csstype@3.2.3)(solid-js@1.9.15) tslib: 2.8.1 transitivePeerDependencies: @@ -23944,7 +23952,7 @@ snapshots: '@tanstack/devtools-utils@0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2))(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.15)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3))': optionalDependencies: - '@angular/core': 22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2) + '@angular/core': 22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3) '@types/react': 19.2.18 preact: 10.29.8 react: 19.2.8 @@ -23964,7 +23972,7 @@ snapshots: '@tanstack/devtools-utils@0.7.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.14)(svelte@5.55.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3))': optionalDependencies: - '@angular/core': 22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2) + '@angular/core': 22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.3) '@types/react': 19.3.0 preact: 10.29.8 react: 19.3.0 @@ -23972,9 +23980,9 @@ snapshots: svelte: 5.55.1(@typescript-eslint/types@8.66.0) vue: 3.5.43(typescript@6.0.3) - '@tanstack/devtools-utils@0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3))': + '@tanstack/devtools-utils@0.7.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@types/react@19.3.0)(preact@10.29.8)(react@19.3.0)(solid-js@1.9.15)(svelte@5.57.1(@typescript-eslint/types@8.66.0))(vue@3.5.43(typescript@6.0.3))': optionalDependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) '@types/react': 19.3.0 preact: 10.29.8 react: 19.3.0 @@ -24182,10 +24190,10 @@ snapshots: vue: 3.5.43(typescript@6.0.3) vue-demi: 0.14.10(vue@3.5.43(typescript@6.0.3)) - '@testing-library/angular@19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1)': + '@testing-library/angular@19.5.0(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/router@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2))(@testing-library/dom@10.4.1)': dependencies: - '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2) - '@angular/router': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)))(rxjs@7.8.2) + '@angular/core': 22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3) + '@angular/router': 22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.1(@angular/common@22.2.1(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.1(@angular/compiler@22.2.1)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2) '@testing-library/dom': 10.4.1 tslib: 2.8.1 @@ -24816,7 +24824,6 @@ snapshots: magic-string: 1.4.2 optionalDependencies: vite: 8.3.2(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) - optional: true '@vitest/spy@5.0.3': {} @@ -25842,7 +25849,7 @@ snapshots: data-urls@8.0.0(@noble/hashes@2.4.0): dependencies: whatwg-mimetype: 5.0.0 - whatwg-url: 17.1.2(@noble/hashes@2.4.0) + whatwg-url: 17.2.0(@noble/hashes@2.4.0) transitivePeerDependencies: - '@noble/hashes' @@ -30067,7 +30074,6 @@ snapshots: jsdom: 30.1.2(@noble/hashes@2.4.0) transitivePeerDependencies: - msw - optional: true volar-service-html@0.0.71(@volar/language-service@2.4.28(typescript@5.9.3)): dependencies: @@ -30428,3 +30434,5 @@ snapshots: zod@4.4.3: {} zod@4.6.5: {} + + zone.js@0.16.3: {} From a2a4d0b70656eee00b0395157fdef2064d05894f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:29 -0300 Subject: [PATCH 14/18] feat(angular-pacer): support injected options factories Accept static defaults or an options factory in providePacerOptions. Use a provider factory for injectable defaults, preserving component-scoped provider support. Validation: intermediate package type checks, lint, and 181 Angular tests passed. --- .changeset/angular-adapter-rework.md | 2 ++ .../src/provider/providePacerOptions.ts | 13 ++++---- .../provider/providePacerOptions.test.ts | 30 +++++++++++++++++-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index 57707f2fe..31f050a22 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -17,3 +17,5 @@ Expose editable helpers as Angular writable signals with paced `set` and `update Dispose lazily created instances on owner destruction, including work started before the first effect, without constructing unused instances during cleanup. Keep core scheduling outside NgZone while running the provided processing function inside NgZone, so plain component field updates render in zone-based applications. + +Allow `providePacerOptions` to accept a defaults factory evaluated in its injection context. diff --git a/packages/angular-pacer/src/provider/providePacerOptions.ts b/packages/angular-pacer/src/provider/providePacerOptions.ts index 2eb4a16ef..348118aea 100644 --- a/packages/angular-pacer/src/provider/providePacerOptions.ts +++ b/packages/angular-pacer/src/provider/providePacerOptions.ts @@ -52,7 +52,7 @@ const PACER_OPTIONS = new InjectionToken( * provider supplies the defaults; local utility options take precedence. A nested * provider replaces the outer defaults rather than merging scopes. * - * @param options Partial default options grouped by utility. + * @param options Partial default options grouped by utility, or an injection-context factory. * @returns An Angular provider for the supplied defaults. * * @example @@ -64,11 +64,12 @@ const PACER_OPTIONS = new InjectionToken( * } * ``` */ -export function providePacerOptions(options: PacerProviderOptions): Provider { - return { - provide: PACER_OPTIONS, - useValue: options, - } +export function providePacerOptions( + options: PacerProviderOptions | (() => PacerProviderOptions), +): Provider { + return typeof options === 'function' + ? { provide: PACER_OPTIONS, useFactory: options } + : { provide: PACER_OPTIONS, useValue: options } } /** diff --git a/packages/angular-pacer/tests/provider/providePacerOptions.test.ts b/packages/angular-pacer/tests/provider/providePacerOptions.test.ts index 9a6a36ce9..5e1f46c0f 100644 --- a/packages/angular-pacer/tests/provider/providePacerOptions.test.ts +++ b/packages/angular-pacer/tests/provider/providePacerOptions.test.ts @@ -1,10 +1,20 @@ -import { Component, computed, input, signal } from '@angular/core' +import { + Component, + InjectionToken, + computed, + inject, + input, + signal, +} from '@angular/core' import { TestBed } from '@angular/core/testing' import { Debouncer } from '@tanstack/pacer/debouncer' import { render } from '@testing-library/angular' import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest' import { injectDebouncer } from '../../src/debouncer/injectDebouncer' -import { providePacerOptions } from '../../src/provider/providePacerOptions' +import { + injectPacerOptions, + providePacerOptions, +} from '../../src/provider/providePacerOptions' it('types provider cleanup callbacks as core instances', () => { TestBed.configureTestingModule({ @@ -180,3 +190,19 @@ describe('provider defaults', () => { }, ) }) + +it('resolves a defaults factory in its provider injection context', () => { + const wait = new InjectionToken('wait') + const factory = vi.fn(() => ({ debouncer: { wait: inject(wait) } })) + TestBed.configureTestingModule({ + providers: [{ provide: wait, useValue: 25 }, providePacerOptions(factory)], + }) + expect(factory).not.toHaveBeenCalled() + expect(TestBed.runInInjectionContext(injectPacerOptions)).toEqual({ + debouncer: { wait: 25 }, + }) + expect(TestBed.runInInjectionContext(injectPacerOptions)).toEqual({ + debouncer: { wait: 25 }, + }) + expect(factory).toHaveBeenCalledOnce() +}) From f97ec762d0f8bc0dbcff952edd4ce7fb807b40c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sat, 10 Oct 2026 21:01:30 -0300 Subject: [PATCH 15/18] refactor(angular-pacer): align reflected helper names Rename reflected Computed helpers to Value and align Queuer Items filenames with their exported APIs. Migrate examples and package names, guides, navigation, generated references, and the consolidated minor changeset. Validation: workspace pnpm test passed all 766 tasks; the four migrated examples passed all eight browser tests. Each preceding commit passed its package type checks, lint, and Angular tests. --- .changeset/angular-adapter-rework.md | 14 ++- docs/config.json | 32 ++--- docs/framework/angular/guides/debouncing.md | 2 +- docs/framework/angular/guides/queuing.md | 8 +- .../framework/angular/guides/rate-limiting.md | 2 +- docs/framework/angular/guides/throttling.md | 2 +- docs/framework/angular/quick-start.md | 52 +++++++- .../reference/functions/injectAsyncBatcher.md | 6 +- .../functions/injectAsyncDebouncer.md | 6 +- .../reference/functions/injectAsyncQueuer.md | 6 +- .../functions/injectAsyncQueuerItems.md | 4 +- .../functions/injectAsyncRateLimiter.md | 6 +- .../functions/injectAsyncThrottler.md | 6 +- .../reference/functions/injectBatcher.md | 6 +- .../functions/injectDebouncedSignal.md | 12 +- ...cedComputed.md => injectDebouncedValue.md} | 20 ++-- .../reference/functions/injectDebouncer.md | 6 +- .../reference/functions/injectPacerOptions.md | 2 +- .../reference/functions/injectQueuedSignal.md | 10 +- ...QueuedComputed.md => injectQueuedValue.md} | 20 ++-- .../reference/functions/injectQueuer.md | 6 +- .../reference/functions/injectQueuerItems.md | 4 +- .../functions/injectRateLimitedSignal.md | 12 +- ...dComputed.md => injectRateLimitedValue.md} | 22 ++-- .../reference/functions/injectRateLimiter.md | 6 +- .../functions/injectThrottledSignal.md | 8 +- ...ledComputed.md => injectThrottledValue.md} | 22 ++-- .../reference/functions/injectThrottler.md | 6 +- .../functions/providePacerOptions.md | 5 +- docs/framework/angular/reference/index.md | 16 +-- .../interfaces/AngularAsyncBatcher.md | 4 +- .../interfaces/AngularAsyncBatcherOptions.md | 4 +- .../interfaces/AngularAsyncDebouncer.md | 4 +- .../AngularAsyncDebouncerOptions.md | 4 +- .../interfaces/AngularAsyncQueuer.md | 4 +- .../interfaces/AngularAsyncQueuerItems.md | 10 +- .../interfaces/AngularAsyncQueuerOptions.md | 4 +- .../interfaces/AngularAsyncRateLimiter.md | 4 +- .../AngularAsyncRateLimiterOptions.md | 4 +- .../interfaces/AngularAsyncThrottler.md | 4 +- .../AngularAsyncThrottlerOptions.md | 4 +- .../reference/interfaces/AngularBatcher.md | 4 +- .../interfaces/AngularBatcherOptions.md | 4 +- .../reference/interfaces/AngularDebouncer.md | 4 +- .../interfaces/AngularDebouncerOptions.md | 4 +- .../interfaces/AngularDebouncerSignal.md | 97 +++++++++++---- ...erComputed.md => AngularDebouncerValue.md} | 14 +-- .../reference/interfaces/AngularQueuer.md | 4 +- .../interfaces/AngularQueuerComputed.md | 63 ---------- .../interfaces/AngularQueuerItems.md | 10 +- .../interfaces/AngularQueuerOptions.md | 4 +- .../interfaces/AngularQueuerSignal.md | 97 ++++++++++++--- .../interfaces/AngularQueuerValue.md | 63 ++++++++++ .../interfaces/AngularRateLimiter.md | 4 +- .../interfaces/AngularRateLimiterOptions.md | 4 +- .../interfaces/AngularRateLimiterSignal.md | 97 +++++++++++---- ...Computed.md => AngularRateLimiterValue.md} | 14 +-- .../reference/interfaces/AngularThrottler.md | 4 +- .../interfaces/AngularThrottlerOptions.md | 4 +- .../interfaces/AngularThrottlerSignal.md | 111 ++++++++++++++---- ...erComputed.md => AngularThrottlerValue.md} | 14 +-- .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 2 +- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 2 +- .../src/app/app.ts | 8 +- .../src/app/inputapp.ts | 4 +- .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 2 +- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../injectQueuedComputed/src/app/app.spec.ts | 55 --------- .../injectQueuedComputed/src/app/app.ts | 30 ----- .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 2 +- .../src/app/app.routes.ts | 0 .../injectQueuedValue/src/app/app.spec.ts | 55 +++++++++ .../angular/injectQueuedValue/src/app/app.ts | 30 +++++ .../src/app/inputapp.ts | 0 .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 2 +- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 2 +- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 2 +- .../src/app/app.ts | 8 +- .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 50 ++++---- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../README.md | 0 .../angular.json | 0 .../eslint.config.js | 0 .../package.json | 2 +- .../public/favicon.ico | Bin .../src/app/app.config.ts | 0 .../src/app/app.css | 0 .../src/app/app.html | 2 +- .../src/app/app.routes.ts | 0 .../src/app/app.spec.ts | 2 +- .../src/app/app.ts | 8 +- .../src/index.html | 0 .../src/main.ts | 0 .../src/styles.css | 0 .../tests/e2e/smoke.spec.mts | 50 ++++---- .../tsconfig.app.json | 0 .../tsconfig.json | 0 .../tsconfig.spec.json | 0 .../angular-pacer/src/async-queuer/index.ts | 2 +- ...euedItems.ts => injectAsyncQueuerItems.ts} | 2 +- packages/angular-pacer/src/debouncer/index.ts | 2 +- ...cedComputed.ts => injectDebouncedValue.ts} | 16 +-- packages/angular-pacer/src/queuer/index.ts | 4 +- ...QueuedComputed.ts => injectQueuedValue.ts} | 16 +-- ...ectQueuedItems.ts => injectQueuerItems.ts} | 2 +- .../angular-pacer/src/rate-limiter/index.ts | 2 +- ...dComputed.ts => injectRateLimitedValue.ts} | 16 +-- packages/angular-pacer/src/throttler/index.ts | 2 +- ...ledComputed.ts => injectThrottledValue.ts} | 16 +-- ...test.ts => injectAsyncQueuerItems.test.ts} | 2 +- ...d.test.ts => injectDebouncedValue.test.ts} | 12 +- ...uted.test.ts => injectQueuedValue.test.ts} | 10 +- ...tems.test.ts => injectQueuerItems.test.ts} | 2 +- ...test.ts => injectRateLimitedValue.test.ts} | 8 +- ...d.test.ts => injectThrottledValue.test.ts} | 8 +- pnpm-lock.yaml | 64 +++++----- 155 files changed, 895 insertions(+), 614 deletions(-) rename docs/framework/angular/reference/functions/{injectDebouncedComputed.md => injectDebouncedValue.md} (68%) rename docs/framework/angular/reference/functions/{injectQueuedComputed.md => injectQueuedValue.md} (63%) rename docs/framework/angular/reference/functions/{injectRateLimitedComputed.md => injectRateLimitedValue.md} (59%) rename docs/framework/angular/reference/functions/{injectThrottledComputed.md => injectThrottledValue.md} (60%) rename docs/framework/angular/reference/interfaces/{AngularDebouncerComputed.md => AngularDebouncerValue.md} (61%) delete mode 100644 docs/framework/angular/reference/interfaces/AngularQueuerComputed.md create mode 100644 docs/framework/angular/reference/interfaces/AngularQueuerValue.md rename docs/framework/angular/reference/interfaces/{AngularRateLimiterComputed.md => AngularRateLimiterValue.md} (60%) rename docs/framework/angular/reference/interfaces/{AngularThrottlerComputed.md => AngularThrottlerValue.md} (61%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/README.md (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/angular.json (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/eslint.config.js (100%) rename examples/angular/{injectQueuedComputed => injectDebouncedValue}/package.json (94%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/public/favicon.ico (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.config.ts (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.css (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.html (95%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.routes.ts (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.spec.ts (97%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/app.ts (72%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/app/inputapp.ts (67%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/index.html (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/main.ts (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/src/styles.css (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/tests/e2e/smoke.spec.mts (95%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/tsconfig.app.json (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/tsconfig.json (100%) rename examples/angular/{injectDebouncedComputed => injectDebouncedValue}/tsconfig.spec.json (100%) delete mode 100644 examples/angular/injectQueuedComputed/src/app/app.spec.ts delete mode 100644 examples/angular/injectQueuedComputed/src/app/app.ts rename examples/angular/{injectQueuedComputed => injectQueuedValue}/README.md (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/angular.json (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/eslint.config.js (100%) rename examples/angular/{injectThrottledComputed => injectQueuedValue}/package.json (94%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/public/favicon.ico (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/app/app.config.ts (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/app/app.css (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/app/app.html (97%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/app/app.routes.ts (100%) create mode 100644 examples/angular/injectQueuedValue/src/app/app.spec.ts create mode 100644 examples/angular/injectQueuedValue/src/app/app.ts rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/app/inputapp.ts (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/index.html (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/main.ts (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/src/styles.css (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/tests/e2e/smoke.spec.mts (96%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/tsconfig.app.json (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/tsconfig.json (100%) rename examples/angular/{injectQueuedComputed => injectQueuedValue}/tsconfig.spec.json (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/README.md (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/angular.json (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/eslint.config.js (100%) rename examples/angular/{injectDebouncedComputed => injectRateLimitedValue}/package.json (94%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/public/favicon.ico (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/app/app.config.ts (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/app/app.css (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/app/app.html (97%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/app/app.routes.ts (100%) rename examples/angular/{injectThrottledComputed => injectRateLimitedValue}/src/app/app.spec.ts (96%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/app/app.ts (80%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/index.html (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/main.ts (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/src/styles.css (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/tests/e2e/smoke.spec.mts (52%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/tsconfig.app.json (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/tsconfig.json (100%) rename examples/angular/{injectRateLimitedComputed => injectRateLimitedValue}/tsconfig.spec.json (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/README.md (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/angular.json (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/eslint.config.js (100%) rename examples/angular/{injectRateLimitedComputed => injectThrottledValue}/package.json (94%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/public/favicon.ico (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/app/app.config.ts (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/app/app.css (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/app/app.html (95%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/app/app.routes.ts (100%) rename examples/angular/{injectRateLimitedComputed => injectThrottledValue}/src/app/app.spec.ts (96%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/app/app.ts (62%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/index.html (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/main.ts (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/src/styles.css (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/tests/e2e/smoke.spec.mts (55%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/tsconfig.app.json (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/tsconfig.json (100%) rename examples/angular/{injectThrottledComputed => injectThrottledValue}/tsconfig.spec.json (100%) rename packages/angular-pacer/src/async-queuer/{injectAsyncQueuedItems.ts => injectAsyncQueuerItems.ts} (100%) rename packages/angular-pacer/src/debouncer/{injectDebouncedComputed.ts => injectDebouncedValue.ts} (87%) rename packages/angular-pacer/src/queuer/{injectQueuedComputed.ts => injectQueuedValue.ts} (85%) rename packages/angular-pacer/src/queuer/{injectQueuedItems.ts => injectQueuerItems.ts} (100%) rename packages/angular-pacer/src/rate-limiter/{injectRateLimitedComputed.ts => injectRateLimitedValue.ts} (86%) rename packages/angular-pacer/src/throttler/{injectThrottledComputed.ts => injectThrottledValue.ts} (87%) rename packages/angular-pacer/tests/async-queuer/{injectAsyncQueuedItems.test.ts => injectAsyncQueuerItems.test.ts} (98%) rename packages/angular-pacer/tests/debouncer/{injectDebouncedComputed.test.ts => injectDebouncedValue.test.ts} (88%) rename packages/angular-pacer/tests/queuer/{injectQueuedComputed.test.ts => injectQueuedValue.test.ts} (86%) rename packages/angular-pacer/tests/queuer/{injectQueuedItems.test.ts => injectQueuerItems.test.ts} (97%) rename packages/angular-pacer/tests/rate-limiter/{injectRateLimitedComputed.test.ts => injectRateLimitedValue.test.ts} (84%) rename packages/angular-pacer/tests/throttler/{injectThrottledComputed.test.ts => injectThrottledValue.test.ts} (87%) diff --git a/.changeset/angular-adapter-rework.md b/.changeset/angular-adapter-rework.md index 31f050a22..56a88c85b 100644 --- a/.changeset/angular-adapter-rework.md +++ b/.changeset/angular-adapter-rework.md @@ -10,12 +10,20 @@ per-utility Angular behavior tests. Support Angular 20 and up, including all Angular LTS versions, using stable public signal, effect, and pending-task APIs. -Avoid processing an unchanged initial source value in debounced, throttled, and rate-limited reflected helpers, preserving stability and capacity for real changes. +Expose editable helpers as Angular writable signals with paced `set` and `update` +methods and a live `asReadonly()` view. Function-valued replacements remain data, +while updater callbacks run against the committed value when processed. + +Dispose lazily created instances on owner destruction even when work starts +before the first effect runs, without creating unused instances during cleanup. -Expose editable helpers as Angular writable signals with paced `set` and `update`, a live `asReadonly()` view, and type-safe function-valued data. Updaters use the committed value when processed. +Keep callable signal values type-safe by allowing raw utility writes only as +updaters returning those values; `set` continues to accept function-valued data. -Dispose lazily created instances on owner destruction, including work started before the first effect, without constructing unused instances during cleanup. +Avoid processing an unchanged initial source value in debounced, throttled, and rate-limited reflected helpers, preserving stability and capacity for real changes. Keep core scheduling outside NgZone while running the provided processing function inside NgZone, so plain component field updates render in zone-based applications. Allow `providePacerOptions` to accept a defaults factory evaluated in its injection context. + +Rename reflected helpers from `*Computed` to `*Value`, align Queuer Items source filenames with their APIs, and update examples and guides. diff --git a/docs/config.json b/docs/config.json index 9468cc057..9ee11d263 100644 --- a/docs/config.json +++ b/docs/config.json @@ -969,8 +969,8 @@ "to": "framework/angular/reference/functions/injectDebouncedSignal" }, { - "label": "injectDebouncedComputed", - "to": "framework/angular/reference/functions/injectDebouncedComputed" + "label": "injectDebouncedValue", + "to": "framework/angular/reference/functions/injectDebouncedValue" }, { "label": "injectAsyncDebouncer", @@ -1444,8 +1444,8 @@ "to": "framework/angular/reference/functions/injectThrottledSignal" }, { - "label": "injectThrottledComputed", - "to": "framework/angular/reference/functions/injectThrottledComputed" + "label": "injectThrottledValue", + "to": "framework/angular/reference/functions/injectThrottledValue" }, { "label": "injectAsyncThrottler", @@ -1895,8 +1895,8 @@ "to": "framework/angular/reference/functions/injectRateLimitedSignal" }, { - "label": "injectRateLimitedComputed", - "to": "framework/angular/reference/functions/injectRateLimitedComputed" + "label": "injectRateLimitedValue", + "to": "framework/angular/reference/functions/injectRateLimitedValue" }, { "label": "injectAsyncRateLimiter", @@ -2358,8 +2358,8 @@ "to": "framework/angular/reference/functions/injectQueuerItems" }, { - "label": "injectQueuedComputed", - "to": "framework/angular/reference/functions/injectQueuedComputed" + "label": "injectQueuedValue", + "to": "framework/angular/reference/functions/injectQueuedValue" }, { "label": "injectQueuedSignal", @@ -3174,8 +3174,8 @@ "to": "framework/angular/examples/injectDebouncedSignal" }, { - "label": "injectDebouncedComputed", - "to": "framework/angular/examples/injectDebouncedComputed" + "label": "injectDebouncedValue", + "to": "framework/angular/examples/injectDebouncedValue" }, { "label": "injectAsyncDebouncer", @@ -3487,8 +3487,8 @@ "to": "framework/angular/examples/injectThrottledSignal" }, { - "label": "injectThrottledComputed", - "to": "framework/angular/examples/injectThrottledComputed" + "label": "injectThrottledValue", + "to": "framework/angular/examples/injectThrottledValue" }, { "label": "injectAsyncThrottler", @@ -3792,8 +3792,8 @@ "to": "framework/angular/examples/injectRateLimitedSignal" }, { - "label": "injectRateLimitedComputed", - "to": "framework/angular/examples/injectRateLimitedComputed" + "label": "injectRateLimitedValue", + "to": "framework/angular/examples/injectRateLimitedValue" }, { "label": "injectAsyncRateLimiter", @@ -4077,8 +4077,8 @@ "to": "framework/angular/examples/injectQueuerItems" }, { - "label": "injectQueuedComputed", - "to": "framework/angular/examples/injectQueuedComputed" + "label": "injectQueuedValue", + "to": "framework/angular/examples/injectQueuedValue" }, { "label": "injectQueuedSignal", diff --git a/docs/framework/angular/guides/debouncing.md b/docs/framework/angular/guides/debouncing.md index 4e2c8dae6..2b23ed9f4 100644 --- a/docs/framework/angular/guides/debouncing.md +++ b/docs/framework/angular/guides/debouncing.md @@ -47,7 +47,7 @@ Choose another utility when: ## Choose an API -- `injectDebouncedSignal` or `injectDebouncedComputed` for delayed signals +- `injectDebouncedSignal` or `injectDebouncedValue` for delayed signals - `injectDebouncer` for lifecycle methods and selected state Use the instance's `maybeExecute` method for event handlers, and state or value helpers for delayed UI state. Keep the instance for cancellation, flushing, selected state, and dynamic options. diff --git a/docs/framework/angular/guides/queuing.md b/docs/framework/angular/guides/queuing.md index e5183c90d..5c56afd78 100644 --- a/docs/framework/angular/guides/queuing.md +++ b/docs/framework/angular/guides/queuing.md @@ -43,7 +43,7 @@ Choose another utility when: ## Choose an API -- `injectQueuedComputed` to queue observed source values +- `injectQueuedValue` to queue observed source values - `injectQueuedSignal` to queue individual `set` and `update` operations - `injectQueuerItems` for a signal of pending queue items - `injectQueuer` for direct queue lifecycle and ordering control @@ -84,11 +84,11 @@ Pass a third-argument selector to expose counters or running state through `queu ## Queued values -`injectQueuedComputed` returns the last processed source value. It queues the initial value and subsequent values observed by Angular effects. Multiple source writes before an effect runs are observed as one change. +`injectQueuedValue` returns the last processed source value. It queues the initial value and subsequent values observed by Angular effects. Multiple source writes before an effect runs are observed as one change. ```ts const source = signal('') -const queued = injectQueuedComputed(source, { wait: 500 }) +const queued = injectQueuedValue(source, { wait: 500 }) source.set('next') console.log(queued()) // Last processed value. ``` @@ -103,7 +103,7 @@ count.queuer.flush() console.log(count()) // 3 ``` -Both helpers accept a selector for their attached `queuer.state()`. The editable helper queues callbacks, so its item-dependent options receive callbacks rather than the displayed value. +Both helpers accept a selector for their attached `queuer.state()`. The editable helper queues replacement values or updater functions. Updaters are evaluated against the committed value when processed, matching the delayed setter behavior of React and Solid. ## Ordering items diff --git a/docs/framework/angular/guides/rate-limiting.md b/docs/framework/angular/guides/rate-limiting.md index 9e07ce713..a5bd1334d 100644 --- a/docs/framework/angular/guides/rate-limiting.md +++ b/docs/framework/angular/guides/rate-limiting.md @@ -81,7 +81,7 @@ Use a sliding window when capacity should return gradually rather than all at on ## Choose an API -- `injectRateLimitedSignal` or `injectRateLimitedComputed` for signals +- `injectRateLimitedSignal` or `injectRateLimitedValue` for signals - `injectRateLimiter` for capacity helpers and selected state Call `maybeExecute()` for quota-controlled operations. Use state or value helpers for UI updates, and the instance for capacity helpers and rejection state. diff --git a/docs/framework/angular/guides/throttling.md b/docs/framework/angular/guides/throttling.md index a5359f4e0..bdfe3908c 100644 --- a/docs/framework/angular/guides/throttling.md +++ b/docs/framework/angular/guides/throttling.md @@ -44,7 +44,7 @@ Choose another utility when: ## Choose an API -- `injectThrottledSignal` or `injectThrottledComputed` for throttled signals +- `injectThrottledSignal` or `injectThrottledValue` for throttled signals - `injectThrottler` for lifecycle methods and selected state Use instance methods for event handlers, and state or value helpers for rate-controlled UI state. diff --git a/docs/framework/angular/quick-start.md b/docs/framework/angular/quick-start.md index 74f920771..06dd9cde3 100644 --- a/docs/framework/angular/quick-start.md +++ b/docs/framework/angular/quick-start.md @@ -7,6 +7,8 @@ redirectFrom: TanStack Pacer controls when your functions run. The Angular adapter, `@tanstack/angular-pacer`, wraps each Pacer utility in an `inject*` function. Call it in an injection context, such as a component field initializer. The utility cleans up when the component is destroyed and exposes the state you select as a signal. +Angular 20 and up are supported, including all Angular LTS versions. + This page starts with a debounced search input, then covers the patterns most apps need next. ## Installation @@ -17,6 +19,12 @@ npm install @tanstack/angular-pacer The adapter re-exports everything from `@tanstack/pacer`, so you do not need to install the core package. See [Installation](../../installation.md) for other package managers. +Feature entry points also export their corresponding core utilities alongside the Angular APIs: + +```ts +import { Debouncer, injectDebouncer } from '@tanstack/angular-pacer/debouncer' +``` + ## Your first debouncer The component below is complete. The input updates on every keystroke. `debouncedQuery()` updates 500 ms after the user stops typing. @@ -52,7 +60,11 @@ Every utility comes in several shapes. They share one engine and differ in what | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | | `injectDebouncedValue` | A signal of the debounced value, with a `debouncer` property | You already have a signal and want a lagging copy | | `injectDebouncedSignal` | A signal with a debounced `set` and a `debouncer` property | You want `signal()` with a debounced setter | -| `injectDebouncer` | The debouncer instance | You need `maybeExecute`, `flush`, `cancel`, or reactive state | +| `injectDebouncer` | A ref with methods and selected state | You need `maybeExecute`, `flush`, `cancel`, or reactive state | + +Editable helpers return an Angular `WritableSignal` with paced `set` and `update` methods, a live `asReadonly()` view, and an attached utility ref. `set` accepts a replacement value; `update` accepts an updater that runs against the committed value when the paced write executes. This applies to `injectDebouncedSignal`, `injectThrottledSignal`, `injectRateLimitedSignal`, and `injectQueuedSignal`. + +The corresponding `Value` helpers observe a source signal or accessor. `injectQueuerItems` and `injectAsyncQueuerItems` expose pending queue items with a `queuer` attribute and an `addItem` shortcut. The other utilities follow the same naming pattern: @@ -70,7 +82,7 @@ Not sure which utility you need? Read [Which Pacer Utility Should I Choose?](../ ### Control the debouncer directly -`injectDebouncer` returns the instance. Call `maybeExecute` from your event handler, and use `flush` or `cancel` when the user acts before the timer fires. +`injectDebouncer` returns a ref with stable methods and selected state. Call `maybeExecute` from your event handler, and use `flush` or `cancel` when the user acts before the timer fires. ```ts import { Component, signal } from '@angular/core' @@ -108,6 +120,8 @@ export class DraftEditorComponent { The third argument is a selector. `saver.state` is a signal, so call `saver.state()` to read it. Without a selector, it returns `{}` and never changes. Select only the fields your template reads, so other state changes do not trigger change detection. +Value, signal, and items helpers also accept a selector as their third argument. Their selected state is available on the attached utility ref. + Each utility's guide lists the state fields it exposes. ### Make options reactive @@ -129,9 +143,7 @@ export class SearchComponent { } ``` -Property getters such as `get wait() { return this.wait() }` work too. Writing `{ wait: this.wait() }` without the factory or getter reads the signal once and never updates. - -With a factory or getters, the adapter waits to create the utility until Angular binds the component inputs. When a signal changes, the adapter updates the same utility. Pending work and state survive. A changed `wait` applies to the next call. It does not reschedule a timer that is already running. Setting `enabled` to `false` cancels pending work. `key`, `initialState`, and `initialItems` apply only when the utility is created. +Passing `{ wait: this.wait() }` instead uses the value at the time of the call. ### Run async work @@ -255,6 +267,8 @@ export const appConfig: ApplicationConfig = { } ``` +You can also pass a factory to `providePacerOptions`; it runs in the provider injection context and can call `inject()`. Continue to pass required options, such as `wait`, or `limit` and `window`, when creating a utility. + To share options between specific utilities instead, define them once with an option helper. Helpers such as `debouncerOptions` return the object you pass in, typed for that utility: ```ts @@ -277,6 +291,34 @@ readonly saver = injectDebouncer(saveDraft, { }) ``` +## Testing + +The adapter integrates with Angular's pending tasks so `fixture.whenStable()` waits for scheduled and asynchronous Pacer work. Wait for stability before checking the rendered result: + +```ts +import { Component } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { injectDebouncedSignal } from '@tanstack/angular-pacer' + +@Component({ template: '

{{ value() }}

' }) +class ExampleComponent { + readonly value = injectDebouncedSignal('initial', { wait: 50 }) +} + +it('renders the debounced value', async () => { + const fixture = TestBed.createComponent(ExampleComponent) + fixture.detectChanges() + + fixture.componentInstance.value.set('updated') + fixture.detectChanges() + await fixture.whenStable() + + expect(fixture.nativeElement.textContent).toContain('updated') +}) +``` + +This example uses real timers. When using fake timers, advance them to complete the scheduled work before awaiting stability. + ## Set up devtools Install the devtools packages: diff --git a/docs/framework/angular/reference/functions/injectAsyncBatcher.md b/docs/framework/angular/reference/functions/injectAsyncBatcher.md index 64184ebbc..cea4457f5 100644 --- a/docs/framework/angular/reference/functions/injectAsyncBatcher.md +++ b/docs/framework/angular/reference/functions/injectAsyncBatcher.md @@ -9,7 +9,7 @@ title: injectAsyncBatcher function injectAsyncBatcher(fn, options?): AngularAsyncBatcher; ``` -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:98](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L98) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:99](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L99) Creates and manages an Angular AsyncBatcher in the current injection context. @@ -19,7 +19,7 @@ Collects items and processes each batch asynchronously when its size or delay th Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectAsyncBatcher( selector): AngularAsyncBatcher; ``` -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:109](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L109) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:110](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L110) Creates an Angular AsyncBatcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md index ef4b0b1f2..1888bd8e1 100644 --- a/docs/framework/angular/reference/functions/injectAsyncDebouncer.md +++ b/docs/framework/angular/reference/functions/injectAsyncDebouncer.md @@ -9,7 +9,7 @@ title: injectAsyncDebouncer function injectAsyncDebouncer(fn, options): AngularAsyncDebouncer; ``` -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L97) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:98](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L98) Creates and manages an Angular AsyncDebouncer in the current injection context. @@ -19,7 +19,7 @@ Waits until calls stop for the configured delay, then runs the latest asynchrono Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectAsyncDebouncer( selector): AngularAsyncDebouncer; ``` -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L108) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:109](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L109) Creates an Angular AsyncDebouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuer.md b/docs/framework/angular/reference/functions/injectAsyncQueuer.md index 7c32f62cf..040367f97 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuer.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuer.md @@ -9,7 +9,7 @@ title: injectAsyncQueuer function injectAsyncQueuer(fn, options?): AngularAsyncQueuer; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:102](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L102) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:103](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L103) Creates and manages an Angular AsyncQueuer in the current injection context. @@ -19,7 +19,7 @@ Processes queued items asynchronously with configurable pacing and concurrency. Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectAsyncQueuer( selector): AngularAsyncQueuer; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:113](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L113) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:114](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L114) Creates an Angular AsyncQueuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md index 84995b50d..15585683c 100644 --- a/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md +++ b/docs/framework/angular/reference/functions/injectAsyncQueuerItems.md @@ -9,7 +9,7 @@ title: injectAsyncQueuerItems function injectAsyncQueuerItems(fn, options?): AngularAsyncQueuerItems; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:51](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L51) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:51](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L51) Creates a readonly Angular signal of pending asynchronous queue items. @@ -68,7 +68,7 @@ function injectAsyncQueuerItems( selector): AngularAsyncQueuerItems; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L59) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L59) Creates the items signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md index 69bfdcb24..2242f5e47 100644 --- a/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectAsyncRateLimiter.md @@ -9,7 +9,7 @@ title: injectAsyncRateLimiter function injectAsyncRateLimiter(fn, options): AngularAsyncRateLimiter; ``` -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L97) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:98](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L98) Creates and manages an Angular AsyncRateLimiter in the current injection context. @@ -19,7 +19,7 @@ Allows asynchronous calls up to the configured limit within a fixed or sliding w Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectAsyncRateLimiter( selector): AngularAsyncRateLimiter; ``` -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L108) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:109](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L109) Creates an Angular AsyncRateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectAsyncThrottler.md b/docs/framework/angular/reference/functions/injectAsyncThrottler.md index 173370c0c..86da57bc1 100644 --- a/docs/framework/angular/reference/functions/injectAsyncThrottler.md +++ b/docs/framework/angular/reference/functions/injectAsyncThrottler.md @@ -9,7 +9,7 @@ title: injectAsyncThrottler function injectAsyncThrottler(fn, options): AngularAsyncThrottler; ``` -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L97) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:98](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L98) Creates and manages an Angular AsyncThrottler in the current injection context. @@ -19,7 +19,7 @@ Limits asynchronous executions to the configured interval, with leading and trai Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectAsyncThrottler( selector): AngularAsyncThrottler; ``` -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L108) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:109](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L109) Creates an Angular AsyncThrottler with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectBatcher.md b/docs/framework/angular/reference/functions/injectBatcher.md index 6451e14b9..df9f30bdd 100644 --- a/docs/framework/angular/reference/functions/injectBatcher.md +++ b/docs/framework/angular/reference/functions/injectBatcher.md @@ -9,7 +9,7 @@ title: injectBatcher function injectBatcher(fn, options?): AngularBatcher; ``` -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:91](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L91) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:92](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L92) Creates and manages an Angular Batcher in the current injection context. @@ -19,7 +19,7 @@ Collects items and processes a batch when its size or delay threshold is reached Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectBatcher( selector): AngularBatcher; ``` -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:102](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L102) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:103](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L103) Creates an Angular Batcher with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectDebouncedSignal.md b/docs/framework/angular/reference/functions/injectDebouncedSignal.md index 734033fe7..7f4a9bfa2 100644 --- a/docs/framework/angular/reference/functions/injectDebouncedSignal.md +++ b/docs/framework/angular/reference/functions/injectDebouncedSignal.md @@ -9,13 +9,13 @@ title: injectDebouncedSignal function injectDebouncedSignal(initialValue, options): AngularDebouncerSignal; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L49) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:48](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L48) Creates an Angular debounced editable signal. The initial value is available synchronously. `set` and `update` debounce writes: a newer write restarts the delay and replaces the pending write. An updater runs against the committed value when the delay expires. -The returned value is a real Angular signal with the underlying utility exposed +The returned value is a real Angular writable signal with the underlying utility exposed on `debouncer`. Options accept a static object or reactive factory and follow [injectDebouncer](injectDebouncer.md) lifecycle and provider behavior. @@ -35,7 +35,7 @@ The initial committed value. #### options -`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`value`) => `void`\>\> Core options or a reactive options factory. @@ -43,7 +43,7 @@ Core options or a reactive options factory. [`AngularDebouncerSignal`](../interfaces/AngularDebouncerSignal.md)\<`TValue`\> -The value signal with `set`, `update`, and a `debouncer` attribute. +The writable signal with `set`, `update`, `asReadonly`, and a `debouncer` attribute. ### Example @@ -64,7 +64,7 @@ function injectDebouncedSignal( selector): AngularDebouncerSignal; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L59) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L58) Creates the value signal with selected state on its attached utility ref. @@ -86,7 +86,7 @@ Creates the value signal with selected state on its attached utility ref. #### options -`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularDebouncerOptions`](../interfaces/AngularDebouncerOptions.md)\<(`value`) => `void`\>\> #### selector diff --git a/docs/framework/angular/reference/functions/injectDebouncedComputed.md b/docs/framework/angular/reference/functions/injectDebouncedValue.md similarity index 68% rename from docs/framework/angular/reference/functions/injectDebouncedComputed.md rename to docs/framework/angular/reference/functions/injectDebouncedValue.md index 92014f794..ecf2dfc68 100644 --- a/docs/framework/angular/reference/functions/injectDebouncedComputed.md +++ b/docs/framework/angular/reference/functions/injectDebouncedValue.md @@ -1,15 +1,15 @@ --- -id: injectDebouncedComputed -title: injectDebouncedComputed +id: injectDebouncedValue +title: injectDebouncedValue --- ## Call Signature ```ts -function injectDebouncedComputed(signalToDebounce, options): AngularDebouncerComputed; +function injectDebouncedValue(signalToDebounce, options): AngularDebouncerValue; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L44) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedValue.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L44) Creates an Angular debounced view of a source signal. @@ -41,7 +41,7 @@ Core options or a reactive options factory. ### Returns -[`AngularDebouncerComputed`](../interfaces/AngularDebouncerComputed.md)\<`TValue`\> +[`AngularDebouncerValue`](../interfaces/AngularDebouncerValue.md)\<`TValue`\> The value signal with a `debouncer` attribute. @@ -50,7 +50,7 @@ The value signal with a `debouncer` attribute. ```ts // In a component or service injection context. const source = signal('initial') -const value = injectDebouncedComputed(source, { wait: 250 }) +const value = injectDebouncedValue(source, { wait: 250 }) source.set('updated') console.log(value()) ``` @@ -58,13 +58,13 @@ console.log(value()) ## Call Signature ```ts -function injectDebouncedComputed( +function injectDebouncedValue( signalToDebounce, options, -selector): AngularDebouncerComputed; +selector): AngularDebouncerValue; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L52) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedValue.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L52) Creates the value signal with selected state on its attached utility ref. @@ -96,4 +96,4 @@ Selects reactive state exposed on the attached utility ref. ### Returns -[`AngularDebouncerComputed`](../interfaces/AngularDebouncerComputed.md)\<`TValue`, `TSelected`\> +[`AngularDebouncerValue`](../interfaces/AngularDebouncerValue.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectDebouncer.md b/docs/framework/angular/reference/functions/injectDebouncer.md index ade6afdd3..48ca113ce 100644 --- a/docs/framework/angular/reference/functions/injectDebouncer.md +++ b/docs/framework/angular/reference/functions/injectDebouncer.md @@ -9,7 +9,7 @@ title: injectDebouncer function injectDebouncer(fn, options): AngularDebouncer; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:95](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L95) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:96](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L96) Creates and manages an Angular Debouncer in the current injection context. @@ -19,7 +19,7 @@ Waits until calls stop for the configured delay, then runs the latest callback. Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectDebouncer( selector): AngularDebouncer; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:106](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L106) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L107) Creates an Angular Debouncer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectPacerOptions.md b/docs/framework/angular/reference/functions/injectPacerOptions.md index 6dce9164d..0dfb9bcca 100644 --- a/docs/framework/angular/reference/functions/injectPacerOptions.md +++ b/docs/framework/angular/reference/functions/injectPacerOptions.md @@ -7,7 +7,7 @@ title: injectPacerOptions function injectPacerOptions(): PacerProviderOptions; ``` -Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:79](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L79) +Defined in: [packages/angular-pacer/src/provider/providePacerOptions.ts:80](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/provider/providePacerOptions.ts#L80) Reads the nearest Pacer options provider in the current injection context. Returns an empty object when no provider is configured. diff --git a/docs/framework/angular/reference/functions/injectQueuedSignal.md b/docs/framework/angular/reference/functions/injectQueuedSignal.md index eaa51258e..eef85f5dc 100644 --- a/docs/framework/angular/reference/functions/injectQueuedSignal.md +++ b/docs/framework/angular/reference/functions/injectQueuedSignal.md @@ -9,7 +9,7 @@ title: injectQueuedSignal function injectQueuedSignal(initialValue, options?): AngularQueuerSignal; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:42](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L42) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L44) Creates an Angular queued editable signal. @@ -34,7 +34,7 @@ The initial committed value. #### options? -`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<() => `void`\>\> +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`SignalWrite`\<`TValue`\>\>\> Core queue options or a reactive options factory. @@ -42,7 +42,7 @@ Core queue options or a reactive options factory. [`AngularQueuerSignal`](../interfaces/AngularQueuerSignal.md)\<`TValue`\> -The processed-value signal with its queuer attached. +The writable processed-value signal with its queuer attached. ### Example @@ -62,7 +62,7 @@ function injectQueuedSignal( selector): AngularQueuerSignal; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:50](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L50) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L52) Creates the queued value signal with selected state on its attached queuer. @@ -84,7 +84,7 @@ Creates the queued value signal with selected state on its attached queuer. #### options -`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<() => `void`\>\> +`MaybeAccessor`\<[`AngularQueuerOptions`](../interfaces/AngularQueuerOptions.md)\<`SignalWrite`\<`TValue`\>\>\> #### selector diff --git a/docs/framework/angular/reference/functions/injectQueuedComputed.md b/docs/framework/angular/reference/functions/injectQueuedValue.md similarity index 63% rename from docs/framework/angular/reference/functions/injectQueuedComputed.md rename to docs/framework/angular/reference/functions/injectQueuedValue.md index bcfdce500..6135ca8c4 100644 --- a/docs/framework/angular/reference/functions/injectQueuedComputed.md +++ b/docs/framework/angular/reference/functions/injectQueuedValue.md @@ -1,15 +1,15 @@ --- -id: injectQueuedComputed -title: injectQueuedComputed +id: injectQueuedValue +title: injectQueuedValue --- ## Call Signature ```ts -function injectQueuedComputed(source, options?): AngularQueuerComputed; +function injectQueuedValue(source, options?): AngularQueuerValue; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:37](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L37) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedValue.ts:37](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L37) Creates an Angular queued view of a source accessor. @@ -40,7 +40,7 @@ Core queue options or a reactive options factory. ### Returns -[`AngularQueuerComputed`](../interfaces/AngularQueuerComputed.md)\<`TValue`\> +[`AngularQueuerValue`](../interfaces/AngularQueuerValue.md)\<`TValue`\> The processed-value signal with its queuer attached. @@ -49,20 +49,20 @@ The processed-value signal with its queuer attached. ```ts // In a component or service injection context. const source = signal('initial') -const value = injectQueuedComputed(source, { wait: 100 }) +const value = injectQueuedValue(source, { wait: 100 }) source.set('next') ``` ## Call Signature ```ts -function injectQueuedComputed( +function injectQueuedValue( source, options, -selector): AngularQueuerComputed; +selector): AngularQueuerValue; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L45) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedValue.ts:45](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L45) Creates the queued value signal with selected state on its attached queuer. @@ -94,4 +94,4 @@ Selects reactive state exposed on the attached queuer. ### Returns -[`AngularQueuerComputed`](../interfaces/AngularQueuerComputed.md)\<`TValue`, `TSelected`\> +[`AngularQueuerValue`](../interfaces/AngularQueuerValue.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectQueuer.md b/docs/framework/angular/reference/functions/injectQueuer.md index 520c7b39f..5360c7606 100644 --- a/docs/framework/angular/reference/functions/injectQueuer.md +++ b/docs/framework/angular/reference/functions/injectQueuer.md @@ -9,7 +9,7 @@ title: injectQueuer function injectQueuer(fn, options?): AngularQueuer; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:96](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L96) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:97](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L97) Creates and manages an Angular Queuer in the current injection context. @@ -19,7 +19,7 @@ Processes queued items in order with configurable pacing, capacity, and priority Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectQueuer( selector): AngularQueuer; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L107) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L108) Creates an Angular Queuer with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectQueuerItems.md b/docs/framework/angular/reference/functions/injectQueuerItems.md index fec1d38ec..0ce738b98 100644 --- a/docs/framework/angular/reference/functions/injectQueuerItems.md +++ b/docs/framework/angular/reference/functions/injectQueuerItems.md @@ -9,7 +9,7 @@ title: injectQueuerItems function injectQueuerItems(fn, options?): AngularQueuerItems; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:48](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L48) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:48](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L48) Creates a readonly Angular signal of pending queue items. @@ -68,7 +68,7 @@ function injectQueuerItems( selector): AngularQueuerItems; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L56) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L56) Creates the items signal with selected state on its attached queuer. diff --git a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md index f963a4dd0..a3951329f 100644 --- a/docs/framework/angular/reference/functions/injectRateLimitedSignal.md +++ b/docs/framework/angular/reference/functions/injectRateLimitedSignal.md @@ -9,13 +9,13 @@ title: injectRateLimitedSignal function injectRateLimitedSignal(initialValue, options): AngularRateLimiterSignal; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L49) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:51](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L51) Creates an Angular ratelimited editable signal. The initial value is available synchronously. `set` and `update` share the execution limit. Accepted writes apply immediately; rejected writes and their updater callbacks are discarded, not replayed later. -The returned value is a real Angular signal with the underlying utility exposed +The returned value is a real Angular writable signal with the underlying utility exposed on `rateLimiter`. Options accept a static object or reactive factory and follow [injectRateLimiter](injectRateLimiter.md) lifecycle and provider behavior. @@ -35,7 +35,7 @@ The initial committed value. #### options -`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`value`) => `void`\>\> Core options or a reactive options factory. @@ -43,7 +43,7 @@ Core options or a reactive options factory. [`AngularRateLimiterSignal`](../interfaces/AngularRateLimiterSignal.md)\<`TValue`\> -The value signal with `set`, `update`, and a `rateLimiter` attribute. +The writable signal with `set`, `update`, `asReadonly`, and a `rateLimiter` attribute. ### Example @@ -64,7 +64,7 @@ function injectRateLimitedSignal( selector): AngularRateLimiterSignal; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L59) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L61) Creates the value signal with selected state on its attached utility ref. @@ -86,7 +86,7 @@ Creates the value signal with selected state on its attached utility ref. #### options -`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularRateLimiterOptions`](../interfaces/AngularRateLimiterOptions.md)\<(`value`) => `void`\>\> #### selector diff --git a/docs/framework/angular/reference/functions/injectRateLimitedComputed.md b/docs/framework/angular/reference/functions/injectRateLimitedValue.md similarity index 59% rename from docs/framework/angular/reference/functions/injectRateLimitedComputed.md rename to docs/framework/angular/reference/functions/injectRateLimitedValue.md index 9729b1b3e..eb5ca71f1 100644 --- a/docs/framework/angular/reference/functions/injectRateLimitedComputed.md +++ b/docs/framework/angular/reference/functions/injectRateLimitedValue.md @@ -1,19 +1,19 @@ --- -id: injectRateLimitedComputed -title: injectRateLimitedComputed +id: injectRateLimitedValue +title: injectRateLimitedValue --- ## Call Signature ```ts -function injectRateLimitedComputed(source, options): AngularRateLimiterComputed; +function injectRateLimitedValue(source, options): AngularRateLimiterValue; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L44) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L44) Creates an Angular ratelimited view of a source signal. -The initial source value is available on first read. An effect passes source values to the rate limiter, including the initial value. Excess updates are discarded; the end of a window does not replay rejected values. +The initial source value is available on first read. The unchanged initial value does not consume capacity. An effect passes subsequent source changes to the rate limiter. Excess updates are discarded; the end of a window does not replay rejected values. The returned value is a real Angular signal with the underlying utility exposed on `rateLimiter`. Options accept a static object or reactive factory and follow @@ -41,7 +41,7 @@ Core options or a reactive options factory. ### Returns -[`AngularRateLimiterComputed`](../interfaces/AngularRateLimiterComputed.md)\<`TValue`\> +[`AngularRateLimiterValue`](../interfaces/AngularRateLimiterValue.md)\<`TValue`\> The value signal with a `rateLimiter` attribute. @@ -50,7 +50,7 @@ The value signal with a `rateLimiter` attribute. ```ts // In a component or service injection context. const source = signal('initial') -const value = injectRateLimitedComputed(source, { limit: 5, window: 1000 }) +const value = injectRateLimitedValue(source, { limit: 5, window: 1000 }) source.set('updated') console.log(value()) ``` @@ -58,13 +58,13 @@ console.log(value()) ## Call Signature ```ts -function injectRateLimitedComputed( +function injectRateLimitedValue( source, options, -selector): AngularRateLimiterComputed; +selector): AngularRateLimiterValue; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L52) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L52) Creates the value signal with selected state on its attached utility ref. @@ -96,4 +96,4 @@ Selects reactive state exposed on the attached utility ref. ### Returns -[`AngularRateLimiterComputed`](../interfaces/AngularRateLimiterComputed.md)\<`TValue`, `TSelected`\> +[`AngularRateLimiterValue`](../interfaces/AngularRateLimiterValue.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectRateLimiter.md b/docs/framework/angular/reference/functions/injectRateLimiter.md index 37f73b699..be7b04c3c 100644 --- a/docs/framework/angular/reference/functions/injectRateLimiter.md +++ b/docs/framework/angular/reference/functions/injectRateLimiter.md @@ -9,7 +9,7 @@ title: injectRateLimiter function injectRateLimiter(fn, options): AngularRateLimiter; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:94](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L94) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:95](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L95) Creates and manages an Angular RateLimiter in the current injection context. @@ -19,7 +19,7 @@ Allows calls up to the configured limit within a fixed or sliding window. Calls Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectRateLimiter( selector): AngularRateLimiter; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:105](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L105) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:106](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L106) Creates an Angular RateLimiter with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/injectThrottledSignal.md b/docs/framework/angular/reference/functions/injectThrottledSignal.md index 8be1687c6..63c744f00 100644 --- a/docs/framework/angular/reference/functions/injectThrottledSignal.md +++ b/docs/framework/angular/reference/functions/injectThrottledSignal.md @@ -15,7 +15,7 @@ Creates an Angular throttled editable signal. The initial value is available synchronously. `set` and `update` share a throttler. Leading writes may apply immediately; subsequent writes replace the pending trailing write without extending its deadline. An updater runs against the committed value when executed. -The returned value is a real Angular signal with the underlying utility exposed +The returned value is a real Angular writable signal with the underlying utility exposed on `throttler`. Options accept a static object or reactive factory and follow [injectThrottler](injectThrottler.md) lifecycle and provider behavior. @@ -35,7 +35,7 @@ The initial committed value. #### options -`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`value`) => `void`\>\> Core options or a reactive options factory. @@ -43,7 +43,7 @@ Core options or a reactive options factory. [`AngularThrottlerSignal`](../interfaces/AngularThrottlerSignal.md)\<`TValue`\> -The value signal with `set`, `update`, and a `throttler` attribute. +The writable signal with `set`, `update`, `asReadonly`, and a `throttler` attribute. ### Example @@ -86,7 +86,7 @@ Creates the value signal with selected state on its attached utility ref. #### options -`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`callback`) => `void`\>\> +`MaybeAccessor`\<[`AngularThrottlerOptions`](../interfaces/AngularThrottlerOptions.md)\<(`value`) => `void`\>\> #### selector diff --git a/docs/framework/angular/reference/functions/injectThrottledComputed.md b/docs/framework/angular/reference/functions/injectThrottledValue.md similarity index 60% rename from docs/framework/angular/reference/functions/injectThrottledComputed.md rename to docs/framework/angular/reference/functions/injectThrottledValue.md index 9cbc067e1..659908ee9 100644 --- a/docs/framework/angular/reference/functions/injectThrottledComputed.md +++ b/docs/framework/angular/reference/functions/injectThrottledValue.md @@ -1,19 +1,19 @@ --- -id: injectThrottledComputed -title: injectThrottledComputed +id: injectThrottledValue +title: injectThrottledValue --- ## Call Signature ```ts -function injectThrottledComputed(source, options): AngularThrottlerComputed; +function injectThrottledValue(source, options): AngularThrottlerValue; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L44) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledValue.ts:44](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L44) Creates an Angular throttled view of a source signal. -The initial source value is available on first read. An effect passes source values to the throttler, including the initial value. Later changes replace the pending trailing value without restarting the interval. +The initial source value is available on first read. The unchanged initial value does not enter the throttler. An effect passes subsequent source changes to the throttler; later changes replace the pending trailing value without restarting the interval. The returned value is a real Angular signal with the underlying utility exposed on `throttler`. Options accept a static object or reactive factory and follow @@ -41,7 +41,7 @@ Core options or a reactive options factory. ### Returns -[`AngularThrottlerComputed`](../interfaces/AngularThrottlerComputed.md)\<`TValue`\> +[`AngularThrottlerValue`](../interfaces/AngularThrottlerValue.md)\<`TValue`\> The value signal with a `throttler` attribute. @@ -50,7 +50,7 @@ The value signal with a `throttler` attribute. ```ts // In a component or service injection context. const source = signal('initial') -const value = injectThrottledComputed(source, { wait: 250 }) +const value = injectThrottledValue(source, { wait: 250 }) source.set('updated') console.log(value()) ``` @@ -58,13 +58,13 @@ console.log(value()) ## Call Signature ```ts -function injectThrottledComputed( +function injectThrottledValue( source, options, -selector): AngularThrottlerComputed; +selector): AngularThrottlerValue; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L52) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledValue.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L52) Creates the value signal with selected state on its attached utility ref. @@ -96,4 +96,4 @@ Selects reactive state exposed on the attached utility ref. ### Returns -[`AngularThrottlerComputed`](../interfaces/AngularThrottlerComputed.md)\<`TValue`, `TSelected`\> +[`AngularThrottlerValue`](../interfaces/AngularThrottlerValue.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/angular/reference/functions/injectThrottler.md b/docs/framework/angular/reference/functions/injectThrottler.md index c4472bed8..93d688081 100644 --- a/docs/framework/angular/reference/functions/injectThrottler.md +++ b/docs/framework/angular/reference/functions/injectThrottler.md @@ -9,7 +9,7 @@ title: injectThrottler function injectThrottler(fn, options): AngularThrottler; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:95](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L95) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:96](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L96) Creates and manages an Angular Throttler in the current injection context. @@ -19,7 +19,7 @@ Limits executions to the configured interval, with leading and trailing calls. L Accepts static options or an options factory. Factories are read lazily, and signal dependencies update the existing core instance. Local options override provider defaults. -Methods apply current options before executing and run outside Angular's zone. +Methods apply current options before executing and schedule work outside Angular's zone. The provided function runs inside Angular's zone. Pass a selector to expose reactive core state through `state()`. Without a selector, `state()` returns `{}`; operations remain available on the ref. @@ -76,7 +76,7 @@ function injectThrottler( selector): AngularThrottler; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:106](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L106) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:107](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L107) Creates an Angular Throttler with a reactive selector result. diff --git a/docs/framework/angular/reference/functions/providePacerOptions.md b/docs/framework/angular/reference/functions/providePacerOptions.md index 923d00d12..c7fe7e771 100644 --- a/docs/framework/angular/reference/functions/providePacerOptions.md +++ b/docs/framework/angular/reference/functions/providePacerOptions.md @@ -19,9 +19,10 @@ provider replaces the outer defaults rather than merging scopes. ### options -[`PacerProviderOptions`](../interfaces/PacerProviderOptions.md) + \| [`PacerProviderOptions`](../interfaces/PacerProviderOptions.md) + \| (() => [`PacerProviderOptions`](../interfaces/PacerProviderOptions.md)) -Partial default options grouped by utility. +Partial default options grouped by utility, or an injection-context factory. ## Returns diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index 610b4926a..c170fba99 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -19,22 +19,22 @@ title: "@tanstack/angular-pacer" - [AngularBatcher](interfaces/AngularBatcher.md) - [AngularBatcherOptions](interfaces/AngularBatcherOptions.md) - [AngularDebouncer](interfaces/AngularDebouncer.md) -- [AngularDebouncerComputed](interfaces/AngularDebouncerComputed.md) - [AngularDebouncerOptions](interfaces/AngularDebouncerOptions.md) - [AngularDebouncerSignal](interfaces/AngularDebouncerSignal.md) +- [AngularDebouncerValue](interfaces/AngularDebouncerValue.md) - [AngularQueuer](interfaces/AngularQueuer.md) -- [AngularQueuerComputed](interfaces/AngularQueuerComputed.md) - [AngularQueuerItems](interfaces/AngularQueuerItems.md) - [AngularQueuerOptions](interfaces/AngularQueuerOptions.md) - [AngularQueuerSignal](interfaces/AngularQueuerSignal.md) +- [AngularQueuerValue](interfaces/AngularQueuerValue.md) - [AngularRateLimiter](interfaces/AngularRateLimiter.md) -- [AngularRateLimiterComputed](interfaces/AngularRateLimiterComputed.md) - [AngularRateLimiterOptions](interfaces/AngularRateLimiterOptions.md) - [AngularRateLimiterSignal](interfaces/AngularRateLimiterSignal.md) +- [AngularRateLimiterValue](interfaces/AngularRateLimiterValue.md) - [AngularThrottler](interfaces/AngularThrottler.md) -- [AngularThrottlerComputed](interfaces/AngularThrottlerComputed.md) - [AngularThrottlerOptions](interfaces/AngularThrottlerOptions.md) - [AngularThrottlerSignal](interfaces/AngularThrottlerSignal.md) +- [AngularThrottlerValue](interfaces/AngularThrottlerValue.md) - [PacerProviderOptions](interfaces/PacerProviderOptions.md) ## Functions @@ -46,18 +46,18 @@ title: "@tanstack/angular-pacer" - [injectAsyncRateLimiter](functions/injectAsyncRateLimiter.md) - [injectAsyncThrottler](functions/injectAsyncThrottler.md) - [injectBatcher](functions/injectBatcher.md) -- [injectDebouncedComputed](functions/injectDebouncedComputed.md) - [injectDebouncedSignal](functions/injectDebouncedSignal.md) +- [injectDebouncedValue](functions/injectDebouncedValue.md) - [injectDebouncer](functions/injectDebouncer.md) - [injectPacerOptions](functions/injectPacerOptions.md) -- [injectQueuedComputed](functions/injectQueuedComputed.md) - [injectQueuedSignal](functions/injectQueuedSignal.md) +- [injectQueuedValue](functions/injectQueuedValue.md) - [injectQueuer](functions/injectQueuer.md) - [injectQueuerItems](functions/injectQueuerItems.md) -- [injectRateLimitedComputed](functions/injectRateLimitedComputed.md) - [injectRateLimitedSignal](functions/injectRateLimitedSignal.md) +- [injectRateLimitedValue](functions/injectRateLimitedValue.md) - [injectRateLimiter](functions/injectRateLimiter.md) -- [injectThrottledComputed](functions/injectThrottledComputed.md) - [injectThrottledSignal](functions/injectThrottledSignal.md) +- [injectThrottledValue](functions/injectThrottledValue.md) - [injectThrottler](functions/injectThrottler.md) - [providePacerOptions](functions/providePacerOptions.md) diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md index a87e03b2d..504528404 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcher.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcher title: AngularAsyncBatcher --- -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L54) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L55) An Angular AsyncBatcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L61) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:62](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L62) Reactive state that will be updated when the batcher state changes diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md index 8d3d4954e..9c391708e 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncBatcherOptions title: AngularAsyncBatcherOptions --- -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L22) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L23) Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncBatcher](../functions/injectAsyncBatcher.md), including optional onUnmount?: (batcher) => void; ``` -Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L29) +Defined in: [packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-batcher/injectAsyncBatcher.ts#L30) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md index d572c9965..a73524a43 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncer.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncer title: AngularAsyncDebouncer --- -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L55) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L56) An Angular AsyncDebouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L60) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L61) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md index 7ceaed147..186f7b8c9 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncDebouncerOptions title: AngularAsyncDebouncerOptions --- -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L26) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L27) Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncDebouncer](../functions/injectAsyncDebouncer.md), includ optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L33) +Defined in: [packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-debouncer/injectAsyncDebouncer.ts#L34) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md index 69d080552..7c2280bad 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuer.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuer title: AngularAsyncQueuer --- -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L60) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L61) An Angular AsyncQueuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:65](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L65) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:66](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L66) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md index ba2b45bca..64d384fe1 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerItems.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuerItems title: AngularAsyncQueuerItems --- -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L16) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L16) A readonly signal of pending queue items, with the Angular queuer attached. @@ -26,7 +26,7 @@ A readonly signal of pending queue items, with the Angular queuer attached. AngularAsyncQueuerItems(): readonly TValue[]; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L16) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L16) A readonly signal of pending queue items, with the Angular queuer attached. @@ -42,7 +42,7 @@ readonly `TValue`[] [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from @@ -58,7 +58,7 @@ Signal.[SIGNAL] addItem: (item, position, runOnItemsChange) => boolean; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L22) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L22) Shortcut for queuer.addItem, with the same arguments and return value. @@ -99,6 +99,6 @@ queuer.addItem('task2', 'front'); queuer: AngularAsyncQueuer; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedItems.ts#L20) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuerItems.ts#L20) The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md index 9aab94801..ba0cc4db4 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncQueuerOptions title: AngularAsyncQueuerOptions --- -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L22) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L23) Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncQueuer](../functions/injectAsyncQueuer.md), including co optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L29) +Defined in: [packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuer.ts#L30) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md index 3604eec05..da816eda6 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiter.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiter title: AngularAsyncRateLimiter --- -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L55) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L56) An Angular AsyncRateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L60) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L61) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md index 0374d6786..8bfa2dc55 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncRateLimiterOptions title: AngularAsyncRateLimiterOptions --- -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L26) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L27) Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncRateLimiter](../functions/injectAsyncRateLimiter.md), in optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L33) +Defined in: [packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-rate-limiter/injectAsyncRateLimiter.ts#L34) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md index 51c21b7f5..f1347ffe0 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottler.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottler title: AngularAsyncThrottler --- -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L55) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:56](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L56) An Angular AsyncThrottler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L60) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:61](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L61) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md index a973f6c62..1784836d8 100644 --- a/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularAsyncThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularAsyncThrottlerOptions title: AngularAsyncThrottlerOptions --- -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L26) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L27) Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectAsyncThrottler](../functions/injectAsyncThrottler.md), includ optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L33) +Defined in: [packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-throttler/injectAsyncThrottler.ts#L34) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution and abort running work). diff --git a/docs/framework/angular/reference/interfaces/AngularBatcher.md b/docs/framework/angular/reference/interfaces/AngularBatcher.md index 0ec9bb23f..ced10f38d 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcher.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcher.md @@ -3,7 +3,7 @@ id: AngularBatcher title: AngularBatcher --- -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L49) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:50](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L50) An Angular Batcher ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L54) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L55) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md index 570da3e66..af151ccd2 100644 --- a/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularBatcherOptions.md @@ -3,7 +3,7 @@ id: AngularBatcherOptions title: AngularBatcherOptions --- -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L22) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L23) Options for [injectBatcher](../functions/injectBatcher.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectBatcher](../functions/injectBatcher.md), including core confi optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L27) +Defined in: [packages/angular-pacer/src/batcher/injectBatcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/batcher/injectBatcher.ts#L28) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending batches). diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncer.md b/docs/framework/angular/reference/interfaces/AngularDebouncer.md index dfacefdb9..da1c327c6 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncer.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncer.md @@ -3,7 +3,7 @@ id: AngularDebouncer title: AngularDebouncer --- -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L53) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L54) An Angular Debouncer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L58) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L59) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md index 7a2089519..5045295eb 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerOptions.md @@ -3,7 +3,7 @@ id: AngularDebouncerOptions title: AngularDebouncerOptions --- -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L26) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L27) Options for [injectDebouncer](../functions/injectDebouncer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectDebouncer](../functions/injectDebouncer.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L33) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncer.ts#L34) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md index 3470ff696..cdde39458 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerSignal.md @@ -3,14 +3,16 @@ id: AngularDebouncerSignal title: AngularDebouncerSignal --- -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L16) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L19) -A readonly Angular value signal with paced `set`/`update` methods. -The `debouncer` attribute exposes the underlying Debouncer methods. +An Angular writable signal whose `set` and `update` writes are debounced. +New writes replace the pending write and restart the delay. Updaters receive +the committed value when the write executes. `asReadonly()` exposes a live +readonly view. The `debouncer` attribute controls execution. ## Extends -- `Signal`\<`TValue`\> +- `WritableSignal`\<`TValue`\> ## Type Parameters @@ -27,10 +29,12 @@ The `debouncer` attribute exposes the underlying Debouncer methods. AngularDebouncerSignal(): TValue; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L16) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L19) -A readonly Angular value signal with paced `set`/`update` methods. -The `debouncer` attribute exposes the underlying Debouncer methods. +An Angular writable signal whose `set` and `update` writes are debounced. +New writes replace the pending write and restart the delay. Updaters receive +the committed value when the write executes. `asReadonly()` exposes a live +readonly view. The `debouncer` attribute controls execution. ## Returns @@ -38,18 +42,34 @@ The `debouncer` attribute exposes the underlying Debouncer methods. ## Properties +### \[ɵWRITABLE\_SIGNAL\] + +```ts +[ɵWRITABLE_SIGNAL]: TValue; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:19 + +#### Inherited from + +```ts +WritableSignal.[ɵWRITABLE_SIGNAL] +``` + +*** + ### \[SIGNAL\] ```ts [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from ```ts -Signal.[SIGNAL] +WritableSignal.[SIGNAL] ``` *** @@ -57,24 +77,48 @@ Signal.[SIGNAL] ### debouncer ```ts -debouncer: AngularDebouncer<(callback) => void, TSelected>; +debouncer: AngularDebouncer<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L25) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:24](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L24) The underlying Angular Debouncer ref for controlling execution. +## Methods + +### asReadonly() + +```ts +asReadonly(): Signal; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:34 + +Returns a readonly version of this signal. Readonly signals can be accessed to read their value +but can't be changed using set or update methods. The readonly signals do _not_ have +any built-in mechanism that would prevent deep-mutation of their value. + +#### Returns + +`Signal`\<`TValue`\> + +#### Inherited from + +```ts +WritableSignal.asReadonly +``` + *** -### set +### set() ```ts -set: (value) => void; +set(value): void; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L21) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:23 -Schedules the replacement value after the debounce delay. +Directly set the signal to a new value, and notify any dependents. #### Parameters @@ -86,24 +130,37 @@ Schedules the replacement value after the debounce delay. `void` +#### Inherited from + +```ts +WritableSignal.set +``` + *** -### update +### update() ```ts -update: (updateFn) => void; +update(updateFn): void; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedSignal.ts#L23) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:28 -Runs the updater with the current committed value when the write executes. +Update the value of the signal based on its current value, and +notify any dependents. #### Parameters ##### updateFn -(`previous`) => `TValue` +(`value`) => `TValue` #### Returns `void` + +#### Inherited from + +```ts +WritableSignal.update +``` diff --git a/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md b/docs/framework/angular/reference/interfaces/AngularDebouncerValue.md similarity index 61% rename from docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md rename to docs/framework/angular/reference/interfaces/AngularDebouncerValue.md index 9c9bea96c..7528c085d 100644 --- a/docs/framework/angular/reference/interfaces/AngularDebouncerComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularDebouncerValue.md @@ -1,9 +1,9 @@ --- -id: AngularDebouncerComputed -title: AngularDebouncerComputed +id: AngularDebouncerValue +title: AngularDebouncerValue --- -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L15) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -24,10 +24,10 @@ The `debouncer` attribute exposes the underlying Debouncer methods. \} ```ts -AngularDebouncerComputed(): TValue; +AngularDebouncerValue(): TValue; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L15) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `debouncer` attribute exposes the underlying Debouncer methods. @@ -44,7 +44,7 @@ The `debouncer` attribute exposes the underlying Debouncer methods. [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from @@ -60,6 +60,6 @@ Signal.[SIGNAL] debouncer: AngularDebouncer<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedComputed.ts#L20) +Defined in: [packages/angular-pacer/src/debouncer/injectDebouncedValue.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/debouncer/injectDebouncedValue.ts#L20) The underlying Angular Debouncer ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuer.md b/docs/framework/angular/reference/interfaces/AngularQueuer.md index 402ab2840..962227156 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuer.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuer.md @@ -3,7 +3,7 @@ id: AngularQueuer title: AngularQueuer --- -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L54) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L55) An Angular Queuer ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L59) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L60) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md b/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md deleted file mode 100644 index 2f2fc3ba7..000000000 --- a/docs/framework/angular/reference/interfaces/AngularQueuerComputed.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: AngularQueuerComputed -title: AngularQueuerComputed ---- - -Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L9) - -A processed-value signal with its underlying queue controls. - -## Extends - -- `Signal`\<`TValue`\> - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -```ts -AngularQueuerComputed(): TValue; -``` - -Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L9) - -A processed-value signal with its underlying queue controls. - -## Returns - -`TValue` - -## Properties - -### \[SIGNAL\] - -```ts -[SIGNAL]: unknown; -``` - -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 - -#### Inherited from - -```ts -Signal.[SIGNAL] -``` - -*** - -### queuer - -```ts -queuer: AngularQueuer; -``` - -Defined in: [packages/angular-pacer/src/queuer/injectQueuedComputed.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedComputed.ts#L14) - -The underlying Angular Queuer ref and its selected state. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerItems.md b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md index 3647d2e04..9614e80d6 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerItems.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerItems.md @@ -3,7 +3,7 @@ id: AngularQueuerItems title: AngularQueuerItems --- -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L13) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L13) A readonly signal of pending queue items, with the Angular queuer attached. @@ -26,7 +26,7 @@ A readonly signal of pending queue items, with the Angular queuer attached. AngularQueuerItems(): readonly TValue[]; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L13) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:13](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L13) A readonly signal of pending queue items, with the Angular queuer attached. @@ -42,7 +42,7 @@ readonly `TValue`[] [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from @@ -58,7 +58,7 @@ Signal.[SIGNAL] addItem: (item, position, runOnItemsChange) => boolean; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L19) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L19) Shortcut for queuer.addItem, with the same arguments and return value. @@ -100,6 +100,6 @@ queuer.addItem('task2', 'front'); queuer: AngularQueuer; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedItems.ts:17](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedItems.ts#L17) +Defined in: [packages/angular-pacer/src/queuer/injectQueuerItems.ts:17](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuerItems.ts#L17) The underlying utility ref for adding, processing, and inspecting items. diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md index 9d07f47fb..0edd0d970 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerOptions.md @@ -3,7 +3,7 @@ id: AngularQueuerOptions title: AngularQueuerOptions --- -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L22) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L23) Options for [injectQueuer](../functions/injectQueuer.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectQueuer](../functions/injectQueuer.md), including core configu optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L27) +Defined in: [packages/angular-pacer/src/queuer/injectQueuer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuer.ts#L28) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (stop automatic processing). diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md index 11f159b08..7ea10e8d4 100644 --- a/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularQueuerSignal.md @@ -3,13 +3,16 @@ id: AngularQueuerSignal title: AngularQueuerSignal --- -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L10) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L16) -A processed-value signal with its underlying queue controls. +An Angular writable signal whose `set` and `update` writes are queued in order. +Updaters receive the committed value when their queue item executes. +`asReadonly()` exposes a live readonly view. The `queuer` attribute controls +processing and exposes selected queue state. ## Extends -- `Signal`\<`TValue`\> +- `WritableSignal`\<`TValue`\> ## Type Parameters @@ -26,9 +29,12 @@ A processed-value signal with its underlying queue controls. AngularQueuerSignal(): TValue; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L10) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L16) -A processed-value signal with its underlying queue controls. +An Angular writable signal whose `set` and `update` writes are queued in order. +Updaters receive the committed value when their queue item executes. +`asReadonly()` exposes a live readonly view. The `queuer` attribute controls +processing and exposes selected queue state. ## Returns @@ -36,18 +42,34 @@ A processed-value signal with its underlying queue controls. ## Properties +### \[ɵWRITABLE\_SIGNAL\] + +```ts +[ɵWRITABLE_SIGNAL]: TValue; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:19 + +#### Inherited from + +```ts +WritableSignal.[ɵWRITABLE_SIGNAL] +``` + +*** + ### \[SIGNAL\] ```ts [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from ```ts -Signal.[SIGNAL] +WritableSignal.[SIGNAL] ``` *** @@ -55,24 +77,48 @@ Signal.[SIGNAL] ### queuer ```ts -queuer: AngularQueuer<() => void, TSelected>; +queuer: AngularQueuer, TSelected>; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L19) +Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L21) The underlying Angular Queuer ref and its selected state. +## Methods + +### asReadonly() + +```ts +asReadonly(): Signal; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:34 + +Returns a readonly version of this signal. Readonly signals can be accessed to read their value +but can't be changed using set or update methods. The readonly signals do _not_ have +any built-in mechanism that would prevent deep-mutation of their value. + +#### Returns + +`Signal`\<`TValue`\> + +#### Inherited from + +```ts +WritableSignal.asReadonly +``` + *** -### set +### set() ```ts -set: (value) => void; +set(value): void; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L15) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:23 -Enqueues a replacement value. +Directly set the signal to a new value, and notify any dependents. #### Parameters @@ -84,24 +130,37 @@ Enqueues a replacement value. `void` +#### Inherited from + +```ts +WritableSignal.set +``` + *** -### update +### update() ```ts -update: (updater) => void; +update(updateFn): void; ``` -Defined in: [packages/angular-pacer/src/queuer/injectQueuedSignal.ts:17](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L17) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:28 -Enqueues an updater evaluated against the committed value when processed. +Update the value of the signal based on its current value, and +notify any dependents. #### Parameters -##### updater +##### updateFn -(`previous`) => `TValue` +(`value`) => `TValue` #### Returns `void` + +#### Inherited from + +```ts +WritableSignal.update +``` diff --git a/docs/framework/angular/reference/interfaces/AngularQueuerValue.md b/docs/framework/angular/reference/interfaces/AngularQueuerValue.md new file mode 100644 index 000000000..6127dd879 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/AngularQueuerValue.md @@ -0,0 +1,63 @@ +--- +id: AngularQueuerValue +title: AngularQueuerValue +--- + +Defined in: [packages/angular-pacer/src/queuer/injectQueuedValue.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L9) + +A processed-value signal with its underlying queue controls. + +## Extends + +- `Signal`\<`TValue`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +AngularQueuerValue(): TValue; +``` + +Defined in: [packages/angular-pacer/src/queuer/injectQueuedValue.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L9) + +A processed-value signal with its underlying queue controls. + +## Returns + +`TValue` + +## Properties + +### \[SIGNAL\] + +```ts +[SIGNAL]: unknown; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 + +#### Inherited from + +```ts +Signal.[SIGNAL] +``` + +*** + +### queuer + +```ts +queuer: AngularQueuer; +``` + +Defined in: [packages/angular-pacer/src/queuer/injectQueuedValue.ts:14](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L14) + +The underlying Angular Queuer ref and its selected state. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md index c2ee56482..e017b594f 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiter.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiter.md @@ -3,7 +3,7 @@ id: AngularRateLimiter title: AngularRateLimiter --- -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:52](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L52) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L53) An Angular RateLimiter ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:57](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L57) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L58) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md index 5b0e864de..a7017b959 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterOptions.md @@ -3,7 +3,7 @@ id: AngularRateLimiterOptions title: AngularRateLimiterOptions --- -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L25) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L26) Options for [injectRateLimiter](../functions/injectRateLimiter.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectRateLimiter](../functions/injectRateLimiter.md), including co optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L32) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimiter.ts#L33) Called when the owning injection context is destroyed. Receives the core instance. There is no default cleanup; use this callback for custom teardown. diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md index 40167c909..0a05aec19 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterSignal.md @@ -3,14 +3,16 @@ id: AngularRateLimiterSignal title: AngularRateLimiterSignal --- -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L16) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L19) -A readonly Angular value signal with paced `set`/`update` methods. -The `rateLimiter` attribute exposes the underlying RateLimiter methods. +An Angular writable signal whose `set` and `update` writes share a rate limit. +Accepted writes execute immediately; rejected writes and their updaters are +discarded. Updaters receive the committed value when executed. `asReadonly()` +exposes a live readonly view. The `rateLimiter` attribute controls execution. ## Extends -- `Signal`\<`TValue`\> +- `WritableSignal`\<`TValue`\> ## Type Parameters @@ -27,10 +29,12 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. AngularRateLimiterSignal(): TValue; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L16) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:19](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L19) -A readonly Angular value signal with paced `set`/`update` methods. -The `rateLimiter` attribute exposes the underlying RateLimiter methods. +An Angular writable signal whose `set` and `update` writes share a rate limit. +Accepted writes execute immediately; rejected writes and their updaters are +discarded. Updaters receive the committed value when executed. `asReadonly()` +exposes a live readonly view. The `rateLimiter` attribute controls execution. ## Returns @@ -38,18 +42,34 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. ## Properties +### \[ɵWRITABLE\_SIGNAL\] + +```ts +[ɵWRITABLE_SIGNAL]: TValue; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:19 + +#### Inherited from + +```ts +WritableSignal.[ɵWRITABLE_SIGNAL] +``` + +*** + ### \[SIGNAL\] ```ts [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from ```ts -Signal.[SIGNAL] +WritableSignal.[SIGNAL] ``` *** @@ -57,24 +77,48 @@ Signal.[SIGNAL] ### rateLimiter ```ts -rateLimiter: AngularRateLimiter<(callback) => void, TSelected>; +rateLimiter: AngularRateLimiter<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L25) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:24](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L24) The underlying Angular RateLimiter ref for controlling execution. +## Methods + +### asReadonly() + +```ts +asReadonly(): Signal; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:34 + +Returns a readonly version of this signal. Readonly signals can be accessed to read their value +but can't be changed using set or update methods. The readonly signals do _not_ have +any built-in mechanism that would prevent deep-mutation of their value. + +#### Returns + +`Signal`\<`TValue`\> + +#### Inherited from + +```ts +WritableSignal.asReadonly +``` + *** -### set +### set() ```ts -set: (value) => void; +set(value): void; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L21) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:23 -Replaces the value immediately if the rate limit permits; rejected writes are discarded. +Directly set the signal to a new value, and notify any dependents. #### Parameters @@ -86,24 +130,37 @@ Replaces the value immediately if the rate limit permits; rejected writes are di `void` +#### Inherited from + +```ts +WritableSignal.set +``` + *** -### update +### update() ```ts -update: (updateFn) => void; +update(updateFn): void; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedSignal.ts#L23) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:28 -Runs the updater with the current committed value when the write executes. +Update the value of the signal based on its current value, and +notify any dependents. #### Parameters ##### updateFn -(`previous`) => `TValue` +(`value`) => `TValue` #### Returns `void` + +#### Inherited from + +```ts +WritableSignal.update +``` diff --git a/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md b/docs/framework/angular/reference/interfaces/AngularRateLimiterValue.md similarity index 60% rename from docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md rename to docs/framework/angular/reference/interfaces/AngularRateLimiterValue.md index f64aa2355..ef76300fd 100644 --- a/docs/framework/angular/reference/interfaces/AngularRateLimiterComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularRateLimiterValue.md @@ -1,9 +1,9 @@ --- -id: AngularRateLimiterComputed -title: AngularRateLimiterComputed +id: AngularRateLimiterValue +title: AngularRateLimiterValue --- -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L15) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -24,10 +24,10 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. \} ```ts -AngularRateLimiterComputed(): TValue; +AngularRateLimiterValue(): TValue; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L15) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `rateLimiter` attribute exposes the underlying RateLimiter methods. @@ -44,7 +44,7 @@ The `rateLimiter` attribute exposes the underlying RateLimiter methods. [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from @@ -60,6 +60,6 @@ Signal.[SIGNAL] rateLimiter: AngularRateLimiter<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedComputed.ts#L20) +Defined in: [packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/rate-limiter/injectRateLimitedValue.ts#L20) The underlying Angular RateLimiter ref for controlling execution. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottler.md b/docs/framework/angular/reference/interfaces/AngularThrottler.md index 9f9f441b1..6a854a223 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottler.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottler.md @@ -3,7 +3,7 @@ id: AngularThrottler title: AngularThrottler --- -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:53](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L53) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:54](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L54) An Angular Throttler ref with stable core methods and readonly selected state. Read `state()` to observe the selector result; without a selector it returns `{}`. @@ -31,6 +31,6 @@ Read `state()` to observe the selector result; without a selector it returns `{} readonly state: Signal>; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:58](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L58) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:59](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L59) The readonly selector result. Returns an empty object when no selector is supplied. diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md index 83bff4f25..1e7c5aee6 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerOptions.md @@ -3,7 +3,7 @@ id: AngularThrottlerOptions title: AngularThrottlerOptions --- -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L26) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L27) Options for [injectThrottler](../functions/injectThrottler.md), including core configuration and Angular cleanup. @@ -25,7 +25,7 @@ Options for [injectThrottler](../functions/injectThrottler.md), including core c optional onUnmount?: (core) => void; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L33) +Defined in: [packages/angular-pacer/src/throttler/injectThrottler.ts:34](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottler.ts#L34) Called when the owning injection context is destroyed. Receives the core instance. Providing this callback replaces the default cleanup (cancel pending execution). diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md index 173193632..ace65a36e 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerSignal.md @@ -3,14 +3,17 @@ id: AngularThrottlerSignal title: AngularThrottlerSignal --- -Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L16) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L20) -A readonly Angular value signal with paced `set`/`update` methods. -The `throttler` attribute exposes the underlying Throttler methods. +An Angular writable signal whose `set` and `update` writes are throttled. +Leading writes may execute immediately; later writes replace the pending +trailing write without extending its deadline. Updaters receive the committed +value when executed. `asReadonly()` exposes a live readonly view. +The `throttler` attribute controls execution. ## Extends -- `Signal`\<`TValue`\> +- `WritableSignal`\<`TValue`\> ## Type Parameters @@ -27,10 +30,13 @@ The `throttler` attribute exposes the underlying Throttler methods. AngularThrottlerSignal(): TValue; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:16](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L16) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L20) -A readonly Angular value signal with paced `set`/`update` methods. -The `throttler` attribute exposes the underlying Throttler methods. +An Angular writable signal whose `set` and `update` writes are throttled. +Leading writes may execute immediately; later writes replace the pending +trailing write without extending its deadline. Updaters receive the committed +value when executed. `asReadonly()` exposes a live readonly view. +The `throttler` attribute controls execution. ## Returns @@ -38,72 +44,125 @@ The `throttler` attribute exposes the underlying Throttler methods. ## Properties +### \[ɵWRITABLE\_SIGNAL\] + +```ts +[ɵWRITABLE_SIGNAL]: TValue; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:19 + +#### Inherited from + +```ts +WritableSignal.[ɵWRITABLE_SIGNAL] +``` + +*** + ### \[SIGNAL\] ```ts [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from ```ts -Signal.[SIGNAL] +WritableSignal.[SIGNAL] ``` *** -### set +### throttler ```ts -set: (value) => void; +throttler: AngularThrottler<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:21](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L21) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L25) -Schedules the replacement value according to leading and trailing throttling options. +The underlying Angular Throttler ref for controlling execution. -#### Parameters +## Methods -##### value +### asReadonly() -`TValue` +```ts +asReadonly(): Signal; +``` + +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:34 + +Returns a readonly version of this signal. Readonly signals can be accessed to read their value +but can't be changed using set or update methods. The readonly signals do _not_ have +any built-in mechanism that would prevent deep-mutation of their value. #### Returns -`void` +`Signal`\<`TValue`\> + +#### Inherited from + +```ts +WritableSignal.asReadonly +``` *** -### throttler +### set() ```ts -throttler: AngularThrottler<(callback) => void, TSelected>; +set(value): void; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:25](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L25) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:23 -The underlying Angular Throttler ref for controlling execution. +Directly set the signal to a new value, and notify any dependents. + +#### Parameters + +##### value + +`TValue` + +#### Returns + +`void` + +#### Inherited from + +```ts +WritableSignal.set +``` *** -### update +### update() ```ts -update: (updateFn) => void; +update(updateFn): void; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledSignal.ts:23](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledSignal.ts#L23) +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:28 -Runs the updater with the current committed value when the write executes. +Update the value of the signal based on its current value, and +notify any dependents. #### Parameters ##### updateFn -(`previous`) => `TValue` +(`value`) => `TValue` #### Returns `void` + +#### Inherited from + +```ts +WritableSignal.update +``` diff --git a/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md b/docs/framework/angular/reference/interfaces/AngularThrottlerValue.md similarity index 61% rename from docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md rename to docs/framework/angular/reference/interfaces/AngularThrottlerValue.md index d47e19306..e2f4d1d7e 100644 --- a/docs/framework/angular/reference/interfaces/AngularThrottlerComputed.md +++ b/docs/framework/angular/reference/interfaces/AngularThrottlerValue.md @@ -1,9 +1,9 @@ --- -id: AngularThrottlerComputed -title: AngularThrottlerComputed +id: AngularThrottlerValue +title: AngularThrottlerValue --- -Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L15) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `throttler` attribute exposes the underlying Throttler methods. @@ -24,10 +24,10 @@ The `throttler` attribute exposes the underlying Throttler methods. \} ```ts -AngularThrottlerComputed(): TValue; +AngularThrottlerValue(): TValue; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L15) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledValue.ts:15](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L15) A readonly Angular value signal with its underlying utility ref. The `throttler` attribute exposes the underlying Throttler methods. @@ -44,7 +44,7 @@ The `throttler` attribute exposes the underlying Throttler methods. [SIGNAL]: unknown; ``` -Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 +Defined in: node\_modules/.pnpm/@angular+core@22.2.1\_@angular+compiler@22.2.1\_rxjs@7.8.2\_zone.js@0.16.3/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:75 #### Inherited from @@ -60,6 +60,6 @@ Signal.[SIGNAL] throttler: AngularThrottler<(value) => void, TSelected>; ``` -Defined in: [packages/angular-pacer/src/throttler/injectThrottledComputed.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledComputed.ts#L20) +Defined in: [packages/angular-pacer/src/throttler/injectThrottledValue.ts:20](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/throttler/injectThrottledValue.ts#L20) The underlying Angular Throttler ref for controlling execution. diff --git a/examples/angular/injectDebouncedComputed/README.md b/examples/angular/injectDebouncedValue/README.md similarity index 100% rename from examples/angular/injectDebouncedComputed/README.md rename to examples/angular/injectDebouncedValue/README.md diff --git a/examples/angular/injectDebouncedComputed/angular.json b/examples/angular/injectDebouncedValue/angular.json similarity index 100% rename from examples/angular/injectDebouncedComputed/angular.json rename to examples/angular/injectDebouncedValue/angular.json diff --git a/examples/angular/injectDebouncedComputed/eslint.config.js b/examples/angular/injectDebouncedValue/eslint.config.js similarity index 100% rename from examples/angular/injectDebouncedComputed/eslint.config.js rename to examples/angular/injectDebouncedValue/eslint.config.js diff --git a/examples/angular/injectQueuedComputed/package.json b/examples/angular/injectDebouncedValue/package.json similarity index 94% rename from examples/angular/injectQueuedComputed/package.json rename to examples/angular/injectDebouncedValue/package.json index ff3aac537..81c557e7e 100644 --- a/examples/angular/injectQueuedComputed/package.json +++ b/examples/angular/injectDebouncedValue/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-queued-computed", + "name": "@tanstack/pacer-example-angular-inject-debounced-value", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectDebouncedComputed/public/favicon.ico b/examples/angular/injectDebouncedValue/public/favicon.ico similarity index 100% rename from examples/angular/injectDebouncedComputed/public/favicon.ico rename to examples/angular/injectDebouncedValue/public/favicon.ico diff --git a/examples/angular/injectDebouncedComputed/src/app/app.config.ts b/examples/angular/injectDebouncedValue/src/app/app.config.ts similarity index 100% rename from examples/angular/injectDebouncedComputed/src/app/app.config.ts rename to examples/angular/injectDebouncedValue/src/app/app.config.ts diff --git a/examples/angular/injectDebouncedComputed/src/app/app.css b/examples/angular/injectDebouncedValue/src/app/app.css similarity index 100% rename from examples/angular/injectDebouncedComputed/src/app/app.css rename to examples/angular/injectDebouncedValue/src/app/app.css diff --git a/examples/angular/injectDebouncedComputed/src/app/app.html b/examples/angular/injectDebouncedValue/src/app/app.html similarity index 95% rename from examples/angular/injectDebouncedComputed/src/app/app.html rename to examples/angular/injectDebouncedValue/src/app/app.html index 0a38d4e65..552304cb5 100644 --- a/examples/angular/injectDebouncedComputed/src/app/app.html +++ b/examples/angular/injectDebouncedValue/src/app/app.html @@ -1,4 +1,4 @@ -

TanStack Pacer injectDebouncedComputed Example

+

TanStack Pacer injectDebouncedValue Example

Counter

diff --git a/examples/angular/injectDebouncedComputed/src/app/app.routes.ts b/examples/angular/injectDebouncedValue/src/app/app.routes.ts similarity index 100% rename from examples/angular/injectDebouncedComputed/src/app/app.routes.ts rename to examples/angular/injectDebouncedValue/src/app/app.routes.ts diff --git a/examples/angular/injectDebouncedComputed/src/app/app.spec.ts b/examples/angular/injectDebouncedValue/src/app/app.spec.ts similarity index 97% rename from examples/angular/injectDebouncedComputed/src/app/app.spec.ts rename to examples/angular/injectDebouncedValue/src/app/app.spec.ts index 1961f05e4..4930b9dee 100644 --- a/examples/angular/injectDebouncedComputed/src/app/app.spec.ts +++ b/examples/angular/injectDebouncedValue/src/app/app.spec.ts @@ -20,7 +20,7 @@ describe('App', () => { await fixture.whenStable() const compiled = fixture.nativeElement as HTMLElement expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectDebouncedComputed Example', + 'TanStack Pacer injectDebouncedValue Example', ) }) }) diff --git a/examples/angular/injectDebouncedComputed/src/app/app.ts b/examples/angular/injectDebouncedValue/src/app/app.ts similarity index 72% rename from examples/angular/injectDebouncedComputed/src/app/app.ts rename to examples/angular/injectDebouncedValue/src/app/app.ts index 8fd12ad6b..35f882bad 100644 --- a/examples/angular/injectDebouncedComputed/src/app/app.ts +++ b/examples/angular/injectDebouncedValue/src/app/app.ts @@ -1,5 +1,5 @@ import { Component, signal } from '@angular/core' -import { injectDebouncedComputed } from '@tanstack/angular-pacer' +import { injectDebouncedValue } from '@tanstack/angular-pacer' import { InputApp } from './inputapp' @Component({ @@ -12,14 +12,14 @@ export class App { readonly search = signal('') readonly currentValue = signal(50) readonly instantExecutions = signal(1) - readonly controlledCount = injectDebouncedComputed(this.instantCount, () => ({ wait: 500 })) - readonly controlledSearch = injectDebouncedComputed(this.search, () => { + readonly controlledCount = injectDebouncedValue(this.instantCount, () => ({ wait: 500 })) + readonly controlledSearch = injectDebouncedValue(this.search, () => { // Update options when the signal changes so disabling cancels pending work. // The callback reads the current event value before the next effect runs. this.search() return { wait: 500, enabled: () => this.search().length > 2 } }) - readonly controlledValue = injectDebouncedComputed(this.currentValue, () => ({ wait: 250 })) + readonly controlledValue = injectDebouncedValue(this.currentValue, () => ({ wait: 250 })) increment(): void { const next = this.instantCount() + 1 this.instantCount.set(next) diff --git a/examples/angular/injectDebouncedComputed/src/app/inputapp.ts b/examples/angular/injectDebouncedValue/src/app/inputapp.ts similarity index 67% rename from examples/angular/injectDebouncedComputed/src/app/inputapp.ts rename to examples/angular/injectDebouncedValue/src/app/inputapp.ts index d9aab4244..6f8b06e6b 100644 --- a/examples/angular/injectDebouncedComputed/src/app/inputapp.ts +++ b/examples/angular/injectDebouncedValue/src/app/inputapp.ts @@ -1,5 +1,5 @@ import { Component, input } from '@angular/core' -import { injectDebouncedComputed } from '@tanstack/angular-pacer' +import { injectDebouncedValue } from '@tanstack/angular-pacer' @Component({ selector: 'app-input', @@ -11,5 +11,5 @@ import { injectDebouncedComputed } from '@tanstack/angular-pacer' }) export class InputApp { readonly value = input.required() - readonly debounced = injectDebouncedComputed(this.value, { wait: 500 }) + readonly debounced = injectDebouncedValue(this.value, { wait: 500 }) } diff --git a/examples/angular/injectDebouncedComputed/src/index.html b/examples/angular/injectDebouncedValue/src/index.html similarity index 100% rename from examples/angular/injectDebouncedComputed/src/index.html rename to examples/angular/injectDebouncedValue/src/index.html diff --git a/examples/angular/injectDebouncedComputed/src/main.ts b/examples/angular/injectDebouncedValue/src/main.ts similarity index 100% rename from examples/angular/injectDebouncedComputed/src/main.ts rename to examples/angular/injectDebouncedValue/src/main.ts diff --git a/examples/angular/injectDebouncedComputed/src/styles.css b/examples/angular/injectDebouncedValue/src/styles.css similarity index 100% rename from examples/angular/injectDebouncedComputed/src/styles.css rename to examples/angular/injectDebouncedValue/src/styles.css diff --git a/examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts b/examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts similarity index 95% rename from examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts rename to examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts index 5d0c33fbe..831c7c643 100644 --- a/examples/angular/injectDebouncedComputed/tests/e2e/smoke.spec.mts +++ b/examples/angular/injectDebouncedValue/tests/e2e/smoke.spec.mts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page, exampleUrl }) => { await page.goto(exampleUrl) await expect( page.getByRole('heading', { - name: 'TanStack Pacer injectDebouncedComputed Example', + name: 'TanStack Pacer injectDebouncedValue Example', exact: true, }), ).toBeVisible() diff --git a/examples/angular/injectDebouncedComputed/tsconfig.app.json b/examples/angular/injectDebouncedValue/tsconfig.app.json similarity index 100% rename from examples/angular/injectDebouncedComputed/tsconfig.app.json rename to examples/angular/injectDebouncedValue/tsconfig.app.json diff --git a/examples/angular/injectDebouncedComputed/tsconfig.json b/examples/angular/injectDebouncedValue/tsconfig.json similarity index 100% rename from examples/angular/injectDebouncedComputed/tsconfig.json rename to examples/angular/injectDebouncedValue/tsconfig.json diff --git a/examples/angular/injectDebouncedComputed/tsconfig.spec.json b/examples/angular/injectDebouncedValue/tsconfig.spec.json similarity index 100% rename from examples/angular/injectDebouncedComputed/tsconfig.spec.json rename to examples/angular/injectDebouncedValue/tsconfig.spec.json diff --git a/examples/angular/injectQueuedComputed/src/app/app.spec.ts b/examples/angular/injectQueuedComputed/src/app/app.spec.ts deleted file mode 100644 index d99f182ea..000000000 --- a/examples/angular/injectQueuedComputed/src/app/app.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { vi } from 'vitest' -import { TestBed } from '@angular/core/testing' -import { App } from './app' - -describe('App', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [App], - }).compileComponents() - }) - - it('should create the app', () => { - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - expect(app).toBeTruthy() - }) - - it('should render title', async () => { - const fixture = TestBed.createComponent(App) - await fixture.whenStable() - const compiled = fixture.nativeElement as HTMLElement - expect(compiled.querySelector('h1')?.textContent).toContain( - 'TanStack Pacer injectQueuedComputed Example', - ) - }) -}) - -// Exercise the real adapter and component with a deterministic clock. -describe('example behavior', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ imports: [App] }).compileComponents() - }) - afterEach(() => { - TestBed.resetTestingModule() - vi.clearAllTimers() - vi.useRealTimers() - vi.restoreAllMocks() - }) - it('publishes a processed scalar and keeps pending values in queue state', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }) - const fixture = TestBed.createComponent(App) - const app = fixture.componentInstance - fixture.detectChanges() - TestBed.tick() - await vi.advanceTimersByTimeAsync(1000) - app.queuer.stop() - app.source.set('angular') - TestBed.tick() - expect(app.queued()).toBe('') - expect(app.queuer.state().items).toEqual(['angular']) - app.queuer.execute() - expect(app.queued()).toBe('angular') - expect(app.queuer.state().items).toEqual([]) - }) -}) diff --git a/examples/angular/injectQueuedComputed/src/app/app.ts b/examples/angular/injectQueuedComputed/src/app/app.ts deleted file mode 100644 index b700f022f..000000000 --- a/examples/angular/injectQueuedComputed/src/app/app.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, signal } from '@angular/core' -import { JsonPipe } from '@angular/common' -import { injectQueuedComputed } from '@tanstack/angular-pacer' -import { InputApp } from './inputapp' - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - imports: [JsonPipe, InputApp], -}) -export class App { - readonly source = signal('') - readonly currentValue = signal(50) - readonly instantExecutions = signal(1) - readonly queued = injectQueuedComputed(this.source, { maxSize: 25, wait: 500 }, (state) => state) - readonly queuer = this.queued.queuer - readonly rangeQueued = injectQueuedComputed( - this.currentValue, - { maxSize: 100, wait: 100 }, - (state) => state, - ) - readonly rangeQueue = this.rangeQueued.queuer - onSearch(value: string): void { - this.source.set(value) - } - onRange(value: number): void { - this.currentValue.set(value) - this.instantExecutions.update((count) => count + 1) - } -} diff --git a/examples/angular/injectQueuedComputed/README.md b/examples/angular/injectQueuedValue/README.md similarity index 100% rename from examples/angular/injectQueuedComputed/README.md rename to examples/angular/injectQueuedValue/README.md diff --git a/examples/angular/injectQueuedComputed/angular.json b/examples/angular/injectQueuedValue/angular.json similarity index 100% rename from examples/angular/injectQueuedComputed/angular.json rename to examples/angular/injectQueuedValue/angular.json diff --git a/examples/angular/injectQueuedComputed/eslint.config.js b/examples/angular/injectQueuedValue/eslint.config.js similarity index 100% rename from examples/angular/injectQueuedComputed/eslint.config.js rename to examples/angular/injectQueuedValue/eslint.config.js diff --git a/examples/angular/injectThrottledComputed/package.json b/examples/angular/injectQueuedValue/package.json similarity index 94% rename from examples/angular/injectThrottledComputed/package.json rename to examples/angular/injectQueuedValue/package.json index f65c79a22..13b9e5b95 100644 --- a/examples/angular/injectThrottledComputed/package.json +++ b/examples/angular/injectQueuedValue/package.json @@ -1,5 +1,5 @@ { - "name": "@tanstack/pacer-example-angular-inject-throttled-computed", + "name": "@tanstack/pacer-example-angular-inject-queued-value", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/examples/angular/injectQueuedComputed/public/favicon.ico b/examples/angular/injectQueuedValue/public/favicon.ico similarity index 100% rename from examples/angular/injectQueuedComputed/public/favicon.ico rename to examples/angular/injectQueuedValue/public/favicon.ico diff --git a/examples/angular/injectQueuedComputed/src/app/app.config.ts b/examples/angular/injectQueuedValue/src/app/app.config.ts similarity index 100% rename from examples/angular/injectQueuedComputed/src/app/app.config.ts rename to examples/angular/injectQueuedValue/src/app/app.config.ts diff --git a/examples/angular/injectQueuedComputed/src/app/app.css b/examples/angular/injectQueuedValue/src/app/app.css similarity index 100% rename from examples/angular/injectQueuedComputed/src/app/app.css rename to examples/angular/injectQueuedValue/src/app/app.css diff --git a/examples/angular/injectQueuedComputed/src/app/app.html b/examples/angular/injectQueuedValue/src/app/app.html similarity index 97% rename from examples/angular/injectQueuedComputed/src/app/app.html rename to examples/angular/injectQueuedValue/src/app/app.html index 0168d5e67..0f80a18fd 100644 --- a/examples/angular/injectQueuedComputed/src/app/app.html +++ b/examples/angular/injectQueuedValue/src/app/app.html @@ -1,4 +1,4 @@ -

TanStack Pacer injectQueuedComputed Example

+

TanStack Pacer injectQueuedValue Example

Queued search value