@gyral/core 0.3.0 → 0.3.2

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 (386) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +19 -0
  3. package/README.md +36 -8
  4. package/dist/call.d.ts +32 -0
  5. package/dist/call.js +63 -0
  6. package/dist/call.js.map +1 -0
  7. package/dist/children.d.ts +25 -3
  8. package/dist/children.js +18 -1
  9. package/dist/children.js.map +1 -1
  10. package/dist/cli/gyral-types.d.ts +2 -0
  11. package/dist/cli/gyral-types.js +23 -0
  12. package/dist/cli/gyral-types.js.map +1 -0
  13. package/dist/command.d.ts +54 -12
  14. package/dist/command.js +25 -11
  15. package/dist/command.js.map +1 -1
  16. package/dist/compiler/ast.d.ts +2 -0
  17. package/dist/compiler/ast.js +5 -1
  18. package/dist/compiler/ast.js.map +1 -1
  19. package/dist/compiler/compile.d.ts +5 -2
  20. package/dist/compiler/compile.js +36 -17
  21. package/dist/compiler/compile.js.map +1 -1
  22. package/dist/compiler/component-entries.d.ts +7 -0
  23. package/dist/compiler/component-entries.js +42 -0
  24. package/dist/compiler/component-entries.js.map +1 -0
  25. package/dist/compiler/components.d.ts +4 -0
  26. package/dist/compiler/components.js +190 -0
  27. package/dist/compiler/components.js.map +1 -0
  28. package/dist/compiler/facts.d.ts +31 -0
  29. package/dist/compiler/facts.js +285 -0
  30. package/dist/compiler/facts.js.map +1 -0
  31. package/dist/compiler/features.d.ts +34 -0
  32. package/dist/compiler/features.js +158 -0
  33. package/dist/compiler/features.js.map +1 -0
  34. package/dist/compiler/hooks.d.ts +4 -0
  35. package/dist/compiler/hooks.js +144 -89
  36. package/dist/compiler/hooks.js.map +1 -1
  37. package/dist/compiler/loader-source.d.ts +14 -0
  38. package/dist/compiler/loader-source.js +86 -0
  39. package/dist/compiler/loader-source.js.map +1 -0
  40. package/dist/compiler/locate.d.ts +14 -0
  41. package/dist/compiler/locate.js +90 -0
  42. package/dist/compiler/locate.js.map +1 -0
  43. package/dist/compiler/parse5-check.d.ts +1 -1
  44. package/dist/compiler/parse5-check.js +8 -3
  45. package/dist/compiler/parse5-check.js.map +1 -1
  46. package/dist/compiler/prop-schemas.d.ts +4 -0
  47. package/dist/compiler/prop-schemas.js +108 -0
  48. package/dist/compiler/prop-schemas.js.map +1 -0
  49. package/dist/compiler/scan.d.ts +13 -3
  50. package/dist/compiler/scan.js +21 -13
  51. package/dist/compiler/scan.js.map +1 -1
  52. package/dist/compiler/scope.d.ts +8 -0
  53. package/dist/compiler/scope.js +116 -0
  54. package/dist/compiler/scope.js.map +1 -0
  55. package/dist/compiler/tag-map.d.ts +41 -0
  56. package/dist/compiler/tag-map.js +126 -0
  57. package/dist/compiler/tag-map.js.map +1 -0
  58. package/dist/components-ready.d.ts +7 -0
  59. package/dist/components-ready.js +19 -0
  60. package/dist/components-ready.js.map +1 -0
  61. package/dist/define.d.ts +12 -4
  62. package/dist/define.js +8 -12
  63. package/dist/define.js.map +1 -1
  64. package/dist/devtools-events.d.ts +8 -0
  65. package/dist/devtools-events.js.map +1 -1
  66. package/dist/devtools-off.d.ts +1 -0
  67. package/dist/devtools-off.js +1 -0
  68. package/dist/devtools-off.js.map +1 -1
  69. package/dist/devtools.d.ts +3 -0
  70. package/dist/devtools.js +14 -0
  71. package/dist/devtools.js.map +1 -1
  72. package/dist/drivers-scope.js.map +1 -1
  73. package/dist/element-internals.d.ts +1 -0
  74. package/dist/element-internals.js +11 -0
  75. package/dist/element-internals.js.map +1 -0
  76. package/dist/element-types.d.ts +7 -4
  77. package/dist/element-types.js.map +1 -1
  78. package/dist/element.js +80 -27
  79. package/dist/element.js.map +1 -1
  80. package/dist/error-view.d.ts +10 -0
  81. package/dist/error-view.js +30 -0
  82. package/dist/error-view.js.map +1 -0
  83. package/dist/errors.d.ts +21 -0
  84. package/dist/errors.js +47 -0
  85. package/dist/errors.js.map +1 -0
  86. package/dist/errors.json +289 -0
  87. package/dist/eslint/imports.d.ts +2 -0
  88. package/dist/eslint/imports.js +2 -2
  89. package/dist/eslint/imports.js.map +1 -1
  90. package/dist/eslint/index.d.ts +2 -1
  91. package/dist/eslint/index.js +11 -3
  92. package/dist/eslint/index.js.map +1 -1
  93. package/dist/eslint/template-rule.d.ts +1 -0
  94. package/dist/eslint/template-rule.js +15 -5
  95. package/dist/eslint/template-rule.js.map +1 -1
  96. package/dist/eslint/unused-intent-rule.d.ts +2 -0
  97. package/dist/eslint/unused-intent-rule.js +186 -0
  98. package/dist/eslint/unused-intent-rule.js.map +1 -0
  99. package/dist/features.d.ts +2 -2
  100. package/dist/features.js.map +1 -1
  101. package/dist/focus.d.ts +6 -0
  102. package/dist/focus.js +32 -2
  103. package/dist/focus.js.map +1 -1
  104. package/dist/form-associated.d.ts +46 -0
  105. package/dist/form-associated.js +77 -0
  106. package/dist/form-associated.js.map +1 -0
  107. package/dist/forms.d.ts +42 -30
  108. package/dist/forms.js +85 -41
  109. package/dist/forms.js.map +1 -1
  110. package/dist/frame-lane.d.ts +11 -0
  111. package/dist/frame-lane.js +28 -0
  112. package/dist/frame-lane.js.map +1 -0
  113. package/dist/head.d.ts +61 -0
  114. package/dist/head.js +85 -0
  115. package/dist/head.js.map +1 -0
  116. package/dist/host-model.d.ts +34 -5
  117. package/dist/host-model.js +146 -23
  118. package/dist/host-model.js.map +1 -1
  119. package/dist/hydration-client.js +2 -2
  120. package/dist/hydration-client.js.map +1 -1
  121. package/dist/hydration-loader.d.ts +13 -0
  122. package/dist/hydration-loader.js +58 -0
  123. package/dist/hydration-loader.js.map +1 -0
  124. package/dist/hydration-off.d.ts +8 -0
  125. package/dist/hydration-off.js +25 -0
  126. package/dist/hydration-off.js.map +1 -0
  127. package/dist/hydration.d.ts +0 -12
  128. package/dist/hydration.js +3 -52
  129. package/dist/hydration.js.map +1 -1
  130. package/dist/index.d.ts +21 -24
  131. package/dist/index.js +10 -19
  132. package/dist/index.js.map +1 -1
  133. package/dist/indexeddb-types.d.ts +97 -0
  134. package/dist/indexeddb-types.js +2 -0
  135. package/dist/indexeddb-types.js.map +1 -0
  136. package/dist/indexeddb.d.ts +5 -0
  137. package/dist/indexeddb.js +118 -0
  138. package/dist/indexeddb.js.map +1 -0
  139. package/dist/intent.d.ts +42 -20
  140. package/dist/intent.js +145 -107
  141. package/dist/intent.js.map +1 -1
  142. package/dist/internal/indexeddb-open.d.ts +8 -0
  143. package/dist/internal/indexeddb-open.js +96 -0
  144. package/dist/internal/indexeddb-open.js.map +1 -0
  145. package/dist/internal/indexeddb-transaction.d.ts +4 -0
  146. package/dist/internal/indexeddb-transaction.js +134 -0
  147. package/dist/internal/indexeddb-transaction.js.map +1 -0
  148. package/dist/internal/interpreter.d.ts +12 -4
  149. package/dist/internal/interpreter.js +36 -48
  150. package/dist/internal/interpreter.js.map +1 -1
  151. package/dist/internal/token.d.ts +1 -0
  152. package/dist/internal/token.js +7 -0
  153. package/dist/internal/token.js.map +1 -0
  154. package/dist/internal.d.ts +20 -0
  155. package/dist/internal.js +22 -0
  156. package/dist/internal.js.map +1 -0
  157. package/dist/invoker-fallback-used.d.ts +4 -0
  158. package/dist/invoker-fallback-used.js +10 -0
  159. package/dist/invoker-fallback-used.js.map +1 -0
  160. package/dist/invoker-fallback.d.ts +4 -0
  161. package/dist/invoker-fallback.js +15 -0
  162. package/dist/invoker-fallback.js.map +1 -0
  163. package/dist/light-dom.d.ts +1 -1
  164. package/dist/light-dom.js.map +1 -1
  165. package/dist/prop.d.ts +30 -7
  166. package/dist/prop.js +19 -0
  167. package/dist/prop.js.map +1 -1
  168. package/dist/props.d.ts +8 -3
  169. package/dist/props.js +22 -15
  170. package/dist/props.js.map +1 -1
  171. package/dist/retry.d.ts +15 -0
  172. package/dist/retry.js +48 -0
  173. package/dist/retry.js.map +1 -0
  174. package/dist/scheduler.d.ts +7 -3
  175. package/dist/scheduler.js +38 -56
  176. package/dist/scheduler.js.map +1 -1
  177. package/dist/script-json.d.ts +2 -0
  178. package/dist/script-json.js +13 -0
  179. package/dist/script-json.js.map +1 -0
  180. package/dist/server-component.d.ts +1 -1
  181. package/dist/server-component.js +45 -14
  182. package/dist/server-component.js.map +1 -1
  183. package/dist/server.d.ts +9 -3
  184. package/dist/server.js +12 -3
  185. package/dist/server.js.map +1 -1
  186. package/dist/settled.d.ts +4 -3
  187. package/dist/settled.js +48 -26
  188. package/dist/settled.js.map +1 -1
  189. package/dist/spec-features-used.d.ts +21 -0
  190. package/dist/spec-features-used.js +50 -0
  191. package/dist/spec-features-used.js.map +1 -0
  192. package/dist/spec-features.d.ts +28 -0
  193. package/dist/spec-features.js +42 -0
  194. package/dist/spec-features.js.map +1 -0
  195. package/dist/states.d.ts +3 -5
  196. package/dist/states.js +22 -8
  197. package/dist/states.js.map +1 -1
  198. package/dist/storage.d.ts +69 -0
  199. package/dist/storage.js +158 -0
  200. package/dist/storage.js.map +1 -0
  201. package/dist/store-binding.js +2 -2
  202. package/dist/store-binding.js.map +1 -1
  203. package/dist/store-scope.d.ts +2 -4
  204. package/dist/store-scope.js +6 -16
  205. package/dist/store-scope.js.map +1 -1
  206. package/dist/store.d.ts +2 -6
  207. package/dist/store.js +36 -8
  208. package/dist/store.js.map +1 -1
  209. package/dist/subscription.d.ts +35 -0
  210. package/dist/subscription.js +74 -0
  211. package/dist/subscription.js.map +1 -0
  212. package/dist/subscriptions.d.ts +26 -0
  213. package/dist/subscriptions.js +98 -0
  214. package/dist/subscriptions.js.map +1 -0
  215. package/dist/transitions.d.ts +4 -5
  216. package/dist/transitions.js +10 -4
  217. package/dist/transitions.js.map +1 -1
  218. package/dist/types.d.ts +141 -32
  219. package/dist/types.js.map +1 -1
  220. package/dist/use-dialogs.d.ts +2 -0
  221. package/dist/use-dialogs.js +9 -0
  222. package/dist/use-dialogs.js.map +1 -0
  223. package/dist/use-disposal.d.ts +2 -0
  224. package/dist/use-disposal.js +9 -0
  225. package/dist/use-disposal.js.map +1 -0
  226. package/dist/use-error-view.d.ts +2 -0
  227. package/dist/use-error-view.js +9 -0
  228. package/dist/use-error-view.js.map +1 -0
  229. package/dist/use-errored.d.ts +2 -0
  230. package/dist/use-errored.js +9 -0
  231. package/dist/use-errored.js.map +1 -0
  232. package/dist/use-form-associated.d.ts +2 -0
  233. package/dist/use-form-associated.js +7 -0
  234. package/dist/use-form-associated.js.map +1 -0
  235. package/dist/use-frame-lane.d.ts +2 -0
  236. package/dist/use-frame-lane.js +10 -0
  237. package/dist/use-frame-lane.js.map +1 -0
  238. package/dist/use-invokers.d.ts +2 -0
  239. package/dist/use-invokers.js +10 -0
  240. package/dist/use-invokers.js.map +1 -0
  241. package/dist/use-states.d.ts +2 -0
  242. package/dist/use-states.js +10 -0
  243. package/dist/use-states.js.map +1 -0
  244. package/dist/use-subscriptions.d.ts +2 -0
  245. package/dist/use-subscriptions.js +10 -0
  246. package/dist/use-subscriptions.js.map +1 -0
  247. package/dist/use-transitions.d.ts +2 -0
  248. package/dist/use-transitions.js +10 -0
  249. package/dist/use-transitions.js.map +1 -0
  250. package/dist/view/attributes.d.ts +5 -0
  251. package/dist/view/attributes.js +5 -0
  252. package/dist/view/attributes.js.map +1 -1
  253. package/dist/view/compiled.d.ts +1 -1
  254. package/dist/view/compiled.js +1 -1
  255. package/dist/view/compiled.js.map +1 -1
  256. package/dist/view/index.d.ts +9 -4
  257. package/dist/view/index.js +9 -3
  258. package/dist/view/index.js.map +1 -1
  259. package/dist/view/loc.d.ts +9 -0
  260. package/dist/view/loc.js +50 -0
  261. package/dist/view/loc.js.map +1 -0
  262. package/dist/view/message.d.ts +17 -0
  263. package/dist/view/message.js +40 -0
  264. package/dist/view/message.js.map +1 -0
  265. package/dist/view/messages.d.ts +43 -0
  266. package/dist/view/messages.js +84 -0
  267. package/dist/view/messages.js.map +1 -0
  268. package/dist/view/normalize/check.d.ts +5 -2
  269. package/dist/view/normalize/check.js +6 -3
  270. package/dist/view/normalize/check.js.map +1 -1
  271. package/dist/view/normalize/emit.d.ts +2 -0
  272. package/dist/view/normalize/emit.js +40 -11
  273. package/dist/view/normalize/emit.js.map +1 -1
  274. package/dist/view/normalize/errors.js +1 -1
  275. package/dist/view/normalize/errors.js.map +1 -1
  276. package/dist/view/normalize/id.d.ts +5 -2
  277. package/dist/view/normalize/id.js +9 -2
  278. package/dist/view/normalize/id.js.map +1 -1
  279. package/dist/view/normalize/normalize.d.ts +7 -4
  280. package/dist/view/normalize/normalize.js +15 -9
  281. package/dist/view/normalize/normalize.js.map +1 -1
  282. package/dist/view/normalize/rules.d.ts +9 -0
  283. package/dist/view/normalize/rules.js +35 -5
  284. package/dist/view/normalize/rules.js.map +1 -1
  285. package/dist/view/normalize/svg.d.ts +14 -0
  286. package/dist/view/normalize/svg.js +40 -0
  287. package/dist/view/normalize/svg.js.map +1 -0
  288. package/dist/view/normalize/tokens.d.ts +5 -1
  289. package/dist/view/normalize/tokens.js.map +1 -1
  290. package/dist/view/normalize/tree.d.ts +8 -2
  291. package/dist/view/normalize/tree.js +58 -13
  292. package/dist/view/normalize/tree.js.map +1 -1
  293. package/dist/view/normalize/types.d.ts +24 -4
  294. package/dist/view/normalize/types.js +4 -1
  295. package/dist/view/normalize/types.js.map +1 -1
  296. package/dist/view/normalize/whitespace.js +4 -3
  297. package/dist/view/normalize/whitespace.js.map +1 -1
  298. package/dist/view/prepare-stub.js +2 -2
  299. package/dist/view/prepare-stub.js.map +1 -1
  300. package/dist/view/prepare.d.ts +5 -2
  301. package/dist/view/prepare.js +9 -3
  302. package/dist/view/prepare.js.map +1 -1
  303. package/dist/view/registry.d.ts +15 -0
  304. package/dist/view/registry.js +10 -2
  305. package/dist/view/registry.js.map +1 -1
  306. package/dist/view/render/adopt-attr.js +7 -1
  307. package/dist/view/render/adopt-attr.js.map +1 -1
  308. package/dist/view/render/adopt.js +21 -4
  309. package/dist/view/render/adopt.js.map +1 -1
  310. package/dist/view/render/attr-parts.d.ts +12 -1
  311. package/dist/view/render/attr-parts.js +38 -5
  312. package/dist/view/render/attr-parts.js.map +1 -1
  313. package/dist/view/render/child-part.js +5 -2
  314. package/dist/view/render/child-part.js.map +1 -1
  315. package/dist/view/render/dispose.d.ts +3 -0
  316. package/dist/view/render/dispose.js +76 -0
  317. package/dist/view/render/dispose.js.map +1 -0
  318. package/dist/view/render/hook-part.d.ts +3 -1
  319. package/dist/view/render/hook-part.js +21 -5
  320. package/dist/view/render/hook-part.js.map +1 -1
  321. package/dist/view/render/hook-run.d.ts +2 -0
  322. package/dist/view/render/hook-run.js +72 -0
  323. package/dist/view/render/hook-run.js.map +1 -0
  324. package/dist/view/render/hooks.d.ts +74 -6
  325. package/dist/view/render/hooks.js +79 -17
  326. package/dist/view/render/hooks.js.map +1 -1
  327. package/dist/view/render/hydrate.js +2 -2
  328. package/dist/view/render/hydrate.js.map +1 -1
  329. package/dist/view/render/keyed.d.ts +6 -0
  330. package/dist/view/render/keyed.js +15 -0
  331. package/dist/view/render/keyed.js.map +1 -0
  332. package/dist/view/render/list.d.ts +1 -1
  333. package/dist/view/render/list.js.map +1 -1
  334. package/dist/view/render/mismatch.js +5 -3
  335. package/dist/view/render/mismatch.js.map +1 -1
  336. package/dist/view/render/modal.d.ts +3 -0
  337. package/dist/view/render/modal.js +39 -0
  338. package/dist/view/render/modal.js.map +1 -0
  339. package/dist/view/render/raw.d.ts +4 -1
  340. package/dist/view/render/raw.js +6 -2
  341. package/dist/view/render/raw.js.map +1 -1
  342. package/dist/view/render/render.d.ts +4 -1
  343. package/dist/view/render/render.js +17 -12
  344. package/dist/view/render/render.js.map +1 -1
  345. package/dist/view/render/toggles.d.ts +6 -0
  346. package/dist/view/render/toggles.js +6 -0
  347. package/dist/view/render/toggles.js.map +1 -0
  348. package/dist/view/render/values.d.ts +1 -1
  349. package/dist/view/render/values.js.map +1 -1
  350. package/dist/view/render/warn.d.ts +14 -0
  351. package/dist/view/render/warn.js +45 -0
  352. package/dist/view/render/warn.js.map +1 -1
  353. package/dist/view/server/component.d.ts +6 -3
  354. package/dist/view/server/component.js +29 -7
  355. package/dist/view/server/component.js.map +1 -1
  356. package/dist/view/server/index.d.ts +20 -0
  357. package/dist/view/server/index.js +12 -4
  358. package/dist/view/server/index.js.map +1 -1
  359. package/dist/view/server/tags.d.ts +13 -2
  360. package/dist/view/server/tags.js +13 -2
  361. package/dist/view/server/tags.js.map +1 -1
  362. package/dist/view/server/values.d.ts +2 -0
  363. package/dist/view/server/values.js +8 -0
  364. package/dist/view/server/values.js.map +1 -1
  365. package/dist/view/server/writer.d.ts +9 -4
  366. package/dist/view/server/writer.js +33 -18
  367. package/dist/view/server/writer.js.map +1 -1
  368. package/dist/view/template-element.d.ts +7 -0
  369. package/dist/view/template-element.js +25 -5
  370. package/dist/view/template-element.js.map +1 -1
  371. package/dist/view/template.d.ts +9 -2
  372. package/dist/view/template.js +61 -8
  373. package/dist/view/template.js.map +1 -1
  374. package/dist/view/trusted-html.d.ts +6 -0
  375. package/dist/view/trusted-html.js +34 -0
  376. package/dist/view/trusted-html.js.map +1 -0
  377. package/dist/vite.d.ts +38 -31
  378. package/dist/vite.js +206 -31
  379. package/dist/vite.js.map +1 -1
  380. package/package.json +34 -3
  381. package/dist/hooks/labelled-by.d.ts +0 -13
  382. package/dist/hooks/labelled-by.js +0 -51
  383. package/dist/hooks/labelled-by.js.map +0 -1
  384. package/dist/random.d.ts +0 -13
  385. package/dist/random.js +0 -19
  386. package/dist/random.js.map +0 -1
@@ -7,8 +7,11 @@
7
7
  import { DEVTOOLS_ENABLED, devCommands, devOwner, devUpdate } from '#devtools';
8
8
  import { splitNext } from './command.js';
9
9
  import { features, } from './features.js';
10
+ import { fail } from './errors.js';
10
11
  import { runInit } from './init.js';
11
- import { requestTransition } from './scheduler.js';
12
+ import { noteActivity, requestTransition } from './scheduler.js';
13
+ import { errored, subscriptions as subscriptionSet } from '#spec-features';
14
+ import { message } from './view/index.js';
12
15
  export class HostModel {
13
16
  #host;
14
17
  #spec;
@@ -20,9 +23,21 @@ export class HostModel {
20
23
  #interpreter;
21
24
  /** Commands kept while disconnected (init's, before the first connect); undefined once connected. */
22
25
  #pending;
26
+ /** Connected to the document (a pending stop checks it, so a move cancels the stop). */
27
+ #on;
28
+ /** `spec.subscriptions`, kept running once the host has rendered (ADR 0025). */
29
+ #subs;
30
+ /** `init` threw (ADR 0024): the host shows its error view, or stays empty. */
31
+ #failure;
32
+ /** Handling `Errored`: a failure now is reported, never sent again (no loops). */
33
+ #erroring;
23
34
  constructor(host, spec) {
24
35
  /** Commands kept while disconnected (init's, before the first connect); undefined once connected. */
25
36
  this.#pending = [];
37
+ /** Connected to the document (a pending stop checks it, so a move cancels the stop). */
38
+ this.#on = false;
39
+ /** Handling `Errored`: a failure now is reported, never sent again (no loops). */
40
+ this.#erroring = false;
26
41
  /** The store binding, created on first use. Only reached once `defineStore()` has run. */
27
42
  this.stores = () => (this.#binding ??= features.stores(this.#host.el, this.#host.tag, this.#spec.stores ?? [], (store, state, prev) => {
28
43
  this.#storeChanged(store, state, prev);
@@ -32,22 +47,58 @@ export class HostModel {
32
47
  this.tag = host.tag;
33
48
  this.root = host.root;
34
49
  this.#spec = spec;
35
- // Sound: dispatch() only calls the reducer whose key equals msg._tag.
36
- this.#reducers = spec.update;
50
+ // Sound: dispatch() only calls the reducer whose key equals msg._tag. A component without
51
+ // messages may have no `update` (its spec type is widened to Tagged here).
52
+ this.#reducers = (spec.update ?? {});
37
53
  }
38
54
  get ready() {
39
55
  return this.#model !== undefined;
40
56
  }
57
+ /** Set when `init` threw; the host has no state then. */
58
+ get failure() {
59
+ return this.#failure;
60
+ }
41
61
  /** The current state, running `init` (and `initialMessages`) on first read. */
42
62
  state(initialMessages = []) {
43
63
  if (this.#model === undefined) {
44
64
  this.#seen = this.#host.props();
45
- this.apply(runInit(this.#spec, this.#seen));
65
+ const next = this.#init();
66
+ if (next === undefined)
67
+ return undefined; // init threw: `failure` says so
68
+ this.apply(next);
46
69
  for (const msg of initialMessages)
47
70
  this.dispatch(msg, false);
48
71
  }
49
72
  return this.#model.value;
50
73
  }
74
+ /** `init(props)`, or undefined after reporting its failure (ADR 0024). */
75
+ #init() {
76
+ try {
77
+ return runInit(this.#spec, this.#seen);
78
+ }
79
+ catch (cause) {
80
+ this.#failure = this.fail(cause, 'init', message(74, this.tag));
81
+ this.#model = { value: undefined };
82
+ return undefined;
83
+ }
84
+ }
85
+ /**
86
+ * Reports a failure of this host (ADR 0024) and, for a failed update, parse or command, sends
87
+ * `Errored` to its optional reducer. A failure while handling `Errored` is only reported.
88
+ */
89
+ fail(cause, phase, text, msg) {
90
+ const error = fail(cause, phase, text, { host: this.el, tag: this.tag, msg });
91
+ if (!this.#erroring && errored !== undefined && this.#reducers['Errored'] !== undefined) {
92
+ this.#erroring = true;
93
+ try {
94
+ errored((msg) => this.dispatch(msg), phase, error);
95
+ }
96
+ finally {
97
+ this.#erroring = false;
98
+ }
99
+ }
100
+ return error;
101
+ }
51
102
  hasReducer(tag) {
52
103
  return this.#reducers[tag] !== undefined;
53
104
  }
@@ -57,41 +108,65 @@ export class HostModel {
57
108
  */
58
109
  resume(seed) {
59
110
  this.#seen = this.#host.props();
60
- const [initial, commands] = splitNext(runInit(this.#spec, this.#seen));
111
+ const next = this.#init();
112
+ if (next === undefined)
113
+ return [];
114
+ const [initial, commands] = splitNext(next);
61
115
  // Sound: the seed is this component's own state, written by the server.
62
116
  this.#model = { value: 'state' in seed ? seed.state : initial };
63
117
  return commands;
64
118
  }
65
- /** PropsChanged when props differ from the last ones seen (`Object.is` per prop). */
119
+ /**
120
+ * PropsChanged when props differ from the last ones seen (`Object.is` per prop). A reducer
121
+ * that throws leaves them unseen, so the next render sends PropsChanged again (ADR 0024).
122
+ */
66
123
  syncProps(names) {
67
124
  const prev = this.#seen;
68
- if (prev === undefined)
125
+ if (prev === undefined || this.#failure !== undefined)
69
126
  return;
70
127
  const props = this.#host.props();
71
128
  if (names.every((name) => Object.is(props[name], prev[name])))
72
129
  return;
73
- this.#seen = props;
74
- this.dispatch({ _tag: 'PropsChanged', props, prev }, false);
130
+ if (this.dispatch({ _tag: 'PropsChanged', props, prev }, false))
131
+ this.#seen = props;
132
+ this.#resubscribe();
75
133
  }
134
+ /**
135
+ * Runs `msg`'s reducer. A reducer that throws changes nothing (no state, no commands): the
136
+ * failure is reported (ADR 0024) and dispatch returns false.
137
+ */
76
138
  dispatch(msg, schedule = true) {
139
+ noteActivity(); // settled() keeps waiting while messages arrive (04)
77
140
  const reducer = this.#reducers[msg._tag];
78
141
  if (reducer === undefined) {
79
142
  // Framework messages have optional reducers; props and stores stay readable as context.
80
- if (msg._tag !== 'PropsChanged' && msg._tag !== 'StoreChanged') {
81
- console.warn(`<${this.#host.tag}> has no update for message "${msg._tag}".`);
143
+ if (msg._tag !== 'PropsChanged' && msg._tag !== 'StoreChanged' && msg._tag !== 'Errored') {
144
+ console.warn(message(10, this.#host.tag, msg._tag));
82
145
  }
83
- return;
146
+ return true;
84
147
  }
148
+ if (this.#failure !== undefined)
149
+ return false;
85
150
  const prev = this.state();
86
- this.apply(reducer(prev, msg, this.ctx()));
87
- const next = this.state();
151
+ let next;
152
+ try {
153
+ next = reducer(prev, msg, this.ctx());
154
+ }
155
+ catch (cause) {
156
+ this.fail(cause, 'update', message(73, this.tag, msg._tag), msg._tag);
157
+ return false;
158
+ }
159
+ this.apply(next);
160
+ const state = this.state();
88
161
  if (DEVTOOLS_ENABLED)
89
- devUpdate(this.#host.el, this.#host.tag, msg, prev, next);
162
+ devUpdate(this.#host.el, this.#host.tag, msg, prev, state);
163
+ this.#resubscribe();
90
164
  if (!schedule)
91
- return;
92
- if (this.#spec.viewTransition?.(prev, next, msg) === true)
165
+ return true;
166
+ if (this.#spec.viewTransition?.(prev, state, msg) === true)
93
167
  requestTransition();
94
168
  this.#host.invalidate(this.#onFrame(msg._tag));
169
+ return true;
95
170
  }
96
171
  apply(next) {
97
172
  const [state, commands] = splitNext(next);
@@ -110,24 +185,64 @@ export class HostModel {
110
185
  run(commands) {
111
186
  this.apply([this.state(), commands]);
112
187
  }
188
+ // Reducers and the view get this too; only parsers' type (ParserCtx) names `state`.
113
189
  ctx() {
114
- return { props: this.#host.props(), read: (store) => this.stores().read(store) };
190
+ return {
191
+ props: this.#host.props(),
192
+ read: (store) => this.stores().read(store),
193
+ state: this.state(),
194
+ };
115
195
  }
116
- /** Resolves stores and runs kept commands. Returns true if store instances were bound. */
196
+ /**
197
+ * Resolves stores and runs kept commands; after a real detach, starts the subscriptions again
198
+ * (05 "Lifecycle"). Returns true if store instances were bound.
199
+ */
117
200
  connect() {
201
+ this.#on = true; // a move: the pending stop finds the host connected and does nothing
118
202
  // The nearest <gyral-stores> may differ after a move.
119
203
  const rebound = (this.#spec.stores?.length ?? 0) > 0 && this.stores().connect();
120
204
  const pending = this.#pending ?? [];
121
205
  this.#pending = undefined;
122
206
  for (const cmd of pending)
123
207
  this.#commands().run(cmd);
208
+ this.#resubscribe(); // a move: unchanged, nothing restarts
124
209
  return rebound;
125
210
  }
211
+ /**
212
+ * Starts `spec.subscriptions` (ADR 0025): after the first render, as init's commands of a
213
+ * server-rendered host are, so nothing they emit can change what hydration adopts.
214
+ */
215
+ subscribe() {
216
+ const derive = this.#spec.subscriptions;
217
+ if (derive === undefined || subscriptionSet === undefined)
218
+ return;
219
+ this.#subs ??= subscriptionSet(this, derive);
220
+ this.#resubscribe();
221
+ }
222
+ /** Aborts the running command of `lane` (a subscription that left, ADR 0025). */
223
+ cancel(lane) {
224
+ this.#interpreter?.cancel(lane);
225
+ }
226
+ /** Syncs the subscriptions while connected and healthy. */
227
+ #resubscribe() {
228
+ if (this.#pending === undefined && this.#failure === undefined)
229
+ this.#subs?.update();
230
+ }
231
+ /**
232
+ * Stops the commands one microtask later, unless the host is connected again first: a move
233
+ * (`appendChild`, `insertBefore`) disconnects and connects in one task and keeps them running.
234
+ */
126
235
  disconnect() {
127
236
  this.#binding?.disconnect();
128
- this.#interpreter?.dispose();
129
- this.#interpreter = undefined;
130
237
  this.#pending ??= [];
238
+ this.#on = false;
239
+ queueMicrotask(() => {
240
+ if (this.#on)
241
+ return;
242
+ this.#interpreter?.dispose();
243
+ this.#interpreter = undefined;
244
+ this.#subs?.reset();
245
+ });
131
246
  }
132
247
  /**
133
248
  * The interpreter, started with the first command after connecting. Only reached for a
@@ -135,9 +250,17 @@ export class HostModel {
135
250
  */
136
251
  #commands() {
137
252
  const el = this.el;
138
- return (this.#interpreter ??= features.commands(el, this.#spec.drivers, (msg) => {
253
+ return (this.#interpreter ??= features.commands(el, (msg) => {
139
254
  this.dispatch(msg);
140
- }, DEVTOOLS_ENABLED ? devCommands(() => devOwner(el, this.tag)) : undefined));
255
+ }, DEVTOOLS_ENABLED ? devCommands(() => devOwner(el, this.tag)) : undefined, (cause, code, driver) => {
256
+ const owner = `<${this.tag}>`;
257
+ const text = code === 40
258
+ ? message(40, owner, driver)
259
+ : code === 41
260
+ ? message(41, owner, driver)
261
+ : message(43, owner, driver);
262
+ this.fail(cause, 'command', text, driver);
263
+ }));
141
264
  }
142
265
  /** Does `tag` render in the frame lane (`spec.renderOnFrame`)? */
143
266
  #onFrame(tag) {
@@ -1 +1 @@
1
- {"version":3,"file":"host-model.js","sourceRoot":"","sources":["../src/host-model.ts"],"names":[],"mappings":"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","sourcesContent":["// The model side of one Gyral host (docs/design-docs/view/05-element.md, ADR 0006, ADR 0013):\n// state, reducers and commands. element.ts owns the DOM side (props, root, lifecycle,\n// rendering) and asks this object for state and context. Reducers run at once, so `state` is\n// always current; only the render waits (04). The interpreter and the store binding come from\n// features.ts, registered by `command()` and `defineStore()`; marker drivers (focus, emit, store\n// sends) carry their own `local` handler.\nimport { DEVTOOLS_ENABLED, devCommands, devOwner, devUpdate } from '#devtools';\nimport { splitNext, type Command, type Next } from './command.js';\nimport {\n features,\n type FeatureHost,\n type LocalDriver,\n type LocalHost,\n type StoreLink,\n} from './features.js';\nimport type { Seed } from './hydration.js';\nimport { runInit } from './init.js';\nimport type { Interpreter } from './internal/interpreter.js';\nimport { requestTransition } from './scheduler.js';\nimport type { AnyStore } from './store.js';\nimport type { ComponentSpec, Ctx, IntentRejected, Tagged } from './types.js';\n\ntype Bag = Readonly<Record<string, unknown>>;\ntype Reducer<S> = (state: S, msg: Tagged, ctx: Ctx<object>) => Next<S, Tagged>;\n/** A command as the model runs it. */\nexport type ModelCommand = Command<Tagged | IntentRejected>;\ntype Cmd = ModelCommand;\n\n/** What the model needs from its element. */\nexport interface ModelHost {\n readonly el: FeatureHost;\n readonly tag: string;\n /** The declared props as components see them (defaults applied). */\n readonly props: () => Bag;\n /** The host's root once it has one (focus commands run against it). */\n readonly root: () => ParentNode | undefined;\n /** Something changed that the next render must show; `onFrame`: in the frame lane (04). */\n readonly invalidate: (onFrame: boolean) => void;\n}\n\nexport class HostModel<S, P> implements LocalHost {\n readonly el: FeatureHost;\n readonly tag: string;\n readonly root: () => ParentNode | undefined;\n readonly #host: ModelHost;\n readonly #spec: ComponentSpec<S, Tagged, P>;\n readonly #reducers: Readonly<Record<string, Reducer<S> | undefined>>;\n #binding: StoreLink | undefined;\n #model: { value: S } | undefined;\n /** Props as of init or the last PropsChanged: the `prev` of the next PropsChanged. */\n #seen: Bag | undefined;\n #interpreter: Interpreter<Tagged | IntentRejected> | undefined;\n /** Commands kept while disconnected (init's, before the first connect); undefined once connected. */\n #pending: Cmd[] | undefined = [];\n\n constructor(host: ModelHost, spec: ComponentSpec<S, Tagged, P>) {\n this.#host = host;\n this.el = host.el;\n this.tag = host.tag;\n this.root = host.root;\n this.#spec = spec;\n // Sound: dispatch() only calls the reducer whose key equals msg._tag.\n this.#reducers = spec.update as unknown as Readonly<Record<string, Reducer<S> | undefined>>;\n }\n\n /** The store binding, created on first use. Only reached once `defineStore()` has run. */\n readonly stores = (): StoreLink =>\n (this.#binding ??= (features.stores as NonNullable<typeof features.stores>)(\n this.#host.el,\n this.#host.tag,\n this.#spec.stores ?? [],\n (store, state, prev) => {\n this.#storeChanged(store, state, prev);\n },\n ));\n\n get ready(): boolean {\n return this.#model !== undefined;\n }\n\n /** The current state, running `init` (and `initialMessages`) on first read. */\n state(initialMessages: readonly Tagged[] = []): S {\n if (this.#model === undefined) {\n this.#seen = this.#host.props();\n this.apply(runInit(this.#spec, this.#seen as P));\n for (const msg of initialMessages) this.dispatch(msg, false);\n }\n return (this.#model as { value: S }).value;\n }\n\n hasReducer(tag: string): boolean {\n return this.#reducers[tag] !== undefined;\n }\n\n /**\n * Resumes from a server seed (ADR 0012): the server's state, or `init(props)`'s when the\n * seed left it out. Returns init's commands, to start after the first render.\n */\n resume(seed: Seed): readonly Cmd[] {\n this.#seen = this.#host.props();\n const [initial, commands] = splitNext(runInit(this.#spec, this.#seen as P));\n // Sound: the seed is this component's own state, written by the server.\n this.#model = { value: 'state' in seed ? (seed.state as S) : initial };\n return commands;\n }\n\n /** PropsChanged when props differ from the last ones seen (`Object.is` per prop). */\n syncProps(names: readonly string[]): void {\n const prev = this.#seen;\n if (prev === undefined) return;\n const props = this.#host.props();\n if (names.every((name) => Object.is(props[name], prev[name]))) return;\n this.#seen = props;\n this.dispatch({ _tag: 'PropsChanged', props, prev } as Tagged, false);\n }\n\n dispatch(msg: Tagged, schedule = true): void {\n const reducer = this.#reducers[msg._tag];\n if (reducer === undefined) {\n // Framework messages have optional reducers; props and stores stay readable as context.\n if (msg._tag !== 'PropsChanged' && msg._tag !== 'StoreChanged') {\n console.warn(`<${this.#host.tag}> has no update for message \"${msg._tag}\".`);\n }\n return;\n }\n const prev = this.state();\n this.apply(reducer(prev, msg, this.ctx() as Ctx<object>));\n const next = this.state();\n if (DEVTOOLS_ENABLED) devUpdate(this.#host.el, this.#host.tag, msg, prev, next);\n if (!schedule) return;\n if (this.#spec.viewTransition?.(prev, next, msg) === true) requestTransition();\n this.#host.invalidate(this.#onFrame(msg._tag));\n }\n\n apply(next: Next<S, Tagged> | Next<S, Tagged | IntentRejected>): void {\n const [state, commands] = splitNext(next as Next<S, Tagged>);\n this.#model = { value: state };\n for (const cmd of commands as readonly Cmd[]) {\n const local = (cmd.driver as Partial<LocalDriver>).local;\n if (local !== undefined) local(this, cmd.input);\n else if (this.#pending !== undefined) this.#pending.push(cmd);\n else this.#commands().run(cmd);\n }\n }\n\n /** Starts commands kept for later (init's, after a server-rendered host's first render). */\n run(commands: readonly Cmd[]): void {\n this.apply([this.state(), commands]);\n }\n\n ctx(): Ctx<P> {\n return { props: this.#host.props() as P, read: (store) => this.stores().read(store) };\n }\n\n /** Resolves stores and runs kept commands. Returns true if store instances were bound. */\n connect(): boolean {\n // The nearest <gyral-stores> may differ after a move.\n const rebound = (this.#spec.stores?.length ?? 0) > 0 && this.stores().connect();\n const pending = this.#pending ?? [];\n this.#pending = undefined;\n for (const cmd of pending) this.#commands().run(cmd);\n return rebound;\n }\n\n disconnect(): void {\n this.#binding?.disconnect();\n this.#interpreter?.dispose();\n this.#interpreter = undefined;\n this.#pending ??= [];\n }\n\n /**\n * The interpreter, started with the first command after connecting. Only reached for a\n * command built by `command()`, which registered it (features.ts).\n */\n #commands(): Interpreter<Tagged | IntentRejected> {\n const el = this.el;\n return (this.#interpreter ??= (features.commands as NonNullable<typeof features.commands>)(\n el,\n this.#spec.drivers,\n (msg) => {\n this.dispatch(msg);\n },\n DEVTOOLS_ENABLED ? devCommands(() => devOwner(el, this.tag)) : undefined,\n ));\n }\n\n /** Does `tag` render in the frame lane (`spec.renderOnFrame`)? */\n #onFrame(tag: string): boolean {\n return this.#spec.renderOnFrame?.includes(tag) === true;\n }\n\n #storeChanged(store: AnyStore, state: unknown, prev: unknown): void {\n if (this.#reducers['StoreChanged'] !== undefined) {\n this.dispatch({ _tag: 'StoreChanged', store: store.name, state, prev } as Tagged);\n } else {\n this.#host.invalidate(this.#onFrame('StoreChanged'));\n }\n }\n}\n"]}
1
+ {"version":3,"file":"host-model.js","sourceRoot":"","sources":["../src/host-model.ts"],"names":[],"mappings":"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","sourcesContent":["// The model side of one Gyral host (docs/design-docs/view/05-element.md, ADR 0006, ADR 0013):\n// state, reducers and commands. element.ts owns the DOM side (props, root, lifecycle,\n// rendering) and asks this object for state and context. Reducers run at once, so `state` is\n// always current; only the render waits (04). The interpreter and the store binding come from\n// features.ts, registered by `command()` and `defineStore()`; marker drivers (focus, emit, store\n// sends) carry their own `local` handler.\nimport { DEVTOOLS_ENABLED, devCommands, devOwner, devUpdate } from '#devtools';\nimport { splitNext, type Command, type Next } from './command.js';\nimport {\n features,\n type FeatureHost,\n type LocalDriver,\n type LocalHost,\n type StoreLink,\n} from './features.js';\nimport { fail, type ErrorPhase, type GyralError } from './errors.js';\nimport type { Seed } from './hydration.js';\nimport { runInit } from './init.js';\nimport type { Interpreter } from './internal/interpreter.js';\nimport { noteActivity, requestTransition } from './scheduler.js';\nimport { errored, subscriptions as subscriptionSet } from '#spec-features';\nimport type { Subscriptions } from './subscriptions.js';\nimport type { AnyStore } from './store.js';\nimport type { ComponentSpec, Ctx, IntentRejected, ParserCtx, Tagged } from './types.js';\nimport { message } from './view/index.js';\n\ntype Bag = Readonly<Record<string, unknown>>;\ntype Reducer<S> = (state: S, msg: Tagged, ctx: Ctx<object>) => Next<S, Tagged>;\n/** A command as the model runs it. */\nexport type ModelCommand = Command<Tagged | IntentRejected>;\ntype Cmd = ModelCommand;\n\n/** What the model needs from its element. */\nexport interface ModelHost {\n readonly el: FeatureHost;\n readonly tag: string;\n /** The declared props as components see them (defaults applied). */\n readonly props: () => Bag;\n /** The host's root once it has one (focus commands run against it). */\n readonly root: () => ParentNode | undefined;\n /** Something changed that the next render must show; `onFrame`: in the frame lane (04). */\n readonly invalidate: (onFrame: boolean) => void;\n}\n\nexport class HostModel<S, P> implements LocalHost {\n readonly el: FeatureHost;\n readonly tag: string;\n readonly root: () => ParentNode | undefined;\n readonly #host: ModelHost;\n readonly #spec: ComponentSpec<S, Tagged, P>;\n readonly #reducers: Readonly<Record<string, Reducer<S> | undefined>>;\n #binding: StoreLink | undefined;\n #model: { value: S } | undefined;\n /** Props as of init or the last PropsChanged: the `prev` of the next PropsChanged. */\n #seen: Bag | undefined;\n #interpreter: Interpreter<Tagged | IntentRejected> | undefined;\n /** Commands kept while disconnected (init's, before the first connect); undefined once connected. */\n #pending: Cmd[] | undefined = [];\n /** Connected to the document (a pending stop checks it, so a move cancels the stop). */\n #on = false;\n /** `spec.subscriptions`, kept running once the host has rendered (ADR 0025). */\n #subs: Subscriptions | undefined;\n /** `init` threw (ADR 0024): the host shows its error view, or stays empty. */\n #failure: GyralError | undefined;\n /** Handling `Errored`: a failure now is reported, never sent again (no loops). */\n #erroring = false;\n\n constructor(host: ModelHost, spec: ComponentSpec<S, Tagged, P>) {\n this.#host = host;\n this.el = host.el;\n this.tag = host.tag;\n this.root = host.root;\n this.#spec = spec;\n // Sound: dispatch() only calls the reducer whose key equals msg._tag. A component without\n // messages may have no `update` (its spec type is widened to Tagged here).\n this.#reducers = ((spec as { readonly update?: object }).update ?? {}) as unknown as Readonly<\n Record<string, Reducer<S> | undefined>\n >;\n }\n\n /** The store binding, created on first use. Only reached once `defineStore()` has run. */\n readonly stores = (): StoreLink =>\n (this.#binding ??= (features.stores as NonNullable<typeof features.stores>)(\n this.#host.el,\n this.#host.tag,\n this.#spec.stores ?? [],\n (store, state, prev) => {\n this.#storeChanged(store, state, prev);\n },\n ));\n\n get ready(): boolean {\n return this.#model !== undefined;\n }\n\n /** Set when `init` threw; the host has no state then. */\n get failure(): GyralError | undefined {\n return this.#failure;\n }\n\n /** The current state, running `init` (and `initialMessages`) on first read. */\n state(initialMessages: readonly Tagged[] = []): S {\n if (this.#model === undefined) {\n this.#seen = this.#host.props();\n const next = this.#init();\n if (next === undefined) return undefined as S; // init threw: `failure` says so\n this.apply(next);\n for (const msg of initialMessages) this.dispatch(msg, false);\n }\n return (this.#model as { value: S }).value;\n }\n\n /** `init(props)`, or undefined after reporting its failure (ADR 0024). */\n #init(): Next<S, Tagged> | undefined {\n try {\n return runInit(this.#spec, this.#seen as P);\n } catch (cause) {\n this.#failure = this.fail(cause, 'init', message(74, this.tag));\n this.#model = { value: undefined as S };\n return undefined;\n }\n }\n\n /**\n * Reports a failure of this host (ADR 0024) and, for a failed update, parse or command, sends\n * `Errored` to its optional reducer. A failure while handling `Errored` is only reported.\n */\n fail(cause: unknown, phase: ErrorPhase, text: string, msg?: string): GyralError {\n const error = fail(cause, phase, text, { host: this.el, tag: this.tag, msg });\n if (!this.#erroring && errored !== undefined && this.#reducers['Errored'] !== undefined) {\n this.#erroring = true;\n try {\n errored((msg) => this.dispatch(msg), phase, error);\n } finally {\n this.#erroring = false;\n }\n }\n return error;\n }\n\n hasReducer(tag: string): boolean {\n return this.#reducers[tag] !== undefined;\n }\n\n /**\n * Resumes from a server seed (ADR 0012): the server's state, or `init(props)`'s when the\n * seed left it out. Returns init's commands, to start after the first render.\n */\n resume(seed: Seed): readonly Cmd[] {\n this.#seen = this.#host.props();\n const next = this.#init();\n if (next === undefined) return [];\n const [initial, commands] = splitNext(next);\n // Sound: the seed is this component's own state, written by the server.\n this.#model = { value: 'state' in seed ? (seed.state as S) : initial };\n return commands;\n }\n\n /**\n * PropsChanged when props differ from the last ones seen (`Object.is` per prop). A reducer\n * that throws leaves them unseen, so the next render sends PropsChanged again (ADR 0024).\n */\n syncProps(names: readonly string[]): void {\n const prev = this.#seen;\n if (prev === undefined || this.#failure !== undefined) return;\n const props = this.#host.props();\n if (names.every((name) => Object.is(props[name], prev[name]))) return;\n if (this.dispatch({ _tag: 'PropsChanged', props, prev } as Tagged, false)) this.#seen = props;\n this.#resubscribe();\n }\n\n /**\n * Runs `msg`'s reducer. A reducer that throws changes nothing (no state, no commands): the\n * failure is reported (ADR 0024) and dispatch returns false.\n */\n dispatch(msg: Tagged, schedule = true): boolean {\n noteActivity(); // settled() keeps waiting while messages arrive (04)\n const reducer = this.#reducers[msg._tag];\n if (reducer === undefined) {\n // Framework messages have optional reducers; props and stores stay readable as context.\n if (msg._tag !== 'PropsChanged' && msg._tag !== 'StoreChanged' && msg._tag !== 'Errored') {\n console.warn(message(10, this.#host.tag, msg._tag));\n }\n return true;\n }\n if (this.#failure !== undefined) return false;\n const prev = this.state();\n let next: Next<S, Tagged>;\n try {\n next = reducer(prev, msg, this.ctx() as Ctx<object>);\n } catch (cause) {\n this.fail(cause, 'update', message(73, this.tag, msg._tag), msg._tag);\n return false;\n }\n this.apply(next);\n const state = this.state();\n if (DEVTOOLS_ENABLED) devUpdate(this.#host.el, this.#host.tag, msg, prev, state);\n this.#resubscribe();\n if (!schedule) return true;\n if (this.#spec.viewTransition?.(prev, state, msg) === true) requestTransition();\n this.#host.invalidate(this.#onFrame(msg._tag));\n return true;\n }\n\n apply(next: Next<S, Tagged> | Next<S, Tagged | IntentRejected>): void {\n const [state, commands] = splitNext(next as Next<S, Tagged>);\n this.#model = { value: state };\n for (const cmd of commands as readonly Cmd[]) {\n const local = (cmd.driver as Partial<LocalDriver>).local;\n if (local !== undefined) local(this, cmd.input);\n else if (this.#pending !== undefined) this.#pending.push(cmd);\n else this.#commands().run(cmd);\n }\n }\n\n /** Starts commands kept for later (init's, after a server-rendered host's first render). */\n run(commands: readonly Cmd[]): void {\n this.apply([this.state(), commands]);\n }\n\n // Reducers and the view get this too; only parsers' type (ParserCtx) names `state`.\n ctx(): ParserCtx<P, S> {\n return {\n props: this.#host.props() as P,\n read: (store) => this.stores().read(store),\n state: this.state(),\n };\n }\n\n /**\n * Resolves stores and runs kept commands; after a real detach, starts the subscriptions again\n * (05 \"Lifecycle\"). Returns true if store instances were bound.\n */\n connect(): boolean {\n this.#on = true; // a move: the pending stop finds the host connected and does nothing\n // The nearest <gyral-stores> may differ after a move.\n const rebound = (this.#spec.stores?.length ?? 0) > 0 && this.stores().connect();\n const pending = this.#pending ?? [];\n this.#pending = undefined;\n for (const cmd of pending) this.#commands().run(cmd);\n this.#resubscribe(); // a move: unchanged, nothing restarts\n return rebound;\n }\n\n /**\n * Starts `spec.subscriptions` (ADR 0025): after the first render, as init's commands of a\n * server-rendered host are, so nothing they emit can change what hydration adopts.\n */\n subscribe(): void {\n const derive = this.#spec.subscriptions;\n if (derive === undefined || subscriptionSet === undefined) return;\n this.#subs ??= subscriptionSet(this, derive);\n this.#resubscribe();\n }\n\n /** Aborts the running command of `lane` (a subscription that left, ADR 0025). */\n cancel(lane: string): void {\n this.#interpreter?.cancel(lane);\n }\n\n /** Syncs the subscriptions while connected and healthy. */\n #resubscribe(): void {\n if (this.#pending === undefined && this.#failure === undefined) this.#subs?.update();\n }\n\n /**\n * Stops the commands one microtask later, unless the host is connected again first: a move\n * (`appendChild`, `insertBefore`) disconnects and connects in one task and keeps them running.\n */\n disconnect(): void {\n this.#binding?.disconnect();\n this.#pending ??= [];\n this.#on = false;\n queueMicrotask(() => {\n if (this.#on) return;\n this.#interpreter?.dispose();\n this.#interpreter = undefined;\n this.#subs?.reset();\n });\n }\n\n /**\n * The interpreter, started with the first command after connecting. Only reached for a\n * command built by `command()`, which registered it (features.ts).\n */\n #commands(): Interpreter<Tagged | IntentRejected> {\n const el = this.el;\n return (this.#interpreter ??= (features.commands as NonNullable<typeof features.commands>)(\n el,\n (msg) => {\n this.dispatch(msg);\n },\n DEVTOOLS_ENABLED ? devCommands(() => devOwner(el, this.tag)) : undefined,\n (cause, code, driver) => {\n const owner = `<${this.tag}>`;\n const text =\n code === 40\n ? message(40, owner, driver)\n : code === 41\n ? message(41, owner, driver)\n : message(43, owner, driver);\n this.fail(cause, 'command', text, driver);\n },\n ));\n }\n\n /** Does `tag` render in the frame lane (`spec.renderOnFrame`)? */\n #onFrame(tag: string): boolean {\n return this.#spec.renderOnFrame?.includes(tag) === true;\n }\n\n #storeChanged(store: AnyStore, state: unknown, prev: unknown): void {\n if (this.#reducers['StoreChanged'] !== undefined) {\n this.dispatch({ _tag: 'StoreChanged', store: store.name, state, prev } as Tagged);\n } else {\n this.#host.invalidate(this.#onFrame('StoreChanged'));\n }\n }\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  // islands. element.ts imports this module with import() when the first host that needs it
3
3
  // connects (one with a seed or `defer-hydration`), so client-only pages never fetch it.
4
4
  import { DEVTOOLS_ENABLED, devMismatch } from '#devtools';
5
- import { DEV, hydrate, HydrationMismatch, render, } from './view/index.js';
5
+ import { DEV, hydrate, HydrationMismatch, message, render, } from './view/index.js';
6
6
  export { scheduleIsland } from './islands.js';
7
7
  /**
8
8
  * Hydrates a server-rendered host's root with its first view (view/07-hydration.md "Steps").
@@ -27,7 +27,7 @@ export function hydrateRoot(host, tag, view, root, sheets, seen) {
27
27
  devMismatch(host, tag, error.message);
28
28
  if (DEV)
29
29
  throw error;
30
- console.warn(`${error.message} <${tag}> was rendered fresh.`);
30
+ console.warn(message(63, tag), error.message);
31
31
  root.replaceChildren();
32
32
  render(view, root, seen);
33
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hydration-client.js","sourceRoot":"","sources":["../src/hydration-client.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,0FAA0F;AAC1F,wFAAwF;AACxF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,GAAG,EACH,OAAO,EACP,iBAAiB,EACjB,MAAM,GAGP,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAAiB,EACjB,GAAW,EACX,IAAgB,EAChB,IAA8B,EAC9B,MAAmC,EACnC,IAAgB;IAEhB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACxB,IAAmB,CAAC,kBAAkB,GAAG,MAAM,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;QAC9B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,EAAE,QAAQ,KAAK,OAAO;YAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,iBAAiB,CAAC;YAAE,MAAM,KAAK,CAAC;QACvD,IAAI,gBAAgB;YAAE,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,GAAG;YAAE,MAAM,KAAK,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,KAAK,GAAG,uBAAuB,CAAC,CAAC;QAC9D,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC","sourcesContent":["// The lazily loaded half of hydration (view/07-hydration.md \"Loading\"): the parallel walk and\n// islands. element.ts imports this module with import() when the first host that needs it\n// connects (one with a seed or `defer-hydration`), so client-only pages never fetch it.\nimport { DEVTOOLS_ENABLED, devMismatch } from '#devtools';\nimport {\n DEV,\n hydrate,\n HydrationMismatch,\n render,\n type ChildValue,\n type SeenMarkup,\n} from './view/index.js';\n\nexport { scheduleIsland } from './islands.js';\n\n/**\n * Hydrates a server-rendered host's root with its first view (view/07-hydration.md \"Steps\").\n * A shadow root swaps styles in the same step (08 \"Hydration\"): the shared sheets are adopted\n * and the server's `<style>` removed. On a mismatch development throws; production recovers\n * this component only: it clears the root, renders fresh and warns.\n */\nexport function hydrateRoot(\n host: HTMLElement,\n tag: string,\n view: ChildValue,\n root: ShadowRoot | HTMLElement,\n sheets: CSSStyleSheet[] | undefined,\n seen: SeenMarkup,\n): void {\n if (sheets !== undefined) {\n (root as ShadowRoot).adoptedStyleSheets = sheets;\n const first = root.firstChild;\n if (sheets.length > 0 && first?.nodeName === 'STYLE') first.remove();\n }\n try {\n hydrate(view, root, `<${tag}>`, seen);\n } catch (error) {\n if (!(error instanceof HydrationMismatch)) throw error;\n if (DEVTOOLS_ENABLED) devMismatch(host, tag, error.message);\n if (DEV) throw error;\n console.warn(`${error.message} <${tag}> was rendered fresh.`);\n root.replaceChildren();\n render(view, root, seen);\n }\n}\n"]}
1
+ {"version":3,"file":"hydration-client.js","sourceRoot":"","sources":["../src/hydration-client.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,0FAA0F;AAC1F,wFAAwF;AACxF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,GAAG,EACH,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,MAAM,GAGP,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAAiB,EACjB,GAAW,EACX,IAAgB,EAChB,IAA8B,EAC9B,MAAmC,EACnC,IAAgB;IAEhB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACxB,IAAmB,CAAC,kBAAkB,GAAG,MAAM,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;QAC9B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,EAAE,QAAQ,KAAK,OAAO;YAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,iBAAiB,CAAC;YAAE,MAAM,KAAK,CAAC;QACvD,IAAI,gBAAgB;YAAE,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,GAAG;YAAE,MAAM,KAAK,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC","sourcesContent":["// The lazily loaded half of hydration (view/07-hydration.md \"Loading\"): the parallel walk and\n// islands. element.ts imports this module with import() when the first host that needs it\n// connects (one with a seed or `defer-hydration`), so client-only pages never fetch it.\nimport { DEVTOOLS_ENABLED, devMismatch } from '#devtools';\nimport {\n DEV,\n hydrate,\n HydrationMismatch,\n message,\n render,\n type ChildValue,\n type SeenMarkup,\n} from './view/index.js';\n\nexport { scheduleIsland } from './islands.js';\n\n/**\n * Hydrates a server-rendered host's root with its first view (view/07-hydration.md \"Steps\").\n * A shadow root swaps styles in the same step (08 \"Hydration\"): the shared sheets are adopted\n * and the server's `<style>` removed. On a mismatch development throws; production recovers\n * this component only: it clears the root, renders fresh and warns.\n */\nexport function hydrateRoot(\n host: HTMLElement,\n tag: string,\n view: ChildValue,\n root: ShadowRoot | HTMLElement,\n sheets: CSSStyleSheet[] | undefined,\n seen: SeenMarkup,\n): void {\n if (sheets !== undefined) {\n (root as ShadowRoot).adoptedStyleSheets = sheets;\n const first = root.firstChild;\n if (sheets.length > 0 && first?.nodeName === 'STYLE') first.remove();\n }\n try {\n hydrate(view, root, `<${tag}>`, seen);\n } catch (error) {\n if (!(error instanceof HydrationMismatch)) throw error;\n if (DEVTOOLS_ENABLED) devMismatch(host, tag, error.message);\n if (DEV) throw error;\n console.warn(message(63, tag), error.message);\n root.replaceChildren();\n render(view, root, seen);\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { type Seed } from './hydration.js';
2
+ /** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */
3
+ export declare function takeSeed(host: Element): Seed | undefined;
4
+ /**
5
+ * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale
6
+ * deployment: server-rendered hosts then render fresh); undefined until then.
7
+ */
8
+ export declare let hydrationCode: typeof import('./hydration-client.js') | null | undefined;
9
+ /**
10
+ * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call
11
+ * order. settled() waits meanwhile (view/07-hydration.md "Loading").
12
+ */
13
+ export declare function whenHydrationLoads(next: () => void): void;
@@ -0,0 +1,58 @@
1
+ // The client's half of seeds and hydration loading (view/07-hydration.md "Loading"), core's
2
+ // `#hydration-loader`: reads a server-rendered host's seed on connect and loads the hydration
3
+ // walk (hydration-client.ts) with import() when the first host that needs it connects.
4
+ // Client-only builds (`gyralVitePreset({ clientOnly: true })`, gyral-c5d.11) resolve
5
+ // `#hydration-loader` to hydration-off.ts instead: no import(), so no hydration chunk.
6
+ import { SEED_ATTRIBUTE } from './hydration.js';
7
+ import { hold } from './scheduler.js';
8
+ import { message } from './view/index.js';
9
+ /** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */
10
+ export function takeSeed(host) {
11
+ const raw = host.getAttribute(SEED_ATTRIBUTE);
12
+ if (raw === null)
13
+ return undefined;
14
+ host.removeAttribute(SEED_ATTRIBUTE);
15
+ try {
16
+ const parsed = JSON.parse(raw);
17
+ if (parsed === null || typeof parsed !== 'object')
18
+ return undefined;
19
+ const props = parsed.props ?? {};
20
+ return 'state' in parsed ? { state: parsed.state, props } : { props };
21
+ }
22
+ catch (error) {
23
+ console.error(message(60, host.localName, SEED_ATTRIBUTE), error);
24
+ return undefined;
25
+ }
26
+ }
27
+ /**
28
+ * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale
29
+ * deployment: server-rendered hosts then render fresh); undefined until then.
30
+ */
31
+ export let hydrationCode;
32
+ /** Hosts waiting for the code, in connection order; undefined when no load is pending. */
33
+ let waiting;
34
+ /**
35
+ * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call
36
+ * order. settled() waits meanwhile (view/07-hydration.md "Loading").
37
+ */
38
+ export function whenHydrationLoads(next) {
39
+ if (waiting !== undefined) {
40
+ waiting.push(next);
41
+ return;
42
+ }
43
+ waiting = [next];
44
+ const done = (code) => {
45
+ hydrationCode = code;
46
+ const hosts = waiting ?? [];
47
+ waiting = undefined;
48
+ // Each in its own microtask, in order: one host throwing doesn't stop the others. They all
49
+ // run before `hold` counts the load as done.
50
+ for (const host of hosts)
51
+ queueMicrotask(host);
52
+ };
53
+ hold(import('./hydration-client.js').then(done, (error) => {
54
+ console.error(message(61), error);
55
+ done(null);
56
+ }));
57
+ }
58
+ //# sourceMappingURL=hydration-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration-loader.js","sourceRoot":"","sources":["../src/hydration-loader.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,8FAA8F;AAC9F,uFAAuF;AACvF,qFAAqF;AACrF,uFAAuF;AACvF,OAAO,EAAE,cAAc,EAAa,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,6FAA6F;AAC7F,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACpE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IACxE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC;QAClE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,IAAI,aAAwE,CAAC;AACpF,0FAA0F;AAC1F,IAAI,OAAmC,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAgB;IACjD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO;IACT,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,IAAI,GAAG,CAAC,IAA0B,EAAQ,EAAE;QAChD,aAAa,GAAG,IAAI,CAAC;QACrB,MAAM,KAAK,GAAG,OAAO,IAAI,EAAE,CAAC;QAC5B,OAAO,GAAG,SAAS,CAAC;QACpB,2FAA2F;QAC3F,6CAA6C;QAC7C,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC;IACF,IAAI,CACF,MAAM,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE;QAC5D,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["// The client's half of seeds and hydration loading (view/07-hydration.md \"Loading\"), core's\n// `#hydration-loader`: reads a server-rendered host's seed on connect and loads the hydration\n// walk (hydration-client.ts) with import() when the first host that needs it connects.\n// Client-only builds (`gyralVitePreset({ clientOnly: true })`, gyral-c5d.11) resolve\n// `#hydration-loader` to hydration-off.ts instead: no import(), so no hydration chunk.\nimport { SEED_ATTRIBUTE, type Seed } from './hydration.js';\nimport { hold } from './scheduler.js';\nimport { message } from './view/index.js';\n\n/** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */\nexport function takeSeed(host: Element): Seed | undefined {\n const raw = host.getAttribute(SEED_ATTRIBUTE);\n if (raw === null) return undefined;\n host.removeAttribute(SEED_ATTRIBUTE);\n try {\n const parsed = JSON.parse(raw) as Partial<Seed> | null;\n if (parsed === null || typeof parsed !== 'object') return undefined;\n const props = parsed.props ?? {};\n return 'state' in parsed ? { state: parsed.state, props } : { props };\n } catch (error) {\n console.error(message(60, host.localName, SEED_ATTRIBUTE), error);\n return undefined;\n }\n}\n\n/**\n * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale\n * deployment: server-rendered hosts then render fresh); undefined until then.\n */\nexport let hydrationCode: typeof import('./hydration-client.js') | null | undefined;\n/** Hosts waiting for the code, in connection order; undefined when no load is pending. */\nlet waiting: (() => void)[] | undefined;\n\n/**\n * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call\n * order. settled() waits meanwhile (view/07-hydration.md \"Loading\").\n */\nexport function whenHydrationLoads(next: () => void): void {\n if (waiting !== undefined) {\n waiting.push(next);\n return;\n }\n waiting = [next];\n const done = (code: typeof hydrationCode): void => {\n hydrationCode = code;\n const hosts = waiting ?? [];\n waiting = undefined;\n // Each in its own microtask, in order: one host throwing doesn't stop the others. They all\n // run before `hold` counts the load as done.\n for (const host of hosts) queueMicrotask(host);\n };\n hold(\n import('./hydration-client.js').then(done, (error: unknown) => {\n console.error(message(61), error);\n done(null);\n }),\n );\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type * as Loader from './hydration-loader.js';
2
+ import { type Seed } from './hydration.js';
3
+ /** A seed means server-rendered markup: drop it, so the host renders fresh. */
4
+ export declare const takeSeed: (host: Element) => Seed | undefined;
5
+ /** Never loads: server-rendered hosts render fresh, as after a failed load. */
6
+ export declare const hydrationCode: typeof Loader.hydrationCode;
7
+ /** Not reached (`hydrationCode` is never undefined); runs `next` at once. */
8
+ export declare const whenHydrationLoads: (next: () => void) => void;
@@ -0,0 +1,25 @@
1
+ import { SEED_ATTRIBUTE } from './hydration.js';
2
+ import { DEV } from './view/index.js';
3
+ let warned = false;
4
+ /** A seed means server-rendered markup: drop it, so the host renders fresh. */
5
+ export const takeSeed = ((host) => {
6
+ if (!host.hasAttribute(SEED_ATTRIBUTE))
7
+ return undefined;
8
+ host.removeAttribute(SEED_ATTRIBUTE);
9
+ if (DEV && !warned) {
10
+ warned = true;
11
+ console.warn(`gyral: <${host.localName}> was server-rendered (it has a hydration seed), but this app ` +
12
+ `is built client-only (gyralVitePreset({ clientOnly: true })), which leaves the ` +
13
+ `hydration code out: it renders fresh, replacing the server's markup, and so does every ` +
14
+ `server-rendered host. Drop clientOnly for apps that render on the server ` +
15
+ `(view/07-hydration.md "Client-only builds").`);
16
+ }
17
+ return { props: {} };
18
+ });
19
+ /** Never loads: server-rendered hosts render fresh, as after a failed load. */
20
+ export const hydrationCode = null;
21
+ /** Not reached (`hydrationCode` is never undefined); runs `next` at once. */
22
+ export const whenHydrationLoads = ((next) => {
23
+ next();
24
+ });
25
+ //# sourceMappingURL=hydration-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration-off.js","sourceRoot":"","sources":["../src/hydration-off.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAa,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAa,EAAoB,EAAE;IAC3D,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC;QAAE,OAAO,SAAS,CAAC;IACzD,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IACrC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,OAAO,CAAC,IAAI,CACV,WAAW,IAAI,CAAC,SAAS,gEAAgE;YACvF,iFAAiF;YACjF,yFAAyF;YACzF,2EAA2E;YAC3E,8CAA8C,CACjD,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACvB,CAAC,CAAkC,CAAC;AAEpC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAgC,IAAI,CAAC;AAE/D,6EAA6E;AAC7E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAgB,EAAQ,EAAE;IAC5D,IAAI,EAAE,CAAC;AACT,CAAC,CAA4C,CAAC","sourcesContent":["// `#hydration-loader` in client-only builds (`gyralVitePreset({ clientOnly: true })`,\n// gyral-c5d.11, view/07-hydration.md \"Client-only builds\"): the same exports as\n// hydration-loader.ts, without the hydration walk or its import(), so the bundle has no\n// hydration chunk (and, without other import()s, no preload helper). A server-rendered host\n// met anyway renders fresh: its seed is dropped (init runs from its attributes), `hydrationCode`\n// is `null` as after a failed load, so element.ts clears the root, and islands start at once.\n// Development warns once; production renders fresh silently.\nimport type * as Loader from './hydration-loader.js';\nimport { SEED_ATTRIBUTE, type Seed } from './hydration.js';\nimport { DEV } from './view/index.js';\n\nlet warned = false;\n\n/** A seed means server-rendered markup: drop it, so the host renders fresh. */\nexport const takeSeed = ((host: Element): Seed | undefined => {\n if (!host.hasAttribute(SEED_ATTRIBUTE)) return undefined;\n host.removeAttribute(SEED_ATTRIBUTE);\n if (DEV && !warned) {\n warned = true;\n console.warn(\n `gyral: <${host.localName}> was server-rendered (it has a hydration seed), but this app ` +\n `is built client-only (gyralVitePreset({ clientOnly: true })), which leaves the ` +\n `hydration code out: it renders fresh, replacing the server's markup, and so does every ` +\n `server-rendered host. Drop clientOnly for apps that render on the server ` +\n `(view/07-hydration.md \"Client-only builds\").`,\n );\n }\n return { props: {} };\n}) satisfies typeof Loader.takeSeed;\n\n/** Never loads: server-rendered hosts render fresh, as after a failed load. */\nexport const hydrationCode: typeof Loader.hydrationCode = null;\n\n/** Not reached (`hydrationCode` is never undefined); runs `next` at once. */\nexport const whenHydrationLoads = ((next: () => void): void => {\n next();\n}) satisfies typeof Loader.whenHydrationLoads;\n"]}
@@ -16,15 +16,3 @@ export declare const sameJson: (a: unknown, b: unknown) => boolean;
16
16
  * (property holes) and whose state is `state`; `initial` is `init(props)`'s state.
17
17
  */
18
18
  export declare function makeSeed(tag: string, state: unknown, initial: unknown, carried: Readonly<Record<string, unknown>>): Seed;
19
- /** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */
20
- export declare function takeSeed(host: Element): Seed | undefined;
21
- /**
22
- * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale
23
- * deployment: server-rendered hosts then render fresh); undefined until then.
24
- */
25
- export declare let hydrationCode: typeof import('./hydration-client.js') | null | undefined;
26
- /**
27
- * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call
28
- * order. settled() waits meanwhile (view/07-hydration.md "Loading").
29
- */
30
- export declare function whenHydrationLoads(next: () => void): void;
package/dist/hydration.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // Server-render seeds (docs/design-docs/0012-ssr.md, view/06-server.md "Components"). The
2
2
  // server writes each component's state, plus the props an attribute can't carry, into one
3
3
  // attribute; the client reads it on connect, before its first render, so both sides start from
4
- // the same state. The walk that then hydrates the server's DOM is hydration-client.ts, which
5
- // core loads lazily (view/07-hydration.md "Loading").
4
+ // the same state. The client reads seeds in hydration-loader.ts (core's `#hydration-loader`:
5
+ // hydration-off.ts in client-only builds); the walk that then hydrates the server's DOM is
6
+ // hydration-client.ts, which the loader imports lazily (view/07-hydration.md "Loading").
6
7
  import { warnJsonHazard } from './json-safety.js';
7
- import { hold } from './scheduler.js';
8
8
  import { DEV } from './view/index.js';
9
9
  /** Host attribute holding the JSON seed. Removed once the client has read it. */
10
10
  export const SEED_ATTRIBUTE = 'data-gyral-seed';
@@ -20,53 +20,4 @@ export function makeSeed(tag, state, initial, carried) {
20
20
  warnJsonHazard(`<${tag}>`, seed, 'seed'); // view/06-server.md "Seed": development
21
21
  return seed;
22
22
  }
23
- /** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */
24
- export function takeSeed(host) {
25
- const raw = host.getAttribute(SEED_ATTRIBUTE);
26
- if (raw === null)
27
- return undefined;
28
- host.removeAttribute(SEED_ATTRIBUTE);
29
- try {
30
- const parsed = JSON.parse(raw);
31
- if (parsed === null || typeof parsed !== 'object')
32
- return undefined;
33
- const props = parsed.props ?? {};
34
- return 'state' in parsed ? { state: parsed.state, props } : { props };
35
- }
36
- catch (error) {
37
- console.error(`<${host.localName}> has an unreadable ${SEED_ATTRIBUTE}`, error);
38
- return undefined;
39
- }
40
- }
41
- /**
42
- * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale
43
- * deployment: server-rendered hosts then render fresh); undefined until then.
44
- */
45
- export let hydrationCode;
46
- /** Hosts waiting for the code, in connection order; undefined when no load is pending. */
47
- let waiting;
48
- /**
49
- * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call
50
- * order. settled() waits meanwhile (view/07-hydration.md "Loading").
51
- */
52
- export function whenHydrationLoads(next) {
53
- if (waiting !== undefined) {
54
- waiting.push(next);
55
- return;
56
- }
57
- waiting = [next];
58
- const done = (code) => {
59
- hydrationCode = code;
60
- const hosts = waiting ?? [];
61
- waiting = undefined;
62
- // Each in its own microtask, in order: one host throwing doesn't stop the others. They all
63
- // run before `hold` counts the load as done.
64
- for (const host of hosts)
65
- queueMicrotask(host);
66
- };
67
- hold(import('./hydration-client.js').then(done, (error) => {
68
- console.error('gyral: hydration code failed to load; rendering fresh', error);
69
- done(null);
70
- }));
71
- }
72
23
  //# sourceMappingURL=hydration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hydration.js","sourceRoot":"","sources":["../src/hydration.ts"],"names":[],"mappings":"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","sourcesContent":["// Server-render seeds (docs/design-docs/0012-ssr.md, view/06-server.md \"Components\"). The\n// server writes each component's state, plus the props an attribute can't carry, into one\n// attribute; the client reads it on connect, before its first render, so both sides start from\n// the same state. The walk that then hydrates the server's DOM is hydration-client.ts, which\n// core loads lazily (view/07-hydration.md \"Loading\").\nimport { warnJsonHazard } from './json-safety.js';\nimport { hold } from './scheduler.js';\nimport { DEV } from './view/index.js';\n\n/** Host attribute holding the JSON seed. Removed once the client has read it. */\nexport const SEED_ATTRIBUTE = 'data-gyral-seed';\n\nexport interface Seed {\n /**\n * The server's state. Omitted when it equals what `init(props)` returns for the seeded\n * props (gyral-czi 4k7.10): the client recomputes it instead of reading a second copy of\n * data the props already carry.\n */\n readonly state?: unknown;\n readonly props: Readonly<Record<string, unknown>>;\n}\n\n/** JSON equality: seeds are JSON, so two values that serialize alike hydrate alike. */\nexport const sameJson = (a: unknown, b: unknown): boolean =>\n JSON.stringify(a) === JSON.stringify(b);\n\n/**\n * Server side: the seed for a component whose props `carried` travel in no attribute\n * (property holes) and whose state is `state`; `initial` is `init(props)`'s state.\n */\nexport function makeSeed(\n tag: string,\n state: unknown,\n initial: unknown,\n carried: Readonly<Record<string, unknown>>,\n): Seed {\n const seed: Seed = sameJson(state, initial) ? { props: carried } : { state, props: carried };\n if (DEV) warnJsonHazard(`<${tag}>`, seed, 'seed'); // view/06-server.md \"Seed\": development\n\n return seed;\n}\n\n/** Client side: reads and removes the seed, if this element was server-rendered by Gyral. */\nexport function takeSeed(host: Element): Seed | undefined {\n const raw = host.getAttribute(SEED_ATTRIBUTE);\n if (raw === null) return undefined;\n host.removeAttribute(SEED_ATTRIBUTE);\n try {\n const parsed = JSON.parse(raw) as Partial<Seed> | null;\n if (parsed === null || typeof parsed !== 'object') return undefined;\n const props = parsed.props ?? {};\n return 'state' in parsed ? { state: parsed.state, props } : { props };\n } catch (error) {\n console.error(`<${host.localName}> has an unreadable ${SEED_ATTRIBUTE}`, error);\n return undefined;\n }\n}\n\n/**\n * The walk and islands (hydration-client.ts) once loaded; `null` if loading failed (a stale\n * deployment: server-rendered hosts then render fresh); undefined until then.\n */\nexport let hydrationCode: typeof import('./hydration-client.js') | null | undefined;\n/** Hosts waiting for the code, in connection order; undefined when no load is pending. */\nlet waiting: (() => void)[] | undefined;\n\n/**\n * Loads the hydration code (once) and runs `next` when it has loaded or failed, in call\n * order. settled() waits meanwhile (view/07-hydration.md \"Loading\").\n */\nexport function whenHydrationLoads(next: () => void): void {\n if (waiting !== undefined) {\n waiting.push(next);\n return;\n }\n waiting = [next];\n const done = (code: typeof hydrationCode): void => {\n hydrationCode = code;\n const hosts = waiting ?? [];\n waiting = undefined;\n // Each in its own microtask, in order: one host throwing doesn't stop the others. They all\n // run before `hold` counts the load as done.\n for (const host of hosts) queueMicrotask(host);\n };\n hold(\n import('./hydration-client.js').then(done, (error: unknown) => {\n console.error('gyral: hydration code failed to load; rendering fresh', error);\n done(null);\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"hydration.js","sourceRoot":"","sources":["../src/hydration.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,0FAA0F;AAC1F,+FAA+F;AAC/F,6FAA6F;AAC7F,2FAA2F;AAC3F,yFAAyF;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAYhD,uFAAuF;AACvF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAE,CAAU,EAAW,EAAE,CAC1D,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAW,EACX,KAAc,EACd,OAAgB,EAChB,OAA0C;IAE1C,MAAM,IAAI,GAAS,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC7F,IAAI,GAAG;QAAE,cAAc,CAAC,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,wCAAwC;IAE3F,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// Server-render seeds (docs/design-docs/0012-ssr.md, view/06-server.md \"Components\"). The\n// server writes each component's state, plus the props an attribute can't carry, into one\n// attribute; the client reads it on connect, before its first render, so both sides start from\n// the same state. The client reads seeds in hydration-loader.ts (core's `#hydration-loader`:\n// hydration-off.ts in client-only builds); the walk that then hydrates the server's DOM is\n// hydration-client.ts, which the loader imports lazily (view/07-hydration.md \"Loading\").\nimport { warnJsonHazard } from './json-safety.js';\nimport { DEV } from './view/index.js';\n\n/** Host attribute holding the JSON seed. Removed once the client has read it. */\nexport const SEED_ATTRIBUTE = 'data-gyral-seed';\n\nexport interface Seed {\n /**\n * The server's state. Omitted when it equals what `init(props)` returns for the seeded\n * props (gyral-czi 4k7.10): the client recomputes it instead of reading a second copy of\n * data the props already carry.\n */\n readonly state?: unknown;\n readonly props: Readonly<Record<string, unknown>>;\n}\n\n/** JSON equality: seeds are JSON, so two values that serialize alike hydrate alike. */\nexport const sameJson = (a: unknown, b: unknown): boolean =>\n JSON.stringify(a) === JSON.stringify(b);\n\n/**\n * Server side: the seed for a component whose props `carried` travel in no attribute\n * (property holes) and whose state is `state`; `initial` is `init(props)`'s state.\n */\nexport function makeSeed(\n tag: string,\n state: unknown,\n initial: unknown,\n carried: Readonly<Record<string, unknown>>,\n): Seed {\n const seed: Seed = sameJson(state, initial) ? { props: carried } : { state, props: carried };\n if (DEV) warnJsonHazard(`<${tag}>`, seed, 'seed'); // view/06-server.md \"Seed\": development\n\n return seed;\n}\n"]}