@webqit/webflo 0.11.61 → 0.20.2-next.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 (235) hide show
  1. package/.github/FUNDING.yml +12 -0
  2. package/.github/workflows/publish.yml +48 -0
  3. package/.gitignore +2 -0
  4. package/LICENSE +2 -2
  5. package/README.md +71 -2050
  6. package/package.json +28 -13
  7. package/site/-/_.md +139 -0
  8. package/site/-/docs.old.md +2010 -0
  9. package/site/.vitepress/cache/deps/@braintree_sanitize-url 2.js +93 -0
  10. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  11. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js 2.map +7 -0
  12. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  13. package/site/.vitepress/cache/deps/_metadata 2.json +85 -0
  14. package/site/.vitepress/cache/deps/_metadata.json +85 -0
  15. package/site/.vitepress/cache/deps/chunk-BUSYA2B4 2.js +9 -0
  16. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  17. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js 2.map +7 -0
  18. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  19. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK 2.js +9719 -0
  20. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js +9719 -0
  21. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js 2.map +7 -0
  22. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js.map +7 -0
  23. package/site/.vitepress/cache/deps/chunk-QAXAIFA7 2.js +12705 -0
  24. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js +12705 -0
  25. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js 2.map +7 -0
  26. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js.map +7 -0
  27. package/site/.vitepress/cache/deps/cytoscape 2.js +30278 -0
  28. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent 2.js +4710 -0
  29. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  30. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js 2.map +7 -0
  31. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  32. package/site/.vitepress/cache/deps/cytoscape.js +30278 -0
  33. package/site/.vitepress/cache/deps/cytoscape.js 2.map +7 -0
  34. package/site/.vitepress/cache/deps/cytoscape.js.map +7 -0
  35. package/site/.vitepress/cache/deps/dayjs 2.js +285 -0
  36. package/site/.vitepress/cache/deps/dayjs.js +285 -0
  37. package/site/.vitepress/cache/deps/dayjs.js 2.map +7 -0
  38. package/site/.vitepress/cache/deps/dayjs.js.map +7 -0
  39. package/site/.vitepress/cache/deps/debug 2.js +453 -0
  40. package/site/.vitepress/cache/deps/debug.js +453 -0
  41. package/site/.vitepress/cache/deps/debug.js 2.map +7 -0
  42. package/site/.vitepress/cache/deps/debug.js.map +7 -0
  43. package/site/.vitepress/cache/deps/package 2.json +3 -0
  44. package/site/.vitepress/cache/deps/package.json +3 -0
  45. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api 2.js +4507 -0
  46. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  47. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js 2.map +7 -0
  48. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  49. package/site/.vitepress/cache/deps/vitepress___@vueuse_core 2.js +584 -0
  50. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  51. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js 2.map +7 -0
  52. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  53. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap 2.js +1166 -0
  54. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1166 -0
  55. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js 2.map +7 -0
  56. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  57. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js 2.js +1667 -0
  58. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  59. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js 2.map +7 -0
  60. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  61. package/site/.vitepress/cache/deps/vitepress___minisearch 2.js +1815 -0
  62. package/site/.vitepress/cache/deps/vitepress___minisearch.js +1815 -0
  63. package/site/.vitepress/cache/deps/vitepress___minisearch.js 2.map +7 -0
  64. package/site/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  65. package/site/.vitepress/cache/deps/vue 2.js +344 -0
  66. package/site/.vitepress/cache/deps/vue.js +344 -0
  67. package/site/.vitepress/cache/deps/vue.js 2.map +7 -0
  68. package/site/.vitepress/cache/deps/vue.js.map +7 -0
  69. package/site/.vitepress/config.ts +147 -0
  70. package/site/.vitepress/theme/custom.css +50 -0
  71. package/site/.vitepress/theme/index.ts +6 -0
  72. package/site/api/webflo-fetch/FormData.md +0 -0
  73. package/site/api/webflo-fetch/Headers.md +0 -0
  74. package/site/api/webflo-fetch/LiveResponse.md +0 -0
  75. package/site/api/webflo-fetch/Request.md +0 -0
  76. package/site/api/webflo-fetch/Response.md +0 -0
  77. package/site/api/webflo-fetch/fetch.md +0 -0
  78. package/site/api/webflo-routing/HttpCookies.md +0 -0
  79. package/site/api/webflo-routing/HttpEvent/respondWith.md +1 -0
  80. package/site/api/webflo-routing/HttpEvent/waitUntil.md +1 -0
  81. package/site/api/webflo-routing/HttpEvent/waitUntilNavigate.md +1 -0
  82. package/site/api/webflo-routing/HttpEvent.md +30 -0
  83. package/site/api/webflo-routing/HttpSession.md +0 -0
  84. package/site/api/webflo-routing/HttpState.md +0 -0
  85. package/site/api/webflo-routing/HttpUser.md +0 -0
  86. package/site/api/webflo-routing/handler/fetch.md +42 -0
  87. package/site/api/webflo-routing/handler/next.md +54 -0
  88. package/site/api/webflo-routing/handler.md +119 -0
  89. package/site/api.md +26 -0
  90. package/site/contributing.md +16 -0
  91. package/site/docs/advanced/lifecycles.md +20 -0
  92. package/site/docs/advanced/redirects.md +0 -0
  93. package/site/docs/advanced/routing.md +1 -0
  94. package/site/docs/advanced.md +9 -0
  95. package/site/docs/concepts/realtime.md +637 -0
  96. package/site/docs/concepts/rendering.md +60 -0
  97. package/site/docs/concepts/request-response.md +47 -0
  98. package/site/docs/concepts/routing.md +656 -0
  99. package/site/docs/concepts/state.md +44 -0
  100. package/site/docs/concepts/templates.md +48 -0
  101. package/site/docs/concepts.md +97 -0
  102. package/site/docs/getting-started.md +378 -0
  103. package/site/docs/tech-stack.md +56 -0
  104. package/site/docs.md +100 -0
  105. package/site/examples/pwa.md +10 -0
  106. package/site/examples/web.md +11 -0
  107. package/site/examples.md +10 -0
  108. package/site/faq.md +13 -0
  109. package/site/guides/guide-auth.md +13 -0
  110. package/site/guides/guide-file-upload.md +11 -0
  111. package/site/guides/guide-service-worker.md +10 -0
  112. package/site/guides/tutorial-1-todo.md +24 -0
  113. package/site/guides.md +15 -0
  114. package/site/index.md +39 -0
  115. package/site/public/img/brand/logo-670x670.png +0 -0
  116. package/site/recipes/realtime.md +11 -0
  117. package/site/recipes/streaming.md +15 -0
  118. package/site/reference/cli.md +11 -0
  119. package/site/reference/config.md +13 -0
  120. package/site/reference/tools.md +9 -0
  121. package/src/Context.js +3 -11
  122. package/src/config-pi/deployment/Env.js +6 -19
  123. package/src/config-pi/deployment/Layout.js +11 -3
  124. package/src/config-pi/runtime/Client.js +40 -48
  125. package/src/config-pi/runtime/Server.js +52 -20
  126. package/src/config-pi/runtime/client/Worker.js +22 -20
  127. package/src/config-pi/static/Init.js +57 -0
  128. package/src/config-pi/static/index.js +2 -0
  129. package/src/deployment-pi/origins/index.js +1 -1
  130. package/src/deployment-pi/util.js +161 -0
  131. package/src/index.js +3 -9
  132. package/src/init-pi/index.js +117 -0
  133. package/src/init-pi/templates/pwa/app/handler.server.js +8 -0
  134. package/src/init-pi/templates/pwa/app/page.html +7 -0
  135. package/src/init-pi/templates/pwa/package.json +19 -0
  136. package/src/init-pi/templates/pwa/public/assets/app.css +16 -0
  137. package/src/init-pi/templates/pwa/public/index.html +39 -0
  138. package/src/init-pi/templates/pwa/public/manifest.json +29 -0
  139. package/src/init-pi/templates/web/app/handler.server.js +8 -0
  140. package/src/init-pi/templates/web/app/page.html +7 -0
  141. package/src/init-pi/templates/web/package.json +19 -0
  142. package/src/init-pi/templates/web/public/assets/app.css +16 -0
  143. package/src/init-pi/templates/web/public/index.html +39 -0
  144. package/src/runtime-pi/WebfloRuntime.js +350 -0
  145. package/src/runtime-pi/index.js +3 -10
  146. package/src/runtime-pi/webflo-client/ClientSideCookies.js +17 -0
  147. package/src/runtime-pi/webflo-client/ClientSideWorkport.js +63 -0
  148. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +213 -0
  149. package/src/runtime-pi/webflo-client/WebfloClient.js +500 -0
  150. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +206 -0
  151. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +113 -0
  152. package/src/runtime-pi/webflo-client/WebfloSubClient.js +118 -0
  153. package/src/runtime-pi/webflo-client/index.js +17 -0
  154. package/src/runtime-pi/webflo-client/webflo-codegen.js +469 -0
  155. package/src/runtime-pi/webflo-client/webflo-devmode.js +243 -0
  156. package/src/runtime-pi/webflo-client/webflo-embedded.js +50 -0
  157. package/src/runtime-pi/webflo-fetch/LiveResponse.js +437 -0
  158. package/src/runtime-pi/webflo-fetch/cookies.js +10 -0
  159. package/src/runtime-pi/webflo-fetch/fetch.js +16 -0
  160. package/src/runtime-pi/webflo-fetch/formdata.js +54 -0
  161. package/src/runtime-pi/webflo-fetch/headers.js +151 -0
  162. package/src/runtime-pi/webflo-fetch/index.js +5 -0
  163. package/src/runtime-pi/webflo-fetch/message.js +49 -0
  164. package/src/runtime-pi/webflo-fetch/request.js +62 -0
  165. package/src/runtime-pi/webflo-fetch/response.js +110 -0
  166. package/src/runtime-pi/webflo-fetch/util.js +28 -0
  167. package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +10 -0
  168. package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +26 -0
  169. package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +87 -0
  170. package/src/runtime-pi/webflo-messaging/WQMessagePort.js +38 -0
  171. package/src/runtime-pi/webflo-messaging/WQRelayPort.js +47 -0
  172. package/src/runtime-pi/webflo-messaging/WQSockPort.js +113 -0
  173. package/src/runtime-pi/webflo-messaging/WQStarPort.js +104 -0
  174. package/src/runtime-pi/webflo-messaging/wq-message-port.js +404 -0
  175. package/src/runtime-pi/webflo-routing/HttpCookies.js +42 -0
  176. package/src/runtime-pi/webflo-routing/HttpEvent.js +112 -0
  177. package/src/runtime-pi/webflo-routing/HttpSession.js +11 -0
  178. package/src/runtime-pi/webflo-routing/HttpState.js +153 -0
  179. package/src/runtime-pi/webflo-routing/HttpUser.js +54 -0
  180. package/src/runtime-pi/webflo-routing/WebfloRouter.js +245 -0
  181. package/src/runtime-pi/webflo-server/ServerSideCookies.js +19 -0
  182. package/src/runtime-pi/webflo-server/ServerSideSession.js +38 -0
  183. package/src/runtime-pi/webflo-server/WebfloServer.js +937 -0
  184. package/src/runtime-pi/webflo-server/index.js +11 -0
  185. package/src/runtime-pi/webflo-server/messaging/Client.js +27 -0
  186. package/src/runtime-pi/webflo-server/messaging/ClientRequestRealtime.js +50 -0
  187. package/src/runtime-pi/webflo-server/messaging/Clients.js +25 -0
  188. package/src/runtime-pi/webflo-server/webflo-devmode.js +326 -0
  189. package/src/runtime-pi/{client → webflo-url}/Url.js +27 -76
  190. package/src/runtime-pi/webflo-url/index.js +1 -0
  191. package/src/runtime-pi/webflo-url/urlpattern.js +38 -0
  192. package/src/runtime-pi/{util-url.js → webflo-url/util.js} +5 -43
  193. package/src/runtime-pi/webflo-url/xURL.js +94 -0
  194. package/src/runtime-pi/webflo-worker/WebfloWorker.js +234 -0
  195. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +19 -0
  196. package/src/runtime-pi/webflo-worker/WorkerSideWorkport.js +18 -0
  197. package/src/runtime-pi/webflo-worker/index.js +11 -0
  198. package/src/services-pi/index.js +2 -0
  199. package/src/services-pi/push/index.js +23 -0
  200. package/src/util.js +10 -0
  201. package/src/{webflo.js → webflo-cli.js} +4 -4
  202. package/src/runtime-pi/Application.js +0 -29
  203. package/src/runtime-pi/Cookies.js +0 -82
  204. package/src/runtime-pi/HttpEvent.js +0 -107
  205. package/src/runtime-pi/Router.js +0 -130
  206. package/src/runtime-pi/Runtime.js +0 -21
  207. package/src/runtime-pi/client/Application.js +0 -76
  208. package/src/runtime-pi/client/Context.js +0 -7
  209. package/src/runtime-pi/client/Router.js +0 -48
  210. package/src/runtime-pi/client/Runtime.js +0 -525
  211. package/src/runtime-pi/client/Workport.js +0 -190
  212. package/src/runtime-pi/client/createStorage.js +0 -58
  213. package/src/runtime-pi/client/generate.js +0 -481
  214. package/src/runtime-pi/client/index.js +0 -21
  215. package/src/runtime-pi/client/worker/Application.js +0 -44
  216. package/src/runtime-pi/client/worker/Context.js +0 -7
  217. package/src/runtime-pi/client/worker/Runtime.js +0 -275
  218. package/src/runtime-pi/client/worker/Workport.js +0 -78
  219. package/src/runtime-pi/client/worker/index.js +0 -21
  220. package/src/runtime-pi/server/Application.js +0 -101
  221. package/src/runtime-pi/server/Context.js +0 -16
  222. package/src/runtime-pi/server/Router.js +0 -159
  223. package/src/runtime-pi/server/Runtime.js +0 -558
  224. package/src/runtime-pi/server/index.js +0 -21
  225. package/src/runtime-pi/util-http.js +0 -86
  226. package/src/runtime-pi/xFormData.js +0 -24
  227. package/src/runtime-pi/xHeaders.js +0 -146
  228. package/src/runtime-pi/xRequest.js +0 -46
  229. package/src/runtime-pi/xRequestHeaders.js +0 -109
  230. package/src/runtime-pi/xResponse.js +0 -33
  231. package/src/runtime-pi/xResponseHeaders.js +0 -117
  232. package/src/runtime-pi/xURL.js +0 -105
  233. package/src/runtime-pi/xfetch.js +0 -23
  234. package/src/runtime-pi/xxHttpMessage.js +0 -102
  235. package/src/static-pi/index.js +0 -11
@@ -0,0 +1,637 @@
1
+ # Webflo Realtime
2
+
3
+ Realtime experiences — collaborative documents, live dashboards, chat windows, streaming progress bars — are what make modern web apps feel _alive_.
4
+ They’re also the difference between form submissions that make you wait and form submissions that feel like a live conversation.
5
+
6
+ Webflo apps work this way by default.
7
+
8
+ They're powered by a rich set of APIs designed for every use case:
9
+
10
+ ```js
11
+ return new LiveResponse(data, { done: false });
12
+ ```
13
+
14
+ ```js
15
+ event.respondWith({ progress: 10 }, { done: false });
16
+ ```
17
+
18
+ ```js
19
+ event.waitUntilNavigate();
20
+ ```
21
+
22
+ ```js
23
+ event.client.addEventListener('message', handle);
24
+ event.client.postMessage({ message: 'You seem to be typing something.' });
25
+ ```
26
+
27
+ ```js
28
+ const result = await event.user.confirm({ message: 'Should we proceed?' });
29
+ const result = await event.user.prompt({ message: 'What is your name?' });
30
+ ```
31
+
32
+ ```js
33
+ const { channel, leave } = event.client.enterChannel('test-channel');
34
+ ```
35
+
36
+ ## The Traditional Setup
37
+
38
+ Historically, building realtime applications required a significant amount of setup.
39
+ It extended beyond the simple request–response model and into a world of parallel WebSocket connections and streaming protocols.
40
+ It presented two protocols, two lifecycles, and an awkward choreography between them — keeping them in sync, closing them both cleanly, and rebuilding them on every reconnect.
41
+
42
+ ```mermaid
43
+ flowchart LR
44
+ subgraph Browser
45
+ B1[HTTP Client]
46
+ B2[WebSocket Client]
47
+ end
48
+ subgraph Server
49
+ S1[HTTP Handler]
50
+ S2[WebSocket Handler]
51
+ end
52
+ B1 <--> |HTTP request / response| S1
53
+ B2 <---> |Realtime messages| S2
54
+ ```
55
+
56
+ This works — but it’s hardly effortless.
57
+ That’s requests on one channel, updates on another.
58
+
59
+ ## The Webflo Model
60
+
61
+ In Webflo, this behavior is built in.
62
+ The same request that starts over HTTP can seamlessly continue as a live, bidirectional exchange — with no moving parts or extra line of code at the application layer.
63
+
64
+ This is like the entire architecture above collapsed into one: an **auto-upgrading connection** that can be used for many different things.
65
+
66
+ ```mermaid
67
+ flowchart LR
68
+ subgraph Browser
69
+ B1[Webflo Client]
70
+ end
71
+ subgraph Server
72
+ S1[Webflo Handler]
73
+ end
74
+ B1 <--> |Auto-upgrading
75
+ HTTP request / response| S1
76
+ ```
77
+
78
+ This is the same HTTP request / response lifecycle, only **with "live" mode built-in**.
79
+
80
+ ## How It Works
81
+
82
+ In Webflo, you simply write route handlers that can talk for as long as they need to — without worrying about the transport layer or the lifecycle of the connection.
83
+
84
+ ```js
85
+ export async function GET(event, next) {
86
+ if (next.stepname) return await next(); // The conventional delegation line
87
+
88
+ event.respondWith({ step: 1 }, { done: false });
89
+ await new Promise((r) => setTimeout(r, 500));
90
+ event.respondWith({ step: 2 });
91
+ }
92
+ ```
93
+
94
+ Here, the browser first renders `{ step: 1 }`, then `{ step: 2 }` half a second later — all within the same request.
95
+
96
+ When a handler continues sending responses or messages, Webflo transparently upgrades the underlying connection and handles the lifecycle behind the scenes.
97
+
98
+ ### What Happens
99
+
100
+ 1. The first response is sent normally over HTTP.
101
+ 2. Webflo detects that the handler hasn’t returned yet.
102
+ 3. It silently upgrades the connection, tagging the initial response with a header that tells the client to connect in the background.
103
+ 4. The client renders the first data and immediately joins the background channel.
104
+ 5. A two-way line now exists — and either side can send or receive messages.
105
+ 6. The handler pushes its next response, this time, it's delivered via the upgraded connection.
106
+ 7. The client renders it instantly — replacing the previous rendered state.
107
+ 8. Webflo closes automatically when the handler finishes or the user navigates away.
108
+
109
+ ::: info Further Details
110
+
111
+ - Full handshake details are covered in **[Appendix A – The Realtime Lifecycle](#appendix-a-–-the-realtime-lifecycle)**.
112
+ :::
113
+
114
+ ## The Two Ends of the Line
115
+
116
+ A realtime conversation always has two sides — _Port A_ and _Port B_.
117
+
118
+ ### _Port A_: Upstream Route Handlers
119
+
120
+ Realtime conversations are initiated by route handlers — as part of responses to HTTP requests.
121
+ These route handlers function as _Port A_.
122
+
123
+ The **primary** realtime API here is `event.client` — a Webflo `MessagePort` supporting things like: `.postMessage()`, `.addEventListener()`, etc.
124
+
125
+ ```js
126
+ export default async function (event) {
127
+ event.client.postMessage({ hello: 'world' });
128
+ }
129
+ ```
130
+
131
+ `event.client` is the API that other APIs like `event.respondWith()`, `event.user.prompt()`, etc. use under the hood.
132
+
133
+ ### _Port B_: Downstream Clients
134
+
135
+ Realtime conversations are received downstream as part of responses to an initial HTTP request.
136
+ These HTTP clients or handlers are the _Port B_.
137
+
138
+ The primary realtime API here is `liveResponse.background` — the same Webflo `MessagePort` interface as `event.client`, supporting things like: `.postMessage()`, `.addEventListener()`, etc.
139
+
140
+ ```js
141
+ const response = await fetch('https://api.example.com/data');
142
+ const liveResponse = LiveResponse.from(response);
143
+
144
+ liveResponse.background.addEventListener('message', (message) => {
145
+ console.log(message);
146
+ });
147
+ ```
148
+
149
+ Typical Port B clients in Webflo are:
150
+
151
+ - The App UI
152
+ - Intermediate Route Handlers
153
+
154
+ ::: info LiveResponse
155
+ `LiveResponse` is the "live" version of the standard `Response` API.
156
+ It extends the native `Response` object with a live body, headers, and status — all of which can be mutated in-place.
157
+ :::
158
+
159
+ #### The App UI
160
+
161
+ Every Webflo SPA has a client-side Webflo instance — `window.webqit.app`. It mounts via the _client_ script that Webflo injects into the page at build time.
162
+
163
+ ```html
164
+ <script src="/app.js"></script>
165
+ ```
166
+
167
+ This client manages in-app navigations, turning them into seamless request–response flows — becoming a typical _Port B_ in the realtime system. In each of those flows, the client occupies the top-most end of the line. It receives everything that handlers send as part of a response, applies live updates to the document, and manages any interactive exchanges.
168
+
169
+ The applicable realtime API here is `window.webqit.app.background` (`app.background`, for short) — an extension of Webflo `MessagePort` supporting things like: `.postMessage()`, `.addEventListener()`, etc.
170
+
171
+ The `app.background` port is also a _multi-port_ hub housing all _active_ background activities across page navigations.
172
+ Every interaction on this API is thus an interaction over all active realtime conversations.
173
+
174
+ ```js
175
+ app.background.addEventListener('message', (message) => {
176
+ console.log(message);
177
+ });
178
+ ```
179
+
180
+ ::: info App-Level Background Activities
181
+ It is possible to listen to all background activities in the app via `app.background`. We'll use this API to drive a custom dialog UI just ahead.
182
+ :::
183
+
184
+ #### Intermediate Route Handlers
185
+
186
+ With Webflo's realtime capabilities being an **upgrade** to the existing HTTP request/response model,
187
+ realtime connections can be intercepted at _any point_ along that flow just as routing in Webflo can happen at various layers along that flow. A client-side handler or service-worker-side handler, for example, can intercept the conversation at their level.
188
+
189
+ The background connection comes along with the upstream response obtained via `next()` or `fetch()`.
190
+
191
+ ```js
192
+ export default async function (event, next) {
193
+ if (next.stepname) return await next(); // The conventional delegation line
194
+
195
+ const response = await next();
196
+ const liveResponse = LiveResponse.from(response);
197
+ liveResponse.background.addEventListener('message', (message) => {
198
+ console.log(message);
199
+ });
200
+
201
+ return liveResponse;
202
+ }
203
+ ```
204
+
205
+ This intermediate handler essentially talks to the upstream via `liveResponse.background` (the _Port B_) while letting the same response flow downstream.
206
+
207
+ The handler’s own `event.client` port remains its own _Port A_ for communicating with the downstream.
208
+
209
+ ---
210
+
211
+ ::: tip
212
+ While not explicitly mentioned, external Webflo servers are just as accessible and interactive as the local server. A server-to-server interaction, for example, is just a matter of `LiveResponse.from(await fetch('https://api.example.com/data'))`.
213
+ :::
214
+
215
+ ## Entering Background Mode
216
+
217
+ A handler **enters background mode** when it responds to a request interactively as any of the below scenarios.
218
+ Webflo automatically upgrades the connection to a realtime connection — initiating the _handshake sequence_ with the client and keeping the communication open until _termination_ is triggered — as in [the Realtime Lifecycle](#appendix-a-–-the-realtime-lifecycle).
219
+
220
+ ### _Handler sends a message at any point in its lifecycle_
221
+
222
+ A handler may send a message at any point in its lifecycle by either:
223
+
224
+ 1. calling `event.client.postMessage()`:
225
+
226
+ ```js
227
+ event.client.postMessage({ progress: 10 });
228
+ ```
229
+
230
+ 2. or achieving the same through higher-level APIs like `event.user.confirm()`:
231
+
232
+ ```js
233
+ const result = await event.user.confirm({ message: 'Are you sure?' });
234
+ ```
235
+
236
+ ### _Handler returns a response and explicitly marks it **not done**_
237
+
238
+ A handler may return a response and explicitly mark it **not done** by calling `event.respondWith()` (or returning `LiveResponse`) with `{ done: false }`:
239
+
240
+ ```js
241
+ event.respondWith(data, { done: false });
242
+ ```
243
+
244
+ ```js
245
+ return new LiveResponse(data, { done: false });
246
+ ```
247
+
248
+ ### _Handler holds down the event lifecycle via promises_
249
+
250
+ A handler may hold down the event lifecycle via promises by either:
251
+
252
+ 1. explicitly calling `event.waitUntil()` or `event.waitUntilNavigate()` before returning a response:
253
+
254
+ ```js
255
+ event.waitUntilNavigate();
256
+ event.respondWith(data);
257
+ ```
258
+
259
+ ```js
260
+ event.waitUntil(new Promise(() => {}));
261
+ return data;
262
+ ```
263
+
264
+ 2. or calling `event.respondWith()` (or returning `LiveResponse`) with a promise that resolves to a response:
265
+
266
+ ```js
267
+ event.respondWith(
268
+ new Promise((resolve) => {
269
+ setTimeout(() => {
270
+ resolve(data);
271
+ }, 1000);
272
+ })
273
+ );
274
+ ```
275
+
276
+ 3. or calling `event.respondWith()` (or returning `LiveResponse`) with an async callback as second argument:
277
+
278
+ ```js
279
+ event.respondWith(data, async ($data /* reactive copy of data */) => {
280
+ await new Promise((resolve) => setTimeout(resolve, 1000));
281
+ $data.someProp = 'someValue';
282
+ await new Promise((resolve) => setTimeout(resolve, 1000));
283
+ $data.someProp = 'someOtherValue';
284
+ });
285
+ ```
286
+
287
+ ### _Handler returns a `Generator` object_
288
+
289
+ A handler may return a `Generator` object by either:
290
+
291
+ 1. being itself a [`generator function declaration`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*):
292
+
293
+ ```js
294
+ export default async function* (event) {
295
+ yield data1;
296
+ yield new Response(data2);
297
+ yield new LiveResponse(null, { headers: { Location: '/' } });
298
+ }
299
+ ```
300
+
301
+ 2. or returning a [`Generator` interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) as value:
302
+
303
+ ```js
304
+ export default async function (event) {
305
+ return {
306
+ next: async () => ({ done: true }),
307
+ return: async () => undefined,
308
+ throw: async () => undefined,
309
+ };
310
+ }
311
+ ```
312
+
313
+ ### _Handler returns a `State` object_
314
+
315
+ A handler may return a `State` object by declaring a `live` function:
316
+
317
+ ```js
318
+ export default async live function(event) {
319
+ const data = { progress: 0 };
320
+
321
+ setInterval(() => {
322
+ data.progress += 10;
323
+ }, 1000);
324
+
325
+ return data;
326
+ }
327
+ ```
328
+
329
+ The `live` keyword is a shorthand for defining a quantum function that returns a `State` object.
330
+
331
+ ---
332
+
333
+ Webflo knows to switch the connection to background mode in all the above cases.
334
+
335
+ ::: tip In Practice
336
+
337
+ - Entering or not entering background mode is often not something you need to be conscious of.
338
+ - A handler just happens to be interactive or not interactive depending on request types and how they lend themselves to be handled.
339
+ - **Webflo simply fulfills the intent of however a handler works**.
340
+ :::
341
+
342
+ ## Real-world Examples
343
+
344
+ ### Live Responses
345
+
346
+ #### Example — Replace state
347
+
348
+ This handler returns a `LiveResponse` whose body starts as `{ step: 1 }` and is replaced with `{ step: 2 }` after ~200ms; the UI updates automatically.
349
+
350
+ **_Result_:** The page first renders step: 1, then updates to step: 2 after ~200ms.
351
+
352
+ ```js
353
+ import { LiveResponse } from '@webqit/webflo';
354
+ ```
355
+
356
+ ```js
357
+ export async function GET(event, next) {
358
+ if (next.stepname) return await next(); // The conventional delegation line
359
+
360
+ const res = LiveResponse.from({ step: 1 }, { done: false });
361
+ setTimeout(() => res.replaceWith({ step: 2 }), 200); // [!code highlight]
362
+
363
+ return res;
364
+ }
365
+ ```
366
+
367
+ #### Example — Mutate state in place
368
+
369
+ This handler returns a skeleton object immediately for fast page load and then builds the object tree progressively as data arrives; the UI reflects every step of the object's construction.
370
+
371
+ **_Result_:** The UI renders a skeleton first, then progressively fills in as the object tree is built from the server.
372
+
373
+ ```js
374
+ import { Observer } from '@webqit/observer';
375
+ ```
376
+
377
+ ```js
378
+ export async function GET(event, next) {
379
+ if (next.stepname) return await next(); // The conventional delegation line
380
+
381
+ const data = {
382
+ menu: [
383
+ { name: 'Home', href: '/' },
384
+ { name: 'About', href: '/about' },
385
+ { name: 'Contact', href: '/contact' },
386
+ ],
387
+ content: {
388
+ header: 'Loading...',
389
+ body: [],
390
+ },
391
+ };
392
+
393
+ event.waitUntil(
394
+ new Promise(async (resolve) => {
395
+ const someData = await fetch('https://api.example.com/data');
396
+ Observer.set(data.content, 'header', someData.header);
397
+
398
+ Observer.proxy(data.content.body).push(
399
+ { text: 'Inventory 1:' + someData.items[0] },
400
+ { text: 'Inventory 2:' + someData.items[1] }
401
+ );
402
+
403
+ const someData2 = await fetch('https://api.example.com/data2');
404
+ Observer.proxy(data.content.body).push(
405
+ { text: 'Inventory 3:' + someData2.items[0] },
406
+ { text: 'Inventory 4:' + someData2.items[1] }
407
+ );
408
+
409
+ resolve();
410
+ })
411
+ );
412
+
413
+ return data;
414
+ }
415
+ ```
416
+
417
+ #### Example — Live database results (LinkedQL)
418
+
419
+ This handler responds with a live posts list from the database using [LinkedQL](https://github.com/linked-db/linked-ql); as upstream data changes, `rows` update continuously.
420
+
421
+ **_Result_:** On the UI, the list grows live as new posts are added upstream.
422
+
423
+ ```js
424
+ export async function GET(event, next) {
425
+ if (next.stepname) return await next(); // The conventional delegation line
426
+
427
+ const result = await client.query(
428
+ `SELECT * FROM posts WHERE date_created > '2023-01-01'`,
429
+ { live: true, signal: event.signal }
430
+ );
431
+
432
+ event.waitUntilNavigate(); // Hold down the event lifecycle
433
+
434
+ return { posts: result.rows }; // [!code highlight]
435
+ }
436
+ ```
437
+
438
+ #### Example — Redirect after work
439
+
440
+ This handler emits progress and then issues a redirect when the work completes.
441
+
442
+ **_Result_:** The page shows intermediate progress, then redirects to `/done` when work completes.
443
+
444
+ ```js
445
+ export async function POST(event, next) {
446
+ if (next.stepname) return await next(); // The conventional delegation line
447
+
448
+ // Initiate response
449
+ event.respondWith({ step: 1 }, { done: false });
450
+ // Make API call
451
+ await fetch('https://api.example.com/data');
452
+ // Update response
453
+ event.respondWith({ step: 2 }, { done: false });
454
+
455
+ // Redirect
456
+ event.respondWith(null, { status: 302, headers: { Location: '/done' } }); // [!code highlight]
457
+ }
458
+ ```
459
+
460
+ ### User Dialogs: Server ↔ User Interaction
461
+
462
+ #### Example — Ask for confirmation
463
+
464
+ This handler pauses request processing to interact with the user before proceeding, using realtime messaging.
465
+
466
+ **_Result_:** The user is prompted to confirm an action; on OK, the handler proceeds.
467
+
468
+ ```js
469
+ export async function DELETE(event, next) {
470
+ if (next.stepname) return await next(); // The conventional delegation line
471
+
472
+ const message = 'This item will be permanently deleted. Are you sure?';
473
+ const answer = await event.user.confirm({ message }); // [!code highlight]
474
+ if (answer?.data === true) {
475
+ // proceed
476
+ }
477
+
478
+ return Response.json({ ok: true });
479
+ }
480
+ ```
481
+
482
+ The Webflo UI client shows the dialog (native `confirm()/prompt()` or custom UI if configured) and returns the user's response.
483
+
484
+ Customization is as simple as intercepting these via `preventDefault()` to render your own modals.
485
+
486
+ ```javascript
487
+ // Intercept at window.webqit.app.background
488
+ window.webqit.app.background.addEventListener('prompt', (e) => {
489
+ e.preventDefault(); // [!code highlight]
490
+ customDialog(e.data.message).then((result) => {
491
+ // Reply to the request on the provided port
492
+ e.ports[0].postMessage(result); // [!code highlight]
493
+ });
494
+ });
495
+ ```
496
+
497
+ ### Message Channels
498
+
499
+ #### Example — Channel chat (server + client + UI)
500
+
501
+ These handlers establish a chat channel and broadcast messages to all participants.
502
+
503
+ **_Result_:** Messages appear in the chat trail every few seconds; incoming vs outgoing messages are styled differently.
504
+
505
+ ```js
506
+ export async function GET(event, next) {
507
+ if (next.stepname) return await next(); // The conventional delegation line
508
+
509
+ // Channel ID - must tally with the client-side ID
510
+ const channelID = 'test-channel';
511
+
512
+ const { channel, leave } = event.client.enterChannel(channelID, {
513
+ // Optionally annotate user's messages with user's name
514
+ resolveData: (message) => ({ ...message, user: 'sample name' }),
515
+ });
516
+
517
+ event.waitUntilNavigate(); // keep open
518
+ event.client.addEventListener('close', (e) => {
519
+ //e.preventDefault();
520
+ console.log('Chat closed');
521
+ });
522
+ }
523
+ ```
524
+
525
+ Then on the client:
526
+
527
+ ```js
528
+ export default async function (event, next) {
529
+ if (next.stepname) return await next(); // The conventional delegation line
530
+
531
+ // Initialize states
532
+ const data = { messageTrail: [{ message: 'Beginning of chat...' }] };
533
+ await event.respondWith(data, { done: false }); // [!code highlight]
534
+
535
+ // The messaging part
536
+ const response = await next(); // Call server
537
+ const chatPort = LiveResponse.from(response).background;
538
+
539
+ // Channel ID - must tally with the server-side ID
540
+ const channelID = 'test-channel';
541
+
542
+ // Listen to upstream messages - from others in the same channel
543
+ chatPort.addEventListener(`${channelID}:message`, (e) => {
544
+ Observer.proxy(data.messageTrail).push(e.data); // [!code highlight]
545
+ });
546
+
547
+ // Send messages to the chat
548
+ setInterval(() => {
549
+ const message = { message: `Hello from ${navigator.userAgent}` };
550
+
551
+ // Send to others in the same channel
552
+ chatPort.postMessage(message, {
553
+ wqEventOptions: { type: `${channelID}:message` },
554
+ }); // [!code highlight]
555
+
556
+ // Add to the local trail
557
+ Observer.proxy(data.messageTrail).push(message); // [!code highlight]
558
+ }, 6000);
559
+ }
560
+ ```
561
+
562
+ ::: details Then on the UI
563
+
564
+ ```html
565
+ <div>
566
+ Welcome to chat!
567
+ <ul render="#items: (m) of data?.messageTrail / 'message'">
568
+ <template def="message" scoped>
569
+ <li
570
+ class="chat-message"
571
+ render="#text: m.event || m.message; ~type: m.user && 'incoming' || 'outgoing';"
572
+ ></li>
573
+ </template>
574
+ </ul>
575
+ <style scoped>
576
+ ul:has(.chat-message) {
577
+ list-style: none;
578
+ padding: 0;
579
+ }
580
+ .chat-message {
581
+ padding: 20px;
582
+ margin: 10px;
583
+ }
584
+ .chat-message[type="incoming"] {
585
+ margin-right: 200px;
586
+ background-color: blue;
587
+ }
588
+ .chat-message[type="outgoing"] {
589
+ margin-left: 200px;
590
+ text-align: right;
591
+ background-color: teal;
592
+ }
593
+ </style>
594
+ </div>
595
+ ```
596
+
597
+ :::
598
+
599
+ ::: warning
600
+
601
+ - Channel IDs must be unique and unguessable across the app.
602
+ - Channels are not persistent and are only active for the duration of the request. A database is required to store channel data.
603
+ :::
604
+
605
+ ## Appendix A – The Realtime Lifecycle
606
+
607
+ ```mermaid
608
+ sequenceDiagram
609
+ participant Browser
610
+ participant Handler
611
+ Browser->>Handler: HTTP request
612
+ Handler-->>Browser: Initial Response (or a Temporary 202 Accepted) <br>+ X-Background-Messaging-Port
613
+ Browser-->>Handler: Connect (WebSocket / Broadcast / MessageChannel)
614
+ Note over Browser,Handler: Background mode established
615
+ Handler-->>Browser: Messages, updates, dialogs, etc.
616
+ Browser-->>Handler: Replies, requests, etc.
617
+ Browser<<-->>Handler: Termination
618
+ ```
619
+
620
+ ### The Handshake
621
+
622
+ On entering background mode, Webflo initiates a handshake sequence as follows:
623
+
624
+ 1. Webflo gives the initial response a unique `X-Background-Messaging-Port` header that tells the client to connect in the background after rendering the initial response.
625
+ > If the scenario is case `1.` above happening _before_ yielding a response, Webflo sends a temporary `202 Accepted` response to the client carrying this header.
626
+ 2. The client reads that header and opens the background channel.
627
+ > If the client fails to connect within the handshake window, Webflo abandons the wait and concludes the request normally.
628
+ 3. On connecting, both sides resume the same request context — now in live mode.
629
+
630
+ ### Termination
631
+
632
+ Communication remains active until one of three events occurs and triggers a controlled closure:
633
+
634
+ 1. The handler completes and no longer holds the event lifecycle.
635
+ 2. The user navigates away, triggering a `navigate` event on the handler's `event.client` port.
636
+ > If the handler calls `event.preventDefault()` on the `navigate` event, Webflo knows to not close the connection. **You must explicitly call `event.client.close()` when finished.**
637
+ 3. Either side explicitly calls `.close()` on its own port to end the conversation.
@@ -0,0 +1,60 @@
1
+ # Rendering
2
+
3
+ Rendering in Webflo is about turning handler data into UI, both on the server (SSR) and in the browser (CSR).
4
+
5
+ ## The Mental Model
6
+
7
+ - Your handler returns data (a plain object or Response).
8
+ - Webflo makes this data available as `document.bindings.data` in your HTML.
9
+ - Rendering can happen on the server, the client, or both—using the same mental model.
10
+
11
+ ## Example: Returning Data
12
+
13
+ ```js
14
+ // app/handler.server.js
15
+ export default async function(event, next) {
16
+ if (next.stepname) return await next();
17
+ return { title: 'Home', greeting: 'Hello World!' };
18
+ }
19
+ ```
20
+
21
+ ## Accessing Data in HTML
22
+
23
+ ```html
24
+ <!-- public/index.html -->
25
+ <!DOCTYPE html>
26
+ <html>
27
+ <head>
28
+ <title>My App</title>
29
+ <script>
30
+ setTimeout(() => {
31
+ document.title = document.bindings.data.title;
32
+ }, 0);
33
+ </script>
34
+ </head>
35
+ <body>
36
+ <h1 id="greeting"></h1>
37
+ <script>
38
+ setTimeout(() => {
39
+ document.getElementById('greeting').textContent = document.bindings.data.greeting;
40
+ }, 0);
41
+ </script>
42
+ </body>
43
+ </html>
44
+ ```
45
+
46
+ ## SSR & CSR
47
+
48
+ - **SSR:** Data is rendered into HTML on the server for fast first paint and SEO.
49
+ - **CSR:** Data is updated in the browser for dynamic, interactive UIs.
50
+ - **Hybrid:** You can use both together for best results.
51
+
52
+ ## Reactivity & OOHTML
53
+
54
+ - Use [OOHTML](https://github.com/webqit/oohtml) for reactive UI logic and templates.
55
+ - OOHTML enables `<script quantum>` and `<template>` for declarative, reactive UIs.
56
+
57
+ ---
58
+
59
+ - [See Templates →](./templates.md)
60
+ - [See Lifecycle →](./lifecycle.md)