@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,656 @@
1
+ # Webflo Routing
2
+
3
+ Functions come into play in Webflo when you need to dynamically handle requests.
4
+ Routing defines how those requests map to functions in a Webflo application.
5
+ It determines which handler responds to a given URL, how requests move across layers of the stack, and how each step in that process composes into a complete response.
6
+
7
+ ## Layout Convention
8
+
9
+ In Webflo, **your filesystem *is* the router**.
10
+ Each folder under `app/` corresponds to a segment in your application’s URL path, and each handler file in that folder defines what happens at that segment.
11
+
12
+ ```html
13
+ app/
14
+ ├── handler.server.js → /
15
+ ├── about/handler.server.js → /about
16
+ ├── products/handler.server.js → /products
17
+ └── products/stickers/handler.server.js → /products/stickers
18
+ ```
19
+
20
+ Handlers can be designed to match any segment using wildcards.
21
+ A folder named `-` acts as a catch-all at its level.
22
+
23
+ ```html
24
+ app/
25
+ ├── -/handler.server.js → /*
26
+ └── products/-/handler.server.js → /products/*
27
+ ```
28
+
29
+ ## Handlers
30
+
31
+ Handlers are standard JavaScript functions that process requests and return responses.
32
+ They share the same base signature:
33
+
34
+ ```js
35
+ export default async function (event, next, fetch) {
36
+ if (next.stepname) return await next();
37
+ return { title: 'Welcome to Webflo' };
38
+ }
39
+ ```
40
+
41
+ A route may provide **[named exports](/api/webflo-routing/handler#naming)** to map specific HTTP requests to specific handlers:
42
+
43
+ ```js
44
+ export async function GET(event, next) { /* ... */ }
45
+ ```
46
+
47
+ Handlers are fully covered in the [Handler API](/api/webflo-routing/handler) section, but below is an overview.
48
+
49
+ ### Parameters
50
+
51
+ Parameters recieved include:
52
+
53
+ | Parameter | Type | Description |
54
+ | :-------- | :--------------------------------- | :------------------------------------------------------- |
55
+ | `event` | [`HttpEvent`](/api/webflo-routing/HttpEvent) | Current HTTP event. |
56
+ | `next` | [`next`](/api/webflo-routing/handler/next) | Control delegation function. |
57
+ | `fetch` | [`fetch`](/api/webflo-routing/handler/fetch) | Context-aware fetch API for inbound and outbound calls. |
58
+
59
+ ### Contextual Parameters
60
+
61
+ Within a handler, contextual properties are available on the [`this`](/api/webflo-routing/handler#the-this-context) and [`next`](/api/webflo-routing/handler/next) interfaces:
62
+
63
+ | Property | Type | Description |
64
+ | :-------------- | :-------------- | ----------------------------------------------------------------- |
65
+ | `next.stepname` | `string` | The name of the next segment in the URL. |
66
+ | `next.pathname` | `string` | The full path _beyond_ the the active step. |
67
+ | `this.stepname` | `string` | The current directory segment being handled. |
68
+ | `this.pathname` | `string` | The current URL pathname _up to_ the active step. |
69
+ | `this.filename` | `string` | The filename of the executing handler (server-side only) |
70
+
71
+ Use these for conditional delegation, or per-segment rules.
72
+
73
+ ### Your First Handler (Again)
74
+
75
+ Your application's routes may be designed with as many or as few handlers as desired.<br>
76
+ The contextual parameters `next.stepname` and `next.pathname` totally make it possible to fit routing logic into a single handler.
77
+
78
+ ```js
79
+ export default function(event, next) {
80
+ // For http://localhost:3000/products
81
+ if (next.pathname === 'products') {
82
+ return { title: 'Products' };
83
+ }
84
+
85
+ // For http://localhost:3000/products/stickers
86
+ if (next.pathname === 'products/stickers') {
87
+ return { title: 'Stickers' };
88
+ }
89
+
90
+ // Should we later support other URLs like static assets http://localhost:3000/logo.png
91
+ if (next.pathname) {
92
+ return next();
93
+ }
94
+
95
+ // For the root URL http://localhost:3000
96
+ return { title: 'Home' };
97
+ }
98
+ ```
99
+
100
+ But the power of Webflo routing model really shines as you spread out to more handlers.
101
+
102
+ ## The Delegation Model
103
+
104
+ In Webflo, nested URLs such as `/products/stickers` don’t directly invoke their corresponding leaf handler (`app/products/stickers/handler.server.js`) in isolation.
105
+ Instead, requests are handled **step-by-step — from parent to child**, forming a **pipeline**.
106
+
107
+ The `next()` function is how a handler delegates control to the next step in that pipeline.
108
+
109
+ This is simulated below for a URL like `/products/stickers`.<br>
110
+ Each handler uses `next()` to delegate, and the final step returns the response.
111
+
112
+ ```js
113
+ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐
114
+ │ app/ │ → │ products/ │ → │ stickers/ │
115
+ │ handler │ │ handler │ │ handler │
116
+ │ next() │ │ next() │ │ return {} │
117
+ └──────────┘ └──────────────┘ └──────────────────┘
118
+ ```
119
+
120
+ ```js
121
+ // app/handler.server.js
122
+ export default async function (event, next) {
123
+ if (next.stepname) return await next();
124
+ return { title: 'Home' };
125
+ }
126
+ ```
127
+
128
+ ```js
129
+ // app/products/handler.server.js
130
+ export default async function (event, next) {
131
+ if (next.stepname) return await next();
132
+ return { title: 'Products' };
133
+ }
134
+ ```
135
+
136
+ ```js
137
+ // app/products/stickers/handler.server.js
138
+ export default async function () {
139
+ if (next.stepname) return await next();
140
+ return { title: 'Stickers' };
141
+ }
142
+ ```
143
+
144
+ * The request enters at the top level (`app/handler.server.js`).
145
+ * Each handler may perform logic and either return a response or call `next()`.
146
+ * `next()` advances the request to the next directory level in the URL.
147
+ * Delegation stops when there are no further segments (`next.stepname` is falsy).
148
+
149
+
150
+ ### Internal Rerouting
151
+
152
+ Beyond the default parent-child flow, a handler can explicitly **reroute** a request to another path within the app by calling `next(path)` or `next(context, path)`.
153
+
154
+ This is simulated below for a URL like `/products/stickers`.<br>
155
+ Here, the root handler conditionally reroutes the request to `/api/inventory`, all within the same app.
156
+
157
+ ```js
158
+ // app/handler.server.js
159
+ export default async function (event, next) {
160
+ if (next.stepname === 'products') {
161
+ const inventory = await next('/api/inventory?range=7d');
162
+ return { title: 'Products', ...inventory };
163
+ }
164
+ return { title: 'Home' };
165
+ }
166
+ ```
167
+
168
+ ```js
169
+ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐
170
+ │ app/ │─┐ │ products/ │ → │ stickers/ │
171
+ │ handler │ │ │ handler │ │ handler │
172
+ │ next(▼) │ │ │ │ │ │
173
+ └──────────┘ │ └──────────────┘ └──────────────────┘
174
+
175
+ │ (internal call)
176
+
177
+ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐
178
+ │ app/ │ → │ api/ │ → │ inventory/ │
179
+ │ handler │ │ handler │ │ handler │
180
+ │ next() │ │ next() │ │ return {} │
181
+ └──────────┘ └──────────────┘ └──────────────────┘
182
+ ```
183
+
184
+ ```js
185
+ // app/api/inventory/handler.server.js
186
+ export default async function (event, next) {
187
+ const range = event.url.searchParams.get('range');
188
+ const result = await db.query(
189
+ `SELECT * FROM inventory WHERE date_added < $1`,
190
+ [range]
191
+ );
192
+ return { title: 'Inventory', result };
193
+ }
194
+ ```
195
+
196
+ The rerouted request travels through the normal routing tree (`app/` → `api/` → `inventory/`) as if it had originated normally.
197
+
198
+ A relative path (e.g., `next('./api/inventory?range=7d')`) may be used to bypass lineage.
199
+ But this must be done intentionally: deeper routes often inherit authentication or other contexts that should not be bypassed.
200
+
201
+ This technique enables **in-app data composition** — using existing route logic without additional network requests.
202
+
203
+ ### Request and Response Rewriting
204
+
205
+ At any stage, a handler may rewrite parts of a request or modify the returned response before passing it on.
206
+ This allows dynamic query shaping, conditional caching, or on-the-fly header injection.
207
+
208
+ This is simulated below for a scenario where the parent adds a parameter (`p=3`) to the child route, and then post-processes the response to set a custom header.
209
+
210
+ ```js
211
+ // app/products/handler.server.js
212
+ export default async function (event, next) {
213
+ // Clone the request with a new query param
214
+ const url = new URL(event.url);
215
+ url.searchParams.set('p', 3);
216
+
217
+ // Delegate with the modified URL
218
+ const res = await next({}, url.pathname + url.search);
219
+
220
+ // Post-process response before returning
221
+ const headers = new Headers(res.headers);
222
+ headers.set('X-Pipeline-Step', 'products');
223
+ return new Response(res.body, { status: res.status, headers });
224
+ }
225
+ ```
226
+
227
+ ```js
228
+ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐
229
+ │ app/ │ → │ products/ │ → │ stickers/?p=3 │
230
+ │ handler │ │ handler │ │ handler │
231
+ │ next() │ ← │ next() │ │ return {} │
232
+ └──────────┘ └──────────────┘ └──────────────────┘
233
+ ```
234
+
235
+ Through this mechanism, Webflo lets handlers **reshape requests or responses inline**, without needing extra middleware layers or global hooks.
236
+
237
+ ## The Client-Server Flow
238
+
239
+ While the **Delegation Model** describes how a request flows through a _horizontal_ route path (parent → child), the **Client-Server Flow** represents the _vertical_ flow of the same request through the application stack (client → server).
240
+
241
+ Webflo follows a model that supports request handling and routing at all three layers in this stack: the browser window layer (**client**), the service worker layer (**worker**), the server layer (**server**).
242
+
243
+ Handlers fit into this stack by their filename suffix:
244
+
245
+ ```html
246
+ handler.client.js → Executes in the browser (first to see navigations)
247
+ handler.worker.js → Executes in the Service Worker (next in line)
248
+ handler.server.js → Executes on the server (last in line)
249
+ handler.js → Executes anywhere (default handler)
250
+ ```
251
+
252
+ Together, these form a vertical routing pipeline.
253
+
254
+ Below is a conceptual diagram of how a navigation request flows donw the layers:
255
+
256
+ ```js
257
+ ┌─────────────┐ │ ┌─────────────────────────────────┐
258
+ │ navigate() │ → │ ? │ handler.client.js ?? handler.js │
259
+ │ │ ← │ └─────────────────────────────────┘
260
+ │ app │ │ ┌─────────────────────────────────┐
261
+ └─────────────┘ │ ? │ handler.worker.js ?? handler.js │
262
+ │ └─────────────────────────────────┘
263
+ │ ┌─────────────────────────────────┐
264
+ │ ? │ handler.server.js ?? handler.js │
265
+ ▼ └─────────────────────────────────┘
266
+ ```
267
+
268
+ Handlers are optional; if a level-specific file doesn’t exist, Webflo automatically falls back to the unsuffixed one `handler.js`,
269
+ if defined. Otherwise, the request continues to the next layer. Each request gets a graceful path from local logic to remote fulfillment.
270
+
271
+ As with the horizontal flow, each layer may intercept, fulfill, or delegate down the request.<br>
272
+ Handlers at higher layers (like the browser) are able to respond instantly or hand the request down the stack.
273
+
274
+ This model grants profound flexibility — enabling progressive enhancement, offline support, and universal routing, all through the same `next()` interface.
275
+
276
+ ### Layer Semantics
277
+
278
+ These layers are differentiated by various factors and use cases.
279
+
280
+ | Scope | Purpose | Typical Usage |
281
+ | :--------------------- | :--------------------------------------------- | :------------------------------------------ |
282
+ | **handler.client.js** | Runs in the browser during navigation | SPA transitions, local data hydration |
283
+ | **handler.worker.js** | Runs in the Service Worker | Offline caching, background synchronization |
284
+ | **handler.server.js** | Runs on the server | Database queries, SSR, API endpoints |
285
+ | **handler.js** | Fallback when no scope-specific handler exists | Shared logic or universal defaults |
286
+
287
+ #### Client-Side Handlers
288
+
289
+ Client handlers intercept navigations directly in the browser — the first layer that sees user-initiated requests.
290
+
291
+ ```js
292
+ // app/handler.client.js
293
+ export default async function (event, next) {
294
+ if (next.stepname) return await next();
295
+
296
+ // Access browser APIs freely
297
+ const theme = window.sessionStorage.getItem('theme');
298
+ return { title: 'Client Navigation', theme };
299
+ }
300
+ ```
301
+
302
+ * Executes during in-app navigations (SPA behavior)
303
+ * Runs within the already loaded document and has access to window
304
+ * Can render instantly from local data or cache
305
+ * Optionally calls `next()` to delegate the request
306
+
307
+ ::: info Handler Lifecycle
308
+ - Client handlers begin their lifecycle *after* the initial page load.
309
+ - They therefore cannot intercept the first page load or page reloads.
310
+ :::
311
+
312
+ #### Worker-Side Handlers
313
+
314
+ Worker handlers run in the Service Worker context, bridging offline and network behavior.
315
+ They are the connective tissue between local interactivity and remote resources.
316
+
317
+ ```js
318
+ // app/handler.worker.js
319
+ export default async function (event, next) {
320
+ if (next.stepname) return await next();
321
+
322
+ // Access Service Worker APIs
323
+ const cache = await caches.open('webflo-assets');
324
+ const cached = await cache.match(event.request);
325
+ if (cached) return cached;
326
+
327
+ const network = await next(); // fallback to server
328
+ cache.put(event.request, network.clone());
329
+ return network;
330
+ }
331
+ ```
332
+
333
+ * Executes for same-origin requests
334
+ * Can serve from cache, perform background syncs, or proxy network calls
335
+ * Can delegates to the server when offline handling isn’t possible
336
+
337
+ ::: info Handler Lifecycle
338
+ - Worker handlers start intercepting once the app’s Service Worker is installed and activated.
339
+ - They therefore cannot intercept the very first page load that installs the app.
340
+ - They continue working even when the page isn’t open, making them ideal for offline logic.
341
+ :::
342
+
343
+ #### Server-Side Handlers
344
+
345
+ Server handlers perform the heavy lifting — database queries, rendering, API endpoints, and integrations.
346
+ They represent the final dynamic layer before static content resolution.
347
+
348
+ ```js
349
+ // app/handler.server.js
350
+ export default async function (event, next) {
351
+ if (next.stepname) return await next();
352
+
353
+ const user = process.env.ADMIN_USER;
354
+ const data = await fetch('https://api.example.com/stats').then(r => r.json());
355
+ return { title: `Dashboard | ${user}`, data };
356
+ }
357
+ ```
358
+
359
+ * Executes for HTTP requests that reach the server
360
+ * Accesses environment variables and external APIs
361
+ * May call `next()` to handoff request to Webflo’s static file layer
362
+
363
+ #### Universal Handlers
364
+
365
+ Universal handlers (`handler.js`) are handlers declared without any layer binding.
366
+ They can coexist with layer-specific handlers but execute wherever no layer-specific handler exists for the current layer, making them perfect for universal logic.
367
+
368
+ ```js
369
+ // app/handler.js
370
+ export default async function (event, next) {
371
+ // Purely portable logic — no window, no caches, no env
372
+ return { message: 'Handled by default' };
373
+ }
374
+ ```
375
+
376
+ ::: tip Progressive Enhancement
377
+ - Because handlers are modular by filename, promoting a route from server-side to client-side is as simple as renaming the file.
378
+ - Webflo’s model turns *progressive enhancement* into a first-class development workflow.
379
+ :::
380
+
381
+ ### Fall-Through Behavior
382
+
383
+ If a handler calls `next()` and no deeper step exists in the current layer, Webflo **falls through to the next layer** in the stack.
384
+ This continuity is built-in.
385
+
386
+ | Scope | Default Action when `next()` reaches edge |
387
+ | :--------- | :----------------------------------------------------- |
388
+ | **Client** | Falls through to the worker layer. |
389
+ | **Worker** | Falls through to either: (cache → server) or (server → cache), depending on worker config. |
390
+ | **Server** | Falls through to the static file layer `/public`; returns 404 if no match. |
391
+
392
+ This is simulated below for a navigation to `/products/stickers`, where the client and worker layers defer to the server for resolution.
393
+
394
+ ```js
395
+ // app/products/handler.client.js
396
+ export default async function (event, next) {
397
+ if (next.stepname) return await next();
398
+ // Defer to deeper layers
399
+ return next();
400
+ }
401
+ ```
402
+
403
+ ```html
404
+ ┌──────────┐ ┌──────────────┐
405
+ │ app/ │ → │ products/ │─┐ (fall-through)
406
+ │ handler │ │ handler │ │
407
+ │ next() │ │ next() │ │
408
+ └──────────┘ └──────────────┘ │
409
+
410
+ │ (server layer)
411
+
412
+ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐
413
+ │ app/ │ → │ products/ │ → │ stickers/ │
414
+ │ handler │ │ handler │ │ handler │
415
+ │ next() │ │ next() │ │ return {} │
416
+ └──────────┘ └──────────────┘ └──────────────────┘
417
+ ```
418
+
419
+ ```js
420
+ // app/products/stickers/handler.server.js
421
+ export default async function () {
422
+ const result = await db.query(
423
+ `SELECT * FROM products WHERE category = 'stickers'`
424
+ );
425
+ return { title: 'Stickers', result };
426
+ }
427
+ ```
428
+
429
+ Here, the client and worker defer, the server handles the query, and the browser receives the composed response.
430
+ This unlocks the full power of composition, progressive enhancement, and resilience per route — whether online, offline, or hybrid.
431
+
432
+ ## Flow Summary
433
+
434
+ While your first handler is perfectly fine to fit routing logic into conditional blocks,
435
+ Webflo's delegation model makes routing all seamsless as you go from a simple _Hello World_ to a standard app, to a fully distributed system.
436
+
437
+ The delegation and composition model turns the traditional “server-first” web into a **collaborative matrix**.
438
+ Each level decides what it can handle best and delegates what it cannot.
439
+
440
+ This composability and control extend to static files handling.
441
+
442
+ ## Static Files
443
+
444
+ At the end of Webflo’s routing chain lies the **static layer** — a built-in static file server that operates by the same rules as every other layer.
445
+
446
+ In Webflo, static resolution is not a separate middleware; it is simply the final stage of the routing pipeline.
447
+
448
+ This layer is reached **from the server routing layer**, when:
449
+
450
+ * a server handler calls `next()` and no further route step exists in the pipeline
451
+
452
+ Because static serving sits in this same flow, route handlers take first-seat complete control — to intercept, rewrite, or even *simulate* static responses before they are served.
453
+
454
+ This flow is simulated below for an image URL: `/img/logo.png` embedded on a page.<br>
455
+ Its resolution goes the standard routing flow until matching a file in the `app/public` directory.
456
+
457
+ ```html
458
+ ┌─────────────┐ │ ┌─────────────────────────────────┐
459
+ │ <img src> │ → │ ? │ handler.client.js ?? handler.js │
460
+ │ │ ← │ └─────────────────────────────────┘
461
+ │ app │ │ ┌─────────────────────────────────┐
462
+ └─────────────┘ │ ? │ handler.worker.js ?? handler.js │
463
+ │ └─────────────────────────────────┘
464
+ │ ┌─────────────────────────────────┐
465
+ │ ? │ handler.server.js ?? handler.js │
466
+ │ └─────────────────────────────────┘
467
+ │ ┌─────────────────────────────────┐
468
+ │ ? │ public/img/pic.png │
469
+ │ │ public/img/banner.png │
470
+ │ │ public/img/logo.png │
471
+ ▼ └─────────────────────────────────┘
472
+ ```
473
+
474
+ Each handler along the flow gets a chance to intercept the request.<br>
475
+ A worker, for example, may serve a cached image or synthesize a response.
476
+ A server handler may rewrite the path before handing off to `/public` or it may _gate_ or authenticate the request before passing it on.
477
+
478
+ This handler-first approach to static files serving ensures that asset delivery fits your application logic, authentication, or cache policies.
479
+
480
+ But this also requires **proper delegation discipline** by handlers.
481
+ Handlers must consciously call `next()` for requests they're not explicitly designed to handle.
482
+
483
+ Overall, by merging dynamic logic and static delivery into one continuous flow, Webflo replaces special-case asset middleware with a **first-class, programmable static pipeline**.
484
+
485
+ ### Default Resolution
486
+
487
+ When a request reaches the static layer, Webflo performs deterministic file resolution:
488
+
489
+ 1. Look for a file in `/public` matching the request path.
490
+ 2. If found, serve it with correct headers (e.g. `Content-Type`, `Content-Length`, caching).
491
+ 3. If not found, return `404`.
492
+
493
+ ## Use Case Patterns
494
+
495
+ The following examples demonstrate how Webflo’s routing primitives—delegation, composition, and explicit fall-through—combine to express real application structures.
496
+ Each pattern is an applied scenario that builds directly on the models we’ve covered so far.
497
+
498
+ ### Parent–Child Composition
499
+
500
+ **Scenario:** A parent route prepares context and then delegates to a child, merging its result.
501
+ This pattern allows *layered composition*—logic in parents, data or view in children.
502
+
503
+ ```js
504
+ // app/handler.server.js
505
+ export default async function (event, context, next) {
506
+ if (next.stepname) {
507
+ const user = await getSessionUser(event.request);
508
+ const childResult = await next({ user });
509
+ return { ...childResult, title: `${childResult.title} | ExampleApp` };
510
+ }
511
+ return { title: 'Home' };
512
+ }
513
+ ```
514
+
515
+ **Takeaway:** Each handler can frame or extend downstream results, making cross-cutting concerns like authentication or analytics fully composable.
516
+
517
+ ### Internal API Consumption
518
+
519
+ **Scenario:** A page handler calls an internal API route using `next(path)` instead of making an HTTP request.
520
+ This lets server code reuse API logic without duplication or latency.
521
+
522
+ ```html
523
+ app/
524
+ ├── api/
525
+ │ └── products/handler.server.js
526
+ └── shop/handler.server.js
527
+ ```
528
+
529
+ ```js
530
+ // app/shop/handler.server.js
531
+ export default async function (event, next) {
532
+ const products = await next('/api/products');
533
+ return { title: 'Shop', ...products };
534
+ }
535
+ ```
536
+
537
+ **Takeaway:** By re-entering the routing pipeline locally, Webflo turns API composition into simple function calls—no network, no boilerplate.
538
+
539
+ ### Auth Guard
540
+
541
+ **Scenario:** A parent route gates access for its children, redirecting unauthenticated users and passing context when authorized.
542
+
543
+ ```js
544
+ // app/account/handler.server.js
545
+ export default async function (event, context, next) {
546
+ const user = await getUserFromSession(event.request);
547
+ if (!user) {
548
+ return new Response(null, { status: 302, headers: { Location: '/login' } });
549
+ }
550
+ return next({ ...context, user });
551
+ }
552
+ ```
553
+
554
+ **Takeaway:** Authentication becomes just another layer in the routing flow—no external middleware required.
555
+
556
+ ### File Guards and Access Control
557
+
558
+ **Scenario:** Restrict access to premium or user-specific files before they reach the static layer.
559
+
560
+ ```js
561
+ // app/files/handler.server.js
562
+ export default async function (event, next) {
563
+ const user = await getUserFromSession(event.request);
564
+ if (!user?.isPremium) {
565
+ return new Response('Access denied', { status: 403 });
566
+ }
567
+ return next();
568
+ }
569
+ ```
570
+
571
+ **Takeaway:** Because static requests flow through the same pipeline, permission checks and audit logic integrate naturally with asset delivery.
572
+
573
+ ### Dynamic File Serving
574
+
575
+ **Scenario:** Rewrite or transform static responses on the fly for caching, personalization, or instrumentation.
576
+
577
+ ```js
578
+ // app/-/handler.server.js
579
+ export default async function (event, next) {
580
+ const res = await next(); // delegate to /public
581
+ if (res && res.ok && res.headers.get('Content-Type')?.includes('text/html')) {
582
+ const headers = new Headers(res.headers);
583
+ headers.set('Cache-Control', 'public, max-age=300');
584
+ headers.set('X-Served-By', 'Webflo');
585
+ return new Response(res.body, { status: res.status, headers });
586
+ }
587
+ return res;
588
+ }
589
+ ```
590
+
591
+ **Takeaway:** Handlers can shape even static responses—embedding application-level awareness into the file server itself.
592
+
593
+ ### Full-Stack Routing
594
+
595
+ **Scenario:** A single navigation passes through multiple layers—client, worker, server, static—each adding incremental behavior.
596
+
597
+ ```js
598
+ CLIENT (handler.client.js)
599
+ │ Intercepted navigation, local cache check
600
+ ▼ next()
601
+ WORKER (handler.worker.js)
602
+ │ Offline fallback or cache refresh
603
+ ▼ next()
604
+ SERVER (handler.server.js)
605
+ │ Query, render, compose
606
+ ▼ next()
607
+ STATIC (public/)
608
+ │ Fallback to static asset
609
+
610
+ Response returned
611
+ ```
612
+
613
+ ```js
614
+ // app/products/handler.client.js
615
+ export default async function (event, next) {
616
+ if (next.stepname) return await next();
617
+ // Attempt to serve from local state
618
+ const cached = sessionStorage.getItem('products');
619
+ if (cached) return JSON.parse(cached);
620
+ return next(); // defer to worker/server
621
+ }
622
+ ```
623
+
624
+ **Takeaway:** Full-stack routing enables progressive enhancement by design—each scope adds value without breaking continuity.
625
+
626
+ ### Remote Procedure Calls Clone
627
+
628
+ **Scenario:** Think of Webflo’s routing pipeline as RPC with spatial awareness.
629
+ Each `next()` is a local procedure call that moves closer to the data or resource in question.
630
+
631
+ ```js
632
+ // app/dashboard/handler.server.js
633
+ export default async function (event, next) {
634
+ const metrics = await next('/api/metrics');
635
+ const reports = await next('/api/reports');
636
+ return { metrics, reports };
637
+ }
638
+ ```
639
+
640
+ **Takeaway:** Unlike traditional RPC, routing in Webflo preserves URL semantics and context propagation while keeping the call local and synchronous.
641
+
642
+ ## Summary
643
+
644
+ Webflo’s routing system unifies **filesystem mapping**, **functional composition**, and **layered execution** into one consistent model.
645
+
646
+ * The filesystem defines your application structure.
647
+ * Handlers define logic for each URL segment.
648
+ * `next()` controls flow between steps and scopes.
649
+ * Default fallbacks ensure graceful completion through the stack.
650
+ * Static serving is part of the same flow, enabling dynamic control.
651
+
652
+ ## Next Steps
653
+
654
+ * [Rendering](./rendering.md): How handler data becomes UI.
655
+ * [Templates](./templates.md): Composing reusable HTML layouts.
656
+ * [State & Reactivity](./state.md): Managing state and mutation across requests.