@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,5 +1,5 @@
|
|
|
1
|
-
import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, p as Link, t as Actor, u as ExternalEvent } from "./folds-
|
|
2
|
-
import {
|
|
1
|
+
import { d as ExternalFile, f as ExternalLink, l as ExternalAuthor, p as Link, t as Actor, u as ExternalEvent } from "./folds-DLUzfibJ.mjs";
|
|
2
|
+
import { U as MessageView, _ as BoardEventView, dt as SourceJoin, mt as SourceRead, t as BoardClient } from "./client-COfYrgfV.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/sync/events.d.ts
|
|
5
5
|
declare const EventBase: z.ZodObject<{
|
|
@@ -401,33 +401,6 @@ declare function render<E extends ProviderEvent = ProviderEvent>(renderer: Rende
|
|
|
401
401
|
*/
|
|
402
402
|
declare function channels<E extends ProviderEvent = ProviderEvent>(map: Record<string, SyncUnit<E>>): SyncFn<E>;
|
|
403
403
|
//#endregion
|
|
404
|
-
//#region src/sync/users.d.ts
|
|
405
|
-
/**
|
|
406
|
-
* People's own platform tokens, so a board message they wrote reaches Slack or
|
|
407
|
-
* GitHub as them, and signing in proves an account is theirs. The board never
|
|
408
|
-
* stores a token: it asks for one at each delivery, and a person without one
|
|
409
|
-
* (never signed in, revoked, expired) is delivered as the bot, as before.
|
|
410
|
-
*/
|
|
411
|
-
type UserTokens = {
|
|
412
|
-
/**
|
|
413
|
-
* A sign-in URL that binds whoever completes it to `principal`. Show it only to them. With
|
|
414
|
-
* `callback`, the platform sends them there once they've approved.
|
|
415
|
-
*/
|
|
416
|
-
authorize(principal: string, options?: {
|
|
417
|
-
callback?: string;
|
|
418
|
-
}): Promise<string>;
|
|
419
|
-
/** `principal`'s token on the platform, or null when they haven't signed in. */
|
|
420
|
-
token(principal: string): Promise<string | null>;
|
|
421
|
-
};
|
|
422
|
-
/**
|
|
423
|
-
* Gives an adapter people's tokens, for your own OAuth: `slack({ adapter:
|
|
424
|
-
* withUserTokens(createSlackAdapter(...), { authorize, token }) }, sync)`. A
|
|
425
|
-
* Connect connector name (`slack('slack/acme', sync)`) does this for you.
|
|
426
|
-
*/
|
|
427
|
-
declare function withUserTokens<A extends object>(adapter: A, tokens: UserTokens): A;
|
|
428
|
-
/** Vercel Connect's user tokens for `connector`: the subject is the board's principal, so Connect binds the account to it. */
|
|
429
|
-
declare function connectUserTokens(connector: string): UserTokens;
|
|
430
|
-
//#endregion
|
|
431
404
|
//#region src/sync/provider.d.ts
|
|
432
405
|
declare const ResolvedTarget: z.ZodObject<{
|
|
433
406
|
target: z.ZodString;
|
|
@@ -449,7 +422,7 @@ type RenderInput = {
|
|
|
449
422
|
/** The message as clients see it: its IDs are refs. */
|
|
450
423
|
message: Readonly<MessageView>;
|
|
451
424
|
author: Readonly<Actor> | null;
|
|
452
|
-
/** How this platform mentions an actor
|
|
425
|
+
/** How this platform mentions an actor when it's one of its own (`<@U123>`); null for anyone else. */
|
|
453
426
|
mentionOf: (actor: string) => string | null;
|
|
454
427
|
label: string;
|
|
455
428
|
target: string;
|
|
@@ -498,8 +471,6 @@ type ProviderOptions = {
|
|
|
498
471
|
uploadsFiles?: boolean;
|
|
499
472
|
/** Whether a new thread's root can carry its files in the same message as its text. */
|
|
500
473
|
startsThreadsWithFiles?: boolean;
|
|
501
|
-
/** People's own tokens, to post as them and to link accounts by signing in. */
|
|
502
|
-
users?: UserTokens | null;
|
|
503
474
|
};
|
|
504
475
|
declare const ReceivedEvent: z.ZodObject<{
|
|
505
476
|
event: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -629,28 +600,27 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
|
|
|
629
600
|
readonly uploadsFiles: boolean;
|
|
630
601
|
readonly startsThreadsWithFiles: boolean;
|
|
631
602
|
private readonly fn;
|
|
632
|
-
/** People's tokens, when the adapter has them: messages they write post as them. */
|
|
633
|
-
readonly users: UserTokens | null;
|
|
634
603
|
protected readonly options: ProviderOptions;
|
|
635
604
|
constructor(options: ProviderOptions, sync: SyncFn<E>);
|
|
636
605
|
abstract receive(key: string, request: Request, emit: Receive): Promise<Response>;
|
|
637
606
|
abstract resolveChannel(key: string): Promise<string>;
|
|
638
607
|
abstract resolveTarget(target: string): Promise<ResolvedTarget>;
|
|
639
608
|
abstract post(target: string, kind: ResolvedTarget['kind'], body: ProviderPost): Promise<ProviderDelivery>;
|
|
640
|
-
/** The platform account a person's token belongs to. */
|
|
641
|
-
whoAmI(_token: string): Promise<ExternalAuthor>;
|
|
642
|
-
/** `post`, as the person whose token this is. */
|
|
643
|
-
postAs(_token: string, _target: string, _kind: ResolvedTarget['kind'], _body: ProviderPost): Promise<ProviderDelivery>;
|
|
644
|
-
editAs(_token: string, _thread: string, _message: string, _body: ProviderPost): Promise<void>;
|
|
645
|
-
deleteAs(_token: string, _thread: string, _message: string): Promise<void>;
|
|
646
609
|
findByEmail(_email: string): Promise<ExternalAuthor | null>;
|
|
647
610
|
mention(_actor: Actor): string | null;
|
|
648
611
|
fetchRoot(_thread: string): Promise<SyncRoot | null>;
|
|
649
612
|
fetchFile(_file: ExternalFile): Promise<Uint8Array | null>;
|
|
650
613
|
/** The platform's own link to an event's message, or null when it has none. */
|
|
651
614
|
permalink(_event: E): Promise<string | null>;
|
|
652
|
-
/**
|
|
653
|
-
|
|
615
|
+
/**
|
|
616
|
+
* A linked message the platform didn't describe, read from the platform. `linker` is the
|
|
617
|
+
* platform message that links it, by its own IDs: what the platform says about the link may be
|
|
618
|
+
* there by now.
|
|
619
|
+
*/
|
|
620
|
+
fetchLink(_link: ExternalLink, _linker: {
|
|
621
|
+
thread: string;
|
|
622
|
+
message: string;
|
|
623
|
+
}): Promise<FetchedLink>;
|
|
654
624
|
/**
|
|
655
625
|
* The message a URL of this platform's names, by its own thread and message IDs, or null for a
|
|
656
626
|
* URL that isn't one. `message` is null for a link to a thread itself. It asks the platform
|
|
@@ -692,4 +662,4 @@ declare abstract class SyncProvider<E extends ProviderEvent = ProviderEvent> {
|
|
|
692
662
|
render(input: RenderInput): string;
|
|
693
663
|
}
|
|
694
664
|
//#endregion
|
|
695
|
-
export {
|
|
665
|
+
export { envelope as A, render as C, SyncOverrides as D, RootOverride as E, SyncResult as O, mirror as S, ProviderEvent as T, SyncIn as _, ProviderOptions as a, channels as b, ReceivedEvent as c, ResolvedTarget as d, SyncContext as f, SyncFn as g, Sync as h, ProviderFile as i, eventSchema as j, SyncRoot as k, RenderInput as l, UnreadableError as m, FileDelivery as n, ProviderPost as o, SyncProvider as p, ProviderDelivery as r, Receive as s, FetchedLink as t, Renderer as u, SyncOut as v, EventBase as w, into as x, SyncUnit as y };
|
|
@@ -5,7 +5,7 @@ import { m as nullProtoRecord, n as NOTIFY_TIMINGS, v as stateTailReaders } from
|
|
|
5
5
|
import { i as presenceTombstones, n as isSerializedEvent, r as parseSerializedEvent, t as bufferedEvents } from "./stream-buffer-DrTyiIuo-Be10yOBR.mjs";
|
|
6
6
|
import { i as streamActivities, n as defaultSleep, r as pollingStream, t as createStreamActivity } from "./store-polling-BZt2Rf3D-Div-bJ_D.mjs";
|
|
7
7
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii-DpTc5PIP.mjs";
|
|
8
|
-
import { i as syncReads, n as project, r as projectSync, t as concernedMessageId } from "./project-
|
|
8
|
+
import { i as syncReads, n as project, r as projectSync, t as concernedMessageId } from "./project-CQ4VUibA.mjs";
|
|
9
9
|
import { i as idempotentReplay, n as decodeCause } from "./store-codec-pAG2oPgQ-SqDXlPBR.mjs";
|
|
10
10
|
import { t as pollPresence } from "./store-presence-polling-JuAy2Ck2-Dh-g_KYM.mjs";
|
|
11
11
|
//#region ../../node_modules/.pnpm/experimental-a2@0.24.0_@upstash+qstash@2.12.0_@vercel+queue@0.7.0_ai@7.0.130_zod@4.6.5__b1e2c243a963f2620a5d202817417c26/node_modules/experimental-a2/dist/store-redis-notify-ys_-VrSP.js
|
|
@@ -3278,12 +3278,6 @@ local out = {}
|
|
|
3278
3278
|
for i = 1, #KEYS do out[i] = redis.call('HGETALL', KEYS[i]) end
|
|
3279
3279
|
return out
|
|
3280
3280
|
`;
|
|
3281
|
-
/** A person's roots posted at or after ARGV[1]. KEYS: messages, their roots by creation time. */
|
|
3282
|
-
const RECENT = `
|
|
3283
|
-
local ids = redis.call('ZRANGEBYSCORE', KEYS[2], ARGV[1], '+inf')
|
|
3284
|
-
if #ids == 0 then return {} end
|
|
3285
|
-
return redis.call('HMGET', KEYS[1], unpack(ids))
|
|
3286
|
-
`;
|
|
3287
3281
|
/**
|
|
3288
3282
|
* What an event reads before it applies, in one script so it reads the primary,
|
|
3289
3283
|
* where the position check runs: a replica can lag it. Nothing, when the
|
|
@@ -3412,8 +3406,6 @@ function redisTables(send, prefix, options = {}) {
|
|
|
3412
3406
|
mention: (actor) => `${p}:i:mn:${actor}`,
|
|
3413
3407
|
attachment: (id) => `${p}:i:at:${id}`,
|
|
3414
3408
|
from: (from) => `${p}:i:f:${from}`,
|
|
3415
|
-
/** A person's roots, by when they were posted: for the echo check. */
|
|
3416
|
-
rootsBy: (principal) => `${p}:rr:${principal}`,
|
|
3417
3409
|
label: (token) => `${p}:i:${token}`,
|
|
3418
3410
|
copies: (id) => `${p}:cp:${id}`
|
|
3419
3411
|
};
|
|
@@ -3522,7 +3514,6 @@ function redisTables(send, prefix, options = {}) {
|
|
|
3522
3514
|
re: m.re,
|
|
3523
3515
|
c: m.channel,
|
|
3524
3516
|
t: m.text.toLowerCase(),
|
|
3525
|
-
ca: Date.parse(m.createdAt),
|
|
3526
3517
|
d: m.deleted
|
|
3527
3518
|
};
|
|
3528
3519
|
out.push([
|
|
@@ -3577,12 +3568,6 @@ function redisTables(send, prefix, options = {}) {
|
|
|
3577
3568
|
id,
|
|
3578
3569
|
id
|
|
3579
3570
|
]);
|
|
3580
|
-
else out.push([
|
|
3581
|
-
"ZADD",
|
|
3582
|
-
k.rootsBy(m.by.principal),
|
|
3583
|
-
String(meta.ca),
|
|
3584
|
-
id
|
|
3585
|
-
]);
|
|
3586
3571
|
}
|
|
3587
3572
|
if (change.message.terms) {
|
|
3588
3573
|
const old = read.message;
|
|
@@ -3903,9 +3888,6 @@ function redisTables(send, prefix, options = {}) {
|
|
|
3903
3888
|
return message ? [message] : [];
|
|
3904
3889
|
});
|
|
3905
3890
|
},
|
|
3906
|
-
async recentRoots(principal, since) {
|
|
3907
|
-
return (await evaluate(RECENT, [k.messages, k.rootsBy(principal)], [since])).map((row) => parse(row)).sort((a, b) => a.id - b.id);
|
|
3908
|
-
},
|
|
3909
3891
|
async close() {}
|
|
3910
3892
|
};
|
|
3911
3893
|
}
|
|
@@ -2,7 +2,7 @@ import { S as storable, _ as RemoteWatcherId, c as ChannelPath, d as LabelKey, g
|
|
|
2
2
|
import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin } from "./external-D3p5T57R.mjs";
|
|
3
3
|
import { n as setOwn, t as own } from "./own-OwgPWdmh.mjs";
|
|
4
4
|
import { a as reducer, i as makeReducerBuilder, t as assertSyncSchema } from "./reducer-Bswr11ar-P324o9vn.mjs";
|
|
5
|
-
import { n as foldMessages, t as foldActors } from "./folds-
|
|
5
|
+
import { n as foldMessages, t as foldActors } from "./folds-Cu9Q9zyQ.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region ../../node_modules/.pnpm/experimental-a2@0.24.0_@upstash+qstash@2.12.0_@vercel+queue@0.7.0_ai@7.0.130_zod@4.6.5__b1e2c243a963f2620a5d202817417c26/node_modules/experimental-a2/dist/contract-DqfBSIqJ.js
|
|
8
8
|
function contract(options) {
|
|
@@ -80,6 +80,14 @@ const boardEvents = {
|
|
|
80
80
|
*/
|
|
81
81
|
importedBy: ActorId.optional()
|
|
82
82
|
}),
|
|
83
|
+
/**
|
|
84
|
+
* A channel made before anything posted to it. A post makes one too, with no event of its own:
|
|
85
|
+
* this is only for one that's empty. It names no message, so it carries no `id`.
|
|
86
|
+
*/
|
|
87
|
+
"channel.created": z.object({
|
|
88
|
+
channel: ChannelPath,
|
|
89
|
+
by: Author
|
|
90
|
+
}),
|
|
83
91
|
/** A message's links changed: an edit linked something new, or the platform sent what a link says. */
|
|
84
92
|
"message.linked": z.object({
|
|
85
93
|
id: MessageId,
|
|
@@ -138,9 +146,7 @@ const boardEvents = {
|
|
|
138
146
|
target: ExternalTarget,
|
|
139
147
|
external: z.object({
|
|
140
148
|
thread: ExternalTarget,
|
|
141
|
-
message: z.string().min(1).max(300)
|
|
142
|
-
/** Posted with its author's own platform token, so it shows as them. Older receipts have none. */
|
|
143
|
-
asUser: z.literal(true).optional()
|
|
149
|
+
message: z.string().min(1).max(300)
|
|
144
150
|
}),
|
|
145
151
|
/**
|
|
146
152
|
* (server) The principal of the sync elsewhere that recorded it: its copy is that sync's to
|
|
@@ -179,22 +185,6 @@ const boardEvents = {
|
|
|
179
185
|
* signs in, whatever its ID or kind, so sync never writes as it. Others have none.
|
|
180
186
|
*/
|
|
181
187
|
host: z.literal(true).optional()
|
|
182
|
-
}),
|
|
183
|
-
/**
|
|
184
|
-
* A platform actor (`slack:user:U0456`) is the same person as a principal
|
|
185
|
-
* (`vercel:user:…`): for display, `--by`, and mentions, never for permissions.
|
|
186
|
-
* `method` says how it was proved: a one-time code posted from that account,
|
|
187
|
-
* or the account's own sign-in (`consent`).
|
|
188
|
-
*/
|
|
189
|
-
"actor.linked": z.object({
|
|
190
|
-
actor: ActorId,
|
|
191
|
-
to: ActorId,
|
|
192
|
-
method: z.enum(["code", "consent"]),
|
|
193
|
-
by: Author
|
|
194
|
-
}),
|
|
195
|
-
"actor.unlinked": z.object({
|
|
196
|
-
actor: ActorId,
|
|
197
|
-
by: Author
|
|
198
188
|
})
|
|
199
189
|
};
|
|
200
190
|
const BOARD_SESSION = "board";
|
|
@@ -328,7 +318,9 @@ const StoredPrincipal = z.object({
|
|
|
328
318
|
/** The board's own Vercel team, as the board knew it when it verified the token. */
|
|
329
319
|
team: z.string().max(200).optional()
|
|
330
320
|
}).optional(),
|
|
331
|
-
scope: z.string().max(SCOPE_MAX).optional()
|
|
321
|
+
scope: z.string().max(SCOPE_MAX).optional(),
|
|
322
|
+
/** The board token it came with: what it stores stops when the token does. */
|
|
323
|
+
token: z.string().min(1).max(64).optional()
|
|
332
324
|
});
|
|
333
325
|
/** A watcher as a board on `http()` registers it: its tail, and when it runs. */
|
|
334
326
|
const WatcherSpec = z.object({
|
|
@@ -350,11 +342,13 @@ const registryContract = contract({
|
|
|
350
342
|
/** Where to wake it: `<url>/wake`. Without one, it pulls on its own. */
|
|
351
343
|
url: z.string().url().max(500).nullable(),
|
|
352
344
|
watch: z.record(RemoteWatcherId, WatcherSpec),
|
|
353
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* Its sync entries: with any, its sync subscribes too, to every post, change, and thread sync
|
|
347
|
+
* where it may sync. Older registrations have none.
|
|
348
|
+
*/
|
|
354
349
|
sync: z.array(z.object({
|
|
355
350
|
key: z.string().min(1).max(64),
|
|
356
|
-
platform: z.string().min(1).max(64)
|
|
357
|
-
signIn: z.boolean()
|
|
351
|
+
platform: z.string().min(1).max(64)
|
|
358
352
|
})).max(32).optional(),
|
|
359
353
|
/** `watchersHash(watch)`, which every wake-up carries so the board can tell it's stale. */
|
|
360
354
|
hash: z.string().min(1).max(100)
|
|
@@ -444,31 +438,53 @@ const schedulesContract = contract({
|
|
|
444
438
|
}
|
|
445
439
|
});
|
|
446
440
|
const SCHEDULES_SESSION = "schedules";
|
|
441
|
+
const Grant = z.union([z.literal("all"), z.array(z.string().min(1).max(16)).max(8)]);
|
|
442
|
+
/** A `can`, as config writes it: board() checks it before it's kept. */
|
|
443
|
+
const Can = z.union([Grant, z.record(z.string().min(1).max(300), Grant)]);
|
|
444
|
+
const ClaimValue = z.string().min(1).max(1e3);
|
|
447
445
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
446
|
+
* Who the board trusts beside its code's rules, outside the board log: grants, and the tokens it
|
|
447
|
+
* issued, of which only a hash is kept. Session `access`. A grant's and a token's ID is the index
|
|
448
|
+
* of the event that made it.
|
|
450
449
|
*/
|
|
451
|
-
const
|
|
452
|
-
name: "board-
|
|
450
|
+
const accessContract = contract({
|
|
451
|
+
name: "board-access",
|
|
453
452
|
events: {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
453
|
+
/** A grant, or a new `can` for the grant with this issuer and claims. */
|
|
454
|
+
"grant.set": z.object({
|
|
455
|
+
issuer: z.string().min(1).max(1e3),
|
|
456
|
+
claims: z.record(z.string().min(1).max(200), z.union([ClaimValue, z.array(ClaimValue).min(1).max(32)])),
|
|
457
|
+
can: Can,
|
|
458
|
+
by: ActorId
|
|
459
|
+
}),
|
|
460
|
+
"grant.revoked": z.object({
|
|
461
|
+
id: z.number().int().positive(),
|
|
462
|
+
by: ActorId
|
|
463
|
+
}),
|
|
464
|
+
"token.minted": z.object({
|
|
465
|
+
/** The secret's SHA-256, hex. */
|
|
466
|
+
hash: z.string().regex(/^[0-9a-f]{64}$/),
|
|
467
|
+
name: z.string().min(1).max(64),
|
|
468
|
+
/** Who it acts as, its scope narrowed to what the token may do. */
|
|
469
|
+
minter: StoredPrincipal,
|
|
470
|
+
profile: z.string().min(1).max(32).nullable(),
|
|
471
|
+
avatar: z.string().max(2048).nullable(),
|
|
472
|
+
session: z.string().min(1).max(128).nullable(),
|
|
473
|
+
/** A person's handle and name when they minted it. */
|
|
474
|
+
handle: Handle.nullable(),
|
|
475
|
+
personName: z.string().min(1).max(128).nullable(),
|
|
476
|
+
expiresAt: Millis,
|
|
477
|
+
/** The token it was minted with, which it stops with. */
|
|
478
|
+
parent: z.number().int().positive().nullable(),
|
|
479
|
+
by: ActorId
|
|
461
480
|
}),
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
actor: ActorId,
|
|
466
|
-
/** The message that used it (`externalMessageKey`): only a retry of that one claims it again. */
|
|
467
|
-
message: z.string().max(1024).optional()
|
|
481
|
+
"token.revoked": z.object({
|
|
482
|
+
id: z.number().int().positive(),
|
|
483
|
+
by: ActorId
|
|
468
484
|
})
|
|
469
485
|
}
|
|
470
486
|
});
|
|
471
|
-
const
|
|
487
|
+
const ACCESS_SESSION = "access";
|
|
472
488
|
//#endregion
|
|
473
489
|
//#region src/contract/reducers.ts
|
|
474
490
|
const messagesReducer = reducer({
|
|
@@ -487,19 +503,24 @@ reducer({
|
|
|
487
503
|
if (event.type === "message.posted") setOwn(state, event.id, event.index);
|
|
488
504
|
return state;
|
|
489
505
|
});
|
|
506
|
+
const emptyChannel = (path) => ({
|
|
507
|
+
path,
|
|
508
|
+
lastIndex: 0,
|
|
509
|
+
messages: 0,
|
|
510
|
+
threads: 0
|
|
511
|
+
});
|
|
490
512
|
const channelsReducer = reducer({
|
|
491
513
|
contract: boardContract,
|
|
492
|
-
name: "board.channels.
|
|
514
|
+
name: "board.channels.v2",
|
|
493
515
|
initialState: {}
|
|
494
516
|
}).fold((state, event) => {
|
|
517
|
+
if (event.type === "channel.created") {
|
|
518
|
+
if (!own(state, event.payload.channel)) setOwn(state, event.payload.channel, emptyChannel(event.payload.channel));
|
|
519
|
+
return state;
|
|
520
|
+
}
|
|
495
521
|
if (event.type !== "message.posted") return state;
|
|
496
522
|
const { channel, re } = event.payload;
|
|
497
|
-
const entry = own(state, channel) ?? setOwn(state, channel,
|
|
498
|
-
path: channel,
|
|
499
|
-
lastIndex: 0,
|
|
500
|
-
messages: 0,
|
|
501
|
-
threads: 0
|
|
502
|
-
});
|
|
523
|
+
const entry = own(state, channel) ?? setOwn(state, channel, emptyChannel(channel));
|
|
503
524
|
entry.lastIndex = event.index;
|
|
504
525
|
entry.messages += 1;
|
|
505
526
|
if (re === null) entry.threads += 1;
|
|
@@ -510,32 +531,8 @@ const actorsReducer = reducer({
|
|
|
510
531
|
name: "board.actors.v2",
|
|
511
532
|
initialState: {
|
|
512
533
|
byId: {},
|
|
513
|
-
byHandle: {}
|
|
514
|
-
links: {}
|
|
534
|
+
byHandle: {}
|
|
515
535
|
}
|
|
516
536
|
}).fold(foldActors);
|
|
517
|
-
const identityCodesReducer = reducer({
|
|
518
|
-
contract: identityContract,
|
|
519
|
-
name: "board.identity-codes.v1",
|
|
520
|
-
initialState: {}
|
|
521
|
-
}).fold((state, event) => {
|
|
522
|
-
if (event.type === "identity.code_issued") {
|
|
523
|
-
const { hash, principal, sync, expiresAt } = event.payload;
|
|
524
|
-
state[hash] ??= {
|
|
525
|
-
principal,
|
|
526
|
-
sync,
|
|
527
|
-
expiresAt,
|
|
528
|
-
usedBy: null,
|
|
529
|
-
usedIn: null
|
|
530
|
-
};
|
|
531
|
-
} else if (event.type === "identity.code_used") {
|
|
532
|
-
const code = state[event.payload.hash];
|
|
533
|
-
if (code && code.usedBy === null) {
|
|
534
|
-
code.usedBy = event.payload.actor;
|
|
535
|
-
code.usedIn = event.payload.message ?? null;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return state;
|
|
539
|
-
});
|
|
540
537
|
//#endregion
|
|
541
|
-
export {
|
|
538
|
+
export { INBOXES_SESSION as a, accessContract as c, inboxSession as d, registryContract as f, BOARD_SESSION as g, watchSession as h, ACCESS_SESSION as i, boardContract as l, watchContract as m, channelsReducer as n, REGISTRY_SESSION as o, schedulesContract as p, messagesReducer as r, SCHEDULES_SESSION as s, actorsReducer as t, inboxContract as u };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { u as BoardError } from "./errors-
|
|
2
|
-
import { s as localTokenFile } from "./node-
|
|
3
|
-
import { t as board } from "./server-
|
|
4
|
-
import { t as oidcTokens } from "./tokens-
|
|
5
|
-
import { t as a2 } from "./a2-
|
|
6
|
-
import { a as fsBlob } from "./blob-
|
|
1
|
+
import { u as BoardError } from "./errors-CJXhhzvT.mjs";
|
|
2
|
+
import { s as localTokenFile } from "./node-BgeGcwX0.mjs";
|
|
3
|
+
import { t as board } from "./server-DG8D6VV0.mjs";
|
|
4
|
+
import { t as oidcTokens } from "./tokens-Dnyd_y1n.mjs";
|
|
5
|
+
import { t as a2 } from "./a2-DngWY2Eb.mjs";
|
|
6
|
+
import { a as fsBlob } from "./blob-WbB2DJKn.mjs";
|
|
7
7
|
import { t as localScheduler } from "./local-C76Fy5ad.mjs";
|
|
8
8
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { userInfo } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createBoard } from "./exports-
|
|
2
|
-
import { t as http } from "./backend-http-
|
|
1
|
+
import { t as createBoard } from "./exports-CWIdN0Ub.mjs";
|
|
2
|
+
import { t as http } from "./backend-http-D8mUzuKQ.mjs";
|
|
3
3
|
//#region src/server/index.ts
|
|
4
4
|
/**
|
|
5
5
|
* The handle on a board. With a `backend`, the board lives there: `a2()` keeps it in this
|
|
@@ -9,8 +9,8 @@ import { t as http } from "./backend-http-16SbvnAK.mjs";
|
|
|
9
9
|
function board(options = {}) {
|
|
10
10
|
return createBoard(options, {
|
|
11
11
|
http: () => http(),
|
|
12
|
-
sync: () => import("./engine-
|
|
13
|
-
serving: () => import("./serving-
|
|
12
|
+
sync: () => import("./engine-DurYIRt8.mjs"),
|
|
13
|
+
serving: () => import("./serving-DBFRo6U_.mjs")
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-
|
|
1
|
+
import { i as ownTeamId, n as discoveryIdentity, r as ownProjectId, t as oidcTokens } from "./tokens-Dnyd_y1n.mjs";
|
|
2
2
|
export { discoveryIdentity, oidcTokens, ownProjectId, ownTeamId };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as externalMessageKey } from "./external-D3p5T57R.mjs";
|
|
2
2
|
import { n as setOwn } from "./own-OwgPWdmh.mjs";
|
|
3
|
-
import { i as syncReads, n as project, r as projectSync, t as concernedMessageId } from "./project-
|
|
3
|
+
import { i as syncReads, n as project, r as projectSync, t as concernedMessageId } from "./project-CQ4VUibA.mjs";
|
|
4
4
|
//#region src/store/sql.ts
|
|
5
5
|
/** The projection's name in `board_positions`, and the A2 consumer that runs it. */
|
|
6
6
|
const TABLES_CONSUMER = "board-tables";
|
|
@@ -61,7 +61,7 @@ function ddl(dialect) {
|
|
|
61
61
|
key text primary key,
|
|
62
62
|
message_id ${big} not null
|
|
63
63
|
)`,
|
|
64
|
-
"
|
|
64
|
+
"drop index if exists board_messages_recent",
|
|
65
65
|
...dialect === "postgres" ? ["drop function if exists board_apply(text, bigint, boolean, jsonb, jsonb, jsonb)", APPLY_FUNCTION] : []
|
|
66
66
|
];
|
|
67
67
|
}
|
|
@@ -558,12 +558,6 @@ order by m.id`,
|
|
|
558
558
|
values: [clean(attachment)]
|
|
559
559
|
})).map((found) => parse(found));
|
|
560
560
|
},
|
|
561
|
-
async recentRoots(principal, since) {
|
|
562
|
-
return (await db.all({
|
|
563
|
-
text: "select data from board_messages where by_principal = $1 and re is null and created_at >= $2 order by id",
|
|
564
|
-
values: [principal, since]
|
|
565
|
-
})).map((found) => parse(found));
|
|
566
|
-
},
|
|
567
561
|
close: () => db.close()
|
|
568
562
|
};
|
|
569
563
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { b as channels, g as SyncFn, p as SyncProvider } from "./provider-gynDUoyW.mjs";
|
|
2
|
+
import { r as ChatTransport, t as ChatInput } from "./chat-provider-BChYrrA7.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
//#region src/sync/
|
|
6
|
-
|
|
4
|
+
import { ReactionEvent } from "chat";
|
|
5
|
+
//#region src/sync/chat-sdk.d.ts
|
|
6
|
+
declare const schemaOf: <P extends string>(platform: P) => z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
7
7
|
channel: z.ZodString;
|
|
8
8
|
thread: z.ZodString;
|
|
9
9
|
subthread: z.ZodNullable<z.ZodString>;
|
|
@@ -49,7 +49,7 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
49
49
|
id: z.ZodString;
|
|
50
50
|
key: z.ZodString;
|
|
51
51
|
raw: z.ZodUnknown;
|
|
52
|
-
platform: z.ZodLiteral<
|
|
52
|
+
platform: z.ZodLiteral<P>;
|
|
53
53
|
type: z.ZodLiteral<"message">;
|
|
54
54
|
}, z.core.$strip>, z.ZodObject<{
|
|
55
55
|
channel: z.ZodString;
|
|
@@ -83,7 +83,7 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
83
83
|
id: z.ZodString;
|
|
84
84
|
key: z.ZodString;
|
|
85
85
|
raw: z.ZodUnknown;
|
|
86
|
-
platform: z.ZodLiteral<
|
|
86
|
+
platform: z.ZodLiteral<P>;
|
|
87
87
|
type: z.ZodLiteral<"message.edited">;
|
|
88
88
|
}, z.core.$strip>, z.ZodObject<{
|
|
89
89
|
channel: z.ZodString;
|
|
@@ -94,7 +94,7 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
id: z.ZodString;
|
|
95
95
|
key: z.ZodString;
|
|
96
96
|
raw: z.ZodUnknown;
|
|
97
|
-
platform: z.ZodLiteral<
|
|
97
|
+
platform: z.ZodLiteral<P>;
|
|
98
98
|
type: z.ZodLiteral<"message.deleted">;
|
|
99
99
|
}, z.core.$strip>, z.ZodObject<{
|
|
100
100
|
channel: z.ZodString;
|
|
@@ -114,7 +114,7 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
114
114
|
id: z.ZodString;
|
|
115
115
|
key: z.ZodString;
|
|
116
116
|
raw: z.ZodUnknown;
|
|
117
|
-
platform: z.ZodLiteral<
|
|
117
|
+
platform: z.ZodLiteral<P>;
|
|
118
118
|
type: z.ZodEnum<{
|
|
119
119
|
"reaction.added": "reaction.added";
|
|
120
120
|
"reaction.removed": "reaction.removed";
|
|
@@ -144,35 +144,31 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
144
144
|
id: z.ZodString;
|
|
145
145
|
key: z.ZodString;
|
|
146
146
|
raw: z.ZodUnknown;
|
|
147
|
-
platform: z.ZodLiteral<
|
|
147
|
+
platform: z.ZodLiteral<P>;
|
|
148
148
|
}, z.core.$strip>], "kind">;
|
|
149
|
-
|
|
150
|
-
type
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
/** What a `chatSdk()` entry's sync function reads as `sync.event`. `P` is its platform's name. */
|
|
150
|
+
export type ChatSdkEvent<P extends string = string> = z.infer<ReturnType<typeof schemaOf<P>>>;
|
|
151
|
+
/** The adapter, and hooks for what board can't read from it. Every hook is optional. */
|
|
152
|
+
export type ChatSdkOptions<P extends string = string> = Omit<ChatTransport, 'platform'> & {
|
|
153
|
+
/** The platform's name, in author IDs, author labels, and `vb sources`. The adapter's `name` unless given. */
|
|
154
|
+
platform?: P;
|
|
155
|
+
/**
|
|
156
|
+
* Whether a new board root opens a thread of its own in a mirrored channel, as on Discord or
|
|
157
|
+
* Teams. Off unless given: on most platforms a chat is one conversation, so the root stays on
|
|
158
|
+
* the board, and threads that came from the platform sync both ways.
|
|
159
|
+
*/
|
|
160
|
+
canStartThreads?: boolean;
|
|
161
|
+
/** The platform's ID for one reaction occurrence, the same on every redelivery. Without it, a reaction is known by the webhook that brought it. */
|
|
162
|
+
reactionId?(reaction: ReactionEvent): string;
|
|
160
163
|
};
|
|
161
164
|
/**
|
|
162
|
-
* Syncs
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* thread never starts one: a board root in a mirrored channel stays on the board.
|
|
165
|
+
* Syncs any Chat SDK adapter with the board: `sync` is what each event does, both ways, like
|
|
166
|
+
* `channels({ '-1001234567890': 'support' })`. Channels are the adapter's own IDs, without its
|
|
167
|
+
* prefix. Slack, GitHub, and Linear have entries of their own, with more than this reads.
|
|
166
168
|
*/
|
|
167
|
-
export declare function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
export
|
|
173
|
-
export declare class KapsoProvider extends ChatSyncProvider<WhatsAppEvent> {
|
|
174
|
-
constructor(options: KapsoOptions, sync: SyncFn<WhatsAppEvent>);
|
|
175
|
-
eventId(key: string, input: ChatInput): string;
|
|
176
|
-
event(context: SyncContext): WhatsAppEvent;
|
|
177
|
-
}
|
|
178
|
-
//#endregion
|
|
169
|
+
export declare function chatSdk<P extends string>(options: ChatSdkOptions<P> & {
|
|
170
|
+
platform: P;
|
|
171
|
+
}, sync: SyncFn<ChatSdkEvent<P>>): SyncProvider<ChatSdkEvent<P>>;
|
|
172
|
+
export declare function chatSdk(options: ChatSdkOptions, sync: SyncFn<ChatSdkEvent>): SyncProvider<ChatSdkEvent>;
|
|
173
|
+
//#endregion
|
|
174
|
+
export { type ChatInput, channels };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { f as channels, i as eventSchema, r as envelope } from "./events-lujPA3gQ.mjs";
|
|
2
|
+
import { t as ChatSyncProvider } from "./chat-provider-DkFp2hrh.mjs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
//#region src/sync/chat-sdk.ts
|
|
5
|
+
const schemaOf = (platform) => eventSchema(platform, "message");
|
|
6
|
+
function chatSdk(options, sync) {
|
|
7
|
+
if (!options?.adapter?.name) throw new Error("chatSdk needs a Chat SDK adapter: chatSdk({ adapter }, channels({ … }))");
|
|
8
|
+
return new ChatSdkProvider(options, sync);
|
|
9
|
+
}
|
|
10
|
+
var ChatSdkProvider = class extends ChatSyncProvider {
|
|
11
|
+
schema;
|
|
12
|
+
reactionId;
|
|
13
|
+
constructor({ platform, reactionId, ...transport }, sync) {
|
|
14
|
+
const name = platform ?? transport.adapter.name;
|
|
15
|
+
super({
|
|
16
|
+
...transport,
|
|
17
|
+
platform: name
|
|
18
|
+
}, sync);
|
|
19
|
+
this.schema = schemaOf(name);
|
|
20
|
+
this.reactionId = reactionId;
|
|
21
|
+
}
|
|
22
|
+
eventId(key, input) {
|
|
23
|
+
if (input.kind !== "reaction") return super.eventId(key, input);
|
|
24
|
+
const reaction = input.reaction;
|
|
25
|
+
const occurrence = this.reactionId?.(reaction) ?? this.occurrence();
|
|
26
|
+
return `reaction:${key}:${this.toNative(reaction.threadId)}|${reaction.messageId}|${reaction.rawEmoji}|${reaction.user.userId}|${reaction.added}|${occurrence}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A reaction's occurrence, when the platform gives it no ID: the webhook that brought it. A
|
|
30
|
+
* platform redelivers the same body, and sends a new one for each occurrence, even where the
|
|
31
|
+
* payload has nothing that tells two apart (Discord's forwarded reactions carry a timestamp
|
|
32
|
+
* beside it).
|
|
33
|
+
*/
|
|
34
|
+
occurrence() {
|
|
35
|
+
const delivery = this.delivery();
|
|
36
|
+
if (delivery === null) throw new Error(`${this.platform}: a reaction outside a webhook has no identity; pass reactionId`);
|
|
37
|
+
return createHash("sha256").update(delivery).digest("base64url");
|
|
38
|
+
}
|
|
39
|
+
event(context) {
|
|
40
|
+
return this.schema.parse(envelope(this.platform, context.key, context.event, context.raw, context.id));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { channels, chatSdk };
|