@vercel/board 0.0.0-stage → 0.15.0

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 (126) hide show
  1. package/README.md +179 -2
  2. package/dist/a2-5EG8FkZ8.d.mts +3359 -0
  3. package/dist/a2-DYGYankn.mjs +3045 -0
  4. package/dist/access-CJU2-vaa.mjs +226 -0
  5. package/dist/access-CV96SYgr.d.mts +32 -0
  6. package/dist/access.d.mts +29 -0
  7. package/dist/access.mjs +4 -0
  8. package/dist/actors-rdN4dDTU.mjs +223 -0
  9. package/dist/attempts-BQUp9MKO.mjs +919 -0
  10. package/dist/auth-COzc09sn.mjs +276 -0
  11. package/dist/auth-wHFcAte3.d.mts +203 -0
  12. package/dist/backend-a2/blob-vercel.d.mts +8 -0
  13. package/dist/backend-a2/blob-vercel.mjs +81 -0
  14. package/dist/backend-a2/contract.d.mts +18 -0
  15. package/dist/backend-a2/contract.mjs +5 -0
  16. package/dist/backend-a2/scheduler-local.d.mts +11 -0
  17. package/dist/backend-a2/scheduler-local.mjs +2 -0
  18. package/dist/backend-a2/scheduler-qstash.d.mts +2 -0
  19. package/dist/backend-a2/scheduler-qstash.mjs +2 -0
  20. package/dist/backend-a2/scheduler-vercel.d.mts +2 -0
  21. package/dist/backend-a2/scheduler-vercel.mjs +2 -0
  22. package/dist/backend-a2/store-memory.d.mts +11 -0
  23. package/dist/backend-a2/store-memory.mjs +19 -0
  24. package/dist/backend-a2/store-postgres.d.mts +38 -0
  25. package/dist/backend-a2/store-postgres.mjs +110 -0
  26. package/dist/backend-a2/store-redis-http.d.mts +17 -0
  27. package/dist/backend-a2/store-redis-http.mjs +32 -0
  28. package/dist/backend-a2/store-redis.d.mts +23 -0
  29. package/dist/backend-a2/store-redis.mjs +39 -0
  30. package/dist/backend-a2/store-sqlite.d.mts +12 -0
  31. package/dist/backend-a2/store-sqlite.mjs +31 -0
  32. package/dist/backend-a2.d.mts +4 -0
  33. package/dist/backend-a2.mjs +2 -0
  34. package/dist/backend-http-8ex5g7Wv.d.mts +630 -0
  35. package/dist/backend-http-BsDSgmeo.mjs +613 -0
  36. package/dist/backend-http-Cv6xbEy9.mjs +469 -0
  37. package/dist/backend-http.d.mts +10 -0
  38. package/dist/backend-http.mjs +2 -0
  39. package/dist/backend-http.node.d.mts +10 -0
  40. package/dist/backend-http.node.mjs +2 -0
  41. package/dist/blob-C0J3wbGv.d.mts +39 -0
  42. package/dist/bots-CGkO76qH.mjs +67 -0
  43. package/dist/chat-provider-XCnQlxq6.d.mts +140 -0
  44. package/dist/chat-provider-jFSeyNmB.mjs +389 -0
  45. package/dist/cli.d.mts +1 -0
  46. package/dist/cli.mjs +2252 -0
  47. package/dist/client-DGDPaN7U.mjs +380 -0
  48. package/dist/conformance-DRR23YNy.mjs +512 -0
  49. package/dist/conformance.d.mts +30 -0
  50. package/dist/conformance.mjs +2 -0
  51. package/dist/contract-CuJmlrhw.mjs +566 -0
  52. package/dist/emoji-CTLpvH-A.mjs +445 -0
  53. package/dist/engine-3EvQ8idk.mjs +1449 -0
  54. package/dist/errors-DX3hpwWt.mjs +64 -0
  55. package/dist/exports-BpgyEdh6.d.mts +4 -0
  56. package/dist/exports-CyY3_FjZ.mjs +1285 -0
  57. package/dist/files-SniSmeEr.mjs +47 -0
  58. package/dist/index.d.mts +13 -0
  59. package/dist/index.default-BZ3lItjH.mjs +12 -0
  60. package/dist/index.mjs +19 -0
  61. package/dist/index.node.d.mts +14 -0
  62. package/dist/index.node.mjs +5 -0
  63. package/dist/inspect/app.js +416 -0
  64. package/dist/inspect/engine.js +5 -0
  65. package/dist/local-Cr_dSjvs.mjs +44 -0
  66. package/dist/own-OwgPWdmh.mjs +15 -0
  67. package/dist/project-CFiArraX.mjs +124 -0
  68. package/dist/provider-BGjz-mDa.d.mts +2473 -0
  69. package/dist/provider-CuewcDNZ.mjs +205 -0
  70. package/dist/redis-tables-B35vv4CZ.mjs +842 -0
  71. package/dist/reducers-B_vUGQRp.d.mts +1172 -0
  72. package/dist/reducers-mUPFuFJV.mjs +293 -0
  73. package/dist/schemas-Be08nyfW.mjs +74 -0
  74. package/dist/schemas-CWxs3ECJ.d.mts +50 -0
  75. package/dist/serve-Ccbb2LSb.mjs +170 -0
  76. package/dist/server-Cxey38B-.mjs +17 -0
  77. package/dist/serving-BNbzc96L.mjs +2 -0
  78. package/dist/sql-DbiEnqM0.mjs +571 -0
  79. package/dist/sqlite-db-Cbj03vmp.mjs +70 -0
  80. package/dist/sync-github.d.mts +473 -0
  81. package/dist/sync-github.mjs +603 -0
  82. package/dist/sync-kapso.d.mts +175 -0
  83. package/dist/sync-kapso.mjs +86 -0
  84. package/dist/sync-slack.d.mts +364 -0
  85. package/dist/sync-slack.mjs +728 -0
  86. package/dist/sync.chat.d.mts +2 -0
  87. package/dist/sync.chat.mjs +2 -0
  88. package/dist/sync.d.mts +3 -0
  89. package/dist/sync.mjs +4 -0
  90. package/dist/targets-ojFEXEyN.mjs +94 -0
  91. package/dist/tokens-DKLn5P9B.mjs +256 -0
  92. package/dist/types-BHCjTZdB.d.mts +104 -0
  93. package/dist/users-Dls7_ANV.mjs +42 -0
  94. package/dist/watch-spec-W0c7ypHJ.mjs +787 -0
  95. package/dist/watchers-hash-D9hbHLKB.mjs +225 -0
  96. package/docs/01-board/01-mounting.md +506 -0
  97. package/docs/01-board/02-database.md +258 -0
  98. package/docs/01-board/03-identity.md +262 -0
  99. package/docs/01-board/04-messages.md +192 -0
  100. package/docs/01-board/05-reading.md +177 -0
  101. package/docs/01-board/06-watchers.md +249 -0
  102. package/docs/01-board/07-schedules.md +133 -0
  103. package/docs/01-board/08-middleware.md +139 -0
  104. package/docs/01-board/09-access.md +499 -0
  105. package/docs/01-quickstart.md +196 -0
  106. package/docs/02-sync/01-overview.md +290 -0
  107. package/docs/02-sync/02-callbacks.md +265 -0
  108. package/docs/02-sync/03-slack.md +248 -0
  109. package/docs/02-sync/04-github.md +201 -0
  110. package/docs/02-sync/05-whatsapp.md +148 -0
  111. package/docs/02-sync/06-providers.md +228 -0
  112. package/docs/03-guides/01-coding-agents.md +226 -0
  113. package/docs/03-guides/02-inspector.md +104 -0
  114. package/docs/03-guides/03-ci.md +251 -0
  115. package/docs/03-guides/04-triage.md +233 -0
  116. package/docs/03-guides/05-agent-factory.md +142 -0
  117. package/docs/04-reference/01-cli.md +606 -0
  118. package/docs/04-reference/02-board.md +579 -0
  119. package/docs/04-reference/03-http.md +489 -0
  120. package/docs/04-reference/04-log.md +250 -0
  121. package/docs/04-reference/05-conformance.md +97 -0
  122. package/docs/index.md +186 -0
  123. package/package.json +165 -3
  124. package/sql/postgres/board.sql +145 -0
  125. package/sql/postgres/notifications.sql +24 -0
  126. package/sql/postgres/schema.sql +451 -0
@@ -0,0 +1,5 @@
1
+ import{C as e,D as t,E as n,S as r,T as i,_ as a,a as o,b as s,c,d as l,f as u,g as d,h as f,i as p,l as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,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,k=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function A(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=k.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 j={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},M=(e,t)=>t?`[${e}](${t})`:e;function N(e){return e.map(e=>`- ${[`${j[e.status.toLowerCase()]??`▲`} **${M(e.project,e.projectUrl)}**`,`${M(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[M(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
3
+ `)}function P(e,t){let n=t===`github:user:35613825`?A(e):null;return n?`**▲ Vercel**\n${N(n)}`:e}const ee=e=>e>10,te=e=>{let t=e.indexOf(`:`),n=e.slice(t+1).indexOf(`:`);return n===-1?e:e.slice(0,t+1+n)};function ne(e){try{Reflect.get(globalThis,Symbol.for(`@vercel/request-context`))?.get?.()?.waitUntil?.(e)}catch{}}s({id:T().min(1),platform:T().min(1),key:T().min(1),...S.shape,raw:r()});const re=s({root:E(e=>typeof e==`function`).optional(),text:T().optional(),labels:v.optional(),unset:a(u).optional(),noSync:C().optional()}).strict();function F(e){if(e&&typeof e==`object`){for(let t of Object.values(e))F(t);Object.freeze(e)}return e}const ie=s({target:T().min(1),channel:T().min(1),kind:d([`channel`,`thread`])}).strict();var ae=class extends Error{code;constructor(e,t){super(t),this.code=e}};const oe=s({event:m,raw:r(),dedupe:T().min(1)}),I=(e,t=!1)=>{let n=f(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,h-1).toWellFormed()}…`:r},L=(e,t,n=e.name??`a file`)=>`📎 ${n}${e.size===void 0?``:` (${i(e.size)})`} didn't come over: ${t}`,R=e=>e instanceof o&&e.code===`no_files`,z=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})`)},B=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
4
+ `)}`:e,se=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]),ce=(e,t)=>e.length===t.length&&e.every((e,n)=>V(e)===V(t[n])),le=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,H=6e4,U=new AbortController().signal,ue=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},W=e=>typeof e==`string`?f(e):Array.isArray(e)?e.map(W):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[f(e),W(t)])):e,G=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`},K=e=>e instanceof o&&e.status===404;function q(r){let a=null,s=()=>{if(!a)throw Error(`sync used before board() bound it`);return a},u={self:`sync`,origin:{via:{kind:`engine`}}};function d(e,t,n=U,r=u){let i=s().backend;return i[e].call(i,r,t,n)}let f=e=>{let t=r[e];if(!t)throw _(`no sync entry named ${e}`);return t},m=e=>e.slice(0,e.indexOf(`:`)),h=e=>r[m(e)]!==void 0,v=e=>e.slice(e.indexOf(`:`)+1);async function S(e){try{return await d(`syncThread`,{thread:e})}catch(e){if(K(e))return null;throw e}}async function C(e){try{let{message:t,replies:n}=await d(`message`,{id:e});return[t,...n]}catch(e){if(K(e))return[];throw e}}async function T(e){return(await C(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await d(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>d(`syncFind`,e,t);async function O(){let{actors:e}=await d(`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 k=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),A=/* @__PURE__ */ new Map,j=e=>{let t=A.get(e);if(t&&(t.complete||Date.now()-t.at<6e4))return t.map;let n=f(e),r=Object.entries(n.mappings),i={complete:!0,at:Date.now(),map:Promise.resolve(/* @__PURE__ */ new Map)};return i.map=Promise.allSettled(r.map(([e])=>n.resolveChannel(e))).then(t=>{let n=/* @__PURE__ */ new Map;return t.forEach((t,a)=>{let[o,s]=r[a];t.status===`fulfilled`?n.set(`${e}:${t.value}`,s):(i.complete=!1,console.warn(`[board] sync ${e}: skipping ${o}: ${t.reason.message}`))}),n}),A.set(e,i),i.map};async function M(e){for(let t of Object.values(r)){let n=await t.findByEmail(e).catch(()=>null);if(n)return{id:n.id,kind:`external`,handle:n.handle,name:n.name,avatar:n.avatar}}return null}async function N(e){let t=[];for(let[n,i]of Object.entries(r))if(i.canStartThreads)for(let[r,i]of await j(n))i.channel===e&&t.push({target:r,kind:`channel`});return t}async function V(e){let t=e.indexOf(`:`);if(t<=0)throw _(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ie.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 _(`can't sync to ${e}: ${t.message}`)}}async function q(e,t){let{event:n,raw:r,dedupe:i}=oe.parse(W({...t,raw:ue(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw _(`provider locations must belong to sync entry ${e}`);((await j(e)).has(n.channel)||await de(n.thread))&&await d(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function de(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 fe(e,t){let n=r[e];if(!n)throw b(`no sync entry named ${e}`);let i;try{i=await n.receive(e,t,t=>q(e,t))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}return ne(J([e]).catch(t=>console.error(`[board] couldn't pull ${e}'s inbox:`,G(t)))),i}async function J(e=Object.keys(r)){let t=0,n=Date.now()+24e4;for(let r of e)for(;Date.now()<n;){let{items:e}=await d(`inboxNext`,{key:r,limit:10});if(e.length===0)break;await Promise.all(e.map(e=>pe(r,e))),t+=e.length}return t}async function pe(e,t){let n;try{await ge(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}:`,G(r)),n=G(r)}await d(`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:`,G(e)))}async function me(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:G(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return W(i)}async function he(t,r,i){let a=[],s=[],c=!1;for(let l of r.slice(0,10)){if(c){s.push(z(l));continue}if(l.size!==void 0&&l.size>26214400){s.push(L(l,`over ${w/1024/1024} MB`));continue}let r;try{let e=await t.fetchFile(l);if(e===null){s.push(L(l,`${t.platform} can't hand it over`));continue}r=e}catch(e){if(i<3)throw e;console.warn(`[board] couldn't fetch ${l.name??`a file`} from ${t.platform}:`,G(e)),s.push(L(l,`${t.platform} wouldn't hand it over`));continue}if(r.byteLength>26214400){s.push(L(l,`over ${w/1024/1024} MB`));continue}if(r.byteLength===0){s.push(L(l,`it was empty`));continue}let u=l.name??`${l.type}`;try{a.push((await d(`upload`,{data:r,name:u,type:l.mimeType||n(u)})).attachment)}catch(t){if(t instanceof o&&(t.code===`too_large`||t.code===`no_direct_uploads`)){s.push(L(l,`over ${e/1024/1024} MB`));continue}if(!R(t))throw t;c=!0,s.push(z(l))}}for(let e of r.slice(10))s.push(L(e,`a message carries at most 10`));return{attachments:a,notes:s}}async function ge(e,t,n){let{event:r,raw:i}=t,a=f(e),o=(await j(e)).get(r.channel),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 ve(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:le.exec(r.text)?.[1];if(i&&await ye(e,r,i))return}let u=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},d=s().client({author:u,key:e,..._e(e,r)},n.id),p,m=!0,h={id:n.id,key:e,event:r,raw:i,board:d,apply(i,a={}){if(!m)throw _(`the sync callback has finished`);if(p)throw _(`an event can only be imported once per callback`);return p=we(e,t,n,l.parse(i),re.parse(a)).catch(async e=>{if(!(e instanceof y))throw e;let t=(await D({thread:r.thread})).thread;return{root:t===null?null:(await S(t))?.root??null,message:null}}),p},client(e){async function t(t,n,r){if(!m)throw _(`the sync callback has finished`);if(typeof n==`string`)return d.sync(t,n);if(typeof t!=`string`||n!==e)throw _(`sync only accepts the current verified event`);return a.sync(h,t,r)}return{...d,sync:t}}};try{if(o?.run){await o.run(h);return}let e=o?.channel??(c?(await S(c))?.channel:void 0);e&&await a.sync(h,e)}finally{m=!1}}function _e(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 ve(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 S(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await C(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 d(`syncRoots`,{by:n.id,since:r-H}),o=await E(a.map(e=>e.id));for(let e of a){let n=await S(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 ye(e,t,n){try{await d(`syncAccount`,{sync:e,account:t.author,code:n,message:x(t.subthread??t.thread,t.message)})}catch(e){if(K(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}:`,G(t))),!0}let be=e=>f(e).users!==null;async function xe(e,t,n){let r=f(e);if(!r.users)throw _(`${e} can't sign people in: link it with a code`);return r.users.authorize(t,n?{callback:n}:{})}async function Se(e,t){let n=f(e);if(!n.users)throw _(`${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 _(`${e} named an account that isn't a ${n.platform} user`);return i}async function Ce(e,t,n){let r=await Se(e,t);return r?(await d(`syncAccount`,{sync:e,account:r,code:n,method:`consent`})).link:null}async function we(e,n,r,i,a){let{event:o,raw:s}=n,l=_e(e,o),p=f(e),m=(await D({thread:o.thread})).thread??void 0,h=m?await S(m):null,g=h?.root,b=e=>c(`board-inbound`,r.id,String(e)),x={key:e,channel:l.via.channel,thread:l.via.thread},C=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await l.respond(t);throw new y(e.dropped)},w=async({attachments:t=[],...n})=>{let r=n.channel??h?.channel??i,a=n.re===void 0?{...u,origin:{...u.origin,config:await N(r)}}:u;return{id:await C(await d(`syncImport`,{op:`post`,from:e,source:x,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}},U,a)),re:n.re??null}},E=e=>({root:e.re??e.id,message:e.id}),O=(e={})=>{let t={...e,...a.labels};for(let e of a.unset??[])delete t[e];return t},k=e=>{if(!a.root)return e;let t=a.root(F(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw _(`root overrides must be synchronous`);let n=re.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},A=(t,n,r)=>d(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:o.thread},set:n,unset:r}),j=async(e,t)=>{if(!a.labels&&!a.unset)return;let n=await T(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 A(e,r,i)};switch(o.kind){case`message`:{let e=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(e!==null)return{root:g??e,message:e};let{attachments:t,notes:n}=o.files?.length?await he(p,o.files,r.attempt):{attachments:[],notes:[]},c=await me(p,o.links??[],h?.channel??i),l={...c.length?{links:c}:{},author:o.author,text:I(B(a.text??o.text,n),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 b(0)},u={...l,...a.noSync?{noSync:!0}:{}};if(g!==void 0)return await j(o.author,g),E(await w({...u,re:g}));let d=await p.fetchRoot(v(o.thread));if(d===null){let e=k({text:o.text,labels:{},author:o.author,raw:s});return E(await w({...l,channel:i,text:I(B(a.text??e.text,n),t.length>0),labels:O(e.labels)}))}let f=k({...d,raw:d.raw??null}),m=await w({author:d.author,channel:i,text:I(f.text),labels:O(f.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`root:${r.id}`,context:null},id:await b(1)});return E(await w({...u,re:m.id}))}case`edited`:case`deleted`:case`reaction`:{let n=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(n===null)return{root:g??null,message:null};let r=`author`in o?o.author:{id:`${p.platform}:system`,handle:p.platform,name:p.platform,avatar:null,isBot:!0},i={thread:o.subthread??o.thread,message:o.message};if(g!==void 0&&await j(r,g),o.kind===`deleted`)await d(`syncImport`,{op:`delete`,author:r,from:e,origin:i});else if(o.kind===`edited`){let t=await T(n),s=I(B(a.text??o.text,t?se(t.text):[]),(t?.attachments.length??0)>0);t&&!o.linksOnly&&t.text!==s&&await C(await d(`syncImport`,{op:`edit`,author:r,from:e,origin:i,text:s,source:x}));let c=t?await me(p,o.links??[],t.channel,t.links):[];t&&!ce(c,t.links)&&await d(`syncImport`,{op:`links`,author:r,from:e,origin:i,links:c})}else{if(p.singleReaction){let a=t(o.emoji);for(let[t,s]of Object.entries((await T(n))?.reactions??{}))s.includes(r.id)&&!(o.added&&t===a)&&await d(`syncImport`,{op:`react`,author:r,from:e,origin:i,emoji:t,remove:!0})}(!p.singleReaction||o.added)&&await d(`syncImport`,{op:`react`,author:r,from:e,origin:i,emoji:o.emoji,remove:!o.added})}return{root:g??null,message:n}}case`lifecycle`:{if(g!==void 0){let e=await T(g),t=e?.origin?.message===`lifecycle:${r.id}`,n=O((t?k({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}):null)?.labels??o.labels),i=e=>o.owns?.some(t=>e.startsWith(t))??!1,c=Object.fromEntries(Object.entries(n).filter(([t,n])=>e?.labels[t]!==n)),l=Object.keys(e?.labels??{}).filter(e=>!(e in n)&&(o.unset.includes(e)||a.unset?.includes(e)||i(e)));return e&&!e.deleted&&(Object.keys(c).length||l.length)&&await A(o.author,c,l),o.reply?E(await w({author:o.author,re:g,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await b(1)})):{root:g,message:t?g:null}}if(!o.createRoot)return{root:null,message:null};let e=k({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}),t=await w({author:o.author,channel:i,text:I(o.reply?e.text:a.text??e.text),labels:O(e.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`lifecycle:${r.id}`,context:null},id:await b(0)});return o.reply?E(await w({author:o.author,re:t.id,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await b(1)})):E(t)}}}function Te(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return p(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?Ee(e,a.id,n)??a.name:null})}function Ee(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 k(n,i)){let t=r(e);if(t)return t}return null}async function De(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:`,G(n)),null}}function Oe(e,t,n){let r=f(m(e)),i=Te(r,t,n,P(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Me(t)}`:i}}async function ke(e,t,n,r,i){let a=f(m(e));if(!r.text.trim())return null;let o=await De(a,r,!0);if(!o)return null;try{return await a.postAs(o,v(t),n,Oe(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,G(t)),null}}function Ae(e,t,n,i){let a=i.byId[n.by.actor],o=a?.name??n.by.actor;n.by.profile&&a?.handle.includes(`/`)&&(o=`${a.name} via ${a.handle.split(`/`)[0]}`),n.from&&(o=`${o} (${r[n.from]?.platform??n.by.actor.slice(0,n.by.actor.indexOf(`:`))})`);let s=f(e),c=s.render(F({message:{...structuredClone(n),text:P(n.text,n.by.actor)},author:a?{...a}:null,mentionOf:e=>Ee(s,e,i),label:o,target:t}));if(typeof c!=`string`)throw _(`a provider renderer must return markdown`);let l=Te(s,n,i,c);return n.by.profile&&!n.from?{markdown:l,as:{name:o,avatar:je(n,a,i)}}:{markdown:l}}function je(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 Me=e=>e.attachments.map(t=>`\n📎 ${t.name} (${i(t.size)}, on the board: #${e.id})`).join(``),Y=(e,t,n,r=!1)=>{let i=m(e),{markdown:a,as:o}=Ae(i,e,t,n);return{markdown:t.attachments.length>0&&(r||!f(i).uploadsFiles)?`${a}\n${Me(t)}`:a,...o?{as:o}:{}}};async function Ne(e,t,n,r){let i=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],a=i!==void 0&&i.thread!==n?i.thread:n,o=f(m(t)),s=Y(t,e,r),c,l;if(!s.markdown.trim()&&e.attachments.length&&o.uploadsFiles)try{let n=await Fe(e,t,a);l=n.message,c=n.files}catch(n){if(!o.refusesFiles(n)&&!R(n))throw n;s=Y(t,e,r,!0),l=(await o.post(v(a),`thread`,s)).message,c=null}else{let n=await ke(t,a,`thread`,e,r);if(n){let r={thread:a,message:n.message,asUser:!0};return await X(e,t,r),r}if(!s.markdown.trim())throw _(`a renderer must return text unless the message has media`);l=(await o.post(v(a),`thread`,s)).message}let u={thread:a,message:l,...c===void 0?{}:{files:c}};return await X(e,t,u),u}async function X(e,t,n){await d(`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 Pe(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await d(`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 Fe(e,t,n){return f(m(t)).uploadFiles(v(n),await Pe(e))}async function Z(e,t,n,r){let i=f(m(t));if(!i.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let a;try{a=(await Fe(e,t,n.thread)).files}catch(o){if(!i.refusesFiles(o)&&!R(o))throw Object.assign(/* @__PURE__ */ Error(`files: ${G(o)}`),{cause:o,data:{error:`files: ${G(o)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${G(o)}); noting them in its text`),a=null,await i.edit(v(n.thread),n.message,Y(t,e,r,!0)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,G(n)))}await d(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:a})}async function Q(e,t){if(t.length!==0){if(e<5||ee(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up delivering #${e} to ${n}:`,G(r)),await d(`syncReceipt`,{kind:`failed`,message:e,target:n,error:G(r)})}}async function Ie(e){if(e.re!==null||e.noSync)return;let t=e.origin?e.origin.channel??te(e.origin.thread):null;for(let n of await N(e.channel))n.target!==t&&await d(`syncReceipt`,{kind:`thread`,message:e.id,target:n.target,targetKind:n.kind,cause:`config`})}async function Le(e,t){let{thread:n,target:r}=e.payload;if(!h(r)||(e.payload.kind??(g(r)?`thread`:`channel`))===`thread`)return;let i=await S(n),[a,...o]=i?await C(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=await O(),c=await E([a.id,...o.map(e=>e.id)]),l=i.external[r];if(l){let e=c[a.id]?.[r];if(e)try{await Z(a,r,e,s)}catch(e){return Q(t,[{id:a.id,target:r,error:e}])}}else try{let e=f(m(r)),t=Y(r,a,s),n=!t.markdown.trim()&&a.attachments.length>0&&e.uploadsFiles;if(!t.markdown.trim()&&!n)throw _(`a renderer must return text unless the message has media`);let i,o=!1,c=await(async()=>{if(!n){let n=await ke(r,r,`channel`,a,s);return n?(o=!0,n):e.post(v(r),`channel`,t)}try{let n=await e.post(v(r),`channel`,{...t,files:await Pe(a)});return i=n.files??[],n}catch(t){if(!e.refusesFiles(t)&&!R(t))throw t;return i=null,e.post(v(r),`channel`,Y(r,a,s,!0))}})();l=`${m(r)}:${c.thread}`;let u={thread:l,message:c.message,...i===void 0?{}:{files:i},...o?{asUser:!0}:{}};await X(a,r,u),await Z(a,r,u,s)}catch(e){return Q(t,[{id:a.id,target:r,error:e}])}let u=[];for(let e of o)if(!(e.deleted||e.noSync||$(e,r)))try{await Z(e,r,c[e.id]?.[r]??await Ne(e,r,l,s),s)}catch(t){u.push({id:e.id,target:r,error:t})}await Q(t,u)}let $=(e,t)=>e.origin?.thread===t||e.from!==null&&m(t)===e.from;async function Re(e,t){if(!e.message)return;if(e.message.re===null)return Ie(e.message);if(e.payload.noSync)return;let n=await S(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 e of n.targets){let t=n.external[e];if(h(e)&&t&&!$(r,e))try{await Z(r,e,(await E([r.id]))[r.id]?.[e]??await Ne(r,e,t,i),i)}catch(t){a.push({id:r.id,target:e,error:t})}}await Q(t,a)}async function ze(e){let n=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(n).length===0)return;let r=e.type===`message.edited`?await T(e.payload.id):e.message;if(e.type===`message.edited`&&(!r||r.deleted))return;let i=await O();for(let[a,o]of Object.entries(n))if(!(!h(a)||e.payload.from!==null&&m(a)===e.payload.from))try{let n=f(m(a)),s=v(o.thread),c=o.asUser&&r&&(e.type===`message.edited`||e.type===`message.deleted`),l=c?await De(n,r,!1):null;if(c&&!l){console.warn(`[board] skipped ${e.type} on ${a}: #${r.id} was posted as its author, and their token is gone`);continue}e.type===`message.edited`&&r?l?await n.editAs(l,s,o.message,Oe(a,r,i)):await n.edit(s,o.message,Y(a,r,i,o.files===null)):e.type===`message.deleted`?(l?await n.deleteAs(l,s,o.message):await n.delete(s,o.message),o.files?.length&&await n.deleteFiles(o.files)):e.type===`reaction.added`?await n.react(s,o.message,t(e.payload.emoji),!0):e.type===`reaction.removed`&&await n.react(s,o.message,t(e.payload.emoji),!1)}catch(t){console.warn(`[board] skipped ${e.type} on ${a}:`,t.message)}}async function Be(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[e,t]of Object.entries(n))if(h(e))try{let n=Y(e,o,a,t.files===null);if(n.markdown===Y(e,s,a,t.files===null).markdown)continue;await f(m(e)).edit(v(t.thread),t.message,n)}catch(t){console.warn(`[board] skipped labels.changed on ${e}:`,G(t))}}async function Ve(e,{attempt:t}){switch(e.type){case`thread.synced`:return Le(e,t);case`message.posted`:return Re(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return ze(e);case`labels.changed`:return Be(e)}}async function He(e,t){let n=e[0]?await S(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(h);return(await Promise.all(r.map(async n=>{let r=m(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}:`,G(e)),null}}))).filter(e=>e!==null)}async function Ue(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw _(`can't resolve inbox channel: ${e.message}`)}}let We=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 Ge(e,t,n){let r=f(e);try{return await r.read(t,{limit:n})}catch(n){throw n instanceof ae?new o(We(n.code),n.code,n.message):_(`can't read ${t} from ${e}: ${G(n)}`,`platform_error`)}}return{entries:r,bind(e){a=e},caller:u,deliver:Ve,receive:ge,pull:J,configTargets:N,normalizeTarget:V,findByEmail:M,handleWebhook:fe,inboxChannel:Ue,signsIn:be,signIn:xe,signedIn:Se,redeemSignIn:Ce,typing:He,readSource:Ge}}export{q as createSyncEngine};
@@ -0,0 +1,44 @@
1
+ import { deliverSchedulerAppend, deliverSchedulerClear } from "experimental-a2/server";
2
+ //#region src/scheduler/local.ts
3
+ /** The longest delay a timer takes: past it, `setTimeout` fires at once. */
4
+ const MAX_DELAY = 2 ** 31 - 1;
5
+ /**
6
+ * A scheduler in this process (`@vercel/board/backend-a2/scheduler-local`): each task runs at its time on
7
+ * a timer, so retries, leases, wake-ups, and scheduled messages work with nothing else running.
8
+ * Its timers live in the process, and a restart drops the pending ones, so it's for development,
9
+ * `vb serve`, tests, and long-running servers, never serverless functions, which stop between
10
+ * requests: there, use `vercelQueues()` or `qstash()`.
11
+ */
12
+ function localScheduler() {
13
+ let servers = [];
14
+ const pending = /* @__PURE__ */ new Set();
15
+ async function run(task) {
16
+ const server = servers.find((candidate) => candidate.contract.name === task.contract);
17
+ if (!server) throw new Error(`no server for contract ${task.contract}: mount it with scheduler.handler(...)`);
18
+ if (task.kind === "drain") await server.drain(task.sessionId);
19
+ else if (task.kind === "append") await deliverSchedulerAppend(server, task);
20
+ else await deliverSchedulerClear(server, task);
21
+ }
22
+ return {
23
+ async schedule(task) {
24
+ const key = task.kind === "drain" ? `drain\u001f${task.contract}\u001f${task.sessionId}\u001f${task.dueAt}` : task.id;
25
+ if (pending.has(key)) return;
26
+ pending.add(key);
27
+ const arm = () => {
28
+ const wait = task.dueAt - Date.now();
29
+ setTimeout(() => {
30
+ if (wait > MAX_DELAY) return arm();
31
+ pending.delete(key);
32
+ run(task).catch((error) => console.error("[board] a scheduled task failed:", error));
33
+ }, Math.min(Math.max(0, wait), MAX_DELAY)).unref?.();
34
+ };
35
+ arm();
36
+ },
37
+ handler(...mounted) {
38
+ servers = mounted;
39
+ return async () => new Response(null, { status: 204 });
40
+ }
41
+ };
42
+ }
43
+ //#endregion
44
+ export { localScheduler as t };
@@ -0,0 +1,15 @@
1
+ //#region src/contract/own.ts
2
+ /** `map[key]` if it's an entry of the map's own, never what the map inherits. */
3
+ const own = (map, key) => Object.hasOwn(map, key) ? map[key] : void 0;
4
+ /** Sets `map[key]` as an entry of its own, even for `__proto__`, and returns `value`. */
5
+ function setOwn(map, key, value) {
6
+ Object.defineProperty(map, key, {
7
+ value,
8
+ writable: true,
9
+ enumerable: true,
10
+ configurable: true
11
+ });
12
+ return value;
13
+ }
14
+ //#endregion
15
+ export { setOwn as n, own as t };
@@ -0,0 +1,124 @@
1
+ import { n as setOwn, t as own } from "./own-OwgPWdmh.mjs";
2
+ import { a as foldMessages, o as foldSync } from "./reducers-mUPFuFJV.mjs";
3
+ //#region src/store/project.ts
4
+ /** The message an event concerns, if any. */
5
+ function concernedMessageId(event) {
6
+ if (event.type === "message.posted") return event.index;
7
+ if ("id" in event.payload) return event.payload.id;
8
+ return null;
9
+ }
10
+ const termsOf = (message) => JSON.stringify([
11
+ message.labels,
12
+ message.mentions,
13
+ message.attachments.map((file) => file.id)
14
+ ]);
15
+ /**
16
+ * Applies one event to what the tables hold, with the same fold the board has
17
+ * always used, on a state of just that message. Pure: the caller reads `before`
18
+ * and writes the change.
19
+ */
20
+ function project(before, event) {
21
+ const id = concernedMessageId(event);
22
+ if (id === null) return {};
23
+ const current = before.message;
24
+ if (event.type !== "message.posted" && !current) return {};
25
+ const state = {
26
+ byId: current ? { [id]: structuredClone(current) } : {},
27
+ replies: {}
28
+ };
29
+ foldMessages(state, event);
30
+ const after = state.byId[id];
31
+ if (!after) return {};
32
+ const change = {};
33
+ const created = current === void 0;
34
+ if (created || JSON.stringify(after) !== JSON.stringify(current)) change.message = {
35
+ after,
36
+ created,
37
+ terms: created || termsOf(after) !== termsOf(current)
38
+ };
39
+ if (event.type === "message.posted") {
40
+ if (after.re === null) change.summary = {
41
+ root: id,
42
+ replies: 0,
43
+ at: id
44
+ };
45
+ else if (before.root) change.summary = {
46
+ root: after.re,
47
+ replies: before.root.replies + 1,
48
+ at: id
49
+ };
50
+ } else if (event.type === "message.deleted" && current && !current.deleted && current.re !== null && before.root) {
51
+ const at = before.root.at === id ? before.previousLast ?? current.re : before.root.at;
52
+ change.summary = {
53
+ root: current.re,
54
+ replies: before.root.replies - 1,
55
+ at
56
+ };
57
+ }
58
+ return change;
59
+ }
60
+ /** The sync rows `event` reads before it applies: none for events that don't touch the sync state. */
61
+ function syncReads(event) {
62
+ switch (event.type) {
63
+ case "message.posted": return {
64
+ thread: null,
65
+ copy: null
66
+ };
67
+ case "thread.synced":
68
+ case "message.synced": return {
69
+ thread: event.payload.thread,
70
+ copy: null
71
+ };
72
+ case "message.files_synced": return {
73
+ thread: null,
74
+ copy: [event.payload.id, event.payload.target]
75
+ };
76
+ default: return {
77
+ thread: null,
78
+ copy: null
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Applies one event to the sync state, with the fold the board has always used,
84
+ * on a state of just what the event touches. Pure, like `project`.
85
+ */
86
+ function projectSync(before, event) {
87
+ const change = {
88
+ externalThreads: [],
89
+ copies: [],
90
+ externalMessages: []
91
+ };
92
+ if (![
93
+ "message.posted",
94
+ "thread.synced",
95
+ "message.synced",
96
+ "message.files_synced"
97
+ ].includes(event.type)) return change;
98
+ const key = event.payload.thread;
99
+ const state = {
100
+ threads: {},
101
+ byExternalThread: {},
102
+ copies: {},
103
+ byExternalMessage: {}
104
+ };
105
+ if (before.thread) setOwn(state.threads, key, structuredClone(before.thread));
106
+ const reads = syncReads(event);
107
+ if (reads.copy && before.copy) state.copies[reads.copy[0]] = { [reads.copy[1]]: structuredClone(before.copy) };
108
+ foldSync(state, event);
109
+ const thread = own(state.threads, key);
110
+ if (thread && JSON.stringify(thread) !== JSON.stringify(before.thread)) change.thread = {
111
+ key,
112
+ data: thread
113
+ };
114
+ for (const [external, owner] of Object.entries(state.byExternalThread)) change.externalThreads.push([external, owner]);
115
+ for (const [message, targets] of Object.entries(state.copies)) for (const [target, copy] of Object.entries(targets)) if (!(reads.copy?.[0] === Number(message) && reads.copy[1] === target && JSON.stringify(copy) === JSON.stringify(before.copy))) change.copies.push([
116
+ Number(message),
117
+ target,
118
+ copy
119
+ ]);
120
+ for (const [externalKey, message] of Object.entries(state.byExternalMessage)) change.externalMessages.push([externalKey, message]);
121
+ return change;
122
+ }
123
+ //#endregion
124
+ export { syncReads as i, project as n, projectSync as r, concernedMessageId as t };