rapier-markdown-kit 1.2.33 → 1.2.42
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 +15 -9
- package/dist/agent/vendor/pretext/NOTICE +2 -2
- package/dist/agent/vendor/pretext/SOURCE.json +62 -78
- package/dist/agent/vendor/pretext/analysis.js +24 -6
- package/dist/agent/vendor/pretext/gecko-line-breaks.js +1 -1
- package/dist/agent/vendor/pretext/generated/engine-break-data.js +1 -1
- package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +1 -1
- package/dist/agent/vendor/pretext/graphemes.js +1 -1
- package/dist/agent/vendor/pretext/han-kerning.js +1 -1
- package/dist/agent/vendor/pretext/layout.js +14 -11
- package/dist/agent/vendor/pretext/line-break.js +170 -195
- package/dist/agent/vendor/pretext/line-breaks.js +1 -7
- package/dist/agent/vendor/pretext/line-text.js +2 -2
- package/dist/agent/vendor/pretext/measurement.js +454 -113
- package/dist/agent/vendor/pretext/prepare.js +164 -244
- package/dist/agent/vendor/pretext/rich-inline.js +57 -37
- package/dist/agent/will.mjs +73 -0
- package/dist/kit/ledger/data.mjs +57 -4
- package/dist/kit/ledger/format.mjs +114 -47
- package/dist/kit/ledger/hash.mjs +43 -2
- package/dist/kit/ledger/history.mjs +6 -0
- package/dist/kit/ledger/inverse-source.mjs +15 -2
- package/dist/kit/ledger/journal-records.mjs +32 -9
- package/dist/kit/ledger/merge.mjs +3 -17
- package/dist/kit/ledger/text-merge.mjs +28 -2
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-markdown.mjs +170 -12
- package/dist/kit/render-print.mjs +3 -3
- package/dist/kit/render-sanitize.mjs +66 -23
- package/dist/kit/render.mjs +10 -10
- package/dist/kit/wiki.mjs +34 -0
- package/dist/kit/will.d.mts +3 -0
- package/dist/layout/hyphenation.mjs +6 -4
- package/dist/layout/line-plan.mjs +96 -0
- package/dist/layout/model.mjs +2 -1
- package/dist/layout/paragraph-fit.mjs +4 -4
- package/dist/layout/prepared-paragraph.mjs +19 -2
- package/dist/layout/typography.mjs +57 -63
- package/dist/spec/document-fonts.mjs +1 -0
- package/dist/spec/document-settings.mjs +1 -0
- package/dist/spec/html-blocks.mjs +10 -7
- package/dist/spec/md-assets.mjs +52 -12
- package/package.json +1 -1
- package/style.css +33 -23
- package/dist/agent/vendor/pretext/entry-geometry.js +0 -183
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/*! Pretext
|
|
1
|
+
/*! Pretext 1.0.0 + main (0d102a5cd) | MIT; BSD/Unicode/ICU notices in LICENSE | Copyright (c) 2026 Pretext contributors */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
alignToSource,
|
|
5
5
|
analyzeText,
|
|
6
6
|
isCollapsibleSpaceCode,
|
|
7
|
+
DROPPED,
|
|
7
8
|
HARD_BREAK,
|
|
8
9
|
KIND_BITS,
|
|
9
10
|
OBJECT,
|
|
@@ -20,7 +21,6 @@ import {
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
} from './analysis.js'
|
|
23
|
-
import { getSegmentEntryWidth, } from './entry-geometry.js'
|
|
24
24
|
import { buildLineTextFromRange, buildRangeText, getGraphemeEnds, } from './line-text.js'
|
|
25
25
|
import {
|
|
26
26
|
getItemTabAdvance,
|
|
@@ -166,7 +166,8 @@ const PRESERVED_WHITE_SPACE = 1 << PRESERVED_SPACE | 1 << TAB
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
|
|
170
|
+
|
|
170
171
|
|
|
171
172
|
|
|
172
173
|
|
|
@@ -300,8 +301,6 @@ export function prepareRichInline(items ,
|
|
|
300
301
|
// Firefox 156 and Safari 27 prepare CJK styled paragraphs again up to 8% faster with the lists
|
|
301
302
|
// made whole than with each filled by a call and a push a segment (RESEARCH.md, Keeping Work
|
|
302
303
|
// Bounded, JavaScript Engines, under A paragraph's sparse lists made whole).
|
|
303
|
-
let entryGeometry = null
|
|
304
|
-
let lineStartProhibitions = null
|
|
305
304
|
let breakableLineStartExtras = null
|
|
306
305
|
let lineStartExtras = null
|
|
307
306
|
let lineEndTrims = null
|
|
@@ -531,8 +530,6 @@ export function prepareRichInline(items ,
|
|
|
531
530
|
}
|
|
532
531
|
const units = getSourceUnits(offsets, normalizedStart, textEnd, starts[index] )
|
|
533
532
|
if (units !== null) (sourceUnits ??= new Array (flags.length).fill(null))[at] = units
|
|
534
|
-
if (sub.entryGeometry !== null && sub.entryGeometry[s] !== null) (entryGeometry ??= new Array (flags.length).fill(null))[at] = sub.entryGeometry[s]
|
|
535
|
-
if (sub.lineStartProhibitions !== null && sub.lineStartProhibitions[s] !== null) (lineStartProhibitions ??= new Array (flags.length).fill(null))[at] = sub.lineStartProhibitions[s]
|
|
536
533
|
if (sub.breakableLineStartExtras !== null && sub.breakableLineStartExtras[s] !== null) (breakableLineStartExtras ??= new Array (flags.length).fill(null))[at] = sub.breakableLineStartExtras[s]
|
|
537
534
|
const startExtra = (sub.lineStartExtras === null ? 0 : sub.lineStartExtras[s] ) + (i > first ? extraWidth : 0)
|
|
538
535
|
if (startExtra !== 0) (lineStartExtras ??= zeroList(flags.length))[at] = startExtra
|
|
@@ -621,7 +618,7 @@ export function prepareRichInline(items ,
|
|
|
621
618
|
itemEndHalts,
|
|
622
619
|
hyphenRooms: discretionaryHyphenContexts !== null && profile.unfitHyphenRetreat === 'reduced-width' ? getHyphenRooms(hyphenWidths, itemSegments, segmentItems) : null,
|
|
623
620
|
insideExtras, fillExtras, openingEdges,
|
|
624
|
-
emptyObjectSpaces: null, emptyObjectReturns: null,
|
|
621
|
+
emptyObjectSpaces: null, emptyObjectReturns: null, hangEnds: null,
|
|
625
622
|
}
|
|
626
623
|
// The properties measureAnalysis() gives a text's handle, in its order, then the paragraph's own.
|
|
627
624
|
const data = {
|
|
@@ -630,11 +627,9 @@ export function prepareRichInline(items ,
|
|
|
630
627
|
simpleLineWalkFastPath: simple,
|
|
631
628
|
simpleLineCountFastPath: false,
|
|
632
629
|
breakableFitAdvances,
|
|
633
|
-
entryGeometry,
|
|
634
630
|
letterSpacing: spacingsDiffer || sharedSpacing === null ? 0 : sharedSpacing,
|
|
635
631
|
discretionaryHyphenWidth: firstTextItem < 0 ? 0 : hyphenWidths[firstTextItem] ,
|
|
636
632
|
discretionaryHyphenContexts,
|
|
637
|
-
lineStartProhibitions,
|
|
638
633
|
breakableLineStartExtras,
|
|
639
634
|
lineStartExtras,
|
|
640
635
|
lineEndTrims,
|
|
@@ -645,6 +640,15 @@ export function prepareRichInline(items ,
|
|
|
645
640
|
}
|
|
646
641
|
data.segments = segments
|
|
647
642
|
if (hasEmptyObject && profile.emptyAtomicAlwaysFits) setEmptyObjectFacts(segmentData, data, items, itemSegments, segmentItems, source, starts)
|
|
643
|
+
// Each item is a text frame: a zero-width space after preserved spaces of its own item ends
|
|
644
|
+
// the run of them that hangs (ParagraphSegmentData, hangEnds).
|
|
645
|
+
if (preserve && profile.hangsSpacesPerTextFrame && source.includes('\u200B')) {
|
|
646
|
+
for (let i = 1; i < segmentCount; i++) {
|
|
647
|
+
if ((segmentFlags[i] & (KIND_BITS | DROPPED)) === ZERO_WIDTH_BREAK && (segmentFlags[i - 1] & KIND_BITS) === PRESERVED_SPACE && segmentItems[i] === segmentItems[i - 1]) {
|
|
648
|
+
;(segmentData.hangEnds ??= new Uint8Array(segmentCount))[i] = 1
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
648
652
|
|
|
649
653
|
let onlyItem = -1
|
|
650
654
|
for (let index = 0; index < items.length && !paddedOrObject; index++) {
|
|
@@ -894,19 +898,13 @@ function getOpeningFit(flags , from , to , extraWidth
|
|
|
894
898
|
// (`startsLine`), the first of them adds what a line that starts with it does
|
|
895
899
|
// (breakableLineStartExtras), as the walker's line does.
|
|
896
900
|
function getPartWidth(data , i , from , to , startsLine ) {
|
|
897
|
-
const { breakableFitAdvances, breakableLineStartExtras,
|
|
901
|
+
const { breakableFitAdvances, breakableLineStartExtras, letterSpacing, items } = data
|
|
898
902
|
const advances = breakableFitAdvances[i]
|
|
899
903
|
if (to < 0) to = advances.length
|
|
900
|
-
const entry = from > 0 && entryGeometry !== null ? entryGeometry[i] : null
|
|
901
|
-
const entryWidth = entry === null ? null : getSegmentEntryWidth(entry, from, to)
|
|
902
904
|
let w = 0
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
for (let g = from; g < to; g++) w += advances[g] + letterSpacing
|
|
907
|
-
const startExtras = startsLine && breakableLineStartExtras !== null ? breakableLineStartExtras[i] : null
|
|
908
|
-
if (startExtras !== null) w += startExtras[from]
|
|
909
|
-
}
|
|
905
|
+
for (let g = from; g < to; g++) w += advances[g] + letterSpacing
|
|
906
|
+
const startExtras = startsLine && breakableLineStartExtras !== null ? breakableLineStartExtras[i] : null
|
|
907
|
+
if (startExtras !== null) w += startExtras[from]
|
|
910
908
|
if (items !== undefined && (from > 0 ? items.insideExtras !== null : items.fillExtras !== null)) w += from > 0 ? items.insideExtras [i] : items.fillExtras [i]
|
|
911
909
|
return w
|
|
912
910
|
}
|
|
@@ -928,7 +926,7 @@ function createOnlyItemLine(
|
|
|
928
926
|
endGraphemeIndex ,
|
|
929
927
|
) {
|
|
930
928
|
const { onlyItem } = flow
|
|
931
|
-
const ended = endSegmentIndex >= flow.data.
|
|
929
|
+
const ended = endSegmentIndex >= flow.data.widths.length
|
|
932
930
|
return {
|
|
933
931
|
fragments: [{
|
|
934
932
|
itemIndex: onlyItem,
|
|
@@ -974,7 +972,7 @@ function createLine(
|
|
|
974
972
|
const segmentItems = flow.segmentItems
|
|
975
973
|
const gapSegments = flow.gapSegments
|
|
976
974
|
const itemCount = itemSegments.length - 1
|
|
977
|
-
const endItemIndex = endSegmentIndex >=
|
|
975
|
+
const endItemIndex = endSegmentIndex >= widths.length ? itemCount : segmentItems[endSegmentIndex]
|
|
978
976
|
const end = { itemIndex: endItemIndex, segmentIndex: endSegmentIndex - itemSegments[endItemIndex] , graphemeIndex: endGraphemeIndex }
|
|
979
977
|
const tabsInAppUnits = getEngineProfile().tabsInAppUnits
|
|
980
978
|
const itemEndHalts = items.itemEndHalts
|
|
@@ -1108,7 +1106,7 @@ function createLine(
|
|
|
1108
1106
|
// The paragraph's segment a cursor names, or the segment count for one past the last item.
|
|
1109
1107
|
function getSegmentIndex(flow , cursor ) {
|
|
1110
1108
|
const { itemSegments } = flow
|
|
1111
|
-
return cursor.itemIndex < itemSegments.length ? itemSegments[cursor.itemIndex] + cursor.segmentIndex : flow.data.
|
|
1109
|
+
return cursor.itemIndex < itemSegments.length ? itemSegments[cursor.itemIndex] + cursor.segmentIndex : flow.data.widths.length
|
|
1112
1110
|
}
|
|
1113
1111
|
|
|
1114
1112
|
export function layoutNextRichInlineLineRange(
|
|
@@ -1142,9 +1140,26 @@ function getSourceUnits(offsets , from , to , itemStart
|
|
|
1142
1140
|
return units
|
|
1143
1141
|
}
|
|
1144
1142
|
|
|
1143
|
+
// Per frozen handle of a paragraph that is one text's handle, the three lists of where its
|
|
1144
|
+
// segments are in the item's text (InternalPreparedRichInline), found when a line of it is
|
|
1145
|
+
// first materialized. A handle keeps them itself; a frozen one can't take them, so they are
|
|
1146
|
+
// kept beside it, as its segments' grapheme ends are (getGraphemeEnds): no line function writes
|
|
1147
|
+
// to a frozen handle or to anything it holds, so one the app froze, or a copy of one in a store
|
|
1148
|
+
// that freezes its state, materializes as any other. Kept beside every handle, the lookup took
|
|
1149
|
+
// a pass that walks and materializes 3,000 of the chat demo's one-item paragraphs, each asked
|
|
1150
|
+
// once, 35-47% longer with the handles out of the processor's caches and 7-25% with them in,
|
|
1151
|
+
// and 40 of them over and over 1-9% (JavaScriptCore of bun 1.4.2, V8 15.4 and node 23.10's,
|
|
1152
|
+
// SpiderMonkey 156, one build a process on the harness's stand-in Canvas, 2026-10-12). Found
|
|
1153
|
+
// while preparing, the lists took such paragraphs 6-13% longer to prepare and count once in
|
|
1154
|
+
// JavaScriptCore and 12-20% in V8, text seen before, and a third more heap a handle, for lines
|
|
1155
|
+
// a long list mostly never paints; found again at every line, walking and materializing took
|
|
1156
|
+
// 5-9 times as long, and over 100 times in a paragraph of 6,000 units.
|
|
1157
|
+
|
|
1158
|
+
const textSources = new WeakMap ()
|
|
1159
|
+
|
|
1145
1160
|
// Per segment of a paragraph that is one text's handle, where its text starts and ends in the
|
|
1146
1161
|
// item's.
|
|
1147
|
-
function findSegmentSources(flow )
|
|
1162
|
+
function findSegmentSources(flow ) {
|
|
1148
1163
|
const { segments } = flow.data
|
|
1149
1164
|
const offsets = alignToSource(flow.text, segments.join(''), getEngineProfile().lineBreakScan)
|
|
1150
1165
|
const sourceStarts = []
|
|
@@ -1156,15 +1171,21 @@ function findSegmentSources(flow ) {
|
|
|
1156
1171
|
sourceEnds.push(flow.text.charCodeAt(end - 1) === 0x0D && flow.text.charCodeAt(end) === 0x0A && segments[i] === '\n' ? end + 1 : end)
|
|
1157
1172
|
sourceUnits.push(getSourceUnits(offsets, at, at + segments[i] .length, 0))
|
|
1158
1173
|
}
|
|
1159
|
-
|
|
1160
|
-
flow
|
|
1161
|
-
|
|
1174
|
+
const sources = { sourceStarts, sourceEnds, sourceUnits }
|
|
1175
|
+
if (Object.isFrozen(flow)) {
|
|
1176
|
+
textSources.set(flow, sources)
|
|
1177
|
+
} else {
|
|
1178
|
+
flow.sourceStarts = sourceStarts
|
|
1179
|
+
flow.sourceEnds = sourceEnds
|
|
1180
|
+
flow.sourceUnits = sourceUnits
|
|
1181
|
+
}
|
|
1182
|
+
return sources
|
|
1162
1183
|
}
|
|
1163
1184
|
|
|
1164
1185
|
// Where unit `unit` of a segment's text is in its item's text.
|
|
1165
|
-
function getSourceOffset(
|
|
1166
|
-
const units =
|
|
1167
|
-
return units === null ?
|
|
1186
|
+
function getSourceOffset(sourceStarts , sourceUnits , segmentIndex , unit ) {
|
|
1187
|
+
const units = sourceUnits === null ? null : sourceUnits[segmentIndex]
|
|
1188
|
+
return units === null ? sourceStarts[segmentIndex] + unit : units[unit]
|
|
1168
1189
|
}
|
|
1169
1190
|
|
|
1170
1191
|
// Bridge from cheap range walking to full fragment text. Lets callers do
|
|
@@ -1176,9 +1197,8 @@ export function materializeRichInlineLineRange(
|
|
|
1176
1197
|
) {
|
|
1177
1198
|
const flow = getInternalPreparedRichInline(prepared)
|
|
1178
1199
|
const { data, itemSegments } = flow
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const sourceEnds = flow.sourceEnds
|
|
1200
|
+
let { sourceStarts, sourceEnds, sourceUnits } = flow
|
|
1201
|
+
if (sourceStarts === null) ({ sourceStarts, sourceEnds, sourceUnits } = textSources.get(flow) ?? findSegmentSources(flow))
|
|
1182
1202
|
const lineEndSegmentIndex = getSegmentIndex(flow, line.end)
|
|
1183
1203
|
const fragments = []
|
|
1184
1204
|
|
|
@@ -1194,11 +1214,11 @@ export function materializeRichInlineLineRange(
|
|
|
1194
1214
|
const endGraphemeIndex = endSegmentIndex < itemEnd ? fragment.end.graphemeIndex : 0
|
|
1195
1215
|
// Only the fragment a line ends with shows the hyphen of a soft hyphen it ends at.
|
|
1196
1216
|
const endsLine = i === line.fragments.length - 1 && endSegmentIndex === lineEndSegmentIndex && endGraphemeIndex === line.end.graphemeIndex
|
|
1197
|
-
let sourceStart = startSegmentIndex < itemEnd ? sourceStarts[startSegmentIndex] : startSegmentIndex > first ? sourceEnds[startSegmentIndex - 1] : 0
|
|
1198
|
-
if (startGraphemeIndex > 0 && startSegmentIndex < itemEnd) sourceStart = getSourceOffset(
|
|
1217
|
+
let sourceStart = startSegmentIndex < itemEnd ? sourceStarts[startSegmentIndex] : startSegmentIndex > first ? sourceEnds [startSegmentIndex - 1] : 0
|
|
1218
|
+
if (startGraphemeIndex > 0 && startSegmentIndex < itemEnd) sourceStart = getSourceOffset(sourceStarts, sourceUnits, startSegmentIndex, getGraphemeEnds(data, startSegmentIndex)[startGraphemeIndex - 1] )
|
|
1199
1219
|
let sourceEnd = sourceStart
|
|
1200
|
-
if (endGraphemeIndex > 0) sourceEnd = getSourceOffset(
|
|
1201
|
-
else if (endSegmentIndex > startSegmentIndex) sourceEnd = sourceEnds[endSegmentIndex - 1]
|
|
1220
|
+
if (endGraphemeIndex > 0) sourceEnd = getSourceOffset(sourceStarts, sourceUnits, endSegmentIndex, getGraphemeEnds(data, endSegmentIndex)[endGraphemeIndex - 1] - 1) + 1
|
|
1221
|
+
else if (endSegmentIndex > startSegmentIndex) sourceEnd = sourceEnds [endSegmentIndex - 1]
|
|
1202
1222
|
fragments.push({
|
|
1203
1223
|
itemIndex: fragment.itemIndex,
|
|
1204
1224
|
text: endsLine ? buildLineTextFromRange(data, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex)
|
package/dist/agent/will.mjs
CHANGED
|
@@ -162,5 +162,78 @@ function _rapierWillStripOneTerminator(text) {
|
|
|
162
162
|
text.charCodeAt(end - 1), end >= 2 ? text.charCodeAt(end - 2) : -1)) : text;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
export function willOffsetBefore(rows, offset) {
|
|
166
|
+
for (let index = rows.length - 1; index >= 0 && offset >= 0; index--) {
|
|
167
|
+
const {pos, removed, inserted} = rows[index];
|
|
168
|
+
if (offset >= pos) offset = offset >= pos + inserted.length ? offset - inserted.length + removed.length : -1;
|
|
169
|
+
}
|
|
170
|
+
return offset;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function _rapierWillRestores(restores, region, splices, text) {
|
|
174
|
+
if (!Array.isArray(restores) || region.law !== 'append') return null;
|
|
175
|
+
const reaching = splices.filter(row => row.pos <= region.end && row.pos + row.removed.length >= region.start &&
|
|
176
|
+
(row.removed || row.inserted)).toSorted((a, b) => a.pos - b.pos);
|
|
177
|
+
if (!reaching.length || reaching.some((row, index) => row.pos < region.start || row.pos + row.removed.length > region.end ||
|
|
178
|
+
(index && row.pos !== reaching[index - 1].pos + reaching[index - 1].removed.length) || !restores.includes(row)) ||
|
|
179
|
+
!/^(?:\r\n|\r|\n)?$/.test(text.slice(reaching.at(-1).pos + reaching.at(-1).removed.length - region.start))) return null;
|
|
180
|
+
return reaching;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function willTransition(beforeText, afterText, splices, markerSplices = splices, {restores = null, restored = null} = {}) {
|
|
184
|
+
const before = _rapierWillParse(beforeText), after = _rapierWillParse(afterText);
|
|
185
|
+
if (!before.present && !after.present) return null;
|
|
186
|
+
const bounds = region => ({start: region.start, end: region.end});
|
|
187
|
+
const faults = will => will.faults.slice(0, 4).map(({mode, line}) => ({mode, line}));
|
|
188
|
+
if (before.faults.length) return {law: 'keep', rule: 'before_faulted', faults: faults(before), start: 0, end: beforeText.length};
|
|
189
|
+
for (const region of before.regions) {
|
|
190
|
+
const markers = [[region.openerStart, region.openerEnd], [region.closerStart, region.closerEnd]];
|
|
191
|
+
for (let index = 0; index < splices.length; index++) {
|
|
192
|
+
const row = splices[index], end = row.pos + row.removed.length;
|
|
193
|
+
for (const marker of markers) {
|
|
194
|
+
if (row.removed.length ? row.pos < marker[1] && marker[0] < end : row.pos > marker[0] && row.pos < marker[1])
|
|
195
|
+
return {law: region.law, rule: 'marker_span_touched', editIndex: index, region: region.index, ...bounds(region)};
|
|
196
|
+
if (end <= marker[0]) { const shift = row.inserted.length - row.removed.length; marker[0] += shift; marker[1] += shift; }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (after.faults.length) return {law: 'keep', rule: 'result_faulted', faults: faults(after), start: 0, end: beforeText.length};
|
|
201
|
+
if (before.markers.length !== after.markers.length || before.markers.some((marker, index) => {
|
|
202
|
+
const next = after.markers[index];
|
|
203
|
+
return marker.kind !== next.kind || beforeText.slice(marker.start, marker.end) !== afterText.slice(next.start, next.end) ||
|
|
204
|
+
willOffsetBefore(markerSplices, next.start) !== marker.start || willOffsetBefore(markerSplices, next.end - 1) !== marker.end - 1;
|
|
205
|
+
})) return {law: 'keep', rule: 'marker_sequence_mismatch', start: 0, end: beforeText.length};
|
|
206
|
+
for (const region of before.regions) {
|
|
207
|
+
const was = beforeText.slice(region.start, region.end), next = after.regions[region.index], now = afterText.slice(next.start, next.end);
|
|
208
|
+
if (region.law === 'keep' && was !== now)
|
|
209
|
+
return {law: region.law, rule: 'law_violated', region: region.index, ...bounds(region)};
|
|
210
|
+
if (region.law !== 'append' || _rapierWillStripOneTerminator(now).startsWith(_rapierWillStripOneTerminator(was))) continue;
|
|
211
|
+
const reaching = _rapierWillRestores(restores, region, splices, was);
|
|
212
|
+
if (!reaching) return {law: region.law, rule: 'law_violated', region: region.index, ...bounds(region)};
|
|
213
|
+
restored?.set(region.index, reaching);
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function evaluateWill(beforeText, splices, {restores = null} = {}) {
|
|
219
|
+
const invalid = rule => ({outcome: 'invalid', reason: 'precondition', rule});
|
|
220
|
+
if (typeof beforeText !== 'string' || !beforeText.isWellFormed()) return invalid('invalid_unicode');
|
|
221
|
+
if (!Array.isArray(splices)) return invalid('malformed_splice');
|
|
222
|
+
const boundary = (text, at) => !(at > 0 && at < text.length &&
|
|
223
|
+
(text.charCodeAt(at - 1) & 0xfc00) === 0xd800 && (text.charCodeAt(at) & 0xfc00) === 0xdc00);
|
|
224
|
+
let afterText = beforeText;
|
|
225
|
+
for (const row of splices) {
|
|
226
|
+
if (!row || !Number.isSafeInteger(row.pos) || row.pos < 0 || Object.is(row.pos, -0) ||
|
|
227
|
+
typeof row.removed !== 'string' || typeof row.inserted !== 'string' ||
|
|
228
|
+
!row.removed.isWellFormed() || !row.inserted.isWellFormed()) return invalid('malformed_splice');
|
|
229
|
+
const end = row.pos + row.removed.length;
|
|
230
|
+
if (end > afterText.length || !boundary(afterText, row.pos) || !boundary(afterText, end)) return invalid('out_of_range_splice');
|
|
231
|
+
if (afterText.slice(row.pos, end) !== row.removed) return invalid('source_mismatch');
|
|
232
|
+
afterText = afterText.slice(0, row.pos) + row.inserted + afterText.slice(end);
|
|
233
|
+
}
|
|
234
|
+
const refusal = splices.length && willTransition(beforeText, afterText, splices, splices, {restores});
|
|
235
|
+
return refusal ? {outcome: 'refused', reason: 'document_law', ...refusal} : {outcome: 'applied'};
|
|
236
|
+
}
|
|
237
|
+
|
|
165
238
|
|
|
166
239
|
export { hasWillMarkers, _rapierWillParse as parseWill, _rapierWillMarkerOf as willMarkerOf, _rapierWillRegionsIn as willRegionsIn, _rapierWillTouchesMarker as willTouchesMarker, _rapierWillGovern as willGovern, _rapierWillIntentOf as willIntentOf, _rapierWillStripOneTerminator as stripOneTerminator, _rapierWillTerminatorWidth as terminatorWidth };
|
package/dist/kit/ledger/data.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export function fields(value, depth, active, sorted = true) {
|
|
|
17
17
|
}
|
|
18
18
|
export function scalar(value) {
|
|
19
19
|
if (value === null || typeof value === 'boolean') return true;
|
|
20
|
-
if (typeof value === 'string') { if (
|
|
20
|
+
if (typeof value === 'string') { if (!value.isWellFormed()) throw fault('unpaired surrogate'); return true; }
|
|
21
21
|
return typeof value === 'number' && Number.isFinite(value) && !Object.is(value, -0);
|
|
22
22
|
}
|
|
23
23
|
export function plainData(value) {
|
|
@@ -39,16 +39,18 @@ function* quoted(text, whole = true) {
|
|
|
39
39
|
let end = Math.min(text.length, at + 16384);
|
|
40
40
|
if (end < text.length && (text.charCodeAt(end - 1) & 0xfc00) === 0xd800 && (text.charCodeAt(end) & 0xfc00) === 0xdc00) end--;
|
|
41
41
|
const part = text.slice(at, end);
|
|
42
|
-
if (whole &&
|
|
42
|
+
if (whole && !part.isWellFormed()) throw fault('unpaired surrogate');
|
|
43
43
|
yield JSON.stringify(part).slice(1, -1); at = end;
|
|
44
44
|
}
|
|
45
45
|
yield '"';
|
|
46
46
|
}
|
|
47
|
-
function* dataParts(value, depth, active, sorted) {
|
|
47
|
+
function* dataParts(value, depth, active, sorted, known = null) {
|
|
48
48
|
if (typeof value === 'string') { yield* quoted(value); return; }
|
|
49
49
|
if (value === null || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value) && !Object.is(value, -0)) {
|
|
50
50
|
yield JSON.stringify(value); return;
|
|
51
51
|
}
|
|
52
|
+
const text = known?.(value, depth);
|
|
53
|
+
if (text !== undefined) { yield text; return; }
|
|
52
54
|
const {array, descriptors, keys} = fields(value, depth, active, sorted);
|
|
53
55
|
active.add(value);
|
|
54
56
|
yield array ? '[' : '{';
|
|
@@ -56,14 +58,65 @@ function* dataParts(value, depth, active, sorted) {
|
|
|
56
58
|
if (i) yield ',';
|
|
57
59
|
const key = keys[i];
|
|
58
60
|
if (!array) { yield* quoted(key, false); yield ':'; }
|
|
59
|
-
yield* dataParts(descriptors[key].value, depth + 1, active, sorted);
|
|
61
|
+
yield* dataParts(descriptors[key].value, depth + 1, active, sorted, known);
|
|
60
62
|
}
|
|
61
63
|
yield array ? ']' : '}';
|
|
62
64
|
active.delete(value);
|
|
63
65
|
}
|
|
64
66
|
export function canonicalParts(value, depth = 0, active = new Set()) { return dataParts(value, depth, active, true); }
|
|
65
67
|
export function jsonParts(value) { return dataParts(value, 0, new Set(), false); }
|
|
68
|
+
export function jsonPartsKnown(value, known) { return dataParts(value, 0, new Set(), false, known); }
|
|
69
|
+
export function jsonTextAt(value, depth) { return Array.from(dataParts(value, depth, new Set(), false)).join(''); }
|
|
66
70
|
export function canonicalJSON(value, depth = 0, active = new Set()) { return Array.from(canonicalParts(value, depth, active)).join(''); }
|
|
71
|
+
const ownedRows = new WeakSet(), rowCanonical = new Map();
|
|
72
|
+
let rowCanonicalChars = 0;
|
|
73
|
+
const ROW_CANONICAL_LIMIT = 32 * 1024 * 1024;
|
|
74
|
+
function freezeData(value) {
|
|
75
|
+
if (!value || typeof value !== 'object' || ownedRows.has(value) || ownedLedgers.has(value)) return value;
|
|
76
|
+
for (const key of Object.keys(value)) freezeData(value[key]);
|
|
77
|
+
return Object.freeze(value);
|
|
78
|
+
}
|
|
79
|
+
export function ownRow(row) { if (!ownedRows.has(row)) { freezeData(row); ownedRows.add(row); } return row; }
|
|
80
|
+
const ownedLedgers = new WeakSet();
|
|
81
|
+
export function ownLedger(ledger) {
|
|
82
|
+
if (ledger && typeof ledger === 'object' && !ownedLedgers.has(ledger)) { freezeData(ledger); ownedLedgers.add(ledger); }
|
|
83
|
+
return ledger;
|
|
84
|
+
}
|
|
85
|
+
export function isOwnedLedger(ledger) { return !!ledger && ownedLedgers.has(ledger); }
|
|
86
|
+
const rowText = new WeakMap();
|
|
87
|
+
function canonicalRow(row, depth) {
|
|
88
|
+
if (!ownedRows.has(row)) return canonicalJSON(row, depth);
|
|
89
|
+
const own = rowText.get(row);
|
|
90
|
+
if (own?.depth === depth) return own.json;
|
|
91
|
+
const json = keyedRow(row, depth);
|
|
92
|
+
rowText.set(row, {depth, json});
|
|
93
|
+
return json;
|
|
94
|
+
}
|
|
95
|
+
function keyedRow(row, depth) {
|
|
96
|
+
const id = typeof row.transaction?.id === 'string' ? row.transaction.id : null, seen = JSON.stringify(row);
|
|
97
|
+
const hit = id === null ? null : rowCanonical.get(id);
|
|
98
|
+
if (hit && hit.depth === depth && hit.seen.includes(seen)) return hit.json;
|
|
99
|
+
const json = canonicalJSON(row, depth);
|
|
100
|
+
if (id !== null && 2 * seen.length + json.length <= ROW_CANONICAL_LIMIT / 4) {
|
|
101
|
+
const size = entry => entry.seen.reduce((sum, text) => sum + text.length, entry.json.length);
|
|
102
|
+
const seenAlso = hit && hit.depth === depth && hit.json === json ? [hit.seen.at(-1), seen] : [seen];
|
|
103
|
+
if (hit) { rowCanonical.delete(id); rowCanonicalChars -= size(hit); }
|
|
104
|
+
const entry = {depth, seen: seenAlso, json};
|
|
105
|
+
rowCanonical.set(id, entry); rowCanonicalChars += size(entry);
|
|
106
|
+
for (const [key, value] of rowCanonical) {
|
|
107
|
+
if (rowCanonicalChars <= ROW_CANONICAL_LIMIT) break;
|
|
108
|
+
rowCanonical.delete(key); rowCanonicalChars -= size(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return json;
|
|
112
|
+
}
|
|
113
|
+
export function* canonicalRowsParts(rows, depth = 0) {
|
|
114
|
+
const {array, keys} = fields(rows, depth, new Set());
|
|
115
|
+
if (!array) { yield* canonicalParts(rows, depth); return; }
|
|
116
|
+
yield '[';
|
|
117
|
+
for (let i = 0; i < keys.length; i++) { if (i) yield ','; yield canonicalRow(rows[keys[i]], depth + 1); }
|
|
118
|
+
yield ']';
|
|
119
|
+
}
|
|
67
120
|
export function exactKeys(value, keys) {
|
|
68
121
|
return value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
69
122
|
}
|