@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.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 (244) hide show
  1. package/CHANGELOG.md +37 -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 +136 -147
  16. package/dist/compiled/core.d.ts.map +1 -1
  17. package/dist/compiled/core.js +2669 -2985
  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 +187 -221
  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 +5 -2
  44. package/dist/dom/events.d.ts.map +1 -1
  45. package/dist/dom/events.js +53 -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 +61 -58
  100. package/dist/hydration-bridge.d.ts.map +1 -1
  101. package/dist/hydration-bridge.js +67 -66
  102. package/dist/hydration-bridge.js.map +1 -1
  103. package/dist/hydration.d.ts +31 -28
  104. package/dist/hydration.d.ts.map +1 -1
  105. package/dist/hydration.js +376 -462
  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 +13 -14
  118. package/dist/keyed-list.d.ts.map +1 -1
  119. package/dist/keyed-list.js +132 -160
  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 +27 -22
  150. package/dist/renderable.d.ts.map +1 -1
  151. package/dist/renderable.js +141 -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 +592 -706
  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 +9 -6
  178. package/dist/source-mask.d.ts.map +1 -1
  179. package/dist/source-mask.js +45 -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 +18 -15
  186. package/dist/state-slot.d.ts.map +1 -1
  187. package/dist/state-slot.js +43 -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 +4026 -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 +365 -0
  203. package/src/dom/attributes.ts +55 -0
  204. package/src/dom/events.ts +93 -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 +183 -0
  220. package/src/hydration.ts +556 -0
  221. package/src/index.ts +92 -0
  222. package/src/indexed-list.ts +20 -0
  223. package/src/jsx-runtime.ts +55 -0
  224. package/src/keyed-list.ts +233 -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 +283 -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 +1143 -0
  238. package/src/shared/promise.ts +28 -0
  239. package/src/source-mask.ts +83 -0
  240. package/src/spread.ts +131 -0
  241. package/src/state-slot.ts +76 -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,556 @@
1
+ import {
2
+ HydrationMismatch,
3
+ installHydrationOperations,
4
+ type HydrationRange,
5
+ } from './hydration-bridge.ts'
6
+
7
+ const HYDRATION_PREFIX = 'vidact:v1'
8
+
9
+ interface HydrationState {
10
+ readonly host: ParentNode
11
+ readonly root: HydrationRange
12
+ readonly components: readonly HydrationRange[]
13
+ readonly claimedElements: WeakSet<Element>
14
+ readonly elementComponents: WeakMap<Element, Comment>
15
+ readonly elements: readonly Element[]
16
+ readonly cursors: WeakMap<Node, Node | null>
17
+ readonly slots: WeakMap<Node, Comment[]>
18
+ componentIndex: number
19
+ claimedElementCount: number
20
+ pendingStructuralParents: number
21
+ activeComponentStart: Comment | undefined
22
+ }
23
+
24
+ let activeHydration: HydrationState | undefined
25
+ let activeInsertionPoint: readonly [parent: Node, before: Node] | undefined
26
+ const hydrationFragments = new WeakMap<DocumentFragment, readonly unknown[]>()
27
+
28
+ export function beginHydration(host: ParentNode): () => void {
29
+ if (activeHydration !== undefined) {
30
+ throw new Error('a Vidact hydration is already active')
31
+ }
32
+ const ranges = scanRanges(host)
33
+ const root = ranges.roots[0]
34
+ if (root === undefined || ranges.roots.length !== 1) {
35
+ throw mismatch('expected exactly one vidact:v1 root marker range')
36
+ }
37
+ activeHydration = {
38
+ host,
39
+ root,
40
+ components: ranges.components,
41
+ claimedElements: new WeakSet(),
42
+ elementComponents: collectElementComponents(host),
43
+ elements: collectPostorderElements(root),
44
+ cursors: new WeakMap([[host as Node, root[0].nextSibling]]),
45
+ slots: new WeakMap(),
46
+ componentIndex: 0,
47
+ claimedElementCount: 0,
48
+ pendingStructuralParents: 0,
49
+ activeComponentStart: undefined,
50
+ }
51
+ return () => {
52
+ activeHydration = undefined
53
+ }
54
+ }
55
+
56
+ export function finishHydration(): void {
57
+ const state = requireHydration()
58
+ if (state.componentIndex !== state.components.length) {
59
+ throw mismatch(
60
+ `claimed ${state.componentIndex} of ${state.components.length} server component ranges`,
61
+ )
62
+ }
63
+ if (state.claimedElementCount !== state.elements.length) {
64
+ throw mismatch(
65
+ `claimed ${state.claimedElementCount} of ${state.elements.length} server elements`,
66
+ )
67
+ }
68
+ const rootCursor = cursor(state, state.host as Node)
69
+ if (rootCursor !== state.root[1]) {
70
+ throw mismatch(
71
+ `compiled root did not consume the complete server root range; stopped at ${JSON.stringify(markerValue(rootCursor as Node) ?? rootCursor?.nodeName ?? null)}`,
72
+ )
73
+ }
74
+ }
75
+
76
+ export function hydrationRootMarkers(): HydrationRange {
77
+ return requireHydration().root
78
+ }
79
+
80
+ export function isHydrating(): boolean {
81
+ return activeHydration !== undefined
82
+ }
83
+
84
+ export function createHydrationFragment(children: readonly unknown[]): DocumentFragment {
85
+ const fragment = document.createDocumentFragment()
86
+ hydrationFragments.set(fragment, children)
87
+ return fragment
88
+ }
89
+
90
+ export function hydrationFragmentChildren(
91
+ fragment: DocumentFragment,
92
+ ): readonly unknown[] | undefined {
93
+ return hydrationFragments.get(fragment)
94
+ }
95
+
96
+ export function isHydrationMismatch(error: unknown): error is HydrationMismatch {
97
+ return error instanceof HydrationMismatch
98
+ }
99
+
100
+ export function claimHydrationElement(
101
+ localName: string,
102
+ namespace: string | null,
103
+ matchesExpected?: (element: Element) => boolean,
104
+ ): Element | undefined {
105
+ const state = activeHydration
106
+ if (state === undefined) return undefined
107
+ const candidates = state.elements.filter(
108
+ (candidate) =>
109
+ !state.claimedElements.has(candidate) &&
110
+ (state.activeComponentStart === undefined ||
111
+ state.elementComponents.get(candidate) === state.activeComponentStart) &&
112
+ candidate.localName === localName &&
113
+ candidate.namespaceURI === namespace,
114
+ )
115
+ const expectedMatches =
116
+ matchesExpected === undefined ? candidates : candidates.filter(matchesExpected)
117
+ const matches = expectedMatches.length === 0 ? candidates : expectedMatches
118
+ const element =
119
+ state.pendingStructuralParents > 0
120
+ ? (matches.find(containsArrayMarker) ?? matches[0])
121
+ : matches[0]
122
+ if (element === undefined) {
123
+ throw mismatch(
124
+ state.pendingStructuralParents > 0
125
+ ? `missing server structural parent <${localName}>`
126
+ : `missing server element <${localName}>`,
127
+ )
128
+ }
129
+ if (state.pendingStructuralParents > 0) state.pendingStructuralParents = 0
130
+ state.claimedElements.add(element)
131
+ state.claimedElementCount += 1
132
+ state.cursors.set(element, element.firstChild)
133
+ return element
134
+ }
135
+
136
+ export function noteHydrationStructuralParent(): void {
137
+ if (activeHydration !== undefined) activeHydration.pendingStructuralParents += 1
138
+ }
139
+
140
+ export function claimHydrationComponentRange(): HydrationRange | undefined {
141
+ const state = activeHydration
142
+ if (state === undefined) return undefined
143
+ const range = state.components[state.componentIndex]
144
+ if (range === undefined) throw mismatch('missing server component marker range')
145
+ state.componentIndex += 1
146
+ const parent = range[0].parentNode
147
+ if (parent === null || range[1].parentNode !== parent) {
148
+ throw mismatch('server component marker range is detached')
149
+ }
150
+ return range
151
+ }
152
+
153
+ export function withHydrationComponentRange<Result>(
154
+ range: HydrationRange,
155
+ operation: () => Result,
156
+ ): Result {
157
+ const state = activeHydration
158
+ if (state === undefined) return operation()
159
+ const previous = state.activeComponentStart
160
+ state.activeComponentStart = range[0]
161
+ try {
162
+ return operation()
163
+ } finally {
164
+ state.activeComponentStart = previous
165
+ }
166
+ }
167
+
168
+ export function withHydrationCursor<Result>(
169
+ parent: Node,
170
+ value: Node | null,
171
+ operation: () => Result,
172
+ ): Result {
173
+ const state = activeHydration
174
+ if (state === undefined) return operation()
175
+ const hadCursor = state.cursors.has(parent)
176
+ const previous = state.cursors.get(parent) ?? null
177
+ state.cursors.set(parent, value)
178
+ try {
179
+ return operation()
180
+ } finally {
181
+ if (hadCursor) state.cursors.set(parent, previous)
182
+ else state.cursors.delete(parent)
183
+ }
184
+ }
185
+
186
+ export function claimHydrationNode(parent: Node, node: Node): boolean {
187
+ const state = activeHydration
188
+ if (state === undefined) return false
189
+ enterHydrationSlot(state, parent)
190
+ const next = cursor(state, parent)
191
+ if (isMarker(next, `${HYDRATION_PREFIX}:s`)) {
192
+ const end = findClosingSibling(next, `${HYDRATION_PREFIX}:s`)
193
+ if (next.nextSibling !== node || node.nextSibling !== end) {
194
+ throw mismatch(`server node range does not contain exactly the expected ${node.nodeName}`)
195
+ }
196
+ setHydrationCursor(state, parent, end.nextSibling)
197
+ return true
198
+ }
199
+ if (next !== node) {
200
+ throw mismatch(`expected existing ${node.nodeName} at the current hydration position`)
201
+ }
202
+ setHydrationCursor(state, parent, node.nextSibling)
203
+ return true
204
+ }
205
+
206
+ export function claimHydrationComponentMount(parent: Node, start: Comment, end: Comment): boolean {
207
+ const state = activeHydration
208
+ if (state === undefined) return false
209
+ enterHydrationSlot(state, parent)
210
+ const current = cursor(state, parent)
211
+ if (
212
+ (current !== start && current !== end) ||
213
+ start.parentNode !== parent ||
214
+ end.parentNode !== parent
215
+ ) {
216
+ throw mismatch('server component range does not match the compiled child position')
217
+ }
218
+ setHydrationCursor(state, parent, end.nextSibling)
219
+ return true
220
+ }
221
+
222
+ export function claimHydrationText(parent: Node, expected: string): Text | null | undefined {
223
+ return claimHydrationTextRange(parent, expected)?.[2]
224
+ }
225
+
226
+ export function claimHydrationTextRange(
227
+ parent: Node,
228
+ expected: string,
229
+ ): readonly [start: Comment, end: Comment, text: Text | null] | undefined {
230
+ const state = activeHydration
231
+ if (state === undefined) return undefined
232
+ enterHydrationSlot(state, parent)
233
+ const start = cursor(state, parent)
234
+ if (!isMarker(start, `${HYDRATION_PREFIX}:t`)) {
235
+ throw mismatch('expected a vidact:v1 text marker')
236
+ }
237
+ const end = findClosingSibling(start, `${HYDRATION_PREFIX}:t`)
238
+ const nodes: Node[] = []
239
+ for (let node = start.nextSibling; node !== null && node !== end; node = node.nextSibling) {
240
+ nodes.push(node)
241
+ }
242
+ if (nodes.length > 1 || (nodes.length === 1 && !(nodes[0] instanceof Text))) {
243
+ throw mismatch('server text marker must contain at most one text node')
244
+ }
245
+ const text = (nodes[0] as Text | undefined) ?? null
246
+ if ((text?.data ?? '') !== expected) {
247
+ throw mismatch(
248
+ `server text ${JSON.stringify(text?.data ?? '')} does not match ${JSON.stringify(expected)}`,
249
+ )
250
+ }
251
+ setHydrationCursor(state, parent, end.nextSibling)
252
+ return [start, end, text]
253
+ }
254
+
255
+ export function claimHydrationArrayRange(parent: Node): HydrationRange | undefined {
256
+ const state = activeHydration
257
+ if (state === undefined) return undefined
258
+ enterHydrationSlot(state, parent)
259
+ const start = cursor(state, parent)
260
+ if (!isMarker(start, `${HYDRATION_PREFIX}:a`)) {
261
+ throw mismatch(
262
+ `expected a vidact:v1 array marker in ${parent.nodeName}; found ${JSON.stringify(markerValue(start as Node) ?? start?.nodeName ?? null)}`,
263
+ )
264
+ }
265
+ const end = findClosingSibling(start, `${HYDRATION_PREFIX}:a`)
266
+ state.cursors.set(parent, start.nextSibling)
267
+ return [start, end]
268
+ }
269
+
270
+ export function finishHydrationArrayRange(parent: Node, end: Comment): void {
271
+ const state = requireHydration()
272
+ if (cursor(state, parent) !== end) {
273
+ throw mismatch('compiled collection did not consume its complete server array range')
274
+ }
275
+ setHydrationCursor(state, parent, end.nextSibling)
276
+ }
277
+
278
+ export function claimHydrationSlotRange(parent: Node): HydrationRange | undefined {
279
+ const state = activeHydration
280
+ if (state === undefined) return undefined
281
+ const start = cursor(state, parent)
282
+ if (!isMarker(start, `${HYDRATION_PREFIX}:b`)) {
283
+ throw mismatch(
284
+ `expected a vidact:v1 child-slot marker in ${parent.nodeName}; found ${JSON.stringify(markerValue(start as Node) ?? start?.nodeName ?? null)} after ${JSON.stringify(markerValue(start?.previousSibling as Node) ?? start?.previousSibling?.nodeName ?? null)}; insertion=${JSON.stringify(markerValue(activeInsertionPoint?.[1] as Node) ?? activeInsertionPoint?.[1]?.nodeName ?? null)}`,
285
+ )
286
+ }
287
+ const end = findClosingSibling(start, `${HYDRATION_PREFIX}:b`)
288
+ const slots = state.slots.get(parent) ?? []
289
+ slots.push(end)
290
+ state.slots.set(parent, slots)
291
+ state.cursors.set(parent, start.nextSibling)
292
+ return [start, end]
293
+ }
294
+
295
+ export function borrowHydrationSlotRange(
296
+ parent: Node,
297
+ current: boolean,
298
+ ): HydrationRange | undefined {
299
+ const state = activeHydration
300
+ if (state === undefined) return undefined
301
+ const start = (
302
+ current ? cursor(state, parent) : cursor(state, parent)?.previousSibling
303
+ ) as Node | null
304
+ if (!isMarker(start, `${HYDRATION_PREFIX}:b`)) return undefined
305
+ if (
306
+ !current &&
307
+ activeInsertionPoint?.[0] === parent &&
308
+ isMarker(activeInsertionPoint[1], `/${HYDRATION_PREFIX}:b`)
309
+ ) {
310
+ return [start, activeInsertionPoint[1]]
311
+ }
312
+ return [start, findClosingSibling(start, `${HYDRATION_PREFIX}:b`)]
313
+ }
314
+
315
+ export function claimHydrationSuspenseFallback(parent: Node): Comment | undefined {
316
+ const state = activeHydration
317
+ if (state === undefined) return undefined
318
+ const marker = cursor(state, parent)
319
+ if (!isMarker(marker, `${HYDRATION_PREFIX}:p`)) return undefined
320
+ setHydrationCursor(state, parent, marker.nextSibling)
321
+ return marker
322
+ }
323
+
324
+ export function withoutHydration<Result>(operation: () => Result): Result {
325
+ const previous = activeHydration
326
+ activeHydration = undefined
327
+ try {
328
+ return operation()
329
+ } finally {
330
+ activeHydration = previous
331
+ }
332
+ }
333
+
334
+ export function withHydrationInsertion<Result>(
335
+ parent: Node,
336
+ before: Node,
337
+ operation: () => Result,
338
+ ): Result {
339
+ if (activeHydration === undefined) return operation()
340
+ const previous = activeInsertionPoint
341
+ activeInsertionPoint = [parent, before]
342
+ try {
343
+ return operation()
344
+ } finally {
345
+ activeInsertionPoint = previous
346
+ }
347
+ }
348
+
349
+ export function hydrationInsertionPoint(): readonly [parent: Node, before: Node] | undefined {
350
+ return activeInsertionPoint
351
+ }
352
+
353
+ export function hydrationCursor(parent: Node): Node | null | undefined {
354
+ const state = activeHydration
355
+ return state === undefined ? undefined : cursor(state, parent)
356
+ }
357
+
358
+ export function hydrationRangeParent(start: Comment, end: Comment): Node | undefined {
359
+ if (activeHydration === undefined) return undefined
360
+ const parent = start.parentNode
361
+ if (parent === null || end.parentNode !== parent) throw mismatch('detached server marker range')
362
+ return parent
363
+ }
364
+
365
+ function requireHydration(): HydrationState {
366
+ if (activeHydration === undefined) throw new Error('no Vidact hydration is active')
367
+ return activeHydration
368
+ }
369
+
370
+ function cursor(state: HydrationState, parent: Node): Node | null {
371
+ if (!state.cursors.has(parent)) state.cursors.set(parent, parent.firstChild)
372
+ return state.cursors.get(parent) ?? null
373
+ }
374
+
375
+ function enterHydrationSlot(state: HydrationState, parent: Node): void {
376
+ const slots = state.slots.get(parent) ?? []
377
+ let start = cursor(state, parent)
378
+ while (isMarker(start, `${HYDRATION_PREFIX}:b`)) {
379
+ slots.push(findClosingSibling(start, `${HYDRATION_PREFIX}:b`))
380
+ start = start.nextSibling
381
+ }
382
+ if (slots.length !== 0) state.slots.set(parent, slots)
383
+ state.cursors.set(parent, start)
384
+ }
385
+
386
+ function setHydrationCursor(state: HydrationState, parent: Node, value: Node | null): void {
387
+ const slots = state.slots.get(parent)
388
+ let next = value
389
+ if (slots !== undefined) {
390
+ for (;;) {
391
+ const end = slots.at(-1)
392
+ if (end !== next) break
393
+ slots.pop()
394
+ next = end.nextSibling
395
+ }
396
+ }
397
+ state.cursors.set(parent, next)
398
+ }
399
+
400
+ function mismatch(message: string): HydrationMismatch {
401
+ return new HydrationMismatch(message)
402
+ }
403
+
404
+ function markerValue(node: Node): string | undefined {
405
+ return node instanceof Comment ? node.data : undefined
406
+ }
407
+
408
+ function isMarker(node: Node | null, value: string): node is Comment {
409
+ return markerValue(node as Node) === value
410
+ }
411
+
412
+ function findClosingSibling(start: Comment, kind: string): Comment {
413
+ let depth = 0
414
+ for (let node = start.nextSibling; node !== null; node = node.nextSibling) {
415
+ if (isMarker(node, kind)) {
416
+ depth += 1
417
+ continue
418
+ }
419
+ if (!isMarker(node, `/${kind}`)) continue
420
+ if (depth === 0) return node
421
+ depth -= 1
422
+ }
423
+ throw mismatch(`missing closing ${kind} marker`)
424
+ }
425
+
426
+ function scanRanges(host: ParentNode): {
427
+ roots: HydrationRange[]
428
+ components: HydrationRange[]
429
+ } {
430
+ const roots: HydrationRange[] = []
431
+ const components: HydrationRange[] = []
432
+ const stacks = new Map<string, Comment[]>()
433
+ const walker = document.createTreeWalker(host, NodeFilter.SHOW_COMMENT)
434
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
435
+ const comment = node as Comment
436
+ const match = comment.data.match(/^\/?vidact:v1:([abchrst])$/)
437
+ if (match === null) continue
438
+ const kind = match[1] as 'a' | 'b' | 'c' | 'h' | 'r' | 's' | 't'
439
+ if (!comment.data.startsWith('/')) {
440
+ const stack = stacks.get(kind) ?? []
441
+ stack.push(comment)
442
+ stacks.set(kind, stack)
443
+ continue
444
+ }
445
+ const start = stacks.get(kind)?.pop()
446
+ if (start === undefined) throw mismatch(`orphan closing vidact:v1:${kind} marker`)
447
+ if (kind === 'r') roots.push([start, comment])
448
+ if (kind === 'c') components.push([start, comment])
449
+ }
450
+ if ([...stacks.values()].some((stack) => stack.length !== 0)) {
451
+ throw mismatch('unclosed vidact:v1 hydration marker')
452
+ }
453
+ components.sort(([left], [right]) => {
454
+ const position = left.compareDocumentPosition(right)
455
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1
456
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1
457
+ return 0
458
+ })
459
+ return { roots, components }
460
+ }
461
+
462
+ function collectPostorderElements(root: HydrationRange): Element[] {
463
+ const elements: Element[] = []
464
+ const visit = (element: Element): void => {
465
+ visitChildren(element)
466
+ elements.push(element)
467
+ }
468
+ const visitChildren = (parent: Node): void => {
469
+ let opaqueDepth = 0
470
+ for (const child of parent.childNodes) {
471
+ if (isMarker(child, `${HYDRATION_PREFIX}:h`)) {
472
+ opaqueDepth += 1
473
+ continue
474
+ }
475
+ if (isMarker(child, `/${HYDRATION_PREFIX}:h`)) {
476
+ opaqueDepth -= 1
477
+ continue
478
+ }
479
+ if (opaqueDepth === 0 && child instanceof Element) visit(child)
480
+ }
481
+ if (opaqueDepth !== 0) throw mismatch('unbalanced vidact:v1 raw HTML marker range')
482
+ }
483
+ let opaqueDepth = 0
484
+ for (let node = root[0].nextSibling; node !== null && node !== root[1]; node = node.nextSibling) {
485
+ if (isMarker(node, `${HYDRATION_PREFIX}:h`)) {
486
+ opaqueDepth += 1
487
+ continue
488
+ }
489
+ if (isMarker(node, `/${HYDRATION_PREFIX}:h`)) {
490
+ opaqueDepth -= 1
491
+ continue
492
+ }
493
+ if (opaqueDepth === 0 && node instanceof Element) visit(node)
494
+ }
495
+ if (opaqueDepth !== 0) throw mismatch('unbalanced vidact:v1 raw HTML marker range')
496
+ return elements
497
+ }
498
+
499
+ function collectElementComponents(host: ParentNode): WeakMap<Element, Comment> {
500
+ const owners = new WeakMap<Element, Comment>()
501
+ const components: Comment[] = []
502
+ const walker = document.createTreeWalker(host, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT)
503
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
504
+ if (isMarker(node, `${HYDRATION_PREFIX}:c`)) {
505
+ components.push(node)
506
+ continue
507
+ }
508
+ if (isMarker(node, `/${HYDRATION_PREFIX}:c`)) {
509
+ components.pop()
510
+ continue
511
+ }
512
+ if (node instanceof Element) {
513
+ const owner = components.at(-1)
514
+ if (owner !== undefined) owners.set(node, owner)
515
+ }
516
+ }
517
+ return owners
518
+ }
519
+
520
+ function containsArrayMarker(element: Element): boolean {
521
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT)
522
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
523
+ if (isMarker(node, `${HYDRATION_PREFIX}:a`)) return true
524
+ }
525
+ return false
526
+ }
527
+
528
+ export function installHydration(): void {
529
+ installHydrationOperations({
530
+ begin: beginHydration,
531
+ finish: finishHydration,
532
+ rootMarkers: hydrationRootMarkers,
533
+ active: isHydrating,
534
+ createFragment: createHydrationFragment,
535
+ fragmentChildren: hydrationFragmentChildren,
536
+ claimElement: claimHydrationElement,
537
+ noteStructuralParent: noteHydrationStructuralParent,
538
+ claimComponentRange: claimHydrationComponentRange,
539
+ withComponentRange: withHydrationComponentRange,
540
+ withCursor: withHydrationCursor,
541
+ claimNode: claimHydrationNode,
542
+ claimComponentMount: claimHydrationComponentMount,
543
+ claimText: claimHydrationText,
544
+ claimTextRange: claimHydrationTextRange,
545
+ claimArrayRange: claimHydrationArrayRange,
546
+ finishArrayRange: finishHydrationArrayRange,
547
+ claimSlotRange: claimHydrationSlotRange,
548
+ borrowSlotRange: borrowHydrationSlotRange,
549
+ claimSuspenseFallback: claimHydrationSuspenseFallback,
550
+ withoutHydration,
551
+ withInsertion: withHydrationInsertion,
552
+ insertionPoint: hydrationInsertionPoint,
553
+ cursor: hydrationCursor,
554
+ rangeParent: hydrationRangeParent,
555
+ })
556
+ }
package/src/index.ts ADDED
@@ -0,0 +1,92 @@
1
+ // oxlint-disable-next-line typescript/triple-slash-reference -- Include the build define in consuming TypeScript programs.
2
+ /// <reference path="./env.d.ts" />
3
+
4
+ export {
5
+ binding,
6
+ choose,
7
+ compiledEvent,
8
+ compiledEffect,
9
+ compiledImperativeHandle,
10
+ compiledInsertionEffect,
11
+ compiledLayoutEffect,
12
+ compiledRoot,
13
+ createCompiledContext,
14
+ createCompiledEffectEvent,
15
+ createCompiledExternalStore,
16
+ createCompiledId,
17
+ createCompiledMemo,
18
+ createCompiledProp,
19
+ createCompiledReducer,
20
+ createCompiledRestProp,
21
+ createCompiledScope,
22
+ createCompiledState,
23
+ createContext,
24
+ createNarrowCompiledScope,
25
+ createPortal,
26
+ deferred,
27
+ dispatch,
28
+ errorBoundary,
29
+ indexed,
30
+ keyed,
31
+ mountCompiled,
32
+ mountCompiledRef,
33
+ objectRest,
34
+ runWithCompiledContext,
35
+ use,
36
+ useCallback,
37
+ useContext,
38
+ useEffect,
39
+ useEffectEvent,
40
+ useId,
41
+ useImperativeHandle,
42
+ useInsertionEffect,
43
+ useLayoutEffect,
44
+ useMemo,
45
+ useSyncExternalStore,
46
+ when,
47
+ type ChoiceMode,
48
+ type MountCompiledOptions,
49
+ } from './compiled/core.ts'
50
+ export type {
51
+ CompiledBinding,
52
+ CompiledComponentResult,
53
+ CompiledContext,
54
+ CompiledErrorHandler,
55
+ CompiledRenderValue,
56
+ CompiledScope,
57
+ OwnedBlock,
58
+ StructuralBinding,
59
+ } from './compiled/types.ts'
60
+ export { compiledComponentSpread } from './component-spread.ts'
61
+ export {
62
+ Fragment,
63
+ h,
64
+ type DirectChild,
65
+ type DirectComponent,
66
+ type DirectProps,
67
+ } from './direct-dom.ts'
68
+ export { nestedProp } from './nested-prop.ts'
69
+ export { assertRuntimeProtocol, VIDACT_RUNTIME_PROTOCOL } from './protocol.ts'
70
+ export { createRef, useRef, type MutableRef } from './ref.ts'
71
+ export {
72
+ cloneRenderable,
73
+ cloneRenderableComponent,
74
+ createReactElement,
75
+ createReactElement as createElement,
76
+ createRenderable,
77
+ dynamicIntrinsicComponent,
78
+ forwardedRef,
79
+ isRenderable,
80
+ keyedFragmentComponent,
81
+ renderableChildren,
82
+ renderableMarker,
83
+ renderableProps,
84
+ renderableRef,
85
+ renderableToArray,
86
+ type CompiledRenderable,
87
+ type RenderablePropsInput,
88
+ } from './renderable.ts'
89
+ export { createRoot, mountHotRoot, type CompiledRoot, type HotContext } from './root.ts'
90
+ export { combineSources, intersectsSources, source, type SourceMask } from './source-mask.ts'
91
+ export { compiledSpread } from './spread.ts'
92
+ export type { StateUpdate } from './state-slot.ts'
@@ -0,0 +1,20 @@
1
+ import { createKeyedList, type KeyedList, type KeyedRenderResult } from './keyed-list.ts'
2
+
3
+ export interface IndexedListOptions<T> {
4
+ readonly render: (value: T, index: number) => KeyedRenderResult<T>
5
+ }
6
+
7
+ export type IndexedList<T> = KeyedList<T>
8
+
9
+ /**
10
+ * Reconciles records by their current position. Existing owners are retained at
11
+ * an index and receive the new value at that index, matching React's unkeyed
12
+ * collection identity semantics.
13
+ */
14
+ export function createIndexedList<T>(
15
+ parent: Node,
16
+ options: IndexedListOptions<T>,
17
+ before: Node | null = null,
18
+ ): IndexedList<T> {
19
+ return createKeyedList(parent, { key: (_value, index) => index, render: options.render }, before)
20
+ }