waku 1.0.0-beta.5 → 1.0.0-beta.7
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 +18 -16
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/lib/global-types.d.ts +0 -1
- package/dist/lib/types.d.ts +3 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils/etags.d.ts +8 -0
- package/dist/lib/utils/etags.js +30 -0
- package/dist/lib/utils/etags.js.map +1 -0
- package/dist/lib/utils/path.d.ts +0 -41
- package/dist/lib/utils/path.js +0 -178
- package/dist/lib/utils/path.js.map +1 -1
- package/dist/lib/utils/render.js +6 -0
- package/dist/lib/utils/render.js.map +1 -1
- package/dist/lib/utils/request.d.ts +2 -1
- package/dist/lib/utils/request.js +18 -5
- package/dist/lib/utils/request.js.map +1 -1
- package/dist/lib/vite-plugins/fs-router-typegen.js +19 -3
- package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
- package/dist/lib/vite-rsc/handler.js +2 -2
- package/dist/lib/vite-rsc/handler.js.map +1 -1
- package/dist/minimal/client-utils/fetch-store.d.ts +3 -0
- package/dist/minimal/client-utils/fetch-store.js +1 -0
- package/dist/minimal/client-utils/fetch-store.js.map +1 -1
- package/dist/minimal/client.d.ts +37 -4
- package/dist/minimal/client.js +173 -49
- package/dist/minimal/client.js.map +1 -1
- package/dist/minimal/server.d.ts +16 -0
- package/dist/minimal/server.js +30 -0
- package/dist/minimal/server.js.map +1 -1
- package/dist/router/base-types.d.ts +13 -2
- package/dist/router/base-types.js +6 -1
- package/dist/router/base-types.js.map +1 -1
- package/dist/router/client-utils/navigate.d.ts +39 -0
- package/dist/router/client-utils/navigate.js +95 -0
- package/dist/router/client-utils/navigate.js.map +1 -0
- package/dist/router/client.d.ts +37 -19
- package/dist/router/client.js +329 -281
- package/dist/router/client.js.map +1 -1
- package/dist/router/create-pages-utils/inferred-path-types.d.ts +7 -1
- package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
- package/dist/router/create-pages-utils/path-spec.d.ts +6 -0
- package/dist/router/create-pages-utils/path-spec.js +22 -0
- package/dist/router/create-pages-utils/path-spec.js.map +1 -0
- package/dist/router/create-pages.d.ts +8 -8
- package/dist/router/create-pages.js +6 -6
- package/dist/router/create-pages.js.map +1 -1
- package/dist/router/define-router-utils/path-spec.d.ts +5 -0
- package/dist/router/define-router-utils/path-spec.js +19 -0
- package/dist/router/define-router-utils/path-spec.js.map +1 -0
- package/dist/router/define-router-utils/task-runner.js.map +1 -0
- package/dist/router/define-router.d.ts +9 -5
- package/dist/router/define-router.js +147 -130
- package/dist/router/define-router.js.map +1 -1
- package/dist/router/fs-router.d.ts +6 -5
- package/dist/router/fs-router.js +1 -1
- package/dist/router/fs-router.js.map +1 -1
- package/dist/router/{client-utils → isomorphic-utils}/build-route-href.d.ts +6 -1
- package/dist/router/{client-utils → isomorphic-utils}/build-route-href.js +2 -2
- package/dist/router/isomorphic-utils/build-route-href.js.map +1 -0
- package/dist/router/{client-utils → isomorphic-utils}/match-route-params.js +2 -2
- package/dist/router/isomorphic-utils/match-route-params.js.map +1 -0
- package/dist/router/isomorphic-utils/path-spec.d.ts +32 -0
- package/dist/router/isomorphic-utils/path-spec.js +143 -0
- package/dist/router/isomorphic-utils/path-spec.js.map +1 -0
- package/dist/router/{common-utils → isomorphic-utils}/route-path.d.ts +0 -2
- package/dist/router/{common-utils → isomorphic-utils}/route-path.js +0 -3
- package/dist/router/isomorphic-utils/route-path.js.map +1 -0
- package/dist/router/isomorphic-utils/search-codec-registry.js.map +1 -0
- package/dist/router/prefetch-cache.d.ts +22 -0
- package/dist/router/prefetch-cache.js +79 -0
- package/dist/router/prefetch-cache.js.map +1 -0
- package/package.json +1 -1
- package/dist/lib/utils/prefetch-cache.d.ts +0 -15
- package/dist/lib/utils/prefetch-cache.js +0 -37
- package/dist/lib/utils/prefetch-cache.js.map +0 -1
- package/dist/lib/utils/task-runner.js.map +0 -1
- package/dist/router/client-utils/build-route-href.js.map +0 -1
- package/dist/router/client-utils/match-route-params.js.map +0 -1
- package/dist/router/client-utils/search-codec-registry.js.map +0 -1
- package/dist/router/common-utils/route-path.js.map +0 -1
- /package/dist/{lib/utils → router/define-router-utils}/task-runner.d.ts +0 -0
- /package/dist/{lib/utils → router/define-router-utils}/task-runner.js +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/match-route-params.d.ts +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/search-codec-registry.d.ts +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/search-codec-registry.js +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
|
|
|
13
13
|
|
|
14
14
|
## Introduction
|
|
15
15
|
|
|
16
|
-
**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It
|
|
16
|
+
**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It's lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and full-stack web apps, small or large. Whether Waku fits is about the architecture you want, not the size of your project: Waku keeps its framework surface minimal and composes with ecosystem libraries, while heavier frameworks own more of those concerns for you.
|
|
17
17
|
|
|
18
18
|
## Getting started
|
|
19
19
|
|
|
@@ -31,19 +31,21 @@ npm create waku@latest
|
|
|
31
31
|
|
|
32
32
|
**Node.js version requirement:** `^26.0.0` or `^24.0.0` or `^22.15.0`
|
|
33
33
|
|
|
34
|
+
For a guided path, start with the [Quick Start](https://waku.gg/guides/quick-start) guide and continue with the Learn series on [waku.gg/guides](https://waku.gg/guides), which builds a small app step by step.
|
|
35
|
+
|
|
34
36
|
## Rendering
|
|
35
37
|
|
|
36
|
-
While there
|
|
38
|
+
While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
|
|
37
39
|
|
|
38
|
-
So please don
|
|
40
|
+
So please don't be intimidated by the `'use client'` directive! Once you get the hang of it, you'll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It's way simpler than maintaining separate codebases for your backend and frontend.
|
|
39
41
|
|
|
40
|
-
And please don
|
|
42
|
+
And please don't fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.
|
|
41
43
|
|
|
42
44
|
> Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.
|
|
43
45
|
|
|
44
46
|
#### Server components
|
|
45
47
|
|
|
46
|
-
Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren
|
|
48
|
+
Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren't included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.
|
|
47
49
|
|
|
48
50
|
```tsx
|
|
49
51
|
// server component
|
|
@@ -133,9 +135,9 @@ Waku provides static prerendering (SSG) and server-side rendering (SSR) options
|
|
|
133
135
|
|
|
134
136
|
Each layout and page in Waku is composed of a React component hierarchy.
|
|
135
137
|
|
|
136
|
-
It begins with a server component at the top of the tree. Then at points down the hierarchy, you
|
|
138
|
+
It begins with a server component at the top of the tree. Then at points down the hierarchy, you'll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point, all imported components are hydrated and will run in the browser as well.
|
|
137
139
|
|
|
138
|
-
Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new
|
|
140
|
+
Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new "React server" layer](https://github.com/reactwg/server-components/discussions/4) that runs _before_ the traditional "React client" layer with which you're already familiar.
|
|
139
141
|
|
|
140
142
|
Client components are still server-side rendered as SSR is separate from RSC. Please see the [linked diagrams](https://github.com/reactwg/server-components/discussions/4) for a helpful visual.
|
|
141
143
|
|
|
@@ -145,7 +147,7 @@ To learn more about the modern React architecture, we recommend [Making Sense of
|
|
|
145
147
|
|
|
146
148
|
## Routing
|
|
147
149
|
|
|
148
|
-
Waku provides a minimal file-based
|
|
150
|
+
Waku provides a minimal file-based "pages router" experience built for the server components era.
|
|
149
151
|
|
|
150
152
|
Its underlying [low-level API](https://github.com/wakujs/waku/blob/main/docs/create-pages.mdx) is also available for those that prefer programmatic routing. This documentation covers file-based routing since many React developers prefer it, but please feel free to try both and see which you like more!
|
|
151
153
|
|
|
@@ -395,7 +397,7 @@ export const getConfig = async () => {
|
|
|
395
397
|
|
|
396
398
|
#### Catch-all routes
|
|
397
399
|
|
|
398
|
-
Catch-all or
|
|
400
|
+
Catch-all or "wildcard" segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.
|
|
399
401
|
|
|
400
402
|
Wildcard routes receive a prop with segment values as an ordered array. For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component.
|
|
401
403
|
|
|
@@ -1250,7 +1252,7 @@ export const ContactForm = () => {
|
|
|
1250
1252
|
|
|
1251
1253
|
#### Configuring API routes
|
|
1252
1254
|
|
|
1253
|
-
API routes are dynamic by default, but if you
|
|
1255
|
+
API routes are dynamic by default, but if you're using them to create a static resource such as an XML document, you can export a `getConfig` function that returns a config object with the render property set to `'static'`.
|
|
1254
1256
|
|
|
1255
1257
|
```ts
|
|
1256
1258
|
// ./src/pages/_api/blog/rss.xml.ts
|
|
@@ -1413,17 +1415,17 @@ export const ContactForm = ({ author = 'guest' }) => {
|
|
|
1413
1415
|
|
|
1414
1416
|
Server actions integrate with many other React APIs such as the [`useTransition`](https://react.dev/reference/react/useTransition) hook for handling pending states, the [`useActionState`](https://react.dev/reference/react/useActionState) hook for accessing returned values, and the [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook for performing optimistic UI updates.
|
|
1415
1417
|
|
|
1416
|
-
See the talk [What
|
|
1418
|
+
See the talk [What's new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.
|
|
1417
1419
|
|
|
1418
1420
|
## State management
|
|
1419
1421
|
|
|
1420
|
-
We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model
|
|
1422
|
+
We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model's performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.
|
|
1421
1423
|
|
|
1422
|
-
> We
|
|
1424
|
+
> We're exploring a deeper integration of atomic state management into Waku to achieve the performance and developer experience of signals while preserving React's declarative programming model.
|
|
1423
1425
|
|
|
1424
1426
|
## Environment variables
|
|
1425
1427
|
|
|
1426
|
-
It
|
|
1428
|
+
It's important to distinguish environment variables that must be kept secret from those that can be made public.
|
|
1427
1429
|
|
|
1428
1430
|
#### Private
|
|
1429
1431
|
|
|
@@ -1431,7 +1433,7 @@ By default all environment variables are considered private and are accessible o
|
|
|
1431
1433
|
|
|
1432
1434
|
#### Public
|
|
1433
1435
|
|
|
1434
|
-
A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users
|
|
1436
|
+
A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users' browsers.
|
|
1435
1437
|
|
|
1436
1438
|
### Runtime agnostic (recommended)
|
|
1437
1439
|
|
|
@@ -1616,7 +1618,7 @@ Please join our friendly [GitHub discussions](https://github.com/wakujs/waku/dis
|
|
|
1616
1618
|
|
|
1617
1619
|
## Roadmap
|
|
1618
1620
|
|
|
1619
|
-
Waku is in active development and we
|
|
1621
|
+
Waku is in active development and we're seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.
|
|
1620
1622
|
|
|
1621
1623
|
## Contributing
|
|
1622
1624
|
|
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getErrorInfo } from './lib/utils/custom-errors.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Experimental, the name and the behavior might change.
|
|
4
4
|
*/ export const unstable_allowServer = (x)=>x;
|
|
5
5
|
/**
|
|
6
6
|
* Unsure if this is going to be a public API in the future.
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { getErrorInfo } from './lib/utils/custom-errors.js';\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { getErrorInfo } from './lib/utils/custom-errors.js';\n\n/**\n * Experimental, the name and the behavior might change.\n */\nexport const unstable_allowServer = <T>(x: T) => x;\n\n/**\n * Unsure if this is going to be a public API in the future.\n */\nexport const unstable_defaultRootOptions = {\n onCaughtError(error: unknown) {\n if (getErrorInfo(error)) {\n return;\n }\n console.error(error);\n },\n};\n"],"names":["getErrorInfo","unstable_allowServer","x","unstable_defaultRootOptions","onCaughtError","error","console"],"mappings":"AAAA,SAASA,YAAY,QAAQ,+BAA+B;AAE5D;;CAEC,GACD,OAAO,MAAMC,uBAAuB,CAAIC,IAASA,EAAE;AAEnD;;CAEC,GACD,OAAO,MAAMC,8BAA8B;IACzCC,eAAcC,KAAc;QAC1B,IAAIL,aAAaK,QAAQ;YACvB;QACF;QACAC,QAAQD,KAAK,CAACA;IAChB;AACF,EAAE"}
|
|
@@ -4,7 +4,6 @@ declare global {
|
|
|
4
4
|
var __WAKU_REFETCH_ROUTE__: (() => void) | undefined;
|
|
5
5
|
var __WAKU_START_PREVIEW_SERVER__: (() => Promise<import('./vite-rsc/preview.js').PreviewServer>) | undefined;
|
|
6
6
|
var __WAKU_SERVER_ENV__: Readonly<Record<string, string>> | undefined;
|
|
7
|
-
var __WAKU_PREFETCHED__: import('./utils/prefetch-cache.js').PrefetchEntry[] | undefined;
|
|
8
7
|
var __WAKU_INITIAL_RSC__: import('./utils/initial-rsc.js').InitialRscEntry | undefined;
|
|
9
8
|
var __WAKU_DEBUG_CHANNELS__: Map<string, {
|
|
10
9
|
readable: ReadableStream;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { Config } from '../config.js';
|
|
3
|
+
import type { Etags } from './utils/etags.js';
|
|
3
4
|
type Elements = Record<string, unknown>;
|
|
4
5
|
export type Unstable_RenderRsc = (elements: Elements, options?: {
|
|
5
6
|
value?: unknown;
|
|
7
|
+
etags?: Etags;
|
|
6
8
|
unstable_clientModuleCallback?: (ids: string[]) => void;
|
|
7
9
|
}) => Promise<ReadableStream>;
|
|
8
10
|
export type Unstable_ParseRsc = (rscStream: ReadableStream) => Promise<Elements>;
|
|
@@ -30,6 +32,7 @@ export type Unstable_HandleRequest = (input: ({
|
|
|
30
32
|
}) & {
|
|
31
33
|
pathname: string;
|
|
32
34
|
req: Request;
|
|
35
|
+
etags?: Etags;
|
|
33
36
|
}, utils: {
|
|
34
37
|
renderRsc: Unstable_RenderRsc;
|
|
35
38
|
parseRsc: Unstable_ParseRsc;
|
package/dist/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { Config } from '../config.js';\n\ntype Elements = Record<string, unknown>;\n\nexport type Unstable_RenderRsc = (\n elements: Elements,\n options?: {\n value?: unknown;\n unstable_clientModuleCallback?: (ids: string[]) => void;\n },\n) => Promise<ReadableStream>;\n\nexport type Unstable_ParseRsc = (\n rscStream: ReadableStream,\n) => Promise<Elements>;\n\nexport type Unstable_RenderHtml = (\n elementsStream: ReadableStream,\n html: ReactNode,\n options: {\n rscPath: string;\n formState?: unknown;\n status?: number;\n nonce?: string;\n unstable_extraScriptContent?: string;\n },\n) => Promise<Response>;\n\nexport type Unstable_EmitFile = (\n filePath: string,\n body: ReadableStream,\n) => Promise<void>;\n\nexport type Unstable_HandleRequest = (\n input: (\n | { type: 'component'; rscPath: string; rscParams: unknown }\n | {\n type: 'function';\n fn: (...args: unknown[]) => Promise<unknown>;\n args: unknown[];\n }\n | {\n type: 'action';\n fn: () => Promise<unknown>;\n }\n | { type: 'custom' }\n ) & {\n pathname: string;\n req: Request;\n },\n utils: {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n loadBuildMetadata: (key: string) => Promise<string | undefined>;\n },\n) => Promise<ReadableStream | Response | 'fallback' | null | undefined>;\n\nexport type Unstable_HandleBuild = (utils: {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n rscPath2pathname: (rscPath: string) => string;\n saveBuildMetadata: (key: string, value: string) => Promise<void>;\n generateFile: (\n fileName: string,\n body: ReadableStream | string,\n ) => Promise<void>;\n generateDefaultHtml: (fileName: string) => Promise<void>;\n unstable_registerPrunableFile: (srcPath: string) => void;\n}) => Promise<void>;\n\nexport type Unstable_Handlers = {\n handleRequest: Unstable_HandleRequest;\n handleBuild: Unstable_HandleBuild;\n [someOtherProperty: string]: unknown;\n};\n\nexport type Unstable_ServerEntry = {\n fetch: (req: Request, ...args: any[]) => Response | Promise<Response>;\n build: (\n utils: {\n emitFile: Unstable_EmitFile;\n unstable_registerPrunableFile: (srcPath: string) => void;\n },\n ...args: any[]\n ) => Promise<void>;\n buildOptions?: Record<string, unknown>;\n buildEnhancers?: string[]; // enhancer module ids\n defaultExport?: unknown;\n [someOtherProperty: string]: unknown;\n};\n\nexport type Unstable_ProcessRequest = (\n arg: Parameters<Unstable_ServerEntry['fetch']>[0],\n) => Promise<Response | null>;\n\nexport type Unstable_ProcessBuild = (\n arg: Parameters<Unstable_ServerEntry['build']>[0],\n) => Promise<void>;\n\nexport type Unstable_CreateServerEntryAdapter = <Options>(\n fn: (\n args: {\n handlers: Unstable_Handlers;\n processRequest: Unstable_ProcessRequest;\n processBuild: Unstable_ProcessBuild;\n setAllEnv: (newEnv: Readonly<Record<string, unknown>>) => void;\n config: Omit<Required<Config>, 'vite'>;\n isBuild: boolean;\n notFoundHtml: string;\n },\n options?: Options,\n ) => Unstable_ServerEntry,\n) => (handlers: Unstable_Handlers, options?: Options) => Unstable_ServerEntry;\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/lib/types.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { Config } from '../config.js';\nimport type { Etags } from './utils/etags.js';\n\ntype Elements = Record<string, unknown>;\n\nexport type Unstable_RenderRsc = (\n elements: Elements,\n options?: {\n value?: unknown;\n etags?: Etags;\n unstable_clientModuleCallback?: (ids: string[]) => void;\n },\n) => Promise<ReadableStream>;\n\nexport type Unstable_ParseRsc = (\n rscStream: ReadableStream,\n) => Promise<Elements>;\n\nexport type Unstable_RenderHtml = (\n elementsStream: ReadableStream,\n html: ReactNode,\n options: {\n rscPath: string;\n formState?: unknown;\n status?: number;\n nonce?: string;\n unstable_extraScriptContent?: string;\n },\n) => Promise<Response>;\n\nexport type Unstable_EmitFile = (\n filePath: string,\n body: ReadableStream,\n) => Promise<void>;\n\nexport type Unstable_HandleRequest = (\n input: (\n | { type: 'component'; rscPath: string; rscParams: unknown }\n | {\n type: 'function';\n fn: (...args: unknown[]) => Promise<unknown>;\n args: unknown[];\n }\n | {\n type: 'action';\n fn: () => Promise<unknown>;\n }\n | { type: 'custom' }\n ) & {\n pathname: string;\n req: Request;\n etags?: Etags;\n },\n utils: {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n loadBuildMetadata: (key: string) => Promise<string | undefined>;\n },\n) => Promise<ReadableStream | Response | 'fallback' | null | undefined>;\n\nexport type Unstable_HandleBuild = (utils: {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n rscPath2pathname: (rscPath: string) => string;\n saveBuildMetadata: (key: string, value: string) => Promise<void>;\n generateFile: (\n fileName: string,\n body: ReadableStream | string,\n ) => Promise<void>;\n generateDefaultHtml: (fileName: string) => Promise<void>;\n unstable_registerPrunableFile: (srcPath: string) => void;\n}) => Promise<void>;\n\nexport type Unstable_Handlers = {\n handleRequest: Unstable_HandleRequest;\n handleBuild: Unstable_HandleBuild;\n [someOtherProperty: string]: unknown;\n};\n\nexport type Unstable_ServerEntry = {\n fetch: (req: Request, ...args: any[]) => Response | Promise<Response>;\n build: (\n utils: {\n emitFile: Unstable_EmitFile;\n unstable_registerPrunableFile: (srcPath: string) => void;\n },\n ...args: any[]\n ) => Promise<void>;\n buildOptions?: Record<string, unknown>;\n buildEnhancers?: string[]; // enhancer module ids\n defaultExport?: unknown;\n [someOtherProperty: string]: unknown;\n};\n\nexport type Unstable_ProcessRequest = (\n arg: Parameters<Unstable_ServerEntry['fetch']>[0],\n) => Promise<Response | null>;\n\nexport type Unstable_ProcessBuild = (\n arg: Parameters<Unstable_ServerEntry['build']>[0],\n) => Promise<void>;\n\nexport type Unstable_CreateServerEntryAdapter = <Options>(\n fn: (\n args: {\n handlers: Unstable_Handlers;\n processRequest: Unstable_ProcessRequest;\n processBuild: Unstable_ProcessBuild;\n setAllEnv: (newEnv: Readonly<Record<string, unknown>>) => void;\n config: Omit<Required<Config>, 'vite'>;\n isBuild: boolean;\n notFoundHtml: string;\n },\n options?: Options,\n ) => Unstable_ServerEntry,\n) => (handlers: Unstable_Handlers, options?: Options) => Unstable_ServerEntry;\n"],"names":[],"mappings":"AAyGA,WAa8E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const ETAG_ID_PREFIX = "_etag:";
|
|
2
|
+
export declare const ETAGS_HEADER = "X-Waku-Etags";
|
|
3
|
+
export declare const IMMUTABLE_ETAG = 1;
|
|
4
|
+
export type Etag = string | typeof IMMUTABLE_ETAG;
|
|
5
|
+
export type Etags = Record<string, Etag>;
|
|
6
|
+
export declare const isValidEtag: (value: unknown) => value is Etag;
|
|
7
|
+
export declare const parseClientEtags: (serialized: string | null) => Etags;
|
|
8
|
+
export declare const serializeClientEtags: (etags: Etags) => string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const ETAG_ID_PREFIX = '_etag:';
|
|
2
|
+
export const ETAGS_HEADER = 'X-Waku-Etags';
|
|
3
|
+
// Non-string sentinel, so it cannot collide with a (string) content validator.
|
|
4
|
+
export const IMMUTABLE_ETAG = 1;
|
|
5
|
+
// '' clears a tag; only printable Latin-1 (no control chars) is header-safe
|
|
6
|
+
export const isValidEtag = (value)=>value === IMMUTABLE_ETAG || typeof value === 'string' && value !== '' && /^[\x20-\x7e\xa0-\xff]+$/.test(value);
|
|
7
|
+
export const parseClientEtags = (serialized)=>{
|
|
8
|
+
if (!serialized) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(serialized);
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const etags = {};
|
|
21
|
+
for (const [key, value] of Object.entries(parsed)){
|
|
22
|
+
if (isValidEtag(value)) {
|
|
23
|
+
etags[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return etags;
|
|
27
|
+
};
|
|
28
|
+
export const serializeClientEtags = (etags)=>JSON.stringify(etags);
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=etags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/etags.ts"],"sourcesContent":["export const ETAG_ID_PREFIX = '_etag:';\nexport const ETAGS_HEADER = 'X-Waku-Etags';\n\n// Non-string sentinel, so it cannot collide with a (string) content validator.\nexport const IMMUTABLE_ETAG = 1;\n\nexport type Etag = string | typeof IMMUTABLE_ETAG;\nexport type Etags = Record<string, Etag>;\n\n// '' clears a tag; only printable Latin-1 (no control chars) is header-safe\nexport const isValidEtag = (value: unknown): value is Etag =>\n value === IMMUTABLE_ETAG ||\n (typeof value === 'string' &&\n value !== '' &&\n /^[\\x20-\\x7e\\xa0-\\xff]+$/.test(value));\n\nexport const parseClientEtags = (serialized: string | null): Etags => {\n if (!serialized) {\n return {};\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(serialized);\n } catch {\n return {};\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return {};\n }\n const etags: Etags = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (isValidEtag(value)) {\n etags[key] = value;\n }\n }\n return etags;\n};\n\nexport const serializeClientEtags = (etags: Etags): string =>\n JSON.stringify(etags);\n"],"names":["ETAG_ID_PREFIX","ETAGS_HEADER","IMMUTABLE_ETAG","isValidEtag","value","test","parseClientEtags","serialized","parsed","JSON","parse","Array","isArray","etags","key","Object","entries","serializeClientEtags","stringify"],"mappings":"AAAA,OAAO,MAAMA,iBAAiB,SAAS;AACvC,OAAO,MAAMC,eAAe,eAAe;AAE3C,+EAA+E;AAC/E,OAAO,MAAMC,iBAAiB,EAAE;AAKhC,4EAA4E;AAC5E,OAAO,MAAMC,cAAc,CAACC,QAC1BA,UAAUF,kBACT,OAAOE,UAAU,YAChBA,UAAU,MACV,0BAA0BC,IAAI,CAACD,OAAQ;AAE3C,OAAO,MAAME,mBAAmB,CAACC;IAC/B,IAAI,CAACA,YAAY;QACf,OAAO,CAAC;IACV;IACA,IAAIC;IACJ,IAAI;QACFA,SAASC,KAAKC,KAAK,CAACH;IACtB,EAAE,OAAM;QACN,OAAO,CAAC;IACV;IACA,IAAI,OAAOC,WAAW,YAAYA,WAAW,QAAQG,MAAMC,OAAO,CAACJ,SAAS;QAC1E,OAAO,CAAC;IACV;IACA,MAAMK,QAAe,CAAC;IACtB,KAAK,MAAM,CAACC,KAAKV,MAAM,IAAIW,OAAOC,OAAO,CAACR,QAAS;QACjD,IAAIL,YAAYC,QAAQ;YACtBS,KAAK,CAACC,IAAI,GAAGV;QACf;IACF;IACA,OAAOS;AACT,EAAE;AAEF,OAAO,MAAMI,uBAAuB,CAACJ,QACnCJ,KAAKS,SAAS,CAACL,OAAO"}
|
package/dist/lib/utils/path.d.ts
CHANGED
|
@@ -4,46 +4,5 @@ export declare const filePathToFileURL: (filePath: string) => string;
|
|
|
4
4
|
export declare const fileURLToFilePath: (fileURL: string) => string;
|
|
5
5
|
export declare const joinPath: (...paths: string[]) => string;
|
|
6
6
|
export declare const extname: (filePath: string) => string;
|
|
7
|
-
export type PathSpecItem = {
|
|
8
|
-
type: 'literal';
|
|
9
|
-
name: string;
|
|
10
|
-
} | {
|
|
11
|
-
type: 'group';
|
|
12
|
-
name?: string;
|
|
13
|
-
prefix?: string;
|
|
14
|
-
suffix?: string;
|
|
15
|
-
} | {
|
|
16
|
-
type: 'wildcard';
|
|
17
|
-
name?: string;
|
|
18
|
-
};
|
|
19
|
-
export type PathSpec = readonly PathSpecItem[];
|
|
20
|
-
export declare const parsePathWithSlug: (path: string) => PathSpec;
|
|
21
|
-
export declare const parseExactPath: (path: string) => PathSpec;
|
|
22
|
-
/**
|
|
23
|
-
* Transform a path spec to a regular expression.
|
|
24
|
-
*/
|
|
25
|
-
export declare const path2regexp: (path: PathSpec) => string;
|
|
26
|
-
/** Convert a path spec to a string for the path */
|
|
27
|
-
export declare const pathSpecAsString: (path: PathSpec) => string;
|
|
28
|
-
/**
|
|
29
|
-
* Helper function to get the path mapping from the path spec and the pathname.
|
|
30
|
-
*
|
|
31
|
-
* @param pathSpec
|
|
32
|
-
* @param pathname - route as a string
|
|
33
|
-
* @example
|
|
34
|
-
* getPathMapping(
|
|
35
|
-
* [
|
|
36
|
-
* { type: 'literal', name: 'foo' },
|
|
37
|
-
* { type: 'group', name: 'a' },
|
|
38
|
-
* ],
|
|
39
|
-
* '/foo/bar',
|
|
40
|
-
* );
|
|
41
|
-
* // => { a: 'bar' }
|
|
42
|
-
*/
|
|
43
|
-
export declare const getPathMapping: (pathSpec: PathSpec, pathname: string) => Record<string, string | string[]> | null;
|
|
44
7
|
export declare function removeBase(url: string, base: string): string;
|
|
45
8
|
export declare function addBase(url: string, base: string): string;
|
|
46
|
-
export declare function countSlugsAndWildcards(pathSpec: PathSpec): {
|
|
47
|
-
numSlugs: number;
|
|
48
|
-
numWildcards: number;
|
|
49
|
-
};
|
package/dist/lib/utils/path.js
CHANGED
|
@@ -59,168 +59,6 @@ export const extname = (filePath)=>{
|
|
|
59
59
|
}
|
|
60
60
|
return filePath.slice(index);
|
|
61
61
|
};
|
|
62
|
-
const SLUG_PATTERN = /^(.*?)\[([^\]]+)\](.*)$/;
|
|
63
|
-
export const parsePathWithSlug = (path)=>path.split('/').filter(Boolean).map((name)=>{
|
|
64
|
-
const match = SLUG_PATTERN.exec(name);
|
|
65
|
-
if (!match) {
|
|
66
|
-
return {
|
|
67
|
-
type: 'literal',
|
|
68
|
-
name
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const [, prefix, inner, suffix] = match;
|
|
72
|
-
if (inner.startsWith('...')) {
|
|
73
|
-
return {
|
|
74
|
-
type: 'wildcard',
|
|
75
|
-
name: inner.slice(3)
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
type: 'group',
|
|
80
|
-
name: inner,
|
|
81
|
-
...prefix ? {
|
|
82
|
-
prefix
|
|
83
|
-
} : {},
|
|
84
|
-
...suffix ? {
|
|
85
|
-
suffix
|
|
86
|
-
} : {}
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
export const parseExactPath = (path)=>path.split('/').filter(Boolean).map((name)=>({
|
|
90
|
-
type: 'literal',
|
|
91
|
-
name
|
|
92
|
-
}));
|
|
93
|
-
const escapeRegExp = (s)=>s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
94
|
-
/**
|
|
95
|
-
* Transform a path spec to a regular expression.
|
|
96
|
-
*/ export const path2regexp = (path)=>{
|
|
97
|
-
const parts = path.map((item)=>{
|
|
98
|
-
if (item.type === 'literal') {
|
|
99
|
-
return escapeRegExp(item.name);
|
|
100
|
-
} else if (item.type === 'group') {
|
|
101
|
-
const prefix = escapeRegExp(item.prefix ?? '');
|
|
102
|
-
const suffix = escapeRegExp(item.suffix ?? '');
|
|
103
|
-
return `${prefix}([^/]+)${suffix}`;
|
|
104
|
-
} else {
|
|
105
|
-
return `(.*)`;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
return `^/${parts.join('/')}$`;
|
|
109
|
-
};
|
|
110
|
-
/** Convert a path spec to a string for the path */ export const pathSpecAsString = (path)=>{
|
|
111
|
-
return '/' + path.map((item)=>{
|
|
112
|
-
if (item.type === 'literal') {
|
|
113
|
-
return item.name;
|
|
114
|
-
} else if (item.type === 'group') {
|
|
115
|
-
const prefix = item.prefix ?? '';
|
|
116
|
-
const suffix = item.suffix ?? '';
|
|
117
|
-
return `${prefix}[${item.name}]${suffix}`;
|
|
118
|
-
} else {
|
|
119
|
-
return `[...${item.name}]`;
|
|
120
|
-
}
|
|
121
|
-
}).join('/');
|
|
122
|
-
};
|
|
123
|
-
const matchSpecSegment = (spec, segment, mapping)=>{
|
|
124
|
-
if (spec.type === 'literal') {
|
|
125
|
-
return spec.name === segment;
|
|
126
|
-
}
|
|
127
|
-
if (segment === undefined) {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
const prefix = spec.prefix ?? '';
|
|
131
|
-
const suffix = spec.suffix ?? '';
|
|
132
|
-
if (prefix || suffix) {
|
|
133
|
-
if (!segment.startsWith(prefix) || !segment.endsWith(suffix)) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
const value = segment.slice(prefix.length, suffix ? -suffix.length : undefined);
|
|
137
|
-
if (!value) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
if (spec.name) {
|
|
141
|
-
mapping[spec.name] = value;
|
|
142
|
-
}
|
|
143
|
-
} else if (spec.name) {
|
|
144
|
-
mapping[spec.name] = segment;
|
|
145
|
-
}
|
|
146
|
-
return true;
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* Helper function to get the path mapping from the path spec and the pathname.
|
|
150
|
-
*
|
|
151
|
-
* @param pathSpec
|
|
152
|
-
* @param pathname - route as a string
|
|
153
|
-
* @example
|
|
154
|
-
* getPathMapping(
|
|
155
|
-
* [
|
|
156
|
-
* { type: 'literal', name: 'foo' },
|
|
157
|
-
* { type: 'group', name: 'a' },
|
|
158
|
-
* ],
|
|
159
|
-
* '/foo/bar',
|
|
160
|
-
* );
|
|
161
|
-
* // => { a: 'bar' }
|
|
162
|
-
*/ export const getPathMapping = (pathSpec, pathname)=>{
|
|
163
|
-
const actual = pathname.split('/').filter(Boolean);
|
|
164
|
-
if (pathSpec.length > actual.length) {
|
|
165
|
-
const wildcardIndex = pathSpec.findIndex((spec)=>spec.type === 'wildcard');
|
|
166
|
-
if (wildcardIndex === -1) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
const isTerminalWildcard = wildcardIndex === pathSpec.length - 1;
|
|
170
|
-
if (isTerminalWildcard) {
|
|
171
|
-
// Terminal wildcards only pass through with zero actual segments
|
|
172
|
-
// (handled by the root-wildcard special case below)
|
|
173
|
-
if (actual.length > 0) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
} else if (actual.length < pathSpec.length - 1) {
|
|
177
|
-
// Non-terminal wildcards can match zero segments; just need enough
|
|
178
|
-
// actual segments for every non-wildcard spec in the pathSpec
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const mapping = {};
|
|
183
|
-
let wildcardStartIndex = -1;
|
|
184
|
-
for(let i = 0; i < pathSpec.length; i++){
|
|
185
|
-
const spec = pathSpec[i];
|
|
186
|
-
if (spec.type === 'wildcard') {
|
|
187
|
-
wildcardStartIndex = i;
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
if (!matchSpecSegment(spec, actual[i], mapping)) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (wildcardStartIndex === -1) {
|
|
195
|
-
if (pathSpec.length !== actual.length) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
return mapping;
|
|
199
|
-
}
|
|
200
|
-
if (wildcardStartIndex === 0 && actual.length === 0) {
|
|
201
|
-
const wildcardName = pathSpec[wildcardStartIndex].name;
|
|
202
|
-
if (wildcardName) {
|
|
203
|
-
mapping[wildcardName] = [];
|
|
204
|
-
}
|
|
205
|
-
return mapping;
|
|
206
|
-
}
|
|
207
|
-
let wildcardEndIndex = -1;
|
|
208
|
-
for(let i = 0; i < pathSpec.length; i++){
|
|
209
|
-
const spec = pathSpec[pathSpec.length - i - 1];
|
|
210
|
-
if (spec.type === 'wildcard') {
|
|
211
|
-
wildcardEndIndex = actual.length - i - 1;
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
if (!matchSpecSegment(spec, actual[actual.length - i - 1], mapping)) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
const wildcardName = pathSpec[wildcardStartIndex].name;
|
|
219
|
-
if (wildcardName) {
|
|
220
|
-
mapping[wildcardName] = actual.slice(wildcardStartIndex, wildcardEndIndex + 1);
|
|
221
|
-
}
|
|
222
|
-
return mapping;
|
|
223
|
-
};
|
|
224
62
|
// basePath config is ensured to have trailing slash (see plugin)
|
|
225
63
|
export function removeBase(url, base) {
|
|
226
64
|
if (base !== '/') {
|
|
@@ -237,21 +75,5 @@ export function addBase(url, base) {
|
|
|
237
75
|
}
|
|
238
76
|
return url;
|
|
239
77
|
}
|
|
240
|
-
export function countSlugsAndWildcards(pathSpec) {
|
|
241
|
-
let numSlugs = 0;
|
|
242
|
-
let numWildcards = 0;
|
|
243
|
-
for (const slug of pathSpec){
|
|
244
|
-
if (slug.type !== 'literal') {
|
|
245
|
-
numSlugs++;
|
|
246
|
-
}
|
|
247
|
-
if (slug.type === 'wildcard') {
|
|
248
|
-
numWildcards++;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
numSlugs,
|
|
253
|
-
numWildcards
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
78
|
|
|
257
79
|
//# sourceMappingURL=path.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/path.ts"],"sourcesContent":["// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path: ' + filePath);\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) =>\n 'file://' + encodeURI(filePath);\n\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n return decodeURI(fileURL.slice('file://'.length));\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(\n ...paths.map((path) => path.split('/')),\n );\n const stack: string[] = [];\n for (const item of items) {\n if (item === '..') {\n if (stack.length && stack[stack.length - 1] !== '..') {\n stack.pop();\n } else if (!isAbsolute) {\n stack.push('..');\n }\n } else if (item && item !== '.') {\n stack.push(item);\n }\n }\n return (isAbsolute ? '/' : '') + stack.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n if (index <= 0) {\n return '';\n }\n if (['/', '.'].includes(filePath[index - 1]!)) {\n return '';\n }\n return filePath.slice(index);\n};\n\nexport type PathSpecItem =\n | { type: 'literal'; name: string }\n | { type: 'group'; name?: string; prefix?: string; suffix?: string }\n | { type: 'wildcard'; name?: string };\nexport type PathSpec = readonly PathSpecItem[];\n\nconst SLUG_PATTERN = /^(.*?)\\[([^\\]]+)\\](.*)$/;\n\nexport const parsePathWithSlug = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => {\n const match = SLUG_PATTERN.exec(name);\n if (!match) {\n return { type: 'literal' as const, name };\n }\n const [, prefix, inner, suffix] = match;\n if (inner!.startsWith('...')) {\n return {\n type: 'wildcard' as const,\n name: inner!.slice(3),\n };\n }\n return {\n type: 'group' as const,\n name: inner!,\n ...(prefix ? { prefix } : {}),\n ...(suffix ? { suffix } : {}),\n };\n });\n\nexport const parseExactPath = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => ({ type: 'literal', name }));\n\nconst escapeRegExp = (s: string) => s.replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&');\n\n/**\n * Transform a path spec to a regular expression.\n */\nexport const path2regexp = (path: PathSpec) => {\n const parts = path.map((item) => {\n if (item.type === 'literal') {\n return escapeRegExp(item.name);\n } else if (item.type === 'group') {\n const prefix = escapeRegExp(item.prefix ?? '');\n const suffix = escapeRegExp(item.suffix ?? '');\n return `${prefix}([^/]+)${suffix}`;\n } else {\n return `(.*)`;\n }\n });\n return `^/${parts.join('/')}$`;\n};\n\n/** Convert a path spec to a string for the path */\nexport const pathSpecAsString = (path: PathSpec) => {\n return (\n '/' +\n path\n .map((item) => {\n if (item.type === 'literal') {\n return item.name;\n } else if (item.type === 'group') {\n const prefix = item.prefix ?? '';\n const suffix = item.suffix ?? '';\n return `${prefix}[${item.name}]${suffix}`;\n } else {\n return `[...${item.name}]`;\n }\n })\n .join('/')\n );\n};\n\nconst matchSpecSegment = (\n spec: Exclude<PathSpecItem, { type: 'wildcard' }>,\n segment: string | undefined,\n mapping: Record<string, string | string[]>,\n): boolean => {\n if (spec.type === 'literal') {\n return spec.name === segment;\n }\n if (segment === undefined) {\n return false;\n }\n const prefix = spec.prefix ?? '';\n const suffix = spec.suffix ?? '';\n if (prefix || suffix) {\n if (!segment.startsWith(prefix) || !segment.endsWith(suffix)) {\n return false;\n }\n const value = segment.slice(\n prefix.length,\n suffix ? -suffix.length : undefined,\n );\n if (!value) {\n return false;\n }\n if (spec.name) {\n mapping[spec.name] = value;\n }\n } else if (spec.name) {\n mapping[spec.name] = segment;\n }\n return true;\n};\n\n/**\n * Helper function to get the path mapping from the path spec and the pathname.\n *\n * @param pathSpec\n * @param pathname - route as a string\n * @example\n * getPathMapping(\n * [\n * { type: 'literal', name: 'foo' },\n * { type: 'group', name: 'a' },\n * ],\n * '/foo/bar',\n * );\n * // => { a: 'bar' }\n */\nexport const getPathMapping = (\n pathSpec: PathSpec,\n pathname: string,\n): Record<string, string | string[]> | null => {\n const actual = pathname.split('/').filter(Boolean);\n if (pathSpec.length > actual.length) {\n const wildcardIndex = pathSpec.findIndex(\n (spec) => spec.type === 'wildcard',\n );\n if (wildcardIndex === -1) {\n return null;\n }\n const isTerminalWildcard = wildcardIndex === pathSpec.length - 1;\n if (isTerminalWildcard) {\n // Terminal wildcards only pass through with zero actual segments\n // (handled by the root-wildcard special case below)\n if (actual.length > 0) {\n return null;\n }\n } else if (actual.length < pathSpec.length - 1) {\n // Non-terminal wildcards can match zero segments; just need enough\n // actual segments for every non-wildcard spec in the pathSpec\n return null;\n }\n }\n const mapping: Record<string, string | string[]> = {};\n let wildcardStartIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[i]!;\n if (spec.type === 'wildcard') {\n wildcardStartIndex = i;\n break;\n }\n if (!matchSpecSegment(spec, actual[i], mapping)) {\n return null;\n }\n }\n if (wildcardStartIndex === -1) {\n if (pathSpec.length !== actual.length) {\n return null;\n }\n return mapping;\n }\n\n if (wildcardStartIndex === 0 && actual.length === 0) {\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = [];\n }\n return mapping;\n }\n\n let wildcardEndIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const spec = pathSpec[pathSpec.length - i - 1]!;\n if (spec.type === 'wildcard') {\n wildcardEndIndex = actual.length - i - 1;\n break;\n }\n if (!matchSpecSegment(spec, actual[actual.length - i - 1], mapping)) {\n return null;\n }\n }\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = actual.slice(\n wildcardStartIndex,\n wildcardEndIndex + 1,\n );\n }\n return mapping;\n};\n\n// basePath config is ensured to have trailing slash (see plugin)\nexport function removeBase(url: string, base: string) {\n if (base !== '/') {\n if (!url.startsWith(base)) {\n throw new Error('pathname must start with basePath: ' + url);\n }\n return url.slice(base.length - 1);\n }\n return url;\n}\n\nexport function addBase(url: string, base: string) {\n if (base !== '/' && url.startsWith('/')) {\n return base.slice(0, -1) + url;\n }\n return url;\n}\n\nexport function countSlugsAndWildcards(pathSpec: PathSpec) {\n let numSlugs = 0;\n let numWildcards = 0;\n for (const slug of pathSpec) {\n if (slug.type !== 'literal') {\n numSlugs++;\n }\n if (slug.type === 'wildcard') {\n numWildcards++;\n }\n }\n return { numSlugs, numWildcards };\n}\n"],"names":["ABSOLUTE_WIN32_PATH_REGEXP","encodeFilePathToAbsolute","filePath","test","Error","startsWith","decodeFilePathFromAbsolute","slice","filePathToFileURL","encodeURI","fileURLToFilePath","fileURL","decodeURI","length","joinPath","paths","isAbsolute","items","concat","map","path","split","stack","item","pop","push","join","extname","index","lastIndexOf","includes","SLUG_PATTERN","parsePathWithSlug","filter","Boolean","name","match","exec","type","prefix","inner","suffix","parseExactPath","escapeRegExp","s","replace","path2regexp","parts","pathSpecAsString","matchSpecSegment","spec","segment","mapping","undefined","endsWith","value","getPathMapping","pathSpec","pathname","actual","wildcardIndex","findIndex","isTerminalWildcard","wildcardStartIndex","i","wildcardName","wildcardEndIndex","removeBase","url","base","addBase","countSlugsAndWildcards","numSlugs","numWildcards","slug"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/path.ts"],"sourcesContent":["// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path: ' + filePath);\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) =>\n 'file://' + encodeURI(filePath);\n\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n return decodeURI(fileURL.slice('file://'.length));\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(\n ...paths.map((path) => path.split('/')),\n );\n const stack: string[] = [];\n for (const item of items) {\n if (item === '..') {\n if (stack.length && stack[stack.length - 1] !== '..') {\n stack.pop();\n } else if (!isAbsolute) {\n stack.push('..');\n }\n } else if (item && item !== '.') {\n stack.push(item);\n }\n }\n return (isAbsolute ? '/' : '') + stack.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n if (index <= 0) {\n return '';\n }\n if (['/', '.'].includes(filePath[index - 1]!)) {\n return '';\n }\n return filePath.slice(index);\n};\n\n// basePath config is ensured to have trailing slash (see plugin)\nexport function removeBase(url: string, base: string) {\n if (base !== '/') {\n if (!url.startsWith(base)) {\n throw new Error('pathname must start with basePath: ' + url);\n }\n return url.slice(base.length - 1);\n }\n return url;\n}\n\nexport function addBase(url: string, base: string) {\n if (base !== '/' && url.startsWith('/')) {\n return base.slice(0, -1) + url;\n }\n return url;\n}\n"],"names":["ABSOLUTE_WIN32_PATH_REGEXP","encodeFilePathToAbsolute","filePath","test","Error","startsWith","decodeFilePathFromAbsolute","slice","filePathToFileURL","encodeURI","fileURLToFilePath","fileURL","decodeURI","length","joinPath","paths","isAbsolute","items","concat","map","path","split","stack","item","pop","push","join","extname","index","lastIndexOf","includes","removeBase","url","base","addBase"],"mappings":"AAAA,eAAe;AACf,0EAA0E;AAC1E,0BAA0B;AAC1B,4EAA4E;AAC5E,8BAA8B;AAC9B,qEAAqE;AACrE,6BAA6B;AAE7B,MAAMA,6BAA6B;AAEnC,OAAO,MAAMC,2BAA2B,CAACC;IACvC,IAAIF,2BAA2BG,IAAI,CAACD,WAAW;QAC7C,MAAM,IAAIE,MAAM,qCAAqCF;IACvD;IACA,IAAIA,SAASG,UAAU,CAAC,MAAM;QAC5B,OAAOH;IACT;IACA,OAAO,MAAMA;AACf,EAAE;AAEF,OAAO,MAAMI,6BAA6B,CAACJ;IACzC,IAAIF,2BAA2BG,IAAI,CAACD,WAAW;QAC7C,OAAOA,SAASK,KAAK,CAAC;IACxB;IACA,OAAOL;AACT,EAAE;AAEF,OAAO,MAAMM,oBAAoB,CAACN,WAChC,YAAYO,UAAUP,UAAU;AAElC,OAAO,MAAMQ,oBAAoB,CAACC;IAChC,IAAI,CAACA,QAAQN,UAAU,CAAC,YAAY;QAClC,MAAM,IAAID,MAAM;IAClB;IACA,OAAOQ,UAAUD,QAAQJ,KAAK,CAAC,UAAUM,MAAM;AACjD,EAAE;AAEF,eAAe;AACf,OAAO,MAAMC,WAAW,CAAC,GAAGC;IAC1B,MAAMC,aAAaD,KAAK,CAAC,EAAE,EAAEV,WAAW;IACxC,MAAMY,QAAQ,AAAC,EAAE,CAAcC,MAAM,IAChCH,MAAMI,GAAG,CAAC,CAACC,OAASA,KAAKC,KAAK,CAAC;IAEpC,MAAMC,QAAkB,EAAE;IAC1B,KAAK,MAAMC,QAAQN,MAAO;QACxB,IAAIM,SAAS,MAAM;YACjB,IAAID,MAAMT,MAAM,IAAIS,KAAK,CAACA,MAAMT,MAAM,GAAG,EAAE,KAAK,MAAM;gBACpDS,MAAME,GAAG;YACX,OAAO,IAAI,CAACR,YAAY;gBACtBM,MAAMG,IAAI,CAAC;YACb;QACF,OAAO,IAAIF,QAAQA,SAAS,KAAK;YAC/BD,MAAMG,IAAI,CAACF;QACb;IACF;IACA,OAAO,AAACP,CAAAA,aAAa,MAAM,EAAC,IAAKM,MAAMI,IAAI,CAAC,QAAQ;AACtD,EAAE;AAEF,OAAO,MAAMC,UAAU,CAACzB;IACtB,MAAM0B,QAAQ1B,SAAS2B,WAAW,CAAC;IACnC,IAAID,SAAS,GAAG;QACd,OAAO;IACT;IACA,IAAI;QAAC;QAAK;KAAI,CAACE,QAAQ,CAAC5B,QAAQ,CAAC0B,QAAQ,EAAE,GAAI;QAC7C,OAAO;IACT;IACA,OAAO1B,SAASK,KAAK,CAACqB;AACxB,EAAE;AAEF,iEAAiE;AACjE,OAAO,SAASG,WAAWC,GAAW,EAAEC,IAAY;IAClD,IAAIA,SAAS,KAAK;QAChB,IAAI,CAACD,IAAI3B,UAAU,CAAC4B,OAAO;YACzB,MAAM,IAAI7B,MAAM,wCAAwC4B;QAC1D;QACA,OAAOA,IAAIzB,KAAK,CAAC0B,KAAKpB,MAAM,GAAG;IACjC;IACA,OAAOmB;AACT;AAEA,OAAO,SAASE,QAAQF,GAAW,EAAEC,IAAY;IAC/C,IAAIA,SAAS,OAAOD,IAAI3B,UAAU,CAAC,MAAM;QACvC,OAAO4B,KAAK1B,KAAK,CAAC,GAAG,CAAC,KAAKyB;IAC7B;IACA,OAAOA;AACT"}
|
package/dist/lib/utils/render.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ETAG_ID_PREFIX } from './etags.js';
|
|
1
2
|
import { sanitizeLog } from './log.js';
|
|
2
3
|
const validateRscElementIds = (elements)=>{
|
|
3
4
|
for (const id of Object.keys(elements)){
|
|
@@ -25,6 +26,11 @@ export function createRenderUtils(temporaryReferences, renderToReadableStream, c
|
|
|
25
26
|
if (options && 'value' in options) {
|
|
26
27
|
data._value = options.value;
|
|
27
28
|
}
|
|
29
|
+
if (options?.etags) {
|
|
30
|
+
for (const [slotId, etag] of Object.entries(options.etags)){
|
|
31
|
+
data[ETAG_ID_PREFIX + slotId] = etag;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
return renderToReadableStream(data, {
|
|
29
35
|
temporaryReferences,
|
|
30
36
|
onError,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/utils/render.ts"],"sourcesContent":["import type {\n Unstable_ParseRsc,\n Unstable_RenderHtml,\n Unstable_RenderRsc,\n} from '../types.js';\nimport { sanitizeLog } from './log.js';\n\nconst validateRscElementIds = (elements: Record<string, unknown>) => {\n for (const id of Object.keys(elements)) {\n if (id.startsWith('_')) {\n throw new Error(\n `RSC element IDs starting with \"_\" are reserved for Waku internals: ${id}`,\n );\n }\n }\n};\n\nexport function createRenderUtils(\n temporaryReferences: unknown,\n renderToReadableStream: (\n data: unknown,\n options?: object,\n extraOptions?: object,\n ) => ReadableStream,\n createFromReadableStream: (\n stream: ReadableStream,\n options?: object,\n ) => Promise<unknown>,\n loadSsrEntryModule: () => Promise<\n typeof import('../vite-entries/entry.ssr.js')\n >,\n buildId: string,\n debugChannel?: { readable?: ReadableStream; writable?: WritableStream },\n debugId?: string,\n): {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n} {\n const onError = (e: unknown) => {\n if (\n e &&\n typeof e === 'object' &&\n 'digest' in e &&\n typeof e.digest === 'string'\n ) {\n return e.digest;\n }\n console.error('Error during rendering:', sanitizeLog(e));\n };\n\n return {\n async renderRsc(elements, options) {\n validateRscElementIds(elements);\n const data: Record<string, unknown> = buildId\n ? { ...elements, _buildId: buildId }\n : { ...elements };\n if (options && 'value' in options) {\n data._value = options.value;\n }\n return renderToReadableStream(\n data,\n {\n temporaryReferences,\n onError,\n debugChannel,\n },\n {\n onClientReference(metadata: {\n id: string;\n name: string;\n deps: { js: string[]; css: string[] };\n }) {\n options?.unstable_clientModuleCallback?.(metadata.deps.js);\n },\n },\n );\n },\n async parseRsc(stream) {\n return createFromReadableStream(stream, {}) as Promise<\n Record<string, unknown>\n >;\n },\n async renderHtml(elementsStream, html, options) {\n const { INTERNAL_renderHtmlStream: renderHtmlStream } =\n await loadSsrEntryModule();\n\n const rscHtmlStream = renderToReadableStream(html, {\n onError,\n });\n const htmlResult = await renderHtmlStream(elementsStream, rscHtmlStream, {\n rscPath: options.rscPath,\n formState: options.formState as never,\n nonce: options.nonce,\n extraScriptContent: options.unstable_extraScriptContent,\n debugId,\n });\n return new Response(htmlResult.stream, {\n status: htmlResult.status || options.status || 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n },\n };\n}\n"],"names":["sanitizeLog","validateRscElementIds","elements","id","Object","keys","startsWith","Error","createRenderUtils","temporaryReferences","renderToReadableStream","createFromReadableStream","loadSsrEntryModule","buildId","debugChannel","debugId","onError","e","digest","console","error","renderRsc","options","data","_buildId","_value","value","onClientReference","metadata","unstable_clientModuleCallback","deps","js","parseRsc","stream","renderHtml","elementsStream","html","INTERNAL_renderHtmlStream","renderHtmlStream","rscHtmlStream","htmlResult","rscPath","formState","nonce","extraScriptContent","unstable_extraScriptContent","Response","status","headers"],"mappings":"AAKA,SAASA,WAAW,QAAQ,WAAW;AAEvC,MAAMC,wBAAwB,CAACC;IAC7B,KAAK,MAAMC,MAAMC,OAAOC,IAAI,CAACH,UAAW;QACtC,IAAIC,GAAGG,UAAU,CAAC,MAAM;YACtB,MAAM,IAAIC,MACR,CAAC,mEAAmE,EAAEJ,IAAI;QAE9E;IACF;AACF;AAEA,OAAO,SAASK,kBACdC,mBAA4B,EAC5BC,sBAImB,EACnBC,wBAGqB,EACrBC,kBAEC,EACDC,OAAe,EACfC,YAAuE,EACvEC,OAAgB;IAMhB,MAAMC,UAAU,CAACC;QACf,IACEA,KACA,OAAOA,MAAM,YACb,YAAYA,KACZ,OAAOA,EAAEC,MAAM,KAAK,UACpB;YACA,OAAOD,EAAEC,MAAM;QACjB;QACAC,QAAQC,KAAK,CAAC,2BAA2BpB,YAAYiB;IACvD;IAEA,OAAO;QACL,MAAMI,WAAUnB,QAAQ,EAAEoB,OAAO;YAC/BrB,sBAAsBC;YACtB,MAAMqB,OAAgCV,UAClC;gBAAE,GAAGX,QAAQ;gBAAEsB,UAAUX;YAAQ,IACjC;gBAAE,GAAGX,QAAQ;YAAC;YAClB,IAAIoB,WAAW,WAAWA,SAAS;gBACjCC,KAAKE,MAAM,GAAGH,QAAQI,KAAK;YAC7B;YACA,
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/render.ts"],"sourcesContent":["import type {\n Unstable_ParseRsc,\n Unstable_RenderHtml,\n Unstable_RenderRsc,\n} from '../types.js';\nimport { ETAG_ID_PREFIX } from './etags.js';\nimport { sanitizeLog } from './log.js';\n\nconst validateRscElementIds = (elements: Record<string, unknown>) => {\n for (const id of Object.keys(elements)) {\n if (id.startsWith('_')) {\n throw new Error(\n `RSC element IDs starting with \"_\" are reserved for Waku internals: ${id}`,\n );\n }\n }\n};\n\nexport function createRenderUtils(\n temporaryReferences: unknown,\n renderToReadableStream: (\n data: unknown,\n options?: object,\n extraOptions?: object,\n ) => ReadableStream,\n createFromReadableStream: (\n stream: ReadableStream,\n options?: object,\n ) => Promise<unknown>,\n loadSsrEntryModule: () => Promise<\n typeof import('../vite-entries/entry.ssr.js')\n >,\n buildId: string,\n debugChannel?: { readable?: ReadableStream; writable?: WritableStream },\n debugId?: string,\n): {\n renderRsc: Unstable_RenderRsc;\n parseRsc: Unstable_ParseRsc;\n renderHtml: Unstable_RenderHtml;\n} {\n const onError = (e: unknown) => {\n if (\n e &&\n typeof e === 'object' &&\n 'digest' in e &&\n typeof e.digest === 'string'\n ) {\n return e.digest;\n }\n console.error('Error during rendering:', sanitizeLog(e));\n };\n\n return {\n async renderRsc(elements, options) {\n validateRscElementIds(elements);\n const data: Record<string, unknown> = buildId\n ? { ...elements, _buildId: buildId }\n : { ...elements };\n if (options && 'value' in options) {\n data._value = options.value;\n }\n if (options?.etags) {\n for (const [slotId, etag] of Object.entries(options.etags)) {\n data[ETAG_ID_PREFIX + slotId] = etag;\n }\n }\n return renderToReadableStream(\n data,\n {\n temporaryReferences,\n onError,\n debugChannel,\n },\n {\n onClientReference(metadata: {\n id: string;\n name: string;\n deps: { js: string[]; css: string[] };\n }) {\n options?.unstable_clientModuleCallback?.(metadata.deps.js);\n },\n },\n );\n },\n async parseRsc(stream) {\n return createFromReadableStream(stream, {}) as Promise<\n Record<string, unknown>\n >;\n },\n async renderHtml(elementsStream, html, options) {\n const { INTERNAL_renderHtmlStream: renderHtmlStream } =\n await loadSsrEntryModule();\n\n const rscHtmlStream = renderToReadableStream(html, {\n onError,\n });\n const htmlResult = await renderHtmlStream(elementsStream, rscHtmlStream, {\n rscPath: options.rscPath,\n formState: options.formState as never,\n nonce: options.nonce,\n extraScriptContent: options.unstable_extraScriptContent,\n debugId,\n });\n return new Response(htmlResult.stream, {\n status: htmlResult.status || options.status || 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n },\n };\n}\n"],"names":["ETAG_ID_PREFIX","sanitizeLog","validateRscElementIds","elements","id","Object","keys","startsWith","Error","createRenderUtils","temporaryReferences","renderToReadableStream","createFromReadableStream","loadSsrEntryModule","buildId","debugChannel","debugId","onError","e","digest","console","error","renderRsc","options","data","_buildId","_value","value","etags","slotId","etag","entries","onClientReference","metadata","unstable_clientModuleCallback","deps","js","parseRsc","stream","renderHtml","elementsStream","html","INTERNAL_renderHtmlStream","renderHtmlStream","rscHtmlStream","htmlResult","rscPath","formState","nonce","extraScriptContent","unstable_extraScriptContent","Response","status","headers"],"mappings":"AAKA,SAASA,cAAc,QAAQ,aAAa;AAC5C,SAASC,WAAW,QAAQ,WAAW;AAEvC,MAAMC,wBAAwB,CAACC;IAC7B,KAAK,MAAMC,MAAMC,OAAOC,IAAI,CAACH,UAAW;QACtC,IAAIC,GAAGG,UAAU,CAAC,MAAM;YACtB,MAAM,IAAIC,MACR,CAAC,mEAAmE,EAAEJ,IAAI;QAE9E;IACF;AACF;AAEA,OAAO,SAASK,kBACdC,mBAA4B,EAC5BC,sBAImB,EACnBC,wBAGqB,EACrBC,kBAEC,EACDC,OAAe,EACfC,YAAuE,EACvEC,OAAgB;IAMhB,MAAMC,UAAU,CAACC;QACf,IACEA,KACA,OAAOA,MAAM,YACb,YAAYA,KACZ,OAAOA,EAAEC,MAAM,KAAK,UACpB;YACA,OAAOD,EAAEC,MAAM;QACjB;QACAC,QAAQC,KAAK,CAAC,2BAA2BpB,YAAYiB;IACvD;IAEA,OAAO;QACL,MAAMI,WAAUnB,QAAQ,EAAEoB,OAAO;YAC/BrB,sBAAsBC;YACtB,MAAMqB,OAAgCV,UAClC;gBAAE,GAAGX,QAAQ;gBAAEsB,UAAUX;YAAQ,IACjC;gBAAE,GAAGX,QAAQ;YAAC;YAClB,IAAIoB,WAAW,WAAWA,SAAS;gBACjCC,KAAKE,MAAM,GAAGH,QAAQI,KAAK;YAC7B;YACA,IAAIJ,SAASK,OAAO;gBAClB,KAAK,MAAM,CAACC,QAAQC,KAAK,IAAIzB,OAAO0B,OAAO,CAACR,QAAQK,KAAK,EAAG;oBAC1DJ,IAAI,CAACxB,iBAAiB6B,OAAO,GAAGC;gBAClC;YACF;YACA,OAAOnB,uBACLa,MACA;gBACEd;gBACAO;gBACAF;YACF,GACA;gBACEiB,mBAAkBC,QAIjB;oBACCV,SAASW,gCAAgCD,SAASE,IAAI,CAACC,EAAE;gBAC3D;YACF;QAEJ;QACA,MAAMC,UAASC,MAAM;YACnB,OAAO1B,yBAAyB0B,QAAQ,CAAC;QAG3C;QACA,MAAMC,YAAWC,cAAc,EAAEC,IAAI,EAAElB,OAAO;YAC5C,MAAM,EAAEmB,2BAA2BC,gBAAgB,EAAE,GACnD,MAAM9B;YAER,MAAM+B,gBAAgBjC,uBAAuB8B,MAAM;gBACjDxB;YACF;YACA,MAAM4B,aAAa,MAAMF,iBAAiBH,gBAAgBI,eAAe;gBACvEE,SAASvB,QAAQuB,OAAO;gBACxBC,WAAWxB,QAAQwB,SAAS;gBAC5BC,OAAOzB,QAAQyB,KAAK;gBACpBC,oBAAoB1B,QAAQ2B,2BAA2B;gBACvDlC;YACF;YACA,OAAO,IAAImC,SAASN,WAAWP,MAAM,EAAE;gBACrCc,QAAQP,WAAWO,MAAM,IAAI7B,QAAQ6B,MAAM,IAAI;gBAC/CC,SAAS;oBAAE,gBAAgB;gBAA2B;YACxD;QACF;IACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactFormState } from 'react-dom/client';
|
|
2
2
|
import type { Config } from '../../config.js';
|
|
3
|
-
export declare function getInput(req: Request, config: Omit<Required<Config>, 'vite'>, temporaryReferences: unknown, decodeReply: (body: string | FormData, options?: object) => Promise<unknown[]>, decodeAction: (body: FormData) => Promise<() => Promise<void
|
|
3
|
+
export declare function getInput(req: Request, config: Omit<Required<Config>, 'vite'>, temporaryReferences: unknown, decodeReply: (body: string | FormData, options?: object) => Promise<unknown[]>, decodeAction: (body: FormData) => Promise<() => Promise<void>> | null, decodeFormState: (actionResult: unknown, body: FormData) => Promise<ReactFormState | undefined>, loadServerAction: (id: string) => Promise<unknown>): Promise<({
|
|
4
4
|
type: "component";
|
|
5
5
|
rscPath: string;
|
|
6
6
|
rscParams: unknown;
|
|
@@ -16,4 +16,5 @@ export declare function getInput(req: Request, config: Omit<Required<Config>, 'v
|
|
|
16
16
|
}) & {
|
|
17
17
|
pathname: string;
|
|
18
18
|
req: Request;
|
|
19
|
+
etags?: import("./etags.js").Etags;
|
|
19
20
|
}>;
|