1import * as Schema from "effect/Schema";
2
3/**
4 * Filename (under `~/.alchemy/credentials/{profile}/`) used to cache the
5 * Cloudflare-deployed HTTP state store's endpoint + bearer token.
6 *
7 * Kept in this leaf module (rather than `StateStore/State.ts`) so the
8 * Cloudflare auth provider can invalidate the cache on
9 * `profile edit` / provider logout without importing the heavy state-store
10 * module and creating an import cycle
11 * (`StateStore/State.ts` -> `Providers.ts` -> `Auth/AuthProvider.ts`).
12 */
13export const CREDENTIALS_FILE = "cloudflare-state-store";
14
15/**
16 * On-disk shape of the cached Cloudflare state-store credentials.
17 *
18 * Extends the generic `HttpStateStoreCredentials` (`{ url, authToken }`)
19 * with the `accountId` the credentials were minted against. The `url` already
20 * encodes the account (via the `*.workers.dev` subdomain), so a cache written
21 * while logged into account A keeps pointing at account A's state store even
22 * after the user re-authenticates against account B. Persisting `accountId`
23 * lets `state()` detect that mismatch and re-derive.
24 *
25 * `accountId` is optional so legacy files written before this field existed
26 * still parse — they are treated as stale (see
27 * {@link isStateStoreCredentialsStale}) and re-derived on next use.
28 */
29export const StoredStateStoreCredentials = Schema.Struct({
30 url: Schema.String,
31 /** Bearer token used to authenticate every request. */
32 authToken: Schema.String,
33 /** Cloudflare account ID the `url`/`authToken` were minted against. */
34 accountId: Schema.optional(Schema.String),
35});
36export type StoredStateStoreCredentials =
37 typeof StoredStateStoreCredentials.Type;
38
39/**
40 * `true` when cached state-store credentials must not be trusted for the
41 * current account:
42 *
43 * - the file predates the `accountId` field (legacy cache — exactly the
44 * artifact that caused deploys to silently target the wrong account), or
45 * - it was minted against a different account than the one now in use.
46 *
47 * A stale cache is discarded and re-derived from the current account.
48 */
49export const isStateStoreCredentialsStale = (
50 credentials: StoredStateStoreCredentials,
51 currentAccountId: string,
52): boolean =>
53 credentials.accountId == null || credentials.accountId !== currentAccountId;