remult 0.18.1-exp.2 → 0.18.1-exp.21
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/README.md +28 -5
- package/esm/index.d.ts +4 -3
- package/esm/index.js +4 -2
- package/esm/live-query/ably.d.ts +6 -6
- package/esm/live-query/ably.js +5 -5
- package/esm/postgres/postgres-data-provider.d.ts +3 -1
- package/esm/postgres/postgres-data-provider.js +11 -1
- package/esm/postgres/schema-builder.d.ts +1 -1
- package/esm/postgres/schema-builder.js +43 -27
- package/esm/remult-express.d.ts +4 -22
- package/esm/remult-express.js +30 -28
- package/esm/remult-fastify.js +1 -1
- package/esm/remult-fresh.js +2 -2
- package/esm/remult-knex/index.d.ts +7 -31
- package/esm/remult-knex/index.js +71 -42
- package/esm/remult-mongo.d.ts +9 -2
- package/esm/remult-mongo.js +24 -8
- package/esm/remult-next.d.ts +11 -0
- package/esm/remult-next.js +27 -0
- package/esm/server/expressBridge.d.ts +20 -52
- package/esm/server/expressBridge.js +55 -29
- package/esm/server/index.d.ts +3 -1
- package/esm/server/index.js +6 -1
- package/esm/src/column-interfaces.d.ts +44 -8
- package/esm/src/context.d.ts +31 -19
- package/esm/src/context.js +35 -42
- package/esm/src/data-api.js +17 -4
- package/esm/src/data-interfaces.d.ts +1 -1
- package/esm/src/data-providers/array-entity-data-provider.d.ts +1 -1
- package/esm/src/data-providers/array-entity-data-provider.js +1 -1
- package/esm/src/data-providers/rest-data-provider.d.ts +4 -15
- package/esm/src/data-providers/rest-data-provider.js +38 -23
- package/esm/src/data-providers/sql-database.d.ts +6 -3
- package/esm/src/data-providers/sql-database.js +57 -16
- package/esm/src/data-providers/web-sql-data-provider.d.ts +2 -0
- package/esm/src/data-providers/web-sql-data-provider.js +17 -8
- package/esm/src/entity.d.ts +2 -2
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +29 -21
- package/esm/src/filter/filter-interfaces.d.ts +5 -5
- package/esm/src/filter/filter-interfaces.js +31 -26
- package/esm/src/live-query/LiveQueryClient.d.ts +1 -19
- package/esm/src/live-query/LiveQueryClient.js +81 -50
- package/esm/src/live-query/SseSubscriptionClient.d.ts +4 -0
- package/esm/src/live-query/{EventSourceLiveQueryProvider.js → SseSubscriptionClient.js} +7 -6
- package/esm/src/live-query/SubscriptionChannel.d.ts +41 -0
- package/esm/src/live-query/{LiveQuerySubscriber.js → SubscriptionChannel.js} +40 -22
- package/esm/src/live-query/SubscriptionServer.d.ts +38 -0
- package/esm/src/live-query/{LiveQueryPublisher.js → SubscriptionServer.js} +60 -73
- package/esm/src/remult-proxy.js +44 -1
- package/esm/src/remult3/RepositoryImplementation.d.ts +8 -2
- package/esm/src/remult3/RepositoryImplementation.js +94 -30
- package/esm/src/remult3/remult3.d.ts +59 -6
- package/esm/src/remult3/remult3.js +1 -0
- package/esm/src/server-action.d.ts +1 -1
- package/esm/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/esm/src/shared-tests/db-tests-setup.js +4 -3
- package/esm/src/shared-tests/db-tests.db-tests.js +55 -9
- package/esm/src/sql-command.d.ts +4 -2
- package/esm/src/tests/entityForCustomFilter.d.ts +6 -6
- package/esm/src/tests/entityForCustomFilter.js +20 -21
- package/esm/src/tests/frontend-database-tests-setup.spec.js +1 -2
- package/esm/src/tests/testHelper.spec.d.ts +2 -2
- package/esm/src/tests/testHelper.spec.js +1 -2
- package/esm/src/valueConverters.js +17 -7
- package/index.d.ts +4 -3
- package/index.js +7 -3
- package/live-query/ably.d.ts +6 -6
- package/live-query/ably.js +12 -12
- package/package.json +18 -9
- package/postgres/postgres-data-provider.d.ts +3 -1
- package/postgres/postgres-data-provider.js +13 -3
- package/postgres/schema-builder.d.ts +1 -1
- package/postgres/schema-builder.js +64 -50
- package/remult-express.d.ts +4 -22
- package/remult-express.js +39 -47
- package/remult-fastify.js +2 -2
- package/remult-fresh.js +1 -1
- package/remult-knex/index.d.ts +7 -31
- package/remult-knex/index.js +100 -53
- package/remult-mongo.d.ts +9 -2
- package/remult-mongo.js +39 -7
- package/remult-next.d.ts +11 -0
- package/remult-next.js +73 -0
- package/server/JsonEntityFileStorage.js +2 -2
- package/server/expressBridge.d.ts +20 -52
- package/server/expressBridge.js +68 -41
- package/server/index.d.ts +3 -1
- package/server/index.js +7 -2
- package/src/column-interfaces.d.ts +44 -8
- package/src/context.d.ts +31 -19
- package/src/context.js +39 -50
- package/src/data-api.js +18 -5
- package/src/data-interfaces.d.ts +1 -1
- package/src/data-providers/array-entity-data-provider.d.ts +1 -1
- package/src/data-providers/array-entity-data-provider.js +1 -1
- package/src/data-providers/rest-data-provider.d.ts +4 -15
- package/src/data-providers/rest-data-provider.js +48 -24
- package/src/data-providers/sql-database.d.ts +6 -3
- package/src/data-providers/sql-database.js +75 -15
- package/src/data-providers/web-sql-data-provider.d.ts +2 -0
- package/src/data-providers/web-sql-data-provider.js +16 -7
- package/src/entity.d.ts +2 -2
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
- package/src/filter/filter-consumer-bridge-to-sql-request.js +59 -50
- package/src/filter/filter-interfaces.d.ts +5 -5
- package/src/filter/filter-interfaces.js +31 -26
- package/src/live-query/LiveQueryClient.d.ts +1 -19
- package/src/live-query/LiveQueryClient.js +117 -64
- package/src/live-query/SseSubscriptionClient.d.ts +4 -0
- package/src/live-query/{EventSourceLiveQueryProvider.js → SseSubscriptionClient.js} +13 -12
- package/src/live-query/SubscriptionChannel.d.ts +41 -0
- package/src/live-query/{LiveQuerySubscriber.js → SubscriptionChannel.js} +51 -47
- package/src/live-query/SubscriptionServer.d.ts +38 -0
- package/src/live-query/SubscriptionServer.js +237 -0
- package/src/remult-proxy.js +137 -2
- package/src/remult3/RepositoryImplementation.d.ts +8 -2
- package/src/remult3/RepositoryImplementation.js +121 -34
- package/src/remult3/remult3.d.ts +59 -6
- package/src/remult3/remult3.js +1 -0
- package/src/server-action.d.ts +1 -1
- package/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/src/shared-tests/db-tests-setup.js +5 -4
- package/src/shared-tests/db-tests.db-tests.js +101 -8
- package/src/sql-command.d.ts +4 -2
- package/src/tests/entityForCustomFilter.d.ts +6 -6
- package/src/tests/entityForCustomFilter.js +30 -31
- package/src/tests/frontend-database-tests-setup.spec.js +4 -5
- package/src/tests/testHelper.spec.d.ts +2 -2
- package/src/tests/testHelper.spec.js +4 -5
- package/src/valueConverters.js +17 -7
- package/esm/live-query/index.d.ts +0 -3
- package/esm/live-query/index.js +0 -3
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -5
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +0 -113
- package/esm/src/backend-tests/my-test.backend-spec.d.ts +0 -1
- package/esm/src/backend-tests/my-test.backend-spec.js +0 -59
- package/esm/src/backend-tests/test-http-server.backend-spec.d.ts +0 -6
- package/esm/src/backend-tests/test-http-server.backend-spec.js +0 -171
- package/esm/src/live-query/EventSourceLiveQueryProvider.d.ts +0 -4
- package/esm/src/live-query/LiveQueryPublisher.d.ts +0 -49
- package/esm/src/live-query/LiveQuerySubscriber.d.ts +0 -67
- package/live-query/index.d.ts +0 -3
- package/live-query/index.js +0 -10
- package/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -5
- package/src/backend-tests/backend-database-test-setup.backend-spec.js +0 -231
- package/src/backend-tests/my-test.backend-spec.d.ts +0 -1
- package/src/backend-tests/my-test.backend-spec.js +0 -160
- package/src/backend-tests/test-http-server.backend-spec.d.ts +0 -6
- package/src/backend-tests/test-http-server.backend-spec.js +0 -342
- package/src/live-query/EventSourceLiveQueryProvider.d.ts +0 -4
- package/src/live-query/LiveQueryPublisher.d.ts +0 -49
- package/src/live-query/LiveQueryPublisher.js +0 -248
- package/src/live-query/LiveQuerySubscriber.d.ts +0 -67
|
@@ -14,18 +14,18 @@ export class Filter {
|
|
|
14
14
|
return await filter();
|
|
15
15
|
return filter;
|
|
16
16
|
}
|
|
17
|
-
static createCustom(
|
|
18
|
-
let
|
|
17
|
+
static createCustom(rawFilterTranslator, key = '') {
|
|
18
|
+
let rawFilterInfo = { key: key, rawFilterTranslator };
|
|
19
19
|
return Object.assign((x) => {
|
|
20
20
|
let z = {};
|
|
21
21
|
if (x == undefined)
|
|
22
22
|
x = {};
|
|
23
|
-
if (!
|
|
23
|
+
if (!rawFilterInfo.key)
|
|
24
24
|
throw "Usage of custom filter before a key was assigned to it";
|
|
25
25
|
return {
|
|
26
|
-
[customUrlToken +
|
|
26
|
+
[customUrlToken + rawFilterInfo.key]: x
|
|
27
27
|
};
|
|
28
|
-
}, {
|
|
28
|
+
}, { rawFilterInfo });
|
|
29
29
|
}
|
|
30
30
|
static fromEntityFilter(entity, whereItem) {
|
|
31
31
|
let result = [];
|
|
@@ -79,6 +79,7 @@ export class Filter {
|
|
|
79
79
|
break;
|
|
80
80
|
case "$ne":
|
|
81
81
|
case "!=":
|
|
82
|
+
case "$nin":
|
|
82
83
|
found = true;
|
|
83
84
|
if (Array.isArray(element)) {
|
|
84
85
|
result.push(fh.isNotIn(element));
|
|
@@ -86,6 +87,10 @@ export class Filter {
|
|
|
86
87
|
else
|
|
87
88
|
result.push(fh.isDifferentFrom(element));
|
|
88
89
|
break;
|
|
90
|
+
case "$in":
|
|
91
|
+
found = true;
|
|
92
|
+
result.push(fh.isIn(element));
|
|
93
|
+
break;
|
|
89
94
|
case "$contains":
|
|
90
95
|
found = true;
|
|
91
96
|
result.push(fh.contains(element));
|
|
@@ -123,9 +128,9 @@ export class Filter {
|
|
|
123
128
|
let r = [];
|
|
124
129
|
for (const key in entity.entityType) {
|
|
125
130
|
const element = entity.entityType[key];
|
|
126
|
-
if (element && element.
|
|
127
|
-
if (element.
|
|
128
|
-
r.push(await Filter.fromEntityFilter(entity, await element.
|
|
131
|
+
if (element && element.rawFilterInfo && element.rawFilterInfo.rawFilterTranslator) {
|
|
132
|
+
if (element.rawFilterInfo.key == filterKey) {
|
|
133
|
+
r.push(await Filter.fromEntityFilter(entity, await element.rawFilterInfo.rawFilterTranslator(custom, remult)));
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
}
|
|
@@ -259,34 +264,34 @@ export class FilterSerializer {
|
|
|
259
264
|
}));
|
|
260
265
|
}
|
|
261
266
|
isNull(col) {
|
|
262
|
-
this.add(col.key + "
|
|
267
|
+
this.add(col.key + ".null", true);
|
|
263
268
|
}
|
|
264
269
|
isNotNull(col) {
|
|
265
|
-
this.add(col.key + "
|
|
270
|
+
this.add(col.key + ".null", false);
|
|
266
271
|
}
|
|
267
272
|
isIn(col, val) {
|
|
268
|
-
this.add(col.key + "
|
|
273
|
+
this.add(col.key + ".in", val.map(x => col.valueConverter.toJson(x)));
|
|
269
274
|
}
|
|
270
275
|
isEqualTo(col, val) {
|
|
271
276
|
this.add(col.key, col.valueConverter.toJson(val));
|
|
272
277
|
}
|
|
273
278
|
isDifferentFrom(col, val) {
|
|
274
|
-
this.add(col.key + '
|
|
279
|
+
this.add(col.key + '.ne', col.valueConverter.toJson(val));
|
|
275
280
|
}
|
|
276
281
|
isGreaterOrEqualTo(col, val) {
|
|
277
|
-
this.add(col.key + '
|
|
282
|
+
this.add(col.key + '.gte', col.valueConverter.toJson(val));
|
|
278
283
|
}
|
|
279
284
|
isGreaterThan(col, val) {
|
|
280
|
-
this.add(col.key + '
|
|
285
|
+
this.add(col.key + '.gt', col.valueConverter.toJson(val));
|
|
281
286
|
}
|
|
282
287
|
isLessOrEqualTo(col, val) {
|
|
283
|
-
this.add(col.key + '
|
|
288
|
+
this.add(col.key + '.lte', col.valueConverter.toJson(val));
|
|
284
289
|
}
|
|
285
290
|
isLessThan(col, val) {
|
|
286
|
-
this.add(col.key + '
|
|
291
|
+
this.add(col.key + '.lt', col.valueConverter.toJson(val));
|
|
287
292
|
}
|
|
288
293
|
containsCaseInsensitive(col, val) {
|
|
289
|
-
this.add(col.key + "
|
|
294
|
+
this.add(col.key + ".contains", val);
|
|
290
295
|
}
|
|
291
296
|
}
|
|
292
297
|
export function entityFilterToJson(entityDefs, where) {
|
|
@@ -337,13 +342,13 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
|
|
|
337
342
|
}
|
|
338
343
|
let c = new filterHelper(col);
|
|
339
344
|
addFilter('', val => val);
|
|
340
|
-
addFilter('
|
|
341
|
-
addFilter('
|
|
342
|
-
addFilter('
|
|
343
|
-
addFilter('
|
|
344
|
-
addFilter('
|
|
345
|
-
addFilter('
|
|
346
|
-
var nullFilter = filterInfo.get(col.key + "
|
|
345
|
+
addFilter('.gt', val => ({ "$gt": val }));
|
|
346
|
+
addFilter('.gte', val => ({ "$gte": val }));
|
|
347
|
+
addFilter('.lt', val => ({ "$lt": val }));
|
|
348
|
+
addFilter('.lte', val => ({ "$lte": val }));
|
|
349
|
+
addFilter('.ne', val => ({ "$ne": val }));
|
|
350
|
+
addFilter('.in', val => val, true);
|
|
351
|
+
var nullFilter = filterInfo.get(col.key + ".null");
|
|
347
352
|
if (nullFilter) {
|
|
348
353
|
nullFilter = nullFilter.toString().trim().toLowerCase();
|
|
349
354
|
switch (nullFilter) {
|
|
@@ -357,7 +362,7 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
|
|
|
357
362
|
break;
|
|
358
363
|
}
|
|
359
364
|
}
|
|
360
|
-
addFilter('
|
|
365
|
+
addFilter('.contains', val => ({ $contains: val }), false, true);
|
|
361
366
|
});
|
|
362
367
|
let val = filterInfo.get('OR');
|
|
363
368
|
if (val) {
|
|
@@ -370,7 +375,7 @@ export function buildFilterFromRequestParameters(entity, filterInfo) {
|
|
|
370
375
|
}
|
|
371
376
|
for (const key in entity.entityType) {
|
|
372
377
|
const element = entity.entityType[key];
|
|
373
|
-
if (element && element.
|
|
378
|
+
if (element && element.rawFilterInfo && element.rawFilterInfo.rawFilterTranslator) {
|
|
374
379
|
let custom = filterInfo.get(customUrlToken + key);
|
|
375
380
|
if (custom !== undefined) {
|
|
376
381
|
where.push({ [customUrlToken + key]: custom });
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { LiveQueryProvider, PubSubClient } from './LiveQuerySubscriber';
|
|
3
|
-
export declare class LiveQueryClient {
|
|
4
|
-
lqp: LiveQueryProvider;
|
|
5
|
-
private provider?;
|
|
6
|
-
wrapMessageHandling: (handleMessage: any) => any;
|
|
7
|
-
private queries;
|
|
8
|
-
private channels;
|
|
9
|
-
constructor(lqp: LiveQueryProvider, provider?: RestDataProviderHttpProvider);
|
|
10
|
-
runPromise(p: Promise<any>): Promise<any>;
|
|
11
|
-
close(): void;
|
|
12
|
-
subscribeChannel<T>(key: string, onResult: (item: T) => void): () => void;
|
|
13
|
-
timeoutToCloseWhenNotClosed: number;
|
|
14
|
-
private closeIfNoListeners;
|
|
15
|
-
subscribe<entityType>(repo: Repository<entityType>, options: FindOptions<entityType>, onResult: (reducer: (prevState: entityType[]) => entityType[]) => void): () => void;
|
|
16
|
-
client: Promise<PubSubClient>;
|
|
17
|
-
interval: any;
|
|
18
|
-
private openIfNoOpened;
|
|
19
|
-
}
|
|
1
|
+
export {};
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RestEntityDataProvider } from '../data-providers/rest-data-provider';
|
|
1
|
+
import { RestDataProvider } from '../data-providers/rest-data-provider';
|
|
3
2
|
import { buildRestDataProvider } from "../buildRestDataProvider";
|
|
4
|
-
import { LiveQuerySubscriber,
|
|
3
|
+
import { LiveQuerySubscriber, liveQueryKeepAliveRoute } from './SubscriptionChannel';
|
|
4
|
+
/* @internal*/
|
|
5
5
|
export class LiveQueryClient {
|
|
6
|
-
constructor(
|
|
7
|
-
this.
|
|
8
|
-
this.provider = provider;
|
|
9
|
-
this.wrapMessageHandling = handleMessage => handleMessage();
|
|
6
|
+
constructor(apiProvider) {
|
|
7
|
+
this.apiProvider = apiProvider;
|
|
10
8
|
this.queries = new Map();
|
|
11
9
|
this.channels = new Map();
|
|
12
10
|
this.timeoutToCloseWhenNotClosed = 1000;
|
|
13
11
|
}
|
|
12
|
+
wrapMessageHandling(handleMessage) {
|
|
13
|
+
var x = this.apiProvider().wrapMessageHandling;
|
|
14
|
+
if (x)
|
|
15
|
+
x(handleMessage);
|
|
16
|
+
else
|
|
17
|
+
handleMessage();
|
|
18
|
+
}
|
|
19
|
+
;
|
|
14
20
|
runPromise(p) {
|
|
15
21
|
return p;
|
|
16
22
|
}
|
|
@@ -25,19 +31,31 @@ export class LiveQueryClient {
|
|
|
25
31
|
let q = this.channels.get(key);
|
|
26
32
|
if (!q) {
|
|
27
33
|
this.channels.set(key, q = new MessageChannel());
|
|
28
|
-
this.client.then(c =>
|
|
34
|
+
this.client.then(c => {
|
|
35
|
+
try {
|
|
36
|
+
q.unsubscribe = c.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value)), err => {
|
|
37
|
+
onResult.error(err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
onResult.error(err);
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
29
45
|
}
|
|
30
46
|
q.listeners.push(onResult);
|
|
31
47
|
onUnsubscribe = () => {
|
|
32
48
|
q.listeners.splice(q.listeners.indexOf(onResult), 1);
|
|
33
49
|
if (q.listeners.length == 0) {
|
|
34
50
|
this.channels.delete(key);
|
|
51
|
+
q.unsubscribe();
|
|
35
52
|
}
|
|
36
53
|
this.closeIfNoListeners();
|
|
37
54
|
};
|
|
38
55
|
});
|
|
39
56
|
return () => {
|
|
40
57
|
onUnsubscribe();
|
|
58
|
+
onUnsubscribe = () => { };
|
|
41
59
|
};
|
|
42
60
|
}
|
|
43
61
|
closeIfNoListeners() {
|
|
@@ -45,7 +63,7 @@ export class LiveQueryClient {
|
|
|
45
63
|
setTimeout(() => {
|
|
46
64
|
if (this.client)
|
|
47
65
|
if (this.queries.size === 0 && this.channels.size === 0) {
|
|
48
|
-
this.runPromise(this.client.then(x => x.
|
|
66
|
+
this.runPromise(this.client.then(x => x.close()));
|
|
49
67
|
this.client = undefined;
|
|
50
68
|
clearInterval(this.interval);
|
|
51
69
|
this.interval = undefined;
|
|
@@ -55,89 +73,91 @@ export class LiveQueryClient {
|
|
|
55
73
|
}));
|
|
56
74
|
}
|
|
57
75
|
//TODO 1 - consider the time that may pass from the get request to the subscribe to the channel, in some cases this could mean, a call to server to get token and a call to the external provider - it may be some time
|
|
58
|
-
subscribe(repo, options,
|
|
59
|
-
let alive = true;
|
|
76
|
+
subscribe(repo, options, listener) {
|
|
60
77
|
let onUnsubscribe = () => { };
|
|
61
|
-
this.runPromise(
|
|
62
|
-
.then(opts =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
.buildFindRequest(opts);
|
|
67
|
-
const eventTypeKey = JSON.stringify({ url, filterObject });
|
|
78
|
+
this.runPromise(repo.buildEntityDataProviderFindOptions(options)
|
|
79
|
+
.then(opts => this.openIfNoOpened()
|
|
80
|
+
.then(() => {
|
|
81
|
+
const { createKey, subscribe } = new RestDataProvider(this.apiProvider).getEntityDataProvider(repo.metadata).buildFindRequest(opts);
|
|
82
|
+
const eventTypeKey = createKey();
|
|
68
83
|
let q = this.queries.get(eventTypeKey);
|
|
69
84
|
if (!q) {
|
|
70
85
|
this.queries.set(eventTypeKey, q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }));
|
|
71
|
-
url.add("__action", 'liveQuery');
|
|
72
86
|
q.subscribeCode = () => {
|
|
73
87
|
if (q.unsubscribe) {
|
|
74
88
|
q.unsubscribe();
|
|
75
|
-
//TODO - consider race scenario where unsubscribe is called before subscribe
|
|
89
|
+
//TODO 1- consider race scenario where unsubscribe is called before subscribe
|
|
76
90
|
q.unsubscribe = () => { };
|
|
77
91
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
this.runPromise(subscribe()
|
|
93
|
+
.then(r => {
|
|
94
|
+
if (q.listeners.length === 0) {
|
|
95
|
+
r.unsubscribe();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let unsubscribeToChannel = this.subscribeChannel(r.queryChannel, {
|
|
99
|
+
next: (value) => this.runPromise(q.handle(value)),
|
|
100
|
+
complete: () => { },
|
|
101
|
+
error: er => {
|
|
102
|
+
q.listeners.forEach(l => l.error(er));
|
|
103
|
+
}
|
|
89
104
|
});
|
|
105
|
+
q.unsubscribe = () => {
|
|
106
|
+
q.unsubscribe = () => { };
|
|
107
|
+
unsubscribeToChannel();
|
|
108
|
+
this.runPromise(r.unsubscribe());
|
|
109
|
+
};
|
|
90
110
|
this.runPromise(q.setAllItems(r.result));
|
|
91
|
-
q.
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
else
|
|
97
|
-
this.runPromise(this.provider.get(url.url).then(thenResult));
|
|
111
|
+
q.queryChannel = r.queryChannel;
|
|
112
|
+
})
|
|
113
|
+
.catch(err => {
|
|
114
|
+
listener.error(err);
|
|
115
|
+
}));
|
|
98
116
|
};
|
|
99
117
|
q.subscribeCode();
|
|
100
118
|
}
|
|
101
119
|
else {
|
|
102
|
-
|
|
120
|
+
q.sendDefaultState(listener.next);
|
|
103
121
|
}
|
|
104
|
-
q.listeners.push(
|
|
122
|
+
q.listeners.push(listener);
|
|
105
123
|
onUnsubscribe = () => {
|
|
106
|
-
q.listeners.splice(q.listeners.indexOf(
|
|
124
|
+
q.listeners.splice(q.listeners.indexOf(listener), 1);
|
|
125
|
+
listener.complete();
|
|
107
126
|
if (q.listeners.length == 0) {
|
|
108
127
|
this.queries.delete(eventTypeKey);
|
|
109
128
|
q.unsubscribe();
|
|
110
129
|
}
|
|
111
130
|
this.closeIfNoListeners();
|
|
112
131
|
};
|
|
113
|
-
}))
|
|
132
|
+
}))
|
|
133
|
+
.catch(err => {
|
|
134
|
+
listener.error(err);
|
|
135
|
+
}));
|
|
114
136
|
return () => {
|
|
115
|
-
alive = false;
|
|
116
137
|
onUnsubscribe();
|
|
117
138
|
};
|
|
118
139
|
}
|
|
119
140
|
openIfNoOpened() {
|
|
120
|
-
if (!this.provider) {
|
|
121
|
-
this.provider = buildRestDataProvider(defaultRemult.apiClient.httpClient);
|
|
122
|
-
}
|
|
123
141
|
if (!this.client) {
|
|
124
142
|
this.interval = setInterval(async () => {
|
|
125
143
|
const ids = [];
|
|
126
144
|
for (const q of this.queries.values()) {
|
|
127
|
-
ids.push(q.
|
|
145
|
+
ids.push(q.queryChannel);
|
|
128
146
|
}
|
|
129
147
|
if (ids.length > 0) {
|
|
130
|
-
|
|
148
|
+
let p = this.apiProvider();
|
|
149
|
+
let { actionInfo } = await import('../server-action');
|
|
150
|
+
const invalidIds = await this.runPromise(await actionInfo.runActionWithoutBlockingUI(() => buildRestDataProvider(p.httpClient).post(p.url + '/' + liveQueryKeepAliveRoute, ids)));
|
|
131
151
|
for (const id of invalidIds) {
|
|
132
152
|
for (const q of this.queries.values()) {
|
|
133
|
-
if (q.
|
|
153
|
+
if (q.queryChannel === id)
|
|
134
154
|
q.subscribeCode();
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
157
|
}
|
|
138
158
|
}, 30000);
|
|
139
159
|
return this.runPromise(this.client =
|
|
140
|
-
this.
|
|
160
|
+
this.apiProvider().subscriptionClient.openConnection(() => {
|
|
141
161
|
for (const q of this.queries.values()) {
|
|
142
162
|
q.subscribeCode();
|
|
143
163
|
}
|
|
@@ -146,3 +166,14 @@ export class LiveQueryClient {
|
|
|
146
166
|
return this.client;
|
|
147
167
|
}
|
|
148
168
|
}
|
|
169
|
+
class MessageChannel {
|
|
170
|
+
constructor() {
|
|
171
|
+
this.unsubscribe = () => { };
|
|
172
|
+
this.listeners = [];
|
|
173
|
+
}
|
|
174
|
+
async handle(message) {
|
|
175
|
+
for (const l of this.listeners) {
|
|
176
|
+
l.next(message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { buildRestDataProvider } from "../buildRestDataProvider";
|
|
2
2
|
import { remult } from "../remult-proxy";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { actionInfo } from "../server-action";
|
|
4
|
+
import { streamUrl } from "./SubscriptionChannel";
|
|
5
|
+
export class SseSubscriptionClient {
|
|
6
|
+
openConnection(onReconnect) {
|
|
6
7
|
let connectionId;
|
|
7
8
|
const channels = new Map();
|
|
8
9
|
const provider = buildRestDataProvider(remult.apiClient.httpClient);
|
|
9
10
|
let connected = false;
|
|
10
11
|
let source;
|
|
11
12
|
const client = {
|
|
12
|
-
|
|
13
|
+
close() {
|
|
13
14
|
source.close();
|
|
14
15
|
},
|
|
15
16
|
subscribe(channel, handler) {
|
|
@@ -22,10 +23,10 @@ export class EventSourceLiveQueryProvider {
|
|
|
22
23
|
return () => {
|
|
23
24
|
listeners.splice(listeners.indexOf(handler, 1));
|
|
24
25
|
if (listeners.length == 0) {
|
|
25
|
-
provider.post(remult.apiClient.url + '/' + streamUrl + '/unsubscribe', {
|
|
26
|
+
actionInfo.runActionWithoutBlockingUI(() => provider.post(remult.apiClient.url + '/' + streamUrl + '/unsubscribe', {
|
|
26
27
|
channel: channel,
|
|
27
28
|
clientId: connectionId
|
|
28
|
-
});
|
|
29
|
+
}));
|
|
29
30
|
channels.delete(channel);
|
|
30
31
|
}
|
|
31
32
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Remult } from '../../index';
|
|
2
|
+
export declare const streamUrl = "stream";
|
|
3
|
+
export interface SubscriptionListener<type> {
|
|
4
|
+
next(message: type): void;
|
|
5
|
+
error(err: any): void;
|
|
6
|
+
complete(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare type Unsubscribe = VoidFunction;
|
|
9
|
+
export interface SubscriptionClientConnection {
|
|
10
|
+
subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Unsubscribe;
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface SubscriptionClient {
|
|
14
|
+
openConnection(onReconnect: VoidFunction): Promise<SubscriptionClientConnection>;
|
|
15
|
+
}
|
|
16
|
+
export declare const liveQueryKeepAliveRoute = "_liveQueryKeepAlive";
|
|
17
|
+
export declare type LiveQueryChange = {
|
|
18
|
+
type: "all";
|
|
19
|
+
data: any[];
|
|
20
|
+
} | {
|
|
21
|
+
type: "add";
|
|
22
|
+
data: any;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'replace';
|
|
25
|
+
data: {
|
|
26
|
+
oldId: any;
|
|
27
|
+
item: any;
|
|
28
|
+
};
|
|
29
|
+
} | {
|
|
30
|
+
type: "remove";
|
|
31
|
+
data: {
|
|
32
|
+
id: any;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export declare class SubscriptionChannel<messageType> {
|
|
36
|
+
channelKey: string;
|
|
37
|
+
constructor(channelKey: string);
|
|
38
|
+
publish(message: messageType, remult?: Remult): void;
|
|
39
|
+
subscribe(next: (message: messageType) => void, remult?: Remult): any;
|
|
40
|
+
subscribe(listener: Partial<SubscriptionListener<messageType>>): any;
|
|
41
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { remult as defaultRemult, Sort } from '../../index';
|
|
2
2
|
import { getId } from '../remult3/getId';
|
|
3
3
|
export const streamUrl = 'stream';
|
|
4
|
+
//@internal
|
|
4
5
|
export class LiveQuerySubscriber {
|
|
5
6
|
constructor(repo, query) {
|
|
6
7
|
this.repo = repo;
|
|
@@ -9,20 +10,40 @@ export class LiveQuerySubscriber {
|
|
|
9
10
|
this.defaultQueryState = [];
|
|
10
11
|
this.listeners = [];
|
|
11
12
|
}
|
|
13
|
+
sendDefaultState(onResult) {
|
|
14
|
+
onResult(this.createReducerType(() => [...this.defaultQueryState], this.allItemsMessage(this.defaultQueryState)));
|
|
15
|
+
}
|
|
12
16
|
async setAllItems(result) {
|
|
13
17
|
const items = await Promise.all(result.map(item => this.repo.fromJson(item)));
|
|
14
18
|
this.forListeners(listener => {
|
|
15
19
|
listener(x => {
|
|
16
20
|
return items;
|
|
17
21
|
});
|
|
18
|
-
});
|
|
22
|
+
}, this.allItemsMessage(items));
|
|
23
|
+
}
|
|
24
|
+
allItemsMessage(items) {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
type: "all",
|
|
28
|
+
data: items
|
|
29
|
+
}
|
|
30
|
+
];
|
|
19
31
|
}
|
|
20
|
-
forListeners(what) {
|
|
32
|
+
forListeners(what, changes) {
|
|
21
33
|
what(reducer => this.defaultQueryState = reducer(this.defaultQueryState));
|
|
22
34
|
for (const l of this.listeners) {
|
|
23
|
-
what(
|
|
35
|
+
what(reducer => {
|
|
36
|
+
l.next(this.createReducerType(reducer, changes));
|
|
37
|
+
});
|
|
24
38
|
}
|
|
25
39
|
}
|
|
40
|
+
createReducerType(applyChanges, changes) {
|
|
41
|
+
return {
|
|
42
|
+
applyChanges,
|
|
43
|
+
changes,
|
|
44
|
+
items: this.defaultQueryState
|
|
45
|
+
};
|
|
46
|
+
}
|
|
26
47
|
async handle(messages) {
|
|
27
48
|
for (const m of messages) {
|
|
28
49
|
switch (m.type) {
|
|
@@ -68,33 +89,30 @@ export class LiveQuerySubscriber {
|
|
|
68
89
|
}
|
|
69
90
|
return items;
|
|
70
91
|
});
|
|
71
|
-
});
|
|
92
|
+
}, messages);
|
|
72
93
|
}
|
|
73
94
|
}
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
this.unsubscribe = () => { };
|
|
77
|
-
this.listeners = [];
|
|
78
|
-
}
|
|
79
|
-
async handle(message) {
|
|
80
|
-
for (const l of this.listeners) {
|
|
81
|
-
l(message);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export const liveQueryKeepAliveRoute = '/_liveQueryKeepAlive';
|
|
86
|
-
//TODO Yoni - keep in remult?
|
|
87
|
-
export class AMessageChannel {
|
|
95
|
+
export const liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
|
|
96
|
+
export class SubscriptionChannel {
|
|
88
97
|
constructor(channelKey) {
|
|
89
98
|
this.channelKey = channelKey;
|
|
90
99
|
}
|
|
91
|
-
|
|
100
|
+
publish(message, remult) {
|
|
92
101
|
remult = remult || defaultRemult;
|
|
93
|
-
remult.
|
|
102
|
+
remult.subscriptionServer.publishMessage(this.channelKey, message);
|
|
94
103
|
}
|
|
95
|
-
|
|
104
|
+
//@internal
|
|
105
|
+
subscribe(next, remult) {
|
|
96
106
|
remult = remult || defaultRemult;
|
|
97
|
-
|
|
107
|
+
let listener = next;
|
|
108
|
+
if (typeof (next) === "function") {
|
|
109
|
+
listener = {
|
|
110
|
+
next
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
listener.error ??= () => { };
|
|
114
|
+
listener.complete ??= () => { };
|
|
115
|
+
return remult.liveQuerySubscriber.subscribeChannel(this.channelKey, listener);
|
|
98
116
|
}
|
|
99
117
|
}
|
|
100
118
|
//TODO2 - consider moving the queued job mechanism into this.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface SubscriptionServer {
|
|
2
|
+
publishMessage<T>(channel: string, message: T): void;
|
|
3
|
+
}
|
|
4
|
+
export interface LiveQueryStorage {
|
|
5
|
+
add(query: StoredQuery): void;
|
|
6
|
+
remove(queryId: any): void;
|
|
7
|
+
forEach(entityKey: string, callback: (args: {
|
|
8
|
+
query: StoredQuery;
|
|
9
|
+
setData(data: any): Promise<void>;
|
|
10
|
+
}) => Promise<void>): Promise<void>;
|
|
11
|
+
keepAliveAndReturnUnknownQueryIds(queryIds: string[]): Promise<string[]>;
|
|
12
|
+
}
|
|
13
|
+
export declare class InMemoryLiveQueryStorage implements LiveQueryStorage {
|
|
14
|
+
debugFileSaver: (x: any) => void;
|
|
15
|
+
debug(): void;
|
|
16
|
+
keepAliveAndReturnUnknownQueryIds(ids: string[]): Promise<string[]>;
|
|
17
|
+
queries: (StoredQuery & {
|
|
18
|
+
lastUsed: string;
|
|
19
|
+
})[];
|
|
20
|
+
constructor();
|
|
21
|
+
add(query: StoredQuery): void;
|
|
22
|
+
removeCountForTesting: number;
|
|
23
|
+
remove(id: any): void;
|
|
24
|
+
forEach(entityKey: string, handle: (args: {
|
|
25
|
+
query: StoredQuery;
|
|
26
|
+
setData(data: any): Promise<void>;
|
|
27
|
+
}) => Promise<void>): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface StoredQuery {
|
|
30
|
+
entityKey: string;
|
|
31
|
+
id: string;
|
|
32
|
+
data: any;
|
|
33
|
+
}
|
|
34
|
+
export interface QueryData {
|
|
35
|
+
findOptionsJson: any;
|
|
36
|
+
requestJson: any;
|
|
37
|
+
lastIds: any[];
|
|
38
|
+
}
|