@webqit/webflo 0.20.4-next.4 → 0.20.4

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 (30) hide show
  1. package/package.json +15 -9
  2. package/src/build-pi/esbuild-plugin-livejs-transform.js +35 -0
  3. package/src/build-pi/index.js +5 -7
  4. package/src/init-pi/index.js +1 -1
  5. package/src/init-pi/templates/pwa/public/manifest.json +2 -2
  6. package/src/runtime-pi/WebfloRuntime.js +14 -23
  7. package/src/runtime-pi/apis.js +1 -1
  8. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
  9. package/src/runtime-pi/webflo-client/WebfloClient.js +2 -3
  10. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +4 -8
  11. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +1 -1
  12. package/src/runtime-pi/webflo-client/WebfloSubClient.js +1 -1
  13. package/src/runtime-pi/webflo-client/bootstrap.js +0 -1
  14. package/src/runtime-pi/webflo-fetch/LiveResponse.js +27 -34
  15. package/src/runtime-pi/webflo-fetch/index.js +14 -15
  16. package/src/runtime-pi/webflo-messaging/wq-message-port.js +1 -1
  17. package/src/runtime-pi/webflo-routing/HttpEvent.js +6 -8
  18. package/src/runtime-pi/webflo-routing/WebfloRouter.js +7 -12
  19. package/src/runtime-pi/webflo-server/WebfloServer.js +22 -57
  20. package/src/runtime-pi/webflo-server/webflo-devmode.js +7 -11
  21. package/src/runtime-pi/webflo-url/Url.js +1 -1
  22. package/src/runtime-pi/webflo-url/xURL.js +1 -1
  23. package/src/runtime-pi/webflo-worker/bootstrap.js +0 -1
  24. package/src/build-pi/esbuild-plugin-uselive-transform.js +0 -42
  25. package/src/init-pi/templates/pwa/.gitignore +0 -6
  26. package/src/init-pi/templates/pwa/.webqit/webflo/client.json +0 -15
  27. package/src/init-pi/templates/pwa/.webqit/webflo/layout.json +0 -7
  28. package/src/init-pi/templates/web/.gitignore +0 -6
  29. package/src/init-pi/templates/web/.webqit/webflo/client.json +0 -12
  30. package/src/init-pi/templates/web/.webqit/webflo/layout.json +0 -7
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.20.4-next.4",
15
+ "version": "0.20.4",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -42,15 +42,25 @@
42
42
  "webflo-certbot-http-cleanup-hook": "src/services-pi/cert/http-cleanup-hook.js"
43
43
  },
44
44
  "dependencies": {
45
+ "@linked-db/linked-ql": "^0.30.13",
45
46
  "@octokit/webhooks": "^7.15.1",
46
47
  "@webqit/backpack": "^0.1.12",
47
- "@webqit/oohtml-ssr": "^2.2.1",
48
- "@webqit/use-live": "^0.5.41",
48
+ "@webqit/oohtml-ssr": "^2.1.1",
49
+ "@webqit/quantum-js": "^4.6.3",
49
50
  "@webqit/util": "^0.8.11",
50
51
  "dotenv": "^16.4.7",
52
+ "fs-extra": "^11.3.0",
53
+ "i": "^0.3.7",
54
+ "ioredis": "^5.5.0",
55
+ "jsdom": "^21.1.1",
56
+ "markdown-it-mathjax3": "^4.3.2",
51
57
  "mime-types": "^2.1.33",
58
+ "npm": "^11.4.0",
59
+ "pg": "^8.13.3",
52
60
  "simple-git": "^2.20.1",
53
- "urlpattern-polyfill": "^4.0.3"
61
+ "urlpattern-polyfill": "^4.0.3",
62
+ "vitepress-plugin-mermaid": "^2.0.17",
63
+ "web-push": "^3.6.7"
54
64
  },
55
65
  "devDependencies": {
56
66
  "chai": "^4.3.6",
@@ -58,13 +68,9 @@
58
68
  "coveralls": "^3.1.1",
59
69
  "esbuild": "^0.14.38",
60
70
  "fast-glob": "^3.3.3",
61
- "jsdom": "^27.0.1",
62
- "markdown-it-mathjax3": "^4.3.2",
63
71
  "mocha": "^10.0.0",
64
72
  "mocha-lcov-reporter": "^1.3.0",
65
- "vitepress": "^1.6.4",
66
- "vitepress-plugin-mermaid": "^2.0.17",
67
- "web-push": "^3.6.7"
73
+ "vitepress": "^1.6.4"
68
74
  },
69
75
  "author": "Oxford Harrison <oxharris.dev@gmail.com>",
70
76
  "maintainers": [
@@ -0,0 +1,35 @@
1
+ import Fs from 'fs/promises';
2
+ //import { transformQuantum } from '@webqit/quantum-js';
3
+
4
+ export function LiveJSTransform() {
5
+ return {
6
+ name: 'livejs-transform',
7
+ setup(build) {
8
+ build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
9
+ let code = await Fs.readFile(args.path, 'utf8');
10
+
11
+ //console.log('LiveJS -- transform:', args);
12
+
13
+ // super dirty detection
14
+ if (!/\bquantum\s+function\b/.test(code) &&
15
+ !/\basync\s+quantum\s+function\b/.test(code)) {
16
+ return { contents: code, loader: 'default' };
17
+ }
18
+
19
+
20
+ console.log('LiveJS transform:', args.path);
21
+
22
+ return { contents: code, loader: 'default' };
23
+ const result = await transformQuantum(code, {
24
+ filename: args.path,
25
+ sourceMaps: true
26
+ });
27
+
28
+ return {
29
+ contents: result.code,
30
+ loader: 'js' // or 'ts' if you want esbuild TS transform after
31
+ };
32
+ });
33
+ }
34
+ };
35
+ }
@@ -10,7 +10,7 @@ import { jsFile } from '@webqit/backpack/src/dotfile/index.js';
10
10
  import { bootstrap as serverBootstrap } from '../runtime-pi/webflo-server/bootstrap.js';
11
11
  import { bootstrap as clientBootstrap } from '../runtime-pi/webflo-client/bootstrap.js';
12
12
  import { bootstrap as workerBootstrap } from '../runtime-pi/webflo-worker/bootstrap.js';
13
- import { UseLiveTransform } from './esbuild-plugin-uselive-transform.js';
13
+ import { LiveJSTransform } from './esbuild-plugin-livejs-transform.js';
14
14
  import { CLIContext } from '../CLIContext.js';
15
15
  import '../runtime-pi/webflo-url/urlpattern.js';
16
16
 
@@ -145,14 +145,14 @@ async function bundleScript({ $context, $source, which, outfile, asModule = true
145
145
  const bundlingConfig = {
146
146
  entryPoints: [moduleFile],
147
147
  outfile,
148
+ bundle: which === 'server' ? false : true,
149
+ minify: true,
148
150
  format: asModule ? 'esm' : 'iife',
149
151
  platform: which === 'server' ? 'node' : 'browser', // optional but good for clarity
150
- bundle: which === 'server' ? false : true,
151
- minify: which === 'server' ? false : true,
152
152
  treeShaking: true, // Important optimization
153
153
  banner: { js: '/** @webqit/webflo */', },
154
154
  footer: { js: '', },
155
- plugins: [UseLiveTransform()],
155
+ plugins: [ LiveJSTransform() ],
156
156
  ...(restParams.buildParams || {})
157
157
  };
158
158
  if (!asModule) {
@@ -290,10 +290,8 @@ async function generateClientScript({ $context, bootstrap, ...restParams }) {
290
290
 
291
291
  const configExport = structuredClone({ ENV: bootstrap.config.ENV, CLIENT: bootstrap.config.CLIENT, WORKER: {} });
292
292
  if (bootstrap.config.CLIENT.capabilities?.service_worker === true) {
293
- const outfile_workerBuild = Path.join(FLAGS.outdir || bootstrap.outdir, bootstrap.config.WORKER.filename);
294
- const outfile_workerBuildPublic = Path.join(publicBaseUrl, Path.relative(bootstrap.config.LAYOUT.PUBLIC_DIR, outfile_workerBuild));
295
293
  configExport.WORKER = {
296
- filename: outfile_workerBuildPublic,
294
+ filename: Path.join(publicBaseUrl.replace(/^\//, ''), bootstrap.config.WORKER.filename),
297
295
  scope: bootstrap.config.WORKER.scope
298
296
  };
299
297
  }
@@ -40,7 +40,7 @@ export async function init(projectName = 'my-webflo-app', projectTitle = '', pro
40
40
  process.exit(1);
41
41
  }
42
42
 
43
- LOGGER?.log(LOGGER.style.keyword(`> `) + `Initializing your webflo app "${projectName}" using template "${template}"...\n`);
43
+ LOGGER?.log(LOGGER.style.keyword(`> `) + `Initializing your webflo app: "${projectName}" using template "${template}"...\n`);
44
44
 
45
45
  // 1. Create project dir
46
46
  await Fs2.mkdir(targetDir, { recursive: true });
@@ -10,12 +10,12 @@
10
10
  "scope": "/",
11
11
  "icons": [
12
12
  {
13
- "src": "/assets/logo.png",
13
+ "src": "/assets/...",
14
14
  "sizes": "192x192",
15
15
  "type": "image/png"
16
16
  },
17
17
  {
18
- "src": "/assets/logo.png",
18
+ "src": "/assets/...",
19
19
  "sizes": "512x512",
20
20
  "type": "image/png",
21
21
  "purpose": "maskable"
@@ -1,6 +1,5 @@
1
1
  import { WebfloRouter } from './webflo-routing/WebfloRouter.js';
2
- import { response as responseShim, headers as headersShim } from './webflo-fetch/index.js';
3
- import { LiveResponse } from './webflo-fetch/LiveResponse.js';
2
+ import { LiveResponse, response as responseShim, headers as headersShim } from './webflo-fetch/index.js';
4
3
  import { AppBootstrap } from './AppBootstrap.js';
5
4
  import { _wq } from '../util.js';
6
5
 
@@ -32,14 +31,6 @@ export class WebfloRuntime {
32
31
  }
33
32
 
34
33
  async initialize() {
35
- if (this.bootstrap.init.SETUP) {
36
- await this.bootstrap.init.SETUP(this);
37
- }
38
- await this.initCreateStorage();
39
- return this.#instanceController;
40
- }
41
-
42
- async initCreateStorage() {
43
34
  if (!this.bootstrap.init.createStorage) {
44
35
  const inmemSessionRegistry = new Map;
45
36
  this.bootstrap.init.createStorage = (namespace) => {
@@ -64,8 +55,8 @@ export class WebfloRuntime {
64
55
  return this.#instanceController;
65
56
  }
66
57
 
67
- createStorage(namespace, ttl) {
68
- return this.bootstrap.init.createStorage(namespace, ttl);
58
+ async createStorage(namespace, ttl) {
59
+ return await this.bootstrap.init.createStorage(namespace, ttl);
69
60
  }
70
61
 
71
62
  createRequest(href, init = {}) {
@@ -99,7 +90,7 @@ export class WebfloRuntime {
99
90
  }
100
91
  // Dispatch event
101
92
  const router = new this.constructor.Router(this, httpEvent.url.pathname);
102
- await router.route(['SETUP'], httpEvent.extend(false));
93
+ await router.route(['SETUP'], httpEvent);
103
94
  // Do proper routing for respone
104
95
  const response = await new Promise(async (resolve) => {
105
96
  let autoLiveResponse, response;
@@ -121,11 +112,10 @@ export class WebfloRuntime {
121
112
  console.error(e);
122
113
  response = new Response(null, { status: 500, statusText: e.message });
123
114
  }
124
- if (!/Response/.test(LiveResponse.test(response))) {
125
- const isLifecyleComplete = httpEvent.lifeCycleComplete();
126
- response = LiveResponse.test(response) !== 'Default' || !isLifecyleComplete
127
- ? await LiveResponse.from(response, { done: isLifecyleComplete })
128
- : responseShim.from.value(response);
115
+ if (!(response instanceof LiveResponse) && !(response instanceof Response)) {
116
+ response = LiveResponse.test(response) === 'Default'
117
+ ? responseShim.from.value(response)
118
+ : await LiveResponse.from(response, { responsesOK: true });
129
119
  }
130
120
  // Any "carry" data?
131
121
  //await this.handleCarries(httpEvent, response);
@@ -136,14 +126,15 @@ export class WebfloRuntime {
136
126
  resolve(response);
137
127
  }
138
128
  });
139
-
140
129
  // Commit data in the exact order. Reason: in how they depend on each other
141
130
  for (const storage of [httpEvent.user, httpEvent.session, httpEvent.cookies]) {
142
131
  await storage?.commit?.(response, FLAGS['dev']);
143
132
  }
144
- // Wait for any whileLive promises to resolve
145
- if (LiveResponse.test(response) === 'LiveResponse' && response.whileLive()) {
133
+ if (response instanceof LiveResponse && response.whileLive()) {
146
134
  httpEvent.waitUntil(response.whileLive(true));
135
+ } else {
136
+ httpEvent.waitUntil(Promise.resolve());
137
+ await null; // We need the above resolved before we move on
147
138
  }
148
139
 
149
140
  // Send the X-Background-Messaging-Port header
@@ -182,9 +173,9 @@ export class WebfloRuntime {
182
173
  });
183
174
  }
184
175
 
185
- if (!this.isClientSide && LiveResponse.test(response) === 'LiveResponse') {
176
+ if (!this.isClientSide && response instanceof LiveResponse) {
186
177
  // Must convert to Response on the server-side before returning
187
- return await response.toResponse({ client: httpEvent.client });
178
+ return response.toResponse({ client: httpEvent.client });
188
179
  }
189
180
 
190
181
  return response;
@@ -1,5 +1,5 @@
1
1
  import { LiveResponse } from './webflo-fetch/LiveResponse.js';
2
- import { Observer } from '@webqit/use-live';
2
+ import { Observer } from '@webqit/quantum-js';
3
3
  import { shim } from './webflo-fetch/index.js';
4
4
 
5
5
  export {
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/use-live';
1
+ import { Observer } from '@webqit/quantum-js';
2
2
 
3
3
  export class DeviceCapabilities {
4
4
 
@@ -1,10 +1,9 @@
1
1
  import { _before, _toTitle } from '@webqit/util/str/index.js';
2
2
  import { _isObject } from '@webqit/util/js/index.js';
3
- import { Observer } from '@webqit/use-live';
3
+ import { Observer } from '@webqit/quantum-js';
4
4
  import { WebfloRuntime } from '../WebfloRuntime.js';
5
5
  import { WQMessageChannel } from '../webflo-messaging/WQMessageChannel.js';
6
- import { response as responseShim } from '../webflo-fetch/index.js';
7
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
6
+ import { LiveResponse, response as responseShim } from '../webflo-fetch/index.js';
8
7
  import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
9
8
  import { ClientSideCookies } from './ClientSideCookies.js';
10
9
  import { HttpSession } from '../webflo-routing/HttpSession.js';
@@ -1,9 +1,8 @@
1
- import { Observer } from '@webqit/use-live';
1
+ import { Observer } from '@webqit/quantum-js';
2
2
  import { WebfloClient } from './WebfloClient.js';
3
3
  import { ClientSideWorkport } from './ClientSideWorkport.js';
4
4
  import { DeviceCapabilities } from './DeviceCapabilities.js';
5
- import { response as responseShim } from '../webflo-fetch/index.js';
6
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
5
+ import { LiveResponse, response as responseShim } from '../webflo-fetch/index.js';
7
6
  import { WebfloHMR } from './webflo-devmode.js';
8
7
 
9
8
  export class WebfloRootClient1 extends WebfloClient {
@@ -93,10 +92,8 @@ export class WebfloRootClient1 extends WebfloClient {
93
92
  cleanups.push(() => this.#capabilities.close());
94
93
  if (this.config.CLIENT.capabilities?.service_worker) {
95
94
  const { filename, ...restServiceWorkerParams } = this.config.WORKER;
96
- this.constructor.Workport.initialize(null, filename, restServiceWorkerParams).then((workport) => {
97
- this.#workport = workport;
98
- cleanups.push(() => this.#workport.close());
99
- });
95
+ this.#workport = await this.constructor.Workport.initialize(null, (this.config.CLIENT.public_base_url || '') + filename, restServiceWorkerParams);
96
+ cleanups.push(() => this.#workport.close());
100
97
  }
101
98
  return instanceController;
102
99
  }
@@ -116,7 +113,6 @@ export class WebfloRootClient1 extends WebfloClient {
116
113
  this.background.addPort(backgroundPort);
117
114
  }
118
115
  if (scopeObj.response.body || backgroundPort) {
119
-
120
116
  const httpEvent = this.createHttpEvent({ request: this.createRequest(this.location.href) }, true);
121
117
  await this.render(httpEvent, scopeObj.response);
122
118
  } else {
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/use-live';
1
+ import { Observer } from '@webqit/quantum-js';
2
2
  import { WebfloRootClient1 } from './WebfloRootClient1.js';
3
3
 
4
4
  export class WebfloRootClient2 extends WebfloRootClient1 {
@@ -1,4 +1,4 @@
1
- import { Observer } from '@webqit/use-live';
1
+ import { Observer } from '@webqit/quantum-js';
2
2
  import { WebfloClient } from './WebfloClient.js';
3
3
  import { defineElement } from './webflo-embedded.js';
4
4
  import { Url } from '../webflo-url/Url.js';
@@ -21,7 +21,6 @@ export async function bootstrap(cx, offset = '') {
21
21
  };
22
22
  if (config.CLIENT.copy_public_variables) {
23
23
  const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
24
- config.ENV.data = config.ENV.data || {};
25
24
  for (const key in process.env) {
26
25
  if (publicEnvPattern.test(key)) {
27
26
  config.ENV.data[key] = process.env[key];
@@ -1,4 +1,4 @@
1
- import { Observer, LiveMode } from '@webqit/use-live';
1
+ import { State, Observer } from '@webqit/quantum-js';
2
2
  import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
3
3
  import { publishMutations, applyMutations } from '../webflo-messaging/wq-message-port.js';
4
4
  import { WQBroadcastChannel } from '../webflo-messaging/WQBroadcastChannel.js';
@@ -9,11 +9,8 @@ import { isTypeStream } from './util.js';
9
9
 
10
10
  export class LiveResponse extends EventTarget {
11
11
 
12
- [Symbol.toStringTag] = 'LiveResponse';
13
-
14
12
  static test(data) {
15
- if (data instanceof LiveResponse
16
- || data?.[Symbol.toStringTag] === 'LiveResponse') {
13
+ if (data instanceof LiveResponse) {
17
14
  return 'LiveResponse';
18
15
  }
19
16
  if (data instanceof Response) {
@@ -22,25 +19,24 @@ export class LiveResponse extends EventTarget {
22
19
  if (isGenerator(data)) {
23
20
  return 'Generator';
24
21
  }
25
- if (data instanceof LiveMode
26
- || data?.[Symbol.toStringTag] === 'LiveMode') {
27
- return 'LiveMode';
22
+ if (data instanceof State) {
23
+ return 'Quantum';
28
24
  }
29
25
  return 'Default';
30
26
  }
31
27
 
32
28
  static async from(data, ...args) {
33
- if (this.test(data) === 'LiveResponse') {
29
+ if (data instanceof LiveResponse) {
34
30
  return data.clone(...args);
35
31
  }
36
- if (this.test(data) === 'Response') {
32
+ if (data instanceof Response) {
37
33
  return await this.fromResponse(data, ...args);
38
34
  }
39
- if (this.test(data) === 'Generator') {
35
+ if (isGenerator(data)) {
40
36
  return await this.fromGenerator(data, ...args);
41
37
  }
42
- if (this.test(data) === 'LiveMode') {
43
- return this.fromLiveMode(data, ...args);
38
+ if (data instanceof State) {
39
+ return this.fromQuantum(data, ...args);
44
40
  }
45
41
  return new this(data, ...args);
46
42
  }
@@ -49,7 +45,7 @@ export class LiveResponse extends EventTarget {
49
45
  if (!(response instanceof Response)) {
50
46
  throw new Error('Argument must be a Response instance.');
51
47
  }
52
-
48
+
53
49
  const body = await responseShim.prototype.parse.value.call(response);
54
50
 
55
51
  // Instance
@@ -112,7 +108,7 @@ export class LiveResponse extends EventTarget {
112
108
  let $$await;
113
109
 
114
110
  const $options = { done: firstFrame.done, ...options };
115
- if (this.test(value) === 'LiveResponse') {
111
+ if (value instanceof LiveResponse) {
116
112
  instance = new this;
117
113
  const responseMeta = _wq(value, 'meta');
118
114
  _wq(instance).set('meta', responseMeta);
@@ -137,18 +133,15 @@ export class LiveResponse extends EventTarget {
137
133
  return instance;
138
134
  }
139
135
 
140
- static async fromLiveMode(liveMode, options = {}) {
141
- if (!this.test(liveMode) === 'LiveMode') {
142
- throw new Error('Argument must be a UseLive LiveMode instance.');
143
- }
144
- const instance = new this;
145
- await instance.replaceWith(liveMode.value, { done: false, ...options });
146
- if (instance.#generatorType === 'Default') {
147
- instance.#generator = liveMode;
148
- instance.#generatorType = 'LiveMode';
136
+ static async fromQuantum(qState, options = {}) {
137
+ if (!(qState instanceof State)) {
138
+ throw new Error('Argument must be a Quantum State instance.');
149
139
  }
140
+ const instance = new this(qState.value, { done: false, ...options });
141
+ instance.#generator = qState;
142
+ instance.#generatorType = 'Quantum';
150
143
  Observer.observe(
151
- liveMode,
144
+ qState,
152
145
  'value',
153
146
  (e) => instance.#replaceWith(e.value),
154
147
  { signal: instance.#abortController.signal }
@@ -163,7 +156,8 @@ export class LiveResponse extends EventTarget {
163
156
  }
164
157
 
165
158
  static getBackground(respone) {
166
- if (!/Response/.test(this.test(respone) )) return;
159
+ if (!(respone instanceof Response)
160
+ && !(respone instanceof LiveResponse)) return;
167
161
  const responseMeta = _wq(respone, 'meta');
168
162
  if (!responseMeta.has('background_port')) {
169
163
  const value = respone.headers.get('X-Background-Messaging-Port')?.trim();
@@ -328,9 +322,8 @@ export class LiveResponse extends EventTarget {
328
322
  const execReplaceWithResponse = async (response, options) => {
329
323
  this.#generator = response;
330
324
  this.#generatorType = response instanceof Response ? 'Response' : 'LiveResponse';
331
- const body = response instanceof Response ? await responseShim.prototype.parse.value.call(response) : response.body;
332
325
  execReplaceWith({
333
- body,
326
+ body: response instanceof Response ? await responseShim.prototype.parse.value.call(response) : response.body,
334
327
  status: responseShim.prototype.status.get.call(response),
335
328
  statusText: response.statusText,
336
329
  headers: response.headers,
@@ -339,7 +332,7 @@ export class LiveResponse extends EventTarget {
339
332
  redirected: response.redirected,
340
333
  url: response.url,
341
334
  });
342
- if (this.test(response) === 'LiveResponse') {
335
+ if (response instanceof LiveResponse) {
343
336
  response.addEventListener('replace', () => execReplaceWith(response), { signal: this.#abortController.signal });
344
337
  return await response.whileLive(true);
345
338
  }
@@ -367,7 +360,7 @@ export class LiveResponse extends EventTarget {
367
360
  };
368
361
 
369
362
  let donePromise;
370
- if (/Response/.test(body)) {
363
+ if (body instanceof Response || body instanceof LiveResponse) {
371
364
  if (frameClosure) {
372
365
  throw new Error('frameClosure unsupported for inputs of type response.');
373
366
  }
@@ -446,8 +439,8 @@ export class LiveResponse extends EventTarget {
446
439
  }));
447
440
  }
448
441
 
449
- toLiveMode({ signal: abortSignal } = {}) {
450
- const state = new LiveModeX;
442
+ toQuantum({ signal: abortSignal } = {}) {
443
+ const state = new StateX;
451
444
  const replaceHandler = () => Observer.defineProperty(state, 'value', { value: this.body, enumerable: true, configurable: true });
452
445
  this.addEventListener('replace', replaceHandler, { signal: abortSignal });
453
446
  replaceHandler();
@@ -469,7 +462,7 @@ export const isGenerator = (obj) => {
469
462
  typeof obj?.return === 'function';
470
463
  };
471
464
 
472
- class LiveModeX extends LiveMode {
465
+ class StateX extends State {
473
466
  constructor() { }
474
- abort() { }
467
+ dispose() { }
475
468
  }
@@ -1,11 +1,10 @@
1
+ export { LiveResponse } from './LiveResponse.js';
1
2
  import { _isObject, _isTypeObject, _isNumeric } from '@webqit/util/js/index.js';
2
3
  import { _from as _arrFrom } from '@webqit/util/arr/index.js';
3
4
  import { _before, _after } from '@webqit/util/str/index.js';
4
5
  import { DeepURLSearchParams } from '../webflo-url/util.js';
5
6
  import { dataType } from './util.js';
6
7
  import { _wq } from '../../util.js';
7
- import { Observer } from '@webqit/use-live';
8
- import { LiveResponse } from './LiveResponse.js';
9
8
 
10
9
  // ----- env & globalize
11
10
 
@@ -31,8 +30,6 @@ export function shim(prefix = 'wq') {
31
30
  patch(api.prototype, prototype);
32
31
  }
33
32
  }
34
- globalThis.LiveResponse = LiveResponse;
35
- globalThis.Observer = Observer;
36
33
  }
37
34
 
38
35
  // ----- request
@@ -339,23 +336,23 @@ export function renderHttpMessageInit(httpMessageInit) {
339
336
  return { ..._headers, [name.toLowerCase()]: httpMessageInit.headers[name] };
340
337
  }, {});
341
338
  // Process body
342
- let body = httpMessageInit.body,
343
- type = dataType(httpMessageInit.body);
344
-
339
+ let body = httpMessageInit.body, type = dataType(httpMessageInit.body);
345
340
  if (['Blob', 'File'].includes(type)) {
346
341
  !headers['content-type'] && (headers['content-type'] = body.type);
347
342
  !headers['content-length'] && (headers['content-length'] = body.size);
348
343
  } else if (['Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer'].includes(type)) {
349
344
  !headers['content-length'] && (headers['content-length'] = body.byteLength);
350
345
  } else if (type === 'json' && _isTypeObject(body)/*JSON object*/) {
351
- const [_body, isJsonfiable] = createFormDataFromJson(body, true/*jsonfy*/, true/*getIsJsonfiable*/);
352
- if (isJsonfiable) {
353
- body = JSON.stringify(body, (k, v) => v instanceof Error ? { ...v, message: v.message } : v);
354
- headers['content-type'] = 'application/json';
355
- headers['content-length'] = (new Blob([body])).size;
356
- } else {
357
- body = _body;
358
- type = 'FormData';
346
+ if (!headers['content-type']) {
347
+ const [_body, isJsonfiable] = createFormDataFromJson(body, true/*jsonfy*/, true/*getIsJsonfiable*/);
348
+ if (isJsonfiable) {
349
+ body = JSON.stringify(body, (k, v) => v instanceof Error ? { ...v, message: v.message } : v);
350
+ headers['content-type'] = 'application/json';
351
+ headers['content-length'] = (new Blob([body])).size;
352
+ } else {
353
+ body = _body;
354
+ type = 'FormData';
355
+ }
359
356
  }
360
357
  } else if (type === 'json'/*JSON string*/ && !headers['content-length']) {
361
358
  (headers['content-length'] = (body + '').length);
@@ -432,5 +429,7 @@ export function renderCookieObjToString(cookieObj) {
432
429
 
433
430
  const importUrl = new URL(import.meta.url);
434
431
  if (importUrl.searchParams.has('shim')) {
432
+ globalThis.LiveResponse = LiveResponse;
435
433
  shim(importUrl.searchParams.get('shim')?.trim());
434
+ console.log('Webflo Fetch APIs shimmed.');
436
435
  }
@@ -2,7 +2,7 @@ import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
2
2
  import { WQMessagePort, WQMessagePortInstanceTag } from './WQMessagePort.js';
3
3
  import { isTypeStream } from '../webflo-fetch/util.js';
4
4
  import { WQMessageEvent } from './WQMessageEvent.js';
5
- import { Observer } from '@webqit/use-live';
5
+ import { Observer } from '@webqit/quantum-js';
6
6
  import { _wq } from '../../util.js';
7
7
 
8
8
  /**
@@ -1,6 +1,6 @@
1
1
  import { _isObject } from '@webqit/util/js/index.js';
2
2
  import { _difference } from '@webqit/util/arr/index.js';
3
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
3
+ import { LiveResponse } from '../webflo-fetch/index.js';
4
4
  import { xURL } from '../webflo-url/xURL.js';
5
5
  import { _wq } from '../../util.js';
6
6
 
@@ -45,8 +45,6 @@ export class HttpEvent {
45
45
  get state() { return { ...(this.#init.state || {}) }; }
46
46
 
47
47
  #lifecyclePromises = new Set;
48
- get lifecyclePromises() { return this.#lifecyclePromises; }
49
-
50
48
  #lifeCycleResolve;
51
49
  #lifeCycleReject;
52
50
  #lifeCycleResolutionPromise = new Promise((resolve, reject) => {
@@ -82,12 +80,12 @@ export class HttpEvent {
82
80
  && !this.#lifecyclePromises.size;
83
81
  }
84
82
 
85
- async waitUntil(promise) {
86
- return await this.#extendLifecycle(promise);
83
+ waitUntil(promise) {
84
+ return this.#extendLifecycle(promise);
87
85
  }
88
86
 
89
87
  waitUntilNavigate() {
90
- this.waitUntil(new Promise(() => { }));
88
+ return this.waitUntil(new Promise(() => { }));
91
89
  }
92
90
 
93
91
  #internalLiveResponse = new LiveResponse(null, { done: false });
@@ -102,8 +100,8 @@ export class HttpEvent {
102
100
  }
103
101
 
104
102
  extend(init = {}) {
105
- const instance = this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
106
- if (init !== false) this.#extendLifecycle(instance.lifeCycleComplete(true));
103
+ const instance = this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...init });
104
+ this.#extendLifecycle(instance.lifeCycleComplete(true));
107
105
  return instance;
108
106
  }
109
107
 
@@ -1,7 +1,8 @@
1
+ import { State } from '@webqit/quantum-js';
1
2
  import { _isFunction, _isArray, _isObject } from '@webqit/util/js/index.js';
2
3
  import { _from as _arrFrom } from '@webqit/util/arr/index.js';
3
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
4
4
  import { path as Path } from '../webflo-url/util.js';
5
+ import { isGenerator } from '../webflo-fetch/LiveResponse.js';
5
6
 
6
7
  export class WebfloRouter {
7
8
 
@@ -169,11 +170,11 @@ export class WebfloRouter {
169
170
  const returnValue = await handler.call(thisContext, thisTick.event, $next/*next*/, $fetch/*fetch*/);
170
171
 
171
172
  // Handle cleanup on abort
172
- if (LiveResponse.test(returnValue) === 'LiveMode') {
173
+ if (returnValue instanceof State) {
173
174
  thisTick.event.signal.addEventListener('abort', () => {
174
- returnValue.abort();
175
+ returnValue.dispose();
175
176
  });
176
- } else if (LiveResponse.test(returnValue) === 'Generator') {
177
+ } else if (isGenerator(returnValue)) {
177
178
  thisTick.event.signal.addEventListener('abort', () => {
178
179
  if (typeof returnValue.return === 'function') {
179
180
  returnValue.return();
@@ -186,19 +187,13 @@ export class WebfloRouter {
186
187
  resolved = 2;
187
188
  resolve(returnValue);
188
189
  } else if (typeof returnValue !== 'undefined') {
189
- thisTick.event.internalLiveResponse.replaceWith(returnValue, { done: true });
190
+ await thisTick.event.internalLiveResponse.replaceWith(returnValue, { done: true });
190
191
  }
191
192
  });
192
193
  }
193
- let returnValue;
194
194
  if (_default) {
195
- returnValue = await _default.call(thisContext, thisTick.event, remoteFetch);
195
+ return await _default.call(thisContext, thisTick.event, remoteFetch);
196
196
  }
197
- try {
198
- // IMPORTANT: Explicitly terminate the event lifecycle if nothing extends it
199
- await thisTick.event.waitUntil();
200
- } catch(e) {}
201
- return returnValue;
202
197
  };
203
198
 
204
199
  return next({
@@ -23,7 +23,7 @@ import { ServerSideSession } from './ServerSideSession.js';
23
23
  import { HttpEvent } from '../webflo-routing/HttpEvent.js';
24
24
  import { HttpUser } from '../webflo-routing/HttpUser.js';
25
25
  import { response as responseShim, headers as headersShim } from '../webflo-fetch/index.js';
26
- import { UseLiveTransform } from '../../build-pi/esbuild-plugin-uselive-transform.js';
26
+ import { LiveJSTransform } from '../../build-pi/esbuild-plugin-livejs-transform.js';
27
27
  import { createWindow } from '@webqit/oohtml-ssr';
28
28
  import { _wq } from '../../util.js';
29
29
  import '../webflo-fetch/index.js';
@@ -57,6 +57,7 @@ export class WebfloServer extends WebfloRuntime {
57
57
  }
58
58
 
59
59
  async initialize() {
60
+ const instanceController = await super.initialize();
60
61
  const { appMeta: APP_META, flags: FLAGS, logger: LOGGER, } = this.cx;
61
62
 
62
63
  // ----------
@@ -64,18 +65,9 @@ export class WebfloServer extends WebfloRuntime {
64
65
  if (FLAGS['dev']) {
65
66
  await this.enterDevMode();
66
67
  } else {
67
- await this.buildRoutes({ server: true });
68
- await this.bundleAssetsIfPending(true);
68
+ await this.buildRoutes();
69
69
  }
70
70
 
71
- // ----------
72
- // Call default-init
73
- const instanceController = await super.initialize();
74
-
75
- // ----------
76
- // Start serving
77
- this.control();
78
-
79
71
  // ----------
80
72
  // Show proxies
81
73
  const { PROXY } = this.config;
@@ -98,6 +90,10 @@ export class WebfloServer extends WebfloRuntime {
98
90
  }
99
91
  }
100
92
 
93
+ // ----------
94
+ // Start serving
95
+ this.control();
96
+
101
97
  // ----------
102
98
  // Show server details
103
99
  if (this.#servers.size) {
@@ -112,22 +108,22 @@ export class WebfloServer extends WebfloRuntime {
112
108
  return instanceController;
113
109
  }
114
110
 
115
- async buildRoutes({ client = false, worker = false, server = false, ...options } = {}) {
111
+ async buildRoutes({ client = false, worker = false, ...options } = {}) {
116
112
  const routeDirs = [...new Set([this.config.LAYOUT.CLIENT_DIR, this.config.LAYOUT.WORKER_DIR, this.config.LAYOUT.SERVER_DIR])];
117
- const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{${client ? ',.client' : ''}${worker ? ',.worker' : ''}${server ? ',.server' : ''}}.js`), { absolute: true })
113
+ const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{${client ? ',.client' : ''}${worker ? ',.worker' : ''},.server}.js`), { absolute: true })
118
114
  .then((files) => files.map((file) => file.replace(/\\/g, '/')));
119
- const initFiles = await $glob(`${process.cwd()}/init.server.js`);
115
+ const entryNames = routeDirs.length === 1 ? `${Path.relative(process.cwd(), routeDirs[0])}/[dir]/[name]` : `[dir]/[name]`;
120
116
  const bundlingConfig = {
121
- entryPoints: entryPoints.concat(initFiles),
117
+ entryPoints,
122
118
  outdir: this.config.RUNTIME_DIR,
123
- outbase: process.cwd(),
119
+ entryNames,
120
+ bundle: true,
124
121
  format: 'esm',
125
- platform: server ? 'node' : 'browser',
126
- bundle: server ? false : true,
127
- minify: server ? false : true,
122
+ minify: false,
128
123
  sourcemap: false,
129
- treeShaking: true,
130
- plugins: [UseLiveTransform()],
124
+ platform: 'browser', // optional but good for clarity
125
+ treeShaking: true, // Important optimization
126
+ plugins: [ LiveJSTransform() ],
131
127
  ...options,
132
128
  };
133
129
  return await EsBuild.build(bundlingConfig);
@@ -145,7 +141,6 @@ export class WebfloServer extends WebfloRuntime {
145
141
  buildSensitivity: parseInt(FLAGS['build-sensitivity'] || 0),
146
142
  });
147
143
  await this.#hmr.buildRoutes(true);
148
- await this.#hmr.bundleAssetsIfPending(true);
149
144
  if (FLAGS['open']) {
150
145
  for (let [proto, def] of this.#servers) {
151
146
  const url = `${proto}://${def.hostnames.find((h) => h !== '*') || 'localhost'}:${def.port}`;
@@ -154,35 +149,6 @@ export class WebfloServer extends WebfloRuntime {
154
149
  }
155
150
  }
156
151
 
157
- async initCreateStorage() {
158
- if (this.bootstrap.init.createStorage
159
- || !this.bootstrap.init.redis) {
160
- return super.initCreateStorage();
161
- }
162
- const redis = this.bootstrap.init.redis;
163
- this.bootstrap.init.createStorage = (namespace, ttl = null) => ({
164
- async has(key) { return await redis.hexists(namespace, key); },
165
- async get(key) {
166
- const value = await redis.hget(namespace, key);
167
- return typeof value === 'undefined' ? value : JSON.parse(value);
168
- },
169
- async set(key, value) {
170
- const returnValue = await redis.hset(namespace, key, JSON.stringify(value));
171
- if (!this.ttlApplied && ttl) {
172
- await redis.expire(namespace, ttl);
173
- this.ttlApplied = true;
174
- }
175
- return returnValue;
176
- },
177
- async delete(key) { return await redis.hdel(namespace, key); },
178
- async clear() { return await redis.del(namespace); },
179
- async keys() { return await redis.hkeys(namespace); },
180
- async values() { return (await redis.hvals(namespace) || []).map((value) => typeof value === 'undefined' ? value : JSON.parse(value)); },
181
- async entries() { return Object.entries(await redis.hgetall(namespace) || {}).map(([key, value]) => [key, typeof value === 'undefined' ? value : JSON.parse(value)]); },
182
- get size() { return redis.hlen(namespace); },
183
- });
184
- }
185
-
186
152
  control() {
187
153
  const { flags: FLAGS } = this.cx;
188
154
  const { SERVER, PROXY } = this.config;
@@ -562,10 +528,6 @@ export class WebfloServer extends WebfloRuntime {
562
528
  }
563
529
  scopeObj.ext = Path.parse(scopeObj.filename).ext;
564
530
  const finalizeResponse = (response) => {
565
- // Qualify Service-Worker responses
566
- if (httpEvent.request.headers.get('Service-Worker') === 'script') {
567
- scopeObj.response.headers.set('Service-Worker-Allowed', this.config.WORKER.scope || '/');
568
- }
569
531
  const responseMeta = _wq(response, 'meta');
570
532
  responseMeta.set('filename', scopeObj.filename);
571
533
  responseMeta.set('static', true);
@@ -635,7 +597,10 @@ export class WebfloServer extends WebfloRuntime {
635
597
  scopeObj.response.headers.set('X-Frame-Options', 'SAMEORIGIN');
636
598
  // 5. Partial content support
637
599
  scopeObj.response.headers.set('Accept-Ranges', 'bytes');
638
-
600
+ // 6. Qualify Service-Worker responses
601
+ if (httpEvent.request.headers.get('Service-Worker') === 'script') {
602
+ scopeObj.response.headers.set('Service-Worker-Allowed', this.config.WORKER.scope || '/');
603
+ }
639
604
  return finalizeResponse(scopeObj.response);
640
605
  }
641
606
 
@@ -710,7 +675,7 @@ export class WebfloServer extends WebfloRuntime {
710
675
  const asHTML = requestAccept?.match('text/html');
711
676
  const asIs = requestAccept?.match(response.headers.get('Content-Type'));
712
677
  const responseMeta = _wq(response, 'meta');
713
- if (requestAccept && asHTML > asIs && !responseMeta.get('static')) {
678
+ if (requestAccept && asHTML >= asIs && !responseMeta.get('static')) {
714
679
  response = await this.render(httpEvent, response);
715
680
  } else if (requestAccept && response.headers.get('Content-Type') && !asIs) {
716
681
  return new Response(response.body, { status: 406, statusText: 'Not Acceptable', headers: response.headers });
@@ -182,17 +182,13 @@ export class WebfloHMR {
182
182
  const bundlingConfig = {
183
183
  client: true,
184
184
  worker: true,
185
- server: true,
186
185
  metafile: true, // This is key
187
186
  logLevel: 'silent', // Suppress output
188
187
  incremental: true,
189
188
  };
190
189
  buildResult = await this.#app.buildRoutes(bundlingConfig);
191
190
  }
192
- } catch (e) {
193
- //console.error(e);
194
- return false;
195
- }
191
+ } catch (e) { return false; }
196
192
 
197
193
  // 1. Forward dependency graph (file -> [imported files])
198
194
  const forward = {};
@@ -232,25 +228,25 @@ export class WebfloHMR {
232
228
  return true;
233
229
  }
234
230
 
235
- async bundleAssetsIfPending(ohForce = false) {
231
+ async bundleAssetsIfPending() {
236
232
  const entries = {};
237
233
 
238
- if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected || ohForce) {
234
+ if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected) {
239
235
  entries.js = {};
240
- entries.js.client = !!this.#dirtiness.clientRoutesAffected.size || ohForce;
241
- entries.js.worker = this.#dirtiness.serviceWorkerAffected || ohForce;
236
+ entries.js.client = !!this.#dirtiness.clientRoutesAffected.size;
237
+ entries.js.worker = this.#dirtiness.serviceWorkerAffected;
242
238
  entries.js.server = false;
243
239
  // Clear state
244
240
  this.#dirtiness.clientRoutesAffected.clear();
245
241
  this.#dirtiness.serviceWorkerAffected = false;
246
242
  }
247
243
 
248
- if (this.#dirtiness.HTMLAffected || ohForce) {
244
+ if (this.#dirtiness.HTMLAffected) {
249
245
  this.#dirtiness.HTMLAffected = false;
250
246
  entries.html = {};
251
247
  }
252
248
 
253
- if (this.#dirtiness.CSSAffected || ohForce) {
249
+ if (this.#dirtiness.CSSAffected) {
254
250
  this.#dirtiness.CSSAffected = false;
255
251
  entries.css = {};
256
252
  }
@@ -1,7 +1,7 @@
1
1
  import { _with } from '@webqit/util/obj/index.js';
2
2
  import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
3
3
  import { DeepURLSearchParams } from './util.js';
4
- import { Observer } from '@webqit/use-live';
4
+ import { Observer } from '@webqit/quantum-js';
5
5
 
6
6
  export class Url {
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { _isObject } from '@webqit/util/js/index.js';
2
- import { Observer } from '@webqit/use-live';
2
+ import { Observer } from '@webqit/quantum-js';
3
3
  import { DeepURLSearchParams } from './util.js';
4
4
 
5
5
  export class xURL extends URL {
@@ -21,7 +21,6 @@ export async function bootstrap(cx, offset = '') {
21
21
  };
22
22
  if (config.CLIENT.copy_public_variables) {
23
23
  const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
24
- config.ENV.data = config.ENV.data || {};
25
24
  for (const key in process.env) {
26
25
  if (publicEnvPattern.test(key)) {
27
26
  config.ENV.data[key] = process.env[key];
@@ -1,42 +0,0 @@
1
- import Fs from 'fs/promises';
2
- import { parse, compile, matchPrologDirective, serialize } from '@webqit/use-live';
3
-
4
- export function UseLiveTransform() {
5
- return {
6
- name: 'uselive-transform',
7
- setup(build) {
8
- build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
9
- const code = await Fs.readFile(args.path, 'utf8');
10
-
11
- // Super dirty detection
12
- if (matchPrologDirective(code)) {
13
- // Actual check...
14
-
15
- let ast;
16
- try { ast = parse(code, parserParams); } catch (e) { console.error(args.path, '\nUseLive transform error:', e); }
17
-
18
- if (ast?.isLiveProgram || ast?.hasLiveFunctions) {
19
- const result = await compile(parserParams.sourceType+'-file', ast, {
20
- liveMode: ast.isLiveProgram, // Regarding top-level
21
- fileName: args.path,
22
- });
23
- return { contents: serialize(result), loader: 'js' };
24
- }
25
- }
26
-
27
- return { contents: code, loader: 'default' };
28
- });
29
- }
30
- };
31
- }
32
-
33
- export const parserParams = {
34
- ecmaVersion: 'latest',
35
- sourceType: 'module',
36
- executionMode: 'RegularProgram', // 'LiveProgram'
37
- allowReturnOutsideFunction: true,
38
- allowAwaitOutsideFunction: true,
39
- allowSuperOutsideMethod: false,
40
- preserveParens: false,
41
- locations: true,
42
- };
@@ -1,6 +0,0 @@
1
- node_modules
2
- .*
3
- !/.github
4
- !/.gitignore
5
- !/.webqit
6
- /.webqit/webflo/@runtime
@@ -1,15 +0,0 @@
1
- {
2
- "filename": "app.js",
3
- "public_base_url": "/",
4
- "copy_public_variables": true,
5
- "spa_routing": true,
6
- "capabilities": {
7
- "service_worker": true,
8
- "webpush": true,
9
- "custom_install": true,
10
- "exposed": [
11
- "display-mode",
12
- "notifications"
13
- ]
14
- }
15
- }
@@ -1,7 +0,0 @@
1
- {
2
- "PUBLIC_DIR": "./public",
3
- "VIEWS_DIR": "./app",
4
- "SERVER_DIR": "./app",
5
- "CLIENT_DIR": "./app",
6
- "WORKER_DIR": "./app"
7
- }
@@ -1,6 +0,0 @@
1
- node_modules
2
- .*
3
- !/.github
4
- !/.gitignore
5
- !/.webqit
6
- /.webqit/webflo/@runtime
@@ -1,12 +0,0 @@
1
- {
2
- "filename": "app.js",
3
- "public_base_url": "/",
4
- "copy_public_variables": true,
5
- "spa_routing": true,
6
- "capabilities": {
7
- "service_worker": false,
8
- "webpush": false,
9
- "custom_install": false,
10
- "exposed": []
11
- }
12
- }
@@ -1,7 +0,0 @@
1
- {
2
- "PUBLIC_DIR": "./public",
3
- "VIEWS_DIR": "./app",
4
- "SERVER_DIR": "./app",
5
- "CLIENT_DIR": "./app",
6
- "WORKER_DIR": "./app"
7
- }