@microsoft/webui-framework 0.0.25 → 0.0.27

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 (53) hide show
  1. package/README.md +209 -53
  2. package/dist/component-asset/asset.d.ts +4 -2
  3. package/dist/component-asset/asset.js +19 -3
  4. package/dist/component-asset/generated-manifest.d.ts +1 -0
  5. package/dist/component-asset/generated-manifest.js +32 -0
  6. package/dist/component-asset/loader.d.ts +2 -1
  7. package/dist/component-asset/loader.js +98 -22
  8. package/dist/component-asset/manifest.d.ts +2 -1
  9. package/dist/component-asset.d.ts +1 -1
  10. package/dist/component-asset.js +12 -2
  11. package/dist/element/link-styles.d.ts +20 -0
  12. package/dist/element/link-styles.js +1042 -0
  13. package/dist/element/markers.d.ts +4 -0
  14. package/dist/element/markers.js +60 -5
  15. package/dist/element/styles.d.ts +39 -1
  16. package/dist/element/styles.js +623 -13
  17. package/dist/element/types.d.ts +5 -3
  18. package/dist/element.js +9 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +1 -0
  21. package/dist/interaction-hydration.d.ts +8 -0
  22. package/dist/interaction-hydration.js +167 -0
  23. package/dist/static-host.js +43 -1
  24. package/dist/streaming-activation.d.ts +2 -2
  25. package/dist/streaming-activation.js +4 -2
  26. package/dist/streaming-bootstrap.d.ts +2 -2
  27. package/dist/streaming-bootstrap.js +7 -1
  28. package/dist/streaming-cleanup.d.ts +1 -0
  29. package/dist/streaming-cleanup.js +66 -14
  30. package/dist/streaming-coordinator.d.ts +3 -2
  31. package/dist/streaming-coordinator.js +107 -47
  32. package/dist/streaming-deferred.d.ts +14 -4
  33. package/dist/streaming-deferred.js +200 -87
  34. package/dist/streaming-dom.d.ts +12 -3
  35. package/dist/streaming-dom.js +41 -27
  36. package/dist/streaming-mode.d.ts +5 -0
  37. package/dist/streaming-mode.js +4 -0
  38. package/dist/streaming-protocol.d.ts +19 -3
  39. package/dist/streaming-protocol.js +4 -3
  40. package/dist/streaming-spans.d.ts +9 -0
  41. package/dist/streaming-spans.js +200 -0
  42. package/dist/template-content.d.ts +17 -0
  43. package/dist/template-content.js +106 -0
  44. package/dist/template-element.d.ts +17 -2
  45. package/dist/template-element.js +493 -177
  46. package/dist/template-events.d.ts +7 -0
  47. package/dist/template-events.js +7 -1
  48. package/dist/template-types.d.ts +1 -2
  49. package/dist/template.d.ts +7 -1
  50. package/dist/template.js +79 -16
  51. package/package.json +9 -3
  52. package/dist/component-asset/resources.d.ts +0 -4
  53. package/dist/component-asset/resources.js +0 -83
@@ -2,10 +2,11 @@ import { abortStreamingGate, markBoundaryCommitted, markBoundaryPending, } from
2
2
  import { activateRootsBetween, } from './streaming-activation.js';
3
3
  import { applyBoundaryBootstrap } from './streaming-bootstrap.js';
4
4
  import { abandonDeferredDocumentRoots, abandonDeferredRange, } from './streaming-cleanup.js';
5
- import { abandonPendingWaiters, configureStreamingFailureHandler, elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, resetDeferredActivationForTests, } from './streaming-deferred.js';
6
- import { findBoundaryScript, findEndMarkerByPrefix, findStartMarkerByPrefix, removeBoundaryScaffolding, resolveBoundaryRange, streamingErrorMessage, } from './streaming-dom.js';
7
- import { parseBoundaryEnvelope, RECORD_KIND_STATE_UPDATE, RECORD_KIND_TERMINAL, RECORD_KIND_UPDATABLE_CHECKPOINT, } from './streaming-protocol.js';
5
+ import { abandonPendingWaiters, configureStreamingFailureHandler, elementHasPendingStateForTests, pendingBarrierRootCountForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, resetDeferredActivationForTests, } from './streaming-deferred.js';
6
+ import { findBoundaryScript, findRangeEndMarkerByPrefix, findRangeStartMarkerByPrefix, markerlessRecordViolation, removeBoundaryScaffolding, resolveBoundaryRange, resolveSpanRange, streamingErrorMessage, } from './streaming-dom.js';
7
+ import { parseBoundaryEnvelope, RECORD_KIND_FINAL_CHECKPOINT, RECORD_KIND_SPAN_COMPLETION, RECORD_KIND_STATE_UPDATE, RECORD_KIND_TERMINAL, RECORD_KIND_UPDATABLE_CHECKPOINT, } from './streaming-protocol.js';
8
8
  import { applyStateUpdate } from './streaming-state.js';
9
+ import { abandonOpenSpans, completeSpan, hasOpenSpans, openSpanCountForTests, prepareSpanCompletion, registerEnclosingSpans, spanHostFor, } from './streaming-spans.js';
9
10
  const MAX_QUEUED_BOUNDARIES = 512;
10
11
  const MAX_UPDATABLE_BOUNDARIES = 128;
11
12
  const MAX_RETAINED_UPDATE_ROOTS = 50_000;
@@ -13,6 +14,7 @@ const BOUNDARY_HYDRATED_EVENT = 'webui:boundary-hydrated';
13
14
  const BOUNDARY_MARK_PREFIX = 'webui:boundary:';
14
15
  const UPDATE_MARK_SUFFIX = ':update';
15
16
  const TERMINAL_MARK = 'webui:streaming:terminal';
17
+ const SPAN_MARK_PREFIX = 'webui:span:';
16
18
  const perf = globalThis.performance;
17
19
  const queue = [];
18
20
  let queueHead = 0;
@@ -20,7 +22,7 @@ let pumpScheduled = false;
20
22
  let slicedDrainActive = false;
21
23
  let halted = false;
22
24
  let nextExpectedRecordSequence = 0;
23
- let nextExpectedBoundaryId = 0;
25
+ let nextExpectedBoundaryInstanceId = 0;
24
26
  let terminalCommitted = false;
25
27
  let pendingTerminalSequence = null;
26
28
  let terminalValidationScheduled = false;
@@ -106,6 +108,7 @@ function fail(reason) {
106
108
  console.error(`[WebUI] streaming hydration halted: ${reason}.`);
107
109
  abortStreamingGate();
108
110
  abandonPendingWaiters();
111
+ abandonOpenSpans();
109
112
  clearUpdatableBoundaries();
110
113
  settlePendingTerminal(false);
111
114
  for (let i = queueHead; i < queue.length; i++) {
@@ -124,9 +127,9 @@ function discardRejectedBoundary(sentinel) {
124
127
  let endMarker = null;
125
128
  let startMarker = null;
126
129
  if (scriptEl) {
127
- endMarker = findEndMarkerByPrefix(scriptEl);
130
+ endMarker = findRangeEndMarkerByPrefix(scriptEl);
128
131
  if (endMarker)
129
- startMarker = findStartMarkerByPrefix(endMarker);
132
+ startMarker = findRangeStartMarkerByPrefix(endMarker);
130
133
  }
131
134
  if (startMarker && endMarker) {
132
135
  abandonDeferredRange(startMarker, endMarker);
@@ -153,7 +156,22 @@ function processSentinel(sentinel) {
153
156
  failBoundary(sentinel, `expected streaming record sequence ${nextExpectedRecordSequence}, received ${sequence}`);
154
157
  return;
155
158
  }
156
- if (kind === RECORD_KIND_STATE_UPDATE) {
159
+ if (kind === RECORD_KIND_STATE_UPDATE || kind === RECORD_KIND_TERMINAL) {
160
+ const terminal = kind === RECORD_KIND_TERMINAL;
161
+ const violation = markerlessRecordViolation(scriptEl, terminal ? 'terminal' : 'state update');
162
+ if (violation) {
163
+ failBoundary(sentinel, violation);
164
+ return;
165
+ }
166
+ if (terminal) {
167
+ if (hasOpenSpans()) {
168
+ failBoundary(sentinel, 'terminal record arrived before every component span completed');
169
+ return;
170
+ }
171
+ nextExpectedRecordSequence++;
172
+ commitTerminal(sequence, sentinel, scriptEl);
173
+ return;
174
+ }
157
175
  const boundary = updatableBoundaries.get(target);
158
176
  if (!boundary) {
159
177
  failBoundary(sentinel, `state update targets boundary ${target}, which is not committed as updatable`);
@@ -163,71 +181,105 @@ function processSentinel(sentinel) {
163
181
  commitStateUpdate(boundary, payload, target, sequence, sentinel, scriptEl);
164
182
  return;
165
183
  }
166
- if (kind === RECORD_KIND_TERMINAL) {
167
- const resolved = resolveBoundaryRange(scriptEl, 0, true);
184
+ if (kind === RECORD_KIND_SPAN_COMPLETION) {
185
+ const resolved = resolveSpanRange(scriptEl, target);
168
186
  if (!resolved.ok) {
169
- failBoundary(sentinel, resolved.reason);
187
+ failRangeResolution(sentinel, scriptEl, resolved);
170
188
  return;
171
189
  }
172
190
  nextExpectedRecordSequence++;
173
- commitTerminal(sequence, sentinel, scriptEl);
191
+ commitRecord(true, payload, resolved.range, sequence, target, false, sentinel, scriptEl);
174
192
  return;
175
193
  }
176
- if (target !== nextExpectedBoundaryId) {
177
- failBoundary(sentinel, `expected boundary ID ${nextExpectedBoundaryId}, received ${target}`);
194
+ if (kind !== RECORD_KIND_FINAL_CHECKPOINT &&
195
+ kind !== RECORD_KIND_UPDATABLE_CHECKPOINT) {
196
+ failBoundary(sentinel, `unsupported streaming record kind ${kind}`);
178
197
  return;
179
198
  }
180
- const resolved = resolveBoundaryRange(scriptEl, target, false);
199
+ if (target !== nextExpectedBoundaryInstanceId) {
200
+ failBoundary(sentinel, `expected boundary instance ${nextExpectedBoundaryInstanceId}, received ${target}`);
201
+ return;
202
+ }
203
+ const resolved = resolveBoundaryRange(scriptEl, target);
181
204
  if (!resolved.ok) {
182
- if (resolved.truncated) {
183
- if (resolved.start) {
184
- abandonDeferredRange(resolved.start, scriptEl);
185
- }
186
- removeBoundaryScaffolding(sentinel, scriptEl, resolved.start, null);
187
- fail(resolved.reason);
188
- }
189
- else {
190
- failBoundary(sentinel, resolved.reason);
191
- }
205
+ failRangeResolution(sentinel, scriptEl, resolved);
192
206
  return;
193
207
  }
194
208
  nextExpectedRecordSequence++;
195
- nextExpectedBoundaryId++;
196
- commitCheckpoint(payload, resolved.range, sequence, target, kind === RECORD_KIND_UPDATABLE_CHECKPOINT, sentinel, scriptEl);
209
+ nextExpectedBoundaryInstanceId++;
210
+ commitRecord(false, payload, resolved.range, sequence, target, kind === RECORD_KIND_UPDATABLE_CHECKPOINT, sentinel, scriptEl);
197
211
  }
198
- function commitCheckpoint(bootstrap, range, sequence, target, updatable, sentinel, scriptEl) {
212
+ function commitRecord(span, payload, range, sequence, target, updatable, sentinel, scriptEl) {
199
213
  markBoundaryPending();
200
214
  let committed = false;
201
215
  try {
202
- applyBoundaryBootstrap(bootstrap);
203
- if (range.start && range.end) {
204
- const boundary = updatable
205
- ? { roots: [], retained: 0, pendingRoots: 0 }
206
- : undefined;
207
- activateRootsBetween(range.start, range.end, bootstrap.state, boundary);
208
- if (boundary)
209
- retainUpdatableBoundary(target, boundary);
216
+ if (span) {
217
+ hydrateSpanCompletion(payload, range, target);
210
218
  }
211
- else if (updatable) {
212
- retainUpdatableBoundary(target, {
213
- roots: [],
214
- retained: 0,
215
- pendingRoots: 0,
216
- });
219
+ else {
220
+ hydrateCheckpoint(payload, range, target, updatable);
217
221
  }
218
222
  committed = true;
219
223
  }
220
224
  catch (error) {
221
- fail(`error committing boundary ${sequence}: ${streamingErrorMessage(error)}`);
225
+ const detail = streamingErrorMessage(error);
226
+ fail(span
227
+ ? `error completing span ${target}: ${detail}`
228
+ : `error committing boundary ${sequence}: ${detail}`);
222
229
  }
223
230
  finally {
224
231
  removeBoundaryScaffolding(sentinel, scriptEl, range.start, range.end);
225
232
  if (committed) {
226
- notifyCommit(`${BOUNDARY_MARK_PREFIX}${target}`, sequence, 'checkpoint');
233
+ notifyCommit(`${span ? SPAN_MARK_PREFIX : BOUNDARY_MARK_PREFIX}${target}`, sequence, span ? 'span' : 'checkpoint');
227
234
  }
228
235
  markBoundaryCommitted(false);
229
236
  }
230
237
  }
238
+ function hydrateCheckpoint(bootstrap, range, target, updatable) {
239
+ let bypass;
240
+ const enclosing = bootstrap.enclosingSpanInstanceId;
241
+ if (enclosing !== undefined && range.start?.parentNode) {
242
+ const invalid = registerEnclosingSpans(range.start.parentNode, enclosing);
243
+ if (invalid)
244
+ throw new Error(invalid);
245
+ const host = spanHostFor(enclosing);
246
+ if (host)
247
+ bypass = { id: String(enclosing), host };
248
+ }
249
+ applyBoundaryBootstrap(bootstrap);
250
+ if (range.start && range.end) {
251
+ const boundary = updatable
252
+ ? newUpdatableBoundary()
253
+ : undefined;
254
+ activateRootsBetween(range.start, range.end, bootstrap.state, boundary, bypass);
255
+ if (boundary)
256
+ retainUpdatableBoundary(target, boundary);
257
+ }
258
+ else if (updatable) {
259
+ retainUpdatableBoundary(target, newUpdatableBoundary());
260
+ }
261
+ }
262
+ function newUpdatableBoundary() {
263
+ return { roots: [], active: true, retained: 0, pendingRoots: 0 };
264
+ }
265
+ function hydrateSpanCompletion(payload, range, target) {
266
+ const invalid = prepareSpanCompletion(target, range);
267
+ if (invalid)
268
+ throw new Error(invalid);
269
+ applyBoundaryBootstrap(payload);
270
+ activateRootsBetween(range.start, range.end, payload.state);
271
+ completeSpan(target);
272
+ }
273
+ function failRangeResolution(sentinel, scriptEl, resolved) {
274
+ if (!resolved.truncated) {
275
+ failBoundary(sentinel, resolved.reason);
276
+ return;
277
+ }
278
+ if (resolved.start)
279
+ abandonDeferredRange(resolved.start, scriptEl);
280
+ removeBoundaryScaffolding(sentinel, scriptEl, resolved.start, null);
281
+ fail(resolved.reason);
282
+ }
231
283
  function retainUpdatableBoundary(target, boundary) {
232
284
  if (updatableBoundaries.size >= MAX_UPDATABLE_BOUNDARIES) {
233
285
  throw new Error(`updatable boundary count exceeds ${MAX_UPDATABLE_BOUNDARIES}`);
@@ -269,10 +321,17 @@ function commitTerminal(sequence, sentinel, scriptEl) {
269
321
  removeBoundaryScaffolding(sentinel, scriptEl, null, null);
270
322
  terminalCommitted = true;
271
323
  pendingTerminalSequence = sequence;
272
- clearUpdatableBoundaries();
324
+ clearUpdatableBoundaries(true);
273
325
  scheduleTerminalValidation();
274
326
  }
275
- function clearUpdatableBoundaries() {
327
+ function clearUpdatableBoundaries(preservePendingPatches = false) {
328
+ for (const boundary of updatableBoundaries.values()) {
329
+ boundary.active = false;
330
+ boundary.roots.length = 0;
331
+ if (!preservePendingPatches || boundary.pendingRoots === 0) {
332
+ boundary.patch = undefined;
333
+ }
334
+ }
276
335
  updatableBoundaries.clear();
277
336
  retainedUpdateRoots = 0;
278
337
  }
@@ -341,6 +400,7 @@ function onDomContentLoaded(generation) {
341
400
  }
342
401
  export function resetStreamingCoordinatorStateForTests() {
343
402
  resetDeferredActivationForTests();
403
+ abandonOpenSpans();
344
404
  settlePendingTerminal(false);
345
405
  clearUpdatableBoundaries();
346
406
  coordinatorGeneration++;
@@ -350,7 +410,7 @@ export function resetStreamingCoordinatorStateForTests() {
350
410
  slicedDrainActive = false;
351
411
  halted = false;
352
412
  nextExpectedRecordSequence = 0;
353
- nextExpectedBoundaryId = 0;
413
+ nextExpectedBoundaryInstanceId = 0;
354
414
  terminalCommitted = false;
355
415
  pendingTerminalSequence = null;
356
416
  terminalValidationScheduled = false;
@@ -361,4 +421,4 @@ export function isStreamingHaltedForTests() {
361
421
  export function streamingRetentionStateForTests() {
362
422
  return [updatableBoundaries.size, retainedUpdateRoots];
363
423
  }
364
- export { elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, };
424
+ export { elementHasPendingStateForTests, openSpanCountForTests, pendingBarrierRootCountForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, };
@@ -1,16 +1,25 @@
1
- export declare const ACTIVATION_MISSING_TEMPLATE = 3;
2
- export declare const ELEMENT_IGNORED = 0;
3
- export declare const ELEMENT_DEFERRED = 4;
4
- export declare const ELEMENT_LIMIT_FAILURE = 5;
1
+ export declare const ELEMENT_IGNORED = 10;
2
+ export declare const ELEMENT_DEFERRED = 11;
3
+ export declare const ELEMENT_LIMIT_FAILURE = 12;
4
+ export declare const ELEMENT_ACTIVATED_FROM_PENDING = 13;
5
+ export declare const ELEMENT_BARRIER_LIMIT_FAILURE = 14;
6
+ export declare const ELEMENT_INVALID_OUTCOME = 15;
5
7
  export declare const MAX_PENDING_UNDEFINED_ROOTS = 50000;
8
+ export declare const MAX_PENDING_BARRIER_ROOTS = 50000;
9
+ export interface SpanBypass {
10
+ readonly id: string;
11
+ readonly host: Element;
12
+ }
6
13
  export interface PendingBoundaryUpdates {
7
14
  readonly roots: Element[];
15
+ active: boolean;
8
16
  retained: number;
9
17
  pendingRoots: number;
10
18
  patch?: Record<string, unknown>;
11
19
  }
12
20
  export interface DeferredActivationOptions {
13
21
  updates?: PendingBoundaryUpdates;
22
+ bypass?: SpanBypass;
14
23
  countRetention?: boolean;
15
24
  }
16
25
  export declare function configureStreamingFailureHandler(handler: (reason: string) => void): void;
@@ -19,4 +28,5 @@ export declare function abandonPendingWaiters(): void;
19
28
  export declare function resetDeferredActivationForTests(): void;
20
29
  export declare function pendingTagWaiterCountForTests(): number;
21
30
  export declare function pendingUndefinedRootCountForTests(): number;
31
+ export declare function pendingBarrierRootCountForTests(): number;
22
32
  export declare function elementHasPendingStateForTests(el: Element): boolean;