@vercel/board 0.19.0 → 0.20.1

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 (63) hide show
  1. package/dist/{a2-C1obi0Qb.mjs → a2-QtmN9EhW.mjs} +36 -6
  2. package/dist/access.mjs +3 -3
  3. package/dist/{actors-BOamkEuz.mjs → actors-rPnEQLZu.mjs} +2 -2
  4. package/dist/{auth-Drg9FiC2.mjs → auth-5nLsx9qB.mjs} +1 -1
  5. package/dist/{backend-B5GCP1oK.d.mts → backend-CWmd4UH-.d.mts} +8 -1
  6. package/dist/backend-a2.d.mts +22 -4
  7. package/dist/backend-a2.mjs +1 -1
  8. package/dist/{backend-http-Ca6b0FPA.d.mts → backend-http-BfWQfvqB.d.mts} +22 -4
  9. package/dist/{backend-http-BMVDbtD_.mjs → backend-http-CbcjkHEn.mjs} +7 -3
  10. package/dist/{backend-http-DMpQ750P.mjs → backend-http-maHy-JW3.mjs} +19 -6
  11. package/dist/backend-http.d.mts +1 -1
  12. package/dist/backend-http.mjs +1 -1
  13. package/dist/backend-http.node.d.mts +1 -1
  14. package/dist/backend-http.node.mjs +1 -1
  15. package/dist/{chat-provider-B5uCc0an.d.mts → chat-provider-D0k3y8zY.d.mts} +4 -1
  16. package/dist/{chat-provider-YHy-WbrB.mjs → chat-provider-DnHibqnM.mjs} +4 -1
  17. package/dist/cli-embedded.d.mts +47 -0
  18. package/dist/cli-embedded.mjs +64 -0
  19. package/dist/cli.mjs +73 -1661
  20. package/dist/{client-BA-J7usH.mjs → client-ClznIBMg.mjs} +2 -0
  21. package/dist/{conformance-D0Z4dXC_.mjs → conformance-DQ9qVzWP.mjs} +1 -1
  22. package/dist/conformance.d.mts +1 -1
  23. package/dist/conformance.mjs +1 -1
  24. package/dist/{engine-D0oBUBEK.mjs → engine-kHfSOXnQ.mjs} +15 -4
  25. package/dist/{errors-DX3hpwWt.mjs → errors-CQ-fwxM3.mjs} +13 -1
  26. package/dist/{exports-CUGshfRF.mjs → exports-Dd8YdgO-.mjs} +15 -6
  27. package/dist/exports-ggfE9jNv.d.mts +4 -0
  28. package/dist/index-DsMPCv_K.d.mts +11 -0
  29. package/dist/index.d.mts +4 -4
  30. package/dist/{index.default-Cm6GsLdj.mjs → index.default-BqstUHvz.mjs} +1 -1
  31. package/dist/index.mjs +6 -6
  32. package/dist/index.node.d.mts +4 -12
  33. package/dist/index.node.mjs +4 -4
  34. package/dist/inspect/app.js +93 -92
  35. package/dist/inspect/engine.js +4 -4
  36. package/dist/main-CRLa7i3I.mjs +1745 -0
  37. package/dist/{middleware-CqaCwucP.mjs → middleware-DUjyQP_a.mjs} +1 -1
  38. package/dist/{provider-B1Z3Ok86.d.mts → provider-D11Dfda0.d.mts} +86 -32
  39. package/dist/{provider-B3DpYSDq.mjs → provider-poIkXapd.mjs} +4 -0
  40. package/dist/{serve-DB9ijyQ7.mjs → serve-1kBFSHe_.mjs} +4 -4
  41. package/dist/{server-BujqWBP5.mjs → server-CuqhG9E6.mjs} +4 -4
  42. package/dist/{serving-CNkgUPW1.mjs → serving-B307gppu.mjs} +1 -1
  43. package/dist/sync-github.d.mts +2 -2
  44. package/dist/sync-github.mjs +2 -2
  45. package/dist/sync-kapso.d.mts +2 -2
  46. package/dist/sync-kapso.mjs +2 -2
  47. package/dist/sync-linear.d.mts +2 -2
  48. package/dist/sync-linear.mjs +2 -2
  49. package/dist/sync-slack.d.mts +11 -2
  50. package/dist/sync-slack.mjs +57 -17
  51. package/dist/sync.chat.d.mts +1 -1
  52. package/dist/sync.chat.mjs +1 -1
  53. package/dist/sync.d.mts +1 -1
  54. package/dist/sync.mjs +1 -1
  55. package/dist/{tokens-DhuzKzAl.mjs → tokens-DUXaXwOj.mjs} +2 -2
  56. package/dist/{watchers-hash-B2TevhFZ.mjs → watchers-hash-Cd6ZLo9m.mjs} +38 -25
  57. package/docs/02-sync/03-slack.md +14 -1
  58. package/docs/02-sync/06-providers.md +1 -0
  59. package/docs/04-reference/01-cli.md +53 -2
  60. package/docs/04-reference/02-board.md +8 -3
  61. package/docs/04-reference/03-http.md +4 -3
  62. package/package.json +5 -1
  63. package/dist/exports-B3AFzT_f.d.mts +0 -4
@@ -1,5 +1,5 @@
1
1
  import { m as MESSAGE_TEXT_MAX, p as Labels } from "./schemas-Be08nyfW.mjs";
2
- import { t as HttpError } from "./errors-DX3hpwWt.mjs";
2
+ import { t as HttpError } from "./errors-CQ-fwxM3.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/server/middleware.ts
5
5
  /** A write the middleware didn't let through: the API answers 422 with what it responded. */
@@ -115,11 +115,27 @@ declare const InboxQuery: z.ZodObject<{
115
115
  }, z.core.$strip>;
116
116
  /** How many watchers one registration holds: the board matches every event against every tail. */
117
117
  declare const REGISTERED_WATCHERS_MAX = 32;
118
- /** What a board on `http()` registers with the board it uses: its watchers' tails, and where to wake it. */
119
- declare const RegisterBody: z.ZodObject<{
118
+ /**
119
+ * What a board on `http()` registers with the board it uses: its watchers, and where to wake it,
120
+ * at a URL or at a path on its own project, never both.
121
+ */
122
+ declare const RegisterBody: z.ZodUnion<readonly [z.ZodObject<{
123
+ watch: z.ZodRecord<z.ZodString, z.ZodObject<{
124
+ tail: z.ZodString;
125
+ when: z.ZodDefault<z.ZodEnum<{
126
+ enters: "enters";
127
+ matches: "matches";
128
+ }>>;
129
+ }, z.core.$strip>>;
130
+ sync: z.ZodOptional<z.ZodArray<z.ZodObject<{
131
+ key: z.ZodString;
132
+ platform: z.ZodString;
133
+ signIn: z.ZodDefault<z.ZodBoolean>;
134
+ }, z.core.$strip>>>;
120
135
  url: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
121
- path: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
122
- branch: z.ZodOptional<z.ZodNullable<z.ZodString>>;
136
+ path: z.ZodOptional<z.ZodNull>;
137
+ branch: z.ZodOptional<z.ZodNull>;
138
+ }, z.core.$strip>, z.ZodObject<{
123
139
  watch: z.ZodRecord<z.ZodString, z.ZodObject<{
124
140
  tail: z.ZodString;
125
141
  when: z.ZodDefault<z.ZodEnum<{
@@ -132,7 +148,12 @@ declare const RegisterBody: z.ZodObject<{
132
148
  platform: z.ZodString;
133
149
  signIn: z.ZodDefault<z.ZodBoolean>;
134
150
  }, z.core.$strip>>>;
135
- }, z.core.$strip>;
151
+ url: z.ZodOptional<z.ZodNull>;
152
+ path: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
153
+ branch: z.ZodOptional<z.ZodNullable<z.ZodString>>;
154
+ }, z.core.$strip>]>;
155
+ /** A registration as a board on `http()` sends it: `{ url?, watch, sync? }` or `{ path, branch?, watch, sync? }`. */
156
+ type RegisterInput = z.input<typeof RegisterBody>;
136
157
  /** A pull of the caller's matches: at most `limit`, waiting up to `wait` seconds for one. */
137
158
  declare const MatchesQuery: z.ZodObject<{
138
159
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -152,6 +173,10 @@ declare const SourceReadQuery: z.ZodObject<{
152
173
  target: z.ZodString;
153
174
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
154
175
  }, z.core.$strip>;
176
+ /** A channel to add the board's app to, so it can be read on demand: `#name`, an ID, or a link into it. */
177
+ declare const SourceJoinBody: z.ZodObject<{
178
+ target: z.ZodString;
179
+ }, z.core.$strip>;
155
180
  /** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
156
181
  declare const LinkedQuery: z.ZodObject<{
157
182
  url: z.ZodString;
@@ -688,16 +713,13 @@ type Discovery = {
688
713
  */
689
714
  wake_by_path?: boolean;
690
715
  };
691
- /** Watchers a board on `http()` registered here: one registration per principal. */
716
+ /**
717
+ * Watchers a board on `http()` registered here: one registration per principal, woken at a URL
718
+ * or at a path on its own project, never both.
719
+ */
692
720
  type RegistrationView = {
693
721
  /** The principal that registered. Its watchers write as it, with their IDs as profiles. */
694
722
  owner: string;
695
- /** Where it's woken: `<url>/wake`. `null` when it pulls on its own, or is woken by `path`. */
696
- url: string | null;
697
- /** Where it's woken on its own Vercel project, on a board with `wake_by_path`. */
698
- path?: string | null;
699
- /** The branch whose latest ready preview a preview's `path` wakes. */
700
- branch?: string | null;
701
723
  watch: Record<string, {
702
724
  tail: string;
703
725
  when: 'matches' | 'enters';
@@ -710,7 +732,18 @@ type RegistrationView = {
710
732
  }[];
711
733
  /** Every wake-up carries it, so the board can tell its registration is stale. */
712
734
  hash: string;
713
- };
735
+ } & ({
736
+ /** Where it's woken: `<url>/wake`. `null` when it pulls on its own. */
737
+ url: string | null;
738
+ path?: undefined;
739
+ branch?: undefined;
740
+ } | {
741
+ url: null;
742
+ /** Where it's woken on its own Vercel project, on a board with `wake_by_path`. */
743
+ path: string;
744
+ /** The branch whose latest ready preview a preview's `path` wakes. */
745
+ branch?: string;
746
+ });
714
747
  /** One match, leased to the registrant that pulled it. */
715
748
  type MatchView = {
716
749
  /** The match's ID, which its watcher's writes derive their IDs from. */
@@ -880,6 +913,12 @@ type SourceRead = {
880
913
  /** The platform has more than `limit`: older messages in a channel, later replies in a thread. */
881
914
  more: boolean;
882
915
  };
916
+ /** A channel the board's app joined, or was already in (`joined: false`). */
917
+ type SourceJoin = {
918
+ channel: string;
919
+ name: string | null;
920
+ joined: boolean;
921
+ };
883
922
  type ApiError = {
884
923
  error: {
885
924
  code: string;
@@ -1497,6 +1536,14 @@ declare const routes: {
1497
1536
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1498
1537
  }, z.core.$strip>;
1499
1538
  };
1539
+ /** Adds the app to a public channel so it can be read, where that sync runs and opted in with `join`. Full access only. */
1540
+ readonly joinSource: {
1541
+ readonly method: 'POST';
1542
+ readonly path: '/sources/:key/join';
1543
+ readonly body: z.ZodObject<{
1544
+ target: z.ZodString;
1545
+ }, z.core.$strip>;
1546
+ };
1500
1547
  readonly webhook: {
1501
1548
  readonly method: 'POST';
1502
1549
  readonly path: '/webhooks/:key';
@@ -1588,10 +1635,23 @@ declare const routes: {
1588
1635
  readonly register: {
1589
1636
  readonly method: 'POST';
1590
1637
  readonly path: '/watchers';
1591
- readonly body: z.ZodObject<{
1638
+ readonly body: z.ZodUnion<readonly [z.ZodObject<{
1639
+ watch: z.ZodRecord<z.ZodString, z.ZodObject<{
1640
+ tail: z.ZodString;
1641
+ when: z.ZodDefault<z.ZodEnum<{
1642
+ enters: "enters";
1643
+ matches: "matches";
1644
+ }>>;
1645
+ }, z.core.$strip>>;
1646
+ sync: z.ZodOptional<z.ZodArray<z.ZodObject<{
1647
+ key: z.ZodString;
1648
+ platform: z.ZodString;
1649
+ signIn: z.ZodDefault<z.ZodBoolean>;
1650
+ }, z.core.$strip>>>;
1592
1651
  url: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
1593
- path: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
1594
- branch: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1652
+ path: z.ZodOptional<z.ZodNull>;
1653
+ branch: z.ZodOptional<z.ZodNull>;
1654
+ }, z.core.$strip>, z.ZodObject<{
1595
1655
  watch: z.ZodRecord<z.ZodString, z.ZodObject<{
1596
1656
  tail: z.ZodString;
1597
1657
  when: z.ZodDefault<z.ZodEnum<{
@@ -1604,7 +1664,10 @@ declare const routes: {
1604
1664
  platform: z.ZodString;
1605
1665
  signIn: z.ZodDefault<z.ZodBoolean>;
1606
1666
  }, z.core.$strip>>>;
1607
- }, z.core.$strip>;
1667
+ url: z.ZodOptional<z.ZodNull>;
1668
+ path: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
1669
+ branch: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1670
+ }, z.core.$strip>]>;
1608
1671
  };
1609
1672
  /** The caller's matches it can run now, each leased to it until it acks. */
1610
1673
  readonly matches: {
@@ -1827,20 +1890,7 @@ declare function createBoardClient(options: ClientOptions): {
1827
1890
  limit?: number;
1828
1891
  }) => Promise<InboxEntry[]>;
1829
1892
  /** Registers the caller's watchers, replacing its last registration: what a board on `http()` does. */
1830
- register: (input: {
1831
- url?: string | null;
1832
- path?: string | null;
1833
- branch?: string | null;
1834
- watch: Record<string, {
1835
- tail: string;
1836
- when?: 'matches' | 'enters';
1837
- }>;
1838
- sync?: {
1839
- key: string;
1840
- platform: string;
1841
- signIn?: boolean;
1842
- }[];
1843
- }) => Promise<RegistrationView>;
1893
+ register: (input: RegisterInput) => Promise<RegistrationView>;
1844
1894
  /** Registered watchers: every one to `manage` everywhere, the caller's own to anyone else. */
1845
1895
  watchers: () => Promise<RegistrationView[]>;
1846
1896
  /** The caller's matches it can run now, each leased to it until it acks. `wait` holds the request up to that many seconds for one. */
@@ -1864,6 +1914,8 @@ declare function createBoardClient(options: ClientOptions): {
1864
1914
  readSource: (key: string, target: string, opts?: {
1865
1915
  limit?: number;
1866
1916
  }) => Promise<SourceRead>;
1917
+ /** Adds the app to a public channel so `readSource` can read it, where the sync opted in with `join`. Full access only. */
1918
+ joinSource: (key: string, target: string) => Promise<SourceJoin>;
1867
1919
  poll: (query?: Filters & {
1868
1920
  after?: string;
1869
1921
  wait?: number;
@@ -2595,6 +2647,8 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2595
2647
  read(_target: string, _options: {
2596
2648
  limit: number;
2597
2649
  }): Promise<SourceRead>;
2650
+ /** Adds the board's app to a channel so `read` can read it, where the board opted in. Throws `UnreadableError` otherwise. */
2651
+ join(_target: string): Promise<SourceJoin>;
2598
2652
  edit(_thread: string, _message: string, _body: ProviderPost): Promise<void>;
2599
2653
  delete(_thread: string, _message: string): Promise<void>;
2600
2654
  react(_thread: string, _message: string, _emoji: string, _added: boolean): Promise<void>;
@@ -2615,4 +2669,4 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2615
2669
  render(input: RenderInput): string;
2616
2670
  }
2617
2671
  //#endregion
2618
- 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 };
2672
+ export { Discovery as $, SyncCodeView as $t, SyncOverrides as A, Ref as At, ReplyOptions as B, SchedulesQuery as Bt, channels as C, MessagesQueryInput as Ct, EventBase as D, PostInput as Dt, render as E, PostBody as Et, BoardClient as F, RouteName as Ft, Written as G, SourceJoinBody as Gt, ScheduleTiming as H, SchedulesQueryOutput as Ht, BoardError as I, ScheduleBody as It, ActorView as J, SourceReadQuery as Jt, duration as K, SourceMessage as Kt, Filters as L, ScheduleBodyOutput as Lt, SyncRoot as M, RegisterInput as Mt, envelope as N, RegistrationView as Nt, ProviderEvent as O, REGISTERED_WATCHERS_MAX as Ot, eventSchema as P, ResolvedSyncTarget as Pt, DeleteBody as Q, SyncCodeBody as Qt, PostOptions as R, ScheduleInput as Rt, SyncUnit as S, routes as Sn, MessagesQuery as St, mirror as T, PersonView as Tt, UploadInput as U, SignInQuery as Ut, ScheduleOptions as V, SchedulesQueryInput as Vt, WaitOptions as W, SourceJoin as Wt, BoardEventView as X, SyncAccountInput as Xt, ApiError as Y, SyncAccountBody as Yt, ChannelView as Z, SyncBody as Zt, withUserTokens as _, ThreadsQueryInput as _n, MatchView as _t, ProviderOptions as a, SyncImportBody as an, IdentityUnlinkBody as at, SyncIn as b, UploadStartBody as bn, MessageView as bt, ReceivedEvent as c, SyncReceiptInput as cn, InboxEntry as ct, ResolvedTarget as d, SyncUnlinkBody as dn, InboxPushBody as dt, SyncCopiesQuery as en, EditBody as et, SyncContext as f, SyncedView as fn, InboxQuery as ft, connectUserTokens as g, ThreadsQuery as gn, LinkedQuery as gt, UserTokens as h, ThreadsPage as hn, LinkedAccount as ht, ProviderFile as i, SyncFindView as in, FiltersInput as it, SyncResult as j, RegisterBody as jt, RootOverride as k, ReactBody as kt, RenderInput as l, SyncRootsQuery as ln, InboxItem as lt, UnreadableError as m, ThreadView as mn, LinkView as mt, FileDelivery as n, SyncFindInput as nn, EventsQuery as nt, ProviderPost as o, SyncImportInput as on, IdentityView as ot, SyncProvider as p, ThreadSummaryView as pn, LabelBody as pt, AckBody as q, SourceRead as qt, ProviderDelivery as r, SyncFindQuery as rn, EventsQueryInput as rt, Receive as s, SyncReceiptBody as sn, InboxAckBody as st, FetchedLink as t, SyncCopiesView as tn, EventsPage as tt, Renderer as u, SyncThreadView as un, InboxNextQuery as ut, Sync as v, TypingBody as vn, MatchesQuery as vt, into as w, ParsedFilters as wt, SyncOut as x, UploadTicket as xn, MessagesPage as xt, SyncFn as y, TypingResult as yn, Me as yt, ProfileOption as z, ScheduleView as zt };
@@ -188,6 +188,10 @@ var SyncProvider = class {
188
188
  async read(_target, _options) {
189
189
  throw new UnreadableError("unsupported", `${this.platform} can't be read on demand`);
190
190
  }
191
+ /** Adds the board's app to a channel so `read` can read it, where the board opted in. Throws `UnreadableError` otherwise. */
192
+ async join(_target) {
193
+ throw new UnreadableError("unsupported", `${this.platform} can't join channels on demand`);
194
+ }
191
195
  async edit(_thread, _message, _body) {}
192
196
  async delete(_thread, _message) {}
193
197
  async react(_thread, _message, _emoji, _added) {}
@@ -1,7 +1,7 @@
1
- import { o as localTokenFile } from "./backend-http-DMpQ750P.mjs";
2
- import { t as board } from "./server-BujqWBP5.mjs";
3
- import { t as oidcTokens } from "./tokens-DhuzKzAl.mjs";
4
- import { n as fsBlob, t as a2 } from "./a2-C1obi0Qb.mjs";
1
+ import { o as localTokenFile } from "./backend-http-maHy-JW3.mjs";
2
+ import { t as board } from "./server-CuqhG9E6.mjs";
3
+ import { t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
4
+ import { n as fsBlob, t as a2 } from "./a2-QtmN9EhW.mjs";
5
5
  import { t as localScheduler } from "./local-DhkrAqPP.mjs";
6
6
  import { mkdir, rm, writeFile } from "node:fs/promises";
7
7
  import { userInfo } from "node:os";
@@ -1,5 +1,5 @@
1
- import { t as createBoard } from "./exports-CUGshfRF.mjs";
2
- import { t as http } from "./backend-http-DMpQ750P.mjs";
1
+ import { t as createBoard } from "./exports-Dd8YdgO-.mjs";
2
+ import { t as http } from "./backend-http-maHy-JW3.mjs";
3
3
  //#region src/server/index.ts
4
4
  /**
5
5
  * The handle on a board. With a `backend`, the board lives there: `a2()` keeps it in this
@@ -9,8 +9,8 @@ import { t as http } from "./backend-http-DMpQ750P.mjs";
9
9
  function board(options = {}) {
10
10
  return createBoard(options, {
11
11
  http: () => http(),
12
- sync: () => import("./engine-D0oBUBEK.mjs"),
13
- serving: () => import("./serving-CNkgUPW1.mjs")
12
+ sync: () => import("./engine-kHfSOXnQ.mjs"),
13
+ serving: () => import("./serving-B307gppu.mjs")
14
14
  });
15
15
  }
16
16
  //#endregion
@@ -1,2 +1,2 @@
1
- import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-DhuzKzAl.mjs";
1
+ import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
2
2
  export { discoveryIdentity, oidcTokens, ownProjectId, ownTeamId };
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-B1Z3Ok86.mjs";
2
- import { n as ChatSyncProvider } from "./chat-provider-B5uCc0an.mjs";
1
+ import { S as SyncUnit, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
+ import { n as ChatSyncProvider } from "./chat-provider-D0k3y8zY.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/github.d.ts
@@ -1,7 +1,7 @@
1
- import { c as into, d as withRoutes } from "./provider-B3DpYSDq.mjs";
1
+ import { c as into, d as withRoutes } from "./provider-poIkXapd.mjs";
2
2
  import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
3
3
  import { r as withUserTokens, t as connectUserTokens } from "./users-yryHKFMo.mjs";
4
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-YHy-WbrB.mjs";
4
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
5
5
  import { z } from "zod";
6
6
  import { connectGitHubAdapter } from "@vercel/connect/chat";
7
7
  import { createGitHubAdapter } from "@chat-adapter/github";
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-B1Z3Ok86.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-B5uCc0an.mjs";
1
+ import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/kapso.d.ts
@@ -1,6 +1,6 @@
1
- import { s as channels } from "./provider-B3DpYSDq.mjs";
1
+ import { s as channels } from "./provider-poIkXapd.mjs";
2
2
  import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
3
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-YHy-WbrB.mjs";
3
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/sync/kapso.ts
6
6
  const decode = (id) => id.split(":").map((part) => Buffer.from(part, "base64url").toString("utf8")).join(":");
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-B1Z3Ok86.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-B5uCc0an.mjs";
1
+ import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
2
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/linear.d.ts
@@ -1,6 +1,6 @@
1
- import { i as UnreadableError, s as channels } from "./provider-B3DpYSDq.mjs";
1
+ import { i as UnreadableError, s as channels } from "./provider-poIkXapd.mjs";
2
2
  import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
3
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-YHy-WbrB.mjs";
3
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
4
4
  import "zod";
5
5
  import { connectLinearAdapter } from "@vercel/connect/chat";
6
6
  import { createLinearAdapter } from "@chat-adapter/linear";
@@ -1,6 +1,6 @@
1
1
  import { f as ExternalLink } from "./folds-CRYY14kc.mjs";
2
- import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-B1Z3Ok86.mjs";
3
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-B5uCc0an.mjs";
2
+ import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-D11Dfda0.mjs";
3
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
4
4
  import { z } from "zod";
5
5
  import { Adapter } from "chat";
6
6
  //#region src/sync/slack.d.ts
@@ -46,6 +46,10 @@ export type SlackClient = {
46
46
  messages?: SlackMessage[];
47
47
  has_more?: boolean;
48
48
  }>;
49
+ /** Adds the app to a public channel, for `vb sources <key> join`; needs `channels:join`. */
50
+ join?(args: {
51
+ channel: string;
52
+ }): Promise<unknown>;
49
53
  };
50
54
  users?: {
51
55
  info(args: {
@@ -320,6 +324,11 @@ export type SlackOptions = {
320
324
  adapter: Adapter | string;
321
325
  /** Not needed with a connector. */
322
326
  client?: () => Promise<SlackClient>;
327
+ /**
328
+ * Lets `vb sources <key> join` add the app to a public channel, so it can be read on demand
329
+ * without syncing it. Off by default; the app needs `channels:join`.
330
+ */
331
+ join?: boolean;
323
332
  };
324
333
  /**
325
334
  * Syncs a Slack workspace with the board: `app` is a Vercel Connect connector
@@ -1,9 +1,9 @@
1
1
  import { m as MESSAGE_TEXT_MAX } from "./schemas-Be08nyfW.mjs";
2
2
  import "./external-BV_cUncs.mjs";
3
- import { i as UnreadableError, s as channels } from "./provider-B3DpYSDq.mjs";
3
+ import { i as UnreadableError, s as channels } from "./provider-poIkXapd.mjs";
4
4
  import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
5
5
  import { r as withUserTokens, t as connectUserTokens } from "./users-yryHKFMo.mjs";
6
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-YHy-WbrB.mjs";
6
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
7
7
  import { z } from "zod";
8
8
  import { stringifyMarkdown } from "chat";
9
9
  import { createSlackAdapter } from "@chat-adapter/slack";
@@ -318,6 +318,26 @@ var SlackProvider = class extends ChatSyncProvider {
318
318
  if (!found) throw new UnreadableError("channel_not_found", publicOnly ? `no public Slack channel named #${name} (the app lacks groups:read, so a private channel needs its ID)` : `no Slack channel named #${name} (a private channel shows up once the app is invited)`);
319
319
  return found;
320
320
  }
321
+ async function targetOf(web, target) {
322
+ const link = new RegExp(PERMALINK.source).exec(target);
323
+ if (link) {
324
+ const [, id, seconds, micros, query] = link;
325
+ return {
326
+ channel: id,
327
+ thread: /[?&]thread_ts=(\d+\.\d+)/.exec(query?.replaceAll("&amp;", "&") ?? "")?.[1] ?? `${seconds}.${micros}`
328
+ };
329
+ }
330
+ if (target.startsWith("#")) return {
331
+ channel: (await find(web, target.slice(1))).id,
332
+ thread: null
333
+ };
334
+ const parsed = /^([A-Z0-9]+)(?::(\d+\.\d+))?$/.exec(target);
335
+ if (!parsed) throw new UnreadableError("invalid_target", `not a Slack channel, thread, or message link: ${target} (try #name, C0123, C0123:1727000000.123456, or a permalink)`);
336
+ return {
337
+ channel: parsed[1],
338
+ thread: parsed[2] ?? null
339
+ };
340
+ }
321
341
  const avatars = /* @__PURE__ */ new Map();
322
342
  function avatarOf(user) {
323
343
  let promise = avatars.get(user);
@@ -488,20 +508,9 @@ var SlackProvider = class extends ChatSyncProvider {
488
508
  async read(target, { limit }) {
489
509
  const web = await client();
490
510
  if (!web) throw new UnreadableError("no_client", "no Slack client to read it with");
491
- let channel;
492
- let thread = null;
493
- const link = new RegExp(PERMALINK.source).exec(target);
494
- if (link) {
495
- const [, id, seconds, micros, query] = link;
496
- channel = id;
497
- thread = /[?&]thread_ts=(\d+\.\d+)/.exec(query?.replaceAll("&amp;", "&") ?? "")?.[1] ?? `${seconds}.${micros}`;
498
- } else if (target.startsWith("#")) channel = (await find(web, target.slice(1))).id;
499
- else {
500
- const parsed = /^([A-Z0-9]+)(?::(\d+\.\d+))?$/.exec(target);
501
- if (!parsed) throw new UnreadableError("invalid_target", `not a Slack channel, thread, or message link: ${target} (try #name, C0123, C0123:1727000000.123456, or a permalink)`);
502
- channel = parsed[1];
503
- thread = parsed[2] ?? null;
504
- }
511
+ const parsed = await targetOf(web, target);
512
+ const channel = parsed.channel;
513
+ let thread = parsed.thread;
505
514
  let found;
506
515
  let more = false;
507
516
  let name = null;
@@ -512,7 +521,7 @@ var SlackProvider = class extends ChatSyncProvider {
512
521
  const label = name ? `#${name}` : channel;
513
522
  if (info.is_private || info.is_im || info.is_mpim) throw new UnreadableError("private_channel", `${label} is private: the board only reads public channels on demand`);
514
523
  if (info.is_ext_shared) throw new UnreadableError("shared_channel", `${label} is shared with another organization: the board doesn't read those on demand`);
515
- if (!info.is_member) throw new UnreadableError("not_in_channel", `the app isn't in ${label}: someone needs to add it (it won't join on its own)`);
524
+ if (!info.is_member) throw new UnreadableError("not_in_channel", options.join ? `the app isn't in ${label}: join it first with \`vb sources <key> join ${label}\`, which adds the app there` : `the app isn't in ${label}: someone needs to add it (it won't join on its own)`);
516
525
  if (thread) {
517
526
  const replies = web.conversations.replies;
518
527
  if (!replies) throw new UnreadableError("no_client", "this Slack client can't read threads");
@@ -562,6 +571,37 @@ var SlackProvider = class extends ChatSyncProvider {
562
571
  more: more || found.length > limit
563
572
  };
564
573
  },
574
+ async join(target) {
575
+ if (!options.join) throw new UnreadableError("join_disabled", "this board doesn't join Slack channels: it needs `join: true` on slack()");
576
+ const web = await client();
577
+ if (!web) throw new UnreadableError("no_client", "no Slack client to join with");
578
+ const { channel } = await targetOf(web, target);
579
+ try {
580
+ const info = (await web.conversations.info({ channel })).channel;
581
+ if (!info) throw new UnreadableError("channel_not_found", `no Slack channel ${channel}`);
582
+ const name = info.name ?? null;
583
+ const label = name ? `#${name}` : channel;
584
+ if (info.is_private || info.is_im || info.is_mpim) throw new UnreadableError("private_channel", `${label} is private: the app can only be invited there`);
585
+ if (info.is_ext_shared) throw new UnreadableError("shared_channel", `${label} is shared with another organization: the board doesn't join those`);
586
+ if (info.is_member) return {
587
+ channel,
588
+ name,
589
+ joined: false
590
+ };
591
+ if (!web.conversations.join) throw new UnreadableError("no_client", "this Slack client can't join channels");
592
+ await web.conversations.join({ channel });
593
+ return {
594
+ channel,
595
+ name,
596
+ joined: true
597
+ };
598
+ } catch (error) {
599
+ if (error instanceof UnreadableError) throw error;
600
+ const code = slackError(error).split(",")[0];
601
+ if (code === "missing_scope") throw new UnreadableError(code, "the Slack app can't join channels: it needs the channels:join scope");
602
+ throw new UnreadableError(code, `Slack refused to join ${target}: ${slackError(error)}`);
603
+ }
604
+ },
565
605
  intercept(key, _headers, body) {
566
606
  let event;
567
607
  try {
@@ -1,2 +1,2 @@
1
- import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-B5uCc0an.mjs";
1
+ import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-D0k3y8zY.mjs";
2
2
  export { ChatInput, ChatSyncProvider, ChatTransport, externalAuthor };
@@ -1,2 +1,2 @@
1
- import { n as externalAuthor, t as ChatSyncProvider } from "./chat-provider-YHy-WbrB.mjs";
1
+ import { n as externalAuthor, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
2
2
  export { ChatSyncProvider, externalAuthor };
package/dist/sync.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { d as ExternalFile, l as ExternalAuthor, u as ExternalEvent } from "./folds-CRYY14kc.mjs";
2
- import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-B1Z3Ok86.mjs";
2
+ import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-D11Dfda0.mjs";
3
3
  export { EventBase, type ExternalAuthor, ExternalEvent, type ExternalFile, type FileDelivery, type ProviderDelivery, type ProviderEvent, type ProviderFile, type ProviderOptions, type ProviderPost, type Receive, ReceivedEvent, type RenderInput, type Renderer, ResolvedTarget, type RootOverride, type Sync, type SyncContext, type SyncFn, type SyncIn, type SyncOut, SyncOverrides, SyncProvider, type SyncResult, type SyncRoot, type SyncUnit, UnreadableError, type UserTokens, channels, connectUserTokens, envelope, eventSchema, into, mirror, render, withUserTokens };
package/dist/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as ExternalEvent } from "./external-BV_cUncs.mjs";
2
- import { c as into, i as UnreadableError, l as mirror, n as ResolvedTarget, r as SyncProvider, s as channels, t as ReceivedEvent, u as render } from "./provider-B3DpYSDq.mjs";
2
+ import { c as into, i as UnreadableError, l as mirror, n as ResolvedTarget, r as SyncProvider, s as channels, t as ReceivedEvent, u as render } from "./provider-poIkXapd.mjs";
3
3
  import { i as eventSchema, n as SyncOverrides, r as envelope, t as EventBase } from "./events-CewD4kw7.mjs";
4
4
  import { r as withUserTokens, t as connectUserTokens } from "./users-yryHKFMo.mjs";
5
5
  export { EventBase, ExternalEvent, ReceivedEvent, ResolvedTarget, SyncOverrides, SyncProvider, UnreadableError, channels, connectUserTokens, envelope, eventSchema, into, mirror, render, withUserTokens };
@@ -1,5 +1,5 @@
1
- import { l as unauthorized } from "./errors-DX3hpwWt.mjs";
2
- import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-Drg9FiC2.mjs";
1
+ import { l as unauthorized } from "./errors-CQ-fwxM3.mjs";
2
+ import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-5nLsx9qB.mjs";
3
3
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
4
4
  //#region src/server/identity.ts
5
5
  let fromDisk;
@@ -1,7 +1,7 @@
1
1
  import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { S as storable, a as AttachmentId, c as ChannelPath, d as LabelKey, g as MessageText, l as Emoji, m as MESSAGE_TEXT_MAX, n as ActorId, p as Labels, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
3
3
  import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin, t as ExternalAuthor } from "./external-BV_cUncs.mjs";
4
- import { r as badRequest, t as HttpError } from "./errors-DX3hpwWt.mjs";
4
+ import { r as badRequest, t as HttpError } from "./errors-CQ-fwxM3.mjs";
5
5
  import { z } from "zod";
6
6
  //#region src/api/routes.ts
7
7
  /**
@@ -114,22 +114,8 @@ const InboxQuery = z.object({
114
114
  channel: z.string().optional(),
115
115
  limit: z.coerce.number().int().min(1).max(500).optional()
116
116
  });
117
- /** What a board on `http()` registers with the board it uses: its watchers' tails, and where to wake it. */
118
- const RegisterBody = z.object({
119
- /** Without one, or a `path`, the board pulls on its own instead of being woken. */
120
- url: z.string().url().max(500).refine((url) => /^https:\/\//.test(url), "wake-ups go over https").transform((url) => url.replace(/\/+$/, "")).nullable().optional(),
121
- /**
122
- * Where to wake the caller on its own Vercel project, on a board whose discovery says
123
- * `wake_by_path`. The board takes the project and environment from the caller's token and
124
- * finds the deployment, so a project can only register itself, and anyone else gets 403
125
- * `not_a_project`.
126
- */
127
- path: z.string().max(300).regex(/^\/[^?#]*$/, "a path starts with / and has no query or fragment").transform((path) => path.replace(/(.)\/+$/, "$1")).nullable().optional(),
128
- /**
129
- * With a `path`, from a preview: the branch whose latest ready preview is woken, since a
130
- * token says which project and environment called but not which deployment. Production names none.
131
- */
132
- branch: z.string().min(1).max(255).nullable().optional(),
117
+ /** What every registration carries: its watchers' tails, and its sync entries. */
118
+ const Registering = z.object({
133
119
  watch: z.record(z.string().max(100), z.object({
134
120
  tail: z.string().max(1e3),
135
121
  when: z.enum(["matches", "enters"]).default("matches")
@@ -143,13 +129,31 @@ const RegisterBody = z.object({
143
129
  platform: z.string().min(1).max(64),
144
130
  signIn: z.boolean().default(false)
145
131
  })).max(32).optional()
146
- }).refine((body) => !(body.url && body.path), {
147
- message: "register a url or a path, not both",
148
- path: ["path"]
149
- }).refine((body) => !body.branch || body.path, {
150
- message: "a branch goes with a path",
151
- path: ["branch"]
152
132
  });
133
+ /**
134
+ * What a board on `http()` registers with the board it uses: its watchers, and where to wake it,
135
+ * at a URL or at a path on its own project, never both.
136
+ */
137
+ const RegisterBody = z.union([Registering.extend({
138
+ /** Without one, the board pulls on its own instead of being woken. */
139
+ url: z.string().url().max(500).refine((url) => /^https:\/\//.test(url), "wake-ups go over https").transform((url) => url.replace(/\/+$/, "")).nullable().optional(),
140
+ path: z.null({ error: "register a url or a path, not both" }).optional(),
141
+ branch: z.null({ error: "a branch goes with a path" }).optional()
142
+ }), Registering.extend({
143
+ url: z.null({ error: "register a url or a path, not both" }).optional(),
144
+ /**
145
+ * Where to wake the caller on its own Vercel project, on a board whose discovery says
146
+ * `wake_by_path`. The board takes the project and environment from the caller's token and
147
+ * finds the deployment, so a project can only register itself, and anyone else gets 403
148
+ * `not_a_project`.
149
+ */
150
+ path: z.string().max(300).regex(/^\/[^?#]*$/, "a path starts with / and has no query or fragment").transform((path) => path.replace(/(.)\/+$/, "$1")),
151
+ /**
152
+ * From a preview: the branch whose latest ready preview is woken, since a token says which
153
+ * project and environment called but not which deployment. Production names none.
154
+ */
155
+ branch: z.string().min(1).max(255).nullable().optional()
156
+ })]);
153
157
  /** A pull of the caller's matches: at most `limit`, waiting up to `wait` seconds for one. */
154
158
  const MatchesQuery = z.object({
155
159
  limit: z.coerce.number().int().min(1).max(50).optional(),
@@ -169,6 +173,8 @@ const SourceReadQuery = z.object({
169
173
  target: z.string().min(1).max(4e3),
170
174
  limit: z.coerce.number().int().min(1).max(200).optional()
171
175
  });
176
+ /** A channel to add the board's app to, so it can be read on demand: `#name`, an ID, or a link into it. */
177
+ const SourceJoinBody = z.object({ target: z.string().min(1).max(4e3) });
172
178
  /** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
173
179
  const LinkedQuery = z.object({ url: z.string().url().max(4e3) });
174
180
  const IdentityUnlinkBody = z.object({ actor: z.string().min(1).max(256) });
@@ -556,6 +562,12 @@ const routes = {
556
562
  path: "/sources/:key/read",
557
563
  query: SourceReadQuery
558
564
  },
565
+ /** Adds the app to a public channel so it can be read, where that sync runs and opted in with `join`. Full access only. */
566
+ joinSource: {
567
+ method: "POST",
568
+ path: "/sources/:key/join",
569
+ body: SourceJoinBody
570
+ },
559
571
  webhook: {
560
572
  method: "POST",
561
573
  path: "/webhooks/:key"
@@ -649,7 +661,7 @@ const routes = {
649
661
  const SYNC = "@sync";
650
662
  //#endregion
651
663
  //#region src/version.ts
652
- const VERSION = "0.19.0";
664
+ const VERSION = "0.20.1";
653
665
  //#endregion
654
666
  //#region src/server/routing.ts
655
667
  /** board()'s own routes: the files it serves, and sync's, which run where board() does. */
@@ -705,6 +717,7 @@ const METHODS = [
705
717
  "syncUnlink",
706
718
  "typing",
707
719
  "readSource",
720
+ "joinSource",
708
721
  "synced",
709
722
  "linked"
710
723
  ];
@@ -1007,4 +1020,4 @@ function canonicalWatchers(watch, sync = []) {
1007
1020
  return JSON.stringify(canonical);
1008
1021
  }
1009
1022
  //#endregion
1010
- export { ScheduleBody as A, SyncReceiptBody as B, LinkedQuery as C, ReactBody as D, PostBody as E, SyncBody as F, UploadStartBody as G, SyncUnlinkBody as H, SyncCodeBody as I, routes as K, SyncCopiesQuery as L, SignInQuery as M, SourceReadQuery as N, Ref as O, SyncAccountBody as P, SyncFindQuery as R, LabelBody as S, MessagesQuery as T, ThreadsQuery as U, SyncRootsQuery as V, TypingBody as W, IdentityUnlinkBody as _, withMentions as a, InboxPushBody as b, openapi as c, readJson as d, VERSION as f, EventsQuery as g, EditBody as h, FILTER_FLAGS as i, SchedulesQuery as j, RegisterBody as k, queryObject as l, AckBody as m, compileWatcher as n, METHODS as o, SYNC as p, hasCriteria as r, createRouter as s, canonicalWatchers as t, readCapped as u, InboxAckBody as v, MatchesQuery as w, InboxQuery as x, InboxNextQuery as y, SyncImportBody as z };
1023
+ export { ScheduleBody as A, SyncImportBody as B, LinkedQuery as C, ReactBody as D, PostBody as E, SyncAccountBody as F, TypingBody as G, SyncRootsQuery as H, SyncBody as I, UploadStartBody as K, SyncCodeBody as L, SignInQuery as M, SourceJoinBody as N, Ref as O, SourceReadQuery as P, SyncCopiesQuery as R, LabelBody as S, MessagesQuery as T, SyncUnlinkBody as U, SyncReceiptBody as V, ThreadsQuery as W, IdentityUnlinkBody as _, withMentions as a, InboxPushBody as b, openapi as c, readJson as d, VERSION as f, EventsQuery as g, EditBody as h, FILTER_FLAGS as i, SchedulesQuery as j, RegisterBody as k, queryObject as l, AckBody as m, compileWatcher as n, METHODS as o, SYNC as p, routes as q, hasCriteria as r, createRouter as s, canonicalWatchers as t, readCapped as u, InboxAckBody as v, MatchesQuery as w, InboxQuery as x, InboxNextQuery as y, SyncFindQuery as z };