js-bao 0.12.0-alpha.6 → 0.12.0-alpha.7
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/browser.cjs +183 -55
- package/dist/browser.d.cts +71 -16
- package/dist/browser.d.ts +71 -16
- package/dist/browser.js +183 -55
- package/dist/format2-worker.js +102 -38
- package/dist/index.cjs +185 -55
- package/dist/index.d.cts +71 -16
- package/dist/index.d.ts +71 -16
- package/dist/index.js +182 -54
- package/dist/node.cjs +185 -55
- package/dist/node.d.cts +71 -16
- package/dist/node.d.ts +71 -16
- package/dist/node.js +182 -54
- package/package.json +1 -1
package/dist/browser.cjs
CHANGED
|
@@ -2494,16 +2494,18 @@ function refusedWritesOf(entry) {
|
|
|
2494
2494
|
seq: entry.seq
|
|
2495
2495
|
}));
|
|
2496
2496
|
}
|
|
2497
|
-
var RECORD_LOG_DEFAULT_EPOCH_ENTRIES, RECORD_LOG_ENTRY_MAX_BYTES, RECORD_LOG_FRAME_TARGET_BYTES,
|
|
2497
|
+
var RECORD_LOG_DEFAULT_EPOCH_ENTRIES, RECORD_LOG_ENTRY_MAX_BYTES, RECORD_LOG_FRAME_TARGET_BYTES, RECORD_LOG_FRAMES_IN_FLIGHT, RECORD_LOG_FRAME_WAIT_MS, RECORD_LOG_TICK_MS, RECORD_LOG_UP_BATCH_MAX, RECORD_LOG_FRAME_ENTRIES_MAX, RECORD_LOG_FIRST_EPOCH, RecordLogStaleReceiptError, RecordLogStaleReplacementError, RecordLogWriteRefusedError;
|
|
2498
2498
|
var init_types = __esm({
|
|
2499
2499
|
"src/record-log-client/types.ts"() {
|
|
2500
2500
|
"use strict";
|
|
2501
2501
|
RECORD_LOG_DEFAULT_EPOCH_ENTRIES = 1e4;
|
|
2502
2502
|
RECORD_LOG_ENTRY_MAX_BYTES = 1024 * 1024;
|
|
2503
2503
|
RECORD_LOG_FRAME_TARGET_BYTES = 64 * 1024;
|
|
2504
|
-
|
|
2504
|
+
RECORD_LOG_FRAMES_IN_FLIGHT = 1;
|
|
2505
|
+
RECORD_LOG_FRAME_WAIT_MS = 2e3;
|
|
2505
2506
|
RECORD_LOG_TICK_MS = 5;
|
|
2506
2507
|
RECORD_LOG_UP_BATCH_MAX = 200;
|
|
2508
|
+
RECORD_LOG_FRAME_ENTRIES_MAX = 64;
|
|
2507
2509
|
RECORD_LOG_FIRST_EPOCH = 1;
|
|
2508
2510
|
RecordLogStaleReceiptError = class _RecordLogStaleReceiptError extends Error {
|
|
2509
2511
|
/**
|
|
@@ -8737,9 +8739,11 @@ __export(browser_exports, {
|
|
|
8737
8739
|
RECORD_LOG_DRAIN_WAIT_MS: () => RECORD_LOG_DRAIN_WAIT_MS,
|
|
8738
8740
|
RECORD_LOG_ENTRY_MAX_BYTES: () => RECORD_LOG_ENTRY_MAX_BYTES,
|
|
8739
8741
|
RECORD_LOG_FIRST_EPOCH: () => RECORD_LOG_FIRST_EPOCH,
|
|
8742
|
+
RECORD_LOG_FRAMES_IN_FLIGHT: () => RECORD_LOG_FRAMES_IN_FLIGHT,
|
|
8743
|
+
RECORD_LOG_FRAME_ENTRIES_MAX: () => RECORD_LOG_FRAME_ENTRIES_MAX,
|
|
8740
8744
|
RECORD_LOG_FRAME_TARGET_BYTES: () => RECORD_LOG_FRAME_TARGET_BYTES,
|
|
8745
|
+
RECORD_LOG_FRAME_WAIT_MS: () => RECORD_LOG_FRAME_WAIT_MS,
|
|
8741
8746
|
RECORD_LOG_RESULT_GAP_MS: () => RECORD_LOG_RESULT_GAP_MS,
|
|
8742
|
-
RECORD_LOG_SEND_WINDOW: () => RECORD_LOG_SEND_WINDOW,
|
|
8743
8747
|
RECORD_LOG_STORE_CHANNEL: () => RECORD_LOG_STORE_CHANNEL,
|
|
8744
8748
|
RECORD_LOG_TEARDOWN_DRAIN_BOUND_MS: () => RECORD_LOG_TEARDOWN_DRAIN_BOUND_MS,
|
|
8745
8749
|
RECORD_LOG_TICK_MS: () => RECORD_LOG_TICK_MS,
|
|
@@ -10593,6 +10597,16 @@ var RecordLogClient = class {
|
|
|
10593
10597
|
get pending() {
|
|
10594
10598
|
return this.tables.pending(this.writer);
|
|
10595
10599
|
}
|
|
10600
|
+
/** One of a writer's unjudged entries, or `undefined`. */
|
|
10601
|
+
entryOf(writer, seq) {
|
|
10602
|
+
if (this.tables.pendingEntry) return this.tables.pendingEntry(writer, seq);
|
|
10603
|
+
return this.tables.pending(writer).find((p) => p.seq === seq);
|
|
10604
|
+
}
|
|
10605
|
+
/** A writer's unjudged entries at or below `seq`, in `seq` order. */
|
|
10606
|
+
entriesThrough(writer, seq) {
|
|
10607
|
+
if (this.tables.pendingThrough) return this.tables.pendingThrough(writer, seq);
|
|
10608
|
+
return this.tables.pending(writer).filter((p) => p.seq <= seq);
|
|
10609
|
+
}
|
|
10596
10610
|
/**
|
|
10597
10611
|
* Write an entry: the next `seq`, the open epoch, the cursor. It joins the
|
|
10598
10612
|
* pending log; the store applies it to the view as a guess.
|
|
@@ -10662,7 +10676,7 @@ var RecordLogClient = class {
|
|
|
10662
10676
|
}
|
|
10663
10677
|
/** The room has moved past a withdrawn entry's sequence number. */
|
|
10664
10678
|
settled(seq) {
|
|
10665
|
-
const entry = this.
|
|
10679
|
+
const entry = this.entryOf(this.writer, seq);
|
|
10666
10680
|
if (entry?.void) this.tables.deletePending(this.writer, seq);
|
|
10667
10681
|
return { touched: [] };
|
|
10668
10682
|
}
|
|
@@ -10677,7 +10691,7 @@ var RecordLogClient = class {
|
|
|
10677
10691
|
receive(item) {
|
|
10678
10692
|
const out = { touched: [], acknowledged: [], settled: [] };
|
|
10679
10693
|
if ("void" in item) {
|
|
10680
|
-
const entry = this.
|
|
10694
|
+
const entry = this.entryOf(this.writer, item.seq);
|
|
10681
10695
|
if (entry) {
|
|
10682
10696
|
this.tables.deletePending(this.writer, item.seq);
|
|
10683
10697
|
out.settled.push({ writer: this.writer, seq: item.seq });
|
|
@@ -10686,7 +10700,7 @@ var RecordLogClient = class {
|
|
|
10686
10700
|
return out;
|
|
10687
10701
|
}
|
|
10688
10702
|
if ("duplicate" in item) {
|
|
10689
|
-
const entry = this.
|
|
10703
|
+
const entry = this.entryOf(this.writer, item.seq);
|
|
10690
10704
|
out.acknowledged.push({ writer: this.writer, seq: item.seq });
|
|
10691
10705
|
if (entry) {
|
|
10692
10706
|
this.tables.deletePending(this.writer, item.seq);
|
|
@@ -10696,7 +10710,7 @@ var RecordLogClient = class {
|
|
|
10696
10710
|
return out;
|
|
10697
10711
|
}
|
|
10698
10712
|
if (item.w !== void 0 && item.seq !== void 0 && this.tables.isWriter(item.w)) {
|
|
10699
|
-
const entry = this.
|
|
10713
|
+
const entry = this.entryOf(item.w, item.seq);
|
|
10700
10714
|
out.acknowledged.push({ writer: item.w, seq: item.seq });
|
|
10701
10715
|
if (entry && !entry.void && entry.acked === void 0) {
|
|
10702
10716
|
this.tables.putPending(item.w, { ...entry, acked: item.l, ...item.alias ? { alias: item.alias } : {} });
|
|
@@ -10729,7 +10743,7 @@ var RecordLogClient = class {
|
|
|
10729
10743
|
*/
|
|
10730
10744
|
settleAcknowledged(out) {
|
|
10731
10745
|
for (const { writer, seq } of this.tables.acknowledgedThrough(this.tables.cursor())) {
|
|
10732
|
-
const entry = this.
|
|
10746
|
+
const entry = this.entryOf(writer, seq);
|
|
10733
10747
|
if (!entry) continue;
|
|
10734
10748
|
this.tables.deletePending(writer, seq);
|
|
10735
10749
|
out.settled.push({ writer, seq });
|
|
@@ -10744,8 +10758,7 @@ var RecordLogClient = class {
|
|
|
10744
10758
|
* wrote since.
|
|
10745
10759
|
*/
|
|
10746
10760
|
confirmEntry(writer, seq, alias, out) {
|
|
10747
|
-
for (const p of this.
|
|
10748
|
-
if (p.seq > seq) continue;
|
|
10761
|
+
for (const p of this.entriesThrough(writer, seq)) {
|
|
10749
10762
|
this.tables.deletePending(writer, p.seq);
|
|
10750
10763
|
out.settled.push({ writer, seq: p.seq });
|
|
10751
10764
|
if (!p.void) out.touched.push(...keysOf(p));
|
|
@@ -11208,6 +11221,12 @@ var MemoryRecordLogTables = class {
|
|
|
11208
11221
|
pending(writer) {
|
|
11209
11222
|
return [...this.writers.get(writer)?.pending ?? []];
|
|
11210
11223
|
}
|
|
11224
|
+
pendingEntry(writer, seq) {
|
|
11225
|
+
return this.writers.get(writer)?.pending.find((p) => p.seq === seq);
|
|
11226
|
+
}
|
|
11227
|
+
pendingThrough(writer, seq) {
|
|
11228
|
+
return (this.writers.get(writer)?.pending ?? []).filter((p) => p.seq <= seq);
|
|
11229
|
+
}
|
|
11211
11230
|
putPending(writer, entry) {
|
|
11212
11231
|
const w = this.writerOf(writer);
|
|
11213
11232
|
const at = w.pending.findIndex((p) => p.seq === entry.seq);
|
|
@@ -11332,6 +11351,10 @@ function recordLogClientDDL() {
|
|
|
11332
11351
|
PRIMARY KEY (doc_id, writer, seq)
|
|
11333
11352
|
)`,
|
|
11334
11353
|
`CREATE INDEX IF NOT EXISTS record_log_pending_acked ON record_log_pending(doc_id, acked) WHERE acked IS NOT NULL`,
|
|
11354
|
+
// A write numbers its entry after the highest `ord` and counts its
|
|
11355
|
+
// epoch's unwithdrawn entries; neither reads the entries.
|
|
11356
|
+
`CREATE INDEX IF NOT EXISTS record_log_pending_by_ord ON record_log_pending(doc_id, ord)`,
|
|
11357
|
+
`CREATE INDEX IF NOT EXISTS record_log_pending_by_epoch ON record_log_pending(doc_id, e) WHERE void = 0`,
|
|
11335
11358
|
`CREATE TABLE IF NOT EXISTS record_log_pending_keys (
|
|
11336
11359
|
doc_id TEXT NOT NULL,
|
|
11337
11360
|
writer TEXT NOT NULL,
|
|
@@ -11341,7 +11364,10 @@ function recordLogClientDDL() {
|
|
|
11341
11364
|
resolved INTEGER NOT NULL DEFAULT 0,
|
|
11342
11365
|
PRIMARY KEY (doc_id, writer, seq, _type, _id)
|
|
11343
11366
|
)`,
|
|
11344
|
-
|
|
11367
|
+
// The entries touching a record, answered from the index alone: without
|
|
11368
|
+
// `writer, seq` in it SQLite reads the keys' primary key by document.
|
|
11369
|
+
`DROP INDEX IF EXISTS record_log_pending_keys_by_record`,
|
|
11370
|
+
`CREATE INDEX IF NOT EXISTS record_log_pending_keys_by_record_entry ON record_log_pending_keys(doc_id, _type, _id, writer, seq)`,
|
|
11345
11371
|
`CREATE TABLE IF NOT EXISTS record_log_pending_values (
|
|
11346
11372
|
doc_id TEXT NOT NULL,
|
|
11347
11373
|
writer TEXT NOT NULL,
|
|
@@ -11445,6 +11471,11 @@ function queryRow(row, spec) {
|
|
|
11445
11471
|
function rowsOf(sql, query, ...bindings) {
|
|
11446
11472
|
return Array.from(sql.exec(query, ...bindings));
|
|
11447
11473
|
}
|
|
11474
|
+
function pendingRow(row) {
|
|
11475
|
+
const entry = JSON.parse(String(row.entry));
|
|
11476
|
+
if (row.acked !== null && row.acked !== void 0) entry.acked = Number(row.acked);
|
|
11477
|
+
return entry;
|
|
11478
|
+
}
|
|
11448
11479
|
var SqlRecordLogTables = class {
|
|
11449
11480
|
constructor(sql, docId) {
|
|
11450
11481
|
this.sql = sql;
|
|
@@ -11456,6 +11487,11 @@ var SqlRecordLogTables = class {
|
|
|
11456
11487
|
heldWarned = false;
|
|
11457
11488
|
/** Records of entries changed or removed since the last {@link takeDirty}, resolutions included. */
|
|
11458
11489
|
dirty = [];
|
|
11490
|
+
/**
|
|
11491
|
+
* Inside {@link countingEpochs}, each epoch's unwithdrawn pending entries
|
|
11492
|
+
* once counted, kept as entries are added; `null` outside it.
|
|
11493
|
+
*/
|
|
11494
|
+
epochPending = null;
|
|
11459
11495
|
state() {
|
|
11460
11496
|
return rowsOf(this.sql, `SELECT * FROM record_log_state WHERE doc_id = ?`, this.docId)[0] ?? null;
|
|
11461
11497
|
}
|
|
@@ -11680,16 +11716,32 @@ var SqlRecordLogTables = class {
|
|
|
11680
11716
|
`SELECT entry, acked FROM record_log_pending WHERE doc_id = ? AND writer = ? ORDER BY seq`,
|
|
11681
11717
|
this.docId,
|
|
11682
11718
|
writer
|
|
11683
|
-
).map(
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11719
|
+
).map(pendingRow);
|
|
11720
|
+
}
|
|
11721
|
+
pendingEntry(writer, seq) {
|
|
11722
|
+
const [row] = rowsOf(
|
|
11723
|
+
this.sql,
|
|
11724
|
+
`SELECT entry, acked FROM record_log_pending WHERE doc_id = ? AND writer = ? AND seq = ?`,
|
|
11725
|
+
this.docId,
|
|
11726
|
+
writer,
|
|
11727
|
+
seq
|
|
11728
|
+
);
|
|
11729
|
+
return row ? pendingRow(row) : void 0;
|
|
11730
|
+
}
|
|
11731
|
+
pendingThrough(writer, seq) {
|
|
11732
|
+
return rowsOf(
|
|
11733
|
+
this.sql,
|
|
11734
|
+
`SELECT entry, acked FROM record_log_pending WHERE doc_id = ? AND writer = ? AND seq <= ? ORDER BY seq`,
|
|
11735
|
+
this.docId,
|
|
11736
|
+
writer,
|
|
11737
|
+
seq
|
|
11738
|
+
).map(pendingRow);
|
|
11688
11739
|
}
|
|
11689
11740
|
putPending(writer, entry) {
|
|
11690
11741
|
const { acked, ...stored } = entry;
|
|
11691
11742
|
const exists = rowsOf(this.sql, `SELECT 1 FROM record_log_pending WHERE doc_id = ? AND writer = ? AND seq = ?`, this.docId, writer, entry.seq).length > 0;
|
|
11692
11743
|
if (exists) {
|
|
11744
|
+
this.epochPending?.clear();
|
|
11693
11745
|
this.markDirty(writer, entry.seq);
|
|
11694
11746
|
this.sql.exec(
|
|
11695
11747
|
`UPDATE record_log_pending SET entry = ?, void = ?, acked = ?, e = ? WHERE doc_id = ? AND writer = ? AND seq = ?`,
|
|
@@ -11721,6 +11773,8 @@ var SqlRecordLogTables = class {
|
|
|
11721
11773
|
entry.void ? 1 : 0,
|
|
11722
11774
|
acked ?? null
|
|
11723
11775
|
);
|
|
11776
|
+
const counted = this.epochPending?.get(entry.e);
|
|
11777
|
+
if (!entry.void && counted !== void 0) this.epochPending.set(entry.e, counted + 1);
|
|
11724
11778
|
}
|
|
11725
11779
|
this.putValues(writer, entry);
|
|
11726
11780
|
for (const key of keysOf(entry)) {
|
|
@@ -11761,6 +11815,7 @@ var SqlRecordLogTables = class {
|
|
|
11761
11815
|
}
|
|
11762
11816
|
}
|
|
11763
11817
|
deletePending(writer, seq) {
|
|
11818
|
+
this.epochPending?.clear();
|
|
11764
11819
|
this.markDirty(writer, seq);
|
|
11765
11820
|
this.sql.exec(`DELETE FROM record_log_pending WHERE doc_id = ? AND writer = ? AND seq = ?`, this.docId, writer, seq);
|
|
11766
11821
|
this.sql.exec(`DELETE FROM record_log_pending_keys WHERE doc_id = ? AND writer = ? AND seq = ?`, this.docId, writer, seq);
|
|
@@ -11810,13 +11865,30 @@ var SqlRecordLogTables = class {
|
|
|
11810
11865
|
);
|
|
11811
11866
|
}
|
|
11812
11867
|
pendingInEpoch(e) {
|
|
11868
|
+
const counted = this.epochPending?.get(e);
|
|
11869
|
+
if (counted !== void 0) return counted;
|
|
11813
11870
|
const [row] = rowsOf(
|
|
11814
11871
|
this.sql,
|
|
11815
11872
|
`SELECT COUNT(*) AS n FROM record_log_pending WHERE doc_id = ? AND void = 0 AND e = ?`,
|
|
11816
11873
|
this.docId,
|
|
11817
11874
|
e
|
|
11818
11875
|
);
|
|
11819
|
-
|
|
11876
|
+
const n = Number(row?.n ?? 0);
|
|
11877
|
+
this.epochPending?.set(e, n);
|
|
11878
|
+
return n;
|
|
11879
|
+
}
|
|
11880
|
+
/**
|
|
11881
|
+
* Run `fn` counting each epoch's pending entries once: a commit's writes
|
|
11882
|
+
* each check the budget, and inside one transaction only they change the
|
|
11883
|
+
* count. Any other change to the pending log counts again.
|
|
11884
|
+
*/
|
|
11885
|
+
countingEpochs(fn) {
|
|
11886
|
+
this.epochPending = /* @__PURE__ */ new Map();
|
|
11887
|
+
try {
|
|
11888
|
+
return fn();
|
|
11889
|
+
} finally {
|
|
11890
|
+
this.epochPending = null;
|
|
11891
|
+
}
|
|
11820
11892
|
}
|
|
11821
11893
|
};
|
|
11822
11894
|
function modelsOf(keys) {
|
|
@@ -12441,31 +12513,33 @@ var RecordLogStore = class {
|
|
|
12441
12513
|
* epochs this client may write into are full; nothing of the command lands.
|
|
12442
12514
|
*/
|
|
12443
12515
|
commitEntries(command) {
|
|
12444
|
-
return this.sql.transaction(
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
const
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12516
|
+
return this.sql.transaction(
|
|
12517
|
+
() => this.tables.countingEpochs(() => {
|
|
12518
|
+
const client = this.client(command.writer);
|
|
12519
|
+
const results = [];
|
|
12520
|
+
const refreshed = [];
|
|
12521
|
+
for (const write of command.writes) {
|
|
12522
|
+
const stale = (write.expect ?? []).some(
|
|
12523
|
+
({ model, id, revision }) => recordLogRowRevision(this.read(model, id)) !== revision
|
|
12524
|
+
);
|
|
12525
|
+
if (stale) {
|
|
12526
|
+
results.push({ stale: true });
|
|
12527
|
+
continue;
|
|
12528
|
+
}
|
|
12529
|
+
client.budgetCheck(this.docId);
|
|
12530
|
+
const [m, ...also] = write.mutations;
|
|
12531
|
+
const entry = client.write(m, {
|
|
12532
|
+
also,
|
|
12533
|
+
...write.upsertOn !== void 0 ? { upsertOn: write.upsertOn } : {},
|
|
12534
|
+
ts: command.ts ?? Date.now()
|
|
12535
|
+
});
|
|
12536
|
+
refreshed.push(...this.refreshView([...keysOf(entry), ...this.tables.takeDirty()]));
|
|
12537
|
+
results.push({ entry });
|
|
12538
|
+
}
|
|
12539
|
+
const changed = this.settleView([], command.project ?? [], refreshed);
|
|
12540
|
+
return { results, changed, cursor: this.tables.cursor() };
|
|
12541
|
+
})
|
|
12542
|
+
);
|
|
12469
12543
|
}
|
|
12470
12544
|
/** Apply what the room relayed, item by item. */
|
|
12471
12545
|
receive(command) {
|
|
@@ -16294,20 +16368,23 @@ var RecordLogSender = class {
|
|
|
16294
16368
|
constructor(transport, options = {}) {
|
|
16295
16369
|
this.transport = transport;
|
|
16296
16370
|
this.tickMs = options.tickMs ?? RECORD_LOG_TICK_MS;
|
|
16297
|
-
this.window = options.window ?? RECORD_LOG_SEND_WINDOW;
|
|
16298
16371
|
this.frameTargetBytes = options.frameTargetBytes ?? RECORD_LOG_FRAME_TARGET_BYTES;
|
|
16299
|
-
this.batchMax = options.batchMax ??
|
|
16372
|
+
this.batchMax = options.batchMax ?? RECORD_LOG_FRAME_ENTRIES_MAX;
|
|
16373
|
+
this.frameWaitMs = options.frameWaitMs ?? RECORD_LOG_FRAME_WAIT_MS;
|
|
16300
16374
|
this.clock = options.clock ?? platformClock;
|
|
16301
16375
|
}
|
|
16302
16376
|
queue = [];
|
|
16303
16377
|
inFlight = /* @__PURE__ */ new Set();
|
|
16378
|
+
/** The frames sent and not yet answered in full, oldest first: the numbers each still owes. */
|
|
16379
|
+
frames = [];
|
|
16304
16380
|
timer = null;
|
|
16381
|
+
/** Runs out when the oldest frame has waited {@link frameWaitMs}. */
|
|
16382
|
+
frameTimer = null;
|
|
16305
16383
|
tickMs;
|
|
16306
16384
|
frameTargetBytes;
|
|
16307
16385
|
batchMax;
|
|
16386
|
+
frameWaitMs;
|
|
16308
16387
|
clock;
|
|
16309
|
-
/** How many unacknowledged entries may be in flight; a bulk-saving app may widen it. */
|
|
16310
|
-
window;
|
|
16311
16388
|
/** Entries queued and not yet sent. */
|
|
16312
16389
|
queued() {
|
|
16313
16390
|
return this.queue.filter((q) => q.kind === "entry").length;
|
|
@@ -16316,6 +16393,10 @@ var RecordLogSender = class {
|
|
|
16316
16393
|
inFlightCount() {
|
|
16317
16394
|
return this.inFlight.size;
|
|
16318
16395
|
}
|
|
16396
|
+
/** `record.entries` frames that hold the next back: sent, not answered in full, and within their wait. */
|
|
16397
|
+
framesInFlight() {
|
|
16398
|
+
return this.frames.length;
|
|
16399
|
+
}
|
|
16319
16400
|
/** Queue an entry to send; one already queued or in flight is not queued twice. */
|
|
16320
16401
|
enqueue(entry) {
|
|
16321
16402
|
if (this.inFlight.has(entry.seq)) return;
|
|
@@ -16336,10 +16417,12 @@ var RecordLogSender = class {
|
|
|
16336
16417
|
}
|
|
16337
16418
|
/**
|
|
16338
16419
|
* The room is missing entries from `expected` on: every entry in flight
|
|
16339
|
-
* from there is sent again, in `seq` order, ahead of what is queued.
|
|
16420
|
+
* from there is sent again, in `seq` order, ahead of what is queued. A
|
|
16421
|
+
* frame no longer owes them; one that owed nothing else is done.
|
|
16340
16422
|
*/
|
|
16341
16423
|
resendFrom(expected, entries) {
|
|
16342
16424
|
for (const seq of [...this.inFlight]) if (seq >= expected) this.inFlight.delete(seq);
|
|
16425
|
+
this.forget((seq) => seq >= expected);
|
|
16343
16426
|
const again = entries.filter((entry) => entry.seq >= expected).filter((entry) => !this.queue.some((q) => q.kind === "entry" && q.entry.seq === entry.seq)).sort((a, b) => a.seq - b.seq).map((entry) => ({ kind: "entry", entry, bytes: recordLogJsonBytes(entry) }));
|
|
16344
16427
|
this.queue.unshift(...again);
|
|
16345
16428
|
if (this.queue.length) this.schedule();
|
|
@@ -16349,7 +16432,7 @@ var RecordLogSender = class {
|
|
|
16349
16432
|
* entry it already judged only when it arrives again. Every entry in
|
|
16350
16433
|
* flight goes back to the head of the queue in `seq` order, ahead of what
|
|
16351
16434
|
* was queued after it, in its form among `unjudged`; one no longer there
|
|
16352
|
-
* is dropped.
|
|
16435
|
+
* is dropped. No frame is in flight any more.
|
|
16353
16436
|
*/
|
|
16354
16437
|
requeueInFlight(unjudged) {
|
|
16355
16438
|
const bySeq = new Map(unjudged.map((entry) => [entry.seq, entry]));
|
|
@@ -16360,6 +16443,7 @@ var RecordLogSender = class {
|
|
|
16360
16443
|
again.push({ kind: "entry", entry, bytes: recordLogJsonBytes(entry) });
|
|
16361
16444
|
}
|
|
16362
16445
|
this.inFlight.clear();
|
|
16446
|
+
this.clearFrames();
|
|
16363
16447
|
this.queue.unshift(...again);
|
|
16364
16448
|
if (this.queue.length) this.schedule();
|
|
16365
16449
|
}
|
|
@@ -16368,14 +16452,21 @@ var RecordLogSender = class {
|
|
|
16368
16452
|
this.queue.push({ kind: "control", frame });
|
|
16369
16453
|
this.schedule();
|
|
16370
16454
|
}
|
|
16371
|
-
/**
|
|
16455
|
+
/**
|
|
16456
|
+
* The room has judged `seq` (a result, `void`, `duplicate` or a refusal):
|
|
16457
|
+
* its frame no longer owes it, and a frame answered in full lets the next
|
|
16458
|
+
* leave at the next tick.
|
|
16459
|
+
*/
|
|
16372
16460
|
acknowledged(seq) {
|
|
16373
|
-
|
|
16461
|
+
const answered = this.inFlight.delete(seq);
|
|
16462
|
+
this.forget((owed) => owed === seq);
|
|
16463
|
+
if (answered && this.queue.length) this.schedule();
|
|
16374
16464
|
}
|
|
16375
16465
|
/** Forget everything queued and in flight: the connection that would carry it is gone. */
|
|
16376
16466
|
reset() {
|
|
16377
16467
|
this.queue.length = 0;
|
|
16378
16468
|
this.inFlight.clear();
|
|
16469
|
+
this.clearFrames();
|
|
16379
16470
|
if (this.timer !== null) this.clock.clearTimeout(this.timer);
|
|
16380
16471
|
this.timer = null;
|
|
16381
16472
|
}
|
|
@@ -16390,7 +16481,39 @@ var RecordLogSender = class {
|
|
|
16390
16481
|
this.flush();
|
|
16391
16482
|
}, this.tickMs);
|
|
16392
16483
|
}
|
|
16393
|
-
/**
|
|
16484
|
+
/** The frames no longer owe the numbers `owed` picks; one that owes nothing is done. */
|
|
16485
|
+
forget(owed) {
|
|
16486
|
+
const oldest = this.frames[0];
|
|
16487
|
+
for (const frame of this.frames) for (const seq of [...frame]) if (owed(seq)) frame.delete(seq);
|
|
16488
|
+
this.frames = this.frames.filter((frame) => frame.size > 0);
|
|
16489
|
+
if (this.frames[0] !== oldest) {
|
|
16490
|
+
this.clearFrameTimer();
|
|
16491
|
+
this.armFrameTimer();
|
|
16492
|
+
}
|
|
16493
|
+
}
|
|
16494
|
+
clearFrames() {
|
|
16495
|
+
this.frames = [];
|
|
16496
|
+
this.clearFrameTimer();
|
|
16497
|
+
}
|
|
16498
|
+
clearFrameTimer() {
|
|
16499
|
+
if (this.frameTimer !== null) this.clock.clearTimeout(this.frameTimer);
|
|
16500
|
+
this.frameTimer = null;
|
|
16501
|
+
}
|
|
16502
|
+
/**
|
|
16503
|
+
* Time the oldest frame. Unanswered at the end of its wait, it stops
|
|
16504
|
+
* holding the next back; its numbers stay in flight, so a `record.gap`
|
|
16505
|
+
* still sends them again and their answers are still taken.
|
|
16506
|
+
*/
|
|
16507
|
+
armFrameTimer() {
|
|
16508
|
+
if (this.frameTimer !== null || !this.frames.length) return;
|
|
16509
|
+
this.frameTimer = this.clock.setTimeout(() => {
|
|
16510
|
+
this.frameTimer = null;
|
|
16511
|
+
this.frames.shift();
|
|
16512
|
+
this.armFrameTimer();
|
|
16513
|
+
if (this.queue.length) this.schedule();
|
|
16514
|
+
}, this.frameWaitMs);
|
|
16515
|
+
}
|
|
16516
|
+
/** Send what the frame in flight and the transport allow now. */
|
|
16394
16517
|
flush() {
|
|
16395
16518
|
if (!this.transport.connected()) return;
|
|
16396
16519
|
let batch = [];
|
|
@@ -16398,6 +16521,8 @@ var RecordLogSender = class {
|
|
|
16398
16521
|
const send = () => {
|
|
16399
16522
|
if (!batch.length) return;
|
|
16400
16523
|
this.transport.send({ type: "record.entries", entries: batch });
|
|
16524
|
+
this.frames.push(new Set(batch.map((entry) => entry.seq)));
|
|
16525
|
+
this.armFrameTimer();
|
|
16401
16526
|
batch = [];
|
|
16402
16527
|
bytes = 0;
|
|
16403
16528
|
};
|
|
@@ -16409,7 +16534,7 @@ var RecordLogSender = class {
|
|
|
16409
16534
|
this.transport.send(head.frame);
|
|
16410
16535
|
continue;
|
|
16411
16536
|
}
|
|
16412
|
-
if (this.
|
|
16537
|
+
if (!batch.length && this.frames.length >= RECORD_LOG_FRAMES_IN_FLIGHT) break;
|
|
16413
16538
|
const fits = batch.length < this.batchMax && (batch.length === 0 || bytes + head.bytes <= this.frameTargetBytes);
|
|
16414
16539
|
if (!fits) {
|
|
16415
16540
|
send();
|
|
@@ -16777,6 +16902,7 @@ var RecordLogDocumentSession = class {
|
|
|
16777
16902
|
await this.store.refreshMarks();
|
|
16778
16903
|
if (generation !== this.generation) return;
|
|
16779
16904
|
this.current = { writer, orphan: false };
|
|
16905
|
+
this.sender.reset();
|
|
16780
16906
|
await this.sendOpen();
|
|
16781
16907
|
return;
|
|
16782
16908
|
}
|
|
@@ -17394,11 +17520,13 @@ var RecordLogDocumentSession = class {
|
|
|
17394
17520
|
return;
|
|
17395
17521
|
}
|
|
17396
17522
|
const writer = this.current.writer;
|
|
17397
|
-
const before = await this.store.pendingOf(writer);
|
|
17398
|
-
const named = before.find((p) => p.seq === refusal.seq);
|
|
17399
17523
|
const reply = await this.store.refused(refusal, this.host.projectionSpecs(), writer);
|
|
17400
17524
|
const sender = this.senderOf(writer);
|
|
17401
17525
|
const after = new Map((await this.store.pendingOf(writer)).map((p) => [p.seq, p]));
|
|
17526
|
+
if (after.get(refusal.seq)?.void) {
|
|
17527
|
+
await this.store.dispatch({ kind: "settled", writer, seq: refusal.seq });
|
|
17528
|
+
after.delete(refusal.seq);
|
|
17529
|
+
}
|
|
17402
17530
|
for (const frame of reply.frames) sender?.enqueueControl(frame);
|
|
17403
17531
|
const handed = reply.handedBack.filter((p) => !p.void);
|
|
17404
17532
|
for (const p of reply.handedBack) {
|
|
@@ -17407,7 +17535,6 @@ var RecordLogDocumentSession = class {
|
|
|
17407
17535
|
}
|
|
17408
17536
|
sender?.acknowledged(refusal.seq);
|
|
17409
17537
|
this.answered([refusal.seq, ...reply.handedBack.map((p) => p.seq)]);
|
|
17410
|
-
if (named?.void) await this.store.dispatch({ kind: "settled", writer, seq: refusal.seq });
|
|
17411
17538
|
if (!handed.length) {
|
|
17412
17539
|
Logger.debug(`[record-log] ${this.docId}: refusal of ${refusal.seq} (${refusal.code}) withdrew nothing new`);
|
|
17413
17540
|
return;
|
|
@@ -17513,6 +17640,7 @@ var RecordLogDocumentSession = class {
|
|
|
17513
17640
|
if (!current.orphan) {
|
|
17514
17641
|
this.store.renumbered(renumbered);
|
|
17515
17642
|
await this.store.refreshMarks();
|
|
17643
|
+
this.sender.reset();
|
|
17516
17644
|
}
|
|
17517
17645
|
this.current = { writer, orphan: current.orphan };
|
|
17518
17646
|
await this.sendOpen();
|