@webjsdev/server 0.8.82 → 0.8.83

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/server",
3
- "version": "0.8.82",
3
+ "version": "0.8.83",
4
4
  "type": "module",
5
5
  "description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
6
6
  "main": "index.js",
@@ -385,6 +385,28 @@ function textValuesOf(formData) {
385
385
  return out;
386
386
  }
387
387
 
388
+ /**
389
+ * A failure result with `values` completed from the submission (#1581).
390
+ *
391
+ * Every submitted text field is there, so a page refills a field with
392
+ * `value=${actionData?.values?.x}` without the action having to echo it, which
393
+ * is the one step generated and hand-written actions both keep forgetting. The
394
+ * action's own `values` win, so an action that normalizes or deliberately
395
+ * blanks a field (a password) still decides. A repeated name (a checkbox group)
396
+ * keeps its last value, as `textValuesOf` does; read the FormData for the full
397
+ * list. Files are left out. The author's object is copied, never mutated.
398
+ *
399
+ * @param {unknown} result
400
+ * @param {FormData} formData
401
+ * @returns {unknown}
402
+ */
403
+ function withSubmittedValues(result, formData) {
404
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return result;
405
+ const own = /** @type any */ (result).values;
406
+ const ownValues = own && typeof own === 'object' && !Array.isArray(own) ? own : {};
407
+ return { ...result, values: { ...textValuesOf(formData), ...ownValues } };
408
+ }
409
+
388
410
  /**
389
411
  * A page path exists but only renders: the method is the problem, not the url.
390
412
  * @returns {Response}
@@ -523,8 +545,9 @@ export async function runFormAction(route, params, url, req, ssrOpts, deps) {
523
545
  return await formActionErrorResponse(v.thrown, ssrOpts.dev);
524
546
  }
525
547
  // A structured failure envelope is a NORMAL result the page renders, so
526
- // it takes the 422 re-render path exactly as a failing action would.
527
- return ssrPage(route, params, url, { ...ssrOpts, req, actionData: v.result, status: 422 });
548
+ // it takes the 422 re-render path exactly as a failing action would,
549
+ // submitted values included (#1581).
550
+ return ssrPage(route, params, url, { ...ssrOpts, req, actionData: withSubmittedValues(v.result, formData), status: 422 });
528
551
  }
529
552
  args = [v.value];
530
553
  }
@@ -634,10 +657,11 @@ export async function runFormAction(route, params, url, req, ssrOpts, deps) {
634
657
  }
635
658
 
636
659
  // FAILURE: re-render the SAME page with the action result available on
637
- // ctx.actionData, status 422. Repopulation is the page author's job (native
638
- // `value=${actionData.values?.field}`).
660
+ // ctx.actionData, status 422. `actionData.values` carries every submitted
661
+ // text field (#1581), so `value=${actionData?.values?.field}` refills a field
662
+ // whether or not the action echoed it.
639
663
  const status = typeof result.status === 'number' && result.status >= 400 ? result.status : 422;
640
- const page = await ssrPage(route, params, url, { ...ssrOpts, req, actionData: result, status });
664
+ const page = await ssrPage(route, params, url, { ...ssrOpts, req, actionData: withSubmittedValues(result, formData), status });
641
665
  // A failure result can still have mutated (a partial write, then a rejected
642
666
  // second step), so the eviction above applies here too and this response, not
643
667
  // being a redirect, is one the router can actually read the header off.