chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
@@ -0,0 +1,347 @@
import { CURRENT_API_VERSION } from "~/api/versions";
import {
NativeRealtimeClient,
type RealtimeListEnvironment,
} from "~/services/realtime/nativeRealtimeClient.server";
import { type RealtimeRunRow } from "~/services/realtime/electricStreamProtocol.server";
import { EnvChangeRouter } from "~/services/realtime/envChangeRouter.server";
import { setTimeout as sleep } from "node:timers/promises";
import { describe, expect, it, vi } from "vitest";
const ENV: RealtimeListEnvironment = { id: "env_1", organizationId: "org_1", projectId: "proj_1" };
function row(id: string): RealtimeRunRow {
// Only id/createdAt/updatedAt are read directly; the rest serialize to null.
return {
id,
createdAt: new Date("2026-06-07T09:00:00.000Z"),
updatedAt: new Date("2026-06-07T10:00:00.000Z"),
} as unknown as RealtimeRunRow;
}
function makeClient(overrides: Record<string, unknown> = {}) {
const resolveSpy = vi.fn(async () => ["run_1", "run_2"]);
const hydrateSpy = vi.fn(async (_env: string, ids: string[]) => ids.map(row));
const client = new NativeRealtimeClient({
runReader: { getRunById: async () => null, hydrateByIds: hydrateSpy } as any,
runListResolver: { resolveMatchingRunIds: resolveSpy } as any,
// No-op source: live polls never get a router wake, so they fall through to the
// backstop full-resolve — which is what the live tests below assert on.
router: new EnvChangeRouter({
source: { subscribeToEnv: () => () => {} },
hydrator: { hydrateByIds: hydrateSpy },
replayWindowMs: 0,
unsubscribeLingerMs: 0,
}),
limiter: { incrementAndCheck: async () => true, decrement: async () => {} } as any,
cachedLimitProvider: { getCachedLimit: async () => 100 },
maximumCreatedAtFilterAgeMs: 24 * 60 * 60 * 1000,
runSetResolveCacheTtlMs: 5_000,
...overrides,
});
return { client, resolveSpy, hydrateSpy };
}
// streamBatch with offset=-1 takes the snapshot path, which calls the coalescing
// resolve+hydrate directly (no concurrency slot / subscription needed).
function snapshot(client: NativeRealtimeClient, batchId: string, skipColumns?: string) {
const skip = skipColumns ? `&skipColumns=${skipColumns}` : "";
return client.streamBatch(
`http://localhost:3030/realtime/v1/batches/${batchId}?offset=-1${skip}`,
ENV,
batchId,
CURRENT_API_VERSION,
undefined,
"1.0.0"
);
}
// Tag-list snapshot (offset=-1) — exercises the createdAt bucketing + cache key.
function snapshotTag(client: NativeRealtimeClient, tags: string[]) {
return client.streamRuns(
"http://localhost:3030/realtime/v1/runs?offset=-1",
ENV,
{ tags },
CURRENT_API_VERSION,
undefined,
"1.0.0"
);
}
describe("NativeRealtimeClient run-set resolve coalescing + cache", () => {
it("coalesces concurrent same-filter resolves into one ClickHouse + Postgres query", async () => {
const { client, resolveSpy, hydrateSpy } = makeClient();
let release!: (ids: string[]) => void;
const gate = new Promise<string[]>((resolve) => {
release = resolve;
});
resolveSpy.mockReturnValueOnce(gate);
const p1 = snapshot(client, "batch_1");
const p2 = snapshot(client, "batch_1");
release(["run_1"]);
await Promise.all([p1, p2]);
expect(resolveSpy).toHaveBeenCalledTimes(1);
expect(hydrateSpy).toHaveBeenCalledTimes(1);
});
it("serves a second same-filter request from the cache within the TTL", async () => {
const { client, resolveSpy, hydrateSpy } = makeClient();
await snapshot(client, "batch_1");
await snapshot(client, "batch_1");
expect(resolveSpy).toHaveBeenCalledTimes(1);
expect(hydrateSpy).toHaveBeenCalledTimes(1);
});
it("does not share the cache across different filters", async () => {
const { client, resolveSpy } = makeClient();
await snapshot(client, "batch_1");
await snapshot(client, "batch_2");
expect(resolveSpy).toHaveBeenCalledTimes(2);
});
it("re-queries after the cache TTL expires", async () => {
vi.useFakeTimers({ toFake: ["Date"] });
try {
const { client, resolveSpy } = makeClient({ runSetResolveCacheTtlMs: 1_000 });
await snapshot(client, "batch_1");
vi.advanceTimersByTime(1_001);
await snapshot(client, "batch_1");
expect(resolveSpy).toHaveBeenCalledTimes(2);
} finally {
vi.useRealTimers();
}
});
it("passes the client's skipColumns through to the hydrator (column projection)", async () => {
const { client, hydrateSpy } = makeClient();
await snapshot(client, "batch_1", "payload,output");
expect(hydrateSpy).toHaveBeenCalledWith("env_1", expect.any(Array), ["payload", "output"]);
});
it("reports resolve outcomes (miss then hit) to the metrics hook", async () => {
const results: string[] = [];
const { client } = makeClient({ onRunSetResolve: (r: string) => results.push(r) });
await snapshot(client, "batch_1");
await snapshot(client, "batch_1");
expect(results).toEqual(["miss", "hit"]);
});
it("mints a distinct batch handle per connection and echoes a client-provided one", async () => {
const { client } = makeClient();
// Two subscribers to the SAME batch must never share a handle (the working-set
// cache is keyed by it; sharing lets one suppress the other's deltas forever).
const res1 = await snapshot(client, "batch_1");
const res2 = await snapshot(client, "batch_1");
const h1 = res1.headers.get("electric-handle");
const h2 = res2.headers.get("electric-handle");
expect(h1).toBeTruthy();
expect(h1).not.toBe(h2);
// Catch-up under an existing handle keeps it.
const res3 = await client.streamBatch(
`http://localhost:3030/realtime/v1/batches/batch_1?offset=123_1&handle=${h1}`,
ENV,
"batch_1",
CURRENT_API_VERSION,
undefined,
"1.0.0"
);
expect(res3.headers.get("electric-handle")).toBe(h1);
});
});
describe("NativeRealtimeClient resolve admission gate (mass-reconnect stampede)", () => {
// A resolver that blocks each invocation until released, so we can watch how many run
// concurrently. Tracks peak concurrency and exposes a release-one-at-a-time drain.
function gatedResolver() {
let active = 0;
let peak = 0;
const releases: Array<() => void> = [];
const resolve = vi.fn(async () => {
active++;
peak = Math.max(peak, active);
await new Promise<void>((r) => releases.push(r));
active--;
return ["run_1"];
});
return {
resolve,
peak: () => peak,
releaseOne: () => releases.shift()?.(),
waiting: () => releases.length,
};
}
function makeGatedClient(
resolveAdmissionLimit: number,
resolver: ReturnType<typeof gatedResolver>
) {
const hydrateSpy = vi.fn(async (_env: string, ids: string[]) => ids.map(row));
return new NativeRealtimeClient({
runReader: { getRunById: async () => null, hydrateByIds: hydrateSpy } as any,
runListResolver: { resolveMatchingRunIds: resolver.resolve } as any,
router: new EnvChangeRouter({
source: { subscribeToEnv: () => () => {} },
hydrator: { hydrateByIds: hydrateSpy },
replayWindowMs: 0,
unsubscribeLingerMs: 0,
}),
limiter: { incrementAndCheck: async () => true, decrement: async () => {} } as any,
cachedLimitProvider: { getCachedLimit: async () => 100 },
maximumCreatedAtFilterAgeMs: 24 * 60 * 60 * 1000,
runSetResolveCacheTtlMs: 0, // no cache -> every distinct filter is a fresh resolve
resolveAdmissionLimit,
});
}
it("throttles a distinct-filter stampede to the admission limit of concurrent CH resolves", async () => {
const resolver = gatedResolver();
const client = makeGatedClient(2, resolver);
// 5 distinct batchIds => 5 distinct filters => 5 fresh resolves, fired at once.
const polls = [0, 1, 2, 3, 4].map((i) => snapshot(client, `batch_${i}`));
// Only the limit (2) may run concurrently; the rest queue for a permit.
await vi.waitFor(() => expect(resolver.resolve).toHaveBeenCalledTimes(2));
await sleep(20);
expect(resolver.resolve).toHaveBeenCalledTimes(2); // 3 still queued behind the gate
expect(resolver.peak()).toBe(2);
// Drain: each release frees a permit, admitting exactly one queued resolve.
while (resolver.waiting() > 0) {
resolver.releaseOne();
await sleep(5);
}
await Promise.all(polls);
expect(resolver.resolve).toHaveBeenCalledTimes(5); // all ran...
expect(resolver.peak()).toBe(2); // ...but never more than the limit at once
});
it("lets a same-filter burst through on a single permit (coalesces before the gate)", async () => {
const resolver = gatedResolver();
const client = makeGatedClient(1, resolver); // limit 1 would deadlock if each took a permit
// 5 identical filters fired at once -> single-flight collapses to one in-flight resolve.
const polls = [0, 1, 2, 3, 4].map(() => snapshot(client, "batch_same"));
await vi.waitFor(() => expect(resolver.resolve).toHaveBeenCalledTimes(1));
await sleep(20);
resolver.releaseOne();
await Promise.all(polls);
expect(resolver.resolve).toHaveBeenCalledTimes(1); // one resolve, one permit, no queue
});
});
describe("NativeRealtimeClient tag-list createdAt bucketing", () => {
it("floors the resolved createdAt lower bound to the bucket boundary", async () => {
// Fix the clock to a non-bucket-aligned instant so the assertion is deterministic.
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-06-07T10:00:30.500Z"));
try {
const { client, resolveSpy } = makeClient({ runSetCreatedAtBucketMs: 60_000 });
await snapshotTag(client, ["critical"]);
const passed = resolveSpy.mock.calls[0][0].createdAtAfter as Date;
expect(passed.getTime() % 60_000).toBe(0);
} finally {
vi.useRealTimers();
}
});
it("lets two same-tag feeds in the same bucket share one resolve", async () => {
// A large bucket guarantees both windows floor to the same boundary regardless of
// the sub-millisecond gap between the two calls.
const { client, resolveSpy, hydrateSpy } = makeClient({
runSetCreatedAtBucketMs: 60 * 60_000,
});
await snapshotTag(client, ["critical"]);
await snapshotTag(client, ["critical"]);
expect(resolveSpy).toHaveBeenCalledTimes(1);
expect(hydrateSpy).toHaveBeenCalledTimes(1);
});
it("does not share across different tags", async () => {
const { client, resolveSpy } = makeClient({ runSetCreatedAtBucketMs: 60 * 60_000 });
await snapshotTag(client, ["critical"]);
await snapshotTag(client, ["debug"]);
expect(resolveSpy).toHaveBeenCalledTimes(2);
});
it("does not collide a comma-containing tag with two separate tags", async () => {
const { client, resolveSpy } = makeClient({ runSetCreatedAtBucketMs: 60 * 60_000 });
await snapshotTag(client, ["a,b"]); // one tag "a,b"
await snapshotTag(client, ["a", "b"]); // two tags a OR b — a different filter
expect(resolveSpy).toHaveBeenCalledTimes(2);
});
it("keeps each feed's exact lower bound when bucketing is disabled (0)", async () => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-06-07T10:00:30.500Z"));
try {
const { client, resolveSpy } = makeClient({ runSetCreatedAtBucketMs: 0 });
await snapshotTag(client, ["critical"]);
const passed = resolveSpy.mock.calls[0][0].createdAtAfter as Date;
// Exact (now - 24h) lower bound, not floored to a 60s boundary.
expect(passed.getTime() % 60_000).not.toBe(0);
} finally {
vi.useRealTimers();
}
});
});
describe("NativeRealtimeClient review fixes", () => {
// makeClient's router has a no-op source, so the live poll never gets a wake and falls
// through to its backstop timeout — the full ClickHouse resolve these tests assert on
// (createdAt clamp / concurrency limit).
it("clamps a stale/crafted handle's createdAt up to the max-age floor", async () => {
const maxAge = 24 * 60 * 60 * 1000;
const { client, resolveSpy } = makeClient({
maximumCreatedAtFilterAgeMs: maxAge,
runSetCreatedAtBucketMs: 0,
livePollTimeoutMs: 50,
});
const before = Date.now();
// Handle encodes createdAt = 1ms epoch, far older than the 24h ceiling.
await client.streamRuns(
"http://localhost:3030/realtime/v1/runs?offset=123_1&live=true&handle=runs_1_7",
ENV,
{ tags: ["t"] },
CURRENT_API_VERSION,
undefined,
"1.0.0"
);
const passed = resolveSpy.mock.calls[0][0].createdAtAfter as Date;
// Clamped to ~now - maxAge, not the epoch value encoded in the handle.
expect(passed.getTime()).toBeGreaterThan(before - maxAge - 1_000);
});
it("enforces a concurrency limit of 0 instead of failing with a 500", async () => {
let limitCheckedWith: number | undefined;
const { client } = makeClient({
cachedLimitProvider: { getCachedLimit: async () => 0 },
limiter: {
incrementAndCheck: async (_env: string, _id: string, limit: number) => {
limitCheckedWith = limit;
return true;
},
decrement: async () => {},
},
livePollTimeoutMs: 50,
});
const res = await client.streamBatch(
"http://localhost:3030/realtime/v1/batches/batch_1?offset=123_1&live=true&handle=batch_batch_1_7_abc",
ENV,
"batch_1",
CURRENT_API_VERSION,
undefined,
"1.0.0"
);
expect(res.status).toBe(200);
expect(limitCheckedWith).toBe(0);
});
});