@webjsdev/cli 0.10.12 → 0.10.13
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/README.md +1 -1
- package/bin/webjs.js +19 -13
- package/lib/create.js +24 -18
- package/package.json +3 -7
- package/templates/.claude.json +1 -1
- package/templates/AGENTS.md +17 -15
- package/templates/CONVENTIONS.md +5 -5
- package/lib/check-json.js +0 -47
- package/lib/mcp-docs.js +0 -400
- package/lib/mcp-source.js +0 -244
- package/lib/mcp.js +0 -557
- package/resources/AGENTS.md +0 -404
- package/resources/agent-docs/advanced.md +0 -1090
- package/resources/agent-docs/built-ins.md +0 -367
- package/resources/agent-docs/components.md +0 -486
- package/resources/agent-docs/configuration.md +0 -207
- package/resources/agent-docs/framework-dev.md +0 -65
- package/resources/agent-docs/lit-muscle-memory-gotchas.md +0 -456
- package/resources/agent-docs/metadata.md +0 -334
- package/resources/agent-docs/recipes.md +0 -440
- package/resources/agent-docs/service-worker.md +0 -100
- package/resources/agent-docs/ssr-partial-nav-design.md +0 -214
- package/resources/agent-docs/styling.md +0 -235
- package/resources/agent-docs/testing.md +0 -372
- package/resources/agent-docs/typescript.md +0 -334
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
# TypeScript without a build step + full-stack type safety
|
|
2
|
-
|
|
3
|
-
Files ending in `.ts` / `.mts` are supported everywhere `.js` / `.mjs`
|
|
4
|
-
are. Same routing conventions, same server-action behaviour. No `tsc`
|
|
5
|
-
run is part of the user-visible workflow, no separate build step:
|
|
6
|
-
|
|
7
|
-
- **Editor** (VS Code) runs the TypeScript language server continuously. Red-squiggle on wrong types, including non-erasable syntax (see below).
|
|
8
|
-
- **CI** (optional) runs `tsc --noEmit` against `tsconfig.json`. Type-check only. Also catches non-erasable syntax via `erasableSyntaxOnly`.
|
|
9
|
-
- **Dev + prod server** (runtime, both directions): Node 24+'s built-in TypeScript type-stripping handles server-side `.ts` imports automatically (`process.features.typescript === 'strip'`). Browser-bound `.ts` requests go through `module.stripTypeScriptTypes` on the dev server, which performs whitespace replacement: every (line, column) in the source maps to the same position in the stripped output, so no sourcemap needs to be shipped and stack traces are byte-exact. The transform is cached by mtime (~microseconds per cache hit). Implementation backing: Node ships the [`amaro`](https://github.com/nodejs/amaro) package internally, which wraps SWC's WASM TypeScript transform in a position-preserving strip-only mode. If the framework ever needs to run on a non-Node runtime (Bun, Deno) we will install `amaro` directly or an equivalent position-preserving stripper (Sucrase preserves lines but not columns; SWC's strip mode also works).
|
|
10
|
-
|
|
11
|
-
## TypeScript feature support: erasable only
|
|
12
|
-
|
|
13
|
-
The framework uses Node 24+'s built-in `module.stripTypeScriptTypes`,
|
|
14
|
-
which only supports **erasable TypeScript**: type annotations,
|
|
15
|
-
`interface`, `type`, `declare`, generics, `import type`, `as` casts,
|
|
16
|
-
and `satisfies`. Non-erasable syntax is rejected.
|
|
17
|
-
|
|
18
|
-
Use the **erasable equivalents** instead:
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
// Not allowed (rejected at compile + runtime)
|
|
22
|
-
enum Color { Red, Green, Blue }
|
|
23
|
-
class Foo { constructor(public x: number) {} }
|
|
24
|
-
namespace Util { export const helper = ...; }
|
|
25
|
-
import = require('something');
|
|
26
|
-
@legacyDecorator class C {}
|
|
27
|
-
|
|
28
|
-
// Allowed (canonical erasable forms)
|
|
29
|
-
const Color = { Red: 'Red', Green: 'Green', Blue: 'Blue' } as const;
|
|
30
|
-
type Color = typeof Color[keyof typeof Color];
|
|
31
|
-
|
|
32
|
-
class Foo {
|
|
33
|
-
x: number;
|
|
34
|
-
constructor(x: number) { this.x = x; }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const Util = { helper: ... };
|
|
38
|
-
|
|
39
|
-
import { thing } from './thing.ts';
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Enforce this at edit time by setting `erasableSyntaxOnly: true` in
|
|
43
|
-
`tsconfig.json`. The TypeScript compiler then flags any non-erasable
|
|
44
|
-
syntax as a red squiggle in the editor and `tsc --noEmit` error in CI.
|
|
45
|
-
|
|
46
|
-
The `erasable-typescript-only` convention check verifies the flag is
|
|
47
|
-
set. Run `webjs check` to confirm.
|
|
48
|
-
|
|
49
|
-
### Fallback for third-party `.ts` dependencies
|
|
50
|
-
|
|
51
|
-
If a third-party package ships `.ts` source using non-erasable
|
|
52
|
-
syntax (rare; most npm packages publish compiled `.js`), the dev
|
|
53
|
-
server fails at strip time and returns a 500 naming the file and
|
|
54
|
-
pointing at the `no-non-erasable-typescript` lint rule. webjs is
|
|
55
|
-
buildless end-to-end and has no bundler fallback. Your own code
|
|
56
|
-
never hits this as long as `erasableSyntaxOnly` is set.
|
|
57
|
-
|
|
58
|
-
If you manually turn `erasableSyntaxOnly` off and write non-erasable
|
|
59
|
-
syntax in your own code, the dev server fails the same way. The
|
|
60
|
-
`erasable-typescript-only` convention check warns when the flag is
|
|
61
|
-
off so you catch the configuration drift before runtime.
|
|
62
|
-
|
|
63
|
-
## Import convention
|
|
64
|
-
|
|
65
|
-
Use explicit `.ts` extensions in imports. Node 24+'s built-in
|
|
66
|
-
type-stripping and the dev server's HTTP handler both key on the
|
|
67
|
-
file URL ending in `.ts` / `.mts`. For mixed codebases, `.js` imports
|
|
68
|
-
that point at a `.ts` sibling also resolve in the dev server. Still
|
|
69
|
-
prefer explicit `.ts`.
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
// modules/posts/queries/list-posts.server.ts
|
|
73
|
-
import { prisma } from '../../../lib/prisma.js'; // JS file unchanged
|
|
74
|
-
import { formatPost } from '../utils/slugify.ts'; // TS file
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Minimum viable `tsconfig.json`
|
|
78
|
-
|
|
79
|
-
```json
|
|
80
|
-
{
|
|
81
|
-
"compilerOptions": {
|
|
82
|
-
"target": "ES2022",
|
|
83
|
-
"module": "NodeNext",
|
|
84
|
-
"moduleResolution": "NodeNext",
|
|
85
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
86
|
-
"strict": true,
|
|
87
|
-
"noEmit": true,
|
|
88
|
-
"checkJs": true,
|
|
89
|
-
"allowJs": true,
|
|
90
|
-
"allowImportingTsExtensions": true,
|
|
91
|
-
"skipLibCheck": true,
|
|
92
|
-
"erasableSyntaxOnly": true
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The `erasableSyntaxOnly: true` line is the non-negotiable one. It
|
|
98
|
-
aligns the TypeScript compiler's accepted syntax with what Node's
|
|
99
|
-
strip-types accepts, so violations surface as editor diagnostics
|
|
100
|
-
instead of a runtime 500.
|
|
101
|
-
|
|
102
|
-
## Full-stack type safety
|
|
103
|
-
|
|
104
|
-
### Server actions: type-safe automatically
|
|
105
|
-
|
|
106
|
-
Calling a server action from a client component resolves at type-check
|
|
107
|
-
time to the action's real source file. The dev server's runtime stub
|
|
108
|
-
replacement is invisible to the type checker.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
// modules/posts/actions/create-post.server.ts
|
|
112
|
-
export async function createPost(
|
|
113
|
-
input: { title: string; body: string },
|
|
114
|
-
): Promise<ActionResult<PostFormatted>> { /* … */ }
|
|
115
|
-
|
|
116
|
-
// modules/posts/components/new-post.ts
|
|
117
|
-
import { createPost } from '../actions/create-post.server.ts';
|
|
118
|
-
const r = await createPost({ title, body });
|
|
119
|
-
// ^ Promise<ActionResult<PostFormatted>>
|
|
120
|
-
if (r.success) r.data.title; // ← PostFormatted.title: string
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Runtime matches types** because the RPC wire uses webjs's built-in
|
|
124
|
-
ESM serializer: `Date` → `Date`, `Map` → `Map`, `BigInt` → `BigInt`.
|
|
125
|
-
Supported: `Date`, `Map`, `Set`, `BigInt`, `Error`, `undefined`,
|
|
126
|
-
`NaN`/`Infinity`/`-0`, `TypedArray`, `ArrayBuffer`, `DataView`, `Blob`,
|
|
127
|
-
`File`, `FormData`, `Symbol.for(...)`, reference cycles. Class
|
|
128
|
-
instances come through as plain objects, with prototypes lost and methods
|
|
129
|
-
gone (matches React Server Actions).
|
|
130
|
-
|
|
131
|
-
### API routes: opt in via content negotiation
|
|
132
|
-
|
|
133
|
-
`route.ts` handlers use standard JSON by default so external consumers
|
|
134
|
-
keep working. Opt into rich types for your own UI code:
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
// app/api/posts/route.ts: server
|
|
138
|
-
import { json } from '@webjsdev/server';
|
|
139
|
-
import { listPosts } from '../../../modules/posts/queries/list-posts.server.ts';
|
|
140
|
-
|
|
141
|
-
export async function GET() {
|
|
142
|
-
return json(await listPosts()); // content-negotiates automatically
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
// caller: client
|
|
148
|
-
import { richFetch } from '@webjsdev/core';
|
|
149
|
-
const posts = await richFetch<Post[]>('/api/posts');
|
|
150
|
-
// posts[0].createdAt is a Date here.
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
The `json()` helper reads the in-flight Request via AsyncLocalStorage:
|
|
154
|
-
- `Accept: application/vnd.webjs+json` → encoded with the webjs serializer, served with `Content-Type: application/vnd.webjs+json` and `Vary: Accept`.
|
|
155
|
-
- Otherwise → plain JSON.
|
|
156
|
-
|
|
157
|
-
Request bodies parse with `readBody(req)` from `@webjsdev/server`.
|
|
158
|
-
|
|
159
|
-
### Page metadata: the `Metadata` type
|
|
160
|
-
|
|
161
|
-
A page or layout exports `metadata` (static) or `generateMetadata(ctx)`
|
|
162
|
-
(request-scoped). Annotate the return with the exported `Metadata` type so
|
|
163
|
-
a misspelled field or a wrong-typed value is a compile-time error, the
|
|
164
|
-
same ergonomics as Next.js's `import type { Metadata } from 'next'`.
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
import type { Metadata, MetadataContext } from '@webjsdev/core';
|
|
168
|
-
|
|
169
|
-
export const metadata: Metadata = {
|
|
170
|
-
title: 'Blog',
|
|
171
|
-
description: 'Latest posts',
|
|
172
|
-
openGraph: { type: 'website', image: '/og.png' },
|
|
173
|
-
twitter: { card: 'summary_large_image' },
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
export async function generateMetadata(ctx: MetadataContext): Promise<Metadata> {
|
|
177
|
-
return { title: `Post: ${ctx.params.slug}`, metadataBase: new URL(ctx.url).origin };
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
`Metadata` covers every field the SSR pipeline reads (see
|
|
182
|
-
`agent-docs/metadata.md`); each field is optional, and string-or-object
|
|
183
|
-
fields (`title`, `viewport`, `robots`, `appleWebApp`, `icons`) are unions.
|
|
184
|
-
It is a pure type in `packages/core/src/metadata.d.ts`, so it is erased at
|
|
185
|
-
runtime with zero build cost. `MetadataContext` types the
|
|
186
|
-
`generateMetadata` argument (`{ params, searchParams, url, actionData }`,
|
|
187
|
-
where `actionData` is set only on a failed-page-action re-render).
|
|
188
|
-
|
|
189
|
-
### Typed page / layout / route-handler props (`PageProps`, `LayoutProps`, `RouteHandlerContext`)
|
|
190
|
-
|
|
191
|
-
A page default-export receives `{ params, searchParams, url, actionData }`; a
|
|
192
|
-
layout receives the same plus `children`; a `route.{js,ts}` handler receives
|
|
193
|
-
`(request, { params })`. Type each with the exported helpers so a typo in a
|
|
194
|
-
param name or a wrong-typed field is a compile-time error.
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
import type { PageProps, LayoutProps, RouteHandlerContext } from '@webjsdev/core';
|
|
198
|
-
|
|
199
|
-
// A static route: `params` is `Record<string, string>`.
|
|
200
|
-
export default function About({ searchParams }: PageProps) { /* ... */ }
|
|
201
|
-
|
|
202
|
-
// A dynamic route: pass the route literal to narrow `params`.
|
|
203
|
-
export default function Post({ params }: PageProps<'/blog/[slug]'>) {
|
|
204
|
-
const slug = params.slug; // typed `string`
|
|
205
|
-
/* ... */
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// A layout adds `children: TemplateResult`.
|
|
209
|
-
export default function RootLayout({ children }: LayoutProps) { /* ... */ }
|
|
210
|
-
|
|
211
|
-
// A route handler's 2nd arg.
|
|
212
|
-
export async function GET(req: Request, ctx: RouteHandlerContext) {
|
|
213
|
-
return Response.json({ id: ctx.params.id });
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
`PageProps<R>` / `LayoutProps<R>` / `RouteHandlerContext<R>` take an optional
|
|
218
|
-
route literal `R`. With no `R` (or in an app that has not generated route
|
|
219
|
-
types), `params` is `Record<string, string>`, the runtime default. With `R`
|
|
220
|
-
set to a generated dynamic route, `params` narrows to its exact shape
|
|
221
|
-
(`{ slug: string }`, `{ rest: string[] }`, `{ slug?: string[] }`). The shapes
|
|
222
|
-
mirror what `packages/server/src/ssr.js` and `packages/server/src/api.js`
|
|
223
|
-
actually pass, NOT Next.js's superset. Pure types in
|
|
224
|
-
`packages/core/src/routes.d.ts`, erased at runtime with zero build cost.
|
|
225
|
-
|
|
226
|
-
### The generated route union (`webjs types`) types `navigate()` and catches bad hrefs
|
|
227
|
-
|
|
228
|
-
Run `webjs types` to generate `.webjs/routes.d.ts`, an opt-in overlay that
|
|
229
|
-
augments `@webjsdev/core` with one key per route in `app/`. It narrows two
|
|
230
|
-
things at tsserver time:
|
|
231
|
-
|
|
232
|
-
- The `Route` href type: `navigate('/blog/anything')` is accepted,
|
|
233
|
-
`navigate('/nonexistent')` is a type error. (Until you generate the types,
|
|
234
|
-
`Route` is `string`, so `navigate()` is unconstrained, non-breaking for
|
|
235
|
-
JSDoc apps and un-generated apps alike.)
|
|
236
|
-
- Per-route `params`: `PageProps<'/blog/[slug]'>['params']` becomes
|
|
237
|
-
`{ slug: string }`, derived from the generated `RouteParamMap`.
|
|
238
|
-
|
|
239
|
-
```sh
|
|
240
|
-
webjs types # writes .webjs/routes.d.ts (count of routes printed)
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
`webjs dev` also emits it automatically at startup and re-emits after each
|
|
244
|
-
route rebuild, so an editor always has fresh route types. The file is
|
|
245
|
-
gitignored (regenerated per machine, like Next's `.next/types`); the scaffold
|
|
246
|
-
`tsconfig.json` lists `.webjs/routes.d.ts` in `include` so tsserver picks it
|
|
247
|
-
up. To opt in for an existing app, run `webjs types` once and ensure your
|
|
248
|
-
`tsconfig.json` `include` lists `.webjs/routes.d.ts`.
|
|
249
|
-
|
|
250
|
-
This is webjs's no-build equivalent of Next 15's `typedRoutes`, achieved via
|
|
251
|
-
interface declaration-merging (`declare module '@webjsdev/core'`) rather than a
|
|
252
|
-
bundler. The mechanism is `generateRouteTypes(appDir)` in
|
|
253
|
-
`packages/server/src/route-types.js`, which reuses the one route enumerator
|
|
254
|
-
(`buildRouteTable`). Output is deterministic (sorted keys), so re-running
|
|
255
|
-
yields a byte-identical file.
|
|
256
|
-
|
|
257
|
-
### The `webjs` package.json config block: `WebjsConfig` + JSON Schema
|
|
258
|
-
|
|
259
|
-
The `webjs` object in `package.json` (the `elide` / `headers` / `redirects` /
|
|
260
|
-
`trailingSlash` / `csp` knobs plus the ingress body-size and timeout caps) has
|
|
261
|
-
two typed references, so a typo'd key is diagnosed instead of silently dropped:
|
|
262
|
-
|
|
263
|
-
- **A JSON Schema**, `packages/server/webjs-config.schema.json` (shipped in the
|
|
264
|
-
`@webjsdev/server` package). The scaffold's `.vscode/settings.json` associates
|
|
265
|
-
it with the `webjs` property of `package.json`, so VS Code flags an unknown
|
|
266
|
-
key natively while you edit the JSON. `additionalProperties: false` on the
|
|
267
|
-
block is what turns a typo into an editor warning.
|
|
268
|
-
- **The `WebjsConfig` type**, exported from `@webjsdev/core`, a typed reference
|
|
269
|
-
for an agent or human authoring the block (with `WebjsHeaderRule`,
|
|
270
|
-
`WebjsRedirectRule`, `WebjsCspConfig`, `WebjsTrailingSlash` for the nested
|
|
271
|
-
shapes).
|
|
272
|
-
|
|
273
|
-
```ts
|
|
274
|
-
import type { WebjsConfig } from '@webjsdev/core';
|
|
275
|
-
|
|
276
|
-
const config: WebjsConfig = {
|
|
277
|
-
trailingSlash: 'never',
|
|
278
|
-
csp: true,
|
|
279
|
-
redirects: [{ source: '/old', destination: '/new' }],
|
|
280
|
-
};
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
The schema and the type mirror what the server readers actually consume
|
|
284
|
-
(`readElideEnabled`, `compileHeaderRules`, `compileRedirectRules` /
|
|
285
|
-
`readTrailingSlashPolicy`, `readCspConfig`, `readBodyLimits` /
|
|
286
|
-
`computeServerTimeouts`). Adding a `webjs.*` key means updating the schema, the
|
|
287
|
-
type, AND the reader in lockstep, the one procedure documented in
|
|
288
|
-
`packages/server/AGENTS.md`. A drift test
|
|
289
|
-
(`packages/server/test/config/webjs-config-schema.test.js`) fails if the schema
|
|
290
|
-
and the reader key set diverge.
|
|
291
|
-
|
|
292
|
-
### Both runtime packages ship a type overlay
|
|
293
|
-
|
|
294
|
-
Both `@webjsdev/core` AND `@webjsdev/server` ship a hand-authored `.d.ts`
|
|
295
|
-
overlay plus a `types` export condition, so a `strict` + `nodenext` app
|
|
296
|
-
resolves real types for either import with no TS7016 ("could not find a
|
|
297
|
-
declaration file") error. The server overlay (`packages/server/index.d.ts`,
|
|
298
|
-
with `src/check.d.ts` and `src/testing.d.ts` for the `./check` / `./testing`
|
|
299
|
-
subpaths) types the full public surface (`createRequestHandler`, `startServer`,
|
|
300
|
-
`cors`, `cache`, `createAuth`, `rateLimit`, `sitemap`, `Session`, `json`,
|
|
301
|
-
`readBody`, the `revalidate*` family, the context helpers, the cache stores, the
|
|
302
|
-
auth providers, the test harness, and the convention validator), reusing the
|
|
303
|
-
core prop / metadata types rather than redefining them. The runtime stays plain
|
|
304
|
-
`.js` + JSDoc; the overlay is types-only with zero runtime cost. A drift test
|
|
305
|
-
keeps `index.d.ts` in lockstep with `index.js`'s runtime exports.
|
|
306
|
-
|
|
307
|
-
### TypeScript is not required
|
|
308
|
-
|
|
309
|
-
JS + JSDoc gets the same call-site type safety. The TypeScript language
|
|
310
|
-
server reads `@typedef` / `@param` / `@returns` identically to `.ts`
|
|
311
|
-
syntax. Add `"checkJs": true` to enforce types in editor + CI.
|
|
312
|
-
|
|
313
|
-
## Editor plugin: `@webjsdev/ts-plugin`
|
|
314
|
-
|
|
315
|
-
**Editor-only. Not required for the framework to run.** The runtime
|
|
316
|
-
has no dependency on it.
|
|
317
|
-
|
|
318
|
-
A single plugin. As of `@webjsdev/ts-plugin@0.4.0`, `ts-lit-plugin`
|
|
319
|
-
is bundled internally, so list one entry:
|
|
320
|
-
|
|
321
|
-
```jsonc
|
|
322
|
-
"plugins": [
|
|
323
|
-
{ "name": "@webjsdev/ts-plugin" }
|
|
324
|
-
]
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
Gives you:
|
|
328
|
-
- Type-check + diagnostics for attribute *values* inside `` html`` `` templates.
|
|
329
|
-
- Go-to-definition from `<my-counter>` to the class registered via `MyCounter.register('my-counter')`.
|
|
330
|
-
- Diagnostic suppression for "Unknown tag" / "Unknown attribute" on tags any webjs class registers.
|
|
331
|
-
- Attribute auto-complete from `static properties = { … }` keys.
|
|
332
|
-
- Attribute-value type-check: `<my-counter count=${expr}>` assignability-checks `typeof expr` against `declare count: T`.
|
|
333
|
-
|
|
334
|
-
Both behaviours are gated on import-graph reachability: a tag is recognized only if the file registering it is reachable from the file you're editing.
|