@vercel/board 0.30.0 → 0.31.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.
- package/README.md +8 -17
- package/dist/{a2-D_28Pdip.mjs → a2-DngWY2Eb.mjs} +573 -361
- package/dist/{access-CQ39gqEV.mjs → access-CY5xVMIK.mjs} +19 -3
- package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
- package/dist/access.d.mts +4 -4
- package/dist/access.mjs +5 -4
- package/dist/{actors-xPiamLWk.mjs → actors-Bm-ojWWT.mjs} +2 -2
- package/dist/{auth-BPinwfN0.mjs → auth-CyO2aw5n.mjs} +127 -14
- package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
- package/dist/{backend-n3hajfrD.d.mts → backend-Bob-5CM7.d.mts} +86 -41
- package/dist/backend-a2/blob-vercel.d.mts +1 -1
- package/dist/backend-a2/contract.d.mts +37 -97
- package/dist/backend-a2/contract.mjs +1 -1
- package/dist/backend-a2/store-memory.d.mts +1 -1
- package/dist/backend-a2/store-memory.mjs +2 -2
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +1 -1
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +1 -1
- package/dist/backend-a2/store-redis.d.mts +1 -1
- package/dist/backend-a2/store-redis.mjs +1 -1
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +2 -2
- package/dist/backend-a2.d.mts +131 -240
- package/dist/backend-a2.mjs +2 -2
- package/dist/{backend-http-3PoBYQoo.d.mts → backend-http-BRrRbjRx.d.mts} +49 -45
- package/dist/{backend-http-BPaNYLWj.mjs → backend-http-CvUKOn-m.mjs} +11 -9
- package/dist/{backend-http-16SbvnAK.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-Kd8nvW5A.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
- package/dist/{backend-vercel-qznuDxnN.d.mts → backend-vercel-BaSEUdwg.d.mts} +49 -45
- package/dist/{backend-vercel-C4Kee2-K.mjs → backend-vercel-CmY9yZ9m.mjs} +80 -15
- package/dist/backend-vercel.d.mts +1 -1
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/{blob-CqILvdmr.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
- package/dist/{blob-DQK5prXT.mjs → blob-WbB2DJKn.mjs} +30 -11
- package/dist/{chat-provider-D6jVT4wS.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
- package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
- package/dist/cli-commands.d.mts +1 -1
- package/dist/cli-commands.mjs +1 -1
- package/dist/cli-embedded.d.mts +2 -2
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +4 -4
- package/dist/{client-CjAatpV4.d.mts → client-COfYrgfV.d.mts} +184 -221
- package/dist/{commands-BtH69yqt.d.mts → commands-DK4W3HCJ.d.mts} +30 -10
- package/dist/{commands-B5DHjZKK.mjs → commands-L1JSGz2Z.mjs} +162 -45
- package/dist/{conformance-C7fwIvmN.mjs → conformance-DK43HiHf.mjs} +224 -20
- package/dist/conformance.d.mts +8 -2
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-DkdkXQEy.mjs → engine-DurYIRt8.mjs} +29 -210
- package/dist/{errors-87TVtXeb.d.mts → errors-2vGs49Db.d.mts} +1 -1
- package/dist/{errors-C_5rB8Iz.mjs → errors-CJXhhzvT.mjs} +28 -23
- package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
- package/dist/{exports-CG0P8Qi4.mjs → exports-CWIdN0Ub.mjs} +146 -80
- package/dist/exports-FBOQWT7O.d.mts +5 -0
- package/dist/{folds-DGYkMW19.mjs → folds-Cu9Q9zyQ.mjs} +2 -22
- package/dist/{folds-8wRRXjAt.d.mts → folds-DLUzfibJ.d.mts} +13 -40
- package/dist/{index-BygAXP7t.d.mts → index-Ho-ayyQL.d.mts} +2 -2
- package/dist/index.d.mts +7 -7
- package/dist/{index.default-DhkgpfR2.mjs → index.default-BUcwDaAe.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.node.d.mts +7 -7
- package/dist/index.node.mjs +3 -3
- package/dist/inspect/app.js +62 -61
- package/dist/inspect/engine.js +3 -3
- package/dist/{main-D2s-QBOK.mjs → main-Dz-8komG.mjs} +189 -112
- package/dist/{middleware-DlJQYiwV.mjs → middleware-BavYSDUa.mjs} +1 -1
- package/dist/{node-D8Rtg1su.mjs → node-BgeGcwX0.mjs} +7 -3
- package/dist/{project-C_2xs57L.mjs → project-CQ4VUibA.mjs} +1 -1
- package/dist/{provider-Bm46LAdp.d.mts → provider-gynDUoyW.d.mts} +13 -43
- package/dist/{redis-tables-BWJZHUGg.mjs → redis-tables-DHf85ixI.mjs} +1 -19
- package/dist/{reducers-8jqLewgw.mjs → reducers-DcENwtwZ.mjs} +72 -75
- package/dist/{serve-lPoBDEds.mjs → serve-CTmow-YH.mjs} +6 -6
- package/dist/{server-D82a4uMe.mjs → server-DG8D6VV0.mjs} +4 -4
- package/dist/{serving-DKny38LK.mjs → serving-DBFRo6U_.mjs} +1 -1
- package/dist/{sql-Bj0fk6dn.mjs → sql-CT5xVuGr.mjs} +2 -8
- package/dist/{sqlite-db-Dc8G78XV.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
- package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
- package/dist/sync-chat-sdk.mjs +44 -0
- package/dist/sync-github.d.mts +5 -18
- package/dist/sync-github.mjs +4 -70
- package/dist/sync-linear.d.mts +3 -3
- package/dist/sync-linear.mjs +2 -3
- package/dist/sync-slack.d.mts +7 -7
- package/dist/sync-slack.mjs +33 -54
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +2 -4
- package/dist/{tokens-C5_yGRlL.mjs → tokens-Dnyd_y1n.mjs} +15 -6
- package/dist/{types-shZmGZ-q.d.mts → types-CmrFjpml.d.mts} +1 -3
- package/dist/{watchers-hash-B_5b0-ZG.mjs → watchers-hash-BjwlnGD9.mjs} +85 -105
- package/docs/01-board/01-mounting.md +2 -2
- package/docs/01-board/03-identity.md +5 -6
- package/docs/01-board/04-messages.md +3 -2
- package/docs/01-board/05-reading.md +2 -1
- package/docs/01-board/06-watchers.md +8 -6
- package/docs/01-board/08-middleware.md +1 -1
- package/docs/01-board/09-access.md +145 -14
- package/docs/02-sync/01-overview.md +19 -26
- package/docs/02-sync/02-functions.md +8 -9
- package/docs/02-sync/03-slack.md +5 -5
- package/docs/02-sync/04-github.md +2 -2
- package/docs/02-sync/06-providers.md +91 -62
- package/docs/03-guides/01-coding-agents.md +1 -1
- package/docs/03-guides/03-ci.md +7 -4
- package/docs/04-reference/01-cli.md +86 -41
- package/docs/04-reference/02-board.md +56 -23
- package/docs/04-reference/03-http.md +59 -32
- package/docs/04-reference/04-log.md +8 -11
- package/docs/04-reference/05-conformance.md +35 -6
- package/docs/index.md +2 -2
- package/package.json +6 -9
- package/sql/postgres/board.sql +1 -1
- package/sql/postgres/schema.sql +1 -1
- package/dist/events-qf5XG5VQ.mjs +0 -75
- package/dist/exports-CrGA5_yN.d.mts +0 -5
- package/dist/sync-kapso.mjs +0 -89
- package/dist/sync.chat.d.mts +0 -2
- package/dist/sync.chat.mjs +0 -2
- package/dist/users-yryHKFMo.mjs +0 -42
- package/docs/02-sync/05-whatsapp.md +0 -152
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
|
-
import { S as storable, a as AttachmentId, c as ChannelPath, d as LabelKey, f as LabelValue, g as MessageText, l as Emoji, m as MESSAGE_TEXT_MAX,
|
|
2
|
+
import { S as storable, a as AttachmentId, c as ChannelPath, d as LabelKey, f as LabelValue, g as MessageText, l as Emoji, m as MESSAGE_TEXT_MAX, p as Labels, x as platformAvatar } from "./schemas-DBmUO6pr.mjs";
|
|
3
3
|
import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin, t as ExternalAuthor } from "./external-D3p5T57R.mjs";
|
|
4
|
-
import { r as badRequest, t as HttpError } from "./errors-
|
|
5
|
-
import { o as options } from "./commands-
|
|
4
|
+
import { r as badRequest, t as HttpError } from "./errors-CJXhhzvT.mjs";
|
|
5
|
+
import { o as options } from "./commands-L1JSGz2Z.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/api/routes.ts
|
|
8
8
|
/**
|
|
@@ -14,6 +14,13 @@ const Ref = z.string().min(1).max(128);
|
|
|
14
14
|
/** The message's `revision` as it was read: if it's been edited or deleted since, 409 `revision_conflict`. */
|
|
15
15
|
const Revision = z.number().int().positive();
|
|
16
16
|
const id = Ref;
|
|
17
|
+
/** A boolean query parameter: `true` or `1`, `false` or `0`. */
|
|
18
|
+
const flag = z.enum([
|
|
19
|
+
"true",
|
|
20
|
+
"false",
|
|
21
|
+
"1",
|
|
22
|
+
"0"
|
|
23
|
+
]).transform((value) => value === "true" || value === "1");
|
|
17
24
|
const repeated = z.union([z.string(), z.array(z.string())]).transform((value) => Array.isArray(value) ? value : [value]);
|
|
18
25
|
/**
|
|
19
26
|
* `pr` → `['pr', null]` (has the label), `status=open` → `['status', ['open']]`,
|
|
@@ -53,7 +60,7 @@ const ThreadsQuery = Filters.extend({
|
|
|
53
60
|
const EventsQuery = Filters.extend({
|
|
54
61
|
after: Ref.optional(),
|
|
55
62
|
wait: z.coerce.number().min(0).max(300).optional(),
|
|
56
|
-
all:
|
|
63
|
+
all: flag.optional()
|
|
57
64
|
});
|
|
58
65
|
const PostBody = z.object({
|
|
59
66
|
channel: ChannelPath.optional(),
|
|
@@ -119,7 +126,27 @@ const SchedulesQuery = z.object({
|
|
|
119
126
|
/** An actor ID or handle: the schedules it created. */
|
|
120
127
|
by: z.string().optional(),
|
|
121
128
|
/** Include done and canceled schedules. */
|
|
122
|
-
all:
|
|
129
|
+
all: flag.optional()
|
|
130
|
+
});
|
|
131
|
+
const CanGrant = z.union([z.literal("all"), z.array(z.string().min(1).max(16)).max(8)]);
|
|
132
|
+
/** What a grant or token lets do: `'all'`, a list for every channel, or a map from channel pattern to one. board() checks the patterns. */
|
|
133
|
+
const Can = z.union([CanGrant, z.record(z.string().min(1).max(300), CanGrant)]);
|
|
134
|
+
const ClaimValue = z.string().min(1).max(1e3);
|
|
135
|
+
/** Who a grant lets in: an issuer (`vercel`, `github`, or an https URL) and the claims its tokens carry, exactly. */
|
|
136
|
+
const GrantBody = z.object({
|
|
137
|
+
issuer: z.string().min(1).max(1e3),
|
|
138
|
+
claims: z.record(z.string().min(1).max(200), z.union([ClaimValue, z.array(ClaimValue).min(1).max(32)])),
|
|
139
|
+
can: Can.optional()
|
|
140
|
+
});
|
|
141
|
+
/** A token acting as the caller: what it may do, and the profile and session every request with it writes as. */
|
|
142
|
+
const TokenBody = z.object({
|
|
143
|
+
name: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/, "a token name is up to 64 letters, numbers, and ._-"),
|
|
144
|
+
can: Can.optional(),
|
|
145
|
+
profile: z.string().regex(/^[A-Za-z][A-Za-z0-9-]{0,31}$/, "a profile is up to 32 letters, numbers, and hyphens").optional(),
|
|
146
|
+
avatar: z.string().url().max(2048).optional(),
|
|
147
|
+
session: z.string().regex(/^[A-Za-z0-9_.:-]{1,128}$/, "a session is up to 128 letters, numbers, and _.:-").optional(),
|
|
148
|
+
/** How long it lives: `30d` by default, at most `90d`. */
|
|
149
|
+
expires: Duration.optional()
|
|
123
150
|
});
|
|
124
151
|
const SyncBody = z.object({ target: z.string().min(3).max(300) });
|
|
125
152
|
const InboxQuery = z.object({
|
|
@@ -134,12 +161,11 @@ const Registering = z.object({
|
|
|
134
161
|
})).refine((watch) => Object.keys(watch).length <= 32, `a board registers at most 32 watchers`),
|
|
135
162
|
/**
|
|
136
163
|
* Its sync entries: its sync subscribes too, to every post, change, and thread sync in the channels
|
|
137
|
-
* where it may sync, and the board lists the entries
|
|
164
|
+
* where it may sync, and the board lists the entries.
|
|
138
165
|
*/
|
|
139
166
|
sync: z.array(z.object({
|
|
140
167
|
key: z.string().min(1).max(64),
|
|
141
|
-
platform: z.string().min(1).max(64)
|
|
142
|
-
signIn: z.boolean().default(false)
|
|
168
|
+
platform: z.string().min(1).max(64)
|
|
143
169
|
})).max(32).optional()
|
|
144
170
|
});
|
|
145
171
|
/**
|
|
@@ -191,7 +217,6 @@ const SourceReadQuery = z.object({
|
|
|
191
217
|
const SourceJoinBody = z.object({ target: z.string().min(1).max(4e3) });
|
|
192
218
|
/** A platform's link to a message or thread, such as a Slack permalink or a GitHub comment's URL. */
|
|
193
219
|
const LinkedQuery = z.object({ url: z.string().url().max(4e3) });
|
|
194
|
-
const IdentityUnlinkBody = z.object({ actor: z.string().min(1).max(256) });
|
|
195
220
|
/** A sync entry's key: `slack`, `github`. */
|
|
196
221
|
const SyncKey = z.string().min(1).max(64);
|
|
197
222
|
/** A platform message an import changes, by its platform thread and ID; without a message, the root of the board thread the platform thread belongs to. */
|
|
@@ -301,8 +326,7 @@ const SyncReceiptBody = z.discriminatedUnion("kind", [
|
|
|
301
326
|
target: ExternalTarget,
|
|
302
327
|
external: z.object({
|
|
303
328
|
thread: ExternalTarget,
|
|
304
|
-
message: z.string().min(1).max(300)
|
|
305
|
-
asUser: z.literal(true).optional()
|
|
329
|
+
message: z.string().min(1).max(300)
|
|
306
330
|
}),
|
|
307
331
|
/** Its files, when they went with it: the platform's IDs, or null when it refused them. */
|
|
308
332
|
files: PlatformFiles.optional()
|
|
@@ -329,24 +353,15 @@ const SyncReceiptBody = z.discriminatedUnion("kind", [
|
|
|
329
353
|
cause: z.enum(["config", "request"])
|
|
330
354
|
})
|
|
331
355
|
]);
|
|
332
|
-
/** What sync looks up by platform ID: a thread or
|
|
356
|
+
/** What sync looks up by platform ID: a thread or a message. */
|
|
333
357
|
const SyncFindQuery = z.object({
|
|
334
|
-
/** A live one-time code: who it was issued to, for a sign-in that carries it. */
|
|
335
|
-
code: z.string().min(1).max(64).optional(),
|
|
336
358
|
/** The post the caller's sync imported with this client ID. */
|
|
337
359
|
post: z.string().min(1).max(200).optional(),
|
|
338
360
|
thread: ExternalTarget.optional(),
|
|
339
361
|
/** With `thread`: the platform message in it. */
|
|
340
|
-
message: z.string().min(1).max(300).optional()
|
|
341
|
-
account: ActorId.optional(),
|
|
342
|
-
person: ActorId.optional()
|
|
362
|
+
message: z.string().min(1).max(300).optional()
|
|
343
363
|
});
|
|
344
364
|
const SyncCopiesQuery = z.object({ message: repeated.pipe(z.array(Ref).min(1).max(100)) });
|
|
345
|
-
/** A person's own roots since `since`, in epoch milliseconds. */
|
|
346
|
-
const SyncRootsQuery = z.object({
|
|
347
|
-
by: ActorId,
|
|
348
|
-
since: z.coerce.number().int().nonnegative()
|
|
349
|
-
});
|
|
350
365
|
/** Platform events for a sync key's inbox, each with the platform's own ID for it, which dedupes it. */
|
|
351
366
|
const InboxPushBody = z.object({ events: z.array(z.object({
|
|
352
367
|
id: z.string().min(1).max(300),
|
|
@@ -358,26 +373,6 @@ const InboxAckBody = z.object({
|
|
|
358
373
|
lease: z.string().min(1).max(64),
|
|
359
374
|
error: z.string().min(1).max(500).optional()
|
|
360
375
|
});
|
|
361
|
-
/** A one-time code for linking an account on sync entry `sync`: a person's own, for themselves. */
|
|
362
|
-
const SyncCodeBody = z.object({ sync: SyncKey });
|
|
363
|
-
/**
|
|
364
|
-
* A platform account, linked to whoever a code was issued to: one posted from the account, or
|
|
365
|
-
* carried through its sign-in. `message` is the platform message that posted it.
|
|
366
|
-
*/
|
|
367
|
-
const SyncAccountBody = z.object({
|
|
368
|
-
sync: SyncKey,
|
|
369
|
-
account: PlatformUser,
|
|
370
|
-
code: z.string().min(1).max(64),
|
|
371
|
-
message: z.string().max(1024).optional(),
|
|
372
|
-
/** `consent` when the code came through the account's own sign-in, so the board posts as them. */
|
|
373
|
-
method: z.enum(["code", "consent"]).optional()
|
|
374
|
-
});
|
|
375
|
-
/** A sign-in that carries a code, on the board whose sync runs the entry. */
|
|
376
|
-
const SignInQuery = z.object({
|
|
377
|
-
sync: SyncKey,
|
|
378
|
-
code: z.string().min(1).max(64)
|
|
379
|
-
});
|
|
380
|
-
const SyncUnlinkBody = z.object({ account: ActorId });
|
|
381
376
|
/**
|
|
382
377
|
* A direct upload, before its bytes: what the file will be called, its type, its exact size, and
|
|
383
378
|
* the SHA-256 of its bytes, in base64, for a store that signs its URL for them (Vercel's).
|
|
@@ -393,12 +388,14 @@ const EditBody = z.object({
|
|
|
393
388
|
revision: Revision.optional()
|
|
394
389
|
});
|
|
395
390
|
const DeleteBody = z.object({ revision: Revision.optional() }).optional();
|
|
396
|
-
/** `status`: what Slack shows in place of "Typing…" (
|
|
391
|
+
/** `status`: what Slack shows in place of "Typing…" (other platforms ignore it). */
|
|
397
392
|
const TypingBody = z.object({ status: z.string().trim().min(1).max(100).optional() }).optional();
|
|
398
393
|
const ReactBody = z.object({
|
|
399
394
|
emoji: Emoji,
|
|
400
395
|
remove: z.boolean().optional()
|
|
401
396
|
});
|
|
397
|
+
/** A channel to make before anything posts to it. */
|
|
398
|
+
const CreateChannelBody = z.object({ path: ChannelPath });
|
|
402
399
|
const LabelBody = z.object({
|
|
403
400
|
set: Labels.optional(),
|
|
404
401
|
unset: z.array(LabelKey).optional(),
|
|
@@ -410,7 +407,7 @@ const LabelBody = z.object({
|
|
|
410
407
|
}).refine((body) => body.set !== void 0 || body.unset !== void 0, { message: "nothing to change: pass `set` or `unset`" });
|
|
411
408
|
/** The route table: method, path, and the schemas that validate input. */
|
|
412
409
|
const routes = {
|
|
413
|
-
/** Where the board is, its project, and
|
|
410
|
+
/** Where the board is, its project, and its sync entries. No token. */
|
|
414
411
|
discovery: {
|
|
415
412
|
method: "GET",
|
|
416
413
|
path: "/"
|
|
@@ -423,6 +420,12 @@ const routes = {
|
|
|
423
420
|
method: "GET",
|
|
424
421
|
path: "/channels"
|
|
425
422
|
},
|
|
423
|
+
/** Makes a channel before anything posts to it: needs `read` and `post` there. One that's there already is left as it is. */
|
|
424
|
+
createChannel: {
|
|
425
|
+
method: "POST",
|
|
426
|
+
path: "/channels",
|
|
427
|
+
body: CreateChannelBody
|
|
428
|
+
},
|
|
426
429
|
actors: {
|
|
427
430
|
method: "GET",
|
|
428
431
|
path: "/actors"
|
|
@@ -506,6 +509,34 @@ const routes = {
|
|
|
506
509
|
method: "POST",
|
|
507
510
|
path: "/schedules/:id/cancel"
|
|
508
511
|
},
|
|
512
|
+
/** Grants need manage in every channel. Setting one with the same issuer and claims changes its `can`. */
|
|
513
|
+
grants: {
|
|
514
|
+
method: "GET",
|
|
515
|
+
path: "/grants"
|
|
516
|
+
},
|
|
517
|
+
createGrant: {
|
|
518
|
+
method: "POST",
|
|
519
|
+
path: "/grants",
|
|
520
|
+
body: GrantBody
|
|
521
|
+
},
|
|
522
|
+
revokeGrant: {
|
|
523
|
+
method: "POST",
|
|
524
|
+
path: "/grants/:id/revoke"
|
|
525
|
+
},
|
|
526
|
+
/** Your tokens, or every one with manage in every channel. A token acts as whoever minted it. */
|
|
527
|
+
tokens: {
|
|
528
|
+
method: "GET",
|
|
529
|
+
path: "/tokens"
|
|
530
|
+
},
|
|
531
|
+
createToken: {
|
|
532
|
+
method: "POST",
|
|
533
|
+
path: "/tokens",
|
|
534
|
+
body: TokenBody
|
|
535
|
+
},
|
|
536
|
+
revokeToken: {
|
|
537
|
+
method: "POST",
|
|
538
|
+
path: "/tokens/:id/revoke"
|
|
539
|
+
},
|
|
509
540
|
/** The caller's own post with the client ID it gave: what `POST /messages` answered, for telling a retry before posting. */
|
|
510
541
|
posted: {
|
|
511
542
|
method: "GET",
|
|
@@ -564,30 +595,6 @@ const routes = {
|
|
|
564
595
|
path: "/sync/copies",
|
|
565
596
|
query: SyncCopiesQuery
|
|
566
597
|
},
|
|
567
|
-
/** A person's recent roots, for telling their own platform copies from new messages. */
|
|
568
|
-
syncRoots: {
|
|
569
|
-
method: "GET",
|
|
570
|
-
path: "/sync/roots",
|
|
571
|
-
query: SyncRootsQuery
|
|
572
|
-
},
|
|
573
|
-
/** A one-time code for linking a platform account. */
|
|
574
|
-
syncCode: {
|
|
575
|
-
method: "POST",
|
|
576
|
-
path: "/sync/codes",
|
|
577
|
-
body: SyncCodeBody
|
|
578
|
-
},
|
|
579
|
-
/** Links a platform account, by a code or for a person. */
|
|
580
|
-
syncAccount: {
|
|
581
|
-
method: "POST",
|
|
582
|
-
path: "/sync/accounts",
|
|
583
|
-
body: SyncAccountBody
|
|
584
|
-
},
|
|
585
|
-
/** Unlinks a platform account. */
|
|
586
|
-
syncUnlink: {
|
|
587
|
-
method: "POST",
|
|
588
|
-
path: "/sync/accounts/unlink",
|
|
589
|
-
body: SyncUnlinkBody
|
|
590
|
-
},
|
|
591
598
|
/** Reads a platform channel or thread on demand, where that sync runs, without syncing or storing it. Full access only. */
|
|
592
599
|
readSource: {
|
|
593
600
|
method: "GET",
|
|
@@ -637,28 +644,6 @@ const routes = {
|
|
|
637
644
|
method: "GET",
|
|
638
645
|
path: "/openapi.json"
|
|
639
646
|
},
|
|
640
|
-
/** Your linked platform accounts. */
|
|
641
|
-
identity: {
|
|
642
|
-
method: "GET",
|
|
643
|
-
path: "/identity"
|
|
644
|
-
},
|
|
645
|
-
/** Starts a sign-in that carries a code (no token), on the board that runs the entry's sync. */
|
|
646
|
-
signIn: {
|
|
647
|
-
method: "GET",
|
|
648
|
-
path: "/identity/sign-in",
|
|
649
|
-
query: SignInQuery
|
|
650
|
-
},
|
|
651
|
-
/** Where a sign-in comes back (no token): links the account it authorized to the code's person. */
|
|
652
|
-
signedIn: {
|
|
653
|
-
method: "GET",
|
|
654
|
-
path: "/identity/signed-in",
|
|
655
|
-
query: SignInQuery
|
|
656
|
-
},
|
|
657
|
-
identityUnlink: {
|
|
658
|
-
method: "POST",
|
|
659
|
-
path: "/identity/unlink",
|
|
660
|
-
body: IdentityUnlinkBody
|
|
661
|
-
},
|
|
662
647
|
/** Watchers registered by boards on `http()`: every one to `manage` everywhere, the caller's own to anyone else. */
|
|
663
648
|
watchers: {
|
|
664
649
|
method: "GET",
|
|
@@ -693,7 +678,7 @@ const routes = {
|
|
|
693
678
|
const SYNC = "@sync";
|
|
694
679
|
//#endregion
|
|
695
680
|
//#region src/version.ts
|
|
696
|
-
const VERSION = "0.
|
|
681
|
+
const VERSION = "0.31.0";
|
|
697
682
|
//#endregion
|
|
698
683
|
//#region src/server/routing.ts
|
|
699
684
|
/** board()'s own routes: the files it serves, and sync's, which run where board() does. */
|
|
@@ -701,9 +686,6 @@ const BOARD_ROUTES = [
|
|
|
701
686
|
"avatar",
|
|
702
687
|
"openapi",
|
|
703
688
|
"webhook",
|
|
704
|
-
"signIn",
|
|
705
|
-
"signedIn",
|
|
706
|
-
"identityUnlink",
|
|
707
689
|
"sync"
|
|
708
690
|
];
|
|
709
691
|
/** Every protocol method, by the route it's served at. */
|
|
@@ -711,8 +693,8 @@ const METHODS = [
|
|
|
711
693
|
"discovery",
|
|
712
694
|
"me",
|
|
713
695
|
"channels",
|
|
696
|
+
"createChannel",
|
|
714
697
|
"actors",
|
|
715
|
-
"identity",
|
|
716
698
|
"messages",
|
|
717
699
|
"threads",
|
|
718
700
|
"message",
|
|
@@ -725,6 +707,12 @@ const METHODS = [
|
|
|
725
707
|
"schedule",
|
|
726
708
|
"schedules",
|
|
727
709
|
"cancelSchedule",
|
|
710
|
+
"grants",
|
|
711
|
+
"createGrant",
|
|
712
|
+
"revokeGrant",
|
|
713
|
+
"tokens",
|
|
714
|
+
"createToken",
|
|
715
|
+
"revokeToken",
|
|
728
716
|
"upload",
|
|
729
717
|
"attachment",
|
|
730
718
|
"uploadStart",
|
|
@@ -739,14 +727,10 @@ const METHODS = [
|
|
|
739
727
|
"syncThread",
|
|
740
728
|
"syncFind",
|
|
741
729
|
"syncCopies",
|
|
742
|
-
"syncRoots",
|
|
743
730
|
"inboxPush",
|
|
744
731
|
"inboxNext",
|
|
745
732
|
"inboxAck",
|
|
746
733
|
"inbox",
|
|
747
|
-
"syncCode",
|
|
748
|
-
"syncAccount",
|
|
749
|
-
"syncUnlink",
|
|
750
734
|
"typing",
|
|
751
735
|
"readSource",
|
|
752
736
|
"joinSource",
|
|
@@ -1041,12 +1025,8 @@ function canonicalWatchers(watch, sync = []) {
|
|
|
1041
1025
|
watch[id].tail,
|
|
1042
1026
|
watch[id].when
|
|
1043
1027
|
]);
|
|
1044
|
-
if (sync.length > 0) canonical.push(["sync", [...sync].sort((a, b) => byCodeUnit(a.key, b.key)).map((entry) => [
|
|
1045
|
-
entry.key,
|
|
1046
|
-
entry.platform,
|
|
1047
|
-
entry.signIn
|
|
1048
|
-
])]);
|
|
1028
|
+
if (sync.length > 0) canonical.push(["sync", [...sync].sort((a, b) => byCodeUnit(a.key, b.key)).map((entry) => [entry.key, entry.platform])]);
|
|
1049
1029
|
return JSON.stringify(canonical);
|
|
1050
1030
|
}
|
|
1051
1031
|
//#endregion
|
|
1052
|
-
export {
|
|
1032
|
+
export { RegisterBody as A, ThreadsQuery as B, LabelBody as C, PostBody as D, MessagesQuery as E, SyncBody as F, TypingBody as H, SyncCopiesQuery as I, SyncFindQuery as L, SchedulesQuery as M, SourceJoinBody as N, ReactBody as O, SourceReadQuery as P, SyncImportBody as R, InboxQuery as S, MatchesQuery as T, UploadStartBody as U, TokenBody as V, routes as W, EventsQuery as _, METHODS as a, InboxNextQuery as b, queryObject as c, VERSION as d, SYNC as f, EditBody as g, DeleteBody as h, withMentions as i, ScheduleBody as j, Ref as k, readCapped as l, CreateChannelBody as m, compileWatcher as n, createRouter as o, AckBody as p, hasCriteria as r, openapi as s, canonicalWatchers as t, readJson as u, GrantBody as v, LinkedQuery as w, InboxPushBody as x, InboxAckBody as y, SyncReceiptBody as z };
|
|
@@ -60,8 +60,8 @@ export const GET = team.fetch, POST = team.fetch
|
|
|
60
60
|
|
|
61
61
|
A board on `http()` keeps nothing:
|
|
62
62
|
|
|
63
|
-
- **It answers only wake-ups and webhooks
|
|
64
|
-
|
|
63
|
+
- **It answers only wake-ups and webhooks.** Anything else, discovery
|
|
64
|
+
included, answers 404 `<its URL>
|
|
65
65
|
answers only wake-ups and webhooks`, and verifies nothing. People and the
|
|
66
66
|
CLI use the other board, by its URL. With an
|
|
67
67
|
`access` of its own, it serves its callers too, as a gateway: see
|
|
@@ -13,8 +13,7 @@ a host can recognize its own credentials too: see
|
|
|
13
13
|
[A host's own verifier](./09-access.md#a-hosts-own-verifier).
|
|
14
14
|
|
|
15
15
|
Only a few routes take no token: discovery, the built-in avatars, the OpenAPI
|
|
16
|
-
document, the
|
|
17
|
-
and `/webhooks/:key`, which the platform's adapter authenticates instead. A
|
|
16
|
+
document, and `/webhooks/:key`, which the platform's adapter authenticates instead. A
|
|
18
17
|
board on `http()` also answers `POST /wake` without one: a wake-up only says
|
|
19
18
|
to pull.
|
|
20
19
|
|
|
@@ -24,7 +23,7 @@ The CLI and `board()` in Node find a token themselves. The first match wins:
|
|
|
24
23
|
|
|
25
24
|
| Where | How | Principal |
|
|
26
25
|
| --- | --- | --- |
|
|
27
|
-
| Explicit | `BOARD_TOKEN`: an OIDC JWT for the board's audience, from Vercel, GitHub Actions, or an issuer `access` names
|
|
26
|
+
| Explicit | `BOARD_TOKEN`: an OIDC JWT for the board's audience, from Vercel, GitHub Actions, or an issuer `access` or a grant names; or a [board token](./09-access.md#tokens) | the issuer's, or a board token's minter |
|
|
28
27
|
| A board from `vb serve` | the token it writes to a file only you can read, for its `127.0.0.1` URL | `local:user:<your OS user>` |
|
|
29
28
|
| GitHub Actions | the job's OIDC token, for the board's audience; needs `permissions: id-token: write` | `github:repo:<repository_id>` |
|
|
30
29
|
| A Vercel deployment | `getVercelOidcToken({ audience })` from `@vercel/oidc`, when `VERCEL=1` | `vercel:project:<project_id>:<env>` |
|
|
@@ -268,6 +267,6 @@ exist ([Access](./09-access.md#what-a-caller-sees)). Mentions are stored as acto
|
|
|
268
267
|
IDs, so a later handle change never reroutes an old one, and edits resolve them
|
|
269
268
|
again. Platform text never fails a write: a mention there that doesn't resolve
|
|
270
269
|
stays plain text. On their own platform, mentions render natively: `<@U0456>` in
|
|
271
|
-
Slack, `@login` on GitHub.
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
Slack, `@login` on GitHub. Anyone else, a board person included, reads as their
|
|
271
|
+
name there: mention a Slack user in Slack by their actor
|
|
272
|
+
(`@[slack:user:U0456]`) or their email (`@[ada@vercel.com]`).
|
|
@@ -11,8 +11,9 @@ vb post www/production "rollout paused"
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
A channel is a path: one or more segments of lowercase letters, numbers, `_`,
|
|
14
|
-
and `-`, joined by `/`, up to 80 characters. The first post creates it,
|
|
15
|
-
|
|
14
|
+
and `-`, joined by `/`, up to 80 characters. The first post creates it, or
|
|
15
|
+
`vb channels create` does before that, and its path never changes. Prefixes
|
|
16
|
+
are display groups: `vb channels` lists
|
|
16
17
|
`www/production` and `www/preview` under `www/`, and filters take `www/` for the
|
|
17
18
|
whole subtree.
|
|
18
19
|
|
|
@@ -74,7 +74,7 @@ read includes it.
|
|
|
74
74
|
| `<channel>` | that channel; `www/` with a trailing slash for the subtree |
|
|
75
75
|
| `--re <id>` | the thread rooted at `<id>`: the root and its replies |
|
|
76
76
|
| `--by <handle>` | messages by that actor: a handle without the `@`, or an exact actor ID |
|
|
77
|
-
| `--mentions` | messages that mention you; `--mentions @ada` for someone else |
|
|
77
|
+
| `--mentions` | messages that mention you; `--mentions @ada` for someone else, or `--mentions ada@vercel.com` for whoever has that email on Slack |
|
|
78
78
|
| `--source <source>` | where it was written: a sync key (`slack`, `github`), or `board` |
|
|
79
79
|
| `--label <key>` | messages with that label; `key=value`, or `key=a\|b` for either |
|
|
80
80
|
| `--text <words>` | text containing the words, case-insensitive (a substring, not search) |
|
|
@@ -84,6 +84,7 @@ read includes it.
|
|
|
84
84
|
vb read prs --label state=open # what's pending
|
|
85
85
|
vb read issues --label tag.bug --source github
|
|
86
86
|
vb read --mentions # everywhere I'm mentioned
|
|
87
|
+
vb tail --mentions julian@vercel.com # Slack pings of me, as they land
|
|
87
88
|
vb read errors-www --by ada --text ENOTFOUND
|
|
88
89
|
```
|
|
89
90
|
|
|
@@ -49,7 +49,9 @@ A channel (or `prefix/` for a subtree) and the shared
|
|
|
49
49
|
- **Positions aren't filters.** A watcher starts at the head when it's deployed
|
|
50
50
|
and sees every match after it, so `--after` fails the boot.
|
|
51
51
|
- **A watcher has no "me".** `--mentions` alone fails; name a handle:
|
|
52
|
-
`--mentions @ada`, or `--mentions @julian/claude` for an agent.
|
|
52
|
+
`--mentions @ada`, or `--mentions @julian/claude` for an agent. A watcher
|
|
53
|
+
doesn't look emails up: name a Slack user by actor
|
|
54
|
+
(`--mentions slack:user:U0456`).
|
|
53
55
|
- **A bad id or expression fails the boot** with the CLI's error:
|
|
54
56
|
`watch triage: --after isn't a filter`.
|
|
55
57
|
- **Ids** are lowercase letters, numbers, `-`, and `_`.
|
|
@@ -251,8 +253,8 @@ export const GET = team.fetch, POST = team.fetch
|
|
|
251
253
|
for the next match to wake this board. A board that pulls on its own
|
|
252
254
|
doesn't wait.
|
|
253
255
|
- **Its [sync](../02-sync/01-overview.md#on-a-board-elsewhere) subscribes the
|
|
254
|
-
same way,** with its entries in its registration (`sync: [{ key, platform
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
same way,** with its entries in its registration (`sync: [{ key, platform }]`):
|
|
257
|
+
it gets every post, change, and thread sync in the channels its deployment
|
|
258
|
+
may `sync`, as matches of watcher `@sync`, and runs them here. The other
|
|
259
|
+
board lists those entries in its discovery, and refuses a key its own sync
|
|
260
|
+
has (409 `sync_key_taken`).
|
|
@@ -34,7 +34,7 @@ export const { scheduler } = backend
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Every post and edit goes through `middleware`, in order, before it lands: the
|
|
37
|
-
API (the CLI, scripts, CI), messages synced in from Slack, GitHub, and
|
|
37
|
+
API (the CLI, scripts, CI), messages synced in from Slack, GitHub, and Linear
|
|
38
38
|
(sync callbacks' writes included), watchers, the board's
|
|
39
39
|
[handle](./01-mounting.md#the-handle), and scheduled sends. Reactions, labels,
|
|
40
40
|
and deletes don't. It's `board()`'s, so it runs before the write reaches the
|