@vercel/board 0.0.0-stage → 0.15.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 (126) hide show
  1. package/README.md +179 -2
  2. package/dist/a2-5EG8FkZ8.d.mts +3359 -0
  3. package/dist/a2-DYGYankn.mjs +3045 -0
  4. package/dist/access-CJU2-vaa.mjs +226 -0
  5. package/dist/access-CV96SYgr.d.mts +32 -0
  6. package/dist/access.d.mts +29 -0
  7. package/dist/access.mjs +4 -0
  8. package/dist/actors-rdN4dDTU.mjs +223 -0
  9. package/dist/attempts-BQUp9MKO.mjs +919 -0
  10. package/dist/auth-COzc09sn.mjs +276 -0
  11. package/dist/auth-wHFcAte3.d.mts +203 -0
  12. package/dist/backend-a2/blob-vercel.d.mts +8 -0
  13. package/dist/backend-a2/blob-vercel.mjs +81 -0
  14. package/dist/backend-a2/contract.d.mts +18 -0
  15. package/dist/backend-a2/contract.mjs +5 -0
  16. package/dist/backend-a2/scheduler-local.d.mts +11 -0
  17. package/dist/backend-a2/scheduler-local.mjs +2 -0
  18. package/dist/backend-a2/scheduler-qstash.d.mts +2 -0
  19. package/dist/backend-a2/scheduler-qstash.mjs +2 -0
  20. package/dist/backend-a2/scheduler-vercel.d.mts +2 -0
  21. package/dist/backend-a2/scheduler-vercel.mjs +2 -0
  22. package/dist/backend-a2/store-memory.d.mts +11 -0
  23. package/dist/backend-a2/store-memory.mjs +19 -0
  24. package/dist/backend-a2/store-postgres.d.mts +38 -0
  25. package/dist/backend-a2/store-postgres.mjs +110 -0
  26. package/dist/backend-a2/store-redis-http.d.mts +17 -0
  27. package/dist/backend-a2/store-redis-http.mjs +32 -0
  28. package/dist/backend-a2/store-redis.d.mts +23 -0
  29. package/dist/backend-a2/store-redis.mjs +39 -0
  30. package/dist/backend-a2/store-sqlite.d.mts +12 -0
  31. package/dist/backend-a2/store-sqlite.mjs +31 -0
  32. package/dist/backend-a2.d.mts +4 -0
  33. package/dist/backend-a2.mjs +2 -0
  34. package/dist/backend-http-8ex5g7Wv.d.mts +630 -0
  35. package/dist/backend-http-BsDSgmeo.mjs +613 -0
  36. package/dist/backend-http-Cv6xbEy9.mjs +469 -0
  37. package/dist/backend-http.d.mts +10 -0
  38. package/dist/backend-http.mjs +2 -0
  39. package/dist/backend-http.node.d.mts +10 -0
  40. package/dist/backend-http.node.mjs +2 -0
  41. package/dist/blob-C0J3wbGv.d.mts +39 -0
  42. package/dist/bots-CGkO76qH.mjs +67 -0
  43. package/dist/chat-provider-XCnQlxq6.d.mts +140 -0
  44. package/dist/chat-provider-jFSeyNmB.mjs +389 -0
  45. package/dist/cli.d.mts +1 -0
  46. package/dist/cli.mjs +2252 -0
  47. package/dist/client-DGDPaN7U.mjs +380 -0
  48. package/dist/conformance-DRR23YNy.mjs +512 -0
  49. package/dist/conformance.d.mts +30 -0
  50. package/dist/conformance.mjs +2 -0
  51. package/dist/contract-CuJmlrhw.mjs +566 -0
  52. package/dist/emoji-CTLpvH-A.mjs +445 -0
  53. package/dist/engine-3EvQ8idk.mjs +1449 -0
  54. package/dist/errors-DX3hpwWt.mjs +64 -0
  55. package/dist/exports-BpgyEdh6.d.mts +4 -0
  56. package/dist/exports-CyY3_FjZ.mjs +1285 -0
  57. package/dist/files-SniSmeEr.mjs +47 -0
  58. package/dist/index.d.mts +13 -0
  59. package/dist/index.default-BZ3lItjH.mjs +12 -0
  60. package/dist/index.mjs +19 -0
  61. package/dist/index.node.d.mts +14 -0
  62. package/dist/index.node.mjs +5 -0
  63. package/dist/inspect/app.js +416 -0
  64. package/dist/inspect/engine.js +5 -0
  65. package/dist/local-Cr_dSjvs.mjs +44 -0
  66. package/dist/own-OwgPWdmh.mjs +15 -0
  67. package/dist/project-CFiArraX.mjs +124 -0
  68. package/dist/provider-BGjz-mDa.d.mts +2473 -0
  69. package/dist/provider-CuewcDNZ.mjs +205 -0
  70. package/dist/redis-tables-B35vv4CZ.mjs +842 -0
  71. package/dist/reducers-B_vUGQRp.d.mts +1172 -0
  72. package/dist/reducers-mUPFuFJV.mjs +293 -0
  73. package/dist/schemas-Be08nyfW.mjs +74 -0
  74. package/dist/schemas-CWxs3ECJ.d.mts +50 -0
  75. package/dist/serve-Ccbb2LSb.mjs +170 -0
  76. package/dist/server-Cxey38B-.mjs +17 -0
  77. package/dist/serving-BNbzc96L.mjs +2 -0
  78. package/dist/sql-DbiEnqM0.mjs +571 -0
  79. package/dist/sqlite-db-Cbj03vmp.mjs +70 -0
  80. package/dist/sync-github.d.mts +473 -0
  81. package/dist/sync-github.mjs +603 -0
  82. package/dist/sync-kapso.d.mts +175 -0
  83. package/dist/sync-kapso.mjs +86 -0
  84. package/dist/sync-slack.d.mts +364 -0
  85. package/dist/sync-slack.mjs +728 -0
  86. package/dist/sync.chat.d.mts +2 -0
  87. package/dist/sync.chat.mjs +2 -0
  88. package/dist/sync.d.mts +3 -0
  89. package/dist/sync.mjs +4 -0
  90. package/dist/targets-ojFEXEyN.mjs +94 -0
  91. package/dist/tokens-DKLn5P9B.mjs +256 -0
  92. package/dist/types-BHCjTZdB.d.mts +104 -0
  93. package/dist/users-Dls7_ANV.mjs +42 -0
  94. package/dist/watch-spec-W0c7ypHJ.mjs +787 -0
  95. package/dist/watchers-hash-D9hbHLKB.mjs +225 -0
  96. package/docs/01-board/01-mounting.md +506 -0
  97. package/docs/01-board/02-database.md +258 -0
  98. package/docs/01-board/03-identity.md +262 -0
  99. package/docs/01-board/04-messages.md +192 -0
  100. package/docs/01-board/05-reading.md +177 -0
  101. package/docs/01-board/06-watchers.md +249 -0
  102. package/docs/01-board/07-schedules.md +133 -0
  103. package/docs/01-board/08-middleware.md +139 -0
  104. package/docs/01-board/09-access.md +499 -0
  105. package/docs/01-quickstart.md +196 -0
  106. package/docs/02-sync/01-overview.md +290 -0
  107. package/docs/02-sync/02-callbacks.md +265 -0
  108. package/docs/02-sync/03-slack.md +248 -0
  109. package/docs/02-sync/04-github.md +201 -0
  110. package/docs/02-sync/05-whatsapp.md +148 -0
  111. package/docs/02-sync/06-providers.md +228 -0
  112. package/docs/03-guides/01-coding-agents.md +226 -0
  113. package/docs/03-guides/02-inspector.md +104 -0
  114. package/docs/03-guides/03-ci.md +251 -0
  115. package/docs/03-guides/04-triage.md +233 -0
  116. package/docs/03-guides/05-agent-factory.md +142 -0
  117. package/docs/04-reference/01-cli.md +606 -0
  118. package/docs/04-reference/02-board.md +579 -0
  119. package/docs/04-reference/03-http.md +489 -0
  120. package/docs/04-reference/04-log.md +250 -0
  121. package/docs/04-reference/05-conformance.md +97 -0
  122. package/docs/index.md +186 -0
  123. package/package.json +165 -3
  124. package/sql/postgres/board.sql +145 -0
  125. package/sql/postgres/notifications.sql +24 -0
  126. package/sql/postgres/schema.sql +451 -0
@@ -0,0 +1,140 @@
1
+ import { _ as ExternalFile, h as ExternalAuthor, t as Actor, v as ExternalLink } from "./reducers-B_vUGQRp.mjs";
2
+ import { Pt as SourceRead, a as ProviderFile, b as ProviderEvent, c as Receive, d as ResolvedTarget, h as SyncProvider, i as ProviderDelivery, l as ReceivedEvent, n as FetchedLink, o as ProviderOptions, r as FileDelivery, s as ProviderPost, w as SyncRoot } from "./provider-BGjz-mDa.mjs";
3
+ import { Adapter, Author, FileUpload, Message, ReactionEvent } from "chat";
4
+ //#region src/sync/types.d.ts
5
+ declare function externalAuthor(platform: string, author: Author): ExternalAuthor;
6
+ //#endregion
7
+ //#region src/sync/chat-provider.d.ts
8
+ type ChatInput = {
9
+ kind: 'message' | 'edited';
10
+ message: Message;
11
+ } | {
12
+ kind: 'deleted';
13
+ thread: string;
14
+ message: string;
15
+ raw: unknown;
16
+ } | {
17
+ kind: 'reaction';
18
+ reaction: ReactionEvent;
19
+ };
20
+ type ChatTransport = Omit<ProviderOptions, 'uploadsFiles'> & {
21
+ adapter: Adapter;
22
+ findByEmail?(email: string): Promise<ExternalAuthor | null>;
23
+ mention?(actor: Actor): string | null;
24
+ author?(author: Author, raw: unknown): ExternalAuthor;
25
+ resolveChannel(key: string): Promise<string>;
26
+ resolveTarget(target: string): Promise<ResolvedTarget>;
27
+ groupThread?(thread: string): string;
28
+ channelOf?(thread: string): string;
29
+ isRoot?(thread: string, message: string): boolean;
30
+ fromChatId?(id: string): string;
31
+ toChatId?(id: string): string;
32
+ context?(message: Message): string | null;
33
+ markdown?(message: Message): string;
34
+ /** Platform messages a message links to, located under sync key `key`. */
35
+ links?(key: string, message: Message): ExternalLink[];
36
+ fetchLink?(link: ExternalLink): Promise<FetchedLink>;
37
+ /** A channel or thread, read on demand without syncing it. */
38
+ read?(target: string, options: {
39
+ limit: number;
40
+ }): Promise<SourceRead>;
41
+ avatar?(message: Message): Promise<string | null>;
42
+ fetchRoot?(thread: string): Promise<Omit<SyncRoot, 'raw'> & {
43
+ raw?: unknown;
44
+ }>;
45
+ intercept?(key: string, headers: Headers, body: string): ReceivedEvent[];
46
+ /** Edits and deletes in a webhook that the adapter doesn't emit itself, as the adapter would have. */
47
+ missed?(headers: Headers, body: string): ChatInput[];
48
+ uploadFiles?(thread: string, files: FileUpload[]): Promise<FileDelivery>;
49
+ /** The name a file is uploaded under, when the platform reads more into a name than the board does. */
50
+ uploadName?(file: ProviderFile): string;
51
+ fileIds?(raw: unknown): string[];
52
+ refusesFiles?(error: unknown): boolean;
53
+ deleteFiles?(ids: string[]): Promise<void>;
54
+ /** What the adapter needs to fetch a file again, if not everything the event carried. */
55
+ fileToFetch?(file: ExternalFile): ExternalFile;
56
+ startTyping?(thread: string, latest: string | null, status?: string): Promise<void>;
57
+ /** Runs adapter calls with a person's token instead of the bot's: how a message posts as them. */
58
+ runAs?<T>(token: string, fn: () => Promise<T>): Promise<T>;
59
+ /** The platform account a person's token belongs to. */
60
+ whoAmI?(token: string): Promise<ExternalAuthor>;
61
+ };
62
+ declare abstract class ChatSyncProvider<E extends ProviderEvent = ProviderEvent> extends SyncProvider<E> {
63
+ protected readonly transport: ChatTransport;
64
+ private readonly chat;
65
+ private readonly receiving;
66
+ private initialized;
67
+ constructor(transport: ChatTransport);
68
+ protected toNative(id: string): string;
69
+ protected toChat(id: string): string;
70
+ protected locate(key: string, chatThread: string): {
71
+ channel: string;
72
+ thread: string;
73
+ subthread: string | null;
74
+ };
75
+ inboxChannel(key: string, thread: string): string;
76
+ eventId(key: string, input: ChatInput): string;
77
+ protected author(author: Author, raw: unknown): {
78
+ id: string;
79
+ handle: string;
80
+ name: string;
81
+ avatar: string | null;
82
+ isBot: boolean;
83
+ };
84
+ protected authorOf(message: Message): Promise<{
85
+ id: string;
86
+ handle: string;
87
+ name: string;
88
+ avatar: string | null;
89
+ isBot: boolean;
90
+ }>;
91
+ protected normalize(key: string, input: ChatInput): Promise<ReceivedEvent>;
92
+ private emit;
93
+ receive(key: string, request: Request, emit: Receive): Promise<Response>;
94
+ private receiveRequest;
95
+ protected ready(): Promise<Adapter<unknown, unknown>>;
96
+ resolveChannel(key: string): Promise<string>;
97
+ resolveTarget(target: string): Promise<{
98
+ target: string;
99
+ channel: string;
100
+ kind: "channel" | "thread";
101
+ }>;
102
+ findByEmail(email: string): Promise<{
103
+ id: string;
104
+ handle: string;
105
+ name: string;
106
+ avatar: string | null;
107
+ isBot: boolean;
108
+ } | null>;
109
+ mention(actor: Actor): string | null;
110
+ fetchRoot(thread: string): Promise<SyncRoot | null>;
111
+ fetchLink(link: ExternalLink): Promise<FetchedLink>;
112
+ read(target: string, options: {
113
+ limit: number;
114
+ }): Promise<SourceRead>;
115
+ fetchFile(file: ExternalFile): Promise<Uint8Array | null>;
116
+ post(target: string, kind: ResolvedTarget['kind'], { as: _sender, files, ...body }: ProviderPost): Promise<ProviderDelivery>;
117
+ whoAmI(token: string): Promise<{
118
+ id: string;
119
+ handle: string;
120
+ name: string;
121
+ avatar: string | null;
122
+ isBot: boolean;
123
+ }>;
124
+ /** As the person: ready first, so the adapter learns its own identity with the bot's token, never theirs. */
125
+ private as;
126
+ postAs(token: string, target: string, kind: ResolvedTarget['kind'], body: ProviderPost): Promise<ProviderDelivery>;
127
+ editAs(token: string, thread: string, message: string, body: ProviderPost): Promise<void>;
128
+ deleteAs(token: string, thread: string, message: string): Promise<void>;
129
+ edit(thread: string, message: string, { as: _sender, files, ...body }: ProviderPost): Promise<void>;
130
+ delete(thread: string, message: string): Promise<void>;
131
+ react(thread: string, message: string, emoji: string, added: boolean): Promise<void>;
132
+ startTyping(thread: string, latest: string | null, status?: string): Promise<void>;
133
+ uploadFiles(thread: string, files: ProviderFile[]): Promise<FileDelivery>;
134
+ /** Files as the Chat SDK takes them: Buffers, named as the platform wants. */
135
+ private named;
136
+ refusesFiles(error: unknown): boolean;
137
+ deleteFiles(ids: string[]): Promise<void>;
138
+ }
139
+ //#endregion
140
+ export { externalAuthor as i, ChatSyncProvider as n, ChatTransport as r, ChatInput as t };
@@ -0,0 +1,389 @@
1
+ import "./contract-CuJmlrhw.mjs";
2
+ import { n as ResolvedTarget, r as SyncProvider } from "./provider-CuewcDNZ.mjs";
3
+ import { n as userTokensOf } from "./users-Dls7_ANV.mjs";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { Chat, stringifyMarkdown } from "chat";
6
+ //#region src/sync/state.ts
7
+ /**
8
+ * Chat SDK state for sync. Correctness comes from the board log (deterministic
9
+ * inbox IDs, the `sync` reducer), and Chat SDK runs with `concurrency:
10
+ * 'concurrent'` and no subscriptions, so this only holds best-effort caches.
11
+ */
12
+ function ephemeralState() {
13
+ const values = /* @__PURE__ */ new Map();
14
+ const lists = /* @__PURE__ */ new Map();
15
+ const queues = /* @__PURE__ */ new Map();
16
+ const locks = /* @__PURE__ */ new Map();
17
+ const subscriptions = /* @__PURE__ */ new Set();
18
+ const live = (key) => {
19
+ const entry = values.get(key);
20
+ if (entry && entry.expiresAt <= Date.now()) values.delete(key);
21
+ return values.get(key);
22
+ };
23
+ const expiry = (ttlMs) => ttlMs ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;
24
+ return {
25
+ connect: async () => {},
26
+ disconnect: async () => {},
27
+ subscribe: async (threadId) => void subscriptions.add(threadId),
28
+ unsubscribe: async (threadId) => void subscriptions.delete(threadId),
29
+ isSubscribed: async (threadId) => subscriptions.has(threadId),
30
+ acquireLock: async (threadId, ttlMs) => {
31
+ const held = locks.get(threadId);
32
+ if (held && held.expiresAt > Date.now()) return null;
33
+ const lock = {
34
+ threadId,
35
+ token: crypto.randomUUID(),
36
+ expiresAt: Date.now() + ttlMs
37
+ };
38
+ locks.set(threadId, lock);
39
+ return lock;
40
+ },
41
+ extendLock: async (lock, ttlMs) => {
42
+ const held = locks.get(lock.threadId);
43
+ if (held?.token !== lock.token) return false;
44
+ held.expiresAt = Date.now() + ttlMs;
45
+ return true;
46
+ },
47
+ releaseLock: async (lock) => {
48
+ if (locks.get(lock.threadId)?.token === lock.token) locks.delete(lock.threadId);
49
+ },
50
+ forceReleaseLock: async (threadId) => void locks.delete(threadId),
51
+ get: async (key) => live(key)?.value ?? null,
52
+ set: async (key, value, ttlMs) => void values.set(key, {
53
+ value,
54
+ expiresAt: expiry(ttlMs)
55
+ }),
56
+ setIfNotExists: async (key, value, ttlMs) => {
57
+ if (live(key)) return false;
58
+ values.set(key, {
59
+ value,
60
+ expiresAt: expiry(ttlMs)
61
+ });
62
+ return true;
63
+ },
64
+ delete: async (key) => void values.delete(key),
65
+ appendToList: async (key, value, options) => {
66
+ const list = lists.get(key) ?? [];
67
+ list.push(value);
68
+ if (options?.maxLength && list.length > options.maxLength) list.splice(0, list.length - options.maxLength);
69
+ lists.set(key, list);
70
+ },
71
+ getList: async (key) => lists.get(key) ?? [],
72
+ enqueue: async (threadId, entry, maxSize) => {
73
+ const queue = queues.get(threadId) ?? [];
74
+ queue.push(entry);
75
+ if (queue.length > maxSize) queue.splice(0, queue.length - maxSize);
76
+ queues.set(threadId, queue);
77
+ return queue.length;
78
+ },
79
+ dequeue: async (threadId) => queues.get(threadId)?.shift() ?? null,
80
+ queueDepth: async (threadId) => queues.get(threadId)?.length ?? 0
81
+ };
82
+ }
83
+ //#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
+ //#region src/sync/types.ts
106
+ function externalAuthor(platform, author) {
107
+ const handle = author.userName || author.userId;
108
+ return {
109
+ id: `${platform}:user:${author.userId}`,
110
+ handle,
111
+ name: author.fullName || handle,
112
+ avatar: null,
113
+ isBot: author.isBot === true
114
+ };
115
+ }
116
+ //#endregion
117
+ //#region src/sync/chat-provider.ts
118
+ const externalFile = (file) => ({
119
+ type: file.type,
120
+ ...file.name ? { name: file.name.slice(0, 1e3) } : {},
121
+ ...file.mimeType ? { mimeType: file.mimeType.slice(0, 255) } : {},
122
+ ...typeof file.size === "number" && Number.isInteger(file.size) && file.size >= 0 ? { size: file.size } : {},
123
+ ...file.url && file.url.length <= 4e3 ? { url: file.url } : {},
124
+ ...file.fetchMetadata ? { fetchMetadata: file.fetchMetadata } : {}
125
+ });
126
+ var ChatSyncProvider = class extends SyncProvider {
127
+ transport;
128
+ chat;
129
+ receiving = new AsyncLocalStorage();
130
+ initialized;
131
+ constructor(transport) {
132
+ super({
133
+ ...transport,
134
+ users: transport.users ?? userTokensOf(transport.adapter),
135
+ uploadsFiles: !!transport.uploadFiles
136
+ });
137
+ this.transport = transport;
138
+ this.chat = createChat(transport.adapter);
139
+ const onMessage = async (_thread, message) => this.emit({
140
+ kind: "message",
141
+ message
142
+ });
143
+ this.chat.onNewMessage(/[\s\S]*/, onMessage);
144
+ this.chat.onNewMention(onMessage);
145
+ this.chat.onMessageUpdated(async (_thread, message) => this.emit({
146
+ kind: "edited",
147
+ message
148
+ }));
149
+ this.chat.onMessageDeleted(async (event) => this.emit({
150
+ kind: "deleted",
151
+ thread: event.threadId,
152
+ message: event.messageId,
153
+ raw: event.raw
154
+ }));
155
+ this.chat.onReaction(async (reaction) => this.emit({
156
+ kind: "reaction",
157
+ reaction
158
+ }));
159
+ }
160
+ toNative(id) {
161
+ const native = id.slice(id.indexOf(":") + 1);
162
+ return this.transport.fromChatId?.(native) ?? native;
163
+ }
164
+ toChat(id) {
165
+ return `${this.transport.adapter.name}:${this.transport.toChatId?.(id) ?? id}`;
166
+ }
167
+ locate(key, chatThread) {
168
+ const native = this.toNative(chatThread);
169
+ const precise = `${key}:${native}`;
170
+ const thread = `${key}:${this.transport.groupThread?.(native) ?? native}`;
171
+ return {
172
+ channel: `${key}:${this.transport.channelOf?.(native) ?? this.toNative(this.transport.adapter.channelIdFromThreadId(chatThread))}`,
173
+ thread,
174
+ subthread: precise === thread ? null : precise
175
+ };
176
+ }
177
+ inboxChannel(key, thread) {
178
+ const native = thread.slice(key.length + 1);
179
+ return `${key}:${this.transport.channelOf?.(native) ?? this.toNative(this.transport.adapter.channelIdFromThreadId(this.toChat(native)))}`;
180
+ }
181
+ eventId(key, input) {
182
+ if (input.kind === "reaction") throw new Error(`${this.platform} must supply a stable reaction occurrence ID`);
183
+ if (input.kind === "deleted") return `deleted:${key}:${this.toNative(input.thread)}|${input.message}`;
184
+ const message = input.message;
185
+ const location = `${key}:${this.toNative(message.threadId)}|${message.id}`;
186
+ return input.kind === "message" ? `message:${location}` : `edited:${location}|${message.text}`;
187
+ }
188
+ author(author, raw) {
189
+ return this.transport.author?.(author, raw) ?? externalAuthor(this.platform, author);
190
+ }
191
+ async authorOf(message) {
192
+ const author = this.author(message.author, message.raw);
193
+ const avatar = await this.transport.avatar?.(message).catch(() => null);
194
+ return avatar ? {
195
+ ...author,
196
+ avatar
197
+ } : author;
198
+ }
199
+ async normalize(key, input) {
200
+ const dedupe = this.eventId(key, input);
201
+ if (input.kind === "deleted") return {
202
+ dedupe,
203
+ raw: input.raw,
204
+ event: {
205
+ kind: "deleted",
206
+ ...this.locate(key, input.thread),
207
+ message: input.message
208
+ }
209
+ };
210
+ if (input.kind === "reaction") {
211
+ const event = input.reaction;
212
+ const emoji = (event.emoji?.name ?? event.rawEmoji).toLowerCase().replace(/[^a-z0-9_+-]/g, "_").slice(0, 64) || "unknown";
213
+ return {
214
+ dedupe,
215
+ raw: event.raw,
216
+ event: {
217
+ kind: "reaction",
218
+ ...this.locate(key, event.threadId),
219
+ message: event.messageId,
220
+ emoji,
221
+ added: event.added,
222
+ author: this.author(event.user, event.raw)
223
+ }
224
+ };
225
+ }
226
+ const message = input.message;
227
+ const found = this.transport.links?.(key, message) ?? [];
228
+ const links = found.length ? { links: found.slice(0, 10) } : {};
229
+ const common = {
230
+ ...this.locate(key, message.threadId),
231
+ message: message.id,
232
+ text: this.transport.markdown?.(message) ?? markdownOf(message),
233
+ author: await this.authorOf(message),
234
+ ...links
235
+ };
236
+ return {
237
+ dedupe,
238
+ raw: message.raw,
239
+ event: input.kind === "edited" ? {
240
+ kind: "edited",
241
+ ...common
242
+ } : {
243
+ kind: "message",
244
+ ...common,
245
+ isRoot: this.transport.isRoot?.(this.toNative(message.threadId), message.id) ?? false,
246
+ context: this.transport.context?.(message) ?? null,
247
+ ...message.attachments?.length ? { files: message.attachments.slice(0, 20).map(externalFile) } : {}
248
+ }
249
+ };
250
+ }
251
+ async emit(input) {
252
+ const context = this.receiving.getStore();
253
+ if (!context) throw new Error("Chat SDK emitted an event outside a verified request");
254
+ await context.emit(await this.normalize(context.key, input));
255
+ }
256
+ receive(key, request, emit) {
257
+ return this.receiving.run({
258
+ key,
259
+ emit
260
+ }, () => this.receiveRequest(key, request, emit));
261
+ }
262
+ async receiveRequest(key, request, emit) {
263
+ const body = this.transport.intercept || this.transport.missed ? await request.clone().text() : null;
264
+ const tasks = [];
265
+ const response = await this.chat.webhooks[this.transport.adapter.name](request, { waitUntil: (task) => void tasks.push(task) });
266
+ const results = [];
267
+ while (tasks.length) results.push(...await Promise.allSettled(tasks.splice(0)));
268
+ const failed = results.find((result) => result.status === "rejected");
269
+ if (failed) throw failed.reason;
270
+ if (response.ok && body !== null) {
271
+ for (const input of this.transport.missed?.(request.headers, body) ?? []) await emit(await this.normalize(key, input));
272
+ for (const received of this.transport.intercept?.(key, request.headers, body) ?? []) await emit(received);
273
+ }
274
+ return response;
275
+ }
276
+ async ready() {
277
+ this.initialized ??= this.chat.initialize().catch((error) => {
278
+ this.initialized = void 0;
279
+ throw error;
280
+ });
281
+ await this.initialized;
282
+ return this.transport.adapter;
283
+ }
284
+ resolveChannel(key) {
285
+ return this.transport.resolveChannel(key);
286
+ }
287
+ async resolveTarget(target) {
288
+ return ResolvedTarget.parse(await this.transport.resolveTarget.call(this, target));
289
+ }
290
+ async findByEmail(email) {
291
+ return this.transport.findByEmail?.(email) ?? null;
292
+ }
293
+ mention(actor) {
294
+ return this.transport.mention?.(actor) ?? null;
295
+ }
296
+ async fetchRoot(thread) {
297
+ const root = await this.transport.fetchRoot?.(thread);
298
+ return root ? {
299
+ ...root,
300
+ raw: root.raw ?? null
301
+ } : null;
302
+ }
303
+ async fetchLink(link) {
304
+ return this.transport.fetchLink ? this.transport.fetchLink(link) : super.fetchLink(link);
305
+ }
306
+ async read(target, options) {
307
+ return this.transport.read ? this.transport.read(target, options) : super.read(target, options);
308
+ }
309
+ async fetchFile(file) {
310
+ const fetch = (await this.ready()).rehydrateAttachment?.({ ...this.transport.fileToFetch?.(file) ?? file }).fetchData;
311
+ if (!fetch) return null;
312
+ const data = await fetch();
313
+ return data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
314
+ }
315
+ async post(target, kind, { as: _sender, files, ...body }) {
316
+ if (kind === "channel" && !this.canStartThreads) throw new Error(`${this.platform} cannot start threads`);
317
+ await this.ready();
318
+ const sent = await (kind === "channel" ? this.chat.channel(this.toChat(target)) : this.chat.thread(this.toChat(target))).post(files?.length ? {
319
+ ...body,
320
+ files: this.named(files)
321
+ } : body);
322
+ return {
323
+ thread: this.toNative(sent.threadId),
324
+ message: sent.id,
325
+ ...files?.length ? { files: this.transport.fileIds?.(sent.raw) ?? [] } : {}
326
+ };
327
+ }
328
+ async whoAmI(token) {
329
+ if (!this.transport.whoAmI) return super.whoAmI(token);
330
+ await this.ready();
331
+ return this.transport.whoAmI(token);
332
+ }
333
+ /** As the person: ready first, so the adapter learns its own identity with the bot's token, never theirs. */
334
+ async as(token, fn) {
335
+ if (!this.transport.runAs) throw new Error(`${this.platform} can't post as a person`);
336
+ await this.ready();
337
+ return this.transport.runAs(token, fn);
338
+ }
339
+ async postAs(token, target, kind, body) {
340
+ return this.as(token, () => this.post(target, kind, body));
341
+ }
342
+ async editAs(token, thread, message, body) {
343
+ await this.as(token, () => this.edit(thread, message, body));
344
+ }
345
+ async deleteAs(token, thread, message) {
346
+ await this.as(token, () => this.delete(thread, message));
347
+ }
348
+ async edit(thread, message, { as: _sender, files, ...body }) {
349
+ await (await this.ready()).editMessage(this.toChat(thread), message, files ? {
350
+ ...body,
351
+ files: this.named(files)
352
+ } : body);
353
+ }
354
+ async delete(thread, message) {
355
+ await (await this.ready()).deleteMessage(this.toChat(thread), message);
356
+ }
357
+ async react(thread, message, emoji, added) {
358
+ const adapter = await this.ready();
359
+ if (added) await adapter.addReaction(this.toChat(thread), message, emoji);
360
+ else await adapter.removeReaction(this.toChat(thread), message, emoji);
361
+ }
362
+ async startTyping(thread, latest, status) {
363
+ const adapter = await this.ready();
364
+ if (this.transport.startTyping) await this.transport.startTyping(this.toChat(thread), latest, status);
365
+ else await adapter.startTyping(this.toChat(thread), status);
366
+ }
367
+ async uploadFiles(thread, files) {
368
+ await this.ready();
369
+ if (!this.transport.uploadFiles) return super.uploadFiles(thread, files);
370
+ return this.transport.uploadFiles(this.toChat(thread), this.named(files));
371
+ }
372
+ /** Files as the Chat SDK takes them: Buffers, named as the platform wants. */
373
+ named(files) {
374
+ const name = this.transport.uploadName;
375
+ return files.map((file) => ({
376
+ ...file,
377
+ data: Buffer.isBuffer(file.data) ? file.data : Buffer.from(file.data.buffer, file.data.byteOffset, file.data.byteLength),
378
+ ...name ? { filename: name(file) } : {}
379
+ }));
380
+ }
381
+ refusesFiles(error) {
382
+ return this.transport.refusesFiles?.(error) ?? false;
383
+ }
384
+ async deleteFiles(ids) {
385
+ await this.transport.deleteFiles?.(ids);
386
+ }
387
+ };
388
+ //#endregion
389
+ export { externalAuthor as n, markdownOf as r, ChatSyncProvider as t };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}