@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 3.9.3-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.
Files changed (275) hide show
  1. package/README.md +23 -0
  2. package/dist/cjs/backend-federation-build/codegen.js +29 -18
  3. package/dist/cjs/backend-federation-build/config.js +84 -45
  4. package/dist/cjs/backend-federation-build/emit.js +63 -22
  5. package/dist/cjs/build-config/build-environment.js +25 -162
  6. package/dist/cjs/build-config/internal-effect-discovery.js +143 -0
  7. package/dist/cjs/build-config/public.js +2 -5
  8. package/dist/cjs/build-config/remote-address.js +58 -0
  9. package/dist/cjs/cloudflare/artifacts.js +8 -19
  10. package/dist/cjs/cloudflare/delivery-unit.js +125 -43
  11. package/dist/cjs/cloudflare/index.js +14 -4
  12. package/dist/cjs/cloudflare/native-renderer.js +100 -0
  13. package/dist/cjs/cloudflare/renderer-identity.js +106 -0
  14. package/dist/cjs/cloudflare/worker-manifest.js +54 -13
  15. package/dist/cjs/cloudflare/worker-options.js +48 -0
  16. package/dist/cjs/cloudflare/wrangler-config.js +210 -13
  17. package/dist/cjs/cloudflare-builder.js +307 -89
  18. package/dist/cjs/cloudflare-output-contract.js +36 -4
  19. package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
  20. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  21. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  22. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  23. package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
  24. package/dist/cjs/config.js +10 -5
  25. package/dist/cjs/deploy-output/npmAliases.js +92 -6
  26. package/dist/cjs/deploy-output/plugin.js +27 -6
  27. package/dist/cjs/deploy-output/public-assets.js +146 -0
  28. package/dist/cjs/deploy-output/target.js +57 -1
  29. package/dist/cjs/deploy-output/trace-files.js +99 -0
  30. package/dist/cjs/module-federation-config/exposes.js +70 -0
  31. package/dist/cjs/module-federation-config/index.js +73 -0
  32. package/dist/cjs/module-federation-config/inspect.js +103 -0
  33. package/dist/cjs/module-federation-config/object-literal.js +87 -0
  34. package/dist/cjs/module-federation-config/syntax.js +151 -0
  35. package/dist/cjs/module-federation-config/types.js +18 -0
  36. package/dist/cjs/native-type-checker.js +130 -13
  37. package/dist/cjs/policy-defaults.js +38 -29
  38. package/dist/cjs/release-envelope/canonical.js +2 -9
  39. package/dist/cjs/release-envelope/contract.js +68 -0
  40. package/dist/cjs/release-envelope/framework-output.js +296 -105
  41. package/dist/cjs/release-envelope/index.js +150 -36
  42. package/dist/cjs/release-envelope/plugin.js +24 -15
  43. package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
  44. package/dist/cjs/release-envelope/types.js +10 -8
  45. package/dist/cjs/release-identity.js +8 -5
  46. package/dist/cjs/renderer-build-identity.js +134 -0
  47. package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  48. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  49. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  50. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  51. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  52. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  53. package/dist/cjs/workspace-topology.js +84 -0
  54. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  55. package/dist/esm/backend-federation-build/config.mjs +74 -38
  56. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  57. package/dist/esm/build-config/build-environment.mjs +26 -160
  58. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  59. package/dist/esm/build-config/public.mjs +1 -1
  60. package/dist/esm/build-config/remote-address.mjs +20 -0
  61. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  62. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  63. package/dist/esm/cloudflare/index.mjs +15 -5
  64. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  65. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  66. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  67. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  68. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  69. package/dist/esm/cloudflare-builder.mjs +301 -89
  70. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  71. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  72. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  73. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  74. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  75. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  76. package/dist/esm/config.mjs +3 -1
  77. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  78. package/dist/esm/deploy-output/plugin.mjs +24 -6
  79. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  80. package/dist/esm/deploy-output/target.mjs +50 -3
  81. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  82. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  83. package/dist/esm/module-federation-config/index.mjs +4 -0
  84. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  85. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  86. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  87. package/dist/esm/module-federation-config/types.mjs +0 -0
  88. package/dist/esm/native-type-checker.mjs +124 -13
  89. package/dist/esm/policy-defaults.mjs +36 -30
  90. package/dist/esm/release-envelope/canonical.mjs +2 -9
  91. package/dist/esm/release-envelope/contract.mjs +21 -0
  92. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  93. package/dist/esm/release-envelope/index.mjs +149 -38
  94. package/dist/esm/release-envelope/plugin.mjs +24 -15
  95. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  96. package/dist/esm/release-envelope/types.mjs +2 -3
  97. package/dist/esm/release-identity.mjs +8 -5
  98. package/dist/esm/renderer-build-identity.mjs +82 -0
  99. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  100. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  101. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  102. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  103. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  104. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  105. package/dist/esm/workspace-topology.mjs +21 -0
  106. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  107. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  108. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  109. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  110. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  111. package/dist/esm-node/build-config/public.mjs +1 -1
  112. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  113. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  114. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  115. package/dist/esm-node/cloudflare/index.mjs +15 -5
  116. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  117. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  118. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  119. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  120. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  121. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  122. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  123. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  124. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  125. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  126. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  127. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  128. package/dist/esm-node/config.mjs +3 -1
  129. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  130. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  131. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  132. package/dist/esm-node/deploy-output/target.mjs +50 -3
  133. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  134. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  135. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  136. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  137. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  138. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  139. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  140. package/dist/esm-node/native-type-checker.mjs +124 -13
  141. package/dist/esm-node/policy-defaults.mjs +36 -30
  142. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  143. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  144. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  145. package/dist/esm-node/release-envelope/index.mjs +149 -38
  146. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  147. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  148. package/dist/esm-node/release-envelope/types.mjs +2 -3
  149. package/dist/esm-node/release-identity.mjs +8 -5
  150. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  151. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  152. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  153. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  154. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  155. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  156. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  157. package/dist/esm-node/workspace-topology.mjs +22 -0
  158. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  159. package/dist/types/backend-federation-build/config.d.ts +10 -57
  160. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  161. package/dist/types/backend-federation-build/index.d.ts +1 -1
  162. package/dist/types/build-config/build-environment.d.ts +1 -9
  163. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  164. package/dist/types/build-config/public.d.ts +1 -1
  165. package/dist/types/build-config/remote-address.d.ts +3 -0
  166. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  167. package/dist/types/cloudflare/constants.d.ts +1 -1
  168. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  169. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  170. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  171. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  172. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  173. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  174. package/dist/types/cloudflare-builder.d.ts +51 -8
  175. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  176. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  177. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  178. package/dist/types/config.d.ts +98 -13
  179. package/dist/types/deploy-output/plugin.d.ts +29 -9
  180. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  181. package/dist/types/deploy-output/target.d.ts +28 -5
  182. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  183. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  184. package/dist/types/module-federation-config/index.d.ts +5 -0
  185. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  186. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  187. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  188. package/dist/types/module-federation-config/types.d.ts +20 -0
  189. package/dist/types/native-type-checker.d.ts +26 -3
  190. package/dist/types/policy-defaults.d.ts +12 -2
  191. package/dist/types/release-envelope/canonical.d.ts +5 -5
  192. package/dist/types/release-envelope/contract.d.mts +8 -0
  193. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  194. package/dist/types/release-envelope/index.d.ts +17 -5
  195. package/dist/types/release-envelope/plugin.d.ts +10 -6
  196. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  197. package/dist/types/release-envelope/types.d.ts +42 -4
  198. package/dist/types/release-identity.d.ts +3 -2
  199. package/dist/types/renderer-build-identity.d.ts +30 -0
  200. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  201. package/dist/types/workspace-topology.d.ts +91 -0
  202. package/package.json +87 -27
  203. package/src/backend-federation-build/codegen.ts +28 -19
  204. package/src/backend-federation-build/config.ts +189 -123
  205. package/src/backend-federation-build/emit.ts +119 -38
  206. package/src/backend-federation-build/index.ts +4 -1
  207. package/src/build-config/build-environment.ts +59 -285
  208. package/src/build-config/internal-effect-discovery.ts +239 -0
  209. package/src/build-config/public.ts +0 -1
  210. package/src/build-config/remote-address.ts +33 -0
  211. package/src/cloudflare/artifacts.ts +22 -43
  212. package/src/cloudflare/delivery-unit.ts +283 -60
  213. package/src/cloudflare/index.ts +35 -19
  214. package/src/cloudflare/native-renderer.ts +117 -0
  215. package/src/cloudflare/renderer-identity.ts +171 -0
  216. package/src/cloudflare/worker-manifest.ts +108 -10
  217. package/src/cloudflare/worker-options.ts +60 -0
  218. package/src/cloudflare/wrangler-config.ts +390 -14
  219. package/src/cloudflare-builder.ts +543 -170
  220. package/src/cloudflare-output-contract.ts +45 -2
  221. package/src/cloudflare-output-verifier/identity.ts +138 -7
  222. package/src/cloudflare-output-verifier/index.ts +20 -0
  223. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  224. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  225. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  226. package/src/config.ts +101 -19
  227. package/src/deploy-output/npmAliases.ts +141 -4
  228. package/src/deploy-output/plugin.ts +55 -18
  229. package/src/deploy-output/public-assets.ts +224 -0
  230. package/src/deploy-output/target.ts +97 -14
  231. package/src/deploy-output/trace-files.ts +80 -0
  232. package/src/module-federation-config/exposes.ts +46 -0
  233. package/src/module-federation-config/index.ts +22 -0
  234. package/src/module-federation-config/inspect.ts +157 -0
  235. package/src/module-federation-config/object-literal.ts +76 -0
  236. package/src/module-federation-config/syntax.ts +212 -0
  237. package/src/module-federation-config/types.ts +22 -0
  238. package/src/native-type-checker.ts +233 -20
  239. package/src/policy-defaults.ts +47 -17
  240. package/src/release-envelope/canonical.ts +6 -18
  241. package/src/release-envelope/contract.mjs +28 -0
  242. package/src/release-envelope/framework-output.ts +681 -156
  243. package/src/release-envelope/index.ts +315 -60
  244. package/src/release-envelope/plugin.ts +54 -38
  245. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  246. package/src/release-envelope/types.ts +75 -6
  247. package/src/release-identity.ts +19 -2
  248. package/src/renderer-build-identity.ts +168 -0
  249. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  250. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  251. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  252. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  253. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  254. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  255. package/src/workspace-topology.ts +125 -0
  256. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  257. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  258. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  259. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  260. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  261. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  262. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  263. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  264. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  265. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  266. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  267. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  268. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  269. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  270. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  271. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  272. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  273. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  274. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  275. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -10,29 +10,60 @@ export const CLOUDFLARE_RUNTIME_TYPE = 'cloudflare-module-worker';
10
10
  export const CLOUDFLARE_OUTPUT_PACKAGE_TYPE = 'module';
11
11
  export const CLOUDFLARE_WORKER_PACKAGE_TYPE = 'commonjs';
12
12
  export const CLOUDFLARE_WORKER_BUNDLE_FORMAT = 'commonjs';
13
- // Import availability for the default 2026-06-02 nodejs_compat target.
14
- // Some modules are runtime-provided stubs whose unsupported operations throw.
13
+ // Each SSR entry with route data loaders gets a worker bundle that answers
14
+ // `?__loader=` route data requests through this export.
15
+ export const CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT = 'handleRouteDataRequest';
16
+ export const getCloudflareWorkerRouteDataEntryName = (entryName: string) =>
17
+ `${entryName}-server-loaders`;
18
+ // Every Node.js built-in that workerd resolves for the default 2026-06-02
19
+ // nodejs_compat target (tests/cloudflare-worker-node-builtins.test.ts probes
20
+ // workerd against Node's own `builtinModules`). Names are listed without the
21
+ // `node:` scheme; bare-name imports are only valid where Node itself accepts
22
+ // them (`isBuiltin`), so prefix-only modules such as `sqlite` and `test` are
23
+ // externalized solely as `node:sqlite` and `node:test`. Some modules are
24
+ // runtime-provided stubs whose unsupported operations throw. Earlier
25
+ // compatibility dates are rejected when the Wrangler config is generated.
15
26
  export const CLOUDFLARE_WORKER_NODE_BUILTINS = [
27
+ '_http_agent',
28
+ '_http_client',
29
+ '_http_common',
30
+ '_http_incoming',
31
+ '_http_outgoing',
32
+ '_http_server',
33
+ '_tls_common',
34
+ '_tls_wrap',
16
35
  'assert',
17
36
  'assert/strict',
18
37
  'async_hooks',
19
38
  'buffer',
20
39
  'child_process',
40
+ 'cluster',
41
+ 'console',
42
+ 'constants',
21
43
  'crypto',
22
44
  'dgram',
45
+ 'diagnostics_channel',
23
46
  'dns',
47
+ 'dns/promises',
24
48
  'domain',
25
49
  'events',
26
50
  'fs',
27
51
  'fs/promises',
28
52
  'http',
29
53
  'http2',
54
+ 'https',
30
55
  'inspector',
56
+ 'inspector/promises',
31
57
  'module',
32
58
  'net',
33
59
  'os',
34
60
  'path',
61
+ 'path/posix',
62
+ 'path/win32',
63
+ 'perf_hooks',
35
64
  'process',
65
+ 'punycode',
66
+ 'querystring',
36
67
  'readline',
37
68
  'readline/promises',
38
69
  'repl',
@@ -42,6 +73,8 @@ export const CLOUDFLARE_WORKER_NODE_BUILTINS = [
42
73
  'stream/promises',
43
74
  'stream/web',
44
75
  'string_decoder',
76
+ 'sys',
77
+ 'test',
45
78
  'timers',
46
79
  'timers/promises',
47
80
  'tls',
@@ -58,10 +91,20 @@ export const CLOUDFLARE_WORKER_NODE_BUILTINS = [
58
91
  ] as const;
59
92
  // Platform imports available with the default 2026-06-02 nodejs_compat target.
60
93
  // Import availability does not imply support for every Node API operation.
94
+ // `cloudflare:workers` exposes the Worker `env` at module scope, which is how
95
+ // clients built once per isolate reach Hyperdrive, VPC and service bindings.
61
96
  export const CLOUDFLARE_WORKER_PLATFORM_MODULES = [
62
97
  'cloudflare:sockets',
98
+ 'cloudflare:workers',
63
99
  ] as const;
64
100
  export const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
65
101
  'nodejs_compat',
66
102
  'global_fetch_strictly_public',
103
+ // Without this flag, a deployed Worker's `request.signal` never fires on
104
+ // client disconnect, so SSR keeps rendering and streaming after the client
105
+ // has gone away. The native request/session layer (see
106
+ // renderer-core/src/session/request.ts) already forwards `request.signal`
107
+ // into its render-abort `AbortController`; this flag is what makes the
108
+ // platform actually raise that signal in production.
109
+ 'enable_request_signal',
67
110
  ] as const;
@@ -2,14 +2,20 @@ import {
2
2
  DELIVERY_UNIT_IDENTITY_FIELDS,
3
3
  type DeliveryUnitIdentity,
4
4
  deliveryUnitIdentityFieldValue,
5
+ validateRendererIdentity,
6
+ validateRendererProfile,
7
+ validateRendererProfileCompatibility,
5
8
  } from '@modern-js/backend-federation-contracts';
9
+ import type { TopologyUiSurface } from '../cloudflare/delivery-unit';
10
+ import { isRecord } from '../cloudflare/utils';
6
11
  import type { CloudflareOutputVerifierIssue, JsonObject } from './issues';
7
12
  import { addIssue, assertEqual } from './issues';
8
13
 
9
14
  export type CloudflareDeliveryUnitIdentity = DeliveryUnitIdentity & {
10
- surfaces?: Partial<
11
- Record<'api' | 'ui', DeliveryUnitIdentity & { surface: 'api' | 'ui' }>
12
- >;
15
+ surfaces?: {
16
+ ui?: TopologyUiSurface;
17
+ api?: DeliveryUnitIdentity & { surface: 'api' };
18
+ };
13
19
  };
14
20
 
15
21
  export const verifyDeliveryUnitIdentity = (
@@ -19,9 +25,8 @@ export const verifyDeliveryUnitIdentity = (
19
25
  declared: CloudflareDeliveryUnitIdentity | undefined,
20
26
  ) => {
21
27
  const stamped = manifest?.deliveryUnit;
22
- const hasStamp = Boolean(stamped) && typeof stamped === 'object';
28
+ const hasStamp = isRecord(stamped);
23
29
 
24
- // Legacy outputs (no topology declaration and no stamp) are unchanged.
25
30
  if (declared) {
26
31
  if (!hasStamp) {
27
32
  addIssue(issues, {
@@ -46,11 +51,46 @@ export const verifyDeliveryUnitIdentity = (
46
51
 
47
52
  // Every profile-declared surface must derive from the one stamped record;
48
53
  // profiles must not claim a UI/API surface they do not emit.
49
- if (hasStamp && stamped.surfaces && typeof stamped.surfaces === 'object') {
54
+ if (hasStamp) {
55
+ if (!isRecord(stamped.surfaces)) {
56
+ addIssue(issues, {
57
+ code: 'missing-delivery-unit',
58
+ message:
59
+ 'Cloudflare worker manifest is missing delivery-unit surface markers.',
60
+ path: manifestPath,
61
+ });
62
+ return;
63
+ }
64
+ for (const surface of Object.keys(stamped.surfaces)) {
65
+ if (surface !== 'ui' && surface !== 'api') {
66
+ addIssue(issues, {
67
+ code: 'delivery-unit-drift',
68
+ message: `Cloudflare worker manifest declares an unsupported ${surface} delivery-unit surface.`,
69
+ path: manifestPath,
70
+ });
71
+ }
72
+ }
73
+ if (
74
+ !declared?.surfaces &&
75
+ !Object.hasOwn(stamped.surfaces, 'ui') &&
76
+ !Object.hasOwn(stamped.surfaces, 'api')
77
+ ) {
78
+ addIssue(issues, {
79
+ code: 'missing-delivery-unit',
80
+ message:
81
+ 'Cloudflare worker manifest is missing delivery-unit surface markers.',
82
+ path: manifestPath,
83
+ });
84
+ return;
85
+ }
50
86
  for (const surface of ['ui', 'api'] as const) {
51
87
  const marker = stamped.surfaces[surface];
52
88
  const expected = declared?.surfaces?.[surface];
53
89
 
90
+ if (!expected && !marker) {
91
+ continue;
92
+ }
93
+
54
94
  if (declared?.surfaces && !expected) {
55
95
  if (marker) {
56
96
  addIssue(issues, {
@@ -62,7 +102,7 @@ export const verifyDeliveryUnitIdentity = (
62
102
  continue;
63
103
  }
64
104
 
65
- if (!marker || typeof marker !== 'object') {
105
+ if (!isRecord(marker)) {
66
106
  addIssue(issues, {
67
107
  code: 'missing-delivery-unit',
68
108
  message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
@@ -71,6 +111,11 @@ export const verifyDeliveryUnitIdentity = (
71
111
  continue;
72
112
  }
73
113
 
114
+ assertEqual(issues, marker.surface, surface, {
115
+ code: 'delivery-unit-drift',
116
+ message: `Cloudflare worker manifest ${surface} surface must declare surface "${surface}".`,
117
+ path: manifestPath,
118
+ });
74
119
  for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
75
120
  const markerValue = deliveryUnitIdentityFieldValue(marker, field);
76
121
  const stampedValue = deliveryUnitIdentityFieldValue(stamped, field);
@@ -82,6 +127,92 @@ export const verifyDeliveryUnitIdentity = (
82
127
  path: manifestPath,
83
128
  });
84
129
  }
130
+
131
+ if (surface === 'api') {
132
+ for (const field of ['rendererIdentity', 'rendererProfile']) {
133
+ if (Object.hasOwn(marker, field)) {
134
+ addIssue(issues, {
135
+ code: 'delivery-unit-drift',
136
+ message: `Cloudflare worker manifest API surface must not declare ${field}.`,
137
+ path: manifestPath,
138
+ });
139
+ }
140
+ }
141
+ continue;
142
+ }
143
+ const identity = marker.rendererIdentity;
144
+ const profile = marker.rendererProfile;
145
+ const errors = [
146
+ ...validateRendererIdentity(identity).errors,
147
+ ...validateRendererProfile(profile).errors,
148
+ ];
149
+ for (const error of errors) {
150
+ addIssue(issues, {
151
+ code: 'delivery-unit-drift',
152
+ message: `Cloudflare worker manifest UI ${error.path} ${error.message}`,
153
+ path: manifestPath,
154
+ });
155
+ }
156
+ if (errors.length || !isRecord(identity) || !isRecord(profile)) {
157
+ continue;
158
+ }
159
+ assertEqual(issues, identity.buildId, marker.buildMarker, {
160
+ code: 'delivery-unit-drift',
161
+ message:
162
+ 'Cloudflare worker manifest UI rendererIdentity.buildId must match its surface buildMarker.',
163
+ path: manifestPath,
164
+ });
165
+ assertEqual(issues, identity.appId, marker.appId, {
166
+ code: 'delivery-unit-drift',
167
+ message:
168
+ 'Cloudflare worker manifest UI rendererIdentity.appId must match its surface appId.',
169
+ path: manifestPath,
170
+ });
171
+ assertEqual(issues, marker.appId, stamped.appId, {
172
+ code: 'delivery-unit-drift',
173
+ message:
174
+ 'Cloudflare worker manifest UI surface appId must match its delivery-unit appId.',
175
+ path: manifestPath,
176
+ });
177
+ for (const field of ['renderer', 'protocolVersion']) {
178
+ assertEqual(issues, identity[field], profile[field], {
179
+ code: 'delivery-unit-drift',
180
+ message: `Cloudflare worker manifest UI rendererProfile.${field} must match its renderer identity.`,
181
+ path: manifestPath,
182
+ });
183
+ }
184
+ const expectedUi = declared?.surfaces?.ui;
185
+ // A topology without a renderer projection declares no identity to match.
186
+ if (expectedUi?.rendererIdentity) {
187
+ for (const field of [
188
+ 'renderer',
189
+ 'appId',
190
+ 'entryName',
191
+ 'protocolVersion',
192
+ 'buildId',
193
+ ] as const) {
194
+ assertEqual(
195
+ issues,
196
+ identity[field],
197
+ expectedUi.rendererIdentity[field],
198
+ {
199
+ code: 'delivery-unit-drift',
200
+ message: `Cloudflare worker manifest UI rendererIdentity.${field} must match the topology renderer identity.`,
201
+ path: manifestPath,
202
+ },
203
+ );
204
+ }
205
+ for (const error of validateRendererProfileCompatibility(
206
+ expectedUi.rendererProfile,
207
+ profile,
208
+ ).errors) {
209
+ addIssue(issues, {
210
+ code: 'delivery-unit-drift',
211
+ message: `Cloudflare worker manifest UI ${error.path} ${error.message}`,
212
+ path: manifestPath,
213
+ });
214
+ }
215
+ }
85
216
  }
86
217
  }
87
218
  };
@@ -174,6 +174,26 @@ export const verifyCloudflareOutput = async (
174
174
  }
175
175
 
176
176
  if (wrangler) {
177
+ if (
178
+ typeof wrangler.name !== 'string' ||
179
+ wrangler.name.trim().length === 0
180
+ ) {
181
+ addIssue(issues, {
182
+ code: 'invalid-wrangler',
183
+ message: 'wrangler.json name must be a non-empty worker name.',
184
+ path: wranglerPath,
185
+ });
186
+ }
187
+ if (
188
+ typeof wrangler.compatibility_date !== 'string' ||
189
+ !/^\d{4}-\d{2}-\d{2}$/u.test(wrangler.compatibility_date)
190
+ ) {
191
+ addIssue(issues, {
192
+ code: 'invalid-wrangler',
193
+ message: 'wrangler.json compatibility_date must use YYYY-MM-DD.',
194
+ path: wranglerPath,
195
+ });
196
+ }
177
197
  assertEqual(issues, wrangler.main, outputPlan.wrangler.main, {
178
198
  code: 'invalid-wrangler',
179
199
  message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
@@ -61,6 +61,83 @@ export const verifyManifestShape = (
61
61
  path: manifestPath,
62
62
  });
63
63
  }
64
+ const security = manifest.security;
65
+ if (
66
+ !security ||
67
+ typeof security !== 'object' ||
68
+ typeof security.enabled !== 'boolean'
69
+ ) {
70
+ addIssue(issues, {
71
+ code: 'invalid-manifest',
72
+ message: 'Cloudflare output manifest security.enabled must be a boolean.',
73
+ path: manifestPath,
74
+ });
75
+ } else {
76
+ const cors = security.cors;
77
+ if (
78
+ !cors ||
79
+ typeof cors !== 'object' ||
80
+ typeof cors.assets !== 'boolean' ||
81
+ !Array.isArray(cors.allowedOrigins) ||
82
+ !Array.isArray(cors.allowedMethods) ||
83
+ !Array.isArray(cors.allowedHeaders)
84
+ ) {
85
+ addIssue(issues, {
86
+ code: 'invalid-manifest',
87
+ message: 'Cloudflare output manifest security.cors is incomplete.',
88
+ path: manifestPath,
89
+ });
90
+ }
91
+ if (security.enabled) {
92
+ const headers = security.headers;
93
+ const csp = security.contentSecurityPolicy;
94
+ const noindex = security.noindex;
95
+ if (
96
+ !headers ||
97
+ typeof headers !== 'object' ||
98
+ !['string', 'boolean'].includes(typeof headers.referrerPolicy) ||
99
+ !['string', 'boolean'].includes(typeof headers.contentTypeOptions) ||
100
+ !['string', 'boolean'].includes(typeof headers.permissionsPolicy)
101
+ ) {
102
+ addIssue(issues, {
103
+ code: 'invalid-manifest',
104
+ message: 'Cloudflare output manifest security.headers is incomplete.',
105
+ path: manifestPath,
106
+ });
107
+ }
108
+ if (
109
+ !csp ||
110
+ !['off', 'report-only', 'enforce'].includes(csp.mode) ||
111
+ !csp.directives ||
112
+ typeof csp.directives !== 'object' ||
113
+ Array.isArray(csp.directives) ||
114
+ Object.values(csp.directives).some(
115
+ value =>
116
+ !Array.isArray(value) ||
117
+ value.some(entry => typeof entry !== 'string'),
118
+ )
119
+ ) {
120
+ addIssue(issues, {
121
+ code: 'invalid-manifest',
122
+ message:
123
+ 'Cloudflare output manifest security.contentSecurityPolicy is invalid.',
124
+ path: manifestPath,
125
+ });
126
+ }
127
+ if (
128
+ !noindex ||
129
+ typeof noindex.workersDev !== 'boolean' ||
130
+ typeof noindex.localhost !== 'boolean' ||
131
+ !Array.isArray(noindex.previewHostnames)
132
+ ) {
133
+ addIssue(issues, {
134
+ code: 'invalid-manifest',
135
+ message: 'Cloudflare output manifest security.noindex is incomplete.',
136
+ path: manifestPath,
137
+ });
138
+ }
139
+ }
140
+ }
64
141
  if (manifest.bff?.runtimeFramework === 'effect') {
65
142
  assertEqual(
66
143
  issues,
@@ -140,7 +140,17 @@ export const verifyCloudflareOutputMutationPolicy = async (
140
140
  if (excludedFiles.has(file)) {
141
141
  continue;
142
142
  }
143
- const source = await fs.readFile(file, 'utf-8');
143
+ let source: string;
144
+ try {
145
+ source = await fs.readFile(file, 'utf-8');
146
+ } catch (error) {
147
+ // Concurrent sibling builds delete transient files (for example tsgo
148
+ // resolved configs) between the walk and the read.
149
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
150
+ continue;
151
+ }
152
+ throw error;
153
+ }
144
154
  for (const forbidden of FORBIDDEN_MUTATION_PATTERNS) {
145
155
  if (forbidden.pattern.test(source)) {
146
156
  addIssue(issues, {
@@ -5,6 +5,7 @@ import { isDeepStrictEqual } from 'node:util';
5
5
  import { parse } from '@babel/parser';
6
6
  import traverse, { type NodePath } from '@babel/traverse';
7
7
  import {
8
+ CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT,
8
9
  CLOUDFLARE_WORKER_BUNDLE_DIRECTORY,
9
10
  CLOUDFLARE_WORKER_NODE_BUILTINS,
10
11
  CLOUDFLARE_WORKER_PLATFORM_MODULES,
@@ -14,7 +15,7 @@ import { addIssue } from './issues';
14
15
 
15
16
  interface WorkerBundleReference {
16
17
  dispatcherExport?: string;
17
- kind: 'effect-bff' | 'route';
18
+ kind: 'effect-bff' | 'route' | 'route-data';
18
19
  reference: string;
19
20
  }
20
21
 
@@ -22,12 +23,15 @@ interface ResolvedWorkerBundleReference extends WorkerBundleReference {
22
23
  path: string;
23
24
  }
24
25
 
25
- const getReferencedRouteWorkers = (manifest: JsonObject) =>
26
+ const getReferencedRouteWorkers = (
27
+ manifest: JsonObject,
28
+ field: 'routeDataWorker' | 'worker',
29
+ ) =>
26
30
  Array.isArray(manifest?.routeSpec?.routes)
27
31
  ? manifest.routeSpec.routes
28
32
  .map((route: any) =>
29
- typeof route?.worker === 'string' && route.worker.length > 0
30
- ? route.worker
33
+ typeof route?.[field] === 'string' && route[field].length > 0
34
+ ? route[field]
31
35
  : undefined,
32
36
  )
33
37
  .filter(
@@ -58,10 +62,19 @@ export const getWorkerBundleReferences = (
58
62
  },
59
63
  ]
60
64
  : []),
61
- ...getReferencedRouteWorkers(manifest).map((reference: string) => ({
62
- kind: 'route' as const,
63
- reference,
64
- })),
65
+ ...getReferencedRouteWorkers(manifest, 'worker').map(
66
+ (reference: string) => ({
67
+ kind: 'route' as const,
68
+ reference,
69
+ }),
70
+ ),
71
+ ...getReferencedRouteWorkers(manifest, 'routeDataWorker').map(
72
+ (reference: string) => ({
73
+ dispatcherExport: CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT,
74
+ kind: 'route-data' as const,
75
+ reference,
76
+ }),
77
+ ),
65
78
  ];
66
79
  };
67
80
 
@@ -101,7 +114,9 @@ export const resolveWorkerBundleReference = (
101
114
  export const missingWorkerBundleMessage = (reference: WorkerBundleReference) =>
102
115
  reference.kind === 'effect-bff'
103
116
  ? 'Cloudflare Effect BFF manifest points to a missing worker bundle.'
104
- : 'Cloudflare route worker manifest points to a missing worker bundle.';
117
+ : reference.kind === 'route-data'
118
+ ? 'Cloudflare route data worker manifest points to a missing worker bundle.'
119
+ : 'Cloudflare route worker manifest points to a missing worker bundle.';
105
120
 
106
121
  interface AstNode {
107
122
  type: string;
@@ -611,14 +626,15 @@ export const verifyWorkerBundleReferences = async (
611
626
  contract,
612
627
  );
613
628
  if (
614
- worker.kind === 'effect-bff' &&
615
629
  typeof worker.dispatcherExport === 'string' &&
616
630
  !entryAnalysis?.exports.has(worker.dispatcherExport)
617
631
  ) {
618
632
  addIssue(issues, {
619
633
  code: 'invalid-worker-bundle',
620
634
  message:
621
- 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
635
+ worker.kind === 'route-data'
636
+ ? `Cloudflare route data worker bundle must expose ${worker.dispatcherExport}.`
637
+ : 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
622
638
  path: worker.path,
623
639
  });
624
640
  }
package/src/config.ts CHANGED
@@ -2,15 +2,10 @@ export type { ResolveEffectTsgoCompilerOptions } from './build-config/public';
2
2
  export {
3
3
  getBuildConfigEnvironment,
4
4
  resolveEffectTsgoCompiler,
5
- withBuildConfigEnvironment,
6
5
  } from './build-config/public';
7
-
8
- export type DeployTarget =
9
- | 'node'
10
- | 'vercel'
11
- | 'netlify'
12
- | 'ghPages'
13
- | 'cloudflare';
6
+ export { createRemoteManifestUrl } from './build-config/remote-address';
7
+ export type { DeployTarget } from './deploy-output/target';
8
+ export { resolveDeployTarget } from './deploy-output/target';
14
9
 
15
10
  export type CloudflareWorkerSecurityCspMode = 'enforce' | 'report-only' | 'off';
16
11
 
@@ -46,6 +41,18 @@ export interface CloudflareWorkerPublicAssetConfig {
46
41
  to: string;
47
42
  }
48
43
 
44
+ export interface NodePublicAssetConfig {
45
+ /**
46
+ * Source file or directory, relative to the app root, to copy into the
47
+ * Node deploy output's public directory.
48
+ */
49
+ from: string;
50
+ /**
51
+ * Destination path relative to `.output/public`.
52
+ */
53
+ to: string;
54
+ }
55
+
49
56
  export interface CloudflareWorkerD1DatabaseConfig {
50
57
  /**
51
58
  * Worker binding name, for example `DB`.
@@ -92,6 +99,19 @@ export interface CloudflareWorkerServiceBindingConfig {
92
99
  fragments?: CloudflareWorkerServiceBindingFragmentConfig[];
93
100
  }
94
101
 
102
+ export interface CloudflareWorkerVpcServiceConfig {
103
+ /** Worker binding name exposed on the module worker `env` object. */
104
+ binding: string;
105
+ /** Workers VPC service id (`wrangler vpc service create`). */
106
+ serviceId: string;
107
+ /**
108
+ * Optional application path prefix that Modern.js should dispatch to this
109
+ * private origin with `env[binding].fetch(request)`, exactly like a
110
+ * prefixed Worker service binding.
111
+ */
112
+ prefix?: string;
113
+ }
114
+
95
115
  export interface CloudflareWorkerServiceBindingFragmentConfig {
96
116
  /** Stable remote id used by the shell's composition contract. */
97
117
  remote: string;
@@ -187,17 +207,79 @@ export interface CloudflareWorkerSecurityConfig {
187
207
  reason?: string;
188
208
  }
189
209
 
210
+ export interface CloudflareWorkerDeployConfig {
211
+ name?: string;
212
+ /**
213
+ * Cloudflare Workers compatibility date for generated wrangler config.
214
+ * Use YYYY-MM-DD. Defaults to the date validated against the bundled
215
+ * Wrangler version used by UltraModern generated workspaces.
216
+ */
217
+ compatibilityDate?: string;
218
+ ssr?: boolean;
219
+ security?: CloudflareWorkerSecurityConfig;
220
+ /**
221
+ * Raw Wrangler-compatible config merged into `.output/wrangler.json`.
222
+ * Framework-owned worker invariants still win for `main`, the assets
223
+ * binding/directory/run mode, and required compatibility flags.
224
+ */
225
+ wrangler?: Record<string, JsonValue>;
226
+ /**
227
+ * Additional app-root files or directories to stage under `.output`.
228
+ * Use this for provider resources such as migrations or generated config.
229
+ */
230
+ artifacts?: CloudflareWorkerArtifactConfig[];
231
+ /**
232
+ * Additional app-root files or directories to serve as Cloudflare Worker
233
+ * Static Assets under `.output/public`. Use `to: '.'` to copy a
234
+ * source directory's contents into the public asset root.
235
+ */
236
+ publicAssets?: CloudflareWorkerPublicAssetConfig[];
237
+ /**
238
+ * First-class Cloudflare D1 bindings. Modern.js writes these to
239
+ * `wrangler.json` as `d1_databases` and stages configured migration
240
+ * directories into `.output`.
241
+ */
242
+ d1Databases?: CloudflareWorkerD1DatabaseConfig[];
243
+ /**
244
+ * First-class Cloudflare service bindings. Modern.js writes these to
245
+ * `wrangler.json` as `services`; when a binding also has `prefix`,
246
+ * the generated Worker dispatches matching requests through
247
+ * `env[binding].fetch(request)`.
248
+ */
249
+ services?: CloudflareWorkerServiceBindingConfig[];
250
+ /**
251
+ * First-class Workers VPC service bindings to private origins reached
252
+ * through a Cloudflare Tunnel. Modern.js writes these to `wrangler.json`
253
+ * as `vpc_services`; a binding with `prefix` joins the prefix dispatch of
254
+ * `services`.
255
+ */
256
+ vpcServices?: CloudflareWorkerVpcServiceConfig[];
257
+ /**
258
+ * Dist output paths that must not be copied into Cloudflare public assets.
259
+ * Entries are slash-normalized path prefixes relative to the app dist root.
260
+ * Top-level `api` and `shared` dist directories are excluded when matching
261
+ * source directories exist in the app root because they conventionally
262
+ * contain server-only implementation code.
263
+ */
264
+ publicAssetExcludes?: string[];
265
+ }
266
+
190
267
  export interface CloudflareDeployConfig {
191
- worker?: {
192
- name?: string;
193
- compatibilityDate?: string;
194
- ssr?: boolean;
195
- security?: CloudflareWorkerSecurityConfig;
196
- wrangler?: Record<string, JsonValue>;
197
- artifacts?: CloudflareWorkerArtifactConfig[];
198
- publicAssets?: CloudflareWorkerPublicAssetConfig[];
199
- d1Databases?: CloudflareWorkerD1DatabaseConfig[];
200
- services?: CloudflareWorkerServiceBindingConfig[];
201
- publicAssetExcludes?: string[];
268
+ /** Release surface owned by this application, independent of federation production. */
269
+ releaseEnvelopeRole?: 'microvertical' | 'shell';
270
+ worker?: CloudflareWorkerDeployConfig;
271
+ }
272
+
273
+ export interface NodeDeployConfig {
274
+ node?: {
275
+ /**
276
+ * Additional app-root files or directories to stage under
277
+ * `.output/public` when `modern deploy` targets Node, the counterpart of
278
+ * `deploy.worker.publicAssets`. The Node server does not route these
279
+ * files; serve them from the app, for example from an API handler.
280
+ * Release envelopes record them as declared public assets, so API-only
281
+ * units may ship them.
282
+ */
283
+ publicAssets?: NodePublicAssetConfig[];
202
284
  };
203
285
  }