@stream-mdx/react 0.4.0 → 0.5.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -4
  2. package/README.md +65 -15
  3. package/dist/components/bottom-stick-scroll-area.cjs +344 -0
  4. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  5. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  6. package/dist/components/bottom-stick-scroll-area.mjs +320 -0
  7. package/dist/components/index.cjs +503 -172
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +350 -22
  11. package/dist/{index-D2MYusAM.d.cts → index-BVhp_pcF.d.ts} +3 -0
  12. package/dist/{index-D2MYusAM.d.ts → index-EaeouYp2.d.cts} +3 -0
  13. package/dist/index.cjs +1472 -486
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +1256 -272
  17. package/dist/mdx-client.cjs +1 -1
  18. package/dist/mdx-client.mjs +1 -1
  19. package/dist/mdx-coordinator.cjs +28 -7
  20. package/dist/mdx-coordinator.d.cts +2 -2
  21. package/dist/mdx-coordinator.d.ts +2 -2
  22. package/dist/mdx-coordinator.mjs +28 -7
  23. package/dist/renderer/node-views.cjs +271 -26
  24. package/dist/renderer/node-views.d.cts +3 -1
  25. package/dist/renderer/node-views.d.ts +3 -1
  26. package/dist/renderer/node-views.mjs +272 -27
  27. package/dist/renderer/patch-batching.cjs +2 -0
  28. package/dist/renderer/patch-batching.d.cts +1 -1
  29. package/dist/renderer/patch-batching.d.ts +1 -1
  30. package/dist/renderer/patch-batching.mjs +2 -1
  31. package/dist/renderer/patch-commit-scheduler.cjs +82 -30
  32. package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
  33. package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
  34. package/dist/renderer/patch-commit-scheduler.mjs +84 -30
  35. package/dist/renderer/store.cjs +382 -36
  36. package/dist/renderer/store.d.cts +3 -0
  37. package/dist/renderer/store.d.ts +3 -0
  38. package/dist/renderer/store.mjs +382 -36
  39. package/dist/renderer.cjs +758 -114
  40. package/dist/renderer.d.cts +3 -1
  41. package/dist/renderer.d.ts +3 -1
  42. package/dist/renderer.mjs +760 -116
  43. package/dist/server.cjs +669 -71
  44. package/dist/server.d.cts +5 -0
  45. package/dist/server.d.ts +5 -0
  46. package/dist/server.mjs +670 -72
  47. package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +788 -121
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +790 -123
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +14 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
@@ -24,6 +24,7 @@ __export(patch_commit_scheduler_exports, {
24
24
  PatchCommitScheduler: () => PatchCommitScheduler
25
25
  });
26
26
  module.exports = __toCommonJS(patch_commit_scheduler_exports);
27
+ var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
27
28
 
28
29
  // src/renderer/patch-coalescing.ts
29
30
  var import_core = require("@stream-mdx/core");
@@ -35,6 +36,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
35
36
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
36
37
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
37
38
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
39
+ function shouldSkipCoalescing(patches) {
40
+ return patches.length <= 1;
41
+ }
38
42
  function getDefaultNow() {
39
43
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
40
44
  return () => performance.now();
@@ -61,8 +65,9 @@ function getDefaultCancelRaf() {
61
65
  }
62
66
  var PatchCommitScheduler = class {
63
67
  constructor(params) {
64
- this.highQueue = [];
65
- this.lowQueue = [];
68
+ this.semanticQueue = [];
69
+ this.enrichmentHighQueue = [];
70
+ this.enrichmentLowQueue = [];
66
71
  this.scheduled = false;
67
72
  this.flushing = false;
68
73
  this.scheduledHandle = null;
@@ -74,6 +79,7 @@ var PatchCommitScheduler = class {
74
79
  this.scheduleToken = 0;
75
80
  this.coalescingDurationSamples = [];
76
81
  this.adaptiveBudgetActive = false;
82
+ this.flushCount = 0;
77
83
  this.store = params.store;
78
84
  this.onFlush = params.onFlush;
79
85
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -89,6 +95,8 @@ var PatchCommitScheduler = class {
89
95
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
90
96
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
91
97
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
98
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
99
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
92
100
  const requestedBatch = params.options?.batch;
93
101
  if (requestedBatch === "microtask") {
94
102
  this.batchStrategy = "microtask";
@@ -115,19 +123,23 @@ var PatchCommitScheduler = class {
115
123
  patches: input.patches,
116
124
  meta: input.meta ?? {},
117
125
  receivedAt: input.meta?.receivedAt ?? this.now(),
126
+ kind: input.meta?.kind === "semantic" || input.meta?.kind === "enrichment" ? input.meta.kind : input.patches.some((patch) => (0, import_patch_batching.getPatchKind)(patch) === "semantic") ? "semantic" : "enrichment",
118
127
  priority: input.meta?.priority === "low" ? "low" : "high"
119
128
  };
120
- if (batch.priority === "low") {
121
- this.lowQueue.push(batch);
129
+ if (batch.kind === "semantic") {
130
+ batch.priority = "high";
131
+ this.semanticQueue.push(batch);
132
+ } else if (batch.priority === "low") {
133
+ this.enrichmentLowQueue.push(batch);
122
134
  } else {
123
- this.highQueue.push(batch);
135
+ this.enrichmentHighQueue.push(batch);
124
136
  }
125
137
  if (!this.paused) {
126
138
  this.schedule();
127
139
  }
128
140
  }
129
141
  flushAll() {
130
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
142
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
131
143
  return null;
132
144
  }
133
145
  this.cancelScheduled();
@@ -137,9 +149,11 @@ var PatchCommitScheduler = class {
137
149
  }
138
150
  clear() {
139
151
  this.cancelScheduled();
140
- this.highQueue = [];
141
- this.lowQueue = [];
152
+ this.semanticQueue = [];
153
+ this.enrichmentHighQueue = [];
154
+ this.enrichmentLowQueue = [];
142
155
  this.flushing = false;
156
+ this.flushCount = 0;
143
157
  this.coalescingDurationSamples = [];
144
158
  this.adaptiveBudgetActive = false;
145
159
  this.adaptiveHighBatchCap = void 0;
@@ -147,10 +161,10 @@ var PatchCommitScheduler = class {
147
161
  this.maybeResolveIdle();
148
162
  }
149
163
  getPendingCount() {
150
- return this.highQueue.length + this.lowQueue.length;
164
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
151
165
  }
152
166
  isIdle() {
153
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
167
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
154
168
  }
155
169
  awaitIdle() {
156
170
  if (this.isIdle()) {
@@ -190,7 +204,8 @@ var PatchCommitScheduler = class {
190
204
  this.runFlush(this.frameBudgetMs, false);
191
205
  this.maybeResolveIdle();
192
206
  };
193
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
207
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
208
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
194
209
  this.scheduledViaRaf = false;
195
210
  this.scheduledHandle = token;
196
211
  queueMicrotask(execute);
@@ -220,12 +235,14 @@ var PatchCommitScheduler = class {
220
235
  if (this.paused && !manual) {
221
236
  return null;
222
237
  }
223
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
238
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
224
239
  return null;
225
240
  }
226
241
  this.flushing = true;
227
242
  const startedAt = this.now();
228
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
243
+ const initialSemanticQueueSize = this.semanticQueue.length;
244
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
245
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
229
246
  const batches = [];
230
247
  let totalInputPatches = 0;
231
248
  let totalAppliedPatches = 0;
@@ -240,7 +257,15 @@ var PatchCommitScheduler = class {
240
257
  consumed += 1;
241
258
  const applyStart = this.now();
242
259
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
243
- const { patches: coalesced, metrics: coalescingMetrics } = (0, import_core.coalescePatchesWithMetrics)(next.patches, import_core.DEFAULT_COALESCE_CONFIG);
260
+ let coalesced;
261
+ let coalescingMetrics;
262
+ if (shouldSkipCoalescing(next.patches)) {
263
+ coalesced = next.patches;
264
+ } else {
265
+ const result = (0, import_core.coalescePatchesWithMetrics)(next.patches, import_core.DEFAULT_COALESCE_CONFIG);
266
+ coalesced = result.patches;
267
+ coalescingMetrics = result.metrics;
268
+ }
244
269
  const touched = this.store.applyPatches(coalesced, {
245
270
  coalesced: true,
246
271
  metrics: coalescingMetrics
@@ -260,6 +285,7 @@ var PatchCommitScheduler = class {
260
285
  receivedAt: next.receivedAt,
261
286
  appliedAt: applyStart,
262
287
  priority: next.priority,
288
+ kind: next.kind,
263
289
  coalescing: coalescingMetrics
264
290
  });
265
291
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -276,27 +302,32 @@ var PatchCommitScheduler = class {
276
302
  queue.splice(0, consumed);
277
303
  }
278
304
  };
279
- const initialHighQueueSize = this.highQueue.length;
305
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
306
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
280
307
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
281
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
282
- processQueue(this.highQueue, highBatchLimit, budgetMs);
308
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
309
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
283
310
  if (manual) {
284
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
285
- } else if (this.lowQueue.length > 0) {
311
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
312
+ } else if (this.enrichmentLowQueue.length > 0) {
286
313
  elapsed = this.now() - startedAt;
287
314
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
288
- if (budgetForLow > 0) {
289
- const initialLowQueueSize = this.lowQueue.length;
315
+ const processedHigherPriorityWork = batches.length > 0;
316
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
317
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
290
318
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
291
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
292
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
319
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
320
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
293
321
  }
294
322
  }
295
323
  this.flushing = false;
296
324
  const completedAt = this.now();
297
325
  const totalDurationMs = completedAt - startedAt;
298
- const remaining = this.highQueue.length + this.lowQueue.length;
326
+ const remainingSemantic = this.semanticQueue.length;
327
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
328
+ const remaining = remainingSemantic + remainingEnrichment;
299
329
  if (batches.length > 0) {
330
+ this.flushCount += 1;
300
331
  try {
301
332
  this.onFlush({
302
333
  batches,
@@ -304,7 +335,11 @@ var PatchCommitScheduler = class {
304
335
  totalAppliedPatches,
305
336
  totalDurationMs,
306
337
  remainingQueueSize: remaining,
338
+ remainingSemanticQueueSize: remainingSemantic,
339
+ remainingEnrichmentQueueSize: remainingEnrichment,
307
340
  queueDepthBefore: initialQueueSize,
341
+ semanticQueueDepthBefore: initialSemanticQueueSize,
342
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
308
343
  flushStartedAt: startedAt,
309
344
  flushCompletedAt: completedAt
310
345
  });
@@ -315,21 +350,27 @@ var PatchCommitScheduler = class {
315
350
  if (remaining > 0 && !this.paused) {
316
351
  this.schedule();
317
352
  }
318
- const coalescingStats = this.computeCoalescingDurationStats();
319
- this.updateAdaptiveBudget(coalescingStats.p95);
320
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
353
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
354
+ if (this.adaptiveBudgeting) {
355
+ this.updateAdaptiveBudget(coalescingStats.p95);
356
+ }
357
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
321
358
  const flushResult = batches.length > 0 ? {
322
359
  batches,
323
360
  totalPatches: totalInputPatches,
324
361
  totalAppliedPatches,
325
362
  totalDurationMs,
326
363
  remainingQueueSize: remaining,
364
+ remainingSemanticQueueSize: remainingSemantic,
365
+ remainingEnrichmentQueueSize: remainingEnrichment,
327
366
  queueDepthBefore: initialQueueSize,
367
+ semanticQueueDepthBefore: initialSemanticQueueSize,
368
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
328
369
  flushStartedAt: startedAt,
329
370
  flushCompletedAt: completedAt,
330
371
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
331
372
  coalescingDurationSampleCount: coalescingStats.sampleCount,
332
- adaptiveBudgetActive: adaptiveState.active,
373
+ adaptiveBudgetActive: adaptiveState?.active,
333
374
  adaptiveBudgetState: adaptiveState
334
375
  } : null;
335
376
  if (flushResult) {
@@ -365,6 +406,9 @@ var PatchCommitScheduler = class {
365
406
  };
366
407
  }
367
408
  updateAdaptiveBudget(p95) {
409
+ if (!this.adaptiveBudgeting) {
410
+ return;
411
+ }
368
412
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
369
413
  if (!this.adaptiveBudgetActive) {
370
414
  this.adaptiveBudgetActive = true;
@@ -438,11 +482,13 @@ var PatchCommitScheduler = class {
438
482
  }
439
483
  restart() {
440
484
  this.cancelScheduled();
441
- this.highQueue = [];
442
- this.lowQueue = [];
485
+ this.semanticQueue = [];
486
+ this.enrichmentHighQueue = [];
487
+ this.enrichmentLowQueue = [];
443
488
  this.flushing = false;
444
489
  this.sequence = 0;
445
490
  this.history = [];
491
+ this.flushCount = 0;
446
492
  this.scheduleToken++;
447
493
  this.paused = false;
448
494
  this.coalescingDurationSamples = [];
@@ -489,6 +535,12 @@ var PatchCommitScheduler = class {
489
535
  if (typeof options.historyLimit !== "undefined") {
490
536
  this.setHistoryLimit(options.historyLimit);
491
537
  }
538
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
539
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
540
+ }
541
+ if (typeof options.adaptiveBudgeting !== "undefined") {
542
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
543
+ }
492
544
  if (typeof options.batch !== "undefined") {
493
545
  const requestedBatch = options.batch;
494
546
  if (requestedBatch === "microtask") {
@@ -1,4 +1,4 @@
1
- import { PatchMetrics, CoalescingMetrics, Patch } from '@stream-mdx/core';
1
+ import { PatchMetrics, PatchKind, CoalescingMetrics, Patch } from '@stream-mdx/core';
2
2
  import { RendererStore } from './store.cjs';
3
3
 
4
4
  type NowFn = () => number;
@@ -21,6 +21,7 @@ interface PatchBatchMeta {
21
21
  metrics?: PatchMetrics;
22
22
  receivedAt?: number;
23
23
  priority?: "high" | "low";
24
+ kind?: PatchKind;
24
25
  }
25
26
  interface PatchBatchInput {
26
27
  patches: Patch[];
@@ -37,6 +38,7 @@ interface PatchFlushBatchResult {
37
38
  receivedAt: number;
38
39
  appliedAt: number;
39
40
  priority: "high" | "low";
41
+ kind: PatchKind;
40
42
  coalescing?: CoalescingMetrics;
41
43
  }
42
44
  interface PatchFlushResult {
@@ -45,7 +47,11 @@ interface PatchFlushResult {
45
47
  totalAppliedPatches: number;
46
48
  totalDurationMs: number;
47
49
  remainingQueueSize: number;
50
+ remainingSemanticQueueSize: number;
51
+ remainingEnrichmentQueueSize: number;
48
52
  queueDepthBefore: number;
53
+ semanticQueueDepthBefore: number;
54
+ enrichmentQueueDepthBefore: number;
49
55
  flushStartedAt: number;
50
56
  flushCompletedAt: number;
51
57
  coalescingDurationP95?: number | null;
@@ -65,6 +71,8 @@ interface PatchCommitSchedulerOptions {
65
71
  urgentQueueThreshold?: number;
66
72
  batch?: "rAF" | "microtask" | "timeout";
67
73
  historyLimit?: number;
74
+ startupMicrotaskFlushes?: number;
75
+ adaptiveBudgeting?: boolean;
68
76
  }
69
77
  declare class PatchCommitScheduler {
70
78
  private readonly store;
@@ -78,8 +86,10 @@ declare class PatchCommitScheduler {
78
86
  private lowPriorityFrameBudgetMs;
79
87
  private maxLowPriorityBatchesPerFlush?;
80
88
  private urgentQueueThreshold;
81
- private highQueue;
82
- private lowQueue;
89
+ private adaptiveBudgeting;
90
+ private semanticQueue;
91
+ private enrichmentHighQueue;
92
+ private enrichmentLowQueue;
83
93
  private scheduled;
84
94
  private flushing;
85
95
  private scheduledHandle;
@@ -95,6 +105,8 @@ declare class PatchCommitScheduler {
95
105
  private adaptiveBudgetActive;
96
106
  private adaptiveHighBatchCap;
97
107
  private adaptiveLowBatchCap;
108
+ private startupMicrotaskFlushes;
109
+ private flushCount;
98
110
  constructor(params: {
99
111
  store: RendererStore;
100
112
  onFlush: (result: PatchFlushResult) => void;
@@ -1,4 +1,4 @@
1
- import { PatchMetrics, CoalescingMetrics, Patch } from '@stream-mdx/core';
1
+ import { PatchMetrics, PatchKind, CoalescingMetrics, Patch } from '@stream-mdx/core';
2
2
  import { RendererStore } from './store.js';
3
3
 
4
4
  type NowFn = () => number;
@@ -21,6 +21,7 @@ interface PatchBatchMeta {
21
21
  metrics?: PatchMetrics;
22
22
  receivedAt?: number;
23
23
  priority?: "high" | "low";
24
+ kind?: PatchKind;
24
25
  }
25
26
  interface PatchBatchInput {
26
27
  patches: Patch[];
@@ -37,6 +38,7 @@ interface PatchFlushBatchResult {
37
38
  receivedAt: number;
38
39
  appliedAt: number;
39
40
  priority: "high" | "low";
41
+ kind: PatchKind;
40
42
  coalescing?: CoalescingMetrics;
41
43
  }
42
44
  interface PatchFlushResult {
@@ -45,7 +47,11 @@ interface PatchFlushResult {
45
47
  totalAppliedPatches: number;
46
48
  totalDurationMs: number;
47
49
  remainingQueueSize: number;
50
+ remainingSemanticQueueSize: number;
51
+ remainingEnrichmentQueueSize: number;
48
52
  queueDepthBefore: number;
53
+ semanticQueueDepthBefore: number;
54
+ enrichmentQueueDepthBefore: number;
49
55
  flushStartedAt: number;
50
56
  flushCompletedAt: number;
51
57
  coalescingDurationP95?: number | null;
@@ -65,6 +71,8 @@ interface PatchCommitSchedulerOptions {
65
71
  urgentQueueThreshold?: number;
66
72
  batch?: "rAF" | "microtask" | "timeout";
67
73
  historyLimit?: number;
74
+ startupMicrotaskFlushes?: number;
75
+ adaptiveBudgeting?: boolean;
68
76
  }
69
77
  declare class PatchCommitScheduler {
70
78
  private readonly store;
@@ -78,8 +86,10 @@ declare class PatchCommitScheduler {
78
86
  private lowPriorityFrameBudgetMs;
79
87
  private maxLowPriorityBatchesPerFlush?;
80
88
  private urgentQueueThreshold;
81
- private highQueue;
82
- private lowQueue;
89
+ private adaptiveBudgeting;
90
+ private semanticQueue;
91
+ private enrichmentHighQueue;
92
+ private enrichmentLowQueue;
83
93
  private scheduled;
84
94
  private flushing;
85
95
  private scheduledHandle;
@@ -95,6 +105,8 @@ declare class PatchCommitScheduler {
95
105
  private adaptiveBudgetActive;
96
106
  private adaptiveHighBatchCap;
97
107
  private adaptiveLowBatchCap;
108
+ private startupMicrotaskFlushes;
109
+ private flushCount;
98
110
  constructor(params: {
99
111
  store: RendererStore;
100
112
  onFlush: (result: PatchFlushResult) => void;
@@ -1,5 +1,8 @@
1
1
  "use client";
2
2
 
3
+ // src/renderer/patch-commit-scheduler.ts
4
+ import { getPatchKind } from "@stream-mdx/core/perf/patch-batching";
5
+
3
6
  // src/renderer/patch-coalescing.ts
4
7
  import {
5
8
  DEFAULT_COALESCE_CONFIG,
@@ -16,6 +19,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
16
19
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
17
20
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
18
21
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
22
+ function shouldSkipCoalescing(patches) {
23
+ return patches.length <= 1;
24
+ }
19
25
  function getDefaultNow() {
20
26
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
21
27
  return () => performance.now();
@@ -42,8 +48,9 @@ function getDefaultCancelRaf() {
42
48
  }
43
49
  var PatchCommitScheduler = class {
44
50
  constructor(params) {
45
- this.highQueue = [];
46
- this.lowQueue = [];
51
+ this.semanticQueue = [];
52
+ this.enrichmentHighQueue = [];
53
+ this.enrichmentLowQueue = [];
47
54
  this.scheduled = false;
48
55
  this.flushing = false;
49
56
  this.scheduledHandle = null;
@@ -55,6 +62,7 @@ var PatchCommitScheduler = class {
55
62
  this.scheduleToken = 0;
56
63
  this.coalescingDurationSamples = [];
57
64
  this.adaptiveBudgetActive = false;
65
+ this.flushCount = 0;
58
66
  this.store = params.store;
59
67
  this.onFlush = params.onFlush;
60
68
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -70,6 +78,8 @@ var PatchCommitScheduler = class {
70
78
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
71
79
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
72
80
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
81
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
82
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
73
83
  const requestedBatch = params.options?.batch;
74
84
  if (requestedBatch === "microtask") {
75
85
  this.batchStrategy = "microtask";
@@ -96,19 +106,23 @@ var PatchCommitScheduler = class {
96
106
  patches: input.patches,
97
107
  meta: input.meta ?? {},
98
108
  receivedAt: input.meta?.receivedAt ?? this.now(),
109
+ kind: input.meta?.kind === "semantic" || input.meta?.kind === "enrichment" ? input.meta.kind : input.patches.some((patch) => getPatchKind(patch) === "semantic") ? "semantic" : "enrichment",
99
110
  priority: input.meta?.priority === "low" ? "low" : "high"
100
111
  };
101
- if (batch.priority === "low") {
102
- this.lowQueue.push(batch);
112
+ if (batch.kind === "semantic") {
113
+ batch.priority = "high";
114
+ this.semanticQueue.push(batch);
115
+ } else if (batch.priority === "low") {
116
+ this.enrichmentLowQueue.push(batch);
103
117
  } else {
104
- this.highQueue.push(batch);
118
+ this.enrichmentHighQueue.push(batch);
105
119
  }
106
120
  if (!this.paused) {
107
121
  this.schedule();
108
122
  }
109
123
  }
110
124
  flushAll() {
111
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
125
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
112
126
  return null;
113
127
  }
114
128
  this.cancelScheduled();
@@ -118,9 +132,11 @@ var PatchCommitScheduler = class {
118
132
  }
119
133
  clear() {
120
134
  this.cancelScheduled();
121
- this.highQueue = [];
122
- this.lowQueue = [];
135
+ this.semanticQueue = [];
136
+ this.enrichmentHighQueue = [];
137
+ this.enrichmentLowQueue = [];
123
138
  this.flushing = false;
139
+ this.flushCount = 0;
124
140
  this.coalescingDurationSamples = [];
125
141
  this.adaptiveBudgetActive = false;
126
142
  this.adaptiveHighBatchCap = void 0;
@@ -128,10 +144,10 @@ var PatchCommitScheduler = class {
128
144
  this.maybeResolveIdle();
129
145
  }
130
146
  getPendingCount() {
131
- return this.highQueue.length + this.lowQueue.length;
147
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
132
148
  }
133
149
  isIdle() {
134
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
150
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
135
151
  }
136
152
  awaitIdle() {
137
153
  if (this.isIdle()) {
@@ -171,7 +187,8 @@ var PatchCommitScheduler = class {
171
187
  this.runFlush(this.frameBudgetMs, false);
172
188
  this.maybeResolveIdle();
173
189
  };
174
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
190
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
191
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
175
192
  this.scheduledViaRaf = false;
176
193
  this.scheduledHandle = token;
177
194
  queueMicrotask(execute);
@@ -201,12 +218,14 @@ var PatchCommitScheduler = class {
201
218
  if (this.paused && !manual) {
202
219
  return null;
203
220
  }
204
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
221
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
205
222
  return null;
206
223
  }
207
224
  this.flushing = true;
208
225
  const startedAt = this.now();
209
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
226
+ const initialSemanticQueueSize = this.semanticQueue.length;
227
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
228
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
210
229
  const batches = [];
211
230
  let totalInputPatches = 0;
212
231
  let totalAppliedPatches = 0;
@@ -221,7 +240,15 @@ var PatchCommitScheduler = class {
221
240
  consumed += 1;
222
241
  const applyStart = this.now();
223
242
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
224
- const { patches: coalesced, metrics: coalescingMetrics } = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
243
+ let coalesced;
244
+ let coalescingMetrics;
245
+ if (shouldSkipCoalescing(next.patches)) {
246
+ coalesced = next.patches;
247
+ } else {
248
+ const result = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
249
+ coalesced = result.patches;
250
+ coalescingMetrics = result.metrics;
251
+ }
225
252
  const touched = this.store.applyPatches(coalesced, {
226
253
  coalesced: true,
227
254
  metrics: coalescingMetrics
@@ -241,6 +268,7 @@ var PatchCommitScheduler = class {
241
268
  receivedAt: next.receivedAt,
242
269
  appliedAt: applyStart,
243
270
  priority: next.priority,
271
+ kind: next.kind,
244
272
  coalescing: coalescingMetrics
245
273
  });
246
274
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -257,27 +285,32 @@ var PatchCommitScheduler = class {
257
285
  queue.splice(0, consumed);
258
286
  }
259
287
  };
260
- const initialHighQueueSize = this.highQueue.length;
288
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
289
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
261
290
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
262
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
263
- processQueue(this.highQueue, highBatchLimit, budgetMs);
291
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
292
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
264
293
  if (manual) {
265
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
266
- } else if (this.lowQueue.length > 0) {
294
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
295
+ } else if (this.enrichmentLowQueue.length > 0) {
267
296
  elapsed = this.now() - startedAt;
268
297
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
269
- if (budgetForLow > 0) {
270
- const initialLowQueueSize = this.lowQueue.length;
298
+ const processedHigherPriorityWork = batches.length > 0;
299
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
300
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
271
301
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
272
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
273
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
302
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
303
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
274
304
  }
275
305
  }
276
306
  this.flushing = false;
277
307
  const completedAt = this.now();
278
308
  const totalDurationMs = completedAt - startedAt;
279
- const remaining = this.highQueue.length + this.lowQueue.length;
309
+ const remainingSemantic = this.semanticQueue.length;
310
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
311
+ const remaining = remainingSemantic + remainingEnrichment;
280
312
  if (batches.length > 0) {
313
+ this.flushCount += 1;
281
314
  try {
282
315
  this.onFlush({
283
316
  batches,
@@ -285,7 +318,11 @@ var PatchCommitScheduler = class {
285
318
  totalAppliedPatches,
286
319
  totalDurationMs,
287
320
  remainingQueueSize: remaining,
321
+ remainingSemanticQueueSize: remainingSemantic,
322
+ remainingEnrichmentQueueSize: remainingEnrichment,
288
323
  queueDepthBefore: initialQueueSize,
324
+ semanticQueueDepthBefore: initialSemanticQueueSize,
325
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
289
326
  flushStartedAt: startedAt,
290
327
  flushCompletedAt: completedAt
291
328
  });
@@ -296,21 +333,27 @@ var PatchCommitScheduler = class {
296
333
  if (remaining > 0 && !this.paused) {
297
334
  this.schedule();
298
335
  }
299
- const coalescingStats = this.computeCoalescingDurationStats();
300
- this.updateAdaptiveBudget(coalescingStats.p95);
301
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
336
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
337
+ if (this.adaptiveBudgeting) {
338
+ this.updateAdaptiveBudget(coalescingStats.p95);
339
+ }
340
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
302
341
  const flushResult = batches.length > 0 ? {
303
342
  batches,
304
343
  totalPatches: totalInputPatches,
305
344
  totalAppliedPatches,
306
345
  totalDurationMs,
307
346
  remainingQueueSize: remaining,
347
+ remainingSemanticQueueSize: remainingSemantic,
348
+ remainingEnrichmentQueueSize: remainingEnrichment,
308
349
  queueDepthBefore: initialQueueSize,
350
+ semanticQueueDepthBefore: initialSemanticQueueSize,
351
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
309
352
  flushStartedAt: startedAt,
310
353
  flushCompletedAt: completedAt,
311
354
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
312
355
  coalescingDurationSampleCount: coalescingStats.sampleCount,
313
- adaptiveBudgetActive: adaptiveState.active,
356
+ adaptiveBudgetActive: adaptiveState?.active,
314
357
  adaptiveBudgetState: adaptiveState
315
358
  } : null;
316
359
  if (flushResult) {
@@ -346,6 +389,9 @@ var PatchCommitScheduler = class {
346
389
  };
347
390
  }
348
391
  updateAdaptiveBudget(p95) {
392
+ if (!this.adaptiveBudgeting) {
393
+ return;
394
+ }
349
395
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
350
396
  if (!this.adaptiveBudgetActive) {
351
397
  this.adaptiveBudgetActive = true;
@@ -419,11 +465,13 @@ var PatchCommitScheduler = class {
419
465
  }
420
466
  restart() {
421
467
  this.cancelScheduled();
422
- this.highQueue = [];
423
- this.lowQueue = [];
468
+ this.semanticQueue = [];
469
+ this.enrichmentHighQueue = [];
470
+ this.enrichmentLowQueue = [];
424
471
  this.flushing = false;
425
472
  this.sequence = 0;
426
473
  this.history = [];
474
+ this.flushCount = 0;
427
475
  this.scheduleToken++;
428
476
  this.paused = false;
429
477
  this.coalescingDurationSamples = [];
@@ -470,6 +518,12 @@ var PatchCommitScheduler = class {
470
518
  if (typeof options.historyLimit !== "undefined") {
471
519
  this.setHistoryLimit(options.historyLimit);
472
520
  }
521
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
522
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
523
+ }
524
+ if (typeof options.adaptiveBudgeting !== "undefined") {
525
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
526
+ }
473
527
  if (typeof options.batch !== "undefined") {
474
528
  const requestedBatch = options.batch;
475
529
  if (requestedBatch === "microtask") {