@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,11 @@
1
+ import { WebfloServer } from './WebfloServer.js';
2
+
3
+ export async function start() {
4
+ const instance = WebfloServer.create(this || {});
5
+ await instance.initialize();
6
+ return instance;
7
+ }
8
+
9
+ export {
10
+ WebfloServer
11
+ }
@@ -0,0 +1,27 @@
1
+ import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
2
+ import { ClientRequestRealtime } from './ClientRequestRealtime.js';
3
+
4
+ export class Client extends WQStarPort {
5
+
6
+ #clientID;
7
+ get clientID() { return this.#clientID; }
8
+
9
+ constructor(clientID) {
10
+ super();
11
+ this.#clientID = clientID;
12
+ }
13
+
14
+ getRequestRealtime(portID) {
15
+ return this.findPort((port) => port.portID === portID);
16
+ }
17
+
18
+ createRequestRealtime(portID, url = null) {
19
+ const requestPort = new ClientRequestRealtime(portID, url);
20
+ const cleanup = this.addPort(requestPort);
21
+ setTimeout(() => {
22
+ if (requestPort.length || !this.findPort((port) => port === requestPort)) return;
23
+ cleanup();
24
+ }, 30000/*30sec*/);
25
+ return requestPort;
26
+ }
27
+ }
@@ -0,0 +1,50 @@
1
+ import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
2
+ import { _wq } from '../../../util.js';
3
+
4
+ export class ClientRequestRealtime extends WQStarPort {
5
+
6
+ #portID;
7
+ get portID() { return this.#portID; }
8
+
9
+ #url;
10
+ get url() { return this.#url; }
11
+
12
+ #navigatedIn = false;
13
+ navigatedIn() { return this.#navigatedIn; }
14
+
15
+ #navigatedAway = false;
16
+ navigatedAway() { return this.#navigatedAway; }
17
+
18
+ constructor(portID, url) {
19
+ super();
20
+ this.#portID = portID;
21
+ this.#url = url;
22
+ const $url = new URL(url);
23
+ let lastNavigationEvent;
24
+ this.addEventListener('navigate', (e) => {
25
+ if (e.data.pathname === $url.pathname) {
26
+ this.#navigatedIn = true;
27
+ lastNavigationEvent = 'navigatein';
28
+ } else {
29
+ this.#navigatedAway = true;
30
+ lastNavigationEvent = 'navigateaway';
31
+ }
32
+ const event = new Event(lastNavigationEvent);
33
+ this.dispatchEvent(event);
34
+ if (event.defaultPrevented) {
35
+ e.preventDefault();
36
+ }
37
+ });
38
+ }
39
+
40
+ enterChannel(channelID, { resolveData = null } = {}) {
41
+ const client = _wq(this, 'meta').get('parentNode');
42
+ const clients = client && _wq(client, 'meta').get('parentNode');
43
+ if (!clients) {
44
+ throw new Error('Instance seem not connected to the messaging system.');
45
+ }
46
+ const channel = clients.getChannel(channelID, true);
47
+ const leave = channel.addPort(client, { resolveData });
48
+ return { channel, leave };
49
+ }
50
+ }
@@ -0,0 +1,25 @@
1
+ import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
2
+ import { WQRelayPort } from '../../webflo-messaging/WQRelayPort.js';
3
+ import { Client } from './Client.js';
4
+
5
+ export class Clients extends WQStarPort {
6
+ #channels = new Map;
7
+
8
+ getClient(clientID, autoCreate = false) {
9
+ if (autoCreate && !this.findPort((client) => client.clientID === clientID)) {
10
+ const client = new Client(clientID);
11
+ const cleanup = this.addPort(client);
12
+ client.wqLifecycle.close.then(cleanup);
13
+ }
14
+ return this.findPort((client) => client.clientID === clientID);
15
+ }
16
+
17
+ getChannel(channelName, autoCreate = false) {
18
+ if (!this.#channels.has(channelName) && autoCreate) {
19
+ const channel = new WQRelayPort(channelName);
20
+ this.#channels.set(channelName, channel);
21
+ channel.wqLifecycle.close.then(() => this.#channels.delete(channelName));
22
+ }
23
+ return this.#channels.get(channelName);
24
+ }
25
+ }
@@ -0,0 +1,326 @@
1
+ import Path from 'path';
2
+ import $glob from 'fast-glob';
3
+ import EsBuild from 'esbuild';
4
+ import chokidar from 'chokidar';
5
+ import { exec, spawn } from 'child_process';
6
+ import { platform } from 'os';
7
+
8
+ export class WebfloHMR {
9
+
10
+ static manage(app, options = {}) {
11
+ return new this(app, options);
12
+ }
13
+
14
+ #app;
15
+
16
+ #options;
17
+ get options() { return this.#options; }
18
+
19
+ #jsMeta = {
20
+ dependencyMap: null/*!IMPORTANT!*/,
21
+ mustRevalidate: true,
22
+ prevBuildResult: null,
23
+ };
24
+
25
+ #layoutDirsMatchMap;
26
+ #routeDirs;
27
+ #handlerMatch;
28
+
29
+ #clients = new Set;
30
+ get clients() { return this.#clients; }
31
+
32
+ #watcher;
33
+ get watcher() { return this.#watcher; }
34
+
35
+ #dirtiness = {
36
+ CSSAffected: false,
37
+ HTMLAffected: false,
38
+ clientRoutesAffected: new Set,
39
+ serviceWorkerAffected: false,
40
+ };
41
+ get dirtiness() { return this.#dirtiness; }
42
+
43
+ #ignoreList = new Set;
44
+ get ignoreList() { return this.#ignoreList; }
45
+
46
+ #eventQueue = [];
47
+
48
+ constructor(app, options = {}) {
49
+ this.#app = app;
50
+ this.#options = options;
51
+ // Filesytem matching
52
+ const _layoutDirPattern = (dirs) => `^(${[...dirs].map((d) => Path.relative(process.cwd(), d).replace(/^\.\//g, '').replace(/\./g, '\\.').replace(/\//g, '\\/')).join('|')})`;
53
+ this.#layoutDirsMatchMap = Object.fromEntries(['CLIENT_DIR', 'WORKER_DIR', 'SERVER_DIR', 'VIEWS_DIR', 'PUBLIC_DIR'].map((name) => {
54
+ return [name, new RegExp(_layoutDirPattern([app.config.LAYOUT[name]]))];
55
+ }));
56
+ this.#routeDirs = new Set([app.config.LAYOUT.CLIENT_DIR, app.config.LAYOUT.WORKER_DIR, app.config.LAYOUT.SERVER_DIR]);
57
+ this.#handlerMatch = new RegExp(`${_layoutDirPattern(this.#routeDirs)}(\\/.+)?\\/handler(?:\\.(client|worker|server))?\\.js$`);
58
+ // The watch and event ordering logic
59
+ const totalWatchDirs = new Set([...this.#routeDirs, app.config.LAYOUT.VIEWS_DIR, app.config.LAYOUT.PUBLIC_DIR]);
60
+ this.#watcher = chokidar.watch([...totalWatchDirs], { ignoreInitial: true });
61
+ let flushTimer;
62
+ const scheduleFlush = () => {
63
+ clearTimeout(flushTimer);
64
+ flushTimer = setTimeout(() => this.#flushEvents(), EVENT_FLUSH_DELAY);
65
+ };
66
+ const EVENT_FLUSH_DELAY = 200; // milliseconds
67
+ this.#watcher.on('all', async (type, $target) => {
68
+ if (!['unlink', 'add', 'change', 'addDir', 'unlinkDir'].includes(type)) return;
69
+ this.#eventQueue.push({ type, $target });
70
+ scheduleFlush();
71
+ });
72
+ }
73
+
74
+ async #flushEvents() {
75
+ // Sort by event priority and path depth (deepest files first for unlinks)
76
+ // The Dir events are for structuring purposes
77
+ const priority = {
78
+ unlink: 1,
79
+ unlinkDir: 2,
80
+ addDir: 3,
81
+ add: 4
82
+ };
83
+ const eventQueue = this.#eventQueue.splice(0).sort((a, b) => {
84
+ if (priority[a.type] !== priority[b.type]) {
85
+ return priority[a.type] - priority[b.type];
86
+ }
87
+ return b.$target.split('/').length - a.$target.split('/').length; // deeper first
88
+ });
89
+ const events = new Set;
90
+ let hasJustBeenRebuilt = false;
91
+ for (const { type, $target } of eventQueue) {
92
+ if (this.#ignoreList.has($target)) continue;
93
+ const target = Path.relative(process.cwd(), $target);
94
+ if (this.#layoutDirsMatchMap.PUBLIC_DIR.test(target)) { // Assets
95
+ if (target.endsWith('.css')) {
96
+ events.add({ type, target, fileType: 'css', kind: 'asset' });
97
+ }
98
+ }
99
+ if (this.#layoutDirsMatchMap.VIEWS_DIR.test(target)) { // Views
100
+ if (/Dir$/.test(type)) { // (addDir | unlinkDir)
101
+ events.add({ type, target, fileType: null, kind: 'view' });
102
+ } else if (target.endsWith('.html')) {
103
+ events.add({ type, target, fileType: 'html', kind: 'view' });
104
+ }
105
+ }
106
+ if (target.endsWith('.js')) {
107
+ if (/add|unlink/.test(type)) {
108
+ this.#jsMeta.mustRevalidate = true; // Invalidate graph
109
+ }
110
+ if ((!hasJustBeenRebuilt && type !== 'unlink') || !this.#jsMeta.dependencyMap) { // We need graph in place to process affected routes for an unlink event
111
+ await this.buildJS(this.#jsMeta.mustRevalidate/*fullBuild*/);
112
+ hasJustBeenRebuilt = true;
113
+ }
114
+ const affectedHandlers = this.#jsMeta.dependencyMap[target] || [];
115
+ for (const affectedHandler of affectedHandlers) {
116
+ const [, dir, affectedRoute = '/', realm] = this.#handlerMatch.exec(affectedHandler) || [];
117
+ for (const r of ['client', 'worker', 'server']) {
118
+ const scopeObj = {};
119
+ if (type === 'unlink' && realm === r && target === affectedHandler // Dedicated handlers directly removed. Calculate fallback!
120
+ && (scopeObj.genericHandler = _toGeneric(target)) in this.#jsMeta.dependencyMap) {
121
+ // A fallback to generic handler happened
122
+ events.add({ type, target, fileType: 'js', affectedRoute, affectedHandler: scopeObj.genericHandler, realm: r, actionableEffect: 'change' });
123
+ } else if (realm === r || !realm/*generic handler*/ && (
124
+ this.#layoutDirsMatchMap[`${r.toUpperCase()}_DIR`].test(dir) && !(_toDedicated(target, r) in this.#jsMeta.dependencyMap)/*no dedicated handler exists*/
125
+ )) {
126
+ const actionableEffect = target === affectedHandler ? type : 'change';
127
+ events.add({ type, target, fileType: 'js', affectedRoute, affectedHandler, realm: r, actionableEffect });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if (events.size) {
134
+ this.fire(events);
135
+ }
136
+ }
137
+
138
+ async fire(events) {
139
+ for (const event of events) {
140
+ if (event.realm === 'client') {
141
+ this.#dirtiness.clientRoutesAffected.add(event.affectedRoute);
142
+ } else if (event.realm === 'worker') {
143
+ this.#dirtiness.serviceWorkerAffected = true;
144
+ } else if (event.realm === 'server') {
145
+ if (/^unlink/.test(event.actionableEffect)) {
146
+ delete this.#app.routes[event.affectedRoute];
147
+ } else if (event.realm === 'server') {
148
+ this.#app.routes[event.affectedRoute] = `${Path.join(this.#app.config.DEV_DIR, event.affectedHandler)}?_webflohmrhash=${Date.now()}`;
149
+ }
150
+ } else if (event.fileType === 'css') {
151
+ this.#dirtiness.CSSAffected = true;
152
+ } else if (event.fileType === 'html' || !event.fileType) {
153
+ this.#dirtiness.HTMLAffected = true;
154
+ }
155
+ }
156
+ if (this.#options.buildSensitivity === 1) {
157
+ await this.bundleAssetsIfPending();
158
+ }
159
+ // Broadcast to clients
160
+ const PUBLIC_DIR = Path.relative(process.cwd(), this.#app.config.LAYOUT.PUBLIC_DIR);
161
+ const $events = [...events].map((event) => {
162
+ const $event = { ...event };
163
+ $event.target = Path.relative(PUBLIC_DIR, event.target);
164
+ if (event.affectedHandler) {
165
+ $event.affectedHandler = Path.relative(PUBLIC_DIR, event.affectedHandler);
166
+ }
167
+ return $event;
168
+ });
169
+
170
+ for (const client of this.#clients) {
171
+ client.send(JSON.stringify($events));
172
+ }
173
+ }
174
+
175
+ async buildJS(fullBuild = false) {
176
+ // 0. Generate graph
177
+ let buildResult;
178
+ try {
179
+ if (this.#jsMeta.prevBuildResult) {
180
+ if (fullBuild) await this.#jsMeta.prevBuildResult.rebuild.dispose();
181
+ else buildResult = await buildResult.rebuild();
182
+ }
183
+ if (!buildResult) {
184
+ const routeDirs = [...this.#routeDirs];
185
+ const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{,.client,.worker,.server}.js`), { absolute: true })
186
+ .then((files) => files.map((file) => file.replace(/\\/g, '/')));
187
+ const entryNames = routeDirs.length === 1 ? `${Path.relative(process.cwd(), routeDirs[0])}/[dir]/[name]` : `[dir]/[name]`;
188
+ const bundlingConfig = {
189
+ entryPoints,
190
+ outdir: this.#app.config.DEV_DIR,
191
+ entryNames,
192
+ bundle: true,
193
+ format: 'esm',
194
+ platform: 'browser', // optional but good for clarity
195
+ metafile: true, // This is key
196
+ treeShaking: true, // Optional optimization
197
+ logLevel: 'silent', // Suppress output
198
+ minify: false,
199
+ sourcemap: false,
200
+ incremental: true,
201
+ };
202
+ buildResult = await EsBuild.build(bundlingConfig);
203
+ }
204
+ } catch (e) { return false; }
205
+
206
+ // 1. Forward dependency graph (file -> [imported files])
207
+ const forward = {};
208
+ for (const [file, data] of Object.entries(buildResult.metafile.inputs)) {
209
+ forward[file] = data.imports?.map((imp) => Path.normalize(imp.path)) || [];
210
+ }
211
+
212
+ // 2. Reverse dependency graph (file -> [parents])
213
+ const reverse = {};
214
+ for (const [file, imports] of Object.entries(forward)) {
215
+ for (const dep of imports) {
216
+ if (!reverse[dep]) reverse[dep] = [];
217
+ reverse[dep].push(file);
218
+ }
219
+ }
220
+
221
+ // 3. Trace from leaf file to roots (handler files)
222
+ const handlers = Object.keys(buildResult.metafile.inputs).filter((f) => this.#handlerMatch.test(f));
223
+ const handlerDepsMap = {};
224
+
225
+ for (const handler of handlers) {
226
+ const visited = new Set();
227
+ const stack = [handler];
228
+ while (stack.length) {
229
+ const current = stack.pop();
230
+ if (visited.has(current)) continue;
231
+ visited.add(current);
232
+ if (!handlerDepsMap[current]) handlerDepsMap[current] = [];
233
+ handlerDepsMap[current].push(handler);
234
+ const deps = forward[current] || [];
235
+ stack.push(...deps);
236
+ }
237
+ }
238
+ this.#jsMeta.dependencyMap = handlerDepsMap;
239
+ this.#jsMeta.mustRevalidate = false;
240
+
241
+ return true;
242
+ }
243
+
244
+ async bundleAssetsIfPending() {
245
+ const entries = {};
246
+
247
+ if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected) {
248
+ entries.js = {};
249
+ entries.js.client = !!this.#dirtiness.clientRoutesAffected.size;
250
+ entries.js.worker = this.#dirtiness.serviceWorkerAffected;
251
+ // Clear state
252
+ this.#dirtiness.clientRoutesAffected.clear();
253
+ this.#dirtiness.serviceWorkerAffected = false;
254
+ }
255
+
256
+ if (this.#dirtiness.HTMLAffected) {
257
+ this.#dirtiness.HTMLAffected = false;
258
+ entries.html = {};
259
+ }
260
+
261
+ if (this.#dirtiness.CSSAffected) {
262
+ this.#dirtiness.CSSAffected = false;
263
+ entries.css = {};
264
+ }
265
+
266
+ for (const e in entries) {
267
+ const buildKey = `build:${e}`;
268
+ let buildScript,
269
+ buildScriptName = this.#options.buildScripts?.[buildKey];
270
+ if (buildScriptName === true) {
271
+ buildScriptName = buildKey;
272
+ }
273
+ if (buildScriptName
274
+ && (buildScript = this.#options.appMeta.scripts?.[buildScriptName])) {
275
+ await this.#spawnProcess(buildScript, entries[e]);
276
+ }
277
+ }
278
+ }
279
+
280
+ async #spawnProcess(command, options = {}) {
281
+ const commandArr = [...new Set(
282
+ command.split(/\s+?/).concat(Object.keys(options)).filter((s) => !(s in options) || options[s])
283
+ )];
284
+ return await new Promise((resolve, reject) => {
285
+ const child = spawn(commandArr.shift(), commandArr, {
286
+ stdio: ['pipe', 'pipe', 'inherit', 'ipc'],
287
+ shell: true, // for Windows compatibility
288
+ });
289
+ child.on('message', (msg) => {
290
+ for (const file of msg?.outfiles || []) {
291
+ this.#ignoreList.add(file);
292
+ }
293
+ });
294
+ child.on('exit', (code) => {
295
+ if (code === 0) resolve(child);
296
+ else reject(new Error(`Process exited with code ${code}`));
297
+ });
298
+ });
299
+ }
300
+ }
301
+
302
+ const _toGeneric = (file) => {
303
+ return file.replace(/\.(client|worker|server)\.js$/, '.js');
304
+ };
305
+
306
+ const _toDedicated = (file, suffix) => {
307
+ return file.replace(/\.js$/, `.${suffix}.js`);
308
+ };
309
+
310
+ export function openBrowser(url) {
311
+ const plat = platform();
312
+ let command;
313
+ if (plat === 'darwin') {
314
+ command = `open "${url}"`;
315
+ } else if (plat === 'win32') {
316
+ command = `start "" "${url}"`;
317
+ } else if (plat === 'linux') {
318
+ command = `xdg-open "${url}"`;
319
+ } else {
320
+ console.warn('🌐 Unable to auto-open browser on this platform.');
321
+ return;
322
+ }
323
+ exec(command, (err) => {
324
+ if (err) console.error('❌ Failed to open browser:', err);
325
+ });
326
+ }
@@ -1,28 +1,10 @@
1
-
2
- /**
3
- * @imports
4
- */
5
1
  import { _with } from '@webqit/util/obj/index.js';
6
2
  import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
7
- import { Observer } from './Runtime.js';
8
- import { params } from '../util-url.js';
9
-
10
- /**
11
- * ---------------------------
12
- * The Url class
13
- * ---------------------------
14
- */
3
+ import { DeepURLSearchParams } from './util.js';
4
+ import { Observer } from '@webqit/quantum-js';
15
5
 
16
- export default class Url {
6
+ export class Url {
17
7
 
18
- /**
19
- * Constructs a new Url instance.
20
- *
21
- * @param object input
22
- * @param object pathMappingScheme
23
- *
24
- * @return void
25
- */
26
8
  constructor(input) {
27
9
  const Self = this.constructor;
28
10
  // -----------------------
@@ -56,6 +38,9 @@ export default class Url {
56
38
  // ----------
57
39
  if (e.key === 'href' && e.related.length === 1) {
58
40
  var urlObj = Self.parseUrl(e.value);
41
+ if (urlObj.pathname) {
42
+ urlObj.pathname = '/' + urlObj.pathname.split('/').filter(s => s.trim()).join('/');
43
+ }
59
44
  delete urlObj.query;
60
45
  delete urlObj.href;
61
46
  onlyHrefChanged = true;
@@ -68,13 +53,27 @@ export default class Url {
68
53
  urlObj.search = search;
69
54
  }
70
55
  }
71
- if (e.key === 'search') {
56
+ if (e.key === 'search' && !e.related.includes('query')) {
72
57
  // "search" was updated. So we update "query"
73
58
  var query = Self.toQuery(urlObj.search || this.search); // Not e.value, as that might be a href value
74
59
  if (!_strictEven(query, this.query)) {
75
60
  urlObj.query = query;
76
61
  }
77
62
  }
63
+ if (e.key === 'pathname' && !e.related.includes('ancestorPathname')) {
64
+ // "pathname" was updated. So we update "ancestorPathname"
65
+ var ancestorPathname = (urlObj.pathname || this.pathname).replace(new RegExp('/[^/]+(?:/)?$'), '');
66
+ if (ancestorPathname !== this.ancestorPathname) {
67
+ urlObj.ancestorPathname = ancestorPathname;
68
+ }
69
+ }
70
+ if (e.key === 'ancestorPathname' && !e.related.includes('pathname')) {
71
+ // "ancestorPathname" was updated. So we update "pathname"
72
+ var pathname = '/' + (urlObj.ancestorPathname || this.ancestorPathname).split('/').filter(s => s).concat((urlObj.pathname || this.pathname).split('/').filter(s => s).pop()).join('/');
73
+ if (pathname !== this.pathname) {
74
+ urlObj.pathname = pathname;
75
+ }
76
+ }
78
77
  }
79
78
  if (!onlyHrefChanged) {
80
79
  var fullOrigin = this.origin,
@@ -110,85 +109,37 @@ export default class Url {
110
109
  Observer.set(this, _isString(input) ? Self.parseUrl(input) : Url.copy(input));
111
110
  }
112
111
 
113
- /**
114
- * Converts the instance to string.
115
- *
116
- * @return string
117
- */
118
112
  toString() {
119
113
  return this.href;
120
114
  }
121
115
 
122
- /**
123
- * Creates an instance from parsing an URL string
124
- * or from a regular object.
125
- *
126
- * @param string|object href
127
- *
128
- * @return Url
129
- */
130
116
  static from(href) {
131
117
  return new this(_isObject(href) ? href : this.parseUrl(href));
132
118
  }
133
119
 
134
- /**
135
- * Copies URL properties off
136
- * the given object.
137
- *
138
- * @param object urlObj
139
- *
140
- * @return object
141
- */
142
120
  static copy(urlObj) {
143
- var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop]), {});
121
+ var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop] || ''), {});
144
122
  if (!('query' in urlObj)) {
145
123
  delete url.query;
146
124
  }
147
125
  return url;
148
126
  }
149
127
 
150
- /**
151
- * Parses an URL and returns its properties
152
- *
153
- * @param string href
154
- *
155
- * @return object
156
- */
157
128
  static parseUrl(href) {
158
- var a = document.createElement('a');
159
- a.href = href;
129
+ var a = new URL(href);
160
130
  return this.copy(a);
161
131
  }
162
132
 
163
- /**
164
- * Parses the input search string into a named map
165
- *
166
- * @param string search
167
- *
168
- * @return object
169
- */
170
133
  static toQuery(search) {
171
- return params.parse(search);
134
+ return DeepURLSearchParams.eval({}, search);
172
135
  }
173
136
 
174
- /**
175
- * Stringifies the input query to search string.
176
- *
177
- * @param object query
178
- *
179
- * @return string
180
- */
181
137
  static toSearch(query) {
182
- var search = params.stringify(query);
138
+ var search = DeepURLSearchParams.stringify(query);
183
139
  return search ? '?' + search : '';
184
- }}
140
+ }
141
+ }
185
142
 
186
- /**
187
- * These are standard
188
- * and shouldnt'/can't be modified
189
- *
190
- * @array
191
- */
192
143
  const urlProperties = [
193
144
  'protocol',
194
145
  'username',
@@ -0,0 +1 @@
1
+ import './urlpattern.js';
@@ -0,0 +1,38 @@
1
+ import { _isNumeric } from '@webqit/util/js/index.js';
2
+ if (typeof URLPattern === 'undefined') {
3
+ await import('urlpattern-polyfill');
4
+ }
5
+
6
+ const { exec: execMethod } = URLPattern.prototype;
7
+ const urlPatternMethods = {
8
+ isPattern: {
9
+ value: function () {
10
+ return Object.keys(this.keys || {}).some((compName) => this.keys[compName].length);
11
+ }
12
+ },
13
+ exec: {
14
+ value: function (...args) {
15
+ let components = execMethod.call(this, ...args);
16
+ if (!components) return;
17
+ components.vars = Object.keys(this.keys).reduce(({ named, unnamed }, compName) => {
18
+ this.keys[compName].forEach(key => {
19
+ let value = components[compName].groups[key.name];
20
+ if (typeof key.name === 'number') {
21
+ unnamed.push(value);
22
+ } else {
23
+ named[key.name] = value;
24
+ }
25
+ });
26
+ return { named, unnamed };
27
+ }, { named: {}, unnamed: [] });
28
+ components.render = (str) => {
29
+ return str.replace(/\$(\$|[0-9A-Z]+)/gi, (a, b) => {
30
+ return b === '$' ? '$' : (_isNumeric(b) ? components.vars.unnamed[b - 1] : components.vars.named[b]) || '';
31
+ });
32
+ }
33
+ return components;
34
+ }
35
+ },
36
+ };
37
+
38
+ Object.defineProperties(URLPattern.prototype, urlPatternMethods);