@stream-mdx/react 0.1.1 → 0.3.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/dist/components/index.cjs +497 -163
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.mjs +496 -163
- package/dist/{index-Bt1opGCs.d.cts → index-D7px9jug.d.cts} +27 -2
- package/dist/{index-Bt1opGCs.d.ts → index-D7px9jug.d.ts} +27 -2
- package/dist/index.cjs +3767 -2043
- package/dist/index.d.cts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.mjs +3991 -2265
- package/dist/mdx-client.cjs +60 -18
- package/dist/mdx-client.d.cts +11 -0
- package/dist/mdx-client.d.ts +11 -0
- package/dist/mdx-client.mjs +60 -18
- package/dist/mdx-coordinator.cjs +60 -18
- package/dist/mdx-coordinator.mjs +60 -18
- package/dist/renderer/node-views.cjs +466 -133
- package/dist/renderer/node-views.d.cts +1 -1
- package/dist/renderer/node-views.d.ts +1 -1
- package/dist/renderer/node-views.mjs +409 -68
- package/dist/renderer/patch-commit-scheduler.cjs +68 -7
- package/dist/renderer/patch-commit-scheduler.d.cts +6 -5
- package/dist/renderer/patch-commit-scheduler.d.ts +6 -5
- package/dist/renderer/patch-commit-scheduler.mjs +68 -7
- package/dist/renderer/store.cjs +481 -56
- package/dist/renderer/store.d.cts +2 -1
- package/dist/renderer/store.d.ts +2 -1
- package/dist/renderer/store.mjs +479 -47
- package/dist/renderer/virtualized-code.cjs +8 -2
- package/dist/renderer/virtualized-code.d.cts +4 -0
- package/dist/renderer/virtualized-code.d.ts +4 -0
- package/dist/renderer/virtualized-code.mjs +8 -2
- package/dist/renderer.cjs +3199 -2208
- package/dist/renderer.d.cts +4 -2
- package/dist/renderer.d.ts +4 -2
- package/dist/renderer.mjs +2956 -1957
- package/dist/streaming-markdown-DSC4L0xR.d.cts +157 -0
- package/dist/streaming-markdown-Dp1IDgMT.d.ts +157 -0
- package/dist/streaming-markdown.cjs +3950 -2248
- package/dist/streaming-markdown.d.cts +6 -95
- package/dist/streaming-markdown.d.ts +6 -95
- package/dist/streaming-markdown.mjs +3962 -2252
- package/dist/utils/inline-html.d.cts +1 -1
- package/dist/utils/inline-html.d.ts +1 -1
- package/package.json +3 -3
package/dist/renderer/store.mjs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/renderer/store.ts
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
cloneBlock,
|
|
6
|
+
createBlockSnapshot,
|
|
7
|
+
extractCodeWrapperAttributes,
|
|
8
|
+
getDefaultCodeWrapperAttributes,
|
|
9
|
+
sanitizeCodeHTML,
|
|
10
|
+
sanitizeHTML,
|
|
11
|
+
PATCH_ROOT_ID
|
|
12
|
+
} from "@stream-mdx/core";
|
|
8
13
|
|
|
9
14
|
// src/renderer/list-utils.ts
|
|
10
15
|
function updateNodeDepth(node, depth) {
|
|
@@ -29,6 +34,22 @@ function normalizeAllListDepths(map, touched, rootId = "__root__") {
|
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
}
|
|
37
|
+
function normalizeListDepthsForIds(map, touched, listIds, rootId = "__root__") {
|
|
38
|
+
if (listIds.size === 0) {
|
|
39
|
+
normalizeAllListDepths(map, touched, rootId);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const visited = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const listId of listIds) {
|
|
44
|
+
const base = map.get(listId);
|
|
45
|
+
if (!base) continue;
|
|
46
|
+
const listNode = base.type === "list" ? base : findNearestListAncestor(map, base);
|
|
47
|
+
if (!listNode || visited.has(listNode.id)) continue;
|
|
48
|
+
visited.add(listNode.id);
|
|
49
|
+
const depth = computeListDepth(map, listNode, rootId);
|
|
50
|
+
normalizeListDepthRecursive(map, listNode, depth, touched);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
32
53
|
function normalizeListDepthRecursive(map, listNode, depth, touched) {
|
|
33
54
|
if (!listNode) return;
|
|
34
55
|
if (updateNodeDepth(listNode, depth)) {
|
|
@@ -53,6 +74,28 @@ function normalizeListDepthRecursive(map, listNode, depth, touched) {
|
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
76
|
}
|
|
77
|
+
function findNearestListAncestor(map, node) {
|
|
78
|
+
let current = node;
|
|
79
|
+
while (current) {
|
|
80
|
+
if (current.type === "list") return current;
|
|
81
|
+
if (!current.parentId) return void 0;
|
|
82
|
+
current = map.get(current.parentId);
|
|
83
|
+
}
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
function computeListDepth(map, listNode, rootId) {
|
|
87
|
+
let depth = 0;
|
|
88
|
+
let current = listNode;
|
|
89
|
+
while (current && current.parentId && current.parentId !== rootId) {
|
|
90
|
+
const parent = map.get(current.parentId);
|
|
91
|
+
if (!parent) break;
|
|
92
|
+
if (parent.type === "list") {
|
|
93
|
+
depth += 1;
|
|
94
|
+
}
|
|
95
|
+
current = parent;
|
|
96
|
+
}
|
|
97
|
+
return depth;
|
|
98
|
+
}
|
|
56
99
|
|
|
57
100
|
// src/renderer/patch-coalescing.ts
|
|
58
101
|
import {
|
|
@@ -73,12 +116,35 @@ function snapshotToBlock(node) {
|
|
|
73
116
|
}
|
|
74
117
|
var nodeSnapshotCache = /* @__PURE__ */ new Map();
|
|
75
118
|
var childrenSnapshotCache = /* @__PURE__ */ new Map();
|
|
119
|
+
var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
120
|
+
var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
|
|
121
|
+
var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
122
|
+
var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
|
|
123
|
+
function pruneCache(cache, max, buffer) {
|
|
124
|
+
if (cache.size <= max + buffer) return;
|
|
125
|
+
let remaining = cache.size - max;
|
|
126
|
+
for (const key of cache.keys()) {
|
|
127
|
+
cache.delete(key);
|
|
128
|
+
remaining -= 1;
|
|
129
|
+
if (remaining <= 0) break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
76
132
|
var EMPTY_CHILDREN = Object.freeze([]);
|
|
77
133
|
var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
|
|
78
134
|
var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
|
|
79
135
|
version: -1,
|
|
80
136
|
children: EMPTY_CHILDREN
|
|
81
137
|
});
|
|
138
|
+
function isListPatchDebugEnabled() {
|
|
139
|
+
if (typeof globalThis !== "undefined") {
|
|
140
|
+
const flag = globalThis.__STREAMING_LIST_DEBUG__;
|
|
141
|
+
if (flag === true) return true;
|
|
142
|
+
}
|
|
143
|
+
if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_STREAMING_LIST_DEBUG === "true") {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
82
148
|
var CODE_BLOCK_DEBUG_ENABLED = (() => {
|
|
83
149
|
try {
|
|
84
150
|
if (typeof process !== "undefined" && process.env && process.env.NEXT_PUBLIC_STREAMING_DEBUG_CODELINES === "1") {
|
|
@@ -94,6 +160,128 @@ var CODE_BLOCK_DEBUG_ENABLED = (() => {
|
|
|
94
160
|
}
|
|
95
161
|
return false;
|
|
96
162
|
})() || false;
|
|
163
|
+
var CODE_BLOCK_VALIDATION_ENABLED = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
|
|
164
|
+
var PATCH_PERF_HISTORY_LIMIT = 120;
|
|
165
|
+
var getPerfNow = (() => {
|
|
166
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
167
|
+
return () => performance.now();
|
|
168
|
+
}
|
|
169
|
+
return () => Date.now();
|
|
170
|
+
})();
|
|
171
|
+
function isPatchPerfDebugEnabled() {
|
|
172
|
+
try {
|
|
173
|
+
if (typeof process !== "undefined" && process.env) {
|
|
174
|
+
const value = process.env.NEXT_PUBLIC_STREAMING_DEBUG_PATCH_PERF;
|
|
175
|
+
if (value === "1" || value === "true") {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const debug = globalThis.__STREAMING_DEBUG__;
|
|
183
|
+
if (debug?.patchPerf) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
function recordPatchPerf(summary) {
|
|
191
|
+
try {
|
|
192
|
+
if (typeof globalThis === "undefined") return;
|
|
193
|
+
const root = globalThis;
|
|
194
|
+
const history = root.__STREAM_MDX_PATCH_STATS__ ?? [];
|
|
195
|
+
history.push(summary);
|
|
196
|
+
if (history.length > PATCH_PERF_HISTORY_LIMIT) {
|
|
197
|
+
history.splice(0, history.length - PATCH_PERF_HISTORY_LIMIT);
|
|
198
|
+
}
|
|
199
|
+
root.__STREAM_MDX_PATCH_STATS__ = history;
|
|
200
|
+
root.__STREAM_MDX_PATCH_STATS_LAST__ = summary;
|
|
201
|
+
const totals = root.__STREAM_MDX_PATCH_STATS_TOTALS__ ?? {
|
|
202
|
+
calls: 0,
|
|
203
|
+
totalMs: 0,
|
|
204
|
+
applyMs: 0,
|
|
205
|
+
coalesceMs: 0,
|
|
206
|
+
listNormalizeMs: 0,
|
|
207
|
+
patches: 0,
|
|
208
|
+
coalescedPatches: 0,
|
|
209
|
+
ops: {},
|
|
210
|
+
setPropsByType: {},
|
|
211
|
+
setPropsBySize: {}
|
|
212
|
+
};
|
|
213
|
+
totals.calls += 1;
|
|
214
|
+
totals.totalMs += summary.totalMs;
|
|
215
|
+
totals.applyMs += summary.applyMs;
|
|
216
|
+
totals.coalesceMs += summary.coalesceMs;
|
|
217
|
+
totals.listNormalizeMs += summary.listNormalizeMs;
|
|
218
|
+
totals.patches += summary.patches;
|
|
219
|
+
totals.coalescedPatches += summary.coalescedPatches;
|
|
220
|
+
for (const [op, stats] of Object.entries(summary.ops)) {
|
|
221
|
+
const entry = totals.ops[op] ?? { count: 0, durationMs: 0 };
|
|
222
|
+
entry.count += stats.count;
|
|
223
|
+
entry.durationMs += stats.durationMs;
|
|
224
|
+
totals.ops[op] = entry;
|
|
225
|
+
}
|
|
226
|
+
if (summary.setPropsByType) {
|
|
227
|
+
for (const [key, stats] of Object.entries(summary.setPropsByType)) {
|
|
228
|
+
const entry = totals.setPropsByType[key] ?? { count: 0, durationMs: 0 };
|
|
229
|
+
entry.count += stats.count;
|
|
230
|
+
entry.durationMs += stats.durationMs;
|
|
231
|
+
totals.setPropsByType[key] = entry;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (summary.setPropsBySize) {
|
|
235
|
+
for (const [key, stats] of Object.entries(summary.setPropsBySize)) {
|
|
236
|
+
const entry = totals.setPropsBySize[key] ?? { count: 0, durationMs: 0 };
|
|
237
|
+
entry.count += stats.count;
|
|
238
|
+
entry.durationMs += stats.durationMs;
|
|
239
|
+
totals.setPropsBySize[key] = entry;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
root.__STREAM_MDX_PATCH_STATS_TOTALS__ = totals;
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function estimateBlockPayloadSize(block) {
|
|
247
|
+
if (!block || !block.payload) return 0;
|
|
248
|
+
let size = 0;
|
|
249
|
+
const payload = block.payload;
|
|
250
|
+
if (typeof payload.raw === "string") size += payload.raw.length;
|
|
251
|
+
if (typeof payload.highlightedHtml === "string") size += payload.highlightedHtml.length;
|
|
252
|
+
if (typeof payload.sanitizedHtml === "string") size += payload.sanitizedHtml.length;
|
|
253
|
+
if (typeof payload.compiledMdxModule?.code === "string") size += payload.compiledMdxModule.code.length;
|
|
254
|
+
if (Array.isArray(payload.inline)) size += payload.inline.length * 8;
|
|
255
|
+
return size;
|
|
256
|
+
}
|
|
257
|
+
function estimatePropsSize(props, block) {
|
|
258
|
+
let size = 0;
|
|
259
|
+
if (props) {
|
|
260
|
+
for (const [key, value] of Object.entries(props)) {
|
|
261
|
+
if (key === "block") continue;
|
|
262
|
+
if (typeof value === "string") {
|
|
263
|
+
size += value.length;
|
|
264
|
+
} else if (typeof value === "number") {
|
|
265
|
+
size += 8;
|
|
266
|
+
} else if (typeof value === "boolean") {
|
|
267
|
+
size += 4;
|
|
268
|
+
} else if (Array.isArray(value)) {
|
|
269
|
+
size += value.length * 4;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (block) {
|
|
274
|
+
size += estimateBlockPayloadSize(block);
|
|
275
|
+
}
|
|
276
|
+
return size;
|
|
277
|
+
}
|
|
278
|
+
function bucketBySize(size) {
|
|
279
|
+
if (size < 1024) return "<1k";
|
|
280
|
+
if (size < 10 * 1024) return "1-10k";
|
|
281
|
+
if (size < 50 * 1024) return "10-50k";
|
|
282
|
+
if (size < 200 * 1024) return "50-200k";
|
|
283
|
+
return ">=200k";
|
|
284
|
+
}
|
|
97
285
|
function debugCodeBlock(event, payload) {
|
|
98
286
|
if (!CODE_BLOCK_DEBUG_ENABLED) return;
|
|
99
287
|
try {
|
|
@@ -170,7 +358,7 @@ function escapeHtml(value) {
|
|
|
170
358
|
}
|
|
171
359
|
function stripOuterLineSpan(html) {
|
|
172
360
|
if (!html) return null;
|
|
173
|
-
const openTagMatch = html.match(/<span[^>]
|
|
361
|
+
const openTagMatch = html.match(/<span[^>]*>/i);
|
|
174
362
|
if (!openTagMatch) {
|
|
175
363
|
return null;
|
|
176
364
|
}
|
|
@@ -287,8 +475,12 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
|
287
475
|
touched.add(node.id);
|
|
288
476
|
mutated = true;
|
|
289
477
|
}
|
|
290
|
-
const
|
|
291
|
-
if (
|
|
478
|
+
const incoming = { index: idx, text: node.props?.text, html: node.props?.html };
|
|
479
|
+
if (Object.prototype.hasOwnProperty.call(node.props ?? {}, "tokens")) {
|
|
480
|
+
incoming.tokens = node.props.tokens;
|
|
481
|
+
}
|
|
482
|
+
const normalizedProps = normalizeCodeLineProps(incoming, node.props);
|
|
483
|
+
if (normalizedProps !== node.props) {
|
|
292
484
|
node.props = normalizedProps;
|
|
293
485
|
node.version++;
|
|
294
486
|
touched.add(node.id);
|
|
@@ -334,43 +526,68 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
|
|
|
334
526
|
children: parent.children.slice()
|
|
335
527
|
});
|
|
336
528
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
issues: validation.issues
|
|
342
|
-
});
|
|
343
|
-
if (!rebuildCodeBlockFromSnapshot(nodes, parent, touched, validation.issues)) {
|
|
344
|
-
console.warn("[renderer-store] unable to fully normalize code block", {
|
|
529
|
+
if (CODE_BLOCK_VALIDATION_ENABLED) {
|
|
530
|
+
const validation = validateCodeBlockChildren(nodes, parent);
|
|
531
|
+
if (!validation.ok) {
|
|
532
|
+
debugCodeBlock("code-block-validation-failed", {
|
|
345
533
|
parentId: parent.id,
|
|
346
534
|
issues: validation.issues
|
|
347
535
|
});
|
|
536
|
+
if (!rebuildCodeBlockFromSnapshot(nodes, parent, touched, validation.issues)) {
|
|
537
|
+
console.warn("[renderer-store] unable to fully normalize code block", {
|
|
538
|
+
parentId: parent.id,
|
|
539
|
+
issues: validation.issues
|
|
540
|
+
});
|
|
541
|
+
}
|
|
348
542
|
}
|
|
349
543
|
}
|
|
350
544
|
}
|
|
351
545
|
function normalizeCodeLineProps(incoming, previous) {
|
|
352
|
-
const
|
|
546
|
+
const previousIndex = typeof previous?.index === "number" ? previous?.index : 0;
|
|
547
|
+
const index = typeof incoming.index === "number" ? incoming.index : previousIndex;
|
|
353
548
|
const previousText = typeof previous?.text === "string" ? previous?.text : "";
|
|
354
549
|
const previousHtml = typeof previous?.html === "string" ? previous?.html : null;
|
|
550
|
+
const previousTokens = Object.prototype.hasOwnProperty.call(previous ?? {}, "tokens") ? previous?.tokens : void 0;
|
|
355
551
|
const hasIncomingText = typeof incoming.text === "string";
|
|
552
|
+
const hasIncomingHtml = typeof incoming.html === "string";
|
|
553
|
+
const hasIncomingTokens = Object.prototype.hasOwnProperty.call(incoming, "tokens");
|
|
554
|
+
const incomingTokens = hasIncomingTokens ? incoming.tokens : void 0;
|
|
356
555
|
const rawText = hasIncomingText ? incoming.text : previousText;
|
|
556
|
+
if (previous && !hasIncomingHtml && !hasIncomingTokens && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
|
|
557
|
+
return previous;
|
|
558
|
+
}
|
|
559
|
+
if (previous && hasIncomingHtml && !hasIncomingTokens && incoming.html === previousHtml && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
|
|
560
|
+
return previous;
|
|
561
|
+
}
|
|
357
562
|
let highlight = null;
|
|
358
|
-
if (
|
|
563
|
+
if (hasIncomingHtml) {
|
|
359
564
|
highlight = incoming.html;
|
|
360
565
|
} else if (!hasIncomingText || rawText === previousText) {
|
|
361
566
|
highlight = previousHtml;
|
|
362
567
|
}
|
|
363
|
-
|
|
568
|
+
let tokens = void 0;
|
|
569
|
+
if (hasIncomingTokens) {
|
|
570
|
+
tokens = incomingTokens ?? null;
|
|
571
|
+
} else if (!hasIncomingText || rawText === previousText) {
|
|
572
|
+
tokens = previousTokens;
|
|
573
|
+
} else {
|
|
574
|
+
tokens = null;
|
|
575
|
+
}
|
|
576
|
+
return createCodeLineProps(index, rawText, highlight, tokens);
|
|
364
577
|
}
|
|
365
|
-
function createCodeLineProps(index, text, highlight) {
|
|
578
|
+
function createCodeLineProps(index, text, highlight, tokens) {
|
|
366
579
|
const safeText = typeof text === "string" ? text : "";
|
|
367
580
|
const sanitized = sanitizeLineInnerHtml(highlight, safeText);
|
|
368
581
|
const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml(safeText);
|
|
369
|
-
|
|
582
|
+
const props = {
|
|
370
583
|
index,
|
|
371
584
|
text: safeText,
|
|
372
585
|
html
|
|
373
586
|
};
|
|
587
|
+
if (tokens !== void 0) {
|
|
588
|
+
props.tokens = tokens;
|
|
589
|
+
}
|
|
590
|
+
return props;
|
|
374
591
|
}
|
|
375
592
|
function clampIndex(index, length) {
|
|
376
593
|
if (!Number.isFinite(index)) return 0;
|
|
@@ -412,8 +629,13 @@ function mergeInlineSegmentProps(previous, incoming) {
|
|
|
412
629
|
function applyCodeBlockMetadata(record) {
|
|
413
630
|
const block = record.block;
|
|
414
631
|
const highlightedHtml = block?.payload.highlightedHtml ?? "";
|
|
415
|
-
const { preAttrs, codeAttrs } = extractCodeWrapperAttributes(highlightedHtml);
|
|
416
632
|
const lang = typeof block?.payload.meta?.lang === "string" ? block?.payload.meta?.lang : record.props?.lang;
|
|
633
|
+
let { preAttrs, codeAttrs } = extractCodeWrapperAttributes(highlightedHtml);
|
|
634
|
+
if (!preAttrs || !codeAttrs) {
|
|
635
|
+
const defaults = getDefaultCodeWrapperAttributes(typeof lang === "string" ? lang : void 0);
|
|
636
|
+
preAttrs = preAttrs ?? defaults.preAttrs;
|
|
637
|
+
codeAttrs = codeAttrs ?? defaults.codeAttrs;
|
|
638
|
+
}
|
|
417
639
|
record.props = {
|
|
418
640
|
...record.props ?? {},
|
|
419
641
|
lang,
|
|
@@ -426,7 +648,8 @@ function normalizeNodeProps(snapshot) {
|
|
|
426
648
|
const index = typeof snapshot.props?.index === "number" ? snapshot.props?.index : 0;
|
|
427
649
|
const text = typeof snapshot.props?.text === "string" ? snapshot.props?.text : "";
|
|
428
650
|
const html = typeof snapshot.props?.html === "string" ? snapshot.props?.html : null;
|
|
429
|
-
|
|
651
|
+
const tokens = Object.prototype.hasOwnProperty.call(snapshot.props ?? {}, "tokens") ? snapshot.props?.tokens : void 0;
|
|
652
|
+
return createCodeLineProps(index, text, html, tokens);
|
|
430
653
|
}
|
|
431
654
|
if (isInlineSegmentType(snapshot.type)) {
|
|
432
655
|
const incoming = snapshot.props ? { ...snapshot.props } : {};
|
|
@@ -694,6 +917,13 @@ function createRendererStore(initialBlocks = []) {
|
|
|
694
917
|
lastCoalescingMetrics = options?.metrics ?? null;
|
|
695
918
|
return touched;
|
|
696
919
|
}
|
|
920
|
+
const perfEnabled = isPatchPerfDebugEnabled();
|
|
921
|
+
const perfStart = perfEnabled ? getPerfNow() : 0;
|
|
922
|
+
const opStats = perfEnabled ? {} : {};
|
|
923
|
+
const setPropsByType = perfEnabled ? {} : {};
|
|
924
|
+
const setPropsBySize = perfEnabled ? {} : {};
|
|
925
|
+
let applyMs = 0;
|
|
926
|
+
let listNormalizeMs = 0;
|
|
697
927
|
let coalescedPatches = patches;
|
|
698
928
|
let metrics = options?.metrics ?? null;
|
|
699
929
|
if (!options?.coalesced) {
|
|
@@ -708,32 +938,111 @@ function createRendererStore(initialBlocks = []) {
|
|
|
708
938
|
}
|
|
709
939
|
let mutatedBlocks = false;
|
|
710
940
|
let listDepthDirty = false;
|
|
941
|
+
const listDepthDirtyNodes = /* @__PURE__ */ new Set();
|
|
942
|
+
const debugListPatches = isListPatchDebugEnabled();
|
|
943
|
+
const isListRelated = (type) => type === "list" || type === "list-item";
|
|
944
|
+
const logListPatch = (label, details) => {
|
|
945
|
+
if (!debugListPatches) return;
|
|
946
|
+
try {
|
|
947
|
+
console.debug(`[list-patch] ${label}`, details);
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
const findNearestListNode = (nodeId) => {
|
|
952
|
+
if (!nodeId) return void 0;
|
|
953
|
+
let current = nodes.get(nodeId);
|
|
954
|
+
while (current) {
|
|
955
|
+
if (current.type === "list") return current;
|
|
956
|
+
if (!current.parentId) return void 0;
|
|
957
|
+
current = nodes.get(current.parentId);
|
|
958
|
+
}
|
|
959
|
+
return void 0;
|
|
960
|
+
};
|
|
961
|
+
const markListDepthDirty = (nodeId) => {
|
|
962
|
+
const listNode = findNearestListNode(nodeId);
|
|
963
|
+
if (listNode) {
|
|
964
|
+
listDepthDirtyNodes.add(listNode.id);
|
|
965
|
+
} else {
|
|
966
|
+
listDepthDirty = true;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
711
969
|
const applyPropsUpdate = (at, props) => {
|
|
970
|
+
const perfStartLocal = perfEnabled ? getPerfNow() : 0;
|
|
712
971
|
const targetId = at.nodeId ?? at.blockId;
|
|
713
972
|
if (!targetId) return;
|
|
714
973
|
const target = nodes.get(targetId);
|
|
715
974
|
if (!target) return;
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
975
|
+
const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
|
|
976
|
+
if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
|
|
977
|
+
logListPatch("setProps", {
|
|
978
|
+
id: target.id,
|
|
979
|
+
type: target.type,
|
|
980
|
+
parentType,
|
|
981
|
+
keys: props ? Object.keys(props) : []
|
|
982
|
+
});
|
|
720
983
|
}
|
|
721
984
|
let propsChanged = false;
|
|
722
985
|
if (target.type === "code-line") {
|
|
723
|
-
const
|
|
724
|
-
|
|
986
|
+
const incoming = {
|
|
987
|
+
index: props?.index,
|
|
988
|
+
text: props?.text,
|
|
989
|
+
html: props?.html
|
|
990
|
+
};
|
|
991
|
+
if (Object.prototype.hasOwnProperty.call(props ?? {}, "tokens")) {
|
|
992
|
+
incoming.tokens = props.tokens;
|
|
993
|
+
}
|
|
994
|
+
const normalized = normalizeCodeLineProps(incoming, target.props);
|
|
995
|
+
if (normalized !== target.props) {
|
|
725
996
|
target.props = normalized;
|
|
726
997
|
propsChanged = true;
|
|
727
998
|
}
|
|
999
|
+
if (propsChanged) {
|
|
1000
|
+
target.version++;
|
|
1001
|
+
touched.add(target.id);
|
|
1002
|
+
const parent = target.parentId ? nodes.get(target.parentId) : void 0;
|
|
1003
|
+
if (parent) {
|
|
1004
|
+
parent.version++;
|
|
1005
|
+
touched.add(parent.id);
|
|
1006
|
+
if (parent.type === "code") {
|
|
1007
|
+
normalizeCodeBlockChildren(nodes, parent, touched);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (perfEnabled) {
|
|
1012
|
+
const duration = getPerfNow() - perfStartLocal;
|
|
1013
|
+
const typeKey = target.type || "unknown";
|
|
1014
|
+
const sizeKey = bucketBySize(estimatePropsSize(props, void 0));
|
|
1015
|
+
const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
|
|
1016
|
+
typeEntry.count += 1;
|
|
1017
|
+
typeEntry.durationMs += duration;
|
|
1018
|
+
setPropsByType[typeKey] = typeEntry;
|
|
1019
|
+
const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
|
|
1020
|
+
sizeEntry.count += 1;
|
|
1021
|
+
sizeEntry.durationMs += duration;
|
|
1022
|
+
setPropsBySize[sizeKey] = sizeEntry;
|
|
1023
|
+
}
|
|
1024
|
+
return;
|
|
728
1025
|
} else if (isInlineSegmentType(target.type)) {
|
|
1026
|
+
const nextProps = props ? { ...props } : {};
|
|
1027
|
+
if ("block" in nextProps) {
|
|
1028
|
+
delete nextProps.block;
|
|
1029
|
+
}
|
|
729
1030
|
const mergedSegments = mergeInlineSegmentProps(target.props, nextProps);
|
|
730
1031
|
if (!shallowEqualRecords(target.props, mergedSegments)) {
|
|
731
1032
|
target.props = mergedSegments;
|
|
732
1033
|
propsChanged = true;
|
|
733
1034
|
}
|
|
734
1035
|
} else {
|
|
1036
|
+
const nextProps = props ? { ...props } : {};
|
|
1037
|
+
if ("block" in nextProps) {
|
|
1038
|
+
delete nextProps.block;
|
|
1039
|
+
}
|
|
735
1040
|
const base = target.props ? { ...target.props } : {};
|
|
736
1041
|
let localChanged = false;
|
|
1042
|
+
if (Object.prototype.hasOwnProperty.call(base, "block")) {
|
|
1043
|
+
delete base.block;
|
|
1044
|
+
localChanged = true;
|
|
1045
|
+
}
|
|
737
1046
|
for (const [key, value] of Object.entries(nextProps)) {
|
|
738
1047
|
if (value === void 0) {
|
|
739
1048
|
if (Object.prototype.hasOwnProperty.call(base, key)) {
|
|
@@ -751,6 +1060,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
751
1060
|
}
|
|
752
1061
|
}
|
|
753
1062
|
let blockChanged = false;
|
|
1063
|
+
const incomingBlock = props?.block;
|
|
754
1064
|
if (incomingBlock && typeof incomingBlock === "object") {
|
|
755
1065
|
target.block = cloneBlock(incomingBlock);
|
|
756
1066
|
if (target.type === "code") {
|
|
@@ -780,13 +1090,26 @@ function createRendererStore(initialBlocks = []) {
|
|
|
780
1090
|
} else if (parentForNormalization) {
|
|
781
1091
|
normalizeCodeBlockChildren(nodes, parentForNormalization, touched);
|
|
782
1092
|
}
|
|
783
|
-
const
|
|
784
|
-
if (target.type
|
|
785
|
-
|
|
1093
|
+
const parentType2 = target.parentId ? nodes.get(target.parentId)?.type : void 0;
|
|
1094
|
+
if (isListRelated(target.type) || isListRelated(parentType2)) {
|
|
1095
|
+
markListDepthDirty(target.id);
|
|
786
1096
|
}
|
|
787
1097
|
}
|
|
1098
|
+
if (perfEnabled) {
|
|
1099
|
+
const duration = getPerfNow() - perfStartLocal;
|
|
1100
|
+
const typeKey = target.type || "unknown";
|
|
1101
|
+
const sizeKey = bucketBySize(estimatePropsSize(props, incomingBlock && typeof incomingBlock === "object" ? incomingBlock : void 0));
|
|
1102
|
+
const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
|
|
1103
|
+
typeEntry.count += 1;
|
|
1104
|
+
typeEntry.durationMs += duration;
|
|
1105
|
+
setPropsByType[typeKey] = typeEntry;
|
|
1106
|
+
const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
|
|
1107
|
+
sizeEntry.count += 1;
|
|
1108
|
+
sizeEntry.durationMs += duration;
|
|
1109
|
+
setPropsBySize[sizeKey] = sizeEntry;
|
|
1110
|
+
}
|
|
788
1111
|
};
|
|
789
|
-
|
|
1112
|
+
const applyPatch = (patch) => {
|
|
790
1113
|
switch (patch.op) {
|
|
791
1114
|
case "insertChild": {
|
|
792
1115
|
const parent = resolveParent(nodes, patch.at);
|
|
@@ -796,8 +1119,20 @@ function createRendererStore(initialBlocks = []) {
|
|
|
796
1119
|
touched.add(id);
|
|
797
1120
|
}
|
|
798
1121
|
mutatedBlocks = true;
|
|
799
|
-
if (inserted.record.type
|
|
800
|
-
|
|
1122
|
+
if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
|
|
1123
|
+
markListDepthDirty(inserted.record.id);
|
|
1124
|
+
if (parent) {
|
|
1125
|
+
markListDepthDirty(parent.id);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (debugListPatches && (isListRelated(inserted.record.type) || isListRelated(parent?.type))) {
|
|
1129
|
+
logListPatch("insertChild", {
|
|
1130
|
+
id: inserted.record.id,
|
|
1131
|
+
type: inserted.record.type,
|
|
1132
|
+
parentId: parent?.id,
|
|
1133
|
+
parentType: parent?.type,
|
|
1134
|
+
index: patch.index
|
|
1135
|
+
});
|
|
801
1136
|
}
|
|
802
1137
|
}
|
|
803
1138
|
break;
|
|
@@ -814,8 +1149,17 @@ function createRendererStore(initialBlocks = []) {
|
|
|
814
1149
|
touched.add(parent.id);
|
|
815
1150
|
ensureUniqueChildren(parent, touched);
|
|
816
1151
|
mutatedBlocks = true;
|
|
817
|
-
if (parent.type
|
|
818
|
-
|
|
1152
|
+
if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
|
|
1153
|
+
markListDepthDirty(parent.id);
|
|
1154
|
+
}
|
|
1155
|
+
if (debugListPatches && (isListRelated(parent.type) || isListRelated(removedNode?.type))) {
|
|
1156
|
+
logListPatch("deleteChild", {
|
|
1157
|
+
id: childId,
|
|
1158
|
+
type: removedNode?.type,
|
|
1159
|
+
parentId: parent.id,
|
|
1160
|
+
parentType: parent.type,
|
|
1161
|
+
index: patch.index
|
|
1162
|
+
});
|
|
819
1163
|
}
|
|
820
1164
|
}
|
|
821
1165
|
break;
|
|
@@ -826,8 +1170,15 @@ function createRendererStore(initialBlocks = []) {
|
|
|
826
1170
|
replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
|
|
827
1171
|
touched.add(parent.id);
|
|
828
1172
|
mutatedBlocks = true;
|
|
829
|
-
if (parent.type
|
|
830
|
-
|
|
1173
|
+
if (isListRelated(parent.type)) {
|
|
1174
|
+
markListDepthDirty(parent.id);
|
|
1175
|
+
}
|
|
1176
|
+
if (debugListPatches && isListRelated(parent.type)) {
|
|
1177
|
+
logListPatch("replaceChild", {
|
|
1178
|
+
parentId: parent.id,
|
|
1179
|
+
parentType: parent.type,
|
|
1180
|
+
index: patch.index
|
|
1181
|
+
});
|
|
831
1182
|
}
|
|
832
1183
|
break;
|
|
833
1184
|
}
|
|
@@ -853,6 +1204,15 @@ function createRendererStore(initialBlocks = []) {
|
|
|
853
1204
|
touched.add(target.id);
|
|
854
1205
|
mutatedBlocks = true;
|
|
855
1206
|
}
|
|
1207
|
+
if (isListRelated(target.type)) {
|
|
1208
|
+
markListDepthDirty(target.id);
|
|
1209
|
+
}
|
|
1210
|
+
if (debugListPatches && isListRelated(target.type)) {
|
|
1211
|
+
logListPatch("finalize", {
|
|
1212
|
+
id: target.id,
|
|
1213
|
+
type: target.type
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
856
1216
|
break;
|
|
857
1217
|
}
|
|
858
1218
|
case "reorder": {
|
|
@@ -870,8 +1230,17 @@ function createRendererStore(initialBlocks = []) {
|
|
|
870
1230
|
parent.children.splice(to, 0, ...moved);
|
|
871
1231
|
parent.version++;
|
|
872
1232
|
touched.add(parent.id);
|
|
873
|
-
if (parent.type
|
|
874
|
-
|
|
1233
|
+
if (isListRelated(parent.type)) {
|
|
1234
|
+
markListDepthDirty(parent.id);
|
|
1235
|
+
}
|
|
1236
|
+
if (debugListPatches && isListRelated(parent.type)) {
|
|
1237
|
+
logListPatch("reorder", {
|
|
1238
|
+
parentId: parent.id,
|
|
1239
|
+
parentType: parent.type,
|
|
1240
|
+
from: patch.from,
|
|
1241
|
+
to: patch.to,
|
|
1242
|
+
count: patch.count
|
|
1243
|
+
});
|
|
875
1244
|
}
|
|
876
1245
|
break;
|
|
877
1246
|
}
|
|
@@ -892,7 +1261,14 @@ function createRendererStore(initialBlocks = []) {
|
|
|
892
1261
|
const lineId = `${parent.id}::line:${absoluteIndex}`;
|
|
893
1262
|
insertedIds.push(lineId);
|
|
894
1263
|
insertedSet.add(lineId);
|
|
895
|
-
const
|
|
1264
|
+
const hasTokens = Object.prototype.hasOwnProperty.call(patch, "tokens");
|
|
1265
|
+
const nextTokens = hasTokens ? patch.tokens?.[offset] ?? null : void 0;
|
|
1266
|
+
const nextLineProps = createCodeLineProps(
|
|
1267
|
+
absoluteIndex,
|
|
1268
|
+
patch.lines[offset] ?? "",
|
|
1269
|
+
patch.highlight?.[offset] ?? null,
|
|
1270
|
+
nextTokens
|
|
1271
|
+
);
|
|
896
1272
|
const existing = nodes.get(lineId);
|
|
897
1273
|
if (existing && existing.type === "code-line") {
|
|
898
1274
|
const existingIndex = parent.children.indexOf(lineId);
|
|
@@ -945,8 +1321,12 @@ function createRendererStore(initialBlocks = []) {
|
|
|
945
1321
|
const childId = parent.children[idx];
|
|
946
1322
|
const child = nodes.get(childId);
|
|
947
1323
|
if (child && child.type === "code-line") {
|
|
948
|
-
const
|
|
949
|
-
if (
|
|
1324
|
+
const incoming = { index: idx, text: child.props?.text, html: child.props?.html };
|
|
1325
|
+
if (Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens")) {
|
|
1326
|
+
incoming.tokens = child.props.tokens;
|
|
1327
|
+
}
|
|
1328
|
+
const normalized = normalizeCodeLineProps(incoming, child.props);
|
|
1329
|
+
if (normalized !== child.props) {
|
|
950
1330
|
child.props = normalized;
|
|
951
1331
|
child.version++;
|
|
952
1332
|
touched.add(child.id);
|
|
@@ -989,17 +1369,66 @@ function createRendererStore(initialBlocks = []) {
|
|
|
989
1369
|
default:
|
|
990
1370
|
break;
|
|
991
1371
|
}
|
|
1372
|
+
};
|
|
1373
|
+
if (perfEnabled) {
|
|
1374
|
+
const loopStart = getPerfNow();
|
|
1375
|
+
for (const patch of coalescedPatches) {
|
|
1376
|
+
const opStart = getPerfNow();
|
|
1377
|
+
applyPatch(patch);
|
|
1378
|
+
const opDuration = getPerfNow() - opStart;
|
|
1379
|
+
const entry = opStats[patch.op] ?? { count: 0, durationMs: 0 };
|
|
1380
|
+
entry.count += 1;
|
|
1381
|
+
entry.durationMs += opDuration;
|
|
1382
|
+
opStats[patch.op] = entry;
|
|
1383
|
+
}
|
|
1384
|
+
applyMs = getPerfNow() - loopStart;
|
|
1385
|
+
} else {
|
|
1386
|
+
for (const patch of coalescedPatches) {
|
|
1387
|
+
applyPatch(patch);
|
|
1388
|
+
}
|
|
992
1389
|
}
|
|
993
1390
|
if (mutatedBlocks) {
|
|
994
1391
|
blocksDirty = true;
|
|
995
1392
|
}
|
|
996
1393
|
if (listDepthDirty) {
|
|
997
|
-
|
|
1394
|
+
if (perfEnabled) {
|
|
1395
|
+
const start = getPerfNow();
|
|
1396
|
+
normalizeAllListDepths(nodes, touched, PATCH_ROOT_ID);
|
|
1397
|
+
listNormalizeMs = getPerfNow() - start;
|
|
1398
|
+
} else {
|
|
1399
|
+
normalizeAllListDepths(nodes, touched, PATCH_ROOT_ID);
|
|
1400
|
+
}
|
|
1401
|
+
} else if (listDepthDirtyNodes.size > 0) {
|
|
1402
|
+
if (perfEnabled) {
|
|
1403
|
+
const start = getPerfNow();
|
|
1404
|
+
normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
|
|
1405
|
+
listNormalizeMs = getPerfNow() - start;
|
|
1406
|
+
} else {
|
|
1407
|
+
normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
|
|
1408
|
+
}
|
|
998
1409
|
}
|
|
999
1410
|
if (touched.size > 0 || mutatedBlocks) {
|
|
1000
1411
|
version++;
|
|
1001
1412
|
scheduleNotify();
|
|
1002
1413
|
}
|
|
1414
|
+
if (perfEnabled) {
|
|
1415
|
+
const totalMs = getPerfNow() - perfStart;
|
|
1416
|
+
const coalesceMs = typeof metrics?.durationMs === "number" ? metrics.durationMs : 0;
|
|
1417
|
+
recordPatchPerf({
|
|
1418
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1419
|
+
totalMs,
|
|
1420
|
+
applyMs,
|
|
1421
|
+
coalesceMs,
|
|
1422
|
+
listNormalizeMs,
|
|
1423
|
+
patches: patches.length,
|
|
1424
|
+
coalescedPatches: coalescedPatches.length,
|
|
1425
|
+
touchedCount: touched.size,
|
|
1426
|
+
mutatedBlocks,
|
|
1427
|
+
ops: opStats,
|
|
1428
|
+
setPropsByType,
|
|
1429
|
+
setPropsBySize
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1003
1432
|
return touched;
|
|
1004
1433
|
},
|
|
1005
1434
|
getBlocks() {
|
|
@@ -1032,6 +1461,7 @@ function createRendererStore(initialBlocks = []) {
|
|
|
1032
1461
|
}
|
|
1033
1462
|
const snapshot = { version: record.version, node: record };
|
|
1034
1463
|
nodeSnapshotCache.set(id, snapshot);
|
|
1464
|
+
pruneCache(nodeSnapshotCache, NODE_SNAPSHOT_CACHE_LIMIT, NODE_SNAPSHOT_CACHE_BUFFER);
|
|
1035
1465
|
return snapshot;
|
|
1036
1466
|
},
|
|
1037
1467
|
getChildren(id) {
|
|
@@ -1045,11 +1475,12 @@ function createRendererStore(initialBlocks = []) {
|
|
|
1045
1475
|
return EMPTY_CHILDREN_SNAPSHOT;
|
|
1046
1476
|
}
|
|
1047
1477
|
const cached = childrenSnapshotCache.get(id);
|
|
1048
|
-
if (cached && cached.version === record.version
|
|
1478
|
+
if (cached && cached.version === record.version) {
|
|
1049
1479
|
return cached;
|
|
1050
1480
|
}
|
|
1051
|
-
const snapshot = { version: record.version, children: record.children };
|
|
1481
|
+
const snapshot = { version: record.version, children: record.children.slice() };
|
|
1052
1482
|
childrenSnapshotCache.set(id, snapshot);
|
|
1483
|
+
pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
|
|
1053
1484
|
return snapshot;
|
|
1054
1485
|
},
|
|
1055
1486
|
getLastCoalescingMetrics() {
|
|
@@ -1065,5 +1496,6 @@ function createRendererStore(initialBlocks = []) {
|
|
|
1065
1496
|
return store;
|
|
1066
1497
|
}
|
|
1067
1498
|
export {
|
|
1068
|
-
createRendererStore
|
|
1499
|
+
createRendererStore,
|
|
1500
|
+
isListPatchDebugEnabled
|
|
1069
1501
|
};
|