@webjsdev/core 0.7.6 → 0.7.8
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/webjs-core-browser.js +3 -3
- package/dist/webjs-core-browser.js.map +2 -2
- package/dist/webjs-core-testing.js +2 -2
- package/dist/webjs-core-testing.js.map +2 -2
- package/dist/webjs-core.js +3 -3
- package/dist/webjs-core.js.map +2 -2
- package/index-browser.js +10 -2
- package/index.d.ts +52 -1
- package/index.js +9 -2
- package/package.json +1 -1
- package/src/component.js +114 -0
- package/src/directives.js +7 -1
- package/src/metadata.d.ts +279 -0
- package/src/render-server.js +22 -8
- package/src/router-client.js +17 -0
- package/src/routes.d.ts +136 -0
- package/src/webjs-config.d.ts +179 -0
package/src/routes.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript overlay for typed page / layout / route-handler props plus an
|
|
3
|
+
* opt-in, generated route union that types `navigate()` and catches bad
|
|
4
|
+
* hrefs at tsserver time (#258).
|
|
5
|
+
*
|
|
6
|
+
* webjs identity holds: NO build step, NO runtime cost, types-only, opt-in
|
|
7
|
+
* for JSDoc apps. The mechanism mirrors Next 15's `typedRoutes` but via
|
|
8
|
+
* interface declaration-merging instead of a bundler.
|
|
9
|
+
*
|
|
10
|
+
* import type { PageProps, LayoutProps } from '@webjsdev/core';
|
|
11
|
+
* export default function Post({ params }: PageProps<'/blog/[slug]'>) { … }
|
|
12
|
+
*
|
|
13
|
+
* Two augmentation targets, `WebjsRoutes` and `RouteParamMap`, are EMPTY by
|
|
14
|
+
* default, so an un-generated app sees `Route = string` and `params =
|
|
15
|
+
* Record<string, string>` (non-breaking). Running `webjs types` writes
|
|
16
|
+
* `.webjs/routes.d.ts`, which augments both with one key per route literal,
|
|
17
|
+
* narrowing `Route` to the concrete app routes and giving every page typed
|
|
18
|
+
* `params`.
|
|
19
|
+
*
|
|
20
|
+
* The shapes mirror what packages/server/src/ssr.js actually constructs for
|
|
21
|
+
* the page / layout context and what packages/server/src/api.js passes a
|
|
22
|
+
* route handler, NOT a guess. Zero runtime cost: nothing here ships to the
|
|
23
|
+
* browser.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { TemplateResult } from './html.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The augmentation target the generated `.webjs/routes.d.ts` fills with one
|
|
30
|
+
* key per route literal (e.g. `'/blog/[slug]': true`). Empty by default so an
|
|
31
|
+
* un-generated app keeps `Route = string`. App code (or the generator)
|
|
32
|
+
* augments it via `declare module '@webjsdev/core'`.
|
|
33
|
+
*/
|
|
34
|
+
export interface WebjsRoutes {}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The augmentation target mapping a dynamic route key to its params object
|
|
38
|
+
* (`'/blog/[slug]': { slug: string }`). Emitted by the generator with the
|
|
39
|
+
* exact `string` / `string[]` / optional shapes known at generation time,
|
|
40
|
+
* which is far more robust than deep template-literal inference. Empty by
|
|
41
|
+
* default; a static route is never given an entry here, so its params fall
|
|
42
|
+
* through to `Record<string, string>` (the runtime default), not `{}`.
|
|
43
|
+
*/
|
|
44
|
+
export interface RouteParamMap {}
|
|
45
|
+
|
|
46
|
+
/** True once either augmentation target carries at least one key. */
|
|
47
|
+
type _HasRoutes = keyof WebjsRoutes extends never ? false : true;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Turn a route key into the set of concrete path patterns it matches by
|
|
51
|
+
* replacing each dynamic segment with `${string}`. The optional catch-all
|
|
52
|
+
* `[[...x]]` is handled by the GENERATOR, which emits BOTH the with-segment
|
|
53
|
+
* and without-segment keys as separate `WebjsRoutes` entries (so `/docs` and
|
|
54
|
+
* `/docs/a/b` both satisfy a `/docs/[[...slug]]` route). Keeping that collapse
|
|
55
|
+
* in the generator (not in pure types) avoids the fragile `//`-collapse, so
|
|
56
|
+
* `RoutePattern` only ever needs the single-segment `[x]` and catch-all
|
|
57
|
+
* `[...x]` cases.
|
|
58
|
+
*/
|
|
59
|
+
type RoutePattern<K extends string> =
|
|
60
|
+
K extends `${infer A}[...${string}]${infer B}`
|
|
61
|
+
? `${A}${string}${RoutePattern<B>}`
|
|
62
|
+
: K extends `${infer A}[${string}]${infer B}`
|
|
63
|
+
? `${A}${string}${RoutePattern<B>}`
|
|
64
|
+
: K;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The valid-href type. `string` when no routes are generated (so `navigate()`
|
|
68
|
+
* and JSDoc apps are never broken); once `WebjsRoutes` is augmented it is the
|
|
69
|
+
* union of every static route literal PLUS a template-literal pattern per
|
|
70
|
+
* dynamic route.
|
|
71
|
+
*/
|
|
72
|
+
export type Route = _HasRoutes extends true
|
|
73
|
+
? { [K in keyof WebjsRoutes]: RoutePattern<K & string> }[keyof WebjsRoutes]
|
|
74
|
+
: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The params object for a route key. A generated dynamic route resolves to
|
|
78
|
+
* its `RouteParamMap` entry (`{ slug: string }`, `{ rest: string[] }`,
|
|
79
|
+
* `{ slug?: string[] }`); anything else (a static route, or any route in an
|
|
80
|
+
* un-generated app) resolves to `Record<string, string>`, the runtime
|
|
81
|
+
* default the SSR pipeline produces.
|
|
82
|
+
*/
|
|
83
|
+
export type RouteParams<R extends string> = R extends keyof RouteParamMap
|
|
84
|
+
? RouteParamMap[R]
|
|
85
|
+
: Record<string, string>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The argument a page default-export receives. Mirrors the `ctx` object
|
|
89
|
+
* packages/server/src/ssr.js builds: `{ params, searchParams, url,
|
|
90
|
+
* actionData }`.
|
|
91
|
+
*
|
|
92
|
+
* `searchParams` is typed `Record<string, string | string[]>` to match the
|
|
93
|
+
* established page-context convention, even though the runtime builds it with
|
|
94
|
+
* `Object.fromEntries(url.searchParams.entries())` (so a repeated key is
|
|
95
|
+
* last-wins `string`, never an array). The wider type keeps a future
|
|
96
|
+
* multi-value reader source-compatible.
|
|
97
|
+
*/
|
|
98
|
+
export interface PageProps<R extends string = string> {
|
|
99
|
+
/**
|
|
100
|
+
* Path params. A generated dynamic route narrows this to its exact shape
|
|
101
|
+
* (`{ slug: string }`); a static or un-generated route is
|
|
102
|
+
* `Record<string, string>`.
|
|
103
|
+
*/
|
|
104
|
+
params: R extends keyof RouteParamMap ? RouteParamMap[R] : Record<string, string>;
|
|
105
|
+
/** Query string, as an object. Repeated keys are last-wins at runtime. */
|
|
106
|
+
searchParams: Record<string, string | string[]>;
|
|
107
|
+
/** The full request URL string. */
|
|
108
|
+
url: string;
|
|
109
|
+
/**
|
|
110
|
+
* Present ONLY on the re-render after a failed page `action` submission
|
|
111
|
+
* (#244). `undefined` on a normal GET render. Read `actionData.fieldErrors`
|
|
112
|
+
* / `actionData.values` to surface validation errors and repopulate inputs.
|
|
113
|
+
*/
|
|
114
|
+
actionData?: unknown;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The argument a layout default-export receives: every `PageProps` field plus
|
|
119
|
+
* the rendered `children`. Mirrors the `{ children, params, searchParams, url
|
|
120
|
+
* }` object packages/server/src/ssr.js passes a layout.
|
|
121
|
+
*/
|
|
122
|
+
export interface LayoutProps<R extends string = string> extends PageProps<R> {
|
|
123
|
+
/** The nested page / inner-layout content this layout must embed. */
|
|
124
|
+
children: TemplateResult;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The 2nd argument a `route.{js,ts}` `GET` / `POST` / … handler receives.
|
|
129
|
+
* Mirrors what packages/server/src/api.js passes: `{ params }`. The params
|
|
130
|
+
* are a plain `Record<string, string>` at the handler layer (the route table
|
|
131
|
+
* decodes each captured segment to a string), with an optional `R` generic to
|
|
132
|
+
* narrow them against a generated route the same way `PageProps` does.
|
|
133
|
+
*/
|
|
134
|
+
export interface RouteHandlerContext<R extends string = string> {
|
|
135
|
+
params: R extends keyof RouteParamMap ? RouteParamMap[R] : Record<string, string>;
|
|
136
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript overlay for the `webjs` config block in a webjs app's
|
|
3
|
+
* package.json.
|
|
4
|
+
*
|
|
5
|
+
* // package.json is JSON, so author it there, but a typed reference
|
|
6
|
+
* // helps an agent or a human author the block correctly:
|
|
7
|
+
* import type { WebjsConfig } from '@webjsdev/core';
|
|
8
|
+
* const config: WebjsConfig = { trailingSlash: 'never', csp: true };
|
|
9
|
+
*
|
|
10
|
+
* The server reads this object key by key. Without a type or schema a
|
|
11
|
+
* typo'd key (e.g. `redirect` for `redirects`) was silently dropped and
|
|
12
|
+
* the feature stayed at its default with no diagnostic. This type plus
|
|
13
|
+
* the published JSON Schema close that gap.
|
|
14
|
+
*
|
|
15
|
+
* LOCKSTEP: this file, the JSON Schema at
|
|
16
|
+
* packages/server/webjs-config.schema.json, and the server reader
|
|
17
|
+
* functions MUST stay in sync. The readers are: readElideEnabled
|
|
18
|
+
* (dev.js, elide), compileHeaderRules (headers.js, headers),
|
|
19
|
+
* compileRedirectRules / readTrailingSlashPolicy (redirects.js,
|
|
20
|
+
* redirects / trailingSlash), readBasePath (base-path.js, basePath),
|
|
21
|
+
* readCspConfig (csp.js, csp), and
|
|
22
|
+
* readBodyLimits / computeServerTimeouts (body-limit.js, the byte caps
|
|
23
|
+
* and timeouts). Adding a `webjs.*` key means updating all three places.
|
|
24
|
+
* See packages/server/AGENTS.md for the one documented procedure.
|
|
25
|
+
*
|
|
26
|
+
* Every key is optional (the whole block is optional and every key has a
|
|
27
|
+
* default). Zero runtime cost: nothing in this file ships to the browser.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** One header directive in a `webjs.headers` rule. */
|
|
31
|
+
export interface WebjsHeaderDirective {
|
|
32
|
+
/** Header name, e.g. `X-Frame-Options`. */
|
|
33
|
+
key: string;
|
|
34
|
+
/**
|
|
35
|
+
* Header value. A `null` or `false` value REMOVES the header on a
|
|
36
|
+
* match, the escape hatch that drops a secure default on a path.
|
|
37
|
+
* `true` is intentionally not allowed (the runtime would stringify it
|
|
38
|
+
* to the literal `"true"`, which is never a useful header value).
|
|
39
|
+
*/
|
|
40
|
+
value?: string | null | false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One per-path response-header rule in `webjs.headers`. */
|
|
44
|
+
export interface WebjsHeaderRule {
|
|
45
|
+
/**
|
|
46
|
+
* Path pattern matched with the native URLPattern API, so `:param` and
|
|
47
|
+
* `:rest*` syntax works.
|
|
48
|
+
*/
|
|
49
|
+
source: string;
|
|
50
|
+
/** Header directives applied on a match. */
|
|
51
|
+
headers: WebjsHeaderDirective[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** One declarative redirect rule in `webjs.redirects`. */
|
|
55
|
+
export interface WebjsRedirectRule {
|
|
56
|
+
/**
|
|
57
|
+
* Path pattern matched with the native URLPattern API, so `:param` and
|
|
58
|
+
* `:rest*` syntax works.
|
|
59
|
+
*/
|
|
60
|
+
source: string;
|
|
61
|
+
/**
|
|
62
|
+
* Target path, a path referencing named groups captured by `source`,
|
|
63
|
+
* or an absolute URL. The incoming query string is preserved and
|
|
64
|
+
* merged onto the destination.
|
|
65
|
+
*/
|
|
66
|
+
destination: string;
|
|
67
|
+
/**
|
|
68
|
+
* `true` (the default) is a 308 Permanent Redirect, `false` is a 307
|
|
69
|
+
* Temporary Redirect. Both preserve the request method and body.
|
|
70
|
+
* `statusCode` wins over this when set.
|
|
71
|
+
*/
|
|
72
|
+
permanent?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Explicit redirect status, for a tool needing a legacy code. Wins
|
|
75
|
+
* over `permanent`. One of 301, 302, 303, 307, 308.
|
|
76
|
+
*/
|
|
77
|
+
statusCode?: 301 | 302 | 303 | 307 | 308;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The trailing-slash canonicalization policy in `webjs.trailingSlash`. */
|
|
81
|
+
export type WebjsTrailingSlash = 'never' | 'always' | 'ignore';
|
|
82
|
+
|
|
83
|
+
/** The object form of `webjs.csp` (the non-boolean shape). */
|
|
84
|
+
export interface WebjsCspConfig {
|
|
85
|
+
/**
|
|
86
|
+
* Directive map merged over the strict defaults, e.g.
|
|
87
|
+
* `{ 'connect-src': "'self' https://api.example.com" }`. A `null` /
|
|
88
|
+
* `false` / `''` value drops a default directive. A `__NONCE__` token
|
|
89
|
+
* in a value is replaced with the per-request nonce.
|
|
90
|
+
*/
|
|
91
|
+
directives?: Record<string, string | null | boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* `true` emits `Content-Security-Policy-Report-Only` instead of the
|
|
94
|
+
* enforcing header (the staged-rollout path).
|
|
95
|
+
*/
|
|
96
|
+
reportOnly?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The `webjs` object in a webjs app's package.json. Every key is
|
|
101
|
+
* optional. Mirrors what the server readers actually consume, NOT a
|
|
102
|
+
* Next.js superset.
|
|
103
|
+
*/
|
|
104
|
+
export interface WebjsConfig {
|
|
105
|
+
/**
|
|
106
|
+
* Display-only and inert-route dead-JS elision switch. Default `true`.
|
|
107
|
+
* Set to `false` to ship every module's JS app-wide. The `WEBJS_ELIDE`
|
|
108
|
+
* env override wins over this.
|
|
109
|
+
*/
|
|
110
|
+
elide?: boolean;
|
|
111
|
+
|
|
112
|
+
/** Per-path response-header rules, shaped like Next's. */
|
|
113
|
+
headers?: WebjsHeaderRule[];
|
|
114
|
+
|
|
115
|
+
/** Declarative permanent / temporary redirects for moved URLs. */
|
|
116
|
+
redirects?: WebjsRedirectRule[];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Trailing-slash canonicalization policy. Default `'ignore'` (no-op).
|
|
120
|
+
* `'never'` strips a trailing slash, `'always'` adds one (both via a
|
|
121
|
+
* 308 redirect).
|
|
122
|
+
*/
|
|
123
|
+
trailingSlash?: WebjsTrailingSlash;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sub-path deployment prefix for an app mounted under
|
|
127
|
+
* `example.com/app/` behind a proxy that does NOT strip the prefix.
|
|
128
|
+
* `'app'`, `'/app'`, and `'/app/'` all normalize to `'/app'`; an empty
|
|
129
|
+
* value (the default) is a root mount and a pure no-op. The prefix is
|
|
130
|
+
* stripped from the incoming path at ingress and prepended to every
|
|
131
|
+
* framework-emitted URL (importmap targets, modulepreload hints, boot
|
|
132
|
+
* module specifiers, the dev reload src). Author-written `<a href>`
|
|
133
|
+
* links and client-router navigation are NOT auto-prefixed (a
|
|
134
|
+
* documented follow-up).
|
|
135
|
+
*/
|
|
136
|
+
basePath?: string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Content-Security-Policy config. Off by default. `true` enables a
|
|
140
|
+
* strict nonce-based default policy. An object customizes directives
|
|
141
|
+
* and report-only mode.
|
|
142
|
+
*/
|
|
143
|
+
csp?: boolean | WebjsCspConfig;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* JSON / RPC request body cap in bytes. Default 1048576 (1 MiB). `0`
|
|
147
|
+
* disables the cap. The `WEBJS_MAX_BODY_BYTES` env override wins.
|
|
148
|
+
*/
|
|
149
|
+
maxBodyBytes?: number;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Form / multipart request body cap in bytes. Default 10485760 (10
|
|
153
|
+
* MiB). `0` disables the cap. The `WEBJS_MAX_MULTIPART_BYTES` env
|
|
154
|
+
* override wins.
|
|
155
|
+
*/
|
|
156
|
+
maxMultipartBytes?: number;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Max time in ms to receive the ENTIRE request (headers plus body).
|
|
160
|
+
* Default 30000. `0` disables the timeout. The
|
|
161
|
+
* `WEBJS_REQUEST_TIMEOUT_MS` env override wins.
|
|
162
|
+
*/
|
|
163
|
+
requestTimeoutMs?: number;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Max time in ms to receive just the request headers. Default 20000.
|
|
167
|
+
* Clamped strictly under `requestTimeoutMs` per node semantics. `0`
|
|
168
|
+
* disables the timeout. The `WEBJS_HEADERS_TIMEOUT_MS` env override
|
|
169
|
+
* wins.
|
|
170
|
+
*/
|
|
171
|
+
headersTimeoutMs?: number;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Idle time in ms before a kept-alive socket is closed. Default 5000.
|
|
175
|
+
* `0` disables the timeout. The `WEBJS_KEEP_ALIVE_TIMEOUT_MS` env
|
|
176
|
+
* override wins.
|
|
177
|
+
*/
|
|
178
|
+
keepAliveTimeoutMs?: number;
|
|
179
|
+
}
|