@fluojs/react 0.1.0 → 0.2.0
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.ko.md +629 -16
- package/README.md +644 -25
- package/dist/decorators.d.ts +3 -3
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +10 -4
- package/dist/diagnostics.d.ts +110 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +180 -0
- package/dist/error-representation.d.ts +28 -0
- package/dist/error-representation.d.ts.map +1 -0
- package/dist/error-representation.js +28 -0
- package/dist/experimental/rsc-flight-response.d.ts.map +1 -1
- package/dist/experimental/rsc-flight-response.js +2 -6
- package/dist/experimental/server-functions-server.js +1 -1
- package/dist/experimental/server-functions-types.d.ts +1 -1
- package/dist/experimental/server-functions-types.d.ts.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/module.d.ts +8 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +31 -8
- package/dist/page-catalog.d.ts +29 -0
- package/dist/page-catalog.d.ts.map +1 -0
- package/dist/page-catalog.js +46 -0
- package/dist/page-metadata.d.ts +58 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +139 -0
- package/dist/page-renderer.d.ts +24 -0
- package/dist/page-renderer.d.ts.map +1 -0
- package/dist/page-renderer.js +18 -0
- package/dist/page-result.d.ts +16 -0
- package/dist/page-result.d.ts.map +1 -0
- package/dist/page-result.js +101 -0
- package/dist/render-diagnostics.d.ts +25 -0
- package/dist/render-diagnostics.d.ts.map +1 -0
- package/dist/render-diagnostics.js +94 -0
- package/dist/render-policy-metadata.d.ts +40 -0
- package/dist/render-policy-metadata.d.ts.map +1 -0
- package/dist/render-policy-metadata.js +136 -0
- package/dist/render-policy.d.ts +87 -0
- package/dist/render-policy.d.ts.map +1 -0
- package/dist/render-policy.js +203 -0
- package/dist/render-stream.d.ts +1 -1
- package/dist/render-stream.d.ts.map +1 -1
- package/dist/render-stream.js +80 -15
- package/dist/render.d.ts +12 -3
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +70 -76
- package/dist/server-entry.d.ts +13 -1
- package/dist/server-entry.d.ts.map +1 -1
- package/dist/server-entry.js +21 -11
- package/dist/typegen-artifact.d.ts +9 -0
- package/dist/typegen-artifact.d.ts.map +1 -0
- package/dist/typegen-artifact.js +116 -0
- package/dist/typegen.d.ts +41 -0
- package/dist/typegen.d.ts.map +1 -0
- package/dist/typegen.js +162 -0
- package/package.json +11 -6
package/README.ko.md
CHANGED
|
@@ -4,19 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
fluo 애플리케이션을 위한 런타임 중립 React 통합입니다.
|
|
6
6
|
|
|
7
|
+
Coordinated Node 24 릴리스를 준비한다면 패키지 업그레이드 전에 [소비자 마이그레이션 가이드](../../docs/getting-started/migrate-node24.ko.md)를 따르세요. React는 `0.x`의 `minor` 릴리스를 유지하며 `1.0`으로 승격하지 않습니다.
|
|
8
|
+
|
|
7
9
|
## 목차
|
|
8
10
|
|
|
9
11
|
- [설치](#설치)
|
|
10
12
|
- [사용 시점](#사용-시점)
|
|
13
|
+
- [Zero-to-First-Page Workflow](#zero-to-first-page-workflow)
|
|
11
14
|
- [Stable SSR Mental Model](#stable-ssr-mental-model)
|
|
15
|
+
- [React 사용자 개념 번역](#react-사용자-개념-번역)
|
|
12
16
|
- [런타임 및 피어 계약](#런타임-및-피어-계약)
|
|
13
17
|
- [Phase Boundaries](#phase-boundaries)
|
|
14
18
|
- [ReactModule Registration](#reactmodule-registration)
|
|
19
|
+
- [Application Page Renderer](#application-page-renderer)
|
|
20
|
+
- [Render Policy Decorators](#render-policy-decorators)
|
|
21
|
+
- [SSR Diagnostic Phases](#ssr-diagnostic-phases)
|
|
22
|
+
- [HTTP Error Documents](#http-error-documents)
|
|
15
23
|
- [Router 및 Path Decorators](#router-및-path-decorators)
|
|
24
|
+
- [Bootstrap-Resolved Page Catalog](#bootstrap-resolved-page-catalog)
|
|
25
|
+
- [Path-Only Page Type Generation](#path-only-page-type-generation)
|
|
26
|
+
- [Consumer Testing Loop](#consumer-testing-loop)
|
|
16
27
|
- [Web Streams SSR](#web-streams-ssr)
|
|
17
28
|
- [Hydration Asset Contract](#hydration-asset-contract)
|
|
18
29
|
- [Vite Asset Manifest Integration](#vite-asset-manifest-integration)
|
|
19
30
|
- [Client Navigation Runtime](#client-navigation-runtime)
|
|
31
|
+
- [Native Form Mutations](#native-form-mutations)
|
|
20
32
|
- [Experimental RSC Prototype](#experimental-rsc-prototype)
|
|
21
33
|
- [Experimental Server Functions](#experimental-server-functions)
|
|
22
34
|
- [RSC Graduation Policy](#rsc-graduation-policy)
|
|
@@ -27,8 +39,8 @@ fluo 애플리케이션을 위한 런타임 중립 React 통합입니다.
|
|
|
27
39
|
|
|
28
40
|
## 설치
|
|
29
41
|
|
|
30
|
-
이
|
|
31
|
-
|
|
42
|
+
이 패키지는 초기 `0.1.0` 릴리스를 완료했으며 더 이상 `0.0.0` bootstrap placeholder를 사용하지
|
|
43
|
+
않습니다. 이후 버전은 commit된 Changeset으로 기록하고 canonical release workflow를 통해서만 게시합니다.
|
|
32
44
|
|
|
33
45
|
패키지가 게시되면 React와 React DOM을 peer로 함께 설치합니다.
|
|
34
46
|
|
|
@@ -44,6 +56,35 @@ router를 일반 module controller metadata에 배치하고, `@Router(...)`와 `
|
|
|
44
56
|
controller 및 `GET` route metadata 위의 React facade이므로 request DTO binding, versioning,
|
|
45
57
|
guards, interceptors, headers, route validation, matching, dispatch는 계속 HTTP runtime contract를 사용합니다.
|
|
46
58
|
|
|
59
|
+
## Zero-to-First-Page Workflow
|
|
60
|
+
|
|
61
|
+
첫 page를 편집하기 전에 모든 stable seam을 직접 조립하지 않고 streamed SSR, hydration, Vite asset을
|
|
62
|
+
사용하려면 공식 generated composition으로 시작하세요.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fluo new my-react-app --starter react-vite-ssr
|
|
66
|
+
cd my-react-app
|
|
67
|
+
pnpm dev
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`/products/sku-42?preview=true`를 열고 `src/page.tsx`를 편집합니다. 명시적인
|
|
71
|
+
`@Router(...)` / `@Path(...)` handler는 `src/app.ts`에 남아 `createElement(ProductPage)`를 하나의
|
|
72
|
+
`ReactElement`로 반환하므로 matching, DTO binding/validation, middleware, guard, interceptor, request scope,
|
|
73
|
+
not-found behavior는 계속 `@fluojs/http`가 소유합니다.
|
|
74
|
+
|
|
75
|
+
Generated wiring은 첫 편집에 부수적인 작업을 다음 application file로 이동합니다.
|
|
76
|
+
|
|
77
|
+
- `src/entry-server.tsx`는 교체 가능한 `ReactPageRenderer`를 소유하고, application이 로드한 manifest를
|
|
78
|
+
`@fluojs/react/vite`로 parse하며, `createReactServerEntry(...)`로 `ReactServerEntry`를 반환합니다.
|
|
79
|
+
- `src/react-app.tsx`는 server rendering과 hydration에 하나의 document,
|
|
80
|
+
`ReactClientRouterProvider`, route snapshot, stylesheet composition을 제공합니다.
|
|
81
|
+
- `src/entry-client.tsx`는 같은 tree를 hydrate하고, `src/main.ts`와 `src/load-manifest.ts`는
|
|
82
|
+
filesystem loading 및 actionable build-output failure를 Node.js application boundary에 유지합니다.
|
|
83
|
+
|
|
84
|
+
Advanced application은 generated renderer를 교체하거나 `createReactServerEntry(...)`에 명시적인
|
|
85
|
+
hydration option을 전달할 수 있으며 아래 stable API도 그대로 사용할 수 있습니다. Runtime-neutral root
|
|
86
|
+
export는 Node.js, Vite, browser code를 import하지 않고, starter는 별도 route matcher를 추가하지 않습니다.
|
|
87
|
+
|
|
47
88
|
## Stable SSR Mental Model
|
|
48
89
|
|
|
49
90
|
안정 `0.1.0` 모델은 HTTP-first React SSR입니다. `@Router(...)`와 `@Path(...)`는
|
|
@@ -56,8 +97,17 @@ guards, interceptors, headers, module middleware, request scope, request lifecyc
|
|
|
56
97
|
따라서 이 패키지는 Next.js App Router clone, React Server Components framework, TanStack route tree,
|
|
57
98
|
Angular `Routes[]` table, file-route scanner, primary React-owned `routes: []` configuration model이
|
|
58
99
|
**아닙니다**. React router는 page 형태를 가진 HTTP handler로 이해하세요. Route discovery와 dispatch는
|
|
59
|
-
기존 fluo module/controller pipeline에 남아
|
|
60
|
-
|
|
100
|
+
기존 fluo module/controller pipeline에 남아 있습니다. Page handler는 일반 HTTP 값을 반환하거나,
|
|
101
|
+
configured application page renderer가 처리할 유효한 `ReactElement` 하나를 반환하거나, route별 SSR option이
|
|
102
|
+
필요할 때 `createReactServerEntry(...)`를 명시적으로 반환할 수 있습니다.
|
|
103
|
+
|
|
104
|
+
## React 사용자 개념 번역
|
|
105
|
+
|
|
106
|
+
Page, layout, loader, action, navigation, pending UI, metadata, hydration, build asset 용어에서
|
|
107
|
+
출발한다면 간결한 [fluo의 React 개념 가이드](../../docs/guides/react-user-concepts.ko.md)를 사용하세요.
|
|
108
|
+
이 가이드는 shipped equivalent, intentionally different behavior, experimental surface, unsupported
|
|
109
|
+
concept를 구분하고, `@Router(...)`/`@Path(...)`에서 application renderer, Vite asset, hydration,
|
|
110
|
+
client navigation, native form mutation으로 이어지는 실행 가능한 path 하나를 따라갑니다.
|
|
61
111
|
|
|
62
112
|
## 런타임 및 피어 계약
|
|
63
113
|
|
|
@@ -129,8 +179,275 @@ class AppModule {}
|
|
|
129
179
|
source가 되므로 `createHandlerMapping(...)`과 `Dispatcher`가 계속 duplicate route detection,
|
|
130
180
|
module-level middleware, request scope 생성, guard 및 interceptor 실행, route versioning을 담당합니다.
|
|
131
181
|
|
|
182
|
+
## Application Page Renderer
|
|
183
|
+
|
|
184
|
+
모든 page가 같은 document shell, provider, hydration asset, route snapshot wiring,
|
|
185
|
+
recoverable-render policy를 공유해야 한다면 application-owned `renderPage` callback 하나를 등록하세요.
|
|
186
|
+
Callback은 `ReactPageRenderer`를 구현하고 하나의 `ReactElement`와 활성 `ReactRenderContext`를 받아
|
|
187
|
+
`ReactServerEntry`를 반환해야 합니다. `ReactModule.forRoot(...)`는 callback을
|
|
188
|
+
`REACT_PAGE_RENDERER`로 등록하고 export하므로 router는 별도 response path를 만들지 않고 callback을
|
|
189
|
+
inject할 수 있습니다.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { Module } from '@fluojs/core';
|
|
193
|
+
import {
|
|
194
|
+
Path,
|
|
195
|
+
ReactModule,
|
|
196
|
+
Router,
|
|
197
|
+
createReactServerEntry,
|
|
198
|
+
type ReactPageRenderer,
|
|
199
|
+
type ReactRenderContext,
|
|
200
|
+
} from '@fluojs/react';
|
|
201
|
+
import {
|
|
202
|
+
ReactClientRouterProvider,
|
|
203
|
+
createReactRouteSnapshot,
|
|
204
|
+
} from '@fluojs/react/client';
|
|
205
|
+
|
|
206
|
+
const hydrationOptions = {
|
|
207
|
+
assetMap: { 'client.js': '/assets/client.123.js' },
|
|
208
|
+
bootstrapModules: ['/assets/client.123.js'],
|
|
209
|
+
} as const;
|
|
210
|
+
|
|
211
|
+
const renderPage: ReactPageRenderer = (page, context) => {
|
|
212
|
+
const initialSnapshot = createReactRouteSnapshot({
|
|
213
|
+
params: context.request.params,
|
|
214
|
+
url: context.request.url,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return createReactServerEntry(
|
|
218
|
+
<html lang="ko">
|
|
219
|
+
<body>
|
|
220
|
+
<ReactClientRouterProvider initialSnapshot={initialSnapshot}>
|
|
221
|
+
{page}
|
|
222
|
+
</ReactClientRouterProvider>
|
|
223
|
+
</body>
|
|
224
|
+
</html>,
|
|
225
|
+
hydrationOptions,
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
@Router('/products')
|
|
230
|
+
class ProductRouter {
|
|
231
|
+
@Path('/:id')
|
|
232
|
+
show(_input: undefined, context: ReactRenderContext) {
|
|
233
|
+
return <main>Product {context.request.params.id}</main>;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@Module({
|
|
238
|
+
imports: [ReactModule.forRoot({ controllers: [ProductRouter], renderPage })],
|
|
239
|
+
})
|
|
240
|
+
class AppModule {}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Root package는 `@fluojs/react/client`나 `@fluojs/react/vite`를 import하지 않습니다. 애플리케이션은 위와
|
|
244
|
+
같이 client helper를 명시적으로 compose할 수 있고, 이미 로드한 manifest를
|
|
245
|
+
`createReactViteAssetManifest(...)`로 처리해 얻은 hydration option을 callback closure에서 사용할 수
|
|
246
|
+
있습니다. Renderer는 manifest discovery, bundle generation, route matching을 수행하지 않으며 DTO
|
|
247
|
+
binding, middleware, guard, interceptor, request scope, abort propagation, shell failure handling,
|
|
248
|
+
recoverable streaming behavior를 우회하지 않습니다.
|
|
249
|
+
|
|
250
|
+
`renderPage`가 configured 상태라면 `@Path(...)` request의 최종 값 중 React
|
|
251
|
+
`isValidElement(...)`가 단일 `ReactElement`로 판별한 값만 callback에 전달합니다. Plain object, string,
|
|
252
|
+
array, `null`, 그 밖의 일반 값은 기존 HTTP response path를 유지합니다. 명시적인 `ReactServerEntry`는
|
|
253
|
+
그대로 유지되고 configured callback을 통과하지 않습니다. 다른 application provider가 같은 renderer를
|
|
254
|
+
명시적으로 호출해야 할 때는 `REACT_PAGE_RENDERER` token을 계속 사용할 수 있습니다.
|
|
255
|
+
|
|
256
|
+
`renderPage`를 설정하지 않고 `ReactElement`를 반환하면 response commit 전에
|
|
257
|
+
`ReactSsrDiagnosticError`가 발생합니다. Code는 `react-ssr-missing-page-renderer`이고 해결 방법을 포함한
|
|
258
|
+
message를 제공합니다. `ReactModule.forRoot({ ..., renderPage })`를 설정하거나
|
|
259
|
+
`createReactServerEntry(...)`를 명시적으로 반환하세요.
|
|
260
|
+
|
|
261
|
+
## Render Policy Decorators
|
|
262
|
+
|
|
263
|
+
React router class 또는 `@Path(...)` method 하나에 route-local composition이 필요하지만 application은
|
|
264
|
+
하나의 `renderPage` callback을 유지해야 할 때 `@PageLayout(...)`, `@SuspenseFallback(...)`,
|
|
265
|
+
`@PageMetadata(...)`를 사용하세요. Layout과 fallback decorator는 미리 생성한 JSX element가 아니라
|
|
266
|
+
component reference를 받고, `PageMetadata`는 synchronous request-aware factory를 받습니다. Metadata는 HTTP
|
|
267
|
+
matching 이후 resolve되어 application page renderer에만 전달되며 path, matching precedence, param, error,
|
|
268
|
+
not-found behavior를 변경하지 않습니다.
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { Suspense, createElement, type ReactElement } from 'react';
|
|
272
|
+
import {
|
|
273
|
+
PageLayout,
|
|
274
|
+
PageMetadata,
|
|
275
|
+
Path,
|
|
276
|
+
ReactModule,
|
|
277
|
+
Router,
|
|
278
|
+
SuspenseFallback,
|
|
279
|
+
createReactPageMetadataElements,
|
|
280
|
+
createReactServerEntry,
|
|
281
|
+
resolveReactPageMetadata,
|
|
282
|
+
type ReactPageLayoutProps,
|
|
283
|
+
type ReactPageRenderer,
|
|
284
|
+
type ReactSuspenseFallbackProps,
|
|
285
|
+
} from '@fluojs/react';
|
|
286
|
+
|
|
287
|
+
function ShopLayout({ children, context }: ReactPageLayoutProps) {
|
|
288
|
+
return <section data-request-path={context.request.path}>{children}</section>;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function ProductFallback({ context }: ReactSuspenseFallbackProps) {
|
|
292
|
+
return <p>Loading {context.request.params.id}…</p>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const renderPage: ReactPageRenderer = (page, context, policies) => {
|
|
296
|
+
const metadata = resolveReactPageMetadata(policies, context);
|
|
297
|
+
const pageBoundary = policies.suspenseFallback === undefined
|
|
298
|
+
? page
|
|
299
|
+
: createElement(Suspense, {
|
|
300
|
+
fallback: createElement(policies.suspenseFallback, { context }),
|
|
301
|
+
}, page);
|
|
302
|
+
const composedPage = policies.layouts.reduceRight<ReactElement>(
|
|
303
|
+
(children, Layout) => createElement(Layout, { children, context }),
|
|
304
|
+
pageBoundary,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
return createReactServerEntry(
|
|
308
|
+
<html lang="ko">
|
|
309
|
+
<head>{createReactPageMetadataElements(metadata)}</head>
|
|
310
|
+
<body>{composedPage}</body>
|
|
311
|
+
</html>,
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
@PageLayout(ShopLayout)
|
|
316
|
+
@Router('/products')
|
|
317
|
+
class ProductRouter {
|
|
318
|
+
@PageMetadata(({ request }) => ({
|
|
319
|
+
meta: [{ content: `Product ${request.params.id}`, name: 'description' }],
|
|
320
|
+
title: `Product ${request.params.id}`,
|
|
321
|
+
}))
|
|
322
|
+
@SuspenseFallback(ProductFallback)
|
|
323
|
+
@Path('/:id')
|
|
324
|
+
show() {
|
|
325
|
+
return <ProductPage />;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
ReactModule.forRoot({ controllers: [ProductRouter], renderPage });
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Resolved layout은 outermost에서 innermost 순서로 base class, derived class, base method, derived method를
|
|
333
|
+
따릅니다. Layout은 inheritance 전체에서 compose됩니다. 가장 가까운 fallback이 우선하므로 method
|
|
334
|
+
fallback은 class fallback을 대체하고 derived declaration은 base declaration을 대체합니다. Class 또는
|
|
335
|
+
method site는 각 policy kind를 한 번만 선언할 수 있으며 같은 site의 duplicate는 bootstrap 중 실패합니다.
|
|
336
|
+
|
|
337
|
+
Metadata factory는 같은 broad-to-specific class/method order를 사용합니다. 가장 가까운 defined title이
|
|
338
|
+
우선합니다. Later `<meta>` descriptor는 같은 `name` 또는 `property` identity의 earlier descriptor를
|
|
339
|
+
대체하고, later `<link>` descriptor는 정확히 같은 `rel`과 `href` pair를 대체합니다. 관련 없는 descriptor는
|
|
340
|
+
declaration order를 유지합니다. `resolveReactPageMetadata(...)`는 freeze된 bounded snapshot을 반환하고,
|
|
341
|
+
`createReactPageMetadataElements(...)`는 ordinary React title, meta, link element를 만들어 text와 attribute
|
|
342
|
+
escaping을 React에 맡깁니다. 두 helper 모두 Vite asset을 discover하거나 script tag를 만들거나 raw HTML을
|
|
343
|
+
허용하거나 임의 inline data를 serialize하지 않습니다.
|
|
344
|
+
|
|
345
|
+
`ReactRenderContext`는 활성 request-scope `container`를 포함합니다. Policy component는 이 context를
|
|
346
|
+
명시적인 prop으로 받지만 fluo가 React component를 DI로 instantiate하거나 token을 대신 resolve하지는
|
|
347
|
+
않습니다. `renderPage` 없는 policy, `@Router(...)` 밖의 class policy, `@Path(...)` 밖의 method policy는
|
|
348
|
+
`ReactRenderPolicyConfigurationError`와 `REACT_RENDER_POLICY_DIAGNOSTIC_CODES`의 stable value로
|
|
349
|
+
bootstrap을 실패시킵니다.
|
|
350
|
+
|
|
351
|
+
Metadata factory는 active `request`, optional `requestId`, handler 및 renderer와 같은 request-scope
|
|
352
|
+
`container` identity를 가진 `ReactPageMetadataContext`를 받습니다. 이 context는 의도적으로 `response`를
|
|
353
|
+
제외하므로 metadata가 status, header, commit timing을 바꿀 수 없습니다. fluo는 factory를 DI로
|
|
354
|
+
instantiate하거나 token을 대신 resolve하지 않습니다. Async data는 page composition 전에 matched HTTP
|
|
355
|
+
handler에서 load해야 하며 metadata factory는 synchronous로 유지됩니다.
|
|
356
|
+
|
|
357
|
+
`@SuspenseFallback(...)`은 SSR 중 suspend하는 descendant를 위한 ordinary React Suspense fallback을
|
|
358
|
+
제공합니다. Handler `await`, effect, event handler, native form submission, full-document/client navigation
|
|
359
|
+
pending state는 관찰하지 않습니다. HTTP pipeline error, not-found/404 response, pre-commit shell failure,
|
|
360
|
+
request abort, post-shell recoverable error는 기존의 별도 phase를 유지합니다. 전체 ordering, inheritance,
|
|
361
|
+
duplicate, phase 결정은 [React render policy decorator decision](../../docs/architecture/react-render-policy-decorators.ko.md)에
|
|
362
|
+
기록되어 있습니다. 후속 [React page render policy decision](../../docs/architecture/react-page-render-policies.ko.md)은
|
|
363
|
+
metadata를 채택하지만 generic error-presentation decorator와 page-local not-found presentation은 거부합니다.
|
|
364
|
+
HTTP pipeline failure, shell failure, post-shell recoverable error, request abort, client React error, unmatched
|
|
365
|
+
route, handler-thrown `NotFoundException` outcome은 기존 owner를 유지합니다. Placeholder error 또는
|
|
366
|
+
not-found export는 제공하지 않습니다.
|
|
367
|
+
|
|
368
|
+
## SSR Diagnostic Phases
|
|
369
|
+
|
|
370
|
+
Application logging이나 diagnostics tooling이 HTTP 및 React rendering boundary 전체에서 하나의 안정된 event
|
|
371
|
+
shape를 사용해야 한다면 `ReactModule.forRoot(...)`에 `onDiagnostic`을 등록하세요.
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
ReactModule.forRoot({
|
|
375
|
+
controllers: [ProductRouter],
|
|
376
|
+
renderPage,
|
|
377
|
+
onDiagnostic(diagnostic) {
|
|
378
|
+
applicationDiagnostics.report(diagnostic);
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
각 `ReactSsrDiagnostic`은 `code`, `phase`, 원본 `error`, 활성 `request`, optional `requestId`를
|
|
384
|
+
포함합니다. Callback은 observational contract입니다. Callback이 throw해도 request outcome을 대체하지
|
|
385
|
+
않습니다. Stable phase와 code는 다음과 같습니다.
|
|
386
|
+
|
|
387
|
+
| Phase | Code | Boundary |
|
|
388
|
+
| --- | --- | --- |
|
|
389
|
+
| `http-pipeline` | `react-ssr-http-pipeline-failure` | React shell rendering 전 DTO binding, middleware, guard, interceptor, handler 및 기타 HTTP pipeline failure입니다. |
|
|
390
|
+
| `http-pipeline` | `react-ssr-missing-page-renderer` | 유효한 `ReactElement`가 `renderPage` 설정 없이 `@Path(...)` response에 도달했습니다. |
|
|
391
|
+
| `pre-commit-shell` | `react-ssr-pre-commit-shell-failure` | Response commit 전 React shell 생성 또는 buffered stream collection이 실패했습니다. 원본 throw된 `Error` identity를 보존합니다. |
|
|
392
|
+
| `request-abort` | `react-ssr-request-abort` | Request signal 또는 abort probe가 React rendering을 중단했습니다. 기존 no-commit 또는 committed-stream abort behavior는 바뀌지 않습니다. |
|
|
393
|
+
| `post-shell-recoverable` | `react-ssr-post-shell-recoverable-error` | Shell을 쓸 수 있게 된 뒤 React가 recoverable render error를 보고했습니다. Status와 header를 다시 쓰지 않습니다. |
|
|
394
|
+
|
|
395
|
+
`REACT_SSR_DIAGNOSTIC_PHASES`와 `REACT_SSR_DIAGNOSTIC_CODES`는 비교에 사용할 수 있는 이 값을
|
|
396
|
+
노출합니다. 기존 entry `onRecoverableError` hook도 `ReactRecoverableErrorContext`의 `code`와 `phase`를
|
|
397
|
+
받습니다.
|
|
398
|
+
|
|
399
|
+
## HTTP Error Documents
|
|
400
|
+
|
|
401
|
+
Application이 HTTP error representation contract가 선택한 optional HTML byte를 React로 만들고 싶다면
|
|
402
|
+
`createReactErrorRepresentationProvider(...)`를 사용한다.
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { bootstrapApplication } from '@fluojs/runtime';
|
|
406
|
+
import {
|
|
407
|
+
createReactErrorRepresentationProvider,
|
|
408
|
+
createReactServerEntry,
|
|
409
|
+
} from '@fluojs/react';
|
|
410
|
+
|
|
411
|
+
const html = createReactErrorRepresentationProvider({
|
|
412
|
+
renderDocument({ json }) {
|
|
413
|
+
return createReactServerEntry(
|
|
414
|
+
<html lang="ko">
|
|
415
|
+
<body><main>{json.error.status}: {json.error.message}</main></body>
|
|
416
|
+
</html>,
|
|
417
|
+
);
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const app = await bootstrapApplication({
|
|
422
|
+
errorRepresentation: { html },
|
|
423
|
+
rootModule: AppModule,
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`@fluojs/http`가 adapter 실행 전에 route-miss conversion과 `Accept` negotiation을 수행한다. Callback은
|
|
428
|
+
canonical JSON과 active request-scope container를 포함한 `HttpErrorRepresentationContext`를 받지만 response
|
|
429
|
+
mutation authority는 받지 않는다. React entry는 HTTP가 status, `Content-Type`, `Vary`, `HEAD` suppression,
|
|
430
|
+
commit을 적용하기 전에 완전히 buffer된다.
|
|
431
|
+
|
|
432
|
+
예제의 `json.error.message`는 JSX text child이므로 React가 escape한다. 그래도 complete trusted-HTML document
|
|
433
|
+
contract는 application이 소유한다. Request-derived 또는 error-derived content를 application이 승인한 sanitizer나
|
|
434
|
+
동등한 trusted-content boundary 없이 `dangerouslySetInnerHTML`로 옮기면 안 된다.
|
|
435
|
+
|
|
436
|
+
이 path에서는 `ReactServerEntry.status`와 `ReactServerEntry.headers`를 무시한다. Helper는
|
|
437
|
+
`ReactPageRenderer`, `PageLayout`, `PageMetadata`, `SuspenseFallback`, page catalog를 호출하지 않고 URL도
|
|
438
|
+
match하지 않는다. Render failure는 HTTP의 one-shot canonical JSON fallback으로 전달된다. Matched-page
|
|
439
|
+
pre-commit shell failure는 별도 React SSR diagnostic phase로 남으며 provider를 호출하지 않는다. 자세한 계약은
|
|
440
|
+
[HTTP error representation decision](../../docs/architecture/http-error-representations.ko.md)을 참고한다.
|
|
441
|
+
|
|
132
442
|
## Router 및 Path Decorators
|
|
133
443
|
|
|
444
|
+
`@Path()`와 `@Path(undefined)`는 `''`를 사용하므로 `@Router('/cats')`에서는 GET `/cats`,
|
|
445
|
+
`@Router()`에서는 GET `/`을 처리합니다. 명시적 `'/'`는 유효 route가 같지만 raw path가
|
|
446
|
+
다르며 prefix를 무시하지 않습니다. Options를 생략하면 React metadata의 `options` 속성을
|
|
447
|
+
만들지 않습니다. `Path(undefined, options)`는 명시적 options를 기록합니다. HTTP, React,
|
|
448
|
+
inspection metadata와 route 충돌/grammar 거부 계약은 유지하며, `PageLayout`,
|
|
449
|
+
`SuspenseFallback`, `PageMetadata`에는 계속 값이 필요합니다.
|
|
450
|
+
|
|
134
451
|
`@Router(basePath)`는 class를 React router로 표시하고 `@Controller(basePath)`와 동등한 HTTP
|
|
135
452
|
controller metadata를 기록합니다. 또한 diagnostics 및 향후 rendering integration을 위해
|
|
136
453
|
`getReactRouterMetadata(...)`로 읽을 수 있는 React router marker metadata를 저장합니다.
|
|
@@ -172,9 +489,151 @@ regex-like token, `user-:id` 같은 mixed literal/parameter segment, `:id.json`
|
|
|
172
489
|
wildcard 도입을 유예합니다. React는 자체 syntax를 추가하지 않습니다. Page handler는 명시적인 server
|
|
173
490
|
route를 유지해야 하며, 향후 catch-all은 먼저 승인된 `@fluojs/http` contract가 되어야 합니다.
|
|
174
491
|
|
|
492
|
+
## Bootstrap-Resolved Page Catalog
|
|
493
|
+
|
|
494
|
+
Tooling에서 React page의 read-only 목록이 필요하면 HTTP handler compilation 이후
|
|
495
|
+
`createReactPageCatalog(...)`를 사용합니다. `createHandlerMapping(...)` 또는
|
|
496
|
+
`app.dispatcher.describeRoutes()`가 제공하는 authoritative `HandlerDescriptor[]`를 넘기세요. 결과에는
|
|
497
|
+
`@Router(...)`와 `@Path(...)` marker를 모두 가진 handler만 descriptor registration order로 포함됩니다.
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
import { createHandlerMapping } from '@fluojs/http';
|
|
501
|
+
import { createReactPageCatalog } from '@fluojs/react';
|
|
502
|
+
|
|
503
|
+
const mapping = createHandlerMapping([{ controllerToken: ProductRouter }]);
|
|
504
|
+
const pages = createReactPageCatalog(mapping.descriptors);
|
|
505
|
+
|
|
506
|
+
console.log(pages[0]);
|
|
507
|
+
// {
|
|
508
|
+
// kind: 'react-page', method: 'GET', path: '/v2/products/:productId',
|
|
509
|
+
// version: '2', params: ['productId'], router: 'ProductRouter', handler: 'show'
|
|
510
|
+
// }
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
`getReactRouterMetadata(...)`와 `getReactPathMetadata(...)`는 router base path, relative page path,
|
|
514
|
+
React option처럼 decorator site에 작성된 static 값을 노출합니다. Catalog는 다릅니다. Method, path,
|
|
515
|
+
version, params, module, router, handler field는 bootstrap-resolved HTTP descriptor에서 project되므로
|
|
516
|
+
controller composition과 URI versioning이 이미 반영되어 있습니다. 반환 array, entry, `params` array는
|
|
517
|
+
모두 freeze된 defensive snapshot입니다.
|
|
518
|
+
|
|
519
|
+
Catalog 생성은 관찰 전용입니다. HTTP matcher를 호출하거나 대체하지 않으며 route conflict detection,
|
|
520
|
+
request dispatch, not-found behavior, non-React handler에 관여하지 않습니다. Route tree, client manifest,
|
|
521
|
+
relative-route model, prefetch layer, cache도 만들지 않습니다.
|
|
522
|
+
|
|
523
|
+
## Path-Only Page Type Generation
|
|
524
|
+
|
|
525
|
+
`fluo typegen`은 application module을 bootstrap하고 compiled React page catalog를 project한 뒤
|
|
526
|
+
application-owned TypeScript route helper를 작성합니다. 출력은 전용 `@fluojs/react/typegen` subpath에서
|
|
527
|
+
생성되며 runtime-neutral package root는 generator를 export하지 않습니다.
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts
|
|
531
|
+
fluo typegen ./src/admin.ts --export AdminModule --output ./src/generated/admin-pages.ts
|
|
532
|
+
fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --check
|
|
533
|
+
fluo typegen ./src/app.ts --output ./src/generated/react-pages.ts --watch
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
명령은 필요하면 output directory를 만들고 stale output을 deterministic하게 덮어쓰며 `CREATE`,
|
|
537
|
+
`UPDATE`, `UNCHANGED` 중 하나를 보고합니다. Atomic replacement는 partial publication을 막고 byte-identical
|
|
538
|
+
run은 파일을 다시 쓰지 않습니다. `--check`는 write 없이 exact generated byte를 비교하며 structure가
|
|
539
|
+
손상된 현재-version body를 stale이 아닌 malformed로 분류합니다. `--watch`는 startup generation 전에
|
|
540
|
+
application-module watcher를 설치하고, 해당 generation 중 관찰된 change를 readiness 전에 한 번 rerun한
|
|
541
|
+
뒤 coalesced change를 serialize하며 generation failure 뒤에도 마지막 valid artifact를 보존합니다. 각
|
|
542
|
+
generation은 native `.js`와 `.mjs` dependency를 현재 module graph에서 평가합니다. 모든 run은
|
|
543
|
+
`app.dispatcher.describeRoutes()`를 읽은 뒤 bootstrap한 application을 닫습니다. 생성 artifact는
|
|
544
|
+
application code에서 import합니다.
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
import {
|
|
548
|
+
reactPageRoutes,
|
|
549
|
+
type ReactPageParams,
|
|
550
|
+
type ReactPagePath,
|
|
551
|
+
type ReactPageRouteId,
|
|
552
|
+
} from './generated/react-pages.js';
|
|
553
|
+
|
|
554
|
+
const productHref = reactPageRoutes['GET /products/:productId ProductRouter show'].href({
|
|
555
|
+
productId: 'desk/chair',
|
|
556
|
+
});
|
|
557
|
+
// /products/desk%2Fchair
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
생성 route id는 stable catalog `id`를 사용합니다. Static builder는 parameter를 받지 않고 dynamic
|
|
561
|
+
builder는 모든 catalog path parameter를 요구하며 각 값을 `encodeURIComponent(...)`로 encode합니다.
|
|
562
|
+
Artifact는 `ReactPagePathById`, `ReactPageParamsById`, `ReactPagePath<RouteId>`,
|
|
563
|
+
`ReactPageParams<RouteId>`, `ReactPageRoute`, `ReactPageLinkProps`, `ReactPageNavigator`도 export합니다.
|
|
564
|
+
|
|
565
|
+
각 generated route는 같은 href builder를 declarative `Link`와 programmatic `push`/`replace` authoring에도
|
|
566
|
+
연결합니다. `link(...)`는 기존 real-anchor `Link`에 전달할 일반 `{ href: string }` object를 반환하고,
|
|
567
|
+
`push(router, ...)`와 `replace(router, ...)`는 같은 absolute href를 resolve한 뒤 기존 `ReactRouter` string
|
|
568
|
+
overload를 호출합니다. 따라서 두 번째 runtime route table 없이 route identity와 정확한 param이 callsite에
|
|
569
|
+
계속 드러납니다.
|
|
570
|
+
|
|
571
|
+
```tsx
|
|
572
|
+
import { Link, useRouter } from '@fluojs/react/client';
|
|
573
|
+
import { reactPageRoutes } from './generated/react-pages.js';
|
|
574
|
+
|
|
575
|
+
const productsRoute = reactPageRoutes['GET /products ProductRouter index'];
|
|
576
|
+
const productRoute = reactPageRoutes['GET /products/:productId ProductRouter show'];
|
|
577
|
+
|
|
578
|
+
function ProductNavigation({ productId }: { readonly productId: string }) {
|
|
579
|
+
const router = useRouter();
|
|
580
|
+
|
|
581
|
+
return (
|
|
582
|
+
<nav>
|
|
583
|
+
<Link {...productsRoute.link()}>Products</Link>
|
|
584
|
+
<Link {...productRoute.link({ productId })}>Current product</Link>
|
|
585
|
+
<button type="button" onClick={() => productRoute.push(router, { productId })}>Open</button>
|
|
586
|
+
<button type="button" onClick={() => productRoute.replace(router, { productId })}>Replace</button>
|
|
587
|
+
</nav>
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Static `link`, `push`, `replace` method는 param을 받지 않고 parameterized method는 모든 path param을
|
|
593
|
+
요구하며 누락되거나 추가된 key를 거부합니다. 기존 generated `href(...)` builder,
|
|
594
|
+
`<Link href={stringOrUrl}>`, `router.push(...)` / `router.replace(...)`의 string 또는 `URL` 호출은 계속
|
|
595
|
+
지원됩니다. Generated method는 absolute href string을 생성하거나 기존 API에 전달할 뿐이므로 real-anchor
|
|
596
|
+
fallback, full-document HTTP navigation, matching, DTO binding, guard, interceptor, not-found behavior는 현재
|
|
597
|
+
owner를 그대로 유지합니다.
|
|
598
|
+
|
|
599
|
+
이 contract는 의도적으로 path-only입니다. Query string, fragment, relative route, optional parameter,
|
|
600
|
+
client route tree를 생성하지 않습니다. Typegen은 `version`이 있는 모든 catalog entry를 거부합니다.
|
|
601
|
+
Compiled catalog만으로는 version selection이 URI, header, media type, custom strategy 중 어디에서
|
|
602
|
+
왔는지 구분할 수 없으므로 하나의 absolute href를 생성하면 실제와 다른 URL contract를 약속할 수
|
|
603
|
+
있기 때문입니다.
|
|
604
|
+
|
|
605
|
+
## Consumer Testing Loop
|
|
606
|
+
|
|
607
|
+
기존 React, HTTP, testing, TypeScript, browser seam을 하나의 layered consumer loop로 사용하세요. 반복 setup은
|
|
608
|
+
일반 fixture로 이미 격리할 수 있고 request test의 대상은 real dispatcher여야 하므로 React-specific testing
|
|
609
|
+
helper를 추가할 필요가 없습니다.
|
|
610
|
+
|
|
611
|
+
1. **Render-policy unit**: Pure policy 및 metadata helper를 직접 호출합니다. Browser bootstrap 없이
|
|
612
|
+
broad-to-specific layout order, nearest fallback 또는 metadata precedence, configuration diagnostic을 검증합니다.
|
|
613
|
+
2. **Request dispatch**: 작성한 module을 `createTestApp({ rootModule })`로 만들고 explicit `@Path(...)` route를
|
|
614
|
+
`app.request(...).send()`로 요청한 뒤 `finally`에서 app을 닫습니다. Direct `ReactElement` return과
|
|
615
|
+
missing-renderer diagnostic을 포함해 real HTTP dispatcher, request scope, response ownership, application
|
|
616
|
+
renderer를 함께 검증합니다.
|
|
617
|
+
3. **Generated-route compilation**: Bootstrapped `HandlerDescriptor` catalog에서 generate하고 positive/negative
|
|
618
|
+
consumer fixture에 TypeScript를 실행하며 CI에서 `fluo typegen ... --check`를 실행합니다. Positive fixture는
|
|
619
|
+
known route id와 complete param을 사용하고 negative fixture는 unknown id, missing/extra param 및 stale,
|
|
620
|
+
malformed, missing, unsupported-version artifact를 검증합니다.
|
|
621
|
+
4. **Hydration**: 같은 application component를 server에서 render하고 `hydrateRoot(...)`로 hydrate합니다.
|
|
622
|
+
Positive fixture는 diagnostic 없이 interaction이 동작해야 합니다. 의도적으로 mismatch한 fixture는
|
|
623
|
+
`onRecoverableError`를 capture해 mismatch reporting 자체의 regression을 막습니다.
|
|
624
|
+
5. **Production browser 및 progressive enhancement**: Build asset을 대상으로 Playwright를 실행하고
|
|
625
|
+
console/page error에서 실패하게 합니다. 별도 `javaScriptEnabled: false` context에서 native form을 일반
|
|
626
|
+
`POST` → `303` → `GET` flow로 submit합니다.
|
|
627
|
+
|
|
628
|
+
Executable reference는 `src/render-policy.test.ts`, `src/direct-page-return.test.ts`,
|
|
629
|
+
`../cli/src/commands/typegen-navigation.test.ts`, `../../examples/react-vite-ssr/src/app.test.ts`,
|
|
630
|
+
`../../examples/react-vite-ssr/src/hydration.test.ts`,
|
|
631
|
+
`../../examples/react-vite-ssr/tests/production-hydration.spec.ts`입니다.
|
|
632
|
+
|
|
175
633
|
## Web Streams SSR
|
|
176
634
|
|
|
177
|
-
|
|
635
|
+
Configured application page renderer를 통해 `ReactElement` 하나를 반환하거나
|
|
636
|
+
`createReactServerEntry(...)`를 명시적으로 반환하면 기존 fluo HTTP dispatcher를 통해 HTML을
|
|
178
637
|
streaming합니다. Guard, interceptor, module middleware, route header, `@HttpCode(...)`, DTO binding,
|
|
179
638
|
request scope, duplicate route detection은 모두 `renderReactResponse(...)`가 HTML response를 finalize하기
|
|
180
639
|
전에 실행됩니다.
|
|
@@ -212,6 +671,9 @@ Renderer는 기본적으로 `react-dom/server`의 `renderToReadableStream(...)`
|
|
|
212
671
|
throw합니다. Recoverable Suspense error는 `onRecoverableError`로 보고되며 이미 committed된 status를 다시
|
|
213
672
|
쓰지 않습니다. Handler가 entry를 dispatcher에 반환하지 않고 직접 response를 finalize해야 할 때만
|
|
214
673
|
`renderReactResponse(entry, requestContext)`를 호출하세요.
|
|
674
|
+
Streaming host에서는 response sink가 일찍 닫히거나 `write(...)` / `waitForDrain()`이 실패하면 완료되지
|
|
675
|
+
않은 React reader를 정확히 한 번 cancel하고 lock을 해제합니다. Sink failure는 reader cancellation cleanup에
|
|
676
|
+
의해 대체되지 않고 원래 failure로 보고됩니다.
|
|
215
677
|
|
|
216
678
|
## Hydration Asset Contract
|
|
217
679
|
|
|
@@ -407,6 +869,82 @@ server는 명시적인 `@Path(...)`/HTTP route를 match하거나 정상적인 no
|
|
|
407
869
|
의도적인 deployment-level document rewrite를 별도로 설정할 수 있지만, 이는 React route grammar를 만들거나
|
|
408
870
|
server DTO validation을 변경하지 않습니다.
|
|
409
871
|
|
|
872
|
+
## Native Form Mutations
|
|
873
|
+
|
|
874
|
+
React page mutation이 hydration 전이나 client JavaScript disabled 환경에서도 동작해야 한다면 native HTML
|
|
875
|
+
form을 사용하세요. React-owned action transport를 만들지 말고 일반 `@Post(...)` route로 제출합니다. 실행 가능한
|
|
876
|
+
`examples/react-vite-ssr/` slice는 `multipart/form-data`를 사용합니다. Browser가 multipart boundary를
|
|
877
|
+
제공하면 기존 adapter와 `@RequestDto(...)` body-binding path가 field를 materialize합니다.
|
|
878
|
+
|
|
879
|
+
```tsx
|
|
880
|
+
function ProductForm({ name, sku }: { readonly name: string; readonly sku: string }) {
|
|
881
|
+
return (
|
|
882
|
+
<form action={`/products/${encodeURIComponent(sku)}`} encType="multipart/form-data" method="post">
|
|
883
|
+
<label htmlFor="product-name">Product name</label>
|
|
884
|
+
<input defaultValue={name} id="product-name" minLength={3} name="name" required />
|
|
885
|
+
<button type="submit">Save product</button>
|
|
886
|
+
</form>
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
Target은 일반 HTTP handler로 남습니다. DTO binding과 validation은 request boundary에서 한 번 실행되고,
|
|
892
|
+
module middleware, guard, interceptor, request-scoped provider, observer, adapter response writing은 일반
|
|
893
|
+
HTTP pipeline의 ordering과 ownership을 그대로 유지합니다.
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
import {
|
|
897
|
+
FromBody,
|
|
898
|
+
FromPath,
|
|
899
|
+
Post,
|
|
900
|
+
RequestDto,
|
|
901
|
+
type RequestContext,
|
|
902
|
+
UseGuards,
|
|
903
|
+
UseInterceptors,
|
|
904
|
+
} from '@fluojs/http';
|
|
905
|
+
import { Router } from '@fluojs/react';
|
|
906
|
+
import { IsString, MinLength } from '@fluojs/validation';
|
|
907
|
+
|
|
908
|
+
class RenameProductRequest {
|
|
909
|
+
@MinLength(3, {
|
|
910
|
+
code: 'PRODUCT_NAME_TOO_SHORT',
|
|
911
|
+
message: 'Product name must contain at least 3 characters.',
|
|
912
|
+
})
|
|
913
|
+
@IsString()
|
|
914
|
+
@FromBody('name')
|
|
915
|
+
name = '';
|
|
916
|
+
|
|
917
|
+
@IsString()
|
|
918
|
+
@FromPath('sku')
|
|
919
|
+
sku = '';
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
@Router('/products')
|
|
923
|
+
class ProductRouter {
|
|
924
|
+
@Post('/:sku')
|
|
925
|
+
@RequestDto(RenameProductRequest)
|
|
926
|
+
@UseGuards(EditorGuard)
|
|
927
|
+
@UseInterceptors(MutationAuditInterceptor)
|
|
928
|
+
rename(input: RenameProductRequest, context: RequestContext) {
|
|
929
|
+
productCatalog.rename(input.sku, input.name);
|
|
930
|
+
context.response.redirect(303, `/products/${encodeURIComponent(input.sku)}?updated=true`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
잘못된 제출은 canonical HTTP `400` validation envelope를 유지합니다. `details` entry에는 안전한 field,
|
|
936
|
+
source, code, application-authored validation message만 포함하고 제출 값, stack, internal exception은 노출하지
|
|
937
|
+
않습니다. 성공한 mutation은 `303 See Other`를 사용하므로 browser가 `GET`으로 후속 요청을 보내고, destination은
|
|
938
|
+
일반 HTTP dispatcher를 통해 다시 match됩니다. Authorization과 CSRF policy는 애플리케이션 책임입니다.
|
|
939
|
+
Non-React route와 같은 session/cookie, guard, middleware policy를 사용하세요.
|
|
940
|
+
|
|
941
|
+
이 recipe는 React Router action/fetcher, Astro Actions, Next.js Server Actions와 의도적으로 다릅니다. fluo는
|
|
942
|
+
function reference를 compile하거나, route matching을 소유하거나, loader/client cache를 revalidate하거나,
|
|
943
|
+
document response를 교체하지 않습니다. Experimental fluo Server Functions transport와도 별개입니다. Native
|
|
944
|
+
form이 이미 완전한 fallback을 제공하고 `@fluojs/react/client`가 mutation route나 cache invalidation을 소유하지
|
|
945
|
+
않으므로 이 phase에서는 stable submit-state helper를 추가하지 않습니다. Application은 실제 form action과
|
|
946
|
+
native submission을 유지하는 경우에만 hydration 이후 local pending UI를 추가할 수 있습니다.
|
|
947
|
+
|
|
410
948
|
## Experimental RSC Prototype
|
|
411
949
|
|
|
412
950
|
> **Experimental contract:** `@fluojs/react/experimental/rsc`는 명시적인 graduation issue가 RSC API를
|
|
@@ -474,6 +1012,10 @@ Flight encoding도 애플리케이션이 소유합니다. 선택한 renderer가
|
|
|
474
1012
|
interceptor, request scope, error, adapter response writing을 계속 소유합니다. Helper는 고정된
|
|
475
1013
|
`text/x-component; charset=utf-8` content type만 추가하며 별도 router를 만들지 않습니다.
|
|
476
1014
|
|
|
1015
|
+
Streamed Flight payload도 stable SSR과 같은 lifecycle guarantee를 사용합니다. Response sink가 일찍 닫히거나
|
|
1016
|
+
write/drain이 실패하면 완료되지 않은 reader를 정확히 한 번 cancel하고 lock을 해제하며, sink failure는 일반
|
|
1017
|
+
HTTP error pipeline에 그대로 전달합니다.
|
|
1018
|
+
|
|
477
1019
|
```ts
|
|
478
1020
|
import { Controller, Get } from '@fluojs/http';
|
|
479
1021
|
import { createReactFlightResponse } from '@fluojs/react/experimental/rsc';
|
|
@@ -624,9 +1166,15 @@ stable subpath를 추가하지 않고 deprecation window도 시작하지 않습
|
|
|
624
1166
|
- Next.js App Router, TanStack route tree, Angular `Routes[]`, file-route scanner, React-owned
|
|
625
1167
|
`routes: []` table
|
|
626
1168
|
- 자동 client bundle 생성
|
|
1169
|
+
- versioned React page의 href 생성. Catalog가 URI versioning과 non-path version strategy를 구분할 수
|
|
1170
|
+
있을 때까지 path-only typegen은 versioned catalog entry를 거부합니다.
|
|
627
1171
|
- filesystem scanning 또는 자동 manifest file discovery. 이미 로드한 manifest 값을 `@fluojs/react/vite`에 넘기세요.
|
|
628
1172
|
- `bootstrapScriptContent`로 임의 data를 자동 serialize하는 기능
|
|
629
1173
|
- `renderToPipeableStream(...)` 같은 Node 전용 `react-dom/server` pipeable stream root API
|
|
1174
|
+
- Next.js-style segment `loading`, `error`, `notFound`, template 또는 layout ancestry semantic.
|
|
1175
|
+
`@SuspenseFallback(...)`은 SSR-descendant Suspense metadata만 제공합니다.
|
|
1176
|
+
- generic page error-presentation policy 또는 page-local not-found renderer. Optional HTML은
|
|
1177
|
+
global/application HTTP representation이며 page ancestry나 matching semantic을 추가하지 않습니다.
|
|
630
1178
|
|
|
631
1179
|
## Public API
|
|
632
1180
|
|
|
@@ -634,16 +1182,55 @@ stable subpath를 추가하지 않고 deprecation window도 시작하지 않습
|
|
|
634
1182
|
- `Path` — HTTP `GET` route metadata와 React render metadata를 함께 기록하는 method decorator입니다.
|
|
635
1183
|
- `getReactRouterMetadata` — router class에서 React router marker metadata를 읽습니다.
|
|
636
1184
|
- `getReactPathMetadata` — router method에서 React render metadata를 읽습니다.
|
|
1185
|
+
- `createReactPageCatalog` — matching이나 dispatch에 관여하지 않고 authoritative compiled HTTP
|
|
1186
|
+
descriptor에서 freeze된 read-only React page catalog를 만듭니다.
|
|
1187
|
+
- `ReactPageCatalogEntry` — effective HTTP method/path/version/params와 originating router/handler를
|
|
1188
|
+
담는 type-only bootstrap-resolved page descriptor입니다.
|
|
1189
|
+
- `@fluojs/react/typegen` subpath — package root를 넓히거나 runtime route table을 추가하지 않고
|
|
1190
|
+
deterministic path-only declaration, versioned artifact check, absolute href builder, route-bound `Link` prop,
|
|
1191
|
+
typed `push`/`replace` method를 제공하는 `generateReactPageTypes(...)`,
|
|
1192
|
+
`inspectReactPageTypeArtifact(...)`, `REACT_PAGE_TYPEGEN_ARTIFACT_VERSION`,
|
|
1193
|
+
`ReactPageTypeArtifactInspection`, `ReactPageTypegenError`, `REACT_PAGE_TYPEGEN_ERROR_CODES`,
|
|
1194
|
+
`ReactPageTypegenErrorCode`를 제공합니다.
|
|
637
1195
|
- `ReactModule` — `forRoot(...)`가 기존 fluo module/controller metadata path를 통해 React router를
|
|
638
1196
|
등록하는 런타임 중립 module facade입니다.
|
|
1197
|
+
- `REACT_PAGE_RENDERER` — `ReactModule.forRoot({ renderPage })`가 등록하는 application page renderer의
|
|
1198
|
+
dependency-injection token입니다.
|
|
1199
|
+
- `ReactPageRenderer` — `ReactElement`와 활성 `ReactRenderContext`를 기존 `ReactServerEntry`로 compose하는
|
|
1200
|
+
과정에 resolved `ReactRenderPolicies`도 전달받는 type-only application callback입니다.
|
|
1201
|
+
- `PageLayout`, `SuspenseFallback`, `PageMetadata` — renderer-only component reference 또는 synchronous
|
|
1202
|
+
metadata factory 하나를 기록하는 class-or-method decorator입니다.
|
|
1203
|
+
- `resolveReactPageMetadata` — response mutation authority를 노출하지 않고 active request의 ordered metadata
|
|
1204
|
+
factory를 compose합니다.
|
|
1205
|
+
- `createReactPageMetadataElements` — resolved metadata snapshot에서 ordinary escaped React title, meta, link
|
|
1206
|
+
element를 생성합니다.
|
|
1207
|
+
- `getReactRenderPolicies` — inherited class/method policy를 outer-to-inner order로 resolve합니다.
|
|
1208
|
+
- `REACT_RENDER_POLICY_DIAGNOSTIC_CODES` 및 `ReactRenderPolicyConfigurationError` — duplicate,
|
|
1209
|
+
invalid-target, invalid-reference, missing-renderer policy declaration을 위한 stable bootstrap diagnostic입니다.
|
|
1210
|
+
- `ReactPageLayout`, `ReactPageLayoutProps`, `ReactSuspenseFallback`,
|
|
1211
|
+
`ReactSuspenseFallbackProps`, `ReactRenderPolicies`, `ReactRenderPolicyDiagnosticCode` — type-only
|
|
1212
|
+
render-policy composition contract입니다.
|
|
1213
|
+
- `ReactPageMetadata`, `ReactPageMetadataFactory`, `ReactPageMetadataContext`, `ReactPageMeta`,
|
|
1214
|
+
`ReactPageLink` — type-only bounded page metadata contract입니다.
|
|
1215
|
+
- `REACT_SSR_DIAGNOSTIC_PHASES` 및 `REACT_SSR_DIAGNOSTIC_CODES` — stable machine-readable SSR
|
|
1216
|
+
lifecycle phase 및 diagnostic code constant입니다.
|
|
1217
|
+
- `ReactSsrDiagnosticError` — stable `code`와 `phase` metadata를 가진 typed pre-commit
|
|
1218
|
+
configuration/render failure입니다.
|
|
1219
|
+
- `ReactSsrDiagnostic`, `ReactSsrDiagnosticCode`, `ReactSsrDiagnosticErrorOptions`,
|
|
1220
|
+
`ReactSsrDiagnosticHandler`, `ReactSsrDiagnosticPhase` — application diagnostics tooling을 위한
|
|
1221
|
+
type-only contract입니다.
|
|
1222
|
+
- `createReactErrorRepresentationProvider` — application React error document renderer를 HTTP-owned HTML
|
|
1223
|
+
provider seam에 adapt하고 commit 전에 buffer합니다.
|
|
1224
|
+
- `ReactErrorDocumentRenderer`, `ReactErrorRepresentationProviderOptions` — application callback, optional
|
|
1225
|
+
availability constraint, renderer override를 위한 type-only contract입니다.
|
|
639
1226
|
- `createReactServerEntry` — page handler가 Web Streams SSR을 위해 반환하는 runtime-neutral React server
|
|
640
1227
|
entry를 생성합니다.
|
|
641
1228
|
- `renderReactResponse` — lazy `react-dom/server` loading으로 React server entry 하나를 fluo HTML
|
|
642
1229
|
response에 렌더링합니다.
|
|
643
1230
|
- `ReactAssetMap`, `ReactBootstrapAsset`, `ReactBootstrapScriptDescriptor` — build-produced asset map 및
|
|
644
1231
|
React DOM bootstrap script/module entry를 위한 type-only contract입니다.
|
|
645
|
-
- `ReactModuleOptions` — `controllers`, `imports`, `providers`, `exports`, module-level `middleware
|
|
646
|
-
`ReactModule.forRoot(...)` option입니다.
|
|
1232
|
+
- `ReactModuleOptions` — `controllers`, `imports`, `providers`, `exports`, module-level `middleware`, optional
|
|
1233
|
+
`renderPage` registration을 포함하는 `ReactModule.forRoot(...)` option입니다.
|
|
647
1234
|
- `ReactServerEntry`, `ReactServerEntryOptions`, `ReactServerEntryHeaders`,
|
|
648
1235
|
`ReactRecoverableErrorHandler`, `ReactRecoverableErrorContext`, `ReactRenderContext`,
|
|
649
1236
|
`ReactReadableStream`, `ReactReadableStreamRenderer`, `ReactReadableStreamRenderOptions`,
|
|
@@ -653,25 +1240,42 @@ stable subpath를 추가하지 않고 deprecation window도 시작하지 않습
|
|
|
653
1240
|
integration을 위한 type-only metadata contract입니다.
|
|
654
1241
|
- `@fluojs/react/vite` subpath — Vite manifest를 root에서 Vite를 import하지 않고 안정 hydration asset contract로
|
|
655
1242
|
파싱하는 `createReactViteAssetManifest(...)`, `ReactViteBuildManifest`, `ReactViteBuildManifestChunk`,
|
|
656
|
-
`
|
|
1243
|
+
`ReactViteManifestEntries`, `ReactViteManifestOptions`, `ReactViteManifestDiagnostic`,
|
|
1244
|
+
`ReactViteManifestDiagnosticCode`, `ReactViteAssetManifest`, `ReactViteAssetManifestResult`,
|
|
657
1245
|
`ReactViteHydrationOptions`, `ReactViteJavaScriptAssets`, `ReactViteBootstrapData`,
|
|
658
1246
|
`ReactViteResolvedEntry`를 제공합니다.
|
|
659
1247
|
- `@fluojs/react/client` subpath — root package를 넓히거나 client route grammar를 추가하지 않고
|
|
660
1248
|
progressive HTTP-first browser navigation을 제공하는 `Link`, `ReactClientRouterProvider`,
|
|
661
|
-
`
|
|
662
|
-
`
|
|
663
|
-
|
|
664
|
-
`
|
|
665
|
-
|
|
666
|
-
`
|
|
667
|
-
|
|
1249
|
+
`ReactClientNavigationError`, `ReactClientRouterContextError`, `createReactRouteSnapshot(...)`,
|
|
1250
|
+
`useRouter()`, `usePathname()`, `useParams()`, `useSearchParams()`, `useNavigation()`,
|
|
1251
|
+
`useRouterState()`를 제공합니다. Type export는 `LinkProps`, `ReactClientNavigationErrorCode`,
|
|
1252
|
+
`ReactClientRouterProviderProps`, `ReactNavigationSnapshot`, `ReactNavigationStatus`,
|
|
1253
|
+
`ReactNavigationType`, `ReactReadonlySearchParams`, `ReactRouteSnapshot`,
|
|
1254
|
+
`ReactRouteSnapshotInput`, `ReactRouter`입니다.
|
|
1255
|
+
- `@fluojs/react/experimental/rsc` subpath — runtime export는 `REACT_RSC_DIAGNOSTIC_CODES`,
|
|
1256
|
+
`REACT_RSC_FLIGHT_CONTENT_TYPE`, `REACT_RSC_SUPPORTED_VERSION`,
|
|
1257
|
+
`REACT_SERVER_FUNCTION_ERROR_CODES`, `REACT_SERVER_FUNCTION_REQUEST_HEADER`,
|
|
1258
|
+
`ReactServerFunctionClientError`, `ReactServerFunctionConfigurationError`,
|
|
1259
|
+
`createReactFlightResponse(...)`, `createReactRscManifest(...)`,
|
|
1260
|
+
`createReactServerFunctionClient(...)`, `createReactServerFunctionRegistry(...)`,
|
|
1261
|
+
`inspectReactRscEnvironment(...)`입니다. Type export는 `ReactFlightPayload`, `ReactFlightResponse`,
|
|
1262
|
+
`ReactFlightResponseHeaders`, `ReactFlightResponseOptions`, `ReactRscBuildCapabilities`,
|
|
1263
|
+
`ReactRscClientReference`, `ReactRscClientReferenceManifest`, `ReactRscDiagnostic`,
|
|
1264
|
+
`ReactRscDiagnosticCode`, `ReactRscEnvironmentOptions`, `ReactRscManifest`,
|
|
1265
|
+
`ReactRscManifestInput`, `ReactRscManifestResult`, `ReactRscRuntimeCapabilities`,
|
|
1266
|
+
`ReactRscServerClientModuleMap`, `ReactRscSupportResult`, `ReactServerFunctionClient`,
|
|
1267
|
+
`ReactServerFunctionClientOptions`, `ReactServerFunctionErrorCode`, `ReactServerFunctionFetch`,
|
|
1268
|
+
`ReactServerFunctionHandler`, `ReactServerFunctionReference`, `ReactServerFunctionRegistry`,
|
|
1269
|
+
`ReactServerFunctionRegistryOptions`, `ReactServerFunctionResponse`,
|
|
1270
|
+
`ReactServerFunctionValue`이며 root나 stable client subpath에서 re-export하지 않습니다.
|
|
668
1271
|
|
|
669
1272
|
## 관련 패키지
|
|
670
1273
|
|
|
671
1274
|
- `@fluojs/core`: 스캐폴드가 사용하는 standard `@Module` decorator를 제공합니다.
|
|
672
1275
|
- `@fluojs/http`: `@Router(...)`와 `@Path(...)`가 재사용하는 controller, route, DTO, guard,
|
|
673
1276
|
interceptor, header, version metadata pipeline을 제공합니다.
|
|
674
|
-
- `@fluojs/runtime`:
|
|
1277
|
+
- `@fluojs/runtime`: React root import boundary를 넓히지 않고 기존 application bootstrap contract와 함께
|
|
1278
|
+
optional HTTP error representation을 등록합니다.
|
|
675
1279
|
- `@fluojs/vite`: Vite TC39 decorator transform boundary를 소유합니다. React hydration manifest를 파싱하지
|
|
676
1280
|
않으므로 React server/client asset mapping에는 `@fluojs/react/vite`를 사용하세요.
|
|
677
1281
|
- Application-selected Flight renderer: RSC payload를 encode하고 renderer-specific build manifest를
|
|
@@ -688,6 +1292,7 @@ stable subpath를 추가하지 않고 deprecation window도 시작하지 않습
|
|
|
688
1292
|
- `packages/react/src/experimental/rsc.test.ts`
|
|
689
1293
|
- `packages/react/src/experimental/rsc-diagnostics.test.ts`
|
|
690
1294
|
- `packages/react/src/experimental/rsc-flight.test.ts`
|
|
1295
|
+
- `packages/react/src/experimental/rsc-flight-stream-lifecycle.test.ts`
|
|
691
1296
|
- `packages/react/src/experimental/rsc-manifest.test.ts`
|
|
692
1297
|
- `packages/react/src/experimental/server-functions-server.ts`
|
|
693
1298
|
- `packages/react/src/experimental/server-functions-client.ts`
|
|
@@ -698,7 +1303,15 @@ stable subpath를 추가하지 않고 deprecation window도 시작하지 않습
|
|
|
698
1303
|
- `packages/react/src/decorators.ts`
|
|
699
1304
|
- `packages/react/src/server-entry.ts`
|
|
700
1305
|
- `packages/react/src/render.ts`
|
|
1306
|
+
- `packages/react/src/error-representation.ts`
|
|
1307
|
+
- `packages/react/src/error-representation.test.ts`
|
|
701
1308
|
- `packages/react/src/module.ts`
|
|
1309
|
+
- `packages/react/src/page-renderer.ts`
|
|
1310
|
+
- `packages/react/src/render-policy.ts`
|
|
1311
|
+
- `packages/react/src/render-policy-metadata.ts`
|
|
1312
|
+
- `packages/react/src/render-policy.test.ts`
|
|
1313
|
+
- `packages/react/src/page-metadata.ts`
|
|
1314
|
+
- `packages/react/src/page-metadata.test.ts`
|
|
702
1315
|
- `packages/react/src/render.test.ts`
|
|
703
1316
|
- `packages/react/src/dispatcher-ssr.test.ts`
|
|
704
1317
|
- `packages/react/src/hydration-assets.test.ts`
|