@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,6 +1,6 @@
1
1
  import { n as AccessSummary } from "./access-CV96SYgr.mjs";
2
2
  import { i as Attachment } from "./schemas-CWxs3ECJ.mjs";
3
- import { _ as ExternalFile, a as ExternalCopy, g as ExternalEvent, h as ExternalAuthor, o as Message, p as BoardEvent, r as Channel, t as Actor, v as ExternalLink, y as Link } from "./reducers-B_vUGQRp.mjs";
3
+ import { a as ExternalCopy, d as ExternalFile, f as ExternalLink, g as BoardEvent, l as ExternalAuthor, o as Message, p as Link, r as Channel, t as Actor, u as ExternalEvent } from "./folds-CRYY14kc.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/api/routes.d.ts
6
6
  /**
@@ -26,6 +26,8 @@ declare const Filters$1: z.ZodObject<{
26
26
  from: z.ZodOptional<z.ZodString>;
27
27
  text: z.ZodOptional<z.ZodString>;
28
28
  }, z.core.$strip>;
29
+ /** The filters a query asks for, as the board parsed them. */
30
+ type ParsedFilters = z.output<typeof Filters$1>;
29
31
  declare const MessagesQuery: z.ZodObject<{
30
32
  channel: z.ZodOptional<z.ZodString>;
31
33
  re: z.ZodOptional<z.ZodString>;
@@ -148,6 +150,10 @@ declare const SourceReadQuery: z.ZodObject<{
148
150
  target: z.ZodString;
149
151
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
150
152
  }, z.core.$strip>;
153
+ /** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
154
+ declare const LinkedQuery: z.ZodObject<{
155
+ url: z.ZodString;
156
+ }, z.core.$strip>;
151
157
  declare const IdentityUnlinkBody: z.ZodObject<{
152
158
  actor: z.ZodString;
153
159
  }, z.core.$strip>;
@@ -468,6 +474,7 @@ declare const InboxPushBody: z.ZodObject<{
468
474
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
469
475
  reply: z.ZodNullable<z.ZodString>;
470
476
  createRoot: z.ZodBoolean;
477
+ retitled: z.ZodOptional<z.ZodBoolean>;
471
478
  author: z.ZodObject<{
472
479
  id: z.ZodString;
473
480
  handle: z.ZodString;
@@ -791,6 +798,25 @@ type MessagesPage = {
791
798
  messages: MessageView[];
792
799
  cursor: string;
793
800
  };
801
+ /**
802
+ * Where a message's thread is synced: each target, the platform thread it is there, and this
803
+ * message's copy, with the platform's link to it when the sync that knows the platform runs here.
804
+ */
805
+ type SyncedView = {
806
+ targets: {
807
+ /** As the thread's sync records it: `slack:C0123ABC`, `github:acme/www:12`. */
808
+ target: string;
809
+ /** The sync entry it belongs to: `slack`. */
810
+ key: string;
811
+ /** The platform thread this message's copy is in, or the one the target is, once it has one. */
812
+ thread: string | null;
813
+ /** This message's copy there, by the platform's ID: null when it has none. */
814
+ message: string | null;
815
+ url: string | null;
816
+ /** Why the message didn't reach it, when it didn't. */
817
+ undelivered: string | null;
818
+ }[];
819
+ };
794
820
  type ThreadView = {
795
821
  message: MessageView;
796
822
  replies: MessageView[];
@@ -973,6 +999,19 @@ declare const routes: {
973
999
  target: z.ZodString;
974
1000
  }, z.core.$strip>;
975
1001
  };
1002
+ /** Where the message's thread is synced, and its copy on each target: needs `read` where it is. */
1003
+ readonly synced: {
1004
+ readonly method: 'GET';
1005
+ readonly path: '/messages/:id/sync';
1006
+ };
1007
+ /** The board message a platform link names, from what the board synced: 404 `message_not_found` for one it didn't, or that the caller can't read. */
1008
+ readonly linked: {
1009
+ readonly method: 'GET';
1010
+ readonly path: '/links';
1011
+ readonly query: z.ZodObject<{
1012
+ url: z.ZodString;
1013
+ }, z.core.$strip>;
1014
+ };
976
1015
  /** Ephemeral: shows typing in the thread's synced platform threads, and writes nothing to the log. Needs `post` where the thread is. */
977
1016
  readonly typing: {
978
1017
  readonly method: 'POST';
@@ -1141,6 +1180,7 @@ declare const routes: {
1141
1180
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
1142
1181
  reply: z.ZodNullable<z.ZodString>;
1143
1182
  createRoot: z.ZodBoolean;
1183
+ retitled: z.ZodOptional<z.ZodBoolean>;
1144
1184
  author: z.ZodObject<{
1145
1185
  id: z.ZodString;
1146
1186
  handle: z.ZodString;
@@ -1752,11 +1792,22 @@ declare function createBoardClient(options: ClientOptions): {
1752
1792
  * `status` replaces "Typing…" on Slack ("running tests…").
1753
1793
  */
1754
1794
  typing: (id: string, status?: string) => Promise<string[]>;
1755
- /** Attaches the message's thread to an external target, backfilling it. Attaching twice is a no-op. */
1756
- sync: (id: string, target: string) => Promise<{
1757
- target: string;
1758
- attached: boolean;
1759
- }>;
1795
+ /**
1796
+ * With a target, attaches the message's thread to it, backfilling it: attaching twice is a no-op.
1797
+ * Without one, where the thread is synced, and this message's copy on each target, with its link.
1798
+ */
1799
+ sync: {
1800
+ (id: string): Promise<SyncedView>;
1801
+ (id: string, target: string): Promise<{
1802
+ target: string;
1803
+ attached: boolean;
1804
+ }>;
1805
+ };
1806
+ /**
1807
+ * The thread of the board message a platform link names (a Slack permalink, a GitHub comment's
1808
+ * URL), from what the board synced: the platform is never asked. 404 for one it didn't sync.
1809
+ */
1810
+ linked: (url: string) => Promise<ThreadView>;
1760
1811
  /** Raw platform events for a sync entry, newest first. */
1761
1812
  inbox: (key: string, opts?: {
1762
1813
  channel?: string;
@@ -2003,6 +2054,7 @@ declare function envelope(platform: string, key: string, event: ExternalEvent, r
2003
2054
  owns?: string[] | undefined;
2004
2055
  reply: string | null;
2005
2056
  createRoot: boolean;
2057
+ retitled?: boolean | undefined;
2006
2058
  author: {
2007
2059
  id: string;
2008
2060
  handle: string;
@@ -2145,6 +2197,7 @@ declare function eventSchema<P extends string, M extends string>(platform: P, me
2145
2197
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
2146
2198
  reply: z.ZodNullable<z.ZodString>;
2147
2199
  createRoot: z.ZodBoolean;
2200
+ retitled: z.ZodOptional<z.ZodBoolean>;
2148
2201
  author: z.ZodObject<{
2149
2202
  id: z.ZodString;
2150
2203
  handle: z.ZodString;
@@ -2181,6 +2234,88 @@ type SyncResult = {
2181
2234
  message: string | null;
2182
2235
  };
2183
2236
  //#endregion
2237
+ //#region src/sync/fn.d.ts
2238
+ /** An event coming in from the platform. */
2239
+ type SyncIn<E extends ProviderEvent = ProviderEvent> = {
2240
+ direction: 'in';
2241
+ /** The sync entry's key: `slack` in `sync: { slack: slack(…) }`. */
2242
+ key: string;
2243
+ /** The platform channel it happened in, as the platform names it: `C0123ABC`, `owner/repo:pulls`, a phone number ID. */
2244
+ channel: string;
2245
+ /** The event, validated and frozen. */
2246
+ event: E;
2247
+ /** The board, as the platform user the event came from: its writes are theirs. */
2248
+ board: BoardClient;
2249
+ /**
2250
+ * Imports the event: into its board thread when the board syncs its platform thread, otherwise
2251
+ * as a new root in `channel`. Without a channel, an event in a thread the board doesn't sync
2252
+ * stays off it. Runs once: a second call answers what the first did.
2253
+ */
2254
+ import(channel?: string, overrides?: SyncOverrides): Promise<SyncResult>;
2255
+ /** The platform's own link to the event's message, or null when it has none. Nothing is synced or joined. */
2256
+ permalink(): Promise<string | null>;
2257
+ /** A platform channel's ID from its name (`#dev` → `C0123ABC`), as `channel` names it. */
2258
+ resolve(name: string): Promise<string>;
2259
+ };
2260
+ /** A board event going out to the platform. */
2261
+ type SyncOut = {
2262
+ direction: 'out';
2263
+ key: string;
2264
+ /** The platform channel `target` is in, as the platform names it; null for a new root, which isn't on the platform yet. */
2265
+ channel: string | null;
2266
+ event: BoardEventView;
2267
+ /** The message the event is about, as it is now. */
2268
+ message: MessageView;
2269
+ /** Where it goes: a platform thread or channel the board thread syncs with (`slack:C0123ABC:1727000000.123456`), or null for a new root. */
2270
+ target: string | null;
2271
+ /**
2272
+ * Sends the event to `target`: a new thread's root and what's been said in it, a reply, an
2273
+ * edit, a delete, a reaction, or the copy a label change re-renders. A new root has no target
2274
+ * yet, so it sends nothing. Runs once: a second call answers what the first did.
2275
+ */
2276
+ deliver(options?: {
2277
+ render?: Renderer;
2278
+ }): Promise<void>;
2279
+ /**
2280
+ * Syncs the message's thread with a platform channel (`#dev`, `C0123ABC`): the board starts a
2281
+ * thread there, and its root and replies follow. Never the channel it came from.
2282
+ */
2283
+ start(channel: string): Promise<void>;
2284
+ resolve(name: string): Promise<string>;
2285
+ };
2286
+ type Sync<E extends ProviderEvent = ProviderEvent> = SyncIn<E> | SyncOut;
2287
+ /**
2288
+ * What every event does, both ways. Inbound, it calls `import` to bring the event onto the board;
2289
+ * outbound, `deliver` to send it to the platform, or `start` to open a new root's thread there.
2290
+ * One that calls neither does nothing. A throw is retried.
2291
+ *
2292
+ * The webhook keeps an event (stores it in the inbox for the function) when the board syncs its
2293
+ * thread, or when its channel is one that `channels`, `repos`, or `numbers` routes. A function of
2294
+ * your own at the top keeps every event the platform sends, so keep the helper outermost.
2295
+ */
2296
+ type SyncFn<E extends ProviderEvent = ProviderEvent> = (sync: Sync<E>) => unknown;
2297
+ /** A sync function, or a board channel: what a string means is up to the helper that takes it. */
2298
+ type SyncUnit<E extends ProviderEvent = ProviderEvent> = string | SyncFn<E>;
2299
+ /**
2300
+ * A two-way mirror of a board channel: what comes in lands in `channel`, what's said on the
2301
+ * board goes out, and a new root in `channel` starts a thread in the platform channel.
2302
+ */
2303
+ declare function mirror(channel: string): SyncFn;
2304
+ /** Imports into a board channel, and sends nothing back. */
2305
+ declare function into(channel: string): SyncFn;
2306
+ /** `inner`, delivering with `renderer` unless it passes its own. */
2307
+ declare function render<E extends ProviderEvent = ProviderEvent>(renderer: Renderer, inner: SyncFn<E>): SyncFn<E>;
2308
+ /**
2309
+ * Routes each event by its platform channel. Keys are platform channels, by name (`'#dev'`) or
2310
+ * ID (`'C0123ABC'`); values are a board channel to `mirror`, or a sync function. A new root goes
2311
+ * to every channel's function, with `channel` set to that one. A thread the board syncs outside
2312
+ * these channels (someone ran `vb sync` on it) stays synced both ways. A key the platform says
2313
+ * doesn't exist is skipped. While one can't be checked, an event outside the resolved channels
2314
+ * throws, so it's retried, rather than going past that key's function to the default. A new root
2315
+ * starts only in the channels that resolved.
2316
+ */
2317
+ declare function channels<E extends ProviderEvent = ProviderEvent>(map: Record<string, SyncUnit<E>>): SyncFn<E>;
2318
+ //#endregion
2184
2319
  //#region src/sync/users.d.ts
2185
2320
  /**
2186
2321
  * People's own platform tokens, so a board message they wrote reaches Slack or
@@ -2201,8 +2336,8 @@ type UserTokens = {
2201
2336
  };
2202
2337
  /**
2203
2338
  * Gives an adapter people's tokens, for your own OAuth: `slack({ adapter:
2204
- * withUserTokens(createSlackAdapter(...), { authorize, token }) })`. A Connect
2205
- * connector name (`adapter: 'slack/acme'`) does this for you.
2339
+ * withUserTokens(createSlackAdapter(...), { authorize, token }) }, sync)`. A
2340
+ * Connect connector name (`slack('slack/acme', sync)`) does this for you.
2206
2341
  */
2207
2342
  declare function withUserTokens<A extends object>(adapter: A, tokens: UserTokens): A;
2208
2343
  /** Vercel Connect's user tokens for `connector`: the subject is the board's principal, so Connect binds the account to it. */
@@ -2218,32 +2353,12 @@ declare const ResolvedTarget: z.ZodObject<{
2218
2353
  }>;
2219
2354
  }, z.core.$strict>;
2220
2355
  type ResolvedTarget = z.infer<typeof ResolvedTarget>;
2221
- type SyncClient<E extends ProviderEvent = ProviderEvent> = Omit<BoardClient, 'sync'> & {
2222
- sync(id: string, target: string): ReturnType<BoardClient['sync']>;
2223
- sync(channel: string, event: E, overrides?: SyncOverrides): Promise<SyncResult>;
2224
- };
2225
- type SyncHandler<E extends ProviderEvent> = (args: {
2226
- event: E;
2227
- board: SyncClient<E>;
2228
- /**
2229
- * The platform's own link to the event's message (Slack's `chat.getPermalink`), fetched on
2230
- * the first call and kept for the rest of the callback. Null when the event has no single
2231
- * message or the platform has no links. It only reads the URL: nothing is synced or joined.
2232
- */
2233
- permalink(): Promise<string | null>;
2234
- }) => unknown;
2356
+ /** The fields a provider's `event` builds its typed event from. */
2235
2357
  type SyncContext = {
2236
2358
  id: string;
2237
2359
  key: string;
2238
2360
  event: ExternalEvent;
2239
2361
  raw: unknown;
2240
- board: BoardClient;
2241
- client<E extends ProviderEvent>(event: E): SyncClient<E>;
2242
- apply(channel: string, overrides?: SyncOverrides): Promise<SyncResult>;
2243
- };
2244
- type ProviderMapping = {
2245
- channel?: string;
2246
- run?: (context: SyncContext) => unknown;
2247
2362
  };
2248
2363
  type RenderInput = {
2249
2364
  /** The message as clients see it: its IDs are refs. */
@@ -2254,16 +2369,19 @@ type RenderInput = {
2254
2369
  label: string;
2255
2370
  target: string;
2256
2371
  };
2257
- type EventMapping<E extends ProviderEvent> = string | SyncHandler<E> | {
2258
- channel: string;
2259
- run: SyncHandler<E>;
2260
- };
2372
+ /** How a platform shows a board message: markdown, which the platform converts to its own format. */
2373
+ type Renderer = (input: RenderInput) => string;
2261
2374
  type FileDelivery = {
2262
2375
  message: string;
2263
2376
  files: string[];
2264
2377
  };
2265
2378
  /** What a linked message said, or why the platform wouldn't say. */
2266
2379
  type FetchedLink = Pick<Link, 'snapshot' | 'unavailable'>;
2380
+ /** Why a platform wouldn't read a channel or thread on demand. Its message is shown as is. */
2381
+ declare class UnreadableError extends Error {
2382
+ readonly code: string;
2383
+ constructor(code: string, message: string);
2384
+ }
2267
2385
  /** A file to send: its bytes (a Node `Buffer` is one), its name, and its type. */
2268
2386
  type ProviderFile = {
2269
2387
  data: Uint8Array;
@@ -2293,7 +2411,6 @@ type ProviderOptions = {
2293
2411
  canStartThreads?: boolean;
2294
2412
  singleReaction?: boolean;
2295
2413
  uploadsFiles?: boolean;
2296
- render?: (input: RenderInput) => string;
2297
2414
  /** People's own tokens, to post as them and to link accounts by signing in. */
2298
2415
  users?: UserTokens | null;
2299
2416
  };
@@ -2403,6 +2520,7 @@ declare const ReceivedEvent: z.ZodObject<{
2403
2520
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
2404
2521
  reply: z.ZodNullable<z.ZodString>;
2405
2522
  createRoot: z.ZodBoolean;
2523
+ retitled: z.ZodOptional<z.ZodBoolean>;
2406
2524
  author: z.ZodObject<{
2407
2525
  id: z.ZodString;
2408
2526
  handle: z.ZodString;
@@ -2421,11 +2539,11 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2421
2539
  readonly canStartThreads: boolean;
2422
2540
  readonly singleReaction: boolean;
2423
2541
  readonly uploadsFiles: boolean;
2424
- readonly mappings: Record<string, ProviderMapping>;
2542
+ private readonly fn;
2425
2543
  /** People's tokens, when the adapter has them: messages they write post as them. */
2426
2544
  readonly users: UserTokens | null;
2427
2545
  protected readonly options: ProviderOptions;
2428
- constructor(options: ProviderOptions);
2546
+ constructor(options: ProviderOptions, sync: SyncFn<E>);
2429
2547
  abstract receive(key: string, request: Request, emit: Receive): Promise<Response>;
2430
2548
  abstract resolveChannel(key: string): Promise<string>;
2431
2549
  abstract resolveTarget(target: string): Promise<ResolvedTarget>;
@@ -2444,6 +2562,17 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2444
2562
  permalink(_event: E): Promise<string | null>;
2445
2563
  /** A linked message the platform didn't describe, read from the platform. */
2446
2564
  fetchLink(_link: ExternalLink): Promise<FetchedLink>;
2565
+ /**
2566
+ * The message a URL of this platform's names, by its own thread and message IDs, or null for a
2567
+ * URL that isn't one. `message` is null for a link to a thread itself. It asks the platform
2568
+ * nothing: the board answers from what it synced (`vb read <url>`).
2569
+ */
2570
+ locate(_url: string): {
2571
+ thread: string;
2572
+ message: string | null;
2573
+ } | null;
2574
+ /** The platform's URL for one of its messages, by its own IDs, or null when it has none (`vb sync <id>`). */
2575
+ url(_thread: string, _message: string): Promise<string | null>;
2447
2576
  /**
2448
2577
  * A channel or thread, read on demand for whoever asked: nothing is synced,
2449
2578
  * stored, or joined. Throws `UnreadableError` for what it won't read.
@@ -2464,10 +2593,11 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2464
2593
  deleteFiles(_ids: string[]): Promise<void>;
2465
2594
  refusesFiles(_error: unknown): boolean;
2466
2595
  uploadFiles(_thread: string, _files: ProviderFile[]): Promise<FileDelivery>;
2467
- abstract event(context: Pick<SyncContext, 'id' | 'key' | 'event' | 'raw'>): E;
2468
- sync(context: SyncContext, channel: string, overrides?: SyncOverrides): Promise<SyncResult>;
2596
+ abstract event(context: SyncContext): E;
2597
+ /** Runs the sync function the provider was made with: what every event does, both ways. */
2598
+ sync(sync: Sync<E>): unknown;
2599
+ /** The default rendering: `**${label}**: ${text}`. A `Renderer` passed to `deliver` replaces it. */
2469
2600
  render(input: RenderInput): string;
2470
- protected map(key: string, value: EventMapping<E>): void;
2471
2601
  }
2472
2602
  //#endregion
2473
- export { InboxAckBody as $, ThreadSummaryView as $t, PostOptions as A, SchedulesQueryInput as At, ActorView as B, SyncCodeView as Bt, SyncResult as C, ResolvedSyncTarget as Ct, BoardClient as D, ScheduleInput as Dt, eventSchema as E, ScheduleBodyOutput as Et, UploadInput as F, SourceReadQuery as Ft, Discovery as G, SyncFindView as Gt, BoardEventView as H, SyncCopiesView as Ht, WaitOptions as I, SyncAccountBody as It, EventsQuery as J, SyncReceiptBody as Jt, EditBody as K, SyncImportBody as Kt, Written as L, SyncAccountInput as Lt, ReplyOptions as M, SignInQuery as Mt, ScheduleOptions as N, SourceMessage as Nt, BoardError as O, ScheduleView as Ot, ScheduleTiming as P, SourceRead as Pt, IdentityView as Q, SyncUnlinkBody as Qt, duration as R, SyncBody as Rt, SyncOverrides as S, RegistrationView as St, envelope as T, ScheduleBody as Tt, ChannelView as U, SyncFindInput as Ut, ApiError as V, SyncCopiesQuery as Vt, DeleteBody as W, SyncFindQuery as Wt, FiltersInput as X, SyncRootsQuery as Xt, EventsQueryInput as Y, SyncReceiptInput as Yt, IdentityUnlinkBody as Z, SyncThreadView as Zt, connectUserTokens as _, PostInput as _t, ProviderFile as a, TypingResult as an, LabelBody as at, ProviderEvent as b, Ref as bt, Receive as c, routes as cn, MatchView as ct, ResolvedTarget as d, MessageView as dt, ThreadView as en, InboxEntry as et, SyncClient as f, MessagesPage as ft, UserTokens as g, PostBody as gt, SyncProvider as h, PersonView as ht, ProviderDelivery as i, TypingBody as in, InboxQuery as it, ProfileOption as j, SchedulesQueryOutput as jt, Filters as k, SchedulesQuery as kt, ReceivedEvent as l, MatchesQuery as lt, SyncHandler as m, MessagesQueryInput as mt, FetchedLink as n, ThreadsQuery as nn, InboxNextQuery as nt, ProviderOptions as o, UploadStartBody as on, LinkView as ot, SyncContext as p, MessagesQuery as pt, EventsPage as q, SyncImportInput as qt, FileDelivery as r, ThreadsQueryInput as rn, InboxPushBody as rt, ProviderPost as s, UploadTicket as sn, LinkedAccount as st, EventMapping as t, ThreadsPage as tn, InboxItem as tt, RenderInput as u, Me as ut, withUserTokens as v, REGISTERED_WATCHERS_MAX as vt, SyncRoot as w, RouteName as wt, RootOverride as x, RegisterBody as xt, EventBase as y, ReactBody as yt, AckBody as z, SyncCodeBody as zt };
2603
+ export { Discovery as $, SyncFindInput as $t, SyncOverrides as A, Ref as At, ReplyOptions as B, SchedulesQueryInput as Bt, channels as C, MessagesQueryInput as Ct, EventBase as D, PostInput as Dt, render as E, PostBody as Et, BoardClient as F, ScheduleBody as Ft, Written as G, SourceReadQuery as Gt, ScheduleTiming as H, SignInQuery as Ht, BoardError as I, ScheduleBodyOutput as It, ActorView as J, SyncBody as Jt, duration as K, SyncAccountBody as Kt, Filters as L, ScheduleInput as Lt, SyncRoot as M, RegistrationView as Mt, envelope as N, ResolvedSyncTarget as Nt, ProviderEvent as O, REGISTERED_WATCHERS_MAX as Ot, eventSchema as P, RouteName as Pt, DeleteBody as Q, SyncCopiesView as Qt, PostOptions as R, ScheduleView as Rt, SyncUnit as S, MessagesQuery as St, mirror as T, PersonView as Tt, UploadInput as U, SourceMessage as Ut, ScheduleOptions as V, SchedulesQueryOutput as Vt, WaitOptions as W, SourceRead as Wt, BoardEventView as X, SyncCodeView as Xt, ApiError as Y, SyncCodeBody as Yt, ChannelView as Z, SyncCopiesQuery as Zt, withUserTokens as _, UploadStartBody as _n, MatchView as _t, ProviderOptions as a, SyncReceiptInput as an, IdentityUnlinkBody as at, SyncIn as b, MessageView as bt, ReceivedEvent as c, SyncUnlinkBody as cn, InboxEntry as ct, ResolvedTarget as d, ThreadView as dn, InboxPushBody as dt, SyncFindQuery as en, EditBody as et, SyncContext as f, ThreadsPage as fn, InboxQuery as ft, connectUserTokens as g, TypingResult as gn, LinkedQuery as gt, UserTokens as h, TypingBody as hn, LinkedAccount as ht, ProviderFile as i, SyncReceiptBody as in, FiltersInput as it, SyncResult as j, RegisterBody as jt, RootOverride as k, ReactBody as kt, RenderInput as l, SyncedView as ln, InboxItem as lt, UnreadableError as m, ThreadsQueryInput as mn, LinkView as mt, FileDelivery as n, SyncImportBody as nn, EventsQuery as nt, ProviderPost as o, SyncRootsQuery as on, IdentityView as ot, SyncProvider as p, ThreadsQuery as pn, LabelBody as pt, AckBody as q, SyncAccountInput as qt, ProviderDelivery as r, SyncImportInput as rn, EventsQueryInput as rt, Receive as s, SyncThreadView as sn, InboxAckBody as st, FetchedLink as t, SyncFindView as tn, EventsPage as tt, Renderer as u, ThreadSummaryView as un, InboxNextQuery as ut, Sync as v, UploadTicket as vn, MatchesQuery as vt, into as w, ParsedFilters as wt, SyncOut as x, MessagesPage as xt, SyncFn as y, routes as yn, Me as yt, ProfileOption as z, SchedulesQuery as zt };
@@ -0,0 +1,220 @@
1
+ import { n as ExternalEvent } from "./external-BV_cUncs.mjs";
2
+ import { z } from "zod";
3
+ //#region src/sync/fn.ts
4
+ const ROUTES = Symbol.for("board.syncRoutes");
5
+ /** A helper's function, marked with the channels it routes. */
6
+ function withRoutes(fn, routes) {
7
+ Object.defineProperty(fn, ROUTES, {
8
+ value: routes,
9
+ enumerable: false,
10
+ configurable: true
11
+ });
12
+ return fn;
13
+ }
14
+ /** The channels a helper's function routes; undefined for a function of your own, which keeps everything. */
15
+ const routesOf = (fn) => fn[ROUTES];
16
+ /**
17
+ * A two-way mirror of a board channel: what comes in lands in `channel`, what's said on the
18
+ * board goes out, and a new root in `channel` starts a thread in the platform channel.
19
+ */
20
+ function mirror(channel) {
21
+ return async (sync) => {
22
+ if (sync.direction === "in") return void await sync.import(channel);
23
+ if (sync.target !== null) return sync.deliver();
24
+ if (sync.channel !== null && sync.message.channel === channel) await sync.start(sync.channel);
25
+ };
26
+ }
27
+ /** Imports into a board channel, and sends nothing back. */
28
+ function into(channel) {
29
+ return async (sync) => {
30
+ if (sync.direction === "in") await sync.import(channel);
31
+ };
32
+ }
33
+ /** `inner`, delivering with `renderer` unless it passes its own. */
34
+ function render(renderer, inner) {
35
+ const fn = (sync) => inner(sync.direction === "in" ? sync : {
36
+ ...sync,
37
+ deliver: (options = {}) => sync.deliver({
38
+ render: renderer,
39
+ ...options
40
+ })
41
+ });
42
+ const routes = routesOf(inner);
43
+ return routes ? withRoutes(fn, routes) : fn;
44
+ }
45
+ /** What a thread the board syncs does outside the mapped channels: it stays synced, both ways. */
46
+ const follow = async (sync) => {
47
+ if (sync.direction === "in") await sync.import();
48
+ else await sync.deliver();
49
+ };
50
+ /**
51
+ * Routes each event by its platform channel. Keys are platform channels, by name (`'#dev'`) or
52
+ * ID (`'C0123ABC'`); values are a board channel to `mirror`, or a sync function. A new root goes
53
+ * to every channel's function, with `channel` set to that one. A thread the board syncs outside
54
+ * these channels (someone ran `vb sync` on it) stays synced both ways. A key the platform says
55
+ * doesn't exist is skipped. While one can't be checked, an event outside the resolved channels
56
+ * throws, so it's retried, rather than going past that key's function to the default. A new root
57
+ * starts only in the channels that resolved.
58
+ */
59
+ function channels(map) {
60
+ const units = Object.entries(map).map(([name, unit]) => [name, typeof unit === "string" ? mirror(unit) : unit]);
61
+ const resolved = async (resolve) => {
62
+ const ids = await Promise.allSettled(units.map(([name]) => resolve(name)));
63
+ const found = /* @__PURE__ */ new Map();
64
+ let unchecked;
65
+ ids.forEach((id, i) => {
66
+ if (id.status === "fulfilled") found.set(id.value, units[i][1]);
67
+ else if (!channelMissing(id.reason)) unchecked ??= { reason: id.reason };
68
+ });
69
+ return {
70
+ found,
71
+ unchecked
72
+ };
73
+ };
74
+ const fn = async (sync) => {
75
+ const { found, unchecked } = await resolved(sync.resolve);
76
+ if (sync.direction === "out" && sync.target === null) {
77
+ for (const [channel, unit] of found) await unit({
78
+ ...sync,
79
+ channel
80
+ });
81
+ return;
82
+ }
83
+ const unit = found.get(sync.channel);
84
+ if (unit) return void await unit(sync);
85
+ if (unchecked) throw unchecked.reason;
86
+ await follow(sync);
87
+ };
88
+ return withRoutes(fn, async (channel, resolve) => (await resolved(resolve)).found.has(channel));
89
+ }
90
+ //#endregion
91
+ //#region src/sync/provider.ts
92
+ const ResolvedTarget = z.object({
93
+ target: z.string().min(1),
94
+ channel: z.string().min(1),
95
+ kind: z.enum(["channel", "thread"])
96
+ }).strict();
97
+ /** Why a platform wouldn't read a channel or thread on demand. Its message is shown as is. */
98
+ var UnreadableError = class extends Error {
99
+ code;
100
+ constructor(code, message) {
101
+ super(message);
102
+ this.code = code;
103
+ }
104
+ };
105
+ /**
106
+ * Whether `resolveChannel` failed because the platform has no such channel, so no event can be
107
+ * in it, rather than because it couldn't tell (a failed listing, a missing scope).
108
+ */
109
+ const channelMissing = (error) => error instanceof UnreadableError && error.code === "channel_not_found";
110
+ const ReceivedEvent = z.object({
111
+ event: ExternalEvent,
112
+ raw: z.unknown(),
113
+ dedupe: z.string().min(1)
114
+ });
115
+ const routed = /* @__PURE__ */ new WeakMap();
116
+ /** The channels a provider's sync function routes, when a helper made it: the webhook keeps only theirs. */
117
+ const routesFor = (provider) => routed.get(provider);
118
+ var SyncProvider = class {
119
+ platform;
120
+ canStartThreads;
121
+ singleReaction;
122
+ uploadsFiles;
123
+ fn;
124
+ /** People's tokens, when the adapter has them: messages they write post as them. */
125
+ users;
126
+ options;
127
+ constructor(options, sync) {
128
+ if (typeof sync !== "function") throw new Error(`${options.platform} needs a sync function: what each event does, like channels({ '#dev': 'dev' })`);
129
+ this.fn = { run: sync };
130
+ const routes = routesOf(sync);
131
+ if (routes) routed.set(this, routes);
132
+ this.options = options;
133
+ this.users = options.users ?? null;
134
+ this.platform = options.platform;
135
+ this.canStartThreads = options.canStartThreads ?? false;
136
+ this.singleReaction = options.singleReaction ?? false;
137
+ this.uploadsFiles = options.uploadsFiles ?? false;
138
+ }
139
+ /** The platform account a person's token belongs to. */
140
+ async whoAmI(_token) {
141
+ throw new Error(`${this.platform} can't post as a person`);
142
+ }
143
+ /** `post`, as the person whose token this is. */
144
+ async postAs(_token, _target, _kind, _body) {
145
+ throw new Error(`${this.platform} can't post as a person`);
146
+ }
147
+ async editAs(_token, _thread, _message, _body) {}
148
+ async deleteAs(_token, _thread, _message) {}
149
+ async findByEmail(_email) {
150
+ return null;
151
+ }
152
+ mention(_actor) {
153
+ return null;
154
+ }
155
+ async fetchRoot(_thread) {
156
+ return null;
157
+ }
158
+ async fetchFile(_file) {
159
+ return null;
160
+ }
161
+ /** The platform's own link to an event's message, or null when it has none. */
162
+ async permalink(_event) {
163
+ return null;
164
+ }
165
+ /** A linked message the platform didn't describe, read from the platform. */
166
+ async fetchLink(_link) {
167
+ return {
168
+ snapshot: null,
169
+ unavailable: `${this.platform} can't read it`
170
+ };
171
+ }
172
+ /**
173
+ * The message a URL of this platform's names, by its own thread and message IDs, or null for a
174
+ * URL that isn't one. `message` is null for a link to a thread itself. It asks the platform
175
+ * nothing: the board answers from what it synced (`vb read <url>`).
176
+ */
177
+ locate(_url) {
178
+ return null;
179
+ }
180
+ /** The platform's URL for one of its messages, by its own IDs, or null when it has none (`vb sync <id>`). */
181
+ async url(_thread, _message) {
182
+ return null;
183
+ }
184
+ /**
185
+ * A channel or thread, read on demand for whoever asked: nothing is synced,
186
+ * stored, or joined. Throws `UnreadableError` for what it won't read.
187
+ */
188
+ async read(_target, _options) {
189
+ throw new UnreadableError("unsupported", `${this.platform} can't be read on demand`);
190
+ }
191
+ async edit(_thread, _message, _body) {}
192
+ async delete(_thread, _message) {}
193
+ async react(_thread, _message, _emoji, _added) {}
194
+ async startTyping(_thread, _latest, _status) {}
195
+ /**
196
+ * The channel whose inbox a thread's events land in (`slack:C0123ABC` for `slack:C0123ABC:1727.1234`),
197
+ * worked out without calling the platform. Null when the provider can't tell: a key's inbox then
198
+ * lists only the mapped channels'.
199
+ */
200
+ inboxChannel(_key, _thread) {
201
+ return null;
202
+ }
203
+ async deleteFiles(_ids) {}
204
+ refusesFiles(_error) {
205
+ return false;
206
+ }
207
+ async uploadFiles(_thread, _files) {
208
+ throw new Error(`${this.platform} does not support file uploads`);
209
+ }
210
+ /** Runs the sync function the provider was made with: what every event does, both ways. */
211
+ sync(sync) {
212
+ return this.fn.run(sync);
213
+ }
214
+ /** The default rendering: `**${label}**: ${text}`. A `Renderer` passed to `deliver` replaces it. */
215
+ render(input) {
216
+ return `**${input.label}**: ${input.message.text}`;
217
+ }
218
+ };
219
+ //#endregion
220
+ export { channelMissing as a, into as c, withRoutes as d, UnreadableError as i, mirror as l, ResolvedTarget as n, routesFor as o, SyncProvider as r, channels as s, ReceivedEvent as t, render as u };