@webqit/webflo 0.20.26 → 0.20.28

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 -103
  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 -269
  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
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
2
+
3
+ export class HttpSession110 extends HttpKeyvalInterface {}
@@ -1,43 +1,82 @@
1
- export class HttpThread {
1
+ import { KV } from '@webqit/keyval/inmemory';
2
2
 
3
- static create({ store, threadID, realm }) {
3
+ export class HttpThread111 {
4
+
5
+ // ------ factory
6
+
7
+ static create({ context = {}, store, threadID, realm = 0 }) {
8
+ let hydrationMode = true;
4
9
  if (!threadID || !(new RegExp(`^wq\\.${realm}\\.`)).test(threadID)) {
5
- threadID = `wq.${realm}.${crypto.randomUUID()}`;
10
+ threadID = `wq.${realm}.${(0 | Math.random() * 9e6).toString(36)}`;//`wq.${realm}.${crypto.randomUUID()}`;
11
+ hydrationMode = false;
6
12
  }
7
- return new this({ store, threadID, realm });
13
+ return new this({ context, store, threadID, realm, lifecycle: { dirty: hydrationMode, extended: false } });
8
14
  }
9
15
 
16
+ // ------
17
+
18
+ #context = {};
10
19
  #store;
11
20
  #threadID;
12
21
  #realm;
13
- #extended = false;
22
+ #lifecycle;
14
23
 
15
24
  get threadID() { return this.#threadID; }
25
+ get dirty() { return !!this.#lifecycle.dirty; }
26
+ get extended() { return !!this.#lifecycle.extended; }
16
27
 
17
- constructor({ store, threadID, realm }) {
18
- this.#store = store || new Map;
28
+ get _context() { return this.#context; }
29
+ get _parentEvent() { return this.#context?.parentEvent; }
30
+
31
+ constructor({ context = {}, store, threadID, realm = 0, lifecycle = { dirty: false, extended: false } }) {
32
+ if (!(store instanceof KV)) {
33
+ throw new Error('HttpKeyval expects a valid store instance!');
34
+ }
35
+ if (context) Object.assign(this.#context, context);
36
+ this.#store = store;
19
37
  this.#threadID = threadID;
20
38
  this.#realm = realm;
39
+ this.#lifecycle = lifecycle;
21
40
  }
22
41
 
23
- get extended() { return this.#extended; }
42
+ // ------ lifecycle
43
+
44
+ extend(set = true) { this.#lifecycle.extended = !!set; }
24
45
 
25
- extend(set = true) { this.#extended = !!set; }
46
+ clone() {
47
+ return new this.constructor({
48
+ context: this.#context,
49
+ store: this.#store,
50
+ threadID: this.#threadID,
51
+ realm: this.#realm,
52
+ lifecycle: this.#lifecycle,
53
+ });
54
+ }
26
55
 
27
- spawn(_threadID = null) {
56
+ spawn(threadID = null) {
28
57
  return this.constructor.create({
58
+ context: this.#context,
29
59
  store: this.#store,
30
- threadID: _threadID,
60
+ threadID: threadID,
31
61
  realm: this.#realm
32
62
  });
33
63
  }
34
64
 
65
+ async _cleanup() {
66
+ if (this.#lifecycle.extended) return;
67
+ await this.clear();
68
+ }
69
+
70
+ // ------ standard methods
71
+
35
72
  async keys() {
73
+ if (!this.#lifecycle.dirty) return [];
36
74
  const thread = await this.#store.get(this.#threadID) || {};
37
75
  return Object.keys(thread);
38
76
  }
39
77
 
40
78
  async has(key, filter = null) {
79
+ if (!this.#lifecycle.dirty) return false;
41
80
  if (filter === true || !filter) return (await this.keys()).includes(key);
42
81
  const thread = await this.#store.get(this.#threadID) || {};
43
82
  const values = [].concat(thread[key] ?? []);
@@ -45,14 +84,15 @@ export class HttpThread {
45
84
  }
46
85
 
47
86
  async append(key, value) {
87
+ this.#lifecycle.dirty = true;
48
88
  const thread = await this.#store.get(this.#threadID) || {};
49
89
  thread[key] = [].concat(thread[key] ?? []);
50
90
  thread[key].push(value);
51
91
  await this.#store.set(this.#threadID, thread);
52
- return this;
53
92
  }
54
93
 
55
94
  async get(key, filter = null) {
95
+ if (!this.#lifecycle.dirty) return filter === true ? [] : undefined;
56
96
  const thread = await this.#store.get(this.#threadID) || {};
57
97
  const values = [].concat(thread[key] ?? []);
58
98
 
@@ -67,6 +107,7 @@ export class HttpThread {
67
107
  }
68
108
 
69
109
  async consume(key, filter = null) {
110
+ if (!this.#lifecycle.dirty) return filter === true ? [] : undefined;
70
111
  const thread = await this.#store.get(this.#threadID) || {};
71
112
  const values = [].concat(thread[key] ?? []);
72
113
 
@@ -94,7 +135,7 @@ export class HttpThread {
94
135
  }
95
136
 
96
137
  async clear() {
138
+ if (!this.#lifecycle.dirty) return;
97
139
  await this.#store.delete(this.#threadID);
98
- return this;
99
140
  }
100
141
  }
@@ -1,21 +1,8 @@
1
- import { HttpState } from './HttpState.js';
1
+ import { HttpKeyvalInterface } from './HttpKeyvalInterface.js';
2
2
 
3
- export class HttpUser extends HttpState {
3
+ export class HttpUser111 extends HttpKeyvalInterface {
4
4
 
5
- static create({ store, request, thread, client }) {
6
- return new this({ store, request, thread, client });
7
- }
8
-
9
- #client;
10
-
11
- constructor({ store, request, thread, client }) {
12
- super({
13
- store,
14
- request,
15
- thread
16
- });
17
- this.#client = client;
18
- }
5
+ get _client() { return this._parentEvent.client; }
19
6
 
20
7
  async isSignedIn(callback = null, options = {}) {
21
8
  const isSignedIn = await this.get('id');
@@ -23,7 +10,7 @@ export class HttpUser extends HttpState {
23
10
  await callback(isSignedIn);
24
11
  return options.once
25
12
  ? undefined
26
- : this.observe('id', callback, options);
13
+ : this.subscribe('id', callback, options);
27
14
  }
28
15
  return !!isSignedIn;
29
16
  }
@@ -41,20 +28,20 @@ export class HttpUser extends HttpState {
41
28
 
42
29
  async confirm(data, callback, options = {}) {
43
30
  return await new Promise((resolve) => {
44
- this.#client.postRequest(
31
+ this._client.postRequest(
45
32
  data,
46
- (event) => resolve(callback ? callback(event) : event),
47
- { ...options, wqEventOptions: { type: 'confirm' } }
33
+ (event) => resolve(callback ? callback(event.data) : event.data),
34
+ { ...options, type: 'confirm' }
48
35
  );
49
36
  });
50
37
  }
51
38
 
52
39
  async prompt(data, callback, options = {}) {
53
40
  return await new Promise((resolve) => {
54
- this.#client.postRequest(
41
+ this._client.postRequest(
55
42
  data,
56
- (event) => resolve(callback ? callback(event) : event),
57
- { ...options, wqEventOptions: { type: 'prompt' } }
43
+ (event) => resolve(callback ? callback(event.data) : event.data),
44
+ { ...options, type: 'prompt' }
58
45
  );
59
46
  });
60
47
  }
@@ -0,0 +1,53 @@
1
+ import { KeyvalsFactoryInterface } from './KeyvalsFactoryInterface.js';
2
+ import { RedisKV, createClient } from '@webqit/keyval/redis';
3
+ import { InMemoryKV } from '@webqit/keyval/inmemory';
4
+ import { FileKV } from '@webqit/keyval/file';
5
+
6
+ export class KeyvalsFactory001 extends KeyvalsFactoryInterface {
7
+
8
+ #redisNamespace;
9
+ #redisChannel;
10
+ #redisUrl;
11
+ #localDir;
12
+
13
+ constructor({ localDir = null, redisUrl = null, redisNamespace = '*', redisChannel = null, } = {}) {
14
+ super();
15
+ this.#localDir = localDir;
16
+ this.#redisUrl = redisUrl;
17
+ this.#redisNamespace = redisNamespace;
18
+ this.#redisChannel = redisChannel ?? `__webflo_app__@${this.#redisNamespace}`;
19
+
20
+ // Set up Redis watch
21
+ if (this.#redisUrl && this.#redisChannel) {
22
+ const watchClient = createClient({ url: this.#redisUrl });
23
+ watchClient.connect().then(() => {
24
+ watchClient.subscribe(this.#redisChannel, async (message) => {
25
+ try {
26
+ const event = JSON.parse(message?.trim());
27
+ if (!Array.isArray(event?.origins)
28
+ || event.origins[1] === this.instanceID) return;
29
+ await this.kvHandle._fire(event);
30
+ } catch (e) {
31
+ console.error('Failed to parse message JSON:', message);
32
+ console.error(e, '\n\n');
33
+ }
34
+ });
35
+ });
36
+ }
37
+ }
38
+
39
+ create({ type, ...options }) {
40
+ const { path, ttl = 0 } = options;
41
+
42
+ if (!Array.isArray(path) || path.length !== 2) throw new Error('Path must be an array of length 2');
43
+ if (options.origins && (!Array.isArray(options.origins) || options.origins.length !== 1)) throw new Error('Origins must be an array of length 1');
44
+ if (ttl && typeof ttl !== 'number') throw new Error('TTL must be a number');
45
+
46
+ const origins = (options.origins || this.defaultOrigins).concat(this.instanceID);
47
+
48
+ if (type === 'redis' || !type && this.#redisUrl) return RedisKV.create({ path, ttl, redisUrl: this.#redisUrl, channel: this.#redisChannel, namespace: this.#redisNamespace, registry: this.registry, origins });
49
+ if (type === 'file' || !type && this.#localDir) return FileKV.create({ path, ttl, dir: this.#localDir, registry: this.registry, origins });
50
+ if (type === 'inmemory' || !type) return InMemoryKV.create({ path, ttl, registry: this.registry, origins });
51
+ throw new Error(`Invalid type: ${type}`);
52
+ }
53
+ }
@@ -0,0 +1,48 @@
1
+ import { KeyvalsFactoryInterface } from './KeyvalsFactoryInterface.js';
2
+ import { IndexedDBKV } from '@webqit/keyval/indexeddb';
3
+ import { CookieStoreKV } from '@webqit/keyval/cookiestore';
4
+ import { InMemoryKV } from '@webqit/keyval/inmemory';
5
+
6
+ export class KeyvalsFactory110 extends KeyvalsFactoryInterface {
7
+
8
+ #dbName;
9
+ #channel;
10
+ #cookiePath;
11
+
12
+ constructor({ dbName = 'webflo_keyval', cookiePath = '/', channel = '__webflo_app__' } = {}) {
13
+ super();
14
+ this.#dbName = dbName;
15
+ this.#cookiePath = cookiePath;
16
+ this.#channel = channel;
17
+
18
+ // Set up Redis watch
19
+ if (this.#channel) {
20
+ const watchClient = new BroadcastChannel(this.#channel);
21
+ watchClient.onmessage = async (message) => {
22
+ try {
23
+ const event = message.data;
24
+ if (!Array.isArray(event?.origins)
25
+ || event.origins[1] === this.instanceID) return;
26
+ await this.kvHandle._fire(event);
27
+ } catch (e) {
28
+ console.error('Failed to parse message JSON:', message);
29
+ }
30
+ };
31
+ }
32
+ }
33
+
34
+ create({ type = 'indexeddb', ...options }) {
35
+ const { path, ttl = 0 } = options;
36
+
37
+ if (!Array.isArray(path) || path.length !== 2) throw new Error('Path must be an array of length 2');
38
+ if (options.origins && (!Array.isArray(options.origins) || options.origins.length !== 1)) throw new Error('Origins must be an array of length 1');
39
+ if (ttl && typeof ttl !== 'number') throw new Error('TTL must be a number');
40
+
41
+ const origins = (options.origins || this.defaultOrigins).concat(this.instanceID);
42
+
43
+ if (type === 'indexeddb') return IndexedDBKV.create({ dbName: this.#dbName, path, ttl, channel: this.#channel, registry: this.registry, origins });
44
+ if (type === 'cookiestore') return CookieStoreKV.create({ path, ttl, cookiePath: this.#cookiePath, registry: this.registry, origins });
45
+ if (type === 'inmemory') return InMemoryKV.create({ path, ttl, registry: this.registry, origins });
46
+ throw new Error(`Invalid type: ${type}`);
47
+ }
48
+ }
@@ -0,0 +1,56 @@
1
+ import { KV } from '@webqit/keyval/inmemory';
2
+
3
+ export class KeyvalsFactoryInterface {
4
+
5
+ #instanceID = (0 | Math.random() * 9e6).toString(36);
6
+ #registry = new Map;
7
+ #defaultOrigins = ['*'/* requestID */];
8
+ #kvHandle;
9
+
10
+ get instanceID() { return this.#instanceID; }
11
+ get registry() { return this.#registry; }
12
+ get defaultOrigins() { return this.#defaultOrigins; }
13
+ get kvHandle() { return this.#kvHandle; }
14
+
15
+ constructor() {
16
+ this.#kvHandle = KV.create({ path: [], registry: this.#registry, origins: this.#defaultOrigins.concat(this.#instanceID) });
17
+ }
18
+
19
+ subscribe(kvKey, callback, { scope = 1, ...options } = {}) {
20
+ if (!kvKey) throw new Error('kvKey must be specified');
21
+ return this.#kvHandle.subscribe(kvKey, callback, { scope, ...options });
22
+ }
23
+
24
+ #handlers = new Map;
25
+
26
+ defineHandler(kvKey, key, ...handlers) {
27
+ const $handlers = [];
28
+ for (let handler of handlers) {
29
+ if (typeof handler === 'function') {
30
+ handler = { callback: handler };
31
+ } else if (typeof handler === 'string') {
32
+ handler = { url: handler };
33
+ } else if (!(typeof handler === 'object' && handler && (handler = { ...handler }))
34
+ || typeof handler.callback !== 'function' && typeof handler.url !== 'string') {
35
+ throw new Error(`Handler must be either an URL or a function or an object specifying either an URL (handler.url) or a function (handler.callback)`);
36
+ }
37
+ if (typeof handler.with === 'object' && handler.with) {
38
+ handler.with = { ...handler.with };
39
+ } else if (handler.with) {
40
+ throw new Error(`The "with" parameter must be a valid JSON object`);
41
+ }
42
+ $handlers.push(handler);
43
+ }
44
+ if (!this.#handlers.has(kvKey)) {
45
+ this.#handlers.set(kvKey, new Map);
46
+ }
47
+ this.#handlers.get(kvKey).set(key, $handlers);
48
+ }
49
+
50
+ getHandlers(kvKey, autoCreate = false) {
51
+ if (!this.#handlers.has(kvKey) && autoCreate) {
52
+ this.#handlers.set(kvKey, new Map);
53
+ }
54
+ return this.#handlers.get(kvKey);
55
+ }
56
+ }
@@ -1,10 +1,9 @@
1
1
  import { _isFunction, _isArray, _isObject } from '@webqit/util/js/index.js';
2
2
  import { _from as _arrFrom } from '@webqit/util/arr/index.js';
3
- import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
4
- import { request as requestShim } from '../webflo-fetch/index.js';
5
- import { path as Path } from '../webflo-url/util.js';
3
+ import { LiveResponse, RequestPlus } from '@webqit/fetch-plus';
4
+ import { Path } from '@webqit/url-plus';
6
5
 
7
- export class WebfloRouter {
6
+ export class WebfloRouter111 {
8
7
 
9
8
  #runtime;
10
9
  #path;
@@ -103,7 +102,7 @@ export class WebfloRouter {
103
102
  // Build request inheritance chain
104
103
  const requestInheritanceChain = [url];
105
104
  if (!isFetch && thisTick.event.request instanceof Request) {
106
- const { url: _, ...init } = await requestShim.copy.value(thisTick.event.request);
105
+ const { url: _, ...init } = await RequestPlus.copy(thisTick.event.request);
107
106
  requestInheritanceChain.push(init);
108
107
  }
109
108
  const noArg2 = () => {
@@ -111,7 +110,7 @@ export class WebfloRouter {
111
110
  };
112
111
  if (args[0] instanceof Request) {
113
112
  if (args[1]) noArg2();
114
- const { url: _, ...init } = await requestShim.copy.value(args[0]);
113
+ const { url: _, ...init } = await RequestPlus.copy(args[0]);
115
114
  requestInheritanceChain.push(init);
116
115
  } else if (!isFetch && _isObject(args[0])) {
117
116
  if (args[1]) noArg2();