@vercel/board 0.26.0 → 0.26.2
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/dist/{a2-Qzj0kCWU.mjs → a2-D6TOZNV2.mjs} +107 -39
- package/dist/{attempts-CLG840Oj.mjs → attempts-HXcskN1w.mjs} +1 -11
- package/dist/backend-a2/contract.mjs +1 -1
- package/dist/backend-a2.d.mts +3 -0
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-F0W6e36G.mjs → backend-http-CDu_eapz.mjs} +1 -1
- package/dist/{backend-http-BB0Gld6Q.mjs → backend-http-DApbemsm.mjs} +18 -4
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-CtB46vMQ.mjs → backend-vercel-C2YcUuyT.mjs} +3 -3
- package/dist/{backend-vercel-DUWIcFi9.mjs → backend-vercel-QuUJ3dW_.mjs} +8 -3
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{engine-ROa7-IQY.mjs → engine-Bfe6pcXb.mjs} +2 -2
- package/dist/{exports-NFjBgIT2.mjs → exports-T-ART7P7.mjs} +2 -2
- package/dist/{index.default-Boe7x-mK.mjs → index.default-_e7u6Z-I.mjs} +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.node.mjs +1 -1
- package/dist/inspect/app.js +29 -29
- package/dist/inspect/engine.js +5 -5
- package/dist/{main-BuahKR16.mjs → main-Ci7R6qkK.mjs} +9 -4
- package/dist/{reducers-BFmIXilu.mjs → reducers-BcmSs4qf.mjs} +3 -1
- package/dist/{serve-BbHEQk_Y.mjs → serve-CNklIudj.mjs} +2 -2
- package/dist/{server-Bphio_82.mjs → server-DMNfPSAZ.mjs} +3 -3
- package/dist/sync-linear.mjs +6 -5
- package/dist/{ids-C6AYo1bu.mjs → wait-until-Dw4XrIDQ.mjs} +12 -1
- package/dist/{watchers-hash-BfDPW95j.mjs → watchers-hash-C10qKYPa.mjs} +1 -1
- package/docs/01-board/06-watchers.md +4 -2
- package/docs/01-board/07-schedules.md +12 -5
- package/docs/02-sync/07-linear.md +4 -2
- package/docs/04-reference/01-cli.md +2 -1
- package/docs/04-reference/03-http.md +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as DIRECT_UPLOAD_BYTES_MAX, r as IMPORT_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { _ as RemoteWatcherId, b as actorKind, i as Attachment, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
|
|
3
3
|
import "./external-BV_cUncs.mjs";
|
|
4
|
-
import { a as METHODS, d as VERSION, f as SYNC, n as compileWatcher, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-
|
|
4
|
+
import { a as METHODS, d as VERSION, f as SYNC, n as compileWatcher, r as hasCriteria, t as canonicalWatchers } from "./watchers-hash-C10qKYPa.mjs";
|
|
5
5
|
import { r as duration, t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { i as isKeyIdOf, r as derivedId, t as waitUntil } from "./wait-until-Dw4XrIDQ.mjs";
|
|
7
7
|
import { a as forbidden, c as toErrorResponse, i as duplicateId, r as badRequest, s as notFound, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
8
8
|
import { t as own } from "./own-OwgPWdmh.mjs";
|
|
9
9
|
import { n as replaceMentions } from "./mentions-CB6ShY4R.mjs";
|
|
@@ -13,12 +13,12 @@ import { n as MIDDLEWARE_ACTOR, t as DroppedError } from "./middleware-ChKM1zuu.
|
|
|
13
13
|
import { n as syncTarget, r as withEmailMentions } from "./targets-C-wdR5wP.mjs";
|
|
14
14
|
import { t as A2Error } from "./session-clear-wHd5GCpZ-BnKSxReK.mjs";
|
|
15
15
|
import { a as reducer } from "./reducer-CjmvZSAb-D_OSISmj.mjs";
|
|
16
|
-
import { _ as BOARD_SESSION, a as IDENTITY_SESSION, c as SCHEDULES_SESSION, d as inboxContract, f as inboxSession, g as watchSession, h as watchContract, l as boardContract, m as schedulesContract, n as channelsReducer, o as INBOXES_SESSION, p as registryContract, r as identityCodesReducer, s as REGISTRY_SESSION, t as actorsReducer, u as identityContract } from "./reducers-
|
|
16
|
+
import { _ as BOARD_SESSION, a as IDENTITY_SESSION, c as SCHEDULES_SESSION, d as inboxContract, f as inboxSession, g as watchSession, h as watchContract, l as boardContract, m as schedulesContract, n as channelsReducer, o as INBOXES_SESSION, p as registryContract, r as identityCodesReducer, s as REGISTRY_SESSION, t as actorsReducer, u as identityContract } from "./reducers-BcmSs4qf.mjs";
|
|
17
17
|
import { n as createServer, t as consumer } from "./server-DtFrWxi3-Boa6gdWO.mjs";
|
|
18
18
|
import { a as linkedTo, i as iso, n as foldMessages, o as personOf, t as foldActors } from "./folds-Dyjput8M.mjs";
|
|
19
19
|
import { t as concernedMessageId } from "./project-DMDST9kl.mjs";
|
|
20
20
|
import { t as TABLES_CONSUMER } from "./sql-U9WTxrwJ.mjs";
|
|
21
|
-
import {
|
|
21
|
+
import { t as afterRetry } from "./attempts-HXcskN1w.mjs";
|
|
22
22
|
import { createHash, randomBytes } from "node:crypto";
|
|
23
23
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
@@ -1277,13 +1277,22 @@ function foldSchedules(state, event) {
|
|
|
1277
1277
|
schedule.next = event.payload.next;
|
|
1278
1278
|
return state;
|
|
1279
1279
|
}
|
|
1280
|
+
case "schedule.takeover":
|
|
1281
|
+
state.takeover = {
|
|
1282
|
+
deployment: event.payload.deployment,
|
|
1283
|
+
index: event.index
|
|
1284
|
+
};
|
|
1285
|
+
return state;
|
|
1280
1286
|
default: return state;
|
|
1281
1287
|
}
|
|
1282
1288
|
}
|
|
1283
1289
|
const schedulesReducer = reducer({
|
|
1284
1290
|
contract: schedulesContract,
|
|
1285
|
-
name: "board.schedules.
|
|
1286
|
-
initialState: {
|
|
1291
|
+
name: "board.schedules.v2",
|
|
1292
|
+
initialState: {
|
|
1293
|
+
byId: {},
|
|
1294
|
+
takeover: null
|
|
1295
|
+
}
|
|
1287
1296
|
}).fold(foldSchedules);
|
|
1288
1297
|
/** The send after one due at `at`: the next slot after now, so downtime skips missed slots instead of replaying them. */
|
|
1289
1298
|
function nextSend(schedule, n, at, now) {
|
|
@@ -1345,10 +1354,23 @@ function createSchedules(options) {
|
|
|
1345
1354
|
let accessNow = null;
|
|
1346
1355
|
/** Posts as `identity`, through what every post does: the middleware too. */
|
|
1347
1356
|
let send = null;
|
|
1348
|
-
/**
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1357
|
+
/** Vercel Queues delivers a timer only to the deployment that armed it, which runs its own code and access rules. */
|
|
1358
|
+
const deployment = process.env.VERCEL_DEPLOYMENT_ID || null;
|
|
1359
|
+
/**
|
|
1360
|
+
* Where this deployment's timers land: a session of its own on Vercel. A queue drains one session
|
|
1361
|
+
* at a time, and Vercel Queues drains it on the deployment that appended to it, so an earlier
|
|
1362
|
+
* deployment never drains the one that sends' timers, and can't drop them there.
|
|
1363
|
+
*/
|
|
1364
|
+
const sends = deployment === null ? SCHEDULES_SESSION : `${SCHEDULES_SESSION}:${deployment}`;
|
|
1365
|
+
/**
|
|
1366
|
+
* Arms send `n`, due at `at`: the whole way, or one hop toward it. A timer's name is its identity
|
|
1367
|
+
* in the queue, so arming the same send under the same name again adds nothing; only a hop's names
|
|
1368
|
+
* the hop, since a send waits through several.
|
|
1369
|
+
*/
|
|
1370
|
+
async function arm(name, id, n, at) {
|
|
1371
|
+
const hop = Math.max(Math.min(at, Date.now() + MAX_HOP_MS), Date.now());
|
|
1372
|
+
const named = name === "hop" ? `hop-${id}-${n}-${hop}` : `${name}-${id}-${n}`;
|
|
1373
|
+
await session.forSends.schedule(named, { at: new Date(hop) }, {
|
|
1352
1374
|
type: "schedule.due",
|
|
1353
1375
|
payload: {
|
|
1354
1376
|
id,
|
|
@@ -1357,24 +1379,33 @@ function createSchedules(options) {
|
|
|
1357
1379
|
}
|
|
1358
1380
|
});
|
|
1359
1381
|
}
|
|
1382
|
+
/** Appends to the schedules, once for each `key`, from a send in any session. */
|
|
1383
|
+
async function record(event, ...key) {
|
|
1384
|
+
await session.append({
|
|
1385
|
+
...event,
|
|
1386
|
+
id: await idempotentId("board-schedule-record", ...key)
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1360
1389
|
async function due(ctx) {
|
|
1361
1390
|
if (!writes || !accessNow || !send) throw new Error("schedules used before the board bound them");
|
|
1362
1391
|
const { id, n, at } = ctx.event.payload;
|
|
1363
|
-
const { state } = await
|
|
1392
|
+
const { state } = await session.state(schedulesReducer, { through: "latest" });
|
|
1364
1393
|
const schedule = state.byId[id];
|
|
1394
|
+
if (deployment !== null && state.takeover !== null && state.takeover.deployment !== deployment) return;
|
|
1395
|
+
if (schedule && ctx.attempt > 1 && schedule.sent === n + 1 && schedule.next !== null) return arm("next", id, n + 1, schedule.next);
|
|
1365
1396
|
if (!schedule || schedule.next !== at || schedule.sent !== n) return;
|
|
1366
|
-
if (Date.now() < at - EARLY_MS) return arm(
|
|
1397
|
+
if (Date.now() < at - EARLY_MS) return arm("hop", id, n, at);
|
|
1367
1398
|
if (schedule.re !== null) {
|
|
1368
1399
|
const root = await writes.reads.message(schedule.re, ctx.signal);
|
|
1369
1400
|
if (!root || root.deleted) {
|
|
1370
|
-
await
|
|
1401
|
+
await record({
|
|
1371
1402
|
type: "schedule.canceled",
|
|
1372
1403
|
payload: {
|
|
1373
1404
|
id,
|
|
1374
1405
|
by: SYSTEM.author,
|
|
1375
1406
|
reason: "thread_deleted"
|
|
1376
1407
|
}
|
|
1377
|
-
});
|
|
1408
|
+
}, "ended", String(id));
|
|
1378
1409
|
return;
|
|
1379
1410
|
}
|
|
1380
1411
|
}
|
|
@@ -1386,14 +1417,14 @@ function createSchedules(options) {
|
|
|
1386
1417
|
}).catch(refused);
|
|
1387
1418
|
if (!access || !proofs) {
|
|
1388
1419
|
console.info(`[board] schedule s${id}'s creator can no longer post it in ${schedule.channel}`);
|
|
1389
|
-
await
|
|
1420
|
+
await record({
|
|
1390
1421
|
type: "schedule.canceled",
|
|
1391
1422
|
payload: {
|
|
1392
1423
|
id,
|
|
1393
1424
|
by: SYSTEM.author,
|
|
1394
1425
|
reason: "revoked"
|
|
1395
1426
|
}
|
|
1396
|
-
});
|
|
1427
|
+
}, "ended", String(id));
|
|
1397
1428
|
return;
|
|
1398
1429
|
}
|
|
1399
1430
|
const input = {
|
|
@@ -1426,18 +1457,18 @@ function createSchedules(options) {
|
|
|
1426
1457
|
} catch (error) {
|
|
1427
1458
|
if (!(error instanceof DroppedError)) throw error;
|
|
1428
1459
|
console.info(`[board] the middleware dropped schedule s${id}: ${error.message}`);
|
|
1429
|
-
await
|
|
1460
|
+
await record({
|
|
1430
1461
|
type: "schedule.canceled",
|
|
1431
1462
|
payload: {
|
|
1432
1463
|
id,
|
|
1433
1464
|
by: SYSTEM.author,
|
|
1434
1465
|
reason: "dropped"
|
|
1435
1466
|
}
|
|
1436
|
-
});
|
|
1467
|
+
}, "ended", String(id));
|
|
1437
1468
|
return;
|
|
1438
1469
|
}
|
|
1439
1470
|
const next = nextSend(schedule, n, at, Date.now());
|
|
1440
|
-
await
|
|
1471
|
+
await record({
|
|
1441
1472
|
type: "schedule.fired",
|
|
1442
1473
|
payload: {
|
|
1443
1474
|
id,
|
|
@@ -1445,13 +1476,12 @@ function createSchedules(options) {
|
|
|
1445
1476
|
message: posted,
|
|
1446
1477
|
next
|
|
1447
1478
|
}
|
|
1448
|
-
});
|
|
1449
|
-
if (next !== null) await arm(
|
|
1479
|
+
}, "fired", String(id), String(n));
|
|
1480
|
+
if (next !== null) await arm("next", id, n + 1, next);
|
|
1450
1481
|
}
|
|
1451
1482
|
/**
|
|
1452
|
-
* Arms each schedule's next send, as the schedule stands now, from the first schedule on:
|
|
1453
|
-
*
|
|
1454
|
-
* reach this one. The deployment that gets there first sends, and `due` makes the other's a no-op.
|
|
1483
|
+
* Arms each schedule's next send, as the schedule stands now, from the first schedule on: the
|
|
1484
|
+
* first deploy with this consumer arms every schedule from before it.
|
|
1455
1485
|
*/
|
|
1456
1486
|
const arming = consumer({
|
|
1457
1487
|
contract: schedulesContract,
|
|
@@ -1464,7 +1494,7 @@ function createSchedules(options) {
|
|
|
1464
1494
|
const { state } = await ctx.session.state(schedulesReducer, { through: "latest" });
|
|
1465
1495
|
const schedule = state.byId[ctx.event.index];
|
|
1466
1496
|
if (!schedule || schedule.next === null) return;
|
|
1467
|
-
await arm(
|
|
1497
|
+
await arm("first", schedule.id, schedule.sent, schedule.next);
|
|
1468
1498
|
});
|
|
1469
1499
|
/** Makes each due send, one schedule at a time. */
|
|
1470
1500
|
const sending = consumer({
|
|
@@ -1479,19 +1509,53 @@ function createSchedules(options) {
|
|
|
1479
1509
|
...options.scheduler ? { scheduler: options.scheduler } : {},
|
|
1480
1510
|
consumers: [arming, sending]
|
|
1481
1511
|
});
|
|
1482
|
-
const session = server.session(SCHEDULES_SESSION);
|
|
1512
|
+
const session = Object.assign(server.session(SCHEDULES_SESSION), { forSends: server.session(sends) });
|
|
1513
|
+
const scheduled = options.scheduler !== void 0;
|
|
1514
|
+
/**
|
|
1515
|
+
* Makes this deployment the one that sends, unless it already is, and arms every pending
|
|
1516
|
+
* schedule's next send here, since the timers an earlier deployment armed only reach it. Each
|
|
1517
|
+
* instance arms them again, under the same names, so one created on an earlier deployment
|
|
1518
|
+
* after the takeover, or missed by a takeover that failed partway, still gets a timer here.
|
|
1519
|
+
*/
|
|
1520
|
+
async function takeOver() {
|
|
1521
|
+
if (deployment === null) return;
|
|
1522
|
+
const read = await session.state(schedulesReducer);
|
|
1523
|
+
let { state } = read;
|
|
1524
|
+
if (state.takeover?.deployment !== deployment) {
|
|
1525
|
+
await session.append({
|
|
1526
|
+
type: "schedule.takeover",
|
|
1527
|
+
payload: { deployment },
|
|
1528
|
+
id: await idempotentId("board-schedule-takeover", deployment, String(read.index))
|
|
1529
|
+
});
|
|
1530
|
+
state = (await session.state(schedulesReducer)).state;
|
|
1531
|
+
}
|
|
1532
|
+
const since = state.takeover.index;
|
|
1533
|
+
for (const schedule of Object.values(state.byId)) if (schedule.next !== null) await arm(`takeover-${since}`, schedule.id, schedule.sent, schedule.next);
|
|
1534
|
+
}
|
|
1483
1535
|
/**
|
|
1484
|
-
* The first
|
|
1485
|
-
*
|
|
1536
|
+
* The first call on an instance starts the arming catching up, and doesn't wait: on the first
|
|
1537
|
+
* deploy with it, every schedule from before gets its next send armed here. The first call that
|
|
1538
|
+
* isn't a queue's delivery takes schedules over too: a deployment nobody visits any more still
|
|
1539
|
+
* gets its own timers, and those mustn't take schedules back from the one people reach.
|
|
1486
1540
|
*/
|
|
1487
1541
|
let catching = null;
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
catching =
|
|
1493
|
-
|
|
1494
|
-
|
|
1542
|
+
let taking = null;
|
|
1543
|
+
function catchUp(options) {
|
|
1544
|
+
if (!scheduled) return;
|
|
1545
|
+
if (!catching) {
|
|
1546
|
+
catching = session.consume(arming).catch((error) => {
|
|
1547
|
+
console.warn("[board] schedules are not caught up:", error.message);
|
|
1548
|
+
catching = null;
|
|
1549
|
+
});
|
|
1550
|
+
waitUntil(catching);
|
|
1551
|
+
}
|
|
1552
|
+
if (options.takeOver && !taking) {
|
|
1553
|
+
taking = takeOver().catch((error) => {
|
|
1554
|
+
console.warn("[board] schedules are not taken over:", error.message);
|
|
1555
|
+
taking = null;
|
|
1556
|
+
});
|
|
1557
|
+
waitUntil(taking);
|
|
1558
|
+
}
|
|
1495
1559
|
}
|
|
1496
1560
|
/** `visible`: which schedules the caller may see. */
|
|
1497
1561
|
async function list(query, visible = () => true) {
|
|
@@ -1562,8 +1626,10 @@ function createSchedules(options) {
|
|
|
1562
1626
|
}
|
|
1563
1627
|
});
|
|
1564
1628
|
const { state } = await session.state(schedulesReducer);
|
|
1629
|
+
const created = state.byId[row.index];
|
|
1630
|
+
if (created.next !== null) await arm("first", created.id, created.sent, created.next);
|
|
1565
1631
|
const { state: actors } = await writes.session.state(actorsReducer);
|
|
1566
|
-
const schedule = scheduleView(
|
|
1632
|
+
const schedule = scheduleView(created, actors);
|
|
1567
1633
|
return unknown.length && identity.principal.access.readable(channel) ? {
|
|
1568
1634
|
schedule,
|
|
1569
1635
|
unknownMentions: unknown
|
|
@@ -2467,7 +2533,7 @@ function scheduleAt(value) {
|
|
|
2467
2533
|
return index;
|
|
2468
2534
|
}
|
|
2469
2535
|
new AbortController().signal;
|
|
2470
|
-
/** A wake-up only has to arrive: the board it wakes
|
|
2536
|
+
/** A wake-up only has to arrive: the board it wakes answers once what it pulls has run, or after 2 seconds while it keeps going. */
|
|
2471
2537
|
const WAKE_TIMEOUT_MS = 5e3;
|
|
2472
2538
|
/**
|
|
2473
2539
|
* `idempotentId`'s shape, a UUIDv8: the IDs the board derives for its own writes (schedule
|
|
@@ -3969,6 +4035,8 @@ function a2(options = {}) {
|
|
|
3969
4035
|
if (!binding) throw new Error("a2: this backend isn't serving a board yet: pass it to board({ backend })");
|
|
3970
4036
|
throw new Error("a2: its default store opens on the board's first request: pass a store to reach it before that");
|
|
3971
4037
|
};
|
|
4038
|
+
/** A queue's delivery, through the scheduler callback, and everything it runs: watchers' writes too. */
|
|
4039
|
+
const delivery = new AsyncLocalStorage();
|
|
3972
4040
|
const ready = async () => {
|
|
3973
4041
|
if (!built) {
|
|
3974
4042
|
if (!binding) throw new Error("a2: this backend isn't serving a board yet: pass it to board({ backend })");
|
|
@@ -3980,7 +4048,7 @@ function a2(options = {}) {
|
|
|
3980
4048
|
opening.catch(() => opening = null);
|
|
3981
4049
|
await opening;
|
|
3982
4050
|
}
|
|
3983
|
-
built.schedules.catchUp();
|
|
4051
|
+
built.schedules.catchUp({ takeOver: !delivery.getStore() });
|
|
3984
4052
|
return built;
|
|
3985
4053
|
};
|
|
3986
4054
|
const backend = {
|
|
@@ -3996,7 +4064,7 @@ function a2(options = {}) {
|
|
|
3996
4064
|
}, given);
|
|
3997
4065
|
},
|
|
3998
4066
|
/** The scheduler's callback: `export { scheduler as POST }` from a route of its own. A board without a scheduler answers 404. */
|
|
3999
|
-
scheduler: async (
|
|
4067
|
+
scheduler: (request) => delivery.run(true, async () => (await ready()).scheduler(request)),
|
|
4000
4068
|
/** The A2 servers behind this board, to share one scheduler with other A2 servers. */
|
|
4001
4069
|
get servers() {
|
|
4002
4070
|
return now().servers;
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
//#region src/server/wait-until.ts
|
|
2
|
-
/**
|
|
3
|
-
* Keeps `task` running after the response, where the platform offers it: Vercel's request context, as A2
|
|
4
|
-
* reads it too. Elsewhere it runs on in the process.
|
|
5
|
-
*/
|
|
6
|
-
function waitUntil(task) {
|
|
7
|
-
try {
|
|
8
|
-
Reflect.get(globalThis, Symbol.for("@vercel/request-context"))?.get?.()?.waitUntil?.(task);
|
|
9
|
-
} catch {}
|
|
10
|
-
}
|
|
11
1
|
/**
|
|
12
2
|
* Whether a run comes after a retry from a dead letter. A2 keeps counting
|
|
13
3
|
* `ctx.attempt` across one, and no earlier run sees an attempt past A2_TRIES:
|
|
@@ -17,4 +7,4 @@ function waitUntil(task) {
|
|
|
17
7
|
*/
|
|
18
8
|
const afterRetry = (attempt) => attempt > 10;
|
|
19
9
|
//#endregion
|
|
20
|
-
export {
|
|
10
|
+
export { afterRetry as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as DIRECT_UPLOAD_BYTES_MAX, r as IMPORT_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "../files-SniSmeEr.mjs";
|
|
2
2
|
import { a as AttachmentId, c as ChannelPath, d as LabelKey, f as LabelValue, g as MessageText, h as MessageId, i as Attachment, l as Emoji, m as MESSAGE_TEXT_MAX, n as ActorId, o as Author, p as Labels, r as ActorKind, s as CHANNEL_PATH_MAX, t as ATTACHMENTS_PER_MESSAGE_MAX, u as Handle } from "../schemas-Be08nyfW.mjs";
|
|
3
3
|
import { o as Origin } from "../external-BV_cUncs.mjs";
|
|
4
|
-
import { _ as BOARD_SESSION, i as messagesReducer, l as boardContract, n as channelsReducer, t as actorsReducer } from "../reducers-
|
|
4
|
+
import { _ as BOARD_SESSION, i as messagesReducer, l as boardContract, n as channelsReducer, t as actorsReducer } from "../reducers-BcmSs4qf.mjs";
|
|
5
5
|
export { ATTACHMENTS_PER_MESSAGE_MAX, ATTACHMENT_BYTES_MAX, ActorId, ActorKind, Attachment, AttachmentId, Author, BOARD_SESSION, CHANNEL_PATH_MAX, ChannelPath, DIRECT_UPLOAD_BYTES_MAX, Emoji, Handle, IMPORT_BYTES_MAX, LabelKey, LabelValue, Labels, MESSAGE_TEXT_MAX, MessageId, MessageText, Origin, actorsReducer, boardContract, channelsReducer, messagesReducer };
|
package/dist/backend-a2.d.mts
CHANGED
|
@@ -1194,6 +1194,9 @@ export declare function a2(options?: A2Options): {
|
|
|
1194
1194
|
message: z.ZodNumber;
|
|
1195
1195
|
next: z.ZodNullable<z.ZodNumber>;
|
|
1196
1196
|
}, z.core.$strip>;
|
|
1197
|
+
'schedule.takeover': z.ZodObject<{
|
|
1198
|
+
deployment: z.ZodString;
|
|
1199
|
+
}, z.core.$strip>;
|
|
1197
1200
|
}, Record<never, never>, undefined>)[];
|
|
1198
1201
|
session: {
|
|
1199
1202
|
readonly id: string;
|
package/dist/backend-a2.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-
|
|
1
|
+
import { n as fsBlob, r as memoryBlob, t as a2 } from "./a2-D6TOZNV2.mjs";
|
|
2
2
|
export { a2, fsBlob, memoryBlob };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as createHttp } from "./backend-http-
|
|
1
|
+
import { r as createHttp } from "./backend-http-DApbemsm.mjs";
|
|
2
2
|
import { n as connect, r as environmentProfile, t as boardUrl } from "./node-Ybi9E6bO.mjs";
|
|
3
3
|
//#region src/backend-http/index.ts
|
|
4
4
|
const environment = {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import "./files-SniSmeEr.mjs";
|
|
2
2
|
import "./schemas-Be08nyfW.mjs";
|
|
3
|
-
import { K as routes, O as RegisterBody, a as METHODS, n as compileWatcher, t as canonicalWatchers } from "./watchers-hash-
|
|
3
|
+
import { K as routes, O as RegisterBody, a as METHODS, n as compileWatcher, t as canonicalWatchers } from "./watchers-hash-C10qKYPa.mjs";
|
|
4
4
|
import { n as createBoardClient, t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as keyId, n as DERIVED_ID, o as sha256, r as derivedId, t as waitUntil } from "./wait-until-Dw4XrIDQ.mjs";
|
|
6
6
|
import { a as forbidden, o as json, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
7
7
|
//#region src/server/registrant.ts
|
|
8
8
|
/** Registering again this soon after the same registration only repeats it. */
|
|
9
9
|
const REGISTER_EVERY_MS = 1e4;
|
|
10
10
|
/** How long one wake-up runs matches before it leaves the rest to the next. */
|
|
11
11
|
const DRAIN_MS = 24e4;
|
|
12
|
+
/** How long a wake-up waits for what it pulled to run before it answers: under the 3 seconds Vercel's API gives it. */
|
|
13
|
+
const WAKE_ANSWER_MS = 2e3;
|
|
12
14
|
/** A pull with nothing to hand out holds this long, when the board pulls on its own. */
|
|
13
15
|
const POLL_WAIT_S = 25;
|
|
14
16
|
const describe$1 = (error) => ((error instanceof Error ? error.message : String(error)) || "unknown error").slice(0, 500);
|
|
@@ -142,8 +144,20 @@ function registrant(options) {
|
|
|
142
144
|
path: "/wake",
|
|
143
145
|
async handle(request) {
|
|
144
146
|
started ??= Promise.resolve();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
const stale = request.headers.get("x-board-watchers") !== await options.hash();
|
|
148
|
+
const running = (async () => {
|
|
149
|
+
if (stale) await register(wakeable(bound().url()));
|
|
150
|
+
return drain();
|
|
151
|
+
})();
|
|
152
|
+
waitUntil(running.catch((error) => console.error(`[board] couldn't run what woke it:`, describe$1(error))));
|
|
153
|
+
let timer;
|
|
154
|
+
const waited = new Promise((resolve) => timer = setTimeout(() => resolve(null), WAKE_ANSWER_MS));
|
|
155
|
+
try {
|
|
156
|
+
const ran = await Promise.race([running.catch(rethrow), waited]);
|
|
157
|
+
return ran === null ? new Response(null, { status: 202 }) : json({ ran });
|
|
158
|
+
} finally {
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
}
|
|
147
161
|
}
|
|
148
162
|
}, {
|
|
149
163
|
method: "POST",
|
package/dist/backend-http.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as http } from "./index.default-
|
|
1
|
+
import { t as http } from "./index.default-_e7u6Z-I.mjs";
|
|
2
2
|
export { http };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as http } from "./backend-http-
|
|
1
|
+
import { t as http } from "./backend-http-CDu_eapz.mjs";
|
|
2
2
|
export { http };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as RemoteWatcherId } from "./schemas-Be08nyfW.mjs";
|
|
2
|
-
import { O as RegisterBody, a as METHODS, d as VERSION, n as compileWatcher, t as canonicalWatchers } from "./watchers-hash-
|
|
3
|
-
import {
|
|
2
|
+
import { O as RegisterBody, a as METHODS, d as VERSION, n as compileWatcher, t as canonicalWatchers } from "./watchers-hash-C10qKYPa.mjs";
|
|
3
|
+
import { o as sha256, r as derivedId } from "./wait-until-Dw4XrIDQ.mjs";
|
|
4
4
|
import { a as forbidden, o as json, s as notFound, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
5
|
-
import { a as wakePath, i as registrant, n as callerProfile, t as PROJECTS_OWN } from "./backend-http-
|
|
5
|
+
import { a as wakePath, i as registrant, n as callerProfile, t as PROJECTS_OWN } from "./backend-http-DApbemsm.mjs";
|
|
6
6
|
//#region src/server/backend-vercel.ts
|
|
7
7
|
const PROFILE = /^[A-Za-z][A-Za-z0-9-]{0,31}$/;
|
|
8
8
|
const SESSION = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
2
2
|
import { c as vercelLogin, l as vercelProjectFile, o as loginHanded, r as environmentProfile } from "./node-Ybi9E6bO.mjs";
|
|
3
|
-
import { t as createVercel } from "./backend-vercel-
|
|
3
|
+
import { t as createVercel } from "./backend-vercel-C2YcUuyT.mjs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { decodeJwt } from "jose";
|
|
6
6
|
import { getVercelOidcToken } from "@vercel/oidc";
|
|
@@ -38,10 +38,15 @@ function handTeam(resolve) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* The team: the one a CLI that runs board's hands it, `VERCEL_ORG_ID`, the one the deployment's OIDC
|
|
41
|
-
* token names, or the linked project's (the nearest `.vercel/project.json`).
|
|
41
|
+
* token names, or the linked project's (the nearest `.vercel/project.json`). A handed one that
|
|
42
|
+
* throws fails the request with what it threw: the app's own words, not an internal error.
|
|
42
43
|
*/
|
|
43
44
|
async function team() {
|
|
44
|
-
if (handed)
|
|
45
|
+
if (handed) try {
|
|
46
|
+
return await handed();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new HttpError(502, "no_team", error instanceof Error ? error.message : String(error), { origin: "handle" });
|
|
49
|
+
}
|
|
45
50
|
if (process.env.VERCEL_ORG_ID) return process.env.VERCEL_ORG_ID;
|
|
46
51
|
if (!process.env.VERCEL_TOKEN && deployed()) try {
|
|
47
52
|
const owner = decodeJwt(await getVercelOidcToken()).owner_id;
|
package/dist/backend-vercel.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createVercel } from "./backend-vercel-
|
|
1
|
+
import { t as createVercel } from "./backend-vercel-C2YcUuyT.mjs";
|
|
2
2
|
//#region src/backend-vercel/index.default.ts
|
|
3
3
|
/**
|
|
4
4
|
* Your team's board, in Vercel's API. Outside Node there's no environment to find credentials or
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-
|
|
1
|
+
import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-QuUJ3dW_.mjs";
|
|
2
2
|
export { credentialSource, handTeam, vercel };
|
package/dist/cli-embedded.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as commandLine, c as failureLines, i as mainWith, l as formatFailure, n as commandWord, o as exitCode, r as main, s as failureDocument, t as Failed } from "./main-
|
|
1
|
+
import { a as commandLine, c as failureLines, i as mainWith, l as formatFailure, n as commandWord, o as exitCode, r as main, s as failureDocument, t as Failed } from "./main-Ci7R6qkK.mjs";
|
|
2
2
|
//#region src/cli/embedded.ts
|
|
3
3
|
/**
|
|
4
4
|
* Board's CLI as `appName` names it in help, usage, and errors (`vercel board`). With no board
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
|
3
3
|
import "./schemas-Be08nyfW.mjs";
|
|
4
4
|
import { a as option } from "./commands-nEG47RM_.mjs";
|
|
5
5
|
import { s as projectDir } from "./node-Ybi9E6bO.mjs";
|
|
6
|
-
import { r as main, u as who } from "./main-
|
|
6
|
+
import { r as main, u as who } from "./main-Ci7R6qkK.mjs";
|
|
7
7
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { appendFile, readFile } from "node:fs/promises";
|
|
9
9
|
import { join } from "node:path";
|
|
@@ -661,7 +661,7 @@ const local = {
|
|
|
661
661
|
if (args.length > 0) throw usage("serve takes no arguments: pass its port with --port, and its directory with --dir.");
|
|
662
662
|
const port = portFlag();
|
|
663
663
|
const root = projectDir();
|
|
664
|
-
const { localHandle, serveLocal } = await import("./serve-
|
|
664
|
+
const { localHandle, serveLocal } = await import("./serve-CNklIudj.mjs");
|
|
665
665
|
const handle = flags.handle ?? process.env.BOARD_HANDLE;
|
|
666
666
|
const local = await serveLocal({
|
|
667
667
|
dir: flags.dir ?? join(root, ".board"),
|
|
@@ -2,11 +2,11 @@ import { a as typeOf, i as fileSize, r as IMPORT_BYTES_MAX, t as ATTACHMENT_BYTE
|
|
|
2
2
|
import { C as toStorable, c as ChannelPath, m as MESSAGE_TEXT_MAX } from "./schemas-Be08nyfW.mjs";
|
|
3
3
|
import { c as externalMessageKey, l as isThreadTarget, s as channelOf } from "./external-BV_cUncs.mjs";
|
|
4
4
|
import { t as canonicalEmoji } from "./emoji-CTLpvH-A.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { r as derivedId, t as waitUntil } from "./wait-until-Dw4XrIDQ.mjs";
|
|
6
6
|
import { r as badRequest, s as notFound, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
7
7
|
import { n as replaceMentions } from "./mentions-CB6ShY4R.mjs";
|
|
8
8
|
import { t as DroppedError } from "./middleware-ChKM1zuu.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { t as afterRetry } from "./attempts-HXcskN1w.mjs";
|
|
10
10
|
import { a as channelMissing, i as UnreadableError, n as ResolvedTarget, o as routesFor, t as ReceivedEvent } from "./provider-poIkXapd.mjs";
|
|
11
11
|
import { a as immutable, n as SyncOverrides } from "./events-CewD4kw7.mjs";
|
|
12
12
|
import { t as displayText } from "./bots-CGkO76qH.mjs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as ATTACHMENT_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import "./schemas-Be08nyfW.mjs";
|
|
3
|
-
import { A as SchedulesQuery, B as SyncReceiptBody, C as MatchesQuery, D as Ref, E as ReactBody, F as SyncBody, G as UploadStartBody, H as SyncUnlinkBody, I as SyncCodeBody, L as SyncCopiesQuery, M as SourceJoinBody, N as SourceReadQuery, O as RegisterBody, P as SyncAccountBody, R as SyncFindQuery, S as LinkedQuery, T as PostBody, U as ThreadsQuery, V as SyncRootsQuery, W as TypingBody, _ as InboxAckBody, a as METHODS, b as InboxQuery, c as queryObject, d as VERSION, f as SYNC, g as IdentityUnlinkBody, h as EventsQuery, j as SignInQuery, k as ScheduleBody, l as readCapped, m as EditBody, o as createRouter, p as AckBody, s as openapi, u as readJson, v as InboxNextQuery, w as MessagesQuery, x as LabelBody, y as InboxPushBody, z as SyncImportBody } from "./watchers-hash-
|
|
3
|
+
import { A as SchedulesQuery, B as SyncReceiptBody, C as MatchesQuery, D as Ref, E as ReactBody, F as SyncBody, G as UploadStartBody, H as SyncUnlinkBody, I as SyncCodeBody, L as SyncCopiesQuery, M as SourceJoinBody, N as SourceReadQuery, O as RegisterBody, P as SyncAccountBody, R as SyncFindQuery, S as LinkedQuery, T as PostBody, U as ThreadsQuery, V as SyncRootsQuery, W as TypingBody, _ as InboxAckBody, a as METHODS, b as InboxQuery, c as queryObject, d as VERSION, f as SYNC, g as IdentityUnlinkBody, h as EventsQuery, j as SignInQuery, k as ScheduleBody, l as readCapped, m as EditBody, o as createRouter, p as AckBody, s as openapi, u as readJson, v as InboxNextQuery, w as MessagesQuery, x as LabelBody, y as InboxPushBody, z as SyncImportBody } from "./watchers-hash-C10qKYPa.mjs";
|
|
4
4
|
import { n as createBoardClient, t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { r as derivedId } from "./wait-until-Dw4XrIDQ.mjs";
|
|
6
6
|
import { a as forbidden, c as toErrorResponse, l as unauthorized, n as RejectedError, o as json, r as badRequest, s as notFound, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
7
7
|
import { d as issuersOf, g as verifiesTokens, l as hostPrincipal, o as createAuthority, s as fromTokens } from "./auth-lTkOgvcE.mjs";
|
|
8
8
|
import { i as resolveActor, r as claimsOf, t as KNOWN_AGENTS } from "./actors-BLXVp4oZ.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as createHttp } from "./backend-http-
|
|
1
|
+
import { r as createHttp } from "./backend-http-DApbemsm.mjs";
|
|
2
2
|
//#region src/backend-http/index.default.ts
|
|
3
3
|
/**
|
|
4
4
|
* The board at `url`, which keeps the log. Outside Node there's no environment to find it or a
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { r as duration, t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
2
2
|
import { t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
3
|
-
import { t as http } from "./index.default-
|
|
4
|
-
import { t as createBoard } from "./exports-
|
|
3
|
+
import { t as http } from "./index.default-_e7u6Z-I.mjs";
|
|
4
|
+
import { t as createBoard } from "./exports-T-ART7P7.mjs";
|
|
5
5
|
import { t as DroppedError } from "./middleware-ChKM1zuu.mjs";
|
|
6
6
|
//#region src/server/index.default.ts
|
|
7
7
|
/**
|
|
@@ -11,7 +11,7 @@ import { t as DroppedError } from "./middleware-ChKM1zuu.mjs";
|
|
|
11
11
|
function board(options = {}) {
|
|
12
12
|
return createBoard(options, {
|
|
13
13
|
http: () => http(),
|
|
14
|
-
sync: () => import("./engine-
|
|
14
|
+
sync: () => import("./engine-Bfe6pcXb.mjs"),
|
|
15
15
|
serving: null
|
|
16
16
|
});
|
|
17
17
|
}
|
package/dist/index.node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as duration, t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
2
2
|
import { t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
3
3
|
import { t as DroppedError } from "./middleware-ChKM1zuu.mjs";
|
|
4
|
-
import { t as board } from "./server-
|
|
4
|
+
import { t as board } from "./server-DMNfPSAZ.mjs";
|
|
5
5
|
export { BoardError, DroppedError, HttpError, board, duration };
|