remult 3.3.19-next.2 → 3.4.0-next.2
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.
- package/SseSubscriptionServer.d.ts +2 -0
- package/SseSubscriptionServer.js +4 -2
- package/esm/SseSubscriptionServer.js +3 -1
- package/esm/index.js +6 -7
- package/esm/live-query/data-provider-live-query-storage.js +3 -1
- package/esm/remult-d1.js +0 -1
- package/esm/remult-knex/index.js +2 -112
- package/esm/remult-mongo.js +8 -17
- package/esm/remult-sqlite-core.js +0 -1
- package/esm/server/remult-api-server.js +6 -1
- package/esm/src/data-api.js +1 -0
- package/esm/src/data-interfaces.js +4 -4
- package/esm/src/data-providers/array-entity-data-provider.js +2 -14
- package/esm/src/data-providers/json-data-provider.js +2 -2
- package/esm/src/data-providers/rest-data-provider.js +12 -11
- package/esm/src/data-providers/sql-database.js +43 -110
- package/esm/src/live-query/LiveQueryClient.js +163 -52
- package/esm/src/live-query/SseSubscriptionClient.js +101 -43
- package/esm/src/live-query/SubscriptionChannel.js +37 -3
- package/esm/src/live-query/SubscriptionServer.js +11 -3
- package/esm/src/remult3/RepositoryImplementation.js +60 -90
- package/esm/src/remult3/remult3.js +6 -0
- package/index.d.ts +17 -31
- package/index.js +2 -5
- package/live-query/data-provider-live-query-storage.d.ts +4 -1
- package/live-query/data-provider-live-query-storage.js +18 -15
- package/package.json +1 -1
- package/remult-d1.d.ts +0 -1
- package/remult-d1.js +0 -1
- package/remult-knex/index.js +44 -329
- package/remult-mongo.js +36 -80
- package/remult-sqlite-core.d.ts +0 -1
- package/remult-sqlite-core.js +0 -1
- package/server/remult-api-server.js +13 -9
- package/src/data-api.js +1 -0
- package/src/data-interfaces.d.ts +3 -2
- package/src/data-interfaces.js +4 -4
- package/src/data-providers/array-entity-data-provider.d.ts +2 -2
- package/src/data-providers/array-entity-data-provider.js +14 -92
- package/src/data-providers/json-data-provider.js +2 -2
- package/src/data-providers/rest-data-provider.d.ts +3 -2
- package/src/data-providers/rest-data-provider.js +17 -18
- package/src/data-providers/sql-database.js +105 -254
- package/src/live-query/LiveQueryClient.js +215 -97
- package/src/live-query/SseSubscriptionClient.js +143 -83
- package/src/live-query/SubscriptionChannel.d.ts +8 -0
- package/src/live-query/SubscriptionChannel.js +47 -9
- package/src/live-query/SubscriptionServer.d.ts +7 -2
- package/src/live-query/SubscriptionServer.js +20 -10
- package/src/remult3/RepositoryImplementation.d.ts +1 -1
- package/src/remult3/RepositoryImplementation.js +270 -384
- package/src/remult3/remult3.d.ts +8 -2
- package/src/remult3/remult3.js +6 -0
- package/src/sql-command.d.ts +0 -2
- package/src/standard-schema/index.d.ts +1 -1
- package/esm/src/data-providers/indexed-db-data-provider.js +0 -811
- package/src/data-providers/indexed-db-data-provider.d.ts +0 -39
- package/src/data-providers/indexed-db-data-provider.js +0 -1387
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { buildRestDataProvider } from '../buildRestDataProvider.js';
|
|
2
2
|
import { RestDataProvider } from '../data-providers/rest-data-provider.js';
|
|
3
3
|
import { remultStatic } from '../remult-static.js';
|
|
4
|
+
import { flags } from '../remult3/remult3.js';
|
|
4
5
|
import { getRepositoryInternals } from '../remult3/repository-internals.js';
|
|
5
|
-
import { liveQueryKeepAliveRoute, LiveQuerySubscriber, } from './SubscriptionChannel.js';
|
|
6
|
+
import { liveQueryKeepAliveRoute, LiveQuerySubscriber, onBrowserForeground, } from './SubscriptionChannel.js';
|
|
6
7
|
/* @internal*/
|
|
7
8
|
export class LiveQueryClient {
|
|
8
9
|
apiProvider;
|
|
@@ -15,10 +16,10 @@ export class LiveQueryClient {
|
|
|
15
16
|
handleMessage();
|
|
16
17
|
}
|
|
17
18
|
queries = new Map();
|
|
19
|
+
channels = new Map();
|
|
18
20
|
hasQueriesForTesting() {
|
|
19
21
|
return this.queries.size > 0;
|
|
20
22
|
}
|
|
21
|
-
channels = new Map();
|
|
22
23
|
constructor(apiProvider, getUserId) {
|
|
23
24
|
this.apiProvider = apiProvider;
|
|
24
25
|
this.getUserId = getUserId;
|
|
@@ -29,6 +30,7 @@ export class LiveQueryClient {
|
|
|
29
30
|
close() {
|
|
30
31
|
this.queries.clear();
|
|
31
32
|
this.channels.clear();
|
|
33
|
+
this.detachForeground();
|
|
32
34
|
this.closeIfNoListeners();
|
|
33
35
|
}
|
|
34
36
|
async subscribeChannel(key, onResult) {
|
|
@@ -68,13 +70,15 @@ export class LiveQueryClient {
|
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
closeIfNoListeners() {
|
|
71
|
-
if (this.
|
|
72
|
-
if (this.
|
|
73
|
-
this.runPromise(this.client.then((x) => x.close()));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
if (this.queries.size === 0 && this.channels.size === 0) {
|
|
74
|
+
if (this.client)
|
|
75
|
+
this.runPromise(this.client.then((x) => x.close()).catch(() => { }));
|
|
76
|
+
this.client = undefined;
|
|
77
|
+
this.openedConnection = undefined;
|
|
78
|
+
this.detachForeground();
|
|
79
|
+
clearInterval(this.interval);
|
|
80
|
+
this.interval = undefined;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
subscribe(repo, options, listener) {
|
|
80
84
|
let alive = true;
|
|
@@ -94,43 +98,56 @@ export class LiveQueryClient {
|
|
|
94
98
|
if (!q) {
|
|
95
99
|
this.queries.set(eventTypeKey, (q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, options }, this.getUserId())));
|
|
96
100
|
q.subscribeCode = () => {
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
q.unsubscribeChannel();
|
|
102
|
+
let unsubscribeToChannel = () => { };
|
|
103
|
+
q.unsubscribeChannel = () => {
|
|
104
|
+
unsubscribeToChannel();
|
|
105
|
+
unsubscribeToChannel = () => { };
|
|
106
|
+
};
|
|
107
|
+
q.unsubscribe = () => {
|
|
99
108
|
q.unsubscribe = () => { };
|
|
100
|
-
|
|
109
|
+
q.unsubscribeChannel();
|
|
110
|
+
q.unsubscribeQuery();
|
|
111
|
+
};
|
|
112
|
+
let snapshotDone = false;
|
|
113
|
+
const applySnapshot = (r) => {
|
|
114
|
+
if (q.listeners.length === 0) {
|
|
115
|
+
r.unsubscribe();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
q.unsubscribeQuery = () => {
|
|
119
|
+
this.runPromise(r.unsubscribe());
|
|
120
|
+
};
|
|
121
|
+
snapshotDone = true;
|
|
122
|
+
return this.runPromise(q.setAllItems(r.result));
|
|
123
|
+
};
|
|
101
124
|
this.runPromise(this.subscribeChannel(q.queryChannel, {
|
|
102
125
|
next: (value) => this.runPromise(q.handle(value)),
|
|
103
126
|
complete: () => { },
|
|
104
127
|
error: (er) => {
|
|
105
128
|
q.listeners.forEach((l) => l.error(er));
|
|
106
129
|
},
|
|
107
|
-
})
|
|
130
|
+
})
|
|
131
|
+
.then((unsub) => {
|
|
108
132
|
if (q.listeners.length == 0) {
|
|
109
|
-
|
|
133
|
+
unsub();
|
|
110
134
|
return;
|
|
111
135
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.runPromise(q.setAllItems(r.result));
|
|
120
|
-
q.unsubscribe = () => {
|
|
121
|
-
q.unsubscribe = () => { };
|
|
122
|
-
unsubscribeToChannel();
|
|
123
|
-
this.runPromise(r.unsubscribe());
|
|
124
|
-
};
|
|
125
|
-
})
|
|
126
|
-
.catch((err) => {
|
|
127
|
-
q.listeners.forEach((l) => l.error(err));
|
|
128
|
-
unsubscribeToChannel();
|
|
129
|
-
this.queries.delete(eventTypeKey);
|
|
130
|
-
}));
|
|
131
|
-
})).catch((err) => {
|
|
136
|
+
unsubscribeToChannel = unsub;
|
|
137
|
+
if (snapshotDone)
|
|
138
|
+
return this.runPromise(this.runKeepAlive({ checkVersions: true }));
|
|
139
|
+
})
|
|
140
|
+
.catch((err) => {
|
|
132
141
|
q.listeners.forEach((l) => l.error(err));
|
|
133
|
-
});
|
|
142
|
+
}));
|
|
143
|
+
this.runPromise(subscribe(q.queryChannel)
|
|
144
|
+
.then(applySnapshot)
|
|
145
|
+
.catch((err) => {
|
|
146
|
+
q.listeners.forEach((l) => l.error(err));
|
|
147
|
+
unsubscribeToChannel();
|
|
148
|
+
this.queries.delete(eventTypeKey);
|
|
149
|
+
this.closeIfNoListeners();
|
|
150
|
+
}));
|
|
134
151
|
};
|
|
135
152
|
q.subscribeCode();
|
|
136
153
|
}
|
|
@@ -156,29 +173,123 @@ export class LiveQueryClient {
|
|
|
156
173
|
};
|
|
157
174
|
}
|
|
158
175
|
client;
|
|
176
|
+
openedConnection;
|
|
159
177
|
interval;
|
|
178
|
+
lastKeepAliveAt = 0;
|
|
179
|
+
keepAliveBackoffMs = flags.liveQueryPollWhenStaleMs;
|
|
180
|
+
keepAliveInFlight;
|
|
181
|
+
foregroundKeepAliveTimer;
|
|
182
|
+
isSseStale() {
|
|
183
|
+
const conn = this.openedConnection;
|
|
184
|
+
if (!conn || conn.lastServerEvent === undefined)
|
|
185
|
+
return false;
|
|
186
|
+
return Date.now() - conn.lastServerEvent > flags.sseStaleMs;
|
|
187
|
+
}
|
|
188
|
+
async runKeepAlive(opts) {
|
|
189
|
+
if (this.keepAliveInFlight)
|
|
190
|
+
return this.keepAliveInFlight;
|
|
191
|
+
this.keepAliveInFlight = this.sendKeepAlive(opts).finally(() => {
|
|
192
|
+
this.keepAliveInFlight = undefined;
|
|
193
|
+
});
|
|
194
|
+
return this.keepAliveInFlight;
|
|
195
|
+
}
|
|
196
|
+
async sendKeepAlive(opts) {
|
|
197
|
+
const ids = [];
|
|
198
|
+
for (const q of this.queries.values()) {
|
|
199
|
+
ids.push(q.queryChannel);
|
|
200
|
+
}
|
|
201
|
+
if (ids.length === 0)
|
|
202
|
+
return;
|
|
203
|
+
let p = this.apiProvider();
|
|
204
|
+
const raw = await this.runPromise(remultStatic.actionInfo.runActionWithoutBlockingUI(() => buildRestDataProvider(p.httpClient).post(p.url + '/' + liveQueryKeepAliveRoute, { queryIds: ids })));
|
|
205
|
+
const unknownIds = Array.isArray(raw)
|
|
206
|
+
? raw
|
|
207
|
+
: (raw?.unknownQueryIds ?? []);
|
|
208
|
+
const versions = Array.isArray(raw)
|
|
209
|
+
? {}
|
|
210
|
+
: (raw?.versions ?? {});
|
|
211
|
+
const stale = opts?.checkVersions || this.isSseStale();
|
|
212
|
+
let reloaded = false;
|
|
213
|
+
for (const q of this.queries.values()) {
|
|
214
|
+
const serverVersion = versions[q.queryChannel];
|
|
215
|
+
const unknown = unknownIds.includes(q.queryChannel);
|
|
216
|
+
const mismatch = stale && serverVersion !== undefined && serverVersion !== q.version;
|
|
217
|
+
if (unknown || mismatch) {
|
|
218
|
+
reloaded = true;
|
|
219
|
+
if (serverVersion !== undefined)
|
|
220
|
+
q.version = serverVersion;
|
|
221
|
+
q.subscribeCode();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (this.isSseStale()) {
|
|
225
|
+
this.keepAliveBackoffMs = reloaded
|
|
226
|
+
? flags.liveQueryPollWhenStaleMs
|
|
227
|
+
: Math.min(this.keepAliveBackoffMs * 2, flags.liveQueryKeepAliveMs);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.keepAliveBackoffMs = flags.liveQueryPollWhenStaleMs;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async maybeKeepAlive() {
|
|
234
|
+
const stale = this.isSseStale();
|
|
235
|
+
if (!stale)
|
|
236
|
+
this.keepAliveBackoffMs = flags.liveQueryPollWhenStaleMs;
|
|
237
|
+
const interval = stale
|
|
238
|
+
? this.keepAliveBackoffMs
|
|
239
|
+
: flags.liveQueryKeepAliveMs;
|
|
240
|
+
if (Date.now() - this.lastKeepAliveAt < interval)
|
|
241
|
+
return;
|
|
242
|
+
this.lastKeepAliveAt = Date.now();
|
|
243
|
+
if (stale)
|
|
244
|
+
this.openedConnection?.resume?.(true);
|
|
245
|
+
await this.runKeepAlive();
|
|
246
|
+
}
|
|
247
|
+
detachForeground = () => { };
|
|
248
|
+
attachForeground() {
|
|
249
|
+
this.detachForeground();
|
|
250
|
+
const unsub = onBrowserForeground(() => {
|
|
251
|
+
this.openedConnection?.resume?.();
|
|
252
|
+
if (this.foregroundKeepAliveTimer !== undefined)
|
|
253
|
+
clearTimeout(this.foregroundKeepAliveTimer);
|
|
254
|
+
this.foregroundKeepAliveTimer = setTimeout(() => {
|
|
255
|
+
this.foregroundKeepAliveTimer = undefined;
|
|
256
|
+
this.keepAliveBackoffMs = flags.liveQueryPollWhenStaleMs;
|
|
257
|
+
this.lastKeepAliveAt = 0;
|
|
258
|
+
this.runPromise(this.runKeepAlive({ checkVersions: true }));
|
|
259
|
+
}, 300);
|
|
260
|
+
});
|
|
261
|
+
this.detachForeground = () => {
|
|
262
|
+
unsub();
|
|
263
|
+
if (this.foregroundKeepAliveTimer !== undefined) {
|
|
264
|
+
clearTimeout(this.foregroundKeepAliveTimer);
|
|
265
|
+
this.foregroundKeepAliveTimer = undefined;
|
|
266
|
+
}
|
|
267
|
+
this.detachForeground = () => { };
|
|
268
|
+
};
|
|
269
|
+
}
|
|
160
270
|
openIfNoOpened() {
|
|
161
271
|
if (!this.client) {
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
for (const id of invalidIds) {
|
|
171
|
-
for (const q of this.queries.values()) {
|
|
172
|
-
if (q.queryChannel === id)
|
|
173
|
-
q.subscribeCode();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}, 30000);
|
|
178
|
-
return this.runPromise((this.client = this.apiProvider().subscriptionClient.openConnection(() => {
|
|
272
|
+
this.lastKeepAliveAt = Date.now();
|
|
273
|
+
this.keepAliveBackoffMs = flags.liveQueryPollWhenStaleMs;
|
|
274
|
+
this.attachForeground();
|
|
275
|
+
this.interval = setInterval(() => {
|
|
276
|
+
this.runPromise(this.maybeKeepAlive());
|
|
277
|
+
}, Math.min(flags.liveQueryPollWhenStaleMs, flags.liveQueryKeepAliveMs));
|
|
278
|
+
return this.runPromise((this.client = this.apiProvider()
|
|
279
|
+
.subscriptionClient.openConnection(() => {
|
|
179
280
|
for (const q of this.queries.values()) {
|
|
180
281
|
q.subscribeCode();
|
|
181
282
|
}
|
|
283
|
+
})
|
|
284
|
+
.then((c) => {
|
|
285
|
+
this.openedConnection = c;
|
|
286
|
+
return c;
|
|
287
|
+
})
|
|
288
|
+
.catch((err) => {
|
|
289
|
+
this.client = undefined;
|
|
290
|
+
this.openedConnection = undefined;
|
|
291
|
+
this.closeIfNoListeners();
|
|
292
|
+
throw err;
|
|
182
293
|
})));
|
|
183
294
|
}
|
|
184
295
|
return this.client;
|
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
import { buildRestDataProvider } from '../buildRestDataProvider.js';
|
|
2
2
|
import { remult } from '../remult-proxy.js';
|
|
3
3
|
import { remultStatic } from '../remult-static.js';
|
|
4
|
-
import { flags } from '../remult3/remult3.js';
|
|
5
4
|
import { streamUrl } from './SubscriptionChannel.js';
|
|
6
5
|
export class SseSubscriptionClient {
|
|
7
6
|
openConnection(onReconnect) {
|
|
8
7
|
let connectionId;
|
|
9
8
|
const channels = new Map();
|
|
10
9
|
const provider = buildRestDataProvider(remult.apiClient.httpClient);
|
|
11
|
-
let connected = false;
|
|
12
10
|
let source;
|
|
11
|
+
let retryCount = 0;
|
|
12
|
+
let closed = false;
|
|
13
|
+
let connected = false;
|
|
14
|
+
let reconnectTimer;
|
|
15
|
+
let connectionReady;
|
|
16
|
+
let connectionPromise = new Promise((res) => {
|
|
17
|
+
connectionReady = res;
|
|
18
|
+
});
|
|
19
|
+
function clearReconnectTimer() {
|
|
20
|
+
if (reconnectTimer !== undefined) {
|
|
21
|
+
clearTimeout(reconnectTimer);
|
|
22
|
+
reconnectTimer = undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function scheduleReconnect() {
|
|
26
|
+
if (closed)
|
|
27
|
+
return;
|
|
28
|
+
clearReconnectTimer();
|
|
29
|
+
const delay = Math.min(500 * 2 ** Math.min(retryCount, 5), 8_000);
|
|
30
|
+
retryCount++;
|
|
31
|
+
reconnectTimer = setTimeout(() => {
|
|
32
|
+
reconnectTimer = undefined;
|
|
33
|
+
createConnection();
|
|
34
|
+
}, delay);
|
|
35
|
+
}
|
|
36
|
+
function resume(force) {
|
|
37
|
+
if (closed)
|
|
38
|
+
return;
|
|
39
|
+
retryCount = 0;
|
|
40
|
+
clearReconnectTimer();
|
|
41
|
+
if (force) {
|
|
42
|
+
createConnection();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const open = source &&
|
|
46
|
+
typeof EventSource !== 'undefined' &&
|
|
47
|
+
source.readyState !== EventSource.CLOSED;
|
|
48
|
+
if (!open)
|
|
49
|
+
createConnection();
|
|
50
|
+
}
|
|
13
51
|
const client = {
|
|
52
|
+
lastServerEvent: Date.now(),
|
|
53
|
+
resume,
|
|
14
54
|
close() {
|
|
15
|
-
|
|
55
|
+
closed = true;
|
|
56
|
+
clearReconnectTimer();
|
|
57
|
+
source?.close();
|
|
16
58
|
},
|
|
17
59
|
async subscribe(channel, handler) {
|
|
18
60
|
let listeners = channels.get(channel);
|
|
19
61
|
if (!listeners) {
|
|
20
62
|
channels.set(channel, (listeners = []));
|
|
21
|
-
await
|
|
63
|
+
await connectionPromise;
|
|
64
|
+
if (!closed)
|
|
65
|
+
await subscribeToChannel(channel);
|
|
22
66
|
}
|
|
23
67
|
listeners.push(handler);
|
|
24
68
|
return () => {
|
|
@@ -33,45 +77,52 @@ export class SseSubscriptionClient {
|
|
|
33
77
|
};
|
|
34
78
|
},
|
|
35
79
|
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
source.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
function noteServerEvent() {
|
|
81
|
+
client.lastServerEvent = Date.now();
|
|
82
|
+
}
|
|
83
|
+
function createConnection() {
|
|
84
|
+
if (closed)
|
|
85
|
+
return;
|
|
86
|
+
clearReconnectTimer();
|
|
87
|
+
if (source)
|
|
88
|
+
source.close();
|
|
89
|
+
source = SseSubscriptionClient.createEventSource(remult.apiClient.url + '/' + streamUrl);
|
|
90
|
+
source.onmessage = (e) => {
|
|
91
|
+
noteServerEvent();
|
|
92
|
+
let message = JSON.parse(e.data);
|
|
93
|
+
const listeners = channels.get(message.channel);
|
|
94
|
+
if (listeners)
|
|
95
|
+
listeners.forEach((x) => x(message.data));
|
|
96
|
+
};
|
|
97
|
+
source.addEventListener('keep-alive', () => {
|
|
98
|
+
noteServerEvent();
|
|
99
|
+
});
|
|
100
|
+
source.onerror = (e) => {
|
|
101
|
+
console.error('Live Query Event Source Error', e);
|
|
102
|
+
source.close();
|
|
103
|
+
if (closed)
|
|
104
|
+
return;
|
|
105
|
+
scheduleReconnect();
|
|
106
|
+
};
|
|
107
|
+
source.addEventListener('connectionId', async (e) => {
|
|
108
|
+
//@ts-ignore
|
|
109
|
+
connectionId = e.data;
|
|
110
|
+
retryCount = 0;
|
|
111
|
+
noteServerEvent();
|
|
112
|
+
if (connected) {
|
|
113
|
+
for (const channel of channels.keys()) {
|
|
114
|
+
await subscribeToChannel(channel);
|
|
70
115
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
116
|
+
onReconnect();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
connected = true;
|
|
120
|
+
connectionReady();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
createConnection();
|
|
125
|
+
return Promise.resolve(client);
|
|
75
126
|
async function subscribeToChannel(channel) {
|
|
76
127
|
const result = await remultStatic.actionInfo.runActionWithoutBlockingUI(() => {
|
|
77
128
|
return provider.post(remult.apiClient.url + '/' + streamUrl + '/subscribe', {
|
|
@@ -80,7 +131,14 @@ export class SseSubscriptionClient {
|
|
|
80
131
|
});
|
|
81
132
|
});
|
|
82
133
|
if (result === ConnectionNotFoundError) {
|
|
83
|
-
|
|
134
|
+
connected = false;
|
|
135
|
+
connectionPromise = new Promise((res) => {
|
|
136
|
+
connectionReady = res;
|
|
137
|
+
});
|
|
138
|
+
createConnection();
|
|
139
|
+
await connectionPromise;
|
|
140
|
+
if (!closed)
|
|
141
|
+
await subscribeToChannel(channel);
|
|
84
142
|
}
|
|
85
143
|
}
|
|
86
144
|
}
|
|
@@ -12,6 +12,8 @@ export class LiveQuerySubscriber {
|
|
|
12
12
|
queryChannel;
|
|
13
13
|
subscribeCode;
|
|
14
14
|
unsubscribe = () => { };
|
|
15
|
+
unsubscribeChannel = () => { };
|
|
16
|
+
unsubscribeQuery = () => { };
|
|
15
17
|
async setAllItems(result) {
|
|
16
18
|
const items = await getRepositoryInternals(this.repo)._fromJsonArray(result, this.query.options);
|
|
17
19
|
this.forListeners((listener) => {
|
|
@@ -19,6 +21,7 @@ export class LiveQuerySubscriber {
|
|
|
19
21
|
return items;
|
|
20
22
|
});
|
|
21
23
|
}, this.allItemsMessage(items));
|
|
24
|
+
this.version = 0;
|
|
22
25
|
}
|
|
23
26
|
allItemsMessage(items) {
|
|
24
27
|
return [
|
|
@@ -52,8 +55,19 @@ export class LiveQuerySubscriber {
|
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
async handle(messages) {
|
|
58
|
+
const ver = messages.find((m) => m.type === 'version');
|
|
59
|
+
if (ver) {
|
|
60
|
+
if (ver.from < this.version)
|
|
61
|
+
return;
|
|
62
|
+
if (ver.from > this.version) {
|
|
63
|
+
this.version = ver.to;
|
|
64
|
+
this.subscribeCode?.();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const data = messages.filter((m) => m.type !== 'version');
|
|
55
69
|
{
|
|
56
|
-
let x =
|
|
70
|
+
let x = data.filter(({ type }) => type == 'add' || type == 'replace');
|
|
57
71
|
let loadedItems = await getRepositoryInternals(this.repo)._fromJsonArray(x.map((m) => m.data.item), this.query.options);
|
|
58
72
|
for (let index = 0; index < x.length; index++) {
|
|
59
73
|
const element = x[index];
|
|
@@ -64,7 +78,7 @@ export class LiveQuerySubscriber {
|
|
|
64
78
|
listener((items) => {
|
|
65
79
|
if (!items)
|
|
66
80
|
items = [];
|
|
67
|
-
for (const message of
|
|
81
|
+
for (const message of data) {
|
|
68
82
|
switch (message.type) {
|
|
69
83
|
case 'all':
|
|
70
84
|
this.setAllItems(message.data);
|
|
@@ -87,11 +101,13 @@ export class LiveQuerySubscriber {
|
|
|
87
101
|
}
|
|
88
102
|
return items;
|
|
89
103
|
});
|
|
90
|
-
},
|
|
104
|
+
}, data);
|
|
105
|
+
this.version = ver ? ver.to : this.version + (data.length > 0 ? 1 : 0);
|
|
91
106
|
}
|
|
92
107
|
defaultQueryState = [];
|
|
93
108
|
listeners = [];
|
|
94
109
|
id = String(crypto.randomUUID());
|
|
110
|
+
version = 0;
|
|
95
111
|
constructor(repo, query, userId) {
|
|
96
112
|
this.repo = repo;
|
|
97
113
|
this.query = query;
|
|
@@ -99,6 +115,24 @@ export class LiveQuerySubscriber {
|
|
|
99
115
|
this.id = this.queryChannel;
|
|
100
116
|
}
|
|
101
117
|
}
|
|
118
|
+
//@internal
|
|
119
|
+
export function onBrowserForeground(handler) {
|
|
120
|
+
if (typeof document === 'undefined')
|
|
121
|
+
return () => { };
|
|
122
|
+
function onForeground() {
|
|
123
|
+
if (document.visibilityState === 'hidden')
|
|
124
|
+
return;
|
|
125
|
+
handler();
|
|
126
|
+
}
|
|
127
|
+
document.addEventListener('visibilitychange', onForeground);
|
|
128
|
+
window.addEventListener('online', onForeground);
|
|
129
|
+
window.addEventListener('pageshow', onForeground);
|
|
130
|
+
return () => {
|
|
131
|
+
document.removeEventListener('visibilitychange', onForeground);
|
|
132
|
+
window.removeEventListener('online', onForeground);
|
|
133
|
+
window.removeEventListener('pageshow', onForeground);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
102
136
|
export const liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
|
|
103
137
|
/**
|
|
104
138
|
* The `SubscriptionChannel` class is used to send messages from the backend to the frontend,
|
|
@@ -57,6 +57,11 @@ export class LiveQueryPublisher {
|
|
|
57
57
|
messages,
|
|
58
58
|
});
|
|
59
59
|
query.lastIds = currentIds;
|
|
60
|
+
if (messages.length > 0) {
|
|
61
|
+
const from = query.version ?? 0;
|
|
62
|
+
query.version = from + 1;
|
|
63
|
+
messages.push({ type: 'version', from, to: query.version });
|
|
64
|
+
}
|
|
60
65
|
await setData(query);
|
|
61
66
|
if (messages.length > 0)
|
|
62
67
|
this.subscriptionServer().publishMessage(q.id, messages);
|
|
@@ -70,21 +75,24 @@ export class InMemoryLiveQueryStorage {
|
|
|
70
75
|
this.debugFileSaver(this.queries);
|
|
71
76
|
}
|
|
72
77
|
async keepAliveAndReturnUnknownQueryIds(ids) {
|
|
73
|
-
const
|
|
78
|
+
const unknownQueryIds = [];
|
|
79
|
+
const versions = {};
|
|
74
80
|
for (const id of ids) {
|
|
75
81
|
let q = this.queries.find((q) => q.id === id);
|
|
76
82
|
if (q) {
|
|
77
83
|
q.lastUsed = new Date().toISOString();
|
|
84
|
+
versions[id] = q.data?.version ?? 0;
|
|
78
85
|
}
|
|
79
86
|
else
|
|
80
|
-
|
|
87
|
+
unknownQueryIds.push(id);
|
|
81
88
|
}
|
|
82
89
|
this.debug();
|
|
83
|
-
return
|
|
90
|
+
return { unknownQueryIds, versions };
|
|
84
91
|
}
|
|
85
92
|
queries = [];
|
|
86
93
|
constructor() { }
|
|
87
94
|
async add(query) {
|
|
95
|
+
this.queries = this.queries.filter((q) => q.id !== query.id);
|
|
88
96
|
this.queries.push({ ...query, lastUsed: new Date().toISOString() });
|
|
89
97
|
this.debug();
|
|
90
98
|
}
|