@solidjs/web 2.0.0-beta.32 → 2.0.0-beta.33

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 (52) hide show
  1. package/dist/dev.cjs +51 -15
  2. package/dist/dev.js +47 -16
  3. package/dist/server.cjs +710 -71
  4. package/dist/server.js +707 -74
  5. package/dist/web.cjs +51 -15
  6. package/dist/web.js +47 -16
  7. package/frames/dist/client.cjs +181 -64
  8. package/frames/dist/client.dev.cjs +185 -64
  9. package/frames/dist/client.dev.js +183 -62
  10. package/frames/dist/client.js +179 -62
  11. package/frames/dist/server.cjs +969 -133
  12. package/frames/dist/server.js +971 -135
  13. package/package.json +17 -6
  14. package/serialization/decode/package.json +20 -0
  15. package/serialization/dist/decode.cjs +110 -0
  16. package/serialization/dist/decode.js +104 -0
  17. package/serialization/dist/serialization.cjs +98 -43
  18. package/serialization/dist/serialization.js +99 -44
  19. package/serialization/types/index.d.ts +18 -160
  20. package/serialization/types/serializer-decode.d.ts +182 -0
  21. package/serialization/types-cjs/index.d.cts +18 -160
  22. package/serialization/types-cjs/serializer-decode.d.cts +182 -0
  23. package/server-functions/dist/client.cjs +101 -105
  24. package/server-functions/dist/client.js +101 -105
  25. package/server-functions/dist/server.cjs +131 -107
  26. package/server-functions/dist/server.dev.cjs +131 -107
  27. package/server-functions/dist/server.dev.js +132 -108
  28. package/server-functions/dist/server.js +132 -108
  29. package/types/client.d.ts +23 -1
  30. package/types/cookies.d.ts +93 -0
  31. package/types/core.d.ts +1 -1
  32. package/types/frames/frame-client.d.ts +26 -0
  33. package/types/frames/frame-transport.d.ts +1 -1
  34. package/types/frames/serializer.d.ts +18 -160
  35. package/types/serializer-decode.d.ts +182 -0
  36. package/types/serializer.d.ts +18 -160
  37. package/types/server-functions/client.d.ts +1 -1
  38. package/types/server-functions/server.d.ts +1 -1
  39. package/types/server-functions/shared.d.ts +57 -1
  40. package/types/server.d.ts +21 -1
  41. package/types-cjs/client.d.cts +23 -1
  42. package/types-cjs/cookies.d.cts +93 -0
  43. package/types-cjs/core.d.cts +1 -1
  44. package/types-cjs/frames/frame-client.d.cts +26 -0
  45. package/types-cjs/frames/frame-transport.d.cts +1 -1
  46. package/types-cjs/frames/serializer.d.cts +18 -160
  47. package/types-cjs/serializer-decode.d.cts +182 -0
  48. package/types-cjs/serializer.d.cts +18 -160
  49. package/types-cjs/server-functions/client.d.cts +1 -1
  50. package/types-cjs/server-functions/server.d.cts +1 -1
  51. package/types-cjs/server-functions/shared.d.cts +57 -1
  52. package/types-cjs/server.d.cts +21 -1
@@ -1,8 +1,6 @@
1
- import { getOwner, onCleanup, createMemo, runWithOwner, createSignal, createRenderEffect, createLoadingBoundary, createOwner, sharedConfig } from 'solid-js';
1
+ import { getOwner, onCleanup, createMemo, runWithOwner, createSignal, createRenderEffect, createLoadingBoundary, createOwner, sharedConfig, materializeContainerTrace } from 'solid-js';
2
2
  import { insert } from '@solidjs/web';
3
- import { createPlugin, fromCrossJSON, Feature } from 'seroval';
4
3
  import { ChunkReader, frameAddress, SINGLE_FLIGHT_HEADER, deserializeStream, getServerFunctionsCodec, createChunk, getFlightDataConsumer, ERROR_HEADER, REVALIDATE_HEADER, configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
5
- import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
6
4
 
7
5
  const ELEMENT_NODE = 1;
8
6
  const TEXT_NODE = 3;
@@ -65,16 +63,32 @@ function chunkToRecords(chunk) {
65
63
  args: chunk.args
66
64
  }
67
65
  };
66
+ case "hole":
67
+ return {
68
+ [`hole:${chunk.key}`]: {
69
+ kind: "html",
70
+ value: chunk.html
71
+ }
72
+ };
73
+ case "attr":
74
+ return {
75
+ [`attr:${chunk.key}`]: {
76
+ kind: "attrs",
77
+ value: chunk.attrs,
78
+ removed: chunk.removed
79
+ }
80
+ };
68
81
  case "complete":
69
82
  return {
70
83
  ":complete": true
71
84
  };
72
85
  case "error":
73
- return chunk.key ? {
74
- [`seg:${chunk.key}:error`]: chunk.error
75
- } : {
86
+ if (!chunk.key) return {
76
87
  ":error": chunk.error
77
88
  };
89
+ return {
90
+ [`${/^lha?:/.test(chunk.key) ? "hole" : "seg"}:${chunk.key}:error`]: chunk.error
91
+ };
78
92
  default:
79
93
  return {};
80
94
  }
@@ -159,7 +173,10 @@ function createFrameHost(options = {}) {
159
173
  },
160
174
  resolve(ref, frameId) {
161
175
  return options.resolve ? options.resolve(ref, frameId) : undefined;
162
- }
176
+ },
177
+ revive: options.revive,
178
+ isContainer: options.isContainer,
179
+ prepareData: options.prepareData
163
180
  };
164
181
  }
165
182
  const FRAME_APPLIED_EVENT = "frame:applied";
@@ -175,6 +192,7 @@ class FrameImpl {
175
192
  #errorNotified = false;
176
193
  #revealed = new Set();
177
194
  #fallbackShown = new Set();
195
+ #appliedHoles = new Map();
178
196
  #slots;
179
197
  #mountedSlots = new Set();
180
198
  #slotCleanups = new Map();
@@ -275,6 +293,7 @@ class FrameImpl {
275
293
  #resetStreamState(root) {
276
294
  this.#revealed.clear();
277
295
  this.#fallbackShown.clear();
296
+ this.#appliedHoles.clear();
278
297
  this.#errorNotified = false;
279
298
  clearStreamRecords(this.#store, root);
280
299
  }
@@ -316,6 +335,24 @@ class FrameImpl {
316
335
  }
317
336
  }
318
337
  }
338
+ for (const key in this.#store) {
339
+ const record = this.#store[key];
340
+ if (!record || this.#appliedHoles.get(key) === record) continue;
341
+ if (key.startsWith("hole:")) {
342
+ if (key.endsWith(":error")) {
343
+ this.#appliedHoles.set(key, record);
344
+ console.error(`Live hole ${key.slice(5, -6)} failed on the server; latched:`, record);
345
+ } else if (this.#applyHole(key.slice(5), record.value)) {
346
+ this.#appliedHoles.set(key, record);
347
+ this.#applied(version, "morph");
348
+ }
349
+ } else if (key.startsWith("attr:")) {
350
+ if (this.#applyAttrs(key.slice(5), record.value, record.removed)) {
351
+ this.#appliedHoles.set(key, record);
352
+ this.#applied(version, "morph");
353
+ }
354
+ }
355
+ }
319
356
  for (const key in this.#store) {
320
357
  if (this.#processedAssets.has(key) || !key.endsWith(":assets")) continue;
321
358
  this.#processedAssets.add(key);
@@ -492,7 +529,7 @@ class FrameImpl {
492
529
  }
493
530
  props[key] = entry.element;
494
531
  } else {
495
- props[key] = value;
532
+ props[key] = host && host.revive ? host.revive(value) : value;
496
533
  }
497
534
  }
498
535
  return props;
@@ -520,6 +557,10 @@ class FrameImpl {
520
557
  if (isDataRef(va) && isDataRef(vb) && cache && key in cache) {
521
558
  const host = this.#options.host;
522
559
  const next = host ? host.resolve(vb, this.#options.id) : undefined;
560
+ if (host && host.isContainer && (host.isContainer(next) || host.isContainer(cache[key]))) {
561
+ if (next === cache[key]) continue;
562
+ return false;
563
+ }
523
564
  if (isAsyncLike(next) || isAsyncLike(cache[key])) return false;
524
565
  try {
525
566
  if (JSON.stringify(next) === JSON.stringify(cache[key])) continue;
@@ -628,6 +669,38 @@ class FrameImpl {
628
669
  if (ranges.size) for (const root of grafts) flushGrafts(root, ranges);
629
670
  }
630
671
  }
672
+ #applyHole(marker, html) {
673
+ if (!this.#hasContent) return false;
674
+ const open = findLiveTarget(this.#firstContent(), this.#end, n => n.nodeType === COMMENT_NODE && n.data === marker);
675
+ if (!open) return false;
676
+ const close = rangeClose(open, "lh:/" + marker.slice(3));
677
+ if (!close) {
678
+ {
679
+ console.error(`Live hole "${marker}" is missing its closing comment; update dropped. ` + `Likely an HTML-rewriting layer stripped it.`, open);
680
+ }
681
+ return false;
682
+ }
683
+ const claim = claimHandlers() ? this.#claimTree : null;
684
+ reconcileChildren(open.parentNode, parseFragment(html), open, close, claim);
685
+ return true;
686
+ }
687
+ #applyAttrs(addr, text, removed) {
688
+ if (!this.#hasContent) return false;
689
+ const el = findLiveTarget(this.#firstContent(), this.#end, n => n.nodeType === ELEMENT_NODE && n.getAttribute("data-lha") === addr);
690
+ if (!el) return false;
691
+ const parsed = parseFragment(`<i${text}></i>`).firstChild;
692
+ if (parsed) {
693
+ for (let i = 0; i < parsed.attributes.length; i++) {
694
+ const {
695
+ name,
696
+ value
697
+ } = parsed.attributes[i];
698
+ if (el.getAttribute(name) !== value) el.setAttribute(name, value);
699
+ }
700
+ }
701
+ if (removed) for (const name of removed) el.removeAttribute(name);
702
+ return true;
703
+ }
631
704
  #clearContent() {
632
705
  removeUntil(this.#parent(), this.#firstContent(), this.#end);
633
706
  }
@@ -823,6 +896,20 @@ function rangeClose(start, id) {
823
896
  }
824
897
  return null;
825
898
  }
899
+ function findLiveTarget(n, end, test) {
900
+ while (n && n !== end) {
901
+ if (n.nodeType === ELEMENT_NODE) {
902
+ const fid = isFrameElement(n) ? n.getAttribute(FRAME_ID_ATTR) : null;
903
+ if (fid === null || fid.includes(".")) {
904
+ if (test(n)) return n;
905
+ const found = findLiveTarget(n.firstChild, null, test);
906
+ if (found) return found;
907
+ }
908
+ } else if (test(n)) return n;
909
+ n = n.nextSibling;
910
+ }
911
+ return null;
912
+ }
826
913
  function findPlaceholder(n, end, id) {
827
914
  while (n && n !== end) {
828
915
  if (isPlaceholderStart(n, id)) return n;
@@ -882,7 +969,9 @@ function eachInRange(start, key, cb) {
882
969
  }
883
970
  function clearStreamRecords(records, root) {
884
971
  for (const key in records) {
885
- if (key.startsWith("seg:") || key === ":error" || root && key === "") delete records[key];
972
+ if (/^(seg|hole|attr):/.test(key) || key === ":error" || root && key === "") {
973
+ delete records[key];
974
+ }
886
975
  }
887
976
  }
888
977
  function argsEquivalent(a, b) {
@@ -1147,6 +1236,7 @@ async function applyFrameResponse(response, host, options = {}) {
1147
1236
  if (v === undefined) perFrame.set(chunk.id, v = version(chunk.id));
1148
1237
  chunk.version = v;
1149
1238
  } else if (version !== undefined) chunk.version = version;
1239
+ if (chunk.type === "data" && host.prepareData) await host.prepareData();
1150
1240
  host.apply(chunk);
1151
1241
  }
1152
1242
  result = await reader.next();
@@ -1163,7 +1253,7 @@ function parseServerComponent(value, ctx) {
1163
1253
  address: ctx.parse(value[SERVER_COMPONENT_ADDRESS])
1164
1254
  };
1165
1255
  }
1166
- const ServerComponentPlugin = /*#__PURE__*/createPlugin({
1256
+ const ServerComponentPlugin = {
1167
1257
  tag: "dom-expressions/server-component",
1168
1258
  test(value) {
1169
1259
  return typeof value === "function" && SERVER_COMPONENT in value;
@@ -1190,12 +1280,10 @@ const ServerComponentPlugin = /*#__PURE__*/createPlugin({
1190
1280
  }
1191
1281
  return globalThis._$SC.r(id);
1192
1282
  }
1193
- });
1283
+ };
1194
1284
  function flightCodec(codec) {
1195
1285
  const plugins = codec && codec.plugins || [];
1196
- for (const plugin of plugins) {
1197
- if (plugin && plugin.tag === "dom-expressions/server-component") return codec;
1198
- }
1286
+ if (plugins.some(plugin => plugin && plugin.tag === ServerComponentPlugin.tag)) return codec;
1199
1287
  return {
1200
1288
  ...codec,
1201
1289
  plugins: [...plugins, ServerComponentPlugin]
@@ -1312,72 +1400,74 @@ function createServerComponentHandler({
1312
1400
  }
1313
1401
  }
1314
1402
 
1315
- Feature.AggregateError | Feature.BigIntTypedArray;
1316
- const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
1317
- CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
1318
- FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
1319
- function resolveSerializerPlugins(customPlugins) {
1320
- return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
1321
- }
1322
- const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
1323
- const JSON_CODEC_DEPTH_LIMIT = 64;
1324
- function resolveCodecOptions({
1325
- plugins,
1326
- disabledFeatures,
1327
- depthLimit
1328
- } = {}) {
1329
- return {
1330
- plugins: resolveSerializerPlugins(plugins),
1331
- disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
1332
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
1333
- };
1403
+ const STATE = Symbol.for("dom-expressions.container-trace-state");
1404
+ const state = globalThis[STATE] || (globalThis[STATE] = {
1405
+ materialized: new WeakMap(),
1406
+ materializedValues: new WeakSet()
1407
+ });
1408
+ function setContainerTraceMaterializer(fn) {
1409
+ state.materializeTrace = fn;
1334
1410
  }
1335
- function createJSONDeserializer(options) {
1336
- const refs = new Map();
1337
- const resolved = resolveCodecOptions(options);
1338
- return function deserializeJSONChunk(node) {
1339
- return fromCrossJSON(node, {
1340
- refs,
1341
- ...resolved
1342
- });
1343
- };
1411
+ function materialize(marker) {
1412
+ let value = state.materialized.get(marker.$tr);
1413
+ if (value === undefined) {
1414
+ value = state.materializeTrace(marker);
1415
+ state.materialized.set(marker.$tr, value);
1416
+ if (value !== null && typeof value === "object") state.materializedValues.add(value);
1417
+ }
1418
+ return value;
1344
1419
  }
1345
- function createJSONDataTable(options) {
1346
- const deserialize = createJSONDeserializer(options);
1347
- const table = new Map();
1348
- return {
1349
- apply(record) {
1350
- const value = deserialize(record.node);
1351
- if (record.initial) table.set(record.key, value);
1352
- },
1353
- get(key) {
1354
- return table.get(key);
1355
- },
1356
- resolve(ref) {
1357
- return table.get(ref.$ref);
1358
- }
1359
- };
1420
+ function isMaterializedContainer(value) {
1421
+ return value !== null && typeof value === "object" && state.materializedValues.has(value);
1422
+ }
1423
+ function isContainerTraceMarker(value) {
1424
+ return value != null && typeof value === "object" && value.$tr != null && typeof value.$tr[Symbol.asyncIterator] === "function";
1425
+ }
1426
+ function reviveContainerTraces(value) {
1427
+ if (!state.materializeTrace || value == null || typeof value !== "object") return value;
1428
+ if (isContainerTraceMarker(value)) return materialize(value);
1429
+ if (Array.isArray(value)) {
1430
+ for (let i = 0; i < value.length; i++) value[i] = reviveContainerTraces(value[i]);
1431
+ } else if (Object.getPrototypeOf(value) === Object.prototype) {
1432
+ for (const key of Object.keys(value)) value[key] = reviveContainerTraces(value[key]);
1433
+ }
1434
+ return value;
1360
1435
  }
1361
1436
 
1437
+ setContainerTraceMaterializer(materializeContainerTrace);
1362
1438
  function asyncArg(value) {
1363
1439
  return value;
1364
1440
  }
1365
1441
  let sharedHost;
1442
+ let codec;
1443
+ let codecLoading;
1444
+ function loadCodec() {
1445
+ return codecLoading ??= import('@solidjs/web/serialization/decode').then(m => {
1446
+ codec = m;
1447
+ });
1448
+ }
1366
1449
  const tables = new Map();
1450
+ function ensureTable(root) {
1451
+ let table = tables.get(root);
1452
+ if (!table && codec) tables.set(root, table = codec.createJSONDataTable());
1453
+ return table;
1454
+ }
1367
1455
  function tableFor(id) {
1368
- const table = tables.get(id);
1369
- if (table) return table;
1370
- for (const [root, t] of tables) if (id.startsWith(root + ".")) return t;
1456
+ if (tables.has(id)) return ensureTable(id);
1457
+ for (const root of tables.keys()) if (id.startsWith(root + ".")) return ensureTable(root);
1371
1458
  return undefined;
1372
1459
  }
1373
1460
  function beginStream(frameId) {
1374
- tables.set(frameId, createJSONDataTable());
1461
+ tables.set(frameId, undefined);
1375
1462
  }
1376
1463
  function getFrameHost() {
1377
1464
  if (!sharedHost) {
1378
1465
  sharedHost = createFrameHost({
1466
+ prepareData: loadCodec,
1379
1467
  applyData: c => tableFor(c.id)?.apply(c),
1380
- resolve: (ref, id) => tableFor(id)?.resolve(ref)
1468
+ resolve: (ref, id) => tableFor(id)?.resolve(ref),
1469
+ revive: reviveContainerTraces,
1470
+ isContainer: isMaterializedContainer
1381
1471
  });
1382
1472
  }
1383
1473
  return sharedHost;
@@ -1449,6 +1539,7 @@ function slotArgsProxy(args) {
1449
1539
  return new Proxy({}, {
1450
1540
  get: (_, key) => {
1451
1541
  const v = args()[key];
1542
+ if (isMaterializedContainer(v)) return v;
1452
1543
  if (!isAsyncValue(v)) return v;
1453
1544
  let read = asyncReads.get(key);
1454
1545
  if (!read) {
@@ -1600,6 +1691,24 @@ function boundaryComponent(host, fnId) {
1600
1691
  };
1601
1692
  }
1602
1693
  const claimedBoundaries = new Set();
1694
+ const liveOps = new Map();
1695
+ const liveAppliers = new Set();
1696
+ let livePumped = null;
1697
+ function pumpLiveChannel() {
1698
+ const stream = globalThis._$HY?.r?.["sc:live"];
1699
+ if (!stream || stream === livePumped || typeof stream.getReader !== "function") return;
1700
+ livePumped = stream;
1701
+ const reader = stream.getReader();
1702
+ const pump = () => reader.read().then(r => {
1703
+ if (r.done) return;
1704
+ const op = r.value;
1705
+ liveOps.set(`${op.type}:${op.fid || ""}:${op.key || ""}`, op);
1706
+ for (const apply of liveAppliers) apply(op);
1707
+ return pump();
1708
+ });
1709
+ pump().catch(() => {
1710
+ });
1711
+ }
1603
1712
  let boundaryIndex = null;
1604
1713
  const isBoundaryId = id => !id.includes(".");
1605
1714
  function indexBoundaries(root) {
@@ -1678,6 +1787,7 @@ function adoptBoundary(host, id, el, props, binding) {
1678
1787
  const drainRecords = () => {
1679
1788
  const hy = globalThis._$HY;
1680
1789
  if (!hy || !hy.r) return;
1790
+ pumpLiveChannel();
1681
1791
  const slotPrefix = `sc:slot:${id}:`;
1682
1792
  for (const key of Object.keys(hy.r)) {
1683
1793
  if (appliedRecords.has(key)) continue;
@@ -1712,11 +1822,22 @@ function adoptBoundary(host, id, el, props, binding) {
1712
1822
  if (fr.claim && parent && el.contains(parent)) claimRegionFragments(parent);
1713
1823
  drainRecords();
1714
1824
  }) : undefined;
1825
+ const applyLiveOp = op => {
1826
+ if (op.type === "slot" && op.fid !== id) return;
1827
+ host.apply({
1828
+ ...op,
1829
+ id: address,
1830
+ version: 0
1831
+ });
1832
+ };
1833
+ liveAppliers.add(applyLiveOp);
1715
1834
  onCleanup(() => {
1835
+ liveAppliers.delete(applyLiveOp);
1716
1836
  unsubscribe && unsubscribe();
1717
1837
  if (fr && fr.release) for (const fragId of claimedFragments) fr.release(fragId);
1718
1838
  });
1719
1839
  drainRecords();
1840
+ for (const op of liveOps.values()) applyLiveOp(op);
1720
1841
  const owner = getOwner();
1721
1842
  let release;
1722
1843
  let setGate;