@vercel/board 0.16.0 → 0.17.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 +27 -27
- package/dist/{a2-C41hD8SF.mjs → a2-Bck5x1Kh.mjs} +969 -25
- package/dist/access.d.mts +1 -1
- package/dist/access.mjs +3 -3
- package/dist/{actors-DjMeZpnU.mjs → actors-BOamkEuz.mjs} +3 -73
- package/dist/attempts-CLG840Oj.mjs +20 -0
- package/dist/{auth-COzc09sn.mjs → auth-Drg9FiC2.mjs} +225 -2
- package/dist/backend-B-SJ7p6p.d.mts +680 -0
- package/dist/backend-a2/contract.d.mts +873 -2
- package/dist/backend-a2/contract.mjs +2 -2
- package/dist/backend-a2/scheduler-local.d.mts +1 -1
- package/dist/backend-a2/scheduler-local.mjs +1 -1
- package/dist/backend-a2/scheduler-qstash.d.mts +78 -1
- package/dist/backend-a2/scheduler-qstash.mjs +521 -1
- package/dist/backend-a2/scheduler-vercel.d.mts +37 -1
- package/dist/backend-a2/scheduler-vercel.mjs +203 -1
- package/dist/backend-a2/store-memory.d.mts +2 -2
- package/dist/backend-a2/store-memory.mjs +3 -3
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +2850 -2
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +490 -2
- package/dist/backend-a2/store-redis.d.mts +10 -2
- package/dist/backend-a2/store-redis.mjs +51 -2
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +3 -3
- package/dist/backend-a2.d.mts +2706 -3
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-Bid7RIpC.d.mts → backend-http-7cNb8nJI.d.mts} +10 -3
- package/dist/{backend-http-0roEmjl0.mjs → backend-http-C5I7Wfdw.mjs} +2 -2
- package/dist/{backend-http-BVRno-ca.mjs → backend-http-CuUIACCs.mjs} +32 -3
- 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/{chat-provider-XCnQlxq6.d.mts → chat-provider-DNJQyajS.d.mts} +16 -4
- package/dist/{chat-provider-jFSeyNmB.mjs → chat-provider-YHy-WbrB.mjs} +15 -9
- package/dist/cli.mjs +30 -10
- package/dist/{client-DGDPaN7U.mjs → client-BA-J7usH.mjs} +10 -2
- package/dist/{conformance-DRR23YNy.mjs → conformance-CT5EeHeC.mjs} +5 -2
- package/dist/conformance.d.mts +1 -1
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-Bir5s8ts.mjs → engine-D0oBUBEK.mjs} +249 -163
- package/dist/events-CewD4kw7.mjs +75 -0
- package/dist/{exports-Dvc1o52I.mjs → exports-DOdA7KhF.mjs} +35 -32
- package/dist/exports-ZsgjzEnM.d.mts +4 -0
- package/dist/external-BV_cUncs.mjs +151 -0
- package/dist/folds-CRYY14kc.d.mts +534 -0
- package/dist/{reducers-mUPFuFJV.mjs → folds-Dyjput8M.mjs} +6 -73
- package/dist/ids-C6AYo1bu.mjs +31 -0
- package/dist/index.d.mts +4 -4
- package/dist/{index.default-Bkg77Pxn.mjs → index.default-n4qKN7EL.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.node.d.mts +4 -4
- package/dist/index.node.mjs +3 -3
- package/dist/inspect/app.js +31 -31
- package/dist/inspect/engine.js +5 -5
- package/dist/internal-CvsrprnU-CRY89I-b.mjs +203 -0
- package/dist/{local-Cr_dSjvs.mjs → local-DhkrAqPP.mjs} +1 -1
- package/dist/mentions-CB6ShY4R.mjs +72 -0
- package/dist/{targets-yM5JBUgr.mjs → middleware-CqaCwucP.mjs} +1 -33
- package/dist/platform-B4TnJtWu-CwcTyKg2.mjs +32 -0
- package/dist/{project-CFiArraX.mjs → project-DMDST9kl.mjs} +1 -1
- package/dist/{provider-BGjz-mDa.d.mts → provider-B0b9Qxw4.d.mts} +170 -40
- package/dist/provider-B3DpYSDq.mjs +220 -0
- package/dist/redis-tables-DN9PfIDi.mjs +3897 -0
- package/dist/reducer-CYPHsSmd-BQIfozGi.d.mts +170 -0
- package/dist/reducer-CjmvZSAb-D_OSISmj.mjs +74 -0
- package/dist/{contract-CuJmlrhw.mjs → reducers-BFmIXilu.mjs} +260 -287
- package/dist/retryable-lazy-DZWmHpii-Bye-W8q7.mjs +19 -0
- package/dist/scheduler-task-X1SToDjP-BVoWuInI.mjs +69 -0
- package/dist/{serve-BsOOc-6_.mjs → serve-CNYvA4pT.mjs} +5 -5
- package/dist/server-DtFrWxi3-Boa6gdWO.mjs +3602 -0
- package/dist/server-qgez0Xny-D5mLfqFQ.d.mts +385 -0
- package/dist/{server-jwPY7_wi.mjs → server-snYFkITb.mjs} +4 -4
- package/dist/{serving-BNbzc96L.mjs → serving-CNkgUPW1.mjs} +1 -1
- package/dist/session-clear-wHd5GCpZ-BnKSxReK.mjs +51 -0
- package/dist/{sql-DbiEnqM0.mjs → sql-U9WTxrwJ.mjs} +2 -2
- package/dist/{sqlite-db-Cbj03vmp.mjs → sqlite-db-Ds4majtT.mjs} +1 -1
- package/dist/store-D0MhBvg6-4Z2L0Kqd.d.mts +530 -0
- package/dist/store-N8PXxDAS-BWBgzBHp.mjs +5 -0
- package/dist/store-codec-DwrWlgQx-CYxGW0aD.mjs +61 -0
- package/dist/store-memory-Bas9Uq5C.mjs +872 -0
- package/dist/store-memory-ZLiZbH3R.mjs +2 -0
- package/dist/store-polling-BZt2Rf3D-BiEM6_HB.mjs +148 -0
- package/dist/store-presence-polling-JuAy2Ck2-CYpuK75X.mjs +109 -0
- package/dist/store-sqlite-BmwL3pUR.mjs +2 -0
- package/dist/store-sqlite-CxxPCcCT.mjs +1053 -0
- package/dist/stream-buffer-DrTyiIuo-BKy-vI14.mjs +53 -0
- package/dist/sync-github.d.mts +24 -17
- package/dist/sync-github.mjs +89 -22
- package/dist/sync-kapso.d.mts +15 -13
- package/dist/sync-kapso.mjs +17 -14
- package/dist/sync-slack.d.mts +12 -17
- package/dist/sync-slack.mjs +54 -42
- package/dist/sync.chat.d.mts +1 -1
- package/dist/sync.chat.mjs +1 -1
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +5 -4
- package/dist/targets-C-wdR5wP.mjs +33 -0
- package/dist/{tokens-DKLn5P9B.mjs → tokens-DhuzKzAl.mjs} +1 -1
- package/dist/{types-BHCjTZdB.d.mts → types-8yq2VnKI.d.mts} +2 -2
- package/dist/{users-Dls7_ANV.mjs → users-yryHKFMo.mjs} +2 -2
- package/dist/{watch-spec-W0c7ypHJ.mjs → watchers-hash-DojIuoFg.mjs} +233 -28
- package/docs/01-board/01-mounting.md +1 -1
- package/docs/02-sync/01-overview.md +69 -47
- package/docs/02-sync/02-functions.md +395 -0
- package/docs/02-sync/03-slack.md +30 -25
- package/docs/02-sync/04-github.md +62 -26
- package/docs/02-sync/05-whatsapp.md +23 -19
- package/docs/02-sync/06-providers.md +58 -26
- package/docs/03-guides/04-triage.md +1 -1
- package/docs/04-reference/01-cli.md +26 -6
- package/docs/04-reference/02-board.md +18 -5
- package/docs/04-reference/03-http.md +5 -2
- package/docs/04-reference/05-conformance.md +1 -1
- package/docs/index.md +5 -10
- package/package.json +22 -6
- package/dist/a2-D24cvwpN.d.mts +0 -3359
- package/dist/access-CJU2-vaa.mjs +0 -226
- package/dist/attempts-Bxp9KgPf.mjs +0 -919
- package/dist/exports-DLiE7fLY.d.mts +0 -4
- package/dist/provider-CuewcDNZ.mjs +0 -205
- package/dist/redis-tables-B35vv4CZ.mjs +0 -842
- package/dist/reducers-B_vUGQRp.d.mts +0 -1172
- package/dist/watchers-hash-CLjsa6od.mjs +0 -225
- package/docs/02-sync/02-callbacks.md +0 -265
|
@@ -1,305 +1,210 @@
|
|
|
1
|
-
import { S as storable, _ as RemoteWatcherId, c as ChannelPath, d as LabelKey, g as MessageText, h as MessageId, i as Attachment, l as Emoji,
|
|
1
|
+
import { S as storable, _ as RemoteWatcherId, c as ChannelPath, d as LabelKey, g as MessageText, h as MessageId, i as Attachment, l as Emoji, n as ActorId, o as Author, p as Labels, u as Handle, v as SCOPE_MAX, y as SeenActorKind } from "./schemas-Be08nyfW.mjs";
|
|
2
|
+
import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin } from "./external-BV_cUncs.mjs";
|
|
3
|
+
import { n as setOwn, t as own } from "./own-OwgPWdmh.mjs";
|
|
4
|
+
import { a as reducer, i as makeReducerBuilder, t as assertSyncSchema } from "./reducer-CjmvZSAb-D_OSISmj.mjs";
|
|
5
|
+
import { n as foldMessages, t as foldActors } from "./folds-Dyjput8M.mjs";
|
|
2
6
|
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
url: z.string().min(1).max(4e3),
|
|
38
|
-
message: MessageId.nullable(),
|
|
39
|
-
snapshot: LinkSnapshot.nullable(),
|
|
40
|
-
/** Why there's no snapshot: `not_in_channel`, `channel_not_found`. */
|
|
41
|
-
unavailable: z.string().max(300).nullable()
|
|
42
|
-
});
|
|
7
|
+
//#region ../../node_modules/.pnpm/experimental-a2@0.22.0_@upstash+qstash@2.12.0_@vercel+queue@0.7.0_ai@7.0.130_zod@4.6.5__52060f00b21aecfab049c27fa3abd2f9/node_modules/experimental-a2/dist/contract-CWukt01d.js
|
|
8
|
+
function contract(options) {
|
|
9
|
+
for (const type of Object.keys(options.events ?? {})) if (type.startsWith("a2.")) throw new TypeError(`contract '${String(options.name)}': event types starting with 'a2.' are reserved`);
|
|
10
|
+
return defineContract(options);
|
|
11
|
+
}
|
|
12
|
+
function defineContract(options) {
|
|
13
|
+
const { name } = options;
|
|
14
|
+
if (typeof name !== "string" || name.length === 0) throw new TypeError("contract name must be a non-empty string");
|
|
15
|
+
if (name.includes("")) throw new TypeError("contract name contains a reserved control character");
|
|
16
|
+
const defs = options.events;
|
|
17
|
+
for (const [type, schema] of Object.entries(defs)) {
|
|
18
|
+
if (typeof schema?.["~standard"]?.validate !== "function") throw new TypeError(`contract '${name}': the value for '${type}' is not a Standard Schema (expected an object with '~standard')`);
|
|
19
|
+
assertSyncSchema(schema, `event '${type}'`);
|
|
20
|
+
}
|
|
21
|
+
const events = Object.freeze({ ...defs });
|
|
22
|
+
const presenceDefs = options.presence ?? {};
|
|
23
|
+
for (const [field, schema] of Object.entries(presenceDefs)) {
|
|
24
|
+
if (typeof schema?.["~standard"]?.validate !== "function") throw new TypeError(`contract '${name}': the value for presence field '${field}' is not a Standard Schema (expected an object with '~standard')`);
|
|
25
|
+
assertSyncSchema(schema, `presence field '${field}'`);
|
|
26
|
+
}
|
|
27
|
+
const presence = Object.freeze({ ...presenceDefs });
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
events,
|
|
31
|
+
presence,
|
|
32
|
+
batch: (...items) => items,
|
|
33
|
+
reducer: options.reducer === void 0 ? void 0 : {
|
|
34
|
+
...options.reducer,
|
|
35
|
+
...makeReducerBuilder(events, presence, options.reducer).fold(options.reducer.fold)
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/contract/events.ts
|
|
43
41
|
/** The sync key a write came from, or null when it was written on the board. */
|
|
44
42
|
const From = z.string().nullable();
|
|
45
43
|
/** The thread key: the event ID of the thread's root. Lanes and sync state are keyed by it. */
|
|
46
44
|
const ThreadKey = z.string().min(1).max(200).refine(storable, "thread key has a NUL character or a lone surrogate");
|
|
47
45
|
/** The board's one log. Every board fact is an event in session `board`. */
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
n: z.number().int().nonnegative()
|
|
74
|
-
}).optional(),
|
|
75
|
-
/**
|
|
76
|
-
* (server) Its author couldn't read the channel it posted in. Where a
|
|
77
|
-
* caller can't read, it edits and deletes only these of its own. Events
|
|
78
|
-
* from before channel access have none: everyone read everything then.
|
|
79
|
-
*/
|
|
80
|
-
blind: z.literal(true).optional(),
|
|
81
|
-
/**
|
|
82
|
-
* (server) The principal of the sync elsewhere that imported it, whose copy `origin` names:
|
|
83
|
-
* that sync's to act on. None for the board's own sync, or a write on the board.
|
|
84
|
-
*/
|
|
85
|
-
importedBy: ActorId.optional()
|
|
86
|
-
}),
|
|
87
|
-
/** A message's links changed: an edit linked something new, or the platform sent what a link says. */
|
|
88
|
-
"message.linked": z.object({
|
|
89
|
-
id: MessageId,
|
|
90
|
-
thread: ThreadKey,
|
|
91
|
-
links: z.array(Link).max(10),
|
|
92
|
-
by: Author,
|
|
93
|
-
from: From
|
|
94
|
-
}),
|
|
95
|
-
"message.edited": z.object({
|
|
96
|
-
id: MessageId,
|
|
97
|
-
thread: ThreadKey,
|
|
98
|
-
text: MessageText,
|
|
99
|
-
mentions: z.array(ActorId),
|
|
100
|
-
by: Author,
|
|
101
|
-
from: From
|
|
102
|
-
}),
|
|
103
|
-
"message.deleted": z.object({
|
|
104
|
-
id: MessageId,
|
|
105
|
-
thread: ThreadKey,
|
|
106
|
-
by: Author,
|
|
107
|
-
from: From
|
|
108
|
-
}),
|
|
109
|
-
"reaction.added": z.object({
|
|
110
|
-
id: MessageId,
|
|
111
|
-
thread: ThreadKey,
|
|
112
|
-
emoji: Emoji,
|
|
113
|
-
by: Author,
|
|
114
|
-
from: From
|
|
115
|
-
}),
|
|
116
|
-
"reaction.removed": z.object({
|
|
117
|
-
id: MessageId,
|
|
118
|
-
thread: ThreadKey,
|
|
119
|
-
emoji: Emoji,
|
|
120
|
-
by: Author,
|
|
121
|
-
from: From
|
|
122
|
-
}),
|
|
123
|
-
"labels.changed": z.object({
|
|
124
|
-
id: MessageId,
|
|
125
|
-
thread: ThreadKey,
|
|
126
|
-
set: Labels,
|
|
127
|
-
unset: z.array(LabelKey),
|
|
128
|
-
by: Author
|
|
129
|
-
}),
|
|
130
|
-
/** A thread is synced with an external channel (start a thread there) or thread (join it). */
|
|
131
|
-
"thread.synced": z.object({
|
|
132
|
-
thread: ThreadKey,
|
|
133
|
-
target: ExternalTarget,
|
|
134
|
-
cause: z.enum(["config", "request"]),
|
|
135
|
-
kind: z.enum(["channel", "thread"]).optional(),
|
|
136
|
-
by: Author
|
|
137
|
-
}),
|
|
138
|
-
/** An outbound delivery receipt: board message `id` exists on the platform as `external`. */
|
|
139
|
-
"message.synced": z.object({
|
|
140
|
-
id: MessageId,
|
|
141
|
-
thread: ThreadKey,
|
|
142
|
-
target: ExternalTarget,
|
|
143
|
-
external: z.object({
|
|
144
|
-
thread: ExternalTarget,
|
|
145
|
-
message: z.string().min(1).max(300),
|
|
146
|
-
/** Posted with its author's own platform token, so it shows as them. Older receipts have none. */
|
|
147
|
-
asUser: z.literal(true).optional()
|
|
148
|
-
}),
|
|
149
|
-
/**
|
|
150
|
-
* (server) The principal of the sync elsewhere that recorded it: its copy is that sync's to
|
|
151
|
-
* change. None for the board's own sync.
|
|
152
|
-
*/
|
|
153
|
-
syncedBy: ActorId.optional()
|
|
154
|
-
}),
|
|
46
|
+
const boardEvents = {
|
|
47
|
+
"message.posted": z.object({
|
|
48
|
+
channel: ChannelPath,
|
|
49
|
+
/** Empty only when the message has attachments. */
|
|
50
|
+
text: z.union([MessageText, z.literal("")]),
|
|
51
|
+
/** The thread root, or null for a root. */
|
|
52
|
+
re: MessageId.nullable(),
|
|
53
|
+
/** The exact message replied to, when it isn't the root. */
|
|
54
|
+
replyTo: MessageId.nullable(),
|
|
55
|
+
thread: ThreadKey,
|
|
56
|
+
labels: Labels,
|
|
57
|
+
noSync: z.boolean(),
|
|
58
|
+
mentions: z.array(ActorId),
|
|
59
|
+
by: Author,
|
|
60
|
+
origin: Origin.nullable(),
|
|
61
|
+
from: From,
|
|
62
|
+
/** Files on the message. Events written before attachments have none. */
|
|
63
|
+
attachments: z.array(Attachment).max(10).optional(),
|
|
64
|
+
/** Platform messages it links to. Events written before links have none. */
|
|
65
|
+
links: z.array(Link).max(10).optional(),
|
|
66
|
+
/** (server) Posted by a schedule: its ID and which send this is, from 0. Other messages have none. */
|
|
67
|
+
schedule: z.object({
|
|
68
|
+
id: z.number().int().positive(),
|
|
69
|
+
n: z.number().int().nonnegative()
|
|
70
|
+
}).optional(),
|
|
155
71
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
72
|
+
* (server) Its author couldn't read the channel it posted in. Where a
|
|
73
|
+
* caller can't read, it edits and deletes only these of its own. Events
|
|
74
|
+
* from before channel access have none: everyone read everything then.
|
|
159
75
|
*/
|
|
160
|
-
|
|
161
|
-
id: MessageId,
|
|
162
|
-
thread: ThreadKey,
|
|
163
|
-
target: ExternalTarget,
|
|
164
|
-
files: z.array(z.string().min(1).max(300)).max(10).nullable(),
|
|
165
|
-
/** (server) As on `message.synced`: only the sync that recorded the copy sets its files. */
|
|
166
|
-
syncedBy: ActorId.optional()
|
|
167
|
-
}),
|
|
168
|
-
/** A delivery that gave up: board message `id` didn't reach `target`, and `error` says why. */
|
|
169
|
-
"message.sync_failed": z.object({
|
|
170
|
-
id: MessageId,
|
|
171
|
-
thread: ThreadKey,
|
|
172
|
-
target: ExternalTarget,
|
|
173
|
-
error: z.string().min(1).max(500)
|
|
174
|
-
}),
|
|
175
|
-
"actor.seen": z.object({
|
|
176
|
-
actor: ActorId,
|
|
177
|
-
kind: SeenActorKind,
|
|
178
|
-
handle: Handle,
|
|
179
|
-
name: z.string().min(1).max(128).refine(storable, "name has a NUL character or a lone surrogate"),
|
|
180
|
-
avatar: z.string().url().nullable(),
|
|
181
|
-
/**
|
|
182
|
-
* (server) A principal a host's `authenticate` made, or one of its profiles: someone the board
|
|
183
|
-
* signs in, whatever its ID or kind, so sync never writes as it. Others have none.
|
|
184
|
-
*/
|
|
185
|
-
host: z.literal(true).optional()
|
|
186
|
-
}),
|
|
76
|
+
blind: z.literal(true).optional(),
|
|
187
77
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* `method` says how it was proved: a one-time code posted from that account,
|
|
191
|
-
* or the account's own sign-in (`consent`).
|
|
78
|
+
* (server) The principal of the sync elsewhere that imported it, whose copy `origin` names:
|
|
79
|
+
* that sync's to act on. None for the board's own sync, or a write on the board.
|
|
192
80
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
81
|
+
importedBy: ActorId.optional()
|
|
82
|
+
}),
|
|
83
|
+
/** A message's links changed: an edit linked something new, or the platform sent what a link says. */
|
|
84
|
+
"message.linked": z.object({
|
|
85
|
+
id: MessageId,
|
|
86
|
+
thread: ThreadKey,
|
|
87
|
+
links: z.array(Link).max(10),
|
|
88
|
+
by: Author,
|
|
89
|
+
from: From
|
|
90
|
+
}),
|
|
91
|
+
"message.edited": z.object({
|
|
92
|
+
id: MessageId,
|
|
93
|
+
thread: ThreadKey,
|
|
94
|
+
text: MessageText,
|
|
95
|
+
mentions: z.array(ActorId),
|
|
96
|
+
by: Author,
|
|
97
|
+
from: From
|
|
98
|
+
}),
|
|
99
|
+
"message.deleted": z.object({
|
|
100
|
+
id: MessageId,
|
|
101
|
+
thread: ThreadKey,
|
|
102
|
+
by: Author,
|
|
103
|
+
from: From
|
|
104
|
+
}),
|
|
105
|
+
"reaction.added": z.object({
|
|
106
|
+
id: MessageId,
|
|
107
|
+
thread: ThreadKey,
|
|
108
|
+
emoji: Emoji,
|
|
109
|
+
by: Author,
|
|
110
|
+
from: From
|
|
111
|
+
}),
|
|
112
|
+
"reaction.removed": z.object({
|
|
113
|
+
id: MessageId,
|
|
114
|
+
thread: ThreadKey,
|
|
115
|
+
emoji: Emoji,
|
|
116
|
+
by: Author,
|
|
117
|
+
from: From
|
|
118
|
+
}),
|
|
119
|
+
"labels.changed": z.object({
|
|
120
|
+
id: MessageId,
|
|
121
|
+
thread: ThreadKey,
|
|
122
|
+
set: Labels,
|
|
123
|
+
unset: z.array(LabelKey),
|
|
124
|
+
by: Author
|
|
125
|
+
}),
|
|
126
|
+
/** A thread is synced with an external channel (start a thread there) or thread (join it). */
|
|
127
|
+
"thread.synced": z.object({
|
|
128
|
+
thread: ThreadKey,
|
|
129
|
+
target: ExternalTarget,
|
|
130
|
+
cause: z.enum(["config", "request"]),
|
|
131
|
+
kind: z.enum(["channel", "thread"]).optional(),
|
|
132
|
+
by: Author
|
|
133
|
+
}),
|
|
134
|
+
/** An outbound delivery receipt: board message `id` exists on the platform as `external`. */
|
|
135
|
+
"message.synced": z.object({
|
|
136
|
+
id: MessageId,
|
|
137
|
+
thread: ThreadKey,
|
|
138
|
+
target: ExternalTarget,
|
|
139
|
+
external: z.object({
|
|
140
|
+
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()
|
|
198
144
|
}),
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
*/
|
|
218
|
-
const ExternalFile = z.object({
|
|
219
|
-
type: z.enum([
|
|
220
|
-
"image",
|
|
221
|
-
"file",
|
|
222
|
-
"video",
|
|
223
|
-
"audio"
|
|
224
|
-
]),
|
|
225
|
-
name: z.string().max(1e3).optional(),
|
|
226
|
-
mimeType: z.string().max(255).optional(),
|
|
227
|
-
size: z.number().int().nonnegative().optional(),
|
|
228
|
-
url: z.string().max(4e3).optional(),
|
|
229
|
-
fetchMetadata: z.record(z.string(), z.string().max(4e3)).optional()
|
|
230
|
-
});
|
|
231
|
-
/**
|
|
232
|
-
* A platform message a platform message links to, at intake: where it lives
|
|
233
|
-
* (`thread`, with the sync key prefix, and `message`), and what it said when the
|
|
234
|
-
* platform sent that along (Slack's unfurl).
|
|
235
|
-
*/
|
|
236
|
-
const ExternalLink = z.object({
|
|
237
|
-
url: z.string().min(1).max(4e3),
|
|
238
|
-
thread: ExternalTarget,
|
|
239
|
-
message: z.string().min(1).max(300),
|
|
240
|
-
snapshot: LinkSnapshot.nullable()
|
|
241
|
-
});
|
|
242
|
-
const ExternalLocation = z.object({
|
|
243
|
-
channel: ExternalTarget,
|
|
244
|
-
thread: ExternalTarget,
|
|
245
|
-
subthread: ExternalTarget.nullable()
|
|
246
|
-
});
|
|
247
|
-
/** A platform event, normalized at intake. Thread IDs are external IDs with the sync key prefix. */
|
|
248
|
-
const ExternalEvent = z.discriminatedUnion("kind", [
|
|
249
|
-
z.object({
|
|
250
|
-
kind: z.literal("message"),
|
|
251
|
-
...ExternalLocation.shape,
|
|
252
|
-
message: z.string(),
|
|
253
|
-
isRoot: z.boolean(),
|
|
254
|
-
text: z.string(),
|
|
255
|
-
context: z.string().nullable(),
|
|
256
|
-
author: ExternalAuthor,
|
|
257
|
-
/** Files on the message, fetched when it lands on the board. Older events have none. */
|
|
258
|
-
files: z.array(ExternalFile).max(20).optional(),
|
|
259
|
-
/** Platform messages it links to. Older events have none. */
|
|
260
|
-
links: z.array(ExternalLink).max(10).optional()
|
|
145
|
+
/**
|
|
146
|
+
* (server) The principal of the sync elsewhere that recorded it: its copy is that sync's to
|
|
147
|
+
* change. None for the board's own sync.
|
|
148
|
+
*/
|
|
149
|
+
syncedBy: ActorId.optional()
|
|
150
|
+
}),
|
|
151
|
+
/**
|
|
152
|
+
* Board message `id`'s files reached `target` after its text: `files` are the
|
|
153
|
+
* platform's IDs for them, or null when the platform refused files and the
|
|
154
|
+
* text got a line for each instead.
|
|
155
|
+
*/
|
|
156
|
+
"message.files_synced": z.object({
|
|
157
|
+
id: MessageId,
|
|
158
|
+
thread: ThreadKey,
|
|
159
|
+
target: ExternalTarget,
|
|
160
|
+
files: z.array(z.string().min(1).max(300)).max(10).nullable(),
|
|
161
|
+
/** (server) As on `message.synced`: only the sync that recorded the copy sets its files. */
|
|
162
|
+
syncedBy: ActorId.optional()
|
|
261
163
|
}),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
links: z.array(ExternalLink).max(10).optional(),
|
|
269
|
-
/** Only the links changed (the platform sent what they say), so `text` isn't news. */
|
|
270
|
-
linksOnly: z.boolean().optional()
|
|
164
|
+
/** A delivery that gave up: board message `id` didn't reach `target`, and `error` says why. */
|
|
165
|
+
"message.sync_failed": z.object({
|
|
166
|
+
id: MessageId,
|
|
167
|
+
thread: ThreadKey,
|
|
168
|
+
target: ExternalTarget,
|
|
169
|
+
error: z.string().min(1).max(500)
|
|
271
170
|
}),
|
|
272
|
-
z.object({
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
171
|
+
"actor.seen": z.object({
|
|
172
|
+
actor: ActorId,
|
|
173
|
+
kind: SeenActorKind,
|
|
174
|
+
handle: Handle,
|
|
175
|
+
name: z.string().min(1).max(128).refine(storable, "name has a NUL character or a lone surrogate"),
|
|
176
|
+
avatar: z.string().url().nullable(),
|
|
177
|
+
/**
|
|
178
|
+
* (server) A principal a host's `authenticate` made, or one of its profiles: someone the board
|
|
179
|
+
* signs in, whatever its ID or kind, so sync never writes as it. Others have none.
|
|
180
|
+
*/
|
|
181
|
+
host: z.literal(true).optional()
|
|
276
182
|
}),
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
284
194
|
}),
|
|
285
|
-
z.object({
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
...ExternalLocation.omit({ subthread: true }).shape,
|
|
289
|
-
type: z.string(),
|
|
290
|
-
title: z.string(),
|
|
291
|
-
url: z.string().nullable(),
|
|
292
|
-
labels: z.record(z.string(), z.string()),
|
|
293
|
-
unset: z.array(z.string()),
|
|
294
|
-
/** Key prefixes the platform owns on the root, e.g. `tag.`: keys under them that aren't in `labels` are unset. */
|
|
295
|
-
owns: z.array(z.string()).optional(),
|
|
296
|
-
/** A reply to add to the thread, e.g. "merged by julian". */
|
|
297
|
-
reply: z.string().nullable(),
|
|
298
|
-
/** Create the root if the board hasn't seen this thread. */
|
|
299
|
-
createRoot: z.boolean(),
|
|
300
|
-
author: ExternalAuthor
|
|
195
|
+
"actor.unlinked": z.object({
|
|
196
|
+
actor: ActorId,
|
|
197
|
+
by: Author
|
|
301
198
|
})
|
|
302
|
-
|
|
199
|
+
};
|
|
200
|
+
const BOARD_SESSION = "board";
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/contract/contract.ts
|
|
203
|
+
/** The board's one log. Every board fact is an event in session `board`. */
|
|
204
|
+
const boardContract = contract({
|
|
205
|
+
name: "board",
|
|
206
|
+
events: boardEvents
|
|
207
|
+
});
|
|
303
208
|
/** Raw platform events live outside the board log, one session per external channel. */
|
|
304
209
|
const inboxContract = contract({
|
|
305
210
|
name: "board-inbox",
|
|
@@ -563,4 +468,72 @@ const identityContract = contract({
|
|
|
563
468
|
});
|
|
564
469
|
const IDENTITY_SESSION = "identity";
|
|
565
470
|
//#endregion
|
|
566
|
-
|
|
471
|
+
//#region src/contract/reducers.ts
|
|
472
|
+
const messagesReducer = reducer({
|
|
473
|
+
contract: boardContract,
|
|
474
|
+
name: "board.messages.v5",
|
|
475
|
+
initialState: {
|
|
476
|
+
byId: {},
|
|
477
|
+
replies: {}
|
|
478
|
+
}
|
|
479
|
+
}).fold(foldMessages);
|
|
480
|
+
reducer({
|
|
481
|
+
contract: boardContract,
|
|
482
|
+
name: "board.posted.v1",
|
|
483
|
+
initialState: {}
|
|
484
|
+
}).fold((state, event) => {
|
|
485
|
+
if (event.type === "message.posted") setOwn(state, event.id, event.index);
|
|
486
|
+
return state;
|
|
487
|
+
});
|
|
488
|
+
const channelsReducer = reducer({
|
|
489
|
+
contract: boardContract,
|
|
490
|
+
name: "board.channels.v1",
|
|
491
|
+
initialState: {}
|
|
492
|
+
}).fold((state, event) => {
|
|
493
|
+
if (event.type !== "message.posted") return state;
|
|
494
|
+
const { channel, re } = event.payload;
|
|
495
|
+
const entry = own(state, channel) ?? setOwn(state, channel, {
|
|
496
|
+
path: channel,
|
|
497
|
+
lastIndex: 0,
|
|
498
|
+
messages: 0,
|
|
499
|
+
threads: 0
|
|
500
|
+
});
|
|
501
|
+
entry.lastIndex = event.index;
|
|
502
|
+
entry.messages += 1;
|
|
503
|
+
if (re === null) entry.threads += 1;
|
|
504
|
+
return state;
|
|
505
|
+
});
|
|
506
|
+
const actorsReducer = reducer({
|
|
507
|
+
contract: boardContract,
|
|
508
|
+
name: "board.actors.v2",
|
|
509
|
+
initialState: {
|
|
510
|
+
byId: {},
|
|
511
|
+
byHandle: {},
|
|
512
|
+
links: {}
|
|
513
|
+
}
|
|
514
|
+
}).fold(foldActors);
|
|
515
|
+
const identityCodesReducer = reducer({
|
|
516
|
+
contract: identityContract,
|
|
517
|
+
name: "board.identity-codes.v1",
|
|
518
|
+
initialState: {}
|
|
519
|
+
}).fold((state, event) => {
|
|
520
|
+
if (event.type === "identity.code_issued") {
|
|
521
|
+
const { hash, principal, sync, expiresAt } = event.payload;
|
|
522
|
+
state[hash] ??= {
|
|
523
|
+
principal,
|
|
524
|
+
sync,
|
|
525
|
+
expiresAt,
|
|
526
|
+
usedBy: null,
|
|
527
|
+
usedIn: null
|
|
528
|
+
};
|
|
529
|
+
} else if (event.type === "identity.code_used") {
|
|
530
|
+
const code = state[event.payload.hash];
|
|
531
|
+
if (code && code.usedBy === null) {
|
|
532
|
+
code.usedBy = event.payload.actor;
|
|
533
|
+
code.usedIn = event.payload.message ?? null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return state;
|
|
537
|
+
});
|
|
538
|
+
//#endregion
|
|
539
|
+
export { BOARD_SESSION as _, IDENTITY_SESSION as a, SCHEDULES_SESSION as c, inboxContract as d, inboxSession as f, watchSession as g, watchContract as h, messagesReducer as i, boardContract as l, schedulesContract as m, channelsReducer as n, INBOXES_SESSION as o, registryContract as p, identityCodesReducer as r, REGISTRY_SESSION as s, actorsReducer as t, identityContract as u };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/experimental-a2@0.22.0_@upstash+qstash@2.12.0_@vercel+queue@0.7.0_ai@7.0.130_zod@4.6.5__52060f00b21aecfab049c27fa3abd2f9/node_modules/experimental-a2/dist/retryable-lazy-DZWmHpii.js
|
|
2
|
+
const retryableLazy = (initialize) => {
|
|
3
|
+
let current = null;
|
|
4
|
+
const get = () => {
|
|
5
|
+
if (current) return current;
|
|
6
|
+
const attempt = Promise.resolve().then(initialize);
|
|
7
|
+
current = attempt;
|
|
8
|
+
attempt.catch(() => {
|
|
9
|
+
if (current === attempt) current = null;
|
|
10
|
+
});
|
|
11
|
+
return attempt;
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
get,
|
|
15
|
+
peek: () => current
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { retryableLazy as t };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/experimental-a2@0.22.0_@upstash+qstash@2.12.0_@vercel+queue@0.7.0_ai@7.0.130_zod@4.6.5__52060f00b21aecfab049c27fa3abd2f9/node_modules/experimental-a2/dist/scheduler-task-X1SToDjP.js
|
|
2
|
+
const recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3
|
+
const validName = (value) => typeof value === "string" && value.length > 0;
|
|
4
|
+
const validDueAt = (value) => typeof value === "number" && Number.isInteger(value) && value % 1e3 === 0;
|
|
5
|
+
const validIndex = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
6
|
+
const baseTask = (record) => Object.hasOwn(record, "contract") && Object.hasOwn(record, "sessionId") && validName(record["contract"]) && validName(record["sessionId"]) ? {
|
|
7
|
+
contract: record["contract"],
|
|
8
|
+
sessionId: record["sessionId"]
|
|
9
|
+
} : null;
|
|
10
|
+
/**
|
|
11
|
+
* Validate one delivered task.
|
|
12
|
+
* Adapters call this only after their transport authentication has succeeded.
|
|
13
|
+
*/
|
|
14
|
+
function normalizeSchedulerTask(value) {
|
|
15
|
+
const record = recordOf(value);
|
|
16
|
+
if (!record) return null;
|
|
17
|
+
const base = baseTask(record);
|
|
18
|
+
if (!base) return null;
|
|
19
|
+
if (record["version"] !== 1) return null;
|
|
20
|
+
if (record["kind"] === "drain") {
|
|
21
|
+
const dueAt = record["dueAt"];
|
|
22
|
+
if (!validDueAt(dueAt)) return null;
|
|
23
|
+
return {
|
|
24
|
+
version: 1,
|
|
25
|
+
kind: "drain",
|
|
26
|
+
...base,
|
|
27
|
+
dueAt
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (record["kind"] === "clear") {
|
|
31
|
+
const { dueAt, id, ifLastIndex } = record;
|
|
32
|
+
if (!validName(id) || !validDueAt(dueAt) || !validIndex(ifLastIndex)) return null;
|
|
33
|
+
return {
|
|
34
|
+
version: 1,
|
|
35
|
+
kind: "clear",
|
|
36
|
+
id,
|
|
37
|
+
...base,
|
|
38
|
+
dueAt,
|
|
39
|
+
ifLastIndex
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (record["kind"] !== "append") return null;
|
|
43
|
+
const { dueAt, events, id, originIndex } = record;
|
|
44
|
+
if (!validName(id) || !validDueAt(dueAt)) return null;
|
|
45
|
+
if (originIndex !== void 0 && (!validIndex(originIndex) || originIndex === 0)) return null;
|
|
46
|
+
if (!Array.isArray(events) || events.length === 0) return null;
|
|
47
|
+
const parsedEvents = [];
|
|
48
|
+
for (const event of events) {
|
|
49
|
+
const item = recordOf(event);
|
|
50
|
+
if (!item || typeof item["id"] !== "string" || typeof item["type"] !== "string" || !Object.hasOwn(item, "payload")) return null;
|
|
51
|
+
parsedEvents.push({
|
|
52
|
+
id: item["id"],
|
|
53
|
+
type: item["type"],
|
|
54
|
+
payload: item["payload"]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (new Set(parsedEvents.map((event) => event.id)).size !== parsedEvents.length) return null;
|
|
58
|
+
return {
|
|
59
|
+
version: 1,
|
|
60
|
+
kind: "append",
|
|
61
|
+
id,
|
|
62
|
+
...base,
|
|
63
|
+
dueAt,
|
|
64
|
+
events: parsedEvents,
|
|
65
|
+
...originIndex === void 0 ? {} : { originIndex }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { normalizeSchedulerTask as t };
|