Files
agency/.pnpm-store/v3/files/fe/35cd7fcf32fe28763c0435616ebd680d5959a042d594bb2dd816224c9a33ab8a819f8cac50bb6c020c104786438955ae45cf40ba07fe0df841561008e21838
hanzo-agent 5af6960b87 run project
Run: sess_f37640604ae254cb4d11dc6217f7b721
2026-09-30 00:04:56 +00:00

1 line
12 KiB
Plaintext

{"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Browser-safe REST client for a Hanzo Base waitlist plugin.\n//\n// The client speaks the /v1/waitlist/{join,status,export} surface. It has\n// no React or DOM dependency — safe to use from server code, Node tools,\n// or other frontends.\n\nexport interface JoinInput {\n /** Waitlist slug or id, as configured on the Base server. */\n waitlist: string\n email: string\n /** Optional referral code from the URL `?ref=<code>` parameter. */\n referrerCode?: string\n /** Cloudflare Turnstile token, if Turnstile is enabled server-side. */\n turnstileToken?: string\n}\n\nexport interface StatusInput {\n waitlist: string\n email: string\n}\n\nexport interface PointBreakdown {\n referrals: number\n shares: number\n invitesSent: number\n invitesConverted: number\n /** Points from verified social follows/joins (X, Discord, Telegram, ...). */\n social?: number\n /** Points from running a hanzod node. */\n hanzod?: number\n}\n\nexport interface PointValues {\n REFERRAL: number\n SHARE: number\n INVITE_SENT: number\n INVITE_CONVERTED: number\n}\n\nexport interface WaitlistEntry {\n ok: true\n waitlist: string\n email: string\n refCode: string\n rank: number\n total: number\n referralCount: number\n shareUrl: string\n /** Total points (referrals*REFERRAL + shares*SHARE + invitesSent + invitesConverted*INVITE_CONVERTED). */\n points: number\n pointBreakdown: PointBreakdown\n pointValues: PointValues\n alreadyJoined?: boolean\n aheadOf?: number\n}\n\nexport interface TrackShareInput {\n waitlist: string\n refCode: string\n platform: string\n}\n\nexport interface TrackShareResponse {\n ok: true\n awarded: number\n alreadyClaimed: boolean\n points: number\n pointBreakdown: PointBreakdown\n}\n\nexport interface InviteInput {\n waitlist: string\n refCode: string\n emails: string[]\n message?: string\n}\n\nexport interface InviteResponse {\n ok: true\n sent: number\n skipped: number\n invalid: number\n duplicates: number\n pointsAwarded: number\n points: number\n pointBreakdown: PointBreakdown\n}\n\nexport type ActivityType = 'join' | 'share' | 'invite' | 'referral' | 'social' | 'hanzod'\n\nexport interface ActivityEvent {\n ts: number\n type: ActivityType\n who: string\n source?: 'direct' | 'referral'\n platform?: string\n invited?: string\n}\n\nexport interface ActivityInput {\n waitlist: string\n limit?: number\n types?: ActivityType[]\n}\n\nexport interface ActivityResponse {\n ok: true\n waitlist: string\n /** Server wall-clock at the time of response — use this to compute\n * relative times consistently even with client clock skew. */\n now: number\n entries: ActivityEvent[]\n}\n\nexport interface ListInput {\n waitlist: string\n page?: number\n pageSize?: number\n}\n\nexport interface LeaderboardEntry {\n rank: number\n email: string // masked unless admin auth attached\n refCode: string | null\n points: number\n referralCount: number\n createdAt: string\n}\n\nexport interface LeaderboardPage {\n ok: true\n waitlist: string\n page: number\n pageSize: number\n total: number\n totalPages: number\n entries: LeaderboardEntry[]\n}\n\nexport interface NeighborhoodInput {\n waitlist: string\n email: string\n /** How many entries to show above and below the caller (1..100, default 25). */\n window?: number\n}\n\nexport interface NeighborRow {\n rank: number\n email: string // masked\n points: number\n referralCount: number\n isMe?: boolean\n}\n\nexport interface NeighborhoodResponse {\n ok: true\n waitlist: string\n email: string\n rank: number\n total: number\n points: number\n window: number\n /** The caller and its nearest neighbors, ordered by rank (best first). */\n entries: NeighborRow[]\n}\n\nexport interface ApiError {\n ok: false\n status: number\n message: string\n data?: unknown\n}\n\nexport type Result<T> = T | ApiError\n\nexport interface ClientOptions {\n /**\n * Base URL of the Hanzo Base server, e.g. `https://api.example.com`.\n * Defaults to the current page origin.\n */\n baseUrl?: string\n /**\n * Optional path prefix to prepend before `/v1/waitlist`. Useful when\n * Base is mounted under a sub-path (`BASE_API_PREFIX=/v1/foo`).\n * Should NOT include `/v1` — defaults to empty.\n */\n pathPrefix?: string\n /** Optional fetch override, for testing or custom transports. */\n fetch?: typeof fetch\n}\n\nexport class WaitlistClient {\n private readonly baseUrl: string\n private readonly prefix: string\n private readonly fetchFn: typeof fetch\n\n constructor(opts: ClientOptions = {}) {\n this.baseUrl = (opts.baseUrl ?? (typeof window !== 'undefined' ? window.location.origin : '')).replace(/\\/$/, '')\n this.prefix = (opts.pathPrefix ?? '').replace(/\\/$/, '')\n this.fetchFn = opts.fetch ?? (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : (() => { throw new Error('fetch is not available') })()) as typeof fetch\n }\n\n private url(path: string): string {\n return `${this.baseUrl}${this.prefix}/v1/waitlist${path}`\n }\n\n async join(input: JoinInput): Promise<Result<WaitlistEntry>> {\n const res = await this.fetchFn(this.url('/join'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(input),\n })\n return this.parse(res)\n }\n\n async status(input: StatusInput): Promise<Result<WaitlistEntry>> {\n const q = new URLSearchParams({ waitlist: input.waitlist, email: input.email })\n const res = await this.fetchFn(this.url(`/status?${q}`), { method: 'GET' })\n return this.parse(res)\n }\n\n async list(input: ListInput): Promise<Result<LeaderboardPage>> {\n const q = new URLSearchParams({ waitlist: input.waitlist })\n if (input.page) q.set('page', String(input.page))\n if (input.pageSize) q.set('pageSize', String(input.pageSize))\n const res = await this.fetchFn(this.url(`/list?${q}`), { method: 'GET' })\n return this.parseGeneric<LeaderboardPage>(res)\n }\n\n /**\n * The scalable \"around me\" leaderboard view: the caller's absolute rank in a\n * list of any size, plus the `window` entries just above and below. Backed by\n * keyset index seeks server-side, so it stays cheap (~0.1ms) even on a\n * multi-million-row list — never fetch the whole leaderboard to show a slice.\n */\n async neighborhood(input: NeighborhoodInput): Promise<Result<NeighborhoodResponse>> {\n const q = new URLSearchParams({ waitlist: input.waitlist, email: input.email })\n if (input.window) q.set('window', String(input.window))\n const res = await this.fetchFn(this.url(`/neighborhood?${q}`), { method: 'GET' })\n return this.parseGeneric<NeighborhoodResponse>(res)\n }\n\n async trackShare(input: TrackShareInput): Promise<Result<TrackShareResponse>> {\n const res = await this.fetchFn(this.url('/track-share'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(input),\n })\n return this.parseGeneric<TrackShareResponse>(res)\n }\n\n async invite(input: InviteInput): Promise<Result<InviteResponse>> {\n const res = await this.fetchFn(this.url('/invite'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(input),\n })\n return this.parseGeneric<InviteResponse>(res)\n }\n\n async activity(input: ActivityInput): Promise<Result<ActivityResponse>> {\n const q = new URLSearchParams({ waitlist: input.waitlist })\n if (input.limit) q.set('limit', String(input.limit))\n if (input.types?.length) q.set('types', input.types.join(','))\n const res = await this.fetchFn(this.url(`/activity?${q}`), { method: 'GET' })\n return this.parseGeneric<ActivityResponse>(res)\n }\n\n /**\n * Returns a CSV string. Requires either a superuser auth cookie/token\n * already attached to the request, OR an admin secret passed via\n * `Authorization: Bearer <secret>`.\n */\n async exportCsv(waitlist: string, init: { authorization?: string } = {}): Promise<string> {\n const q = new URLSearchParams({ waitlist })\n const headers: Record<string, string> = {}\n if (init.authorization) headers.Authorization = init.authorization\n const res = await this.fetchFn(this.url(`/export?${q}`), { method: 'GET', headers })\n if (!res.ok) throw new Error(`export failed: ${res.status} ${await res.text()}`)\n return res.text()\n }\n\n private async parse(res: Response): Promise<Result<WaitlistEntry>> {\n return this.parseGeneric<WaitlistEntry>(res)\n }\n\n private async parseGeneric<T>(res: Response): Promise<Result<T>> {\n let body: unknown = null\n try { body = await res.json() } catch { /* fall through */ }\n if (!res.ok) {\n const msg = (body && typeof body === 'object' && 'message' in body && typeof (body as { message: unknown }).message === 'string')\n ? (body as { message: string }).message\n : `request failed with ${res.status}`\n return { ok: false, status: res.status, message: msg, data: body }\n }\n return body as T\n }\n}\n\n/** Convenience one-shot client. */\nexport async function join(input: JoinInput, opts?: ClientOptions): Promise<Result<WaitlistEntry>> {\n return new WaitlistClient(opts).join(input)\n}\n\nexport async function status(input: StatusInput, opts?: ClientOptions): Promise<Result<WaitlistEntry>> {\n return new WaitlistClient(opts).status(input)\n}\n"],"mappings":"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"}