import { env } from "cloudflare:workers"; import { forgetScope } from "./scoped-db/locators"; const ID_PATTERN = /^[0-9a-z]{20}$/; /** * Credentials minted after the AccountState move carry their account id: `__`. * Returns the embedded id, or null for older tokens (resolve those through a locator). */ export function embeddedAccountId(token: string, prefix: string): string | null { if (!token.startsWith(`${prefix}_`)) return null; const parts = token.slice(prefix.length + 1).split("_"); if (parts.length !== 2) return null; return ID_PATTERN.test(parts[0]) ? parts[0] : null; } export function accountToken(prefix: string, accountId: string, secretHex: string): string { return `${prefix}_${accountId}_${secretHex}`; } /** Wipes the account's AccountState and every locator pointing into it. */ export async function dropAccountState(accountId: string): Promise { try { const stub = env.ACCOUNT_STATE.get(env.ACCOUNT_STATE.idFromName(accountId)) as unknown as { destroy(): Promise; }; await stub.destroy(); } catch (error) { console.error("AccountState destroy failed", accountId, error); } await forgetScope(accountId); } /** Runs `task` for every id with bounded concurrency. */ export async function forEachScope(ids: string[], task: (id: string) => Promise, limit = 8): Promise { const results: T[] = new Array(ids.length); let next = 0; const workers = Array.from({ length: Math.min(limit, ids.length) }, async () => { while (next < ids.length) { const index = next++; results[index] = await task(ids[index]); } }); await Promise.all(workers); return results; }