@webjsdev/core 0.7.30 → 0.7.32
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.js +3 -3
- package/dist/webjs-core.js.map +2 -2
- package/index-browser.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/nav.d.ts +4 -0
- package/src/nav.js +41 -0
- package/src/render-client.js +22 -2
- package/src/routes.d.ts +20 -5
package/index-browser.js
CHANGED
|
@@ -28,7 +28,7 @@ export { WebComponent, prop } from './src/component.js';
|
|
|
28
28
|
export { register, lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
29
29
|
export { render } from './src/render-client.js';
|
|
30
30
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
31
|
-
export { notFound, redirect, isNotFound, isRedirect } from './src/nav.js';
|
|
31
|
+
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
32
32
|
export { cspNonce } from './src/csp-nonce.js';
|
|
33
33
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
34
34
|
export { Suspense, isSuspense } from './src/suspense.js';
|
package/index.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export { register, lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tag
|
|
|
74
74
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
75
75
|
export { render } from './src/render-client.js';
|
|
76
76
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
77
|
-
export { notFound, redirect, isNotFound, isRedirect } from './src/nav.js';
|
|
77
|
+
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
78
78
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
79
79
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
80
80
|
export { connectWS } from './src/websocket-client.js';
|
package/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { register, lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tag
|
|
|
13
13
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
14
14
|
export { render } from './src/render-client.js';
|
|
15
15
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
16
|
-
export { notFound, redirect, isNotFound, isRedirect } from './src/nav.js';
|
|
16
|
+
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
17
17
|
export { cspNonce, setCspNonceProvider } from './src/csp-nonce.js';
|
|
18
18
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
19
19
|
export { Suspense, isSuspense } from './src/suspense.js';
|
package/package.json
CHANGED
package/src/nav.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export function notFound(): never;
|
|
2
2
|
export function redirect(url: string, status?: number | { status?: number }): never;
|
|
3
|
+
export function forbidden(): never;
|
|
4
|
+
export function unauthorized(): never;
|
|
3
5
|
export function isNotFound(e: unknown): boolean;
|
|
4
6
|
export function isRedirect(e: unknown): boolean;
|
|
7
|
+
export function isForbidden(e: unknown): boolean;
|
|
8
|
+
export function isUnauthorized(e: unknown): boolean;
|
package/src/nav.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
const NOT_FOUND = Symbol.for('webjs.notFound');
|
|
15
15
|
const REDIRECT = Symbol.for('webjs.redirect');
|
|
16
|
+
const FORBIDDEN = Symbol.for('webjs.forbidden');
|
|
17
|
+
const UNAUTHORIZED = Symbol.for('webjs.unauthorized');
|
|
16
18
|
|
|
17
19
|
/** @returns {never} */
|
|
18
20
|
export function notFound() {
|
|
@@ -21,6 +23,35 @@ export function notFound() {
|
|
|
21
23
|
throw err;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Throw a 403 Forbidden sentinel the SSR pipeline catches (#848, Next 15/16
|
|
28
|
+
* parity). Renders the nearest `forbidden.{js,ts}` boundary at status 403, or a
|
|
29
|
+
* default 403 page when none exists. Use for an authenticated user who lacks
|
|
30
|
+
* permission (contrast `unauthorized()` for a not-logged-in user).
|
|
31
|
+
*
|
|
32
|
+
* @returns {never}
|
|
33
|
+
*/
|
|
34
|
+
export function forbidden() {
|
|
35
|
+
const err = new Error('webjs: forbidden()');
|
|
36
|
+
/** @type any */ (err).__webjs = FORBIDDEN;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Throw a 401 Unauthorized sentinel the SSR pipeline catches (#848, Next 15/16
|
|
42
|
+
* parity). Renders the nearest `unauthorized.{js,ts}` boundary at status 401, or
|
|
43
|
+
* a default 401 page when none exists. Use for a request that is not
|
|
44
|
+
* authenticated (contrast `forbidden()` for an authenticated-but-not-allowed
|
|
45
|
+
* user).
|
|
46
|
+
*
|
|
47
|
+
* @returns {never}
|
|
48
|
+
*/
|
|
49
|
+
export function unauthorized() {
|
|
50
|
+
const err = new Error('webjs: unauthorized()');
|
|
51
|
+
/** @type any */ (err).__webjs = UNAUTHORIZED;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
/**
|
|
25
56
|
* Throw a redirect sentinel the SSR pipeline catches and turns into a 3xx.
|
|
26
57
|
*
|
|
@@ -62,3 +93,13 @@ export function isNotFound(e) {
|
|
|
62
93
|
export function isRedirect(e) {
|
|
63
94
|
return !!e && /** @type any */ (e).__webjs === REDIRECT;
|
|
64
95
|
}
|
|
96
|
+
|
|
97
|
+
/** @param {unknown} e */
|
|
98
|
+
export function isForbidden(e) {
|
|
99
|
+
return !!e && /** @type any */ (e).__webjs === FORBIDDEN;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @param {unknown} e */
|
|
103
|
+
export function isUnauthorized(e) {
|
|
104
|
+
return !!e && /** @type any */ (e).__webjs === UNAUTHORIZED;
|
|
105
|
+
}
|
package/src/render-client.js
CHANGED
|
@@ -264,6 +264,15 @@ function compile(tr) {
|
|
|
264
264
|
statics,
|
|
265
265
|
group,
|
|
266
266
|
};
|
|
267
|
+
// The mixed attribute is rebuilt from ALL its holes' values, but
|
|
268
|
+
// it is anchored at a single part (group[0]). The later holes stay
|
|
269
|
+
// `noop`, so a change confined to one of them would be skipped by
|
|
270
|
+
// updateInstance's per-hole dirty-check and the attribute would go
|
|
271
|
+
// stale. Point every non-anchor member back at the anchor so a
|
|
272
|
+
// change to any hole re-applies the whole attribute.
|
|
273
|
+
for (let m = 1; m < group.length; m++) {
|
|
274
|
+
parts[group[m]] = { kind: 'noop', path: [], mixedAnchor: idx0 };
|
|
275
|
+
}
|
|
267
276
|
mixedAttr = null;
|
|
268
277
|
}
|
|
269
278
|
state = 'in-tag';
|
|
@@ -488,7 +497,7 @@ function createInstance(tr, container) {
|
|
|
488
497
|
* @returns {BoundPart}
|
|
489
498
|
*/
|
|
490
499
|
function bindPart(p, root) {
|
|
491
|
-
if (p.kind === 'noop') return /** @type any */ ({ kind: 'noop' });
|
|
500
|
+
if (p.kind === 'noop') return /** @type any */ ({ kind: 'noop', mixedAnchor: /** @type any */ (p).mixedAnchor });
|
|
492
501
|
let node = /** @type Node */ (root);
|
|
493
502
|
for (const i of p.path) node = node.childNodes[i];
|
|
494
503
|
if (p.kind === 'child') {
|
|
@@ -533,7 +542,18 @@ function updateInstance(inst, values) {
|
|
|
533
542
|
for (let i = 0; i < values.length; i++) {
|
|
534
543
|
const next = values[i];
|
|
535
544
|
if (Object.is(next, inst.lastValues[i])) continue;
|
|
536
|
-
|
|
545
|
+
const bp = inst.bound[i];
|
|
546
|
+
// A hole that belongs to a mixed attribute (`class="a ${x} b ${y}"`) is a
|
|
547
|
+
// `noop` pointing at the attribute's anchor part; re-apply the anchor so the
|
|
548
|
+
// whole attribute is rebuilt from every hole's current value, not just the
|
|
549
|
+
// anchor hole's. Without this, a change confined to a non-anchor hole is
|
|
550
|
+
// dropped (the attribute goes stale).
|
|
551
|
+
const anchor = /** @type any */ (bp).mixedAnchor;
|
|
552
|
+
if (bp.kind === 'noop' && anchor != null) {
|
|
553
|
+
applyPart(inst.bound[anchor], values[anchor], inst.lastValues[anchor], values);
|
|
554
|
+
} else {
|
|
555
|
+
applyPart(bp, next, inst.lastValues[i], values);
|
|
556
|
+
}
|
|
537
557
|
inst.lastValues[i] = next;
|
|
538
558
|
}
|
|
539
559
|
}
|
package/src/routes.d.ts
CHANGED
|
@@ -84,6 +84,14 @@ export type RouteParams<R extends string> = R extends keyof RouteParamMap
|
|
|
84
84
|
? RouteParamMap[R]
|
|
85
85
|
: Record<string, string>;
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* A value that is BOTH synchronously usable as `T` and `await`-able to `T`
|
|
89
|
+
* (#848). The webjs runtime hands `params` / `searchParams` as a plain object
|
|
90
|
+
* carrying a non-enumerable `then`, so `params.id` and `await params` both
|
|
91
|
+
* work; this type expresses that dual nature to TypeScript.
|
|
92
|
+
*/
|
|
93
|
+
export type Awaitable<T> = T & PromiseLike<T>;
|
|
94
|
+
|
|
87
95
|
/**
|
|
88
96
|
* The argument a page default-export receives. Mirrors the `ctx` object
|
|
89
97
|
* packages/server/src/ssr.js builds: `{ params, searchParams, url,
|
|
@@ -94,16 +102,22 @@ export type RouteParams<R extends string> = R extends keyof RouteParamMap
|
|
|
94
102
|
* `Object.fromEntries(url.searchParams.entries())` (so a repeated key is
|
|
95
103
|
* last-wins `string`, never an array). The wider type keeps a future
|
|
96
104
|
* multi-value reader source-compatible.
|
|
105
|
+
*
|
|
106
|
+
* `params` / `searchParams` are `Awaitable<T>` (#848): synchronously readable
|
|
107
|
+
* (`params.id`) AND `await`-able (`const { id } = await params`) for Next
|
|
108
|
+
* 15/16 muscle-memory parity. The runtime object carries a non-enumerable
|
|
109
|
+
* `then` (see packages/server/src/thenable-params.js), so a spread / JSON /
|
|
110
|
+
* `Object.keys` sees only the data keys.
|
|
97
111
|
*/
|
|
98
112
|
export interface PageProps<R extends string = string> {
|
|
99
113
|
/**
|
|
100
114
|
* Path params. A generated dynamic route narrows this to its exact shape
|
|
101
115
|
* (`{ slug: string }`); a static or un-generated route is
|
|
102
|
-
* `Record<string, string>`.
|
|
116
|
+
* `Record<string, string>`. Sync-readable and `await`-able (#848).
|
|
103
117
|
*/
|
|
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[]
|
|
118
|
+
params: Awaitable<R extends keyof RouteParamMap ? RouteParamMap[R] : Record<string, string>>;
|
|
119
|
+
/** Query string, as an object. Repeated keys are last-wins at runtime. Sync-readable and `await`-able (#848). */
|
|
120
|
+
searchParams: Awaitable<Record<string, string | string[]>>;
|
|
107
121
|
/** The full request URL string. */
|
|
108
122
|
url: string;
|
|
109
123
|
/**
|
|
@@ -132,5 +146,6 @@ export interface LayoutProps<R extends string = string> extends PageProps<R> {
|
|
|
132
146
|
* narrow them against a generated route the same way `PageProps` does.
|
|
133
147
|
*/
|
|
134
148
|
export interface RouteHandlerContext<R extends string = string> {
|
|
135
|
-
|
|
149
|
+
/** Sync-readable and `await`-able (#848), same as `PageProps['params']`. */
|
|
150
|
+
params: Awaitable<R extends keyof RouteParamMap ? RouteParamMap[R] : Record<string, string>>;
|
|
136
151
|
}
|