@vercel/board 0.26.0 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{a2-Qzj0kCWU.mjs → a2-D6TOZNV2.mjs} +107 -39
  2. package/dist/{attempts-CLG840Oj.mjs → attempts-HXcskN1w.mjs} +1 -11
  3. package/dist/backend-a2/contract.mjs +1 -1
  4. package/dist/backend-a2.d.mts +3 -0
  5. package/dist/backend-a2.mjs +1 -1
  6. package/dist/{backend-http-F0W6e36G.mjs → backend-http-CDu_eapz.mjs} +1 -1
  7. package/dist/{backend-http-BB0Gld6Q.mjs → backend-http-DApbemsm.mjs} +18 -4
  8. package/dist/backend-http.mjs +1 -1
  9. package/dist/backend-http.node.mjs +1 -1
  10. package/dist/{backend-vercel-CtB46vMQ.mjs → backend-vercel-C2YcUuyT.mjs} +3 -3
  11. package/dist/{backend-vercel-DUWIcFi9.mjs → backend-vercel-QuUJ3dW_.mjs} +8 -3
  12. package/dist/backend-vercel.mjs +1 -1
  13. package/dist/backend-vercel.node.mjs +1 -1
  14. package/dist/cli-embedded.mjs +1 -1
  15. package/dist/cli.mjs +2 -2
  16. package/dist/{engine-ROa7-IQY.mjs → engine-Bfe6pcXb.mjs} +2 -2
  17. package/dist/{exports-NFjBgIT2.mjs → exports-T-ART7P7.mjs} +2 -2
  18. package/dist/{index.default-Boe7x-mK.mjs → index.default-_e7u6Z-I.mjs} +1 -1
  19. package/dist/index.mjs +3 -3
  20. package/dist/index.node.mjs +1 -1
  21. package/dist/inspect/app.js +29 -29
  22. package/dist/inspect/engine.js +5 -5
  23. package/dist/{main-BuahKR16.mjs → main-Ci7R6qkK.mjs} +9 -4
  24. package/dist/{reducers-BFmIXilu.mjs → reducers-BcmSs4qf.mjs} +3 -1
  25. package/dist/{serve-BbHEQk_Y.mjs → serve-CNklIudj.mjs} +2 -2
  26. package/dist/{server-Bphio_82.mjs → server-DMNfPSAZ.mjs} +3 -3
  27. package/dist/sync-linear.mjs +6 -5
  28. package/dist/{ids-C6AYo1bu.mjs → wait-until-Dw4XrIDQ.mjs} +12 -1
  29. package/dist/{watchers-hash-BfDPW95j.mjs → watchers-hash-C10qKYPa.mjs} +1 -1
  30. package/docs/01-board/06-watchers.md +4 -2
  31. package/docs/01-board/07-schedules.md +12 -5
  32. package/docs/02-sync/07-linear.md +4 -2
  33. package/docs/04-reference/01-cli.md +2 -1
  34. package/docs/04-reference/03-http.md +1 -1
  35. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,l as h,m as g,n as _,o as ee,p as v,r as y,s as b,t as te,u as x,v as S,w as C,x as w,y as T}from"./app.js";const E=/^\[vc\]: #\S*\r?\n/,D=/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,O=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function k(e){if(!E.test(e))return null;let t=e.search(/<details><summary>\d+ Skipped Deployments?<\/summary>/),n=[],r=0;for(let i of e.split(/\r?\n/)){let a=r;if(r=e.indexOf(`
2
- `,r)+1||e.length,!i.startsWith(`|`)||/^\|\s*(Project|Name)\s*\|/.test(i)||/^\|\s*:?-/.test(i))continue;let o=i.split(`|`).slice(1,-1).map(e=>e.trim());if(o.length<2)continue;let s=O.exec(o[1]);if(!s)continue;let c=[...o[0].matchAll(D)].at(-1),l=c?.[1]??o[0].replace(/<[^>]*>/g,``).trim();if(!l)continue;let u=o[2]?[...o[2].matchAll(D)].find(e=>/preview/i.test(e[1])):void 0,d=o[3]?.match(/<relative-time[^>]*>([^<]+)<\/relative-time>/)?.[1]?.trim()??(o[3]?.replace(/<[^>]*>/g,``).trim()||null);n.push({project:l,projectUrl:c?.[2]??null,status:s[1].trim(),deploymentUrl:s[2]??null,previewUrl:u?.[2]??null,updated:d,skipped:t!==-1&&a>t})}return n.length>0?n:null}const A={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},j=(e,t)=>t?`[${e}](${t})`:e;function ne(e){return e.map(e=>`- ${[`${A[e.status.toLowerCase()]??`▲`} **${j(e.project,e.projectUrl)}**`,`${j(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[j(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
3
- `)}function re(e,t){let n=t===`github:user:35613825`?k(e):null;return n?`**▲ Vercel**\n${ne(n)}`:e}const ie=e=>e>10;function ae(e){try{Reflect.get(globalThis,Symbol.for(`@vercel/request-context`))?.get?.()?.waitUntil?.(e)}catch{}}w({id:i().min(1),platform:i().min(1),key:i().min(1),...l.shape,raw:e()});const M=w({root:c(e=>typeof e==`function`).optional(),text:i().optional(),labels:g.optional(),unset:S(v).optional(),noSync:T().optional()}).strict();function N(e){if(e&&typeof e==`object`){for(let t of Object.values(e))N(t);Object.freeze(e)}return e}const oe=w({target:i().min(1),channel:i().min(1),kind:o([`channel`,`thread`])}).strict();var P=class extends Error{code;constructor(e,t){super(t),this.code=e}};const se=e=>e instanceof P&&e.code===`channel_not_found`,ce=w({event:b,raw:e(),dedupe:i().min(1)}),F=/* @__PURE__ */ new WeakMap,le=e=>F.get(e),I=(e,t=!1)=>{let n=f(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,p-1).toWellFormed()}…`:r},L=(e,t,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${n(e.size)})`} didn't come over: ${t}`,R=e=>e instanceof y&&e.code===`no_files`,ue=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return L(e,`this board keeps no files`);let n=t.replace(/[[\]\\]/g,e=>`\\${e}`),r=e.url.replace(/[ ()]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`);return L(e,`this board keeps no files`,`[${n}](${r})`)},z=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
4
- `)}`:e,de=e=>e.split(`
5
- `).filter(e=>/^📎 .* didn't come over: /u.test(e)),B=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),fe=(e,t)=>e.length===t.length&&e.every((e,n)=>B(e)===B(t[n])),pe=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,V=6e4,me=new AbortController().signal,he=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},H=e=>typeof e==`string`?f(e):Array.isArray(e)?e.map(H):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[f(e),H(t)])):e,U=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return f(n.slice(0,500))||`unknown error`},W=e=>e instanceof y&&e.status===404;function G(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=me,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw m(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),g=t=>e[p(t)]!==void 0,v=e=>e.slice(e.indexOf(`:`)+1);async function b(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(W(e))return null;throw e}}async function S(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(W(e))return[];throw e}}async function w(e){return(await S(e)).find(t=>t.id===e)??null}async function T(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let E=(e,t)=>l(`syncFind`,e,t);async function D(){let{actors:e}=await l(`actors`,{}),t={byId:{},links:{}};for(let{person:n,...r}of e)t.byId[r.id]={id:r.id,kind:r.kind,handle:r.handle,name:r.name,avatar:r.avatar},n&&(t.links[r.id]=n.id);return t}let O=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),k=/* @__PURE__ */ new Map;function A(e,t){let n=`${e}:${t}`,r=k.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=se(r);!a&&k.get(n)===i&&k.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),k.set(n,i),i.id}function j(e,t){if(!u(t))return v(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:v(n)}catch{return null}}async function ne(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function F(e){let t=e.indexOf(`:`);if(t<=0)throw m(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=oe.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw m(`can't sync to ${e}: ${t.message}`)}}async function B(e,t){let{event:n,raw:r,dedupe:i}=ce.parse(H({...t,raw:he(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw m(`provider locations must belong to sync entry ${e}`);let a=le(f(e));(!a||await a(v(n.channel),t=>A(e,t))||await G(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function G(e){try{return(await E({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function ge(t,n){let r=e[t];if(!r)throw s(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>B(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}return ae(_e([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,U(e)))),i}async function _e(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>ve(e,t))),n+=t.length}return n}async function ve(e,t){let n;try{await xe(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,U(r)),n=U(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,U(e)))}async function ye(e,t,n,r=[]){if(t.length===0)return[];let i=[];for(let a of t){if(i.some(e=>e.url===a.url))continue;let t=(await E({thread:a.thread,message:a.message})).message,o=t===null?null:await w(t);if(o&&!o.deleted&&o.channel===n){i.push({url:a.url,message:o.id,unavailable:null,snapshot:{author:o.author?.name??o.by.actor,text:o.text,createdAt:o.createdAt,files:o.attachments.map(e=>({name:e.name,url:null}))}});continue}if(a.snapshot){i.push({url:a.url,message:null,snapshot:a.snapshot,unavailable:null});continue}let s=r.find(e=>e.url===a.url&&e.snapshot&&e.message===null);if(s){i.push(s);continue}let c=await e.fetchLink(a).catch(e=>({snapshot:null,unavailable:U(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return H(i)}async function be(e,n,r){let i=[],o=[],s=!1;for(let c of n.slice(0,10)){if(s){o.push(ue(c));continue}if(c.size!==void 0&&c.size>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}let n;try{let t=await e.fetchFile(c);if(t===null){o.push(L(c,`${e.platform} can't hand it over`));continue}n=t}catch(t){if(r<3)throw t;console.warn(`[board] couldn't fetch ${c.name??`a file`} from ${e.platform}:`,U(t)),o.push(L(c,`${e.platform} wouldn't hand it over`));continue}if(n.byteLength>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}if(n.byteLength===0){o.push(L(c,`it was empty`));continue}let u=c.name??`${c.type}`;try{i.push((await l(`upload`,{data:n,name:u,type:c.mimeType||t(u)})).attachment)}catch(e){if(e instanceof y&&(e.code===`too_large`||e.code===`no_direct_uploads`)){o.push(L(c,`over ${C/1024/1024} MB`));continue}if(!R(e))throw e;s=!0,o.push(ue(c))}}for(let e of n.slice(10))o.push(L(e,`a message carries at most 10`));return{attachments:i,notes:o}}async function xe(e,t,n){let{event:r,raw:i}=t,a=f(e),s=(await E({thread:r.thread})).thread??void 0;if(r.kind===`message`){let t=(await E({thread:r.subthread??r.thread,message:r.message})).message;if(t!==null){let e=(await w(t))?.origin;if(!e||e.message!==r.message||(e.subthread??e.thread)!==(r.subthread??r.thread))return}else if(a.users&&n.attempt<6&&await we(s,r))throw Error(`holding ${r.message} until ${r.author.id}'s board message there has its receipt`);let i=r.author.isBot?void 0:pe.exec(r.text)?.[1];if(i&&await Te(e,r,i))return}let c=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},l,u=()=>l??=N(a.event({id:n.id,key:e,event:r,raw:i})),d,p,h=!0,g={direction:`in`,key:e,channel:v(r.channel),get event(){return u()},board:o().client({author:c,key:e,...Ce(e,r)},n.id),import(r,i={}){if(!h)throw m(`the sync function has finished`);return d??=Se(e,t,n,s,r,i),d},permalink(){let e=p??a.permalink(u());return p=e,e.catch(()=>{p===e&&(p=void 0)}),e},resolve:t=>A(e,t)};try{await a.sync(g),await d}finally{h=!1}}async function Se(e,t,n,r,i,a){let o=i??(r?(await b(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await Ae(e,t,n,d.parse(o),M.parse(a))}catch(e){if(!(e instanceof te))throw e;let n=(await E({thread:t.event.thread})).thread;return{root:n===null?null:(await b(n))?.root??null,message:null}}}function Ce(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(v(r),`thread`,{markdown:e})}}}async function we(e,t){let n=(await E({account:t.author.id})).person;if(!n||n.method!==`consent`)return!1;let r=Date.now(),i=(e,t,i)=>e.by.principal===n.id&&e.by.profile===null&&e.from===null&&!e.deleted&&!e.noSync&&r-Date.parse(e.createdAt)<V&&!i[t]&&!e.undelivered[t];if(e!==void 0){let n=await b(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await S(n.root),o=await T(a.map(e=>e.id));return a.some(e=>r.some(t=>i(e,t,o[e.id]??{})))}let{roots:a}=await l(`syncRoots`,{by:n.id,since:r-V}),o=await T(a.map(e=>e.id));for(let e of a){let n=await b(e.thread);if(n?.targets.includes(t.channel)&&!n.external[t.channel]&&i(e,t.channel,o[e.id]??{}))return!0}return!1}async function Te(e,t,n){try{await l(`syncAccount`,{sync:e,account:t.author,code:n,message:x(t.subthread??t.thread,t.message)})}catch(e){if(W(e))return!1;throw e}return await f(e).react(v(t.subthread??t.thread),t.message,`check`,!0).catch(t=>console.warn(`[board] couldn't confirm a linking code on ${e}:`,U(t))),!0}let Ee=e=>f(e).users!==null;async function De(e,t,n){let r=f(e);if(!r.users)throw m(`${e} can't sign people in: link it with a code`);return r.users.authorize(t,n?{callback:n}:{})}async function Oe(e,t){let n=f(e);if(!n.users)throw m(`${e} can't sign people in: link it with a code`);let r=await n.users.token(t);if(!r)return null;let i=await n.whoAmI(r);if(!i.id.startsWith(`${n.platform}:user:`))throw m(`${e} named an account that isn't a ${n.platform} user`);return i}async function ke(e,t,n){let r=await Oe(e,t);return r?(await l(`syncAccount`,{sync:e,account:r,code:n,method:`consent`})).link:null}async function Ae(e,t,n,i,a){let{event:o,raw:s}=t,c=Ce(e,o),u=f(e),d=(await E({thread:o.thread})).thread??void 0,p=d?await b(d):null,h=p?.root,g=e=>ee(`board-inbound`,n.id,String(e)),_={key:e,channel:c.via.channel,thread:c.via.thread},y=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await c.respond(t);throw new te(e.dropped)},x=async({attachments:t=[],...n})=>({id:await y(await l(`syncImport`,{op:`post`,from:e,source:_,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),S=e=>({root:e.re??e.id,message:e.id}),C=(e={})=>{let t={...e,...a.labels};for(let e of a.unset??[])delete t[e];return t},T=e=>{if(!a.root)return e;let t=a.root(N(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw m(`root overrides must be synchronous`);let n=M.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},D=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:o.thread},set:n,unset:r}),O=async(e,t)=>{if(!a.labels&&!a.unset)return;let n=await w(t),r=Object.fromEntries(Object.entries(a.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),i=(a.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||i.length)&&await D(e,r,i)};switch(o.kind){case`message`:{let e=(await E({thread:o.subthread??o.thread,message:o.message})).message;if(e!==null)return{root:h??e,message:e};let{attachments:t,notes:r}=o.files?.length?await be(u,o.files,n.attempt):{attachments:[],notes:[]},c=await ye(u,o.links??[],p?.channel??i),l={...c.length?{links:c}:{},author:o.author,text:I(z(a.text??o.text,r),t.length>0),...t.length?{attachments:t}:{},origin:{channel:o.channel,thread:o.thread,subthread:o.subthread,message:o.message,context:o.context},id:await g(0)},d={...l,...a.noSync?{noSync:!0}:{}};if(h!==void 0)return await O(o.author,h),S(await x({...d,re:h}));let f=await u.fetchRoot(v(o.thread));if(f===null){let e=T({text:o.text,labels:{},author:o.author,raw:s});return S(await x({...l,channel:i,text:I(z(a.text??e.text,r),t.length>0),labels:C(e.labels)}))}let m=T({...f,raw:f.raw??null}),_=await x({author:f.author,channel:i,text:I(m.text),labels:C(m.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`root:${n.id}`,context:null},id:await g(1)});return S(await x({...d,re:_.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await E({thread:o.subthread??o.thread,message:o.message})).message;if(t===null)return{root:h??null,message:null};let n=`author`in o?o.author:{id:`${u.platform}:system`,handle:u.platform,name:u.platform,avatar:null,isBot:!0},i={thread:o.subthread??o.thread,message:o.message};if(h!==void 0&&await O(n,h),o.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:i});else if(o.kind===`edited`){let r=await w(t),s=I(z(a.text??o.text,r?de(r.text):[]),(r?.attachments.length??0)>0);r&&!o.linksOnly&&r.text!==s&&await y(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:i,text:s,source:_}));let c=r?await ye(u,o.links??[],r.channel,r.links):[];r&&!fe(c,r.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:i,links:c})}else{if(u.singleReaction){let a=r(o.emoji);for(let[r,s]of Object.entries((await w(t))?.reactions??{}))s.includes(n.id)&&!(o.added&&r===a)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:r,remove:!0})}(!u.singleReaction||o.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:o.emoji,remove:!o.added})}return{root:h??null,message:t}}case`lifecycle`:{if(h!==void 0){let t=await w(h),r=t?.origin?.message===`lifecycle:${n.id}`,i=C((r?T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}):null)?.labels??o.labels),c=e=>o.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(i).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in i)&&(o.unset.includes(e)||a.unset?.includes(e)||c(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await D(o.author,u,d),o.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===o.thread){let n=I(a.text??T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:t.labels,author:o.author,raw:s}).text);t.text!==n&&await y(await l(`syncImport`,{op:`edit`,author:o.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return o.reply?S(await x({author:o.author,re:h,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await g(1)})):{root:h,message:r?h:null}}if(!o.createRoot)return{root:null,message:null};let t=T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}),r=await x({author:o.author,channel:i,text:I(o.reply?t.text:a.text??t.text),labels:C(t.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await g(0)});return o.reply?S(await x({author:o.author,re:r.id,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await g(1)})):S(r)}}}function je(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return _(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?K(e,a.id,n)??a.name:null})}function K(e,t,n){let r=t=>{let r=n.byId[t];return r?e.mention(r):null},i=n.links[t]??t,a=r(t)??(i===t?null:r(i));if(a)return a;for(let e of O(n,i)){let t=r(e);if(t)return t}return null}async function Me(e,t,n){if(!e.users||t.from!==null||t.by.profile!==null||!t.by.principal.startsWith(`vercel:user:`))return null;if(n){let{accounts:n}=await E({person:t.by.principal});if(!n.some(t=>t.method===`consent`&&t.id.startsWith(`${e.platform}:user:`)))return null}try{return await e.users.token(t.by.principal)}catch(n){return console.warn(`[board] no ${e.platform} token for ${t.by.principal}; posting #${t.id} as the app:`,U(n)),null}}function Ne(e,t,n){let r=f(p(e)),i=je(r,t,n,re(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Le(t)}`:i}}async function Pe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Me(a,r,!0);if(!o)return null;try{return await a.postAs(o,v(t),n,Ne(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,U(t)),null}}function Fe(t,n,r,i,a){let o=i.byId[r.by.actor],s=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(s=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(s=`${s} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let c=f(t),l=(a??(e=>c.render(e)))(N({message:{...structuredClone(r),text:re(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>K(c,e,i),label:s,target:n}));if(typeof l!=`string`)throw m(`a provider renderer must return markdown`);let u=je(c,r,i,l);return r.by.profile&&!r.from?{markdown:u,as:{name:s,avatar:Ie(r,o,i)}}:{markdown:u}}function Ie(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Le=e=>e.attachments.map(t=>`\n📎 ${t.name} (${n(t.size)}, on the board: #${e.id})`).join(``),q=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=Fe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Le(t)}`:o,...s?{as:s}:{}}};async function Re(e,t,n,r,i){let a=e.replyTo===null?void 0:(await T([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,s=f(p(t)),c=q(t,e,r,!1,i),l,u;if(!c.markdown.trim()&&e.attachments.length&&s.uploadsFiles)try{let n=await Be(e,t,o);u=n.message,l=n.files}catch(n){if(!s.refusesFiles(n)&&!R(n))throw n;c=q(t,e,r,!0,i),u=(await s.post(v(o),`thread`,c)).message,l=null}else{let n=await Pe(t,o,`thread`,e,r);if(n){let r={thread:o,message:n.message,asUser:!0};return await J(e,t,r),r}if(!c.markdown.trim())throw m(`a renderer must return text unless the message has media`);u=(await s.post(v(o),`thread`,c)).message}let d={thread:o,message:u,...l===void 0?{}:{files:l}};return await J(e,t,d),d}async function J(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message,...n.asUser?{asUser:!0}:{}},...n.files===void 0?{}:{files:n.files}})}async function ze(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function Be(e,t,n){return f(p(t)).uploadFiles(v(n),await ze(e))}async function Y(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await Be(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!R(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${U(s)}`),{cause:s,data:{error:`files: ${U(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${U(s)}); noting them in its text`),o=null,await a.edit(v(n.thread),n.message,q(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,U(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let X=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Z(e,t){if(t.length!==0){if(e<5||ie(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,U(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:U(r)})}}async function Q(e,t,n,r,i){let a,o=!0,s=()=>m(`the sync function has finished`),c={direction:`out`,key:e,channel:r===null?null:j(e,r),event:N(structuredClone(t)),message:N(structuredClone(n)),target:r,deliver(e={}){if(!o)throw s();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw s();return Ve(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(c),await a}finally{o=!1}}async function Ve(e,t,n){let{target:r,kind:i}=await F(`${e}:${n}`),a=t.re===null?t:await w(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??h(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function He(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await w(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await Q(e,t,i,null,async()=>{})}catch(e){X(a,i.id,null,e)}await Z(n,a)}async function Ue(e,t){let{thread:n,target:r}=e.payload;if(!g(r)||(e.payload.kind??(u(r)?`thread`:`channel`))===`thread`)return;let i=await b(n),[a,...o]=i?await S(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await Q(p(r),e,a,r,e=>We(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function We(e,t,n,r,i,a){let o=await D(),s=await T([t.id,...n.map(e=>e.id)]),c=e=>(i.push({id:t.id,target:r,error:e}),e),l=e.external[r];if(l){let e=s[t.id]?.[r];if(e)try{await Y(t,r,e,o,a)}catch(e){throw c(e)}}else try{let e=f(p(r)),n=q(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw m(`a renderer must return text unless the message has media`);let s,c=!1,u=await(async()=>{if(!i){let i=await Pe(r,r,`channel`,t,o);return i?(c=!0,i):e.post(v(r),`channel`,n)}try{let i=await e.post(v(r),`channel`,{...n,files:await ze(t)});return s=i.files??[],i}catch(n){if(!e.refusesFiles(n)&&!R(n))throw n;return s=null,e.post(v(r),`channel`,q(r,t,o,!0,a))}})();l=`${p(r)}:${u.thread}`;let d={thread:l,message:u.message,...s===void 0?{}:{files:s},...c?{asUser:!0}:{}};await J(t,r,d),await Y(t,r,d,o,a)}catch(e){throw c(e)}let u=[];for(let e of n)if(!(e.deleted||e.noSync||Ge(e,r)))try{await Y(e,r,s[e.id]?.[r]??await Re(e,r,l,o,a),o,a)}catch(t){u.push({id:e.id,target:r,error:t})}if(i.push(...u),u.length)throw u[0].error}let Ge=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ke(e,t){if(!e.message)return;if(e.message.re===null)return He(e,t);if(e.payload.noSync)return;let n=await b(e.payload.thread);if(!n||n.targets.length===0)return;let r=await w(e.message.id);if(!r||r.deleted)return;let i=await D(),a=[];for(let t of n.targets){let o=n.external[t];if(g(t)&&o&&!Ge(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await T([r.id]))[r.id]?.[t]??await Re(r,t,o,i,e);await Y(r,t,n,i,e)})}catch(e){X(a,r.id,t,e)}}await Z(t,a)}async function qe(e){let t=(await T([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await w(e.payload.id):e.message??await w(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let i=await D();for(let[a,o]of Object.entries(t))if(!(!g(a)||e.payload.from!==null&&p(a)===e.payload.from))try{await Q(p(a),e,n,a,async t=>{let s=f(p(a)),c=v(o.thread),l=o.asUser&&(e.type===`message.edited`||e.type===`message.deleted`),u=l?await Me(s,n,!1):null;if(l&&!u){console.warn(`[board] skipped ${e.type} on ${a}: #${n.id} was posted as its author, and their token is gone`);return}e.type===`message.edited`?u?await s.editAs(u,c,o.message,Ne(a,n,i)):await s.edit(c,o.message,q(a,n,i,o.files===null,t)):e.type===`message.deleted`?(u?await s.deleteAs(u,c,o.message):await s.delete(c,o.message),o.files?.length&&await s.deleteFiles(o.files)):e.type===`reaction.added`?await s.react(c,o.message,r(e.payload.emoji),!0):await s.react(c,o.message,r(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${a}:`,t.message)}}async function Je(e){let{id:t}=e.payload,n=(await T([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await w(t);if(!i||i.deleted)return;let a=await D(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(g(t))try{await Q(p(t),e,o,t,async e=>{let n=q(t,o,a,r.files===null,e);n.markdown!==q(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(v(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,U(e))}}async function Ye(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ue(e,t);case`message.posted`:return Ke(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return qe(e);case`labels.changed`:return Je(e)}}async function Xe(e,t){let n=e[0]?await b(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(g);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(v(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,U(e)),null}}))).filter(e=>e!==null)}async function Ze(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw m(`can't resolve inbox channel: ${e.message}`)}}let $=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function Qe(e,t,n){let r=f(e);try{return await r.read(t,{limit:n})}catch(n){throw n instanceof P?new y($(n.code),n.code,n.message):m(`can't read ${t} from ${e}: ${U(n)}`,`platform_error`)}}async function $e(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof P?new y($(n.code),n.code,n.message):m(`can't join ${t} on ${e}: ${U(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Ye,receive:xe,pull:_e,normalizeTarget:F,findByEmail:ne,handleWebhook:ge,inboxChannel:Ze,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Xe,readSource:Qe,joinSource:$e}}export{G as createSyncEngine};
1
+ import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,k as h,l as g,m as _,n as v,o as y,p as b,r as x,s as ee,t as te,u as S,v as C,w,x as T,y as E}from"./app.js";const D=/^\[vc\]: #\S*\r?\n/,O=/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,ne=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function k(e){if(!D.test(e))return null;let t=e.search(/<details><summary>\d+ Skipped Deployments?<\/summary>/),n=[],r=0;for(let i of e.split(/\r?\n/)){let a=r;if(r=e.indexOf(`
2
+ `,r)+1||e.length,!i.startsWith(`|`)||/^\|\s*(Project|Name)\s*\|/.test(i)||/^\|\s*:?-/.test(i))continue;let o=i.split(`|`).slice(1,-1).map(e=>e.trim());if(o.length<2)continue;let s=ne.exec(o[1]);if(!s)continue;let c=[...o[0].matchAll(O)].at(-1),l=c?.[1]??o[0].replace(/<[^>]*>/g,``).trim();if(!l)continue;let u=o[2]?[...o[2].matchAll(O)].find(e=>/preview/i.test(e[1])):void 0,d=o[3]?.match(/<relative-time[^>]*>([^<]+)<\/relative-time>/)?.[1]?.trim()??(o[3]?.replace(/<[^>]*>/g,``).trim()||null);n.push({project:l,projectUrl:c?.[2]??null,status:s[1].trim(),deploymentUrl:s[2]??null,previewUrl:u?.[2]??null,updated:d,skipped:t!==-1&&a>t})}return n.length>0?n:null}const A={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},j=(e,t)=>t?`[${e}](${t})`:e;function re(e){return e.map(e=>`- ${[`${A[e.status.toLowerCase()]??`▲`} **${j(e.project,e.projectUrl)}**`,`${j(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[j(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
3
+ `)}function M(e,t){let n=t===`github:user:35613825`?k(e):null;return n?`**▲ Vercel**\n${re(n)}`:e}const ie=e=>e>10;i({id:e().min(1),platform:e().min(1),key:e().min(1),...g.shape,raw:w()});const N=i({root:T(e=>typeof e==`function`).optional(),text:e().optional(),labels:p.optional(),unset:E(_).optional(),noSync:c().optional()}).strict();function P(e){if(e&&typeof e==`object`){for(let t of Object.values(e))P(t);Object.freeze(e)}return e}const ae=i({target:e().min(1),channel:e().min(1),kind:C([`channel`,`thread`])}).strict();var F=class extends Error{code;constructor(e,t){super(t),this.code=e}};const oe=e=>e instanceof F&&e.code===`channel_not_found`,se=i({event:l,raw:w(),dedupe:e().min(1)}),I=/* @__PURE__ */ new WeakMap,ce=e=>I.get(e),L=(e,t=!1)=>{let n=o(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,f-1).toWellFormed()}…`:r},R=(e,n,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${t(e.size)})`} didn't come over: ${n}`,z=e=>e instanceof m&&e.code===`no_files`,le=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return R(e,`this board keeps no files`);let n=t.replace(/[[\]\\]/g,e=>`\\${e}`),r=e.url.replace(/[ ()]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`);return R(e,`this board keeps no files`,`[${n}](${r})`)},B=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
4
+ `)}`:e,ue=e=>e.split(`
5
+ `).filter(e=>/^📎 .* didn't come over: /u.test(e)),V=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),de=(e,t)=>e.length===t.length&&e.every((e,n)=>V(e)===V(t[n])),fe=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,H=6e4,pe=new AbortController().signal,me=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},U=e=>typeof e==`string`?o(e):Array.isArray(e)?e.map(U):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[o(e),U(t)])):e,W=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return o(n.slice(0,500))||`unknown error`},G=e=>e instanceof m&&e.status===404;function he(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=pe,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw s(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),g=t=>e[p(t)]!==void 0,_=e=>e.slice(e.indexOf(`:`)+1);async function C(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(G(e))return null;throw e}}async function w(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(G(e))return[];throw e}}async function T(e){return(await w(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>l(`syncFind`,e,t);async function O(){let{actors:e}=await l(`actors`,{}),t={byId:{},links:{}};for(let{person:n,...r}of e)t.byId[r.id]={id:r.id,kind:r.kind,handle:r.handle,name:r.name,avatar:r.avatar},n&&(t.links[r.id]=n.id);return t}let ne=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),k=/* @__PURE__ */ new Map;function A(e,t){let n=`${e}:${t}`,r=k.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=oe(r);!a&&k.get(n)===i&&k.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),k.set(n,i),i.id}function j(e,t){if(!d(t))return _(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:_(n)}catch{return null}}async function re(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function I(e){let t=e.indexOf(`:`);if(t<=0)throw s(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ae.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw s(`can't sync to ${e}: ${t.message}`)}}async function V(e,t){let{event:n,raw:r,dedupe:i}=se.parse(U({...t,raw:me(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw s(`provider locations must belong to sync entry ${e}`);let a=ce(f(e));(!a||await a(_(n.channel),t=>A(e,t))||await he(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function he(e){try{return(await D({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function ge(t,n){let r=e[t];if(!r)throw y(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>V(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}let a=_e([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,W(e)));return x(a),i}async function _e(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>ve(e,t))),n+=t.length}return n}async function ve(e,t){let n;try{await xe(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,W(r)),n=W(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,W(e)))}async function ye(e,t,n,r=[]){if(t.length===0)return[];let i=[];for(let a of t){if(i.some(e=>e.url===a.url))continue;let t=(await D({thread:a.thread,message:a.message})).message,o=t===null?null:await T(t);if(o&&!o.deleted&&o.channel===n){i.push({url:a.url,message:o.id,unavailable:null,snapshot:{author:o.author?.name??o.by.actor,text:o.text,createdAt:o.createdAt,files:o.attachments.map(e=>({name:e.name,url:null}))}});continue}if(a.snapshot){i.push({url:a.url,message:null,snapshot:a.snapshot,unavailable:null});continue}let s=r.find(e=>e.url===a.url&&e.snapshot&&e.message===null);if(s){i.push(s);continue}let c=await e.fetchLink(a).catch(e=>({snapshot:null,unavailable:W(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return U(i)}async function be(e,t,i){let o=[],s=[],c=!1;for(let u of t.slice(0,10)){if(c){s.push(le(u));continue}if(u.size!==void 0&&u.size>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}let t;try{let n=await e.fetchFile(u);if(n===null){s.push(R(u,`${e.platform} can't hand it over`));continue}t=n}catch(t){if(i<3)throw t;console.warn(`[board] couldn't fetch ${u.name??`a file`} from ${e.platform}:`,W(t)),s.push(R(u,`${e.platform} wouldn't hand it over`));continue}if(t.byteLength>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}if(t.byteLength===0){s.push(R(u,`it was empty`));continue}let d=u.name??`${u.type}`;try{o.push((await l(`upload`,{data:t,name:d,type:u.mimeType||r(d)})).attachment)}catch(e){if(e instanceof m&&(e.code===`too_large`||e.code===`no_direct_uploads`)){s.push(R(u,`over ${a/1024/1024} MB`));continue}if(!z(e))throw e;c=!0,s.push(le(u))}}for(let e of t.slice(10))s.push(R(e,`a message carries at most 10`));return{attachments:o,notes:s}}async function xe(e,t,n){let{event:r,raw:i}=t,a=f(e),c=(await D({thread:r.thread})).thread??void 0;if(r.kind===`message`){let t=(await D({thread:r.subthread??r.thread,message:r.message})).message;if(t!==null){let e=(await T(t))?.origin;if(!e||e.message!==r.message||(e.subthread??e.thread)!==(r.subthread??r.thread))return}else if(a.users&&n.attempt<6&&await we(c,r))throw Error(`holding ${r.message} until ${r.author.id}'s board message there has its receipt`);let i=r.author.isBot?void 0:fe.exec(r.text)?.[1];if(i&&await Te(e,r,i))return}let l=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},u,d=()=>u??=P(a.event({id:n.id,key:e,event:r,raw:i})),p,m,h=!0,g={direction:`in`,key:e,channel:_(r.channel),get event(){return d()},board:o().client({author:l,key:e,...Ce(e,r)},n.id),import(r,i={}){if(!h)throw s(`the sync function has finished`);return p??=Se(e,t,n,c,r,i),p},permalink(){let e=m??a.permalink(d());return m=e,e.catch(()=>{m===e&&(m=void 0)}),e},resolve:t=>A(e,t)};try{await a.sync(g),await p}finally{h=!1}}async function Se(e,t,n,r,i,a){let o=i??(r?(await C(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await Ae(e,t,n,b.parse(o),N.parse(a))}catch(e){if(!(e instanceof te))throw e;let n=(await D({thread:t.event.thread})).thread;return{root:n===null?null:(await C(n))?.root??null,message:null}}}function Ce(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(_(r),`thread`,{markdown:e})}}}async function we(e,t){let n=(await D({account:t.author.id})).person;if(!n||n.method!==`consent`)return!1;let r=Date.now(),i=(e,t,i)=>e.by.principal===n.id&&e.by.profile===null&&e.from===null&&!e.deleted&&!e.noSync&&r-Date.parse(e.createdAt)<H&&!i[t]&&!e.undelivered[t];if(e!==void 0){let n=await C(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await w(n.root),o=await E(a.map(e=>e.id));return a.some(e=>r.some(t=>i(e,t,o[e.id]??{})))}let{roots:a}=await l(`syncRoots`,{by:n.id,since:r-H}),o=await E(a.map(e=>e.id));for(let e of a){let n=await C(e.thread);if(n?.targets.includes(t.channel)&&!n.external[t.channel]&&i(e,t.channel,o[e.id]??{}))return!0}return!1}async function Te(e,t,n){try{await l(`syncAccount`,{sync:e,account:t.author,code:n,message:u(t.subthread??t.thread,t.message)})}catch(e){if(G(e))return!1;throw e}return await f(e).react(_(t.subthread??t.thread),t.message,`check`,!0).catch(t=>console.warn(`[board] couldn't confirm a linking code on ${e}:`,W(t))),!0}let Ee=e=>f(e).users!==null;async function De(e,t,n){let r=f(e);if(!r.users)throw s(`${e} can't sign people in: link it with a code`);return r.users.authorize(t,n?{callback:n}:{})}async function Oe(e,t){let n=f(e);if(!n.users)throw s(`${e} can't sign people in: link it with a code`);let r=await n.users.token(t);if(!r)return null;let i=await n.whoAmI(r);if(!i.id.startsWith(`${n.platform}:user:`))throw s(`${e} named an account that isn't a ${n.platform} user`);return i}async function ke(e,t,n){let r=await Oe(e,t);return r?(await l(`syncAccount`,{sync:e,account:r,code:n,method:`consent`})).link:null}async function Ae(e,t,n,r,i){let{event:a,raw:o}=t,c=Ce(e,a),u=f(e),d=(await D({thread:a.thread})).thread??void 0,p=d?await C(d):null,m=p?.root,g=e=>ee(`board-inbound`,n.id,String(e)),v={key:e,channel:c.via.channel,thread:c.via.thread},y=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await c.respond(t);throw new te(e.dropped)},b=async({attachments:t=[],...n})=>({id:await y(await l(`syncImport`,{op:`post`,from:e,source:v,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),x=e=>({root:e.re??e.id,message:e.id}),S=(e={})=>{let t={...e,...i.labels};for(let e of i.unset??[])delete t[e];return t},w=e=>{if(!i.root)return e;let t=i.root(P(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw s(`root overrides must be synchronous`);let n=N.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},E=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:a.thread},set:n,unset:r}),O=async(e,t)=>{if(!i.labels&&!i.unset)return;let n=await T(t),r=Object.fromEntries(Object.entries(i.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),a=(i.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||a.length)&&await E(e,r,a)};switch(a.kind){case`message`:{let e=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(e!==null)return{root:m??e,message:e};let{attachments:t,notes:s}=a.files?.length?await be(u,a.files,n.attempt):{attachments:[],notes:[]},c=await ye(u,a.links??[],p?.channel??r),l={...c.length?{links:c}:{},author:a.author,text:L(B(i.text??a.text,s),t.length>0),...t.length?{attachments:t}:{},origin:{channel:a.channel,thread:a.thread,subthread:a.subthread,message:a.message,context:a.context},id:await g(0)},d={...l,...i.noSync?{noSync:!0}:{}};if(m!==void 0)return await O(a.author,m),x(await b({...d,re:m}));let f=await u.fetchRoot(_(a.thread));if(f===null){let e=w({text:a.text,labels:{},author:a.author,raw:o});return x(await b({...l,channel:r,text:L(B(i.text??e.text,s),t.length>0),labels:S(e.labels)}))}let h=w({...f,raw:f.raw??null}),v=await b({author:f.author,channel:r,text:L(h.text),labels:S(h.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`root:${n.id}`,context:null},id:await g(1)});return x(await b({...d,re:v.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(t===null)return{root:m??null,message:null};let n=`author`in a?a.author:{id:`${u.platform}:system`,handle:u.platform,name:u.platform,avatar:null,isBot:!0},r={thread:a.subthread??a.thread,message:a.message};if(m!==void 0&&await O(n,m),a.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:r});else if(a.kind===`edited`){let o=await T(t),s=L(B(i.text??a.text,o?ue(o.text):[]),(o?.attachments.length??0)>0);o&&!a.linksOnly&&o.text!==s&&await y(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:r,text:s,source:v}));let c=o?await ye(u,a.links??[],o.channel,o.links):[];o&&!de(c,o.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:r,links:c})}else{if(u.singleReaction){let i=h(a.emoji);for(let[o,s]of Object.entries((await T(t))?.reactions??{}))s.includes(n.id)&&!(a.added&&o===i)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:o,remove:!0})}(!u.singleReaction||a.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:a.emoji,remove:!a.added})}return{root:m??null,message:t}}case`lifecycle`:{if(m!==void 0){let t=await T(m),r=t?.origin?.message===`lifecycle:${n.id}`,s=S((r?w({text:a.url?`${a.title}\n${a.url}`:a.title,labels:a.labels,author:a.author,raw:o}):null)?.labels??a.labels),c=e=>a.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(s).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in s)&&(a.unset.includes(e)||i.unset?.includes(e)||c(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await E(a.author,u,d),a.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===a.thread){let n=L(i.text??w({text:a.url?`${a.title}\n${a.url}`:a.title,labels:t.labels,author:a.author,raw:o}).text);t.text!==n&&await y(await l(`syncImport`,{op:`edit`,author:a.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return a.reply?x(await b({author:a.author,re:m,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):{root:m,message:r?m:null}}if(!a.createRoot)return{root:null,message:null};let t=w({text:a.url?`${a.title}\n${a.url}`:a.title,labels:a.labels,author:a.author,raw:o}),s=await b({author:a.author,channel:r,text:L(a.reply?t.text:i.text??t.text),labels:S(t.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await g(0)});return a.reply?x(await b({author:a.author,re:s.id,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):x(s)}}}function je(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return v(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?K(e,a.id,n)??a.name:null})}function K(e,t,n){let r=t=>{let r=n.byId[t];return r?e.mention(r):null},i=n.links[t]??t,a=r(t)??(i===t?null:r(i));if(a)return a;for(let e of ne(n,i)){let t=r(e);if(t)return t}return null}async function Me(e,t,n){if(!e.users||t.from!==null||t.by.profile!==null||!t.by.principal.startsWith(`vercel:user:`))return null;if(n){let{accounts:n}=await D({person:t.by.principal});if(!n.some(t=>t.method===`consent`&&t.id.startsWith(`${e.platform}:user:`)))return null}try{return await e.users.token(t.by.principal)}catch(n){return console.warn(`[board] no ${e.platform} token for ${t.by.principal}; posting #${t.id} as the app:`,W(n)),null}}function Ne(e,t,n){let r=f(p(e)),i=je(r,t,n,M(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Le(t)}`:i}}async function Pe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Me(a,r,!0);if(!o)return null;try{return await a.postAs(o,_(t),n,Ne(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,W(t)),null}}function Fe(t,n,r,i,a){let o=i.byId[r.by.actor],c=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(c=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(c=`${c} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let l=f(t),u=(a??(e=>l.render(e)))(P({message:{...structuredClone(r),text:M(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>K(l,e,i),label:c,target:n}));if(typeof u!=`string`)throw s(`a provider renderer must return markdown`);let d=je(l,r,i,u);return r.by.profile&&!r.from?{markdown:d,as:{name:c,avatar:Ie(r,o,i)}}:{markdown:d}}function Ie(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Le=e=>e.attachments.map(n=>`\n📎 ${n.name} (${t(n.size)}, on the board: #${e.id})`).join(``),q=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=Fe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Le(t)}`:o,...s?{as:s}:{}}};async function Re(e,t,n,r,i){let a=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,c=f(p(t)),l=q(t,e,r,!1,i),u,d;if(!l.markdown.trim()&&e.attachments.length&&c.uploadsFiles)try{let n=await Be(e,t,o);d=n.message,u=n.files}catch(n){if(!c.refusesFiles(n)&&!z(n))throw n;l=q(t,e,r,!0,i),d=(await c.post(_(o),`thread`,l)).message,u=null}else{let n=await Pe(t,o,`thread`,e,r);if(n){let r={thread:o,message:n.message,asUser:!0};return await J(e,t,r),r}if(!l.markdown.trim())throw s(`a renderer must return text unless the message has media`);d=(await c.post(_(o),`thread`,l)).message}let m={thread:o,message:d,...u===void 0?{}:{files:u}};return await J(e,t,m),m}async function J(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message,...n.asUser?{asUser:!0}:{}},...n.files===void 0?{}:{files:n.files}})}async function ze(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function Be(e,t,n){return f(p(t)).uploadFiles(_(n),await ze(e))}async function Y(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await Be(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!z(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${W(s)}`),{cause:s,data:{error:`files: ${W(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${W(s)}); noting them in its text`),o=null,await a.edit(_(n.thread),n.message,q(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,W(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let X=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Z(e,t){if(t.length!==0){if(e<5||ie(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,W(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:W(r)})}}async function Q(e,t,n,r,i){let a,o=!0,c=()=>s(`the sync function has finished`),l={direction:`out`,key:e,channel:r===null?null:j(e,r),event:P(structuredClone(t)),message:P(structuredClone(n)),target:r,deliver(e={}){if(!o)throw c();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw c();return Ve(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function Ve(e,t,n){let{target:r,kind:i}=await I(`${e}:${n}`),a=t.re===null?t:await T(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??S(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function He(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await T(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await Q(e,t,i,null,async()=>{})}catch(e){X(a,i.id,null,e)}await Z(n,a)}async function Ue(e,t){let{thread:n,target:r}=e.payload;if(!g(r)||(e.payload.kind??(d(r)?`thread`:`channel`))===`thread`)return;let i=await C(n),[a,...o]=i?await w(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await Q(p(r),e,a,r,e=>We(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function We(e,t,n,r,i,a){let o=await O(),c=await E([t.id,...n.map(e=>e.id)]),l=e=>(i.push({id:t.id,target:r,error:e}),e),u=e.external[r];if(u){let e=c[t.id]?.[r];if(e)try{await Y(t,r,e,o,a)}catch(e){throw l(e)}}else try{let e=f(p(r)),n=q(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw s(`a renderer must return text unless the message has media`);let c,l=!1,d=await(async()=>{if(!i){let i=await Pe(r,r,`channel`,t,o);return i?(l=!0,i):e.post(_(r),`channel`,n)}try{let i=await e.post(_(r),`channel`,{...n,files:await ze(t)});return c=i.files??[],i}catch(n){if(!e.refusesFiles(n)&&!z(n))throw n;return c=null,e.post(_(r),`channel`,q(r,t,o,!0,a))}})();u=`${p(r)}:${d.thread}`;let m={thread:u,message:d.message,...c===void 0?{}:{files:c},...l?{asUser:!0}:{}};await J(t,r,m),await Y(t,r,m,o,a)}catch(e){throw l(e)}let d=[];for(let e of n)if(!(e.deleted||e.noSync||Ge(e,r)))try{await Y(e,r,c[e.id]?.[r]??await Re(e,r,u,o,a),o,a)}catch(t){d.push({id:e.id,target:r,error:t})}if(i.push(...d),d.length)throw d[0].error}let Ge=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ke(e,t){if(!e.message)return;if(e.message.re===null)return He(e,t);if(e.payload.noSync)return;let n=await C(e.payload.thread);if(!n||n.targets.length===0)return;let r=await T(e.message.id);if(!r||r.deleted)return;let i=await O(),a=[];for(let t of n.targets){let o=n.external[t];if(g(t)&&o&&!Ge(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await Re(r,t,o,i,e);await Y(r,t,n,i,e)})}catch(e){X(a,r.id,t,e)}}await Z(t,a)}async function qe(e){let t=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await T(e.payload.id):e.message??await T(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let r=await O();for(let[i,a]of Object.entries(t))if(!(!g(i)||e.payload.from!==null&&p(i)===e.payload.from))try{await Q(p(i),e,n,i,async t=>{let o=f(p(i)),s=_(a.thread),c=a.asUser&&(e.type===`message.edited`||e.type===`message.deleted`),l=c?await Me(o,n,!1):null;if(c&&!l){console.warn(`[board] skipped ${e.type} on ${i}: #${n.id} was posted as its author, and their token is gone`);return}e.type===`message.edited`?l?await o.editAs(l,s,a.message,Ne(i,n,r)):await o.edit(s,a.message,q(i,n,r,a.files===null,t)):e.type===`message.deleted`?(l?await o.deleteAs(l,s,a.message):await o.delete(s,a.message),a.files?.length&&await o.deleteFiles(a.files)):e.type===`reaction.added`?await o.react(s,a.message,h(e.payload.emoji),!0):await o.react(s,a.message,h(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${i}:`,t.message)}}async function Je(e){let{id:t}=e.payload,n=(await E([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await T(t);if(!i||i.deleted)return;let a=await O(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(g(t))try{await Q(p(t),e,o,t,async e=>{let n=q(t,o,a,r.files===null,e);n.markdown!==q(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(_(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,W(e))}}async function Ye(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ue(e,t);case`message.posted`:return Ke(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return qe(e);case`labels.changed`:return Je(e)}}async function Xe(e,t){let n=e[0]?await C(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(g);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(_(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,W(e)),null}}))).filter(e=>e!==null)}async function Ze(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw s(`can't resolve inbox channel: ${e.message}`)}}let $=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function Qe(e,t,n){let r=f(e);try{return await r.read(t,{limit:n})}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't read ${t} from ${e}: ${W(n)}`,`platform_error`)}}async function $e(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't join ${t} on ${e}: ${W(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Ye,receive:xe,pull:_e,normalizeTarget:I,findByEmail:re,handleWebhook:ge,inboxChannel:Ze,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Xe,readSource:Qe,joinSource:$e}}export{he as createSyncEngine};
@@ -1,14 +1,14 @@
1
1
  import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { a as AttachmentId } from "./schemas-Be08nyfW.mjs";
3
- import { d as VERSION, i as withMentions } from "./watchers-hash-BfDPW95j.mjs";
3
+ import { d as VERSION, i as withMentions } from "./watchers-hash-C10qKYPa.mjs";
4
4
  import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
5
5
  import { t as BoardError } from "./client-D0Px7KzF.mjs";
6
6
  import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-nEG47RM_.mjs";
7
7
  import { i as handLogin, l as vercelProjectFile, n as connect, t as boardUrl } from "./node-Ybi9E6bO.mjs";
8
- import { t as http } from "./backend-http-F0W6e36G.mjs";
9
- import { t as board } from "./server-Bphio_82.mjs";
8
+ import { t as http } from "./backend-http-CDu_eapz.mjs";
9
+ import { t as board } from "./server-DMNfPSAZ.mjs";
10
10
  import { t as conformance } from "./conformance-DlnfuzEB.mjs";
11
- import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-DUWIcFi9.mjs";
11
+ import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-QuUJ3dW_.mjs";
12
12
  import { t as displayText } from "./bots-CGkO76qH.mjs";
13
13
  import { spawn } from "node:child_process";
14
14
  import { readFile, stat, writeFile } from "node:fs/promises";
@@ -1073,6 +1073,11 @@ function failureOf(error) {
1073
1073
  ...own("limit", "payload_too_large"),
1074
1074
  message: `Can't ${doing()}. ${sentence(message)}`
1075
1075
  };
1076
+ case "no_team": return {
1077
+ ...own("platform", "api_error"),
1078
+ message: "Couldn't tell which team's board to use.",
1079
+ details: [message]
1080
+ };
1076
1081
  case "upload_failed": return {
1077
1082
  ...own("platform", "upload_failed"),
1078
1083
  message: `Failed to ${doing()}.`,
@@ -438,7 +438,9 @@ const schedulesContract = contract({
438
438
  n: z.number().int().nonnegative(),
439
439
  message: MessageId,
440
440
  next: Millis.nullable()
441
- })
441
+ }),
442
+ /** (server) The Vercel deployment `deployment` took over every send: an earlier one's timers no longer post. */
443
+ "schedule.takeover": z.object({ deployment: z.string().min(1) })
442
444
  }
443
445
  });
444
446
  const SCHEDULES_SESSION = "schedules";
@@ -1,8 +1,8 @@
1
1
  import { t as BoardError } from "./client-D0Px7KzF.mjs";
2
2
  import { a as localTokenFile } from "./node-Ybi9E6bO.mjs";
3
- import { t as board } from "./server-Bphio_82.mjs";
3
+ import { t as board } from "./server-DMNfPSAZ.mjs";
4
4
  import { t as oidcTokens } from "./tokens-BuCU-61R.mjs";
5
- import { n as fsBlob, t as a2 } from "./a2-Qzj0kCWU.mjs";
5
+ import { n as fsBlob, t as a2 } from "./a2-D6TOZNV2.mjs";
6
6
  import { t as localScheduler } from "./local-DhkrAqPP.mjs";
7
7
  import { mkdir, rm, writeFile } from "node:fs/promises";
8
8
  import { userInfo } from "node:os";
@@ -1,5 +1,5 @@
1
- import { t as createBoard } from "./exports-NFjBgIT2.mjs";
2
- import { t as http } from "./backend-http-F0W6e36G.mjs";
1
+ import { t as createBoard } from "./exports-T-ART7P7.mjs";
2
+ import { t as http } from "./backend-http-CDu_eapz.mjs";
3
3
  //#region src/server/index.ts
4
4
  /**
5
5
  * The handle on a board. With a `backend`, the board lives there: `a2()` keeps it in this
@@ -9,7 +9,7 @@ import { t as http } from "./backend-http-F0W6e36G.mjs";
9
9
  function board(options = {}) {
10
10
  return createBoard(options, {
11
11
  http: () => http(),
12
- sync: () => import("./engine-ROa7-IQY.mjs"),
12
+ sync: () => import("./engine-Bfe6pcXb.mjs"),
13
13
  serving: () => import("./serving-CvOWEym2.mjs")
14
14
  });
15
15
  }
@@ -132,12 +132,13 @@ var LinearProvider = class extends ChatSyncProvider {
132
132
  if (payload.type !== "Issue" || !payload.data?.identifier) return [];
133
133
  const { data } = payload;
134
134
  const created = payload.action === "create";
135
- const moved = payload.action === "update" && payload.updatedFrom?.stateId !== void 0;
135
+ if (!created && payload.action !== "update") return [];
136
136
  const retitled = payload.action === "update" && payload.updatedFrom?.title !== void 0;
137
- if (!created && !moved && !retitled) return [];
138
137
  const team = data.team?.key ?? teamOf(data.identifier);
139
138
  const state = stateOf(data.state);
140
- const own = payload.actor?.id !== void 0 && payload.actor.id === adapter.defaultBotUserId;
139
+ const bot = adapter.defaultBotUserId;
140
+ const own = payload.actor?.id !== void 0 && payload.actor.id === bot;
141
+ const opens = created ? !own : !own && (bot === null || data.creatorId !== bot) && payload.actor?.type === "user" && !data.archivedAt;
141
142
  const delivery = headers.get("linear-delivery") ?? `${payload.webhookId}:${payload.webhookTimestamp}`;
142
143
  return [{
143
144
  event: {
@@ -149,11 +150,11 @@ var LinearProvider = class extends ChatSyncProvider {
149
150
  url: data.url ?? payload.url ?? null,
150
151
  labels: {
151
152
  issue: data.identifier,
152
- ...state && (created || moved) ? { state } : {}
153
+ ...state ? { state } : {}
153
154
  },
154
155
  unset: [],
155
156
  reply: null,
156
- createRoot: created && !own,
157
+ createRoot: opens,
157
158
  ...retitled ? { retitled: true } : {},
158
159
  author: author(payload.actor, own)
159
160
  },
@@ -28,4 +28,15 @@ const keyId = async (principal, derived) => `key:${principal}:${(await sha256(`b
28
28
  */
29
29
  const isKeyIdOf = (id, principal) => id.startsWith(`key:${principal}:`) && /^[0-9a-f]{32}$/.test(id.slice(`key:${principal}:`.length));
30
30
  //#endregion
31
- export { sha256 as a, keyId as i, derivedId as n, isKeyIdOf as r, DERIVED_ID as t };
31
+ //#region src/server/wait-until.ts
32
+ /**
33
+ * Keeps `task` running after the response, where the platform offers it: Vercel's request context, as A2
34
+ * reads it too. Elsewhere it runs on in the process.
35
+ */
36
+ function waitUntil(task) {
37
+ try {
38
+ Reflect.get(globalThis, Symbol.for("@vercel/request-context"))?.get?.()?.waitUntil?.(task);
39
+ } catch {}
40
+ }
41
+ //#endregion
42
+ export { keyId as a, isKeyIdOf as i, DERIVED_ID as n, sha256 as o, derivedId as r, waitUntil as t };
@@ -662,7 +662,7 @@ const routes = {
662
662
  const SYNC = "@sync";
663
663
  //#endregion
664
664
  //#region src/version.ts
665
- const VERSION = "0.26.0";
665
+ const VERSION = "0.26.2";
666
666
  //#endregion
667
667
  //#region src/server/routing.ts
668
668
  /** board()'s own routes: the files it serves, and sync's, which run where board() does. */
@@ -209,8 +209,10 @@ export const GET = team.fetch, POST = team.fetch
209
209
  from.
210
210
  - **A match wakes it with nothing in the wake-up:** a `POST <url>/wake` with
211
211
  no event. It then pulls what matched with its own credentials, runs it, and
212
- acks it, until nothing's left, so a burst of matches wakes it once. A
213
- forged wake-up costs it one pull that finds nothing.
212
+ acks it, until nothing's left, so a burst of matches wakes it once. It
213
+ answers when that's done, or after 2 seconds while it keeps going, so
214
+ whoever woke it never waits on slow watchers. A forged wake-up costs it one
215
+ pull that finds nothing.
214
216
  - **A wake-up that doesn't get there is tried again** a minute or so on. While
215
217
  they keep failing, the wait doubles, up to an hour, and the other board logs
216
218
  the first failure and the start of the back-off. It starts over when this
@@ -123,11 +123,18 @@ await team.cancelSchedule(reminder.id) // { schedule, canceled }
123
123
  out arrives in hops of up to 5.
124
124
  - **Canceling can't revoke a timer.** A canceled schedule's timer, or one for a
125
125
  send already made, fires into a no-op.
126
- - **A deploy that's new to schedules arms them again.** Vercel Queues delivers a
127
- timer to the deployment that armed it, so the first deploy on A2's consumers
128
- arms every pending schedule's next send itself, on its first request. If the
129
- earlier deployment is still up, its timer fires too, and whichever comes
130
- second is a no-op.
126
+ - **The newest deployment sends.** Vercel Queues delivers a timer to the
127
+ deployment that armed it, so each deployment takes schedules over on its
128
+ first request: it arms every pending schedule's next send itself, and the
129
+ timers an earlier deployment armed fire into a no-op. A queue's delivery
130
+ never takes schedules over, so an old deployment that only its own timers
131
+ still reach doesn't take them back. With a scheduler that delivers to one URL, like
132
+ `qstash()` pointed at the production alias, an earlier deployment's timers
133
+ reach the newest one, which sends them once it has taken over. Each send runs under
134
+ the access rules of the deployment that took over last, so a deploy that
135
+ takes a grant away stops the schedules that grant was sending. Deployments
136
+ that share one store, like a preview on production's database, take
137
+ schedules over from each other.
131
138
 
132
139
  Schedules live in their own A2 session, outside the board log; only the messages
133
140
  they post land on the board. See [The log](../04-reference/04-log.md).
@@ -52,7 +52,9 @@ in the map stays off the board, except threads someone syncs by hand.
52
52
  thread on Linear, and a board reply to one goes back into it. Edits and
53
53
  deletes sync too.
54
54
  3. **Moving the issue** to another state updates `state`. A new title edits a
55
- root that Linear opened to say it.
55
+ root that Linear opened to say it. An issue that was open before the mirror
56
+ gets its root the first time a person edits or comments on it. Linear's own
57
+ automations and archiving don't bring old issues in.
56
58
  4. **A root posted on the board** in a mirrored channel opens an issue in that
57
59
  team. Its first line is the title, and the whole message is the description.
58
60
  Editing the root edits the description. Deleting it on the board leaves the
@@ -78,7 +80,7 @@ the board follows the team it's in now. The old thread stays as it was.
78
80
  | Event | What the mirror does |
79
81
  | --- | --- |
80
82
  | `issue.created` | A root labeled `issue=ENG-123` and `state=<type>` |
81
- | `issue.updated` | A new state sets `state`; a new title edits a root Linear opened. Other changes write nothing |
83
+ | `issue.updated` | A new state sets `state`; a new title edits a root Linear opened. A person's edit to an issue the board hasn't seen opens its root, so older issues come in when they're next touched (a bulk edit brings in a whole view). Automations and archiving don't |
82
84
  | `comment` | A reply. Edits and deletes sync too |
83
85
 
84
86
  `state` is the workflow state's type: `triage`, `backlog`, `unstarted`,
@@ -49,7 +49,8 @@ const code = await createApp({
49
49
  onWarning: (warning) => console.error(warning.message),
50
50
  // Your Vercel login, and the team whose board to use when --scope names none, as this CLI
51
51
  // resolves its own: asked for only when a request needs them. Help, a typo, or misuse never asks.
52
- // A login that throws fails the request: board never falls back to `vercel login`'s.
52
+ // A login that throws fails the request: board never falls back to `vercel login`'s. A team
53
+ // that throws fails it too, with what it threw as the failure's details.
53
54
  login: async () => process.env.VERCEL_AUTH_TOKEN!,
54
55
  team: async () => process.env.VERCEL_ORG_ID ?? null,
55
56
  }).run(process.argv.slice(3))
@@ -122,7 +122,7 @@ a refused write it can read answers 403. `GET /me` returns the caller's
122
122
  | GET | `/watchers` | `{ watchers }`: the caller's registration, or every one for `manage` in every channel |
123
123
  | GET | `/watchers/matches` | `{ matches }`: the caller's matches it can run now, one thread at a time and in order, each `{ id, watcher, thread, lease, attempt, event }` and leased to it for 5 minutes. Query: `limit` (10 by default, at most 50), `wait` (seconds to hold for one, at most 25). 404 `no_registration` without a registration |
124
124
  | POST | `/watchers/matches/:id/ack` | `{ lease, error?, skip? }`: done, an error to try again after a wait (after 5 tries it's `watch.failed`), or `skip` to drop it. 404 `no_match`, 409 `stale_lease` for a lease that ran out |
125
- | POST | `/wake` | On a board on `http()` (no token): it has matches to pull. Answers `{ ran }` |
125
+ | POST | `/wake` | On a board on `http()` (no token): it has matches to pull. Answers `{ ran }` once they've run, or 202 after 2 seconds while they keep running |
126
126
  | GET | `/inbox/:key` | `{ events }`: raw platform events for a sync entry, newest first. Query: `channel`, `limit` (50 by default, at most 500). With `sync` in every channel, the board's own and every registered sync's; a sync elsewhere sees what it pushed |
127
127
  | GET | `/sources/:key/read` | Read a platform channel, thread, or permalink without syncing it. Query: `target`, `limit` (50 by default, at most 200). The board reads it with entry `key`'s app, through the sync it runs, and needs `manage` in every channel; a board that runs no entry `key` answers 404 `not_synced_here` |
128
128
  | POST | `/sources/:key/join` | Add entry `key`'s app to a public channel so `/sources/:key/read` can read it. Body: `{ target }`, a channel (`#name`, an ID) or a link into it. Answers `{ channel, name, joined }`, `joined: false` when the app was already in. Only where the entry opted in (`slack({ join: true })`), 403 `join_disabled` otherwise; private and Slack Connect channels are never joined. Needs `manage` in every channel; a board that runs no entry `key` answers 404 `not_synced_here` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/board",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "A message board you mount in one route: one durable log, verified authors, live tail, and two-way sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",