Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/web/src/state/npmRelease.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { FetchHttpClient } from "effect/http";

import { checkNpmReleasePublished } from "./npmRelease";

const VERSION = "0.0.46-nightly.20261013.3042";

// Acts like a browser talking to registry.npmjs.org: a simple GET succeeds,
// but any non-safelisted header forces a preflight the registry rejects.
const registryFetch: typeof globalThis.fetch = async (_url, init) => {
const headers = new Headers(init?.headers);
if (headers.has("traceparent") || headers.has("b3")) {
throw new TypeError("Failed to fetch");
}
return Response.json({ version: VERSION });
};

const RegistryHttpClient = FetchHttpClient.layer.pipe(
Layer.provide(Layer.succeed(FetchHttpClient.Fetch, registryFetch)),
);

describe("checkNpmReleasePublished", () => {
it.effect("sends a request the registry accepts from a browser", () =>
Effect.gen(function* () {
expect(yield* checkNpmReleasePublished(VERSION)).toBe(true);
}).pipe(Effect.provide(RegistryHttpClient)),
);
});
38 changes: 22 additions & 16 deletions apps/web/src/state/npmRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@ import { AsyncResult, Atom } from "effect/reactivity";

/**
* Whether `t3@<version>` is published on npm. A 404 is false; network and
* registry errors stay unknown. Both re-check every few minutes, so a release
* that lands later shows up without a reload.
* registry errors fail, and callers treat that as unknown.
*
* Trace propagation stays off: its `b3` and `traceparent` headers turn this
* into a CORS preflight, which the registry rejects, so browsers could never
* complete the check.
*/
export const checkNpmReleasePublished = (version: string) =>
Effect.gen(function* () {
const response = yield* HttpClient.get(
`https://registry.npmjs.org/t3/${encodeURIComponent(version)}`,
);
if (response.status === 404) return false;
const body = yield* HttpClientResponse.filterStatusOk(response).pipe(
Effect.flatMap((ok) => ok.json),
);
return typeof body === "object" && body !== null && "version" in body
? body.version === version
: false;
}).pipe(Effect.provideService(HttpClient.TracerPropagationEnabled, false));

/** Re-checks every few minutes, so a release that lands later shows up
without a reload. */
const npmReleasePublishedAtom = Atom.family((version: string) =>
Atom.make(
Effect.gen(function* () {
const response = yield* HttpClient.get(
`https://registry.npmjs.org/t3/${encodeURIComponent(version)}`,
);
if (response.status === 404) return false;
const body = yield* HttpClientResponse.filterStatusOk(response).pipe(
Effect.flatMap((ok) => ok.json),
);
return typeof body === "object" && body !== null && "version" in body
? body.version === version
: false;
}).pipe(Effect.provide(FetchHttpClient.layer)),
).pipe(
Atom.make(checkNpmReleasePublished(version).pipe(Effect.provide(FetchHttpClient.layer))).pipe(
Atom.withRefresh("5 minutes"),
Atom.keepAlive,
Atom.withLabel(`npm-release-published:${version}`),
Expand Down
Loading