rapier-markdown-kit 1.1.90 → 1.1.92
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 +71 -37
- package/dist/agent/diff.mjs +52 -0
- package/dist/kit/ledger/authorship.mjs +3 -3
- package/dist/kit/ledger/carried.mjs +10 -10
- package/dist/kit/ledger/format.mjs +44 -28
- package/dist/kit/ledger/history.mjs +526 -0
- package/dist/kit/ledger/journal-records.mjs +216 -45
- package/dist/kit/ledger/merge.mjs +191 -51
- package/dist/kit/ledger/transport.mjs +318 -0
- package/dist/kit/rapier-ledger.mjs +12 -2
- package/dist/kit/render-markdown.mjs +227 -109
- package/dist/kit/render-styles.mjs +5 -4
- package/dist/kit/render-work.mjs +145 -0
- package/dist/kit/render.mjs +249 -76
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Paired source placement, shared by live editing and canonical-history proof.
|
|
3
|
+
import {_rapierTransformSplices, _rapierValidAuthored, _rapierHistoryEffects} from './journal-records.mjs';
|
|
4
|
+
import {sha256} from './hash.mjs';
|
|
5
|
+
import {canonicalJSON} from './data.mjs';
|
|
6
|
+
|
|
7
|
+
const whole = text => typeof text === 'string' && !/[\uD800-\uDFFF]/u.test(text);
|
|
8
|
+
function admitted(rows) {
|
|
9
|
+
if (!Array.isArray(rows)) throw new RangeError('source_edits_invalid');
|
|
10
|
+
let end = 0;
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
if (!row || !Number.isSafeInteger(row.at) || row.at < end || Object.is(row.at, -0) ||
|
|
13
|
+
!Number.isSafeInteger(row.remove) || row.remove < 0 || Object.is(row.remove, -0) ||
|
|
14
|
+
!Number.isSafeInteger(row.at + row.remove) || !whole(row.insert)) throw new RangeError('source_edits_invalid');
|
|
15
|
+
end = row.at + row.remove;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function emit(rows, at, remove, insert = '') {
|
|
19
|
+
if (!remove && !insert) return;
|
|
20
|
+
const prior = rows.at(-1);
|
|
21
|
+
if (prior && prior.at + prior.remove === at && (!insert || !prior.remove)) {
|
|
22
|
+
prior.remove += remove; prior.insert += insert;
|
|
23
|
+
} else rows.push({at, remove, insert});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Sweep original-source boundaries. Each counter names a coordinate in one input's
|
|
27
|
+
// resulting document; concurrent insertions never enter the other input's removals.
|
|
28
|
+
export function transformPair(left, right, leftFirst) {
|
|
29
|
+
admitted(left); admitted(right);
|
|
30
|
+
const points = [...new Set([0, ...left.flatMap(row => [row.at, row.at + row.remove]),
|
|
31
|
+
...right.flatMap(row => [row.at, row.at + row.remove])])].sort((a, b) => a - b);
|
|
32
|
+
const out = [[], []], rows = [left, right], index = [0, 0], position = [0, 0], deletingUntil = [0, 0];
|
|
33
|
+
for (let point = 0; point < points.length; point++) {
|
|
34
|
+
const at = points[point], insertions = ['', ''];
|
|
35
|
+
for (let side = 0; side < 2; side++) while (rows[side][index[side]]?.at === at) {
|
|
36
|
+
const row = rows[side][index[side]++];
|
|
37
|
+
insertions[side] += row.insert;
|
|
38
|
+
deletingUntil[side] = Math.max(deletingUntil[side], row.at + row.remove);
|
|
39
|
+
}
|
|
40
|
+
for (const side of leftFirst ? [0, 1] : [1, 0]) if (insertions[side]) {
|
|
41
|
+
emit(out[side], position[1 - side], 0, insertions[side]);
|
|
42
|
+
position[side] += insertions[side].length;
|
|
43
|
+
}
|
|
44
|
+
const length = (points[point + 1] ?? at) - at;
|
|
45
|
+
for (let side = 0; side < 2; side++) {
|
|
46
|
+
if (deletingUntil[side] > at && deletingUntil[1 - side] <= at)
|
|
47
|
+
emit(out[side], position[1 - side], length);
|
|
48
|
+
}
|
|
49
|
+
for (let side = 0; side < 2; side++) if (deletingUntil[side] <= at) position[side] += length;
|
|
50
|
+
}
|
|
51
|
+
return {left: out[0], right: out[1]};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function applyBatch(text, rows) {
|
|
55
|
+
const exact = rows.slice().reverse().map(row => ({pos: row.at,
|
|
56
|
+
removed: text.slice(row.at, row.at + row.remove), inserted: row.insert}));
|
|
57
|
+
if (rows.some(row => row.at + row.remove > text.length)) throw new RangeError('source_edits_invalid');
|
|
58
|
+
const after = _rapierTransformSplices(text, exact);
|
|
59
|
+
if (after === null) throw new RangeError('source_edits_invalid');
|
|
60
|
+
return {text: after, splices: exact};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Transport one sequential canonical act and every crossed record together. The
|
|
64
|
+
// reciprocal path is checked against exact source, rather than only equal end text.
|
|
65
|
+
// Canonical transport refuses deleting any authored unit already removed elsewhere.
|
|
66
|
+
export function transposeRecord(base, splices, id, records) {
|
|
67
|
+
if (typeof base !== 'string' || !Array.isArray(splices) || typeof id !== 'string' || !Array.isArray(records))
|
|
68
|
+
throw new RangeError('canonical_transport_invalid');
|
|
69
|
+
let source = base, current = base;
|
|
70
|
+
let crossed = records.map(record => ({...record, splices: record.splices.map(row => ({...row}))}));
|
|
71
|
+
for (const record of crossed) {
|
|
72
|
+
const next = _rapierTransformSplices(current, record.splices);
|
|
73
|
+
if (next === null) throw new RangeError('canonical_transport_invalid');
|
|
74
|
+
current = next;
|
|
75
|
+
}
|
|
76
|
+
const moved = [], sourceIndices = [];
|
|
77
|
+
for (const [sourceIndex, row] of splices.entries()) {
|
|
78
|
+
const submitted = _rapierTransformSplices(source, [row]);
|
|
79
|
+
if (submitted === null) throw new RangeError('canonical_transport_invalid');
|
|
80
|
+
let left = [{at: row.pos, remove: row.removed.length, insert: row.inserted}];
|
|
81
|
+
const reciprocal = [];
|
|
82
|
+
for (const record of crossed) {
|
|
83
|
+
const steps = [];
|
|
84
|
+
for (const other of record.splices) {
|
|
85
|
+
const pair = transformPair(left, [{at: other.pos, remove: other.removed.length, insert: other.inserted}], id < record.transaction.id);
|
|
86
|
+
left = pair.left; steps.push(pair.right);
|
|
87
|
+
}
|
|
88
|
+
reciprocal.push({record, steps});
|
|
89
|
+
}
|
|
90
|
+
if (left.reduce((sum, part) => sum + part.remove, 0) !== row.removed.length ||
|
|
91
|
+
left.map(part => part.insert).join('') !== row.inserted) throw new RangeError('canonical_act_rebase_conflict');
|
|
92
|
+
const applied = applyBatch(current, left);
|
|
93
|
+
let replay = submitted;
|
|
94
|
+
crossed = reciprocal.map(({record, steps}) => {
|
|
95
|
+
const transported = [];
|
|
96
|
+
for (const step of steps) {
|
|
97
|
+
const value = applyBatch(replay, step); replay = value.text; transported.push(...value.splices);
|
|
98
|
+
}
|
|
99
|
+
const before = record.splices.reduce((sum, part) => sum + part.removed.length, 0);
|
|
100
|
+
if (transported.reduce((sum, part) => sum + part.removed.length, 0) !== before ||
|
|
101
|
+
transported.map(part => part.inserted).join('') !== record.splices.map(part => part.inserted).join(''))
|
|
102
|
+
throw new RangeError('canonical_act_rebase_conflict');
|
|
103
|
+
return {...record, splices: transported};
|
|
104
|
+
});
|
|
105
|
+
if (replay !== applied.text) throw new RangeError('canonical_transport_invalid');
|
|
106
|
+
moved.push(...applied.splices); sourceIndices.push(...applied.splices.map(() => sourceIndex)); current = applied.text; source = submitted;
|
|
107
|
+
}
|
|
108
|
+
return {splices: moved, sourceIndices, against: crossed, text: current};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Hosted and editor callers already own a reciprocal transport log. Convert only
|
|
112
|
+
// its admitted exact source to records, then prove canonical-unit preservation.
|
|
113
|
+
export function transposeSource(base, splices, client, log) {
|
|
114
|
+
let source = base;
|
|
115
|
+
const records = log.map(entry => {
|
|
116
|
+
const applied = applyBatch(source, entry.splices); source = applied.text;
|
|
117
|
+
return {transaction: {id: entry.client}, splices: applied.splices};
|
|
118
|
+
});
|
|
119
|
+
return transposeRecord(base, splices, client, records);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The frontier names only causal maxima. Linear history needs one predecessor,
|
|
123
|
+
// and concurrent predecessors remain separate until an act observes both.
|
|
124
|
+
function authoredFrontier(records, visit = null) {
|
|
125
|
+
const dependencies = new Map(), frontier = new Set();
|
|
126
|
+
for (const record of records) {
|
|
127
|
+
const id = record.transaction.id, basis = record.authored?.basis;
|
|
128
|
+
if (visit) visit(record, basis ?? [...frontier].sort());
|
|
129
|
+
if (!basis) { dependencies.set(id, [...frontier]); frontier.clear(); }
|
|
130
|
+
else {
|
|
131
|
+
const pending = [...basis], seen = new Set();
|
|
132
|
+
while (pending.length) {
|
|
133
|
+
const prior = pending.pop();
|
|
134
|
+
if (seen.has(prior)) continue;
|
|
135
|
+
if (!dependencies.has(prior)) throw new RangeError('canonical_origin_invalid');
|
|
136
|
+
seen.add(prior); frontier.delete(prior); pending.push(...dependencies.get(prior));
|
|
137
|
+
}
|
|
138
|
+
dependencies.set(id, basis);
|
|
139
|
+
}
|
|
140
|
+
frontier.add(id);
|
|
141
|
+
}
|
|
142
|
+
return [...frontier].sort();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function authoredBasis(records) { return authoredFrontier(records); }
|
|
146
|
+
|
|
147
|
+
export function authoredPlacements(base, records) {
|
|
148
|
+
const origins = new Map(); let source = base;
|
|
149
|
+
authoredFrontier(records, (record, basis) => {
|
|
150
|
+
origins.set(record.transaction.id, record.authored ?? authoredPlacement(source, record.splices, basis));
|
|
151
|
+
source = _rapierTransformSplices(source, record.splices);
|
|
152
|
+
if (source === null) throw new RangeError('canonical_origin_invalid');
|
|
153
|
+
});
|
|
154
|
+
return origins;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Immutable authored placement stays on its own act. Basis IDs refer only to this
|
|
158
|
+
// canonical history; no source snapshot or duplicate history is stored.
|
|
159
|
+
export function authoredPlacement(source, splices, basis) {
|
|
160
|
+
const origin = {basis: basis.slice().sort(), source: sha256(source), splices: splices.map(row => ({...row}))};
|
|
161
|
+
if (!_rapierValidAuthored(origin) || _rapierTransformSplices(source, splices) === null)
|
|
162
|
+
throw new RangeError('canonical_origin_invalid');
|
|
163
|
+
return origin;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Reconstruct an authored view from the same records, then prove its exact physical
|
|
167
|
+
// placement. Equality of resulting text alone cannot prove a repeated-text anchor.
|
|
168
|
+
function authoredReplay(base, records, append = null) {
|
|
169
|
+
const origins = new Map(), byId = new Map(), originalOrder = [], cache = new Map(), frontier = new Set();
|
|
170
|
+
const effects = _rapierHistoryEffects(append ? [...records, append] : records);
|
|
171
|
+
if (!effects.ok) throw new RangeError('canonical_origin_invalid');
|
|
172
|
+
let source = base;
|
|
173
|
+
for (const record of append ? [...records, append] : records) {
|
|
174
|
+
const id = record.transaction.id;
|
|
175
|
+
if (byId.has(id)) throw new RangeError('canonical_origin_invalid');
|
|
176
|
+
const origin = Object.hasOwn(record, 'authored') ? record.authored : authoredPlacement(source, record.splices, [...frontier]);
|
|
177
|
+
if (!_rapierValidAuthored(origin) || origin.basis.some(prior => !byId.has(prior)))
|
|
178
|
+
throw new RangeError('canonical_origin_invalid');
|
|
179
|
+
const seen = new Set(), pending = [...origin.basis];
|
|
180
|
+
while (pending.length) {
|
|
181
|
+
const prior = pending.pop(); if (seen.has(prior)) continue;
|
|
182
|
+
seen.add(prior); pending.push(...origins.get(prior).basis);
|
|
183
|
+
}
|
|
184
|
+
if (origin.basis.some(id => origin.basis.some(other => other !== id && (() => {
|
|
185
|
+
const pending = [...origins.get(other).basis], visited = new Set();
|
|
186
|
+
while (pending.length) { const next = pending.pop(); if (next === id) return true;
|
|
187
|
+
if (!visited.has(next)) { visited.add(next); pending.push(...origins.get(next).basis); } }
|
|
188
|
+
return false;
|
|
189
|
+
})()))) throw new RangeError('canonical_origin_invalid');
|
|
190
|
+
const refs = effects.references.get(id) || [];
|
|
191
|
+
if (refs.some(id => !seen.has(id))) throw new RangeError('canonical_origin_invalid');
|
|
192
|
+
for (const prior of seen) frontier.delete(prior); frontier.add(id);
|
|
193
|
+
origins.set(id, origin); byId.set(id, record); originalOrder.push(id);
|
|
194
|
+
if (record !== append) {
|
|
195
|
+
source = _rapierTransformSplices(source, record.splices);
|
|
196
|
+
if (source === null) throw new RangeError('canonical_origin_invalid');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const same = (a, b) => canonicalJSON(a) === canonicalJSON(b);
|
|
200
|
+
const ordered = (ids, selected = null) => {
|
|
201
|
+
const order = [], known = new Set(ids), waiting = new Map(), dependents = new Map(), ready = [];
|
|
202
|
+
const rank = id => selected ? Number(selected.has(id)) : Number(origins.get(id).splices.some(row => row.removed.length));
|
|
203
|
+
const compare = (a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0);
|
|
204
|
+
const push = id => { let at = ready.length; ready.push(id);
|
|
205
|
+
while (at) { const parent = (at - 1) >> 1; if (compare(ready[parent], id) <= 0) break;
|
|
206
|
+
ready[at] = ready[parent]; at = parent; } ready[at] = id; };
|
|
207
|
+
const pop = () => { const first = ready[0], last = ready.pop(); if (ready.length) {
|
|
208
|
+
let at = 0; while (at * 2 + 1 < ready.length) { let child = at * 2 + 1;
|
|
209
|
+
if (child + 1 < ready.length && compare(ready[child + 1], ready[child]) < 0) child++;
|
|
210
|
+
if (compare(last, ready[child]) <= 0) break; ready[at] = ready[child]; at = child;
|
|
211
|
+
} ready[at] = last; } return first; };
|
|
212
|
+
for (const id of ids) {
|
|
213
|
+
const basis = origins.get(id)?.basis;
|
|
214
|
+
if (!basis || basis.some(prior => !known.has(prior))) throw new RangeError('canonical_origin_invalid');
|
|
215
|
+
waiting.set(id, basis.length);
|
|
216
|
+
for (const prior of basis) { if (!dependents.has(prior)) dependents.set(prior, []); dependents.get(prior).push(id); }
|
|
217
|
+
if (!basis.length) push(id);
|
|
218
|
+
}
|
|
219
|
+
while (ready.length) {
|
|
220
|
+
const id = pop(); order.push(id);
|
|
221
|
+
for (const next of dependents.get(id) || []) { waiting.set(next, waiting.get(next) - 1); if (!waiting.get(next)) push(next); }
|
|
222
|
+
}
|
|
223
|
+
if (order.length !== ids.length) throw new RangeError('canonical_origin_invalid');
|
|
224
|
+
return order;
|
|
225
|
+
};
|
|
226
|
+
const closureOf = basis => {
|
|
227
|
+
const closure = new Set(), pending = [...basis];
|
|
228
|
+
while (pending.length) { const prior = pending.pop(); if (closure.has(prior)) continue;
|
|
229
|
+
closure.add(prior); pending.push(...origins.get(prior).basis); }
|
|
230
|
+
return closure;
|
|
231
|
+
};
|
|
232
|
+
const canonicalOrder = ordered(originalOrder), firstExplicit = (append ? [...records, append] : records).findIndex(record => Object.hasOwn(record, 'authored'));
|
|
233
|
+
const implicitPrefix = firstExplicit < 0 ? records.length : firstExplicit;
|
|
234
|
+
const materialize = order => {
|
|
235
|
+
const key = canonicalJSON(order);
|
|
236
|
+
if (cache.has(key)) return cache.get(key);
|
|
237
|
+
const placed = [], ids = new Set(), sourceIndices = new Map(); let text = base;
|
|
238
|
+
if (!order.length) { const empty = {text, records: [], sourceIndices}; cache.set(key, empty); return empty; }
|
|
239
|
+
// An implicit linear prefix already is its authored view. Replay it once;
|
|
240
|
+
// reconstructing every growing prefix adds no evidence.
|
|
241
|
+
let prefix = 0;
|
|
242
|
+
while (prefix < order.length && prefix < implicitPrefix && order[prefix] === originalOrder[prefix]) {
|
|
243
|
+
const record = byId.get(order[prefix]); text = _rapierTransformSplices(text, record.splices);
|
|
244
|
+
if (text === null) throw new RangeError('canonical_origin_invalid');
|
|
245
|
+
placed.push(record); ids.add(order[prefix++]);
|
|
246
|
+
if (record.derivedCommentIndex != null) sourceIndices.set(record.transaction.id, record.splices.map((_, index) => index));
|
|
247
|
+
}
|
|
248
|
+
for (const id of order.slice(prefix)) {
|
|
249
|
+
const original = byId.get(id), origin = origins.get(id);
|
|
250
|
+
if (!original || ids.has(id) || origin.basis.some(prior => !ids.has(prior)))
|
|
251
|
+
throw new RangeError('canonical_origin_invalid');
|
|
252
|
+
const closure = closureOf(origin.basis), basisOrder = canonicalOrder.filter(prior => closure.has(prior)), basis = materialize(basisOrder);
|
|
253
|
+
if (sha256(basis.text) !== origin.source) throw new RangeError('canonical_origin_invalid');
|
|
254
|
+
// Reconstruct causal context before crossing it. Physical gaps may already
|
|
255
|
+
// have collapsed under a deletion; they cannot choose concurrent ordering.
|
|
256
|
+
const contextOrder = canonicalOrder.filter(prior => ids.has(prior)), context = same(contextOrder, [...ids]) ? {text, records: placed} : materialize(contextOrder);
|
|
257
|
+
if (context.text !== text) throw new RangeError('canonical_origin_invalid');
|
|
258
|
+
const alignedOrder = [...basisOrder, ...contextOrder.filter(prior => !closure.has(prior))];
|
|
259
|
+
const aligned = same(alignedOrder, [...ids]) ? {text, records: placed} : materialize(alignedOrder);
|
|
260
|
+
if (aligned.text !== text) throw new RangeError('canonical_origin_invalid');
|
|
261
|
+
const crossed = aligned.records.slice(basisOrder.length);
|
|
262
|
+
const moved = transposeRecord(basis.text, origin.splices, id, crossed);
|
|
263
|
+
if (_rapierTransformSplices(text, moved.splices) !== moved.text) throw new RangeError('canonical_origin_invalid');
|
|
264
|
+
placed.push({...original, splices: moved.splices}); text = moved.text; ids.add(id);
|
|
265
|
+
if (original.derivedCommentIndex != null) sourceIndices.set(id, moved.sourceIndices);
|
|
266
|
+
cache.set(canonicalJSON(order.slice(0, placed.length)), {text, records: placed.slice(), sourceIndices: new Map(sourceIndices)});
|
|
267
|
+
}
|
|
268
|
+
const value = {text, records: placed, sourceIndices}; cache.set(key, value); return value;
|
|
269
|
+
};
|
|
270
|
+
const physicalOrder = append ? originalOrder.slice(0, -1) : originalOrder, physical = materialize(physicalOrder);
|
|
271
|
+
if (physical.text !== source || physical.records.some((row, index) => !same(row.splices, records[index].splices)))
|
|
272
|
+
throw new RangeError('canonical_origin_invalid');
|
|
273
|
+
return {source, origins, originalOrder, materialize, ordered};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function authoredHistory(base, records, selected = null) {
|
|
277
|
+
const replay = authoredReplay(base, records);
|
|
278
|
+
if (!selected) return {text: replay.source, records, origins: replay.origins};
|
|
279
|
+
// Targets follow independent work only in this transient Undo projection.
|
|
280
|
+
const projected = replay.materialize(replay.ordered(replay.originalOrder, selected));
|
|
281
|
+
if (projected.text !== replay.source) throw new RangeError('canonical_origin_invalid');
|
|
282
|
+
return {...projected, origins: replay.origins};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Semantic annotations use the original splice coordinates, even when an accepted
|
|
286
|
+
// physical placement split those rows. The same replay proves the causal basis first.
|
|
287
|
+
export function authoredSourceReader(base, records) {
|
|
288
|
+
const replay = authoredReplay(base, records);
|
|
289
|
+
return actId => {
|
|
290
|
+
const origin = replay.origins.get(actId);
|
|
291
|
+
if (!origin) throw new RangeError('canonical_origin_invalid');
|
|
292
|
+
const ids = new Set(), pending = [...origin.basis];
|
|
293
|
+
while (pending.length) {
|
|
294
|
+
const id = pending.pop();
|
|
295
|
+
if (ids.has(id)) continue;
|
|
296
|
+
ids.add(id); pending.push(...replay.origins.get(id).basis);
|
|
297
|
+
}
|
|
298
|
+
const basis = replay.materialize(replay.ordered([...ids]));
|
|
299
|
+
if (sha256(basis.text) !== origin.source) throw new RangeError('canonical_origin_invalid');
|
|
300
|
+
return {source: basis.text, splices: origin.splices};
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// The original view and the accepted view are both reconstructed from the same
|
|
305
|
+
// immutable acts. The reciprocal rows are an exact ACK, not another history.
|
|
306
|
+
export function transposeAuthored(base, records, incoming, submitted) {
|
|
307
|
+
if (!incoming.authored || !Array.isArray(submitted)) throw new RangeError('canonical_origin_invalid');
|
|
308
|
+
const replay = authoredReplay(base, records, incoming), id = incoming.transaction.id;
|
|
309
|
+
const authored = replay.materialize([...submitted, id]);
|
|
310
|
+
if (canonicalJSON(authored.records.at(-1).splices) !== canonicalJSON(incoming.splices))
|
|
311
|
+
throw new RangeError('canonical_origin_invalid');
|
|
312
|
+
const accepted = replay.materialize(replay.originalOrder), known = new Set([...submitted, id]);
|
|
313
|
+
const reciprocal = replay.materialize([...submitted, id, ...replay.ordered(replay.originalOrder).filter(prior => !known.has(prior))]);
|
|
314
|
+
if (reciprocal.text !== accepted.text) throw new RangeError('canonical_origin_invalid');
|
|
315
|
+
return {text: accepted.text, splices: accepted.records.at(-1).splices,
|
|
316
|
+
submittedBefore: replay.materialize(submitted).text, submitted: authored.text,
|
|
317
|
+
against: reciprocal.records.slice(submitted.length + 1)};
|
|
318
|
+
}
|
|
@@ -5,5 +5,15 @@ import {authorship, readAuthorship, ledgerFromAuthorship} from './ledger/authors
|
|
|
5
5
|
export {authorship, readAuthorship, ledgerFromAuthorship};
|
|
6
6
|
import {merge, transportInterval, transportTouchedInterval} from './ledger/merge.mjs';
|
|
7
7
|
export {merge, transportInterval, transportTouchedInterval};
|
|
8
|
-
import {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord} from './ledger/journal-records.mjs';
|
|
9
|
-
export {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord};
|
|
8
|
+
import {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierRecordMetadata, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect} from './ledger/journal-records.mjs';
|
|
9
|
+
export {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord, _rapierRecordMetadata, _rapierValidMetadata, _rapierValidMetadataEffect, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata, _rapierHasHistoryEffect};
|
|
10
|
+
import {replayHistory, sourceBefore, historyProjection, historyPlaces, selectiveUndo, groupHistoryActs} from './ledger/history.mjs';
|
|
11
|
+
export {replayHistory, sourceBefore, historyProjection, historyPlaces, selectiveUndo, groupHistoryActs};
|
|
12
|
+
|
|
13
|
+
import {transposeSource} from './ledger/transport.mjs';
|
|
14
|
+
export {transposeSource};
|
|
15
|
+
|
|
16
|
+
import {_rapierValidAuthored} from './ledger/journal-records.mjs';
|
|
17
|
+
export {_rapierValidAuthored};
|
|
18
|
+
import {authoredBasis, authoredPlacement, authoredHistory, transposeAuthored} from './ledger/transport.mjs';
|
|
19
|
+
export {authoredBasis, authoredPlacement, authoredHistory, transposeAuthored};
|