@vercel/board 0.16.0 → 0.18.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-ChRLRupa.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-CMlL3yq3.mjs} +2 -2
- package/dist/{backend-http-BVRno-ca.mjs → backend-http-Ddzd5kTS.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-CgjfMrQ-.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-C5Cpz3sa.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-BGYw0uNL.mjs} +5 -5
- package/dist/{server-jwPY7_wi.mjs → server-DPxBSogx.mjs} +4 -4
- package/dist/server-DtFrWxi3-Boa6gdWO.mjs +3602 -0
- package/dist/server-qgez0Xny-D5mLfqFQ.d.mts +385 -0
- 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-linear.d.mts +219 -0
- package/dist/sync-linear.mjs +311 -0
- 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-RLtnSF-t.mjs} +233 -28
- package/docs/01-board/01-mounting.md +1 -1
- package/docs/02-sync/01-overview.md +72 -48
- package/docs/02-sync/02-functions.md +398 -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 +60 -27
- package/docs/02-sync/07-linear.md +129 -0
- 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 +31 -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,5 +1,493 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { t as A2Error } from "../session-clear-wHd5GCpZ-BnKSxReK.mjs";
|
|
2
|
+
import { n as NOTIFY_TIMINGS } from "../internal-CvsrprnU-CRY89I-b.mjs";
|
|
3
|
+
import { n as defaultSleep } from "../store-polling-BZt2Rf3D-BiEM6_HB.mjs";
|
|
4
|
+
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "../store-N8PXxDAS-BWBgzBHp.mjs";
|
|
5
|
+
import { n as createRedisNotifyStore, t as redisTables } from "../redis-tables-DN9PfIDi.mjs";
|
|
6
|
+
//#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/store-redis-http.js
|
|
7
|
+
function redisHttpSubscriptions({ url, token, onReconnect, fallback }) {
|
|
8
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
9
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
10
|
+
let closed = false;
|
|
11
|
+
let supported = false;
|
|
12
|
+
const subscribe = (channel) => {
|
|
13
|
+
if (fallback.enabled()) return Promise.resolve();
|
|
14
|
+
const existing = subscriptions.get(channel);
|
|
15
|
+
if (existing) return existing.ready;
|
|
16
|
+
let resolve;
|
|
17
|
+
let reject;
|
|
18
|
+
const ready = new Promise((yes, no) => {
|
|
19
|
+
resolve = yes;
|
|
20
|
+
reject = no;
|
|
21
|
+
});
|
|
22
|
+
let stopped = false;
|
|
23
|
+
let connected = false;
|
|
24
|
+
let request;
|
|
25
|
+
let retry;
|
|
26
|
+
const subscription = {
|
|
27
|
+
ready,
|
|
28
|
+
stop() {
|
|
29
|
+
stopped = true;
|
|
30
|
+
request?.abort();
|
|
31
|
+
retry?.cancel();
|
|
32
|
+
resolve();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
subscriptions.set(channel, subscription);
|
|
36
|
+
const run = async () => {
|
|
37
|
+
let delay = 100;
|
|
38
|
+
for (;;) {
|
|
39
|
+
if (stopped) return;
|
|
40
|
+
request = new AbortController();
|
|
41
|
+
const timeout = setTimeout(() => request?.abort(), NOTIFY_TIMINGS.safetyReadMs);
|
|
42
|
+
let reader;
|
|
43
|
+
let connectedAt;
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`${url.replace(/\/$/, "")}/subscribe/${encodeURIComponent(channel)}`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
authorization: `Bearer ${token}`,
|
|
49
|
+
accept: "text/event-stream"
|
|
50
|
+
},
|
|
51
|
+
signal: request.signal
|
|
52
|
+
});
|
|
53
|
+
if (!supported && [
|
|
54
|
+
404,
|
|
55
|
+
405,
|
|
56
|
+
501
|
|
57
|
+
].includes(response.status)) {
|
|
58
|
+
await response.body?.cancel();
|
|
59
|
+
await fallback.checkCommands();
|
|
60
|
+
if (stopped) return;
|
|
61
|
+
if (!supported) {
|
|
62
|
+
fallback.enable();
|
|
63
|
+
for (const active of subscriptions.values()) active.stop();
|
|
64
|
+
subscriptions.clear();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok || !response.headers.get("content-type")?.startsWith("text/event-stream") || !response.body) {
|
|
69
|
+
await response.body?.cancel();
|
|
70
|
+
throw new A2Error("STORE_UNAVAILABLE", `redis rest subscription failed: status ${response.status}`);
|
|
71
|
+
}
|
|
72
|
+
reader = response.body.getReader();
|
|
73
|
+
const decoder = new TextDecoder();
|
|
74
|
+
let buffer = "";
|
|
75
|
+
let data = [];
|
|
76
|
+
let acknowledged = false;
|
|
77
|
+
for (;;) {
|
|
78
|
+
if (stopped) break;
|
|
79
|
+
const chunk = await reader.read();
|
|
80
|
+
if (chunk.done) break;
|
|
81
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
82
|
+
let newline;
|
|
83
|
+
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
84
|
+
const line = buffer.slice(0, newline).replace(/\r$/, "");
|
|
85
|
+
buffer = buffer.slice(newline + 1);
|
|
86
|
+
if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
|
|
87
|
+
else if (line === "" && data.length > 0) {
|
|
88
|
+
const message = data.join("\n");
|
|
89
|
+
data = [];
|
|
90
|
+
if (message.startsWith(`subscribe,${channel},`)) {
|
|
91
|
+
if (acknowledged) continue;
|
|
92
|
+
acknowledged = true;
|
|
93
|
+
supported = true;
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
connectedAt = Date.now();
|
|
96
|
+
resolve();
|
|
97
|
+
if (connected) onReconnect(channel);
|
|
98
|
+
connected = true;
|
|
99
|
+
} else if (message.startsWith(`message,${channel},`)) {
|
|
100
|
+
const payload = message.slice(`message,${channel},`.length);
|
|
101
|
+
for (const listener of listeners) listener(channel, payload);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!connected && !stopped) throw new A2Error("STORE_UNAVAILABLE", "redis rest subscription ended before acknowledgement");
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (!connected && !stopped) {
|
|
109
|
+
reject(error);
|
|
110
|
+
if (subscriptions.get(channel) === subscription) subscriptions.delete(channel);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
request.abort();
|
|
116
|
+
await reader?.cancel().catch(() => {});
|
|
117
|
+
reader?.releaseLock();
|
|
118
|
+
}
|
|
119
|
+
if (stopped) return;
|
|
120
|
+
if (connectedAt !== void 0 && Date.now() - connectedAt >= NOTIFY_TIMINGS.safetyReadMs) delay = 100;
|
|
121
|
+
retry = defaultSleep(delay);
|
|
122
|
+
await retry.promise;
|
|
123
|
+
delay = Math.min(delay * 2, NOTIFY_TIMINGS.safetyReadMs);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
run();
|
|
127
|
+
return ready;
|
|
128
|
+
};
|
|
129
|
+
const connection = {
|
|
130
|
+
call(command, ...args) {
|
|
131
|
+
if (closed) return Promise.reject(new A2Error("STORE_UNAVAILABLE", "the store was closed"));
|
|
132
|
+
const channel = String(args[0]);
|
|
133
|
+
if (command === "echo") return Promise.resolve(args[0]);
|
|
134
|
+
if (command === "subscribe") return subscribe(channel);
|
|
135
|
+
if (command === "unsubscribe") {
|
|
136
|
+
subscriptions.get(channel)?.stop();
|
|
137
|
+
subscriptions.delete(channel);
|
|
138
|
+
return Promise.resolve(null);
|
|
139
|
+
}
|
|
140
|
+
return Promise.reject(/* @__PURE__ */ new Error(`unsupported subscription command: ${command}`));
|
|
141
|
+
},
|
|
142
|
+
duplicate: () => connection,
|
|
143
|
+
on(_event, listener) {
|
|
144
|
+
listeners.add(listener);
|
|
145
|
+
},
|
|
146
|
+
disconnect() {
|
|
147
|
+
closed = true;
|
|
148
|
+
for (const subscription of subscriptions.values()) subscription.stop();
|
|
149
|
+
subscriptions.clear();
|
|
150
|
+
listeners.clear();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return connection;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Index just past the array or string `scan` is inside, or -1 when `text`
|
|
157
|
+
* ends first; `scan` then carries the state into the next chunk. Strings
|
|
158
|
+
* are skipped with `indexOf`, so the per-character loop only walks
|
|
159
|
+
* structure. A quote is escaped when an odd run of backslashes precedes
|
|
160
|
+
* it; a run is counted back only to where the string scan resumed, and
|
|
161
|
+
* `escaped` carries an odd trailing run across the chunk boundary.
|
|
162
|
+
*/
|
|
163
|
+
function scanValue(text, from, scan) {
|
|
164
|
+
let at = from;
|
|
165
|
+
while (at < text.length) {
|
|
166
|
+
if (scan.quoted) {
|
|
167
|
+
if (scan.escaped) {
|
|
168
|
+
scan.escaped = false;
|
|
169
|
+
at++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const quote = text.indexOf("\"", at);
|
|
173
|
+
const end = quote < 0 ? text.length : quote;
|
|
174
|
+
let run = 0;
|
|
175
|
+
while (end - run - 1 >= at && text.charCodeAt(end - run - 1) === 92) run++;
|
|
176
|
+
if (quote < 0) {
|
|
177
|
+
scan.escaped = run % 2 === 1;
|
|
178
|
+
return -1;
|
|
179
|
+
}
|
|
180
|
+
at = quote + 1;
|
|
181
|
+
if (run % 2 === 1) continue;
|
|
182
|
+
scan.quoted = false;
|
|
183
|
+
if (scan.depth === 0) return at;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const code = text.charCodeAt(at++);
|
|
187
|
+
if (code === 34) scan.quoted = true;
|
|
188
|
+
else if (code === 91 || code === 123) scan.depth++;
|
|
189
|
+
else if ((code === 93 || code === 125) && --scan.depth === 0) return at;
|
|
190
|
+
}
|
|
191
|
+
return -1;
|
|
192
|
+
}
|
|
193
|
+
var JsonReply = class {
|
|
194
|
+
reader;
|
|
195
|
+
decoder = new TextDecoder("utf-8", { fatal: true });
|
|
196
|
+
signal;
|
|
197
|
+
text = "";
|
|
198
|
+
position = 0;
|
|
199
|
+
done = false;
|
|
200
|
+
abort;
|
|
201
|
+
array = "first";
|
|
202
|
+
scan = {
|
|
203
|
+
depth: 0,
|
|
204
|
+
quoted: false,
|
|
205
|
+
escaped: false
|
|
206
|
+
};
|
|
207
|
+
fragments = [];
|
|
208
|
+
failure;
|
|
209
|
+
constructor(options) {
|
|
210
|
+
const { body, signal } = options;
|
|
211
|
+
this.reader = body.getReader();
|
|
212
|
+
this.signal = signal;
|
|
213
|
+
this.abort = () => {
|
|
214
|
+
this.reader.cancel(signal.reason).catch(() => {});
|
|
215
|
+
};
|
|
216
|
+
signal.addEventListener("abort", this.abort, { once: true });
|
|
217
|
+
if (signal.aborted) this.abort();
|
|
218
|
+
}
|
|
219
|
+
async available() {
|
|
220
|
+
while (this.position === this.text.length && !this.done) {
|
|
221
|
+
this.signal.throwIfAborted();
|
|
222
|
+
const chunk = await this.reader.read();
|
|
223
|
+
this.signal.throwIfAborted();
|
|
224
|
+
this.done = Boolean(chunk.done);
|
|
225
|
+
this.text = chunk.done ? this.decoder.decode() : this.decoder.decode(chunk.value, { stream: true });
|
|
226
|
+
this.position = 0;
|
|
227
|
+
}
|
|
228
|
+
return this.position < this.text.length;
|
|
229
|
+
}
|
|
230
|
+
async peek() {
|
|
231
|
+
while (await this.available()) while (this.position < this.text.length) {
|
|
232
|
+
const value = this.text[this.position];
|
|
233
|
+
if (value !== " " && value !== "\n" && value !== "\r" && value !== " ") return value;
|
|
234
|
+
this.position++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async expect(value) {
|
|
238
|
+
if (await this.peek() !== value) throw new Error("Invalid or truncated Redis HTTP reply");
|
|
239
|
+
this.position++;
|
|
240
|
+
}
|
|
241
|
+
async value() {
|
|
242
|
+
const first = await this.peek();
|
|
243
|
+
if (first !== "[" && first !== "\"") throw new Error("Invalid Redis HTTP reply value");
|
|
244
|
+
const scan = {
|
|
245
|
+
depth: 0,
|
|
246
|
+
quoted: false,
|
|
247
|
+
escaped: false
|
|
248
|
+
};
|
|
249
|
+
const fragments = [];
|
|
250
|
+
while (await this.available()) {
|
|
251
|
+
const start = this.position;
|
|
252
|
+
const end = scanValue(this.text, start, scan);
|
|
253
|
+
this.position = end < 0 ? this.text.length : end;
|
|
254
|
+
fragments.push(this.text.slice(start, this.position));
|
|
255
|
+
if (end >= 0) return JSON.parse(fragments.join(""));
|
|
256
|
+
}
|
|
257
|
+
throw new Error("Truncated Redis HTTP reply value");
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The next entries of an open array, parsed together: every entry that
|
|
261
|
+
* completes in the current transport chunk, reading further chunks only
|
|
262
|
+
* until one has completed. Entries before a malformed one are returned
|
|
263
|
+
* first; the malformed one fails the next call. `undefined` once the
|
|
264
|
+
* array has closed.
|
|
265
|
+
*/
|
|
266
|
+
async entries() {
|
|
267
|
+
if (this.failure !== void 0) throw this.failure;
|
|
268
|
+
const texts = [];
|
|
269
|
+
const fail = (message) => {
|
|
270
|
+
const error = new Error(message);
|
|
271
|
+
if (texts.length === 0) throw error;
|
|
272
|
+
this.failure = error;
|
|
273
|
+
};
|
|
274
|
+
while (this.array !== "closed") {
|
|
275
|
+
if (this.position === this.text.length) {
|
|
276
|
+
if (texts.length > 0) break;
|
|
277
|
+
if (!await this.available()) throw new Error(this.array === "entry" ? "Truncated Redis HTTP reply value" : this.array === "separator" ? "Invalid or truncated Redis HTTP reply" : "Invalid Redis HTTP reply value");
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (this.array === "entry") {
|
|
281
|
+
const start = this.position;
|
|
282
|
+
const end = scanValue(this.text, start, this.scan);
|
|
283
|
+
this.position = end < 0 ? this.text.length : end;
|
|
284
|
+
this.fragments.push(this.text.slice(start, this.position));
|
|
285
|
+
if (end < 0) continue;
|
|
286
|
+
texts.push(this.fragments.length === 1 ? this.fragments[0] : this.fragments.join(""));
|
|
287
|
+
this.fragments = [];
|
|
288
|
+
this.array = "separator";
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const value = this.text[this.position];
|
|
292
|
+
if (value === " " || value === "\n" || value === "\r" || value === " ") {
|
|
293
|
+
this.position++;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (this.array === "separator" || this.array === "first" && value === "]") {
|
|
297
|
+
if (value === "]") this.array = "closed";
|
|
298
|
+
else if (value === ",") this.array = "value";
|
|
299
|
+
else {
|
|
300
|
+
fail("Invalid or truncated Redis HTTP reply");
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
this.position++;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (value !== "[" && value !== "\"") {
|
|
307
|
+
fail("Invalid Redis HTTP reply value");
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
this.scan = {
|
|
311
|
+
depth: 0,
|
|
312
|
+
quoted: false,
|
|
313
|
+
escaped: false
|
|
314
|
+
};
|
|
315
|
+
this.array = "entry";
|
|
316
|
+
}
|
|
317
|
+
return texts.length > 0 ? this.parse(texts) : void 0;
|
|
318
|
+
}
|
|
319
|
+
parse(texts) {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(`[${texts.join(",")}]`);
|
|
322
|
+
} catch {
|
|
323
|
+
const parsed = [];
|
|
324
|
+
for (const text of texts) try {
|
|
325
|
+
parsed.push(JSON.parse(text));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
this.failure = error;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
if (parsed.length === 0) throw this.failure;
|
|
331
|
+
return parsed;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async end() {
|
|
335
|
+
await this.expect("}");
|
|
336
|
+
if (await this.peek() !== void 0) throw new Error("Trailing data in Redis HTTP reply");
|
|
337
|
+
}
|
|
338
|
+
async close() {
|
|
339
|
+
this.signal.removeEventListener("abort", this.abort);
|
|
340
|
+
try {
|
|
341
|
+
await this.reader.cancel();
|
|
342
|
+
} finally {
|
|
343
|
+
this.reader.releaseLock();
|
|
344
|
+
this.text = "";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* The `result` array of an Upstash REST reply, in batches of the entries
|
|
350
|
+
* that complete in each transport chunk.
|
|
351
|
+
*/
|
|
352
|
+
async function* redisHttpReply(options) {
|
|
353
|
+
const reply = new JsonReply(options);
|
|
354
|
+
try {
|
|
355
|
+
await reply.expect("{");
|
|
356
|
+
const key = await reply.value();
|
|
357
|
+
await reply.expect(":");
|
|
358
|
+
if (key === "error") {
|
|
359
|
+
const error = await reply.value();
|
|
360
|
+
if (typeof error !== "string") throw new Error("Invalid Redis HTTP error");
|
|
361
|
+
await reply.end();
|
|
362
|
+
throw new Error(`Redis HTTP command failed: ${String(error)}`);
|
|
363
|
+
}
|
|
364
|
+
if (key !== "result") throw new Error("Invalid Redis HTTP reply envelope");
|
|
365
|
+
await reply.expect("[");
|
|
366
|
+
for (;;) {
|
|
367
|
+
const entries = await reply.entries();
|
|
368
|
+
if (entries === void 0) break;
|
|
369
|
+
yield entries;
|
|
370
|
+
}
|
|
371
|
+
await reply.end();
|
|
372
|
+
} finally {
|
|
373
|
+
await reply.close();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const REDIS_HTTP_STREAM_READ_COUNT = 1e3;
|
|
377
|
+
async function* readRedisStreamPage({ key, from, signal, url, token, count }) {
|
|
378
|
+
signal.throwIfAborted();
|
|
379
|
+
const response = await fetch(url, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
headers: {
|
|
382
|
+
authorization: `Bearer ${token}`,
|
|
383
|
+
"content-type": "application/json"
|
|
384
|
+
},
|
|
385
|
+
body: JSON.stringify([
|
|
386
|
+
"XRANGE",
|
|
387
|
+
key,
|
|
388
|
+
from,
|
|
389
|
+
"+",
|
|
390
|
+
"COUNT",
|
|
391
|
+
String(count)
|
|
392
|
+
]),
|
|
393
|
+
signal
|
|
394
|
+
});
|
|
395
|
+
if (!response.ok || !response.body) {
|
|
396
|
+
const body = await response.json().catch(() => null);
|
|
397
|
+
throw new A2Error("STORE_UNAVAILABLE", `redis rest command failed: ${typeof body?.error === "string" ? body.error : `status ${response.status}`}`);
|
|
398
|
+
}
|
|
399
|
+
let last = Number(from.slice(0, -2)) - 1;
|
|
400
|
+
for await (const batch of redisHttpReply({
|
|
401
|
+
body: response.body,
|
|
402
|
+
signal
|
|
403
|
+
})) {
|
|
404
|
+
const entries = [];
|
|
405
|
+
try {
|
|
406
|
+
for (const entry of batch) {
|
|
407
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string" || !Array.isArray(entry[1]) || entry[1].length % 2 !== 0 || !entry[1].every((field) => typeof field === "string")) throw new Error("Invalid Redis stream entry");
|
|
408
|
+
const index = /^\d+-0$/.test(entry[0]) ? Number(entry[0].slice(0, -2)) : NaN;
|
|
409
|
+
if (!Number.isSafeInteger(index) || index <= last) throw new Error("Redis stream entry did not advance");
|
|
410
|
+
last = index;
|
|
411
|
+
entries.push(entry);
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (entries.length > 0) yield entries;
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
yield entries;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function redisHttp$1(options) {
|
|
421
|
+
if (!options.url || !options.token) throw new TypeError("redisHttp() needs a url and a token");
|
|
422
|
+
const { url, token } = options;
|
|
423
|
+
let polling = false;
|
|
424
|
+
let fallbackCheck;
|
|
425
|
+
const call = async (command, ...args) => {
|
|
426
|
+
let response;
|
|
427
|
+
try {
|
|
428
|
+
response = await fetch(url, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: {
|
|
431
|
+
authorization: `Bearer ${token}`,
|
|
432
|
+
"content-type": "application/json"
|
|
433
|
+
},
|
|
434
|
+
body: JSON.stringify([command, ...args.map(String)]),
|
|
435
|
+
...command === "PING" ? { signal: AbortSignal.timeout(NOTIFY_TIMINGS.safetyReadMs) } : {}
|
|
436
|
+
});
|
|
437
|
+
} catch (err) {
|
|
438
|
+
throw new A2Error("STORE_UNAVAILABLE", "redis rest request failed", { cause: err });
|
|
439
|
+
}
|
|
440
|
+
const body = await response.json().catch(() => null);
|
|
441
|
+
if (body === null || !response.ok || body.error !== void 0) throw new A2Error("STORE_UNAVAILABLE", `redis rest command failed: ${body?.error ?? `status ${response.status}`}`);
|
|
442
|
+
return body.result;
|
|
443
|
+
};
|
|
444
|
+
const readStream = (read) => readRedisStreamPage({
|
|
445
|
+
...read,
|
|
446
|
+
url,
|
|
447
|
+
token,
|
|
448
|
+
count: REDIS_HTTP_STREAM_READ_COUNT
|
|
449
|
+
});
|
|
450
|
+
let subscriber;
|
|
451
|
+
let onReconnect;
|
|
452
|
+
const client = {
|
|
453
|
+
call,
|
|
454
|
+
duplicate: () => subscriber ??= redisHttpSubscriptions({
|
|
455
|
+
url,
|
|
456
|
+
token,
|
|
457
|
+
onReconnect: (channel) => onReconnect?.(channel),
|
|
458
|
+
fallback: {
|
|
459
|
+
enabled: () => polling,
|
|
460
|
+
checkCommands() {
|
|
461
|
+
fallbackCheck ??= call("PING").then((result) => {
|
|
462
|
+
if (result !== "PONG") throw new A2Error("STORE_UNAVAILABLE", "redis rest capability check failed");
|
|
463
|
+
}).catch((error) => {
|
|
464
|
+
fallbackCheck = void 0;
|
|
465
|
+
throw error;
|
|
466
|
+
});
|
|
467
|
+
return fallbackCheck;
|
|
468
|
+
},
|
|
469
|
+
enable() {
|
|
470
|
+
polling = true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}),
|
|
474
|
+
on() {},
|
|
475
|
+
disconnect: () => subscriber?.disconnect()
|
|
476
|
+
};
|
|
477
|
+
return createRedisNotifyStore({
|
|
478
|
+
client: () => Promise.resolve(client),
|
|
479
|
+
closeClient: async () => client.disconnect(),
|
|
480
|
+
polling: () => polling,
|
|
481
|
+
onReconnect: (listener) => {
|
|
482
|
+
onReconnect = listener;
|
|
483
|
+
},
|
|
484
|
+
clock: options.clock ?? SYSTEM_CLOCK,
|
|
485
|
+
ids: options.ids ?? RANDOM_IDS,
|
|
486
|
+
keyPrefix: options.keyPrefix ?? "a2",
|
|
487
|
+
readStream
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
3
491
|
//#region src/store/redis-http.ts
|
|
4
492
|
/** A board on Redis over Upstash's REST API: no connection to keep, so it suits functions. */
|
|
5
493
|
function redisHttp(options) {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { t as BoardStore } from "../types-
|
|
2
|
-
|
|
1
|
+
import { t as BoardStore } from "../types-8yq2VnKI.mjs";
|
|
2
|
+
//#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/store-redis.d.ts
|
|
3
|
+
//#region src/store-redis-notify.d.ts
|
|
4
|
+
type RedisConnection = {
|
|
5
|
+
call(command: string, ...args: Array<string | number>): Promise<unknown>;
|
|
6
|
+
duplicate(): RedisConnection;
|
|
7
|
+
on(event: "message", listener: (channel: string, message: string) => void): unknown;
|
|
8
|
+
disconnect(): void;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
3
11
|
//#region src/store/redis.d.ts
|
|
4
12
|
export type RedisOptions = {
|
|
5
13
|
/**
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { t as A2Error } from "../session-clear-wHd5GCpZ-BnKSxReK.mjs";
|
|
2
|
+
import { t as retryableLazy } from "../retryable-lazy-DZWmHpii-Bye-W8q7.mjs";
|
|
3
|
+
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "../store-N8PXxDAS-BWBgzBHp.mjs";
|
|
4
|
+
import { n as createRedisNotifyStore, t as redisTables } from "../redis-tables-DN9PfIDi.mjs";
|
|
5
|
+
//#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/store-redis.js
|
|
6
|
+
/**
|
|
7
|
+
* experimental-a2/store-redis — the Redis-protocol store backend, on Redis
|
|
8
|
+
* Streams. The storage semantics live in store-redis-core.ts (shared
|
|
9
|
+
* with experimental-a2/store-redis-http); this module owns the connection and
|
|
10
|
+
* the live feed.
|
|
11
|
+
*
|
|
12
|
+
* `stream()` is notify-driven: every write publishes a wake-up that
|
|
13
|
+
* carries the appended events, one shared subscriber connection per backend
|
|
14
|
+
* serves every local feed, and a caught-up feed applies carried events that
|
|
15
|
+
* continue its sequence without reading. A gap, an oversized batch, a
|
|
16
|
+
* reconnect, or the safety re-read (NOTIFY_TIMINGS) triggers an `XRANGE`
|
|
17
|
+
* catch-up, so a lost wake-up costs latency, never delivery. Connections
|
|
18
|
+
* scale with processes (one command client plus one subscriber), not with
|
|
19
|
+
* concurrent viewers.
|
|
20
|
+
*
|
|
21
|
+
* Works with any Redis-protocol server on a single instance or a
|
|
22
|
+
* non-cluster provider (Upstash — durable by default — Redis, Valkey).
|
|
23
|
+
* `ioredis` is an optional peer dependency; pass `url`, or inject any client
|
|
24
|
+
* exposing `call`/`duplicate`/`on`/`disconnect`.
|
|
25
|
+
*/
|
|
26
|
+
function redis$1(options = {}) {
|
|
27
|
+
const clock = options.clock ?? SYSTEM_CLOCK;
|
|
28
|
+
const ids = options.ids ?? RANDOM_IDS;
|
|
29
|
+
const prefix = options.keyPrefix ?? "a2";
|
|
30
|
+
if (!options.client && !options.url) throw new TypeError("redis() needs a url or an injected client");
|
|
31
|
+
const connection = retryableLazy(async () => {
|
|
32
|
+
if (options.client) return options.client;
|
|
33
|
+
return new (await (import("ioredis").catch(() => {
|
|
34
|
+
throw new A2Error("STORE_NOT_CONFIGURED", "experimental-a2/store-redis with a url needs the 'ioredis' package (optional peer dependency) — install it, or inject a client");
|
|
35
|
+
}))).default(options.url);
|
|
36
|
+
});
|
|
37
|
+
const client = connection.get;
|
|
38
|
+
return createRedisNotifyStore({
|
|
39
|
+
client,
|
|
40
|
+
...options.client ? {} : { callBytes: async (command, ...args) => (await client()).callBuffer(command, ...args) },
|
|
41
|
+
clock,
|
|
42
|
+
ids,
|
|
43
|
+
keyPrefix: prefix,
|
|
44
|
+
async closeClient() {
|
|
45
|
+
const current = connection.peek();
|
|
46
|
+
if (!current) return;
|
|
47
|
+
(await current.catch(() => null))?.disconnect();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
3
52
|
//#region src/store/redis.ts
|
|
4
53
|
/** `ioredis` is an optional peer, installed by the apps that use Redis over TCP. */
|
|
5
54
|
const IOREDIS = "ioredis";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as sqlTables } from "../sql-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
1
|
+
import { r as sqlTables } from "../sql-U9WTxrwJ.mjs";
|
|
2
|
+
import { t as sqlite$1 } from "../store-sqlite-CxxPCcCT.mjs";
|
|
3
|
+
import { t as sqliteDb } from "../sqlite-db-Ds4majtT.mjs";
|
|
4
4
|
//#region src/store/sqlite.ts
|
|
5
5
|
/**
|
|
6
6
|
* A board in one SQLite file, A2's log and board's tables side by side: `a2()`'s
|