@barefootjs/hono 0.31.6 → 0.31.7
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/adapter/hono-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +2 -2
- package/dist/conformance-pins.d.ts +5 -7
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/utils.d.ts +30 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +15 -0
- package/dist/vite.js +6 -2
- package/package.json +2 -2
- package/src/__tests__/serialize-hydration-props.test.ts +103 -0
- package/src/adapter/hono-adapter.ts +4 -3
- package/src/conformance-pins.ts +6 -7
- package/src/utils.ts +69 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,MAAM,EAIX,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEf,UAAU,EAQX,MAAM,iBAAiB,CAAA;AAExB;;;;;GAKG;AACH,KAAK,aAAa,GAAG;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAGD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAuBD,qBAAa,WAAY,SAAQ,UAAW,YAAW,aAAa,CAAC,aAAa,CAAC;IACjF,IAAI,SAAS;IACb,SAAS,SAAS;IAClB,gBAAgB,SAAiC;IAmBjD,mBAAmB,QAAO,OAAO,CAAQ;IAEzC,SAAS,CAAC,SAAS,EAAE,gBAAgB,CAA0B;IAE/D,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,sBAAsB,CAAiB;IAC/C,OAAO,CAAC,wBAAwB,CAAiB;IACjD;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB,CAAC,CAAgC;IAC9D,uFAAuF;IACvF,OAAO,CAAC,YAAY,CAAmD;IACvE;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,YAAY,CAAC,CAAU;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,aAAa,CAAC,CAAU;IAEhC,YAAY,OAAO,GAAE,kBAAuB,EAQ3C;IAED,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,aAAa,CAiEzE;IAED;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAIvB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter/index.js
CHANGED
|
@@ -95,7 +95,7 @@ export default ${this.componentName}` : "";
|
|
|
95
95
|
generateImports(ir, componentCode) {
|
|
96
96
|
const lines = [];
|
|
97
97
|
const utilImports = [];
|
|
98
|
-
for (const util of ["bfComment", "bfText", "bfTextEnd"]) {
|
|
98
|
+
for (const util of ["bfComment", "bfText", "bfTextEnd", "serializeHydrationProps"]) {
|
|
99
99
|
if (new RegExp(`\\b${util}\\b`).test(componentCode)) {
|
|
100
100
|
utilImports.push(util);
|
|
101
101
|
}
|
|
@@ -261,7 +261,7 @@ export default ${this.componentName}` : "";
|
|
|
261
261
|
const callerKey = p.sourceName ?? p.name;
|
|
262
262
|
lines.push(` if (typeof ${propAccess} !== 'function' && !(typeof ${propAccess} === 'object' && ${propAccess} !== null && 'isEscaped' in ${propAccess})) __hydrateProps['${callerKey}'] = ${propAccess}`);
|
|
263
263
|
}
|
|
264
|
-
lines.push(` const __bfPropsJson = __bfParentProps || (
|
|
264
|
+
lines.push(` const __bfPropsJson = __bfParentProps || serializeHydrationProps(__hydrateProps, '${name}')`);
|
|
265
265
|
} else if (hasClientInteractivity && isRootComponent) {
|
|
266
266
|
lines.push("");
|
|
267
267
|
lines.push(` const __bfPropsJson = __bfParentProps`);
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
* Per-fixture build-time contracts for shapes the Hono adapter
|
|
3
3
|
* intentionally refuses to lower. Hono's SSR runtime is JS — its
|
|
4
4
|
* `acceptsTemplateCall` is broad enough to cover every adapter-specific
|
|
5
|
-
* lowering gap, so
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the call (#2273). Consumed by this package's own conformance test (as
|
|
11
|
-
* `expectedDiagnostics`) and by `bf compat` (issue-URL attribution).
|
|
5
|
+
* lowering gap, so the only pins here are compiler-level refusals that
|
|
6
|
+
* fire ahead of `adapter.generate()` and apply identically everywhere.
|
|
7
|
+
* `date-method-uncatalogued` stays pinned even here: hydrate-init
|
|
8
|
+
* re-evaluates the expression against a JSON-de-riched receiver, so a
|
|
9
|
+
* Hono-specific carve-out was evaluated and rejected (#2356).
|
|
12
10
|
*/
|
|
13
11
|
import type { ConformancePins } from '@barefootjs/jsx';
|
|
14
12
|
export declare const conformancePins: ConformancePins;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eAG7B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -95,7 +95,7 @@ export default ${this.componentName}` : "";
|
|
|
95
95
|
generateImports(ir, componentCode) {
|
|
96
96
|
const lines = [];
|
|
97
97
|
const utilImports = [];
|
|
98
|
-
for (const util of ["bfComment", "bfText", "bfTextEnd"]) {
|
|
98
|
+
for (const util of ["bfComment", "bfText", "bfTextEnd", "serializeHydrationProps"]) {
|
|
99
99
|
if (new RegExp(`\\b${util}\\b`).test(componentCode)) {
|
|
100
100
|
utilImports.push(util);
|
|
101
101
|
}
|
|
@@ -261,7 +261,7 @@ export default ${this.componentName}` : "";
|
|
|
261
261
|
const callerKey = p.sourceName ?? p.name;
|
|
262
262
|
lines.push(` if (typeof ${propAccess} !== 'function' && !(typeof ${propAccess} === 'object' && ${propAccess} !== null && 'isEscaped' in ${propAccess})) __hydrateProps['${callerKey}'] = ${propAccess}`);
|
|
263
263
|
}
|
|
264
|
-
lines.push(` const __bfPropsJson = __bfParentProps || (
|
|
264
|
+
lines.push(` const __bfPropsJson = __bfParentProps || serializeHydrationProps(__hydrateProps, '${name}')`);
|
|
265
265
|
} else if (hasClientInteractivity && isRootComponent) {
|
|
266
266
|
lines.push("");
|
|
267
267
|
lines.push(` const __bfPropsJson = __bfParentProps`);
|
|
@@ -668,7 +668,8 @@ export default ${this.componentName}` : "";
|
|
|
668
668
|
var honoAdapter = new HonoAdapter;
|
|
669
669
|
// src/conformance-pins.ts
|
|
670
670
|
var conformancePins = {
|
|
671
|
-
"date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2356" }]
|
|
671
|
+
"date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2356" }],
|
|
672
|
+
"rich-prop-client-read": [{ code: "BF049", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2648" }]
|
|
672
673
|
};
|
|
673
674
|
export {
|
|
674
675
|
honoAdapter,
|
package/dist/utils.d.ts
CHANGED
|
@@ -13,4 +13,34 @@ export declare function bfText(slotId: string): import("hono/utils/html").HtmlEs
|
|
|
13
13
|
* Renders <!--/-->
|
|
14
14
|
*/
|
|
15
15
|
export declare function bfTextEnd(): import("hono/utils/html").HtmlEscapedString;
|
|
16
|
+
/**
|
|
17
|
+
* Serialize a component's hydration props into the `bf-p` JSON payload,
|
|
18
|
+
* throwing a clear, actionable error instead of `JSON.stringify`'s opaque
|
|
19
|
+
* `TypeError` (for a `BigInt`) or silently succeeding with data loss (for a
|
|
20
|
+
* `Map`/`Set`/etc.) — the runtime backstop for #2643's compile-time check
|
|
21
|
+
* (`checkRichTypePropSerialization`, BF049), which can only catch a prop
|
|
22
|
+
* whose type is PROVABLE from `propsType`. An imported/aliased type
|
|
23
|
+
* (`type Timestamp = Map<…>`), or a prop typed too loosely to resolve
|
|
24
|
+
* statically, reaches this function uncaught by BF049, so it needs its own
|
|
25
|
+
* check here to avoid regressing to the pre-#2643 failure modes.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately checks only TOP-LEVEL prop values, not nested shapes —
|
|
28
|
+
* `JSON.stringify`'s existing deep-degradation behavior for a nested rich
|
|
29
|
+
* value (e.g. a `RegExp` inside a plain object) is unchanged, matching a
|
|
30
|
+
* real, shipped, TOLERATED pattern in this repo (`site/ui`'s InputOTP demo
|
|
31
|
+
* passes a live `RegExp` `pattern` prop that degrades to `{}` today; the
|
|
32
|
+
* component's own design accounts for that). Widening this to a deep walk
|
|
33
|
+
* would turn that accepted degradation into a new SSR 500.
|
|
34
|
+
*
|
|
35
|
+
* The throw set is narrower than BF049's flag set, but NOT because
|
|
36
|
+
* `RegExp` / `Error` / `URLSearchParams` preserve more data than a `Map` or
|
|
37
|
+
* `Set` — `JSON.stringify` degrades `RegExp` and `Error` to `{}` exactly
|
|
38
|
+
* like a `Map`/`Set` (only `URLSearchParams` happens to round-trip its
|
|
39
|
+
* key/value pairs as a plain object). They're excluded from the runtime
|
|
40
|
+
* throw purely to avoid a breaking behavior change: `site/ui`'s InputOTP
|
|
41
|
+
* demo already ships a live `RegExp` `pattern` prop that degrades to `{}`
|
|
42
|
+
* today, and the component's own design tolerates that. They stay
|
|
43
|
+
* diagnosable at compile time (BF049) only, not a runtime hard-stop.
|
|
44
|
+
*/
|
|
45
|
+
export declare function serializeHydrationProps(props: Record<string, unknown>, componentName: string): string | undefined;
|
|
16
46
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,+CAEpC;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,+CAEpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,gDAExB"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,+CAEpC;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,+CAEpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,gDAExB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsCjH"}
|
package/dist/utils.js
CHANGED
|
@@ -9,7 +9,22 @@ function bfText(slotId) {
|
|
|
9
9
|
function bfTextEnd() {
|
|
10
10
|
return raw("<!--/-->");
|
|
11
11
|
}
|
|
12
|
+
function serializeHydrationProps(props, componentName) {
|
|
13
|
+
const keys = Object.keys(props);
|
|
14
|
+
if (keys.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
const value = props[key];
|
|
18
|
+
const offender = typeof value === "bigint" ? "BigInt" : typeof value === "symbol" ? "Symbol" : value instanceof Map ? "Map" : value instanceof Set ? "Set" : value instanceof WeakMap ? "WeakMap" : value instanceof WeakSet ? "WeakSet" : value instanceof Promise ? "Promise" : null;
|
|
19
|
+
if (offender !== null) {
|
|
20
|
+
const consequence = offender === "BigInt" ? "" : offender === "Symbol" ? " (the prop would be silently omitted from the payload entirely)" : offender === "Promise" ? " (its eventual value would be silently unreachable — JSON.stringify can't wait for a promise to settle)" : " (its entries would be silently dropped, serializing to {})";
|
|
21
|
+
throw new TypeError(`[barefootjs] Cannot serialize prop '${key}' of <${componentName}> for hydration: a ${offender} does not ` + `survive the bf-p JSON boundary${consequence}. Pre-compute a JSON-safe value (string/number/array/plain ` + `object) server-side and pass that instead. See BF049 (https://github.com/piconic-ai/barefootjs/issues/2643).`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(props);
|
|
25
|
+
}
|
|
12
26
|
export {
|
|
27
|
+
serializeHydrationProps,
|
|
13
28
|
bfTextEnd,
|
|
14
29
|
bfText,
|
|
15
30
|
bfComment
|
package/dist/vite.js
CHANGED
|
@@ -433,6 +433,7 @@ var ErrorCodes = {
|
|
|
433
433
|
COMPONENT_REQUIRED_PROP_MISSING: "BF046",
|
|
434
434
|
JSX_BRANCH_LOCAL_IN_CALLBACK: "BF047",
|
|
435
435
|
SIBLING_COMPONENT_NOT_COMPILED: "BF048",
|
|
436
|
+
RICH_TYPE_PROP_NOT_HYDRATABLE: "BF049",
|
|
436
437
|
SHARED_PROGRAM_REQUIRED: "BF050",
|
|
437
438
|
WRONG_PACKAGE_IMPORT: "BF051",
|
|
438
439
|
BUILTIN_REQUIRES_IMPORT: "BF054",
|
|
@@ -463,6 +464,7 @@ var errorMessages = {
|
|
|
463
464
|
[ErrorCodes.COMPONENT_REQUIRED_PROP_MISSING]: "Built-in component is missing a required prop.",
|
|
464
465
|
[ErrorCodes.JSX_BRANCH_LOCAL_IN_CALLBACK]: "JSX-typed local declared inside an `if`-block cannot be referenced from a callback body (ref / event handler). " + "Render it as a child instead: `<div ref={...}>{local}</div>`.",
|
|
465
466
|
[ErrorCodes.SIBLING_COMPONENT_NOT_COMPILED]: "Referenced component did not compile to a template, so this reference would throw " + "`ReferenceError` at render time. Multi-return JSX dispatch (a `switch` or `if`/`else` " + "chain across multiple JSX-returning branches) cannot compile as a component in a " + `'use client' file. Extract it to a separate non-"use client" file (where it is preserved ` + "verbatim, see #932), or rewrite it as a single-return ternary/conditional chain so the " + "component pipeline can compile it.",
|
|
467
|
+
[ErrorCodes.RICH_TYPE_PROP_NOT_HYDRATABLE]: "Rich-typed prop cannot cross the bf-p hydration boundary as JSON.",
|
|
466
468
|
[ErrorCodes.SHARED_PROGRAM_REQUIRED]: "Shared ts.Program required for type-based reactivity classification. This source imports a Reactive<T>-branded library (e.g. @barefootjs/form) whose getters cannot be classified by regex alone. Pass `options.program` (built via `createProgramForCorpus`) so the analyzer can resolve the brand through the TypeChecker.",
|
|
467
469
|
[ErrorCodes.WRONG_PACKAGE_IMPORT]: "Import from wrong package.",
|
|
468
470
|
[ErrorCodes.BUILTIN_REQUIRES_IMPORT]: "Built-in <Async> / <Region> must be imported from '@barefootjs/client'. " + "The compiler recognises these tags by their import (not by tag name), " + "so an unimported tag with this name is treated as an undeclared component.",
|
|
@@ -496,6 +498,8 @@ var HOST_RICH_TYPE_NAMES = new Set([
|
|
|
496
498
|
"BigInt",
|
|
497
499
|
"Function"
|
|
498
500
|
]);
|
|
501
|
+
var JSON_REVIVABLE_RICH_TYPE_NAMES = new Set(["Date", "URL"]);
|
|
502
|
+
var JSON_UNSAFE_RICH_TYPE_NAMES = new Set([...HOST_RICH_TYPE_NAMES].filter((n) => !JSON_REVIVABLE_RICH_TYPE_NAMES.has(n)));
|
|
499
503
|
function baseTypeName(raw) {
|
|
500
504
|
const idx = raw.indexOf("<");
|
|
501
505
|
return (idx === -1 ? raw : raw.slice(0, idx)).trim();
|
|
@@ -2449,7 +2453,7 @@ export default ${this.componentName}` : "";
|
|
|
2449
2453
|
generateImports(ir, componentCode) {
|
|
2450
2454
|
const lines = [];
|
|
2451
2455
|
const utilImports = [];
|
|
2452
|
-
for (const util of ["bfComment", "bfText", "bfTextEnd"]) {
|
|
2456
|
+
for (const util of ["bfComment", "bfText", "bfTextEnd", "serializeHydrationProps"]) {
|
|
2453
2457
|
if (new RegExp(`\\b${util}\\b`).test(componentCode)) {
|
|
2454
2458
|
utilImports.push(util);
|
|
2455
2459
|
}
|
|
@@ -2615,7 +2619,7 @@ export default ${this.componentName}` : "";
|
|
|
2615
2619
|
const callerKey = p.sourceName ?? p.name;
|
|
2616
2620
|
lines.push(` if (typeof ${propAccess} !== 'function' && !(typeof ${propAccess} === 'object' && ${propAccess} !== null && 'isEscaped' in ${propAccess})) __hydrateProps['${callerKey}'] = ${propAccess}`);
|
|
2617
2621
|
}
|
|
2618
|
-
lines.push(` const __bfPropsJson = __bfParentProps || (
|
|
2622
|
+
lines.push(` const __bfPropsJson = __bfParentProps || serializeHydrationProps(__hydrateProps, '${name}')`);
|
|
2619
2623
|
} else if (hasClientInteractivity && isRootComponent) {
|
|
2620
2624
|
lines.push("");
|
|
2621
2625
|
lines.push(` const __bfPropsJson = __bfParentProps`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/hono",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.7",
|
|
4
4
|
"description": "Hono integration for BarefootJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
125
|
-
"@barefootjs/vite": "0.31.
|
|
125
|
+
"@barefootjs/vite": "0.31.7",
|
|
126
126
|
"@types/jsdom": "^27.0.0",
|
|
127
127
|
"hono": "^4.6.0",
|
|
128
128
|
"jsdom": "^27.3.0",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `serializeHydrationProps` (#2643) — the Hono-only runtime backstop for
|
|
3
|
+
* BF049 (`packages/jsx/src/rich-type-refusal.ts`'s
|
|
4
|
+
* `checkRichTypePropSerialization`). BF049 can only catch a prop whose type
|
|
5
|
+
* is PROVABLE from `propsType`; an imported/aliased type or a loosely-typed
|
|
6
|
+
* prop reaches Hono's serialization step uncaught, so this helper is the
|
|
7
|
+
* actual floor: it throws a clear, actionable error for a value that cannot
|
|
8
|
+
* survive the `bf-p` JSON boundary, instead of `JSON.stringify`'s opaque
|
|
9
|
+
* `TypeError` (BigInt) or silent data loss (Map/Set/…).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, test, expect } from 'bun:test'
|
|
13
|
+
import { serializeHydrationProps } from '../utils'
|
|
14
|
+
import { renderHonoComponent } from '../test-render'
|
|
15
|
+
import { HonoAdapter } from '../adapter/hono-adapter'
|
|
16
|
+
|
|
17
|
+
describe('serializeHydrationProps — unit', () => {
|
|
18
|
+
test('empty props return undefined', () => {
|
|
19
|
+
expect(serializeHydrationProps({}, 'Foo')).toBeUndefined()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('a plain JSON-safe props object serializes normally', () => {
|
|
23
|
+
expect(serializeHydrationProps({ a: 1, b: 'x', c: [1, 2], d: null }, 'Foo')).toBe(
|
|
24
|
+
JSON.stringify({ a: 1, b: 'x', c: [1, 2], d: null }),
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('a Date prop passes through untouched (handled by JSON.stringify’s own toJSON)', () => {
|
|
29
|
+
const d = new Date('2024-01-01T00:00:00.000Z')
|
|
30
|
+
expect(serializeHydrationProps({ createdAt: d }, 'Foo')).toBe(JSON.stringify({ createdAt: d }))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test.each([
|
|
34
|
+
['BigInt', { n: 123n }, 'n', 'BigInt'],
|
|
35
|
+
['Symbol', { s: Symbol('x') }, 's', 'Symbol'],
|
|
36
|
+
['Map', { data: new Map([['x', 1]]) }, 'data', 'Map'],
|
|
37
|
+
['Set', { tags: new Set(['x']) }, 'tags', 'Set'],
|
|
38
|
+
['WeakMap', { wm: new WeakMap() }, 'wm', 'WeakMap'],
|
|
39
|
+
['WeakSet', { ws: new WeakSet() }, 'ws', 'WeakSet'],
|
|
40
|
+
['Promise', { p: Promise.resolve(1) }, 'p', 'Promise'],
|
|
41
|
+
] as const)('%s prop throws a clear TypeError naming the prop, component, and #2643', (_label, props, propName, offender) => {
|
|
42
|
+
expect(() => serializeHydrationProps(props, 'Foo')).toThrow(TypeError)
|
|
43
|
+
try {
|
|
44
|
+
serializeHydrationProps(props, 'Foo')
|
|
45
|
+
throw new Error('expected a throw')
|
|
46
|
+
} catch (e) {
|
|
47
|
+
expect((e as Error).message).toContain(`prop '${propName}'`)
|
|
48
|
+
expect((e as Error).message).toContain('<Foo>')
|
|
49
|
+
expect((e as Error).message).toContain(offender)
|
|
50
|
+
expect((e as Error).message).toContain('2643')
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('a nested RegExp (inside a plain object) does not throw — only top-level values are checked', () => {
|
|
55
|
+
// Matches the existing, shipped, TOLERATED degradation the InputOTP demo
|
|
56
|
+
// relies on: JSON.stringify's own deep behavior for a nested rich value
|
|
57
|
+
// is unchanged by this helper.
|
|
58
|
+
const value = { wrapper: { pattern: /^[0-9]+$/ } }
|
|
59
|
+
expect(() => serializeHydrationProps(value, 'Foo')).not.toThrow()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('a top-level RegExp/Error/URLSearchParams degrades like JSON.stringify already did — not in the throw set', () => {
|
|
63
|
+
expect(() => serializeHydrationProps({ pattern: /x/ }, 'Foo')).not.toThrow()
|
|
64
|
+
expect(() => serializeHydrationProps({ err: new Error('x') }, 'Foo')).not.toThrow()
|
|
65
|
+
expect(() => serializeHydrationProps({ q: new URLSearchParams('a=1') }, 'Foo')).not.toThrow()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('serializeHydrationProps — integration via real Hono SSR render', () => {
|
|
70
|
+
// Prop typed `unknown` so BF049 has no static evidence to fire on —
|
|
71
|
+
// exercises the runtime backstop specifically, independent of the
|
|
72
|
+
// compile-time check.
|
|
73
|
+
const SOURCE = `
|
|
74
|
+
'use client'
|
|
75
|
+
export function Foo({ data }: { data: unknown }) {
|
|
76
|
+
return <button onClick={() => console.log(data)}>go</button>
|
|
77
|
+
}
|
|
78
|
+
`
|
|
79
|
+
|
|
80
|
+
// `renderHonoComponent` routes through Hono's own default error handler
|
|
81
|
+
// (`app.request()`, no custom `app.onError`), which swallows a thrown
|
|
82
|
+
// error's message into a generic 500 body — so these integration tests
|
|
83
|
+
// only prove the WIRING actually fails the request end-to-end (the
|
|
84
|
+
// `serializeHydrationProps` call is really reached from real generated
|
|
85
|
+
// component code, via the real `@barefootjs/hono/utils` import). The
|
|
86
|
+
// exact thrown message is already pinned by the direct unit tests above.
|
|
87
|
+
test('a Map prop causes SSR to fail (the runtime backstop is really wired into generated code)', async () => {
|
|
88
|
+
await expect(
|
|
89
|
+
renderHonoComponent({ adapter: new HonoAdapter(), source: SOURCE, props: { data: new Map([['x', 1]]) } }),
|
|
90
|
+
).rejects.toThrow(/Render failed with status 500/)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('a BigInt prop causes SSR to fail (previously an opaque JSON.stringify TypeError; behavior unchanged, now via the clearer helper)', async () => {
|
|
94
|
+
await expect(
|
|
95
|
+
renderHonoComponent({ adapter: new HonoAdapter(), source: SOURCE, props: { data: 123n } }),
|
|
96
|
+
).rejects.toThrow(/Render failed with status 500/)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('an array prop (JSON-safe) renders successfully', async () => {
|
|
100
|
+
const html = await renderHonoComponent({ adapter: new HonoAdapter(), source: SOURCE, props: { data: [1, 2, 3] } })
|
|
101
|
+
expect(html).toContain('bf-p=')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -270,9 +270,10 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
270
270
|
private generateImports(ir: ComponentIR, componentCode: string): string {
|
|
271
271
|
const lines: string[] = []
|
|
272
272
|
|
|
273
|
-
// Only import bfComment/bfText/bfTextEnd utilities
|
|
273
|
+
// Only import bfComment/bfText/bfTextEnd/serializeHydrationProps utilities
|
|
274
|
+
// that are actually used
|
|
274
275
|
const utilImports: string[] = []
|
|
275
|
-
for (const util of ['bfComment', 'bfText', 'bfTextEnd']) {
|
|
276
|
+
for (const util of ['bfComment', 'bfText', 'bfTextEnd', 'serializeHydrationProps']) {
|
|
276
277
|
if (new RegExp(`\\b${util}\\b`).test(componentCode)) {
|
|
277
278
|
utilImports.push(util)
|
|
278
279
|
}
|
|
@@ -618,7 +619,7 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
618
619
|
const callerKey = p.sourceName ?? p.name
|
|
619
620
|
lines.push(` if (typeof ${propAccess} !== 'function' && !(typeof ${propAccess} === 'object' && ${propAccess} !== null && 'isEscaped' in ${propAccess})) __hydrateProps['${callerKey}'] = ${propAccess}`)
|
|
620
621
|
}
|
|
621
|
-
lines.push(` const __bfPropsJson = __bfParentProps || (
|
|
622
|
+
lines.push(` const __bfPropsJson = __bfParentProps || serializeHydrationProps(__hydrateProps, '${name}')`)
|
|
622
623
|
} else if (hasClientInteractivity && isRootComponent) {
|
|
623
624
|
// No own props, but root is a component — pass through parent's props
|
|
624
625
|
lines.push('')
|
package/src/conformance-pins.ts
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
* Per-fixture build-time contracts for shapes the Hono adapter
|
|
3
3
|
* intentionally refuses to lower. Hono's SSR runtime is JS — its
|
|
4
4
|
* `acceptsTemplateCall` is broad enough to cover every adapter-specific
|
|
5
|
-
* lowering gap, so
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the call (#2273). Consumed by this package's own conformance test (as
|
|
11
|
-
* `expectedDiagnostics`) and by `bf compat` (issue-URL attribution).
|
|
5
|
+
* lowering gap, so the only pins here are compiler-level refusals that
|
|
6
|
+
* fire ahead of `adapter.generate()` and apply identically everywhere.
|
|
7
|
+
* `date-method-uncatalogued` stays pinned even here: hydrate-init
|
|
8
|
+
* re-evaluates the expression against a JSON-de-riched receiver, so a
|
|
9
|
+
* Hono-specific carve-out was evaluated and rejected (#2356).
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
12
|
import type { ConformancePins } from '@barefootjs/jsx'
|
|
15
13
|
|
|
16
14
|
export const conformancePins: ConformancePins = {
|
|
17
15
|
'date-method-uncatalogued': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2356' }],
|
|
16
|
+
'rich-prop-client-read': [{ code: 'BF049', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2648' }],
|
|
18
17
|
}
|
package/src/utils.ts
CHANGED
|
@@ -24,3 +24,72 @@ export function bfTextEnd() {
|
|
|
24
24
|
return raw('<!--/-->')
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Serialize a component's hydration props into the `bf-p` JSON payload,
|
|
29
|
+
* throwing a clear, actionable error instead of `JSON.stringify`'s opaque
|
|
30
|
+
* `TypeError` (for a `BigInt`) or silently succeeding with data loss (for a
|
|
31
|
+
* `Map`/`Set`/etc.) — the runtime backstop for #2643's compile-time check
|
|
32
|
+
* (`checkRichTypePropSerialization`, BF049), which can only catch a prop
|
|
33
|
+
* whose type is PROVABLE from `propsType`. An imported/aliased type
|
|
34
|
+
* (`type Timestamp = Map<…>`), or a prop typed too loosely to resolve
|
|
35
|
+
* statically, reaches this function uncaught by BF049, so it needs its own
|
|
36
|
+
* check here to avoid regressing to the pre-#2643 failure modes.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately checks only TOP-LEVEL prop values, not nested shapes —
|
|
39
|
+
* `JSON.stringify`'s existing deep-degradation behavior for a nested rich
|
|
40
|
+
* value (e.g. a `RegExp` inside a plain object) is unchanged, matching a
|
|
41
|
+
* real, shipped, TOLERATED pattern in this repo (`site/ui`'s InputOTP demo
|
|
42
|
+
* passes a live `RegExp` `pattern` prop that degrades to `{}` today; the
|
|
43
|
+
* component's own design accounts for that). Widening this to a deep walk
|
|
44
|
+
* would turn that accepted degradation into a new SSR 500.
|
|
45
|
+
*
|
|
46
|
+
* The throw set is narrower than BF049's flag set, but NOT because
|
|
47
|
+
* `RegExp` / `Error` / `URLSearchParams` preserve more data than a `Map` or
|
|
48
|
+
* `Set` — `JSON.stringify` degrades `RegExp` and `Error` to `{}` exactly
|
|
49
|
+
* like a `Map`/`Set` (only `URLSearchParams` happens to round-trip its
|
|
50
|
+
* key/value pairs as a plain object). They're excluded from the runtime
|
|
51
|
+
* throw purely to avoid a breaking behavior change: `site/ui`'s InputOTP
|
|
52
|
+
* demo already ships a live `RegExp` `pattern` prop that degrades to `{}`
|
|
53
|
+
* today, and the component's own design tolerates that. They stay
|
|
54
|
+
* diagnosable at compile time (BF049) only, not a runtime hard-stop.
|
|
55
|
+
*/
|
|
56
|
+
export function serializeHydrationProps(props: Record<string, unknown>, componentName: string): string | undefined {
|
|
57
|
+
const keys = Object.keys(props)
|
|
58
|
+
if (keys.length === 0) return undefined
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
const value = props[key]
|
|
61
|
+
const offender =
|
|
62
|
+
typeof value === 'bigint'
|
|
63
|
+
? 'BigInt'
|
|
64
|
+
: typeof value === 'symbol'
|
|
65
|
+
? 'Symbol'
|
|
66
|
+
: value instanceof Map
|
|
67
|
+
? 'Map'
|
|
68
|
+
: value instanceof Set
|
|
69
|
+
? 'Set'
|
|
70
|
+
: value instanceof WeakMap
|
|
71
|
+
? 'WeakMap'
|
|
72
|
+
: value instanceof WeakSet
|
|
73
|
+
? 'WeakSet'
|
|
74
|
+
: value instanceof Promise
|
|
75
|
+
? 'Promise'
|
|
76
|
+
: null
|
|
77
|
+
if (offender !== null) {
|
|
78
|
+
const consequence =
|
|
79
|
+
offender === 'BigInt'
|
|
80
|
+
? ''
|
|
81
|
+
: offender === 'Symbol'
|
|
82
|
+
? ' (the prop would be silently omitted from the payload entirely)'
|
|
83
|
+
: offender === 'Promise'
|
|
84
|
+
? " (its eventual value would be silently unreachable — JSON.stringify can't wait for a promise to settle)"
|
|
85
|
+
: ' (its entries would be silently dropped, serializing to {})'
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
`[barefootjs] Cannot serialize prop '${key}' of <${componentName}> for hydration: a ${offender} does not ` +
|
|
88
|
+
`survive the bf-p JSON boundary${consequence}. Pre-compute a JSON-safe value (string/number/array/plain ` +
|
|
89
|
+
`object) server-side and pass that instead. See BF049 (https://github.com/piconic-ai/barefootjs/issues/2643).`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return JSON.stringify(props)
|
|
94
|
+
}
|
|
95
|
+
|