@webqit/webflo 0.20.26 → 0.20.27

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 (57) hide show
  1. package/package.json +8 -5
  2. package/src/build-pi/index.js +6 -4
  3. package/src/init-pi/index.js +0 -1
  4. package/src/runtime-pi/{WebfloRuntime.js → AppRuntime.js} +57 -113
  5. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
  6. package/src/runtime-pi/webflo-client/WebfloClient.js +163 -95
  7. package/src/runtime-pi/webflo-client/{WebfloRootClient1.js → WebfloRootClientA.js} +39 -56
  8. package/src/runtime-pi/webflo-client/{WebfloRootClient2.js → WebfloRootClientB.js} +3 -3
  9. package/src/runtime-pi/webflo-client/WebfloSubClient.js +28 -15
  10. package/src/runtime-pi/webflo-client/index.js +3 -3
  11. package/src/runtime-pi/webflo-messaging/ClientPortMixin.js +13 -0
  12. package/src/runtime-pi/{webflo-server/messaging/ClientRequestRealtime.js → webflo-messaging/ClientRequestPort001.js} +13 -9
  13. package/src/runtime-pi/webflo-messaging/ClientRequestPort010.js +4 -0
  14. package/src/runtime-pi/webflo-messaging/ClientRequestPort100.js +17 -0
  15. package/src/runtime-pi/webflo-messaging/WebfloTenancy001.js +27 -0
  16. package/src/runtime-pi/webflo-messaging/WebfloTenant001.js +27 -0
  17. package/src/runtime-pi/webflo-routing/HttpCookies101.js +53 -0
  18. package/src/runtime-pi/webflo-routing/HttpCookies110.js +3 -0
  19. package/src/runtime-pi/webflo-routing/{HttpEvent.js → HttpEvent111.js} +95 -73
  20. package/src/runtime-pi/webflo-routing/HttpKeyvalInterface.js +120 -0
  21. package/src/runtime-pi/webflo-routing/HttpSession001.js +24 -0
  22. package/src/runtime-pi/webflo-routing/HttpSession110.js +3 -0
  23. package/src/runtime-pi/webflo-routing/{HttpThread.js → HttpThread111.js} +54 -13
  24. package/src/runtime-pi/webflo-routing/{HttpUser.js → HttpUser111.js} +10 -23
  25. package/src/runtime-pi/webflo-routing/KeyvalsFactory001.js +53 -0
  26. package/src/runtime-pi/webflo-routing/KeyvalsFactory110.js +48 -0
  27. package/src/runtime-pi/webflo-routing/KeyvalsFactoryInterface.js +56 -0
  28. package/src/runtime-pi/webflo-routing/{WebfloRouter.js → WebfloRouter111.js} +5 -6
  29. package/src/runtime-pi/webflo-server/WebfloServer.js +262 -266
  30. package/src/runtime-pi/webflo-worker/WebfloWorker.js +97 -44
  31. package/src/util.js +3 -2
  32. package/src/runtime-pi/apis.js +0 -9
  33. package/src/runtime-pi/webflo-client/ClientSideCookies.js +0 -18
  34. package/src/runtime-pi/webflo-fetch/LiveResponse.js +0 -476
  35. package/src/runtime-pi/webflo-fetch/index.js +0 -419
  36. package/src/runtime-pi/webflo-fetch/util.js +0 -28
  37. package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +0 -10
  38. package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +0 -26
  39. package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +0 -87
  40. package/src/runtime-pi/webflo-messaging/WQMessagePort.js +0 -38
  41. package/src/runtime-pi/webflo-messaging/WQRelayPort.js +0 -47
  42. package/src/runtime-pi/webflo-messaging/WQSockPort.js +0 -111
  43. package/src/runtime-pi/webflo-messaging/WQStarPort.js +0 -112
  44. package/src/runtime-pi/webflo-messaging/wq-message-port.js +0 -413
  45. package/src/runtime-pi/webflo-routing/HttpCookies.js +0 -43
  46. package/src/runtime-pi/webflo-routing/HttpSession.js +0 -11
  47. package/src/runtime-pi/webflo-routing/HttpState.js +0 -182
  48. package/src/runtime-pi/webflo-server/ServerSideCookies.js +0 -22
  49. package/src/runtime-pi/webflo-server/ServerSideSession.js +0 -40
  50. package/src/runtime-pi/webflo-server/messaging/Client.js +0 -27
  51. package/src/runtime-pi/webflo-server/messaging/Clients.js +0 -25
  52. package/src/runtime-pi/webflo-url/Url.js +0 -156
  53. package/src/runtime-pi/webflo-url/index.js +0 -1
  54. package/src/runtime-pi/webflo-url/urlpattern.js +0 -38
  55. package/src/runtime-pi/webflo-url/util.js +0 -109
  56. package/src/runtime-pi/webflo-url/xURL.js +0 -94
  57. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +0 -21
@@ -1,8 +1,8 @@
1
- import { Observer } from '@webqit/use-live';
1
+ import { Observer } from '@webqit/observer';
2
+ import { LiveResponse } from '@webqit/fetch-plus';
2
3
  import { WebfloClient } from './WebfloClient.js';
3
4
  import { defineElement } from './webflo-embedded.js';
4
- import { Url } from '../webflo-url/Url.js';
5
- import { _wq } from '../../util.js';
5
+ import { _meta } from '../../util.js';
6
6
 
7
7
  export class WebfloSubClient extends WebfloClient {
8
8
 
@@ -40,21 +40,26 @@ export class WebfloSubClient extends WebfloClient {
40
40
  if (this.host.location.origin !== window.location.origin) {
41
41
  throw new Error(`Webflo embeddable origin violation in "${window.location}"`);
42
42
  }
43
+
43
44
  const instanceController = await super.initialize();
44
- _wq(this.background, 'meta').set('parentNode', this.#superRuntime.background);
45
+
46
+ _meta(this.background).set('parentNode', this.#superRuntime.background);
45
47
  instanceController.signal.addEventListener('abort', () => {
46
- if (_wq(this.background, 'meta').get('parentNode') === this.#superRuntime.background) {
47
- _wq(this.background, 'meta').set('parentNode', null);
48
+ if (_meta(this.background).get('parentNode') === this.#superRuntime.background) {
49
+ _meta(this.background).set('parentNode', null);
48
50
  }
49
51
  }, { once: true });
52
+
50
53
  return instanceController;
51
54
  }
52
55
 
53
56
  async hydrate() {
54
57
  const instanceController = await super.hydrate();
58
+
55
59
  if (this.host.hasAttribute('location')) {
56
60
  await this.navigate(this.location.href);
57
61
  }
62
+
58
63
  return instanceController;
59
64
  }
60
65
 
@@ -62,6 +67,7 @@ export class WebfloSubClient extends WebfloClient {
62
67
  const locationCallback = (newHref) => {
63
68
  this.host.reflectLocation(newHref);
64
69
  };
70
+
65
71
  return super.controlClassic/*IMPORTANT*/(locationCallback);
66
72
  }
67
73
 
@@ -87,36 +93,43 @@ export class WebfloSubClient extends WebfloClient {
87
93
  if (httpEvent.url.hash) {
88
94
  this.host.querySelector(httpEvent.url.hash)?.scrollIntoView();
89
95
  } else await super.applyPostRenderState(httpEvent);
96
+
90
97
  (this.host.querySelector('[autofocus]') || this.host).focus();
91
98
  }
92
99
 
93
- redirect(location, response = null) {
100
+ async redirect(location, response = null) {
94
101
  location = typeof location === 'string' ? new URL(location, this.location.origin) : location;
102
+
95
103
  const width = Math.min(800, window.innerWidth);
96
104
  const height = Math.min(600, window.innerHeight);
97
105
  const left = (window.outerWidth - width) / 2;
98
106
  const top = (window.outerHeight - height) / 2;
99
107
  const popup = window.open(location, '_blank', `popup=true,width=${width},height=${height},left=${left},top=${top}`);
100
- if (response && LiveResponse.hasBackground(response)) {
101
- Observer.set(this.navigator, 'redirecting', new Url/*NOT URL*/(location), { diff: true });
102
- const backgroundPort = LiveResponse.getBackground(response);
103
- backgroundPort.postMessage(true, { wqEventOptions: { type: 'keep-alive' } });
104
- backgroundPort.addEventListener('close', (e) => {
108
+
109
+ const backgroundPort = response instanceof LiveResponse
110
+ ? response.port
111
+ : LiveResponse.getPort(response);
112
+ if (backgroundPort) {
113
+ Observer.set(this.navigator, 'redirecting', new URL(location), { diff: true });
114
+
115
+ backgroundPort.readyStateChange('close').then((e) => {
105
116
  window.removeEventListener('message', windowMessageHandler);
117
+
106
118
  Observer.set(this.navigator, 'redirecting', null);
107
119
  popup.postMessage('timeout:5');
120
+
108
121
  setTimeout(() => {
109
122
  popup.close();
110
123
  }, 5000);
111
- }, { once: true });
124
+ });
125
+
112
126
  const windowMessageHandler = (e) => {
113
127
  if (e.source === popup && e.data === 'close') {
114
128
  backgroundPort.close();
115
129
  }
116
130
  };
131
+
117
132
  window.addEventListener('message', windowMessageHandler);
118
133
  }
119
-
120
- return 3; // keep-alive
121
134
  }
122
135
  }
@@ -1,9 +1,9 @@
1
- import { WebfloRootClient1 } from './WebfloRootClient1.js';
2
- import { WebfloRootClient2 } from './WebfloRootClient2.js';
1
+ import { WebfloRootClientA } from './WebfloRootClientA.js';
2
+ import { WebfloRootClientB } from './WebfloRootClientB.js';
3
3
  import { WebfloSubClient } from './WebfloSubClient.js';
4
4
 
5
5
  export async function start(bootstrap) {
6
- const WebfloRootClient = window.navigation ? WebfloRootClient2 : WebfloRootClient1;
6
+ const WebfloRootClient = window.navigation ? WebfloRootClientB : WebfloRootClientA;
7
7
  const instance = WebfloRootClient.create(bootstrap, document);
8
8
  await instance.initialize();
9
9
  WebfloSubClient.defineElement();
@@ -0,0 +1,13 @@
1
+ export const ClientPortMixin = (superClass) => class extends superClass {
2
+
3
+ async query(query, callback, options = {}) {
4
+ return await new Promise((resolve) => {
5
+ this.postRequest(
6
+ { query },
7
+ (event) => resolve(callback ? callback(event) : event),
8
+ { ...options, type: 'query' }
9
+ );
10
+ });
11
+ }
12
+
13
+ };
@@ -1,7 +1,8 @@
1
- import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
2
- import { _wq } from '../../../util.js';
1
+ import { ClientPortMixin } from './ClientPortMixin.js';
2
+ import { StarPort } from '@webqit/port-plus';
3
+ import { _meta } from '../../util.js';
3
4
 
4
- export class ClientRequestRealtime extends WQStarPort {
5
+ export class ClientRequestPort001 extends ClientPortMixin(StarPort) {
5
6
 
6
7
  #portID;
7
8
  get portID() { return this.#portID; }
@@ -15,8 +16,8 @@ export class ClientRequestRealtime extends WQStarPort {
15
16
  #navigatedAway = false;
16
17
  navigatedAway() { return this.#navigatedAway; }
17
18
 
18
- constructor(portID, url) {
19
- super();
19
+ constructor(portID, url, options = {}) {
20
+ super(options);
20
21
  this.#portID = portID;
21
22
  this.#url = url;
22
23
  const $url = new URL(url);
@@ -38,13 +39,16 @@ export class ClientRequestRealtime extends WQStarPort {
38
39
  }
39
40
 
40
41
  enterChannel(channelID, { resolveData = null } = {}) {
41
- const client = _wq(this, 'meta').get('parentNode');
42
- const clients = client && _wq(client, 'meta').get('parentNode');
42
+ const webfloTenant = _meta(this).get('parentNode');
43
+ const clients = webfloTenant && _meta(webfloTenant).get('parentNode');
44
+
43
45
  if (!clients) {
44
46
  throw new Error('Instance seem not connected to the messaging system.');
45
47
  }
48
+
46
49
  const channel = clients.getChannel(channelID, true);
47
- const leave = channel.addPort(client, { resolveData });
50
+ const leave = channel.addPort(webfloTenant, { resolveData });
51
+
48
52
  return { channel, leave };
49
53
  }
50
- }
54
+ }
@@ -0,0 +1,4 @@
1
+ import { ClientPortMixin } from './ClientPortMixin.js';
2
+ import { BroadcastChannelPlus } from '@webqit/port-plus';
3
+
4
+ export class ClientRequestPort010 extends ClientPortMixin(BroadcastChannelPlus) {}
@@ -0,0 +1,17 @@
1
+ import { ClientPortMixin } from './ClientPortMixin.js';
2
+ import { MessageChannelPlus } from '@webqit/port-plus';
3
+
4
+ export class ClientRequestPort100 extends MessageChannelPlus {
5
+
6
+ constructor(options = {}) {
7
+ super(options);
8
+
9
+ const superInstance = new (ClientPortMixin(class { }));
10
+ Object.defineProperty(this.port1, 'query', {
11
+ value: function (...args) {
12
+ return superInstance.query.apply(this, args);
13
+ }
14
+ });
15
+
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ import { RelayPort, StarPort } from '@webqit/port-plus';
2
+ import { WebfloTenant001 } from './WebfloTenant001.js';
3
+
4
+ export class WebfloTenancy001 extends StarPort {
5
+
6
+ #channels = new Map;
7
+
8
+ getTenant(tenantID, autoCreate = false) {
9
+ if (autoCreate && !this.findPort((tenant) => tenant.tenantID === tenantID)) {
10
+ const tenant = new WebfloTenant001(tenantID, { handshake: 1, postAwaitsOpen: true, autoClose: false });
11
+ const cleanup = this.addPort(tenant);
12
+ tenant.readyStateChange('close').then(cleanup);
13
+ }
14
+ return this.findPort((tenant) => tenant.tenantID === tenantID);
15
+ }
16
+
17
+ getChannel(channelName, autoCreate = false) {
18
+ if (!this.#channels.has(channelName) && autoCreate) {
19
+ const channel = new RelayPort(channelName, { handshake: 1, postAwaitsOpen: true, autoClose: true });
20
+
21
+ this.#channels.set(channelName, channel);
22
+ channel.readyStateChange('close').then(() => this.#channels.delete(channelName));
23
+ }
24
+
25
+ return this.#channels.get(channelName);
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ import { StarPort } from '@webqit/port-plus';
2
+ import { ClientRequestPort001 } from './ClientRequestPort001.js';
3
+
4
+ export class WebfloTenant001 extends StarPort {
5
+
6
+ #tenantID;
7
+ get tenantID() { return this.#tenantID; }
8
+
9
+ constructor(tenantID, options = {}) {
10
+ super(options);
11
+ this.#tenantID = tenantID;
12
+ }
13
+
14
+ getRequestPort(portID) {
15
+ return this.findPort((port) => port.portID === portID);
16
+ }
17
+
18
+ createRequestPort(portID, url = null) {
19
+ const requestPort = new ClientRequestPort001(portID, url, { handshake: 1, postAwaitsOpen: true, autoClose: true });
20
+ this.addPort(requestPort);
21
+ setTimeout(() => {
22
+ if (requestPort.length || !this.findPort((port) => port === requestPort)) return;
23
+ requestPort.close(true);
24
+ }, 15000/*15sec*/);
25
+ return requestPort;
26
+ }
27
+ }
@@ -0,0 +1,53 @@
1
+ import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
2
+
3
+ export class HttpCookies101 extends HttpKeyvalInterface {
4
+
5
+ #initial;
6
+
7
+ constructor({ context = {}, store, initial = {}, realm = 0 }) {
8
+ super({ context, store, initial, realm });
9
+ this.#initial = initial;
10
+ }
11
+
12
+ // ------ lifecycle
13
+
14
+ async _commit(response, devMode = false) {
15
+ // Mutate in-place - to reflect across clones
16
+ Object.keys(this.#initial).forEach((k) => delete this.#initial[k]);
17
+ Object.assign(this.#initial, await this.json());
18
+ }
19
+
20
+ // ------ extras
21
+
22
+ async render() {
23
+ const json = await this.json(true);
24
+
25
+ const entries = Object.keys(json).concat(Object.keys(this.#initial)).map((key) => {
26
+ const a = this.#initial[key];
27
+ const b = json[key];
28
+ if (a === b.value) {
29
+ // Same
30
+ return;
31
+ }
32
+ if ([undefined, null].includes(b)) {
33
+ // Deleted
34
+ return { name: key, value: '', maxAge: 0 };
35
+ }
36
+ // Added or modified
37
+ return { name: key, ...b };
38
+ }).filter((e) => e);
39
+
40
+ return entries.map((e) => renderCookieObjToString(e));
41
+ }
42
+ }
43
+
44
+ export function renderCookieObjToString(cookieObj) {
45
+ const attrsArr = [`${cookieObj.name}=${/*encodeURIComponent*/(cookieObj.value)}`];
46
+ for (const attrName in cookieObj) {
47
+ if (['name', 'value'].includes(attrName)) continue;
48
+ let _attrName = attrName[0].toUpperCase() + attrName.substring(1);
49
+ if (_attrName === 'MaxAge') { _attrName = 'Max-Age' };
50
+ attrsArr.push(cookieObj[attrName] === true ? _attrName : `${_attrName}=${cookieObj[attrName]}`);
51
+ }
52
+ return attrsArr.join('; ');
53
+ }
@@ -0,0 +1,3 @@
1
+ import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
2
+
3
+ export class HttpCookies110 extends HttpKeyvalInterface {}
@@ -1,106 +1,140 @@
1
1
  import { _isObject } from '@webqit/util/js/index.js';
2
- import { _difference } from '@webqit/util/arr/index.js';
3
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
4
- import { xURL } from '../webflo-url/xURL.js';
5
- import { _wq } from '../../util.js';
2
+ import { LiveResponse } from '@webqit/fetch-plus';
3
+ import { URLPlus } from '@webqit/url-plus';
6
4
 
7
- export class HttpEvent {
5
+ export class HttpEvent111 {
8
6
 
9
- static create(parentEvent, init = {}) {
10
- return new this(parentEvent, init);
7
+ static create(init = {}) {
8
+ return new this(init);
11
9
  }
12
10
 
13
- #parentEvent;
14
- #url;
11
+ #context = {};
15
12
  #init;
16
- #abortController = new AbortController;
17
-
18
- constructor(parentEvent, { request, cookies, session, user, client, detail, signal, state, ...rest }) {
19
- this.#parentEvent = parentEvent;
20
- this.#init = { request, cookies, session, user, client, detail, signal, state, ...rest };
21
- this.#url = new xURL(this.#init.request.url);
22
- this.#parentEvent?.signal.addEventListener('abort', () => this.#abortController.abort(), { once: true });
23
- this.#init.request.signal?.addEventListener('abort', () => this.#abortController.abort(), { once: true });
24
- this.#lifeCycleResolutionPromise.finally(() => {
25
- this.#abortController.abort();
26
- });
27
- }
13
+ #url;
28
14
 
29
- get signal() { return this.#abortController.signal; }
15
+ #readyStates;
16
+ #abortController = new AbortController;
30
17
 
31
- get url() { return this.#url; }
18
+ #internalLiveResponse = new LiveResponse(null, { done: false });
19
+ get internalLiveResponse() { return this.#internalLiveResponse; }
32
20
 
33
- get request() { return this.#init.request; }
21
+ get _context() { return this.#context; }
22
+ get _parentEvent() { return this.#context?.parentEvent; }
34
23
 
35
- get thread() { return this.#init.thread; }
24
+ constructor({ context = {}, request, thread, cookies, session, user, client, detail, signal, state, ...rest }) {
25
+ if (context) Object.assign(this.#context, context);
36
26
 
37
- get client() { return this.#init.client; }
27
+ this.#init = { context, request, thread, cookies, session, user, client, detail, signal, state, ...rest };
28
+ [thread, cookies, session, user].forEach((node) => {
29
+ if (!node) return;
30
+ node._context.parentEvent = this;
31
+ });
38
32
 
39
- get cookies() { return this.#init.cookies; }
33
+ this.#url = new URLPlus(this.#init.request.url);
40
34
 
41
- get session() { return this.#init.session; }
35
+ this._parentEvent?.signal.addEventListener('abort', () => this.#abortController.abort(), { once: true });
36
+ this.#init.request.signal?.addEventListener('abort', () => this.#abortController.abort(), { once: true });
42
37
 
43
- get user() { return this.#init.user; }
38
+ const $ref = (o) => {
39
+ o.promise = new Promise((res, rej) => (o.resolve = res, o.reject = rej));
40
+ return o;
41
+ };
42
+ this.#readyStates = {
43
+ live: $ref({}),
44
+ done: $ref({}),
45
+ };
44
46
 
45
- get detail() { return this.#init.detail; }
47
+ this.#readyStates.done.promise.finally(() => {
48
+ this.#abortController.abort();
49
+ });
50
+ }
46
51
 
47
- get state() { return { ...(this.#init.state || {}) }; }
52
+ // ------ lifecycle
48
53
 
49
54
  #lifecyclePromises = new Set;
50
- get lifecyclePromises() { return this.#lifecyclePromises; }
51
-
52
- #lifeCycleResolve;
53
- #lifeCycleReject;
54
- #lifeCycleResolutionPromise = new Promise((resolve, reject) => {
55
- this.#lifeCycleResolve = resolve;
56
- this.#lifeCycleReject = reject;
57
- });
58
-
59
55
  #extendLifecycle(promise) {
60
- if (this.#lifecyclePromises.dirty && !this.#lifecyclePromises.size) {
56
+ if (this.#readyStates.done.state) {
61
57
  throw new Error('Event lifecycle already complete.');
62
58
  }
63
- if (this.#parentEvent) {
64
- this.#parentEvent.waitUntil(promise);
59
+
60
+ if (this._parentEvent) {
61
+ this._parentEvent.waitUntil(promise);
65
62
  }
63
+
66
64
  promise = Promise.resolve(promise);
67
65
  this.#lifecyclePromises.add(promise);
68
- this.#lifecyclePromises.dirty = true;
66
+
67
+ if (!this.#readyStates.live.state) {
68
+ this.#readyStates.live.state = true;
69
+ this.#readyStates.live.resolve();
70
+ }
71
+
69
72
  return promise.then((value) => {
70
73
  this.#lifecyclePromises.delete(promise);
71
74
  if (!this.#lifecyclePromises.size) {
72
- this.#lifeCycleResolve(value);
75
+ this.#readyStates.done.state = true;
76
+ this.#readyStates.done.resolve(value);
73
77
  }
74
- }).catch(() => {
78
+ }).catch((e) => {
75
79
  this.#lifecyclePromises.delete(promise);
76
80
  if (!this.#lifecyclePromises.size) {
77
- this.#lifeCycleReject();
81
+ this.#readyStates.done.state = true;
82
+ this.#readyStates.done.reject(e);
78
83
  }
79
84
  });
80
85
  }
81
86
 
82
- lifeCycleComplete(returningThePromise = false) {
83
- if (returningThePromise) {
84
- return this.#lifeCycleResolutionPromise;
85
- }
86
- if (this.#lifecyclePromises.dirty === undefined) {
87
- // Hasn't been initialized yet
88
- return null;
87
+ get readyState() {
88
+ return this.#readyStates.done.state ? 'done'
89
+ : (this.#readyStates.live.state ? 'live' : 'waiting');
90
+ }
91
+
92
+ readyStateChange(query) {
93
+ if (!['live', 'done'].includes(query)) {
94
+ throw new Error(`Invalid readyState query "${query}"`);
89
95
  }
90
- return !this.#lifecyclePromises.size;
96
+ return this.#readyStates[query].promise;
97
+ }
98
+
99
+ spawn(init = {}) {
100
+ const clone = this.clone(init);
101
+ clone._context.parentEvent = this;
102
+ return clone;
91
103
  }
92
104
 
105
+ abort() { this.#abortController.abort(); }
106
+
107
+ clone(init = {}) {
108
+ const $init = { ...this.#init, ...init };
109
+ ['thread', 'cookies', 'session', 'user'].forEach((nodeName) => {
110
+ if (!init[nodeName]) $init[nodeName] = $init[nodeName].clone();
111
+ });
112
+ return this.constructor.create($init);
113
+ }
114
+
115
+ // ------
116
+
117
+ get signal() { return this.#abortController.signal; }
118
+ get url() { return this.#url; }
119
+ get request() { return this.#init.request; }
120
+ get thread() { return this.#init.thread; }
121
+ get cookies() { return this.#init.cookies; }
122
+ get session() { return this.#init.session; }
123
+ get user() { return this.#init.user; }
124
+ get client() { return this.#init.client; }
125
+ get detail() { return this.#init.detail; }
126
+ get state() { return { ...(this.#init.state || {}) }; }
127
+
128
+ // ------
129
+
93
130
  async waitUntil(promise) {
94
131
  return await this.#extendLifecycle(promise);
95
132
  }
96
133
 
97
- waitUntilNavigate() {
98
- this.waitUntil(new Promise(() => { }));
134
+ async waitUntilNavigate() {
135
+ /* DO NOT AWAIT */this.waitUntil(new Promise(() => { }));
99
136
  }
100
137
 
101
- #internalLiveResponse = new LiveResponse(null, { done: false });
102
- get internalLiveResponse() { return this.#internalLiveResponse; }
103
-
104
138
  async respondWith(data, ...args) {
105
139
  await this.#internalLiveResponse.replaceWith(data, ...args);
106
140
  }
@@ -141,16 +175,4 @@ export class HttpEvent {
141
175
  //-----
142
176
  return await this.respondWith(null, { status, ...options, headers: { Location: urlRewrite.href } });
143
177
  }
144
-
145
- clone(init = {}) {
146
- return this.constructor.create(this.#parentEvent, { ...this.#init, ...init });
147
- }
148
-
149
- spawn(init = {}) {
150
- return this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
151
- }
152
-
153
- abort() {
154
- this.#abortController.abort();
155
- }
156
- }
178
+ }
@@ -0,0 +1,120 @@
1
+ import { LiveResponse } from '@webqit/fetch-plus';
2
+ import { KV } from '@webqit/keyval/inmemory';
3
+
4
+ export class HttpKeyvalInterface {
5
+
6
+ static create(init = {}) {
7
+ return new this(init);
8
+ }
9
+
10
+ #context = {};
11
+ #store;
12
+ #realm;
13
+ #rest;
14
+
15
+ #cleanups = new Set
16
+
17
+ get _context() { return this.#context; }
18
+ get _parentEvent() { return this.#context?.parentEvent; }
19
+
20
+ constructor({ context = {}, store, realm = 0, ...rest }) {
21
+ if (!(store instanceof KV)) {
22
+ throw new Error('HttpKeyval expects a valid store instance!');
23
+ }
24
+ if (context) Object.assign(this.#context, context);
25
+ this.#store = store;
26
+ this.#realm = realm;
27
+ this.#rest = rest;
28
+ }
29
+
30
+ // ------ lifecycle
31
+
32
+ clone() {
33
+ return new this.constructor({
34
+ context: this.#context,
35
+ store: this.#store,
36
+ realm: this.#realm,
37
+ ...this.#rest,
38
+ });
39
+ }
40
+
41
+ async _cleanup() { this.#cleanups.forEach((fn) => fn()); }
42
+
43
+ // ------ standard methods
44
+
45
+ async count() { return await this.#store.count(); }
46
+ async keys() { return [...await this.#store.keys()]; }
47
+ async values() { return [...await this.#store.values()]; }
48
+ async entries() { return [...await this.#store.entries()]; }
49
+ async has(key) { return await this.#store.has(key); }
50
+ async get(key) { return await this.#store.get(key); }
51
+ async set(key, value) { await this.#store.set(key, value); }
52
+ async delete(key) { await this.#store.delete(key); }
53
+ async clear() { await this.#store.clear(); }
54
+ async json(...args) { return await this.#store.json(...args); }
55
+
56
+ // ------ extras
57
+
58
+ subscribe(...args) {
59
+ const cleanup = this.#store.subscribe(...args);
60
+ this.#cleanups.add(cleanup);
61
+ return cleanup;
62
+ }
63
+
64
+ async require(attrs, callback = null, options = {}) {
65
+ const handlersRegistry = this.#context?.handlersRegistry;
66
+ if (!handlersRegistry) throw new Error(`No handlers registry in context`);
67
+
68
+ if (typeof callback === 'object' && callback && arguments.length === 2) {
69
+ options = callback;
70
+ callback = null;
71
+ }
72
+
73
+ if (callback && typeof callback !== 'function') {
74
+ throw new Error('Callback must be a valid function when provided');
75
+ }
76
+
77
+ const entries = [];
78
+ main: for await (const attr of [].concat(attrs)) {
79
+ const value = await this.get(attr);
80
+ if (value === undefined && !await this.has(attr)
81
+ || options.noNulls && [undefined, null].includes(value)) {
82
+
83
+ const handlers = handlersRegistry.get(attr);
84
+ if (!handlers) {
85
+ throw new Error(`No handler defined for the user attribute: ${attr}`);
86
+ }
87
+
88
+ for (let i = 0; i < handlers.length; i++) {
89
+ const handler = handlers[i];
90
+
91
+ if (handler.callback) {
92
+ const returnValue = await handler.callback(this._parentEvent, attr);
93
+ if (returnValue instanceof Response || returnValue instanceof LiveResponse) {
94
+ await this._parentEvent.respondWith(returnValue);
95
+ return new Promise(() => { });
96
+ }
97
+
98
+ if ((typeof returnValue === 'undefined' || (options.noNulls && returnValue === null)) && i < handlers.length - 1) {
99
+ continue;
100
+ }
101
+
102
+ entries.push(returnValue);
103
+ continue main;
104
+ }
105
+
106
+ await this._parentEvent.redirectWith(handler.url, handler.with || {});
107
+ return new Promise(() => { });
108
+ }
109
+ }
110
+
111
+ entries.push(value);
112
+ }
113
+
114
+ if (callback) {
115
+ return await callback(...entries);
116
+ }
117
+
118
+ return entries;
119
+ }
120
+ }
@@ -0,0 +1,24 @@
1
+ import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
2
+
3
+ export class HttpSession001 extends HttpKeyvalInterface {
4
+
5
+ #sessionID;
6
+ get sessionID() { return this.#sessionID; }
7
+ #ttl;
8
+
9
+ constructor({ context = {}, store, request, thread, sessionID, ttl }) {
10
+ if (!sessionID) {
11
+ throw new Error(`sessionID is required`);
12
+ }
13
+ super({
14
+ context,
15
+ store,
16
+ request,
17
+ thread,
18
+ sessionID,
19
+ ttl
20
+ });
21
+ this.#sessionID = sessionID;
22
+ this.#ttl = ttl;
23
+ }
24
+ }