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: `<prefix>_<accountId>_<secret>`.
* 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<void> {
try {
const stub = env.ACCOUNT_STATE.get(env.ACCOUNT_STATE.idFromName(accountId)) as unknown as {
destroy(): Promise<void>;
};
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<T>(ids: string[], task: (id: string) => Promise<T>, limit = 8): Promise<T[]> {
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++;