acrylblends 0.6.3 → 0.6.4

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 (269) hide show
  1. package/README.md +3 -2
  2. package/docs/framework/README.md +2 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/guides/jsx.md +24 -0
  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/basic-auth.d.ts +1 -1
  13. package/lib/bearer-auth.d.ts +1 -1
  14. package/lib/body-limit.d.ts +1 -1
  15. package/lib/cache.d.ts +1 -1
  16. package/lib/cache.js +4 -4
  17. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  18. package/lib/client.d.ts +2 -2
  19. package/lib/client.js +2 -1
  20. package/lib/combine.d.ts +1 -1
  21. package/lib/{components-Ce5FRyHU.js → components-0o0CBYNv.js} +2 -2
  22. package/lib/{components-DYZdxJIJ.js → components-DdI7-ns7.js} +1 -1
  23. package/lib/compress.d.ts +1 -1
  24. package/lib/conninfo.d.ts +2 -2
  25. package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
  26. package/lib/context-storage.d.ts +1 -1
  27. package/lib/cookie.d.ts +1 -1
  28. package/lib/cors.d.ts +1 -1
  29. package/lib/csrf.d.ts +1 -1
  30. package/lib/dev.d.ts +2 -2
  31. package/lib/etag.d.ts +1 -1
  32. package/lib/factory.d.ts +2 -2
  33. package/lib/{hooks-Bq5q9IMl.js → hooks-BMw6BAyP.js} +5 -5
  34. package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
  35. package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
  36. package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
  37. package/lib/index.d.ts +4 -4
  38. package/lib/ip-restriction.d.ts +2 -2
  39. package/lib/jsx/dom/client.js +1 -1
  40. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  41. package/lib/jsx/dom/jsx-runtime.js +1 -1
  42. package/lib/jsx/dom/server.js +1 -1
  43. package/lib/jsx/dom.js +3 -3
  44. package/lib/jsx/streaming.js +1 -1
  45. package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-D18Y35TI.js} +1 -1
  46. package/lib/jsx-renderer.d.ts +75 -0
  47. package/lib/jsx-renderer.js +99 -0
  48. package/lib/jsx.js +3 -3
  49. package/lib/jwk.d.ts +1 -1
  50. package/lib/jwt.d.ts +1 -1
  51. package/lib/language.d.ts +1 -1
  52. package/lib/logger.d.ts +1 -1
  53. package/lib/loopback-only.d.ts +1 -1
  54. package/lib/method-not-allowed.d.ts +2 -2
  55. package/lib/method-override.d.ts +2 -2
  56. package/lib/mount.d.ts +1 -1
  57. package/lib/node-server/conninfo.d.ts +1 -1
  58. package/lib/node-server/serve-static.d.ts +1 -1
  59. package/lib/node-server/serve-static.js +1 -1
  60. package/lib/node-server.d.ts +1 -1
  61. package/lib/node-server.js +1 -1
  62. package/lib/powered-by.d.ts +1 -1
  63. package/lib/pretty-json.d.ts +1 -1
  64. package/lib/quick.d.ts +1 -1
  65. package/lib/request-id.d.ts +1 -1
  66. package/lib/request.d.ts +1 -1
  67. package/lib/route.d.ts +1 -1
  68. package/lib/schema-validator.d.ts +1 -1
  69. package/lib/secure-headers.d.ts +1 -1
  70. package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
  71. package/lib/serve-static.d.ts +1 -1
  72. package/lib/ssg-BwjcA-Ge.js +389 -0
  73. package/lib/ssg.d.ts +156 -0
  74. package/lib/ssg.js +2 -0
  75. package/lib/streaming.d.ts +1 -1
  76. package/lib/testing.d.ts +3 -3
  77. package/lib/testing.js +1 -1
  78. package/lib/timeout.d.ts +1 -1
  79. package/lib/timing.d.ts +1 -1
  80. package/lib/tiny.d.ts +1 -1
  81. package/lib/trailing-slash.d.ts +1 -1
  82. package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  83. package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
  84. package/lib/types.d.ts +1 -1
  85. package/lib/utils/body.d.ts +1 -1
  86. package/lib/utils/ipaddr.d.ts +1 -1
  87. package/lib/utils-Cx61GFMC.js +125 -0
  88. package/lib/validator.d.ts +1 -1
  89. package/lib/ws.d.ts +1 -1
  90. package/package.json +9 -1
  91. package/plugins/acryl-inspect/upstream.json +1 -1
  92. package/plugins/acryl-node-server/upstream.json +1 -1
  93. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  94. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  95. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  96. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  97. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  98. package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
  99. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  100. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  101. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  102. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  103. package/plugins/acryl-router/upstream.json +2 -2
  104. package/plugins/acryl-router-http/upstream.json +1 -1
  105. package/plugins/acryl-state/upstream.json +1 -1
  106. package/plugins/desktop/upstream.json +1 -1
  107. package/plugins/desktop-web/upstream.json +1 -1
  108. package/plugins/extensions/resolve.ts +4 -0
  109. package/shared/AGENTS.core.md +4 -0
  110. package/templates/agent/tsconfig.json +7 -1
  111. package/templates/todo/upstream.json +1 -1
  112. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  113. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  114. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  115. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  116. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  117. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  118. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  119. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  120. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  121. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  122. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  123. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  124. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  125. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  126. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  127. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  128. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  129. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  130. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  131. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  132. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  133. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  134. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  135. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  136. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  137. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  138. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  139. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  140. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  141. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  142. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  143. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  144. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  145. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  146. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  147. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  148. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  149. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  150. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  151. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  152. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  153. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  154. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  155. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  156. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  157. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  158. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  159. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  160. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  161. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
  162. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
  163. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  164. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  165. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  166. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  167. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  168. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
  169. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  170. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  171. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  172. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  173. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  174. package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
  175. package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
  176. package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
  177. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
  178. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
  179. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
  180. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
  181. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
  182. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
  183. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
  184. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
  185. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
  186. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
  187. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
  188. package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
  189. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
  190. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
  191. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
  192. package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
  193. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  194. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  195. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  196. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  197. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  198. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  199. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  200. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  201. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  202. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  203. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  204. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  205. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  206. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  207. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  208. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  209. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  210. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  211. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  212. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  213. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  214. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  215. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  216. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  217. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  218. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  219. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  220. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  221. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  222. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  223. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  224. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  225. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  226. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  227. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  228. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  229. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  230. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  231. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  232. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  233. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  234. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  235. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  236. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  237. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  238. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  239. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  240. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  241. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  242. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  243. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  244. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  245. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  246. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  247. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  248. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  249. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  250. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  251. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  252. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  253. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  254. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  255. package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
  256. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  257. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  258. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  259. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  260. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  261. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  262. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  263. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  264. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  265. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  266. package/plugins/acryl-state/src/react.ts +0 -11
  267. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  268. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  269. 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
- })