Skip to content

Commit ec290c6

Browse files
committed
refactor(webapp): move the pure streams-version resolver out of the env-bound module
The resolver was written to take its configuration rather than read `env`, but it still lived beside the singleton that reads it, so the unit test pulled `env.server` in transitively, which the webapp rules forbid. Same split as nativeRealtimeClient and its instance module: the pure function has no imports at all, and the env-bound wrapper stays where the singletons are. Renames the test to match the function it exercises.
1 parent e482967 commit ec290c6

3 files changed

Lines changed: 50 additions & 37 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Pure realtime-streams version resolution. Deliberately free of `env` and of
3+
* any module-scope singletons so it can be tested with injected values, the
4+
* same split as `nativeRealtimeClient` and `nativeRealtimeClientInstance`.
5+
* The env-bound wrapper is `determineRealtimeStreamsVersion` in
6+
* `v1StreamsGlobal.server.ts`.
7+
*/
8+
9+
export type RealtimeStreamsVersionConfig = {
10+
defaultVersion: "v1" | "v2";
11+
/** A basin that will actually resolve at read/write time, or undefined if none will. */
12+
basin?: string;
13+
accessToken?: string;
14+
skipAccessTokens: boolean;
15+
};
16+
17+
/**
18+
* Resolve the streams version to stamp on a run, falling back to the
19+
* deployment default when the caller expresses no preference.
20+
*
21+
* v2 is only ever returned when S2 can actually serve it. A run stamped v2 on a
22+
* deployment without S2 is unusable: `getRealtimeStreamInstance` throws for the
23+
* life of the run, and no read or write against its streams can succeed. v1 is
24+
* a working backend, so an unsatisfiable v2 degrades to it.
25+
*
26+
* The basin must be one that will actually resolve later. Enabling per-org
27+
* basins is not enough on its own: provisioning is out of band, so an
28+
* unprovisioned organization has no basin and a global setting may not exist
29+
* to fall back to.
30+
*/
31+
export function resolveRealtimeStreamsVersion(
32+
streamVersion: string | undefined,
33+
config: RealtimeStreamsVersionConfig
34+
): "v1" | "v2" {
35+
const requested = streamVersion ?? config.defaultVersion;
36+
37+
if (requested !== "v2") {
38+
return "v1";
39+
}
40+
41+
const hasCredentials = Boolean(config.accessToken) || config.skipAccessTokens;
42+
43+
return hasCredentials && Boolean(config.basin) ? "v2" : "v1";
44+
}

apps/webapp/app/services/realtime/v1StreamsGlobal.server.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { singleton } from "~/utils/singleton";
1010
import type { AuthenticatedEnvironment } from "../apiAuth.server";
1111
import { RedisRealtimeStreams } from "./redisRealtimeStreams.server";
1212
import { S2RealtimeStreams } from "./s2realtimeStreams.server";
13+
import {
14+
resolveRealtimeStreamsVersion,
15+
type RealtimeStreamsVersionConfig,
16+
} from "./realtimeStreamsVersion";
1317
import type { StreamIngestor, StreamResponder } from "./types";
1418

1519
function initializeRedisRealtimeStreams() {
@@ -96,42 +100,7 @@ function streamPrefixFor(environment: AuthenticatedEnvironment, basin: string):
96100
return segments.join("/");
97101
}
98102

99-
export type RealtimeStreamsVersionConfig = {
100-
defaultVersion: "v1" | "v2";
101-
/** A basin that will actually resolve at read/write time, or undefined if none will. */
102-
basin?: string;
103-
accessToken?: string;
104-
skipAccessTokens: boolean;
105-
};
106-
107-
/**
108-
* Resolve the streams version to stamp on a run, falling back to the
109-
* deployment default when the caller expresses no preference.
110-
*
111-
* v2 is only ever returned when S2 can actually serve it. A run stamped v2 on a
112-
* deployment without S2 is unusable: `getRealtimeStreamInstance` throws for the
113-
* life of the run, and no read or write against its streams can succeed. v1 is
114-
* a working backend, so an unsatisfiable v2 degrades to it.
115-
*
116-
* The basin must be one that will actually resolve later. Enabling per-org
117-
* basins is not enough on its own: provisioning is out of band, so an
118-
* unprovisioned organization has no basin and a global setting may not exist
119-
* to fall back to.
120-
*/
121-
export function resolveRealtimeStreamsVersion(
122-
streamVersion: string | undefined,
123-
config: RealtimeStreamsVersionConfig
124-
): "v1" | "v2" {
125-
const requested = streamVersion ?? config.defaultVersion;
126-
127-
if (requested !== "v2") {
128-
return "v1";
129-
}
130-
131-
const hasCredentials = Boolean(config.accessToken) || config.skipAccessTokens;
132-
133-
return hasCredentials && Boolean(config.basin) ? "v2" : "v1";
134-
}
103+
export type { RealtimeStreamsVersionConfig };
135104

136105
/**
137106
* Pass `organizationBasinName` wherever the caller has it. It mirrors the

apps/webapp/test/determineRealtimeStreamsVersion.test.ts renamed to apps/webapp/test/realtimeStreamsVersion.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import {
33
resolveRealtimeStreamsVersion,
44
type RealtimeStreamsVersionConfig,
5-
} from "~/services/realtime/v1StreamsGlobal.server";
5+
} from "~/services/realtime/realtimeStreamsVersion";
66

77
const NO_S2: RealtimeStreamsVersionConfig = {
88
defaultVersion: "v1",

0 commit comments

Comments
 (0)