fix: index many-to-many query invalidation

This commit is contained in:
DongHyeonka
2026-08-01 23:17:09 +09:00
parent 853c2e3f30
commit 73a50426d6
18 changed files with 574 additions and 325 deletions
+53 -3
View File
@@ -4,8 +4,14 @@ import {
createRuntimeAdapters,
createRuntimeHttpClient,
} from "../../src/bootstrap/runtime-adapters.ts";
import { QUERY_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import { REFERENCE_FEATURE_ID } from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { INVALIDATION_REGISTRY } from "../../src/features/installed-feature-contracts.ts";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
} from "../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
import { bindQuery } from "../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../src/contracts/server-state-scope.ts";
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
@@ -54,6 +60,33 @@ const release: Release = {
routeChunks: { "route-home": "assets/home.js" },
};
function referenceBoundQueryKey() {
const scope: CacheScopeSnapshot = {
generation: 1,
fingerprint: "runtime-scope-fingerprint-0001",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => "runtime-identity-token-0001",
}),
signal: new AbortController().signal,
isCurrent: () => true,
};
return bindQuery(
{
definitionId: "reference-resource-runtime-test-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "GET_REFERENCE_RESOURCE",
profileId: "DETAIL_STANDARD",
measureResult: () => ({ itemCount: 1, estimatedBytes: 8 }),
execute: async () => ({ ok: true as const, value: "reference-1" }),
},
"reference-1",
scope,
).queryKey;
}
describe("runtime adapter composition", () => {
it("constructs the local demo seam and infrastructure adapters", async () => {
const adapters = await createRuntimeAdapters({
@@ -79,6 +112,23 @@ describe("runtime adapter composition", () => {
adapters.infrastructure.dispose();
});
it("invalidates a real bound query through the installed production graph", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const queryKey = referenceBoundQueryKey();
adapters.infrastructure.queryClient.setQueryData(queryKey, {
resourceId: "reference-1",
});
await adapters.infrastructure.queryInvalidation.invalidate([
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
]);
expect(
adapters.infrastructure.queryClient.getQueryState(queryKey)?.isInvalidated,
).toBe(true);
adapters.infrastructure.dispose();
});
it("executes installed feature HTTP through the composed contract registry", async () => {
const fetcher = vi.fn(async () =>
Response.json([{ id: "reference-1", name: "Direct contract payload" }]),
@@ -132,7 +182,7 @@ describe("runtime adapter composition", () => {
previousCoordinator,
);
const topic = Object.values(QUERY_REGISTRY)[0]?.invalidationTopic;
const topic = INVALIDATION_REGISTRY.topics[0];
if (!topic) throw new Error("expected an installed invalidation topic");
await previousCoordinator.invalidate([topic]);
expect(invalidatePrevious).not.toHaveBeenCalled();