@solidjs/web 2.0.0-beta.27 → 2.0.0-beta.29

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 (43) hide show
  1. package/dist/dev.cjs +40 -2
  2. package/dist/dev.js +39 -4
  3. package/dist/server.cjs +113 -37
  4. package/dist/server.js +112 -39
  5. package/dist/web.cjs +40 -2
  6. package/dist/web.js +39 -4
  7. package/frames/dist/client.cjs +370 -209
  8. package/frames/dist/client.dev.cjs +370 -210
  9. package/frames/dist/client.dev.js +371 -211
  10. package/frames/dist/client.js +371 -210
  11. package/frames/dist/server.cjs +373 -74
  12. package/frames/dist/server.js +373 -75
  13. package/package.json +3 -3
  14. package/server-functions/dist/client.cjs +100 -3
  15. package/server-functions/dist/client.js +92 -4
  16. package/server-functions/dist/server.cjs +85 -17
  17. package/server-functions/dist/server.js +83 -17
  18. package/types/client.d.ts +15 -0
  19. package/types/core.d.ts +1 -1
  20. package/types/frames/client.d.ts +8 -5
  21. package/types/frames/frame-client.d.ts +17 -0
  22. package/types/frames/frame-sink.d.ts +29 -6
  23. package/types/frames/frame-transport.d.ts +76 -12
  24. package/types/frames/server.d.ts +29 -1
  25. package/types/index.d.ts +74 -0
  26. package/types/server-functions/client.d.ts +25 -0
  27. package/types/server-functions/server.d.ts +105 -18
  28. package/types/server-functions/shared.d.ts +22 -0
  29. package/types/server-mock.d.ts +6 -2
  30. package/types/server.d.ts +39 -1
  31. package/types-cjs/client.d.cts +15 -0
  32. package/types-cjs/core.d.cts +1 -1
  33. package/types-cjs/frames/client.d.cts +8 -5
  34. package/types-cjs/frames/frame-client.d.cts +17 -0
  35. package/types-cjs/frames/frame-sink.d.cts +29 -6
  36. package/types-cjs/frames/frame-transport.d.cts +76 -12
  37. package/types-cjs/frames/server.d.cts +29 -1
  38. package/types-cjs/index.d.cts +74 -0
  39. package/types-cjs/server-functions/client.d.cts +25 -0
  40. package/types-cjs/server-functions/server.d.cts +105 -18
  41. package/types-cjs/server-functions/shared.d.cts +22 -0
  42. package/types-cjs/server-mock.d.cts +6 -2
  43. package/types-cjs/server.d.cts +39 -1
@@ -1,7 +1,7 @@
1
- import { getOwner, onCleanup, createMemo, runWithOwner, createLoadingBoundary, sharedConfig, createOwner } from 'solid-js';
1
+ import { getOwner, onCleanup, createMemo, runWithOwner, createLoadingBoundary, createOwner, sharedConfig } from 'solid-js';
2
2
  import { insert } from '@solidjs/web';
3
- import { configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
4
- import { fromCrossJSON, Feature } from 'seroval';
3
+ import { createPlugin, fromCrossJSON, Feature } from 'seroval';
4
+ import { ChunkReader, frameAddress, SINGLE_FLIGHT_HEADER, deserializeStream, getServerFunctionsCodec, createChunk, getFlightDataConsumer, ERROR_HEADER, REVALIDATE_HEADER, configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
5
5
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
6
6
 
7
7
  const ELEMENT_NODE = 1;
@@ -82,6 +82,7 @@ function chunkToRecords(chunk) {
82
82
  function createFrameHost(options = {}) {
83
83
  const frames = new Map();
84
84
  const pending = new Map();
85
+ const retained = new Map();
85
86
  const deliver = (frame, chunk) => {
86
87
  if (chunk.type === "data") {
87
88
  options.applyData && options.applyData(chunk);
@@ -107,6 +108,14 @@ function createFrameHost(options = {}) {
107
108
  }
108
109
  return;
109
110
  }
111
+ const kept = retained.get(id);
112
+ if (kept) {
113
+ retained.delete(id);
114
+ frame.apply({
115
+ version: kept.version,
116
+ r: kept.records
117
+ });
118
+ }
110
119
  const buffered = pending.get(id);
111
120
  if (buffered) {
112
121
  pending.delete(id);
@@ -130,6 +139,12 @@ function createFrameHost(options = {}) {
130
139
  const set = frames.get(id);
131
140
  if (set && frame) set.delete(frame);
132
141
  if (!set || !frame || !set.size) {
142
+ if (frame) {
143
+ const kept = frame.snapshot && frame.snapshot();
144
+ if (kept) retained.set(id, kept);
145
+ } else {
146
+ retained.delete(id);
147
+ }
133
148
  frames.delete(id);
134
149
  pending.delete(id);
135
150
  }
@@ -342,7 +357,7 @@ class FrameImpl {
342
357
  #syncSlots(root) {
343
358
  if (!this.#slots && !this.#options.resolveSlot) return;
344
359
  const found = new Map();
345
- if (root) collectSlots(root, found);else this.#collectSlots(found);
360
+ if (root) collectSlots(root.firstChild, null, found);else this.#collectSlots(found);
346
361
  for (const [occurrence, start] of found) {
347
362
  const callback = this.#resolveSlot(propOf(occurrence));
348
363
  if (!callback) continue;
@@ -381,12 +396,28 @@ class FrameImpl {
381
396
  }
382
397
  #invokeSlot(occurrence, callback, record, start, adopted) {
383
398
  const cleanups = this.#slotCleanups.get(occurrence) ?? [];
399
+ let existing = [];
400
+ let end = null;
401
+ if (start) {
402
+ const endData = slotEnd(occurrence);
403
+ let n = start.nextSibling;
404
+ while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
405
+ existing.push(n);
406
+ n = n.nextSibling;
407
+ }
408
+ end = n;
409
+ }
384
410
  const ctx = {
385
411
  frame: this.#options.claimScope ?? this.#options.id,
386
412
  key: occurrence,
387
413
  adopted: !!adopted,
414
+ invoked: !!(record && record.kind === "slot"),
388
415
  onCleanup: fn => cleanups.push(fn),
389
- existing: start ? rangeInterior(start, slotEnd(occurrence)) : []
416
+ existing,
417
+ range: end ? {
418
+ start,
419
+ end
420
+ } : undefined
390
421
  };
391
422
  const props = record && record.kind === "slot" ? this.#resolveArgs(occurrence, record.args) : {};
392
423
  const regions = this.#slotRegions.get(occurrence);
@@ -396,7 +427,8 @@ class FrameImpl {
396
427
  if (!(argKey in props)) props[argKey] = entry.element;
397
428
  }
398
429
  }
399
- const content = callback(props, ctx);
430
+ const scope = this.#options.ownerScope;
431
+ const content = scope ? scope(() => callback(props, ctx)) : callback(props, ctx);
400
432
  this.#slotArgs.set(occurrence, record);
401
433
  if (cleanups.length) this.#slotCleanups.set(occurrence, cleanups);
402
434
  if (content == null) return null;
@@ -427,7 +459,6 @@ class FrameImpl {
427
459
  } of regions.values()) frame?.dispose();
428
460
  this.#slotRegions.delete(key);
429
461
  }
430
- this.#slotArgs.delete(key);
431
462
  }
432
463
  #runSlotCleanups(key) {
433
464
  const cleanups = this.#slotCleanups.get(key);
@@ -540,36 +571,35 @@ class FrameImpl {
540
571
  }
541
572
  }
542
573
  #collectSlots(found) {
543
- let n = this.#firstContent();
544
- const end = this.#end;
545
- while (n && n !== end) {
546
- const id = slotStartId(n);
547
- if (id !== null) {
548
- devCheckRange(n, id);
549
- if (!found.has(id)) found.set(id, n);
550
- n = afterRange(n, id);
551
- continue;
552
- }
553
- if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, found);
554
- n = n.nextSibling;
555
- }
574
+ collectSlots(this.#firstContent(), this.#end, found);
556
575
  }
557
576
  #findPlaceholder(name) {
558
- const id = placeholderId(name);
559
- let n = this.#firstContent();
560
- const end = this.#end;
561
- while (n && n !== end) {
562
- if (isPlaceholderStart(n, id)) return n;
563
- if (n.nodeType === ELEMENT_NODE) {
564
- const found = findPlaceholder(n, id);
565
- if (found) return found;
566
- }
567
- n = n.nextSibling;
577
+ return findPlaceholder(this.#firstContent(), this.#end, placeholderId(name));
578
+ }
579
+ snapshot() {
580
+ if (this.#disposed) return null;
581
+ const records = {
582
+ ...this.#store
583
+ };
584
+ if (!records[""] && this.#element && this.#hasContent) {
585
+ records[""] = {
586
+ kind: "html",
587
+ value: this.#element.innerHTML
588
+ };
568
589
  }
569
- return null;
590
+ if (!records[""] && this.#version === undefined) return null;
591
+ return {
592
+ version: this.#version ?? 0,
593
+ records
594
+ };
570
595
  }
571
596
  dispose() {
572
597
  if (this.#disposed) return;
598
+ const {
599
+ host,
600
+ id
601
+ } = this.#options;
602
+ if (host && id !== undefined) host.unregister(id, this);
573
603
  this.#disposed = true;
574
604
  for (const key of [...this.#slotCleanups.keys()]) this.#runSlotCleanups(key);
575
605
  for (const key of this.#mountedSlots) this.#removeSlotRecord(key);
@@ -580,11 +610,6 @@ class FrameImpl {
580
610
  }
581
611
  this.#slotRegions.clear();
582
612
  this.#mountedSlots.clear();
583
- const {
584
- host,
585
- id
586
- } = this.#options;
587
- if (host && id !== undefined) host.unregister(id, this);
588
613
  }
589
614
  #applyRoot(html) {
590
615
  const fragment = parseFragment(html);
@@ -596,7 +621,9 @@ class FrameImpl {
596
621
  this.#hasContent = true;
597
622
  } else {
598
623
  const claim = claimHandlers() ? this.#claimTree : null;
599
- reconcileChildren(parent, fragment, this.#start, this.#end, claim);
624
+ const ranges = new Map();
625
+ this.#collectSlots(ranges);
626
+ reconcileChildren(parent, fragment, this.#start, this.#end, claim, ranges);
600
627
  }
601
628
  }
602
629
  #clearContent() {
@@ -791,21 +818,19 @@ function rangeClose(start, id) {
791
818
  }
792
819
  return null;
793
820
  }
794
- function findPlaceholder(root, id) {
795
- let n = root.firstChild;
796
- while (n) {
821
+ function findPlaceholder(n, end, id) {
822
+ while (n && n !== end) {
797
823
  if (isPlaceholderStart(n, id)) return n;
798
824
  if (n.nodeType === ELEMENT_NODE) {
799
- const found = findPlaceholder(n, id);
825
+ const found = findPlaceholder(n.firstChild, null, id);
800
826
  if (found) return found;
801
827
  }
802
828
  n = n.nextSibling;
803
829
  }
804
830
  return null;
805
831
  }
806
- function collectSlots(root, out) {
807
- let n = root.firstChild;
808
- while (n) {
832
+ function collectSlots(n, end, out) {
833
+ while (n && n !== end) {
809
834
  const id = slotStartId(n);
810
835
  if (id !== null) {
811
836
  devCheckRange(n, id);
@@ -813,7 +838,7 @@ function collectSlots(root, out) {
813
838
  n = afterRange(n, id);
814
839
  continue;
815
840
  }
816
- if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, out);
841
+ if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n.firstChild, null, out);
817
842
  n = n.nextSibling;
818
843
  }
819
844
  }
@@ -850,15 +875,6 @@ function argsEquivalent(a, b) {
850
875
  }
851
876
  return true;
852
877
  }
853
- function rangeInterior(start, endData) {
854
- const nodes = [];
855
- let n = start.nextSibling;
856
- while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
857
- nodes.push(n);
858
- n = n.nextSibling;
859
- }
860
- return nodes;
861
- }
862
878
  function slotStartId(node) {
863
879
  if (node.nodeType !== COMMENT_NODE) return null;
864
880
  const m = SLOT_START.exec(node.data);
@@ -904,11 +920,11 @@ function morphAttributes(oldEl, newEl, claim) {
904
920
  }
905
921
  if (claim && (reclaim || changed && oldEl.matches(CLAIMED_ELEMENTS))) claim(oldEl, true);
906
922
  }
907
- function morphNode(oldNode, newNode, claim) {
923
+ function morphNode(oldNode, newNode, claim, ranges) {
908
924
  if (oldNode.nodeType === ELEMENT_NODE) {
909
925
  if (oldNode.hasAttribute("data-preserve")) return;
910
926
  morphAttributes(oldNode, newNode, claim);
911
- reconcileChildren(oldNode, newNode, null, null, claim);
927
+ reconcileChildren(oldNode, newNode, null, null, claim, ranges);
912
928
  } else if (oldNode.data !== newNode.data) {
913
929
  oldNode.data = newNode.data;
914
930
  }
@@ -931,9 +947,6 @@ function devCheckRange(start, id) {
931
947
  }
932
948
  console.error(`Frame slot range "${id}" is missing its end marker (<!--${end}-->) among its start ` + `marker's siblings. Slots after it in this content cannot be discovered. Likely causes: ` + `invalid HTML nesting split the range during parsing (e.g. a block element inside <p>), ` + `or an HTML-rewriting layer (CDN/minifier/translator) removed or moved the comment — ` + `serve frame documents with Cache-Control: no-transform.`, start);
933
949
  }
934
- function skipRange(start, id) {
935
- return afterRange(start, id);
936
- }
937
950
  function findRangeStart(from, id, bound) {
938
951
  const target = `slot:${id}:start`;
939
952
  let n = from;
@@ -971,7 +984,7 @@ function adoptRange(parent, start, id, ref, claim) {
971
984
  }
972
985
  return after;
973
986
  }
974
- function reconcileChildren(parent, source, boundStart = null, boundEnd = null, claim = null) {
987
+ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, claim = null, ranges = null) {
975
988
  let oldChild = boundStart ? boundStart.nextSibling : parent.firstChild;
976
989
  let newChild = source.firstChild;
977
990
  while (newChild) {
@@ -981,12 +994,18 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
981
994
  if (pid !== null) {
982
995
  if (old && slotStartId(old) === pid) {
983
996
  oldChild = afterRange(old, pid);
984
- newChild = skipRange(newChild, pid);
997
+ newChild = afterRange(newChild, pid);
985
998
  } else {
986
- const existing = findRangeStart(old, pid, boundEnd);
999
+ const displaced = ranges && ranges.get(pid);
1000
+ const existing = displaced || findRangeStart(old, pid, boundEnd);
987
1001
  if (existing) {
988
- moveRangeBefore(parent, existing, pid, old ?? boundEnd);
989
- newChild = skipRange(newChild, pid);
1002
+ if (ranges) ranges.delete(pid);
1003
+ if (existing.nodeType === 11 ) {
1004
+ parent.insertBefore(existing, old ?? boundEnd);
1005
+ } else {
1006
+ moveRangeBefore(parent, existing, pid, old ?? boundEnd);
1007
+ }
1008
+ newChild = afterRange(newChild, pid);
990
1009
  } else {
991
1010
  newChild = adoptRange(parent, newChild, pid, old ?? boundEnd, claim);
992
1011
  }
@@ -1006,7 +1025,7 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1006
1025
  continue;
1007
1026
  }
1008
1027
  if (compatible(old, newChild)) {
1009
- morphNode(old, newChild, claim);
1028
+ morphNode(old, newChild, claim, ranges);
1010
1029
  oldChild = old.nextSibling;
1011
1030
  newChild = nextNew;
1012
1031
  continue;
@@ -1019,7 +1038,7 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1019
1038
  }
1020
1039
  if (ahead && ahead !== boundEnd) {
1021
1040
  parent.insertBefore(ahead, old);
1022
- morphNode(ahead, newChild, claim);
1041
+ morphNode(ahead, newChild, claim, ranges);
1023
1042
  newChild = nextNew;
1024
1043
  continue;
1025
1044
  }
@@ -1030,113 +1049,29 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1030
1049
  }
1031
1050
  while (oldChild && oldChild !== boundEnd) {
1032
1051
  const next = oldChild.nextSibling;
1052
+ const pid = ranges ? slotStartId(oldChild) : null;
1053
+ if (pid !== null && ranges.get(pid) === oldChild) {
1054
+ const frag = document.createDocumentFragment();
1055
+ const after = stashRange(frag, oldChild, pid);
1056
+ ranges.set(pid, frag);
1057
+ oldChild = after;
1058
+ continue;
1059
+ }
1033
1060
  parent.removeChild(oldChild);
1034
1061
  oldChild = next;
1035
1062
  }
1036
1063
  }
1037
-
1038
- Feature.AggregateError | Feature.BigIntTypedArray;
1039
- const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
1040
- CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
1041
- FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
1042
- function resolveSerializerPlugins(customPlugins) {
1043
- return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
1044
- }
1045
- const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
1046
- const JSON_CODEC_DEPTH_LIMIT = 64;
1047
- function resolveCodecOptions({
1048
- plugins,
1049
- disabledFeatures,
1050
- depthLimit
1051
- } = {}) {
1052
- return {
1053
- plugins: resolveSerializerPlugins(plugins),
1054
- disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
1055
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
1056
- };
1057
- }
1058
- function createJSONDeserializer(options) {
1059
- const refs = new Map();
1060
- const resolved = resolveCodecOptions(options);
1061
- return function deserializeJSONChunk(node) {
1062
- return fromCrossJSON(node, {
1063
- refs,
1064
- ...resolved
1065
- });
1066
- };
1067
- }
1068
- function createJSONDataTable(options) {
1069
- const deserialize = createJSONDeserializer(options);
1070
- const table = new Map();
1071
- return {
1072
- apply(record) {
1073
- const value = deserialize(record.node);
1074
- if (record.initial) table.set(record.key, value);
1075
- },
1076
- get(key) {
1077
- return table.get(key);
1078
- },
1079
- resolve(ref) {
1080
- return table.get(ref.$ref);
1081
- }
1082
- };
1083
- }
1084
-
1085
- class ChunkReader {
1086
- constructor(stream) {
1087
- this.reader = stream.getReader();
1088
- this.buffer = new Uint8Array(0);
1089
- this.done = false;
1090
- }
1091
- async readChunk() {
1092
- const chunk = await this.reader.read();
1093
- if (!chunk.done) {
1094
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
1095
- newBuffer.set(this.buffer);
1096
- newBuffer.set(chunk.value, this.buffer.length);
1097
- this.buffer = newBuffer;
1098
- } else {
1099
- this.done = true;
1100
- }
1101
- }
1102
- async next() {
1103
- while (this.buffer.length < 12) {
1104
- if (this.done) {
1105
- if (this.buffer.length === 0) return {
1106
- done: true,
1107
- value: undefined
1108
- };
1109
- throw new Error("Malformed server function stream.");
1110
- }
1111
- await this.readChunk();
1112
- }
1113
- const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
1114
- const bytes = Number.parseInt(head, 16);
1115
- if (Number.isNaN(bytes)) {
1116
- throw new Error("Malformed server function stream.");
1117
- }
1118
- while (bytes > this.buffer.length - 12) {
1119
- if (this.done) {
1120
- throw new Error("Malformed server function stream.");
1121
- }
1122
- await this.readChunk();
1123
- }
1124
- const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
1125
- this.buffer = this.buffer.subarray(12 + bytes);
1126
- return {
1127
- done: false,
1128
- value: partial
1129
- };
1130
- }
1131
- async drain(interpret) {
1132
- while (true) {
1133
- const result = await this.next();
1134
- if (result.done) {
1135
- break;
1136
- }
1137
- interpret(result.value);
1138
- }
1064
+ function stashRange(frag, start, id) {
1065
+ const end = slotEnd(id);
1066
+ let n = start;
1067
+ while (n) {
1068
+ const next = n.nextSibling;
1069
+ const isEnd = n.nodeType === COMMENT_NODE && n.data === end;
1070
+ frag.appendChild(n);
1071
+ if (isEnd) return next;
1072
+ n = next;
1139
1073
  }
1074
+ return null;
1140
1075
  }
1141
1076
 
1142
1077
  const FRAME_STREAM_HEADER = "X-Frame-Stream";
@@ -1147,55 +1082,133 @@ async function applyFrameResponse(response, host, options = {}) {
1147
1082
  const rootId = response.headers.get(FRAME_STREAM_HEADER) ?? "";
1148
1083
  const as = options.as;
1149
1084
  const version = options.version;
1085
+ const perFrame = typeof version === "function" ? new Map() : null;
1150
1086
  const reader = new ChunkReader(response.body);
1151
1087
  let result = await reader.next();
1152
1088
  while (!result.done) {
1153
1089
  const chunk = JSON.parse(result.value);
1154
- if (as !== undefined && chunk.id === rootId) chunk.id = as;
1155
- if (version !== undefined) chunk.version = version;
1156
- host.apply(chunk);
1090
+ if (chunk.type === "outcome") {
1091
+ if (options.onOutcome) options.onOutcome(chunk.payload);
1092
+ } else {
1093
+ if (as !== undefined && chunk.id === rootId) chunk.id = as;else if (options.route) chunk.id = options.route(chunk.id);
1094
+ if (perFrame) {
1095
+ let v = perFrame.get(chunk.id);
1096
+ if (v === undefined) perFrame.set(chunk.id, v = version(chunk.id));
1097
+ chunk.version = v;
1098
+ } else if (version !== undefined) chunk.version = version;
1099
+ host.apply(chunk);
1100
+ }
1157
1101
  result = await reader.next();
1158
1102
  }
1159
1103
  return as !== undefined ? as : rootId;
1160
1104
  }
1105
+ const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1106
+ const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1107
+ let resolveServerComponent;
1108
+ function parseServerComponent(value, ctx) {
1109
+ return {
1110
+ id: ctx.parse(value[SERVER_COMPONENT]),
1111
+ address: ctx.parse(value[SERVER_COMPONENT_ADDRESS])
1112
+ };
1113
+ }
1114
+ const ServerComponentPlugin = /*#__PURE__*/createPlugin({
1115
+ tag: "dom-expressions/server-component",
1116
+ test(value) {
1117
+ return typeof value === "function" && SERVER_COMPONENT in value;
1118
+ },
1119
+ parse: {
1120
+ sync: parseServerComponent,
1121
+ async async(value, ctx) {
1122
+ return {
1123
+ id: await ctx.parse(value[SERVER_COMPONENT]),
1124
+ address: await ctx.parse(value[SERVER_COMPONENT_ADDRESS])
1125
+ };
1126
+ },
1127
+ stream: parseServerComponent
1128
+ },
1129
+ serialize(node, ctx) {
1130
+ return "self._$SC.r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1131
+ },
1132
+ deserialize(node, ctx) {
1133
+ const id = ctx.deserialize(node.id);
1134
+ const address = ctx.deserialize(node.address);
1135
+ if (address !== undefined && resolveServerComponent) {
1136
+ return resolveServerComponent(id, address);
1137
+ }
1138
+ return globalThis._$SC.r(id);
1139
+ }
1140
+ });
1141
+ function flightCodec(codec) {
1142
+ const plugins = codec && codec.plugins || [];
1143
+ for (const plugin of plugins) {
1144
+ if (plugin && plugin.tag === "dom-expressions/server-component") return codec;
1145
+ }
1146
+ return {
1147
+ ...codec,
1148
+ plugins: [...plugins, ServerComponentPlugin]
1149
+ };
1150
+ }
1161
1151
  function createServerComponentHandler({
1162
1152
  host,
1163
1153
  component,
1164
- capture,
1165
1154
  onStream,
1166
1155
  documentComponent,
1167
- intercept
1156
+ intercept,
1157
+ consumer = getFlightDataConsumer,
1158
+ codec = getServerFunctionsCodec
1168
1159
  }) {
1169
- const byContext = new WeakMap();
1170
- const byFunction = new Map();
1171
- let boundary = 0;
1160
+ const byAddress = new Map();
1161
+ const boundaryFor = address => {
1162
+ let entry = byAddress.get(address);
1163
+ if (!entry) {
1164
+ entry = {
1165
+ frameId: address,
1166
+ component: component(address)
1167
+ };
1168
+ byAddress.set(address, entry);
1169
+ }
1170
+ return entry;
1171
+ };
1172
+ const resolveAddress = (id, address) => boundaryFor(address).component;
1173
+ resolveServerComponent = resolveAddress;
1174
+ const versions = new Map();
1175
+ const bump = frameId => {
1176
+ const version = (versions.get(frameId) || 0) + 1;
1177
+ versions.set(frameId, version);
1178
+ return version;
1179
+ };
1172
1180
  return {
1173
- capture,
1174
- intercept,
1181
+ intercept: intercept && (info => {
1182
+ const hit = intercept(info);
1183
+ if (hit !== undefined) {
1184
+ const address = frameAddress(info.id, info.args);
1185
+ byAddress.set(address, {
1186
+ frameId: info.id,
1187
+ component: hit
1188
+ });
1189
+ }
1190
+ return hit;
1191
+ }),
1175
1192
  handle(response, ctx) {
1176
1193
  if (!isFrameStreamResponse(response)) return undefined;
1177
- const key = ctx.context;
1178
- const keyed = key !== null && typeof key === "object";
1179
- let entry = keyed ? byContext.get(key) : byFunction.get(ctx.id);
1194
+ const address = frameAddress(ctx.id, ctx.args);
1195
+ let entry = byAddress.get(address);
1180
1196
  if (!entry) {
1181
1197
  const adopted = documentComponent && documentComponent(ctx.id);
1182
1198
  if (adopted) {
1183
1199
  entry = {
1184
1200
  frameId: ctx.id,
1185
- version: 0,
1186
1201
  component: adopted
1187
1202
  };
1203
+ byAddress.set(address, entry);
1188
1204
  } else {
1189
- const frameId = `sc:${boundary++}`;
1190
- entry = {
1191
- frameId,
1192
- version: 0,
1193
- component: component(frameId)
1194
- };
1205
+ entry = boundaryFor(address);
1195
1206
  }
1196
- keyed ? byContext.set(key, entry) : byFunction.set(ctx.id, entry);
1197
1207
  }
1198
- const version = ++entry.version;
1208
+ if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
1209
+ return applyFlightResponse(response, entry);
1210
+ }
1211
+ const version = bump(entry.frameId);
1199
1212
  if (onStream) onStream(entry.frameId, version, response);
1200
1213
  applyFrameResponse(response, host, {
1201
1214
  as: entry.frameId,
@@ -1209,6 +1222,101 @@ function createServerComponentHandler({
1209
1222
  }
1210
1223
  }));
1211
1224
  return entry.component;
1225
+ },
1226
+ showing(address, functionId, component) {
1227
+ if (!byAddress.has(address)) {
1228
+ byAddress.set(address, {
1229
+ frameId: functionId,
1230
+ component,
1231
+ address
1232
+ });
1233
+ }
1234
+ }
1235
+ };
1236
+ async function applyFlightResponse(response, entry) {
1237
+ const rootId = response.headers.get(FRAME_STREAM_HEADER) ?? "";
1238
+ const as = rootId ? entry.frameId : undefined;
1239
+ let feed;
1240
+ const source = new ReadableStream({
1241
+ start(controller) {
1242
+ feed = controller;
1243
+ }
1244
+ });
1245
+ const payload = deserializeStream(new Response(source), flightCodec(codec()));
1246
+ let carried = false;
1247
+ await applyFrameResponse(response, host, {
1248
+ as,
1249
+ route: id => {
1250
+ const local = byAddress.get(id);
1251
+ return local ? local.frameId : id;
1252
+ },
1253
+ version: frameId => {
1254
+ const version = bump(frameId);
1255
+ if (onStream) onStream(frameId, version, response);
1256
+ return version;
1257
+ },
1258
+ onOutcome: text => {
1259
+ carried = true;
1260
+ feed.enqueue(createChunk(text));
1261
+ }
1262
+ });
1263
+ feed.close();
1264
+ if (!carried) throw new Error("Single-flight frame response carried no outcome");
1265
+ const envelope = await payload;
1266
+ const deliver = consumer();
1267
+ if (deliver) await deliver(envelope.data, {
1268
+ response
1269
+ });
1270
+ if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
1271
+ throw envelope.value;
1272
+ }
1273
+ return rootId ? entry.component : envelope.value;
1274
+ }
1275
+ }
1276
+
1277
+ Feature.AggregateError | Feature.BigIntTypedArray;
1278
+ const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
1279
+ CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
1280
+ FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
1281
+ function resolveSerializerPlugins(customPlugins) {
1282
+ return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
1283
+ }
1284
+ const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
1285
+ const JSON_CODEC_DEPTH_LIMIT = 64;
1286
+ function resolveCodecOptions({
1287
+ plugins,
1288
+ disabledFeatures,
1289
+ depthLimit
1290
+ } = {}) {
1291
+ return {
1292
+ plugins: resolveSerializerPlugins(plugins),
1293
+ disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
1294
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
1295
+ };
1296
+ }
1297
+ function createJSONDeserializer(options) {
1298
+ const refs = new Map();
1299
+ const resolved = resolveCodecOptions(options);
1300
+ return function deserializeJSONChunk(node) {
1301
+ return fromCrossJSON(node, {
1302
+ refs,
1303
+ ...resolved
1304
+ });
1305
+ };
1306
+ }
1307
+ function createJSONDataTable(options) {
1308
+ const deserialize = createJSONDeserializer(options);
1309
+ const table = new Map();
1310
+ return {
1311
+ apply(record) {
1312
+ const value = deserialize(record.node);
1313
+ if (record.initial) table.set(record.key, value);
1314
+ },
1315
+ get(key) {
1316
+ return table.get(key);
1317
+ },
1318
+ resolve(ref) {
1319
+ return table.get(ref.$ref);
1212
1320
  }
1213
1321
  };
1214
1322
  }
@@ -1262,8 +1370,10 @@ function claimRender(prefix, existing, render) {
1262
1370
  if (!registry.size) return render();
1263
1371
  const prevRegistry = sc.registry;
1264
1372
  const prevHydrating = sc.hydrating;
1373
+ const prevClaimRoots = sc.claimRoots;
1265
1374
  sc.registry = registry;
1266
1375
  sc.hydrating = true;
1376
+ sc.claimRoots = existing;
1267
1377
  try {
1268
1378
  return runWithOwner(createOwner({
1269
1379
  id: prefix
@@ -1271,13 +1381,28 @@ function claimRender(prefix, existing, render) {
1271
1381
  } finally {
1272
1382
  sc.registry = prevRegistry;
1273
1383
  sc.hydrating = prevHydrating;
1384
+ sc.claimRoots = prevClaimRoots;
1385
+ }
1386
+ }
1387
+ function isReactiveContent(value) {
1388
+ if (typeof value === "function") return true;
1389
+ if (Array.isArray(value)) {
1390
+ for (const v of value) if (isReactiveContent(v)) return true;
1274
1391
  }
1392
+ return false;
1275
1393
  }
1276
1394
  function slotsFor(props) {
1395
+ const bindings = new Map();
1277
1396
  return new Proxy({}, {
1278
1397
  get(_, prop) {
1279
1398
  if (typeof prop !== "string" || !(prop in props)) return undefined;
1280
1399
  return (slotProps, ctx) => {
1400
+ const key = ctx && ctx.key;
1401
+ const prev = key !== undefined && bindings.get(key);
1402
+ if (prev) {
1403
+ bindings.delete(key);
1404
+ prev.dispose();
1405
+ }
1281
1406
  const settle = out => {
1282
1407
  const existing = ctx && ctx.existing || [];
1283
1408
  if (existing.length) {
@@ -1287,14 +1412,37 @@ function slotsFor(props) {
1287
1412
  }
1288
1413
  return out;
1289
1414
  };
1290
- const render = () => {
1415
+ const evaluate = () => {
1291
1416
  const v = props[prop];
1292
- return settle(normalizeSlotContent(typeof v === "function" ? v(slotProps) : v));
1417
+ return typeof v === "function" && ctx && ctx.invoked ? v(slotProps) : v;
1293
1418
  };
1294
- if (ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length) {
1295
- return claimRender(`sc-${ctx.frame}-${ctx.key}-`, ctx.existing, render);
1419
+ const adopted = !!(ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length);
1420
+ const prefix = ctx && ctx.frame ? `sc-${ctx.frame}-${ctx.key}-` : "";
1421
+ const value = adopted ? claimRender(prefix, ctx.existing, evaluate) : evaluate();
1422
+ if (!isReactiveContent(value)) {
1423
+ return settle(normalizeSlotContent(value));
1296
1424
  }
1297
- return render();
1425
+ if (ctx && ctx.range) {
1426
+ let claim = adopted;
1427
+ const source = value;
1428
+ const accessor = () => {
1429
+ if (claim) {
1430
+ claim = false;
1431
+ return claimRender(prefix, ctx.existing, () => typeof source === "function" ? source() : source);
1432
+ }
1433
+ return typeof source === "function" ? source() : source;
1434
+ };
1435
+ const owner = createOwner();
1436
+ bindings.set(key, owner);
1437
+ ctx.onCleanup(() => {
1438
+ if (bindings.get(key) === owner) bindings.delete(key);
1439
+ owner.dispose();
1440
+ });
1441
+ const end = ctx.range.end;
1442
+ runWithOwner(owner, () => insert(end.parentNode, accessor, end, [...ctx.existing]));
1443
+ return undefined;
1444
+ }
1445
+ return settle(normalizeSlotContent(value));
1298
1446
  };
1299
1447
  }
1300
1448
  });
@@ -1302,6 +1450,9 @@ function slotsFor(props) {
1302
1450
  function revealSeam(owner) {
1303
1451
  return seam => runWithOwner(owner, () => insert(seam.before.parentNode, createLoadingBoundary(() => seam.content(), () => seam.fallback), seam.before));
1304
1452
  }
1453
+ function boundaryScope(owner) {
1454
+ return fn => getOwner() ? fn() : runWithOwner(owner, fn);
1455
+ }
1305
1456
  function boundaryComponent(host, id) {
1306
1457
  return props => {
1307
1458
  const owner = getOwner();
@@ -1312,7 +1463,7 @@ function boundaryComponent(host, id) {
1312
1463
  host,
1313
1464
  id,
1314
1465
  slots: slotsFor(props),
1315
- ownerScope: fn => runWithOwner(owner, fn),
1466
+ ownerScope: boundaryScope(owner),
1316
1467
  reveal: revealSeam(owner)
1317
1468
  });
1318
1469
  onCleanup(dispose);
@@ -1406,7 +1557,7 @@ function adoptBoundary(host, id, el, props) {
1406
1557
  host,
1407
1558
  id,
1408
1559
  slots: slotsFor(props),
1409
- ownerScope: fn => runWithOwner(owner, fn),
1560
+ ownerScope: boundaryScope(owner),
1410
1561
  reveal: revealSeam(owner)
1411
1562
  });
1412
1563
  onCleanup(() => frame.dispose());
@@ -1417,27 +1568,36 @@ function installServerComponents(host = getFrameHost()) {
1417
1568
  if (!g._$SC) {
1418
1569
  g._$SC = {
1419
1570
  c: {},
1420
- r(i) {
1571
+ a: {},
1572
+ r(i, a) {
1573
+ if (a) {
1574
+ g._$SC.a[a] = i;
1575
+ g._$SC.reg && g._$SC.reg(a, i);
1576
+ }
1421
1577
  return g._$SC.c[i] || (g._$SC.c[i] = p => g._$SC.impl(i, p));
1422
1578
  }
1423
1579
  };
1424
1580
  }
1425
1581
  g._$SC.impl = (id, props) => documentBoundary(host, id, props);
1426
1582
  installRevealHook();
1583
+ const handler = createServerComponentHandler({
1584
+ host,
1585
+ component: frameId => boundaryComponent(host, frameId),
1586
+ onStream: frameId => beginStream(frameId),
1587
+ documentComponent: functionId => claimedBoundaries.has(functionId) ? undefined : g._$SC.c[functionId],
1588
+ intercept: ({
1589
+ id
1590
+ }) => {
1591
+ if (claimedBoundaries.has(id) || !findBoundaryElement(id)) return undefined;
1592
+ return g._$SC.r(id);
1593
+ }
1594
+ });
1595
+ const showing = (address, id) => handler.showing(address, id, g._$SC.r(id));
1596
+ const records = g._$SC.a || (g._$SC.a = {});
1597
+ for (const address in records) showing(address, records[address]);
1598
+ g._$SC.reg = showing;
1427
1599
  configureServerFunctionsClient({
1428
- responseHandler: createServerComponentHandler({
1429
- host,
1430
- capture: () => getOwner() ?? undefined,
1431
- component: frameId => boundaryComponent(host, frameId),
1432
- onStream: frameId => beginStream(frameId),
1433
- documentComponent: functionId => g._$SC.c[functionId],
1434
- intercept: ({
1435
- id
1436
- }) => {
1437
- if (claimedBoundaries.has(id) || !findBoundaryElement(id)) return undefined;
1438
- return g._$SC.r(id);
1439
- }
1440
- })
1600
+ responseHandler: handler
1441
1601
  });
1442
1602
  }
1443
1603