@typed/template 0.9.6 → 0.10.0

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 (300) hide show
  1. package/RenderQueue/package.json +6 -0
  2. package/dist/cjs/Directive.js +1 -1
  3. package/dist/cjs/Directive.js.map +1 -1
  4. package/dist/cjs/ElementRef.js +1 -1
  5. package/dist/cjs/ElementRef.js.map +1 -1
  6. package/dist/cjs/ElementSource.js +1 -1
  7. package/dist/cjs/ElementSource.js.map +1 -1
  8. package/dist/cjs/EventHandler.js +11 -4
  9. package/dist/cjs/EventHandler.js.map +1 -1
  10. package/dist/cjs/Html.js +84 -44
  11. package/dist/cjs/Html.js.map +1 -1
  12. package/dist/cjs/HtmlChunk.js +67 -21
  13. package/dist/cjs/HtmlChunk.js.map +1 -1
  14. package/dist/cjs/Hydrate.js +6 -6
  15. package/dist/cjs/Hydrate.js.map +1 -1
  16. package/dist/cjs/Many.js +4 -4
  17. package/dist/cjs/Many.js.map +1 -1
  18. package/dist/cjs/Meta.js +10 -3
  19. package/dist/cjs/Meta.js.map +1 -1
  20. package/dist/cjs/Parser.js +1 -1
  21. package/dist/cjs/Placeholder.js +5 -9
  22. package/dist/cjs/Placeholder.js.map +1 -1
  23. package/dist/cjs/Platform.js +7 -5
  24. package/dist/cjs/Platform.js.map +1 -1
  25. package/dist/cjs/Render.js +8 -7
  26. package/dist/cjs/Render.js.map +1 -1
  27. package/dist/cjs/RenderContext.js +8 -92
  28. package/dist/cjs/RenderContext.js.map +1 -1
  29. package/dist/cjs/RenderEvent.js +9 -1
  30. package/dist/cjs/RenderEvent.js.map +1 -1
  31. package/dist/cjs/RenderQueue.js +341 -0
  32. package/dist/cjs/RenderQueue.js.map +1 -0
  33. package/dist/cjs/RenderTemplate.js +1 -1
  34. package/dist/cjs/RenderTemplate.js.map +1 -1
  35. package/dist/cjs/Template.js +12 -0
  36. package/dist/cjs/Template.js.map +1 -1
  37. package/dist/cjs/Test.js +64 -33
  38. package/dist/cjs/Test.js.map +1 -1
  39. package/dist/cjs/Vitest.js +12 -20
  40. package/dist/cjs/Vitest.js.map +1 -1
  41. package/dist/cjs/index.js +6 -3
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/internal/EventSource.js +16 -9
  44. package/dist/cjs/internal/EventSource.js.map +1 -1
  45. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  46. package/dist/cjs/internal/browser.js +11 -10
  47. package/dist/cjs/internal/browser.js.map +1 -1
  48. package/dist/cjs/internal/character-entities.js +2141 -0
  49. package/dist/cjs/internal/character-entities.js.map +1 -0
  50. package/dist/cjs/internal/errors.js +19 -2
  51. package/dist/cjs/internal/errors.js.map +1 -1
  52. package/dist/cjs/internal/indexRefCounter.js +36 -63
  53. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  54. package/dist/cjs/internal/parser.js +18 -17
  55. package/dist/cjs/internal/parser.js.map +1 -1
  56. package/dist/cjs/internal/parser2.js +382 -0
  57. package/dist/cjs/internal/parser2.js.map +1 -0
  58. package/dist/cjs/internal/server-parts.js +124 -0
  59. package/dist/cjs/internal/server-parts.js.map +1 -0
  60. package/dist/cjs/internal/server.js +15 -185
  61. package/dist/cjs/internal/server.js.map +1 -1
  62. package/dist/cjs/internal/utils.js +73 -9
  63. package/dist/cjs/internal/utils.js.map +1 -1
  64. package/dist/cjs/internal/v2/SyncPart.js +6 -0
  65. package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
  66. package/dist/cjs/internal/v2/helpers.js +15 -0
  67. package/dist/cjs/internal/v2/helpers.js.map +1 -0
  68. package/dist/cjs/internal/v2/hydrate.js +202 -0
  69. package/dist/cjs/internal/v2/hydrate.js.map +1 -0
  70. package/dist/cjs/internal/v2/hydration-template.js +269 -0
  71. package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
  72. package/dist/cjs/internal/v2/parts.js +169 -0
  73. package/dist/cjs/internal/v2/parts.js.map +1 -0
  74. package/dist/cjs/internal/v2/render-entry.js +110 -0
  75. package/dist/cjs/internal/v2/render-entry.js.map +1 -0
  76. package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
  77. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
  78. package/dist/cjs/internal/v2/render.js +417 -0
  79. package/dist/cjs/internal/v2/render.js.map +1 -0
  80. package/dist/cjs/internal/v2/sync-parts.js +115 -0
  81. package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
  82. package/dist/dts/ElementRef.d.ts +1 -1
  83. package/dist/dts/ElementRef.d.ts.map +1 -1
  84. package/dist/dts/ElementSource.d.ts +1 -1
  85. package/dist/dts/ElementSource.d.ts.map +1 -1
  86. package/dist/dts/EventHandler.d.ts +12 -8
  87. package/dist/dts/EventHandler.d.ts.map +1 -1
  88. package/dist/dts/Html.d.ts +6 -5
  89. package/dist/dts/Html.d.ts.map +1 -1
  90. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  91. package/dist/dts/Hydrate.d.ts +1 -3
  92. package/dist/dts/Hydrate.d.ts.map +1 -1
  93. package/dist/dts/Many.d.ts +9 -11
  94. package/dist/dts/Many.d.ts.map +1 -1
  95. package/dist/dts/Meta.d.ts +5 -1
  96. package/dist/dts/Meta.d.ts.map +1 -1
  97. package/dist/dts/Parser.d.ts +1 -1
  98. package/dist/dts/Parser.d.ts.map +1 -1
  99. package/dist/dts/Part.d.ts +20 -56
  100. package/dist/dts/Part.d.ts.map +1 -1
  101. package/dist/dts/Placeholder.d.ts +6 -10
  102. package/dist/dts/Placeholder.d.ts.map +1 -1
  103. package/dist/dts/Platform.d.ts +2 -4
  104. package/dist/dts/Platform.d.ts.map +1 -1
  105. package/dist/dts/Render.d.ts +4 -8
  106. package/dist/dts/Render.d.ts.map +1 -1
  107. package/dist/dts/RenderContext.d.ts +3 -22
  108. package/dist/dts/RenderContext.d.ts.map +1 -1
  109. package/dist/dts/RenderEvent.d.ts +6 -1
  110. package/dist/dts/RenderEvent.d.ts.map +1 -1
  111. package/dist/dts/RenderQueue.d.ts +103 -0
  112. package/dist/dts/RenderQueue.d.ts.map +1 -0
  113. package/dist/dts/RenderTemplate.d.ts +3 -2
  114. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  115. package/dist/dts/Renderable.d.ts +1 -1
  116. package/dist/dts/Template.d.ts +14 -1
  117. package/dist/dts/Template.d.ts.map +1 -1
  118. package/dist/dts/Test.d.ts +14 -1
  119. package/dist/dts/Test.d.ts.map +1 -1
  120. package/dist/dts/Vitest.d.ts +11 -8
  121. package/dist/dts/Vitest.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/EventSource.d.ts +2 -1
  125. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  126. package/dist/dts/internal/browser.d.ts +3 -3
  127. package/dist/dts/internal/browser.d.ts.map +1 -1
  128. package/dist/dts/internal/character-entities.d.ts +2133 -0
  129. package/dist/dts/internal/character-entities.d.ts.map +1 -0
  130. package/dist/dts/internal/errors.d.ts +9 -1
  131. package/dist/dts/internal/errors.d.ts.map +1 -1
  132. package/dist/dts/internal/indexRefCounter.d.ts +0 -4
  133. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  134. package/dist/dts/internal/parser.d.ts +13 -0
  135. package/dist/dts/internal/parser.d.ts.map +1 -1
  136. package/dist/dts/internal/parser2.d.ts +12 -0
  137. package/dist/dts/internal/parser2.d.ts.map +1 -0
  138. package/dist/dts/internal/server-parts.d.ts +223 -0
  139. package/dist/dts/internal/server-parts.d.ts.map +1 -0
  140. package/dist/dts/internal/server.d.ts +2 -28
  141. package/dist/dts/internal/server.d.ts.map +1 -1
  142. package/dist/dts/internal/utils.d.ts +4 -1
  143. package/dist/dts/internal/utils.d.ts.map +1 -1
  144. package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
  145. package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
  146. package/dist/dts/internal/v2/helpers.d.ts +3 -0
  147. package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
  148. package/dist/dts/internal/v2/hydrate.d.ts +7 -0
  149. package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
  150. package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
  151. package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
  152. package/dist/dts/internal/v2/parts.d.ts +245 -0
  153. package/dist/dts/internal/v2/parts.d.ts.map +1 -0
  154. package/dist/dts/internal/v2/render-entry.d.ts +6 -0
  155. package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
  156. package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
  157. package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
  158. package/dist/dts/internal/v2/render.d.ts +62 -0
  159. package/dist/dts/internal/v2/render.d.ts.map +1 -0
  160. package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
  161. package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
  162. package/dist/esm/ElementRef.js.map +1 -1
  163. package/dist/esm/EventHandler.js +14 -4
  164. package/dist/esm/EventHandler.js.map +1 -1
  165. package/dist/esm/Html.js +91 -50
  166. package/dist/esm/Html.js.map +1 -1
  167. package/dist/esm/HtmlChunk.js +75 -24
  168. package/dist/esm/HtmlChunk.js.map +1 -1
  169. package/dist/esm/Hydrate.js +5 -5
  170. package/dist/esm/Hydrate.js.map +1 -1
  171. package/dist/esm/Many.js +3 -3
  172. package/dist/esm/Many.js.map +1 -1
  173. package/dist/esm/Meta.js +7 -1
  174. package/dist/esm/Meta.js.map +1 -1
  175. package/dist/esm/Parser.js +1 -1
  176. package/dist/esm/Parser.js.map +1 -1
  177. package/dist/esm/Placeholder.js +4 -8
  178. package/dist/esm/Placeholder.js.map +1 -1
  179. package/dist/esm/Platform.js +3 -1
  180. package/dist/esm/Platform.js.map +1 -1
  181. package/dist/esm/Render.js +6 -5
  182. package/dist/esm/Render.js.map +1 -1
  183. package/dist/esm/RenderContext.js +5 -85
  184. package/dist/esm/RenderContext.js.map +1 -1
  185. package/dist/esm/RenderEvent.js +8 -1
  186. package/dist/esm/RenderEvent.js.map +1 -1
  187. package/dist/esm/RenderQueue.js +336 -0
  188. package/dist/esm/RenderQueue.js.map +1 -0
  189. package/dist/esm/RenderTemplate.js.map +1 -1
  190. package/dist/esm/Template.js +12 -0
  191. package/dist/esm/Template.js.map +1 -1
  192. package/dist/esm/Test.js +71 -30
  193. package/dist/esm/Test.js.map +1 -1
  194. package/dist/esm/Vitest.js +11 -8
  195. package/dist/esm/Vitest.js.map +1 -1
  196. package/dist/esm/index.js +4 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/internal/EventSource.js +12 -7
  199. package/dist/esm/internal/EventSource.js.map +1 -1
  200. package/dist/esm/internal/HydrateContext.js.map +1 -1
  201. package/dist/esm/internal/browser.js +10 -9
  202. package/dist/esm/internal/browser.js.map +1 -1
  203. package/dist/esm/internal/character-entities.js +2134 -0
  204. package/dist/esm/internal/character-entities.js.map +1 -0
  205. package/dist/esm/internal/errors.js +22 -2
  206. package/dist/esm/internal/errors.js.map +1 -1
  207. package/dist/esm/internal/indexRefCounter.js +36 -61
  208. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  209. package/dist/esm/internal/parser.js +18 -18
  210. package/dist/esm/internal/parser.js.map +1 -1
  211. package/dist/esm/internal/parser2.js +393 -0
  212. package/dist/esm/internal/parser2.js.map +1 -0
  213. package/dist/esm/internal/server-parts.js +109 -0
  214. package/dist/esm/internal/server-parts.js.map +1 -0
  215. package/dist/esm/internal/server.js +12 -161
  216. package/dist/esm/internal/server.js.map +1 -1
  217. package/dist/esm/internal/utils.js +71 -7
  218. package/dist/esm/internal/utils.js.map +1 -1
  219. package/dist/esm/internal/v2/SyncPart.js +5 -0
  220. package/dist/esm/internal/v2/SyncPart.js.map +1 -0
  221. package/dist/esm/internal/v2/helpers.js +12 -0
  222. package/dist/esm/internal/v2/helpers.js.map +1 -0
  223. package/dist/esm/internal/v2/hydrate.js +195 -0
  224. package/dist/esm/internal/v2/hydrate.js.map +1 -0
  225. package/dist/esm/internal/v2/hydration-template.js +265 -0
  226. package/dist/esm/internal/v2/hydration-template.js.map +1 -0
  227. package/dist/esm/internal/v2/parts.js +150 -0
  228. package/dist/esm/internal/v2/parts.js.map +1 -0
  229. package/dist/esm/internal/v2/render-entry.js +102 -0
  230. package/dist/esm/internal/v2/render-entry.js.map +1 -0
  231. package/dist/esm/internal/v2/render-sync-parts.js +265 -0
  232. package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
  233. package/dist/esm/internal/v2/render.js +353 -0
  234. package/dist/esm/internal/v2/render.js.map +1 -0
  235. package/dist/esm/internal/v2/sync-parts.js +102 -0
  236. package/dist/esm/internal/v2/sync-parts.js.map +1 -0
  237. package/package.json +20 -13
  238. package/src/ElementRef.ts +1 -1
  239. package/src/ElementSource.ts +1 -1
  240. package/src/EventHandler.ts +29 -11
  241. package/src/Html.ts +199 -90
  242. package/src/HtmlChunk.ts +77 -30
  243. package/src/Hydrate.ts +20 -14
  244. package/src/Many.ts +17 -14
  245. package/src/Meta.ts +8 -1
  246. package/src/Parser.ts +1 -1
  247. package/src/Part.ts +22 -66
  248. package/src/Placeholder.ts +17 -15
  249. package/src/Platform.ts +5 -5
  250. package/src/Render.ts +23 -26
  251. package/src/RenderContext.ts +14 -142
  252. package/src/RenderEvent.ts +10 -1
  253. package/src/RenderQueue.ts +445 -0
  254. package/src/RenderTemplate.ts +7 -2
  255. package/src/Renderable.ts +1 -1
  256. package/src/Template.ts +15 -1
  257. package/src/Test.ts +122 -38
  258. package/src/Vitest.ts +20 -10
  259. package/src/index.ts +4 -0
  260. package/src/internal/EventSource.ts +14 -8
  261. package/src/internal/HydrateContext.ts +3 -4
  262. package/src/internal/browser.ts +26 -21
  263. package/src/internal/character-entities.ts +2136 -0
  264. package/src/internal/errors.ts +30 -3
  265. package/src/internal/indexRefCounter.ts +38 -70
  266. package/src/internal/parser.ts +19 -19
  267. package/src/internal/parser2.ts +468 -0
  268. package/src/internal/server-parts.ts +161 -0
  269. package/src/internal/server.ts +16 -272
  270. package/src/internal/utils.ts +83 -7
  271. package/src/internal/v2/SyncPart.ts +112 -0
  272. package/src/internal/v2/helpers.ts +13 -0
  273. package/src/internal/v2/hydrate.ts +289 -0
  274. package/src/internal/v2/hydration-template.ts +308 -0
  275. package/src/internal/v2/parts.ts +254 -0
  276. package/src/internal/v2/render-entry.ts +131 -0
  277. package/src/internal/v2/render-sync-parts.ts +440 -0
  278. package/src/internal/v2/render.ts +588 -0
  279. package/src/internal/v2/sync-parts.ts +133 -0
  280. package/dist/cjs/internal/hydrate.js +0 -274
  281. package/dist/cjs/internal/hydrate.js.map +0 -1
  282. package/dist/cjs/internal/parts.js +0 -451
  283. package/dist/cjs/internal/parts.js.map +0 -1
  284. package/dist/cjs/internal/render.js +0 -704
  285. package/dist/cjs/internal/render.js.map +0 -1
  286. package/dist/dts/internal/hydrate.d.ts +0 -33
  287. package/dist/dts/internal/hydrate.d.ts.map +0 -1
  288. package/dist/dts/internal/parts.d.ts +0 -314
  289. package/dist/dts/internal/parts.d.ts.map +0 -1
  290. package/dist/dts/internal/render.d.ts +0 -16
  291. package/dist/dts/internal/render.d.ts.map +0 -1
  292. package/dist/esm/internal/hydrate.js +0 -239
  293. package/dist/esm/internal/hydrate.js.map +0 -1
  294. package/dist/esm/internal/parts.js +0 -373
  295. package/dist/esm/internal/parts.js.map +0 -1
  296. package/dist/esm/internal/render.js +0 -689
  297. package/dist/esm/internal/render.js.map +0 -1
  298. package/src/internal/hydrate.ts +0 -366
  299. package/src/internal/parts.ts +0 -609
  300. package/src/internal/render.ts +0 -971
@@ -9,16 +9,11 @@ import { GlobalThis } from "@typed/dom/GlobalThis"
9
9
  import { Window } from "@typed/dom/Window"
10
10
  import type { Environment } from "@typed/environment"
11
11
  import { CurrentEnvironment } from "@typed/environment"
12
- import * as Idle from "@typed/fx/Idle"
13
12
  import type { Rendered } from "@typed/wire"
14
- import * as Effect from "effect/Effect"
15
13
  import * as Layer from "effect/Layer"
16
14
  import * as Option from "effect/Option"
17
- import * as Scope from "effect/Scope"
18
15
  import type { Entry } from "./Entry.js"
19
16
 
20
- // TODO: We should probably have a more explicit environment type between DOM/HTML rendering
21
-
22
17
  /**
23
18
  * The context in which templates are rendered within
24
19
  * @since 1.0.0
@@ -38,11 +33,6 @@ export interface RenderContext {
38
33
  * Cache for individual templates.
39
34
  */
40
35
  readonly templateCache: WeakMap<TemplateStringsArray, Entry>
41
-
42
- /**
43
- * Queue for work to be batched
44
- */
45
- readonly queue: RenderQueue
46
36
  }
47
37
 
48
38
  /**
@@ -50,44 +40,26 @@ export interface RenderContext {
50
40
  * @since 1.0.0
51
41
  */
52
42
  export const RenderContext: Context.Tagged<RenderContext, RenderContext> = Context.Tagged<RenderContext>(
53
- "./RenderContext.js"
43
+ "@typed/template/RenderContext"
54
44
  )
55
45
 
56
46
  /**
57
47
  * @since 1.0.0
58
48
  */
59
- export interface RenderQueue {
60
- readonly add: (part: unknown, task: () => void) => Effect.Effect<void, never, Scope.Scope>
61
- }
62
-
63
- /**
64
- * @since 1.0.0
65
- */
66
- export type RenderContextOptions = IdleRequestOptions & {
49
+ export type RenderContextOptions = {
67
50
  readonly environment: Environment
68
- readonly scope: Scope.Scope
69
- }
70
-
71
- /**
72
- * @since 1.0.0
73
- */
74
- export function make({ ...options }: Omit<RenderContextOptions, "scope">, skipRenderScheduling?: boolean) {
75
- return Effect.scopeWith((scope) => Effect.succeed(unsafeMake({ ...options, scope }, skipRenderScheduling)))
76
51
  }
77
52
 
78
53
  /**
79
54
  * @since 1.0.0
80
55
  */
81
- export function unsafeMake({
82
- environment,
83
- scope,
84
- ...options
85
- }: RenderContextOptions, skipRenderScheduling?: boolean): RenderContext {
56
+ export function make({
57
+ environment
58
+ }: RenderContextOptions): RenderContext {
86
59
  return {
87
60
  environment,
88
61
  renderCache: new WeakMap(),
89
- templateCache: new WeakMap(),
90
- queue: new RenderQueueImpl(scope, options, skipRenderScheduling ?? false)
62
+ templateCache: new WeakMap()
91
63
  }
92
64
  }
93
65
 
@@ -108,9 +80,9 @@ export function getTemplateCache(
108
80
  return Option.fromNullable(templateCache.get(key))
109
81
  }
110
82
 
111
- const buildWithCurrentEnvironment = (environment: Environment, skipRenderScheduling?: boolean) =>
83
+ const buildWithCurrentEnvironment = (environment: Environment) =>
112
84
  Layer.mergeAll(
113
- RenderContext.scoped(make({ environment }, skipRenderScheduling)),
85
+ RenderContext.layer(make({ environment })),
114
86
  CurrentEnvironment.layer(environment)
115
87
  )
116
88
 
@@ -119,14 +91,14 @@ const buildWithCurrentEnvironment = (environment: Environment, skipRenderSchedul
119
91
  */
120
92
  export const dom: (
121
93
  window: Window & GlobalThis,
122
- options?: DomServicesElementParams & { readonly skipRenderScheduling?: boolean }
123
- ) => Layer.Layer<RenderContext | CurrentEnvironment | DomServices> = (window, options) =>
94
+ options?: DomServicesElementParams
95
+ ) => Layer.Layer<RenderContext | CurrentEnvironment | DomServices> = (
96
+ window,
97
+ options
98
+ ) =>
124
99
  Layer.provideMerge(
125
100
  Layer.mergeAll(
126
- buildWithCurrentEnvironment(
127
- "dom",
128
- options?.skipRenderScheduling
129
- ),
101
+ buildWithCurrentEnvironment("dom"),
130
102
  domServices(options)
131
103
  ),
132
104
  Layer.mergeAll(Window.layer(window), GlobalThis.layer(window))
@@ -147,103 +119,3 @@ export {
147
119
  */
148
120
  static_ as static
149
121
  }
150
-
151
- class RenderQueueImpl implements RenderQueue {
152
- queue = new Map<unknown, () => void>()
153
- scheduled = false
154
- run: Effect.Effect<void, never, Scope.Scope>
155
-
156
- constructor(
157
- readonly scope: Scope.Scope,
158
- readonly options?: IdleRequestOptions,
159
- readonly skipRenderScheduling: boolean = false
160
- ) {
161
- this.add.bind(this)
162
-
163
- this.run = typeof requestAnimationFrame === "undefined" ? this.runIdle : this.runAnimationFrame
164
- }
165
-
166
- add(part: unknown, task: () => void) {
167
- if (this.skipRenderScheduling) return Effect.sync(task)
168
-
169
- return Effect.suspend(() => {
170
- this.queue.set(part, task)
171
-
172
- return Effect.zipRight(
173
- Effect.addFinalizer(() =>
174
- Effect.sync(() => {
175
- const currentTask = this.queue.get(part)
176
-
177
- // If the current task is still the same we'll delete it from the queue
178
- if (currentTask === task) {
179
- this.queue.delete(part)
180
- }
181
- })
182
- ),
183
- this.scheduleNextRun
184
- )
185
- })
186
- }
187
-
188
- scheduleNextRun = Effect.suspend(() => {
189
- if (this.queue.size === 0 || this.scheduled) return Effect.unit
190
-
191
- this.scheduled = true
192
-
193
- return this.run.pipe(
194
- Scope.extend(this.scope),
195
- Effect.forkIn(this.scope)
196
- )
197
- })
198
-
199
- runIdle: Effect.Effect<void, never, Scope.Scope> = Effect.suspend(() => {
200
- return Effect.flatMap(
201
- Idle.whenIdle(this.options),
202
- (deadline) =>
203
- Effect.suspend(() => {
204
- const iterator = this.queue.entries()
205
-
206
- while (Idle.shouldContinue(deadline) && this.runTask(iterator)) {
207
- // Continue
208
- }
209
-
210
- // If we have more work to do, schedule another run
211
- if (this.queue.size > 0) {
212
- return this.runIdle
213
- }
214
-
215
- this.scheduled = false
216
-
217
- return Effect.unit
218
- })
219
- )
220
- })
221
-
222
- runAnimationFrame: Effect.Effect<void, never, Scope.Scope> = Effect.zipRight(
223
- Effect.async<void>((cb) => {
224
- const id = requestAnimationFrame(() => cb(Effect.unit))
225
- return Effect.sync(() => cancelAnimationFrame(id))
226
- }),
227
- Effect.sync(() => {
228
- const iterator = this.queue.entries()
229
-
230
- while (this.runTask(iterator)) {
231
- // Continue
232
- }
233
-
234
- this.scheduled = false
235
- })
236
- )
237
-
238
- private runTask = (iterator: Iterator<[unknown, () => void]>) => {
239
- const result = iterator.next()
240
-
241
- if (result.done) return false
242
- else {
243
- const [part, task] = result.value
244
- this.queue.delete(part)
245
- task()
246
- return true
247
- }
248
- }
249
- }
@@ -35,16 +35,18 @@ export function DomRenderEvent(rendered: Rendered): DomRenderEvent {
35
35
  export type HtmlRenderEvent = {
36
36
  readonly _tag: "html"
37
37
  readonly html: string
38
+ readonly done: boolean
38
39
  readonly valueOf: () => string
39
40
  }
40
41
 
41
42
  /**
42
43
  * @since 1.0.0
43
44
  */
44
- export function HtmlRenderEvent(html: string): HtmlRenderEvent {
45
+ export function HtmlRenderEvent(html: string, done: boolean): HtmlRenderEvent {
45
46
  return {
46
47
  _tag: "html",
47
48
  html,
49
+ done,
48
50
  valueOf: () => html
49
51
  }
50
52
  }
@@ -56,6 +58,13 @@ export function isRenderEvent(value: unknown): value is RenderEvent {
56
58
  return isTaggedObject(value) && (value._tag === "html" || value._tag === "dom")
57
59
  }
58
60
 
61
+ /**
62
+ * @since 1.0.0
63
+ */
64
+ export function isHtmlRenderEvent(value: unknown): value is HtmlRenderEvent {
65
+ return isTaggedObject(value) && value._tag === "html"
66
+ }
67
+
59
68
  function isTaggedObject(
60
69
  value: unknown
61
70
  ): value is Record<string, unknown> & { readonly _tag: unknown } {
@@ -0,0 +1,445 @@
1
+ /**
2
+ * The context in which templates are rendered within
3
+ * @since 1.0.0
4
+ */
5
+
6
+ import * as Context from "@typed/context"
7
+ import * as Idle from "@typed/fx/Idle"
8
+ import * as Effect from "effect/Effect"
9
+ import * as FiberRef from "effect/FiberRef"
10
+ import { dual } from "effect/Function"
11
+ import type * as Layer from "effect/Layer"
12
+ import * as Scope from "effect/Scope"
13
+
14
+ /**
15
+ * @since 1.0.0
16
+ */
17
+ export const DEFAULT_PRIORITY = 10
18
+
19
+ /**
20
+ * The context in which templates are rendered within
21
+ * @since 1.0.0
22
+ */
23
+ export const RenderQueue: Context.Tagged<RenderQueue, RenderQueue> = Context.Tagged<RenderQueue>(
24
+ "@typed/template/RenderQueue"
25
+ )
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ */
30
+ export const currentPriority: FiberRef.FiberRef<number> = FiberRef.unsafeMake(DEFAULT_PRIORITY)
31
+
32
+ /**
33
+ * @since 1.0.0
34
+ */
35
+ export interface RenderQueue {
36
+ readonly add: (part: unknown, task: () => void, priority: number) => Effect.Effect<void, never, Scope.Scope>
37
+ }
38
+
39
+ /**
40
+ * @since 1.0.0
41
+ */
42
+ export interface IdleRenderQueueOptions extends IdleRequestOptions {
43
+ readonly scope: Scope.Scope
44
+ }
45
+
46
+ /**
47
+ * @since 1.0.0
48
+ */
49
+ export const unsafeMakeIdleRenderQueue = ({ scope, ...options }: IdleRenderQueueOptions): RenderQueue =>
50
+ new IdleImpl(scope, options)
51
+
52
+ /**
53
+ * @since 1.0.0
54
+ */
55
+ export const unsafeMakeRafRenderQueue = (scope: Scope.Scope): RenderQueue => new RafImpl(scope)
56
+
57
+ /**
58
+ * @since 1.0.0
59
+ */
60
+ export const unsafeMakeMicrotaskRenderQueue = (scope: Scope.Scope): RenderQueue => new MicroTaskImpl(scope)
61
+
62
+ /**
63
+ * @since 1.0.0
64
+ */
65
+ export const unsafeMakeSyncRenderQueue = (): RenderQueue => new SyncImpl()
66
+
67
+ /**
68
+ * @since 1.0.0
69
+ */
70
+ export const idle = (options?: IdleRequestOptions): Layer.Layer<RenderQueue> =>
71
+ RenderQueue.scoped(
72
+ Effect.scopeWith((scope) => Effect.succeed(unsafeMakeIdleRenderQueue({ scope, ...options })))
73
+ )
74
+
75
+ /**
76
+ * @since 1.0.0
77
+ */
78
+ export const raf: Layer.Layer<RenderQueue> = RenderQueue.scoped(
79
+ Effect.scopeWith((scope) => Effect.succeed(unsafeMakeRafRenderQueue(scope)))
80
+ )
81
+
82
+ /**
83
+ * @since 1.0.0
84
+ */
85
+ export const microtask: Layer.Layer<RenderQueue> = RenderQueue.scoped(
86
+ Effect.scopeWith((scope) => Effect.succeed(unsafeMakeMicrotaskRenderQueue(scope)))
87
+ )
88
+
89
+ /**
90
+ * @since 1.0.0
91
+ */
92
+ export const sync: Layer.Layer<RenderQueue> = RenderQueue.layer(Effect.sync(unsafeMakeSyncRenderQueue))
93
+
94
+ const MICRO_TASK_END = DEFAULT_PRIORITY - 1
95
+ const RAF_END = DEFAULT_PRIORITY + 9
96
+ const IDLE_START = RAF_END + 1
97
+
98
+ /**
99
+ * @since 1.0.0
100
+ */
101
+ export const mixed = (options?: IdleRequestOptions): Layer.Layer<RenderQueue> =>
102
+ RenderQueue.scoped(Effect.gen(function*() {
103
+ const scope = yield* Effect.scope
104
+ const queues: Array<readonly [priorityRange: readonly [number, number], RenderQueue]> = [
105
+ [[-1, -1], new SyncImpl()],
106
+ [[0, MICRO_TASK_END], new MicroTaskImpl(scope)],
107
+ [[DEFAULT_PRIORITY, RAF_END], new RafImpl(scope)],
108
+ [[IDLE_START, Number.MAX_SAFE_INTEGER], new IdleImpl(scope, options)]
109
+ ]
110
+
111
+ return new MixedImpl(queues)
112
+ }))
113
+
114
+ /**
115
+ * @since 1.0.0
116
+ */
117
+ export const Priority = {
118
+ Sync: -1,
119
+ /**
120
+ * @example
121
+ * RenderPriority.MicroTask(0-9)
122
+ */
123
+ MicroTask: (priority: number) => Math.min(Math.max(0, priority), MICRO_TASK_END),
124
+ /**
125
+ * @example
126
+ * RenderPriority.Raf(0-9)
127
+ */
128
+ Raf: (priority: number) => Math.min(Math.max(DEFAULT_PRIORITY, DEFAULT_PRIORITY + priority), RAF_END),
129
+ /**
130
+ * @example
131
+ * RenderPriority.Idle(0-9)
132
+ */
133
+ Idle: (priority: number) => Math.max(IDLE_START, IDLE_START + priority)
134
+ } as const
135
+
136
+ class PriorityQueue {
137
+ tasks: Map<unknown, () => void> = new Map()
138
+ priority: Map<unknown, number> = new Map<unknown, number>()
139
+ priorities: Map<number, Set<unknown>> = new Map()
140
+
141
+ add(part: unknown, task: () => void, priority: number) {
142
+ this.tasks.set(part, task)
143
+ this.setPriority(part, priority)
144
+ }
145
+
146
+ get(part: unknown) {
147
+ return this.tasks.get(part)
148
+ }
149
+
150
+ delete(part: unknown) {
151
+ this.tasks.delete(part)
152
+ const priority = this.priority.get(part)
153
+ if (priority === undefined) {
154
+ return false
155
+ } else {
156
+ this.priorities.get(priority)?.delete(part)
157
+ this.priority.delete(part)
158
+ return true
159
+ }
160
+ }
161
+
162
+ get isEmpty() {
163
+ return this.priorities.size === 0
164
+ }
165
+
166
+ *entries() {
167
+ for (const priority of Array.from(this.priorities.keys()).sort((a, b) => a - b)) {
168
+ const parts = this.priorities.get(priority)!
169
+ this.priorities.delete(priority)
170
+ yield this.getTasks(parts)
171
+ }
172
+ }
173
+
174
+ private getTasks(parts: Set<unknown>) {
175
+ return Array.from(parts.values()).flatMap((part) => {
176
+ const task = this.tasks.get(part)
177
+ if (task === undefined) return []
178
+ this.tasks.delete(part)
179
+ this.priority.delete(part)
180
+ return [task]
181
+ })
182
+ }
183
+
184
+ private setPriority(task: unknown, priority: number) {
185
+ const current = this.priority.get(task)
186
+ if (current === undefined) {
187
+ this.priority.set(task, priority)
188
+ this.addTaskToPriority(task, priority)
189
+ return priority
190
+ } else if (priority < current) {
191
+ this.priorities.get(current)?.delete(task)
192
+ this.priority.set(task, priority)
193
+ this.addTaskToPriority(task, priority)
194
+ return priority
195
+ } else {
196
+ return current
197
+ }
198
+ }
199
+
200
+ private addTaskToPriority(task: unknown, priority: number) {
201
+ let set = this.priorities.get(priority)
202
+ if (set === undefined) {
203
+ set = new Set()
204
+ this.priorities.set(priority, set)
205
+ }
206
+ set.add(task)
207
+ }
208
+ }
209
+
210
+ abstract class BaseImpl implements RenderQueue {
211
+ queue = new PriorityQueue()
212
+ scheduled = false
213
+
214
+ constructor(readonly scope: Scope.Scope) {
215
+ this.add.bind(this)
216
+ }
217
+
218
+ add(part: unknown, task: () => void, priority: number) {
219
+ return Effect.suspend(() => {
220
+ this.queue.add(part, task, priority)
221
+
222
+ return Effect.zipRight(
223
+ Effect.addFinalizer(() =>
224
+ Effect.sync(() => {
225
+ const currentTask = this.queue.get(part)
226
+ // If the current task is still the same we'll delete it from the queue
227
+ if (currentTask === task) {
228
+ this.queue.delete(part)
229
+ }
230
+ })
231
+ ),
232
+ this.scheduleNextRun
233
+ )
234
+ })
235
+ }
236
+
237
+ scheduleNextRun = Effect.suspend(() => {
238
+ if (this.queue.isEmpty || this.scheduled) return Effect.void
239
+
240
+ this.scheduled = true
241
+
242
+ return this.run.pipe(
243
+ Scope.extend(this.scope),
244
+ Effect.forkIn(this.scope)
245
+ )
246
+ })
247
+
248
+ abstract run: Effect.Effect<void, never, Scope.Scope>
249
+
250
+ protected runTasks = (iterator: Iterator<Array<() => void>>) => {
251
+ const result = iterator.next()
252
+
253
+ if (result.done) return false
254
+ else {
255
+ for (const task of result.value) {
256
+ this.tryRunTask(task)
257
+ }
258
+
259
+ return true
260
+ }
261
+ }
262
+
263
+ protected tryRunTask = (task: () => void) => {
264
+ try {
265
+ task()
266
+ } catch (error) {
267
+ // TODO: We should probably be able to report this back to a template
268
+ console.error(error)
269
+ }
270
+ }
271
+ }
272
+
273
+ class IdleImpl extends BaseImpl implements RenderQueue {
274
+ constructor(
275
+ scope: Scope.Scope,
276
+ readonly options?: IdleRequestOptions
277
+ ) {
278
+ super(scope)
279
+ }
280
+
281
+ run: Effect.Effect<void, never, Scope.Scope> = Effect.suspend(() => {
282
+ return Effect.flatMap(
283
+ Idle.whenIdle(this.options),
284
+ (deadline) =>
285
+ Effect.suspend(() => {
286
+ const iterator = this.queue.entries()
287
+
288
+ while (Idle.shouldContinue(deadline) && this.runTasks(iterator)) {
289
+ // Continue
290
+ }
291
+
292
+ // If we have more work to do, schedule another run
293
+ if (!this.queue.isEmpty) {
294
+ return this.run
295
+ }
296
+
297
+ this.scheduled = false
298
+
299
+ return Effect.void
300
+ })
301
+ )
302
+ })
303
+ }
304
+
305
+ class RafImpl extends BaseImpl implements RenderQueue {
306
+ private _set: ((callback: FrameRequestCallback) => number) | typeof setTimeout
307
+ private _clear: (handle: number) => void
308
+
309
+ constructor(
310
+ readonly scope: Scope.Scope
311
+ ) {
312
+ super(scope)
313
+
314
+ const [set, clear] = typeof globalThis.requestAnimationFrame === "function"
315
+ ? [requestAnimationFrame.bind(globalThis), cancelAnimationFrame.bind(globalThis)]
316
+ : [setTimeout, clearTimeout]
317
+ this._set = set
318
+ this._clear = clear
319
+ }
320
+
321
+ run: Effect.Effect<void> = Effect.async((cb) => {
322
+ const id = this._set(() => {
323
+ this.runAllTasks()
324
+ return cb(Effect.void)
325
+ })
326
+ return Effect.sync(() => {
327
+ this.scheduled = false
328
+ this._clear(id)
329
+ })
330
+ })
331
+
332
+ private runAllTasks = () => {
333
+ const iterator = this.queue.entries()
334
+ while (this.runTasks(iterator)) {
335
+ // Continue
336
+ }
337
+
338
+ this.scheduled = false
339
+ }
340
+ }
341
+
342
+ const noOp = () => void 0
343
+
344
+ class MicroTaskImpl extends BaseImpl implements RenderQueue {
345
+ private _set: { (callback: VoidFunction): void; (callback: () => void): void } | typeof setTimeout
346
+ private _clear:
347
+ | { (id: number | undefined): void; (timeoutId: string | number | NodeJS.Timeout | undefined): void }
348
+ | (() => undefined)
349
+
350
+ constructor(
351
+ readonly scope: Scope.Scope
352
+ ) {
353
+ super(scope)
354
+
355
+ const [set, clear] = typeof globalThis.queueMicrotask === "function"
356
+ ? [globalThis.queueMicrotask.bind(globalThis), noOp]
357
+ : [setTimeout, clearTimeout]
358
+ this._set = set
359
+ this._clear = clear
360
+ }
361
+
362
+ run: Effect.Effect<void> = Effect.async((cb) => {
363
+ const id = this._set(() => {
364
+ this.runAllTasks()
365
+ return cb(Effect.void)
366
+ })
367
+ return Effect.sync(() => {
368
+ this.scheduled = false
369
+ id && this._clear(id)
370
+ })
371
+ })
372
+
373
+ private runAllTasks = () => {
374
+ const iterator = this.queue.entries()
375
+ while (this.runTasks(iterator)) {
376
+ // Continue
377
+ }
378
+
379
+ this.scheduled = false
380
+ }
381
+ }
382
+
383
+ class SyncImpl implements RenderQueue {
384
+ constructor() {
385
+ this.add.bind(this)
386
+ }
387
+
388
+ add(_: unknown, task: () => void) {
389
+ return Effect.sync(task)
390
+ }
391
+ }
392
+
393
+ class MixedImpl implements RenderQueue {
394
+ private _priorityCache = new Map<number, RenderQueue>()
395
+
396
+ constructor(
397
+ readonly queues: Array<readonly [priorityRange: readonly [number, number], RenderQueue]>
398
+ ) {
399
+ this.queues.sort(([a], [b]) => a[0] - b[0])
400
+
401
+ this.add.bind(this)
402
+ }
403
+
404
+ add(part: unknown, task: () => void, priority: number) {
405
+ let queue = this.getQueueForPriority(priority)
406
+
407
+ if (queue === undefined) {
408
+ queue = this.queues[this.queues.length - 1][1]
409
+ }
410
+
411
+ return queue.add(part, task, priority)
412
+ }
413
+
414
+ private getQueueForPriority(priority: number) {
415
+ if (this._priorityCache.has(priority)) {
416
+ return this._priorityCache.get(priority)!
417
+ }
418
+
419
+ const q = this.queues.find(([range]) => priority >= range[0] && priority <= range[1])
420
+
421
+ if (q) {
422
+ this._priorityCache.set(priority, q[1])
423
+ return q[1]
424
+ }
425
+
426
+ return q
427
+ }
428
+ }
429
+
430
+ /**
431
+ * @since 1.0.0
432
+ */
433
+ export const withCurrentPriority = <A, E, R>(f: (priority: number) => Effect.Effect<A, E, R>) =>
434
+ Effect.flatMap(FiberRef.get(currentPriority), f)
435
+
436
+ /**
437
+ * @since 1.0.0
438
+ */
439
+ export const usingCurrentPriority: {
440
+ (priority: number): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
441
+ <A, E, R>(effect: Effect.Effect<A, E, R>, priority: number): Effect.Effect<A, E, R>
442
+ } = dual(
443
+ 2,
444
+ <A, E, R>(effect: Effect.Effect<A, E, R>, priority: number) => Effect.locally(effect, currentPriority, priority)
445
+ )
@@ -8,6 +8,7 @@ import type { Scope } from "effect/Scope"
8
8
  import type { Placeholder } from "./Placeholder.js"
9
9
  import type { Renderable } from "./Renderable.js"
10
10
  import type { RenderEvent } from "./RenderEvent.js"
11
+ import type { RenderQueue } from "./RenderQueue.js"
11
12
 
12
13
  /**
13
14
  * @since 1.0.0
@@ -16,7 +17,7 @@ export interface RenderTemplate {
16
17
  <Values extends ReadonlyArray<Renderable<any, any>>>(
17
18
  templateStrings: TemplateStringsArray,
18
19
  values: Values
19
- ): Fx.Fx<RenderEvent, Placeholder.Error<Values[number]>, Scope | Placeholder.Context<Values[number]>>
20
+ ): Fx.Fx<RenderEvent, Placeholder.Error<Values[number]>, Scope | RenderQueue | Placeholder.Context<Values[number]>>
20
21
  }
21
22
 
22
23
  /**
@@ -34,6 +35,10 @@ export const RenderTemplate: Context.Tagged<RenderTemplate, RenderTemplate> = Co
34
35
  export function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
35
36
  template: TemplateStringsArray,
36
37
  ...values: Values
37
- ): Fx.Fx<RenderEvent, Placeholder.Error<Values[number]>, RenderTemplate | Scope | Placeholder.Context<Values[number]>> {
38
+ ): Fx.Fx<
39
+ RenderEvent,
40
+ Placeholder.Error<Values[number]>,
41
+ RenderTemplate | RenderQueue | Scope | Placeholder.Context<Values[number]>
42
+ > {
38
43
  return Fx.fromFxEffect(RenderTemplate.with((render) => render(template, values)))
39
44
  }