@vercel/board 0.18.1 → 0.20.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 (64) hide show
  1. package/dist/{a2-CW8jHGD_.mjs → a2-D__qQpe7.mjs} +37 -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-B-SJ7p6p.d.mts → backend-CWmd4UH-.d.mts} +8 -1
  6. package/dist/backend-a2.d.mts +22 -2
  7. package/dist/backend-a2.mjs +1 -1
  8. package/dist/{backend-http-7cNb8nJI.d.mts → backend-http-BfWQfvqB.d.mts} +22 -2
  9. package/dist/{backend-http-CDDS7MLI.mjs → backend-http-CG5y03UP.mjs} +25 -6
  10. package/dist/{backend-http-BjH8jMDi.mjs → backend-http-c-VHpUOs.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-DNJQyajS.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-CT5EeHeC.mjs → conformance-DQ9qVzWP.mjs} +47 -6
  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-CwsfgCul.mjs → exports-DqWJ7nzC.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-jkA7eFFe.mjs → index.default-C3luVnd-.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-wB-VfTCL.mjs +1744 -0
  37. package/dist/{middleware-CqaCwucP.mjs → middleware-DUjyQP_a.mjs} +1 -1
  38. package/dist/{provider-B0b9Qxw4.d.mts → provider-D11Dfda0.d.mts} +91 -22
  39. package/dist/{provider-B3DpYSDq.mjs → provider-poIkXapd.mjs} +4 -0
  40. package/dist/{serve-Dpb7zMJ0.mjs → serve-AnApUWOR.mjs} +4 -4
  41. package/dist/{server-Bj1_ugXd.mjs → server-BqVickb5.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-qlnbKQQ0.mjs → watchers-hash-DYwvkhdX.mjs} +38 -7
  57. package/docs/01-board/06-watchers.md +7 -0
  58. package/docs/02-sync/03-slack.md +14 -1
  59. package/docs/02-sync/06-providers.md +1 -0
  60. package/docs/04-reference/01-cli.md +55 -3
  61. package/docs/04-reference/02-board.md +8 -3
  62. package/docs/04-reference/03-http.md +7 -4
  63. package/package.json +5 -1
  64. package/dist/exports-ZsgjzEnM.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,9 +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>>>>;
136
+ path: z.ZodOptional<z.ZodNull>;
137
+ branch: z.ZodOptional<z.ZodNull>;
138
+ }, z.core.$strip>, z.ZodObject<{
121
139
  watch: z.ZodRecord<z.ZodString, z.ZodObject<{
122
140
  tail: z.ZodString;
123
141
  when: z.ZodDefault<z.ZodEnum<{
@@ -130,7 +148,12 @@ declare const RegisterBody: z.ZodObject<{
130
148
  platform: z.ZodString;
131
149
  signIn: z.ZodDefault<z.ZodBoolean>;
132
150
  }, z.core.$strip>>>;
133
- }, 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>;
134
157
  /** A pull of the caller's matches: at most `limit`, waiting up to `wait` seconds for one. */
135
158
  declare const MatchesQuery: z.ZodObject<{
136
159
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -150,6 +173,10 @@ declare const SourceReadQuery: z.ZodObject<{
150
173
  target: z.ZodString;
151
174
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
152
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>;
153
180
  /** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
154
181
  declare const LinkedQuery: z.ZodObject<{
155
182
  url: z.ZodString;
@@ -680,13 +707,19 @@ type Discovery = {
680
707
  key: string;
681
708
  platform: string;
682
709
  }[];
710
+ /**
711
+ * Whether `POST /watchers` takes a `path`: the board wakes the caller's own Vercel project there,
712
+ * through its Deployment Protection. A board that can't find a project's deployments leaves it out.
713
+ */
714
+ wake_by_path?: boolean;
683
715
  };
684
- /** 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
+ */
685
720
  type RegistrationView = {
686
721
  /** The principal that registered. Its watchers write as it, with their IDs as profiles. */
687
722
  owner: string;
688
- /** Where it's woken: `<url>/wake`. `null` when it pulls on its own. */
689
- url: string | null;
690
723
  watch: Record<string, {
691
724
  tail: string;
692
725
  when: 'matches' | 'enters';
@@ -699,7 +732,18 @@ type RegistrationView = {
699
732
  }[];
700
733
  /** Every wake-up carries it, so the board can tell its registration is stale. */
701
734
  hash: string;
702
- };
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
+ });
703
747
  /** One match, leased to the registrant that pulled it. */
704
748
  type MatchView = {
705
749
  /** The match's ID, which its watcher's writes derive their IDs from. */
@@ -869,6 +913,12 @@ type SourceRead = {
869
913
  /** The platform has more than `limit`: older messages in a channel, later replies in a thread. */
870
914
  more: boolean;
871
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
+ };
872
922
  type ApiError = {
873
923
  error: {
874
924
  code: string;
@@ -1486,6 +1536,14 @@ declare const routes: {
1486
1536
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1487
1537
  }, z.core.$strip>;
1488
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
+ };
1489
1547
  readonly webhook: {
1490
1548
  readonly method: 'POST';
1491
1549
  readonly path: '/webhooks/:key';
@@ -1577,8 +1635,23 @@ declare const routes: {
1577
1635
  readonly register: {
1578
1636
  readonly method: 'POST';
1579
1637
  readonly path: '/watchers';
1580
- 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>>>;
1581
1651
  url: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
1652
+ path: z.ZodOptional<z.ZodNull>;
1653
+ branch: z.ZodOptional<z.ZodNull>;
1654
+ }, z.core.$strip>, z.ZodObject<{
1582
1655
  watch: z.ZodRecord<z.ZodString, z.ZodObject<{
1583
1656
  tail: z.ZodString;
1584
1657
  when: z.ZodDefault<z.ZodEnum<{
@@ -1591,7 +1664,10 @@ declare const routes: {
1591
1664
  platform: z.ZodString;
1592
1665
  signIn: z.ZodDefault<z.ZodBoolean>;
1593
1666
  }, z.core.$strip>>>;
1594
- }, 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>]>;
1595
1671
  };
1596
1672
  /** The caller's matches it can run now, each leased to it until it acks. */
1597
1673
  readonly matches: {
@@ -1814,18 +1890,7 @@ declare function createBoardClient(options: ClientOptions): {
1814
1890
  limit?: number;
1815
1891
  }) => Promise<InboxEntry[]>;
1816
1892
  /** Registers the caller's watchers, replacing its last registration: what a board on `http()` does. */
1817
- register: (input: {
1818
- url?: string | null;
1819
- watch: Record<string, {
1820
- tail: string;
1821
- when?: 'matches' | 'enters';
1822
- }>;
1823
- sync?: {
1824
- key: string;
1825
- platform: string;
1826
- signIn?: boolean;
1827
- }[];
1828
- }) => Promise<RegistrationView>;
1893
+ register: (input: RegisterInput) => Promise<RegistrationView>;
1829
1894
  /** Registered watchers: every one to `manage` everywhere, the caller's own to anyone else. */
1830
1895
  watchers: () => Promise<RegistrationView[]>;
1831
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. */
@@ -1849,6 +1914,8 @@ declare function createBoardClient(options: ClientOptions): {
1849
1914
  readSource: (key: string, target: string, opts?: {
1850
1915
  limit?: number;
1851
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>;
1852
1919
  poll: (query?: Filters & {
1853
1920
  after?: string;
1854
1921
  wait?: number;
@@ -2580,6 +2647,8 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2580
2647
  read(_target: string, _options: {
2581
2648
  limit: number;
2582
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>;
2583
2652
  edit(_thread: string, _message: string, _body: ProviderPost): Promise<void>;
2584
2653
  delete(_thread: string, _message: string): Promise<void>;
2585
2654
  react(_thread: string, _message: string, _emoji: string, _added: boolean): Promise<void>;
@@ -2600,4 +2669,4 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
2600
2669
  render(input: RenderInput): string;
2601
2670
  }
2602
2671
  //#endregion
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 };
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-BjH8jMDi.mjs";
2
- import { t as board } from "./server-Bj1_ugXd.mjs";
3
- import { t as oidcTokens } from "./tokens-DhuzKzAl.mjs";
4
- import { n as fsBlob, t as a2 } from "./a2-CW8jHGD_.mjs";
1
+ import { o as localTokenFile } from "./backend-http-c-VHpUOs.mjs";
2
+ import { t as board } from "./server-BqVickb5.mjs";
3
+ import { t as oidcTokens } from "./tokens-DUXaXwOj.mjs";
4
+ import { n as fsBlob, t as a2 } from "./a2-D__qQpe7.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-CwsfgCul.mjs";
2
- import { t as http } from "./backend-http-BjH8jMDi.mjs";
1
+ import { t as createBoard } from "./exports-DqWJ7nzC.mjs";
2
+ import { t as http } from "./backend-http-c-VHpUOs.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-BjH8jMDi.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-B0b9Qxw4.mjs";
2
- import { n as ChatSyncProvider } from "./chat-provider-DNJQyajS.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-B0b9Qxw4.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DNJQyajS.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-B0b9Qxw4.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DNJQyajS.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-B0b9Qxw4.mjs";
3
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DNJQyajS.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-DNJQyajS.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-B0b9Qxw4.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,10 +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, 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(),
117
+ /** What every registration carries: its watchers' tails, and its sync entries. */
118
+ const Registering = z.object({
121
119
  watch: z.record(z.string().max(100), z.object({
122
120
  tail: z.string().max(1e3),
123
121
  when: z.enum(["matches", "enters"]).default("matches")
@@ -132,6 +130,30 @@ const RegisterBody = z.object({
132
130
  signIn: z.boolean().default(false)
133
131
  })).max(32).optional()
134
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
+ })]);
135
157
  /** A pull of the caller's matches: at most `limit`, waiting up to `wait` seconds for one. */
136
158
  const MatchesQuery = z.object({
137
159
  limit: z.coerce.number().int().min(1).max(50).optional(),
@@ -151,6 +173,8 @@ const SourceReadQuery = z.object({
151
173
  target: z.string().min(1).max(4e3),
152
174
  limit: z.coerce.number().int().min(1).max(200).optional()
153
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) });
154
178
  /** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
155
179
  const LinkedQuery = z.object({ url: z.string().url().max(4e3) });
156
180
  const IdentityUnlinkBody = z.object({ actor: z.string().min(1).max(256) });
@@ -538,6 +562,12 @@ const routes = {
538
562
  path: "/sources/:key/read",
539
563
  query: SourceReadQuery
540
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
+ },
541
571
  webhook: {
542
572
  method: "POST",
543
573
  path: "/webhooks/:key"
@@ -631,7 +661,7 @@ const routes = {
631
661
  const SYNC = "@sync";
632
662
  //#endregion
633
663
  //#region src/version.ts
634
- const VERSION = "0.18.1";
664
+ const VERSION = "0.20.0";
635
665
  //#endregion
636
666
  //#region src/server/routing.ts
637
667
  /** board()'s own routes: the files it serves, and sync's, which run where board() does. */
@@ -687,6 +717,7 @@ const METHODS = [
687
717
  "syncUnlink",
688
718
  "typing",
689
719
  "readSource",
720
+ "joinSource",
690
721
  "synced",
691
722
  "linked"
692
723
  ];
@@ -989,4 +1020,4 @@ function canonicalWatchers(watch, sync = []) {
989
1020
  return JSON.stringify(canonical);
990
1021
  }
991
1022
  //#endregion
992
- 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 };
@@ -236,6 +236,13 @@ export const GET = team.fetch, POST = team.fetch
236
236
  makes this board pull with its own credentials. One that Deployment
237
237
  Protection stops answers 401, and the other board tries less often until
238
238
  this board pulls or registers.
239
+ - **On a board whose discovery says `wake_by_path`,** this board registers
240
+ its mount path on its own project instead of its URL, and its wake-ups get
241
+ through Deployment Protection with nothing to set up. Only on Vercel, from
242
+ production or a preview of a branch, which it names: the other board finds
243
+ the deployment from this project's token and that branch. Anywhere else
244
+ (development, a custom environment, a preview made with `vercel deploy`), it
245
+ registers its URL.
239
246
  - **Retries need the other board's scheduler,** as its own watchers do.
240
247
  Without one there, nothing waits on a clock: a retry, a lease that ran out,
241
248
  and a wake-up that didn't get there (once its back-off is over) each wait