acrylblends 0.6.2 → 0.6.3

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 (181) hide show
  1. package/README.md +4 -1
  2. package/docs/framework/README.md +3 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
  5. package/docs/framework/getting-started/basic.md +1 -0
  6. package/docs/framework/getting-started/desktop.md +41 -0
  7. package/docs/framework/guides/jsx-dom.md +0 -3
  8. package/docs/framework/guides/jsx.md +0 -3
  9. package/docs/framework/helpers/css.md +0 -3
  10. package/docs/framework/upstream.json +3 -3
  11. package/lib/accepts.d.ts +1 -1
  12. package/lib/acryl-base.d.ts +1 -1
  13. package/lib/adapter.d.ts +1 -1
  14. package/lib/{app-BdrVHBlJ.d.ts → app-wFwpg-bt.d.ts} +1 -1
  15. package/lib/base-BpQy2Rk7.d.ts +783 -0
  16. package/lib/base-ddiVmiE7.js +721 -0
  17. package/lib/basic-auth.d.ts +1 -1
  18. package/lib/bearer-auth.d.ts +1 -1
  19. package/lib/body-limit.d.ts +1 -1
  20. package/lib/cache.d.ts +1 -1
  21. package/lib/client.d.ts +2 -2
  22. package/lib/combine.d.ts +1 -1
  23. package/lib/common-D8Z4JRCw.d.ts +46 -0
  24. package/lib/components-Ce5FRyHU.js +268 -0
  25. package/lib/components-DYZdxJIJ.js +24 -0
  26. package/lib/compress.d.ts +1 -1
  27. package/lib/conninfo.d.ts +2 -2
  28. package/lib/constants-BR9jM9BC.js +9 -0
  29. package/lib/{context-B5Y1lLha.d.ts → context-DVQBjuns.d.ts} +1 -1
  30. package/lib/context-DxFdY_un.d.ts +250 -0
  31. package/lib/context-storage.d.ts +1 -1
  32. package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
  33. package/lib/cookie.d.ts +3 -3
  34. package/lib/cors.d.ts +1 -1
  35. package/lib/csrf.d.ts +1 -1
  36. package/lib/css-D2jb7X8b.js +320 -0
  37. package/lib/css.d.ts +82 -0
  38. package/lib/css.js +134 -0
  39. package/lib/dev.d.ts +2 -2
  40. package/lib/etag.d.ts +1 -1
  41. package/lib/factory.d.ts +2 -2
  42. package/lib/hooks-Bq5q9IMl.js +762 -0
  43. package/lib/html-SllAe0YT.d.ts +46 -0
  44. package/lib/html.d.ts +1 -1
  45. package/lib/index-C850MeBA.d.ts +94 -0
  46. package/lib/{index-m12zcmph.d.ts → index-CUTfU69R.d.ts} +3 -3
  47. package/lib/{index-DtEoak1i.d.ts → index-Cqcdluyn.d.ts} +2 -2
  48. package/lib/{index-Kqou8Fjh.d.ts → index-CsvB-F9j.d.ts} +2 -2
  49. package/lib/index.d.ts +4 -4
  50. package/lib/ip-restriction.d.ts +2 -2
  51. package/lib/jsx/dom/client.d.ts +30 -0
  52. package/lib/jsx/dom/client.js +49 -0
  53. package/lib/jsx/dom/css.d.ts +84 -0
  54. package/lib/jsx/dom/css.js +2 -0
  55. package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
  56. package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
  57. package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
  58. package/lib/jsx/dom/jsx-runtime.js +2 -0
  59. package/lib/jsx/dom/server.d.ts +105 -0
  60. package/lib/jsx/dom/server.js +37 -0
  61. package/lib/jsx/dom.d.ts +5 -0
  62. package/lib/jsx/dom.js +74 -0
  63. package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
  64. package/lib/jsx/jsx-dev-runtime.js +14 -0
  65. package/lib/jsx/jsx-runtime.d.ts +10 -0
  66. package/lib/jsx/jsx-runtime.js +30 -0
  67. package/lib/jsx/streaming.d.ts +2 -0
  68. package/lib/jsx/streaming.js +2 -0
  69. package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
  70. package/lib/jsx-dev-runtime-kAhR_4C7.js +354 -0
  71. package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
  72. package/lib/jsx.d.ts +3 -0
  73. package/lib/jsx.js +47 -0
  74. package/lib/jwk.d.ts +4 -4
  75. package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
  76. package/lib/jwt.d.ts +4 -4
  77. package/lib/language.d.ts +1 -1
  78. package/lib/logger.d.ts +1 -1
  79. package/lib/loopback-only.d.ts +1 -1
  80. package/lib/method-not-allowed.d.ts +2 -2
  81. package/lib/method-override.d.ts +2 -2
  82. package/lib/mount.d.ts +1 -1
  83. package/lib/node-server/conninfo.d.ts +1 -1
  84. package/lib/node-server/serve-static.d.ts +1 -1
  85. package/lib/node-server/serve-static.js +1 -1
  86. package/lib/node-server.d.ts +1 -1
  87. package/lib/node-server.js +1 -1
  88. package/lib/powered-by.d.ts +1 -1
  89. package/lib/pretty-json.d.ts +1 -1
  90. package/lib/proxy.d.ts +1 -1
  91. package/lib/quick.d.ts +1 -1
  92. package/lib/request-id.d.ts +1 -1
  93. package/lib/request.d.ts +1 -1
  94. package/lib/route.d.ts +1 -1
  95. package/lib/schema-validator.d.ts +1 -1
  96. package/lib/secure-headers.d.ts +1 -1
  97. package/lib/{serve-static-Dw_m6KxR.js → serve-static-Cw23shXv.js} +1 -0
  98. package/lib/serve-static.d.ts +1 -1
  99. package/lib/streaming.d.ts +1 -1
  100. package/lib/testing.d.ts +4 -4
  101. package/lib/timeout.d.ts +1 -1
  102. package/lib/timing.d.ts +1 -1
  103. package/lib/tiny.d.ts +1 -1
  104. package/lib/trailing-slash.d.ts +1 -1
  105. package/lib/{types-Bd29gh2o.d.ts → types-CQiGmsWW.d.ts} +3 -3
  106. package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
  107. package/lib/{types-ZM9gIiKh.d.ts → types-nEL8VPDC.d.ts} +1 -1
  108. package/lib/types.d.ts +1 -1
  109. package/lib/utils/body.d.ts +1 -1
  110. package/lib/utils/cookie.d.ts +1 -1
  111. package/lib/utils/crypto.d.ts +1 -1
  112. package/lib/utils/html.d.ts +1 -45
  113. package/lib/utils/ipaddr.d.ts +1 -1
  114. package/lib/utils/jwt.d.ts +1 -1
  115. package/lib/utils/types.d.ts +2 -2
  116. package/lib/validator.d.ts +2 -2
  117. package/lib/ws.d.ts +1 -1
  118. package/package.json +45 -1
  119. package/plugins/acryl-inspect/upstream.json +1 -1
  120. package/plugins/acryl-node-server/upstream.json +1 -1
  121. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +506 -0
  122. package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
  123. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +678 -0
  124. package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
  125. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +557 -0
  126. package/plugins/acryl-router/src/core/jsx/base.test.tsx +97 -0
  127. package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
  128. package/plugins/acryl-router/src/core/jsx/children.test.ts +52 -0
  129. package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
  130. package/plugins/acryl-router/src/core/jsx/components.test.tsx +936 -0
  131. package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
  132. package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
  133. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +655 -0
  134. package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
  135. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +128 -0
  136. package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
  137. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +330 -0
  138. package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
  139. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +184 -0
  140. package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
  141. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +149 -0
  142. package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
  143. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +172 -0
  144. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
  145. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +3681 -0
  146. package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
  147. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +1344 -0
  148. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
  149. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
  150. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
  151. package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
  152. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +199 -0
  153. package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
  154. package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
  155. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +859 -0
  156. package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
  157. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +40 -0
  158. package/plugins/acryl-router/src/core/jsx/index.test.tsx +1744 -0
  159. package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
  160. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
  161. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +666 -0
  162. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
  163. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
  164. package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
  165. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +85 -0
  166. package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
  167. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +1281 -0
  168. package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
  169. package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
  170. package/plugins/acryl-router/src/core/jsx/utils.test.ts +366 -0
  171. package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
  172. package/plugins/acryl-router/src/core/utils/types.ts +6 -0
  173. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  174. package/plugins/acryl-router/tests/jsx.spec.tsx +63 -0
  175. package/plugins/acryl-router/upstream.json +3 -3
  176. package/plugins/acryl-router-http/upstream.json +1 -1
  177. package/plugins/acryl-state/upstream.json +1 -1
  178. package/plugins/desktop/upstream.json +1 -1
  179. package/plugins/desktop-web/upstream.json +1 -1
  180. package/plugins/save/plugin.json +1 -1
  181. package/templates/todo/upstream.json +1 -1
@@ -0,0 +1,202 @@
1
+ const normalizeElementKeyMap: Map<string, string> = new Map([
2
+ ['className', 'class'],
3
+ ['htmlFor', 'for'],
4
+ ['crossOrigin', 'crossorigin'],
5
+ ['httpEquiv', 'http-equiv'],
6
+ ['itemProp', 'itemprop'],
7
+ ['fetchPriority', 'fetchpriority'],
8
+ ['noModule', 'nomodule'],
9
+ ['formAction', 'formaction'],
10
+ ])
11
+ export const normalizeIntrinsicElementKey = (key: string): string =>
12
+ normalizeElementKeyMap.get(key) || key
13
+
14
+ // eslint-disable-next-line no-control-regex
15
+ const invalidAttributeNameCharRe = /[\s"'<>/=`\\\x00-\x1f\x7f-\x9f]/
16
+ const validAttributeNameCache = new Set<string>()
17
+ const validAttributeNameCacheMax = 1024
18
+ // reject HTML parser-control tag starters ('!' for comments/doctype, '?' for
19
+ // processing instructions) in addition to the shared invalid-char set
20
+ // eslint-disable-next-line no-control-regex
21
+ const invalidTagNameCharRe = /^[!?]|[\s"'<>/=`\\\x00-\x1f\x7f-\x9f]/
22
+ const validTagNameCache = new Set<unknown>()
23
+ const validTagNameCacheMax = 256
24
+
25
+ const cacheValidName = (cache: Set<unknown>, max: number, name: string): void => {
26
+ if (cache.size >= max) {
27
+ cache.clear()
28
+ }
29
+ cache.add(name)
30
+ }
31
+
32
+ export const isValidTagName = (name: unknown): name is string => {
33
+ if (validTagNameCache.has(name)) {
34
+ return true
35
+ }
36
+ if (typeof name !== 'string') {
37
+ return false
38
+ }
39
+ if (name.length === 0) {
40
+ return true
41
+ }
42
+ if (invalidTagNameCharRe.test(name)) {
43
+ return false
44
+ }
45
+ cacheValidName(validTagNameCache, validTagNameCacheMax, name)
46
+ return true
47
+ }
48
+
49
+ export const isValidAttributeName = (name: string): boolean => {
50
+ if (validAttributeNameCache.has(name)) {
51
+ return true
52
+ }
53
+ const len = name.length
54
+ if (len === 0) {
55
+ return false
56
+ }
57
+ for (let i = 0; i < len; i++) {
58
+ const c = name.charCodeAt(i)
59
+ if (
60
+ !(
61
+ (c >= 0x61 && c <= 0x7a) || // a-z
62
+ (c >= 0x41 && c <= 0x5a) || // A-Z
63
+ (c >= 0x30 && c <= 0x39) || // 0-9
64
+ c === 0x2d || // -
65
+ c === 0x5f || // _
66
+ c === 0x2e || // .
67
+ c === 0x3a // :
68
+ )
69
+ ) {
70
+ // non-fast-path character found — fall back to regex for the full name
71
+ if (!invalidAttributeNameCharRe.test(name)) {
72
+ cacheValidName(validAttributeNameCache, validAttributeNameCacheMax, name)
73
+ return true
74
+ } else {
75
+ return false
76
+ }
77
+ }
78
+ }
79
+ cacheValidName(validAttributeNameCache, validAttributeNameCacheMax, name)
80
+ return true
81
+ }
82
+
83
+ // eslint-disable-next-line no-control-regex
84
+ const invalidStylePropertyNameCharRe = /[\s"'():;\\/\[\]{}\x00-\x1f\x7f-\x9f]/
85
+ const validStylePropertyNameCache = new Set<string>()
86
+ const validStylePropertyNameCacheMax = 1024
87
+
88
+ const isValidStylePropertyName = (name: string): boolean => {
89
+ if (validStylePropertyNameCache.has(name)) {
90
+ return true
91
+ }
92
+ const len = name.length
93
+ if (len === 0) {
94
+ return false
95
+ }
96
+ for (let i = 0; i < len; i++) {
97
+ const c = name.charCodeAt(i)
98
+ if (
99
+ !(
100
+ (c >= 0x61 && c <= 0x7a) || // a-z
101
+ (c >= 0x41 && c <= 0x5a) || // A-Z
102
+ (c >= 0x30 && c <= 0x39) || // 0-9
103
+ c === 0x2d || // -
104
+ c === 0x5f // _
105
+ )
106
+ ) {
107
+ // non-fast-path character found — fall back to regex for the full name
108
+ if (!invalidStylePropertyNameCharRe.test(name)) {
109
+ cacheValidName(validStylePropertyNameCache, validStylePropertyNameCacheMax, name)
110
+ return true
111
+ } else {
112
+ return false
113
+ }
114
+ }
115
+ }
116
+ cacheValidName(validStylePropertyNameCache, validStylePropertyNameCacheMax, name)
117
+ return true
118
+ }
119
+
120
+ const unsafeStyleValueCharRe = /[;"'\\/\[\](){}]/
121
+
122
+ const hasUnsafeStyleValue = (value: string): boolean => {
123
+ if (!unsafeStyleValueCharRe.test(value)) {
124
+ return false
125
+ }
126
+
127
+ let quote = 0
128
+ const blockStack: number[] = []
129
+ for (let i = 0, len = value.length; i < len; i++) {
130
+ const c = value.charCodeAt(i)
131
+ if (c === 0x5c) {
132
+ if (i === len - 1) {
133
+ return true
134
+ }
135
+ i++
136
+ } else if (quote !== 0) {
137
+ if (c === 0x0a || c === 0x0c || c === 0x0d) {
138
+ return true
139
+ }
140
+ if (c === quote) {
141
+ quote = 0
142
+ }
143
+ } else if (c === 0x2f && value.charCodeAt(i + 1) === 0x2a) {
144
+ const end = value.indexOf('*/', i + 2)
145
+ if (end === -1) {
146
+ return true
147
+ }
148
+ i = end + 1
149
+ } else if (c === 0x22 || c === 0x27) {
150
+ quote = c
151
+ } else if (c === 0x28) {
152
+ blockStack.push(0x29)
153
+ } else if (c === 0x5b) {
154
+ blockStack.push(0x5d)
155
+ } else if (c === 0x7b || c === 0x7d) {
156
+ return true
157
+ } else if (c === 0x29 || c === 0x5d) {
158
+ if (blockStack[blockStack.length - 1] !== c) {
159
+ return true
160
+ }
161
+ blockStack.pop()
162
+ } else if (c === 0x3b && blockStack.length === 0) {
163
+ return true
164
+ }
165
+ }
166
+ return quote !== 0 || blockStack.length !== 0
167
+ }
168
+
169
+ export const styleObjectForEach = (
170
+ style: Record<string, unknown>,
171
+ fn: (key: string, value: string | null) => void
172
+ ): void => {
173
+ for (const [k, v] of Object.entries(style)) {
174
+ const key =
175
+ k[0] === '-' || !/[A-Z]/.test(k)
176
+ ? k // a CSS variable or a lowercase only property
177
+ : k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`) // a camelCase property. convert to kebab-case
178
+ if (!isValidStylePropertyName(key)) {
179
+ continue
180
+ }
181
+ if (v == null) {
182
+ fn(key, null)
183
+ continue
184
+ }
185
+ let value: string
186
+ if (typeof v === 'number') {
187
+ value = !key.match(
188
+ /^(?:a|border-im|column(?:-c|s)|flex(?:$|-[^b])|grid-(?!.*gap)(?:ar|[^a])|font-w|li|or|sca|st|ta|wido|z)|ty$/
189
+ )
190
+ ? `${v}px`
191
+ : `${v}`
192
+ } else if (typeof v === 'string') {
193
+ if (hasUnsafeStyleValue(v)) {
194
+ continue
195
+ }
196
+ value = v
197
+ } else {
198
+ continue
199
+ }
200
+ fn(key, value)
201
+ }
202
+ }
@@ -166,3 +166,9 @@ export interface JsonWebKey {
166
166
  x?: string
167
167
  y?: string
168
168
  }
169
+
170
+ /**
171
+ * A DOM event type by its global name: the DOM library's own type when it is loaded (a browser project), otherwise the runtime's `Event`,
172
+ * so the JSX types (event handler props) do not need the DOM library on a server.
173
+ */
174
+ export type DomEvent<Name extends string> = typeof globalThis extends { readonly [K in Name]: { readonly prototype: infer E } } ? E : Event
@@ -4,6 +4,7 @@ export { accepts } from '../core/helper/accepts/index.ts'
4
4
  export { createFactory, createMiddleware, Factory } from '../core/helper/factory/index.ts'
5
5
  export { deleteCookie, generateCookie, generateSignedCookie, getCookie, getSignedCookie, setCookie, setSignedCookie } from '../core/helper/cookie/index.ts'
6
6
  export { getExtension, getMimeType, mimes, type BaseMime } from '../core/utils/mime.ts'
7
+ export { createCssContext, css, cx, keyframes, rawCssString, Style, viewTransition } from '../core/helper/css/index.ts'
7
8
  export { html, raw } from '../core/helper/html/index.ts'
8
9
  export { proxy } from '../core/helper/proxy/index.ts'
9
10
  export { getRouterName, inspectRoutes, showRoutes } from '../core/helper/dev/index.ts'
@@ -0,0 +1,63 @@
1
+ /** @jsxImportSource acryl-router/jsx */
2
+ /**
3
+ * JSX in a Cordis plugin, set up as the docs say (`jsxImportSource: 'acryl-router/jsx'`, which resolves this package's built runtime): a page rendered with the ported JSX renderer and styled with the CSS helper, served through `ctx.router`, and a
4
+ * streamed page whose suspended part arrives after the shell. The ported suites cover the renderer itself; this covers the plugin seam.
5
+ */
6
+
7
+ import { Context } from '@deepseek-ai/cordis'
8
+ import { afterEach, describe, expect, it } from 'vitest'
9
+ import { css, Style } from '../src/helpers/index.ts'
10
+ import { Acryl, apply, name } from '../src/index.ts'
11
+ import { Suspense } from '../src/core/jsx/index.ts'
12
+ import { renderToReadableStream } from '../src/core/jsx/streaming.ts'
13
+
14
+ const contexts: Context[] = []
15
+ const settle = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0))
16
+ afterEach(async () => {
17
+ while (contexts.length > 0) await contexts.pop()?.fiber.dispose()
18
+ })
19
+
20
+ async function serve(app: Acryl): Promise<Context> {
21
+ const ctx = new Context()
22
+ contexts.push(ctx)
23
+ ctx.plugin({ name, apply })
24
+ ctx.plugin({ name: 'pages', inject: ['router'], apply(inner: Context) { inner.router.route('/pages', app) } })
25
+ await settle()
26
+ return ctx
27
+ }
28
+
29
+ describe('JSX through ctx.router', () => {
30
+ it('serves a page rendered from JSX, styled with the CSS helper', async () => {
31
+ const title = css`color: teal;`
32
+ const app = new Acryl().get('/', c => c.html(
33
+ <html>
34
+ <head><Style /></head>
35
+ <body><h1 class={title}>Acryl!</h1></body>
36
+ </html>,
37
+ ))
38
+ const ctx = await serve(app)
39
+
40
+ const response = await ctx.router.request('/pages')
41
+ expect(response.status).toBe(200)
42
+ expect(response.headers.get('content-type')).toBe('text/html; charset=UTF-8')
43
+ const page = await response.text()
44
+ expect(page).toMatch(/^<html><head><style id="acryl-css">\.css-\d+\{color:teal\}<\/style><\/head><body><h1 class="css-\d+">Acryl!<\/h1><\/body><\/html>$/u)
45
+ })
46
+
47
+ it('streams a page: the shell first, then the suspended part', async () => {
48
+ const Late = async () => { await new Promise(resolve => setTimeout(resolve, 10)); return <p>loaded</p> }
49
+ const page = <Suspense fallback={<p>loading</p>}><Late /></Suspense>
50
+ const app = new Acryl().get('/stream', c => c.body(renderToReadableStream(page), 200, {
51
+ 'content-type': 'text/html; charset=UTF-8',
52
+ }))
53
+ const ctx = await serve(app)
54
+
55
+ const response = await ctx.router.request('/pages/stream')
56
+ const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader()
57
+ const chunks: string[] = []
58
+ for (let next = await reader.read(); !next.done; next = await reader.read()) chunks.push(next.value)
59
+ expect(chunks.length).toBeGreaterThan(1)
60
+ expect(chunks[0]).toContain('loading')
61
+ expect(chunks.join('')).toContain('loaded')
62
+ })
63
+ })
@@ -2,15 +2,15 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router",
5
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
5
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
9
9
  "tests",
10
10
  "LICENSE"
11
11
  ],
12
- "rewrittenSpecifiers": 4,
13
- "digest": "sha256:e732a94e1cf98c4390780156359603dc5d3d33597baf21457082fb33c120cb90",
12
+ "rewrittenSpecifiers": 6,
13
+ "digest": "sha256:141895acbc03bfea20b4142356a0614361f83a0beca0a989ae141b5f877f399d",
14
14
  "testsNotRun": {
15
15
  "tests/loader.spec.ts": "loads the built lib/ entry through the real Loader; the library ships source and has no build"
16
16
  },
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-router-http",
5
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
5
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -2,7 +2,7 @@
2
2
  "source": {
3
3
  "repo": "acryldev/acryl",
4
4
  "path": "plugins/acryl-state",
5
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
5
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
6
6
  },
7
7
  "vendored": [
8
8
  "src",
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
8
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -5,7 +5,7 @@
5
5
  "apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
6
6
  "apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
7
7
  },
8
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
8
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
9
9
  },
10
10
  "rewrittenSpecifiers": 0,
11
11
  "digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
@@ -6,7 +6,7 @@
6
6
  "provides": [],
7
7
  "dependencies": {
8
8
  "@deepseek-ai/cordis": "4.0.4",
9
- "@webboxes/app-persistence": "0.1.0"
9
+ "@webboxes/app-persistence": "0.1.1"
10
10
  },
11
11
  "mount": "add a script \"save\": \"node src/plugins/save/cli.ts\" to package.json; mount the plugin to give the agent a save_app tool"
12
12
  }
@@ -5,7 +5,7 @@
5
5
  "examples/acryl-todo/src/index.ts": "src/todo.ts",
6
6
  "examples/acryl-todo/src/page.ts": "src/page.ts"
7
7
  },
8
- "commit": "0174cb01625bfd32d0162ad56179c582f353321e"
8
+ "commit": "60491f81b7346d1f3867fa01ee4e2caffc7baf47"
9
9
  },
10
10
  "rewrittenSpecifiers": 4,
11
11
  "digest": "sha256:9093704776563001f7f472f94cc5c2fdd79206342c1cb59c11fce62aa74df3ed"