{"version":3,"sources":["../../src/logic/primary-position.ts","../../src/logic/profile-summary.ts","../../src/query/client.ts","../../src/query/fetchers/get-profile-view.ts","../../src/query/fetchers/profile-summary.ts","../../src/badge/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Canonical rule for picking the \"primary\" position to surface on profile cards,\n * social previews, and structured-data outputs.\n *\n * Without this shared helper, different surfaces (visible hero, OG image,\n * <meta description>, JSON-LD) re-derived \"primary\" inconsistently and diverged\n * for users with multiple concurrent roles.\n *\n * A `hidden` position is one the user chose not to show on their public profile.\n * It is never surfaced as the featured role — hidden wins over `primary`, so a\n * role flagged both hidden and primary is excluded rather than featured.\n *\n * Rules (in order):\n * 1. A position the user explicitly flagged `primary` AND that is still active AND not hidden.\n * 2. Otherwise, the active, non-hidden position with the most recent `startedAt`.\n * 3. Otherwise, undefined (no eligible position).\n */\n\nexport interface PrimaryPositionCandidate {\n  startedAt?: string;\n  endedAt?: string;\n  primary?: boolean;\n  hidden?: boolean;\n}\n\nexport function pickPrimaryPosition<T extends PrimaryPositionCandidate>(\n  positions: readonly T[] | undefined,\n): T | undefined {\n  if (!positions || positions.length === 0) return undefined;\n\n  const active = positions.filter((p) => !p.endedAt && !p.hidden);\n  if (active.length === 0) return undefined;\n\n  const flagged = active.find((p) => p.primary === true);\n  if (flagged) return flagged;\n\n  return [...active].sort((a, b) => (b.startedAt ?? '').localeCompare(a.startedAt ?? ''))[0];\n}\n","/**\n * Compact professional summary derived from a {@link ProfileView}.\n *\n * The full `id.sifa.getProfileView` payload carries every section (all\n * positions, education, skills, publications, talks, and more). Third-party\n * surfaces that only want to show \"who is this, what do they do now\" — a badge,\n * a maintainer card, a byline — need a much smaller shape. This is that shape,\n * derived with the same canonical rules the Sifa web app uses (see\n * {@link pickPrimaryPosition}) so an embedded summary matches the profile page.\n *\n * Pure and I/O-free: pass it a view you already fetched. For the fetch +\n * summarize convenience, use `fetchProfileSummary` from `@singi-labs/sifa-sdk`.\n */\n\nimport type { ProfileView } from '../types/profile-view.js';\nimport { pickPrimaryPosition } from './primary-position.js';\n\nexport interface ProfileSummary {\n  did: string;\n  handle: string;\n  displayName?: string;\n  avatar?: string;\n  pronouns?: string;\n  headline?: string;\n  /** Title of the current primary role, if the person has an active position. */\n  currentTitle?: string;\n  /** Current employer: the resolved entity name when linked, else the free-text company. */\n  currentCompany?: string;\n  /** Skill names in the order the AppView returns them, capped by `maxSkills`. */\n  topSkills: string[];\n  /** Whether the profile is claimed by its owner (vs an unclaimed placeholder). */\n  claimed: boolean;\n}\n\nexport interface SummarizeProfileViewOptions {\n  /** Maximum number of skills to include in `topSkills`. Default 5; negatives clamp to 0. */\n  maxSkills?: number;\n}\n\nconst DEFAULT_MAX_SKILLS = 5;\n\nexport function summarizeProfileView(\n  view: ProfileView,\n  options: SummarizeProfileViewOptions = {},\n): ProfileSummary {\n  const limit = Math.max(0, options.maxSkills ?? DEFAULT_MAX_SKILLS);\n  const primary = pickPrimaryPosition(view.positions);\n\n  return {\n    did: view.did,\n    handle: view.handle,\n    displayName: view.displayName,\n    avatar: view.avatar,\n    pronouns: view.pronouns,\n    headline: view.headline,\n    currentTitle: primary?.title,\n    currentCompany: primary ? (primary.entityName ?? primary.company) : undefined,\n    topSkills: (view.skills ?? []).slice(0, limit).map((s) => s.name),\n    claimed: view.claimed ?? false,\n  };\n}\n","/**\n * Foundation HTTP client for talking to the Sifa AppView.\n *\n * Stateless. Consumers supply a {@link SifaApiConfig} per call (the React\n * hooks read it from context; non-React consumers pass it explicitly). No\n * singletons, no module-level state.\n */\n\n/** Configuration passed to every fetcher. */\nexport interface SifaApiConfig {\n  /** Base URL of the sifa-api AppView, e.g. `https://api.sifa.id`. No trailing slash. */\n  baseUrl: string;\n  /**\n   * Optional fetch implementation. Defaults to {@link globalThis.fetch}.\n   * Lets Next.js consumers pass their cache-enhanced fetch; node/Expo\n   * consumers can leave this unset.\n   */\n  fetch?: typeof fetch;\n}\n\n/** Options accepted by {@link apiFetch}. */\nexport interface ApiFetchOptions {\n  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n  /** Request body. Serialized to JSON automatically. */\n  body?: unknown;\n  /** AbortSignal. Defaults to `AbortSignal.timeout(timeoutMs)` if `timeoutMs` is set. */\n  signal?: AbortSignal;\n  /** Per-call timeout in milliseconds. Default: 10_000. Ignored if `signal` is provided. */\n  timeoutMs?: number;\n  /** Retry on HTTP 429 up to 3 times with the server's `Retry-After` delay (capped at 3s). */\n  retryOn429?: boolean;\n  /** Additional headers. `Content-Type: application/json` is set automatically when `body` is present. */\n  headers?: Record<string, string>;\n  credentials?: RequestCredentials;\n  cache?: RequestCache;\n  /**\n   * Next.js-specific cache hints. Ignored on non-Next runtimes. Passed\n   * through transparently as part of {@link RequestInit}.\n   */\n  next?: { revalidate?: number | false; tags?: string[] };\n}\n\n/** Error thrown by {@link apiFetch} on non-2xx responses. */\nexport class ApiError extends Error {\n  readonly status: number;\n  readonly body: unknown;\n\n  constructor(message: string, status: number, body?: unknown) {\n    super(message);\n    this.name = 'ApiError';\n    this.status = status;\n    this.body = body;\n  }\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_SECONDS = 3;\n\n/**\n * Encode a handle or DID for safe interpolation into a request path,\n * idempotently.\n *\n * Plain {@link encodeURIComponent} double-encodes an already-encoded\n * identifier: Next.js hands route params to RSC pages percent-encoded on a\n * hard navigation (`did%3Aplc%3A...`) but decoded on client-side navigation\n * (`did:plc:...`). Encoding the former again turns `%` into `%25`, so the\n * AppView reads `did%3A...` as a literal handle, finds no match, and 404s —\n * which is why DID profile links broke on direct visits but worked when\n * clicked within the app.\n *\n * AT identifiers (handles are domain names, DIDs are `did:method:...`) never\n * contain a literal `%`, so decoding a decoded value is a no-op. A malformed\n * percent sequence (lone `%`) makes {@link decodeURIComponent} throw; in that\n * case we keep the original value so behaviour matches the prior bare\n * `encodeURIComponent`.\n */\nexport function encodeIdentifier(value: string): string {\n  let decoded = value;\n  try {\n    decoded = decodeURIComponent(value);\n  } catch {\n    // Malformed percent-encoding — fall back to the raw value.\n  }\n  return encodeURIComponent(decoded);\n}\n\n/**\n * Generic fetcher used by all SDK query and mutation functions.\n *\n * Returns parsed JSON typed as `T`. Throws {@link ApiError} on non-2xx\n * responses. Use {@link apiFetchOrNull} when 404 should resolve to `null`\n * instead.\n */\nexport async function apiFetch<T = unknown>(\n  config: SifaApiConfig,\n  path: string,\n  options: ApiFetchOptions = {},\n): Promise<T> {\n  const fetchFn = config.fetch ?? globalThis.fetch;\n  const url = `${config.baseUrl}${path}`;\n  const maxRetries = options.retryOn429 ? MAX_RATE_LIMIT_RETRIES : 0;\n\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n\n    const headers: Record<string, string> = { ...(options.headers ?? {}) };\n    let body: BodyInit | undefined;\n    if (options.body !== undefined) {\n      headers['Content-Type'] ??= 'application/json';\n      body = JSON.stringify(options.body);\n    }\n\n    // `next` is a Next.js extension to RequestInit; cast through.\n    const init = {\n      method: options.method ?? 'GET',\n      headers,\n      body,\n      signal,\n      credentials: options.credentials,\n      cache: options.cache,\n      ...(options.next ? { next: options.next } : {}),\n    } as RequestInit;\n\n    const res = await fetchFn(url, init);\n\n    if (res.status === 429 && attempt < maxRetries) {\n      const retryAfterRaw = res.headers.get('retry-after');\n      const retryAfter = retryAfterRaw ? Number.parseInt(retryAfterRaw, 10) : 2;\n      const waitSeconds = Math.min(\n        Number.isFinite(retryAfter) ? retryAfter : 2,\n        RATE_LIMIT_RETRY_CAP_SECONDS,\n      );\n      await new Promise((r) => setTimeout(r, waitSeconds * 1000));\n      continue;\n    }\n\n    if (!res.ok) {\n      let errBody: unknown;\n      try {\n        errBody = (await res.json()) as unknown;\n      } catch {\n        try {\n          errBody = await res.text();\n        } catch {\n          errBody = undefined;\n        }\n      }\n      throw new ApiError(`Sifa API ${res.status} on ${path}`, res.status, errBody);\n    }\n\n    // 204 and 205 are defined to carry no body, so res.json() throws on them.\n    // apiWrite treats that throw as a failed write, which turned every\n    // successful no-content response (roadmap unvote, remove reaction,\n    // dismiss endorsement) into a reported error.\n    if (res.status === 204 || res.status === 205) {\n      return undefined as T;\n    }\n\n    return (await res.json()) as T;\n  }\n\n  throw new ApiError(`Sifa API exhausted retries on ${path}`, 429);\n}\n\n/**\n * Variant of {@link apiFetch} that resolves to `null` on HTTP 404 instead\n * of throwing. Useful for \"fetch by handle\" reads where missing is\n * expected (e.g. unknown profile).\n */\nexport async function apiFetchOrNull<T>(\n  config: SifaApiConfig,\n  path: string,\n  options: ApiFetchOptions = {},\n): Promise<T | null> {\n  try {\n    return await apiFetch<T>(config, path, options);\n  } catch (e) {\n    if (e instanceof ApiError && e.status === 404) return null;\n    throw e;\n  }\n}\n\n/**\n * Result returned by record-write mutations (create / update / delete).\n *\n * Never throws -- writes against the user's PDS can fail in many ways\n * (network, PDS unreachable, rate limit) and the UI needs structured\n * results to render appropriate messages.\n */\nexport interface WriteResult {\n  success: boolean;\n  error?: string;\n  /**\n   * PDS hostname returned by sifa-api when a write failed at the user's\n   * Personal Data Server (issue #167). Lets the UI render\n   * \"Your data server (eurosky.social) isn't responding\" instead of a\n   * generic \"Request failed (500)\".\n   */\n  pdsHost?: string;\n}\n\n/** Result returned by create mutations. Includes the newly created `rkey`. */\nexport interface CreateResult extends WriteResult {\n  rkey?: string;\n}\n\ninterface ApiErrorBody {\n  message?: string;\n  pdsHost?: string;\n}\n\nfunction extractWriteError(data: unknown, status: number): { error: string; pdsHost?: string } {\n  const body = (data ?? {}) as ApiErrorBody;\n  return {\n    error: body.message ?? `Request failed (${status})`,\n    ...(body.pdsHost ? { pdsHost: body.pdsHost } : {}),\n  };\n}\n\n/**\n * Write mutation against the Sifa AppView. Wraps {@link apiFetch} with the\n * never-throws contract used by all sifa-web mutations: returns a\n * structured {@link WriteResult} on both success and failure, and\n * preserves the `pdsHost` field when the AppView reports a PDS-side\n * failure (issue #167).\n *\n * On success: returns `{ success: true, ...payload }` where `payload` is\n * whatever the server returned in JSON (or `{}` for 204).\n *\n * On failure: returns `{ success: false, error, pdsHost? }`. Never throws.\n *\n * Use {@link apiWriteCreate} when you specifically need the `rkey` from a\n * create response folded into the result shape.\n */\nexport async function apiWrite<TExtra extends object = Record<never, never>>(\n  config: SifaApiConfig,\n  path: string,\n  method: 'POST' | 'PUT' | 'DELETE' | 'PATCH',\n  options: Omit<ApiFetchOptions, 'method'> = {},\n): Promise<WriteResult & TExtra> {\n  try {\n    const data = await apiFetch<TExtra>(config, path, {\n      method,\n      credentials: 'include',\n      ...options,\n    });\n    return { success: true, ...(data ?? ({} as TExtra)) };\n  } catch (e) {\n    if (e instanceof ApiError) {\n      return { success: false, ...extractWriteError(e.body, e.status) } as WriteResult & TExtra;\n    }\n    return { success: false, error: 'Network error' } as WriteResult & TExtra;\n  }\n}\n\n/**\n * Write mutation that expects the server to return a record key (`rkey`)\n * in its response body. Wraps {@link apiWrite} and folds the `rkey` into\n * the result shape so consumers get `{ success, rkey?, error?, pdsHost? }`.\n *\n * If the server returns additional fields (e.g. `feedUrl` from external\n * account creation), pass them via the `TExtra` generic to keep them\n * typed.\n */\nexport function apiWriteCreate<TExtra extends object = Record<never, never>>(\n  config: SifaApiConfig,\n  path: string,\n  body: unknown,\n  options: Omit<ApiFetchOptions, 'method' | 'body'> = {},\n): Promise<CreateResult & TExtra> {\n  return apiWrite<{ rkey?: string } & TExtra>(config, path, 'POST', {\n    body,\n    ...options,\n  });\n}\n","import type { ProfileView } from '../../types/profile-view.js';\nimport {\n  ApiError,\n  apiFetch,\n  encodeIdentifier,\n  type ApiFetchOptions,\n  type SifaApiConfig,\n} from '../client.js';\n\n/**\n * Read the aggregated public profile view via the `id.sifa.getProfileView`\n * XRPC query. This is the AppView-only join (positions, education, skills,\n * endorsements, and more) exposed as a standard lexicon method, distinct from\n * the internal `/api/profile/*` REST surface.\n *\n * Returns `null` when the AppView has no profile for the actor\n * (`ProfileNotFound`). Throws {@link ApiError} on other non-2xx responses.\n *\n * Server-callable (Next.js RSC) and client-callable (Expo, browser).\n */\nexport async function fetchGetProfileView(\n  config: SifaApiConfig,\n  actor: string,\n  options: ApiFetchOptions = {},\n): Promise<ProfileView | null> {\n  const path = `/xrpc/id.sifa.getProfileView?actor=${encodeIdentifier(actor)}`;\n  try {\n    return await apiFetch<ProfileView>(config, path, { retryOn429: true, ...options });\n  } catch (e) {\n    if (\n      e instanceof ApiError &&\n      e.status === 400 &&\n      typeof e.body === 'object' &&\n      e.body !== null &&\n      (e.body as { error?: unknown }).error === 'ProfileNotFound'\n    ) {\n      return null;\n    }\n    throw e;\n  }\n}\n","import {\n  summarizeProfileView,\n  type ProfileSummary,\n  type SummarizeProfileViewOptions,\n} from '../../logic/profile-summary.js';\nimport { fetchGetProfileView } from './get-profile-view.js';\nimport type { ApiFetchOptions, SifaApiConfig } from '../client.js';\n\n/**\n * Fetch the aggregated public profile via `id.sifa.getProfileView` and reduce it\n * to a compact {@link ProfileSummary} (identity, headline, current role/employer,\n * top skills). Convenience over `fetchGetProfileView` + `summarizeProfileView`,\n * for third-party surfaces that only want the headline facts.\n *\n * Returns `null` when the AppView has no profile for the actor. Server-callable\n * (Next.js RSC) and client-callable (Expo, browser).\n */\nexport async function fetchProfileSummary(\n  config: SifaApiConfig,\n  actor: string,\n  options: SummarizeProfileViewOptions & ApiFetchOptions = {},\n): Promise<ProfileSummary | null> {\n  const { maxSkills, ...fetchOptions } = options;\n  const view = await fetchGetProfileView(config, actor, fetchOptions);\n  return view ? summarizeProfileView(view, { maxSkills }) : null;\n}\n","/**\n * `<sifa-badge>` — a framework-free custom element that renders a person's\n * professional headline (name, current role and employer, top skills) from\n * their Sifa profile. Drop it into any page:\n *\n * ```html\n * <script type=\"module\" src=\"https://esm.sh/@singi-labs/sifa-sdk/badge\"></script>\n * <sifa-badge handle=\"gui.do\"></sifa-badge>\n * ```\n *\n * Or, in a bundler: `import '@singi-labs/sifa-sdk/badge'` (importing registers\n * the element). Attributes: `handle` or `did` (required), `api` (AppView base,\n * default `https://api.sifa.id`), `max-skills` (default 5).\n *\n * Reads the public `id.sifa.getProfileView` query, so it needs no auth or key.\n * Cross-origin browser use depends on that endpoint's open CORS.\n */\n\nimport type { ProfileSummary } from '../logic/profile-summary.js';\nimport { fetchProfileSummary } from '../query/fetchers/profile-summary.js';\n\nconst TAG = 'sifa-badge';\nconst DEFAULT_API_BASE = 'https://api.sifa.id';\nconst PROFILE_BASE = 'https://sifa.id/p/';\n\n// Reference HTMLElement through a guarded base so importing this module never\n// throws in a non-DOM (SSR / Node) context. The real element is only ever\n// registered and instantiated in a browser, where defineSifaBadge runs.\nconst BaseElement: typeof HTMLElement =\n  typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement);\n\nconst STYLES = `\n  :host { display: inline-block; max-width: 22rem; }\n  a.card {\n    display: flex; gap: 0.75rem; align-items: center;\n    padding: 0.75rem 0.9rem; text-decoration: none; color: inherit;\n    border: 1px solid rgba(0,0,0,0.12); border-radius: 0.75rem;\n    background: canvas; font: 400 0.9rem/1.4 system-ui, sans-serif;\n  }\n  a.card:hover { border-color: rgba(0,0,0,0.28); }\n  a.card:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }\n  .avatar { width: 2.75rem; height: 2.75rem; border-radius: 50%; object-fit: cover; flex: none; }\n  .body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }\n  .name { font-weight: 600; }\n  .role { color: color-mix(in srgb, canvastext 65%, canvas); }\n  .skills { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.25rem; }\n  .chip {\n    font-size: 0.72rem; padding: 0.1rem 0.45rem; border-radius: 999px;\n    background: color-mix(in srgb, canvastext 8%, canvas);\n  }\n  .msg { margin: 0; padding: 0.5rem 0.6rem; color: color-mix(in srgb, canvastext 60%, canvas);\n    font: 400 0.85rem/1.4 system-ui, sans-serif; }\n`;\n\nexport class SifaBadgeElement extends BaseElement {\n  static readonly observedAttributes = ['handle', 'did', 'api', 'max-skills'];\n\n  private readonly shadow: ShadowRoot;\n  private renderToken = 0;\n  private scheduled = false;\n\n  constructor() {\n    super();\n    this.shadow = this.attachShadow({ mode: 'open' });\n  }\n\n  connectedCallback(): void {\n    this.schedule();\n  }\n\n  attributeChangedCallback(): void {\n    this.schedule();\n  }\n\n  /** Coalesce a burst of synchronous attribute writes (and the initial connect) into one load. */\n  private schedule(): void {\n    if (this.scheduled) return;\n    this.scheduled = true;\n    queueMicrotask(() => {\n      this.scheduled = false;\n      if (this.isConnected) void this.load();\n    });\n  }\n\n  private async load(): Promise<void> {\n    const token = ++this.renderToken;\n    const actor = this.getAttribute('did') || this.getAttribute('handle');\n    if (!actor) {\n      this.paint(this.message('Set a handle or did attribute on <sifa-badge>.'));\n      return;\n    }\n\n    const baseUrl = this.getAttribute('api') || DEFAULT_API_BASE;\n    const rawMax = this.getAttribute('max-skills');\n    const maxSkills = rawMax != null ? Number(rawMax) : undefined;\n    const options = maxSkills != null && Number.isFinite(maxSkills) ? { maxSkills } : {};\n\n    this.paint(this.message('Loading…'));\n\n    let summary: ProfileSummary | null;\n    try {\n      summary = await fetchProfileSummary({ baseUrl }, actor, options);\n    } catch {\n      if (token === this.renderToken) this.paint(this.message('Could not load this profile.'));\n      return;\n    }\n\n    if (token !== this.renderToken) return; // a newer load superseded this one\n    this.paint(summary ? this.card(summary) : this.message(`No Sifa profile for ${actor}.`));\n  }\n\n  private paint(body: Node): void {\n    const style = document.createElement('style');\n    style.textContent = STYLES;\n    this.shadow.replaceChildren(style, body);\n  }\n\n  private message(text: string): HTMLElement {\n    const p = document.createElement('p');\n    p.className = 'msg';\n    p.textContent = text;\n    return p;\n  }\n\n  private card(summary: ProfileSummary): HTMLElement {\n    const name = summary.displayName || summary.handle;\n    const link = document.createElement('a');\n    link.className = 'card';\n    link.href = PROFILE_BASE + encodeURIComponent(summary.handle);\n    link.target = '_blank';\n    link.rel = 'noopener';\n    link.setAttribute('aria-label', `${name} on Sifa`);\n\n    if (summary.avatar) {\n      const img = document.createElement('img');\n      img.className = 'avatar';\n      img.src = summary.avatar;\n      img.alt = '';\n      img.loading = 'lazy';\n      link.append(img);\n    }\n\n    const body = document.createElement('span');\n    body.className = 'body';\n\n    const nameEl = document.createElement('span');\n    nameEl.className = 'name';\n    nameEl.textContent = name;\n    body.append(nameEl);\n\n    const role =\n      [summary.currentTitle, summary.currentCompany].filter(Boolean).join(' · ') ||\n      summary.headline;\n    if (role) {\n      const roleEl = document.createElement('span');\n      roleEl.className = 'role';\n      roleEl.textContent = role;\n      body.append(roleEl);\n    }\n\n    if (summary.topSkills.length > 0) {\n      const skills = document.createElement('span');\n      skills.className = 'skills';\n      for (const skill of summary.topSkills) {\n        const chip = document.createElement('span');\n        chip.className = 'chip';\n        chip.textContent = skill;\n        skills.append(chip);\n      }\n      body.append(skills);\n    }\n\n    link.append(body);\n    return link;\n  }\n}\n\n/** Register the element (idempotent). Called automatically when this module is imported. */\nexport function defineSifaBadge(tag: string = TAG): void {\n  if (typeof customElements !== 'undefined' && !customElements.get(tag)) {\n    customElements.define(tag, SifaBadgeElement);\n  }\n}\n\ndefineSifaBadge();\n"]}