You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`repeat(items, k, t)`| Keyed list directive — `${repeat(items, it => it.id, it => html\`...\`)}`. Preserves element identity / focus when items reorder. |
364
365
|`Suspense({fallback, children})`| Streaming boundary — server flushes `fallback` immediately, streams `children` (a Promise<TemplateResult>) when it resolves. |
|`richFetch<T>(url, init?)`| Client-side fetch that adds `Accept: application/vnd.webjs+json`, encodes plain-object bodies via superjson, and decodes responses with rich types. |
367
+
|`richFetch<T>(url, init?)`| Client-side fetch that adds `Accept: application/vnd.webjs+json`, encodes plain-object bodies via webjs's built-in serializer, and decodes responses with rich types. |
367
368
368
369
### Directives — `import { … } from '@webjskit/core/directives'`
369
370
@@ -1438,12 +1439,15 @@ const r = await createPost({ title, body });
1438
1439
if (r.success) r.data.title; // ← PostFormatted.title: string
1439
1440
```
1440
1441
1441
-
**Runtime reality matches the types** because the RPC wire is superjson:
1442
-
a `Date` on the server is a `Date` on the client, a `Map` is a `Map`, a
1443
-
`BigInt` is a `BigInt`. Supported types: everything superjson handles
1444
-
(Date, Map, Set, BigInt, undefined, URL, RegExp, Error, Decimal, plus
1445
-
any custom transformer you register). Class instances come through as
1446
-
plain objects — prototypes are lost, methods don't survive.
1442
+
**Runtime reality matches the types** because the RPC wire uses webjs's
1443
+
built-in ESM serializer: a `Date` on the server is a `Date` on the client,
1444
+
a `Map` is a `Map`, a `BigInt` is a `BigInt`. Supported types: `Date`,
Copy file name to clipboardExpand all lines: README.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,7 @@ TypeScript with zero build step, real SSR with Declarative Shadow DOM.
15
15
-**No build step you run.**`.ts` files served directly. The dev server transforms TypeScript via esbuild for both server-side imports (SSR) and browser-bound modules (hydration) — same transformer for both, ~1ms/file, cached by mtime. Full TS feature support (enums, decorators, parameter properties — anything esbuild handles). Edit, refresh, done.
16
16
-**Web components, light DOM by default.** Pages and components render as light DOM so global CSS and Tailwind utilities apply directly — no `::part`, no `:host`, no CSS-var plumbing. Shadow DOM is opt-in (`static shadow = true`) when you need scoped styles or real `<slot>` projection. Both modes SSR fully, no hydration runtime.
17
17
-**Tailwind CSS by default.** The scaffold ships with the Tailwind browser runtime + `@theme` design tokens. Prefer hand-written CSS? Opt out entirely — the framework works just as well with vanilla CSS when you follow the wrapper-scoping convention (`.page-<route>`, `.layout-<name>`, component-tag scoped). Full recipe in the [Styling docs](./docs/app/docs/styling/page.ts).
18
-
-**Full-stack type safety.** Import a `.server.ts` function from a component — TypeScript sees the real signature. superjson on the wire preserves `Date`, `Map`, `Set`, `BigInt`.
18
+
-**Full-stack type safety.** Import a `.server.ts` function from a component — TypeScript sees the real signature. webjs's built-in ESM serializer on the wire preserves `Date`, `Map`, `Set`, `BigInt`, `TypedArray`, `Blob`, `File`, `FormData`, and reference cycles.
19
19
-**Server-file source is unreachable from the browser.** Framework invariant: any file ending `.server.{js,ts}` or starting with `'use server'` is always served as an RPC stub, never its real source. Enforced in the HTTP layer with regression tests.
-**Client router.** Turbo-Drive-style link interception. Shadow-DOM-aware via `composedPath()`. Layouts stay mounted, only page content swaps. No white flash.
@@ -171,7 +171,7 @@ Pre-1.0. 632 unit tests (96.6% line coverage, 87.5% branch, 93.6% function),
-**Core:** SSR with DSD (opt-in) + light-DOM hydration (default), fine-grained client renderer, `repeat()`, `Suspense()`, client router with `composedPath()` for shadow DOM, mixed-attribute interpolation, MutationObserver upgrade safety net
174
-
-**Data:** server actions + superjson (Date/Map/Set/BigInt survive the wire), `expose()` for REST, `json()` + `richFetch()` for content-negotiated APIs, `cache()` for server-side query caching with TTL + `invalidate()`
174
+
-**Data:** server actions with webjs's built-in serializer (Date/Map/Set/BigInt/TypedArray/Blob/File/FormData/cycles survive the wire), `expose()` for REST, `json()` + `richFetch()` for content-negotiated APIs, `cache()` for server-side query caching with TTL + `invalidate()`
175
175
-**Server:** file router, per-segment middleware, `rateLimit()`, WebSockets + `broadcast()`, CSRF, compression, HTTP/2, 103 Early Hints, health probes, graceful shutdown, `Session` class with `SessionStorage` (cookie or store-backed), NextAuth-style `createAuth()` (Credentials, Google, GitHub)
176
176
-**DX:** TypeScript with zero build, `AGENTS.md` contract, `CLAUDE.md`, live reload in dev, optional esbuild bundle for prod, `@webjskit/ts-plugin` for tsserver — tag-name and CSS-class-name go-to-definition inside `html\`\`` templates.
<p>The TypeScript signature IS the API contract. No separate schema file, no OpenAPI spec, no GraphQL SDL. The client component imports the function, TypeScript checks the types, and superjson preserves them on the wire. An AI agent can:</p>
73
+
<p>The TypeScript signature IS the API contract. No separate schema file, no OpenAPI spec, no GraphQL SDL. The client component imports the function, TypeScript checks the types, and webjs's built-in serializer preserves them on the wire. An AI agent can:</p>
74
74
<ol>
75
75
<li>Read the function signature to understand the API.</li>
76
76
<li>Modify the function and know every call site that breaks (via tsc).</li>
Copy file name to clipboardExpand all lines: docs/app/docs/api-routes/page.ts
+9-9Lines changed: 9 additions & 9 deletions
Original file line number
Diff line number
Diff line change
@@ -97,7 +97,7 @@ export async function POST(req: Request) {
97
97
<h2>json() Helper -- Content Negotiation</h2>
98
98
<p>The <code>json()</code> helper from <code>@webjskit/server</code> adds smart content negotiation. It inspects the incoming request's <code>Accept</code> header and responds accordingly:</p>
99
99
<ul>
100
-
<li>If the client sent <code>Accept: application/vnd.webjs+json</code> (e.g. via <code>richFetch()</code>), the response is encoded with <strong>superjson</strong> so that <code>Date</code>, <code>Map</code>, <code>Set</code>, and <code>BigInt</code> survive the round trip.</li>
100
+
<li>If the client sent <code>Accept: application/vnd.webjs+json</code> (e.g. via <code>richFetch()</code>), the response is encoded with the <strong>webjs serializer</strong> so that <code>Date</code>, <code>Map</code>, <code>Set</code>, <code>BigInt</code>, <code>TypedArray</code>, <code>Blob</code>, <code>File</code>, <code>FormData</code>, and reference cycles all survive the round trip.</li>
101
101
<li>Otherwise, the response is plain <code>application/json</code> -- standard for curl, mobile apps, and third-party consumers.</li>
102
102
</ul>
103
103
<pre>// app/api/posts/route.ts
@@ -109,7 +109,7 @@ export async function GET() {
109
109
});
110
110
return json(posts);
111
111
// External client: plain JSON, createdAt is an ISO string
112
-
// richFetch client: superjson, createdAt is a real Date object
112
+
// richFetch client: rich types, createdAt is a real Date object
113
113
}
114
114
115
115
export async function POST(req: Request) {
@@ -120,7 +120,7 @@ export async function POST(req: Request) {
120
120
<p>The helper reads the in-flight Request from an <code>AsyncLocalStorage</code> context set up by the request pipeline, so you do not need to pass the request explicitly.</p>
<p>The <code>readBody()</code> helper from <code>@webjskit/server</code> is the inverse of <code>json()</code>. It parses the request body as superjson when the client sent the <code>application/vnd.webjs+json</code> content type, and as plain JSON otherwise:</p>
123
+
<p>The <code>readBody()</code> helper from <code>@webjskit/server</code> is the inverse of <code>json()</code>. It parses the request body with the webjs rich serializer when the client sent the <code>application/vnd.webjs+json</code> content type, and as plain JSON otherwise:</p>
124
124
<pre>import { json, readBody } from '@webjskit/server';
125
125
126
126
export async function POST(req: Request) {
@@ -132,7 +132,7 @@ export async function POST(req: Request) {
132
132
}</pre>
133
133
134
134
<h2>richFetch() -- Typed Client Calls</h2>
135
-
<p>On the client side, <code>richFetch()</code> from <code>webjs</code> is a drop-in replacement for <code>fetch()</code> that enables the superjson round trip:</p>
135
+
<p>On the client side, <code>richFetch()</code> from <code>webjs</code> is a drop-in replacement for <code>fetch()</code> that enables the rich-type round trip:</p>
body: { title: 'Hello', publishAt: new Date(2026, 5, 1) },
146
-
// body is automatically superjson-stringified
146
+
// body is automatically encoded with the rich serializer
147
147
// Content-Type is set to application/vnd.webjs+json
148
148
});
149
149
@@ -158,8 +158,8 @@ try {
158
158
<p><code>richFetch</code> automatically:</p>
159
159
<ul>
160
160
<li>Sets <code>Accept: application/vnd.webjs+json</code> on outgoing requests</li>
161
-
<li>If <code>body</code> is a plain object (not FormData, Blob, ArrayBuffer, or string), stringifies it with superjson and sets the content type</li>
162
-
<li>Parses the response with superjson when the server responds with the vendor content type, or with plain <code>JSON.parse</code> otherwise</li>
161
+
<li>If <code>body</code> is a plain object (not FormData, Blob, ArrayBuffer, or string), encodes it with the webjs serializer and sets the content type</li>
162
+
<li>Parses the response with the webjs serializer when the server responds with the vendor content type, or with plain <code>JSON.parse</code> otherwise</li>
163
163
<li>Throws an <code>Error</code> with <code>.status</code> and <code>.body</code> properties for non-2xx responses</li>
<p>WebSocket endpoints coexist with HTTP handlers in the same <code>route.ts</code>. The second argument is a <code>Request</code> object from the upgrade handshake, so you can read cookies, headers, and query params for auth.</p>
189
189
190
190
<h2>Content-Negotiated JSON</h2>
191
-
<p>Use the <code>json()</code> helper from <code>@webjskit/server</code> and the <code>richFetch()</code> client helper from <code>webjs</code> for superjson-encoded responses that preserve <code>Date</code>, <code>Map</code>, <code>Set</code>, and <code>BigInt</code>:</p>
191
+
<p>Use the <code>json()</code> helper from <code>@webjskit/server</code> and the <code>richFetch()</code> client helper from <code>webjs</code> for rich-encoded responses that preserve <code>Date</code>, <code>Map</code>, <code>Set</code>, <code>BigInt</code>, <code>TypedArray</code>, <code>Blob</code>, <code>File</code>, <code>FormData</code>, and reference cycles:</p>
<p>The <code>json()</code> helper reads the <code>Accept</code> header. If the client sent <code>Accept: application/vnd.webjs+json</code> (as <code>richFetch</code> does), the response is superjson-encoded. Otherwise, plain <code>application/json</code>. The <code>Vary: Accept</code> header is set automatically.</p>
207
+
<p>The <code>json()</code> helper reads the <code>Accept</code> header. If the client sent <code>Accept: application/vnd.webjs+json</code> (as <code>richFetch</code> does), the response is encoded with the webjs serializer. Otherwise, plain <code>application/json</code>. The <code>Vary: Accept</code> header is set automatically.</p>
208
208
<p>For reading request bodies with the same content negotiation, use <code>readBody(req)</code> from <code>@webjskit/server</code>.</p>
<li><strong>File-based routing</strong> — no manual <code>app.get()</code> / <code>app.post()</code> registration. Drop a <code>route.ts</code> in a folder and it is live.</li>
259
259
<li><strong>Nested middleware</strong> — middleware scoped to route subtrees, not global or per-route.</li>
260
260
<li><strong>TypeScript first</strong> — no build step, no compilation, no config. <code>.ts</code> files run directly.</li>
<li><strong>Routing conventions</strong> — <code>page.ts</code>, <code>layout.ts</code>, <code>route.ts</code>, <code>middleware.ts</code>, <code>error.ts</code>, <code>not-found.ts</code> are the file names. No aliases.</li>
101
101
<li><strong>Shadow DOM by default</strong> — components use shadow DOM unless <code>static shadow = false</code>. No global opt-out.</li>
102
102
<li><strong>CSRF on server actions</strong> — always on for <code>/__webjs/action/*</code> RPC. Can't disable.</li>
103
-
<li><strong>Import map</strong> — auto-generated. Maps <code>webjs</code> and <code>superjson</code> to framework-served URLs.</li>
103
+
<li><strong>Import map</strong> — auto-generated. Maps <code>@webjskit/core</code>sub-paths to framework-served URLs and any bare npm imports your client code uses to vendor bundles.</li>
0 commit comments