acrylblends 0.6.3 → 0.7.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.
Files changed (285) hide show
  1. package/README.md +4 -3
  2. package/docs/framework/README.md +2 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/guides/jsx.md +51 -1
  5. package/docs/framework/helpers/ssg.md +3 -6
  6. package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
  7. package/docs/framework/upstream.json +2 -2
  8. package/lib/accepts.d.ts +1 -1
  9. package/lib/acryl-base.d.ts +1 -1
  10. package/lib/adapter.d.ts +1 -1
  11. package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
  12. package/lib/{base-ddiVmiE7.js → base-NSN_SNv-.js} +1 -0
  13. package/lib/basic-auth.d.ts +1 -1
  14. package/lib/bearer-auth.d.ts +1 -1
  15. package/lib/body-limit.d.ts +1 -1
  16. package/lib/cache.d.ts +1 -1
  17. package/lib/cache.js +4 -4
  18. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  19. package/lib/client.d.ts +2 -2
  20. package/lib/client.js +2 -1
  21. package/lib/combine.d.ts +1 -1
  22. package/lib/{components-DYZdxJIJ.js → components-JXVm45kK.js} +1 -1
  23. package/lib/{components-Ce5FRyHU.js → components-cEW3ovK0.js} +3 -3
  24. package/lib/compress.d.ts +1 -1
  25. package/lib/conninfo.d.ts +2 -2
  26. package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
  27. package/lib/context-storage.d.ts +1 -1
  28. package/lib/cookie.d.ts +1 -1
  29. package/lib/cors.d.ts +1 -1
  30. package/lib/csrf.d.ts +1 -1
  31. package/lib/dev.d.ts +2 -2
  32. package/lib/etag.d.ts +1 -1
  33. package/lib/factory.d.ts +2 -2
  34. package/lib/{hooks-Bq5q9IMl.js → hooks-w72iLeFR.js} +6 -6
  35. package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
  36. package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
  37. package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
  38. package/lib/index.d.ts +4 -4
  39. package/lib/ip-restriction.d.ts +2 -2
  40. package/lib/jsx/dom/client.js +1 -1
  41. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  42. package/lib/jsx/dom/jsx-runtime.js +1 -1
  43. package/lib/jsx/dom/server.js +2 -2
  44. package/lib/jsx/dom.js +4 -4
  45. package/lib/jsx/jsx-dev-runtime.js +1 -1
  46. package/lib/jsx/jsx-runtime.js +1 -1
  47. package/lib/jsx/streaming.js +1 -1
  48. package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-CWLRA2F3.js} +2 -2
  49. package/lib/jsx-renderer.d.ts +75 -0
  50. package/lib/jsx-renderer.js +99 -0
  51. package/lib/jsx.js +4 -4
  52. package/lib/jwk.d.ts +1 -1
  53. package/lib/jwt.d.ts +1 -1
  54. package/lib/language.d.ts +1 -1
  55. package/lib/logger.d.ts +1 -1
  56. package/lib/loopback-only.d.ts +1 -1
  57. package/lib/method-not-allowed.d.ts +2 -2
  58. package/lib/method-override.d.ts +2 -2
  59. package/lib/mount.d.ts +1 -1
  60. package/lib/node-server/conninfo.d.ts +1 -1
  61. package/lib/node-server/register.d.ts +1 -0
  62. package/lib/node-server/register.js +73 -0
  63. package/lib/node-server/serve-static.d.ts +1 -1
  64. package/lib/node-server/serve-static.js +1 -1
  65. package/lib/node-server.d.ts +1 -1
  66. package/lib/node-server.js +1 -1
  67. package/lib/powered-by.d.ts +1 -1
  68. package/lib/pretty-json.d.ts +1 -1
  69. package/lib/quick.d.ts +1 -1
  70. package/lib/request-id.d.ts +1 -1
  71. package/lib/request.d.ts +1 -1
  72. package/lib/route.d.ts +1 -1
  73. package/lib/schema-validator.d.ts +1 -1
  74. package/lib/secure-headers.d.ts +1 -1
  75. package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
  76. package/lib/serve-static.d.ts +1 -1
  77. package/lib/ssg-BwjcA-Ge.js +389 -0
  78. package/lib/ssg.d.ts +156 -0
  79. package/lib/ssg.js +2 -0
  80. package/lib/streaming.d.ts +1 -1
  81. package/lib/testing.d.ts +3 -3
  82. package/lib/testing.js +1 -1
  83. package/lib/timeout.d.ts +1 -1
  84. package/lib/timing.d.ts +1 -1
  85. package/lib/tiny.d.ts +1 -1
  86. package/lib/trailing-slash.d.ts +1 -1
  87. package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  88. package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
  89. package/lib/types.d.ts +1 -1
  90. package/lib/utils/body.d.ts +1 -1
  91. package/lib/utils/ipaddr.d.ts +1 -1
  92. package/lib/utils-Cx61GFMC.js +125 -0
  93. package/lib/validator.d.ts +1 -1
  94. package/lib/ws.d.ts +1 -1
  95. package/package.json +13 -1
  96. package/plugins/acryl-inspect/upstream.json +1 -1
  97. package/plugins/acryl-node-server/src/register.ts +86 -0
  98. package/plugins/acryl-node-server/upstream.json +5 -3
  99. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  100. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  101. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  102. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  103. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  104. package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
  105. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  106. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  107. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  108. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  109. package/plugins/acryl-router/upstream.json +2 -2
  110. package/plugins/acryl-router-http/upstream.json +1 -1
  111. package/plugins/acryl-state/upstream.json +1 -1
  112. package/plugins/desktop/main.ts +4 -1
  113. package/plugins/desktop/upstream.json +1 -1
  114. package/plugins/desktop-web/main.ts +3 -1
  115. package/plugins/desktop-web/upstream.json +1 -1
  116. package/plugins/extensions/resolve.ts +4 -0
  117. package/shared/AGENTS.core.md +3 -0
  118. package/templates/agent/package.json +7 -3
  119. package/templates/agent/tsconfig.json +10 -2
  120. package/templates/blank/package.json +7 -3
  121. package/templates/blank/src/about.tsx +14 -0
  122. package/templates/blank/src/routes.ts +2 -0
  123. package/templates/blank/test/app.test.ts +15 -0
  124. package/templates/blank/tsconfig.json +3 -1
  125. package/templates/todo/package.json +7 -3
  126. package/templates/todo/tsconfig.json +3 -1
  127. package/templates/todo/upstream.json +1 -1
  128. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  129. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  130. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  131. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  132. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  133. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  134. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  135. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  136. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  137. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  138. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  139. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  140. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  141. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  142. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  143. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  144. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  145. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  146. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  147. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  148. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  149. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  150. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  151. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  152. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  153. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  154. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  155. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  156. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  157. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  158. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  159. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  160. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  161. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  162. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  163. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  164. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  165. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  166. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  167. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  168. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  169. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  170. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  171. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  172. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  173. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  174. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  175. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  176. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  177. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
  178. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
  179. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  180. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  181. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  182. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  183. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  184. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
  185. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  186. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  187. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  188. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  189. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  190. package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
  191. package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
  192. package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
  193. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
  194. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
  195. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
  196. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
  197. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
  198. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
  199. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
  200. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
  201. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
  202. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
  203. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
  204. package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
  205. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
  206. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
  207. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
  208. package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
  209. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  210. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  211. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  212. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  213. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  214. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  215. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  216. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  217. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  218. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  219. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  220. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  221. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  222. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  223. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  224. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  225. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  226. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  227. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  228. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  229. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  230. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  231. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  232. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  233. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  234. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  235. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  236. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  237. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  238. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  239. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  240. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  241. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  242. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  243. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  244. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  245. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  246. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  247. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  248. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  249. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  250. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  251. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  252. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  253. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  254. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  255. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  256. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  257. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  258. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  259. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  260. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  261. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  262. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  263. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  264. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  265. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  266. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  267. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  268. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  269. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  270. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  271. package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
  272. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  273. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  274. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  275. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  276. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  277. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  278. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  279. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  280. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  281. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  282. package/plugins/acryl-state/src/react.ts +0 -11
  283. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  284. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  285. package/plugins/acryl-state/tests/service.spec.ts +0 -132
@@ -1,655 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
-
3
- const importIndex = () => import('./index')
4
- const importStreaming = () => import('./streaming')
5
- const importHooks = () => import('./hooks')
6
- const importAsyncHooks = () => import('node:async_hooks')
7
-
8
- /**
9
- * These tests exercise the non-`AsyncLocalStorage` fallback path. They force
10
- * every `AsyncLocalStorage` acquisition route to fail (and keep it disabled
11
- * while the module lazily probes for it), then verify the React-style
12
- * fail-safe: reading context after `await` returns the default value and never
13
- * another concurrent request's value.
14
- */
15
- describe('JSX context isolation without AsyncLocalStorage', () => {
16
- let originalGetBuiltinModule: unknown
17
- let originalMainModule: unknown
18
- let warnSpy: ReturnType<typeof vi.spyOn>
19
-
20
- beforeEach(() => {
21
- // Disable every route `loadAsyncLocalStorage()` tries. These stay in effect
22
- // through the first render so the lazy probe caches "unavailable".
23
- vi.resetModules()
24
- originalGetBuiltinModule = (process as { getBuiltinModule?: unknown }).getBuiltinModule
25
- ;(process as { getBuiltinModule?: unknown }).getBuiltinModule = undefined
26
- originalMainModule = (process as { mainModule?: unknown }).mainModule
27
- ;(process as { mainModule?: unknown }).mainModule = undefined
28
- warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
29
- })
30
-
31
- afterEach(() => {
32
- ;(process as { getBuiltinModule?: unknown }).getBuiltinModule = originalGetBuiltinModule
33
- ;(process as { mainModule?: unknown }).mainModule = originalMainModule
34
- vi.unstubAllGlobals()
35
- vi.resetModules()
36
- warnSpy.mockRestore()
37
- })
38
-
39
- it('falls back to the default value after await and never leaks across concurrent renders', async () => {
40
- const { createContext, useContext, jsx } = await importIndex()
41
-
42
- const SessionContext = createContext<{ username: string; role: string }>({
43
- username: 'nobody',
44
- role: 'none',
45
- })
46
-
47
- const waits = new Map<string, Promise<void>>()
48
- const entered = new Map<string, () => void>()
49
-
50
- const AdminDashboard = async () => {
51
- const initialSession = useContext(SessionContext)
52
- entered.get(initialSession.username)?.()
53
- await waits.get(initialSession.username)
54
- const session = useContext(SessionContext)
55
- if (session.role !== 'admin') {
56
- return jsx('div', {}, `Access Denied for ${session.username}`)
57
- }
58
- return jsx('div', {}, `Welcome Admin ${session.username}. Secret Data: 42`)
59
- }
60
-
61
- const render = async (username: string, role: string): Promise<string> =>
62
- `${await jsx(
63
- SessionContext.Provider,
64
- { value: { username, role } },
65
- jsx(AdminDashboard, {})
66
- ).toString()}`
67
-
68
- let resolveAdmin!: () => void
69
- let resolveAttacker!: () => void
70
- waits.set(
71
- 'admin',
72
- new Promise<void>((resolve) => {
73
- resolveAdmin = resolve
74
- })
75
- )
76
- waits.set(
77
- 'attacker',
78
- new Promise<void>((resolve) => {
79
- resolveAttacker = resolve
80
- })
81
- )
82
- const adminEntered = new Promise<void>((resolve) => {
83
- entered.set('admin', resolve)
84
- })
85
- const attackerEntered = new Promise<void>((resolve) => {
86
- entered.set('attacker', resolve)
87
- })
88
-
89
- const adminReq = render('admin', 'admin')
90
- const attackerReq = render('attacker', 'guest')
91
-
92
- await Promise.all([adminEntered, attackerEntered])
93
- resolveAttacker()
94
- await Promise.resolve()
95
- resolveAdmin()
96
-
97
- const [adminHtml, attackerHtml] = await Promise.all([adminReq, attackerReq])
98
-
99
- // Without AsyncLocalStorage the post-await read sees the default value —
100
- // the same fail-safe React's request storage degrades to. The critical
101
- // guarantee is that neither render observes the other request's value.
102
- expect(adminHtml).toBe('<div>Access Denied for nobody</div>')
103
- expect(attackerHtml).toBe('<div>Access Denied for nobody</div>')
104
- })
105
-
106
- it('warns once when a post-await read falls back to the default value', async () => {
107
- const { createContext, useContext, jsx } = await importIndex()
108
-
109
- const ThemeContext = createContext('default')
110
- const Reader = async () => {
111
- await new Promise((r) => setTimeout(r, 10))
112
- return jsx('div', {}, useContext(ThemeContext))
113
- }
114
- const render = async () =>
115
- `${await jsx(ThemeContext.Provider, { value: 'provided' }, jsx(Reader, {})).toString()}`
116
-
117
- expect(await render()).toBe('<div>default</div>')
118
- expect(await render()).toBe('<div>default</div>')
119
-
120
- // Degrading to the default value is reported once, not per read.
121
- expect(warnSpy).toHaveBeenCalledTimes(1)
122
- expect(warnSpy.mock.calls[0][0]).toContain('AsyncLocalStorage')
123
- })
124
-
125
- it('clears the fallback warning window after an async render rejects', async () => {
126
- const { createContext, useContext, jsx } = await importIndex()
127
-
128
- const ThemeContext = createContext('default')
129
- const Broken = async () => {
130
- await Promise.resolve()
131
- throw new Error('boom')
132
- }
133
-
134
- await expect(jsx(Broken, {}).toString()).rejects.toThrow('boom')
135
-
136
- useContext(ThemeContext)
137
- expect(warnSpy).not.toHaveBeenCalled()
138
- })
139
-
140
- it('finds AsyncLocalStorage via process.mainModule.require (Node < 20.16 CJS)', async () => {
141
- // Re-enable only the legacy route: a CJS entrypoint exposes the main
142
- // module's `require` even where `process.getBuiltinModule` doesn't exist.
143
- // Context must then survive `await` with no warning.
144
- const { AsyncLocalStorage } = await importAsyncHooks()
145
- ;(process as { mainModule?: unknown }).mainModule = {
146
- require: (specifier: string) =>
147
- specifier === 'node:async_hooks' ? { AsyncLocalStorage } : {},
148
- }
149
-
150
- const { createContext, useContext, jsx } = await importIndex()
151
-
152
- const ThemeContext = createContext('default')
153
- const Reader = async () => {
154
- await new Promise((r) => setTimeout(r, 10))
155
- return jsx('div', {}, useContext(ThemeContext))
156
- }
157
-
158
- expect(
159
- `${await jsx(ThemeContext.Provider, { value: 'provided' }, jsx(Reader, {})).toString()}`
160
- ).toBe('<div>provided</div>')
161
- expect(warnSpy).not.toHaveBeenCalled()
162
- })
163
-
164
- it('keeps a synchronous render synchronous and does not warn', async () => {
165
- const { createContext, useContext, jsx } = await importIndex()
166
-
167
- const ThemeContext = createContext('light')
168
- const Consumer = () => jsx('span', {}, useContext(ThemeContext))
169
-
170
- const result = jsx(ThemeContext.Provider, { value: 'dark' }, jsx(Consumer, {})).toString()
171
-
172
- // A purely synchronous render must not be forced async by the serializer.
173
- expect(result).toBe('<span>dark</span>')
174
- expect(warnSpy).not.toHaveBeenCalled()
175
- })
176
-
177
- const drainStream = async (stream: unknown): Promise<string> => {
178
- const textDecoder = new TextDecoder()
179
- let html = ''
180
- for await (const chunk of stream as AsyncIterable<Uint8Array>) {
181
- html += textDecoder.decode(chunk)
182
- }
183
- return html
184
- }
185
-
186
- it('restores provider values for the subtree returned by a resumed async component', async () => {
187
- const { createContext, useContext, jsx } = await importIndex()
188
-
189
- const ThemeContext = createContext('default')
190
- const Inner = () => jsx('span', {}, useContext(ThemeContext))
191
- const Outer = async () => {
192
- await new Promise((r) => setTimeout(r, 10))
193
- // Unlike a useContext() call here in the async body (which degrades to
194
- // the default), the returned subtree renders with the context values
195
- // snapshotted at suspension and restored by the renderer.
196
- return jsx(Inner, {})
197
- }
198
-
199
- expect(
200
- `${await jsx(ThemeContext.Provider, { value: 'provided' }, jsx(Outer, {})).toString()}`
201
- ).toBe('<span>provided</span>')
202
- expect(warnSpy).not.toHaveBeenCalled()
203
- })
204
-
205
- it('resumes a Suspense-deferred subtree in the captured request store', async () => {
206
- const { createContext, useContext, jsx } = await importIndex()
207
- const { Suspense, renderToReadableStream } = await importStreaming()
208
- const { use } = await importHooks()
209
-
210
- // A `use()`-suspended child takes the deferred re-render path, which on the
211
- // fallback path re-establishes the captured render store synchronously.
212
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
213
- const ThemeContext = createContext('default')
214
- const Inner = () => jsx('span', {}, useContext(ThemeContext))
215
- const Content = () => {
216
- const value = use(deferred)
217
- return jsx(ThemeContext.Provider, { value }, jsx(Inner, {}))
218
- }
219
-
220
- const html = await drainStream(
221
- renderToReadableStream(jsx(Suspense, { fallback: jsx('p', {}, 'Loading') }, jsx(Content, {})))
222
- )
223
-
224
- expect(html).toContain('Loading')
225
- expect(html).toContain('<span>done</span>')
226
- // Providers inside the resumed subtree render in the request store, not in
227
- // the module-global fallback — so no degradation warning fires.
228
- expect(warnSpy).not.toHaveBeenCalled()
229
- })
230
-
231
- it('resumes a Suspense-deferred subtree with outer provider values', async () => {
232
- const { createContext, useContext, jsx } = await importIndex()
233
- const { Suspense, renderToReadableStream } = await importStreaming()
234
- const { use } = await importHooks()
235
-
236
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
237
- const ThemeContext = createContext('default')
238
- const Content = () => {
239
- use(deferred)
240
- return jsx('span', {}, useContext(ThemeContext))
241
- }
242
-
243
- const html = await drainStream(
244
- renderToReadableStream(
245
- jsx(
246
- ThemeContext.Provider,
247
- { value: 'outer' },
248
- jsx(Suspense, { fallback: jsx('p', {}, 'Loading') }, jsx(Content, {}))
249
- )
250
- )
251
- )
252
-
253
- expect(html).toContain('Loading')
254
- expect(html).toContain('<span>outer</span>')
255
- expect(html).not.toContain('<span>default</span>')
256
- expect(warnSpy).not.toHaveBeenCalled()
257
- })
258
-
259
- it('renders Suspense fallback with outer provider values', async () => {
260
- const { createContext, useContext, jsx } = await importIndex()
261
- const { Suspense, renderToReadableStream } = await importStreaming()
262
- const { use } = await importHooks()
263
-
264
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
265
- const ThemeContext = createContext('default')
266
- const Fallback = () => jsx('p', {}, `Loading ${useContext(ThemeContext)}`)
267
- const Content = () => {
268
- const value = use(deferred)
269
- return jsx('span', {}, value)
270
- }
271
-
272
- const html = await drainStream(
273
- renderToReadableStream(
274
- jsx(
275
- ThemeContext.Provider,
276
- { value: 'outer' },
277
- jsx(Suspense, { fallback: jsx(Fallback, {}) }, jsx(Content, {}))
278
- )
279
- )
280
- )
281
-
282
- expect(html).toContain('<p>Loading outer</p>')
283
- expect(html).toContain('<span>done</span>')
284
- expect(html).not.toContain('default')
285
- expect(warnSpy).not.toHaveBeenCalled()
286
- })
287
-
288
- it('restores nested provider values in a resumed Suspense subtree', async () => {
289
- const { createContext, useContext, jsx, Fragment } = await importIndex()
290
- const { Suspense, renderToReadableStream } = await importStreaming()
291
- const { use } = await importHooks()
292
-
293
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
294
- const ThemeContext = createContext('default')
295
- const Inner = () => jsx('span', {}, useContext(ThemeContext))
296
- const OuterSibling = () => jsx('em', {}, useContext(ThemeContext))
297
- const Content = () => {
298
- use(deferred)
299
- return jsx(
300
- Fragment,
301
- {},
302
- jsx(ThemeContext.Provider, { value: 'inner' }, jsx(Inner, {})),
303
- jsx(OuterSibling, {})
304
- )
305
- }
306
-
307
- const html = await drainStream(
308
- renderToReadableStream(
309
- jsx(
310
- ThemeContext.Provider,
311
- { value: 'outer' },
312
- jsx(Suspense, { fallback: jsx('p', {}, 'Loading') }, jsx(Content, {}))
313
- )
314
- )
315
- )
316
-
317
- expect(html).toContain('<span>inner</span><em>outer</em>')
318
- expect(html).not.toContain('default')
319
- expect(warnSpy).not.toHaveBeenCalled()
320
- })
321
-
322
- it('keeps sibling Suspense resume contexts isolated when they resolve out of order', async () => {
323
- const { createContext, useContext, jsx, Fragment } = await importIndex()
324
- const { Suspense, renderToReadableStream } = await importStreaming()
325
- const { use } = await importHooks()
326
-
327
- const ThemeContext = createContext('default')
328
- const waits = new Map<string, Promise<void>>()
329
- const entered = new Map<string, () => void>()
330
- let resolveFirst!: () => void
331
- let resolveSecond!: () => void
332
- waits.set(
333
- 'first',
334
- new Promise<void>((resolve) => {
335
- resolveFirst = resolve
336
- })
337
- )
338
- waits.set(
339
- 'second',
340
- new Promise<void>((resolve) => {
341
- resolveSecond = resolve
342
- })
343
- )
344
- const firstEntered = new Promise<void>((resolve) => {
345
- entered.set('first', resolve)
346
- })
347
- const secondEntered = new Promise<void>((resolve) => {
348
- entered.set('second', resolve)
349
- })
350
- const Content = ({ name }: { name: string }) => {
351
- entered.get(name)?.()
352
- use(waits.get(name)!)
353
- return jsx('span', {}, `${name}:${useContext(ThemeContext)}`)
354
- }
355
-
356
- const htmlPromise = drainStream(
357
- renderToReadableStream(
358
- jsx(
359
- Fragment,
360
- {},
361
- jsx(
362
- ThemeContext.Provider,
363
- { value: 'one' },
364
- jsx(
365
- Suspense,
366
- { fallback: jsx('p', {}, 'Loading one') },
367
- jsx(Content, { name: 'first' })
368
- )
369
- ),
370
- jsx(
371
- ThemeContext.Provider,
372
- { value: 'two' },
373
- jsx(
374
- Suspense,
375
- { fallback: jsx('p', {}, 'Loading two') },
376
- jsx(Content, { name: 'second' })
377
- )
378
- )
379
- )
380
- )
381
- )
382
-
383
- await Promise.all([firstEntered, secondEntered])
384
- resolveSecond()
385
- await Promise.resolve()
386
- resolveFirst()
387
-
388
- const html = await htmlPromise
389
-
390
- expect(html).toContain('<span>first:one</span>')
391
- expect(html).toContain('<span>second:two</span>')
392
- expect(html).not.toContain('default')
393
- expect(warnSpy).not.toHaveBeenCalled()
394
- })
395
-
396
- it('resumes ErrorBoundary-deferred children in the captured request store', async () => {
397
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
398
- const { renderToReadableStream } = await importStreaming()
399
-
400
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
401
- let resolved: string | undefined
402
- deferred.then((value) => {
403
- resolved = value
404
- })
405
- const ThemeContext = createContext('default')
406
- const Inner = () => jsx('span', {}, useContext(ThemeContext))
407
- const Content = () => {
408
- if (!resolved) {
409
- // Suspend like use() does so ErrorBoundary defers the re-render.
410
- throw deferred
411
- }
412
- return jsx(ThemeContext.Provider, { value: resolved }, jsx(Inner, {}))
413
- }
414
-
415
- const html = await drainStream(
416
- renderToReadableStream(
417
- jsx(ErrorBoundary, { fallback: jsx('p', {}, 'Failed') }, jsx(Content, {}))
418
- )
419
- )
420
-
421
- expect(html).toContain('<span>done</span>')
422
- expect(warnSpy).not.toHaveBeenCalled()
423
- })
424
-
425
- it('resumes ErrorBoundary-deferred children with outer provider values', async () => {
426
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
427
- const { renderToReadableStream } = await importStreaming()
428
-
429
- const deferred = new Promise<string>((resolve) => setTimeout(() => resolve('done'), 10))
430
- let resolved: string | undefined
431
- deferred.then((value) => {
432
- resolved = value
433
- })
434
- const ThemeContext = createContext('default')
435
- const Content = () => {
436
- if (!resolved) {
437
- // Suspend like use() does so ErrorBoundary defers the re-render.
438
- throw deferred
439
- }
440
- return jsx('span', {}, useContext(ThemeContext))
441
- }
442
-
443
- const html = await drainStream(
444
- renderToReadableStream(
445
- jsx(
446
- ThemeContext.Provider,
447
- { value: 'outer' },
448
- jsx(ErrorBoundary, { fallback: jsx('p', {}, 'Failed') }, jsx(Content, {}))
449
- )
450
- )
451
- )
452
-
453
- expect(html).toContain('<span>outer</span>')
454
- expect(html).not.toContain('<span>default</span>')
455
- expect(warnSpy).not.toHaveBeenCalled()
456
- })
457
-
458
- it('resumes ErrorBoundary fallback rendering with outer provider values', async () => {
459
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
460
- const { renderToReadableStream } = await importStreaming()
461
-
462
- const deferred = new Promise((_resolve, reject) =>
463
- setTimeout(() => reject(new Error('boom')), 10)
464
- )
465
- const ThemeContext = createContext('default')
466
- const Content = () => {
467
- throw deferred
468
- }
469
-
470
- const html = await drainStream(
471
- renderToReadableStream(
472
- jsx(
473
- ThemeContext.Provider,
474
- { value: 'outer' },
475
- jsx(
476
- ErrorBoundary,
477
- {
478
- fallbackRender: (error: Error) =>
479
- jsx('span', {}, `fallback:${useContext(ThemeContext)}:${error.message}`),
480
- },
481
- jsx(Content, {})
482
- )
483
- )
484
- )
485
- )
486
-
487
- expect(html).toContain('<span>fallback:outer:boom</span>')
488
- expect(html).not.toContain('fallback:default')
489
- expect(warnSpy).not.toHaveBeenCalled()
490
- })
491
-
492
- it('resumes ErrorBoundary fallbackRender components with outer provider values', async () => {
493
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
494
- const { renderToReadableStream } = await importStreaming()
495
-
496
- const deferred = new Promise((_resolve, reject) =>
497
- setTimeout(() => reject(new Error('boom')), 10)
498
- )
499
- const ThemeContext = createContext('default')
500
- const Content = () => {
501
- throw deferred
502
- }
503
- const Fallback = () => jsx('span', {}, `fallback:${useContext(ThemeContext)}`)
504
-
505
- const html = await drainStream(
506
- renderToReadableStream(
507
- jsx(
508
- ThemeContext.Provider,
509
- { value: 'outer' },
510
- jsx(
511
- ErrorBoundary,
512
- {
513
- fallbackRender: () => jsx(Fallback, {}),
514
- },
515
- jsx(Content, {})
516
- )
517
- )
518
- )
519
- )
520
-
521
- expect(html).toContain('<span>fallback:outer</span>')
522
- expect(html).not.toContain('fallback:default')
523
- expect(warnSpy).not.toHaveBeenCalled()
524
- })
525
-
526
- it('resumes ErrorBoundary fallback prop rendering with outer provider values', async () => {
527
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
528
- const { renderToReadableStream } = await importStreaming()
529
-
530
- const deferred = new Promise((_resolve, reject) =>
531
- setTimeout(() => reject(new Error('boom')), 10)
532
- )
533
- const ThemeContext = createContext('default')
534
- const Content = () => {
535
- throw deferred
536
- }
537
- const Fallback = () => jsx('span', {}, `fallback:${useContext(ThemeContext)}`)
538
-
539
- const html = await drainStream(
540
- renderToReadableStream(
541
- jsx(
542
- ThemeContext.Provider,
543
- { value: 'outer' },
544
- jsx(ErrorBoundary, { fallback: jsx(Fallback, {}) }, jsx(Content, {}))
545
- )
546
- )
547
- )
548
-
549
- expect(html).toContain('<span>fallback:outer</span>')
550
- expect(html).not.toContain('fallback:default')
551
- expect(warnSpy).not.toHaveBeenCalled()
552
- })
553
-
554
- it('resumes thenable ErrorBoundary fallback prop rendering with outer provider values', async () => {
555
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
556
- const { renderToReadableStream } = await importStreaming()
557
-
558
- const deferred = new Promise((_resolve, reject) =>
559
- setTimeout(() => reject(new Error('boom')), 10)
560
- )
561
- const ThemeContext = createContext('default')
562
- const Content = () => {
563
- throw deferred
564
- }
565
- const Fallback = () => jsx('span', {}, `fallback:${useContext(ThemeContext)}`)
566
- const fallback = {
567
- then(resolve: (value: unknown) => void) {
568
- setTimeout(() => resolve(jsx(Fallback, {})), 0)
569
- },
570
- }
571
-
572
- const html = await drainStream(
573
- renderToReadableStream(
574
- jsx(
575
- ThemeContext.Provider,
576
- { value: 'outer' },
577
- jsx(ErrorBoundary, { fallback }, jsx(Content, {}))
578
- )
579
- )
580
- )
581
-
582
- expect(html).toContain('<span>fallback:outer</span>')
583
- expect(html).not.toContain('fallback:default')
584
- expect(warnSpy).not.toHaveBeenCalled()
585
- })
586
-
587
- it('resumes synchronous ErrorBoundary fallback rendering with outer provider values', async () => {
588
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
589
-
590
- const ThemeContext = createContext('default')
591
- const Content = () => {
592
- throw new Error('boom')
593
- }
594
-
595
- expect(
596
- `${await jsx(
597
- ThemeContext.Provider,
598
- { value: 'outer' },
599
- jsx(
600
- ErrorBoundary,
601
- {
602
- fallbackRender: (error: Error) =>
603
- jsx('span', {}, `fallback:${useContext(ThemeContext)}:${error.message}`),
604
- },
605
- jsx(Content, {})
606
- )
607
- ).toString()}`
608
- ).toBe('<span>fallback:outer:boom</span>')
609
- expect(warnSpy).not.toHaveBeenCalled()
610
- })
611
-
612
- it('resumes synchronous ErrorBoundary fallbackRender components with outer provider values', async () => {
613
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
614
-
615
- const ThemeContext = createContext('default')
616
- const Content = () => {
617
- throw new Error('boom')
618
- }
619
- const Fallback = () => jsx('span', {}, `fallback:${useContext(ThemeContext)}`)
620
-
621
- expect(
622
- `${await jsx(
623
- ThemeContext.Provider,
624
- { value: 'outer' },
625
- jsx(
626
- ErrorBoundary,
627
- {
628
- fallbackRender: () => jsx(Fallback, {}),
629
- },
630
- jsx(Content, {})
631
- )
632
- ).toString()}`
633
- ).toBe('<span>fallback:outer</span>')
634
- expect(warnSpy).not.toHaveBeenCalled()
635
- })
636
-
637
- it('resumes synchronous ErrorBoundary fallback prop rendering with outer provider values', async () => {
638
- const { createContext, useContext, jsx, ErrorBoundary } = await importIndex()
639
-
640
- const ThemeContext = createContext('default')
641
- const Content = () => {
642
- throw new Error('boom')
643
- }
644
- const Fallback = () => jsx('span', {}, `fallback:${useContext(ThemeContext)}`)
645
-
646
- expect(
647
- `${await jsx(
648
- ThemeContext.Provider,
649
- { value: 'outer' },
650
- jsx(ErrorBoundary, { fallback: jsx(Fallback, {}) }, jsx(Content, {}))
651
- ).toString()}`
652
- ).toBe('<span>fallback:outer</span>')
653
- expect(warnSpy).not.toHaveBeenCalled()
654
- })
655
- })