@bleedingdev/modern-js-main-doc 3.5.0-ultramodern.0 → 3.5.0-ultramodern.2

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.
@@ -45,6 +45,13 @@ import EnableBFFCaution from "@site-docs-en/components/enable-bff-caution";
45
45
 
46
46
  `bff.effect` is only effective when `bff.runtimeFramework` is set to `'effect'`.
47
47
 
48
+ Generated UltraModern workspaces use this runtime as the only generated HTTP API
49
+ path. The API contract lives at `shared/api.ts`, the server runtime lives at
50
+ `api/index.ts`, clients live under `src/api/*-client.ts`, and generated checks
51
+ reject `api/effect`, `api/lambda`, `shared/effect`, `src/effect`, Hono server
52
+ imports, raw request handlers, manual request parsing, and manual `Response`
53
+ construction in API modules.
54
+
48
55
  For Effect v4, TypeScript should use export-map aware module resolution.
49
56
 
50
57
  ```json title="tsconfig.json"
@@ -186,3 +193,97 @@ export default defineConfig({
186
193
  `batch.flushIntervalMs` controls the client-side micro-batch window in the generated Effect client. `maxConcurrency` and `requestTimeoutMs` are applied by the server batch gateway when dispatching items.
187
194
 
188
195
  The generated `api.client.*` API only exists for loader-materialized `@api/index` imports. Directly importing the server entry (`api/index`) exposes the Effect BFF definition; its `client` property is a placeholder that fails on operation access.
196
+
197
+ ## Effect cohort
198
+
199
+ UltraModern-generated workspaces pin the framework-compatible Effect cohort
200
+ through `pnpm-workspace.yaml` overrides. For `3.5.0-ultramodern.2`, generated
201
+ apps use:
202
+
203
+ ```yaml
204
+ overrides:
205
+ '@effect/vitest': 4.0.0-beta.91
206
+ effect: 4.0.0-beta.91
207
+ ```
208
+
209
+ Do not add a different direct `effect` version in an app package. A mismatched
210
+ Effect beta can fail while building layers or HTTP middleware because runtime
211
+ services come from different package instances.
212
+
213
+ ## Contract tests
214
+
215
+ Strict Effect APIs should test the declared `HttpApi` contract, not a raw
216
+ request handler. Edge-compatible tests can use the framework helper:
217
+
218
+ ```ts
219
+ import { createEffectBffTestHandler } from '@modern-js/plugin-bff/effect-edge';
220
+ import apiModule from '../api/index';
221
+
222
+ const testApi = await createEffectBffTestHandler({
223
+ module: apiModule,
224
+ prefix: '/api',
225
+ });
226
+
227
+ const response = await testApi.handler(new Request('http://localhost/api/ping'));
228
+ ```
229
+
230
+ If you manually compose an Effect web handler in a low-level proof, provide the
231
+ platform services explicitly:
232
+
233
+ ```ts
234
+ import {
235
+ HttpApiBuilder,
236
+ HttpRouter,
237
+ HttpServer,
238
+ Layer,
239
+ } from '@modern-js/plugin-bff/effect-server';
240
+
241
+ const handler = HttpRouter.toWebHandler(
242
+ HttpApiBuilder.layer(api).pipe(
243
+ Layer.provide(apiGroupLayer),
244
+ Layer.provide(HttpServer.layerServices),
245
+ ),
246
+ ).handler;
247
+ ```
248
+
249
+ Prefer the generated helper unless the test needs to inspect the low-level
250
+ Effect router composition.
251
+
252
+ ## Dynamic CORS
253
+
254
+ For dynamic origin predicates, use Effect HTTP middleware as a layer. In the
255
+ current Effect v4 beta cohort, `HttpRouter.middleware(...)` returns a `Layer`
256
+ directly:
257
+
258
+ ```ts
259
+ import {
260
+ Effect,
261
+ HttpApiBuilder,
262
+ HttpMiddleware,
263
+ HttpRouter,
264
+ Layer,
265
+ } from '@modern-js/plugin-bff/effect-server';
266
+
267
+ const corsLayer = HttpRouter.middleware(
268
+ Effect.succeed(
269
+ HttpMiddleware.cors({
270
+ allowedOrigins: origin =>
271
+ origin.endsWith('.example.com') ? origin : undefined,
272
+ }),
273
+ ),
274
+ );
275
+
276
+ const layer = HttpApiBuilder.layer(api).pipe(
277
+ Layer.provide(apiGroupLayer),
278
+ Layer.provide(corsLayer),
279
+ );
280
+ ```
281
+
282
+ Do not read a `.layer` property from `HttpRouter.middleware(...)`.
283
+
284
+ ## Other transports
285
+
286
+ `strictEffectApproach` governs generated HTTP API modules. Effect RPC,
287
+ WebSockets, and other transports remain valid when they are modeled as explicit
288
+ transport surfaces with typed Effect programs. They do not make raw HTTP
289
+ request handlers valid inside generated UltraModern API modules.
@@ -78,12 +78,33 @@ mise exec -- pnpm check
78
78
  mise exec -- pnpm build
79
79
  ```
80
80
 
81
- Strict generated API migration requires `3.5.0-ultramodern.0` or newer.
81
+ Strict generated API migration requires `3.5.0-ultramodern.2` or newer.
82
82
  `3.4.0-ultramodern.20` and earlier cohorts do not include this full direct
83
- `api/index.ts` generator, generated `.mts` checks, and strict Oxlint boundary
84
- rule set. Agents that cannot install that BleedingDev cohort yet should use the
85
- local Modern.js workspace for migration validation; otherwise pin
86
- `3.5.0-ultramodern.0` or newer with `--ultramodern-package-version`.
83
+ `api/index.ts` generator, generated `.mts` checks, strict Oxlint boundary rule
84
+ set, Effect cohort overrides, and strict Effect migration command. Agents that
85
+ cannot install that BleedingDev cohort yet should use the local Modern.js
86
+ workspace for migration validation; otherwise pin `3.5.0-ultramodern.2` or
87
+ newer with `--ultramodern-package-version`.
88
+
89
+ Before hand-editing package aliases or generated metadata, run the framework
90
+ migration command from the target workspace:
91
+
92
+ ```bash
93
+ pnpm dlx @bleedingdev/modern-js-create@3.5.0-ultramodern.2 ultramodern \
94
+ migrate-strict-effect --version 3.5.0-ultramodern.2
95
+ pnpm api:check
96
+ pnpm contract:check
97
+ ```
98
+
99
+ The command updates `.modernjs/ultramodern.json`, root
100
+ `modernjs.packageSource`, generated Modern package aliases, direct topology API
101
+ metadata, and the pnpm lockfile. Remaining failures are source migration work:
102
+ move code to `shared/api.ts`, `api/index.ts`, and `src/api/*-client.ts`, then
103
+ delete `api/effect`, `api/lambda`, `shared/effect`, and `src/effect`.
104
+
105
+ Generated strict Effect workspaces pin the compatible Effect cohort with pnpm
106
+ overrides: `effect@4.0.0-beta.91` and `@effect/vitest@4.0.0-beta.91`. Do not
107
+ add app-local direct Effect versions that disagree with those overrides.
87
108
 
88
109
  Gradual migration means old, unmigrated Modern.js apps can keep their existing
89
110
  runtime until they are converted. Once a surface is generated or migrated as
@@ -122,6 +143,17 @@ Effect RPC, WebSockets, and other transports should be added as explicit
122
143
  transport surfaces when needed. They do not make raw request handlers valid
123
144
  inside generated HTTP API modules.
124
145
 
146
+ Strict API tests should exercise the `HttpApi` contract. Use
147
+ `createEffectBffTestHandler` from `@modern-js/plugin-bff/effect-edge` for
148
+ edge-compatible proof tests; if you manually compose a web handler, provide
149
+ `HttpServer.layerServices` beside your API group layer before calling
150
+ `HttpRouter.toWebHandler`.
151
+
152
+ For dynamic origin CORS predicates inside strict Effect HTTP APIs, prefer
153
+ `HttpRouter.middleware(Effect.succeed(HttpMiddleware.cors(...)))`.
154
+ `HttpRouter.middleware(...)` returns a `Layer` directly in the pinned Effect
155
+ cohort; do not read a `.layer` property from it.
156
+
125
157
  Use one package-source strategy per repo. The published BleedingDev create
126
158
  package defaults to `--ultramodern-package-source=install` and records the
127
159
  exact cohort in `.modernjs/ultramodern.json`. Keep `--workspace` only for local
@@ -148,6 +180,13 @@ manual edits. Fix topology, ownership, package-source, local overlay, generated
148
180
  contract, Tailwind prefix, or Module Federation conflicts in the owning files
149
181
  instead of patching generated output by hand.
150
182
 
183
+ Cloudflare D1 bindings are first-class on `deploy.worker.d1Databases`; use that
184
+ config instead of app-local postprocessing when a generated app owns D1
185
+ migrations. Cloudflare public output excludes server-only `api` and `shared`
186
+ directories by default, and generated Modern/Rspack caches are isolated per app
187
+ and build target so local `build` and `cloudflare:build` runs do not share the
188
+ same Rspack cache directory.
189
+
151
190
  ## Human Workflow
152
191
 
153
192
  The public BleedingDev create package has one supported generated product. The
@@ -45,6 +45,12 @@ import EnableBFFCaution from "@site-docs/components/enable-bff-caution";
45
45
 
46
46
  仅当 `bff.runtimeFramework` 设置为 `'effect'` 时,`bff.effect` 才会生效。
47
47
 
48
+ 生成的 UltraModern workspace 只把这个运行时作为生成 HTTP API 路径。API 契约固定在
49
+ `shared/api.ts`,服务端运行时固定在 `api/index.ts`,客户端固定在
50
+ `src/api/*-client.ts`。生成检查会拒绝 `api/effect`、`api/lambda`、
51
+ `shared/effect`、`src/effect`、Hono server import、原始 request handler、手写
52
+ request parsing,以及 API 模块里的手写 `Response`。
53
+
48
54
  对于 Effect v4,TypeScript 需要使用支持 `exports` 映射的模块解析方式。
49
55
 
50
56
  ```json title="tsconfig.json"
@@ -185,3 +191,92 @@ export default defineConfig({
185
191
  `batch.flushIntervalMs` 用于控制生成的 Effect 客户端微批处理窗口;`maxConcurrency` 与 `requestTimeoutMs` 由服务端批处理网关用于内部请求分发。
186
192
 
187
193
  生成的 `api.client.*` API 只存在于 loader 物化后的 `@api/index` 导入中。直接导入服务端入口(`api/index`)时拿到的是 Effect BFF 定义;其中的 `client` 属性只是占位,并会在访问具体操作时报错。
194
+
195
+ ## Effect 版本组
196
+
197
+ UltraModern 生成的 workspace 会通过 `pnpm-workspace.yaml` overrides 锁定与框架兼容的
198
+ Effect 版本组。`3.5.0-ultramodern.2` 使用:
199
+
200
+ ```yaml
201
+ overrides:
202
+ '@effect/vitest': 4.0.0-beta.91
203
+ effect: 4.0.0-beta.91
204
+ ```
205
+
206
+ 不要在应用包里添加不同版本的直接 `effect` 依赖。Effect beta 不一致时,Layer 或 HTTP
207
+ middleware 构建可能因为运行时 service 来自不同包实例而失败。
208
+
209
+ ## 契约测试
210
+
211
+ 严格 Effect API 测试应当测试声明的 `HttpApi` 契约,而不是原始 request handler。
212
+ Edge 兼容测试可以使用框架 helper:
213
+
214
+ ```ts
215
+ import { createEffectBffTestHandler } from '@modern-js/plugin-bff/effect-edge';
216
+ import apiModule from '../api/index';
217
+
218
+ const testApi = await createEffectBffTestHandler({
219
+ module: apiModule,
220
+ prefix: '/api',
221
+ });
222
+
223
+ const response = await testApi.handler(new Request('http://localhost/api/ping'));
224
+ ```
225
+
226
+ 如果低层 proof 必须手动组合 web handler,需要在调用 `HttpRouter.toWebHandler` 前提供
227
+ `HttpServer.layerServices`:
228
+
229
+ ```ts
230
+ import {
231
+ HttpApiBuilder,
232
+ HttpRouter,
233
+ HttpServer,
234
+ Layer,
235
+ } from '@modern-js/plugin-bff/effect-server';
236
+
237
+ const handler = HttpRouter.toWebHandler(
238
+ HttpApiBuilder.layer(api).pipe(
239
+ Layer.provide(apiGroupLayer),
240
+ Layer.provide(HttpServer.layerServices),
241
+ ),
242
+ ).handler;
243
+ ```
244
+
245
+ 优先使用生成 helper,除非测试必须检查底层 Effect router 组合。
246
+
247
+ ## 动态 CORS
248
+
249
+ 需要动态 origin predicate 时,使用 Effect HTTP middleware 作为 Layer。在当前锁定的
250
+ Effect v4 beta 版本组中,`HttpRouter.middleware(...)` 直接返回 `Layer`:
251
+
252
+ ```ts
253
+ import {
254
+ Effect,
255
+ HttpApiBuilder,
256
+ HttpMiddleware,
257
+ HttpRouter,
258
+ Layer,
259
+ } from '@modern-js/plugin-bff/effect-server';
260
+
261
+ const corsLayer = HttpRouter.middleware(
262
+ Effect.succeed(
263
+ HttpMiddleware.cors({
264
+ allowedOrigins: origin =>
265
+ origin.endsWith('.example.com') ? origin : undefined,
266
+ }),
267
+ ),
268
+ );
269
+
270
+ const layer = HttpApiBuilder.layer(api).pipe(
271
+ Layer.provide(apiGroupLayer),
272
+ Layer.provide(corsLayer),
273
+ );
274
+ ```
275
+
276
+ 不要从 `HttpRouter.middleware(...)` 读取 `.layer` 属性。
277
+
278
+ ## 其他传输
279
+
280
+ `strictEffectApproach` 约束生成的 HTTP API 模块。Effect RPC、WebSocket 和其他传输在
281
+ 建模为显式传输 surface、并使用类型化 Effect 程序时仍然有效。它们不让生成的
282
+ UltraModern API 模块中的原始 HTTP request handler 变得有效。
@@ -71,11 +71,30 @@ mise exec -- pnpm check
71
71
  mise exec -- pnpm build
72
72
  ```
73
73
 
74
- 严格生成 API 迁移要求 `3.5.0-ultramodern.0` 或更新版本。
74
+ 严格生成 API 迁移要求 `3.5.0-ultramodern.2` 或更新版本。
75
75
  `3.4.0-ultramodern.20` 及更早 cohort 还没有这套完整的直接
76
- `api/index.ts` 生成器、生成的 `.mts` 检查和严格 Oxlint 边界规则。还不能安装该
77
- BleedingDev cohort 的 agent 应使用本地 Modern.js workspace 做迁移校验;否则用
78
- `--ultramodern-package-version` 固定 `3.5.0-ultramodern.0` 或更新版本。
76
+ `api/index.ts` 生成器、生成的 `.mts` 检查、严格 Oxlint 边界规则、Effect 版本组
77
+ overrides 和严格 Effect 迁移命令。还不能安装该 BleedingDev cohort 的 agent 应使用本地
78
+ Modern.js workspace 做迁移校验;否则用 `--ultramodern-package-version` 固定
79
+ `3.5.0-ultramodern.2` 或更新版本。
80
+
81
+ 手写 package alias 或生成 metadata 之前,先在目标 workspace 运行框架迁移命令:
82
+
83
+ ```bash
84
+ pnpm dlx @bleedingdev/modern-js-create@3.5.0-ultramodern.2 ultramodern \
85
+ migrate-strict-effect --version 3.5.0-ultramodern.2
86
+ pnpm api:check
87
+ pnpm contract:check
88
+ ```
89
+
90
+ 该命令会更新 `.modernjs/ultramodern.json`、根 `modernjs.packageSource`、生成的
91
+ Modern package alias、直接 topology API metadata 和 pnpm lockfile。剩余失败就是源码迁移:
92
+ 把代码移到 `shared/api.ts`、`api/index.ts` 和 `src/api/*-client.ts`,再删除
93
+ `api/effect`、`api/lambda`、`shared/effect` 和 `src/effect`。
94
+
95
+ 严格 Effect 生成 workspace 会通过 pnpm overrides 固定兼容版本组:
96
+ `effect@4.0.0-beta.91` 和 `@effect/vitest@4.0.0-beta.91`。不要添加与这些
97
+ overrides 冲突的 app 本地直接 Effect 版本。
79
98
 
80
99
  渐进迁移意味着尚未迁移的旧 Modern.js app 可以保留现有 runtime,直到被转换为
81
100
  UltraModern。一个 surface 一旦生成为或迁移为 UltraModern HTTP API,就只能使用
@@ -110,6 +129,15 @@ shim 来让旧布局通过。
110
129
  Effect RPC、WebSockets 和其他传输方式应在需要时作为显式 transport surface 增加。
111
130
  它们不能作为在生成 HTTP API 模块中重新引入原始 request handler 的理由。
112
131
 
132
+ 严格 API 测试应执行 `HttpApi` 契约。Edge 兼容 proof 测试使用
133
+ `@modern-js/plugin-bff/effect-edge` 的 `createEffectBffTestHandler`;如果必须手动组合
134
+ web handler,在调用 `HttpRouter.toWebHandler` 前要把 `HttpServer.layerServices` 和
135
+ API group layer 一起提供。
136
+
137
+ 动态 origin CORS predicate 使用
138
+ `HttpRouter.middleware(Effect.succeed(HttpMiddleware.cors(...)))`。在固定的 Effect
139
+ 版本组中,`HttpRouter.middleware(...)` 直接返回 `Layer`;不要读取 `.layer` 属性。
140
+
113
141
  每个仓库只使用一种 package-source 策略。已发布的 BleedingDev create 包默认使用
114
142
  `--ultramodern-package-source=install`,并把精确 cohort 记录到
115
143
  `.modernjs/ultramodern.json`。`--workspace` 只用于本地 monorepo
@@ -127,6 +155,11 @@ CommonJS,同时生成的 app package 继续为 Module Federation DTS 保留稳
127
155
  ownership、package-source、本地 overlay、生成契约、Tailwind prefix 或 Module
128
156
  Federation 冲突,都应在对应归属文件中修复,不要手写补丁覆盖生成结果。
129
157
 
158
+ Cloudflare D1 绑定使用一等配置 `deploy.worker.d1Databases`;生成 app 拥有 D1
159
+ migration 时不要再用 app 本地 postprocess。Cloudflare public output 默认排除服务端
160
+ `api` 和 `shared` 目录,并且生成的 Modern/Rspack cache 会按 app 与构建目标隔离,避免
161
+ 本地 `build` 与 `cloudflare:build` 共享同一个 Rspack cache 目录。
162
+
130
163
  ## 人类工作流
131
164
 
132
165
  公开的 BleedingDev create 包只有一个受支持的生成产品。默认命令会创建一个
package/package.json CHANGED
@@ -19,24 +19,24 @@
19
19
  "modern.js",
20
20
  "ultramodern.js"
21
21
  ],
22
- "version": "3.5.0-ultramodern.0",
22
+ "version": "3.5.0-ultramodern.2",
23
23
  "publishConfig": {
24
24
  "registry": "https://registry.npmjs.org/",
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "mermaid": "^11.15.0",
29
- "@modern-js/sandpack-react": "npm:@bleedingdev/modern-js-sandpack-react@3.5.0-ultramodern.0"
28
+ "mermaid": "^11.16.0",
29
+ "@modern-js/sandpack-react": "npm:@bleedingdev/modern-js-sandpack-react@3.5.0-ultramodern.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@rsbuild/plugin-sass": "2.0.0",
33
- "@rspress/core": "2.0.14",
34
- "@rspress/plugin-llms": "2.0.14",
35
- "@rspress/shared": "2.0.14",
33
+ "@rspress/core": "2.0.15",
34
+ "@rspress/plugin-llms": "2.0.15",
35
+ "@rspress/shared": "2.0.15",
36
36
  "@shikijs/transformers": "^4.3.0",
37
37
  "@types/fs-extra": "11.0.4",
38
38
  "@types/node": "^26.0.1",
39
- "@typescript/native-preview": "7.0.0-dev.20260624.1",
39
+ "@typescript/native-preview": "7.0.0-dev.20260628.1",
40
40
  "classnames": "^2.5.1",
41
41
  "clsx": "^2.1.1",
42
42
  "fs-extra": "^11.3.5",