@panphora/clayjs 1.13.1 → 1.15.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/THIRD-PARTY-NOTICES.md +19 -0
- package/dist/clay.standalone.js +69385 -40992
- package/dist/server/checkpoint.cjs +1 -0
- package/dist/server/checkpoint.mjs +8 -0
- package/dist/server/core.cjs +72210 -0
- package/dist/server/rooms.cjs +1 -0
- package/dist/server/rooms.mjs +66 -0
- package/docs/guide.md +127 -99
- package/entries/clay.js +22 -0
- package/entries/sap.js +5 -5
- package/package.json +16 -2
- package/packed-contract.json +18 -3
- package/src/core/boot-phase.js +6 -0
- package/src/core/conflict-download.js +12 -3
- package/src/core/host-meta.js +75 -0
- package/src/core/persist.js +11 -0
- package/src/core/save-core.js +266 -31
- package/src/core/save.js +154 -36
- package/src/core/snapshot.js +51 -13
- package/src/core/style-comparisons.js +37 -0
- package/src/dom/insert-style-tag.js +11 -12
- package/src/lib/derived.js +116 -0
- package/src/lib/effect.js +130 -0
- package/src/lib/mutation.js +3 -1
- package/src/lib/style-adoption.js +9 -0
- package/src/lib/style.js +47 -0
- package/src/live/fast-lane.js +347 -60
- package/src/live/local-lane.js +32 -6
- package/src/live/ops.js +77 -0
- package/src/live/provenance.js +129 -0
- package/src/live/save-history.js +188 -0
- package/src/live/server-lane.js +9 -2
- package/src/live/store.js +194 -16
- package/src/loader-logic.js +16 -4
- package/src/loader.js +37 -1
- package/src/page/address.js +20 -0
- package/src/page/adopt-boot.js +189 -0
- package/src/page/adopt.js +240 -0
- package/src/page/ai-tool.js +188 -0
- package/src/page/announce.js +21 -0
- package/src/page/blocks.js +1170 -0
- package/src/page/blocksel.js +269 -0
- package/src/page/board-drag.js +226 -0
- package/src/page/board-panel.js +353 -0
- package/src/page/board-style.js +146 -0
- package/src/page/board-view.js +447 -0
- package/src/page/board.js +41 -0
- package/src/page/chart.js +220 -0
- package/src/page/checklist.js +143 -0
- package/src/page/chrome.js +587 -0
- package/src/page/csv-import.js +169 -0
- package/src/page/drag.js +202 -0
- package/src/page/drop.js +112 -0
- package/src/page/editor.js +386 -0
- package/src/page/highlight.js +268 -0
- package/src/page/inline.js +60 -1
- package/src/page/items.js +159 -0
- package/src/page/link.js +247 -0
- package/src/page/markdown.js +71 -0
- package/src/page/media.js +570 -0
- package/src/page/merge.js +130 -0
- package/src/page/model.js +71 -9
- package/src/page/placeholder.js +23 -0
- package/src/page/projection.js +647 -0
- package/src/page/recover.js +193 -0
- package/src/page/rich/cell.js +16 -0
- package/src/page/rich/chart.js +104 -0
- package/src/page/rich/csv.js +118 -0
- package/src/page/rich/page-tool.js +86 -0
- package/src/page/rich/tool-runtime.js +195 -0
- package/src/page/save-projection.js +53 -0
- package/src/page/save-states.js +85 -0
- package/src/page/schema.js +12 -5
- package/src/page/table-kinds.js +20 -0
- package/src/page/table-paste.js +57 -0
- package/src/page/table-select.js +244 -0
- package/src/page/table-style.js +25 -0
- package/src/page/table.js +650 -0
- package/src/page/themes/content.css +46 -0
- package/src/page/themes/light-1.css +269 -0
- package/src/page/themes/light-2.css +211 -0
- package/src/page/themes/light-3.css +130 -0
- package/src/page/themes/light-4.css +299 -0
- package/src/page/tool-data.js +25 -0
- package/src/page/tool-view.js +68 -0
- package/src/page/tools.js +133 -0
- package/src/page/view.js +136 -0
- package/src/page/ybind/author.js +25 -0
- package/src/page/ybind/binding.js +92 -0
- package/src/page/ybind/board-commands.js +170 -0
- package/src/page/ybind/board.js +89 -0
- package/src/page/ybind/bridge.js +382 -0
- package/src/page/ybind/conversion.js +26 -0
- package/src/page/ybind/core.js +7 -0
- package/src/page/ybind/decode.js +208 -0
- package/src/page/ybind/doc.js +58 -0
- package/src/page/ybind/external.js +190 -0
- package/src/page/ybind/group.js +124 -0
- package/src/page/ybind/index.js +12 -0
- package/src/page/ybind/layout.js +27 -0
- package/src/page/ybind/marks.js +18 -0
- package/src/page/ybind/migrate.js +152 -0
- package/src/page/ybind/moves.js +288 -0
- package/src/page/ybind/origins.js +257 -0
- package/src/page/ybind/parts.js +44 -0
- package/src/page/ybind/projection-map.js +92 -0
- package/src/page/ybind/provider.js +285 -0
- package/src/page/ybind/recovery.js +411 -0
- package/src/page/ybind/seed.js +27 -0
- package/src/page/ybind/table.js +166 -0
- package/src/page/ybind/tokens.js +93 -0
- package/src/page/ybind/walk.js +333 -0
- package/src/page/ybind/writer.js +307 -0
- package/src/plugins/derive.js +12 -0
- package/src/plugins/indicator.js +16 -6
- package/src/plugins/live.js +742 -65
- package/src/plugins/page.js +547 -0
- package/src/plugins/undo.js +62 -4
- package/src/server/checkpoint/index.js +218 -0
- package/src/server/rooms/index.js +59 -0
- package/src/server/rooms/live-data-ops.js +369 -0
- package/src/server/rooms/live-data-rooms.js +965 -0
- package/src/server/rooms/live-data-tags.js +49 -0
- package/src/server/rooms/live-page-history.js +41 -0
- package/src/server/rooms/live-provenance.js +142 -0
- package/src/server/rooms/page-admission.js +79 -0
- package/src/server/rooms/page-conversion.js +62 -0
- package/src/server/rooms/page-rebase.js +108 -0
- package/src/server/rooms/page-state.js +91 -0
- package/src/server/rooms/page-write-admission.js +63 -0
- package/src/server/rooms/page-write-guard.js +23 -0
- package/src/server/rooms/recent.js +18 -0
- package/src/server/rooms/spec-wire.js +17 -0
- package/src/standalone.js +22 -0
- package/src/sync/conflict-revert.js +2 -0
- package/src/sync/conflicts.js +2 -2
- package/src/sync/live-sync.js +36 -8
- package/src/sync/stream.js +62 -16
- package/src/ui/index.js +4 -0
- package/src/ui/toast.js +21 -5
- package/src/vendor/pierre/pierre.vendor.js +3 -0
- package/src/vendor/sap-core.vendor.js +9 -0
- package/src/vendor/sap-ownership.vendor.js +111 -0
- package/src/vendor/yjs.vendor.js +7 -0
- package/src/page/binding.js +0 -647
- package/src/page/replay.js +0 -578
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
2
|
+
import { toBase64, fromBase64 } from "../../vendor/yjs.vendor.js";
|
|
3
|
+
import { NET } from "./layout.js";
|
|
4
|
+
import { ORIGINS_ORIGIN } from "./origins.js";
|
|
5
|
+
import { PART_KEY, BASE64, entriesOf, captureYValue } from "./parts.js";
|
|
6
|
+
|
|
7
|
+
export { entriesOf } from "./parts.js";
|
|
8
|
+
|
|
9
|
+
export const enc = (u) => toBase64(u);
|
|
10
|
+
export const dec = (s) => fromBase64(s);
|
|
11
|
+
|
|
12
|
+
export const PART = 12 * 1024;
|
|
13
|
+
const baseOf = (k) => PART_KEY.exec(k)?.[1] ?? k;
|
|
14
|
+
const sameBytes = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
15
|
+
|
|
16
|
+
export function yValueOf(ydoc) {
|
|
17
|
+
return { v: 1, g: 0, y: { seed: enc(Y.encodeStateAsUpdateV2(ydoc)) } };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class YDecodeError extends Error {
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "YDecodeError";
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function strictEntries(value) {
|
|
29
|
+
if (value?.v !== 1 || !value.y || typeof value.y !== "object" || Array.isArray(value.y)) {
|
|
30
|
+
throw new YDecodeError("invalid-value", "Expected a version 1 Yjs page value");
|
|
31
|
+
}
|
|
32
|
+
const groups = new Map();
|
|
33
|
+
const whole = new Set();
|
|
34
|
+
for (const [key, text] of Object.entries(value.y)) {
|
|
35
|
+
if (typeof text !== "string") throw new YDecodeError("invalid-update", `Invalid Yjs update: ${key}`);
|
|
36
|
+
const part = PART_KEY.exec(key);
|
|
37
|
+
if (!part) { whole.add(key); continue; }
|
|
38
|
+
const index = Number(part[2]), count = Number(part[3]);
|
|
39
|
+
if (!Number.isSafeInteger(index) || !Number.isSafeInteger(count) || count < 1 || index >= count) {
|
|
40
|
+
throw new YDecodeError("invalid-update", `Invalid Yjs update part: ${key}`);
|
|
41
|
+
}
|
|
42
|
+
const group = groups.get(part[1]) ?? { count, indices: new Set() };
|
|
43
|
+
if (group.count !== count || group.indices.has(index)) {
|
|
44
|
+
throw new YDecodeError("invalid-update", `Conflicting Yjs update parts: ${part[1]}`);
|
|
45
|
+
}
|
|
46
|
+
group.indices.add(index);
|
|
47
|
+
groups.set(part[1], group);
|
|
48
|
+
}
|
|
49
|
+
for (const [key, group] of groups) {
|
|
50
|
+
if (whole.has(key)) throw new YDecodeError("invalid-update", `Conflicting Yjs update forms: ${key}`);
|
|
51
|
+
if (group.indices.size !== group.count) throw new YDecodeError("incomplete-update", `Incomplete Yjs update: ${key}`);
|
|
52
|
+
}
|
|
53
|
+
return entriesOf(value.y);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function docFromValue(value, { strict = false } = {}) {
|
|
57
|
+
const entries = strict ? strictEntries(value) : entriesOf(value.y);
|
|
58
|
+
const d = new Y.Doc();
|
|
59
|
+
try {
|
|
60
|
+
for (const [key, value] of entries) {
|
|
61
|
+
const text = strict ? value.replace(/[\t\n\f\r ]/g, "") : value;
|
|
62
|
+
if (strict && !BASE64.test(text)) throw new YDecodeError("invalid-update", `Invalid base64 in Yjs update: ${key}`);
|
|
63
|
+
Y.applyUpdateV2(d, dec(text), NET);
|
|
64
|
+
}
|
|
65
|
+
if (strict && (d.store.pendingStructs || d.store.pendingDs)) {
|
|
66
|
+
throw new YDecodeError("unresolved-update", "Yjs page updates have missing prerequisites");
|
|
67
|
+
}
|
|
68
|
+
return d;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
d.destroy();
|
|
71
|
+
if (strict && !(error instanceof YDecodeError)) {
|
|
72
|
+
throw new YDecodeError("invalid-update", `Invalid Yjs page update: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
|
|
79
|
+
const svEqual = (a, b) => { if (a.size !== b.size) return false; for (const [c, n] of a) if (b.get(c) !== n) return false; return true; };
|
|
80
|
+
|
|
81
|
+
export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 } }) {
|
|
82
|
+
const initial = entriesOf(store.value.y);
|
|
83
|
+
const known = new Set();
|
|
84
|
+
// Set when this tab's page can no longer reach the room: "lineage" when the room holds
|
|
85
|
+
// another page's history, "too-large" when the room refused an update for its size.
|
|
86
|
+
// Nothing more is offered either way, and the tab keeps its own document.
|
|
87
|
+
let stopped = null;
|
|
88
|
+
const stop = (reason) => {
|
|
89
|
+
if (stopped) return;
|
|
90
|
+
stopped = reason;
|
|
91
|
+
onStopped(reason);
|
|
92
|
+
};
|
|
93
|
+
let canCompact = !initial.some(([k]) => k[0] === "m");
|
|
94
|
+
let counter = 0;
|
|
95
|
+
const me = ydoc.clientID.toString(36);
|
|
96
|
+
const nextKey = (p) => `${p}${me}-${(counter++).toString(36)}`;
|
|
97
|
+
let timer = null;
|
|
98
|
+
|
|
99
|
+
const partsOf = (key, s) => {
|
|
100
|
+
if (s.length <= PART) return [[key, s]];
|
|
101
|
+
const n = Math.ceil(s.length / PART);
|
|
102
|
+
return Array.from({ length: n }, (_, i) => [`${key}.${i}.${n}`, s.slice(i * PART, (i + 1) * PART)]);
|
|
103
|
+
};
|
|
104
|
+
const put = (key, bytes) => {
|
|
105
|
+
canCompact = true;
|
|
106
|
+
known.add(key);
|
|
107
|
+
const parts = partsOf(key, enc(bytes));
|
|
108
|
+
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
for (const [k] of initial) known.add(k);
|
|
112
|
+
|
|
113
|
+
const onUpdate = (u, origin) => {
|
|
114
|
+
if (stopped || origin === NET || origin === ORIGINS_ORIGIN) return;
|
|
115
|
+
put(nextKey("u"), u);
|
|
116
|
+
};
|
|
117
|
+
ydoc.on("updateV2", onUpdate);
|
|
118
|
+
|
|
119
|
+
const applyKey = (k, v) => {
|
|
120
|
+
if (stopped || known.has(k)) return;
|
|
121
|
+
canCompact = true;
|
|
122
|
+
known.add(k);
|
|
123
|
+
stats.applied++;
|
|
124
|
+
Y.applyUpdateV2(ydoc, dec(v), NET);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
let wantReconcile = false;
|
|
128
|
+
let reconcilePending = false;
|
|
129
|
+
const schedule = (full, recovery) => {
|
|
130
|
+
wantReconcile ||= full;
|
|
131
|
+
reconcilePending ||= recovery;
|
|
132
|
+
if (timer) return;
|
|
133
|
+
timer = setTimer(() => {
|
|
134
|
+
timer = null;
|
|
135
|
+
const full = wantReconcile;
|
|
136
|
+
if (full) reconcile();
|
|
137
|
+
maybeCompact();
|
|
138
|
+
}, 0);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const settle = () => { reconcile(); maybeCompact(); };
|
|
142
|
+
|
|
143
|
+
function reconcile() {
|
|
144
|
+
if (stopped) return false;
|
|
145
|
+
const y = store.value.y;
|
|
146
|
+
const scratch = new Y.Doc();
|
|
147
|
+
for (const [, v] of entriesOf(y)) Y.applyUpdateV2(scratch, dec(v), NET);
|
|
148
|
+
const sv = Y.encodeStateVector(scratch);
|
|
149
|
+
const mine = Y.decodeStateVector(Y.encodeStateVector(ydoc));
|
|
150
|
+
const theirs = Y.decodeStateVector(sv);
|
|
151
|
+
if (svEqual(mine, theirs) && sameDeletes(ydoc, scratch)) { scratch.destroy(); wantReconcile = false; reconcilePending = false; return false; }
|
|
152
|
+
const diff = Y.encodeStateAsUpdateV2(ydoc, sv);
|
|
153
|
+
scratch.destroy();
|
|
154
|
+
stats.offered++;
|
|
155
|
+
put(nextKey("r"), diff);
|
|
156
|
+
wantReconcile = false;
|
|
157
|
+
reconcilePending = false;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// A fold briefly holds the old entries and the merged snapshot together, so it
|
|
162
|
+
// needs room for both under the host's 4 MB store cap, less 256 KB for frames in flight.
|
|
163
|
+
const compact = { keys: 32, room: 3840 * 1024, ...limits };
|
|
164
|
+
let fold = null;
|
|
165
|
+
let quietUntil = 0;
|
|
166
|
+
const coverCache = new Map();
|
|
167
|
+
const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
|
|
168
|
+
const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
|
|
169
|
+
|
|
170
|
+
function finishFold(auth) {
|
|
171
|
+
if (fold.parts.every((k) => k in auth)) {
|
|
172
|
+
drop(keysOf(auth, fold.inputs));
|
|
173
|
+
fold = null;
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (store.pending.length) return false;
|
|
177
|
+
drop(fold.parts.filter((k) => k in auth));
|
|
178
|
+
quietUntil = entriesOf(auth).length + compact.keys;
|
|
179
|
+
fold = null;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function dropCovered(auth, entries, crowded) {
|
|
184
|
+
const folds = entries.filter(([k]) => k[0] === "m");
|
|
185
|
+
const val = new Map(entries);
|
|
186
|
+
const norm = new Map(folds);
|
|
187
|
+
const normal = (k) => { const v = norm.get(k); if (typeof v === "string") norm.set(k, Y.mergeUpdatesV2([dec(v)])); return norm.get(k); };
|
|
188
|
+
const covered = (k, v, mk) => {
|
|
189
|
+
const c = `${k}|${mk}`;
|
|
190
|
+
if (!coverCache.has(c)) { const b = normal(mk); coverCache.set(c, sameBytes(Y.mergeUpdatesV2([b, dec(v)]), b)); }
|
|
191
|
+
return coverCache.get(c);
|
|
192
|
+
};
|
|
193
|
+
const gone = new Set();
|
|
194
|
+
for (const [k, v] of entries) {
|
|
195
|
+
if (!crowded && k[0] !== "m") continue;
|
|
196
|
+
for (const [mk] of folds) {
|
|
197
|
+
if (mk === k || gone.has(mk) || !covered(k, v, mk)) continue;
|
|
198
|
+
if (k[0] === "m" && k < mk && covered(mk, val.get(mk), k)) continue;
|
|
199
|
+
gone.add(k);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!gone.size) return false;
|
|
204
|
+
drop(keysOf(auth, gone));
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function maybeCompact() {
|
|
209
|
+
if (!canCompact) return false;
|
|
210
|
+
const auth = store.authoritative.y;
|
|
211
|
+
if (fold) return finishFold(auth);
|
|
212
|
+
const entries = entriesOf(auth);
|
|
213
|
+
const crowded = entries.length > Math.max(compact.keys, quietUntil);
|
|
214
|
+
if (!crowded && entries.filter(([k]) => k[0] === "m").length < 2) return false;
|
|
215
|
+
if (dropCovered(auth, entries, crowded)) return true;
|
|
216
|
+
if (!crowded) return false;
|
|
217
|
+
const merged = enc(Y.mergeUpdatesV2(entries.map(([, v]) => dec(v))));
|
|
218
|
+
const used = Object.values(auth).reduce((n, v) => n + v.length, 0);
|
|
219
|
+
if (used + merged.length > compact.room) {
|
|
220
|
+
quietUntil = entries.length + compact.keys;
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const key = nextKey("m");
|
|
224
|
+
known.add(key);
|
|
225
|
+
const parts = partsOf(key, merged);
|
|
226
|
+
const captured = captureYValue({ v: 1, y: auth }).y;
|
|
227
|
+
const incompleteFolds = Object.keys(auth).filter((k) => !(k in captured)).map(baseOf).filter((k) => k[0] === "m");
|
|
228
|
+
fold = { key, parts: parts.map(([k]) => k), inputs: new Set([...entries.map(([k]) => k), ...incompleteFolds]) };
|
|
229
|
+
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
230
|
+
stats.compactions++;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
// Every page descends from a seed whose Yjs client is derived from its article, and
|
|
237
|
+
// compaction keeps every client in the merged state. A room state that shares no client
|
|
238
|
+
// with this tab's document is a page rebuilt from another article while the tab was away.
|
|
239
|
+
const clientsOf = (y) => {
|
|
240
|
+
const clients = new Set();
|
|
241
|
+
for (const [, v] of entriesOf(y ?? {})) for (const c of Y.decodeStateVector(Y.encodeStateVectorFromUpdateV2(dec(v))).keys()) clients.add(c);
|
|
242
|
+
return clients;
|
|
243
|
+
};
|
|
244
|
+
// A page whose article has no blocks seeds with no client at all; it still shares its
|
|
245
|
+
// lineage with a room that holds the very update this tab started from.
|
|
246
|
+
const started = new Map(initial);
|
|
247
|
+
const sharesLineage = (y) => {
|
|
248
|
+
const theirs = clientsOf(y);
|
|
249
|
+
const mine = [...Y.decodeStateVector(Y.encodeStateVector(ydoc)).keys()];
|
|
250
|
+
if (!theirs.size || !mine.length || mine.some((c) => theirs.has(c))) return true;
|
|
251
|
+
return entriesOf(y ?? {}).some(([k, v]) => started.get(k) === v);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const off = store.onRebase((ev) => {
|
|
255
|
+
if (stopped) return;
|
|
256
|
+
const ops = ev.source === "remote" && ev.frame ? ev.frame.ops : null;
|
|
257
|
+
const plain = ops && ops.every((op) => op.path[0] === "y" && op.path.length === 2 && !PART_KEY.test(op.path[1]));
|
|
258
|
+
if (!plain && ev.cause !== "refused" && !sharesLineage(ev.next.y)) {
|
|
259
|
+
stop("lineage");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (plain) {
|
|
263
|
+
for (const op of ops) if (op.op === "set") applyKey(op.path[1], op.value);
|
|
264
|
+
} else {
|
|
265
|
+
for (const [k, v] of entriesOf(ev.next.y ?? {})) applyKey(k, v);
|
|
266
|
+
}
|
|
267
|
+
schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// A page update the room refused as too large is never offered again: the difference would
|
|
271
|
+
// be refused on every offer. Every update of this tab's the room has not confirmed builds on
|
|
272
|
+
// the refused one, so they are all taken back, with the parts of updates the room does not
|
|
273
|
+
// hold whole, and the room's page stays one a checkpoint can render.
|
|
274
|
+
const offDelivery = store.onDelivery((event) => {
|
|
275
|
+
if (event.phase !== "refused" || event.code !== "too-large" || stopped) return;
|
|
276
|
+
stop("too-large");
|
|
277
|
+
const auth = store.authoritative.y ?? {};
|
|
278
|
+
const whole = captureYValue({ v: 1, y: auth }).y;
|
|
279
|
+
const unconfirmed = Object.keys(store.value.y ?? {}).filter((k) => !(k in auth));
|
|
280
|
+
const partial = Object.keys(auth).filter((k) => PART_KEY.test(k) && !(k in whole) && baseOf(k).slice(1).startsWith(`${me}-`));
|
|
281
|
+
if (unconfirmed.length || partial.length) drop([...unconfirmed, ...partial]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; }, compact: maybeCompact, dispose() { off(); offDelivery(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
|
|
285
|
+
}
|