@solidjs/web 2.0.0-beta.23 → 2.0.0-beta.25

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/dist/server.cjs CHANGED
@@ -313,7 +313,8 @@ function renderToStream(code, options = {}) {
313
313
  }
314
314
  tasks += task + ";";
315
315
  if (!timer && firstFlushed) {
316
- timer = setTimeout(writeTasks);
316
+ timer = true;
317
+ queue(() => queue(writeTasks));
317
318
  }
318
319
  };
319
320
  const onDone = () => {
@@ -396,7 +397,6 @@ function renderToStream(code, options = {}) {
396
397
  buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks}</script>`);
397
398
  tasks = "";
398
399
  }
399
- timer && clearTimeout(timer);
400
400
  timer = null;
401
401
  };
402
402
  let context;
@@ -648,6 +648,26 @@ function renderToStream(code, options = {}) {
648
648
  });
649
649
  shellCompleted = true;
650
650
  }
651
+ const MIN_DRAIN_TURNS = 8;
652
+ let lastBlockingSize = -1;
653
+ let lastRegistrySize = -1;
654
+ let drainTurn = 0;
655
+ const scheduleFlush = fn => {
656
+ const attempt = () => {
657
+ if (registry.size !== lastRegistrySize || drainTurn++ < MIN_DRAIN_TURNS) {
658
+ if (registry.size !== lastRegistrySize) drainTurn = 0;
659
+ lastRegistrySize = registry.size;
660
+ queue(attempt);
661
+ return;
662
+ }
663
+ fn();
664
+ };
665
+ const progressed = blockingPromises.size !== lastBlockingSize;
666
+ lastBlockingSize = blockingPromises.size;
667
+ lastRegistrySize = -1;
668
+ drainTurn = 0;
669
+ progressed ? queue(attempt) : setTimeout(attempt);
670
+ };
651
671
  return {
652
672
  then(fn) {
653
673
  function complete() {
@@ -663,7 +683,7 @@ function renderToStream(code, options = {}) {
663
683
  } else onCompleteAll = complete;
664
684
  function flush() {
665
685
  allSettled(blockingPromises).then(() => {
666
- setTimeout(() => {
686
+ scheduleFlush(() => {
667
687
  if (!resolveRootHoles()) return flush();
668
688
  queue(flushEnd);
669
689
  });
@@ -674,7 +694,7 @@ function renderToStream(code, options = {}) {
674
694
  pipe(w) {
675
695
  function flush() {
676
696
  allSettled(blockingPromises).then(() => {
677
- setTimeout(() => {
697
+ scheduleFlush(() => {
678
698
  doShell();
679
699
  if (!shellCompleted) return flush();
680
700
  buffer = writable = w;
@@ -694,7 +714,7 @@ function renderToStream(code, options = {}) {
694
714
  const p = new Promise(r => resolve = r);
695
715
  function flush() {
696
716
  allSettled(blockingPromises).then(() => {
697
- setTimeout(() => {
717
+ scheduleFlush(() => {
698
718
  doShell();
699
719
  if (!shellCompleted) return flush();
700
720
  const encoder = new TextEncoder();
@@ -997,14 +1017,12 @@ function escape(s, attr) {
997
1017
  }
998
1018
  return s;
999
1019
  }
1000
- const delimCode = attr ? 34 : 60;
1001
- const len = s.length;
1002
- for (let i = 0; i < len; i++) {
1003
- const c = s.charCodeAt(i);
1004
- if (c === 38 || c === delimCode) return escapeSlow(s, attr, i);
1005
- }
1006
- return s;
1020
+ const i = s.search(attr ? ESCAPE_ATTR : ESCAPE_CONTENT);
1021
+ if (i < 0) return s;
1022
+ return escapeSlow(s, attr, i);
1007
1023
  }
1024
+ const ESCAPE_CONTENT = /[&<]/;
1025
+ const ESCAPE_ATTR = /[&"]/;
1008
1026
  function escapeSlow(s, attr, start) {
1009
1027
  const delim = attr ? '"' : "<";
1010
1028
  const delimCode = attr ? 34 : 60;
package/dist/server.js CHANGED
@@ -312,7 +312,8 @@ function renderToStream(code, options = {}) {
312
312
  }
313
313
  tasks += task + ";";
314
314
  if (!timer && firstFlushed) {
315
- timer = setTimeout(writeTasks);
315
+ timer = true;
316
+ queue(() => queue(writeTasks));
316
317
  }
317
318
  };
318
319
  const onDone = () => {
@@ -395,7 +396,6 @@ function renderToStream(code, options = {}) {
395
396
  buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks}</script>`);
396
397
  tasks = "";
397
398
  }
398
- timer && clearTimeout(timer);
399
399
  timer = null;
400
400
  };
401
401
  let context;
@@ -647,6 +647,26 @@ function renderToStream(code, options = {}) {
647
647
  });
648
648
  shellCompleted = true;
649
649
  }
650
+ const MIN_DRAIN_TURNS = 8;
651
+ let lastBlockingSize = -1;
652
+ let lastRegistrySize = -1;
653
+ let drainTurn = 0;
654
+ const scheduleFlush = fn => {
655
+ const attempt = () => {
656
+ if (registry.size !== lastRegistrySize || drainTurn++ < MIN_DRAIN_TURNS) {
657
+ if (registry.size !== lastRegistrySize) drainTurn = 0;
658
+ lastRegistrySize = registry.size;
659
+ queue(attempt);
660
+ return;
661
+ }
662
+ fn();
663
+ };
664
+ const progressed = blockingPromises.size !== lastBlockingSize;
665
+ lastBlockingSize = blockingPromises.size;
666
+ lastRegistrySize = -1;
667
+ drainTurn = 0;
668
+ progressed ? queue(attempt) : setTimeout(attempt);
669
+ };
650
670
  return {
651
671
  then(fn) {
652
672
  function complete() {
@@ -662,7 +682,7 @@ function renderToStream(code, options = {}) {
662
682
  } else onCompleteAll = complete;
663
683
  function flush() {
664
684
  allSettled(blockingPromises).then(() => {
665
- setTimeout(() => {
685
+ scheduleFlush(() => {
666
686
  if (!resolveRootHoles()) return flush();
667
687
  queue(flushEnd);
668
688
  });
@@ -673,7 +693,7 @@ function renderToStream(code, options = {}) {
673
693
  pipe(w) {
674
694
  function flush() {
675
695
  allSettled(blockingPromises).then(() => {
676
- setTimeout(() => {
696
+ scheduleFlush(() => {
677
697
  doShell();
678
698
  if (!shellCompleted) return flush();
679
699
  buffer = writable = w;
@@ -693,7 +713,7 @@ function renderToStream(code, options = {}) {
693
713
  const p = new Promise(r => resolve = r);
694
714
  function flush() {
695
715
  allSettled(blockingPromises).then(() => {
696
- setTimeout(() => {
716
+ scheduleFlush(() => {
697
717
  doShell();
698
718
  if (!shellCompleted) return flush();
699
719
  const encoder = new TextEncoder();
@@ -996,14 +1016,12 @@ function escape(s, attr) {
996
1016
  }
997
1017
  return s;
998
1018
  }
999
- const delimCode = attr ? 34 : 60;
1000
- const len = s.length;
1001
- for (let i = 0; i < len; i++) {
1002
- const c = s.charCodeAt(i);
1003
- if (c === 38 || c === delimCode) return escapeSlow(s, attr, i);
1004
- }
1005
- return s;
1019
+ const i = s.search(attr ? ESCAPE_ATTR : ESCAPE_CONTENT);
1020
+ if (i < 0) return s;
1021
+ return escapeSlow(s, attr, i);
1006
1022
  }
1023
+ const ESCAPE_CONTENT = /[&<]/;
1024
+ const ESCAPE_ATTR = /[&"]/;
1007
1025
  function escapeSlow(s, attr, start) {
1008
1026
  const delim = attr ? '"' : "<";
1009
1027
  const delimCode = attr ? 34 : 60;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var solidJs = require('solid-js');
4
+ var client = require('@solidjs/web/server-functions/client');
4
5
  var seroval = require('seroval');
5
6
  var web = require('seroval-plugins/web');
6
7
 
@@ -126,7 +127,20 @@ function createFrameHost(options = {}) {
126
127
  const buffered = pending.get(id);
127
128
  if (buffered) {
128
129
  pending.delete(id);
129
- for (const chunk of buffered) deliver(frame, chunk);
130
+ const records = {};
131
+ let version;
132
+ for (const chunk of buffered) {
133
+ if (chunk.type === "data") {
134
+ options.applyData && options.applyData(chunk);
135
+ continue;
136
+ }
137
+ version = chunk.version;
138
+ Object.assign(records, chunkToRecords(chunk));
139
+ }
140
+ if (version !== undefined) frame.apply({
141
+ version,
142
+ r: records
143
+ });
130
144
  }
131
145
  },
132
146
  unregister(id, frame) {
@@ -346,11 +360,11 @@ class FrameImpl {
346
360
  this.#runSlotCleanups(occurrence);
347
361
  }
348
362
  if (!this.#mountedSlots.has(occurrence)) {
349
- const nodes = this.#invokeSlot(occurrence, callback, record, start);
363
+ const nodes = this.#invokeSlot(occurrence, callback, record, start, this.#options.adopt);
350
364
  if (nodes) this.#replaceRange(occurrence, start, nodes);
351
365
  this.#slotNodes.set(occurrence, nodes);
352
366
  this.#mountedSlots.add(occurrence);
353
- if (!record) this.#discoverRegions(occurrence, start);
367
+ this.#discoverRegions(occurrence, start);
354
368
  this.#bindRegions(occurrence);
355
369
  } else if (record !== this.#slotArgs.get(occurrence)) {
356
370
  if (this.#refArgsUnchanged(occurrence, record)) {
@@ -367,11 +381,12 @@ class FrameImpl {
367
381
  if (!found.has(occurrence)) this.#unmountSlot(occurrence);
368
382
  }
369
383
  }
370
- #invokeSlot(occurrence, callback, record, start) {
384
+ #invokeSlot(occurrence, callback, record, start, adopted) {
371
385
  const cleanups = this.#slotCleanups.get(occurrence) ?? [];
372
386
  const ctx = {
373
387
  frame: this.#options.claimScope ?? this.#options.id,
374
388
  key: occurrence,
389
+ adopted: !!adopted,
375
390
  onCleanup: fn => cleanups.push(fn),
376
391
  existing: start ? rangeInterior(start, slotEnd(occurrence)) : []
377
392
  };
@@ -514,11 +529,22 @@ class FrameImpl {
514
529
  }
515
530
  #refArgsUnchanged(occurrence, record) {
516
531
  const old = this.#slotArgs.get(occurrence);
517
- if (!old || old.kind !== "slot" || !record || record.kind !== "slot") return false;
518
- const a = old.args || {};
532
+ if (!record || record.kind !== "slot") return false;
533
+ if (old && old.kind !== "slot") return false;
534
+ const a = old && old.args || {};
519
535
  const b = record.args || {};
520
536
  const ka = Object.keys(a);
521
- if (ka.length !== Object.keys(b).length) return false;
537
+ const kb = Object.keys(b);
538
+ if (kb.length < ka.length) return false;
539
+ if (kb.length !== ka.length) {
540
+ const regions = this.#slotRegions.get(occurrence);
541
+ for (const key of kb) {
542
+ if (key in a) continue;
543
+ const vb = b[key];
544
+ if (!vb || typeof vb !== "object" || typeof vb.$frame !== "string") return false;
545
+ if (!regions || !regions.has(vb.$frame)) return false;
546
+ }
547
+ }
522
548
  const cache = this.#slotResolvedRefs.get(occurrence);
523
549
  for (const key of ka) {
524
550
  const va = a[key];
@@ -1258,6 +1284,9 @@ function tableFor(id) {
1258
1284
  for (const [root, t] of tables) if (id.startsWith(root + ".")) return t;
1259
1285
  return undefined;
1260
1286
  }
1287
+ function beginStream(frameId) {
1288
+ tables.set(frameId, createJSONDataTable());
1289
+ }
1261
1290
  function getFrameHost() {
1262
1291
  if (!sharedHost) {
1263
1292
  sharedHost = createFrameHost({
@@ -1322,7 +1351,7 @@ function slotsFor(props) {
1322
1351
  const v = props[prop];
1323
1352
  return settle(normalizeSlotContent(typeof v === "function" ? v(slotProps) : v));
1324
1353
  };
1325
- if (ctx && ctx.frame && ctx.existing && ctx.existing.length) {
1354
+ if (ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length) {
1326
1355
  return claimRender(`sc-${ctx.frame}-${ctx.key}-`, ctx.existing, render);
1327
1356
  }
1328
1357
  return render();
@@ -1427,6 +1456,21 @@ function installServerComponents(host = getFrameHost()) {
1427
1456
  };
1428
1457
  }
1429
1458
  g._$SC.impl = (id, props) => documentBoundary(host, id, props);
1459
+ client.configureServerFunctionsClient({
1460
+ responseHandler: createServerComponentHandler({
1461
+ host,
1462
+ capture: () => solidJs.getOwner() ?? undefined,
1463
+ component: frameId => boundaryComponent(host, frameId),
1464
+ onStream: frameId => beginStream(frameId),
1465
+ documentComponent: functionId => g._$SC.c[functionId],
1466
+ intercept: ({
1467
+ id
1468
+ }) => {
1469
+ if (claimedBoundaries.has(id) || !findBoundaryRange(id)) return undefined;
1470
+ return g._$SC.r(id);
1471
+ }
1472
+ })
1473
+ });
1430
1474
  }
1431
1475
 
1432
1476
  exports.FRAME_APPLIED_EVENT = FRAME_APPLIED_EVENT;
@@ -1,4 +1,5 @@
1
1
  import { getOwner, onCleanup, runWithOwner, sharedConfig, createOwner } from 'solid-js';
2
+ import { configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
2
3
  import { fromCrossJSON, Feature } from 'seroval';
3
4
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
4
5
 
@@ -124,7 +125,20 @@ function createFrameHost(options = {}) {
124
125
  const buffered = pending.get(id);
125
126
  if (buffered) {
126
127
  pending.delete(id);
127
- for (const chunk of buffered) deliver(frame, chunk);
128
+ const records = {};
129
+ let version;
130
+ for (const chunk of buffered) {
131
+ if (chunk.type === "data") {
132
+ options.applyData && options.applyData(chunk);
133
+ continue;
134
+ }
135
+ version = chunk.version;
136
+ Object.assign(records, chunkToRecords(chunk));
137
+ }
138
+ if (version !== undefined) frame.apply({
139
+ version,
140
+ r: records
141
+ });
128
142
  }
129
143
  },
130
144
  unregister(id, frame) {
@@ -344,11 +358,11 @@ class FrameImpl {
344
358
  this.#runSlotCleanups(occurrence);
345
359
  }
346
360
  if (!this.#mountedSlots.has(occurrence)) {
347
- const nodes = this.#invokeSlot(occurrence, callback, record, start);
361
+ const nodes = this.#invokeSlot(occurrence, callback, record, start, this.#options.adopt);
348
362
  if (nodes) this.#replaceRange(occurrence, start, nodes);
349
363
  this.#slotNodes.set(occurrence, nodes);
350
364
  this.#mountedSlots.add(occurrence);
351
- if (!record) this.#discoverRegions(occurrence, start);
365
+ this.#discoverRegions(occurrence, start);
352
366
  this.#bindRegions(occurrence);
353
367
  } else if (record !== this.#slotArgs.get(occurrence)) {
354
368
  if (this.#refArgsUnchanged(occurrence, record)) {
@@ -365,11 +379,12 @@ class FrameImpl {
365
379
  if (!found.has(occurrence)) this.#unmountSlot(occurrence);
366
380
  }
367
381
  }
368
- #invokeSlot(occurrence, callback, record, start) {
382
+ #invokeSlot(occurrence, callback, record, start, adopted) {
369
383
  const cleanups = this.#slotCleanups.get(occurrence) ?? [];
370
384
  const ctx = {
371
385
  frame: this.#options.claimScope ?? this.#options.id,
372
386
  key: occurrence,
387
+ adopted: !!adopted,
373
388
  onCleanup: fn => cleanups.push(fn),
374
389
  existing: start ? rangeInterior(start, slotEnd(occurrence)) : []
375
390
  };
@@ -512,11 +527,22 @@ class FrameImpl {
512
527
  }
513
528
  #refArgsUnchanged(occurrence, record) {
514
529
  const old = this.#slotArgs.get(occurrence);
515
- if (!old || old.kind !== "slot" || !record || record.kind !== "slot") return false;
516
- const a = old.args || {};
530
+ if (!record || record.kind !== "slot") return false;
531
+ if (old && old.kind !== "slot") return false;
532
+ const a = old && old.args || {};
517
533
  const b = record.args || {};
518
534
  const ka = Object.keys(a);
519
- if (ka.length !== Object.keys(b).length) return false;
535
+ const kb = Object.keys(b);
536
+ if (kb.length < ka.length) return false;
537
+ if (kb.length !== ka.length) {
538
+ const regions = this.#slotRegions.get(occurrence);
539
+ for (const key of kb) {
540
+ if (key in a) continue;
541
+ const vb = b[key];
542
+ if (!vb || typeof vb !== "object" || typeof vb.$frame !== "string") return false;
543
+ if (!regions || !regions.has(vb.$frame)) return false;
544
+ }
545
+ }
520
546
  const cache = this.#slotResolvedRefs.get(occurrence);
521
547
  for (const key of ka) {
522
548
  const va = a[key];
@@ -1256,6 +1282,9 @@ function tableFor(id) {
1256
1282
  for (const [root, t] of tables) if (id.startsWith(root + ".")) return t;
1257
1283
  return undefined;
1258
1284
  }
1285
+ function beginStream(frameId) {
1286
+ tables.set(frameId, createJSONDataTable());
1287
+ }
1259
1288
  function getFrameHost() {
1260
1289
  if (!sharedHost) {
1261
1290
  sharedHost = createFrameHost({
@@ -1320,7 +1349,7 @@ function slotsFor(props) {
1320
1349
  const v = props[prop];
1321
1350
  return settle(normalizeSlotContent(typeof v === "function" ? v(slotProps) : v));
1322
1351
  };
1323
- if (ctx && ctx.frame && ctx.existing && ctx.existing.length) {
1352
+ if (ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length) {
1324
1353
  return claimRender(`sc-${ctx.frame}-${ctx.key}-`, ctx.existing, render);
1325
1354
  }
1326
1355
  return render();
@@ -1425,6 +1454,21 @@ function installServerComponents(host = getFrameHost()) {
1425
1454
  };
1426
1455
  }
1427
1456
  g._$SC.impl = (id, props) => documentBoundary(host, id, props);
1457
+ configureServerFunctionsClient({
1458
+ responseHandler: createServerComponentHandler({
1459
+ host,
1460
+ capture: () => getOwner() ?? undefined,
1461
+ component: frameId => boundaryComponent(host, frameId),
1462
+ onStream: frameId => beginStream(frameId),
1463
+ documentComponent: functionId => g._$SC.c[functionId],
1464
+ intercept: ({
1465
+ id
1466
+ }) => {
1467
+ if (claimedBoundaries.has(id) || !findBoundaryRange(id)) return undefined;
1468
+ return g._$SC.r(id);
1469
+ }
1470
+ })
1471
+ });
1428
1472
  }
1429
1473
 
1430
1474
  export { FRAME_APPLIED_EVENT, FRAME_STREAM_HEADER, applyFrameResponse, createFrame, createFrameHost, createFrameInsertable, createJSONDataTable, createServerComponentHandler, getFrameHost, installServerComponents, isFrameStreamResponse };
@@ -266,7 +266,8 @@ function renderToStream(code, options = {}) {
266
266
  }
267
267
  tasks += task + ";";
268
268
  if (!timer && firstFlushed) {
269
- timer = setTimeout(writeTasks);
269
+ timer = true;
270
+ queue(() => queue(writeTasks));
270
271
  }
271
272
  };
272
273
  const onDone = () => {
@@ -349,7 +350,6 @@ function renderToStream(code, options = {}) {
349
350
  buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks}</script>`);
350
351
  tasks = "";
351
352
  }
352
- timer && clearTimeout(timer);
353
353
  timer = null;
354
354
  };
355
355
  let context;
@@ -601,6 +601,26 @@ function renderToStream(code, options = {}) {
601
601
  });
602
602
  shellCompleted = true;
603
603
  }
604
+ const MIN_DRAIN_TURNS = 8;
605
+ let lastBlockingSize = -1;
606
+ let lastRegistrySize = -1;
607
+ let drainTurn = 0;
608
+ const scheduleFlush = fn => {
609
+ const attempt = () => {
610
+ if (registry.size !== lastRegistrySize || drainTurn++ < MIN_DRAIN_TURNS) {
611
+ if (registry.size !== lastRegistrySize) drainTurn = 0;
612
+ lastRegistrySize = registry.size;
613
+ queue(attempt);
614
+ return;
615
+ }
616
+ fn();
617
+ };
618
+ const progressed = blockingPromises.size !== lastBlockingSize;
619
+ lastBlockingSize = blockingPromises.size;
620
+ lastRegistrySize = -1;
621
+ drainTurn = 0;
622
+ progressed ? queue(attempt) : setTimeout(attempt);
623
+ };
604
624
  return {
605
625
  then(fn) {
606
626
  function complete() {
@@ -616,7 +636,7 @@ function renderToStream(code, options = {}) {
616
636
  } else onCompleteAll = complete;
617
637
  function flush() {
618
638
  allSettled(blockingPromises).then(() => {
619
- setTimeout(() => {
639
+ scheduleFlush(() => {
620
640
  if (!resolveRootHoles()) return flush();
621
641
  queue(flushEnd);
622
642
  });
@@ -627,7 +647,7 @@ function renderToStream(code, options = {}) {
627
647
  pipe(w) {
628
648
  function flush() {
629
649
  allSettled(blockingPromises).then(() => {
630
- setTimeout(() => {
650
+ scheduleFlush(() => {
631
651
  doShell();
632
652
  if (!shellCompleted) return flush();
633
653
  buffer = writable = w;
@@ -647,7 +667,7 @@ function renderToStream(code, options = {}) {
647
667
  const p = new Promise(r => resolve = r);
648
668
  function flush() {
649
669
  allSettled(blockingPromises).then(() => {
650
- setTimeout(() => {
670
+ scheduleFlush(() => {
651
671
  doShell();
652
672
  if (!shellCompleted) return flush();
653
673
  const encoder = new TextEncoder();
@@ -861,14 +881,12 @@ function escape(s, attr) {
861
881
  }
862
882
  return s;
863
883
  }
864
- const delimCode = attr ? 34 : 60;
865
- const len = s.length;
866
- for (let i = 0; i < len; i++) {
867
- const c = s.charCodeAt(i);
868
- if (c === 38 || c === delimCode) return escapeSlow(s, attr, i);
869
- }
870
- return s;
884
+ const i = s.search(attr ? ESCAPE_ATTR : ESCAPE_CONTENT);
885
+ if (i < 0) return s;
886
+ return escapeSlow(s, attr, i);
871
887
  }
888
+ const ESCAPE_CONTENT = /[&<]/;
889
+ const ESCAPE_ATTR = /[&"]/;
872
890
  function escapeSlow(s, attr, start) {
873
891
  const delim = attr ? '"' : "<";
874
892
  const delimCode = attr ? 34 : 60;
@@ -1516,7 +1534,7 @@ function renderedHtmlOf(out) {
1516
1534
  try {
1517
1535
  const res = solidJs.sharedConfig.context.resolve(out);
1518
1536
  if (res && res.t && (!res.h || !res.h.length)) {
1519
- return res.t[0].replace(/<!--[^>]*-->/g, "");
1537
+ return res.t[0].replace(/<!--[^>]*-->/g, "").replace(/ _hk=("[^"]*"|[^\s>]+)/g, "");
1520
1538
  }
1521
1539
  } catch (e) {}
1522
1540
  return null;
@@ -264,7 +264,8 @@ function renderToStream(code, options = {}) {
264
264
  }
265
265
  tasks += task + ";";
266
266
  if (!timer && firstFlushed) {
267
- timer = setTimeout(writeTasks);
267
+ timer = true;
268
+ queue(() => queue(writeTasks));
268
269
  }
269
270
  };
270
271
  const onDone = () => {
@@ -347,7 +348,6 @@ function renderToStream(code, options = {}) {
347
348
  buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks}</script>`);
348
349
  tasks = "";
349
350
  }
350
- timer && clearTimeout(timer);
351
351
  timer = null;
352
352
  };
353
353
  let context;
@@ -599,6 +599,26 @@ function renderToStream(code, options = {}) {
599
599
  });
600
600
  shellCompleted = true;
601
601
  }
602
+ const MIN_DRAIN_TURNS = 8;
603
+ let lastBlockingSize = -1;
604
+ let lastRegistrySize = -1;
605
+ let drainTurn = 0;
606
+ const scheduleFlush = fn => {
607
+ const attempt = () => {
608
+ if (registry.size !== lastRegistrySize || drainTurn++ < MIN_DRAIN_TURNS) {
609
+ if (registry.size !== lastRegistrySize) drainTurn = 0;
610
+ lastRegistrySize = registry.size;
611
+ queue(attempt);
612
+ return;
613
+ }
614
+ fn();
615
+ };
616
+ const progressed = blockingPromises.size !== lastBlockingSize;
617
+ lastBlockingSize = blockingPromises.size;
618
+ lastRegistrySize = -1;
619
+ drainTurn = 0;
620
+ progressed ? queue(attempt) : setTimeout(attempt);
621
+ };
602
622
  return {
603
623
  then(fn) {
604
624
  function complete() {
@@ -614,7 +634,7 @@ function renderToStream(code, options = {}) {
614
634
  } else onCompleteAll = complete;
615
635
  function flush() {
616
636
  allSettled(blockingPromises).then(() => {
617
- setTimeout(() => {
637
+ scheduleFlush(() => {
618
638
  if (!resolveRootHoles()) return flush();
619
639
  queue(flushEnd);
620
640
  });
@@ -625,7 +645,7 @@ function renderToStream(code, options = {}) {
625
645
  pipe(w) {
626
646
  function flush() {
627
647
  allSettled(blockingPromises).then(() => {
628
- setTimeout(() => {
648
+ scheduleFlush(() => {
629
649
  doShell();
630
650
  if (!shellCompleted) return flush();
631
651
  buffer = writable = w;
@@ -645,7 +665,7 @@ function renderToStream(code, options = {}) {
645
665
  const p = new Promise(r => resolve = r);
646
666
  function flush() {
647
667
  allSettled(blockingPromises).then(() => {
648
- setTimeout(() => {
668
+ scheduleFlush(() => {
649
669
  doShell();
650
670
  if (!shellCompleted) return flush();
651
671
  const encoder = new TextEncoder();
@@ -859,14 +879,12 @@ function escape(s, attr) {
859
879
  }
860
880
  return s;
861
881
  }
862
- const delimCode = attr ? 34 : 60;
863
- const len = s.length;
864
- for (let i = 0; i < len; i++) {
865
- const c = s.charCodeAt(i);
866
- if (c === 38 || c === delimCode) return escapeSlow(s, attr, i);
867
- }
868
- return s;
882
+ const i = s.search(attr ? ESCAPE_ATTR : ESCAPE_CONTENT);
883
+ if (i < 0) return s;
884
+ return escapeSlow(s, attr, i);
869
885
  }
886
+ const ESCAPE_CONTENT = /[&<]/;
887
+ const ESCAPE_ATTR = /[&"]/;
870
888
  function escapeSlow(s, attr, start) {
871
889
  const delim = attr ? '"' : "<";
872
890
  const delimCode = attr ? 34 : 60;
@@ -1514,7 +1532,7 @@ function renderedHtmlOf(out) {
1514
1532
  try {
1515
1533
  const res = sharedConfig.context.resolve(out);
1516
1534
  if (res && res.t && (!res.h || !res.h.length)) {
1517
- return res.t[0].replace(/<!--[^>]*-->/g, "");
1535
+ return res.t[0].replace(/<!--[^>]*-->/g, "").replace(/ _hk=("[^"]*"|[^\s>]+)/g, "");
1518
1536
  }
1519
1537
  } catch (e) {}
1520
1538
  return null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solidjs/web",
3
3
  "description": "Solid's web runtime: client rendering, hydration, SSR, and DOM-specific control flow (Portal, Dynamic).",
4
- "version": "2.0.0-beta.23",
4
+ "version": "2.0.0-beta.25",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "homepage": "https://solidjs.com",
@@ -296,25 +296,26 @@
296
296
  "seroval-plugins": "~1.5.4"
297
297
  },
298
298
  "peerDependencies": {
299
- "solid-js": "^2.0.0-beta.23"
299
+ "solid-js": "^2.0.0-beta.25"
300
300
  },
301
301
  "devDependencies": {
302
- "solid-js": "2.0.0-beta.23"
302
+ "solid-js": "2.0.0-beta.25"
303
303
  },
304
304
  "scripts": {
305
305
  "build": "npm-run-all -nl build:clean types:copy-jsx build:js",
306
306
  "build:clean": "rimraf dist/",
307
307
  "build:js": "rollup -c",
308
308
  "link": "symlink-dir . node_modules/@solidjs/web",
309
- "types": "npm-run-all -nl types:clean types:copy-jsx types:web types:copy-web types:web-storage types:copy-serialization types:copy-server-functions types:copy-frames types:cjs",
310
- "types:clean": "rimraf types/ types-cjs/ storage/types/ storage/types-cjs/ serialization/types/ serialization/types-cjs/",
309
+ "types": "npm-run-all -nl types:clean types:copy-jsx types:web types:copy-web types:web-storage types:copy-server-functions types:web-frames types:copy-serialization types:copy-frames types:cjs",
310
+ "types:clean": "rimraf types/ types-cjs/ storage/types/ storage/types-cjs/ serialization/types/ serialization/types-cjs/ frames/types/",
311
311
  "types:copy-jsx": "ncp ../../node_modules/@dom-expressions/runtime/src/jsx.d.ts ./src/jsx.d.ts && ncp ../../node_modules/@dom-expressions/runtime/src/jsx-properties.d.ts ./src/jsx-properties.d.ts && dom-expressions-jsx-types --input ./src/jsx.d.ts --element \"SolidElement | Node | ArrayElement\" --import 'import type { Element as SolidElement } from \"solid-js\";'",
312
312
  "types:web": "tsc --project ./tsconfig.build.json",
313
313
  "types:copy-web": "ncp ../../node_modules/@dom-expressions/runtime/src/client.d.ts ./types/client.d.ts && ncp ../../node_modules/@dom-expressions/runtime/src/server.d.ts ./types/server.d.ts && ncp ../../node_modules/@dom-expressions/runtime/src/serializer.d.ts ./types/serializer.d.ts && ncp ../../node_modules/@dom-expressions/runtime/src/response.d.ts ./types/response.d.ts && ncp ./src/jsx.d.ts ./types/jsx.d.ts && ncp ./src/jsx-properties.d.ts ./types/jsx-properties.d.ts",
314
314
  "types:web-storage": "tsc --project ./storage/tsconfig.build.json",
315
+ "types:web-frames": "tsc --project ./frames/tsconfig.build.json",
315
316
  "types:copy-serialization": "node -e \"fs.mkdirSync('./serialization/types', { recursive: true }); fs.copyFileSync('../../node_modules/@dom-expressions/runtime/src/serializer.d.ts', './serialization/types/index.d.ts');\"",
316
317
  "types:copy-server-functions": "node -e \"fs.mkdirSync('./types/server-functions', { recursive: true }); for (const f of ['shared', 'client', 'server']) fs.copyFileSync('../../node_modules/@dom-expressions/runtime/src/server-functions/' + f + '.d.ts', './types/server-functions/' + f + '.d.ts');\"",
317
- "types:copy-frames": "node -e \"fs.mkdirSync('./types/frames', { recursive: true }); for (const f of ['frame-client', 'frame-transport', 'frame-sink', 'serializer']) fs.copyFileSync('../../node_modules/@dom-expressions/runtime/src/' + f + '.d.ts', './types/frames/' + f + '.d.ts'); for (const f of ['client', 'server']) fs.copyFileSync('./frames/types/' + f + '.d.ts', './types/frames/' + f + '.d.ts');\"",
318
+ "types:copy-frames": "node -e \"fs.mkdirSync('./types/frames', { recursive: true }); for (const f of ['frame-client', 'frame-transport', 'frame-sink', 'serializer']) fs.copyFileSync('../../node_modules/@dom-expressions/runtime/src/' + f + '.d.ts', './types/frames/' + f + '.d.ts'); for (const f of ['client', 'server']) fs.writeFileSync('./types/frames/' + f + '.d.ts', fs.readFileSync('./frames/types/' + f + '.d.ts', 'utf8').replaceAll('@dom-expressions/runtime/src/', './'));\"",
318
319
  "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs ./storage/types ./storage/types-cjs ./serialization/types ./serialization/types-cjs",
319
320
  "test": "vitest run && vitest run --config vite.config.server.mjs && vitest run --config vite.config.hydrate.mjs",
320
321
  "test:server": "vitest run --config vite.config.server.mjs",
@@ -332,18 +332,21 @@ async function decodeResponse(response, codecOptions) {
332
332
  const config = {
333
333
  provideEvent: undefined,
334
334
  collectFlightData: undefined,
335
+ transformResult: undefined,
335
336
  transformDirectResult: undefined,
336
337
  endpoint: "/_server"
337
338
  };
338
339
  function configureServerFunctionsServer({
339
340
  provideEvent,
340
341
  collectFlightData,
342
+ transformResult,
341
343
  transformDirectResult,
342
344
  endpoint,
343
345
  codec
344
346
  } = {}) {
345
347
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
346
348
  if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
349
+ if (transformResult !== undefined) config.transformResult = transformResult;
347
350
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
348
351
  if (endpoint !== undefined) config.endpoint = endpoint;
349
352
  if (codec !== undefined) configureServerFunctionsCodec(codec);
@@ -526,6 +529,7 @@ async function handleServerFunctionRequest(request, options = {}) {
526
529
  };
527
530
  const provide = options.provideEvent || provideEvent;
528
531
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
532
+ const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
529
533
  const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
530
534
  const parsed = await parseArguments(request, url, instance, codec);
531
535
  const headers = new Headers();
@@ -536,8 +540,8 @@ async function handleServerFunctionRequest(request, options = {}) {
536
540
  };
537
541
  return serverFunction(...parsed);
538
542
  });
539
- if (options.transformResult) {
540
- result = await options.transformResult(event, result, {
543
+ if (transformResult) {
544
+ result = await transformResult(event, result, {
541
545
  instance,
542
546
  request
543
547
  });
@@ -592,8 +596,8 @@ async function handleServerFunctionRequest(request, options = {}) {
592
596
  return encodeResult(result, headers, status, codec);
593
597
  } catch (x) {
594
598
  if (x instanceof Response || isResponseEnvelope(x)) {
595
- if (options.transformResult) {
596
- x = await options.transformResult(event, x, {
599
+ if (transformResult) {
600
+ x = await transformResult(event, x, {
597
601
  instance,
598
602
  request,
599
603
  thrown: true
@@ -330,18 +330,21 @@ async function decodeResponse(response, codecOptions) {
330
330
  const config = {
331
331
  provideEvent: undefined,
332
332
  collectFlightData: undefined,
333
+ transformResult: undefined,
333
334
  transformDirectResult: undefined,
334
335
  endpoint: "/_server"
335
336
  };
336
337
  function configureServerFunctionsServer({
337
338
  provideEvent,
338
339
  collectFlightData,
340
+ transformResult,
339
341
  transformDirectResult,
340
342
  endpoint,
341
343
  codec
342
344
  } = {}) {
343
345
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
344
346
  if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
347
+ if (transformResult !== undefined) config.transformResult = transformResult;
345
348
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
346
349
  if (endpoint !== undefined) config.endpoint = endpoint;
347
350
  if (codec !== undefined) configureServerFunctionsCodec(codec);
@@ -524,6 +527,7 @@ async function handleServerFunctionRequest(request, options = {}) {
524
527
  };
525
528
  const provide = options.provideEvent || provideEvent;
526
529
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
530
+ const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
527
531
  const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
528
532
  const parsed = await parseArguments(request, url, instance, codec);
529
533
  const headers = new Headers();
@@ -534,8 +538,8 @@ async function handleServerFunctionRequest(request, options = {}) {
534
538
  };
535
539
  return serverFunction(...parsed);
536
540
  });
537
- if (options.transformResult) {
538
- result = await options.transformResult(event, result, {
541
+ if (transformResult) {
542
+ result = await transformResult(event, result, {
539
543
  instance,
540
544
  request
541
545
  });
@@ -590,8 +594,8 @@ async function handleServerFunctionRequest(request, options = {}) {
590
594
  return encodeResult(result, headers, status, codec);
591
595
  } catch (x) {
592
596
  if (x instanceof Response || isResponseEnvelope(x)) {
593
- if (options.transformResult) {
594
- x = await options.transformResult(event, x, {
597
+ if (transformResult) {
598
+ x = await transformResult(event, x, {
595
599
  instance,
596
600
  request,
597
601
  thrown: true
@@ -1,53 +1,17 @@
1
- /**
2
- * @solidjs/web/frames client half. Consume frame streams into live DOM
3
- * boundaries. There is deliberately no server-component API here beyond
4
- * `installServerComponents()`: `dynamic` + server functions IS the client
5
- * surface — a server-function call whose response is a frame stream
6
- * resolves with a stable per-call-site component.
7
- *
8
- * Copied next to the runtime's frame d.ts files at publish (see
9
- * types:copy-frames), so the relative imports below resolve in-place.
10
- */
11
- export {
12
- createFrame,
13
- createFrameHost,
14
- createFrameInsertable,
15
- FRAME_APPLIED_EVENT
16
- } from "./frame-client.js";
17
- export type {
18
- Frame,
19
- FrameChunk,
20
- FrameHost,
21
- FrameHostOptions,
22
- FrameOptions,
23
- FrameWrite,
24
- Slot,
25
- SlotContext
26
- } from "./frame-client.js";
27
- export {
28
- FRAME_STREAM_HEADER,
29
- applyFrameResponse,
30
- isFrameStreamResponse,
31
- createServerComponentHandler
32
- } from "./frame-transport.js";
1
+ export { createFrame, createFrameHost, createFrameInsertable, FRAME_APPLIED_EVENT } from "./frame-client.js";
2
+ export { FRAME_STREAM_HEADER, applyFrameResponse, isFrameStreamResponse, createServerComponentHandler } from "./frame-transport.js";
33
3
  export { createJSONDataTable } from "./serializer.js";
34
- export type { JSONDataTable } from "./serializer.js";
35
-
36
- import type { FrameHost } from "./frame-client.js";
37
-
38
- /**
39
- * The app-wide frame host (created on first use): one chunk router, with
40
- * codec data tables rotated per response — deserializer cross-reference
41
- * space is stream-scoped by contract.
42
- */
43
- export function getFrameHost(): FrameHost;
44
-
4
+ export declare function getFrameHost(): any;
45
5
  /**
46
6
  * Installs the server-component transport policy on the server-function
47
- * client: boundary identity derives from the reactive owner captured at the
48
- * call site, frame-stream responses resolve to stable per-call-site
49
- * components, and document-SSR boundaries adopt their server-rendered
50
- * ranges. Call once in the client entry (the package is sideEffects:false
51
- * a bare import would be tree-shaken); call again to rebind a custom host.
7
+ * client: boundary identity derives from the reactive owner captured at
8
+ * each call site (`getOwner`), so distinct `dynamic()` sources get
9
+ * independent boundaries with nothing declared, refetches from the same
10
+ * source resolve to the identical component, and ownerless calls fall back
11
+ * to one boundary per function id.
12
+ *
13
+ * Call once in the client entry (an explicit call — the package is
14
+ * `sideEffects: false`, so a bare import would be tree-shaken away);
15
+ * call again to rebind to a custom host.
52
16
  */
53
- export function installServerComponents(host?: FrameHost): void;
17
+ export declare function installServerComponents(host?: any): void;
@@ -72,6 +72,14 @@ export interface FrameWrite {
72
72
 
73
73
  /** Context passed to a slot callback. */
74
74
  export interface SlotContext {
75
+ /**
76
+ * True only for the hydration-attach invocation of an adopted
77
+ * document-SSR range — the one call a consumer may answer with a claim
78
+ * (`existing` IS the server-rendered output for these args). Unset on
79
+ * stream-driven re-calls: those must render for real, or content the
80
+ * re-call displaced (e.g. `{$frame}` region ranges) is dropped.
81
+ */
82
+ adopted?: boolean;
75
83
  /**
76
84
  * Register cleanup for when this occurrence's range is removed from the
77
85
  * server content, or the owning frame is disposed.
@@ -203,3 +211,12 @@ export function createFrameInsertable(options: FrameOptions): {
203
211
  readonly frame: Frame | null;
204
212
  dispose(): void;
205
213
  };
214
+
215
+ /**
216
+ * Binds a frame to an EXISTING marker range — the document-SSR adoption
217
+ * path: the page already holds the server-rendered boundary between
218
+ * `frame:<id>:start`/`:end` comments; the frame constructed over it treats
219
+ * that content as its own (first stream morphs rather than materializes)
220
+ * and slots sync immediately, claiming their server-rendered DOM.
221
+ */
222
+ export function adoptFrameRange(start: Comment, end: Comment, options?: FrameOptions): Frame;
@@ -96,10 +96,11 @@ export interface ServerComponentHandlerOptions<C = unknown> {
96
96
  * key a WeakMap of boundaries (dying with their call sites); ownerless calls
97
97
  * fall back to one boundary per function id.
98
98
  */
99
- export function createServerComponentHandler<C>(
100
- options: ServerComponentHandlerOptions<C>
101
- ): {
99
+ export function createServerComponentHandler<C>(options: ServerComponentHandlerOptions<C>): {
102
100
  capture?(info: { id: string; meta: unknown }): unknown;
103
101
  intercept?(info: { id: string; meta: unknown; args: unknown[] }): C | undefined;
104
- handle(response: Response, ctx: { id: string; meta: unknown; args: unknown[]; context: unknown }): C | undefined;
102
+ handle(
103
+ response: Response,
104
+ ctx: { id: string; meta: unknown; args: unknown[]; context: unknown }
105
+ ): C | undefined;
105
106
  };
@@ -1,21 +1,2 @@
1
- /**
2
- * @solidjs/web/frames — server half. Render server components (functions
3
- * returned from server functions) to frame-chunk streams, serve them as
4
- * framed HTTP responses through the server-function handler's
5
- * transformResult hook, and render them inline during document SSR.
6
- *
7
- * Copied next to the runtime's frame d.ts files at publish (see
8
- * types:copy-frames), so the relative imports below resolve in-place.
9
- */
10
- export {
11
- renderToFrameStream,
12
- renderServerComponent,
13
- serverComponentResponse,
14
- frameTransformResult,
15
- createFrameSink,
16
- frameTransformDirectResult,
17
- ServerComponentPlugin,
18
- SERVER_COMPONENT_BOOTSTRAP
19
- } from "./frame-sink.js";
20
- export type { FrameAddress, FrameStream, FrameStreamOptions } from "./frame-sink.js";
1
+ export { renderToFrameStream, renderServerComponent, serverComponentResponse, frameTransformResult, createFrameSink, frameTransformDirectResult, ServerComponentPlugin, SERVER_COMPONENT_BOOTSTRAP } from "./frame-sink.js";
21
2
  export { FRAME_STREAM_HEADER, isFrameStreamResponse } from "./frame-transport.js";
@@ -102,6 +102,23 @@ export interface ServerFunctionsServerConfig {
102
102
  * router); per-handler `collectFlightData` options override it.
103
103
  */
104
104
  collectFlightData?: CollectFlightDataHook;
105
+ /**
106
+ * Server-wide default for the handler's `transformResult` (same contract
107
+ * — see `HandleServerFunctionRequestOptions`); a per-request option
108
+ * overrides it. Registering it here makes result policies (e.g. frames'
109
+ * `frameTransformResult`) work through generic dispatchers that call
110
+ * `handleServerFunctionRequest(request)` with no options.
111
+ */
112
+ transformResult?(
113
+ event: ServerFunctionEvent,
114
+ result: unknown,
115
+ context: { instance: string | null; request: Request; thrown?: boolean }
116
+ ): unknown | ResponseEnvelope | Promise<unknown | ResponseEnvelope>;
117
+ /**
118
+ * The in-process mirror of `transformResult` for direct (same-server)
119
+ * calls during document SSR — e.g. frames' `frameTransformDirectResult`.
120
+ */
121
+ transformDirectResult?(value: unknown, options: { id: string }): unknown;
105
122
  /**
106
123
  * Endpoint the HTTP handler is mounted on, used for the `url` of SSR'd
107
124
  * references (e.g. form actions) — must match the client configuration.
@@ -369,3 +369,20 @@ export function decodeResponse<T = unknown>(
369
369
  response: Response,
370
370
  codecOptions?: JSONCodecOptions
371
371
  ): Promise<T | undefined>;
372
+
373
+ /**
374
+ * Frame one payload for the server-function wire: a `;0x<len32>;` length
375
+ * prefix followed by the utf-8 data. Both transports (server-function
376
+ * responses and frame streams) share this framing.
377
+ */
378
+ export function createChunk(data: string): Uint8Array;
379
+
380
+ /**
381
+ * Incremental decoder for `createChunk` framing over a byte stream: `next()`
382
+ * yields one complete payload string per call (async-iterator result shape),
383
+ * buffering partial frames internally until their length prefix is satisfied.
384
+ */
385
+ export class ChunkReader {
386
+ constructor(stream: ReadableStream<Uint8Array>);
387
+ next(): Promise<{ done: boolean; value: string | undefined }>;
388
+ }
@@ -1,53 +1,17 @@
1
- /**
2
- * @solidjs/web/frames client half. Consume frame streams into live DOM
3
- * boundaries. There is deliberately no server-component API here beyond
4
- * `installServerComponents()`: `dynamic` + server functions IS the client
5
- * surface — a server-function call whose response is a frame stream
6
- * resolves with a stable per-call-site component.
7
- *
8
- * Copied next to the runtime's frame d.ts files at publish (see
9
- * types:copy-frames), so the relative imports below resolve in-place.
10
- */
11
- export {
12
- createFrame,
13
- createFrameHost,
14
- createFrameInsertable,
15
- FRAME_APPLIED_EVENT
16
- } from "./frame-client.cjs";
17
- export type {
18
- Frame,
19
- FrameChunk,
20
- FrameHost,
21
- FrameHostOptions,
22
- FrameOptions,
23
- FrameWrite,
24
- Slot,
25
- SlotContext
26
- } from "./frame-client.cjs";
27
- export {
28
- FRAME_STREAM_HEADER,
29
- applyFrameResponse,
30
- isFrameStreamResponse,
31
- createServerComponentHandler
32
- } from "./frame-transport.cjs";
1
+ export { createFrame, createFrameHost, createFrameInsertable, FRAME_APPLIED_EVENT } from "./frame-client.cjs";
2
+ export { FRAME_STREAM_HEADER, applyFrameResponse, isFrameStreamResponse, createServerComponentHandler } from "./frame-transport.cjs";
33
3
  export { createJSONDataTable } from "./serializer.cjs";
34
- export type { JSONDataTable } from "./serializer.cjs";
35
-
36
- import type { FrameHost } from "./frame-client.cjs";
37
-
38
- /**
39
- * The app-wide frame host (created on first use): one chunk router, with
40
- * codec data tables rotated per response — deserializer cross-reference
41
- * space is stream-scoped by contract.
42
- */
43
- export function getFrameHost(): FrameHost;
44
-
4
+ export declare function getFrameHost(): any;
45
5
  /**
46
6
  * Installs the server-component transport policy on the server-function
47
- * client: boundary identity derives from the reactive owner captured at the
48
- * call site, frame-stream responses resolve to stable per-call-site
49
- * components, and document-SSR boundaries adopt their server-rendered
50
- * ranges. Call once in the client entry (the package is sideEffects:false
51
- * a bare import would be tree-shaken); call again to rebind a custom host.
7
+ * client: boundary identity derives from the reactive owner captured at
8
+ * each call site (`getOwner`), so distinct `dynamic()` sources get
9
+ * independent boundaries with nothing declared, refetches from the same
10
+ * source resolve to the identical component, and ownerless calls fall back
11
+ * to one boundary per function id.
12
+ *
13
+ * Call once in the client entry (an explicit call — the package is
14
+ * `sideEffects: false`, so a bare import would be tree-shaken away);
15
+ * call again to rebind to a custom host.
52
16
  */
53
- export function installServerComponents(host?: FrameHost): void;
17
+ export declare function installServerComponents(host?: any): void;
@@ -72,6 +72,14 @@ export interface FrameWrite {
72
72
 
73
73
  /** Context passed to a slot callback. */
74
74
  export interface SlotContext {
75
+ /**
76
+ * True only for the hydration-attach invocation of an adopted
77
+ * document-SSR range — the one call a consumer may answer with a claim
78
+ * (`existing` IS the server-rendered output for these args). Unset on
79
+ * stream-driven re-calls: those must render for real, or content the
80
+ * re-call displaced (e.g. `{$frame}` region ranges) is dropped.
81
+ */
82
+ adopted?: boolean;
75
83
  /**
76
84
  * Register cleanup for when this occurrence's range is removed from the
77
85
  * server content, or the owning frame is disposed.
@@ -203,3 +211,12 @@ export function createFrameInsertable(options: FrameOptions): {
203
211
  readonly frame: Frame | null;
204
212
  dispose(): void;
205
213
  };
214
+
215
+ /**
216
+ * Binds a frame to an EXISTING marker range — the document-SSR adoption
217
+ * path: the page already holds the server-rendered boundary between
218
+ * `frame:<id>:start`/`:end` comments; the frame constructed over it treats
219
+ * that content as its own (first stream morphs rather than materializes)
220
+ * and slots sync immediately, claiming their server-rendered DOM.
221
+ */
222
+ export function adoptFrameRange(start: Comment, end: Comment, options?: FrameOptions): Frame;
@@ -96,10 +96,11 @@ export interface ServerComponentHandlerOptions<C = unknown> {
96
96
  * key a WeakMap of boundaries (dying with their call sites); ownerless calls
97
97
  * fall back to one boundary per function id.
98
98
  */
99
- export function createServerComponentHandler<C>(
100
- options: ServerComponentHandlerOptions<C>
101
- ): {
99
+ export function createServerComponentHandler<C>(options: ServerComponentHandlerOptions<C>): {
102
100
  capture?(info: { id: string; meta: unknown }): unknown;
103
101
  intercept?(info: { id: string; meta: unknown; args: unknown[] }): C | undefined;
104
- handle(response: Response, ctx: { id: string; meta: unknown; args: unknown[]; context: unknown }): C | undefined;
102
+ handle(
103
+ response: Response,
104
+ ctx: { id: string; meta: unknown; args: unknown[]; context: unknown }
105
+ ): C | undefined;
105
106
  };
@@ -1,21 +1,2 @@
1
- /**
2
- * @solidjs/web/frames — server half. Render server components (functions
3
- * returned from server functions) to frame-chunk streams, serve them as
4
- * framed HTTP responses through the server-function handler's
5
- * transformResult hook, and render them inline during document SSR.
6
- *
7
- * Copied next to the runtime's frame d.ts files at publish (see
8
- * types:copy-frames), so the relative imports below resolve in-place.
9
- */
10
- export {
11
- renderToFrameStream,
12
- renderServerComponent,
13
- serverComponentResponse,
14
- frameTransformResult,
15
- createFrameSink,
16
- frameTransformDirectResult,
17
- ServerComponentPlugin,
18
- SERVER_COMPONENT_BOOTSTRAP
19
- } from "./frame-sink.cjs";
20
- export type { FrameAddress, FrameStream, FrameStreamOptions } from "./frame-sink.cjs";
1
+ export { renderToFrameStream, renderServerComponent, serverComponentResponse, frameTransformResult, createFrameSink, frameTransformDirectResult, ServerComponentPlugin, SERVER_COMPONENT_BOOTSTRAP } from "./frame-sink.cjs";
21
2
  export { FRAME_STREAM_HEADER, isFrameStreamResponse } from "./frame-transport.cjs";
@@ -102,6 +102,23 @@ export interface ServerFunctionsServerConfig {
102
102
  * router); per-handler `collectFlightData` options override it.
103
103
  */
104
104
  collectFlightData?: CollectFlightDataHook;
105
+ /**
106
+ * Server-wide default for the handler's `transformResult` (same contract
107
+ * — see `HandleServerFunctionRequestOptions`); a per-request option
108
+ * overrides it. Registering it here makes result policies (e.g. frames'
109
+ * `frameTransformResult`) work through generic dispatchers that call
110
+ * `handleServerFunctionRequest(request)` with no options.
111
+ */
112
+ transformResult?(
113
+ event: ServerFunctionEvent,
114
+ result: unknown,
115
+ context: { instance: string | null; request: Request; thrown?: boolean }
116
+ ): unknown | ResponseEnvelope | Promise<unknown | ResponseEnvelope>;
117
+ /**
118
+ * The in-process mirror of `transformResult` for direct (same-server)
119
+ * calls during document SSR — e.g. frames' `frameTransformDirectResult`.
120
+ */
121
+ transformDirectResult?(value: unknown, options: { id: string }): unknown;
105
122
  /**
106
123
  * Endpoint the HTTP handler is mounted on, used for the `url` of SSR'd
107
124
  * references (e.g. form actions) — must match the client configuration.
@@ -369,3 +369,20 @@ export function decodeResponse<T = unknown>(
369
369
  response: Response,
370
370
  codecOptions?: JSONCodecOptions
371
371
  ): Promise<T | undefined>;
372
+
373
+ /**
374
+ * Frame one payload for the server-function wire: a `;0x<len32>;` length
375
+ * prefix followed by the utf-8 data. Both transports (server-function
376
+ * responses and frame streams) share this framing.
377
+ */
378
+ export function createChunk(data: string): Uint8Array;
379
+
380
+ /**
381
+ * Incremental decoder for `createChunk` framing over a byte stream: `next()`
382
+ * yields one complete payload string per call (async-iterator result shape),
383
+ * buffering partial frames internally until their length prefix is satisfied.
384
+ */
385
+ export class ChunkReader {
386
+ constructor(stream: ReadableStream<Uint8Array>);
387
+ next(): Promise<{ done: boolean; value: string | undefined }>;
388
+ }