@microsoft/webui-framework 0.0.19 → 0.0.20

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 CHANGED
@@ -126,6 +126,36 @@ The plugin alone preserves full server state. To emit exact `@observable` and
126
126
  `--projection-manifest`. The manifest tooling is build-only; this runtime
127
127
  package does not depend on esbuild or TypeScript.
128
128
 
129
+ ### Progressive streaming hydration
130
+
131
+ Streaming applications opt into a separate side-effect entry:
132
+
133
+ ```ts
134
+ import '@microsoft/webui-framework/streaming.js';
135
+ import './counter-card.js';
136
+ ```
137
+
138
+ Import it before component registration modules and load the application entry
139
+ early with `<script type="module" async>` in `<head>`. The server must render
140
+ authored `<boundary>` directives through
141
+ `WebUIHandler::render_streaming`. The default
142
+ `@microsoft/webui-framework` entry has no dependency on the coordinator, so
143
+ normal applications pay no streaming bundle or initialization cost.
144
+
145
+ Each committed boundary receives its own ephemeral state object directly during
146
+ activation. The coordinator does not publish that state to
147
+ `window.__webui.state`, and it removes generated checkpoint scaffolding after
148
+ commit. Every commit also emits a `performance.mark()` — `webui:boundary:<id>`,
149
+ `webui:boundary:<id>:update`, or `webui:streaming:terminal` — which needs no
150
+ flag and no listener, so tooling that loads after hydration can still read it.
151
+ Set `window.__WEBUI_STREAMING_DEBUG__ = true` only when tooling needs the live
152
+ `webui:boundary-hydrated` event as well.
153
+
154
+ Set `window.__WEBUI_STREAMING_SLICE_MS__` to a positive millisecond budget to
155
+ make the coordinator yield between boundaries instead of draining its queue in
156
+ one pass. That is for pages where an intermediary coalesces the response into a
157
+ single chunk; it costs total hydration time, so leave it unset otherwise.
158
+
129
159
  ### Property binding lifecycle
130
160
 
131
161
  Property bindings use the `:` prefix to pass values directly to child DOM properties:
@@ -145,18 +175,26 @@ re-render it. An `@observable` written before hydration finishes — in a field
145
175
  initializer, the `constructor`, or before `super.connectedCallback()` — cannot
146
176
  update that DOM, so the write is dropped and the runtime logs a
147
177
  `[WebUI] Hydration mismatch` warning naming the properties. Seed such values in
148
- the SSR state, or assign them after `super.connectedCallback()`. The warning is
178
+ the SSR state, or assign them from `hydratedCallback()`. The warning is
149
179
  development-only and is dead-code-eliminated from production bundles via the
150
180
  `__WEBUI_DEV__` compile-time flag (on by default; `webui-press build` sets it to
151
181
  `false`). See the
152
182
  [Interactivity Guide](https://microsoft.github.io/webui/guide/concepts/interactivity#setting-observable-state-during-setup).
153
183
 
154
- `super.connectedCallback()` is the synchronous hydration boundary for an
155
- authored component. When it returns, bindings, events, and `w-ref` references
156
- are wired. Use a parser-inserted, non-async ES module script or a classic
157
- `defer` script. A blocking classic script must follow every SSR instance it may
158
- upgrade. Descendants must not structurally mutate a containing component's SSR
159
- subtree before it hydrates, because hydration relies on stable compiled paths.
184
+ Override the protected `hydratedCallback()` hook for work that requires the
185
+ component's bindings, events, and `w-ref` references to be ready. It runs
186
+ synchronously exactly once after the first successful ordinary SSR hydration,
187
+ client-created mount, deferred streamed activation, or dormant static-host wake.
188
+ Its once-latch is set before author code runs, so a thrown callback is not
189
+ retried on reconnect.
190
+
191
+ `connectedCallback()` remains a native per-connection lifecycle. On ordinary
192
+ SSR and client-created mounts, `super.connectedCallback()` hydrates
193
+ synchronously, but a streamed `data-ws` root returns while still deferred and
194
+ hydrates only when its boundary commits. Therefore `connectedCallback()` cannot
195
+ be used as a universal post-hydration signal. Descendants must not structurally
196
+ mutate a containing component's SSR subtree before it hydrates, because
197
+ hydration relies on stable compiled paths.
160
198
 
161
199
  ### DOM strategy (`--dom`)
162
200
 
@@ -186,6 +224,7 @@ Base class for framework components.
186
224
  | Member | Purpose |
187
225
  |--------|---------|
188
226
  | `static define(tagName)` | Register the class as a custom element |
227
+ | `protected hydratedCallback()` | Run once after the first successful hydration or client mount |
189
228
  | `$emit(name, detail?)` | Dispatch a bubbling, composed `CustomEvent` |
190
229
  | `$update()` | Force a reactive update (normally called automatically) |
191
230
  | `disconnectedCallback()` | Override for cleanup (global listeners, etc.) |
@@ -360,9 +399,9 @@ resource-constrained devices.
360
399
 
361
400
  5. **Single-pass hydration via path mapping.**
362
401
  SSR DOM is matched to compiled template bindings through
363
- template-parallel traversal (`$resolveSSR`). No marker comments, no
364
- data attributes — just path-based node resolution. The hydration walk
365
- touches each DOM node exactly once.
402
+ template-parallel traversal (`$resolveSSR`). Ordinary buffered hydration
403
+ needs no marker comments or data attributes for binding resolution. The
404
+ hydration walk touches each DOM node exactly once.
366
405
 
367
406
  6. **Keep the framework out of the GC's way.**
368
407
  Fewer JS objects = fewer GC pauses. Binding arrays are pre-built at
@@ -424,7 +463,10 @@ Angular all require a JavaScript runtime on the server. This framework's SSR
424
463
  is driven by data (template metadata + state values), not code. Any language
425
464
  that can read the compiled metadata and produce HTML can serve as the SSR
426
465
  backend. No comment markers or data attributes are needed — the runtime
427
- resolves SSR DOM nodes via template-parallel path traversal.
466
+ resolves ordinary buffered SSR nodes via template-parallel path traversal.
467
+ Progressive streaming uses temporary checkpoint scaffolding only to delay
468
+ activation until a complete region arrives; it removes that scaffolding after
469
+ commit.
428
470
 
429
471
  ### Build → Serve → Hydrate → Update
430
472
 
@@ -490,8 +532,8 @@ When the server renders a component, it emits HTML content (as a declarative
490
532
  shadow root or as light DOM children) along with an inert `#webui-data`
491
533
  JSON payload. The browser parses this DOM before any JavaScript runs.
492
534
  When the component's JS loads and `connectedCallback` fires, the framework
493
- uses compiled template paths to resolve SSR DOM nodes without any marker
494
- comments or data attributes:
535
+ uses compiled template paths to resolve ordinary buffered SSR DOM nodes without
536
+ binding markers:
495
537
 
496
538
  ```mermaid
497
539
  sequenceDiagram
@@ -511,6 +553,7 @@ sequenceDiagram
511
553
  FW->>FW: $resolveSSR() — match SSR nodes via ordinal traversal
512
554
  FW->>FW: $wireEvents() + $wireRefs()
513
555
  FW->>FW: $buildPathIndex(), $ready = true
556
+ FW->>CE: hydratedCallback() (once)
514
557
  Note over FW: DOM is already correct from SSR.<br/>No $update() call needed.
515
558
  ```
516
559
 
@@ -535,6 +578,7 @@ sequenceDiagram
535
578
  FW->>FW: $wireEvents() + $wireRefs()
536
579
  FW->>FW: $buildPathIndex(), $ready = true
537
580
  FW->>FW: $update() — flush initial property values
581
+ FW->>CE: hydratedCallback() (once)
538
582
  ```
539
583
 
540
584
  ---
@@ -734,10 +778,11 @@ stylesheet specifier for a component.
734
778
 
735
779
  ## Path-Based Binding Resolution
736
780
 
737
- Unlike frameworks that use comment markers or data attributes to locate
738
- dynamic content, this framework uses **compiled template paths** — arrays of
781
+ Unlike frameworks that use comment markers or data attributes to locate each
782
+ dynamic binding, this framework uses **compiled template paths** — arrays of
739
783
  child-node indices that describe exactly where each binding lives in the DOM
740
- tree.
784
+ tree. Progressive streaming's temporary boundary markers locate complete
785
+ activation regions, not individual bindings.
741
786
 
742
787
  ### Client-created resolution (`$resolve`)
743
788
 
package/dist/element.d.ts CHANGED
@@ -8,14 +8,10 @@ export declare class WebUIElement extends TemplateElement {
8
8
  $emit(name: string, detail?: unknown): boolean;
9
9
  protected $finalize(instance: TemplateInstance, root: Node, meta: TemplateBlockMeta, resolver: (root: Node, path: TemplateNodePath) => Node | null, scope?: ScopeFrame): void;
10
10
  private $wireEvents;
11
- private $resolveDelegatedEvents;
12
11
  private $wireRoot;
13
- private $addDelegatedEvent;
14
- private $dispatchDelegatedEvent;
15
12
  private $addEvent;
16
13
  private $addCleanup;
17
14
  private $callEventHandler;
18
- private $callEventHandlerWithCurrentTarget;
19
15
  private $resolveEventArgs;
20
16
  private $resolveEventArg;
21
17
  private $wireRefs;
package/dist/element.js CHANGED
@@ -29,32 +29,16 @@ export class WebUIElement extends TemplateElement {
29
29
  const groups = meta.eg;
30
30
  if (!groups)
31
31
  return;
32
- const delegateTarget = this.shadowRoot ?? this;
33
32
  for (let i = 0; i < groups.length; i++) {
34
33
  const [eventName, bindings] = groups[i];
35
- const bucket = this.$resolveDelegatedEvents(root, bindings, resolver, scope);
36
- this.$addDelegatedEvent(instance, delegateTarget, eventName, bucket);
37
- }
38
- }
39
- $resolveDelegatedEvents(root, bindings, resolver, scope) {
40
- const entries = [];
41
- for (let i = 0; i < bindings.length; i++) {
42
- const [handlerName, args, target, usesEvent] = bindings[i];
43
- const el = resolver(root, target);
44
- if (!el || el.nodeType !== 1)
45
- continue;
46
- const method = this[handlerName];
47
- if (typeof method !== 'function')
48
- continue;
49
- entries.push({
50
- target: el,
51
- method: method,
52
- args,
53
- usesEvent: usesEvent === 1,
54
- scope,
55
- });
34
+ for (let j = 0; j < bindings.length; j++) {
35
+ const [handlerName, args, target] = bindings[j];
36
+ const el = resolver(root, target);
37
+ if (el && el.nodeType === 1) {
38
+ this.$addEvent(instance, el, eventName, handlerName, args, scope);
39
+ }
40
+ }
56
41
  }
57
- return entries;
58
42
  }
59
43
  $wireRoot(instance, re) {
60
44
  const target = this.shadowRoot ?? this;
@@ -62,27 +46,6 @@ export class WebUIElement extends TemplateElement {
62
46
  this.$addEvent(instance, target, re[i][0], re[i][1], re[i][2], undefined);
63
47
  }
64
48
  }
65
- $addDelegatedEvent(instance, target, eventName, entries) {
66
- if (entries.length === 0)
67
- return;
68
- const listener = (event) => {
69
- this.$dispatchDelegatedEvent(entries, event);
70
- };
71
- target.addEventListener(eventName, listener);
72
- this.$addCleanup(instance, () => target.removeEventListener(eventName, listener));
73
- }
74
- $dispatchDelegatedEvent(entries, event) {
75
- let current = event.target;
76
- while (current) {
77
- for (let i = 0; i < entries.length; i++) {
78
- const entry = entries[i];
79
- if (entry.target === current) {
80
- this.$callEventHandler(entry.method, entry.args, event, entry.scope, entry.target, entry.usesEvent);
81
- }
82
- }
83
- current = current.parentNode;
84
- }
85
- }
86
49
  $addEvent(instance, target, eventName, handlerName, args, scope) {
87
50
  const method = this[handlerName];
88
51
  if (typeof method !== 'function')
@@ -94,11 +57,7 @@ export class WebUIElement extends TemplateElement {
94
57
  $addCleanup(instance, cleanup) {
95
58
  (instance.cleanups ??= []).push(cleanup);
96
59
  }
97
- $callEventHandler(method, args, event, scope, currentTarget, usesEvent = false) {
98
- if (currentTarget && usesEvent) {
99
- this.$callEventHandlerWithCurrentTarget(method, args, event, scope, currentTarget);
100
- return;
101
- }
60
+ $callEventHandler(method, args, event, scope) {
102
61
  switch (args.length) {
103
62
  case 0:
104
63
  method.call(this);
@@ -119,24 +78,6 @@ export class WebUIElement extends TemplateElement {
119
78
  method.apply(this, this.$resolveEventArgs(args, event, scope));
120
79
  }
121
80
  }
122
- $callEventHandlerWithCurrentTarget(method, args, event, scope, currentTarget) {
123
- const descriptor = Object.getOwnPropertyDescriptor(event, 'currentTarget');
124
- Object.defineProperty(event, 'currentTarget', {
125
- configurable: true,
126
- value: currentTarget,
127
- });
128
- try {
129
- this.$callEventHandler(method, args, event, scope);
130
- }
131
- finally {
132
- if (descriptor) {
133
- Object.defineProperty(event, 'currentTarget', descriptor);
134
- }
135
- else {
136
- delete event.currentTarget;
137
- }
138
- }
139
- }
140
81
  $resolveEventArgs(args, event, scope) {
141
82
  const resolved = [];
142
83
  for (let i = 0; i < args.length; i++) {
@@ -1,2 +1,19 @@
1
1
  export declare function hydrationStart(): void;
2
2
  export declare function hydrationEnd(): void;
3
+ export declare function beginStreamingGate(): void;
4
+ export declare function abortStreamingGate(): void;
5
+ export declare function markBoundaryPending(): void;
6
+ export declare function markBoundaryCommitted(terminal: boolean): void;
7
+ export declare function markLateActivationPending(): void;
8
+ export declare function settleLateActivation(): void;
9
+ export declare function __getLifecycleStateForTests(): {
10
+ pendingCount: number;
11
+ started: boolean;
12
+ completed: boolean;
13
+ streamingGateActive: boolean;
14
+ streamingGateAborted: boolean;
15
+ terminalReached: boolean;
16
+ pendingBoundaries: number;
17
+ pendingLateActivations: number;
18
+ };
19
+ export declare function __resetLifecycleForTests(): void;
package/dist/lifecycle.js CHANGED
@@ -1,6 +1,11 @@
1
1
  let pendingCount = 0;
2
2
  let started = false;
3
3
  let completed = false;
4
+ let streamingGateActive = false;
5
+ let terminalReached = false;
6
+ let streamingGateAborted = false;
7
+ let pendingBoundaries = 0;
8
+ let pendingLateActivations = 0;
4
9
  export function hydrationStart() {
5
10
  if (!started) {
6
11
  performance.mark('webui:hydrate:total:start');
@@ -9,11 +14,69 @@ export function hydrationStart() {
9
14
  pendingCount++;
10
15
  }
11
16
  export function hydrationEnd() {
17
+ if (pendingCount === 0)
18
+ return;
12
19
  pendingCount--;
13
- if (pendingCount <= 0 && !completed) {
14
- completed = true;
20
+ tryComplete();
21
+ }
22
+ export function beginStreamingGate() {
23
+ streamingGateActive = true;
24
+ }
25
+ export function abortStreamingGate() {
26
+ streamingGateAborted = true;
27
+ }
28
+ export function markBoundaryPending() {
29
+ pendingBoundaries++;
30
+ }
31
+ export function markBoundaryCommitted(terminal) {
32
+ if (pendingBoundaries === 0)
33
+ return;
34
+ pendingBoundaries--;
35
+ if (terminal)
36
+ terminalReached = true;
37
+ tryComplete();
38
+ }
39
+ export function markLateActivationPending() {
40
+ pendingLateActivations++;
41
+ }
42
+ export function settleLateActivation() {
43
+ if (pendingLateActivations === 0)
44
+ return;
45
+ pendingLateActivations--;
46
+ tryComplete();
47
+ }
48
+ function tryComplete() {
49
+ if (completed || streamingGateAborted || pendingCount !== 0)
50
+ return;
51
+ if (streamingGateActive &&
52
+ (!terminalReached || pendingBoundaries !== 0 || pendingLateActivations !== 0))
53
+ return;
54
+ completed = true;
55
+ if (started) {
15
56
  performance.mark('webui:hydrate:total:end');
16
57
  performance.measure('webui:hydrate:total', 'webui:hydrate:total:start', 'webui:hydrate:total:end');
17
- window.dispatchEvent(new Event('webui:hydration-complete'));
18
58
  }
59
+ window.dispatchEvent(new Event('webui:hydration-complete'));
60
+ }
61
+ export function __getLifecycleStateForTests() {
62
+ return {
63
+ pendingCount,
64
+ started,
65
+ completed,
66
+ streamingGateActive,
67
+ streamingGateAborted,
68
+ terminalReached,
69
+ pendingBoundaries,
70
+ pendingLateActivations,
71
+ };
72
+ }
73
+ export function __resetLifecycleForTests() {
74
+ pendingCount = 0;
75
+ started = false;
76
+ completed = false;
77
+ streamingGateActive = false;
78
+ streamingGateAborted = false;
79
+ terminalReached = false;
80
+ pendingBoundaries = 0;
81
+ pendingLateActivations = 0;
19
82
  }
@@ -22,6 +22,9 @@ function defineTemplateHost(tag, meta) {
22
22
  $shouldApplySSRBootstrapState() {
23
23
  return false;
24
24
  }
25
+ $shouldActivateOnBoundaryCommit() {
26
+ return false;
27
+ }
25
28
  }
26
29
  StaticTemplateHost.define(tag);
27
30
  }
@@ -58,9 +61,8 @@ export function installTemplateElementRuntime() {
58
61
  return;
59
62
  defineTemplateHosts(detail.templates);
60
63
  });
64
+ defineTemplateHosts();
61
65
  if (document.readyState === 'loading') {
62
66
  document.addEventListener('DOMContentLoaded', () => defineTemplateHosts(), { once: true });
63
- return;
64
67
  }
65
- defineTemplateHosts();
66
68
  }
@@ -0,0 +1,2 @@
1
+ import type { PendingBoundaryUpdates } from './streaming-deferred.js';
2
+ export declare function activateRootsBetween(startMarker: Comment, endMarker: Comment, state: Record<string, unknown> | undefined, updates?: PendingBoundaryUpdates): void;
@@ -0,0 +1,13 @@
1
+ import { abandonDeferredRange, } from './streaming-cleanup.js';
2
+ import { activateDeferredTree, } from './streaming-deferred.js';
3
+ export function activateRootsBetween(startMarker, endMarker, state, updates) {
4
+ const root = startMarker.parentNode;
5
+ if (!root) {
6
+ throw new Error('boundary start marker was detached before activation');
7
+ }
8
+ const failure = activateDeferredTree(startMarker.nextSibling, root, endMarker, state, updates ? { updates, retainedRoots: updates.roots } : undefined);
9
+ if (!failure)
10
+ return;
11
+ abandonDeferredRange(startMarker, endMarker);
12
+ throw new Error(failure);
13
+ }
@@ -0,0 +1,2 @@
1
+ import type { BoundaryBootstrap } from './streaming-protocol.js';
2
+ export declare function applyBoundaryBootstrap(bootstrap: BoundaryBootstrap): void;
@@ -0,0 +1,48 @@
1
+ import { registerTemplateData } from './template.js';
2
+ export function applyBoundaryBootstrap(bootstrap) {
3
+ if (bootstrap.templates)
4
+ registerTemplateData(bootstrap.templates);
5
+ const w = window;
6
+ if (!w.__webui)
7
+ w.__webui = {};
8
+ const keys = Object.keys(bootstrap);
9
+ for (let i = 0; i < keys.length; i++) {
10
+ const key = keys[i];
11
+ if (key === 'templates' || key === 'state')
12
+ continue;
13
+ if (key === 'inventory') {
14
+ w.__webui.inventory = mergeInventory(w.__webui.inventory, bootstrap.inventory);
15
+ continue;
16
+ }
17
+ if (key === 'css' || key === 'styles') {
18
+ appendUniqueStrings(w.__webui, key, bootstrap[key]);
19
+ continue;
20
+ }
21
+ w.__webui[key] = bootstrap[key];
22
+ }
23
+ }
24
+ function mergeInventory(existing, delta) {
25
+ const current = typeof existing === 'string' ? existing : '';
26
+ const next = delta ?? '';
27
+ const length = Math.max(current.length, next.length);
28
+ let merged = '';
29
+ for (let i = 0; i < length; i++) {
30
+ const a = i < current.length ? parseInt(current[i], 16) : 0;
31
+ const b = i < next.length ? parseInt(next[i], 16) : 0;
32
+ merged += (a | b).toString(16);
33
+ }
34
+ return merged;
35
+ }
36
+ function appendUniqueStrings(target, key, delta) {
37
+ if (!delta)
38
+ return;
39
+ const existing = target[key];
40
+ const cumulative = Array.isArray(existing) ? existing : [];
41
+ for (let i = 0; i < delta.length; i++) {
42
+ if (cumulative.indexOf(delta[i]) === -1) {
43
+ cumulative.push(delta[i]);
44
+ }
45
+ }
46
+ if (cumulative !== existing)
47
+ target[key] = cumulative;
48
+ }
@@ -0,0 +1,4 @@
1
+ export declare function abandonDeferredElement(el: Element): void;
2
+ export declare function abandonDeferredDescendants(root: Element): void;
3
+ export declare function abandonDeferredRange(startMarker: Comment, endMarker: Node): void;
4
+ export declare function abandonDeferredDocumentRoots(): void;
@@ -0,0 +1,66 @@
1
+ import { firstNodeWithin, MAX_MARKER_SCAN_NODES, nextWithinRoot, safeRemoveAttribute, streamingErrorMessage, } from './streaming-dom.js';
2
+ import { STREAMED_HOST_ATTR } from './streaming-mode.js';
3
+ const STREAMING_BOUNDARY_ABANDON = Symbol.for('microsoft.webui.boundaryAbandon');
4
+ export function abandonDeferredElement(el) {
5
+ try {
6
+ if (!el.hasAttribute(STREAMED_HOST_ATTR))
7
+ return;
8
+ }
9
+ catch {
10
+ safeRemoveAttribute(el, STREAMED_HOST_ATTR);
11
+ return;
12
+ }
13
+ try {
14
+ const hook = el[STREAMING_BOUNDARY_ABANDON];
15
+ if (typeof hook === 'function')
16
+ hook.call(el);
17
+ }
18
+ catch (error) {
19
+ console.error(`[WebUI] streaming: abandon failed for <${el.tagName?.toLowerCase?.() ?? '?'}>: ${streamingErrorMessage(error)}`);
20
+ }
21
+ finally {
22
+ safeRemoveAttribute(el, STREAMED_HOST_ATTR);
23
+ }
24
+ }
25
+ export function abandonDeferredDescendants(root) {
26
+ abandonDeferredNodes(firstNodeWithin(root), root, null);
27
+ }
28
+ export function abandonDeferredRange(startMarker, endMarker) {
29
+ const root = startMarker.parentNode;
30
+ if (root) {
31
+ abandonDeferredNodes(startMarker.nextSibling, root, endMarker);
32
+ }
33
+ }
34
+ function abandonDeferredNodes(first, root, end) {
35
+ let node = first;
36
+ let visited = 0;
37
+ while (node && node !== end && visited < MAX_MARKER_SCAN_NODES) {
38
+ visited++;
39
+ if (node.nodeType === 1) {
40
+ abandonDeferredElement(node);
41
+ }
42
+ node = nextWithinRoot(node, root);
43
+ }
44
+ }
45
+ export function abandonDeferredDocumentRoots() {
46
+ if (typeof document === 'undefined' ||
47
+ typeof document.getElementsByTagName !== 'function') {
48
+ return;
49
+ }
50
+ const elements = document.getElementsByTagName('*');
51
+ let visited = 0;
52
+ for (let i = 0; i < elements.length && visited < MAX_MARKER_SCAN_NODES; i++) {
53
+ const el = elements[i];
54
+ visited++;
55
+ abandonDeferredElement(el);
56
+ const shadowRoot = el.shadowRoot;
57
+ let node = shadowRoot?.firstChild ?? null;
58
+ while (node && visited < MAX_MARKER_SCAN_NODES) {
59
+ visited++;
60
+ if (node.nodeType === 1) {
61
+ abandonDeferredElement(node);
62
+ }
63
+ node = nextWithinRoot(node, shadowRoot);
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,7 @@
1
+ import { elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests } from './streaming-deferred.js';
2
+ export declare function enqueueStreamingSentinel(sentinel: Element): void;
3
+ export declare function installStreamingTruncationGuard(): void;
4
+ export declare function resetStreamingCoordinatorStateForTests(): void;
5
+ export declare function isStreamingHaltedForTests(): boolean;
6
+ export declare function streamingRetentionStateForTests(): readonly [number, number];
7
+ export { elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, };