@solidjs/web 2.0.0-beta.20 → 2.0.0-beta.22

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.
@@ -67,9 +67,55 @@ function configureServerFunctionsCodec(codec) {
67
67
  function getServerFunctionsCodec() {
68
68
  return codecConfig.codec;
69
69
  }
70
+ function subscribeFlightData(consumer) {
71
+ return () => {
72
+ };
73
+ }
74
+ const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
75
+ function getServerFunctionMetadata(fn) {
76
+ if (typeof fn !== "function") return undefined;
77
+ return fn[SERVER_FUNCTION_METADATA] || undefined;
78
+ }
79
+ function isServerFunction(fn) {
80
+ return typeof fn === "function" && !!fn[SERVER_FUNCTION_METADATA];
81
+ }
82
+ function withMeta(fn, meta) {
83
+ const metadata = getServerFunctionMetadata(fn);
84
+ if (!metadata) {
85
+ throw new Error("withMeta expects a server function reference");
86
+ }
87
+ Object.assign(metadata, meta);
88
+ return fn;
89
+ }
70
90
  const FUNCTION_HEADER = "X-Server-Function-Id";
91
+ const ERROR_HEADER = "X-Server-Function-Error";
92
+ const ERROR_HEADER_MARKER = "=?1?";
93
+ const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
94
+ function encodeErrorHeaderValue(value) {
95
+ let stripped = String(value).replace(/[\r\n]+/g, "");
96
+ if (!NEEDS_ENCODING.test(stripped) && !stripped.startsWith(ERROR_HEADER_MARKER) && stripped === stripped.trim()) {
97
+ return stripped;
98
+ }
99
+ if (typeof stripped.toWellFormed === "function") {
100
+ stripped = stripped.toWellFormed();
101
+ } else {
102
+ stripped = stripped.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
103
+ }
104
+ return ERROR_HEADER_MARKER + encodeURIComponent(stripped);
105
+ }
106
+ function decodeErrorHeaderValue(value) {
107
+ if (typeof value !== "string" || !value.startsWith(ERROR_HEADER_MARKER)) {
108
+ return value;
109
+ }
110
+ try {
111
+ return decodeURIComponent(value.slice(ERROR_HEADER_MARKER.length));
112
+ } catch {
113
+ return value;
114
+ }
115
+ }
71
116
  const INSTANCE_HEADER = "X-Server-Function-Instance";
72
117
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
118
+ const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
73
119
  const FILE_FORM_KEY = "__server_function_file__";
74
120
  const BodyFormat = {
75
121
  Serialized: "0",
@@ -282,14 +328,17 @@ async function decodeResponse(response, codecOptions) {
282
328
 
283
329
  const config = {
284
330
  provideEvent: undefined,
331
+ collectFlightData: undefined,
285
332
  endpoint: "/_server"
286
333
  };
287
334
  function configureServerFunctionsServer({
288
335
  provideEvent,
336
+ collectFlightData,
289
337
  endpoint,
290
338
  codec
291
339
  } = {}) {
292
340
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
341
+ if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
293
342
  if (endpoint !== undefined) config.endpoint = endpoint;
294
343
  if (codec !== undefined) configureServerFunctionsCodec(codec);
295
344
  }
@@ -300,6 +349,7 @@ function provideEvent(event, fn) {
300
349
  throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
301
350
  }
302
351
  const REGISTRATIONS = new Map();
352
+ const METHODS = new Map();
303
353
  function registerServerFunction(id, callback) {
304
354
  REGISTRATIONS.set(id, callback);
305
355
  return callback;
@@ -311,24 +361,30 @@ function getServerFunction(id) {
311
361
  }
312
362
  throw new Error("invalid server function: " + id);
313
363
  }
314
- function registerServerReference(id, fn) {
364
+ function registerServerReference(id, fn, name) {
315
365
  registerServerFunction(id, fn);
316
366
  return {
317
367
  id,
318
- fn
368
+ fn,
369
+ name
319
370
  };
320
371
  }
321
372
  function createServerReference({
322
373
  id,
323
- fn
374
+ fn,
375
+ name
324
376
  }) {
325
377
  if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function");
378
+ const metadata = name === undefined ? {} : {
379
+ name
380
+ };
326
381
  return new Proxy(fn, {
327
- get(target, prop, receiver) {
382
+ get(target, prop) {
383
+ if (prop === "id") return id;
328
384
  if (prop === "url") {
329
385
  return `${config.endpoint}?id=${encodeURIComponent(id)}`;
330
386
  }
331
- if (prop === "GET") return receiver;
387
+ if (prop === SERVER_FUNCTION_METADATA) return metadata;
332
388
  return target[prop];
333
389
  },
334
390
  apply(target, thisArg, args) {
@@ -347,6 +403,15 @@ function createServerReference({
347
403
  }
348
404
  });
349
405
  }
406
+ function GET(fn) {
407
+ if (!isServerFunction(fn) || typeof fn.id !== "string") {
408
+ throw new Error("GET expects a server function reference");
409
+ }
410
+ METHODS.set(fn.id, "GET");
411
+ return withMeta(fn, {
412
+ method: "GET"
413
+ });
414
+ }
350
415
  function getServerFunctionMeta() {
351
416
  const event = getRequestEvent();
352
417
  return event && event.locals.serverFunctionMeta;
@@ -360,7 +425,8 @@ function resolveFunctionId(request, url) {
360
425
  }
361
426
  async function parseArguments(request, url, instance, codec) {
362
427
  const parsed = [];
363
- if (!instance || request.method === "GET") {
428
+ const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
429
+ if (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized) {
364
430
  const args = url.searchParams.get("args");
365
431
  if (args) {
366
432
  const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
@@ -370,15 +436,23 @@ async function parseArguments(request, url, instance, codec) {
370
436
  }
371
437
  }
372
438
  if (request.method === "POST" && request.body !== null) {
373
- const format = request.headers.get(BODY_FORMAT_HEADER);
374
439
  const decoded = await extractBody(request.clone(), codec);
375
- if (format === BodyFormat.Serialized) {
440
+ if (bodyFormat === BodyFormat.Serialized) {
376
441
  return decoded;
377
442
  }
378
443
  parsed.push(decoded);
379
444
  }
380
445
  return parsed;
381
446
  }
447
+ async function foldFlightData(hook, event, headers, outcome) {
448
+ const data = await hook(event, outcome);
449
+ if (data === undefined) return outcome.value;
450
+ headers.set(SINGLE_FLIGHT_HEADER, "true");
451
+ return {
452
+ value: outcome.value,
453
+ data
454
+ };
455
+ }
382
456
  function serializedResponse(value, headers, codec) {
383
457
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
384
458
  headers.set("Content-Type", "text/plain");
@@ -421,11 +495,21 @@ async function handleServerFunctionRequest(request, options = {}) {
421
495
  status: 404
422
496
  });
423
497
  }
498
+ if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
499
+ return new Response(process.env.NODE_ENV === "development" ? `Method not allowed for server function: ${functionId}` : null, {
500
+ status: 405,
501
+ headers: {
502
+ Allow: "POST"
503
+ }
504
+ });
505
+ }
424
506
  const event = options.createEvent ? options.createEvent(request) : {
425
507
  request,
426
508
  locals: {}
427
509
  };
428
510
  const provide = options.provideEvent || provideEvent;
511
+ const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
512
+ const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
429
513
  const parsed = await parseArguments(request, url, instance, codec);
430
514
  const headers = new Headers();
431
515
  try {
@@ -442,6 +526,7 @@ async function handleServerFunctionRequest(request, options = {}) {
442
526
  });
443
527
  }
444
528
  let status = 200;
529
+ let metadata;
445
530
  if (isResponseEnvelope(result)) {
446
531
  const {
447
532
  response,
@@ -456,6 +541,7 @@ async function handleServerFunctionRequest(request, options = {}) {
456
541
  if (response && response.status && (response.status < 300 || response.status >= 400)) {
457
542
  status = response.status;
458
543
  }
544
+ metadata = response;
459
545
  result = value;
460
546
  } else if (result instanceof Response) {
461
547
  if (result.headers && result.headers.has("X-Content-Raw")) return result;
@@ -466,11 +552,21 @@ async function handleServerFunctionRequest(request, options = {}) {
466
552
  if (result.status && (result.status < 300 || result.status >= 400)) {
467
553
  status = result.status;
468
554
  }
555
+ metadata = result;
469
556
  if (result.body == null) {
470
557
  result = null;
471
558
  }
472
559
  }
473
560
  }
561
+ if (collectsFlight) {
562
+ result = await foldFlightData(flightHook, event, headers, {
563
+ id: functionId,
564
+ value: result,
565
+ response: metadata,
566
+ request,
567
+ thrown: false
568
+ });
569
+ }
474
570
  if (!instance) {
475
571
  if (options.handleNoJS) return options.handleNoJS(result, request, parsed);
476
572
  if (result instanceof Response) return result;
@@ -487,6 +583,7 @@ async function handleServerFunctionRequest(request, options = {}) {
487
583
  });
488
584
  }
489
585
  let status = 200;
586
+ let metadata;
490
587
  if (isResponseEnvelope(x)) {
491
588
  const {
492
589
  response,
@@ -498,6 +595,7 @@ async function handleServerFunctionRequest(request, options = {}) {
498
595
  if (response && response.status && (!instance || response.status < 300 || response.status >= 400)) {
499
596
  status = response.status;
500
597
  }
598
+ metadata = response;
501
599
  x = value;
502
600
  } else if (x instanceof Response) {
503
601
  if (x.headers) {
@@ -506,11 +604,21 @@ async function handleServerFunctionRequest(request, options = {}) {
506
604
  if (x.status && (!instance || x.status < 300 || x.status >= 400)) {
507
605
  status = x.status;
508
606
  }
607
+ metadata = x;
509
608
  if (x.body == null) {
510
609
  x = null;
511
610
  }
512
611
  }
513
- headers.set("X-Server-Function-Error", "true");
612
+ if (collectsFlight) {
613
+ x = await foldFlightData(flightHook, event, headers, {
614
+ id: functionId,
615
+ value: x,
616
+ response: metadata,
617
+ request,
618
+ thrown: true
619
+ });
620
+ }
621
+ headers.set(ERROR_HEADER, "true");
514
622
  if (!instance) {
515
623
  if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
516
624
  if (x instanceof Response) return x;
@@ -525,18 +633,27 @@ async function handleServerFunctionRequest(request, options = {}) {
525
633
  });
526
634
  }
527
635
  const error = x instanceof Error ? x.message : typeof x === "string" ? x : "true";
528
- headers.set("X-Server-Function-Error", error.replace(/[\r\n]+/g, ""));
636
+ headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
529
637
  return encodeResult(x, headers, 200, codec);
530
638
  }
531
639
  }
532
640
 
641
+ exports.ERROR_HEADER = ERROR_HEADER;
533
642
  exports.FUNCTION_HEADER = FUNCTION_HEADER;
643
+ exports.GET = GET;
534
644
  exports.INSTANCE_HEADER = INSTANCE_HEADER;
645
+ exports.SINGLE_FLIGHT_HEADER = SINGLE_FLIGHT_HEADER;
535
646
  exports.configureServerFunctionsServer = configureServerFunctionsServer;
536
647
  exports.createServerReference = createServerReference;
648
+ exports.decodeErrorHeaderValue = decodeErrorHeaderValue;
537
649
  exports.decodeResponse = decodeResponse;
650
+ exports.encodeErrorHeaderValue = encodeErrorHeaderValue;
538
651
  exports.getServerFunction = getServerFunction;
539
652
  exports.getServerFunctionMeta = getServerFunctionMeta;
653
+ exports.getServerFunctionMetadata = getServerFunctionMetadata;
540
654
  exports.handleServerFunctionRequest = handleServerFunctionRequest;
655
+ exports.isServerFunction = isServerFunction;
541
656
  exports.registerServerFunction = registerServerFunction;
542
657
  exports.registerServerReference = registerServerReference;
658
+ exports.subscribeFlightData = subscribeFlightData;
659
+ exports.withMeta = withMeta;
@@ -65,9 +65,55 @@ function configureServerFunctionsCodec(codec) {
65
65
  function getServerFunctionsCodec() {
66
66
  return codecConfig.codec;
67
67
  }
68
+ function subscribeFlightData(consumer) {
69
+ return () => {
70
+ };
71
+ }
72
+ const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
73
+ function getServerFunctionMetadata(fn) {
74
+ if (typeof fn !== "function") return undefined;
75
+ return fn[SERVER_FUNCTION_METADATA] || undefined;
76
+ }
77
+ function isServerFunction(fn) {
78
+ return typeof fn === "function" && !!fn[SERVER_FUNCTION_METADATA];
79
+ }
80
+ function withMeta(fn, meta) {
81
+ const metadata = getServerFunctionMetadata(fn);
82
+ if (!metadata) {
83
+ throw new Error("withMeta expects a server function reference");
84
+ }
85
+ Object.assign(metadata, meta);
86
+ return fn;
87
+ }
68
88
  const FUNCTION_HEADER = "X-Server-Function-Id";
89
+ const ERROR_HEADER = "X-Server-Function-Error";
90
+ const ERROR_HEADER_MARKER = "=?1?";
91
+ const NEEDS_ENCODING = /[^\x20-\x7e\xa0-\xff]/;
92
+ function encodeErrorHeaderValue(value) {
93
+ let stripped = String(value).replace(/[\r\n]+/g, "");
94
+ if (!NEEDS_ENCODING.test(stripped) && !stripped.startsWith(ERROR_HEADER_MARKER) && stripped === stripped.trim()) {
95
+ return stripped;
96
+ }
97
+ if (typeof stripped.toWellFormed === "function") {
98
+ stripped = stripped.toWellFormed();
99
+ } else {
100
+ stripped = stripped.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
101
+ }
102
+ return ERROR_HEADER_MARKER + encodeURIComponent(stripped);
103
+ }
104
+ function decodeErrorHeaderValue(value) {
105
+ if (typeof value !== "string" || !value.startsWith(ERROR_HEADER_MARKER)) {
106
+ return value;
107
+ }
108
+ try {
109
+ return decodeURIComponent(value.slice(ERROR_HEADER_MARKER.length));
110
+ } catch {
111
+ return value;
112
+ }
113
+ }
69
114
  const INSTANCE_HEADER = "X-Server-Function-Instance";
70
115
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
116
+ const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
71
117
  const FILE_FORM_KEY = "__server_function_file__";
72
118
  const BodyFormat = {
73
119
  Serialized: "0",
@@ -280,14 +326,17 @@ async function decodeResponse(response, codecOptions) {
280
326
 
281
327
  const config = {
282
328
  provideEvent: undefined,
329
+ collectFlightData: undefined,
283
330
  endpoint: "/_server"
284
331
  };
285
332
  function configureServerFunctionsServer({
286
333
  provideEvent,
334
+ collectFlightData,
287
335
  endpoint,
288
336
  codec
289
337
  } = {}) {
290
338
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
339
+ if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
291
340
  if (endpoint !== undefined) config.endpoint = endpoint;
292
341
  if (codec !== undefined) configureServerFunctionsCodec(codec);
293
342
  }
@@ -298,6 +347,7 @@ function provideEvent(event, fn) {
298
347
  throw new Error("No request event provider. Configure one with configureServerFunctionsServer({ provideEvent }).");
299
348
  }
300
349
  const REGISTRATIONS = new Map();
350
+ const METHODS = new Map();
301
351
  function registerServerFunction(id, callback) {
302
352
  REGISTRATIONS.set(id, callback);
303
353
  return callback;
@@ -309,24 +359,30 @@ function getServerFunction(id) {
309
359
  }
310
360
  throw new Error("invalid server function: " + id);
311
361
  }
312
- function registerServerReference(id, fn) {
362
+ function registerServerReference(id, fn, name) {
313
363
  registerServerFunction(id, fn);
314
364
  return {
315
365
  id,
316
- fn
366
+ fn,
367
+ name
317
368
  };
318
369
  }
319
370
  function createServerReference({
320
371
  id,
321
- fn
372
+ fn,
373
+ name
322
374
  }) {
323
375
  if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function");
376
+ const metadata = name === undefined ? {} : {
377
+ name
378
+ };
324
379
  return new Proxy(fn, {
325
- get(target, prop, receiver) {
380
+ get(target, prop) {
381
+ if (prop === "id") return id;
326
382
  if (prop === "url") {
327
383
  return `${config.endpoint}?id=${encodeURIComponent(id)}`;
328
384
  }
329
- if (prop === "GET") return receiver;
385
+ if (prop === SERVER_FUNCTION_METADATA) return metadata;
330
386
  return target[prop];
331
387
  },
332
388
  apply(target, thisArg, args) {
@@ -345,6 +401,15 @@ function createServerReference({
345
401
  }
346
402
  });
347
403
  }
404
+ function GET(fn) {
405
+ if (!isServerFunction(fn) || typeof fn.id !== "string") {
406
+ throw new Error("GET expects a server function reference");
407
+ }
408
+ METHODS.set(fn.id, "GET");
409
+ return withMeta(fn, {
410
+ method: "GET"
411
+ });
412
+ }
348
413
  function getServerFunctionMeta() {
349
414
  const event = getRequestEvent();
350
415
  return event && event.locals.serverFunctionMeta;
@@ -358,7 +423,8 @@ function resolveFunctionId(request, url) {
358
423
  }
359
424
  async function parseArguments(request, url, instance, codec) {
360
425
  const parsed = [];
361
- if (!instance || request.method === "GET") {
426
+ const bodyFormat = request.method === "POST" ? request.headers.get(BODY_FORMAT_HEADER) : null;
427
+ if (!instance || request.method === "GET" || bodyFormat !== BodyFormat.Serialized) {
362
428
  const args = url.searchParams.get("args");
363
429
  if (args) {
364
430
  const result = args.startsWith(";0x") ? await deserializeString(args, codec) : JSON.parse(args);
@@ -368,15 +434,23 @@ async function parseArguments(request, url, instance, codec) {
368
434
  }
369
435
  }
370
436
  if (request.method === "POST" && request.body !== null) {
371
- const format = request.headers.get(BODY_FORMAT_HEADER);
372
437
  const decoded = await extractBody(request.clone(), codec);
373
- if (format === BodyFormat.Serialized) {
438
+ if (bodyFormat === BodyFormat.Serialized) {
374
439
  return decoded;
375
440
  }
376
441
  parsed.push(decoded);
377
442
  }
378
443
  return parsed;
379
444
  }
445
+ async function foldFlightData(hook, event, headers, outcome) {
446
+ const data = await hook(event, outcome);
447
+ if (data === undefined) return outcome.value;
448
+ headers.set(SINGLE_FLIGHT_HEADER, "true");
449
+ return {
450
+ value: outcome.value,
451
+ data
452
+ };
453
+ }
380
454
  function serializedResponse(value, headers, codec) {
381
455
  headers.set(BODY_FORMAT_HEADER, BodyFormat.Serialized);
382
456
  headers.set("Content-Type", "text/plain");
@@ -419,11 +493,21 @@ async function handleServerFunctionRequest(request, options = {}) {
419
493
  status: 404
420
494
  });
421
495
  }
496
+ if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
497
+ return new Response(process.env.NODE_ENV === "development" ? `Method not allowed for server function: ${functionId}` : null, {
498
+ status: 405,
499
+ headers: {
500
+ Allow: "POST"
501
+ }
502
+ });
503
+ }
422
504
  const event = options.createEvent ? options.createEvent(request) : {
423
505
  request,
424
506
  locals: {}
425
507
  };
426
508
  const provide = options.provideEvent || provideEvent;
509
+ const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
510
+ const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
427
511
  const parsed = await parseArguments(request, url, instance, codec);
428
512
  const headers = new Headers();
429
513
  try {
@@ -440,6 +524,7 @@ async function handleServerFunctionRequest(request, options = {}) {
440
524
  });
441
525
  }
442
526
  let status = 200;
527
+ let metadata;
443
528
  if (isResponseEnvelope(result)) {
444
529
  const {
445
530
  response,
@@ -454,6 +539,7 @@ async function handleServerFunctionRequest(request, options = {}) {
454
539
  if (response && response.status && (response.status < 300 || response.status >= 400)) {
455
540
  status = response.status;
456
541
  }
542
+ metadata = response;
457
543
  result = value;
458
544
  } else if (result instanceof Response) {
459
545
  if (result.headers && result.headers.has("X-Content-Raw")) return result;
@@ -464,11 +550,21 @@ async function handleServerFunctionRequest(request, options = {}) {
464
550
  if (result.status && (result.status < 300 || result.status >= 400)) {
465
551
  status = result.status;
466
552
  }
553
+ metadata = result;
467
554
  if (result.body == null) {
468
555
  result = null;
469
556
  }
470
557
  }
471
558
  }
559
+ if (collectsFlight) {
560
+ result = await foldFlightData(flightHook, event, headers, {
561
+ id: functionId,
562
+ value: result,
563
+ response: metadata,
564
+ request,
565
+ thrown: false
566
+ });
567
+ }
472
568
  if (!instance) {
473
569
  if (options.handleNoJS) return options.handleNoJS(result, request, parsed);
474
570
  if (result instanceof Response) return result;
@@ -485,6 +581,7 @@ async function handleServerFunctionRequest(request, options = {}) {
485
581
  });
486
582
  }
487
583
  let status = 200;
584
+ let metadata;
488
585
  if (isResponseEnvelope(x)) {
489
586
  const {
490
587
  response,
@@ -496,6 +593,7 @@ async function handleServerFunctionRequest(request, options = {}) {
496
593
  if (response && response.status && (!instance || response.status < 300 || response.status >= 400)) {
497
594
  status = response.status;
498
595
  }
596
+ metadata = response;
499
597
  x = value;
500
598
  } else if (x instanceof Response) {
501
599
  if (x.headers) {
@@ -504,11 +602,21 @@ async function handleServerFunctionRequest(request, options = {}) {
504
602
  if (x.status && (!instance || x.status < 300 || x.status >= 400)) {
505
603
  status = x.status;
506
604
  }
605
+ metadata = x;
507
606
  if (x.body == null) {
508
607
  x = null;
509
608
  }
510
609
  }
511
- headers.set("X-Server-Function-Error", "true");
610
+ if (collectsFlight) {
611
+ x = await foldFlightData(flightHook, event, headers, {
612
+ id: functionId,
613
+ value: x,
614
+ response: metadata,
615
+ request,
616
+ thrown: true
617
+ });
618
+ }
619
+ headers.set(ERROR_HEADER, "true");
512
620
  if (!instance) {
513
621
  if (options.handleNoJS) return options.handleNoJS(x, request, parsed, true);
514
622
  if (x instanceof Response) return x;
@@ -523,9 +631,9 @@ async function handleServerFunctionRequest(request, options = {}) {
523
631
  });
524
632
  }
525
633
  const error = x instanceof Error ? x.message : typeof x === "string" ? x : "true";
526
- headers.set("X-Server-Function-Error", error.replace(/[\r\n]+/g, ""));
634
+ headers.set(ERROR_HEADER, encodeErrorHeaderValue(error));
527
635
  return encodeResult(x, headers, 200, codec);
528
636
  }
529
637
  }
530
638
 
531
- export { FUNCTION_HEADER, INSTANCE_HEADER, configureServerFunctionsServer, createServerReference, decodeResponse, getServerFunction, getServerFunctionMeta, handleServerFunctionRequest, registerServerFunction, registerServerReference };
639
+ export { ERROR_HEADER, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, configureServerFunctionsServer, createServerReference, decodeErrorHeaderValue, decodeResponse, encodeErrorHeaderValue, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
package/types/client.d.ts CHANGED
@@ -66,6 +66,24 @@ export function assign(
66
66
  ): void;
67
67
  export function setAttribute(node: Element, name: string, value: string): void;
68
68
  export function setAttributeNS(node: Element, namespace: string, name: string, value: string): void;
69
+ /**
70
+ * Register a consumer for compiler-emitted element claims. Compiled DOM
71
+ * output claims navigation-relevant elements (`a[href]`, `form[action]`) at
72
+ * creation, and compiler-owned writes to `href`/`action` re-invoke the same
73
+ * handlers — so handlers must be idempotent and must check the element's
74
+ * relevance themselves (rechecks can fire for any element whose
75
+ * `href`/`action` is written, e.g. `<link href>`). Handlers run under the
76
+ * reactive owner current at element creation; scope per-element state and
77
+ * cleanup through your own reactive system. Dormant until registered —
78
+ * without a handler the emitted claims are null checks. Returns an
79
+ * unregister function.
80
+ */
81
+ export function registerElementClaim(handler: (element: Element) => void): () => void;
82
+ /**
83
+ * Claim `node` for registered consumers (see `registerElementClaim`).
84
+ * Emitted by the compiler at element creation; idempotent by contract.
85
+ */
86
+ export function claimElement<T extends Element>(node: T): T;
69
87
  export function className(node: Element, value: JSX.ClassValue, prev?: JSX.ClassValue): void;
70
88
  export function setProperty(node: Element, name: string, value: any): void;
71
89
  export function setStyleProperty(node: Element, name: string, value: any): void;
package/types/jsx.d.ts CHANGED
@@ -1120,7 +1120,7 @@ export namespace JSX {
1120
1120
 
1121
1121
  interface AnchorHTMLAttributes<T> extends HTMLAttributes<T> {
1122
1122
  download?: string | EnumeratedAcceptsEmpty | RemoveAttribute;
1123
- href?: string | RemoveAttribute;
1123
+ href?: string | SerializableAttributeValue | RemoveAttribute;
1124
1124
  hreflang?: string | RemoveAttribute;
1125
1125
  ping?: string | RemoveAttribute;
1126
1126
  referrerpolicy?: HTMLReferrerPolicy | RemoveAttribute;
@@ -1128,6 +1128,21 @@ export namespace JSX {
1128
1128
  target?: "_self" | "_blank" | "_parent" | "_top" | (string & {}) | RemoveAttribute;
1129
1129
  type?: string | RemoveAttribute;
1130
1130
 
1131
+ // Client-side navigation contract. These attributes are inert markup on
1132
+ // their own — a routing integration that delegates anchor clicks (e.g.
1133
+ // @solidjs/router) reads them off the element at event time. Typed here
1134
+ // so plain `<a>` elements participate without per-router augmentation.
1135
+ /** Marks the anchor as a client-navigation link when the integration requires explicit opt-in. */
1136
+ link?: BooleanAttribute | RemoveAttribute;
1137
+ /** Serialized (JSON) history state pushed alongside the navigation. */
1138
+ state?: string | RemoveAttribute;
1139
+ /** Suppress scroll restoration/reset after the navigation. */
1140
+ noScroll?: BooleanAttribute | RemoveAttribute;
1141
+ /** Replace the current history entry instead of pushing a new one. */
1142
+ replace?: BooleanAttribute | RemoveAttribute;
1143
+ /** Route preload intent; `"false"` disables the integration's default eager preload. */
1144
+ preload?: boolean | "false" | RemoveAttribute;
1145
+
1131
1146
  /** @experimental */
1132
1147
  attributionsrc?: string | RemoveAttribute;
1133
1148
 
@@ -1147,7 +1162,7 @@ export namespace JSX {
1147
1162
  alt?: string | RemoveAttribute;
1148
1163
  coords?: string | RemoveAttribute;
1149
1164
  download?: string | EnumeratedAcceptsEmpty | RemoveAttribute;
1150
- href?: string | RemoveAttribute;
1165
+ href?: string | SerializableAttributeValue | RemoveAttribute;
1151
1166
  ping?: string | RemoveAttribute;
1152
1167
  referrerpolicy?: HTMLReferrerPolicy | RemoveAttribute;
1153
1168
  rel?: string | RemoveAttribute;
@@ -23,6 +23,32 @@ export class ResponseEnvelope<T = unknown> {
23
23
  */
24
24
  export function isResponseEnvelope(value: unknown): value is ResponseEnvelope;
25
25
 
26
+ /**
27
+ * Registered-symbol brand (`Symbol.for("solid.Href")`) marking URL-bearing
28
+ * values. Declared `unique symbol` type-side; the runtime value is the
29
+ * registered symbol, so separately bundled copies agree on identity.
30
+ */
31
+ export declare const HREF: unique symbol;
32
+
33
+ /**
34
+ * A URL-bearing value: coerces to its URL via `toString()` and carries the
35
+ * `HREF` registered-symbol brand. Integrations mint these (e.g. a router's
36
+ * typed path objects answer the brand from their proxy) and URL-accepting
37
+ * APIs like `redirect()` accept them alongside plain strings. The brand is
38
+ * what makes the type meaningful — every object has `toString()`.
39
+ */
40
+ export interface Href {
41
+ [HREF]: true;
42
+ toString(): string;
43
+ }
44
+
45
+ /**
46
+ * Whether `value` is an `Href`-branded URL-bearing value. Registered-symbol
47
+ * check, so it stays correct across duplicated module instances — same
48
+ * rationale as `isResponseEnvelope`.
49
+ */
50
+ export function isHref(value: unknown): value is Href;
51
+
26
52
  /** `ResponseInit` accepted by the response helpers, plus `revalidate`. */
27
53
  export interface ResponseHelperInit extends ResponseInit {
28
54
  /**
@@ -51,7 +77,7 @@ export interface ResponseHelperInit extends ResponseInit {
51
77
  * }
52
78
  * ```
53
79
  */
54
- export function redirect(url: string, init?: number | ResponseHelperInit): Response;
80
+ export function redirect(url: string | Href, init?: number | ResponseHelperInit): Response;
55
81
 
56
82
  /**
57
83
  * Empty response requesting revalidation of the named cache keys — all of