1import { useAtomValue } from "@effect/atom-react";
2import * as Effect from "effect/Effect";
3import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/http";
4import { AsyncResult, Atom } from "effect/reactivity";
5
6/**
7 * Whether `t3@<version>` is published on npm. A 404 is false; network and
8 * registry errors stay unknown. Both re-check every few minutes, so a release
9 * that lands later shows up without a reload.
10 */
11export const npmReleasePublishedAtom = Atom.family((version: string) =>
12 Atom.make(
13 Effect.gen(function* () {
14 const response = yield* HttpClient.get(
15 `https://registry.npmjs.org/t3/${encodeURIComponent(version)}`,
16 );
17 if (response.status === 404) return false;
18 const body = yield* HttpClientResponse.filterStatusOk(response).pipe(
19 Effect.flatMap((ok) => ok.json),
20 );
21 return typeof body === "object" && body !== null && "version" in body
22 ? body.version === version
23 : false;
24 }).pipe(
25 // Tracing headers would make the browser send a CORS preflight, which npm rejects.
26 Effect.provideService(HttpClient.TracerPropagationEnabled, false),
27 Effect.provide(FetchHttpClient.layer),
28 ),
29 ).pipe(
30 Atom.withRefresh("5 minutes"),
31 Atom.keepAlive,
32 Atom.withLabel(`npm-release-published:${version}`),
33 ),
34);
35
36const NO_VERSION_ATOM = Atom.make(AsyncResult.success(false)).pipe(
37 Atom.withLabel("npm-release-published:none"),
38);
39
40/** True only once npm confirms the version, so callers can hide work that
41 would download it until then. */
42export function useNpmReleasePublished(version: string | null): boolean {
43 const result = useAtomValue(version ? npmReleasePublishedAtom(version) : NO_VERSION_ATOM);
44 return AsyncResult.getOrElse(result, () => false);
45}