CINXE.COM

Quick start guide to building applications on AT Protocol - AT Protocol

<!DOCTYPE html><html lang="en" class="h-full"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/786ca9324488b5df.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-496a37b6c26ccd6a.js"/><script src="/_next/static/chunks/fd9d1056-ae1d3d1d803e2e5a.js" async=""></script><script src="/_next/static/chunks/117-545f1187d7f142b2.js" async=""></script><script src="/_next/static/chunks/main-app-3122dc718821276f.js" async=""></script><script src="/_next/static/chunks/972-f3553dcbba031b91.js" async=""></script><script src="/_next/static/chunks/135-8f28b0a70a96c388.js" async=""></script><script src="/_next/static/chunks/28-313384c6289a00c0.js" async=""></script><script src="/_next/static/chunks/212-67e821188dcd5fb8.js" async=""></script><script src="/_next/static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js" async=""></script><script src="/_next/static/chunks/369-a583bbb81b96a7d8.js" async=""></script><script src="/_next/static/chunks/60-3e1824554a9cf3f8.js" async=""></script><script src="/_next/static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js" async=""></script><title>Quick start guide to building applications on AT Protocol - AT Protocol</title><meta name="description" content="In this guide, we&#x27;re going to build a simple multi-user app that publishes your current &quot;status&quot; as an emoji."/><meta property="og:title" content="Quick start guide to building applications on AT Protocol - AT Protocol"/><meta property="og:description" content="In this guide, we&#x27;re going to build a simple multi-user app that publishes your current &quot;status&quot; as an emoji."/><meta property="og:url" content="https://atproto.com/"/><meta property="og:site_name" content="AT Protocol"/><meta property="og:image" content="https://atproto.com/default-social-card.png"/><meta property="og:image:secure_url" content="https://atproto.com/default-social-card.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="Quick start guide to building applications on AT Protocol - AT Protocol"/><meta name="twitter:description" content="In this guide, we&#x27;re going to build a simple multi-user app that publishes your current &quot;status&quot; as an emoji."/><meta name="twitter:image" content="https://atproto.com/default-social-card.png"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="flex min-h-full bg-white antialiased dark:bg-zinc-900"><script>!function(){try{var d=document.documentElement,c=d.classList;c.remove('light','dark');var e=localStorage.getItem('theme');if('system'===e||(!e&&true)){var t='(prefers-color-scheme: dark)',m=window.matchMedia(t);if(m.media!==t||m.matches){d.style.colorScheme = 'dark';c.add('dark')}else{d.style.colorScheme = 'light';c.add('light')}}else if(e){c.add(e|| '')}if(e==='light'||e==='dark')d.style.colorScheme=e}catch(e){}}()</script><div class="w-full"><div class="h-full lg:ml-72 xl:ml-80"><header class="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex"><div class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-zinc-900/10 lg:px-6 lg:pb-8 lg:pt-4 xl:w-80 lg:dark:border-white/10"><div class="hidden lg:flex"><a aria-label="Home" href="/"><svg viewBox="0 0 162 28" aria-hidden="true" class="h-6"><path class="fill-blue-500" d="M14.362 27.78c-1.956 0-3.756-.324-5.4-.972-1.644-.648-3.072-1.566-4.284-2.754a12.247 12.247 0 0 1-2.808-4.158c-.66-1.596-.99-3.33-.99-5.202 0-2.232.342-4.212 1.026-5.94.696-1.728 1.656-3.18 2.88-4.356a12.48 12.48 0 0 1 4.284-2.7c1.632-.612 3.378-.918 5.238-.918 2.28 0 4.278.354 5.994 1.062 1.716.708 3.144 1.668 4.284 2.88a11.706 11.706 0 0 1 2.538 4.158c.552 1.548.804 3.168.756 4.86-.06 2.328-.546 4.116-1.458 5.364-.912 1.236-2.328 1.854-4.248 1.854a5.839 5.839 0 0 1-2.826-.702 3.703 3.703 0 0 1-1.764-2.07l1.044.054c-.492.924-1.164 1.572-2.016 1.944a6.464 6.464 0 0 1-2.61.558c-1.212 0-2.28-.258-3.204-.774a5.682 5.682 0 0 1-2.178-2.214c-.528-.948-.792-2.046-.792-3.294 0-1.284.276-2.394.828-3.33a5.77 5.77 0 0 1 2.232-2.196c.936-.516 1.992-.774 3.168-.774.78 0 1.59.162 2.43.486.852.324 1.512.78 1.98 1.368l-.738.936V8.664h2.412l-.054 6.462c0 .924.18 1.62.54 2.088.36.468.894.702 1.602.702.624 0 1.104-.174 1.44-.522.348-.36.588-.846.72-1.458a10.66 10.66 0 0 0 .252-2.106c.036-1.86-.24-3.426-.828-4.698-.588-1.272-1.386-2.298-2.394-3.078a9.499 9.499 0 0 0-3.294-1.71c-1.2-.36-2.394-.54-3.582-.54-1.68 0-3.174.27-4.482.81-1.308.528-2.412 1.278-3.312 2.25-.888.96-1.56 2.1-2.016 3.42-.444 1.308-.654 2.748-.63 4.32.048 1.56.33 2.964.846 4.212a9.324 9.324 0 0 0 2.16 3.204 9.38 9.38 0 0 0 3.276 2.034c1.26.468 2.64.702 4.14.702.84 0 1.674-.096 2.502-.288.84-.18 1.608-.438 2.304-.774l1.026 2.808c-.924.432-1.896.75-2.916.954a14.649 14.649 0 0 1-3.078.324Zm-.144-10.098c.852 0 1.566-.246 2.142-.738.576-.492.864-1.326.864-2.502 0-1.068-.258-1.872-.774-2.412-.504-.552-1.218-.828-2.142-.828-1.092 0-1.908.288-2.448.864-.54.576-.81 1.368-.81 2.376 0 1.032.276 1.83.828 2.394.564.564 1.344.846 2.34.846Z"></path><path class="fill-blue-600 dark:fill-blue-500" d="M51.799 7.813V5.545h13.509v2.268H59.86V23h-2.624V7.812h-5.438ZM39.392 23h-2.795l6.28-17.455h3.043L52.203 23h-2.796L44.472 8.716h-.137L39.392 23Zm.469-6.835h9.068v2.216h-9.068v-2.216Z"></path><path class="fill-zinc-700 dark:fill-zinc-400" d="M161.144 5.545V23h-2.548V5.545h2.548ZM149.649 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.506 1.017.758 2.204.758 3.562 0 1.352-.252 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.384-1.468.384-2.31 0-.834-.128-1.602-.384-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.801 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.194.631 1.995.631ZM136.032 23.264c-1.267 0-2.358-.287-3.273-.86-.909-.58-1.608-1.378-2.096-2.395-.489-1.018-.733-2.182-.733-3.495 0-1.33.25-2.503.75-3.52.5-1.022 1.204-1.82 2.113-2.395.909-.573 1.98-.86 3.213-.86.995 0 1.881.184 2.659.554a4.764 4.764 0 0 1 1.884 1.534c.483.659.77 1.429.861 2.31h-2.48a2.974 2.974 0 0 0-.938-1.586c-.483-.443-1.13-.665-1.943-.665-.71 0-1.332.188-1.866.563-.529.37-.941.898-1.236 1.585-.296.682-.443 1.489-.443 2.42 0 .955.144 1.779.434 2.472.29.693.699 1.23 1.227 1.61.535.382 1.162.572 1.884.572.483 0 .92-.088 1.313-.264.397-.182.73-.44.997-.776.272-.335.463-.739.571-1.21h2.48a4.823 4.823 0 0 1-.827 2.267 4.76 4.76 0 0 1-1.849 1.568c-.767.38-1.668.571-2.702.571ZM121.571 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.802 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM113.379 9.91v2.044h-7.151V9.91h7.151Zm-5.233-3.137h2.548v12.383c0 .495.074.867.221 1.117.148.244.339.412.572.503.238.085.497.127.775.127.205 0 .384-.014.537-.042l.358-.068.46 2.105a4.307 4.307 0 0 1-.63.17 4.992 4.992 0 0 1-1.023.102 4.483 4.483 0 0 1-1.875-.358 3.208 3.208 0 0 1-1.406-1.159c-.358-.522-.537-1.179-.537-1.968V6.773ZM98.321 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.13-2.361-.506-1.012-.76-2.194-.76-3.546 0-1.358.254-2.545.76-3.562.505-1.017 1.215-1.807 2.13-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.008-2.139c.796 0 1.455-.21 1.978-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.978-.639-.8 0-1.465.213-1.994.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.644 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM84.065 23V9.91h2.463v2.079h.136a3.164 3.164 0 0 1 1.261-1.662 3.61 3.61 0 0 1 2.063-.614 10.896 10.896 0 0 1 1.082.06v2.437a4.577 4.577 0 0 0-.545-.094 5.202 5.202 0 0 0-.784-.06c-.603 0-1.14.129-1.611.384a2.85 2.85 0 0 0-1.517 2.566V23h-2.548ZM68.918 23V5.545h6.221c1.358 0 2.483.248 3.375.742.892.494 1.56 1.17 2.003 2.028.443.853.665 1.813.665 2.881 0 1.074-.225 2.04-.674 2.898-.443.852-1.113 1.528-2.01 2.028-.893.494-2.015.742-3.367.742h-4.279V14.63h4.04c.858 0 1.554-.148 2.088-.444.534-.3.926-.71 1.176-1.227.25-.517.375-1.105.375-1.764 0-.66-.125-1.244-.375-1.756-.25-.511-.645-.912-1.184-1.201-.534-.29-1.239-.435-2.114-.435h-3.307V23h-2.633Z"></path></svg></a></div><div class="fixed inset-x-0 top-0 z-50 flex h-14 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 dark:backdrop-blur bg-white/[var(--bg-opacity-light)] dark:bg-zinc-900/[var(--bg-opacity-dark)]" style="--bg-opacity-light:0.5;--bg-opacity-dark:0.2"><div class="absolute inset-x-0 top-full h-px transition bg-zinc-900/7.5 dark:bg-white/7.5"></div><div class="hidden lg:block lg:max-w-md lg:flex-auto"><button type="button" class="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 ui-not-focus-visible:outline-none lg:flex dark:bg-white/5 dark:text-zinc-400 dark:ring-inset dark:ring-white/10 dark:hover:ring-white/20"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" d="M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25"></path></svg>Find something...<kbd class="ml-auto text-2xs text-zinc-400 dark:text-zinc-500"><kbd class="font-sans"></kbd><kbd class="font-sans">K</kbd></kbd></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--></div><div class="flex items-center gap-5 lg:hidden"><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5" aria-label="Toggle navigation"><svg viewBox="0 0 10 9" fill="none" stroke-linecap="round" aria-hidden="true" class="w-2.5 stroke-zinc-900 dark:stroke-white"><path d="M.5 1h9M.5 8h9M.5 4.5h9"></path></svg></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--><a aria-label="Home" href="/"><svg viewBox="0 0 162 28" aria-hidden="true" class="h-6"><path class="fill-blue-500" d="M14.362 27.78c-1.956 0-3.756-.324-5.4-.972-1.644-.648-3.072-1.566-4.284-2.754a12.247 12.247 0 0 1-2.808-4.158c-.66-1.596-.99-3.33-.99-5.202 0-2.232.342-4.212 1.026-5.94.696-1.728 1.656-3.18 2.88-4.356a12.48 12.48 0 0 1 4.284-2.7c1.632-.612 3.378-.918 5.238-.918 2.28 0 4.278.354 5.994 1.062 1.716.708 3.144 1.668 4.284 2.88a11.706 11.706 0 0 1 2.538 4.158c.552 1.548.804 3.168.756 4.86-.06 2.328-.546 4.116-1.458 5.364-.912 1.236-2.328 1.854-4.248 1.854a5.839 5.839 0 0 1-2.826-.702 3.703 3.703 0 0 1-1.764-2.07l1.044.054c-.492.924-1.164 1.572-2.016 1.944a6.464 6.464 0 0 1-2.61.558c-1.212 0-2.28-.258-3.204-.774a5.682 5.682 0 0 1-2.178-2.214c-.528-.948-.792-2.046-.792-3.294 0-1.284.276-2.394.828-3.33a5.77 5.77 0 0 1 2.232-2.196c.936-.516 1.992-.774 3.168-.774.78 0 1.59.162 2.43.486.852.324 1.512.78 1.98 1.368l-.738.936V8.664h2.412l-.054 6.462c0 .924.18 1.62.54 2.088.36.468.894.702 1.602.702.624 0 1.104-.174 1.44-.522.348-.36.588-.846.72-1.458a10.66 10.66 0 0 0 .252-2.106c.036-1.86-.24-3.426-.828-4.698-.588-1.272-1.386-2.298-2.394-3.078a9.499 9.499 0 0 0-3.294-1.71c-1.2-.36-2.394-.54-3.582-.54-1.68 0-3.174.27-4.482.81-1.308.528-2.412 1.278-3.312 2.25-.888.96-1.56 2.1-2.016 3.42-.444 1.308-.654 2.748-.63 4.32.048 1.56.33 2.964.846 4.212a9.324 9.324 0 0 0 2.16 3.204 9.38 9.38 0 0 0 3.276 2.034c1.26.468 2.64.702 4.14.702.84 0 1.674-.096 2.502-.288.84-.18 1.608-.438 2.304-.774l1.026 2.808c-.924.432-1.896.75-2.916.954a14.649 14.649 0 0 1-3.078.324Zm-.144-10.098c.852 0 1.566-.246 2.142-.738.576-.492.864-1.326.864-2.502 0-1.068-.258-1.872-.774-2.412-.504-.552-1.218-.828-2.142-.828-1.092 0-1.908.288-2.448.864-.54.576-.81 1.368-.81 2.376 0 1.032.276 1.83.828 2.394.564.564 1.344.846 2.34.846Z"></path><path class="fill-blue-600 dark:fill-blue-500" d="M51.799 7.813V5.545h13.509v2.268H59.86V23h-2.624V7.812h-5.438ZM39.392 23h-2.795l6.28-17.455h3.043L52.203 23h-2.796L44.472 8.716h-.137L39.392 23Zm.469-6.835h9.068v2.216h-9.068v-2.216Z"></path><path class="fill-zinc-700 dark:fill-zinc-400" d="M161.144 5.545V23h-2.548V5.545h2.548ZM149.649 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.506 1.017.758 2.204.758 3.562 0 1.352-.252 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.384-1.468.384-2.31 0-.834-.128-1.602-.384-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.801 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.194.631 1.995.631ZM136.032 23.264c-1.267 0-2.358-.287-3.273-.86-.909-.58-1.608-1.378-2.096-2.395-.489-1.018-.733-2.182-.733-3.495 0-1.33.25-2.503.75-3.52.5-1.022 1.204-1.82 2.113-2.395.909-.573 1.98-.86 3.213-.86.995 0 1.881.184 2.659.554a4.764 4.764 0 0 1 1.884 1.534c.483.659.77 1.429.861 2.31h-2.48a2.974 2.974 0 0 0-.938-1.586c-.483-.443-1.13-.665-1.943-.665-.71 0-1.332.188-1.866.563-.529.37-.941.898-1.236 1.585-.296.682-.443 1.489-.443 2.42 0 .955.144 1.779.434 2.472.29.693.699 1.23 1.227 1.61.535.382 1.162.572 1.884.572.483 0 .92-.088 1.313-.264.397-.182.73-.44.997-.776.272-.335.463-.739.571-1.21h2.48a4.823 4.823 0 0 1-.827 2.267 4.76 4.76 0 0 1-1.849 1.568c-.767.38-1.668.571-2.702.571ZM121.571 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.802 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM113.379 9.91v2.044h-7.151V9.91h7.151Zm-5.233-3.137h2.548v12.383c0 .495.074.867.221 1.117.148.244.339.412.572.503.238.085.497.127.775.127.205 0 .384-.014.537-.042l.358-.068.46 2.105a4.307 4.307 0 0 1-.63.17 4.992 4.992 0 0 1-1.023.102 4.483 4.483 0 0 1-1.875-.358 3.208 3.208 0 0 1-1.406-1.159c-.358-.522-.537-1.179-.537-1.968V6.773ZM98.321 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.13-2.361-.506-1.012-.76-2.194-.76-3.546 0-1.358.254-2.545.76-3.562.505-1.017 1.215-1.807 2.13-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.008-2.139c.796 0 1.455-.21 1.978-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.978-.639-.8 0-1.465.213-1.994.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.644 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM84.065 23V9.91h2.463v2.079h.136a3.164 3.164 0 0 1 1.261-1.662 3.61 3.61 0 0 1 2.063-.614 10.896 10.896 0 0 1 1.082.06v2.437a4.577 4.577 0 0 0-.545-.094 5.202 5.202 0 0 0-.784-.06c-.603 0-1.14.129-1.611.384a2.85 2.85 0 0 0-1.517 2.566V23h-2.548ZM68.918 23V5.545h6.221c1.358 0 2.483.248 3.375.742.892.494 1.56 1.17 2.003 2.028.443.853.665 1.813.665 2.881 0 1.074-.225 2.04-.674 2.898-.443.852-1.113 1.528-2.01 2.028-.893.494-2.015.742-3.367.742h-4.279V14.63h4.04c.858 0 1.554-.148 2.088-.444.534-.3.926-.71 1.176-1.227.25-.517.375-1.105.375-1.764 0-.66-.125-1.244-.375-1.756-.25-.511-.645-.912-1.184-1.201-.534-.29-1.239-.435-2.114-.435h-3.307V23h-2.633Z"></path></svg></a></div><div class="flex items-center gap-5"><nav class="hidden md:block"><ul role="list" class="flex items-center gap-8"><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/sdks">SDKs</a></li><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="https://docs.bsky.app/blog">Blog</a></li><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="https://github.com/bluesky-social/atproto">GitHub</a></li><select class="block w-full appearance-none rounded-md border-0 py-1.5 pl-3 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-gray-800 dark:text-gray-100 dark:ring-gray-700"><option value="en" selected="">English</option><option value="pt">Português</option><option value="ja">日本語</option></select></ul></nav><div class="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15"></div><div class="flex gap-4"><div class="contents lg:hidden"><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 ui-not-focus-visible:outline-none lg:hidden dark:hover:bg-white/5" aria-label="Find something..."><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-zinc-900 dark:stroke-white"><path stroke-linecap="round" stroke-linejoin="round" d="M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25"></path></svg></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--></div><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5" aria-label="Toggle theme"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-zinc-900 dark:hidden"><path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z"></path><path stroke-linecap="round" d="M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707"></path></svg><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="hidden h-5 w-5 stroke-white dark:block"><path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z"></path></svg></button></div></div></div><nav class="hidden lg:mt-10 lg:block"><ul role="list"><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/">API</a></li><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="#">Documentation</a></li><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="#">Support</a></li><li class="relative mt-6 md:mt-0"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Home</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/"><span class="truncate">Introduction</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/sdks"><span class="truncate">SDKs</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/glossary"><span class="truncate">Glossary</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/faq"><span class="truncate">FAQ</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Building apps</h2><div class="relative mt-3 pl-2"><div class="absolute inset-x-0 top-0 bg-zinc-800/2.5 will-change-transform dark:bg-white/2.5" style="border-radius:8px;height:32px;top:0;opacity:0"></div><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><div class="absolute left-2 h-6 w-px bg-blue-500" style="top:4px;opacity:1"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a aria-current="page" class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-900 dark:text-white" href="/guides/applications"><span class="truncate">Quick start</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Guides</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/overview"><span class="truncate">Overview</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/self-hosting"><span class="truncate">Self-hosting</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/identity"><span class="truncate">Identity</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/data-repos"><span class="truncate">Data repos</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/lexicon"><span class="truncate">Schemas &amp; Lexicon</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Specs</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/atp"><span class="truncate">AT Protocol</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/data-model"><span class="truncate">Data Model</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/lexicon"><span class="truncate">Lexicon</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/cryptography"><span class="truncate">Cryptography</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/repository"><span class="truncate">Repository</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/blob"><span class="truncate">Blobs</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/label"><span class="truncate">Labels</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/xrpc"><span class="truncate">HTTP API (XRPC)</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/oauth"><span class="truncate">OAuth</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/event-stream"><span class="truncate">Event Stream</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/did"><span class="truncate">DID</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/handle"><span class="truncate">Handle</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/nsid"><span class="truncate">NSID</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/record-key"><span class="truncate">Record Key</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/at-uri-scheme"><span class="truncate">URI Scheme</span></a></li></ul></div></li></ul></nav></div></header><div class="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8"><main class="flex-auto"><article class="flex h-full flex-col pb-24 pt-16"><div class="flex-auto prose dark:prose-invert [html_:where(&amp;&gt;*)]:mx-auto [html_:where(&amp;&gt;*)]:max-w-2xl [html_:where(&amp;&gt;*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&amp;&gt;*)]:lg:max-w-3xl"><h1>Quick start guide to building applications on AT Protocol</h1> <a class="not-prose flex items-center gap-2 bg-blue-100 dark:bg-blue-950 dark:text-white px-4 py-3 text-base rounded-lg hover:underline" href="https://github.com/bluesky-social/statusphere-example-app"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-6 dark:text-white"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"></path></svg><span><p>Find the source code for the example application on GitHub.</p></span></a> <p class="lead">In this guide, we&#x27;re going to build a simple multi-user app that publishes your current &quot;status&quot; as an emoji. Our application will look like this:</p> <img alt="A screenshot of our example application" loading="lazy" width="841" height="530" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-screenshot.1d8d0740.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-screenshot.1d8d0740.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-screenshot.1d8d0740.png&amp;w=1920&amp;q=75"/> <p class="lead">We will cover how to:</p> <ul> <li class="lead">Signin via OAuth</li> <li class="lead">Fetch information about users (profiles)</li> <li class="lead">Listen to the network firehose for new data</li> <li class="lead">Publish data on the user&#x27;s account using a custom schema</li> </ul> <p class="lead">We&#x27;re going to keep this light so you can quickly wrap your head around ATProto. There will be links with more information about each step.</p> <h2 class="scroll-mt-24" id="introduction"><a class="group text-inherit no-underline hover:text-inherit" href="#introduction">Introduction</a></h2> <p>Data in the Atmosphere is stored on users&#x27; personal repos. It&#x27;s almost like each user has their own website. Our goal is to aggregate data from the users into our SQLite DB.</p> <p>Think of our app like a Google. If Google&#x27;s job was to say which emoji each website had under <code>/status.json</code>, then it would show something like:</p> <ul> <li><code>nytimes.com</code> is feeling 📰 according to <code>https://nytimes.com/status.json</code></li> <li><code>bsky.app</code> is feeling 🦋 according to <code>https://bsky.app/status.json</code></li> <li><code>reddit.com</code> is feeling 🤓 according to <code>https://reddit.com/status.json</code></li> </ul> <p>The Atmosphere works the same way, except we&#x27;re going to check <code>at://</code> instead of <code>https://</code>. Each user has a data repo under an <code>at://</code> URL. We&#x27;ll crawl all the user data repos in the Atmosphere for all the &quot;status.json&quot; records and aggregate them into our SQLite database.</p> <blockquote> <p><code>at://</code> is the URL scheme of the AT Protocol. Under the hood it uses common tech like HTTP and DNS, but it adds all of the features we&#x27;ll be using in this tutorial.</p> </blockquote> <h2 class="scroll-mt-24" id="step-1-starting-with-our-express-js-app"><a class="group text-inherit no-underline hover:text-inherit" href="#step-1-starting-with-our-express-js-app">Step 1. Starting with our ExpressJS app</a></h2> <p>Start by cloning the repo and installing packages.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-bash"><span><span style="color: var(--shiki-token-function)">git</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">clone</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">https://github.com/bluesky-social/statusphere-example-app.git</span></span> <span><span style="color: var(--shiki-token-function)">cd</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">statusphere-example-app</span></span> <span><span style="color: var(--shiki-token-function)">cp</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">.env.template</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">.env</span></span> <span><span style="color: var(--shiki-token-function)">npm</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">install</span></span> <span><span style="color: var(--shiki-token-function)">npm</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">run</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">dev</span></span> <span><span style="color: var(--shiki-token-comment)"># Navigate to http://localhost:8080</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Our repo is a regular Web app. We&#x27;re rendering our HTML server-side like it&#x27;s 1999. We also have a SQLite database that we&#x27;re managing with <a href="https://kysely.dev/">Kysely</a>.</p> <p>Our starting stack:</p> <ul> <li>Typescript</li> <li>NodeJS web server (<a href="https://expressjs.com/">express</a>)</li> <li>SQLite database (<a href="https://kysely.dev/">Kysely</a>)</li> <li>Server-side rendering (<a href="https://www.npmjs.com/package/uhtml">uhtml</a>)</li> </ul> <p>With each step we&#x27;ll explain how our Web app taps into the Atmosphere. Refer to the codebase for more detailed code — again, this tutorial is going to keep it light and quick to digest.</p> <h2 class="scroll-mt-24" id="step-2-signing-in-with-o-auth"><a class="group text-inherit no-underline hover:text-inherit" href="#step-2-signing-in-with-o-auth">Step 2. Signing in with OAuth</a></h2> <p>When somebody logs into our app, they&#x27;ll give us read &amp; write access to their personal <code>at://</code> repo. We&#x27;ll use that to write the status json record.</p> <p>We&#x27;re going to accomplish this using OAuth (<a href="https://github.com/bluesky-social/proposals/tree/main/0004-oauth">spec</a>). Most of the OAuth flows are going to be handled for us using the <a href="https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node">@atproto/oauth-client-node</a> library. This is the arrangement we&#x27;re aiming toward:</p> <img alt="A diagram of the OAuth elements" loading="lazy" width="655" height="274" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-oauth.5ebec062.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-oauth.5ebec062.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-oauth.5ebec062.png&amp;w=1920&amp;q=75"/> <p>When the user logs in, the OAuth client will create a new session with their repo server and give us read/write access along with basic user info.</p> <img alt="A screenshot of the login UI" loading="lazy" width="482" height="122" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-login.83cd693f.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-login.83cd693f.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-login.83cd693f.png&amp;w=1080&amp;q=75"/> <p>Our login page just asks the user for their &quot;handle,&quot; which is the domain name associated with their account. For <a href="https://bsky.app">Bluesky</a> users, these tend to look like <code>alice.bsky.social</code>, but they can be any kind of domain (eg <code>alice.com</code>).</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-html"><span><span style="color: var(--shiki-token-comment)">&lt;!-- src/pages/login.ts --&gt;</span></span> <span><span style="color: var(--shiki-color-text)">&lt;</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">action</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;/login&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">method</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;post&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;login-form&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">input</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">type</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;text&quot;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">name</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;handle&quot;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">placeholder</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;Enter your handle (eg alice.bsky.social)&quot;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">required</span></span> <span><span style="color: var(--shiki-color-text)"> /&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">type</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;submit&quot;</span><span style="color: var(--shiki-color-text)">&gt;Log in&lt;/</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)">&lt;/</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>When they submit the form, we tell our OAuth client to initiate the authorization flow and then redirect the user to their server to complete the process.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// Login handler</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.post</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/login&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Initiate the OAuth flow</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">handle</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">req</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">body</span><span style="color: var(--shiki-color-text)">?.handle</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">url</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">oauthClient</span><span style="color: var(--shiki-token-function)">.authorize</span><span style="color: var(--shiki-color-text)">(handle</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> scope</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;atproto transition:generic&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.redirect</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">url</span><span style="color: var(--shiki-token-function)">.toString</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>This is the same kind of SSO flow that Google or GitHub uses. The user will be asked for their password, then asked to confirm the session with your application.</p> <p>When that finishes, the user will be sent back to <code>/oauth/callback</code> on our Web app. The OAuth client will store the access tokens for the user&#x27;s server, and then we attach their account&#x27;s <a href="https://atproto.com/specs/did">DID</a> to the cookie-session.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// OAuth callback to complete session creation</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.get</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/oauth/callback&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Store the credentials</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> { </span><span style="color: var(--shiki-token-constant)">session</span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">oauthClient</span><span style="color: var(--shiki-token-function)">.callback</span><span style="color: var(--shiki-color-text)">(params)</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Attach the account DID to our user via a cookie</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">cookieSession</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getIronSession</span><span style="color: var(--shiki-color-text)">(req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">cookieSession</span><span style="color: var(--shiki-color-text)">.did </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">session</span><span style="color: var(--shiki-color-text)">.did</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">cookieSession</span><span style="color: var(--shiki-token-function)">.save</span><span style="color: var(--shiki-color-text)">()</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Send them back to the app</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.redirect</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;/&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>With that, we&#x27;re in business! We now have a session with the user&#x27;s repo server and can use that to access their data.</p> <h2 class="scroll-mt-24" id="step-3-fetching-the-users-profile"><a class="group text-inherit no-underline hover:text-inherit" href="#step-3-fetching-the-users-profile">Step 3. Fetching the user&#x27;s profile</a></h2> <p>Why don&#x27;t we learn something about our user? In <a href="https://bsky.app">Bluesky</a>, users publish a &quot;profile&quot; record which looks like this:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-keyword)">interface</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">ProfileRecord</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> displayName</span><span style="color: var(--shiki-token-keyword)">?:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">string</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// a human friendly name</span></span> <span><span style="color: var(--shiki-color-text)"> description</span><span style="color: var(--shiki-token-keyword)">?:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">string</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// a short bio</span></span> <span><span style="color: var(--shiki-color-text)"> avatar</span><span style="color: var(--shiki-token-keyword)">?:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">BlobRef</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// small profile picture</span></span> <span><span style="color: var(--shiki-color-text)"> banner</span><span style="color: var(--shiki-token-keyword)">?:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">BlobRef</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// banner image to put on profiles</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">?:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">string</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// declared time this profile data was added</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>You can examine this record directly using <a href="https://atproto-browser.vercel.app">atproto-browser.vercel.app</a>. For instance, <a href="https://atproto-browser.vercel.app/at?u=at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.actor.profile/self">this is the profile record for @bsky.app</a>.</p> <p>We&#x27;re going to use the <a href="https://github.com/bluesky-social/atproto/tree/main/packages/api">Agent</a> associated with the user&#x27;s OAuth session to fetch this record.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">repo</span><span style="color: var(--shiki-token-function)">.getRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The user</span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;app.bsky.actor.profile&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The collection</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;self&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The record key</span></span> <span><span style="color: var(--shiki-color-text)">})</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>When asking for a record, we provide three pieces of information.</p> <ul> <li><strong>repo</strong> The <a href="https://atproto.com/specs/did">DID</a> which identifies the user,</li> <li><strong>collection</strong> The collection name, and</li> <li><strong>rkey</strong> The record key</li> </ul> <p>We&#x27;ll explain the collection name shortly. Record keys are strings with <a href="https://atproto.com/specs/record-key#record-key-syntax">some restrictions</a> and a couple of common patterns. The <code>&quot;self&quot;</code> pattern is used when a collection is expected to only contain one record which describes the user.</p> <p>Let&#x27;s update our homepage to fetch this profile record:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// Homepage</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.get</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// If the user is signed in, get an agent which communicates with their server</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getSessionAgent</span><span style="color: var(--shiki-color-text)">(req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> ctx)</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">agent) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Serve the logged-out view</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.type</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;html&#39;</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.send</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">page</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">home</span><span style="color: var(--shiki-color-text)">()))</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Fetch additional information about the logged-in user</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> { data: </span><span style="color: var(--shiki-token-constant)">profileRecord</span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">repo</span><span style="color: var(--shiki-token-function)">.getRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// our user&#39;s repo</span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;app.bsky.actor.profile&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// the bluesky profile record type</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;self&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// the record&#39;s key</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Serve the logged-in view</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> res</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.type</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;html&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.send</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">page</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">home</span><span style="color: var(--shiki-color-text)">({ profile</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">profileRecord</span><span style="color: var(--shiki-color-text)">.value </span><span style="color: var(--shiki-token-keyword)">||</span><span style="color: var(--shiki-color-text)"> {} })))</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>With that data, we can give a nice personalized welcome banner for our user:</p> <img alt="A screenshot of the banner image" loading="lazy" width="442" height="51" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-banner.1e92c654.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-banner.1e92c654.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-banner.1e92c654.png&amp;w=1080&amp;q=75"/> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-html"><span><span style="color: var(--shiki-token-comment)">&lt;!-- pages/home.ts --&gt;</span></span> <span><span style="color: var(--shiki-color-text)">&lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;card&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> ${profile</span></span> <span><span style="color: var(--shiki-color-text)"> ? html`&lt;</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">action</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;/logout&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">method</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;post&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;session-form&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> Hi, &lt;</span><span style="color: var(--shiki-token-string-expression)">strong</span><span style="color: var(--shiki-color-text)">&gt;${profile.displayName || &#39;friend&#39;}&lt;/</span><span style="color: var(--shiki-token-string-expression)">strong</span><span style="color: var(--shiki-color-text)">&gt;.</span></span> <span><span style="color: var(--shiki-color-text)"> What&#39;s your status today?</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">type</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;submit&quot;</span><span style="color: var(--shiki-color-text)">&gt;Log out&lt;/</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)">&gt;`</span></span> <span><span style="color: var(--shiki-color-text)"> : html`&lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;session-form&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;&lt;</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;/login&quot;</span><span style="color: var(--shiki-color-text)">&gt;Log in&lt;/</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)">&gt; to set your status!&lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;/login&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;button&quot;</span><span style="color: var(--shiki-color-text)">&gt;Log in&lt;/</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;`}</span></span> <span><span style="color: var(--shiki-color-text)">&lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <h2 class="scroll-mt-24" id="step-4-reading-and-writing-records"><a class="group text-inherit no-underline hover:text-inherit" href="#step-4-reading-and-writing-records">Step 4. Reading &amp; writing records</a></h2> <p>You can think of the user repositories as collections of JSON records:</p> <img alt="A diagram of a repository" loading="lazy" width="719" height="417" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-repo.4a34005b.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-repo.4a34005b.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-repo.4a34005b.png&amp;w=1920&amp;q=75"/> <p>Let&#x27;s look again at how we read the &quot;profile&quot; record:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">repo</span><span style="color: var(--shiki-token-function)">.getRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The user</span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;app.bsky.actor.profile&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The collection</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;self&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The record key</span></span> <span><span style="color: var(--shiki-color-text)">})</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>We write records using a similar API. Since our goal is to write &quot;status&quot; records, let&#x27;s look at how that will happen:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">// Generate a time-based key for our record</span></span> <span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">rkey</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">TID</span><span style="color: var(--shiki-token-function)">.nextStr</span><span style="color: var(--shiki-color-text)">()</span></span> <span></span> <span><span style="color: var(--shiki-token-comment)">// Write the </span></span> <span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">repo</span><span style="color: var(--shiki-token-function)">.putRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The user</span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The collection</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// The record key</span></span> <span><span style="color: var(--shiki-color-text)"> record</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { </span><span style="color: var(--shiki-token-comment)">// The record value</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;👍&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)">})</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Our <code>POST /status</code> route is going to use this API to publish the user&#x27;s status to their repo.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// &quot;Set status&quot; handler</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.post</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// If the user is signed in, get an agent which communicates with their server</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getSessionAgent</span><span style="color: var(--shiki-color-text)">(req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> ctx)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">agent) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">401</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.type</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;html&#39;</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.send</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;&lt;h1&gt;Error: Session required&lt;/h1&gt;&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Construct their status record</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> $type</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">req</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">body</span><span style="color: var(--shiki-color-text)">?.status</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">try</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Write the status record to the user&#39;s repository</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.putRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">TID</span><span style="color: var(--shiki-token-function)">.nextStr</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> record</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">catch</span><span style="color: var(--shiki-color-text)"> (err) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">logger</span><span style="color: var(--shiki-token-function)">.warn</span><span style="color: var(--shiki-color-text)">({ err }</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;failed to write record&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">500</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.type</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;html&#39;</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.send</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;&lt;h1&gt;Error: Failed to write record&lt;/h1&gt;&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">200</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">({})</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Now in our homepage we can list out the status buttons:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-html"><span><span style="color: var(--shiki-token-comment)">&lt;!-- src/pages/home.ts --&gt;</span></span> <span><span style="color: var(--shiki-color-text)">&lt;</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">action</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;/status&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">method</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;post&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;status-options&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> ${STATUS_OPTIONS.map(status =&gt; html`</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;status-option&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">name</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;status&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">value</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;${status}&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> ${status}</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">button</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> `)}</span></span> <span><span style="color: var(--shiki-color-text)">&lt;/</span><span style="color: var(--shiki-token-string-expression)">form</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>And here we are!</p> <img alt="A screenshot of the app&#x27;s status options" loading="lazy" width="467" height="157" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-options.6c0bfc19.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-options.6c0bfc19.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-options.6c0bfc19.png&amp;w=1080&amp;q=75"/> <h2 class="scroll-mt-24" id="step-5-creating-a-custom-status-schema"><a class="group text-inherit no-underline hover:text-inherit" href="#step-5-creating-a-custom-status-schema">Step 5. Creating a custom &quot;status&quot; schema</a></h2> <p>Repo collections are typed, meaning that they have a defined schema. The <code>app.bsky.actor.profile</code> type definition <a href="https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/profile.json">can be found here</a>.</p> <p>Anybody can create a new schema using the <a href="https://atproto.com/specs/lexicon">Lexicon</a> language, which is very similar to <a href="http://json-schema.org/">JSON-Schema</a>. The schemas use <a href="https://atproto.com/specs/nsid">reverse-DNS IDs</a> which indicate ownership. In this demo app we&#x27;re going to use <code>xyz.statusphere</code> which we registered specifically for this project (aka statusphere.xyz).</p> <blockquote> <h3>Why create a schema?</h3> <p>Schemas help other applications understand the data your app is creating. By publishing your schemas, you make it easier for other application authors to publish data in a format your app will recognize and handle.</p> </blockquote> <p>Let&#x27;s create our schema in the <code>/lexicons</code> folder of our codebase. You can <a href="https://atproto.com/guides/lexicon">read more about how to define schemas here</a>.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-json"><span><span style="color: var(--shiki-token-comment)">/** lexicons/status.json **/</span></span> <span><span style="color: var(--shiki-color-text)">{</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;lexicon&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;id&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;xyz.statusphere.status&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;defs&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;main&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;type&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;record&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;key&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;tid&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;record&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;type&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;object&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;required&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">&quot;status&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;createdAt&quot;</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;properties&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;status&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;type&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;string&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;minLength&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;maxGraphemes&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;maxLength&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">32</span></span> <span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;createdAt&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;type&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;string&quot;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&quot;format&quot;</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;datetime&quot;</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Now let&#x27;s run some code-generation using our schema:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-bash"><span><span style="color: var(--shiki-token-function)">./node_modules/.bin/lex</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">gen-server</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">./src/lexicon</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">./lexicons/*</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>This will produce Typescript interfaces as well as runtime validation functions that we can use in our app. Here&#x27;s what that generated code looks like:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/lexicon/types/xyz/statusphere/status.ts **/</span></span> <span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">interface</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Record</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">string</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">string</span></span> <span><span style="color: var(--shiki-color-text)"> [k: string]</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">unknown</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span> <span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">function</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">isRecord</span><span style="color: var(--shiki-color-text)">(v</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">unknown</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> v </span><span style="color: var(--shiki-token-keyword)">is</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Record</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> (</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">isObj</span><span style="color: var(--shiki-color-text)">(v) </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">hasProp</span><span style="color: var(--shiki-color-text)">(v</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;$type&#39;</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-constant)">v</span><span style="color: var(--shiki-color-text)">.$type </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status#main&#39;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">||</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">v</span><span style="color: var(--shiki-color-text)">.$type </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> )</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span> <span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">function</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">validateRecord</span><span style="color: var(--shiki-color-text)">(v</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">unknown</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">ValidationResult</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">lexicons</span><span style="color: var(--shiki-token-function)">.validate</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status#main&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> v)</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Let&#x27;s use that code to improve the <code>POST /status</code> route:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">as</span><span style="color: var(--shiki-color-text)"> Status </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;#/lexicon/types/xyz/statusphere/status&#39;</span></span> <span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span><span style="color: var(--shiki-token-comment)">// &quot;Set status&quot; handler</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.post</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Construct &amp; validate their status record</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> $type</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">req</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">body</span><span style="color: var(--shiki-color-text)">?.status</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-token-constant)">Status</span><span style="color: var(--shiki-token-function)">.validateRecord</span><span style="color: var(--shiki-color-text)">(record).success) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">400</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">({ error</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;Invalid status&#39;</span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <h2 class="scroll-mt-24" id="step-6-listening-to-the-firehose"><a class="group text-inherit no-underline hover:text-inherit" href="#step-6-listening-to-the-firehose">Step 6. Listening to the firehose</a></h2> <p>So far, we have:</p> <ul> <li>Logged in via OAuth</li> <li>Created a custom schema</li> <li>Read &amp; written records for the logged in user</li> </ul> <p>Now we want to fetch the status records from other users.</p> <p>Remember how we referred to our app as being like Google, crawling around the repos to get their records? One advantage we have in the AT Protocol is that each repo publishes an event log of their updates.</p> <img alt="A diagram of the event stream" loading="lazy" width="499" height="283" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-event-stream.aa119d83.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-event-stream.aa119d83.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-event-stream.aa119d83.png&amp;w=1080&amp;q=75"/> <p>Using a <a href="https://docs.bsky.app/docs/advanced-guides/federation-architecture#relay">Relay service</a> we can listen to an aggregated firehose of these events across all users in the network. In our case what we&#x27;re looking for are valid <code>xyz.statusphere.status</code> records.</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/ingester.ts **/</span></span> <span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { Firehose } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;@atproto/sync&#39;</span></span> <span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">as</span><span style="color: var(--shiki-color-text)"> Status </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;#/lexicon/types/xyz/statusphere/status&#39;</span></span> <span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span></span> <span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Firehose</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> filterCollections</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handleEvent</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (evt) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Watch for write events</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.event </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;create&#39;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">||</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.event </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;update&#39;</span><span style="color: var(--shiki-color-text)">) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.record</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// If the write is a valid status update</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.collection </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">Status</span><span style="color: var(--shiki-token-function)">.isRecord</span><span style="color: var(--shiki-color-text)">(record) </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">Status</span><span style="color: var(--shiki-token-function)">.validateRecord</span><span style="color: var(--shiki-color-text)">(record).success</span></span> <span><span style="color: var(--shiki-color-text)"> ) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Store the status</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// TODO</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)">})</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Let&#x27;s create a SQLite table to store these statuses:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/db.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// Create our statuses table</span></span> <span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">db</span><span style="color: var(--shiki-color-text)">.schema</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.createTable</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;status&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.addColumn</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;uri&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;varchar&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (col) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">col</span><span style="color: var(--shiki-token-function)">.primaryKey</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.addColumn</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;authorDid&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;varchar&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (col) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">col</span><span style="color: var(--shiki-token-function)">.notNull</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.addColumn</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;varchar&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (col) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">col</span><span style="color: var(--shiki-token-function)">.notNull</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.addColumn</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;createdAt&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;varchar&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (col) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">col</span><span style="color: var(--shiki-token-function)">.notNull</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.addColumn</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;indexedAt&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;varchar&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> (col) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">col</span><span style="color: var(--shiki-token-function)">.notNull</span><span style="color: var(--shiki-color-text)">())</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.execute</span><span style="color: var(--shiki-color-text)">()</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Now we can write these statuses into our database as they arrive from the firehose:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/ingester.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// If the write is a valid status update</span></span> <span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.collection </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">Status</span><span style="color: var(--shiki-token-function)">.isRecord</span><span style="color: var(--shiki-color-text)">(record) </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">Status</span><span style="color: var(--shiki-token-function)">.validateRecord</span><span style="color: var(--shiki-color-text)">(record).success</span></span> <span><span style="color: var(--shiki-color-text)">) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Store the status in our SQLite</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> db</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.insertInto</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;status&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.values</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> uri</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">uri</span><span style="color: var(--shiki-token-function)">.toString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> authorDid</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">evt</span><span style="color: var(--shiki-color-text)">.author</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)">.status</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)">.createdAt</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> indexedAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.onConflict</span><span style="color: var(--shiki-color-text)">((oc) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">oc</span><span style="color: var(--shiki-token-function)">.column</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;uri&#39;</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.doUpdateSet</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)">.status</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> indexedAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> )</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.execute</span><span style="color: var(--shiki-color-text)">()</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>You can almost think of information flowing in a loop:</p> <img alt="A diagram of the flow of information" loading="lazy" width="448" height="140" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75"/> <p>Applications write to the repo. The write events are then emitted on the firehose where they&#x27;re caught by the apps and ingested into their databases.</p> <p>Why sync from the event log like this? Because there are other apps in the network that will write the records we&#x27;re interested in. By subscribing to the event log, we ensure that we catch all the data we&#x27;re interested in — including data published by other apps!</p> <h2 class="scroll-mt-24" id="step-7-listing-the-latest-statuses"><a class="group text-inherit no-underline hover:text-inherit" href="#step-7-listing-the-latest-statuses">Step 7. Listing the latest statuses</a></h2> <p>Now that we have statuses populating our SQLite, we can produce a timeline of status updates by users. We also use a <a href="https://atproto.com/specs/did">DID</a>-to-handle resolver so we can show a nice username with the statuses:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// Homepage</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.get</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Fetch data stored in our SQLite</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">statuses</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> db</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.selectFrom</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;status&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.selectAll</span><span style="color: var(--shiki-color-text)">()</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.orderBy</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;indexedAt&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;desc&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.limit</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">10</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.execute</span><span style="color: var(--shiki-color-text)">()</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Map user DIDs to their domain-name handles</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">didHandleMap</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">resolver</span><span style="color: var(--shiki-token-function)">.resolveDidsToHandles</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">statuses</span><span style="color: var(--shiki-token-function)">.map</span><span style="color: var(--shiki-color-text)">((s) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">s</span><span style="color: var(--shiki-color-text)">.authorDid)</span></span> <span><span style="color: var(--shiki-color-text)"> )</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>Our HTML can now list these status records:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-html"><span><span style="color: var(--shiki-token-comment)">&lt;!-- src/pages/home.ts --&gt;</span></span> <span><span style="color: var(--shiki-color-text)">${statuses.map((status, i) =&gt; {</span></span> <span><span style="color: var(--shiki-color-text)"> const handle = didHandleMap[status.authorDid] || status.authorDid</span></span> <span><span style="color: var(--shiki-color-text)"> return html`</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;status-line&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;status&quot;</span><span style="color: var(--shiki-color-text)">&gt;${status.status}&lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;desc&quot;</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;author&quot;</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">&quot;https://bsky.app/profile/${handle}&quot;</span><span style="color: var(--shiki-color-text)">&gt;@${handle}&lt;/</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> was feeling ${status.status} on ${status.indexedAt}.</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> &lt;/</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">&gt;</span></span> <span><span style="color: var(--shiki-color-text)"> `</span></span> <span><span style="color: var(--shiki-color-text)">})}</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <img alt="A screenshot of the app status timeline" loading="lazy" width="255" height="166" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-history.25e5d14a.png&amp;w=256&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-history.25e5d14a.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fapp-status-history.25e5d14a.png&amp;w=640&amp;q=75"/> <h2 class="scroll-mt-24" id="step-8-optimistic-updates"><a class="group text-inherit no-underline hover:text-inherit" href="#step-8-optimistic-updates">Step 8. Optimistic updates</a></h2> <p>As a final optimization, let&#x27;s introduce &quot;optimistic updates.&quot;</p> <p>Remember the information flow loop with the repo write and the event log?</p> <img alt="A diagram of the flow of information" loading="lazy" width="448" height="140" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75"/> <p>Since we&#x27;re updating our users&#x27; repos locally, we can short-circuit that flow to our own database:</p> <img alt="A diagram illustrating optimistic updates" loading="lazy" width="441" height="154" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-optimistic-update.ca3f4cf1.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-optimistic-update.ca3f4cf1.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-optimistic-update.ca3f4cf1.png&amp;w=1080&amp;q=75"/> <p>This is an important optimization to make, because it ensures that the user sees their own changes while using your app. When the event eventually arrives from the firehose, we just discard it since we already have it saved locally.</p> <p>To do this, we just update <code>POST /status</code> to include an additional write to our SQLite DB:</p> <div class="my-6 overflow-hidden rounded-2xl bg-zinc-50 dark:bg-zinc-900 dark:ring-1 dark:ring-white/10"><div class="not-prose"><div class="group dark:bg-white/2.5"><div class="relative"><pre class="overflow-x-auto p-4 text-xs text-black dark:text-white"><code class="language-typescript"><span><span style="color: var(--shiki-token-comment)">/** src/routes.ts **/</span></span> <span><span style="color: var(--shiki-token-comment)">// &quot;Set status&quot; handler</span></span> <span><span style="color: var(--shiki-token-constant)">router</span><span style="color: var(--shiki-token-function)">.post</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;/status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">handler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-keyword)">async</span><span style="color: var(--shiki-color-text)"> (req</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> res) </span><span style="color: var(--shiki-token-keyword)">=&gt;</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// ...</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> uri</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">try</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Write the status record to the user&#39;s repository</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">com</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">atproto</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">repo</span><span style="color: var(--shiki-token-function)">.putRecord</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> repo</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span></span> <span><span style="color: var(--shiki-color-text)"> collection</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;xyz.statusphere.status&#39;</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> rkey</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">TID</span><span style="color: var(--shiki-token-function)">.nextStr</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> record</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> uri </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-color-text)">.uri</span></span> <span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">catch</span><span style="color: var(--shiki-color-text)"> (err) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">logger</span><span style="color: var(--shiki-token-function)">.warn</span><span style="color: var(--shiki-color-text)">({ err }</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;failed to write record&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">500</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">({ error</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;Failed to write record&#39;</span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">try</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Optimistically update our SQLite &lt;-- HERE!</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> db</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.insertInto</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;status&#39;</span><span style="color: var(--shiki-color-text)">)</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.values</span><span style="color: var(--shiki-color-text)">({</span></span> <span><span style="color: var(--shiki-color-text)"> uri</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> authorDid</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">agent</span><span style="color: var(--shiki-color-text)">.assertDid</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span></span> <span><span style="color: var(--shiki-color-text)"> status</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)">.status</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> createdAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">record</span><span style="color: var(--shiki-color-text)">.createdAt</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> indexedAt</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-function)">.toISOString</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.execute</span><span style="color: var(--shiki-color-text)">()</span></span> <span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">catch</span><span style="color: var(--shiki-color-text)"> (err) {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">logger</span><span style="color: var(--shiki-token-function)">.warn</span><span style="color: var(--shiki-color-text)">(</span></span> <span><span style="color: var(--shiki-color-text)"> { err }</span><span style="color: var(--shiki-token-punctuation)">,</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">&#39;failed to update computed view; ignoring as it should be caught by the firehose&#39;</span></span> <span><span style="color: var(--shiki-color-text)"> )</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">res</span><span style="color: var(--shiki-token-function)">.status</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">200</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-function)">.json</span><span style="color: var(--shiki-color-text)">({})</span></span> <span><span style="color: var(--shiki-color-text)"> })</span></span> <span><span style="color: var(--shiki-color-text)">)</span></span> <span></span></code></pre><button type="button" class="group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100 bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5"><span aria-hidden="false" class="pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300"><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400"><path stroke-width="0" d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"></path><path fill="none" stroke-linejoin="round" d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"></path></svg>Copy</span><span aria-hidden="true" class="pointer-events-none absolute inset-0 flex items-center justify-center text-blue-400 transition duration-300 translate-y-1.5 opacity-0">Copied!</span></button></div></div></div></div> <p>You&#x27;ll notice this code looks almost exactly like what we&#x27;re doing in <code>firehose.ts</code>.</p> <h2 class="scroll-mt-24" id="thinking-in-at-proto"><a class="group text-inherit no-underline hover:text-inherit" href="#thinking-in-at-proto">Thinking in AT Proto</a></h2> <p>In this tutorial we&#x27;ve covered the key steps to building an atproto app. Data is published in its canonical form on users&#x27; <code>at://</code> repos and then aggregated into apps&#x27; databases to produce views of the network.</p> <p>When building your app, think in these four key steps:</p> <ul> <li>Design the <a href="#">Lexicon</a> schemas for the records you&#x27;ll publish into the Atmosphere.</li> <li>Create a database for aggregating the records into useful views.</li> <li>Build your application to write the records on your users&#x27; repos.</li> <li>Listen to the firehose to aggregate data across the network.</li> </ul> <p>Remember this flow of information throughout:</p> <img alt="A diagram of the flow of information" loading="lazy" width="448" height="140" decoding="async" data-nimg="1" class="max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdiagram-info-flow.ccf81d0b.png&amp;w=1080&amp;q=75"/> <p>This is how every app in the Atmosphere works, including the <a href="https://bsky.app">Bluesky social app</a>.</p> <h2 class="scroll-mt-24" id="next-steps"><a class="group text-inherit no-underline hover:text-inherit" href="#next-steps">Next steps</a></h2> <p>If you want to practice what you&#x27;ve learned, here are some additional challenges you could try:</p> <ul> <li>Sync the profile records of all users so that you can show their display names instead of their handles.</li> <li>Count the number of each status used and display the total counts.</li> <li>Fetch the authed user&#x27;s <code>app.bsky.graph.follow</code> follows and show statuses from them.</li> <li>Create a different kind of schema, like a way to post links to websites and rate them 1 through 4 stars.</li> </ul> <div class="not-prose group relative mt-12 flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 dark:bg-white/2.5 dark:hover:shadow-black/5"><div class="pointer-events-none"><div class="absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50"><svg aria-hidden="true" class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5 dark:fill-white/1 dark:stroke-white/2.5"><defs><pattern id=":Rbpsvfeqkva:" width="72" height="56" patternUnits="userSpaceOnUse" x="50%" y="22"><path d="M.5 56V.5H72" fill="none"></path></pattern></defs><rect width="100%" height="100%" stroke-width="0" fill="url(#:Rbpsvfeqkva:)"></rect><svg x="50%" y="22" class="overflow-visible"><rect stroke-width="0" width="73" height="57" x="0" y="56"></rect></svg></svg></div><div class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[#e9f7ff] to-[#cee7f9] opacity-0 transition duration-300 group-hover:opacity-100 dark:from-[#12294d] dark:to-[#003964]" style="mask-image:radial-gradient(180px at 0px 0px, white, transparent);-webkit-mask-image:radial-gradient(180px at 0px 0px, white, transparent)"></div><div class="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100" style="mask-image:radial-gradient(180px at 0px 0px, white, transparent);-webkit-mask-image:radial-gradient(180px at 0px 0px, white, transparent)"><svg aria-hidden="true" class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 dark:fill-white/2.5 dark:stroke-white/10"><defs><pattern id=":R2rpsvfeqkva:" width="72" height="56" patternUnits="userSpaceOnUse" x="50%" y="22"><path d="M.5 56V.5H72" fill="none"></path></pattern></defs><rect width="100%" height="100%" stroke-width="0" fill="url(#:R2rpsvfeqkva:)"></rect><svg x="50%" y="22" class="overflow-visible"><rect stroke-width="0" width="73" height="57" x="0" y="56"></rect></svg></svg></div></div><div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-zinc-900/7.5 group-hover:ring-zinc-900/10 dark:ring-white/10 dark:group-hover:ring-white/20"></div><div class="relative rounded-2xl px-4 pb-4 pt-16"><h3 class="mt-4 text-sm font-semibold leading-7 text-zinc-900 dark:text-white"><a href="/"><span class="absolute inset-0 rounded-2xl"></span>Ready to learn more?</a></h3><div class="mt-1 flex items-center gap-1 text-sm text-zinc-600 dark:text-zinc-400"><span>Specs, guides, and SDKs can be found here.</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path></svg></div></div></div></div></article></main><footer class="mx-auto w-full max-w-2xl space-y-10 pb-16 lg:max-w-5xl"><div class="flex"><div class="flex flex-col items-start gap-3"><a class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300" aria-label="Previous: FAQ" href="/guides/faq"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5 -ml-1 rotate-180"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path></svg>Previous</a><a tabindex="-1" aria-hidden="true" class="text-base font-semibold text-zinc-900 transition hover:text-zinc-600 dark:text-white dark:hover:text-zinc-300" href="/guides/faq">FAQ</a></div><div class="ml-auto flex flex-col items-end gap-3"><a class="inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300" aria-label="Next: Overview" href="/guides/overview">Next<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5 -mr-1"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path></svg></a><a tabindex="-1" aria-hidden="true" class="text-base font-semibold text-zinc-900 transition hover:text-zinc-600 dark:text-white dark:hover:text-zinc-300" href="/guides/overview">Overview</a></div></div><div class="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 sm:flex-row dark:border-white/5"><p class="text-xs text-zinc-600 dark:text-zinc-400">© Copyright <!-- -->2024<!-- -->. All rights reserved.</p><div class="flex gap-4"><a class="group" href="https://bsky.app/profile/atproto.com"><span class="sr-only">Follow us on Bluesky</span><svg viewBox="0 0 360 320" aria-hidden="true" class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 dark:group-hover:fill-zinc-500"><path d="M180 141.964C163.699 110.262 119.308 51.1817 78.0347 22.044C38.4971 -5.86834 23.414 -1.03207 13.526 3.43594C2.08093 8.60755 0 26.1785 0 36.5164C0 46.8542 5.66748 121.272 9.36416 133.694C21.5786 174.738 65.0603 188.607 105.104 184.156C107.151 183.852 109.227 183.572 111.329 183.312C109.267 183.642 107.19 183.924 105.104 184.156C46.4204 192.847 -5.69621 214.233 62.6582 290.33C137.848 368.18 165.705 273.637 180 225.702C194.295 273.637 210.76 364.771 295.995 290.33C360 225.702 313.58 192.85 254.896 184.158C252.81 183.926 250.733 183.645 248.671 183.315C250.773 183.574 252.849 183.855 254.896 184.158C294.94 188.61 338.421 174.74 350.636 133.697C354.333 121.275 360 46.8568 360 36.519C360 26.1811 357.919 8.61012 346.474 3.43851C336.586 -1.02949 321.503 -5.86576 281.965 22.0466C240.692 51.1843 196.301 110.262 180 141.964Z"></path></svg></a><a class="group" href="https://github.com/bluesky-social"><span class="sr-only">Follow us on GitHub</span><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 dark:group-hover:fill-zinc-500"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"></path></svg></a></div></div></footer></div></div></div><script src="/_next/static/chunks/webpack-496a37b6c26ccd6a.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/786ca9324488b5df.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n5:I[4707,[],\"\"]\n7:I[6423,[],\"\"]\na:I[1060,[],\"\"]\n6:[\"locale\",\"en\",\"d\"]\nb:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"UWK7gfTTdjb8rbg3vyV6o\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"guides\",\"applications\"],\"initialTree\":[\"\",{\"children\":[[\"locale\",\"en\",\"d\"],{\"children\":[\"guides\",{\"children\":[\"applications\",{\"children\":[\"__PAGE__\",{}]}]}]},\"$undefined\",\"$undefined\",true]}],\"initialSeedData\":[\"\",{\"children\":[[\"locale\",\"en\",\"d\"],{\"children\":[\"guides\",{\"children\":[\"applications\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",\"$L4\",null],null],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\",\"guides\",\"children\",\"applications\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\",\"guides\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/786ca9324488b5df.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],\"$L8\"],null],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]],null],\"couldBeIntercepted\":false,\"initialHead\":[null,\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"c:I[2972,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"\"]\nd:I[5878,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"Image\"]\ne:I[1684,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"Heading\"]\nf:I[2510,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"Code\"]\n10:I[2510,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"Pre\"]\n28:I[5531,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"714\",\"static/chunks/app/%5Blocale%5D/guides/applications/page-7db3095cf4966e3b.js\"],\"FooterCTA\"]\n11:T5af,\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003egit\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003eclone\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003ehttps://github.com/bluesky-social/statusphere-e"])</script><script>self.__next_f.push([1,"xample-app.git\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ecd\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003estatusphere-example-app\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ecp\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003e.env.template\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003e.env\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003enpm\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003einstall\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003enpm\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003erun\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string)\"\u003edev\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e# Navigate to http://localhost:8080\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e12:Tbed,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e\u0026lt;!-- src/pages/login.ts --\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eaction\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;/login\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003emethod\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;post\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;login-form\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003einput\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003etype\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;text\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ename\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;handle\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eplaceholder\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;Enter your handle (eg alice.bsky.social)\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003erequired\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e /\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003etype\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;submit\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;Log in\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"13:Tef5,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Login handler\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.post\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/login\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Initiate the OAuth flow\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ehandle\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ereq\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ebody\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e?.handle\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eurl\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eoauthClient\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.authorize\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(handle\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e scope\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;atproto transition:generic\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.redirect\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eurl\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"14:T11d8,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// OAuth callback to complete session creation\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.get\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/oauth/callback\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Store the credentials\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e { \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003esession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eoauthClient\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.callback\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(params)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Attach the account DID to our user via a cookie\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecookieSession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003egetIronSession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecookieSession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.did \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003esession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.did\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecookieSession\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.save\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Send them back to the app\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.redirect\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"15:T95e,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003einterface\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eProfileRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e displayName\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e?:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estring\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// a human friendly name\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e description\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e?:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estring\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// a short bio\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e avatar\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e?:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eBlobRef\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// small profile picture\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e banner\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e?:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eBlobRef\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// banner image to put on profiles\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e?:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estring\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// declared time this profile data was added\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"16:T882,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erepo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.getRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The user\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;app.bsky.actor.profile\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The collection\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;self\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The record key\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"17:T1f1d,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Homepage\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.get\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// If the user is signed in, get an agent which communicates with their server\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003egetSessionAgent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ctx)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e!\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003eagent) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Serve the logged-out view\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.send\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003epage\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehome\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()))\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Fetch additional information about the logged-in user\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e { data: \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eprofileRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erepo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.getRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// our user\u0026#39;s repo\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;app.bsky.actor.profile\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// the bluesky profile record type\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;self\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// the record\u0026#39;s key\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Serve the logged-in view\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.send\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003epage\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehome\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({ profile\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eprofileRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.value \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e||\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {} })))\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"18:T1a13,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e\u0026lt;!-- pages/home.ts --\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;card\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ${profile\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ? html`\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eaction\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;/logout\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003emethod\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;post\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;session-form\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e Hi, \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003estrong\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;${profile.displayName || \u0026#39;friend\u0026#39;}\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003estrong\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;.\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e What\u0026#39;s your status today?\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003etype\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;submit\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;Log out\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;`\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e : html`\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;session-form\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehref\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;/login\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;Log in\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt; to set your status!\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehref\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;/login\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;button\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;Log in\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;`}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"19:T882,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erepo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.getRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The user\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;app.bsky.actor.profile\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The collection\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;self\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The record key\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1a:Tf71,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Generate a time-based key for our record\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eTID\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.nextStr\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Write the \u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erepo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.putRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The user\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The collection\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The record key\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e record\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e { \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// The record value\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;👍\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1b:T28e6,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// \u0026quot;Set status\u0026quot; handler\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.post\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// If the user is signed in, get an agent which communicates with their server\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003egetSessionAgent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ctx)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e!\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003eagent) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e401\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.send\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;\u0026lt;h1\u0026gt;Error: Session required\u0026lt;/h1\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Construct their status record\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e $type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ereq\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ebody\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e?.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003etry\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Write the status record to the user\u0026#39;s repository\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.putRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eTID\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.nextStr\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e record\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ecatch\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (err) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003elogger\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.warn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({ err }\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;failed to write record\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e500\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.send\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;\u0026lt;h1\u0026gt;Error: Failed to write record\u0026lt;/h1\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e200\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.json\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1c:Ta8f,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e\u0026lt;!-- src/pages/home.ts --\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eaction\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;/status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003emethod\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;post\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status-options\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ${STATUS_OPTIONS.map(status =\u0026gt; html`\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status-option\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ename\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003evalue\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;${status}\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ${status}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ebutton\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e `)}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003eform\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1d:T1c1c,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** lexicons/status.json **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e{\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;lexicon\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e1\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;id\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;xyz.statusphere.status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;defs\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;main\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;type\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;record\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;key\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;tid\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;record\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;type\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;object\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;required\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e [\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;createdAt\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e]\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;properties\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;type\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;string\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;minLength\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e1\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;maxGraphemes\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e1\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;maxLength\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e32\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;createdAt\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;type\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;string\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026quot;format\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;datetime\u0026quot;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1e:T166f,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/lexicon/types/xyz/statusphere/status.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eexport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003einterface\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estring\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estring\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e [k: string]\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eunknown\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eexport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003efunction\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eisRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(v\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eunknown\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e v \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eis\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eisObj\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(v) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehasProp\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(v\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;$type\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ev\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.$type \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status#main\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e||\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ev\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.$type \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e )\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eexport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003efunction\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003evalidateRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(v\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eunknown\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eValidationResult\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003elexicons\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.validate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status#main\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e v)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"1f:T1611,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eimport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e*\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eas\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e Status \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003efrom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;#/lexicon/types/xyz/statusphere/status\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// \u0026quot;Set status\u0026quot; handler\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.post\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Construct \u0026amp; validate their status record\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e $type\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ereq\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ebody\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e?.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e!\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eStatus\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.validateRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(record).success) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e400\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.json\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({ error\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;Invalid status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"20:T17ed,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/ingester.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eimport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e { Firehose } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003efrom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;@atproto/sync\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eimport\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e*\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eas\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e Status \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003efrom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;#/lexicon/types/xyz/statusphere/status\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eFirehose\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e filterCollections\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e [\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e]\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandleEvent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (evt) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Watch for write events\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.event \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;create\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e||\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.event \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;update\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.record\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// If the write is a valid status update\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.collection \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eStatus\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.isRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(record) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eStatus\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.validateRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(record).success\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e ) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Store the status\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// TODO\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"21:T14cb,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/db.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Create our statuses table\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003edb\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.schema\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.createTable\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.addColumn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;uri\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;varchar\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (col) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecol\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.primaryKey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.addColumn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;authorDid\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;varchar\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (col) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecol\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.notNull\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.addColumn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;varchar\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (col) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecol\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.notNull\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.addColumn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;createdAt\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;varchar\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (col) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecol\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.notNull\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.addColumn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;indexedAt\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;varchar\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (col) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecol\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.notNull\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e())\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.execute\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"22:T1a95,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/ingester.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// If the write is a valid status update\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eif\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.collection \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e===\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eStatus\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.isRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(record) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eStatus\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.validateRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(record).success\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Store the status in our SQLite\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e db\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.insertInto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.values\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e uri\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003euri\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e authorDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eevt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.author\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e indexedAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.onConflict\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e((oc) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eoc\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.column\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;uri\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.doUpdateSet\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e indexedAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e )\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.execute\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"23:T1454,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Homepage\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.get\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Fetch data stored in our SQLite\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estatuses\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e db\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.selectFrom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.selectAll\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.orderBy\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;indexedAt\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;desc\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.limit\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e10\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.execute\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Map user DIDs to their domain-name handles\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003edidHandleMap\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eresolver\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.resolveDidsToHandles\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003estatuses\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.map\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e((s) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003es\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.authorDid)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e )\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"24:Tf1d,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e\u0026lt;!-- src/pages/home.ts --\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e${statuses.map((status, i) =\u0026gt; {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e const handle = didHandleMap[status.authorDid] || status.authorDid\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e return html`\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status-line\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;status\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;${status.status}\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;desc\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eclass\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;author\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehref\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026quot;https://bsky.app/profile/${handle}\u0026quot;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;@${handle}\u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ea\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e was feeling ${status.status} on ${status.indexedAt}.\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u0026lt;/\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003ediv\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e `\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e})}\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"25:{\"src\":\"/_next/static/media/diagram-info-flow.ccf81d0b.png\",\"height\":140,\"width\":448,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAAAAACLoxGUAAAAI0lEQVR42gVAgQkAIAjb/8cWpG4JKoKSXlANPz5jl+hPC2UtOF8XJTZX+H8AAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":3}\n26:T43b,/** src/routes.ts **/\n// \"Set status\" handler\nrouter.post(\n '/status',\n handler(async (req, res) =\u003e {\n // ...\n\n let uri\n try {\n // Write the status record to the user's repository\n const res = await agent.com.atproto.repo.putRecord({\n repo: agent.assertDid, \n collection: 'xyz.statusphere.status',\n rkey: TID.nextStr(),\n record,\n })\n uri = res.uri\n } catch (err) {\n logger.warn({ err }, 'failed to write record')\n return res.status(500).json({ error: 'Failed to write record' })\n }\n\n try {\n // Optimistically update our SQLite \u003c-- HERE!\n await db\n .insertInto('status')\n .values({\n uri,\n authorDid: agent.assertDid, \n status: record.status,\n createdAt: record.createdAt,\n indexedAt: new Date().toISOString(),\n })\n .execute()\n } catch (err) {\n logger.warn(\n { err },\n 'failed to update computed view; ignoring as it should be caught by the firehose'\n )\n }\n\n res.status(200).json({})\n })\n)\n27:T2b42,"])</script><script>self.__next_f.push([1,"\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e/** src/routes.ts **/\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// \u0026quot;Set status\u0026quot; handler\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erouter\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.post\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;/status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003ehandler\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003easync\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (req\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e res) \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u0026gt;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// ...\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003elet\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e uri\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003etry\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Write the status record to the user\u0026#39;s repository\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003econst\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003ecom\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eatproto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erepo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.putRecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e repo\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e collection\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;xyz.statusphere.status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e rkey\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eTID\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.nextStr\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e record\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e uri \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e=\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.uri\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ecatch\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (err) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003elogger\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.warn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({ err }\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;failed to write record\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ereturn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e500\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.json\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({ error\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;Failed to write record\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003etry\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-comment)\"\u003e// Optimistically update our SQLite \u0026lt;-- HERE!\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003eawait\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e db\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.insertInto\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;status\u0026#39;\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.values\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e uri\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e authorDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eagent\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.assertDid\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003erecord\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e.createdAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e indexedAt\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003e:\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003enew\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003eDate\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.toISOString\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.execute\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e()\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e } \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-keyword)\"\u003ecatch\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e (err) {\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003elogger\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.warn\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e { err }\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-punctuation)\"\u003e,\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-string-expression)\"\u003e\u0026#39;failed to update computed view; ignoring as it should be caught by the firehose\u0026#39;\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e )\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e }\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e \u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003eres\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.status\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e(\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-constant)\"\u003e200\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003cspan style=\"color: var(--shiki-token-function)\"\u003e.json\u003c/span\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e({})\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e })\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003cspan style=\"color: var(--shiki-color-text)\"\u003e)\u003c/span\u003e\u003c/span\u003e\n\u003cspan\u003e\u003c/span\u003e"])</script><script>self.__next_f.push([1,"4:[\"$\",\"article\",null,{\"className\":\"flex h-full flex-col pb-24 pt-16\",\"children\":[\"$\",\"div\",null,{\"className\":\"flex-auto prose dark:prose-invert [html_:where(\u0026\u003e*)]:mx-auto [html_:where(\u0026\u003e*)]:max-w-2xl [html_:where(\u0026\u003e*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(\u0026\u003e*)]:lg:max-w-3xl\",\"children\":[[\"$\",\"h1\",null,{\"children\":\"Quick start guide to building applications on AT Protocol\"}],\"\\n\",[\"$\",\"$Lc\",null,{\"href\":\"https://github.com/bluesky-social/statusphere-example-app\",\"className\":\"not-prose flex items-center gap-2 bg-blue-100 dark:bg-blue-950 dark:text-white px-4 py-3 text-base rounded-lg hover:underline\",\"children\":[[\"$\",\"svg\",null,{\"viewBox\":\"0 0 20 20\",\"aria-hidden\":\"true\",\"className\":\"h-6 dark:text-white\",\"children\":[\"$\",\"path\",null,{\"fill\":\"currentColor\",\"fillRule\":\"evenodd\",\"clipRule\":\"evenodd\",\"d\":\"M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z\"}]}],[\"$\",\"span\",null,{\"children\":[\"$\",\"p\",null,{\"children\":\"Find the source code for the example application on GitHub.\"}]}]]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"In this guide, we're going to build a simple multi-user app that publishes your current \\\"status\\\" as an emoji. Our application will look like this:\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A screenshot of our example application\",\"src\":{\"src\":\"/_next/static/media/app-screenshot.1d8d0740.png\",\"height\":530,\"width\":841,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAWklEQVR42kWMWQ6AIAxEvf85wYiJGspSamkRxMSvyWxvaaqtNeectasxFgC6VdXlFSGigrlygUAiYzoKYQrnBtce/VGSV9WvYK4pxox4s3T7o2pHIDLzjObjAVtSdNrJXMSMAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":5},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"We will cover how to:\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":\"Signin via OAuth\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Fetch information about users (profiles)\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Listen to the network firehose for new data\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Publish data on the user's account using a custom schema\",\"className\":\"lead\"}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"We're going to keep this light so you can quickly wrap your head around ATProto. There will be links with more information about each step.\",\"className\":\"lead\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"introduction\",\"children\":\"Introduction\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Data in the Atmosphere is stored on users' personal repos. It's almost like each user has their own website. Our goal is to aggregate data from the users into our SQLite DB.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Think of our app like a Google. If Google's job was to say which emoji each website had under \",[\"$\",\"$Lf\",null,{\"children\":\"/status.json\"}],\", then it would show something like:\"]}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"$Lf\",null,{\"children\":\"nytimes.com\"}],\" is feeling 📰 according to \",[\"$\",\"$Lf\",null,{\"children\":\"https://nytimes.com/status.json\"}]]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"$Lf\",null,{\"children\":\"bsky.app\"}],\" is feeling 🦋 according to \",[\"$\",\"$Lf\",null,{\"children\":\"https://bsky.app/status.json\"}]]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"$Lf\",null,{\"children\":\"reddit.com\"}],\" is feeling 🤓 according to \",[\"$\",\"$Lf\",null,{\"children\":\"https://reddit.com/status.json\"}]]}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"The Atmosphere works the same way, except we're going to check \",[\"$\",\"$Lf\",null,{\"children\":\"at://\"}],\" instead of \",[\"$\",\"$Lf\",null,{\"children\":\"https://\"}],\". Each user has a data repo under an \",[\"$\",\"$Lf\",null,{\"children\":\"at://\"}],\" URL. We'll crawl all the user data repos in the Atmosphere for all the \\\"status.json\\\" records and aggregate them into our SQLite database.\"]}],\"\\n\",[\"$\",\"blockquote\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"children\":[[\"$\",\"$Lf\",null,{\"children\":\"at://\"}],\" is the URL scheme of the AT Protocol. Under the hood it uses common tech like HTTP and DNS, but it adds all of the features we'll be using in this tutorial.\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-1-starting-with-our-express-js-app\",\"children\":\"Step 1. Starting with our ExpressJS app\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Start by cloning the repo and installing packages.\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"bash\",\"code\":\"git clone https://github.com/bluesky-social/statusphere-example-app.git\\ncd statusphere-example-app\\ncp .env.template .env\\nnpm install\\nnpm run dev\\n# Navigate to http://localhost:8080\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-bash\",\"children\":\"$11\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Our repo is a regular Web app. We're rendering our HTML server-side like it's 1999. We also have a SQLite database that we're managing with \",[\"$\",\"$Lc\",null,{\"href\":\"https://kysely.dev/\",\"children\":\"Kysely\"}],\".\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Our starting stack:\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":\"Typescript\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"NodeJS web server (\",[\"$\",\"$Lc\",null,{\"href\":\"https://expressjs.com/\",\"children\":\"express\"}],\")\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"SQLite database (\",[\"$\",\"$Lc\",null,{\"href\":\"https://kysely.dev/\",\"children\":\"Kysely\"}],\")\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"Server-side rendering (\",[\"$\",\"$Lc\",null,{\"href\":\"https://www.npmjs.com/package/uhtml\",\"children\":\"uhtml\"}],\")\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"With each step we'll explain how our Web app taps into the Atmosphere. Refer to the codebase for more detailed code — again, this tutorial is going to keep it light and quick to digest.\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-2-signing-in-with-o-auth\",\"children\":\"Step 2. Signing in with OAuth\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"When somebody logs into our app, they'll give us read \u0026 write access to their personal \",[\"$\",\"$Lf\",null,{\"children\":\"at://\"}],\" repo. We'll use that to write the status json record.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"We're going to accomplish this using OAuth (\",[\"$\",\"$Lc\",null,{\"href\":\"https://github.com/bluesky-social/proposals/tree/main/0004-oauth\",\"children\":\"spec\"}],\"). Most of the OAuth flows are going to be handled for us using the \",[\"$\",\"$Lc\",null,{\"href\":\"https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node\",\"children\":\"@atproto/oauth-client-node\"}],\" library. This is the arrangement we're aiming toward:\"]}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of the OAuth elements\",\"src\":{\"src\":\"/_next/static/media/diagram-oauth.5ebec062.png\",\"height\":274,\"width\":655,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAAAAACLoxGUAAAAIUlEQVR42gVAgQ0AIAji/3crFJfiwBv60418wRKFPGXbXjsbF2s9WONlAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":3},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"When the user logs in, the OAuth client will create a new session with their repo server and give us read/write access along with basic user info.\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A screenshot of the login UI\",\"src\":{\"src\":\"/_next/static/media/app-login.83cd693f.png\",\"height\":122,\"width\":482,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAMUlEQVR42h3IMQ4AIAgEQf7/UQsLBa6QAKJhqs2Su5vZab+rcnGMGXSbCniLqgLIPg+M6C6L1YNtTAAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":2},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Our login page just asks the user for their \\\"handle,\\\" which is the domain name associated with their account. For \",[\"$\",\"$Lc\",null,{\"href\":\"https://bsky.app\",\"children\":\"Bluesky\"}],\" users, these tend to look like \",[\"$\",\"$Lf\",null,{\"children\":\"alice.bsky.social\"}],\", but they can be any kind of domain (eg \",[\"$\",\"$Lf\",null,{\"children\":\"alice.com\"}],\").\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"html\",\"code\":\"\u003c!-- src/pages/login.ts --\u003e\\n\u003cform action=\\\"/login\\\" method=\\\"post\\\" class=\\\"login-form\\\"\u003e\\n \u003cinput\\n type=\\\"text\\\"\\n name=\\\"handle\\\"\\n placeholder=\\\"Enter your handle (eg alice.bsky.social)\\\"\\n required\\n /\u003e\\n \u003cbutton type=\\\"submit\\\"\u003eLog in\u003c/button\u003e\\n\u003c/form\u003e\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-html\",\"children\":\"$12\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"When they submit the form, we tell our OAuth client to initiate the authorization flow and then redirect the user to their server to complete the process.\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\n// Login handler\\nrouter.post(\\n '/login',\\n handler(async (req, res) =\u003e {\\n // Initiate the OAuth flow\\n const handle = req.body?.handle\\n const url = await oauthClient.authorize(handle, {\\n scope: 'atproto transition:generic',\\n })\\n return res.redirect(url.toString())\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$13\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"This is the same kind of SSO flow that Google or GitHub uses. The user will be asked for their password, then asked to confirm the session with your application.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"When that finishes, the user will be sent back to \",[\"$\",\"$Lf\",null,{\"children\":\"/oauth/callback\"}],\" on our Web app. The OAuth client will store the access tokens for the user's server, and then we attach their account's \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/did\",\"children\":\"DID\"}],\" to the cookie-session.\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\n// OAuth callback to complete session creation\\nrouter.get(\\n '/oauth/callback',\\n handler(async (req, res) =\u003e {\\n // Store the credentials\\n const { session } = await oauthClient.callback(params)\\n\\n // Attach the account DID to our user via a cookie\\n const cookieSession = await getIronSession(req, res)\\n cookieSession.did = session.did\\n await cookieSession.save()\\n\\n // Send them back to the app\\n return res.redirect('/')\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$14\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"With that, we're in business! We now have a session with the user's repo server and can use that to access their data.\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-3-fetching-the-users-profile\",\"children\":\"Step 3. Fetching the user's profile\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Why don't we learn something about our user? In \",[\"$\",\"$Lc\",null,{\"href\":\"https://bsky.app\",\"children\":\"Bluesky\"}],\", users publish a \\\"profile\\\" record which looks like this:\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"interface ProfileRecord {\\n displayName?: string // a human friendly name\\n description?: string // a short bio\\n avatar?: BlobRef // small profile picture\\n banner?: BlobRef // banner image to put on profiles\\n createdAt?: string // declared time this profile data was added\\n // ...\\n}\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$15\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"You can examine this record directly using \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto-browser.vercel.app\",\"children\":\"atproto-browser.vercel.app\"}],\". For instance, \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto-browser.vercel.app/at?u=at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.actor.profile/self\",\"children\":\"this is the profile record for @bsky.app\"}],\".\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"We're going to use the \",[\"$\",\"$Lc\",null,{\"href\":\"https://github.com/bluesky-social/atproto/tree/main/packages/api\",\"children\":\"Agent\"}],\" associated with the user's OAuth session to fetch this record.\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"await agent.com.atproto.repo.getRecord({\\n repo: agent.assertDid, // The user\\n collection: 'app.bsky.actor.profile', // The collection\\n rkey: 'self', // The record key\\n})\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$16\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"When asking for a record, we provide three pieces of information.\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"repo\"}],\" The \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/did\",\"children\":\"DID\"}],\" which identifies the user,\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"collection\"}],\" The collection name, and\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[[\"$\",\"strong\",null,{\"children\":\"rkey\"}],\" The record key\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"We'll explain the collection name shortly. Record keys are strings with \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/record-key#record-key-syntax\",\"children\":\"some restrictions\"}],\" and a couple of common patterns. The \",[\"$\",\"$Lf\",null,{\"children\":\"\\\"self\\\"\"}],\" pattern is used when a collection is expected to only contain one record which describes the user.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Let's update our homepage to fetch this profile record:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\n// Homepage\\nrouter.get(\\n '/',\\n handler(async (req, res) =\u003e {\\n // If the user is signed in, get an agent which communicates with their server\\n const agent = await getSessionAgent(req, res, ctx)\\n\\n if (!agent) {\\n // Serve the logged-out view\\n return res.type('html').send(page(home()))\\n }\\n\\n // Fetch additional information about the logged-in user\\n const { data: profileRecord } = await agent.com.atproto.repo.getRecord({\\n repo: agent.assertDid, // our user's repo\\n collection: 'app.bsky.actor.profile', // the bluesky profile record type\\n rkey: 'self', // the record's key\\n })\\n\\n // Serve the logged-in view\\n return res\\n .type('html')\\n .send(page(home({ profile: profileRecord.value || {} })))\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$17\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"With that data, we can give a nice personalized welcome banner for our user:\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A screenshot of the banner image\",\"src\":{\"src\":\"/_next/static/media/app-banner.1e92c654.png\",\"height\":51,\"width\":442,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAYAAADjAO9DAAAAJUlEQVR4nGP8/uPH/////jEwMjIygAA7OzuY/eXbf4bbD/8yAADllQygRQbOBwAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":1},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"html\",\"code\":\"\u003c!-- pages/home.ts --\u003e\\n\u003cdiv class=\\\"card\\\"\u003e\\n ${profile\\n ? html`\u003cform action=\\\"/logout\\\" method=\\\"post\\\" class=\\\"session-form\\\"\u003e\\n \u003cdiv\u003e\\n Hi, \u003cstrong\u003e${profile.displayName || 'friend'}\u003c/strong\u003e.\\n What's your status today?\\n \u003c/div\u003e\\n \u003cdiv\u003e\\n \u003cbutton type=\\\"submit\\\"\u003eLog out\u003c/button\u003e\\n \u003c/div\u003e\\n \u003c/form\u003e`\\n : html`\u003cdiv class=\\\"session-form\\\"\u003e\\n \u003cdiv\u003e\u003ca href=\\\"/login\\\"\u003eLog in\u003c/a\u003e to set your status!\u003c/div\u003e\\n \u003cdiv\u003e\\n \u003ca href=\\\"/login\\\" class=\\\"button\\\"\u003eLog in\u003c/a\u003e\\n \u003c/div\u003e\\n \u003c/div\u003e`}\\n\u003c/div\u003e\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-html\",\"children\":\"$18\"}]}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-4-reading-and-writing-records\",\"children\":\"Step 4. Reading \u0026 writing records\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"You can think of the user repositories as collections of JSON records:\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of a repository\",\"src\":{\"src\":\"/_next/static/media/diagram-repo.4a34005b.png\",\"height\":417,\"width\":719,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAYElEQVR42h2NWw7DIAwEuf9JWzXEYMBe84gLGe3faDXB3WGamIjvKsW6+UswoIlSbrk0gcLQ+xxzBvenCj6/HKnEVCPx98pcZQtX9Osu+5F4rxJLUxyx1gL0xIAxhh+ePzeMc8P4iggvAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":5},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Let's look again at how we read the \\\"profile\\\" record:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"await agent.com.atproto.repo.getRecord({\\n repo: agent.assertDid, // The user\\n collection: 'app.bsky.actor.profile', // The collection\\n rkey: 'self', // The record key\\n})\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$19\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"We write records using a similar API. Since our goal is to write \\\"status\\\" records, let's look at how that will happen:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"// Generate a time-based key for our record\\nconst rkey = TID.nextStr()\\n\\n// Write the \\nawait agent.com.atproto.repo.putRecord({\\n repo: agent.assertDid, // The user\\n collection: 'xyz.statusphere.status', // The collection\\n rkey, // The record key\\n record: { // The record value\\n status: \\\"👍\\\",\\n createdAt: new Date().toISOString()\\n }\\n})\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$1a\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Our \",[\"$\",\"$Lf\",null,{\"children\":\"POST /status\"}],\" route is going to use this API to publish the user's status to their repo.\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\n// \\\"Set status\\\" handler\\nrouter.post(\\n '/status',\\n handler(async (req, res) =\u003e {\\n // If the user is signed in, get an agent which communicates with their server\\n const agent = await getSessionAgent(req, res, ctx)\\n if (!agent) {\\n return res.status(401).type('html').send('\u003ch1\u003eError: Session required\u003c/h1\u003e')\\n }\\n\\n // Construct their status record\\n const record = {\\n $type: 'xyz.statusphere.status',\\n status: req.body?.status,\\n createdAt: new Date().toISOString(),\\n }\\n\\n try {\\n // Write the status record to the user's repository\\n await agent.com.atproto.putRecord({\\n repo: agent.assertDid, \\n collection: 'xyz.statusphere.status',\\n rkey: TID.nextStr(),\\n record,\\n })\\n } catch (err) {\\n logger.warn({ err }, 'failed to write record')\\n return res.status(500).type('html').send('\u003ch1\u003eError: Failed to write record\u003c/h1\u003e')\\n }\\n\\n res.status(200).json({})\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$1b\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now in our homepage we can list out the status buttons:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"html\",\"code\":\"\u003c!-- src/pages/home.ts --\u003e\\n\u003cform action=\\\"/status\\\" method=\\\"post\\\" class=\\\"status-options\\\"\u003e\\n ${STATUS_OPTIONS.map(status =\u003e html`\\n \u003cbutton class=\\\"status-option\\\" name=\\\"status\\\" value=\\\"${status}\\\"\u003e\\n ${status}\\n \u003c/button\u003e\\n `)}\\n\u003c/form\u003e\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-html\",\"children\":\"$1c\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"And here we are!\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A screenshot of the app's status options\",\"src\":{\"src\":\"/_next/static/media/app-status-options.6c0bfc19.png\",\"height\":157,\"width\":467,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAIAAAAhqtkfAAAARElEQVR42gVACw6AIAj1/vesNAVRXv7WxLm/E0qRrAt+Ii6ET3zX5IY84FtzAF1aWHOaGntrrkmtQpReYg4EqcPM9rYD8JJEAj3zIj8AAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":3},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-5-creating-a-custom-status-schema\",\"children\":\"Step 5. Creating a custom \\\"status\\\" schema\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Repo collections are typed, meaning that they have a defined schema. The \",[\"$\",\"$Lf\",null,{\"children\":\"app.bsky.actor.profile\"}],\" type definition \",[\"$\",\"$Lc\",null,{\"href\":\"https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/profile.json\",\"children\":\"can be found here\"}],\".\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Anybody can create a new schema using the \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/lexicon\",\"children\":\"Lexicon\"}],\" language, which is very similar to \",[\"$\",\"$Lc\",null,{\"href\":\"http://json-schema.org/\",\"children\":\"JSON-Schema\"}],\". The schemas use \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/nsid\",\"children\":\"reverse-DNS IDs\"}],\" which indicate ownership. In this demo app we're going to use \",[\"$\",\"$Lf\",null,{\"children\":\"xyz.statusphere\"}],\" which we registered specifically for this project (aka statusphere.xyz).\"]}],\"\\n\",[\"$\",\"blockquote\",null,{\"children\":[\"\\n\",[\"$\",\"h3\",null,{\"children\":\"Why create a schema?\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Schemas help other applications understand the data your app is creating. By publishing your schemas, you make it easier for other application authors to publish data in a format your app will recognize and handle.\"}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Let's create our schema in the \",[\"$\",\"$Lf\",null,{\"children\":\"/lexicons\"}],\" folder of our codebase. You can \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/guides/lexicon\",\"children\":\"read more about how to define schemas here\"}],\".\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"json\",\"code\":\"/** lexicons/status.json **/\\n{\\n \\\"lexicon\\\": 1,\\n \\\"id\\\": \\\"xyz.statusphere.status\\\",\\n \\\"defs\\\": {\\n \\\"main\\\": {\\n \\\"type\\\": \\\"record\\\",\\n \\\"key\\\": \\\"tid\\\",\\n \\\"record\\\": {\\n \\\"type\\\": \\\"object\\\",\\n \\\"required\\\": [\\\"status\\\", \\\"createdAt\\\"],\\n \\\"properties\\\": {\\n \\\"status\\\": {\\n \\\"type\\\": \\\"string\\\",\\n \\\"minLength\\\": 1,\\n \\\"maxGraphemes\\\": 1,\\n \\\"maxLength\\\": 32\\n },\\n \\\"createdAt\\\": {\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"datetime\\\"\\n }\\n }\\n }\\n }\\n }\\n}\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-json\",\"children\":\"$1d\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now let's run some code-generation using our schema:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"bash\",\"code\":\"./node_modules/.bin/lex gen-server ./src/lexicon ./lexicons/*\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-bash\",\"children\":\"\u003cspan\u003e\u003cspan style=\\\"color: var(--shiki-token-function)\\\"\u003e./node_modules/.bin/lex\u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-color-text)\\\"\u003e \u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-token-string)\\\"\u003egen-server\u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-color-text)\\\"\u003e \u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-token-string)\\\"\u003e./src/lexicon\u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-color-text)\\\"\u003e \u003c/span\u003e\u003cspan style=\\\"color: var(--shiki-token-string)\\\"\u003e./lexicons/*\u003c/span\u003e\u003c/span\u003e\\n\u003cspan\u003e\u003c/span\u003e\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"This will produce Typescript interfaces as well as runtime validation functions that we can use in our app. Here's what that generated code looks like:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/lexicon/types/xyz/statusphere/status.ts **/\\nexport interface Record {\\n status: string\\n createdAt: string\\n [k: string]: unknown\\n}\\n\\nexport function isRecord(v: unknown): v is Record {\\n return (\\n isObj(v) \u0026\u0026\\n hasProp(v, '$type') \u0026\u0026\\n (v.$type === 'xyz.statusphere.status#main' || v.$type === 'xyz.statusphere.status')\\n )\\n}\\n\\nexport function validateRecord(v: unknown): ValidationResult {\\n return lexicons.validate('xyz.statusphere.status#main', v)\\n}\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$1e\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Let's use that code to improve the \",[\"$\",\"$Lf\",null,{\"children\":\"POST /status\"}],\" route:\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\nimport * as Status from '#/lexicon/types/xyz/statusphere/status'\\n// ...\\n// \\\"Set status\\\" handler\\nrouter.post(\\n '/status',\\n handler(async (req, res) =\u003e {\\n // ...\\n\\n // Construct \u0026 validate their status record\\n const record = {\\n $type: 'xyz.statusphere.status',\\n status: req.body?.status,\\n createdAt: new Date().toISOString(),\\n }\\n if (!Status.validateRecord(record).success) {\\n return res.status(400).json({ error: 'Invalid status' })\\n }\\n\\n // ...\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$1f\"}]}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-6-listening-to-the-firehose\",\"children\":\"Step 6. Listening to the firehose\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"So far, we have:\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":\"Logged in via OAuth\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Created a custom schema\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Read \u0026 written records for the logged in user\"}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now we want to fetch the status records from other users.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Remember how we referred to our app as being like Google, crawling around the repos to get their records? One advantage we have in the AT Protocol is that each repo publishes an event log of their updates.\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of the event stream\",\"src\":{\"src\":\"/_next/static/media/diagram-event-stream.aa119d83.png\",\"height\":283,\"width\":499,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAAAAABd+vKJAAAALElEQVR42h3BiQ0AIAgDwO4/rXwWNFGJd4i4H6YzWkLDRnO4m6jowq4imecBZRYmgyv3TRUAAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":5},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Using a \",[\"$\",\"$Lc\",null,{\"href\":\"https://docs.bsky.app/docs/advanced-guides/federation-architecture#relay\",\"children\":\"Relay service\"}],\" we can listen to an aggregated firehose of these events across all users in the network. In our case what we're looking for are valid \",[\"$\",\"$Lf\",null,{\"children\":\"xyz.statusphere.status\"}],\" records.\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/ingester.ts **/\\nimport { Firehose } from '@atproto/sync'\\nimport * as Status from '#/lexicon/types/xyz/statusphere/status'\\n// ...\\n\\nnew Firehose({\\n filterCollections: ['xyz.statusphere.status'],\\n handleEvent: async (evt) =\u003e {\\n // Watch for write events\\n if (evt.event === 'create' || evt.event === 'update') {\\n const record = evt.record\\n\\n // If the write is a valid status update\\n if (\\n evt.collection === 'xyz.statusphere.status' \u0026\u0026\\n Status.isRecord(record) \u0026\u0026\\n Status.validateRecord(record).success\\n ) {\\n // Store the status\\n // TODO\\n }\\n }\\n },\\n})\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$20\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Let's create a SQLite table to store these statuses:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/db.ts **/\\n// Create our statuses table\\nawait db.schema\\n .createTable('status')\\n .addColumn('uri', 'varchar', (col) =\u003e col.primaryKey())\\n .addColumn('authorDid', 'varchar', (col) =\u003e col.notNull())\\n .addColumn('status', 'varchar', (col) =\u003e col.notNull())\\n .addColumn('createdAt', 'varchar', (col) =\u003e col.notNull())\\n .addColumn('indexedAt', 'varchar', (col) =\u003e col.notNull())\\n .execute()\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$21\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now we can write these statuses into our database as they arrive from the firehose:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/ingester.ts **/\\n// If the write is a valid status update\\nif (\\n evt.collection === 'xyz.statusphere.status' \u0026\u0026\\n Status.isRecord(record) \u0026\u0026\\n Status.validateRecord(record).success\\n) {\\n // Store the status in our SQLite\\n await db\\n .insertInto('status')\\n .values({\\n uri: evt.uri.toString(),\\n authorDid: evt.author,\\n status: record.status,\\n createdAt: record.createdAt,\\n indexedAt: new Date().toISOString(),\\n })\\n .onConflict((oc) =\u003e\\n oc.column('uri').doUpdateSet({\\n status: record.status,\\n indexedAt: new Date().toISOString(),\\n })\\n )\\n .execute()\\n}\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$22\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"You can almost think of information flowing in a loop:\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of the flow of information\",\"src\":{\"src\":\"/_next/static/media/diagram-info-flow.ccf81d0b.png\",\"height\":140,\"width\":448,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAAAAACLoxGUAAAAI0lEQVR42gVAgQkAIAjb/8cWpG4JKoKSXlANPz5jl+hPC2UtOF8XJTZX+H8AAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":3},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Applications write to the repo. The write events are then emitted on the firehose where they're caught by the apps and ingested into their databases.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Why sync from the event log like this? Because there are other apps in the network that will write the records we're interested in. By subscribing to the event log, we ensure that we catch all the data we're interested in — including data published by other apps!\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-7-listing-the-latest-statuses\",\"children\":\"Step 7. Listing the latest statuses\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Now that we have statuses populating our SQLite, we can produce a timeline of status updates by users. We also use a \",[\"$\",\"$Lc\",null,{\"href\":\"https://atproto.com/specs/did\",\"children\":\"DID\"}],\"-to-handle resolver so we can show a nice username with the statuses:\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"/** src/routes.ts **/\\n// Homepage\\nrouter.get(\\n '/',\\n handler(async (req, res) =\u003e {\\n // ...\\n\\n // Fetch data stored in our SQLite\\n const statuses = await db\\n .selectFrom('status')\\n .selectAll()\\n .orderBy('indexedAt', 'desc')\\n .limit(10)\\n .execute()\\n\\n // Map user DIDs to their domain-name handles\\n const didHandleMap = await resolver.resolveDidsToHandles(\\n statuses.map((s) =\u003e s.authorDid)\\n )\\n\\n // ...\\n })\\n)\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$23\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Our HTML can now list these status records:\"}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"html\",\"code\":\"\u003c!-- src/pages/home.ts --\u003e\\n${statuses.map((status, i) =\u003e {\\n const handle = didHandleMap[status.authorDid] || status.authorDid\\n return html`\\n \u003cdiv class=\\\"status-line\\\"\u003e\\n \u003cdiv\u003e\\n \u003cdiv class=\\\"status\\\"\u003e${status.status}\u003c/div\u003e\\n \u003c/div\u003e\\n \u003cdiv class=\\\"desc\\\"\u003e\\n \u003ca class=\\\"author\\\" href=\\\"https://bsky.app/profile/${handle}\\\"\u003e@${handle}\u003c/a\u003e\\n was feeling ${status.status} on ${status.indexedAt}.\\n \u003c/div\u003e\\n \u003c/div\u003e\\n `\\n})}\\n\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-html\",\"children\":\"$24\"}]}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A screenshot of the app status timeline\",\"src\":{\"src\":\"/_next/static/media/app-status-history.25e5d14a.png\",\"height\":166,\"width\":255,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAWElEQVR42jXL0QrAIAxDUf//S4UNt2mT1lZhTtghb+EmtnqXIiIAdCEJREQyCoG+uYdvc8501J4vUBpJMxUaaN8xHK7N/iK8r40xU+DUJzchALXVoKt4xAs6xHPFBuRLkgAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":5},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"step-8-optimistic-updates\",\"children\":\"Step 8. Optimistic updates\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"As a final optimization, let's introduce \\\"optimistic updates.\\\"\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Remember the information flow loop with the repo write and the event log?\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of the flow of information\",\"src\":\"$25\",\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Since we're updating our users' repos locally, we can short-circuit that flow to our own database:\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram illustrating optimistic updates\",\"src\":{\"src\":\"/_next/static/media/diagram-optimistic-update.ca3f4cf1.png\",\"height\":154,\"width\":441,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAIAAAAhqtkfAAAAPklEQVR42h2KyQ3AMAjAsv+yiHBDBEX1z5ZPd0cEM6uqWWTm6swcYgaArR4v8qkKIprZ2UVEiOhekh93r6oPMwVFdA3mjCwAAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":3},\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"This is an important optimization to make, because it ensures that the user sees their own changes while using your app. When the event eventually arrives from the firehose, we just discard it since we already have it saved locally.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"To do this, we just update \",[\"$\",\"$Lf\",null,{\"children\":\"POST /status\"}],\" to include an additional write to our SQLite DB:\"]}],\"\\n\",[\"$\",\"$L10\",null,{\"language\":\"typescript\",\"code\":\"$26\",\"children\":[\"$\",\"$Lf\",null,{\"className\":\"language-typescript\",\"children\":\"$27\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"You'll notice this code looks almost exactly like what we're doing in \",[\"$\",\"$Lf\",null,{\"children\":\"firehose.ts\"}],\".\"]}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"thinking-in-at-proto\",\"children\":\"Thinking in AT Proto\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"In this tutorial we've covered the key steps to building an atproto app. Data is published in its canonical form on users' \",[\"$\",\"$Lf\",null,{\"children\":\"at://\"}],\" repos and then aggregated into apps' databases to produce views of the network.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"When building your app, think in these four key steps:\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":[\"Design the \",[\"$\",\"$Lc\",null,{\"href\":\"#\",\"children\":\"Lexicon\"}],\" schemas for the records you'll publish into the Atmosphere.\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Create a database for aggregating the records into useful views.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Build your application to write the records on your users' repos.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Listen to the firehose to aggregate data across the network.\"}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Remember this flow of information throughout:\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"alt\":\"A diagram of the flow of information\",\"src\":\"$25\",\"className\":\"max-w-full\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"This is how every app in the Atmosphere works, including the \",[\"$\",\"$Lc\",null,{\"href\":\"https://bsky.app\",\"children\":\"Bluesky social app\"}],\".\"]}],\"\\n\",[\"$\",\"$Le\",null,{\"level\":2,\"id\":\"next-steps\",\"children\":\"Next steps\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"If you want to practice what you've learned, here are some additional challenges you could try:\"}],\"\\n\",[\"$\",\"ul\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":\"Sync the profile records of all users so that you can show their display names instead of their handles.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Count the number of each status used and display the total counts.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"Fetch the authed user's \",[\"$\",\"$Lf\",null,{\"children\":\"app.bsky.graph.follow\"}],\" follows and show statuses from them.\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Create a different kind of schema, like a way to post links to websites and rate them 1 through 4 stars.\"}],\"\\n\"]}],\"\\n\",[\"$\",\"$L28\",null,{\"href\":\"/\",\"title\":\"Ready to learn more?\",\"description\":\"Specs, guides, and SDKs can be found here.\"}]]}]}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"Quick start guide to building applications on AT Protocol - AT Protocol\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"In this guide, we're going to build a simple multi-user app that publishes your current \\\"status\\\" as an emoji.\"}],[\"$\",\"meta\",\"4\",{\"property\":\"og:title\",\"content\":\"Quick start guide to building applications on AT Protocol - AT Protocol\"}],[\"$\",\"meta\",\"5\",{\"property\":\"og:description\",\"content\":\"In this guide, we're going to build a simple multi-user app that publishes your current \\\"status\\\" as an emoji.\"}],[\"$\",\"meta\",\"6\",{\"property\":\"og:url\",\"content\":\"https://atproto.com/\"}],[\"$\",\"meta\",\"7\",{\"property\":\"og:site_name\",\"content\":\"AT Protocol\"}],[\"$\",\"meta\",\"8\",{\"property\":\"og:image\",\"content\":\"https://atproto.com/default-social-card.png\"}],[\"$\",\"meta\",\"9\",{\"property\":\"og:image:secure_url\",\"content\":\"https://atproto.com/default-social-card.png\"}],[\"$\",\"meta\",\"10\",{\"property\":\"og:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"11\",{\"property\":\"og:image:height\",\"content\":\"630\"}],[\"$\",\"meta\",\"12\",{\"property\":\"og:type\",\"content\":\"website\"}],[\"$\",\"meta\",\"13\",{\"name\":\"twitter:card\",\"content\":\"summary_large_image\"}],[\"$\",\"meta\",\"14\",{\"name\":\"twitter:title\",\"content\":\"Quick start guide to building applications on AT Protocol - AT Protocol\"}],[\"$\",\"meta\",\"15\",{\"name\":\"twitter:description\",\"content\":\"In this guide, we're going to build a simple multi-user app that publishes your current \\\"status\\\" as an emoji.\"}],[\"$\",\"meta\",\"16\",{\"name\":\"twitter:image\",\"content\":\"https://atproto.com/default-social-card.png\"}]]\n3:null\n"])</script><script>self.__next_f.push([1,"29:I[5552,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"369\",\"static/chunks/369-a583bbb81b96a7d8.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"60\",\"static/chunks/60-3e1824554a9cf3f8.js\",\"203\",\"static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js\"],\"Providers\"]\n2a:I[5211,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"369\",\"static/chunks/369-a583bbb81b96a7d8.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"60\",\"static/chunks/60-3e1824554a9cf3f8.js\",\"203\",\"static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js\"],\"Layout\"]\n"])</script><script>self.__next_f.push([1,"8:[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"h-full\",\"suppressHydrationWarning\":true,\"children\":[\"$\",\"body\",null,{\"className\":\"flex min-h-full bg-white antialiased dark:bg-zinc-900\",\"children\":[\"$\",\"$L29\",null,{\"children\":[\"$\",\"div\",null,{\"className\":\"w-full\",\"children\":[\"$\",\"$L2a\",null,{\"allSections\":{\"/en.mdx\":[],\"/ja.mdx\":[],\"/pt.mdx\":[],\"/explorer\":[],\"/sdks\":[{\"title\":\"Official libraries\",\"id\":\"official-libraries\"},{\"title\":\"Community libraries\",\"id\":\"community-libraries\"}],\"/articles/atproto-for-distsys-engineers/en.mdx\":[{\"title\":\"Scaling the traditional Web backend\",\"id\":\"scaling-the-traditional-web-backend\"},{\"title\":\"Decentralizing our high-scale backend\",\"id\":\"decentralizing-our-high-scale-backend\"},{\"title\":\"Unifying the data model\",\"id\":\"unifying-the-data-model\"},{\"title\":\"Charting the flow of data\",\"id\":\"charting-the-flow-of-data\"},{\"title\":\"Building practical open systems\",\"id\":\"building-practical-open-systems\"}],\"/articles/atproto-for-distsys-engineers/ja.mdx\":[{\"title\":\"従来の Web バックエンドのスケーリング\",\"id\":\"web\"},{\"title\":\"大規模バックエンドの分散化\",\"id\":\"\"},{\"title\":\"データ モデルの統合\",\"id\":\"\"},{\"title\":\"データの流れを図に表す\",\"id\":\"\"},{\"title\":\"実用的なオープン システムの構築\",\"id\":\"\"}],\"/articles/atproto-for-distsys-engineers/pt.mdx\":[{\"title\":\"Escalando o backend tradicional da Web\",\"id\":\"escalando-o-backend-tradicional-da-web\"},{\"title\":\"Descentralizando nosso backend de alta escala\",\"id\":\"descentralizando-nosso-backend-de-alta-escala\"},{\"title\":\"Unificando o modelo de dados\",\"id\":\"unificando-o-modelo-de-dados\"},{\"title\":\"Mapeando o fluxo de dados\",\"id\":\"mapeando-o-fluxo-de-dados\"},{\"title\":\"Construindo sistemas abertos práticos\",\"id\":\"construindo-sistemas-abertos-praticos\"}],\"/guides/data-repos/en.mdx\":[{\"title\":\"Data Layout\",\"id\":\"data-layout\"},{\"title\":\"Identifier Types\",\"id\":\"identifier-types\"}],\"/guides/data-repos/ja.mdx\":[{\"title\":\"データ レイアウト\",\"id\":\"\"},{\"title\":\"識別子の種類\",\"id\":\"\"}],\"/guides/data-repos/pt.mdx\":[{\"title\":\"Layout de dados\",\"id\":\"layout-de-dados\"},{\"title\":\"Tipos de Identificadores\",\"id\":\"tipos-de-identificadores\"}],\"/guides/faq/en.mdx\":[{\"title\":\"Is the AT Protocol a blockchain?\",\"id\":\"is-the-at-protocol-a-blockchain\"},{\"title\":\"Why not use ActivityPub?\",\"id\":\"why-not-use-activity-pub\"},{\"title\":\"Why create Lexicon instead of using JSON-LD or RDF?\",\"id\":\"why-create-lexicon-instead-of-using-json-ld-or-rdf\"},{\"title\":\"What is “XRPC,” and why not use ___?\",\"id\":\"what-is-xrpc-and-why-not-use\"}],\"/guides/faq/ja.mdx\":[{\"title\":\"AT プロトコルはブロックチェーンですか?\",\"id\":\"at\"},{\"title\":\"ActivityPub を使用しないのはなぜですか?\",\"id\":\"activity-pub\"},{\"title\":\"JSON-LD や RDF を使用する代わりに Lexicon を作成する理由\",\"id\":\"json-ld-rdf-lexicon\"},{\"title\":\"「XRPC」とは何ですか。なぜ ___ を使用しないのですか?\",\"id\":\"xrpc\"}],\"/guides/faq/pt.mdx\":[{\"title\":\"O Protocolo AT é uma blockchain?\",\"id\":\"o-protocolo-at-e-uma-blockchain\"},{\"title\":\"Por que não usar o ActivityPub?\",\"id\":\"por-que-nao-usar-o-activity-pub\"},{\"title\":\"Por que criar o Lexicon em vez de usar JSON-LD ou RDF?\",\"id\":\"por-que-criar-o-lexicon-em-vez-de-usar-json-ld-ou-rdf\"},{\"title\":\"O que é “XRPC” e por que não usar ___?\",\"id\":\"o-que-e-xrpc-e-por-que-nao-usar\"}],\"/guides/glossary/en.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT Protocol\",\"id\":\"at-protocol\"},{\"title\":\"PDS (Personal Data Server)\",\"id\":\"pds-personal-data-server\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"Relay\",\"id\":\"relay\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"Data Repo\",\"id\":\"data-repo\"},{\"title\":\"Collection\",\"id\":\"collection\"},{\"title\":\"Record\",\"id\":\"record\"},{\"title\":\"Blob\",\"id\":\"blob\"},{\"title\":\"Label\",\"id\":\"label\"},{\"title\":\"Handle\",\"id\":\"handle\"},{\"title\":\"DID (Decentralized ID)\",\"id\":\"did-decentralized-id\"},{\"title\":\"NSID (Namespaced ID)\",\"id\":\"nsid-namespaced-id\"},{\"title\":\"TID (Timestamp ID)\",\"id\":\"tid-timestamp-id\"},{\"title\":\"CID (Content ID)\",\"id\":\"cid-content-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/glossary/ja.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT プロトコル\",\"id\":\"at\"},{\"title\":\"PDS (パーソナル データ サーバー)\",\"id\":\"pds\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"リレー\",\"id\":\"\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"データ リポジトリ\",\"id\":\"\"},{\"title\":\"コレクション\",\"id\":\"\"},{\"title\":\"レコード\",\"id\":\"\"},{\"title\":\"BLOB\",\"id\":\"blob\"},{\"title\":\"ラベル\",\"id\":\"\"},{\"title\":\"ハンドル\",\"id\":\"\"},{\"title\":\"DID (分散 ID)\",\"id\":\"did-id\"},{\"title\":\"NSID (名前空間 ID)\",\"id\":\"nsid-id\"},{\"title\":\"TID (タイムスタンプ ID)\",\"id\":\"tid-id\"},{\"title\":\"CID (コンテンツ ID)\",\"id\":\"cid-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/glossary/pt.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT Protocol\",\"id\":\"at-protocol\"},{\"title\":\"PDS (Personal Data Server)\",\"id\":\"pds-personal-data-server\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"Relay\",\"id\":\"relay\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"Data Repo\",\"id\":\"data-repo\"},{\"title\":\"Collection\",\"id\":\"collection\"},{\"title\":\"Record\",\"id\":\"record\"},{\"title\":\"Blob\",\"id\":\"blob\"},{\"title\":\"Label\",\"id\":\"label\"},{\"title\":\"Handle\",\"id\":\"handle\"},{\"title\":\"DID (Decentralized ID)\",\"id\":\"did-decentralized-id\"},{\"title\":\"NSID (Namespaced ID)\",\"id\":\"nsid-namespaced-id\"},{\"title\":\"TID (Timestamp ID)\",\"id\":\"tid-timestamp-id\"},{\"title\":\"CID (Content ID)\",\"id\":\"cid-content-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/identity/en.mdx\":[{\"title\":\"Identifiers\",\"id\":\"identifiers\"},{\"title\":\"DID Methods\",\"id\":\"did-methods\"},{\"title\":\"Handle Resolution\",\"id\":\"handle-resolution\"}],\"/guides/identity/ja.mdx\":[{\"title\":\"識別子\",\"id\":\"\"},{\"title\":\"DID メソッド\",\"id\":\"did\"},{\"title\":\"ハンドル解決\",\"id\":\"\"}],\"/guides/identity/pt.mdx\":[{\"title\":\"Identificadores\",\"id\":\"identificadores\"},{\"title\":\"Métodos DID\",\"id\":\"metodos-did\"},{\"title\":\"Resolução de identificadores\",\"id\":\"resolucao-de-identificadores\"}],\"/guides/lexicon/en.mdx\":[{\"title\":\"Why is Lexicon needed?\",\"id\":\"why-is-lexicon-needed\"},{\"title\":\"HTTP API methods\",\"id\":\"http-api-methods\"},{\"title\":\"Record types\",\"id\":\"record-types\"},{\"title\":\"Tokens\",\"id\":\"tokens\"},{\"title\":\"Versioning\",\"id\":\"versioning\"},{\"title\":\"Schema distribution\",\"id\":\"schema-distribution\"}],\"/guides/lexicon/ja.mdx\":[{\"title\":\"Lexicon が必要な理由\",\"id\":\"lexicon\"},{\"title\":\"HTTP API メソッド\",\"id\":\"http-api\"},{\"title\":\"レコード タイプ\",\"id\":\"\"},{\"title\":\"トークン\",\"id\":\"\"},{\"title\":\"バージョン管理\",\"id\":\"\"},{\"title\":\"スキーマの配布\",\"id\":\"\"}],\"/guides/lexicon/pt.mdx\":[{\"title\":\"Por que o Lexicon é necessário?\",\"id\":\"por-que-o-lexicon-e-necessario\"},{\"title\":\"Métodos de API HTTP\",\"id\":\"metodos-de-api-http\"},{\"title\":\"Tipos de registro\",\"id\":\"tipos-de-registro\"},{\"title\":\"Tokens\",\"id\":\"tokens\"},{\"title\":\"Versionamento\",\"id\":\"versionamento\"},{\"title\":\"Distribuição de esquema\",\"id\":\"distribuicao-de-esquema\"}],\"/guides/overview/en.mdx\":[{\"title\":\"Identity\",\"id\":\"identity\"},{\"title\":\"Data repositories\",\"id\":\"data-repositories\"},{\"title\":\"Federation\",\"id\":\"federation\"},{\"title\":\"Interoperation\",\"id\":\"interoperation\"},{\"title\":\"Achieving scale\",\"id\":\"achieving-scale\"},{\"title\":\"Algorithmic choice\",\"id\":\"algorithmic-choice\"},{\"title\":\"Account portability\",\"id\":\"account-portability\"},{\"title\":\"Speech, reach, and moderation\",\"id\":\"speech-reach-and-moderation\"},{\"title\":\"Specifications\",\"id\":\"specifications\"}],\"/guides/overview/ja.mdx\":[{\"title\":\"アイデンティティ\",\"id\":\"\"},{\"title\":\"データ リポジトリ\",\"id\":\"\"},{\"title\":\"フェデレーション\",\"id\":\"\"},{\"title\":\"相互運用\",\"id\":\"\"},{\"title\":\"スケールの実現\",\"id\":\"\"},{\"title\":\"アルゴリズムの選択\",\"id\":\"\"},{\"title\":\"アカウントの移植性\",\"id\":\"\"},{\"title\":\"スピーチ、リーチ、モデレーション\",\"id\":\"\"},{\"title\":\"仕様\",\"id\":\"\"}],\"/guides/overview/pt.mdx\":[{\"title\":\"Identidade\",\"id\":\"identidade\"},{\"title\":\"Repositórios de dados\",\"id\":\"repositorios-de-dados\"},{\"title\":\"Federação\",\"id\":\"federacao\"},{\"title\":\"Interoperação\",\"id\":\"interoperacao\"},{\"title\":\"Alcançando escala\",\"id\":\"alcancando-escala\"},{\"title\":\"Escolha algorítmica\",\"id\":\"escolha-algoritmica\"},{\"title\":\"Portabilidade de conta\",\"id\":\"portabilidade-de-conta\"},{\"title\":\"Fala, alcance e moderação\",\"id\":\"fala-alcance-e-moderacao\"},{\"title\":\"Especificações\",\"id\":\"especificacoes\"}],\"/guides/self-hosting/en.mdx\":[{\"title\":\"Table of Contents\",\"id\":\"table-of-contents\"},{\"title\":\"Preparation for self-hosting PDS\",\"id\":\"preparation-for-self-hosting-pds\"},{\"title\":\"Open your cloud firewall for HTTP and HTTPS\",\"id\":\"open-your-cloud-firewall-for-http-and-https\"},{\"title\":\"Configure DNS for your domain\",\"id\":\"configure-dns-for-your-domain\"},{\"title\":\"Check that DNS is working as expected\",\"id\":\"check-that-dns-is-working-as-expected\"},{\"title\":\"Installer on Ubuntu 20.04/22.04 and Debian 11/12\",\"id\":\"installer-on-ubuntu-20-04-22-04-and-debian-11-12\"},{\"title\":\"Verifying that your PDS is online and accessible\",\"id\":\"verifying-that-your-pds-is-online-and-accessible\"},{\"title\":\"Creating an account using pdsadmin\",\"id\":\"creating-an-account-using-pdsadmin\"},{\"title\":\"Creating an account using an invite code\",\"id\":\"creating-an-account-using-an-invite-code\"},{\"title\":\"Using the Bluesky app with your PDS\",\"id\":\"using-the-bluesky-app-with-your-pds\"},{\"title\":\"Updating your PDS\",\"id\":\"updating-your-pds\"},{\"title\":\"Getting help\",\"id\":\"getting-help\"}],\"/guides/self-hosting/ja.mdx\":[{\"title\":\"目次\",\"id\":\"\"},{\"title\":\"セルフホスティング PDS の準備\",\"id\":\"pds\"},{\"title\":\"クラウド ファイアウォールを HTTP および HTTPS 用に開く\",\"id\":\"http-https\"},{\"title\":\"ドメインの DNS を構成する\",\"id\":\"dns\"},{\"title\":\"DNS が期待どおりに動作していることを確認します\",\"id\":\"dns-2\"},{\"title\":\"Ubuntu 20.04/22.04 および Debian 11/12 のインストーラー\",\"id\":\"ubuntu-20-04-22-04-debian-11-12\"},{\"title\":\"PDS がオンラインでアクセス可能であることを確認する\",\"id\":\"pds-2\"},{\"title\":\"pdsadmin を使用してアカウントを作成する\",\"id\":\"pdsadmin\"},{\"title\":\"招待コードを使用してアカウントを作成する\",\"id\":\"\"},{\"title\":\"PDS で Bluesky アプリを使用する\",\"id\":\"pds-bluesky\"},{\"title\":\"PDS の更新\",\"id\":\"pds-3\"},{\"title\":\"ヘルプの取得\",\"id\":\"\"}],\"/guides/self-hosting/pt.mdx\":[{\"title\":\"Índice\",\"id\":\"indice\"},{\"title\":\"Preparação para auto-hospedagem de PDS\",\"id\":\"preparacao-para-auto-hospedagem-de-pds\"},{\"title\":\"Abra seu firewall de nuvem para HTTP e HTTPS\",\"id\":\"abra-seu-firewall-de-nuvem-para-http-e-https\"},{\"title\":\"Configure o DNS para seu domínio\",\"id\":\"configure-o-dns-para-seu-dominio\"},{\"title\":\"Verifique se o DNS está funcionando conforme o esperado\",\"id\":\"verifique-se-o-dns-esta-funcionando-conforme-o-esperado\"},{\"title\":\"Instalador no Ubuntu 20.04/22.04 e Debian 11/12\",\"id\":\"instalador-no-ubuntu-20-04-22-04-e-debian-11-12\"},{\"title\":\"Verificando se seu PDS está online e acessível\",\"id\":\"verificando-se-seu-pds-esta-online-e-acessivel\"},{\"title\":\"Criando uma conta usando pdsadmin\",\"id\":\"criando-uma-conta-usando-pdsadmin\"},{\"title\":\"Criando uma conta usando um código de convite\",\"id\":\"criando-uma-conta-usando-um-codigo-de-convite\"},{\"title\":\"Usando o aplicativo Bluesky com seu PDS\",\"id\":\"usando-o-aplicativo-bluesky-com-seu-pds\"},{\"title\":\"Atualizando seu PDS\",\"id\":\"atualizando-seu-pds\"},{\"title\":\"Obtendo ajuda\",\"id\":\"obtendo-ajuda\"}],\"/specs/at-uri-scheme\":[],\"/specs/atp\":[{\"title\":\"Protocol Structure\",\"id\":\"protocol-structure\"},{\"title\":\"Protocol Extension and Applications\",\"id\":\"protocol-extension-and-applications\"},{\"title\":\"What Is Missing?\",\"id\":\"what-is-missing\"},{\"title\":\"Future Work\",\"id\":\"future-work\"}],\"/specs/blob\":[{\"title\":\"Blob Metadata\",\"id\":\"blob-metadata\"},{\"title\":\"Blob Lifecycle\",\"id\":\"blob-lifecycle\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/cryptography\":[{\"title\":\"ECDSA Signature Malleability\",\"id\":\"ecdsa-signature-malleability\"},{\"title\":\"Public Key Encoding\",\"id\":\"public-key-encoding\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/data-model\":[{\"title\":\"Relationship With IPLD\",\"id\":\"relationship-with-ipld\"},{\"title\":\"Data Types\",\"id\":\"data-types\"},{\"title\":\"blob Type\",\"id\":\"blob-type\"},{\"title\":\"JSON Representation\",\"id\":\"json-representation\"},{\"title\":\"Link and CID Formats\",\"id\":\"link-and-cid-formats\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/did\":[{\"title\":\"Blessed DID Methods\",\"id\":\"blessed-did-methods\"},{\"title\":\"AT Protocol DID Identifier Syntax\",\"id\":\"at-protocol-did-identifier-syntax\"},{\"title\":\"DID Documents\",\"id\":\"did-documents\"},{\"title\":\"Representation of Public Keys\",\"id\":\"representation-of-public-keys\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/event-stream\":[{\"title\":\"Streaming Wire Protocol (v0)\",\"id\":\"streaming-wire-protocol-v0\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/handle\":[{\"title\":\"Handle Identifier Syntax\",\"id\":\"handle-identifier-syntax\"},{\"title\":\"Additional Non-Syntax Restrictions\",\"id\":\"additional-non-syntax-restrictions\"},{\"title\":\"Identifier Examples\",\"id\":\"identifier-examples\"},{\"title\":\"Handle Resolution\",\"id\":\"handle-resolution\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/label\":[{\"title\":\"Schema and Data Model\",\"id\":\"schema-and-data-model\"},{\"title\":\"Value\",\"id\":\"value\"},{\"title\":\"Label Lifecycle: Negation and Expiration\",\"id\":\"label-lifecycle-negation-and-expiration\"},{\"title\":\"Signatures\",\"id\":\"signatures\"},{\"title\":\"Self-Labels in Records\",\"id\":\"self-labels-in-records\"},{\"title\":\"Labeler Service Identity\",\"id\":\"labeler-service-identity\"},{\"title\":\"Label Distribution Endpoints\",\"id\":\"label-distribution-endpoints\"},{\"title\":\"Labeler HTTP Headers\",\"id\":\"labeler-http-headers\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/lexicon\":[{\"title\":\"Overview of Types\",\"id\":\"overview-of-types\"},{\"title\":\"Lexicon Files\",\"id\":\"lexicon-files\"},{\"title\":\"Primary Type Definitions\",\"id\":\"primary-type-definitions\"},{\"title\":\"Field Type Definitions\",\"id\":\"field-type-definitions\"},{\"title\":\"String Formats\",\"id\":\"string-formats\"},{\"title\":\"When to use $type\",\"id\":\"when-to-use-type\"},{\"title\":\"Lexicon Evolution\",\"id\":\"lexicon-evolution\"},{\"title\":\"Authority and Control\",\"id\":\"authority-and-control\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/nsid\":[],\"/specs/oauth\":[{\"title\":\"Clients\",\"id\":\"clients\"},{\"title\":\"Identity Authentication\",\"id\":\"identity-authentication\"},{\"title\":\"Authorization Scopes\",\"id\":\"authorization-scopes\"},{\"title\":\"Authorization Requests\",\"id\":\"authorization-requests\"},{\"title\":\"Tokens and Session Lifetime\",\"id\":\"tokens-and-session-lifetime\"},{\"title\":\"Demonstrating Proof of Possession (DPoP)\",\"id\":\"demonstrating-proof-of-possession-d-po-p\"},{\"title\":\"Authorization Servers\",\"id\":\"authorization-servers\"},{\"title\":\"Summary of Authorization Flow\",\"id\":\"summary-of-authorization-flow\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/record-key\":[],\"/specs/repository\":[{\"title\":\"Repo Data Structure (v3)\",\"id\":\"repo-data-structure-v3\"},{\"title\":\"CAR File Serialization\",\"id\":\"car-file-serialization\"},{\"title\":\"Repository Diffs\",\"id\":\"repository-diffs\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/xrpc\":[{\"title\":\"Lexicon HTTP Endpoints\",\"id\":\"lexicon-http-endpoints\"},{\"title\":\"Authentication\",\"id\":\"authentication\"},{\"title\":\"Service Proxying\",\"id\":\"service-proxying\"},{\"title\":\"Summary of HTTP Headers\",\"id\":\"summary-of-http-headers\"},{\"title\":\"Summary of HTTP Status Codes\",\"id\":\"summary-of-http-status-codes\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/guides/applications/en.mdx\":[{\"title\":\"Introduction\",\"id\":\"introduction\"},{\"title\":\"Step 1. Starting with our ExpressJS app\",\"id\":\"step-1-starting-with-our-express-js-app\"},{\"title\":\"Step 2. Signing in with OAuth\",\"id\":\"step-2-signing-in-with-o-auth\"},{\"title\":\"Step 3. Fetching the user's profile\",\"id\":\"step-3-fetching-the-users-profile\"},{\"title\":\"Step 4. Reading \u0026 writing records\",\"id\":\"step-4-reading-and-writing-records\"},{\"title\":\"Step 5. Creating a custom \\\"status\\\" schema\",\"id\":\"step-5-creating-a-custom-status-schema\"},{\"title\":\"Step 6. Listening to the firehose\",\"id\":\"step-6-listening-to-the-firehose\"},{\"title\":\"Step 7. Listing the latest statuses\",\"id\":\"step-7-listing-the-latest-statuses\"},{\"title\":\"Step 8. Optimistic updates\",\"id\":\"step-8-optimistic-updates\"},{\"title\":\"Thinking in AT Proto\",\"id\":\"thinking-in-at-proto\"},{\"title\":\"Next steps\",\"id\":\"next-steps\"}],\"/guides/applications/ja.mdx\":[{\"title\":\"はじめに\",\"id\":\"\"},{\"title\":\"ステップ 1. ExpressJS アプリから始める\",\"id\":\"1-express-js\"},{\"title\":\"ステップ 2. OAuth でサインイン\",\"id\":\"2-o-auth\"},{\"title\":\"ステップ 3. ユーザーのプロファイルを取得する\",\"id\":\"3\"},{\"title\":\"ステップ 4. レコードの読み取りと書き込み\",\"id\":\"4\"},{\"title\":\"ステップ 5. カスタムの「ステータス」スキーマの作成\",\"id\":\"5\"},{\"title\":\"ステップ 6. ファイアホースをリッスン\",\"id\":\"6\"},{\"title\":\"ステップ 7. 最新のステータスを一覧表示する\",\"id\":\"7\"},{\"title\":\"ステップ 8. 楽観的更新\",\"id\":\"8\"},{\"title\":\"AT Proto で考える\",\"id\":\"at-proto\"},{\"title\":\"次のステップ\",\"id\":\"\"}],\"/guides/applications/pt.mdx\":[{\"title\":\"Introdução\",\"id\":\"introducao\"},{\"title\":\"Etapa 1. Começando com nosso aplicativo ExpressJS\",\"id\":\"etapa-1-comecando-com-nosso-aplicativo-express-js\"},{\"title\":\"Etapa 2. Entrando com OAuth\",\"id\":\"etapa-2-entrando-com-o-auth\"},{\"title\":\"Etapa 3. Obtendo o perfil do usuário\",\"id\":\"etapa-3-obtendo-o-perfil-do-usuario\"},{\"title\":\"Etapa 4. Lendo e escrevendo registros\",\"id\":\"etapa-4-lendo-e-escrevendo-registros\"},{\"title\":\"Etapa 5. Criando um esquema de \\\"status\\\" personalizado\",\"id\":\"etapa-5-criando-um-esquema-de-status-personalizado\"},{\"title\":\"Etapa 6. Ouvindo o firehose\",\"id\":\"etapa-6-ouvindo-o-firehose\"},{\"title\":\"Etapa 7. Listando os status mais recentes\",\"id\":\"etapa-7-listando-os-status-mais-recentes\"},{\"title\":\"Etapa 8. Atualizações otimistas\",\"id\":\"etapa-8-atualizacoes-otimistas\"},{\"title\":\"Pensando em AT Proto\",\"id\":\"pensando-em-at-proto\"},{\"title\":\"Próximos passos\",\"id\":\"proximos-passos\"}],\"/articles/why-atproto\":[]},\"children\":[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"div\",null,{\"className\":\"absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden\",\"children\":[\"$\",\"div\",null,{\"className\":\"absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem] dark:[mask-image:linear-gradient(white,transparent)]\",\"children\":[\"$\",\"div\",null,{\"className\":\"absolute inset-0 bg-gradient-to-r from-[#6fa5ff] to-[#9dc0fb] opacity-40 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-[#0090f2]/30 dark:to-[#7588ff]/30 dark:opacity-100\",\"children\":[\"$\",\"svg\",null,{\"aria-hidden\":\"true\",\"className\":\"absolute inset-x-0 inset-y-[-50%] h-[200%] w-full fill-black/40 stroke-black/50 mix-blend-overlay dark:fill-white/2.5 dark:stroke-white/5\",\"children\":[[\"$\",\"defs\",null,{\"children\":[\"$\",\"pattern\",null,{\"id\":\":S1:\",\"width\":72,\"height\":56,\"patternUnits\":\"userSpaceOnUse\",\"x\":-12,\"y\":4,\"children\":[\"$\",\"path\",null,{\"d\":\"M.5 56V.5H72\",\"fill\":\"none\"}]}]}],[\"$\",\"rect\",null,{\"width\":\"100%\",\"height\":\"100%\",\"strokeWidth\":0,\"fill\":\"url(#:S1:)\"}],[\"$\",\"svg\",null,{\"x\":-12,\"y\":4,\"className\":\"overflow-visible\",\"children\":[]}]]}]}]}]}],[\"$\",\"div\",null,{\"className\":\"mx-auto flex h-full max-w-xl flex-col items-center justify-center py-16 text-center\",\"children\":[[\"$\",\"p\",null,{\"className\":\"text-sm font-semibold text-zinc-900 dark:text-white\",\"children\":\"404\"}],[\"$\",\"h1\",null,{\"className\":\"mt-2 text-2xl font-bold text-zinc-900 dark:text-white\",\"children\":\"Page not found\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-base text-zinc-600 dark:text-zinc-400\",\"children\":\"Sorry, we couldn’t find the page you’re looking for.\"}],[\"$\",\"$Lc\",null,{\"className\":\"inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-1 dark:ring-inset dark:ring-blue-400/20 dark:hover:bg-blue-400/10 dark:hover:text-blue-300 dark:hover:ring-blue-300 mt-8\",\"href\":\"/\",\"children\":[false,\"Back to docs\",[\"$\",\"svg\",null,{\"viewBox\":\"0 0 20 20\",\"fill\":\"none\",\"aria-hidden\":\"true\",\"className\":\"mt-0.5 h-5 w-5 -mr-1\",\"children\":[\"$\",\"path\",null,{\"stroke\":\"currentColor\",\"strokeLinecap\":\"round\",\"strokeLinejoin\":\"round\",\"d\":\"m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9\"}]}]]}]]}]],\"notFoundStyles\":[]}]}]}]}]}]}]\n"])</script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10