@vercel/board 0.16.0 → 0.18.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 (130) hide show
  1. package/README.md +27 -27
  2. package/dist/{a2-C41hD8SF.mjs → a2-ChRLRupa.mjs} +969 -25
  3. package/dist/access.d.mts +1 -1
  4. package/dist/access.mjs +3 -3
  5. package/dist/{actors-DjMeZpnU.mjs → actors-BOamkEuz.mjs} +3 -73
  6. package/dist/attempts-CLG840Oj.mjs +20 -0
  7. package/dist/{auth-COzc09sn.mjs → auth-Drg9FiC2.mjs} +225 -2
  8. package/dist/backend-B-SJ7p6p.d.mts +680 -0
  9. package/dist/backend-a2/contract.d.mts +873 -2
  10. package/dist/backend-a2/contract.mjs +2 -2
  11. package/dist/backend-a2/scheduler-local.d.mts +1 -1
  12. package/dist/backend-a2/scheduler-local.mjs +1 -1
  13. package/dist/backend-a2/scheduler-qstash.d.mts +78 -1
  14. package/dist/backend-a2/scheduler-qstash.mjs +521 -1
  15. package/dist/backend-a2/scheduler-vercel.d.mts +37 -1
  16. package/dist/backend-a2/scheduler-vercel.mjs +203 -1
  17. package/dist/backend-a2/store-memory.d.mts +2 -2
  18. package/dist/backend-a2/store-memory.mjs +3 -3
  19. package/dist/backend-a2/store-postgres.d.mts +1 -1
  20. package/dist/backend-a2/store-postgres.mjs +2850 -2
  21. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  22. package/dist/backend-a2/store-redis-http.mjs +490 -2
  23. package/dist/backend-a2/store-redis.d.mts +10 -2
  24. package/dist/backend-a2/store-redis.mjs +51 -2
  25. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  26. package/dist/backend-a2/store-sqlite.mjs +3 -3
  27. package/dist/backend-a2.d.mts +2706 -3
  28. package/dist/backend-a2.mjs +1 -1
  29. package/dist/{backend-http-Bid7RIpC.d.mts → backend-http-7cNb8nJI.d.mts} +10 -3
  30. package/dist/{backend-http-0roEmjl0.mjs → backend-http-CMlL3yq3.mjs} +2 -2
  31. package/dist/{backend-http-BVRno-ca.mjs → backend-http-Ddzd5kTS.mjs} +32 -3
  32. package/dist/backend-http.d.mts +1 -1
  33. package/dist/backend-http.mjs +1 -1
  34. package/dist/backend-http.node.d.mts +1 -1
  35. package/dist/backend-http.node.mjs +1 -1
  36. package/dist/{chat-provider-XCnQlxq6.d.mts → chat-provider-DNJQyajS.d.mts} +16 -4
  37. package/dist/{chat-provider-jFSeyNmB.mjs → chat-provider-YHy-WbrB.mjs} +15 -9
  38. package/dist/cli.mjs +30 -10
  39. package/dist/{client-DGDPaN7U.mjs → client-BA-J7usH.mjs} +10 -2
  40. package/dist/{conformance-DRR23YNy.mjs → conformance-CT5EeHeC.mjs} +5 -2
  41. package/dist/conformance.d.mts +1 -1
  42. package/dist/conformance.mjs +1 -1
  43. package/dist/{engine-Bir5s8ts.mjs → engine-D0oBUBEK.mjs} +249 -163
  44. package/dist/events-CewD4kw7.mjs +75 -0
  45. package/dist/{exports-Dvc1o52I.mjs → exports-CgjfMrQ-.mjs} +35 -32
  46. package/dist/exports-ZsgjzEnM.d.mts +4 -0
  47. package/dist/external-BV_cUncs.mjs +151 -0
  48. package/dist/folds-CRYY14kc.d.mts +534 -0
  49. package/dist/{reducers-mUPFuFJV.mjs → folds-Dyjput8M.mjs} +6 -73
  50. package/dist/ids-C6AYo1bu.mjs +31 -0
  51. package/dist/index.d.mts +4 -4
  52. package/dist/{index.default-Bkg77Pxn.mjs → index.default-C5Cpz3sa.mjs} +1 -1
  53. package/dist/index.mjs +5 -5
  54. package/dist/index.node.d.mts +4 -4
  55. package/dist/index.node.mjs +3 -3
  56. package/dist/inspect/app.js +31 -31
  57. package/dist/inspect/engine.js +5 -5
  58. package/dist/internal-CvsrprnU-CRY89I-b.mjs +203 -0
  59. package/dist/{local-Cr_dSjvs.mjs → local-DhkrAqPP.mjs} +1 -1
  60. package/dist/mentions-CB6ShY4R.mjs +72 -0
  61. package/dist/{targets-yM5JBUgr.mjs → middleware-CqaCwucP.mjs} +1 -33
  62. package/dist/platform-B4TnJtWu-CwcTyKg2.mjs +32 -0
  63. package/dist/{project-CFiArraX.mjs → project-DMDST9kl.mjs} +1 -1
  64. package/dist/{provider-BGjz-mDa.d.mts → provider-B0b9Qxw4.d.mts} +170 -40
  65. package/dist/provider-B3DpYSDq.mjs +220 -0
  66. package/dist/redis-tables-DN9PfIDi.mjs +3897 -0
  67. package/dist/reducer-CYPHsSmd-BQIfozGi.d.mts +170 -0
  68. package/dist/reducer-CjmvZSAb-D_OSISmj.mjs +74 -0
  69. package/dist/{contract-CuJmlrhw.mjs → reducers-BFmIXilu.mjs} +260 -287
  70. package/dist/retryable-lazy-DZWmHpii-Bye-W8q7.mjs +19 -0
  71. package/dist/scheduler-task-X1SToDjP-BVoWuInI.mjs +69 -0
  72. package/dist/{serve-BsOOc-6_.mjs → serve-BGYw0uNL.mjs} +5 -5
  73. package/dist/{server-jwPY7_wi.mjs → server-DPxBSogx.mjs} +4 -4
  74. package/dist/server-DtFrWxi3-Boa6gdWO.mjs +3602 -0
  75. package/dist/server-qgez0Xny-D5mLfqFQ.d.mts +385 -0
  76. package/dist/{serving-BNbzc96L.mjs → serving-CNkgUPW1.mjs} +1 -1
  77. package/dist/session-clear-wHd5GCpZ-BnKSxReK.mjs +51 -0
  78. package/dist/{sql-DbiEnqM0.mjs → sql-U9WTxrwJ.mjs} +2 -2
  79. package/dist/{sqlite-db-Cbj03vmp.mjs → sqlite-db-Ds4majtT.mjs} +1 -1
  80. package/dist/store-D0MhBvg6-4Z2L0Kqd.d.mts +530 -0
  81. package/dist/store-N8PXxDAS-BWBgzBHp.mjs +5 -0
  82. package/dist/store-codec-DwrWlgQx-CYxGW0aD.mjs +61 -0
  83. package/dist/store-memory-Bas9Uq5C.mjs +872 -0
  84. package/dist/store-memory-ZLiZbH3R.mjs +2 -0
  85. package/dist/store-polling-BZt2Rf3D-BiEM6_HB.mjs +148 -0
  86. package/dist/store-presence-polling-JuAy2Ck2-CYpuK75X.mjs +109 -0
  87. package/dist/store-sqlite-BmwL3pUR.mjs +2 -0
  88. package/dist/store-sqlite-CxxPCcCT.mjs +1053 -0
  89. package/dist/stream-buffer-DrTyiIuo-BKy-vI14.mjs +53 -0
  90. package/dist/sync-github.d.mts +24 -17
  91. package/dist/sync-github.mjs +89 -22
  92. package/dist/sync-kapso.d.mts +15 -13
  93. package/dist/sync-kapso.mjs +17 -14
  94. package/dist/sync-linear.d.mts +219 -0
  95. package/dist/sync-linear.mjs +311 -0
  96. package/dist/sync-slack.d.mts +12 -17
  97. package/dist/sync-slack.mjs +54 -42
  98. package/dist/sync.chat.d.mts +1 -1
  99. package/dist/sync.chat.mjs +1 -1
  100. package/dist/sync.d.mts +3 -3
  101. package/dist/sync.mjs +5 -4
  102. package/dist/targets-C-wdR5wP.mjs +33 -0
  103. package/dist/{tokens-DKLn5P9B.mjs → tokens-DhuzKzAl.mjs} +1 -1
  104. package/dist/{types-BHCjTZdB.d.mts → types-8yq2VnKI.d.mts} +2 -2
  105. package/dist/{users-Dls7_ANV.mjs → users-yryHKFMo.mjs} +2 -2
  106. package/dist/{watch-spec-W0c7ypHJ.mjs → watchers-hash-RLtnSF-t.mjs} +233 -28
  107. package/docs/01-board/01-mounting.md +1 -1
  108. package/docs/02-sync/01-overview.md +72 -48
  109. package/docs/02-sync/02-functions.md +398 -0
  110. package/docs/02-sync/03-slack.md +30 -25
  111. package/docs/02-sync/04-github.md +62 -26
  112. package/docs/02-sync/05-whatsapp.md +23 -19
  113. package/docs/02-sync/06-providers.md +60 -27
  114. package/docs/02-sync/07-linear.md +129 -0
  115. package/docs/03-guides/04-triage.md +1 -1
  116. package/docs/04-reference/01-cli.md +26 -6
  117. package/docs/04-reference/02-board.md +18 -5
  118. package/docs/04-reference/03-http.md +5 -2
  119. package/docs/04-reference/05-conformance.md +1 -1
  120. package/docs/index.md +5 -10
  121. package/package.json +31 -6
  122. package/dist/a2-D24cvwpN.d.mts +0 -3359
  123. package/dist/access-CJU2-vaa.mjs +0 -226
  124. package/dist/attempts-Bxp9KgPf.mjs +0 -919
  125. package/dist/exports-DLiE7fLY.d.mts +0 -4
  126. package/dist/provider-CuewcDNZ.mjs +0 -205
  127. package/dist/redis-tables-B35vv4CZ.mjs +0 -842
  128. package/dist/reducers-B_vUGQRp.d.mts +0 -1172
  129. package/dist/watchers-hash-CLjsa6od.mjs +0 -225
  130. package/docs/02-sync/02-callbacks.md +0 -265
@@ -1,2 +1,2 @@
1
- import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-C41hD8SF.mjs";
1
+ import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-ChRLRupa.mjs";
2
2
  export { a2, fsBlob, memoryBlob };
@@ -1,7 +1,7 @@
1
1
  import { i as Attachment } from "./schemas-CWxs3ECJ.mjs";
2
- import { t as Actor } from "./reducers-B_vUGQRp.mjs";
3
- import { Bt as SyncCodeView, G as Discovery, Gt as SyncFindView, Ht as SyncCopiesView, Ot as ScheduleView, Pt as SourceRead, Q as IdentityView, St as RegistrationView, U as ChannelView, Zt as SyncThreadView, ct as MatchView, dt as MessageView, en as ThreadView, et as InboxEntry, ft as MessagesPage, ht as PersonView, j as ProfileOption, q as EventsPage, sn as UploadTicket, st as LinkedAccount, tn as ThreadsPage, tt as InboxItem, ut as Me } from "./provider-BGjz-mDa.mjs";
4
- import { a as BackendRoute, o as BoardBinding, s as Caller } from "./a2-D24cvwpN.mjs";
2
+ import { t as Actor } from "./folds-CRYY14kc.mjs";
3
+ import { $ as Discovery, Mt as RegistrationView, Qt as SyncCopiesView, Rt as ScheduleView, Tt as PersonView, Wt as SourceRead, Xt as SyncCodeView, Z as ChannelView, _t as MatchView, bt as MessageView, ct as InboxEntry, dn as ThreadView, fn as ThreadsPage, ht as LinkedAccount, ln as SyncedView, lt as InboxItem, ot as IdentityView, sn as SyncThreadView, tn as SyncFindView, tt as EventsPage, vn as UploadTicket, xt as MessagesPage, yt as Me, z as ProfileOption } from "./provider-B0b9Qxw4.mjs";
4
+ import { i as Caller, n as BackendRoute, r as BoardBinding } from "./backend-B-SJ7p6p.mjs";
5
5
  //#region src/server/backend-http.d.ts
6
6
  type HttpOptions = {
7
7
  /**
@@ -245,6 +245,12 @@ declare function createHttp(url: string | undefined, options: HttpOptions, envir
245
245
  target: string;
246
246
  limit?: number | undefined;
247
247
  }, signal: AbortSignal) => Promise<SourceRead>;
248
+ synced: (caller: Caller, input: {
249
+ id: string;
250
+ }, signal: AbortSignal) => Promise<SyncedView>;
251
+ linked: (caller: Caller, input: {
252
+ url: string;
253
+ }, signal: AbortSignal) => Promise<ThreadView>;
248
254
  syncImport: (caller: Caller, input: {
249
255
  author: {
250
256
  id: string;
@@ -555,6 +561,7 @@ declare function createHttp(url: string | undefined, options: HttpOptions, envir
555
561
  owns?: string[] | undefined;
556
562
  reply: string | null;
557
563
  createRoot: boolean;
564
+ retitled?: boolean | undefined;
558
565
  author: {
559
566
  id: string;
560
567
  handle: string;
@@ -1,5 +1,5 @@
1
- import { t as BoardError } from "./client-DGDPaN7U.mjs";
2
- import { t as createHttp } from "./backend-http-BVRno-ca.mjs";
1
+ import { t as BoardError } from "./client-BA-J7usH.mjs";
2
+ import { t as createHttp } from "./backend-http-Ddzd5kTS.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -1,8 +1,8 @@
1
1
  import "./files-SniSmeEr.mjs";
2
2
  import "./schemas-Be08nyfW.mjs";
3
- import { V as routes, a as DERIVED_ID, c as keyId, l as sha256, o as derivedId, t as compileWatcher, w as RegisterBody } from "./watch-spec-W0c7ypHJ.mjs";
4
- import { n as createBoardClient, t as BoardError } from "./client-DGDPaN7U.mjs";
5
- import { n as METHODS, t as canonicalWatchers } from "./watchers-hash-CLjsa6od.mjs";
3
+ import { K as routes, k as RegisterBody, n as compileWatcher, o as METHODS, t as canonicalWatchers } from "./watchers-hash-RLtnSF-t.mjs";
4
+ import { n as createBoardClient, t as BoardError } from "./client-BA-J7usH.mjs";
5
+ import { a as sha256, i as keyId, n as derivedId, t as DERIVED_ID } from "./ids-C6AYo1bu.mjs";
6
6
  import { a as forbidden, o as json, t as HttpError } from "./errors-DX3hpwWt.mjs";
7
7
  //#region src/server/backend-http.ts
8
8
  /** A watcher's or client's name is the profile its writes are by. */
@@ -308,6 +308,35 @@ function createHttp(url, options, environment) {
308
308
  if ("self" in caller && bound().sync.some((entry) => entry.key === input.key)) return bound().readSource(input.key, input.target, input.limit ?? 50);
309
309
  return remoteCall("readSource", caller, input, signal);
310
310
  },
311
+ async synced(caller, input, signal) {
312
+ const { targets } = await remoteCall("synced", caller, input, signal);
313
+ return { targets: await Promise.all(targets.map(async (row) => ({
314
+ ...row,
315
+ url: row.url ?? (row.thread !== null && row.message !== null ? await bound().linkOf(row.key, row.thread, row.message) : null)
316
+ }))) };
317
+ },
318
+ async linked(caller, input, signal) {
319
+ const missing = () => new HttpError(404, "message_not_found", "no message on this board for that link");
320
+ const first = async () => {
321
+ for (const { platform, thread, message } of bound().locate(input.url)) for (const entry of bound().sync.filter((candidate) => candidate.platform === platform)) {
322
+ const at = await own.syncFind({
323
+ thread: `${entry.key}:${thread}`,
324
+ ...message !== null ? { message } : {}
325
+ }, { signal }).catch(rethrow);
326
+ const found = message !== null ? at.message : at.thread === null ? null : (await own.syncThread(at.thread).catch(rethrow)).root;
327
+ if (found !== null) return found;
328
+ }
329
+ return null;
330
+ };
331
+ const id = await first();
332
+ if (id === null) return remoteCall("linked", caller, input, signal);
333
+ try {
334
+ return await methods.message(caller, { id }, signal);
335
+ } catch (error) {
336
+ if (error instanceof HttpError && error.status === 404) throw missing();
337
+ throw error;
338
+ }
339
+ },
311
340
  async typing(caller, input, signal) {
312
341
  const { targets } = await remoteCall("typing", caller, input, signal);
313
342
  if (!("self" in caller) || bound().sync.length === 0) return { targets };
@@ -1,4 +1,4 @@
1
- import { n as HttpOptions, t as HttpBackend } from "./backend-http-Bid7RIpC.mjs";
1
+ import { n as HttpOptions, t as HttpBackend } from "./backend-http-7cNb8nJI.mjs";
2
2
  //#region src/backend-http/index.default.d.ts
3
3
  /**
4
4
  * The board at `url`, which keeps the log. Outside Node there's no environment to find it or a
@@ -1,2 +1,2 @@
1
- import { t as http } from "./index.default-Bkg77Pxn.mjs";
1
+ import { t as http } from "./index.default-C5Cpz3sa.mjs";
2
2
  export { http };
@@ -1,4 +1,4 @@
1
- import { n as HttpOptions, t as HttpBackend } from "./backend-http-Bid7RIpC.mjs";
1
+ import { n as HttpOptions, t as HttpBackend } from "./backend-http-7cNb8nJI.mjs";
2
2
  //#region src/backend-http/index.d.ts
3
3
  /**
4
4
  * The board at `url`, which keeps the log: this board uses it as this deployment. Without a
@@ -1,2 +1,2 @@
1
- import { t as http } from "./backend-http-0roEmjl0.mjs";
1
+ import { t as http } from "./backend-http-CMlL3yq3.mjs";
2
2
  export { http };
@@ -1,5 +1,5 @@
1
- import { _ as ExternalFile, h as ExternalAuthor, t as Actor, v as ExternalLink } from "./reducers-B_vUGQRp.mjs";
2
- import { Pt as SourceRead, a as ProviderFile, b as ProviderEvent, c as Receive, d as ResolvedTarget, h as SyncProvider, i as ProviderDelivery, l as ReceivedEvent, n as FetchedLink, o as ProviderOptions, r as FileDelivery, s as ProviderPost, w as SyncRoot } from "./provider-BGjz-mDa.mjs";
1
+ import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, t as Actor } from "./folds-CRYY14kc.mjs";
2
+ import { M as SyncRoot, O as ProviderEvent, Wt as SourceRead, a as ProviderOptions, c as ReceivedEvent, d as ResolvedTarget, i as ProviderFile, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, t as FetchedLink, y as SyncFn } from "./provider-B0b9Qxw4.mjs";
3
3
  import { Adapter, Author, FileUpload, Message, ReactionEvent } from "chat";
4
4
  //#region src/sync/types.d.ts
5
5
  declare function externalAuthor(platform: string, author: Author): ExternalAuthor;
@@ -34,6 +34,13 @@ type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
34
34
  /** Platform messages a message links to, located under sync key `key`. */
35
35
  links?(key: string, message: Message): ExternalLink[];
36
36
  fetchLink?(link: ExternalLink): Promise<FetchedLink>;
37
+ /** The message a URL of the platform's names, by its own IDs: `SyncProvider.locate`. */
38
+ locate?(url: string): {
39
+ thread: string;
40
+ message: string | null;
41
+ } | null;
42
+ /** The platform's URL for a message, by its own IDs: `SyncProvider.url`. */
43
+ url?(thread: string, message: string): Promise<string | null>;
37
44
  /** A channel or thread, read on demand without syncing it. */
38
45
  read?(target: string, options: {
39
46
  limit: number;
@@ -64,10 +71,10 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
64
71
  private readonly chat;
65
72
  private readonly receiving;
66
73
  private initialized;
67
- constructor(transport: ChatTransport);
74
+ constructor(transport: ChatTransport, sync: SyncFn<E>);
68
75
  protected toNative(id: string): string;
69
76
  protected toChat(id: string): string;
70
- protected locate(key: string, chatThread: string): {
77
+ protected locationOf(key: string, chatThread: string): {
71
78
  channel: string;
72
79
  thread: string;
73
80
  subthread: string | null;
@@ -109,6 +116,11 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
109
116
  mention(actor: Actor): string | null;
110
117
  fetchRoot(thread: string): Promise<SyncRoot | null>;
111
118
  fetchLink(link: ExternalLink): Promise<FetchedLink>;
119
+ locate(url: string): {
120
+ thread: string;
121
+ message: string | null;
122
+ } | null;
123
+ url(thread: string, message: string): Promise<string | null>;
112
124
  read(target: string, options: {
113
125
  limit: number;
114
126
  }): Promise<SourceRead>;
@@ -1,6 +1,6 @@
1
- import "./contract-CuJmlrhw.mjs";
2
- import { n as ResolvedTarget, r as SyncProvider } from "./provider-CuewcDNZ.mjs";
3
- import { n as userTokensOf } from "./users-Dls7_ANV.mjs";
1
+ import "./external-BV_cUncs.mjs";
2
+ import { n as ResolvedTarget, r as SyncProvider } from "./provider-B3DpYSDq.mjs";
3
+ import { n as userTokensOf } from "./users-yryHKFMo.mjs";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { Chat, stringifyMarkdown } from "chat";
6
6
  //#region src/sync/state.ts
@@ -128,12 +128,12 @@ var ChatSyncProvider = class extends SyncProvider {
128
128
  chat;
129
129
  receiving = new AsyncLocalStorage();
130
130
  initialized;
131
- constructor(transport) {
131
+ constructor(transport, sync) {
132
132
  super({
133
133
  ...transport,
134
134
  users: transport.users ?? userTokensOf(transport.adapter),
135
135
  uploadsFiles: !!transport.uploadFiles
136
- });
136
+ }, sync);
137
137
  this.transport = transport;
138
138
  this.chat = createChat(transport.adapter);
139
139
  const onMessage = async (_thread, message) => this.emit({
@@ -164,7 +164,7 @@ var ChatSyncProvider = class extends SyncProvider {
164
164
  toChat(id) {
165
165
  return `${this.transport.adapter.name}:${this.transport.toChatId?.(id) ?? id}`;
166
166
  }
167
- locate(key, chatThread) {
167
+ locationOf(key, chatThread) {
168
168
  const native = this.toNative(chatThread);
169
169
  const precise = `${key}:${native}`;
170
170
  const thread = `${key}:${this.transport.groupThread?.(native) ?? native}`;
@@ -203,7 +203,7 @@ var ChatSyncProvider = class extends SyncProvider {
203
203
  raw: input.raw,
204
204
  event: {
205
205
  kind: "deleted",
206
- ...this.locate(key, input.thread),
206
+ ...this.locationOf(key, input.thread),
207
207
  message: input.message
208
208
  }
209
209
  };
@@ -215,7 +215,7 @@ var ChatSyncProvider = class extends SyncProvider {
215
215
  raw: event.raw,
216
216
  event: {
217
217
  kind: "reaction",
218
- ...this.locate(key, event.threadId),
218
+ ...this.locationOf(key, event.threadId),
219
219
  message: event.messageId,
220
220
  emoji,
221
221
  added: event.added,
@@ -227,7 +227,7 @@ var ChatSyncProvider = class extends SyncProvider {
227
227
  const found = this.transport.links?.(key, message) ?? [];
228
228
  const links = found.length ? { links: found.slice(0, 10) } : {};
229
229
  const common = {
230
- ...this.locate(key, message.threadId),
230
+ ...this.locationOf(key, message.threadId),
231
231
  message: message.id,
232
232
  text: this.transport.markdown?.(message) ?? markdownOf(message),
233
233
  author: await this.authorOf(message),
@@ -303,6 +303,12 @@ var ChatSyncProvider = class extends SyncProvider {
303
303
  async fetchLink(link) {
304
304
  return this.transport.fetchLink ? this.transport.fetchLink(link) : super.fetchLink(link);
305
305
  }
306
+ locate(url) {
307
+ return this.transport.locate ? this.transport.locate(url) : super.locate(url);
308
+ }
309
+ async url(thread, message) {
310
+ return this.transport.url ? this.transport.url(thread, message) : super.url(thread, message);
311
+ }
306
312
  async read(target, options) {
307
313
  return this.transport.read ? this.transport.read(target, options) : super.read(target, options);
308
314
  }
package/dist/cli.mjs CHANGED
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
3
3
  import { a as AttachmentId } from "./schemas-Be08nyfW.mjs";
4
- import { i as withMentions, r as FILTER_FLAGS } from "./watch-spec-W0c7ypHJ.mjs";
4
+ import { a as withMentions, f as VERSION, i as FILTER_FLAGS } from "./watchers-hash-RLtnSF-t.mjs";
5
5
  import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
6
- import { t as BoardError } from "./client-DGDPaN7U.mjs";
7
- import { c as VERSION } from "./watchers-hash-CLjsa6od.mjs";
8
- import { a as detectAgent, c as projectUrl, i as connect, n as boardUrl, r as configDir, s as projectDir, t as http } from "./backend-http-0roEmjl0.mjs";
9
- import { t as board } from "./server-jwPY7_wi.mjs";
10
- import { t as conformance } from "./conformance-DRR23YNy.mjs";
6
+ import { t as BoardError } from "./client-BA-J7usH.mjs";
7
+ import { a as detectAgent, c as projectUrl, i as connect, n as boardUrl, r as configDir, s as projectDir, t as http } from "./backend-http-CMlL3yq3.mjs";
8
+ import { t as board } from "./server-DPxBSogx.mjs";
9
+ import { t as conformance } from "./conformance-CT5EeHeC.mjs";
11
10
  import { t as displayText } from "./bots-CGkO76qH.mjs";
12
11
  import { execFileSync, spawn } from "node:child_process";
13
12
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
@@ -806,6 +805,19 @@ function formatEvent(event) {
806
805
  }
807
806
  }
808
807
  /**
808
+ * Where a thread is synced, for `vb sync <id>`: a row a target, its sync key and where it is, then
809
+ * the link to the message's copy there, or why it didn't reach it.
810
+ */
811
+ function formatSynced(targets) {
812
+ const where = (row) => row.target.slice(row.key.length + 1);
813
+ const keys = Math.max(...targets.map((row) => row.key.length));
814
+ const wheres = Math.max(...targets.map((row) => where(row).length));
815
+ return targets.map((row) => {
816
+ const copy = row.undelivered !== null ? `didn't reach it: ${row.undelivered}` : row.url ?? (row.message !== null ? `message ${row.message}` : "no copy yet");
817
+ return `${row.key.padEnd(keys)} ${where(row).padEnd(wheres)} ${terminalText(copy)}`;
818
+ }).join("\n");
819
+ }
820
+ /**
809
821
  * A channel or thread read with `vb sources <key> read`: a header, then each message under its ID,
810
822
  * with the target that reads its thread when it has replies.
811
823
  */
@@ -1284,6 +1296,7 @@ usage: board <command> [options] (also installed as \`vb\`)
1284
1296
  read [channel] [filters] [--limit N] the latest threads, one each: root, replies, latest reply
1285
1297
  (no channel: every channel)
1286
1298
  read <id> one thread, every message (filters: the root and the replies that match)
1299
+ read <link> the thread a Slack or GitHub link names, if the board synced it
1287
1300
 
1288
1301
  post <channel|id> ["<text>"] [--label k=v]... [--attach <file>]... [--sync <target>]... [--no-sync] [--id <key>]
1289
1302
  a new thread in a channel, or with an id, a reply in its thread
@@ -1300,6 +1313,7 @@ usage: board <command> [options] (also installed as \`vb\`)
1300
1313
  label <id> k=v... [--unset k]...
1301
1314
  typing <id> [status] show typing in the thread's synced platform threads (marks WhatsApp read);
1302
1315
  Slack shows the status, if given, instead of "Typing…"
1316
+ sync <id> where the thread is synced, with this message's link on each
1303
1317
  sync <id> <target> attach the thread to e.g. slack:#incidents, backfilling it
1304
1318
  inbox <key> [channel] [--limit N] raw platform events, for debugging sync
1305
1319
  sources the board's sources (its sync entries: slack, github, …)
@@ -1842,8 +1856,9 @@ async function run(command, args) {
1842
1856
  case "read": {
1843
1857
  const target = args[0];
1844
1858
  const board = client();
1845
- if (target && isMessageRef(target)) {
1846
- const thread = await board.thread(messageId(target));
1859
+ const link = target !== void 0 && /^https?:\/\//i.test(target);
1860
+ if (target && (link || isMessageRef(target))) {
1861
+ const thread = link ? await board.linked(target) : await board.thread(messageId(target));
1847
1862
  const narrowed = Object.keys(filters()).length > 0;
1848
1863
  const replies = narrowed ? (await board.messages({
1849
1864
  ...filters(),
@@ -2099,7 +2114,12 @@ async function run(command, args) {
2099
2114
  }
2100
2115
  case "sync": {
2101
2116
  const id = messageId(args[0]);
2102
- if (!args[1]) throw new UsageError("usage: board sync <id> <target> (e.g. slack:#incidents)");
2117
+ if (!args[1]) {
2118
+ const { targets } = await client().sync(id);
2119
+ if (flags.json) return targets.forEach(printJson), 0;
2120
+ out(targets.length ? formatSynced(targets) : "not synced anywhere");
2121
+ return 0;
2122
+ }
2103
2123
  const result = await client().sync(id, args[1]);
2104
2124
  if (flags.json) printJson(result);
2105
2125
  else out(result.attached ? `synced with ${result.target}` : `already synced with ${result.target}`);
@@ -2175,7 +2195,7 @@ async function run(command, args) {
2175
2195
  if (args.length > 0) throw new UsageError("usage: board serve [--port N] [--dir <dir>] [--no-inspect] [--no-open]");
2176
2196
  const port = portFlag();
2177
2197
  const root = projectDir();
2178
- const { localHandle, serveLocal } = await import("./serve-BsOOc-6_.mjs");
2198
+ const { localHandle, serveLocal } = await import("./serve-BGYw0uNL.mjs");
2179
2199
  const handle = flags.handle ?? process.env.BOARD_HANDLE;
2180
2200
  const local = await serveLocal({
2181
2201
  dir: flags.dir ?? join(root, ".board"),
@@ -260,8 +260,16 @@ function createBoardClient(options) {
260
260
  * `status` replaces "Typing…" on Slack ("running tests…").
261
261
  */
262
262
  typing: async (id, status) => (await call("POST", `/messages/${path(id)}/typing`, { body: status ? { status } : {} })).targets,
263
- /** Attaches the message's thread to an external target, backfilling it. Attaching twice is a no-op. */
264
- sync: (id, target) => call("POST", `/messages/${path(id)}/sync`, { body: { target } }),
263
+ /**
264
+ * With a target, attaches the message's thread to it, backfilling it: attaching twice is a no-op.
265
+ * Without one, where the thread is synced, and this message's copy on each target, with its link.
266
+ */
267
+ sync: ((id, target) => target === void 0 ? call("GET", `/messages/${path(id)}/sync`) : call("POST", `/messages/${path(id)}/sync`, { body: { target } })),
268
+ /**
269
+ * The thread of the board message a platform link names (a Slack permalink, a GitHub comment's
270
+ * URL), from what the board synced: the platform is never asked. 404 for one it didn't sync.
271
+ */
272
+ linked: (url) => call("GET", `/links${queryString({ url })}`),
265
273
  /** Raw platform events for a sync entry, newest first. */
266
274
  inbox: async (key, opts = {}) => (await call("GET", `/inbox/${key}${queryString(opts)}`)).events,
267
275
  /** Registers the caller's watchers, replacing its last registration: what a board on `http()` does. */
@@ -1,4 +1,4 @@
1
- import { t as BoardError } from "./client-DGDPaN7U.mjs";
1
+ import { t as BoardError } from "./client-BA-J7usH.mjs";
2
2
  //#region src/conformance/index.ts
3
3
  var Mismatch = class extends Error {};
4
4
  const show = (value) => JSON.stringify(value) ?? String(value);
@@ -298,7 +298,7 @@ const CHECKS = [
298
298
  },
299
299
  {
300
300
  group: "sync",
301
- name: "keeps sync's records: an import as a platform author, its lookups by platform ID, and a receipt",
301
+ name: "keeps sync's records: an import as a platform author, its lookups by platform ID, a receipt, and where it's synced",
302
302
  run: async (ctx) => {
303
303
  const key = "conformance";
304
304
  const thread = `${key}:${ctx.labels.conformance}:1`;
@@ -411,6 +411,9 @@ const CHECKS = [
411
411
  external
412
412
  });
413
413
  same(JSON.stringify((await ctx.board.syncCopies([id]))[id]?.[external.thread]), JSON.stringify(external), "the copy its receipt recorded");
414
+ const { targets } = await ctx.board.sync(id);
415
+ check(targets.some((row) => row.target === external.thread && row.message === external.message), `where the thread is synced should show the copy its receipt recorded, got ${show(targets)}`);
416
+ await rejects(ctx.board.linked(`https://conformance.invalid/${ctx.labels.conformance}`), 404, "message_not_found", "a link no sync here knows");
414
417
  } finally {
415
418
  await ctx.board.syncImport({
416
419
  op: "delete",
@@ -1,4 +1,4 @@
1
- import { m as Board } from "./a2-D24cvwpN.mjs";
1
+ import { d as Board } from "./backend-B-SJ7p6p.mjs";
2
2
  //#region src/conformance/index.d.ts
3
3
  /**
4
4
  * Checks that a board speaks the protocol, through a handle on it (`conformance(board())`), as
@@ -1,2 +1,2 @@
1
- import { t as conformance } from "./conformance-DRR23YNy.mjs";
1
+ import { t as conformance } from "./conformance-CT5EeHeC.mjs";
2
2
  export { conformance };