@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.
@@ -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
+ }