From 97dbfb2aeb26442bf4705b928aff21b242603820 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 18:47:05 +0000 Subject: [PATCH 1/3] test(client-react): give the package a test harness and pin realtime hook behavior (#4682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/client-react` shipped 8 public hooks with `build` and `typecheck` as its only scripts and zero test files. `tsc --noEmit` is structurally blind to what actually breaks in a hook: a dependency array is a value, not a type, so a missing entry, a missing cleanup and a callback that never fires all typecheck perfectly. #4678 was exactly that shape — `useAutoRefresh` ignored predicate writes, the case that dirties a list hardest, and no type noticed. Adds the workspace's first DOM test environment (jsdom + @testing-library/react, `environment: 'jsdom'`; every other package runs `node`) and 17 tests over the realtime hooks covering the three things the type checker cannot see: - re-subscription driven by the dependency array — changing `object` opens a subscription on the new name and releases the old one, an unrelated re-render churns nothing, and the hooks key on the primitive `options?.recordId` / `options?.packageId` rather than the options object's identity, so an equal-but-new object stays a no-op; - release on unmount, including `useAutoRefresh`, which holds two subscriptions; - delivery — events reach state and callbacks, and `useAutoRefresh` refetches on the per-record *and* the bulk stream (the #4678 regression pin). Every assertion was verified by sabotage rather than assumed: dropping the `object` dep (1 failure), deleting a cleanup (3) and reverting `useAutoRefresh` to the single-stream version (3) each turn the suite red; reverting turns it green again. CI needs no wiring — `Test Core` partitions by package off `turbo ls`, and the new `test` script puts client-react on shard 2 (verified with scripts/partition-test-shards.mjs). No runtime code changed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LYnZrTwXbrctB8E8HpJAPT --- .changeset/client-react-test-harness.md | 35 ++ packages/client-react/package.json | 10 +- .../client-react/src/realtime-hooks.test.tsx | 405 +++++++++++++ packages/client-react/tsconfig.json | 2 +- packages/client-react/vitest.config.ts | 16 + pnpm-lock.yaml | 546 +++++++++++++++--- 6 files changed, 926 insertions(+), 88 deletions(-) create mode 100644 .changeset/client-react-test-harness.md create mode 100644 packages/client-react/src/realtime-hooks.test.tsx create mode 100644 packages/client-react/vitest.config.ts diff --git a/.changeset/client-react-test-harness.md b/.changeset/client-react-test-harness.md new file mode 100644 index 0000000000..4d4bc9cfe0 --- /dev/null +++ b/.changeset/client-react-test-harness.md @@ -0,0 +1,35 @@ +--- +"@objectstack/client-react": patch +--- + +test(client-react): give the package a test harness and pin the realtime hooks' behavior (#4682) + +`packages/client-react` shipped 8 public hooks with `build` and `typecheck` as +its only scripts and not a single test file. `tsc --noEmit` cannot see any of +what actually breaks in a hook: a dependency array is a value, not a type, so a +missing entry, a missing cleanup, and a callback that never fires all typecheck +perfectly. #4678 was precisely that shape — `useAutoRefresh` ignored predicate +writes, the one case that dirties a list hardest, and no type noticed. + +Adds the workspace's first DOM test environment (`jsdom` + `@testing-library/ +react`, `environment: 'jsdom'` — every other package runs `node`) and 17 tests +over the realtime hooks, covering the three things the type checker is blind to: + +- **Re-subscription on dependency change** — changing `object` opens a + subscription on the new name and releases the old one; a re-render that + changes nothing must not churn. Also pins that the hooks key on the primitive + `options?.recordId` / `options?.packageId` rather than on the options object's + identity, so an equal-but-new object stays a no-op. +- **Release on unmount** — every subscription hook unsubscribes, including + `useAutoRefresh`, which holds two. +- **Delivery** — events reach state and callbacks, and `useAutoRefresh` + refetches on the per-record *and* the bulk stream (the #4678 regression pin). + +Each assertion was verified by sabotage: dropping the `object` dep, deleting a +cleanup, and reverting `useAutoRefresh` to the single-stream version each turn +the suite red, and only reverting turns it green again. + +The package is picked up by CI's `Test Core` shards automatically — they +partition by package off `turbo ls`, so a `test` script is all that was needed. + +No runtime code changed. diff --git a/packages/client-react/package.json b/packages/client-react/package.json index 78e0165adb..46681e17d4 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -14,6 +14,8 @@ }, "scripts": { "build": "tsup src/index.tsx --config ../../tsup.config.ts", + "test": "vitest run", + "test:watch": "vitest", "typecheck": "tsc --noEmit" }, "peerDependencies": { @@ -25,8 +27,14 @@ "@objectstack/spec": "workspace:*" }, "devDependencies": { + "@testing-library/react": "^16.3.2", "@types/react": "^19.2.17", - "typescript": "^6.0.3" + "@types/react-dom": "^19.2.3", + "jsdom": "^30.0.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "typescript": "^6.0.3", + "vitest": "^4.1.10" }, "keywords": [ "objectstack", diff --git a/packages/client-react/src/realtime-hooks.test.tsx b/packages/client-react/src/realtime-hooks.test.tsx new file mode 100644 index 0000000000..ef5a3aeafe --- /dev/null +++ b/packages/client-react/src/realtime-hooks.test.tsx @@ -0,0 +1,405 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Behavioral tests for the realtime subscription hooks (#4682). + * + * `tsc --noEmit` was this package's only gate until now, and it is structurally + * blind to everything below: a dependency array is a value, not a type, so a + * missing entry, a missing cleanup, and a callback that is simply never invoked + * all typecheck perfectly. + * + * The three things covered here are exactly the three the type checker cannot + * see: + * + * 1. **Re-subscription on dependency change** — changing `object` must drop + * the old subscription and open one on the new name. The mirror case + * matters just as much: a re-render that changes nothing must NOT churn the + * subscription. + * 2. **Unsubscribe on unmount** — a missed cleanup leaks the subscription and + * keeps delivering into an unmounted tree. + * 3. **The callback actually fires** — `useAutoRefresh` must refetch on BOTH + * event streams. That it did not fire for the bulk stream was the whole of + * #4678, and no type would have caught it. + * + * The client is faked rather than driven end-to-end on purpose: the contract + * under test is what these hooks ask of `client.events` and when they let go, + * not whether the transport delivers. `packages/client/src/realtime-api-data. + * test.ts` covers the other side of that boundary (and is the style reference + * for the fixtures below). + */ + +import * as React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { renderHook, act } from '@testing-library/react'; +import type { ObjectStackClient } from '@objectstack/client'; +import type { BulkDataEvent, DataEvent, MetadataEvent } from '@objectstack/spec/api'; +import { ObjectStackProvider } from './context'; +import { + useAutoRefresh, + useBulkDataSubscription, + useBulkDataSubscriptionCallback, + useDataSubscription, + useDataSubscriptionCallback, + useMetadataSubscription, +} from './realtime-hooks'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const RECORD_EVENT: DataEvent = { + id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + type: 'data.record.updated', + object: 'project_task', + recordId: 'task_1', + after: { id: 'task_1', title: 'Ship it', status: 'done' }, + timestamp: '2026-08-02T12:00:00.000Z', +} as DataEvent; + +const BULK_EVENT: BulkDataEvent = { + id: '3f2504e0-4f89-41d3-9a0c-0305e82c3301', + type: 'data.records.updated', + object: 'project_task', + matched: 40, + timestamp: '2026-08-02T12:00:00.000Z', +} as BulkDataEvent; + +const METADATA_EVENT = { + id: '9f8b0f6e-1c2d-4a3b-8c9d-0e1f2a3b4c5d', + type: 'metadata.object.updated', + name: 'project_task', + timestamp: '2026-08-02T12:00:00.000Z', +} as unknown as MetadataEvent; + +// --------------------------------------------------------------------------- +// Fake client +// --------------------------------------------------------------------------- + +interface Recorded { + object: string; + options?: { recordId?: string; packageId?: string }; + /** Push an event into this subscription's callback. */ + deliver: (event: E) => void; + unsubscribe: ReturnType; +} + +/** + * Records every `subscribe*` call and hands back a distinct unsubscribe spy per + * call, so a test can tell "re-subscribed" from "kept the old subscription" — + * a single shared spy could not. + */ +function createFakeClient() { + const data: Recorded[] = []; + const bulk: Recorded[] = []; + const metadata: Recorded[] = []; + + function recorder(log: Recorded[]) { + return ( + object: string, + callback: (event: E) => void, + options?: Recorded['options'] + ) => { + const unsubscribe = vi.fn(); + log.push({ object, options, deliver: callback, unsubscribe }); + return unsubscribe; + }; + } + + const client = { + events: { + subscribeData: vi.fn(recorder(data)), + subscribeBulkData: vi.fn(recorder(bulk)), + subscribeMetadata: vi.fn(recorder(metadata)), + }, + // ObjectStackProvider mirrors the active locale onto the client during + // render; without this the provider throws before any hook runs. + setLocale: vi.fn(), + } as unknown as ObjectStackClient; + + return { client, data, bulk, metadata }; +} + +function wrapperFor(client: ObjectStackClient) { + return function Wrapper({ children }: { children: React.ReactNode }) { + return {children}; + }; +} + +// --------------------------------------------------------------------------- +// 1. Re-subscription driven by the dependency array +// --------------------------------------------------------------------------- + +describe('#4682 dependency arrays drive re-subscription', () => { + it('useDataSubscription re-subscribes to the new object and drops the old one', () => { + const { client, data } = createFakeClient(); + const { rerender } = renderHook(({ object }) => useDataSubscription(object), { + wrapper: wrapperFor(client), + initialProps: { object: 'project_task' }, + }); + + expect(data.map((s) => s.object)).toEqual(['project_task']); + expect(data[0].unsubscribe).not.toHaveBeenCalled(); + + rerender({ object: 'account' }); + + // Both halves matter: a missing `object` dep would leave the subscription + // on 'project_task' (still receiving the wrong object's events), and a + // missing cleanup would leave BOTH open. + expect(data.map((s) => s.object)).toEqual(['project_task', 'account']); + expect(data[0].unsubscribe).toHaveBeenCalledTimes(1); + expect(data[1].unsubscribe).not.toHaveBeenCalled(); + }); + + it('useDataSubscription does not churn the subscription on an unrelated re-render', () => { + const { client, data } = createFakeClient(); + const { rerender } = renderHook(({ object }) => useDataSubscription(object), { + wrapper: wrapperFor(client), + initialProps: { object: 'project_task' }, + }); + + rerender({ object: 'project_task' }); + rerender({ object: 'project_task' }); + + expect(data).toHaveLength(1); + expect(data[0].unsubscribe).not.toHaveBeenCalled(); + }); + + it('useDataSubscription keys on options.recordId, not on the options object identity', () => { + const { client, data } = createFakeClient(); + const { rerender } = renderHook( + ({ options }) => useDataSubscription('project_task', options), + { + wrapper: wrapperFor(client), + initialProps: { options: { recordId: 'task_1' } }, + } + ); + + // A caller writing `useDataSubscription('t', { recordId: id })` inline + // creates a fresh object every render. The hook depends on the primitive + // `options?.recordId`, so an equal-but-new object must be a no-op — + // "simplifying" that dep to `options` would resubscribe on every render. + rerender({ options: { recordId: 'task_1' } }); + expect(data).toHaveLength(1); + + // ...while a genuinely different record must re-subscribe. + rerender({ options: { recordId: 'task_2' } }); + expect(data).toHaveLength(2); + expect(data[1].options?.recordId).toBe('task_2'); + expect(data[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('useBulkDataSubscription re-subscribes when the object changes', () => { + const { client, bulk } = createFakeClient(); + const { rerender } = renderHook(({ object }) => useBulkDataSubscription(object), { + wrapper: wrapperFor(client), + initialProps: { object: 'project_task' }, + }); + + expect(bulk.map((s) => s.object)).toEqual(['project_task']); + + rerender({ object: 'account' }); + + expect(bulk.map((s) => s.object)).toEqual(['project_task', 'account']); + expect(bulk[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('useMetadataSubscription keys on type and options.packageId', () => { + const { client, metadata } = createFakeClient(); + const { rerender } = renderHook( + ({ type, options }) => useMetadataSubscription(type, options), + { + wrapper: wrapperFor(client), + initialProps: { type: 'object', options: { packageId: 'crm' } }, + } + ); + + rerender({ type: 'object', options: { packageId: 'crm' } }); + expect(metadata).toHaveLength(1); + + rerender({ type: 'view', options: { packageId: 'crm' } }); + expect(metadata).toHaveLength(2); + expect(metadata[1].object).toBe('view'); + + rerender({ type: 'view', options: { packageId: 'sales' } }); + expect(metadata).toHaveLength(3); + expect(metadata[2].options?.packageId).toBe('sales'); + }); +}); + +// --------------------------------------------------------------------------- +// 2. Unsubscribe on unmount +// --------------------------------------------------------------------------- + +describe('#4682 subscriptions are released on unmount', () => { + it('useDataSubscription unsubscribes', () => { + const { client, data } = createFakeClient(); + const { unmount } = renderHook(() => useDataSubscription('project_task'), { + wrapper: wrapperFor(client), + }); + + expect(data[0].unsubscribe).not.toHaveBeenCalled(); + unmount(); + expect(data[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('useBulkDataSubscription unsubscribes', () => { + const { client, bulk } = createFakeClient(); + const { unmount } = renderHook(() => useBulkDataSubscription('project_task'), { + wrapper: wrapperFor(client), + }); + + unmount(); + expect(bulk[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('useMetadataSubscription unsubscribes', () => { + const { client, metadata } = createFakeClient(); + const { unmount } = renderHook(() => useMetadataSubscription('object'), { + wrapper: wrapperFor(client), + }); + + unmount(); + expect(metadata[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('useAutoRefresh releases BOTH streams', () => { + const { client, data, bulk } = createFakeClient(); + const refetch = vi.fn(); + const { unmount } = renderHook(() => useAutoRefresh('project_task', refetch), { + wrapper: wrapperFor(client), + }); + + expect(data).toHaveLength(1); + expect(bulk).toHaveLength(1); + + unmount(); + + expect(data[0].unsubscribe).toHaveBeenCalledTimes(1); + expect(bulk[0].unsubscribe).toHaveBeenCalledTimes(1); + }); + + it('an unmounted useDataSubscription does not set state from a late event', () => { + const { client, data } = createFakeClient(); + const { unmount } = renderHook(() => useDataSubscription('project_task'), { + wrapper: wrapperFor(client), + }); + + const deliver = data[0].deliver; + unmount(); + + // A transport that already had the event in flight when the component went + // away. The hook is unsubscribed, so this is a no-op — it must not warn or + // throw. (React logs an "update on unmounted component" error for this + // class of bug rather than throwing, so assert on the console too.) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(() => act(() => deliver(RECORD_EVENT))).not.toThrow(); + expect(consoleError).not.toHaveBeenCalled(); + consoleError.mockRestore(); + }); +}); + +// --------------------------------------------------------------------------- +// 3. The callbacks actually fire +// --------------------------------------------------------------------------- + +describe('#4682 events reach the caller', () => { + it('useDataSubscription exposes the latest per-record event', () => { + const { client, data } = createFakeClient(); + const { result } = renderHook(() => useDataSubscription('project_task'), { + wrapper: wrapperFor(client), + }); + + expect(result.current).toBeNull(); + + act(() => data[0].deliver(RECORD_EVENT)); + + expect(result.current).toEqual(RECORD_EVENT); + expect(result.current?.recordId).toBe('task_1'); + }); + + it('useBulkDataSubscription exposes the latest aggregate event', () => { + const { client, bulk } = createFakeClient(); + const { result } = renderHook(() => useBulkDataSubscription('project_task'), { + wrapper: wrapperFor(client), + }); + + act(() => bulk[0].deliver(BULK_EVENT)); + + expect(result.current?.matched).toBe(40); + expect(result.current?.type).toBe('data.records.updated'); + }); + + it('useDataSubscriptionCallback forwards the event without re-rendering state', () => { + const { client, data } = createFakeClient(); + const seen: DataEvent[] = []; + const callback = (e: DataEvent) => seen.push(e); + + renderHook(() => useDataSubscriptionCallback('project_task', callback), { + wrapper: wrapperFor(client), + }); + + act(() => data[0].deliver(RECORD_EVENT)); + + expect(seen).toHaveLength(1); + expect(seen[0].recordId).toBe('task_1'); + }); + + it('useBulkDataSubscriptionCallback forwards the aggregate event', () => { + const { client, bulk } = createFakeClient(); + const seen: BulkDataEvent[] = []; + const callback = (e: BulkDataEvent) => seen.push(e); + + renderHook(() => useBulkDataSubscriptionCallback('project_task', callback), { + wrapper: wrapperFor(client), + }); + + act(() => bulk[0].deliver(BULK_EVENT)); + + expect(seen).toHaveLength(1); + expect(seen[0].matched).toBe(40); + }); + + it('useAutoRefresh refetches on a per-record write', () => { + const { client, data } = createFakeClient(); + const refetch = vi.fn(); + renderHook(() => useAutoRefresh('project_task', refetch), { + wrapper: wrapperFor(client), + }); + + act(() => data[0].deliver(RECORD_EVENT)); + + expect(refetch).toHaveBeenCalledTimes(1); + }); + + it('useAutoRefresh refetches on a predicate write (#4678 regression)', () => { + const { client, bulk } = createFakeClient(); + const refetch = vi.fn(); + renderHook(() => useAutoRefresh('project_task', refetch), { + wrapper: wrapperFor(client), + }); + + // The defect #4678 fixed: this hook watched only the per-record stream, so + // a `multi: true` write — the one that can change every row on screen — + // left the list stale while a single-row edit refreshed it. Nothing about + // the types changed when it was fixed, which is why this pin exists. + act(() => bulk[0].deliver(BULK_EVENT)); + + expect(refetch).toHaveBeenCalledTimes(1); + }); + + it('useAutoRefresh refetches for a bulk write even when narrowed to one record', () => { + const { client, bulk } = createFakeClient(); + const refetch = vi.fn(); + renderHook(() => useAutoRefresh('project_task', refetch, { recordId: 'task_1' }), { + wrapper: wrapperFor(client), + }); + + // A bulk event carries only a count, so there is no way to tell whether + // task_1 was in the match set. Refetching anyway is the deliberate choice + // (#4678): a redundant query beats showing a record a predicate write + // already changed. + act(() => bulk[0].deliver(BULK_EVENT)); + + expect(refetch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/client-react/tsconfig.json b/packages/client-react/tsconfig.json index 9b95dafa09..a82ab7edfb 100644 --- a/packages/client-react/tsconfig.json +++ b/packages/client-react/tsconfig.json @@ -10,5 +10,5 @@ "types": ["node", "react"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts"] + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"] } diff --git a/packages/client-react/vitest.config.ts b/packages/client-react/vitest.config.ts new file mode 100644 index 0000000000..4af82d1a18 --- /dev/null +++ b/packages/client-react/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // The only DOM environment in the workspace, and deliberately so (#4682): + // these hooks are exercised through React's real renderer, so `document` + // and friends must exist. Every other package here runs `environment: + // 'node'` — copying that default would fail at `document is not defined` + // before a single assertion ran. + environment: 'jsdom', + exclude: [ + '**/node_modules/**', + '**/dist/**', + ], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f948481ac5..18a2a4313e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,7 +157,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) examples/app-showcase: dependencies: @@ -206,7 +206,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) examples/app-todo: dependencies: @@ -249,7 +249,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) examples/embed-objectql: dependencies: @@ -271,7 +271,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/adapters/hono: dependencies: @@ -293,7 +293,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/apps/account: dependencies: @@ -315,7 +315,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/apps/setup: dependencies: @@ -337,7 +337,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/apps/studio: dependencies: @@ -359,7 +359,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/cli: dependencies: @@ -558,7 +558,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) optionalDependencies: better-sqlite3: specifier: ^13.0.1 @@ -596,7 +596,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/client-react: dependencies: @@ -609,16 +609,31 @@ importers: '@objectstack/spec': specifier: workspace:* version: link:../spec - react: - specifier: '>=18.0.0' - version: 19.2.7 devDependencies: + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/react': specifier: ^19.2.17 version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + jsdom: + specifier: ^30.0.1 + version: 30.0.1(@noble/hashes@2.2.0) + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) typescript: specifier: ^6.0.3 version: 6.0.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/cloud-connection: dependencies: @@ -643,7 +658,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/connectors/connector-mcp: dependencies: @@ -668,7 +683,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/connectors/connector-openapi: dependencies: @@ -690,7 +705,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/connectors/connector-rest: dependencies: @@ -712,7 +727,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/connectors/connector-slack: dependencies: @@ -734,7 +749,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/console: {} @@ -758,7 +773,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/create-objectstack: dependencies: @@ -783,7 +798,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/formula: dependencies: @@ -799,7 +814,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/lint: dependencies: @@ -824,7 +839,7 @@ importers: version: 26.1.1 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/mcp: dependencies: @@ -855,7 +870,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/metadata: dependencies: @@ -904,7 +919,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/metadata-core: dependencies: @@ -929,7 +944,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/metadata-fs: dependencies: @@ -951,7 +966,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/metadata-protocol: dependencies: @@ -985,7 +1000,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/objectql: dependencies: @@ -1019,7 +1034,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/observability: dependencies: @@ -1035,7 +1050,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/platform-objects: dependencies: @@ -1057,7 +1072,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/driver-memory: dependencies: @@ -1079,7 +1094,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/driver-mongodb: dependencies: @@ -1110,7 +1125,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/driver-sql: dependencies: @@ -1150,7 +1165,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) optionalDependencies: better-sqlite3: specifier: ^13.0.1 @@ -1188,7 +1203,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/embedder-openai: dependencies: @@ -1204,7 +1219,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/knowledge-memory: dependencies: @@ -1226,7 +1241,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/knowledge-ragflow: dependencies: @@ -1248,7 +1263,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-approvals: dependencies: @@ -1288,7 +1303,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-audit: dependencies: @@ -1310,7 +1325,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-auth: dependencies: @@ -1362,7 +1377,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-dev: dependencies: @@ -1420,7 +1435,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-email: dependencies: @@ -1445,7 +1460,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-hono-server: dependencies: @@ -1476,7 +1491,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-pinyin-search: dependencies: @@ -1501,7 +1516,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-reports: dependencies: @@ -1526,7 +1541,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-security: dependencies: @@ -1551,7 +1566,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-sharing: dependencies: @@ -1582,7 +1597,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/plugins/plugin-webhooks: dependencies: @@ -1604,7 +1619,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/qa/dogfood: dependencies: @@ -1686,7 +1701,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/qa/downstream-contract: dependencies: @@ -1699,7 +1714,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/qa/http-conformance: dependencies: @@ -1727,7 +1742,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/rest: dependencies: @@ -1770,7 +1785,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/runtime: dependencies: @@ -1852,7 +1867,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) optionalDependencies: '@objectstack/driver-mongodb': specifier: workspace:* @@ -1865,7 +1880,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-analytics: dependencies: @@ -1890,7 +1905,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-automation: dependencies: @@ -1912,7 +1927,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-cache: dependencies: @@ -1934,7 +1949,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-cluster: dependencies: @@ -1953,7 +1968,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-cluster-redis: dependencies: @@ -1978,7 +1993,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-datasource: dependencies: @@ -2018,7 +2033,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-i18n: dependencies: @@ -2040,7 +2055,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-job: dependencies: @@ -2065,7 +2080,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-knowledge: dependencies: @@ -2084,7 +2099,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-messaging: dependencies: @@ -2106,7 +2121,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-package: dependencies: @@ -2128,7 +2143,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-queue: dependencies: @@ -2150,7 +2165,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-realtime: dependencies: @@ -2172,7 +2187,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-settings: dependencies: @@ -2200,7 +2215,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-sms: dependencies: @@ -2219,7 +2234,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/services/service-storage: dependencies: @@ -2253,7 +2268,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/spec: dependencies: @@ -2278,7 +2293,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/triggers/trigger-api: dependencies: @@ -2297,7 +2312,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/triggers/trigger-record-change: dependencies: @@ -2322,7 +2337,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/triggers/trigger-schedule: dependencies: @@ -2344,7 +2359,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/types: dependencies: @@ -2357,7 +2372,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/verify: dependencies: @@ -2412,7 +2427,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/vscode-objectstack: devDependencies: @@ -2451,6 +2466,14 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@asamuzakjp/css-color@6.0.5': + resolution: {integrity: sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + '@authenio/xml-encryption@2.0.2': resolution: {integrity: sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg==} engines: {node: '>=12'} @@ -2738,6 +2761,10 @@ packages: '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + '@changesets/apply-release-plan@7.1.1': resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} @@ -2799,6 +2826,42 @@ packages: '@cloudflare/workers-types@4.20260520.1': resolution: {integrity: sha512-wdmf9Fwabp06OgK9ZyCl8Q77GZ94k9J7OA9qA65FbgxZ/hd3hYRkVNiY7Bvsx4tKim+sWmKqGOblecZInAvoRg==} + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.10': + resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7': + resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -2997,6 +3060,15 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + 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 + '@fast-csv/format@4.3.5': resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} @@ -4306,6 +4378,25 @@ packages: '@tanstack/store@0.9.3': resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@textlint/ast-node-types@15.7.1': resolution: {integrity: sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==} @@ -4357,6 +4448,9 @@ packages: '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -4848,6 +4942,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansis@3.17.0: resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} engines: {node: '>=14'} @@ -4880,6 +4978,9 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} @@ -5068,6 +5169,9 @@ packages: resolution: {integrity: sha512-LYpmOXdkpQYf4wmlxkdzW01XGlOXNIbjLg45yNkh0FQ4814VbK9PdOFmhZpYbej+EZtR/i3FDdhEG98HqZdgnA==} engines: {node: '>=22'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -5397,6 +5501,10 @@ packages: css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.2.2: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} @@ -5560,6 +5668,10 @@ packages: dagre-d3-es@7.0.14: resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} @@ -5581,6 +5693,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} @@ -5650,6 +5765,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -5729,6 +5847,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -6340,6 +6462,10 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -6499,6 +6625,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -6597,6 +6726,15 @@ packages: resolution: {integrity: sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==} hasBin: true + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -6983,6 +7121,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -7065,6 +7207,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -7624,6 +7769,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -7827,6 +7975,10 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -7896,6 +8048,9 @@ packages: peerDependencies: react: ^19.2.8 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -7926,10 +8081,6 @@ packages: '@types/react': optional: true - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} - engines: {node: '>=0.10.0'} - react@19.2.8: resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} engines: {node: '>=0.10.0'} @@ -8113,6 +8264,10 @@ packages: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -8381,6 +8536,9 @@ packages: resolution: {integrity: sha512-p4HDLDogGHKRKCrgckQHNs5PEfXkju6JI5jTywueaKJI5hAdjPohEhRtQ0M1SWC/+TA73SPln+r7srr+7e4nZA==} engines: {node: '>=18'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -8498,6 +8656,10 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -8609,6 +8771,10 @@ packages: resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} engines: {node: '>=20.18.1'} + undici@8.9.0: + resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} + engines: {node: '>=22.19.0'} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -8805,6 +8971,10 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -8812,6 +8982,10 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -8825,10 +8999,22 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -8885,6 +9071,10 @@ packages: xml-escape@1.1.0: resolution: {integrity: sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg==} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xml-naming@0.3.0: resolution: {integrity: sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==} engines: {node: '>=16.0.0'} @@ -9011,6 +9201,21 @@ snapshots: package-manager-detector: 1.7.0 tinyexec: 1.2.4 + '@asamuzakjp/css-color@6.0.5': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + '@authenio/xml-encryption@2.0.2': dependencies: '@xmldom/xmldom': 0.8.13 @@ -9444,6 +9649,10 @@ snapshots: '@braintree/sanitize-url@7.1.2': {} + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + '@changesets/apply-release-plan@7.1.1': dependencies: '@changesets/config': 3.1.4 @@ -9592,6 +9801,30 @@ snapshots: '@cloudflare/workers-types@4.20260520.1': optional: true + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -9721,6 +9954,10 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@exodus/bytes@1.15.1(@noble/hashes@2.2.0)': + optionalDependencies: + '@noble/hashes': 2.2.0 + '@fast-csv/format@4.3.5': dependencies: '@types/node': 14.18.63 @@ -10936,6 +11173,27 @@ snapshots: '@tanstack/store@0.9.3': optional: true + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@textlint/ast-node-types@15.7.1': {} '@textlint/linter-formatter@15.7.1': @@ -10994,6 +11252,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 26.1.1 @@ -11300,7 +11560,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.0 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/expect@4.1.10': dependencies: @@ -11522,6 +11782,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansis@3.17.0: {} any-promise@1.3.0: {} @@ -11574,6 +11836,10 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + aria-query@5.3.1: {} array-union@2.1.0: {} @@ -11678,7 +11944,7 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) svelte: 5.56.6(@typescript-eslint/types@8.65.0) - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -11707,6 +11973,10 @@ snapshots: node-addon-api: 8.9.0 optional: true + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + big-integer@1.6.52: {} binary@0.3.0: @@ -12012,6 +12282,11 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + css-what@6.2.2: {} csstype@3.2.3: {} @@ -12200,6 +12475,13 @@ snapshots: d3: 7.9.0 lodash-es: 4.18.1 + data-urls@7.0.0(@noble/hashes@2.2.0): + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.2.0) + transitivePeerDependencies: + - '@noble/hashes' + dayjs@1.11.21: {} debug@4.3.4: @@ -12212,6 +12494,8 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -12265,6 +12549,8 @@ snapshots: dependencies: path-type: 4.0.0 + dom-accessibility-api@0.5.16: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -12346,6 +12632,8 @@ snapshots: entities@7.0.1: {} + entities@8.0.0: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -13142,6 +13430,12 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-encoding-sniffer@6.0.0(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + transitivePeerDependencies: + - '@noble/hashes' + html-escaper@2.0.2: {} html-void-elements@3.0.0: {} @@ -13283,6 +13577,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} is-property@1.0.2: {} @@ -13368,6 +13664,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@30.0.1(@noble/hashes@2.2.0): + dependencies: + '@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.7(css-tree@3.2.1) + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@2.2.0) + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@2.2.0) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.9.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0(@noble/hashes@2.2.0) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -13668,6 +13990,8 @@ snapshots: dependencies: react: 19.2.8 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -13866,6 +14190,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.27.1: {} + mdurl@2.0.0: {} media-typer@1.1.0: {} @@ -14560,6 +14886,10 @@ snapshots: dependencies: entities: 6.0.1 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + parseurl@1.3.3: {} path-browserify@1.0.1: {} @@ -14721,6 +15051,12 @@ snapshots: prettier@2.8.8: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + proc-log@4.2.0: {} process-nextick-args@2.0.1: {} @@ -14798,6 +15134,8 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 + react-is@17.0.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.8): dependencies: react: 19.2.8 @@ -14825,8 +15163,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - react@19.2.7: {} - react@19.2.8: {} read-pkg@9.0.1: @@ -15121,6 +15457,10 @@ snapshots: dependencies: xmlchars: 2.2.0 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} scroll-into-view-if-needed@3.1.0: @@ -15456,6 +15796,8 @@ snapshots: transitivePeerDependencies: - '@typescript-eslint/types' + symbol-tree@3.2.4: {} + table@6.9.0: dependencies: ajv: 8.20.0 @@ -15591,12 +15933,15 @@ snapshots: tough-cookie@6.0.2: dependencies: tldts: 7.4.9 - optional: true tr46@5.1.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + traverse@0.3.9: {} tree-kill@1.2.2: {} @@ -15707,6 +16052,8 @@ snapshots: undici@7.28.0: {} + undici@8.9.0: {} + unicorn-magic@0.1.0: {} unicorn-magic@0.3.0: {} @@ -15846,7 +16193,7 @@ snapshots: tsx: 4.23.1 yaml: 2.9.0 - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(happy-dom@20.10.2)(jsdom@30.0.1(@noble/hashes@2.2.0))(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(msw@2.14.6(@types/node@26.1.1)(typescript@6.0.3))(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) @@ -15873,13 +16220,20 @@ snapshots: '@types/node': 26.1.1 '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) happy-dom: 20.10.2 + jsdom: 30.0.1(@noble/hashes@2.2.0) transitivePeerDependencies: - msw + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + web-namespaces@2.0.1: {} webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -15889,11 +16243,29 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-mimetype@5.0.0: {} + whatwg-url@14.2.0: dependencies: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@16.0.1(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 @@ -15938,6 +16310,8 @@ snapshots: xml-escape@1.1.0: {} + xml-name-validator@5.0.0: {} + xml-naming@0.3.0: {} xml2js@0.5.0: From 00766830facc21a66af7e1fa8bb6681fa05ccc75 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 18:50:45 +0000 Subject: [PATCH 2/3] fix(client-react): typecheck the package's own tests instead of excluding them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:type-check-coverage` failed on the first push, and it was right. The initial commit added `**/*.test.tsx` to tsconfig's `exclude` to match the `**/*.test.ts` entry every sibling package carries — but that entry is frozen DEBT the repo is migrating away from (#4311), not a convention to copy. The gate's own summary makes it explicit: 21 packages still exclude their tests, carrying 2243 frozen raw errors in TEST_DEBT. Excluding one more would have reported green over source `tsc` never read. Drops the test exclusion entirely, so client-react typechecks its own tests and stays out of that ledger. Doing so immediately surfaced a real gap: the `METADATA_EVENT` fixture was declared and never used, because `useMetadataSubscription` was covered for re-subscription and unmount but not for delivery. Adds that assertion rather than deleting the fixture — 18 tests now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LYnZrTwXbrctB8E8HpJAPT --- packages/client-react/src/realtime-hooks.test.tsx | 13 +++++++++++++ packages/client-react/tsconfig.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/client-react/src/realtime-hooks.test.tsx b/packages/client-react/src/realtime-hooks.test.tsx index ef5a3aeafe..d96a73529f 100644 --- a/packages/client-react/src/realtime-hooks.test.tsx +++ b/packages/client-react/src/realtime-hooks.test.tsx @@ -329,6 +329,19 @@ describe('#4682 events reach the caller', () => { expect(result.current?.type).toBe('data.records.updated'); }); + it('useMetadataSubscription exposes the latest metadata event', () => { + const { client, metadata } = createFakeClient(); + const { result } = renderHook(() => useMetadataSubscription('object'), { + wrapper: wrapperFor(client), + }); + + expect(result.current).toBeNull(); + + act(() => metadata[0].deliver(METADATA_EVENT)); + + expect(result.current).toEqual(METADATA_EVENT); + }); + it('useDataSubscriptionCallback forwards the event without re-rendering state', () => { const { client, data } = createFakeClient(); const seen: DataEvent[] = []; diff --git a/packages/client-react/tsconfig.json b/packages/client-react/tsconfig.json index a82ab7edfb..1e9b83d2d9 100644 --- a/packages/client-react/tsconfig.json +++ b/packages/client-react/tsconfig.json @@ -10,5 +10,5 @@ "types": ["node", "react"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"] + "exclude": ["node_modules", "dist"] } From 9e44db445cac760614703100c694a0443b0162fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 19:09:11 +0000 Subject: [PATCH 3/3] fix(client-react): stop five hooks from looping on dependency identity (#4693, #4694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five hooks keyed a `useCallback`/`useEffect` on values the caller supplies inline — `where`/`fields`/`orderBy` objects, `onSuccess`/`onError` handlers, and the `fetcher` `useMetadata` takes as a required positional argument. Inline means a fresh identity every render, so the effect re-ran every render; because the fetch hooks call `setState`, that render caused another. Under the hooks' own documented usage this was an unbounded request loop. Requests issued in 250ms by one mounted component, before → after: useQuery (inline where) 4691 → 1 useInfiniteQuery (inline where) 6611 → 1 useObject (NO options at all) 4306 → 1 useView (inline onSuccess) 8197 → 1 useMetadata (inline fetcher) 7654 → 1 useObject and useMetadata needed no particular usage to loop: the former depended on its own `data`/`etag` state while writing both, the latter takes its fetcher positionally so there is no non-inline way to call it. useMutation was never affected — no effect drives it. The same root cause churned the realtime subscriptions (#4694): useAutoRefresh with an unmemoized `refetch` — which is exactly what useQuery returned every render — resubscribed on both streams every render, losing any event delivered in the unsubscribe/resubscribe gap. Adds two internal primitives (not exported): `stableKey` derives a dependency from a structural VALUE (sorted keys, array order preserved, since `orderBy` is positional) so a rebuilt-but-equal object is a no-op; `useEventCallback` gives a handler a fixed identity while always invoking its latest version, synced in an effect rather than during render so a discarded concurrent render cannot publish a handler that never committed. Fixing this by asking callers to memoize was rejected: the TSDoc examples themselves pass object literals, and correctness must not rest on every call site remembering `useMemo`. 13 tests, each verified by reverting the fix it guards — restoring identity deps in useQuery (3 red), useObject's self-referential state (3), useMetadata's fetcher dep (2) and the subscription callback dep (2). Counts are asserted exactly rather than as an upper bound, which would pass on a loop that merely got slower. Coverage includes the inverse direction: a changed value still refetches, and every stabilized handler runs its newest version, so the ref indirection cannot silently trade a loop for a stale closure. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LYnZrTwXbrctB8E8HpJAPT --- .../client-react-dependency-identity-loops.md | 44 +++ packages/client-react/src/data-hooks.tsx | 45 ++- .../src/hook-dependency-identity.test.tsx | 360 ++++++++++++++++++ packages/client-react/src/internal-deps.ts | 76 ++++ packages/client-react/src/metadata-hooks.tsx | 57 ++- packages/client-react/src/realtime-hooks.tsx | 41 +- 6 files changed, 586 insertions(+), 37 deletions(-) create mode 100644 .changeset/client-react-dependency-identity-loops.md create mode 100644 packages/client-react/src/hook-dependency-identity.test.tsx create mode 100644 packages/client-react/src/internal-deps.ts diff --git a/.changeset/client-react-dependency-identity-loops.md b/.changeset/client-react-dependency-identity-loops.md new file mode 100644 index 0000000000..6589c5a853 --- /dev/null +++ b/.changeset/client-react-dependency-identity-loops.md @@ -0,0 +1,44 @@ +--- +"@objectstack/client-react": patch +--- + +fix(client-react): stop five hooks from looping on dependency identity (#4693, #4694) + +Five hooks keyed a `useCallback`/`useEffect` on values the caller supplies +inline — `where` / `fields` / `orderBy` objects, `onSuccess` / `onError` +handlers, and the `fetcher` `useMetadata` takes as a required positional +argument. Inline means a fresh identity on every render, so the effect re-ran on +every render; because the fetch hooks call `setState`, that render caused +another. The result was an unbounded request loop under the hooks' own +documented usage. + +Requests issued in 250ms by a single mounted component, measured before and +after: + +| hook | before | after | +|-----------------------------------|-------:|------:| +| `useQuery` (inline `where`) | 4691 | 1 | +| `useInfiniteQuery` (inline `where`) | 6611 | 1 | +| `useObject` (no options at all) | 4306 | 1 | +| `useView` (inline `onSuccess`) | 8197 | 1 | +| `useMetadata` (inline `fetcher`) | 7654 | 1 | + +`useObject` and `useMetadata` needed no particular usage to loop: the former +depended on its own `data` and `etag` state while writing both, and the latter +takes its fetcher positionally, so there is no non-inline way to call it. +`useMutation` was never affected — no effect drives it. + +The same root cause churned the realtime subscriptions (#4694): +`useAutoRefresh` with an unmemoized `refetch` — which is what `useQuery` +returned on every render — resubscribed on both streams every render, losing any +event delivered in the unsubscribe/resubscribe gap. + +Two internal primitives fix both halves: `stableKey` derives a dependency from a +structural value (sorted keys, array order preserved) so a rebuilt-but-equal +object is a no-op, and `useEventCallback` gives a handler a fixed identity while +always invoking its latest version. Neither is exported. + +A changed *value* still refetches, and every stabilized handler is asserted to +run its newest version rather than the one captured when the effect first ran — +the ref indirection would otherwise trade a loop for a stale closure. 13 tests +cover this, each verified by reverting the fix it guards. diff --git a/packages/client-react/src/data-hooks.tsx b/packages/client-react/src/data-hooks.tsx index e204fadb19..3a187a9795 100644 --- a/packages/client-react/src/data-hooks.tsx +++ b/packages/client-react/src/data-hooks.tsx @@ -10,6 +10,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { QueryAST, FilterCondition } from '@objectstack/spec/data'; import { PaginatedResult } from '@objectstack/client'; import { useClient } from './context'; +import { stableKey, useEventCallback } from './internal-deps'; /** * Query options for useQuery hook. @@ -113,6 +114,26 @@ export function useQuery( const resolvedLimit = limit; const resolvedOffset = offset; + // The query shape as a VALUE (#4693). `where` / `fields` / `orderBy` are + // objects and arrays, and the documented usage builds them inline, so keying + // the fetch on their identities re-ran it every render — and since it calls + // `setData`, every render caused another render. Measured before this fix: + // `useQuery('todo_task', { where: { status: 'open' } })` issued 4691 `find` + // calls in 250ms; the same call with a hoisted options object issued 1. + const queryKey = stableKey({ + query, + where: resolvedWhere, + fields: resolvedFields, + orderBy: resolvedSort, + limit: resolvedLimit, + offset: resolvedOffset, + }); + + // Handlers say what to do with a result; they are not part of what is being + // fetched, so they must not drive refetching. + const handleSuccess = useEventCallback(onSuccess); + const handleError = useEventCallback(onError); + const fetchData = useCallback(async (isRefetch = false) => { if (!enabled) return; @@ -141,16 +162,16 @@ export function useQuery( } setData(result); - onSuccess?.(result); + handleSuccess(result); } catch (err) { const error = err instanceof Error ? err : new Error('Query failed'); setError(error); - onError?.(error); + handleError(error); } finally { setIsLoading(false); setIsRefetching(false); } - }, [client, object, query, resolvedFields, resolvedWhere, resolvedSort, resolvedLimit, resolvedOffset, enabled, onSuccess, onError]); + }, [client, object, queryKey, enabled, handleSuccess, handleError]); // Initial fetch and dependency-based refetch useEffect(() => { @@ -520,6 +541,18 @@ export function useInfiniteQuery( const resolvedWhere = where; const resolvedSort = orderBy; + // Same value-keyed dependency as useQuery (#4693) — measured at 6611 `find` + // calls in 250ms before this fix, with inline options. + const queryKey = stableKey({ + query, + where: resolvedWhere, + fields: resolvedFields, + orderBy: resolvedSort, + pageSize, + }); + const handleSuccess = useEventCallback(onSuccess); + const handleError = useEventCallback(onError); + const [pages, setPages] = useState[]>([]); const [isLoading, setIsLoading] = useState(true); const [isFetchingNextPage, setIsFetchingNextPage] = useState(false); @@ -564,16 +597,16 @@ export function useInfiniteQuery( const hasMore = fetchedCount === pageSize; setHasNextPage(hasMore); - onSuccess?.(result); + handleSuccess(result); } catch (err) { const error = err instanceof Error ? err : new Error('Query failed'); setError(error); - onError?.(error); + handleError(error); } finally { setIsLoading(false); setIsFetchingNextPage(false); } - }, [client, object, query, resolvedFields, resolvedWhere, resolvedSort, pageSize, onSuccess, onError]); + }, [client, object, queryKey, handleSuccess, handleError]); // Initial fetch useEffect(() => { diff --git a/packages/client-react/src/hook-dependency-identity.test.tsx b/packages/client-react/src/hook-dependency-identity.test.tsx new file mode 100644 index 0000000000..0d53448486 --- /dev/null +++ b/packages/client-react/src/hook-dependency-identity.test.tsx @@ -0,0 +1,360 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Regression tests for the dependency-identity loops (#4693) and the + * subscription churn (#4694). + * + * Five hooks keyed a `useCallback`/`useEffect` on values the caller supplies + * inline — `where`/`fields`/`orderBy` objects, `onSuccess`/`onError` handlers, + * the `fetcher` `useMetadata` takes as a required argument. Inline means a new + * identity every render, so the effect re-ran every render, and because the + * fetch hooks call `setState` that render caused another. Measured on `main` + * before the fix, requests issued in 250ms from a single mounted component: + * + * | hook | before | after | + * |-----------------------------------|-------:|------:| + * | useQuery (inline `where`) | 4691 | 1 | + * | useInfiniteQuery (inline `where`) | 6611 | 1 | + * | useObject (NO options at all) | 4306 | 1 | + * | useView (inline `onSuccess`) | 8197 | 1 | + * | useMetadata (inline `fetcher`) | 7654 | 1 | + * + * `useObject` and `useMetadata` needed no particular usage to loop — the former + * depended on its own `data`/`etag` state while writing both, and the latter + * takes its fetcher positionally, so there is no non-inline way to call it. + * + * The counts are asserted as exact numbers rather than "small": a bound like + * `< 10` would pass on a loop that merely got slower, which is the failure this + * whole file exists to prevent. + * + * Each fix also has a matching staleness test. The mechanism — hold the handler + * in a ref, drop it from the deps — trades one bug for a worse one if the ref + * is read late or synced wrong, so every stabilized handler is asserted to run + * its LATEST version, not the one captured at subscribe time. + */ + +import * as React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, renderHook, act } from '@testing-library/react'; +import type { ObjectStackClient } from '@objectstack/client'; +import type { DataEvent } from '@objectstack/spec/api'; +import { ObjectStackProvider } from './context'; +import { useQuery, useInfiniteQuery } from './data-hooks'; +import { useObject, useView, useMetadata } from './metadata-hooks'; +import { useAutoRefresh, useDataSubscriptionCallback } from './realtime-hooks'; +import { stableKey } from './internal-deps'; + +const RECORD_EVENT: DataEvent = { + id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + type: 'data.record.updated', + object: 'project_task', + recordId: 'task_1', + timestamp: '2026-08-02T12:00:00.000Z', +} as DataEvent; + +/** Long enough for a runaway loop to make itself obvious (it managed ~5000). */ +const settle = () => new Promise((resolve) => setTimeout(resolve, 250)); + +function createFakeClient() { + const find = vi.fn(async (_object: string, _options?: Record) => ({ + records: [], + total: 0, + })); + const getCached = vi.fn(async (_objectName: string, _options?: Record) => ({ + data: { name: 'todo_task' }, + etag: { value: 'W/"1"' }, + notModified: false, + })); + const getItem = vi.fn(async (_kind: string, _objectName: string) => ({ name: 'todo_task' })); + const getView = vi.fn(async (_objectName: string, _viewType: string) => ({ + name: 'todo_task_list', + })); + const subscriptions: { unsubscribe: ReturnType; deliver: (e: DataEvent) => void }[] = []; + + const client = { + data: { find, query: find }, + meta: { getCached, getItem, getView }, + events: { + subscribeData: vi.fn((_object: string, callback: (e: DataEvent) => void) => { + const unsubscribe = vi.fn(); + subscriptions.push({ unsubscribe, deliver: callback }); + return unsubscribe; + }), + subscribeBulkData: vi.fn(() => vi.fn()), + subscribeMetadata: vi.fn(() => vi.fn()), + }, + setLocale: vi.fn(), + } as unknown as ObjectStackClient; + + return { client, find, getCached, getItem, getView, subscriptions }; +} + +function mount(client: ObjectStackClient, Component: React.ComponentType) { + return render( + + + + ); +} + +// --------------------------------------------------------------------------- +// #4693 — the fetch hooks issue exactly one request +// --------------------------------------------------------------------------- + +describe('#4693 fetch hooks key on values, not identities', () => { + it('useQuery fetches once with an inline options object', async () => { + const { client, find } = createFakeClient(); + mount(client, () => { + useQuery('todo_task', { where: { status: 'open' } as any, fields: ['id', 'subject'] }); + return null; + }); + + await settle(); + + expect(find).toHaveBeenCalledTimes(1); + }); + + it('useInfiniteQuery fetches once with an inline options object', async () => { + const { client, find } = createFakeClient(); + mount(client, () => { + useInfiniteQuery('todo_task', { where: { status: 'open' } as any }); + return null; + }); + + await settle(); + + expect(find).toHaveBeenCalledTimes(1); + }); + + it('useObject fetches once — it used to loop on its own state', async () => { + const { client, getCached } = createFakeClient(); + mount(client, () => { + useObject('todo_task'); + return null; + }); + + await settle(); + + // The pre-fix loop needed no options at all: `data` and `etag` were in the + // fetch's dependency array and the fetch wrote both. + expect(getCached).toHaveBeenCalledTimes(1); + }); + + it('useView fetches once with an inline onSuccess', async () => { + const { client, getView } = createFakeClient(); + mount(client, () => { + useView('todo_task', 'list', { onSuccess: () => {} }); + return null; + }); + + await settle(); + + expect(getView).toHaveBeenCalledTimes(1); + }); + + it('useMetadata fetches once with an inline fetcher', async () => { + const { client } = createFakeClient(); + const fetcher = vi.fn(async () => ({ ok: true })); + mount(client, () => { + useMetadata(() => fetcher()); + return null; + }); + + await settle(); + + // `fetcher` is positional and required, so inline is the only natural call. + expect(fetcher).toHaveBeenCalledTimes(1); + }); +}); + +// --------------------------------------------------------------------------- +// #4693 — a changed VALUE still refetches +// --------------------------------------------------------------------------- + +describe('#4693 value changes still drive a refetch', () => { + it('useQuery refetches when the where VALUE changes, not when it is rebuilt', async () => { + const { client, find } = createFakeClient(); + + function Harness({ status }: { status: string }) { + useQuery('todo_task', { where: { status } as any }); + return null; + } + + const { rerender } = render( + + + + ); + await settle(); + expect(find).toHaveBeenCalledTimes(1); + + // Same value, freshly built object — must not refetch. Without the + // value-keyed dependency this is where the loop restarted. + rerender( + + + + ); + await settle(); + expect(find).toHaveBeenCalledTimes(1); + + // Genuinely different filter — must refetch, or the fix would have traded a + // loop for a stale list. + rerender( + + + + ); + await settle(); + expect(find).toHaveBeenCalledTimes(2); + expect((find.mock.calls[1] as any)[1].where).toEqual({ status: 'done' }); + }); + + it('useObject refetches when the object name changes', async () => { + const { client, getCached } = createFakeClient(); + + function Harness({ name }: { name: string }) { + useObject(name); + return null; + } + + const { rerender } = render( + + + + ); + await settle(); + expect(getCached).toHaveBeenCalledTimes(1); + + rerender( + + + + ); + await settle(); + expect(getCached).toHaveBeenCalledTimes(2); + expect(getCached.mock.calls[1][0]).toBe('account'); + }); +}); + +// --------------------------------------------------------------------------- +// #4693 / #4694 — stabilized handlers must not go stale +// --------------------------------------------------------------------------- + +describe('#4693 stabilized handlers still run their latest version', () => { + it('useQuery calls the onSuccess from the current render', async () => { + const { client } = createFakeClient(); + const first = vi.fn(); + const second = vi.fn(); + + function Harness({ onSuccess }: { onSuccess: () => void }) { + const { refetch } = useQuery('todo_task', { where: { status: 'open' } as any, onSuccess }); + // Expose refetch so the test can trigger a second call after the handler + // has been swapped. + (globalThis as any).__refetch = refetch; + return null; + } + + const { rerender } = render( + + + + ); + await settle(); + expect(first).toHaveBeenCalledTimes(1); + + rerender( + + + + ); + await settle(); + + // Swapping the handler must not refetch (that was the loop)... + expect(second).not.toHaveBeenCalled(); + + // ...but the next fetch must reach the NEW handler, not the one captured + // when the effect first ran. + await act(async () => { + await (globalThis as any).__refetch(); + }); + expect(second).toHaveBeenCalledTimes(1); + expect(first).toHaveBeenCalledTimes(1); + delete (globalThis as any).__refetch; + }); +}); + +// --------------------------------------------------------------------------- +// #4694 — subscriptions stop churning +// --------------------------------------------------------------------------- + +describe('#4694 subscription callbacks do not churn the subscription', () => { + it('useAutoRefresh subscribes once across renders with an unmemoized refetch', () => { + const { client } = createFakeClient(); + const events = (client as any).events; + + const { rerender } = renderHook( + () => useAutoRefresh('project_task', () => {}), + { + wrapper: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + } + ); + + rerender(); + rerender(); + + // Before the fix: 3 renders → 3 subscriptions on each stream, each render + // unsubscribing the previous one and losing anything delivered in the gap. + expect(events.subscribeData).toHaveBeenCalledTimes(1); + expect(events.subscribeBulkData).toHaveBeenCalledTimes(1); + }); + + it('useDataSubscriptionCallback delivers to the latest callback after a swap', () => { + const { client, subscriptions } = createFakeClient(); + const first = vi.fn(); + const second = vi.fn(); + + const { rerender } = renderHook( + ({ callback }) => useDataSubscriptionCallback('project_task', callback), + { + wrapper: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + initialProps: { callback: first as (e: DataEvent) => void }, + } + ); + + rerender({ callback: second as (e: DataEvent) => void }); + + // Still one subscription — and it must invoke the CURRENT callback. A ref + // synced in the wrong phase would deliver to `first` forever, which is a + // worse bug than the churn this replaced. + expect(subscriptions).toHaveLength(1); + act(() => subscriptions[0].deliver(RECORD_EVENT)); + + expect(second).toHaveBeenCalledTimes(1); + expect(first).not.toHaveBeenCalled(); + }); +}); + +// --------------------------------------------------------------------------- +// stableKey +// --------------------------------------------------------------------------- + +describe('stableKey', () => { + it('is insensitive to key order but sensitive to values', () => { + expect(stableKey({ a: 1, b: 2 })).toBe(stableKey({ b: 2, a: 1 })); + expect(stableKey({ a: 1 })).not.toBe(stableKey({ a: 2 })); + }); + + it('preserves array order, which is semantic for orderBy', () => { + expect(stableKey(['-created_at', 'name'])).not.toBe(stableKey(['name', '-created_at'])); + }); + + it('distinguishes nested differences and undefined from absent', () => { + expect(stableKey({ where: { a: { b: 1 } } })).not.toBe(stableKey({ where: { a: { b: 2 } } })); + expect(stableKey({ a: undefined })).not.toBe(stableKey({})); + }); +}); diff --git a/packages/client-react/src/internal-deps.ts b/packages/client-react/src/internal-deps.ts new file mode 100644 index 0000000000..58706035aa --- /dev/null +++ b/packages/client-react/src/internal-deps.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Dependency-array primitives (#4693, #4694) — internal, not exported from the + * package entry point. + * + * Every fetch and subscription hook here keys a `useCallback`/`useEffect` on + * values the caller supplies inline: `where` / `fields` / `orderBy` objects, + * `onSuccess` / `onError` handlers, the `fetcher` `useMetadata` takes as a + * required argument. Inline means a fresh identity on every render, so the + * effect re-ran on every render — and for the fetch hooks that effect calls + * `setState`, which renders again. Five hooks were in an unbounded request + * loop under their own documented usage. + * + * The two helpers below fix the two halves of that: + * + * - {@link stableKey} turns a structural value into a dependency that changes + * when the *value* changes rather than when the object is rebuilt; + * - {@link useEventCallback} gives a caller's handler a fixed identity, so + * passing it inline no longer re-runs anything. + * + * Neither is a substitute for the caller memoizing — they remove the need to. + * Correctness must not rest on every call site remembering `useMemo`, least of + * all when the TSDoc examples themselves pass object literals. + */ + +import { useCallback, useEffect, useRef } from 'react'; + +/** + * Order-independent stringify, used to derive a dependency from a structural + * value. Object keys are sorted so `{ a, b }` and `{ b, a }` agree; array order + * is preserved because it is semantic (`orderBy: ['-created_at', 'name']`). + * + * Mirrors the same helper in `service-settings` and `service-automation` — kept + * local for the same reason they are: it is three lines and a shared util + * package would be a heavier dependency than the code it carries. + * + * Values JSON cannot represent (functions, symbols) collapse to `undefined` + * here. That is correct for this use: a handler's identity must not drive a + * refetch, which is exactly what {@link useEventCallback} is for. + */ +export function stableKey(input: unknown): string { + if (input === null || typeof input !== 'object') return JSON.stringify(input) ?? 'undefined'; + if (Array.isArray(input)) return '[' + input.map(stableKey).join(',') + ']'; + const obj = input as Record; + const keys = Object.keys(obj).sort(); + return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableKey(obj[k])).join(',') + '}'; +} + +/** + * Wraps a caller-supplied handler in a function whose identity never changes, + * while always invoking the most recent version. + * + * This is what lets the fetch and subscription effects drop `onSuccess` / + * `onError` / `callback` from their dependency arrays. Those handlers say what + * to do when something happens; they are not part of *what is subscribed to* or + * *what is fetched*, so they have no business re-running an effect. + * + * The ref is updated in an effect rather than during render: a render may be + * thrown away under concurrent rendering, and writing through a ref then would + * publish a handler from a render that never committed. + * + * Returns `undefined` from the call when no handler was supplied, so optional + * handlers need no call-site guard. + */ +export function useEventCallback( + fn: ((...args: A) => R) | undefined +): (...args: A) => R | undefined { + const ref = useRef(fn); + + useEffect(() => { + ref.current = fn; + }, [fn]); + + return useCallback((...args: A) => ref.current?.(...args), []); +} diff --git a/packages/client-react/src/metadata-hooks.tsx b/packages/client-react/src/metadata-hooks.tsx index b31f4cc1b7..bf5ab00100 100644 --- a/packages/client-react/src/metadata-hooks.tsx +++ b/packages/client-react/src/metadata-hooks.tsx @@ -6,8 +6,9 @@ * React hooks for accessing ObjectStack metadata (schemas, views, fields) */ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { useClient, useObjectStackLocale } from './context'; +import { useEventCallback } from './internal-deps'; /** * Metadata query options @@ -89,6 +90,22 @@ export function useObject( onError } = options; + // `data` and `etag` are this hook's OWN state, and the fetch writes both. + // Depending on them made the fetch its own trigger: `setData` → new `data` + // identity → new `fetchMetadata` → the effect below re-ran → fetch again. + // Unlike the data hooks this needed no particular usage to fire — measured at + // 4306 metadata requests in 250ms for a bare `useObject('todo_task')` + // (#4693). They are read, never depended on, so they belong in refs. + const dataRef = useRef(data); + const etagRef = useRef(etag); + useEffect(() => { + dataRef.current = data; + etagRef.current = etag; + }, [data, etag]); + + const handleSuccess = useEventCallback(onSuccess); + const handleError = useEventCallback(onError); + const fetchMetadata = useCallback(async () => { if (!enabled) return; @@ -100,7 +117,7 @@ export function useObject( if (useCache) { // Use cached metadata endpoint const result = await client.meta.getCached(objectName, { - ifNoneMatch: ifNoneMatch || etag, + ifNoneMatch: ifNoneMatch || etagRef.current, ifModifiedSince }); @@ -113,21 +130,21 @@ export function useObject( } } - onSuccess?.(result.data || data); + handleSuccess(result.data || dataRef.current); } else { // Direct fetch without cache const result = await client.meta.getItem('object', objectName); setData(result); - onSuccess?.(result); + handleSuccess(result); } } catch (err) { const error = err instanceof Error ? err : new Error('Failed to fetch object metadata'); setError(error); - onError?.(error); + handleError(error); } finally { setIsLoading(false); } - }, [client, objectName, locale, enabled, useCache, ifNoneMatch, ifModifiedSince, etag, data, onSuccess, onError]); + }, [client, objectName, locale, enabled, useCache, ifNoneMatch, ifModifiedSince, handleSuccess, handleError]); useEffect(() => { fetchMetadata(); @@ -180,6 +197,9 @@ export function useView( const { enabled = true, onSuccess, onError } = options; + const handleSuccess = useEventCallback(onSuccess); + const handleError = useEventCallback(onError); + const fetchView = useCallback(async () => { if (!enabled) return; @@ -189,15 +209,15 @@ export function useView( const result = await client.meta.getView(objectName, viewType); setData(result); - onSuccess?.(result); + handleSuccess(result); } catch (err) { const error = err instanceof Error ? err : new Error('Failed to fetch view configuration'); setError(error); - onError?.(error); + handleError(error); } finally { setIsLoading(false); } - }, [client, objectName, viewType, locale, enabled, onSuccess, onError]); + }, [client, objectName, viewType, locale, enabled, handleSuccess, handleError]); useEffect(() => { fetchView(); @@ -286,6 +306,15 @@ export function useMetadata( const { enabled = true, onSuccess, onError } = options; + // `fetcher` is a REQUIRED positional argument, so an inline arrow is the only + // natural way to call this hook — which made the loop unconditional in + // practice (7654 fetcher invocations in 250ms before this fix, #4693). + // Stabilizing it here means the fetch re-runs on `client` / `locale` / + // `enabled` changes, as intended, and not on the caller's render cadence. + const runFetcher = useEventCallback(fetcher); + const handleSuccess = useEventCallback(onSuccess); + const handleError = useEventCallback(onError); + const fetchMetadata = useCallback(async () => { if (!enabled) return; @@ -293,17 +322,17 @@ export function useMetadata( setIsLoading(true); setError(null); - const result = await fetcher(client); - setData(result); - onSuccess?.(result); + const result = await runFetcher(client); + setData(result as T); + handleSuccess(result as T); } catch (err) { const error = err instanceof Error ? err : new Error('Failed to fetch metadata'); setError(error); - onError?.(error); + handleError(error); } finally { setIsLoading(false); } - }, [client, fetcher, locale, enabled, onSuccess, onError]); + }, [client, runFetcher, locale, enabled, handleSuccess, handleError]); useEffect(() => { fetchMetadata(); diff --git a/packages/client-react/src/realtime-hooks.tsx b/packages/client-react/src/realtime-hooks.tsx index e57ec36251..9135e6c827 100644 --- a/packages/client-react/src/realtime-hooks.tsx +++ b/packages/client-react/src/realtime-hooks.tsx @@ -7,9 +7,10 @@ * Events are automatically cleaned up when components unmount. */ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState } from 'react'; import type { MetadataEvent, DataEvent, BulkDataEvent } from '@objectstack/spec/api'; import { useClient } from './context'; +import { useEventCallback } from './internal-deps'; /** * Hook to subscribe to metadata events @@ -134,20 +135,25 @@ export function useMetadataSubscriptionCallback( options?: { packageId?: string } ): void { const client = useClient(); + // The callback is what RUNS on an event, not part of what is subscribed to + // (#4694). Depending on its identity tore down and reopened the subscription + // on every render whenever the caller passed an inline function — which the + // examples above do — losing any event that arrived in the gap. + const handleEvent = useEventCallback(callback); useEffect(() => { if (!client) return; const unsubscribe = client.events.subscribeMetadata( type, - callback, + handleEvent, options ); return () => { unsubscribe(); }; - }, [client, type, callback, options?.packageId]); + }, [client, type, handleEvent, options?.packageId]); } /** @@ -176,20 +182,23 @@ export function useDataSubscriptionCallback( options?: { recordId?: string } ): void { const client = useClient(); + // Stable identity so an inline callback does not churn the subscription on + // every render (#4694) — see useMetadataSubscriptionCallback. + const handleEvent = useEventCallback(callback); useEffect(() => { if (!client) return; const unsubscribe = client.events.subscribeData( object, - callback, + handleEvent, options ); return () => { unsubscribe(); }; - }, [client, object, callback, options?.recordId]); + }, [client, object, handleEvent, options?.recordId]); } /** @@ -270,16 +279,19 @@ export function useBulkDataSubscriptionCallback( callback: (event: BulkDataEvent) => void ): void { const client = useClient(); + // Stable identity so an inline callback does not churn the subscription on + // every render (#4694) — see useMetadataSubscriptionCallback. + const handleEvent = useEventCallback(callback); useEffect(() => { if (!client) return; - const unsubscribe = client.events.subscribeBulkData(object, callback); + const unsubscribe = client.events.subscribeBulkData(object, handleEvent); return () => { unsubscribe(); }; - }, [client, object, callback]); + }, [client, object, handleEvent]); } /** @@ -355,19 +367,14 @@ export function useAutoRefresh( refetch: () => void, options?: { recordId?: string } ): void { - const handleEvent = useCallback((_event: DataEvent) => { - // Refetch on any data change - refetch(); - }, [refetch]); + // No `useCallback` needed: both subscription hooks stabilize the handler + // themselves (#4694), so a caller passing an unmemoized `refetch` — which + // `useQuery` returned on every render before #4693 — no longer resubscribes. + useDataSubscriptionCallback(object, (_event: DataEvent) => refetch(), options); // A bulk event carries only a count, so when `options.recordId` narrows this // hook to one record there is no way to tell whether that record was in the // match set. Refetch anyway: a redundant query is cheap, and the alternative // is showing a record that a predicate write already changed. - const handleBulkEvent = useCallback((_event: BulkDataEvent) => { - refetch(); - }, [refetch]); - - useDataSubscriptionCallback(object, handleEvent, options); - useBulkDataSubscriptionCallback(object, handleBulkEvent); + useBulkDataSubscriptionCallback(object, (_event: BulkDataEvent) => refetch()); }