@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.11

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 (244) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +1 -1
  3. package/dist/actions.d.ts +32 -30
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +251 -266
  6. package/dist/actions.js.map +1 -1
  7. package/dist/async.d.ts +20 -17
  8. package/dist/async.d.ts.map +1 -1
  9. package/dist/async.js +18 -17
  10. package/dist/async.js.map +1 -1
  11. package/dist/compiled/async-resource.d.ts +9 -6
  12. package/dist/compiled/async-resource.d.ts.map +1 -1
  13. package/dist/compiled/async-resource.js +69 -77
  14. package/dist/compiled/async-resource.js.map +1 -1
  15. package/dist/compiled/core.d.ts +148 -147
  16. package/dist/compiled/core.d.ts.map +1 -1
  17. package/dist/compiled/core.js +3064 -2987
  18. package/dist/compiled/core.js.map +1 -1
  19. package/dist/compiled/types.d.ts +44 -56
  20. package/dist/compiled/types.d.ts.map +1 -1
  21. package/dist/compiled/types.js +10 -6
  22. package/dist/compiled/types.js.map +1 -1
  23. package/dist/compiled/validation.d.ts +4 -1
  24. package/dist/compiled/validation.d.ts.map +1 -1
  25. package/dist/compiled/validation.js +17 -20
  26. package/dist/compiled/validation.js.map +1 -1
  27. package/dist/component-spread.d.ts +5 -3
  28. package/dist/component-spread.d.ts.map +1 -1
  29. package/dist/component-spread.js +49 -67
  30. package/dist/component-spread.js.map +1 -1
  31. package/dist/concurrent.d.ts +20 -18
  32. package/dist/concurrent.d.ts.map +1 -1
  33. package/dist/concurrent.js +191 -210
  34. package/dist/concurrent.js.map +1 -1
  35. package/dist/direct-dom.d.ts +17 -12
  36. package/dist/direct-dom.d.ts.map +1 -1
  37. package/dist/direct-dom.js +279 -222
  38. package/dist/direct-dom.js.map +1 -1
  39. package/dist/dom/attributes.d.ts +9 -6
  40. package/dist/dom/attributes.d.ts.map +1 -1
  41. package/dist/dom/attributes.js +65 -47
  42. package/dist/dom/attributes.js.map +1 -1
  43. package/dist/dom/events.d.ts +8 -2
  44. package/dist/dom/events.d.ts.map +1 -1
  45. package/dist/dom/events.js +118 -65
  46. package/dist/dom/events.js.map +1 -1
  47. package/dist/dom/forms.d.ts +9 -6
  48. package/dist/dom/forms.d.ts.map +1 -1
  49. package/dist/dom/forms.js +152 -198
  50. package/dist/dom/forms.js.map +1 -1
  51. package/dist/dom/intrinsic.d.ts +17 -15
  52. package/dist/dom/intrinsic.d.ts.map +1 -1
  53. package/dist/dom/intrinsic.js +48 -50
  54. package/dist/dom/intrinsic.js.map +1 -1
  55. package/dist/dom/namespace.d.ts +4 -1
  56. package/dist/dom/namespace.d.ts.map +1 -1
  57. package/dist/dom/namespace.js +25 -28
  58. package/dist/dom/namespace.js.map +1 -1
  59. package/dist/dom/namespaces.d.ts +11 -8
  60. package/dist/dom/namespaces.d.ts.map +1 -1
  61. package/dist/dom/namespaces.js +14 -12
  62. package/dist/dom/namespaces.js.map +1 -1
  63. package/dist/dom/properties.d.ts +18 -16
  64. package/dist/dom/properties.d.ts.map +1 -1
  65. package/dist/dom/properties.js +273 -296
  66. package/dist/dom/properties.js.map +1 -1
  67. package/dist/dom/style-properties.d.ts +4 -1
  68. package/dist/dom/style-properties.d.ts.map +1 -1
  69. package/dist/dom/style-properties.js +49 -45
  70. package/dist/dom/style-properties.js.map +1 -1
  71. package/dist/dom/styles.d.ts +5 -2
  72. package/dist/dom/styles.d.ts.map +1 -1
  73. package/dist/dom/styles.js +47 -67
  74. package/dist/dom/styles.js.map +1 -1
  75. package/dist/dom-forms.d.ts +4 -1
  76. package/dist/dom-forms.d.ts.map +1 -1
  77. package/dist/dom-forms.js +16 -13
  78. package/dist/dom-forms.js.map +1 -1
  79. package/dist/dom-styles.d.ts +4 -1
  80. package/dist/dom-styles.d.ts.map +1 -1
  81. package/dist/dom-styles.js +10 -7
  82. package/dist/dom-styles.js.map +1 -1
  83. package/dist/framework-hydrate.d.ts +20 -17
  84. package/dist/framework-hydrate.d.ts.map +1 -1
  85. package/dist/framework-hydrate.js +128 -146
  86. package/dist/framework-hydrate.js.map +1 -1
  87. package/dist/framework-protocol.d.ts +27 -25
  88. package/dist/framework-protocol.d.ts.map +1 -1
  89. package/dist/framework-protocol.js +173 -211
  90. package/dist/framework-protocol.js.map +1 -1
  91. package/dist/framework.d.ts +34 -32
  92. package/dist/framework.d.ts.map +1 -1
  93. package/dist/framework.js +74 -93
  94. package/dist/framework.js.map +1 -1
  95. package/dist/hydrate.d.ts +11 -8
  96. package/dist/hydrate.d.ts.map +1 -1
  97. package/dist/hydrate.js +13 -9
  98. package/dist/hydrate.js.map +1 -1
  99. package/dist/hydration-bridge.d.ts +70 -58
  100. package/dist/hydration-bridge.d.ts.map +1 -1
  101. package/dist/hydration-bridge.js +77 -66
  102. package/dist/hydration-bridge.js.map +1 -1
  103. package/dist/hydration.d.ts +54 -28
  104. package/dist/hydration.d.ts.map +1 -1
  105. package/dist/hydration.js +450 -463
  106. package/dist/hydration.js.map +1 -1
  107. package/dist/index.d.ts +13 -13
  108. package/dist/index.js +11 -13
  109. package/dist/indexed-list.d.ts +8 -5
  110. package/dist/indexed-list.d.ts.map +1 -1
  111. package/dist/indexed-list.js +14 -7
  112. package/dist/indexed-list.js.map +1 -1
  113. package/dist/jsx-runtime.d.ts +10 -8
  114. package/dist/jsx-runtime.d.ts.map +1 -1
  115. package/dist/jsx-runtime.js +28 -23
  116. package/dist/jsx-runtime.js.map +1 -1
  117. package/dist/keyed-list.d.ts +17 -14
  118. package/dist/keyed-list.d.ts.map +1 -1
  119. package/dist/keyed-list.js +244 -161
  120. package/dist/keyed-list.js.map +1 -1
  121. package/dist/metadata.d.ts +6 -3
  122. package/dist/metadata.d.ts.map +1 -1
  123. package/dist/metadata.js +131 -164
  124. package/dist/metadata.js.map +1 -1
  125. package/dist/nested-prop.d.ts +4 -2
  126. package/dist/nested-prop.d.ts.map +1 -1
  127. package/dist/nested-prop.js +17 -16
  128. package/dist/nested-prop.js.map +1 -1
  129. package/dist/profiling.d.ts +11 -8
  130. package/dist/profiling.d.ts.map +1 -1
  131. package/dist/profiling.js +17 -17
  132. package/dist/profiling.js.map +1 -1
  133. package/dist/protocol.d.ts +5 -2
  134. package/dist/protocol.d.ts.map +1 -1
  135. package/dist/protocol.js +8 -5
  136. package/dist/protocol.js.map +1 -1
  137. package/dist/raw-html.d.ts +5 -2
  138. package/dist/raw-html.d.ts.map +1 -1
  139. package/dist/raw-html.js +86 -146
  140. package/dist/raw-html.js.map +1 -1
  141. package/dist/ref.d.ts +8 -3
  142. package/dist/ref.d.ts.map +1 -1
  143. package/dist/ref.js +10 -2
  144. package/dist/ref.js.map +1 -1
  145. package/dist/renderable-protocol.d.ts +26 -8
  146. package/dist/renderable-protocol.d.ts.map +1 -1
  147. package/dist/renderable-protocol.js +30 -9
  148. package/dist/renderable-protocol.js.map +1 -1
  149. package/dist/renderable.d.ts +28 -22
  150. package/dist/renderable.d.ts.map +1 -1
  151. package/dist/renderable.js +152 -133
  152. package/dist/renderable.js.map +1 -1
  153. package/dist/retained-ui.d.ts +9 -6
  154. package/dist/retained-ui.d.ts.map +1 -1
  155. package/dist/retained-ui.js +12 -10
  156. package/dist/retained-ui.js.map +1 -1
  157. package/dist/root.d.ts +18 -15
  158. package/dist/root.d.ts.map +1 -1
  159. package/dist/root.js +73 -85
  160. package/dist/root.js.map +1 -1
  161. package/dist/scheduler.d.ts +10 -8
  162. package/dist/scheduler.d.ts.map +1 -1
  163. package/dist/scheduler.js +97 -105
  164. package/dist/scheduler.js.map +1 -1
  165. package/dist/server-framework.d.ts +45 -42
  166. package/dist/server-framework.d.ts.map +1 -1
  167. package/dist/server-framework.js +209 -217
  168. package/dist/server-framework.js.map +1 -1
  169. package/dist/server.d.ts +130 -128
  170. package/dist/server.d.ts.map +1 -1
  171. package/dist/server.js +603 -710
  172. package/dist/server.js.map +1 -1
  173. package/dist/shared/promise.d.ts +6 -3
  174. package/dist/shared/promise.d.ts.map +1 -1
  175. package/dist/shared/promise.js +21 -19
  176. package/dist/shared/promise.js.map +1 -1
  177. package/dist/source-mask.d.ts +10 -6
  178. package/dist/source-mask.d.ts.map +1 -1
  179. package/dist/source-mask.js +60 -63
  180. package/dist/source-mask.js.map +1 -1
  181. package/dist/spread.d.ts +5 -2
  182. package/dist/spread.d.ts.map +1 -1
  183. package/dist/spread.js +79 -99
  184. package/dist/spread.js.map +1 -1
  185. package/dist/state-slot.d.ts +29 -15
  186. package/dist/state-slot.d.ts.map +1 -1
  187. package/dist/state-slot.js +73 -42
  188. package/dist/state-slot.js.map +1 -1
  189. package/dist/testing.d.ts +9 -6
  190. package/dist/testing.d.ts.map +1 -1
  191. package/dist/testing.js +16 -12
  192. package/dist/testing.js.map +1 -1
  193. package/package.json +8 -7
  194. package/src/actions.ts +415 -0
  195. package/src/async.ts +78 -0
  196. package/src/compiled/async-resource.ts +113 -0
  197. package/src/compiled/core.ts +4647 -0
  198. package/src/compiled/types.ts +96 -0
  199. package/src/compiled/validation.ts +23 -0
  200. package/src/component-spread.ts +86 -0
  201. package/src/concurrent.ts +310 -0
  202. package/src/direct-dom.ts +491 -0
  203. package/src/dom/attributes.ts +55 -0
  204. package/src/dom/events.ts +179 -0
  205. package/src/dom/forms.ts +220 -0
  206. package/src/dom/intrinsic.ts +94 -0
  207. package/src/dom/namespace.ts +30 -0
  208. package/src/dom/namespaces.ts +14 -0
  209. package/src/dom/properties.ts +355 -0
  210. package/src/dom/style-properties.ts +46 -0
  211. package/src/dom/styles.ts +86 -0
  212. package/src/dom-forms.ts +23 -0
  213. package/src/dom-styles.ts +11 -0
  214. package/src/env.d.ts +6 -0
  215. package/src/framework-hydrate.ts +224 -0
  216. package/src/framework-protocol.ts +335 -0
  217. package/src/framework.ts +161 -0
  218. package/src/hydrate.ts +38 -0
  219. package/src/hydration-bridge.ts +202 -0
  220. package/src/hydration.ts +644 -0
  221. package/src/index.ts +94 -0
  222. package/src/indexed-list.ts +20 -0
  223. package/src/jsx-runtime.ts +55 -0
  224. package/src/keyed-list.ts +379 -0
  225. package/src/metadata.ts +212 -0
  226. package/src/nested-prop.ts +27 -0
  227. package/src/profiling.ts +43 -0
  228. package/src/protocol.ts +8 -0
  229. package/src/raw-html.ts +206 -0
  230. package/src/ref.ts +13 -0
  231. package/src/renderable-protocol.ts +63 -0
  232. package/src/renderable.ts +313 -0
  233. package/src/retained-ui.ts +19 -0
  234. package/src/root.ts +122 -0
  235. package/src/scheduler.ts +136 -0
  236. package/src/server-framework.ts +397 -0
  237. package/src/server.ts +1160 -0
  238. package/src/shared/promise.ts +28 -0
  239. package/src/source-mask.ts +103 -0
  240. package/src/spread.ts +131 -0
  241. package/src/state-slot.ts +132 -0
  242. package/src/testing.ts +27 -0
  243. package/dist/index.d.ts.map +0 -1
  244. package/dist/index.js.map +0 -1
@@ -0,0 +1,397 @@
1
+ import type { CompiledComponentResult } from './compiled/types.ts'
2
+ import {
3
+ decodeFrameworkValue,
4
+ encodeFrameworkValue,
5
+ type ClientModuleManifest,
6
+ type ClientReference,
7
+ type FrameworkValue,
8
+ } from './framework-protocol.ts'
9
+ import {
10
+ createServerHydrationBoundary,
11
+ createServerFrameworkRenderContext,
12
+ renderToString,
13
+ withServerFrameworkRenderContext,
14
+ type ServerChild,
15
+ type ServerFrameworkRenderContext,
16
+ type ServerNode,
17
+ type ServerRenderOptions,
18
+ } from './server.ts'
19
+ import { abortReason, withAbort } from './shared/promise.ts'
20
+
21
+ export interface FrameworkRenderOptions extends ServerRenderOptions {
22
+ readonly signal?: AbortSignal
23
+ readonly progressiveChunkSize?: number
24
+ readonly onError?: (error: unknown) => void
25
+ }
26
+
27
+ export interface PipeableRenderOptions extends FrameworkRenderOptions {
28
+ readonly onAllReady?: () => void
29
+ readonly onShellError?: (error: unknown) => void
30
+ readonly onShellReady?: () => void
31
+ }
32
+
33
+ export interface PipeDestination {
34
+ readonly write: (chunk: Uint8Array) => boolean
35
+ readonly end: () => void
36
+ readonly destroy?: (error: unknown) => void
37
+ readonly once?: (event: 'drain', listener: () => void) => void
38
+ readonly off?: (event: 'drain', listener: () => void) => void
39
+ }
40
+
41
+ export interface PipeableStream {
42
+ readonly abort: (reason?: unknown) => void
43
+ readonly pipe: (destination: PipeDestination) => void
44
+ }
45
+
46
+ export type VidactReadableStream = ReadableStream<Uint8Array> & {
47
+ readonly allReady: Promise<void>
48
+ }
49
+
50
+ export interface PrerenderResult<Prelude> {
51
+ readonly prelude: Prelude
52
+ readonly postponed: string | null
53
+ }
54
+
55
+ export interface ServerComponentPayload {
56
+ readonly kind: 'server-component'
57
+ readonly html: string
58
+ readonly model: FrameworkValue
59
+ }
60
+
61
+ export interface ClientBoundaryOptions {
62
+ readonly clientManifest: ClientModuleManifest
63
+ readonly hostProps?: Readonly<Record<string, unknown>>
64
+ }
65
+
66
+ export function createClientBoundary(
67
+ reference: ClientReference,
68
+ props: FrameworkValue,
69
+ serverContent: ServerChild | CompiledComponentResult,
70
+ options: ClientBoundaryOptions,
71
+ ): ServerNode & CompiledComponentResult {
72
+ const payload = encodeFrameworkValue({ reference, props }, options.clientManifest)
73
+ return createServerHydrationBoundary(serverContent as ServerChild, {
74
+ ...options.hostProps,
75
+ 'data-vidact-client-boundary': true,
76
+ 'data-vidact-client-payload': payload,
77
+ }) as ServerNode & CompiledComponentResult
78
+ }
79
+
80
+ export function renderToReadableStream(
81
+ value: ServerChild | (() => ServerChild),
82
+ options: FrameworkRenderOptions = {},
83
+ ): Promise<VidactReadableStream> {
84
+ const controller = linkedAbortController(options.signal)
85
+ const html = renderFrameworkHtml(value, options, controller.signal)
86
+ return Promise.resolve(readableFromText(html, options.progressiveChunkSize, controller))
87
+ }
88
+
89
+ export function renderToPipeableStream(
90
+ value: ServerChild | (() => ServerChild),
91
+ options: PipeableRenderOptions = {},
92
+ ): PipeableStream {
93
+ const controller = linkedAbortController(options.signal)
94
+ const html = renderFrameworkHtml(value, options, controller.signal, options)
95
+ // A caller may create a stream for lifecycle callbacks and never pipe it. Mark the
96
+ // render rejection as observed while retaining the original promise for pipe().
97
+ void html.catch(() => undefined)
98
+ let piped = false
99
+ return {
100
+ abort(reason) {
101
+ controller.abort(reason)
102
+ },
103
+ pipe(destination) {
104
+ if (piped) throw new Error('a Vidact pipeable stream can only be piped once')
105
+ piped = true
106
+ void pipeText(html, destination, options.progressiveChunkSize, controller.signal).catch(
107
+ (error: unknown) => destination.destroy?.(error),
108
+ )
109
+ },
110
+ }
111
+ }
112
+
113
+ export async function prerender(
114
+ value: ServerChild | (() => ServerChild),
115
+ options: FrameworkRenderOptions = {},
116
+ ): Promise<PrerenderResult<ReadableStream<Uint8Array>>> {
117
+ const controller = linkedAbortController(options.signal)
118
+ const result = await prerenderText(value, options, controller.signal)
119
+ return {
120
+ prelude: readableFromText(
121
+ Promise.resolve(result.prelude),
122
+ options.progressiveChunkSize,
123
+ controller,
124
+ ),
125
+ postponed: result.postponed,
126
+ }
127
+ }
128
+
129
+ export async function prerenderToNodeStream(
130
+ value: ServerChild | (() => ServerChild),
131
+ options: FrameworkRenderOptions = {},
132
+ ): Promise<PrerenderResult<AsyncIterable<Uint8Array>>> {
133
+ const controller = linkedAbortController(options.signal)
134
+ const result = await prerenderText(value, options, controller.signal)
135
+ return {
136
+ prelude: textIterable(result.prelude, options.progressiveChunkSize, controller.signal),
137
+ postponed: result.postponed,
138
+ }
139
+ }
140
+
141
+ export function resume(
142
+ postponed: string,
143
+ options: FrameworkRenderOptions = {},
144
+ ): Promise<VidactReadableStream> {
145
+ const controller = linkedAbortController(options.signal)
146
+ const html = Promise.resolve(readContinuation(postponed))
147
+ return Promise.resolve(readableFromText(html, options.progressiveChunkSize, controller))
148
+ }
149
+
150
+ export function resumeToPipeableStream(
151
+ postponed: string,
152
+ options: PipeableRenderOptions = {},
153
+ ): PipeableStream {
154
+ const controller = linkedAbortController(options.signal)
155
+ const html = Promise.resolve(readContinuation(postponed))
156
+ let piped = false
157
+ queueMicrotask(() => options.onShellReady?.())
158
+ void html.then(
159
+ () => options.onAllReady?.(),
160
+ (error: unknown) => options.onShellError?.(error),
161
+ )
162
+ return {
163
+ abort(reason) {
164
+ controller.abort(reason)
165
+ },
166
+ pipe(destination) {
167
+ if (piped) throw new Error('a Vidact pipeable stream can only be piped once')
168
+ piped = true
169
+ void pipeText(html, destination, options.progressiveChunkSize, controller.signal).catch(
170
+ (error: unknown) => destination.destroy?.(error),
171
+ )
172
+ },
173
+ }
174
+ }
175
+
176
+ export async function renderServerComponentPayload(
177
+ value: ServerChild | (() => ServerChild),
178
+ model: FrameworkValue,
179
+ options: FrameworkRenderOptions & { readonly clientManifest?: ClientModuleManifest } = {},
180
+ ): Promise<string> {
181
+ const controller = linkedAbortController(options.signal)
182
+ const html = await renderFrameworkHtml(value, options, controller.signal)
183
+ return encodeFrameworkValue({ kind: 'server-component', html, model }, options.clientManifest)
184
+ }
185
+
186
+ export function decodeServerComponentPayload(
187
+ payload: string,
188
+ clientManifest?: ClientModuleManifest,
189
+ ): ServerComponentPayload {
190
+ const value = decodeFrameworkValue(payload, clientManifest)
191
+ if (
192
+ !isRecord(value) ||
193
+ value.kind !== 'server-component' ||
194
+ typeof value.html !== 'string' ||
195
+ !Object.hasOwn(value, 'model')
196
+ ) {
197
+ throw new TypeError('invalid Vidact Server Component payload')
198
+ }
199
+ return { kind: 'server-component', html: value.html, model: value.model as FrameworkValue }
200
+ }
201
+
202
+ async function renderFrameworkHtml(
203
+ value: ServerChild | (() => ServerChild),
204
+ options: FrameworkRenderOptions,
205
+ signal: AbortSignal,
206
+ callbacks: PipeableRenderOptions = {},
207
+ ): Promise<string> {
208
+ const context = createServerFrameworkRenderContext(signal)
209
+ let shellReported = false
210
+ for (let pass = 0; pass < 100; pass += 1) {
211
+ throwIfAborted(signal)
212
+ let html: string
213
+ try {
214
+ html = renderFrameworkPass(value, options, context)
215
+ } catch (error) {
216
+ options.onError?.(error)
217
+ if (!shellReported) callbacks.onShellError?.(error)
218
+ throw error
219
+ }
220
+ if (!shellReported) {
221
+ shellReported = true
222
+ callbacks.onShellReady?.()
223
+ }
224
+ if (context.pending.size === 0) {
225
+ callbacks.onAllReady?.()
226
+ return html
227
+ }
228
+ // oxlint-disable-next-line eslint/no-await-in-loop -- Each pass discovers the next resource frontier.
229
+ await waitForResources(context.pending, signal)
230
+ }
231
+ throw new Error('Vidact framework render did not stabilize after 100 resource passes')
232
+ }
233
+
234
+ async function prerenderText(
235
+ value: ServerChild | (() => ServerChild),
236
+ options: FrameworkRenderOptions,
237
+ signal: AbortSignal,
238
+ ): Promise<{ readonly prelude: string; readonly postponed: string | null }> {
239
+ const context = createServerFrameworkRenderContext(signal)
240
+ const shell = renderFrameworkPass(value, options, context)
241
+ if (context.pending.size === 0) return { prelude: shell, postponed: null }
242
+ await waitForResources(context.pending, signal)
243
+ const final = await settleFrameworkContext(value, options, context)
244
+ return {
245
+ prelude: shell,
246
+ postponed: encodeFrameworkValue({ kind: 'continuation', html: final }),
247
+ }
248
+ }
249
+
250
+ async function settleFrameworkContext(
251
+ value: ServerChild | (() => ServerChild),
252
+ options: FrameworkRenderOptions,
253
+ context: ServerFrameworkRenderContext,
254
+ ): Promise<string> {
255
+ for (let pass = 0; pass < 100; pass += 1) {
256
+ throwIfAborted(context.signal)
257
+ const html = renderFrameworkPass(value, options, context)
258
+ if (context.pending.size === 0) return html
259
+ // oxlint-disable-next-line eslint/no-await-in-loop -- Each pass discovers the next resource frontier.
260
+ await waitForResources(context.pending, context.signal)
261
+ }
262
+ throw new Error('Vidact prerender did not stabilize after 100 resource passes')
263
+ }
264
+
265
+ function renderFrameworkPass(
266
+ value: ServerChild | (() => ServerChild),
267
+ options: ServerRenderOptions,
268
+ context: ServerFrameworkRenderContext,
269
+ ): string {
270
+ return withServerFrameworkRenderContext(context, () => renderToString(value, options))
271
+ }
272
+
273
+ function readContinuation(postponed: string): string {
274
+ const value = decodeFrameworkValue(postponed)
275
+ if (!isRecord(value) || value.kind !== 'continuation' || typeof value.html !== 'string') {
276
+ throw new TypeError('invalid Vidact continuation payload')
277
+ }
278
+ return value.html
279
+ }
280
+
281
+ function readableFromText(
282
+ text: Promise<string>,
283
+ requestedChunkSize: number | undefined,
284
+ abortController: AbortController,
285
+ ): VidactReadableStream {
286
+ const chunkSize = normalizeChunkSize(requestedChunkSize)
287
+ const encoder = new TextEncoder()
288
+ let content: Uint8Array | undefined
289
+ let offset = 0
290
+ const allReady = text.then(() => undefined)
291
+ const stream = new ReadableStream<Uint8Array>({
292
+ async pull(controller) {
293
+ try {
294
+ throwIfAborted(abortController.signal)
295
+ content ??= encoder.encode(await text)
296
+ if (offset >= content.length) {
297
+ controller.close()
298
+ return
299
+ }
300
+ const next = content.slice(offset, offset + chunkSize)
301
+ offset += next.length
302
+ controller.enqueue(next)
303
+ } catch (error) {
304
+ controller.error(error)
305
+ }
306
+ },
307
+ cancel(reason) {
308
+ abortController.abort(reason)
309
+ },
310
+ }) as VidactReadableStream
311
+ Object.defineProperty(stream, 'allReady', { value: allReady })
312
+ return stream
313
+ }
314
+
315
+ async function pipeText(
316
+ text: Promise<string>,
317
+ destination: PipeDestination,
318
+ requestedChunkSize: number | undefined,
319
+ signal: AbortSignal,
320
+ ): Promise<void> {
321
+ const content = new TextEncoder().encode(await text)
322
+ const chunkSize = normalizeChunkSize(requestedChunkSize)
323
+ for (let offset = 0; offset < content.length; offset += chunkSize) {
324
+ throwIfAborted(signal)
325
+ if (!destination.write(content.slice(offset, offset + chunkSize))) {
326
+ // oxlint-disable-next-line eslint/no-await-in-loop -- Stream chunks must honor destination backpressure in order.
327
+ await waitForDrain(destination, signal)
328
+ }
329
+ }
330
+ destination.end()
331
+ }
332
+
333
+ async function* textIterable(
334
+ text: string,
335
+ requestedChunkSize: number | undefined,
336
+ signal: AbortSignal,
337
+ ): AsyncIterable<Uint8Array> {
338
+ const content = new TextEncoder().encode(text)
339
+ const chunkSize = normalizeChunkSize(requestedChunkSize)
340
+ for (let offset = 0; offset < content.length; offset += chunkSize) {
341
+ throwIfAborted(signal)
342
+ yield content.slice(offset, offset + chunkSize)
343
+ }
344
+ }
345
+
346
+ function waitForResources(
347
+ resources: Set<PromiseLike<unknown>>,
348
+ signal: AbortSignal,
349
+ ): Promise<void> {
350
+ return withAbort(
351
+ Promise.allSettled(resources).then(() => undefined),
352
+ signal,
353
+ )
354
+ }
355
+
356
+ function waitForDrain(destination: PipeDestination, signal: AbortSignal): Promise<void> {
357
+ if (destination.once === undefined) return Promise.resolve()
358
+ return new Promise((resolve, reject) => {
359
+ if (signal.aborted) {
360
+ reject(abortReason(signal))
361
+ return
362
+ }
363
+ const onDrain = (): void => {
364
+ signal.removeEventListener('abort', onAbort)
365
+ resolve()
366
+ }
367
+ const onAbort = (): void => {
368
+ destination.off?.('drain', onDrain)
369
+ reject(abortReason(signal))
370
+ }
371
+ destination.once?.('drain', onDrain)
372
+ signal.addEventListener('abort', onAbort, { once: true })
373
+ })
374
+ }
375
+
376
+ function linkedAbortController(signal?: AbortSignal): AbortController {
377
+ const controller = new AbortController()
378
+ if (signal?.aborted) controller.abort(signal.reason)
379
+ else signal?.addEventListener('abort', () => controller.abort(signal.reason), { once: true })
380
+ return controller
381
+ }
382
+
383
+ function normalizeChunkSize(value: number | undefined): number {
384
+ if (value === undefined) return 16_384
385
+ if (!Number.isSafeInteger(value) || value <= 0) {
386
+ throw new RangeError('progressiveChunkSize must be a positive safe integer')
387
+ }
388
+ return value
389
+ }
390
+
391
+ function throwIfAborted(signal: AbortSignal): void {
392
+ if (signal.aborted) throw abortReason(signal)
393
+ }
394
+
395
+ function isRecord(value: unknown): value is Record<string, FrameworkValue> {
396
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
397
+ }