@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,6 +1,6 @@
|
|
|
1
1
|
import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { i as Attachment } from "./schemas-DBmUO6pr.mjs";
|
|
3
|
-
import { r as badRequest, s as notFound, t as HttpError } from "./errors-
|
|
3
|
+
import { r as badRequest, s as notFound, t as HttpError } from "./errors-CJXhhzvT.mjs";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
@@ -61,8 +61,19 @@ function attachmentId() {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
+
* Whether an upload is the caller's own, to read and attach before a message carries it: its
|
|
65
|
+
* principal sent it, and the caller holds no board token, or holds the one it came with or one
|
|
66
|
+
* that token was minted with. A token acts as its minter, so the principal alone would hand it
|
|
67
|
+
* every file its minter, or another of its minter's tokens, uploaded.
|
|
68
|
+
*/
|
|
69
|
+
function ownUpload(upload, caller) {
|
|
70
|
+
if (upload.uploadedBy !== caller.id) return false;
|
|
71
|
+
return caller.token === void 0 || (upload.uploadedWith?.includes(caller.token) ?? false);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
64
74
|
* Stores an upload under a fresh ID, with its metadata beside it. `uploadedBy`
|
|
65
|
-
* is the principal that sent it, who can read it before any message carries it
|
|
75
|
+
* is the principal that sent it, who can read it before any message carries it,
|
|
76
|
+
* and `uploadedWith` the board tokens it came with (`ownUpload`).
|
|
66
77
|
* `max` is the most it can be: `ATTACHMENT_BYTES_MAX` unless the board's own sync imports it.
|
|
67
78
|
*/
|
|
68
79
|
async function writeUpload(blob, input, max = ATTACHMENT_BYTES_MAX) {
|
|
@@ -78,7 +89,8 @@ async function writeUpload(blob, input, max = ATTACHMENT_BYTES_MAX) {
|
|
|
78
89
|
await blob.put(dataKey(attachment.id), input.data, attachment.type);
|
|
79
90
|
const meta = input.uploadedBy === void 0 ? attachment : {
|
|
80
91
|
...attachment,
|
|
81
|
-
uploadedBy: input.uploadedBy
|
|
92
|
+
uploadedBy: input.uploadedBy,
|
|
93
|
+
...input.uploadedWith ? { uploadedWith: input.uploadedWith } : {}
|
|
82
94
|
};
|
|
83
95
|
await blob.put(metaKey(attachment.id), new TextEncoder().encode(JSON.stringify(meta)), "application/json");
|
|
84
96
|
return attachment;
|
|
@@ -97,9 +109,11 @@ async function readUpload(blob, id) {
|
|
|
97
109
|
const found = Attachment.shape.id.safeParse(id).success ? await blob.get(metaKey(id)) : null;
|
|
98
110
|
if (!found) throw attachmentNotFound(id);
|
|
99
111
|
const meta = JSON.parse(new TextDecoder().decode(await bytes(found.data)));
|
|
112
|
+
const tokens = Array.isArray(meta.uploadedWith) && meta.uploadedWith.every((token) => typeof token === "string") ? meta.uploadedWith : null;
|
|
100
113
|
return {
|
|
101
114
|
attachment: Attachment.parse(meta),
|
|
102
|
-
uploadedBy: typeof meta.uploadedBy === "string" ? meta.uploadedBy : null
|
|
115
|
+
uploadedBy: typeof meta.uploadedBy === "string" ? meta.uploadedBy : null,
|
|
116
|
+
uploadedWith: tokens
|
|
103
117
|
};
|
|
104
118
|
}
|
|
105
119
|
/** An attachment's bytes as the store keeps them, a stream or the bytes, for board() to serve. */
|
|
@@ -127,6 +141,7 @@ async function startUpload(blob, input) {
|
|
|
127
141
|
type: mediaType(input.type),
|
|
128
142
|
size: input.size,
|
|
129
143
|
uploadedBy: input.uploadedBy,
|
|
144
|
+
...input.uploadedWith ? { uploadedWith: input.uploadedWith } : {},
|
|
130
145
|
expiresAt: expires.toISOString()
|
|
131
146
|
};
|
|
132
147
|
await blob.put(ticketKey(ticket.id), new TextEncoder().encode(JSON.stringify(ticket)), "application/json");
|
|
@@ -147,15 +162,15 @@ async function startUpload(blob, input) {
|
|
|
147
162
|
}
|
|
148
163
|
/**
|
|
149
164
|
* Finishes a direct upload: once the store has exactly the bytes its start said, the board records
|
|
150
|
-
* the attachment, as `writeUpload` does. Only its uploader may finish it; a second
|
|
151
|
-
* the same attachment. It never reads the record before writing it: a store with a
|
|
152
|
-
* Blob's CDN) would keep that miss, and the post that names the file would find nothing.
|
|
165
|
+
* the attachment, as `writeUpload` does. Only its uploader may finish it (`ownUpload`); a second
|
|
166
|
+
* finish answers the same attachment. It never reads the record before writing it: a store with a
|
|
167
|
+
* cache (Vercel Blob's CDN) would keep that miss, and the post that names the file would find nothing.
|
|
153
168
|
*/
|
|
154
|
-
async function finishUpload(blob, id,
|
|
169
|
+
async function finishUpload(blob, id, caller) {
|
|
155
170
|
if (!blob) throw noBlob();
|
|
156
171
|
const found = Attachment.shape.id.safeParse(id).success ? await blob.get(ticketKey(id)) : null;
|
|
157
172
|
const ticket = found ? JSON.parse(new TextDecoder().decode(await bytes(found.data))) : null;
|
|
158
|
-
if (!ticket || ticket
|
|
173
|
+
if (!ticket || !ownUpload(ticket, caller)) throw notFound(`no upload: ${id}`, "upload_not_found");
|
|
159
174
|
if (!blob.head) throw noDirectUploads();
|
|
160
175
|
const stored = await blob.head(dataKey(id));
|
|
161
176
|
if (!stored) throw new HttpError(409, "not_uploaded", `nothing's been uploaded for ${id} yet: PUT the file to its URL first`);
|
|
@@ -167,9 +182,13 @@ async function finishUpload(blob, id, principal) {
|
|
|
167
182
|
size: stored.size
|
|
168
183
|
};
|
|
169
184
|
try {
|
|
185
|
+
const uploader = {
|
|
186
|
+
uploadedBy: ticket.uploadedBy,
|
|
187
|
+
...ticket.uploadedWith ? { uploadedWith: ticket.uploadedWith } : {}
|
|
188
|
+
};
|
|
170
189
|
await blob.put(metaKey(id), new TextEncoder().encode(JSON.stringify({
|
|
171
190
|
...attachment,
|
|
172
|
-
|
|
191
|
+
...uploader
|
|
173
192
|
})), "application/json");
|
|
174
193
|
} catch (error) {
|
|
175
194
|
const done = await blob.get(metaKey(id)).catch(() => null);
|
|
@@ -179,4 +198,4 @@ async function finishUpload(blob, id, principal) {
|
|
|
179
198
|
return attachment;
|
|
180
199
|
}
|
|
181
200
|
//#endregion
|
|
182
|
-
export { fsBlob as a,
|
|
201
|
+
export { fsBlob as a, ownUpload as c, writeUpload as d, finishUpload as i, readUpload as l, attachmentNotFound as n, mediaType as o, fileName as r, memoryBlob as s, attachmentBody as t, startUpload as u };
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, t as Actor } from "./folds-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Adapter, Author, FileUpload, Message, ReactionEvent } from "chat";
|
|
5
|
-
//#region src/sync/types.d.ts
|
|
6
|
-
declare function externalAuthor(platform: string, author: Author): ExternalAuthor;
|
|
7
|
-
//#endregion
|
|
1
|
+
import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, t as Actor } from "./folds-DLUzfibJ.mjs";
|
|
2
|
+
import { dt as SourceJoin, mt as SourceRead } from "./client-COfYrgfV.mjs";
|
|
3
|
+
import { T as ProviderEvent, a as ProviderOptions, c as ReceivedEvent, d as ResolvedTarget, g as SyncFn, i as ProviderFile, k as SyncRoot, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, t as FetchedLink } from "./provider-gynDUoyW.mjs";
|
|
4
|
+
import { Adapter, Author, FileUpload, Message, ReactionEvent, StateAdapter } from "chat";
|
|
8
5
|
//#region src/sync/chat-provider.d.ts
|
|
9
6
|
type ChatInput = {
|
|
10
7
|
kind: 'message' | 'edited';
|
|
@@ -20,11 +17,21 @@ type ChatInput = {
|
|
|
20
17
|
};
|
|
21
18
|
type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
|
|
22
19
|
adapter: Adapter;
|
|
20
|
+
/**
|
|
21
|
+
* Where Chat SDK and the adapter keep their state, like `createRedisState()`. In memory unless
|
|
22
|
+
* given, which is enough when the board's log is all an adapter needs to last.
|
|
23
|
+
*/
|
|
24
|
+
state?: StateAdapter;
|
|
23
25
|
findByEmail?(email: string): Promise<ExternalAuthor | null>;
|
|
24
26
|
mention?(actor: Actor): string | null;
|
|
25
27
|
author?(author: Author, raw: unknown): ExternalAuthor;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
/** A channel's name as its native ID. Without it, a name is the ID. */
|
|
29
|
+
resolveChannel?(key: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* A target's channel and kind, called on the provider. Without it, the adapter reads the target
|
|
32
|
+
* as a thread: one it reads as its own channel, or can't read, is a channel.
|
|
33
|
+
*/
|
|
34
|
+
resolveTarget?(this: SyncProvider, target: string): Promise<ResolvedTarget>;
|
|
28
35
|
groupThread?(thread: string): string;
|
|
29
36
|
channelOf?(thread: string): string;
|
|
30
37
|
isRoot?(thread: string, message: string): boolean;
|
|
@@ -34,7 +41,10 @@ type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
|
|
|
34
41
|
markdown?(message: Message): string;
|
|
35
42
|
/** Platform messages a message links to, located under sync key `key`. */
|
|
36
43
|
links?(key: string, message: Message): ExternalLink[];
|
|
37
|
-
fetchLink?(link: ExternalLink
|
|
44
|
+
fetchLink?(link: ExternalLink, linker: {
|
|
45
|
+
thread: string;
|
|
46
|
+
message: string;
|
|
47
|
+
}): Promise<FetchedLink>;
|
|
38
48
|
/** The message a URL of the platform's names, by its own IDs: `SyncProvider.locate`. */
|
|
39
49
|
locate?(url: string): {
|
|
40
50
|
thread: string;
|
|
@@ -65,14 +75,11 @@ type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
|
|
|
65
75
|
/** What the adapter needs to fetch a file again, if not everything the event carried. */
|
|
66
76
|
fileToFetch?(file: ExternalFile): ExternalFile;
|
|
67
77
|
startTyping?(thread: string, latest: string | null, status?: string): Promise<void>;
|
|
68
|
-
/** Runs adapter calls with a person's token instead of the bot's: how a message posts as them. */
|
|
69
|
-
runAs?<T>(token: string, fn: () => Promise<T>): Promise<T>;
|
|
70
|
-
/** The platform account a person's token belongs to. */
|
|
71
|
-
whoAmI?(token: string): Promise<ExternalAuthor>;
|
|
72
78
|
};
|
|
73
79
|
declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent> extends SyncProvider<E> {
|
|
74
80
|
protected readonly transport: ChatTransport;
|
|
75
81
|
private readonly chat;
|
|
82
|
+
private readonly state;
|
|
76
83
|
private readonly receiving;
|
|
77
84
|
private initialized;
|
|
78
85
|
constructor(transport: ChatTransport, sync: SyncFn<E>);
|
|
@@ -101,7 +108,11 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
|
|
|
101
108
|
}>;
|
|
102
109
|
protected normalize(key: string, input: ChatInput): Promise<ReceivedEvent>;
|
|
103
110
|
private emit;
|
|
111
|
+
/** The body of the webhook being handled, as the platform sent it: the same on a redelivery. */
|
|
112
|
+
protected delivery(): string | null;
|
|
104
113
|
receive(key: string, request: Request, emit: Receive): Promise<Response>;
|
|
114
|
+
/** `state`, noting each first claim (`setIfNotExists`) made while a webhook is handled. */
|
|
115
|
+
private claiming;
|
|
105
116
|
private receiveRequest;
|
|
106
117
|
protected ready(): Promise<Adapter<unknown, unknown>>;
|
|
107
118
|
resolveChannel(key: string): Promise<string>;
|
|
@@ -110,6 +121,8 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
|
|
|
110
121
|
channel: string;
|
|
111
122
|
kind: "channel" | "thread";
|
|
112
123
|
}>;
|
|
124
|
+
/** A target as the adapter reads it: a thread in its channel, or a channel. */
|
|
125
|
+
private targetOf;
|
|
113
126
|
findByEmail(email: string): Promise<{
|
|
114
127
|
id: string;
|
|
115
128
|
handle: string;
|
|
@@ -119,7 +132,10 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
|
|
|
119
132
|
} | null>;
|
|
120
133
|
mention(actor: Actor): string | null;
|
|
121
134
|
fetchRoot(thread: string): Promise<SyncRoot | null>;
|
|
122
|
-
fetchLink(link: ExternalLink
|
|
135
|
+
fetchLink(link: ExternalLink, linker: {
|
|
136
|
+
thread: string;
|
|
137
|
+
message: string;
|
|
138
|
+
}): Promise<FetchedLink>;
|
|
123
139
|
locate(url: string): {
|
|
124
140
|
thread: string;
|
|
125
141
|
message: string | null;
|
|
@@ -132,18 +148,6 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
|
|
|
132
148
|
join(target: string): Promise<SourceJoin>;
|
|
133
149
|
fetchFile(file: ExternalFile): Promise<Uint8Array | null>;
|
|
134
150
|
post(target: string, kind: ResolvedTarget['kind'], { as: _sender, files, ...body }: ProviderPost): Promise<ProviderDelivery>;
|
|
135
|
-
whoAmI(token: string): Promise<{
|
|
136
|
-
id: string;
|
|
137
|
-
handle: string;
|
|
138
|
-
name: string;
|
|
139
|
-
avatar: string | null;
|
|
140
|
-
isBot: boolean;
|
|
141
|
-
}>;
|
|
142
|
-
/** As the person: ready first, so the adapter learns its own identity with the bot's token, never theirs. */
|
|
143
|
-
private as;
|
|
144
|
-
postAs(token: string, target: string, kind: ResolvedTarget['kind'], body: ProviderPost): Promise<ProviderDelivery>;
|
|
145
|
-
editAs(token: string, thread: string, message: string, body: ProviderPost): Promise<void>;
|
|
146
|
-
deleteAs(token: string, thread: string, message: string): Promise<void>;
|
|
147
151
|
edit(thread: string, message: string, { as: _sender, files, ...body }: ProviderPost): Promise<void>;
|
|
148
152
|
delete(thread: string, message: string): Promise<void>;
|
|
149
153
|
react(thread: string, message: string, emoji: string, added: boolean): Promise<void>;
|
|
@@ -155,4 +159,4 @@ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent>
|
|
|
155
159
|
deleteFiles(ids: string[]): Promise<void>;
|
|
156
160
|
}
|
|
157
161
|
//#endregion
|
|
158
|
-
export {
|
|
162
|
+
export { ChatSyncProvider as n, ChatTransport as r, ChatInput as t };
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import "./external-D3p5T57R.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { n as userTokensOf } from "./users-yryHKFMo.mjs";
|
|
2
|
+
import { c as SyncProvider, s as ResolvedTarget } from "./events-lujPA3gQ.mjs";
|
|
4
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
4
|
import { Chat, stringifyMarkdown } from "chat";
|
|
5
|
+
//#region src/sync/chat.ts
|
|
6
|
+
/**
|
|
7
|
+
* One Chat SDK instance per sync entry. Platform modules call this, so
|
|
8
|
+
* the board (`@vercel/board`) never loads Chat SDK unless sync is configured.
|
|
9
|
+
*/
|
|
10
|
+
const createChat = (adapter, state) => new Chat({
|
|
11
|
+
userName: "board",
|
|
12
|
+
adapters: { [adapter.name]: adapter },
|
|
13
|
+
state,
|
|
14
|
+
concurrency: "concurrent",
|
|
15
|
+
logger: "warn"
|
|
16
|
+
});
|
|
17
|
+
/** A message as markdown, the board's text format: Chat SDK parses every platform's own. */
|
|
18
|
+
function markdownOf(message) {
|
|
19
|
+
try {
|
|
20
|
+
return stringifyMarkdown(message.formatted).trim() || message.text;
|
|
21
|
+
} catch {
|
|
22
|
+
return message.text;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
6
26
|
//#region src/sync/state.ts
|
|
7
27
|
/**
|
|
8
28
|
* Chat SDK state for sync. Correctness comes from the board log (deterministic
|
|
@@ -81,27 +101,6 @@ function ephemeralState() {
|
|
|
81
101
|
};
|
|
82
102
|
}
|
|
83
103
|
//#endregion
|
|
84
|
-
//#region src/sync/chat.ts
|
|
85
|
-
/**
|
|
86
|
-
* One Chat SDK instance per sync entry. Platform modules call this, so
|
|
87
|
-
* the board (`@vercel/board`) never loads Chat SDK unless sync is configured.
|
|
88
|
-
*/
|
|
89
|
-
const createChat = (adapter) => new Chat({
|
|
90
|
-
userName: "board",
|
|
91
|
-
adapters: { [adapter.name]: adapter },
|
|
92
|
-
state: ephemeralState(),
|
|
93
|
-
concurrency: "concurrent",
|
|
94
|
-
logger: "warn"
|
|
95
|
-
});
|
|
96
|
-
/** A message as markdown, the board's text format: Chat SDK parses every platform's own. */
|
|
97
|
-
function markdownOf(message) {
|
|
98
|
-
try {
|
|
99
|
-
return stringifyMarkdown(message.formatted).trim() || message.text;
|
|
100
|
-
} catch {
|
|
101
|
-
return message.text;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
//#endregion
|
|
105
104
|
//#region src/sync/types.ts
|
|
106
105
|
function externalAuthor(platform, author) {
|
|
107
106
|
const handle = author.userName || author.userId;
|
|
@@ -126,16 +125,32 @@ const externalFile = (file) => ({
|
|
|
126
125
|
var ChatSyncProvider = class extends SyncProvider {
|
|
127
126
|
transport;
|
|
128
127
|
chat;
|
|
128
|
+
state;
|
|
129
129
|
receiving = new AsyncLocalStorage();
|
|
130
130
|
initialized;
|
|
131
131
|
constructor(transport, sync) {
|
|
132
132
|
super({
|
|
133
133
|
...transport,
|
|
134
|
-
users: transport.users ?? userTokensOf(transport.adapter),
|
|
135
134
|
uploadsFiles: !!transport.uploadFiles
|
|
136
135
|
}, sync);
|
|
137
136
|
this.transport = transport;
|
|
138
|
-
this.
|
|
137
|
+
this.state = transport.state ?? ephemeralState();
|
|
138
|
+
this.chat = createChat(transport.adapter, this.claiming(this.state));
|
|
139
|
+
const chat = this.chat;
|
|
140
|
+
for (const name of [
|
|
141
|
+
"processMessage",
|
|
142
|
+
"processMessageUpdated",
|
|
143
|
+
"processMessageDeleted",
|
|
144
|
+
"processReaction"
|
|
145
|
+
]) {
|
|
146
|
+
const original = chat[name];
|
|
147
|
+
if (typeof original !== "function") throw new Error(`Chat SDK has no ${name}: board can't wait for what it processes`);
|
|
148
|
+
chat[name] = (...args) => {
|
|
149
|
+
const task = original.apply(this.chat, args);
|
|
150
|
+
if (task instanceof Promise) this.receiving.getStore()?.tasks.push(task.catch(() => {}));
|
|
151
|
+
return task;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
139
154
|
const onMessage = async (_thread, message) => this.emit({
|
|
140
155
|
kind: "message",
|
|
141
156
|
message
|
|
@@ -251,23 +266,55 @@ var ChatSyncProvider = class extends SyncProvider {
|
|
|
251
266
|
async emit(input) {
|
|
252
267
|
const context = this.receiving.getStore();
|
|
253
268
|
if (!context) throw new Error("Chat SDK emitted an event outside a verified request");
|
|
254
|
-
|
|
269
|
+
try {
|
|
270
|
+
await context.emit(await this.normalize(context.key, input));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
context.failures.push(error);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
255
275
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
276
|
+
/** The body of the webhook being handled, as the platform sent it: the same on a redelivery. */
|
|
277
|
+
delivery() {
|
|
278
|
+
return this.receiving.getStore()?.body ?? null;
|
|
279
|
+
}
|
|
280
|
+
async receive(key, request, emit) {
|
|
281
|
+
const claims = [];
|
|
282
|
+
const body = await request.clone().text();
|
|
283
|
+
try {
|
|
284
|
+
return await this.receiving.run({
|
|
285
|
+
key,
|
|
286
|
+
emit,
|
|
287
|
+
body,
|
|
288
|
+
claims,
|
|
289
|
+
failures: [],
|
|
290
|
+
tasks: []
|
|
291
|
+
}, () => this.receiveRequest(key, request, emit));
|
|
292
|
+
} catch (error) {
|
|
293
|
+
await Promise.allSettled(claims.map((claim) => this.state.delete(claim)));
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/** `state`, noting each first claim (`setIfNotExists`) made while a webhook is handled. */
|
|
298
|
+
claiming(state) {
|
|
299
|
+
return new Proxy(state, { get: (target, property) => {
|
|
300
|
+
if (property === "setIfNotExists") return async (key, value, ttlMs) => {
|
|
301
|
+
const claimed = await target.setIfNotExists(key, value, ttlMs);
|
|
302
|
+
if (claimed) this.receiving.getStore()?.claims.push(key);
|
|
303
|
+
return claimed;
|
|
304
|
+
};
|
|
305
|
+
const value = Reflect.get(target, property, target);
|
|
306
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
307
|
+
} });
|
|
261
308
|
}
|
|
262
309
|
async receiveRequest(key, request, emit) {
|
|
263
|
-
const body
|
|
264
|
-
const tasks = [];
|
|
310
|
+
const { body, tasks, failures } = this.receiving.getStore();
|
|
265
311
|
const response = await this.chat.webhooks[this.transport.adapter.name](request, { waitUntil: (task) => void tasks.push(task) });
|
|
266
312
|
const results = [];
|
|
267
313
|
while (tasks.length) results.push(...await Promise.allSettled(tasks.splice(0)));
|
|
314
|
+
if (failures.length) throw failures[0];
|
|
268
315
|
const failed = results.find((result) => result.status === "rejected");
|
|
269
316
|
if (failed) throw failed.reason;
|
|
270
|
-
if (response.ok &&
|
|
317
|
+
if (response.ok && (this.transport.intercept || this.transport.missed)) {
|
|
271
318
|
for (const input of this.transport.missed?.(request.headers, body) ?? []) await emit(await this.normalize(key, input));
|
|
272
319
|
for (const received of this.transport.intercept?.(key, request.headers, body) ?? []) await emit(received);
|
|
273
320
|
}
|
|
@@ -281,11 +328,29 @@ var ChatSyncProvider = class extends SyncProvider {
|
|
|
281
328
|
await this.initialized;
|
|
282
329
|
return this.transport.adapter;
|
|
283
330
|
}
|
|
284
|
-
resolveChannel(key) {
|
|
285
|
-
return this.transport.resolveChannel(key);
|
|
331
|
+
async resolveChannel(key) {
|
|
332
|
+
return this.transport.resolveChannel ? this.transport.resolveChannel(key) : key;
|
|
286
333
|
}
|
|
287
334
|
async resolveTarget(target) {
|
|
288
|
-
return ResolvedTarget.parse(await this.transport.resolveTarget.call(this, target));
|
|
335
|
+
return ResolvedTarget.parse(this.transport.resolveTarget ? await this.transport.resolveTarget.call(this, target) : this.targetOf(target));
|
|
336
|
+
}
|
|
337
|
+
/** A target as the adapter reads it: a thread in its channel, or a channel. */
|
|
338
|
+
targetOf(target) {
|
|
339
|
+
let channel;
|
|
340
|
+
try {
|
|
341
|
+
channel = this.transport.channelOf?.(target) ?? this.toNative(this.transport.adapter.channelIdFromThreadId(this.toChat(target)));
|
|
342
|
+
} catch {
|
|
343
|
+
return {
|
|
344
|
+
target,
|
|
345
|
+
channel: target,
|
|
346
|
+
kind: "channel"
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
target,
|
|
351
|
+
channel,
|
|
352
|
+
kind: channel === target ? "channel" : "thread"
|
|
353
|
+
};
|
|
289
354
|
}
|
|
290
355
|
async findByEmail(email) {
|
|
291
356
|
return this.transport.findByEmail?.(email) ?? null;
|
|
@@ -300,8 +365,8 @@ var ChatSyncProvider = class extends SyncProvider {
|
|
|
300
365
|
raw: root.raw ?? null
|
|
301
366
|
} : null;
|
|
302
367
|
}
|
|
303
|
-
async fetchLink(link) {
|
|
304
|
-
return this.transport.fetchLink ? this.transport.fetchLink(link) : super.fetchLink(link);
|
|
368
|
+
async fetchLink(link, linker) {
|
|
369
|
+
return this.transport.fetchLink ? this.transport.fetchLink(link, linker) : super.fetchLink(link, linker);
|
|
305
370
|
}
|
|
306
371
|
locate(url) {
|
|
307
372
|
return this.transport.locate ? this.transport.locate(url) : super.locate(url);
|
|
@@ -334,26 +399,6 @@ var ChatSyncProvider = class extends SyncProvider {
|
|
|
334
399
|
...files?.length ? { files: this.transport.fileIds?.(sent.raw) ?? [] } : {}
|
|
335
400
|
};
|
|
336
401
|
}
|
|
337
|
-
async whoAmI(token) {
|
|
338
|
-
if (!this.transport.whoAmI) return super.whoAmI(token);
|
|
339
|
-
await this.ready();
|
|
340
|
-
return this.transport.whoAmI(token);
|
|
341
|
-
}
|
|
342
|
-
/** As the person: ready first, so the adapter learns its own identity with the bot's token, never theirs. */
|
|
343
|
-
async as(token, fn) {
|
|
344
|
-
if (!this.transport.runAs) throw new Error(`${this.platform} can't post as a person`);
|
|
345
|
-
await this.ready();
|
|
346
|
-
return this.transport.runAs(token, fn);
|
|
347
|
-
}
|
|
348
|
-
async postAs(token, target, kind, body) {
|
|
349
|
-
return this.as(token, () => this.post(target, kind, body));
|
|
350
|
-
}
|
|
351
|
-
async editAs(token, thread, message, body) {
|
|
352
|
-
await this.as(token, () => this.edit(thread, message, body));
|
|
353
|
-
}
|
|
354
|
-
async deleteAs(token, thread, message) {
|
|
355
|
-
await this.as(token, () => this.delete(thread, message));
|
|
356
|
-
}
|
|
357
402
|
async edit(thread, message, { as: _sender, files, ...body }) {
|
|
358
403
|
await (await this.ready()).editMessage(this.toChat(thread), message, files ? {
|
|
359
404
|
...body,
|
|
@@ -395,4 +440,4 @@ var ChatSyncProvider = class extends SyncProvider {
|
|
|
395
440
|
}
|
|
396
441
|
};
|
|
397
442
|
//#endregion
|
|
398
|
-
export {
|
|
443
|
+
export { markdownOf as n, ChatSyncProvider as t };
|
package/dist/cli-commands.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as OptionName, c as globalOptions, d as options, f as usage, i as CliOption, l as groups, n as CliCommand, o as commands, r as CliExample, s as filters, t as CliArgument, u as option } from "./commands-
|
|
1
|
+
import { a as OptionName, c as globalOptions, d as options, f as usage, i as CliOption, l as groups, n as CliCommand, o as commands, r as CliExample, s as filters, t as CliArgument, u as option } from "./commands-DK4W3HCJ.mjs";
|
|
2
2
|
export { CliArgument, CliCommand, CliExample, CliOption, OptionName, commands, filters, globalOptions, groups, option, options, usage };
|
package/dist/cli-commands.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as option, i as groups, n as filters, o as options, r as globalOptions, s as usage, t as commands } from "./commands-
|
|
1
|
+
import { a as option, i as groups, n as filters, o as options, r as globalOptions, s as usage, t as commands } from "./commands-L1JSGz2Z.mjs";
|
|
2
2
|
export { commands, filters, globalOptions, groups, option, options, usage };
|
package/dist/cli-embedded.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./index-
|
|
1
|
+
import { f as Board } from "./backend-Bob-5CM7.mjs";
|
|
2
|
+
import "./index-Ho-ayyQL.mjs";
|
|
3
3
|
//#region src/cli/failure.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* What kind of failure it is, which fixes the exit code: 2 for `usage` and `missing_input`, 130
|
package/dist/cli-embedded.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as commandLine, c as failureLines, i as mainWith, l as formatFailure, n as commandWord, o as exitCode, r as main, s as failureDocument, t as Failed } from "./main-
|
|
1
|
+
import { a as commandLine, c as failureLines, i as mainWith, l as formatFailure, n as commandWord, o as exitCode, r as main, s as failureDocument, t as Failed } from "./main-Dz-8komG.mjs";
|
|
2
2
|
//#region src/cli/embedded.ts
|
|
3
3
|
/**
|
|
4
4
|
* Board's CLI as `appName` names it in help, usage, and errors (`vercel board`). With no board
|
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
3
3
|
import "./schemas-DBmUO6pr.mjs";
|
|
4
|
-
import { a as option } from "./commands-
|
|
5
|
-
import { l as projectDir } from "./node-
|
|
6
|
-
import { r as main, u as who } from "./main-
|
|
4
|
+
import { a as option } from "./commands-L1JSGz2Z.mjs";
|
|
5
|
+
import { l as projectDir } from "./node-BgeGcwX0.mjs";
|
|
6
|
+
import { r as main, u as who } from "./main-Dz-8komG.mjs";
|
|
7
7
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { appendFile, readFile } from "node:fs/promises";
|
|
9
9
|
import { join } from "node:path";
|
|
@@ -661,7 +661,7 @@ const local = {
|
|
|
661
661
|
if (args.length > 0) throw usage("serve takes no arguments: pass its port with --port, and its directory with --dir.");
|
|
662
662
|
const port = portFlag();
|
|
663
663
|
const root = projectDir();
|
|
664
|
-
const { localHandle, serveLocal } = await import("./serve-
|
|
664
|
+
const { localHandle, serveLocal } = await import("./serve-CTmow-YH.mjs");
|
|
665
665
|
const handle = flags.handle ?? process.env.BOARD_HANDLE;
|
|
666
666
|
const local = await serveLocal({
|
|
667
667
|
dir: flags.dir ?? join(root, ".board"),
|