@solidjs/web 2.0.0-beta.28 → 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 +90 -31
  4. package/dist/server.js +89 -33
  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 +351 -69
  12. package/frames/dist/server.js +351 -70
  13. package/package.json +3 -3
  14. package/server-functions/dist/client.cjs +87 -3
  15. package/server-functions/dist/client.js +80 -4
  16. package/server-functions/dist/server.cjs +52 -17
  17. package/server-functions/dist/server.js +51 -17
  18. package/types/client.d.ts +15 -0
  19. package/types/core.d.ts +1 -1
  20. package/types/frames/client.d.ts +7 -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 +1 -1
  25. package/types/index.d.ts +74 -0
  26. package/types/server-functions/client.d.ts +24 -0
  27. package/types/server-functions/server.d.ts +75 -16
  28. package/types/server-functions/shared.d.ts +9 -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 +7 -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 +1 -1
  38. package/types-cjs/index.d.cts +74 -0
  39. package/types-cjs/server-functions/client.d.cts +24 -0
  40. package/types-cjs/server-functions/server.d.cts +75 -16
  41. package/types-cjs/server-functions/shared.d.cts +9 -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,35 +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
- if (!found.has(id)) found.set(id, n);
549
- n = afterRange(n, id);
550
- continue;
551
- }
552
- if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, found);
553
- n = n.nextSibling;
554
- }
574
+ collectSlots(this.#firstContent(), this.#end, found);
555
575
  }
556
576
  #findPlaceholder(name) {
557
- const id = placeholderId(name);
558
- let n = this.#firstContent();
559
- const end = this.#end;
560
- while (n && n !== end) {
561
- if (isPlaceholderStart(n, id)) return n;
562
- if (n.nodeType === ELEMENT_NODE) {
563
- const found = findPlaceholder(n, id);
564
- if (found) return found;
565
- }
566
- 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
+ };
567
589
  }
568
- return null;
590
+ if (!records[""] && this.#version === undefined) return null;
591
+ return {
592
+ version: this.#version ?? 0,
593
+ records
594
+ };
569
595
  }
570
596
  dispose() {
571
597
  if (this.#disposed) return;
598
+ const {
599
+ host,
600
+ id
601
+ } = this.#options;
602
+ if (host && id !== undefined) host.unregister(id, this);
572
603
  this.#disposed = true;
573
604
  for (const key of [...this.#slotCleanups.keys()]) this.#runSlotCleanups(key);
574
605
  for (const key of this.#mountedSlots) this.#removeSlotRecord(key);
@@ -579,11 +610,6 @@ class FrameImpl {
579
610
  }
580
611
  this.#slotRegions.clear();
581
612
  this.#mountedSlots.clear();
582
- const {
583
- host,
584
- id
585
- } = this.#options;
586
- if (host && id !== undefined) host.unregister(id, this);
587
613
  }
588
614
  #applyRoot(html) {
589
615
  const fragment = parseFragment(html);
@@ -595,7 +621,9 @@ class FrameImpl {
595
621
  this.#hasContent = true;
596
622
  } else {
597
623
  const claim = claimHandlers() ? this.#claimTree : null;
598
- 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);
599
627
  }
600
628
  }
601
629
  #clearContent() {
@@ -787,28 +815,26 @@ function rangeClose(start, id) {
787
815
  }
788
816
  return null;
789
817
  }
790
- function findPlaceholder(root, id) {
791
- let n = root.firstChild;
792
- while (n) {
818
+ function findPlaceholder(n, end, id) {
819
+ while (n && n !== end) {
793
820
  if (isPlaceholderStart(n, id)) return n;
794
821
  if (n.nodeType === ELEMENT_NODE) {
795
- const found = findPlaceholder(n, id);
822
+ const found = findPlaceholder(n.firstChild, null, id);
796
823
  if (found) return found;
797
824
  }
798
825
  n = n.nextSibling;
799
826
  }
800
827
  return null;
801
828
  }
802
- function collectSlots(root, out) {
803
- let n = root.firstChild;
804
- while (n) {
829
+ function collectSlots(n, end, out) {
830
+ while (n && n !== end) {
805
831
  const id = slotStartId(n);
806
832
  if (id !== null) {
807
833
  if (!out.has(id)) out.set(id, n);
808
834
  n = afterRange(n, id);
809
835
  continue;
810
836
  }
811
- if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, out);
837
+ if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n.firstChild, null, out);
812
838
  n = n.nextSibling;
813
839
  }
814
840
  }
@@ -845,15 +871,6 @@ function argsEquivalent(a, b) {
845
871
  }
846
872
  return true;
847
873
  }
848
- function rangeInterior(start, endData) {
849
- const nodes = [];
850
- let n = start.nextSibling;
851
- while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
852
- nodes.push(n);
853
- n = n.nextSibling;
854
- }
855
- return nodes;
856
- }
857
874
  function slotStartId(node) {
858
875
  if (node.nodeType !== COMMENT_NODE) return null;
859
876
  const m = SLOT_START.exec(node.data);
@@ -899,11 +916,11 @@ function morphAttributes(oldEl, newEl, claim) {
899
916
  }
900
917
  if (claim && (reclaim || changed && oldEl.matches(CLAIMED_ELEMENTS))) claim(oldEl, true);
901
918
  }
902
- function morphNode(oldNode, newNode, claim) {
919
+ function morphNode(oldNode, newNode, claim, ranges) {
903
920
  if (oldNode.nodeType === ELEMENT_NODE) {
904
921
  if (oldNode.hasAttribute("data-preserve")) return;
905
922
  morphAttributes(oldNode, newNode, claim);
906
- reconcileChildren(oldNode, newNode, null, null, claim);
923
+ reconcileChildren(oldNode, newNode, null, null, claim, ranges);
907
924
  } else if (oldNode.data !== newNode.data) {
908
925
  oldNode.data = newNode.data;
909
926
  }
@@ -917,9 +934,6 @@ function afterRange(start, id) {
917
934
  }
918
935
  return null;
919
936
  }
920
- function skipRange(start, id) {
921
- return afterRange(start, id);
922
- }
923
937
  function findRangeStart(from, id, bound) {
924
938
  const target = `slot:${id}:start`;
925
939
  let n = from;
@@ -957,7 +971,7 @@ function adoptRange(parent, start, id, ref, claim) {
957
971
  }
958
972
  return after;
959
973
  }
960
- function reconcileChildren(parent, source, boundStart = null, boundEnd = null, claim = null) {
974
+ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, claim = null, ranges = null) {
961
975
  let oldChild = boundStart ? boundStart.nextSibling : parent.firstChild;
962
976
  let newChild = source.firstChild;
963
977
  while (newChild) {
@@ -967,12 +981,18 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
967
981
  if (pid !== null) {
968
982
  if (old && slotStartId(old) === pid) {
969
983
  oldChild = afterRange(old, pid);
970
- newChild = skipRange(newChild, pid);
984
+ newChild = afterRange(newChild, pid);
971
985
  } else {
972
- const existing = findRangeStart(old, pid, boundEnd);
986
+ const displaced = ranges && ranges.get(pid);
987
+ const existing = displaced || findRangeStart(old, pid, boundEnd);
973
988
  if (existing) {
974
- moveRangeBefore(parent, existing, pid, old ?? boundEnd);
975
- newChild = skipRange(newChild, pid);
989
+ if (ranges) ranges.delete(pid);
990
+ if (existing.nodeType === 11 ) {
991
+ parent.insertBefore(existing, old ?? boundEnd);
992
+ } else {
993
+ moveRangeBefore(parent, existing, pid, old ?? boundEnd);
994
+ }
995
+ newChild = afterRange(newChild, pid);
976
996
  } else {
977
997
  newChild = adoptRange(parent, newChild, pid, old ?? boundEnd, claim);
978
998
  }
@@ -992,7 +1012,7 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
992
1012
  continue;
993
1013
  }
994
1014
  if (compatible(old, newChild)) {
995
- morphNode(old, newChild, claim);
1015
+ morphNode(old, newChild, claim, ranges);
996
1016
  oldChild = old.nextSibling;
997
1017
  newChild = nextNew;
998
1018
  continue;
@@ -1005,7 +1025,7 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1005
1025
  }
1006
1026
  if (ahead && ahead !== boundEnd) {
1007
1027
  parent.insertBefore(ahead, old);
1008
- morphNode(ahead, newChild, claim);
1028
+ morphNode(ahead, newChild, claim, ranges);
1009
1029
  newChild = nextNew;
1010
1030
  continue;
1011
1031
  }
@@ -1016,113 +1036,29 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1016
1036
  }
1017
1037
  while (oldChild && oldChild !== boundEnd) {
1018
1038
  const next = oldChild.nextSibling;
1039
+ const pid = ranges ? slotStartId(oldChild) : null;
1040
+ if (pid !== null && ranges.get(pid) === oldChild) {
1041
+ const frag = document.createDocumentFragment();
1042
+ const after = stashRange(frag, oldChild, pid);
1043
+ ranges.set(pid, frag);
1044
+ oldChild = after;
1045
+ continue;
1046
+ }
1019
1047
  parent.removeChild(oldChild);
1020
1048
  oldChild = next;
1021
1049
  }
1022
1050
  }
1023
-
1024
- Feature.AggregateError | Feature.BigIntTypedArray;
1025
- const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
1026
- CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
1027
- FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
1028
- function resolveSerializerPlugins(customPlugins) {
1029
- return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
1030
- }
1031
- const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
1032
- const JSON_CODEC_DEPTH_LIMIT = 64;
1033
- function resolveCodecOptions({
1034
- plugins,
1035
- disabledFeatures,
1036
- depthLimit
1037
- } = {}) {
1038
- return {
1039
- plugins: resolveSerializerPlugins(plugins),
1040
- disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
1041
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
1042
- };
1043
- }
1044
- function createJSONDeserializer(options) {
1045
- const refs = new Map();
1046
- const resolved = resolveCodecOptions(options);
1047
- return function deserializeJSONChunk(node) {
1048
- return fromCrossJSON(node, {
1049
- refs,
1050
- ...resolved
1051
- });
1052
- };
1053
- }
1054
- function createJSONDataTable(options) {
1055
- const deserialize = createJSONDeserializer(options);
1056
- const table = new Map();
1057
- return {
1058
- apply(record) {
1059
- const value = deserialize(record.node);
1060
- if (record.initial) table.set(record.key, value);
1061
- },
1062
- get(key) {
1063
- return table.get(key);
1064
- },
1065
- resolve(ref) {
1066
- return table.get(ref.$ref);
1067
- }
1068
- };
1069
- }
1070
-
1071
- class ChunkReader {
1072
- constructor(stream) {
1073
- this.reader = stream.getReader();
1074
- this.buffer = new Uint8Array(0);
1075
- this.done = false;
1076
- }
1077
- async readChunk() {
1078
- const chunk = await this.reader.read();
1079
- if (!chunk.done) {
1080
- const newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
1081
- newBuffer.set(this.buffer);
1082
- newBuffer.set(chunk.value, this.buffer.length);
1083
- this.buffer = newBuffer;
1084
- } else {
1085
- this.done = true;
1086
- }
1087
- }
1088
- async next() {
1089
- while (this.buffer.length < 12) {
1090
- if (this.done) {
1091
- if (this.buffer.length === 0) return {
1092
- done: true,
1093
- value: undefined
1094
- };
1095
- throw new Error("Malformed server function stream.");
1096
- }
1097
- await this.readChunk();
1098
- }
1099
- const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
1100
- const bytes = Number.parseInt(head, 16);
1101
- if (Number.isNaN(bytes)) {
1102
- throw new Error("Malformed server function stream.");
1103
- }
1104
- while (bytes > this.buffer.length - 12) {
1105
- if (this.done) {
1106
- throw new Error("Malformed server function stream.");
1107
- }
1108
- await this.readChunk();
1109
- }
1110
- const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
1111
- this.buffer = this.buffer.subarray(12 + bytes);
1112
- return {
1113
- done: false,
1114
- value: partial
1115
- };
1116
- }
1117
- async drain(interpret) {
1118
- while (true) {
1119
- const result = await this.next();
1120
- if (result.done) {
1121
- break;
1122
- }
1123
- interpret(result.value);
1124
- }
1051
+ function stashRange(frag, start, id) {
1052
+ const end = slotEnd(id);
1053
+ let n = start;
1054
+ while (n) {
1055
+ const next = n.nextSibling;
1056
+ const isEnd = n.nodeType === COMMENT_NODE && n.data === end;
1057
+ frag.appendChild(n);
1058
+ if (isEnd) return next;
1059
+ n = next;
1125
1060
  }
1061
+ return null;
1126
1062
  }
1127
1063
 
1128
1064
  const FRAME_STREAM_HEADER = "X-Frame-Stream";
@@ -1133,55 +1069,133 @@ async function applyFrameResponse(response, host, options = {}) {
1133
1069
  const rootId = response.headers.get(FRAME_STREAM_HEADER) ?? "";
1134
1070
  const as = options.as;
1135
1071
  const version = options.version;
1072
+ const perFrame = typeof version === "function" ? new Map() : null;
1136
1073
  const reader = new ChunkReader(response.body);
1137
1074
  let result = await reader.next();
1138
1075
  while (!result.done) {
1139
1076
  const chunk = JSON.parse(result.value);
1140
- if (as !== undefined && chunk.id === rootId) chunk.id = as;
1141
- if (version !== undefined) chunk.version = version;
1142
- host.apply(chunk);
1077
+ if (chunk.type === "outcome") {
1078
+ if (options.onOutcome) options.onOutcome(chunk.payload);
1079
+ } else {
1080
+ if (as !== undefined && chunk.id === rootId) chunk.id = as;else if (options.route) chunk.id = options.route(chunk.id);
1081
+ if (perFrame) {
1082
+ let v = perFrame.get(chunk.id);
1083
+ if (v === undefined) perFrame.set(chunk.id, v = version(chunk.id));
1084
+ chunk.version = v;
1085
+ } else if (version !== undefined) chunk.version = version;
1086
+ host.apply(chunk);
1087
+ }
1143
1088
  result = await reader.next();
1144
1089
  }
1145
1090
  return as !== undefined ? as : rootId;
1146
1091
  }
1092
+ const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1093
+ const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1094
+ let resolveServerComponent;
1095
+ function parseServerComponent(value, ctx) {
1096
+ return {
1097
+ id: ctx.parse(value[SERVER_COMPONENT]),
1098
+ address: ctx.parse(value[SERVER_COMPONENT_ADDRESS])
1099
+ };
1100
+ }
1101
+ const ServerComponentPlugin = /*#__PURE__*/createPlugin({
1102
+ tag: "dom-expressions/server-component",
1103
+ test(value) {
1104
+ return typeof value === "function" && SERVER_COMPONENT in value;
1105
+ },
1106
+ parse: {
1107
+ sync: parseServerComponent,
1108
+ async async(value, ctx) {
1109
+ return {
1110
+ id: await ctx.parse(value[SERVER_COMPONENT]),
1111
+ address: await ctx.parse(value[SERVER_COMPONENT_ADDRESS])
1112
+ };
1113
+ },
1114
+ stream: parseServerComponent
1115
+ },
1116
+ serialize(node, ctx) {
1117
+ return "self._$SC.r(" + ctx.serialize(node.id) + "," + ctx.serialize(node.address) + ")";
1118
+ },
1119
+ deserialize(node, ctx) {
1120
+ const id = ctx.deserialize(node.id);
1121
+ const address = ctx.deserialize(node.address);
1122
+ if (address !== undefined && resolveServerComponent) {
1123
+ return resolveServerComponent(id, address);
1124
+ }
1125
+ return globalThis._$SC.r(id);
1126
+ }
1127
+ });
1128
+ function flightCodec(codec) {
1129
+ const plugins = codec && codec.plugins || [];
1130
+ for (const plugin of plugins) {
1131
+ if (plugin && plugin.tag === "dom-expressions/server-component") return codec;
1132
+ }
1133
+ return {
1134
+ ...codec,
1135
+ plugins: [...plugins, ServerComponentPlugin]
1136
+ };
1137
+ }
1147
1138
  function createServerComponentHandler({
1148
1139
  host,
1149
1140
  component,
1150
- capture,
1151
1141
  onStream,
1152
1142
  documentComponent,
1153
- intercept
1143
+ intercept,
1144
+ consumer = getFlightDataConsumer,
1145
+ codec = getServerFunctionsCodec
1154
1146
  }) {
1155
- const byContext = new WeakMap();
1156
- const byFunction = new Map();
1157
- let boundary = 0;
1147
+ const byAddress = new Map();
1148
+ const boundaryFor = address => {
1149
+ let entry = byAddress.get(address);
1150
+ if (!entry) {
1151
+ entry = {
1152
+ frameId: address,
1153
+ component: component(address)
1154
+ };
1155
+ byAddress.set(address, entry);
1156
+ }
1157
+ return entry;
1158
+ };
1159
+ const resolveAddress = (id, address) => boundaryFor(address).component;
1160
+ resolveServerComponent = resolveAddress;
1161
+ const versions = new Map();
1162
+ const bump = frameId => {
1163
+ const version = (versions.get(frameId) || 0) + 1;
1164
+ versions.set(frameId, version);
1165
+ return version;
1166
+ };
1158
1167
  return {
1159
- capture,
1160
- intercept,
1168
+ intercept: intercept && (info => {
1169
+ const hit = intercept(info);
1170
+ if (hit !== undefined) {
1171
+ const address = frameAddress(info.id, info.args);
1172
+ byAddress.set(address, {
1173
+ frameId: info.id,
1174
+ component: hit
1175
+ });
1176
+ }
1177
+ return hit;
1178
+ }),
1161
1179
  handle(response, ctx) {
1162
1180
  if (!isFrameStreamResponse(response)) return undefined;
1163
- const key = ctx.context;
1164
- const keyed = key !== null && typeof key === "object";
1165
- let entry = keyed ? byContext.get(key) : byFunction.get(ctx.id);
1181
+ const address = frameAddress(ctx.id, ctx.args);
1182
+ let entry = byAddress.get(address);
1166
1183
  if (!entry) {
1167
1184
  const adopted = documentComponent && documentComponent(ctx.id);
1168
1185
  if (adopted) {
1169
1186
  entry = {
1170
1187
  frameId: ctx.id,
1171
- version: 0,
1172
1188
  component: adopted
1173
1189
  };
1190
+ byAddress.set(address, entry);
1174
1191
  } else {
1175
- const frameId = `sc:${boundary++}`;
1176
- entry = {
1177
- frameId,
1178
- version: 0,
1179
- component: component(frameId)
1180
- };
1192
+ entry = boundaryFor(address);
1181
1193
  }
1182
- keyed ? byContext.set(key, entry) : byFunction.set(ctx.id, entry);
1183
1194
  }
1184
- const version = ++entry.version;
1195
+ if (response.headers.has(SINGLE_FLIGHT_HEADER)) {
1196
+ return applyFlightResponse(response, entry);
1197
+ }
1198
+ const version = bump(entry.frameId);
1185
1199
  if (onStream) onStream(entry.frameId, version, response);
1186
1200
  applyFrameResponse(response, host, {
1187
1201
  as: entry.frameId,
@@ -1195,6 +1209,101 @@ function createServerComponentHandler({
1195
1209
  }
1196
1210
  }));
1197
1211
  return entry.component;
1212
+ },
1213
+ showing(address, functionId, component) {
1214
+ if (!byAddress.has(address)) {
1215
+ byAddress.set(address, {
1216
+ frameId: functionId,
1217
+ component,
1218
+ address
1219
+ });
1220
+ }
1221
+ }
1222
+ };
1223
+ async function applyFlightResponse(response, entry) {
1224
+ const rootId = response.headers.get(FRAME_STREAM_HEADER) ?? "";
1225
+ const as = rootId ? entry.frameId : undefined;
1226
+ let feed;
1227
+ const source = new ReadableStream({
1228
+ start(controller) {
1229
+ feed = controller;
1230
+ }
1231
+ });
1232
+ const payload = deserializeStream(new Response(source), flightCodec(codec()));
1233
+ let carried = false;
1234
+ await applyFrameResponse(response, host, {
1235
+ as,
1236
+ route: id => {
1237
+ const local = byAddress.get(id);
1238
+ return local ? local.frameId : id;
1239
+ },
1240
+ version: frameId => {
1241
+ const version = bump(frameId);
1242
+ if (onStream) onStream(frameId, version, response);
1243
+ return version;
1244
+ },
1245
+ onOutcome: text => {
1246
+ carried = true;
1247
+ feed.enqueue(createChunk(text));
1248
+ }
1249
+ });
1250
+ feed.close();
1251
+ if (!carried) throw new Error("Single-flight frame response carried no outcome");
1252
+ const envelope = await payload;
1253
+ const deliver = consumer();
1254
+ if (deliver) await deliver(envelope.data, {
1255
+ response
1256
+ });
1257
+ if (response.headers.has(ERROR_HEADER) && !response.headers.has("Location") && !response.headers.has(REVALIDATE_HEADER)) {
1258
+ throw envelope.value;
1259
+ }
1260
+ return rootId ? entry.component : envelope.value;
1261
+ }
1262
+ }
1263
+
1264
+ Feature.AggregateError | Feature.BigIntTypedArray;
1265
+ const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
1266
+ CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
1267
+ FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
1268
+ function resolveSerializerPlugins(customPlugins) {
1269
+ return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
1270
+ }
1271
+ const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
1272
+ const JSON_CODEC_DEPTH_LIMIT = 64;
1273
+ function resolveCodecOptions({
1274
+ plugins,
1275
+ disabledFeatures,
1276
+ depthLimit
1277
+ } = {}) {
1278
+ return {
1279
+ plugins: resolveSerializerPlugins(plugins),
1280
+ disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
1281
+ depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
1282
+ };
1283
+ }
1284
+ function createJSONDeserializer(options) {
1285
+ const refs = new Map();
1286
+ const resolved = resolveCodecOptions(options);
1287
+ return function deserializeJSONChunk(node) {
1288
+ return fromCrossJSON(node, {
1289
+ refs,
1290
+ ...resolved
1291
+ });
1292
+ };
1293
+ }
1294
+ function createJSONDataTable(options) {
1295
+ const deserialize = createJSONDeserializer(options);
1296
+ const table = new Map();
1297
+ return {
1298
+ apply(record) {
1299
+ const value = deserialize(record.node);
1300
+ if (record.initial) table.set(record.key, value);
1301
+ },
1302
+ get(key) {
1303
+ return table.get(key);
1304
+ },
1305
+ resolve(ref) {
1306
+ return table.get(ref.$ref);
1198
1307
  }
1199
1308
  };
1200
1309
  }
@@ -1248,8 +1357,10 @@ function claimRender(prefix, existing, render) {
1248
1357
  if (!registry.size) return render();
1249
1358
  const prevRegistry = sc.registry;
1250
1359
  const prevHydrating = sc.hydrating;
1360
+ const prevClaimRoots = sc.claimRoots;
1251
1361
  sc.registry = registry;
1252
1362
  sc.hydrating = true;
1363
+ sc.claimRoots = existing;
1253
1364
  try {
1254
1365
  return runWithOwner(createOwner({
1255
1366
  id: prefix
@@ -1257,13 +1368,28 @@ function claimRender(prefix, existing, render) {
1257
1368
  } finally {
1258
1369
  sc.registry = prevRegistry;
1259
1370
  sc.hydrating = prevHydrating;
1371
+ sc.claimRoots = prevClaimRoots;
1372
+ }
1373
+ }
1374
+ function isReactiveContent(value) {
1375
+ if (typeof value === "function") return true;
1376
+ if (Array.isArray(value)) {
1377
+ for (const v of value) if (isReactiveContent(v)) return true;
1260
1378
  }
1379
+ return false;
1261
1380
  }
1262
1381
  function slotsFor(props) {
1382
+ const bindings = new Map();
1263
1383
  return new Proxy({}, {
1264
1384
  get(_, prop) {
1265
1385
  if (typeof prop !== "string" || !(prop in props)) return undefined;
1266
1386
  return (slotProps, ctx) => {
1387
+ const key = ctx && ctx.key;
1388
+ const prev = key !== undefined && bindings.get(key);
1389
+ if (prev) {
1390
+ bindings.delete(key);
1391
+ prev.dispose();
1392
+ }
1267
1393
  const settle = out => {
1268
1394
  const existing = ctx && ctx.existing || [];
1269
1395
  if (existing.length) {
@@ -1273,14 +1399,37 @@ function slotsFor(props) {
1273
1399
  }
1274
1400
  return out;
1275
1401
  };
1276
- const render = () => {
1402
+ const evaluate = () => {
1277
1403
  const v = props[prop];
1278
- return settle(normalizeSlotContent(typeof v === "function" ? v(slotProps) : v));
1404
+ return typeof v === "function" && ctx && ctx.invoked ? v(slotProps) : v;
1279
1405
  };
1280
- if (ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length) {
1281
- return claimRender(`sc-${ctx.frame}-${ctx.key}-`, ctx.existing, render);
1406
+ const adopted = !!(ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length);
1407
+ const prefix = ctx && ctx.frame ? `sc-${ctx.frame}-${ctx.key}-` : "";
1408
+ const value = adopted ? claimRender(prefix, ctx.existing, evaluate) : evaluate();
1409
+ if (!isReactiveContent(value)) {
1410
+ return settle(normalizeSlotContent(value));
1282
1411
  }
1283
- return render();
1412
+ if (ctx && ctx.range) {
1413
+ let claim = adopted;
1414
+ const source = value;
1415
+ const accessor = () => {
1416
+ if (claim) {
1417
+ claim = false;
1418
+ return claimRender(prefix, ctx.existing, () => typeof source === "function" ? source() : source);
1419
+ }
1420
+ return typeof source === "function" ? source() : source;
1421
+ };
1422
+ const owner = createOwner();
1423
+ bindings.set(key, owner);
1424
+ ctx.onCleanup(() => {
1425
+ if (bindings.get(key) === owner) bindings.delete(key);
1426
+ owner.dispose();
1427
+ });
1428
+ const end = ctx.range.end;
1429
+ runWithOwner(owner, () => insert(end.parentNode, accessor, end, [...ctx.existing]));
1430
+ return undefined;
1431
+ }
1432
+ return settle(normalizeSlotContent(value));
1284
1433
  };
1285
1434
  }
1286
1435
  });
@@ -1288,6 +1437,9 @@ function slotsFor(props) {
1288
1437
  function revealSeam(owner) {
1289
1438
  return seam => runWithOwner(owner, () => insert(seam.before.parentNode, createLoadingBoundary(() => seam.content(), () => seam.fallback), seam.before));
1290
1439
  }
1440
+ function boundaryScope(owner) {
1441
+ return fn => getOwner() ? fn() : runWithOwner(owner, fn);
1442
+ }
1291
1443
  function boundaryComponent(host, id) {
1292
1444
  return props => {
1293
1445
  const owner = getOwner();
@@ -1298,7 +1450,7 @@ function boundaryComponent(host, id) {
1298
1450
  host,
1299
1451
  id,
1300
1452
  slots: slotsFor(props),
1301
- ownerScope: fn => runWithOwner(owner, fn),
1453
+ ownerScope: boundaryScope(owner),
1302
1454
  reveal: revealSeam(owner)
1303
1455
  });
1304
1456
  onCleanup(dispose);
@@ -1392,7 +1544,7 @@ function adoptBoundary(host, id, el, props) {
1392
1544
  host,
1393
1545
  id,
1394
1546
  slots: slotsFor(props),
1395
- ownerScope: fn => runWithOwner(owner, fn),
1547
+ ownerScope: boundaryScope(owner),
1396
1548
  reveal: revealSeam(owner)
1397
1549
  });
1398
1550
  onCleanup(() => frame.dispose());
@@ -1403,27 +1555,36 @@ function installServerComponents(host = getFrameHost()) {
1403
1555
  if (!g._$SC) {
1404
1556
  g._$SC = {
1405
1557
  c: {},
1406
- r(i) {
1558
+ a: {},
1559
+ r(i, a) {
1560
+ if (a) {
1561
+ g._$SC.a[a] = i;
1562
+ g._$SC.reg && g._$SC.reg(a, i);
1563
+ }
1407
1564
  return g._$SC.c[i] || (g._$SC.c[i] = p => g._$SC.impl(i, p));
1408
1565
  }
1409
1566
  };
1410
1567
  }
1411
1568
  g._$SC.impl = (id, props) => documentBoundary(host, id, props);
1412
1569
  installRevealHook();
1570
+ const handler = createServerComponentHandler({
1571
+ host,
1572
+ component: frameId => boundaryComponent(host, frameId),
1573
+ onStream: frameId => beginStream(frameId),
1574
+ documentComponent: functionId => claimedBoundaries.has(functionId) ? undefined : g._$SC.c[functionId],
1575
+ intercept: ({
1576
+ id
1577
+ }) => {
1578
+ if (claimedBoundaries.has(id) || !findBoundaryElement(id)) return undefined;
1579
+ return g._$SC.r(id);
1580
+ }
1581
+ });
1582
+ const showing = (address, id) => handler.showing(address, id, g._$SC.r(id));
1583
+ const records = g._$SC.a || (g._$SC.a = {});
1584
+ for (const address in records) showing(address, records[address]);
1585
+ g._$SC.reg = showing;
1413
1586
  configureServerFunctionsClient({
1414
- responseHandler: createServerComponentHandler({
1415
- host,
1416
- capture: () => getOwner() ?? undefined,
1417
- component: frameId => boundaryComponent(host, frameId),
1418
- onStream: frameId => beginStream(frameId),
1419
- documentComponent: functionId => g._$SC.c[functionId],
1420
- intercept: ({
1421
- id
1422
- }) => {
1423
- if (claimedBoundaries.has(id) || !findBoundaryElement(id)) return undefined;
1424
- return g._$SC.r(id);
1425
- }
1426
- })
1587
+ responseHandler: handler
1427
1588
  });
1428
1589
  }
1429
1590