remult 0.18.1-exp.25 → 0.18.1-exp.26
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/esm/graphql.js +81 -42
- package/esm/live-query/ably.js +6 -3
- package/esm/src/context.js +1 -1
- package/esm/src/data-api.d.ts +2 -1
- package/esm/src/data-api.js +11 -13
- package/esm/src/data-providers/rest-data-provider.d.ts +1 -0
- package/esm/src/data-providers/rest-data-provider.js +5 -4
- package/esm/src/live-query/LiveQueryClient.js +60 -55
- package/esm/src/live-query/SseSubscriptionClient.js +4 -4
- package/esm/src/live-query/SubscriptionChannel.d.ts +1 -1
- package/esm/src/live-query/SubscriptionChannel.js +14 -11
- package/esm/src/server-action.d.ts +1 -1
- package/graphql.js +164 -97
- package/live-query/ably.js +17 -5
- package/package.json +1 -1
- package/src/context.js +1 -1
- package/src/data-api.d.ts +2 -1
- package/src/data-api.js +21 -21
- package/src/data-providers/rest-data-provider.d.ts +1 -0
- package/src/data-providers/rest-data-provider.js +10 -6
- package/src/live-query/LiveQueryClient.js +94 -73
- package/src/live-query/SseSubscriptionClient.js +35 -20
- package/src/live-query/SubscriptionChannel.d.ts +1 -1
- package/src/live-query/SubscriptionChannel.js +14 -11
- package/src/server-action.d.ts +1 -1
- package/esm/live-query/DataProviderLiveQueryStorage.d.ts +0 -21
- package/esm/live-query/DataProviderLiveQueryStorage.js +0 -71
- package/live-query/DataProviderLiveQueryStorage.d.ts +0 -21
- package/live-query/DataProviderLiveQueryStorage.js +0 -184
package/esm/graphql.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { Remult } from ".";
|
|
2
2
|
import { allEntities } from "./src/context";
|
|
3
|
-
import { getEntityKey } from "./src/remult3";
|
|
4
3
|
import { DataApi } from "./src/data-api";
|
|
5
4
|
export function remultGraphql(api) {
|
|
6
5
|
let r = new Remult();
|
|
7
|
-
let types =
|
|
6
|
+
let types = [];
|
|
7
|
+
let filterTypes = '';
|
|
8
8
|
let query = '';
|
|
9
9
|
let root = {};
|
|
10
|
+
function getType(key) {
|
|
11
|
+
let t = types.find(t => t.key === key);
|
|
12
|
+
if (!t)
|
|
13
|
+
types.push((t = ({ fields: "", key, moreFields: "", resultProcessors: [] })));
|
|
14
|
+
return t;
|
|
15
|
+
}
|
|
10
16
|
for (const e of allEntities) {
|
|
11
17
|
let meta = r.repo(e).metadata;
|
|
12
|
-
let fields = '';
|
|
13
18
|
let filterFields = '';
|
|
14
|
-
let key =
|
|
19
|
+
let key = meta.key;
|
|
20
|
+
const t = getType(key);
|
|
21
|
+
let q = "\n\t" + key + "(options: options, filter:" + key + "Filter): [" + key + "]";
|
|
15
22
|
if (key) {
|
|
16
23
|
for (const f of meta.fields) {
|
|
17
24
|
{
|
|
@@ -19,6 +26,7 @@ export function remultGraphql(api) {
|
|
|
19
26
|
switch (f.valueType) {
|
|
20
27
|
case Boolean:
|
|
21
28
|
type = "Boolean";
|
|
29
|
+
break;
|
|
22
30
|
case Number:
|
|
23
31
|
{
|
|
24
32
|
if (f.valueConverter?.fieldTypeInDb == 'integer')
|
|
@@ -26,8 +34,31 @@ export function remultGraphql(api) {
|
|
|
26
34
|
else
|
|
27
35
|
type = "Float";
|
|
28
36
|
}
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (allEntities.includes(f.valueType)) {
|
|
40
|
+
const refKey = r.repo(f.valueType).metadata.key;
|
|
41
|
+
t.fields += "\n\t" + f.key + ":" + refKey;
|
|
42
|
+
t.resultProcessors.push(r => {
|
|
43
|
+
const val = r[f.key];
|
|
44
|
+
r[f.key] = async (args, req, gqlInfo) => {
|
|
45
|
+
const queryResult = await root[refKey]({ ...args.filter, filter: { id: val }, options: { limit: 1 } }, req, gqlInfo);
|
|
46
|
+
if (queryResult.length > 0)
|
|
47
|
+
return queryResult[0];
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
let refT = getType(refKey);
|
|
52
|
+
refT.moreFields += q;
|
|
53
|
+
refT.resultProcessors.push(r => {
|
|
54
|
+
const val = r.id;
|
|
55
|
+
r[key] = async (args, req, gqlInfo) => {
|
|
56
|
+
return await root[key]({ filter: { ...args.filter, [f.key]: val }, options: args.options }, req, gqlInfo);
|
|
57
|
+
};
|
|
58
|
+
});
|
|
29
59
|
}
|
|
30
|
-
|
|
60
|
+
else
|
|
61
|
+
t.fields += "\n\t" + f.key + ":" + type;
|
|
31
62
|
for (const operator of ["", "_ne"]) {
|
|
32
63
|
filterFields += "\n\t" + f.key + operator + ":" + type;
|
|
33
64
|
}
|
|
@@ -44,41 +75,49 @@ export function remultGraphql(api) {
|
|
|
44
75
|
filterFields += "\n\t" + f.key + "_in:[" + type + "]";
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
filterTypes += "input " + key + "Filter{" + filterFields + "\n\tOR:[" + key + "Filter]\n}\n";
|
|
79
|
+
query += q;
|
|
80
|
+
root[key] = async ({ options, filter }, req, a) => {
|
|
81
|
+
return new Promise(async (res, error) => {
|
|
82
|
+
api.withRemult(req, undefined, async () => {
|
|
83
|
+
let remult = await api.getRemult(req);
|
|
84
|
+
let repo = remult.repo(e);
|
|
85
|
+
let dApi = new DataApi(repo, remult);
|
|
86
|
+
let result;
|
|
87
|
+
let err;
|
|
88
|
+
await dApi.getArray({
|
|
89
|
+
success: x => result = x.map(y => {
|
|
90
|
+
t.resultProcessors.forEach(z => z(y));
|
|
91
|
+
return y;
|
|
92
|
+
}),
|
|
93
|
+
created: undefined,
|
|
94
|
+
deleted: undefined,
|
|
95
|
+
error: x => err = x,
|
|
96
|
+
forbidden: () => err = 'forbidden',
|
|
97
|
+
notFound: () => err = 'not found',
|
|
98
|
+
progress: undefined
|
|
99
|
+
}, {
|
|
100
|
+
get: key => {
|
|
101
|
+
if (options)
|
|
102
|
+
switch (key) {
|
|
103
|
+
case "_limit":
|
|
104
|
+
return options.limit;
|
|
105
|
+
case "_page":
|
|
106
|
+
return options.page;
|
|
107
|
+
case "_sort":
|
|
108
|
+
return options.sort;
|
|
109
|
+
case "_order":
|
|
110
|
+
return options.order;
|
|
111
|
+
}
|
|
76
112
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
}, filter);
|
|
114
|
+
if (err) {
|
|
115
|
+
error(err);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
res(result);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
82
121
|
};
|
|
83
122
|
}
|
|
84
123
|
}
|
|
@@ -88,15 +127,15 @@ export function remultGraphql(api) {
|
|
|
88
127
|
}
|
|
89
128
|
return {
|
|
90
129
|
rootValue: root,
|
|
91
|
-
schema:
|
|
130
|
+
schema: `${types.map(({ key, fields, moreFields }) => "type " + key + "{" + fields + moreFields + "\n}\n").join('')}
|
|
131
|
+
${query}
|
|
132
|
+
${filterTypes}
|
|
92
133
|
input options{
|
|
93
134
|
limit:Int
|
|
94
135
|
page:Int
|
|
95
136
|
sort:String
|
|
96
137
|
order:String
|
|
97
138
|
}
|
|
98
|
-
${types}
|
|
99
|
-
${query}
|
|
100
139
|
`
|
|
101
140
|
};
|
|
102
141
|
}
|
package/esm/live-query/ably.js
CHANGED
|
@@ -7,10 +7,13 @@ export class AblySubscriptionClient {
|
|
|
7
7
|
close: () => {
|
|
8
8
|
this.ably.connection.close();
|
|
9
9
|
},
|
|
10
|
-
subscribe: (channel, handler) => {
|
|
10
|
+
subscribe: async (channel, handler) => {
|
|
11
11
|
let myHandler = (y) => handler(y.data);
|
|
12
|
-
this.ably.channels.get(channel).subscribe(y => myHandler(y));
|
|
13
|
-
return () => {
|
|
12
|
+
await this.ably.channels.get(channel).subscribe(y => myHandler(y));
|
|
13
|
+
return () => {
|
|
14
|
+
myHandler = () => { };
|
|
15
|
+
this.ably.channels.get(channel).unsubscribe();
|
|
16
|
+
};
|
|
14
17
|
}
|
|
15
18
|
};
|
|
16
19
|
}
|
package/esm/src/context.js
CHANGED
|
@@ -19,7 +19,7 @@ export class Remult {
|
|
|
19
19
|
itemChanged: () => { }
|
|
20
20
|
};
|
|
21
21
|
/* @internal*/
|
|
22
|
-
this.liveQuerySubscriber = new LiveQueryClient(() => this.apiClient);
|
|
22
|
+
this.liveQuerySubscriber = new LiveQueryClient(() => this.apiClient, () => this.user?.id);
|
|
23
23
|
/** context information that can be used to store custom information that will be disposed as part of the `remult` object */
|
|
24
24
|
this.context = {};
|
|
25
25
|
/** The api client that will be used by `remult` to perform calls to the `api` */
|
package/esm/src/data-api.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class DataApi<T = any> {
|
|
|
15
15
|
findOptions: FindOptions<T>;
|
|
16
16
|
}>;
|
|
17
17
|
getArray(response: DataApiResponse, request: DataApiRequest, filterBody?: any): Promise<void>;
|
|
18
|
-
liveQuery(response: DataApiResponse, request: DataApiRequest, filterBody: any, serializeRequest: () => any): Promise<void>;
|
|
18
|
+
liveQuery(response: DataApiResponse, request: DataApiRequest, filterBody: any, serializeRequest: () => any, queryChannel: string): Promise<void>;
|
|
19
19
|
private buildWhere;
|
|
20
20
|
private doOnId;
|
|
21
21
|
put(response: DataApiResponse, id: any, body: any): Promise<void>;
|
|
@@ -36,3 +36,4 @@ export interface DataApiRequest {
|
|
|
36
36
|
}
|
|
37
37
|
export declare function determineSort(sortUrlParm: string, dirUrlParam: string): any;
|
|
38
38
|
export declare function serializeError(data: ErrorInfo): ErrorInfo<any>;
|
|
39
|
+
export declare function getLiveQueryChannel(queryId: string, userId: string): string;
|
package/esm/src/data-api.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { v4 as uuid } from 'uuid';
|
|
2
1
|
import { customUrlToken, buildFilterFromRequestParameters } from './filter/filter-interfaces';
|
|
3
2
|
import { doTransaction } from './context';
|
|
4
3
|
import { Filter } from './filter/filter-interfaces';
|
|
5
4
|
import { ForbiddenError } from './server-action';
|
|
6
5
|
import { getId } from './remult3/getId';
|
|
7
|
-
import { findOptionsToJson } from './data-providers/rest-data-provider';
|
|
6
|
+
import { findOptionsToJson, liveQueryAction } from './data-providers/rest-data-provider';
|
|
8
7
|
export class DataApi {
|
|
9
8
|
constructor(repository, remult) {
|
|
10
9
|
this.repository = repository;
|
|
@@ -12,9 +11,9 @@ export class DataApi {
|
|
|
12
11
|
}
|
|
13
12
|
httpGet(res, req, serializeRequest) {
|
|
14
13
|
const action = req?.get("__action");
|
|
14
|
+
if (action?.startsWith(liveQueryAction))
|
|
15
|
+
return this.liveQuery(res, req, undefined, serializeRequest, getLiveQueryChannel(action.substring(liveQueryAction.length), this.remult.user?.id));
|
|
15
16
|
switch (action) {
|
|
16
|
-
case "liveQuery":
|
|
17
|
-
return this.liveQuery(res, req, undefined, serializeRequest);
|
|
18
17
|
case "get":
|
|
19
18
|
case "count":
|
|
20
19
|
return this.count(res, req, undefined);
|
|
@@ -23,15 +22,15 @@ export class DataApi {
|
|
|
23
22
|
}
|
|
24
23
|
httpPost(res, req, body, serializeRequest) {
|
|
25
24
|
const action = req?.get("__action");
|
|
25
|
+
if (action?.startsWith(liveQueryAction))
|
|
26
|
+
return this.liveQuery(res, req, undefined, serializeRequest, getLiveQueryChannel(action.substring(liveQueryAction.length), this.remult.user?.id));
|
|
26
27
|
switch (action) {
|
|
27
|
-
case "liveQuery":
|
|
28
|
-
return this.liveQuery(res, req, body, serializeRequest);
|
|
29
28
|
case "get":
|
|
30
29
|
return this.getArray(res, req, body);
|
|
31
30
|
case "count":
|
|
32
31
|
return this.count(res, req, body);
|
|
33
32
|
case "endLiveQuery":
|
|
34
|
-
this.remult.liveQueryStorage.remove(body.id);
|
|
33
|
+
this.remult.liveQueryStorage.remove(getLiveQueryChannel(body.id, this.remult.user?.id));
|
|
35
34
|
res.success("ok");
|
|
36
35
|
return;
|
|
37
36
|
default:
|
|
@@ -122,14 +121,13 @@ export class DataApi {
|
|
|
122
121
|
response.error(err);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
|
-
async liveQuery(response, request, filterBody, serializeRequest) {
|
|
124
|
+
async liveQuery(response, request, filterBody, serializeRequest, queryChannel) {
|
|
126
125
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
127
126
|
response.forbidden();
|
|
128
127
|
return;
|
|
129
128
|
}
|
|
130
129
|
try {
|
|
131
130
|
const r = await this.getArrayImpl(response, request, filterBody);
|
|
132
|
-
const queryChannel = `users:${this.remult.user?.id}:queries:${uuid()}`;
|
|
133
131
|
const data = {
|
|
134
132
|
requestJson: serializeRequest(),
|
|
135
133
|
findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
|
|
@@ -140,10 +138,7 @@ export class DataApi {
|
|
|
140
138
|
id: queryChannel,
|
|
141
139
|
data
|
|
142
140
|
});
|
|
143
|
-
response.success(
|
|
144
|
-
queryChannel,
|
|
145
|
-
result: r.r
|
|
146
|
-
});
|
|
141
|
+
response.success(r.r);
|
|
147
142
|
}
|
|
148
143
|
catch (err) {
|
|
149
144
|
if (err.isForbiddenError)
|
|
@@ -271,3 +266,6 @@ export function serializeError(data) {
|
|
|
271
266
|
data = { message: x };
|
|
272
267
|
return data;
|
|
273
268
|
}
|
|
269
|
+
export function getLiveQueryChannel(queryId, userId) {
|
|
270
|
+
return `users:${userId}:queries:${queryId}`;
|
|
271
|
+
}
|
|
@@ -117,13 +117,13 @@ export class RestEntityDataProvider {
|
|
|
117
117
|
return {
|
|
118
118
|
createKey: () => JSON.stringify({ url, filterObject }),
|
|
119
119
|
run,
|
|
120
|
-
subscribe: async () => {
|
|
121
|
-
const result = await run(
|
|
120
|
+
subscribe: async (queryId) => {
|
|
121
|
+
const result = await run(liveQueryAction + queryId);
|
|
122
122
|
return {
|
|
123
|
-
|
|
123
|
+
result,
|
|
124
124
|
unsubscribe: async () => {
|
|
125
125
|
return actionInfo.runActionWithoutBlockingUI(() => this.http().post(this.url() + "?__action=endLiveQuery", {
|
|
126
|
-
id:
|
|
126
|
+
id: queryId
|
|
127
127
|
}));
|
|
128
128
|
}
|
|
129
129
|
};
|
|
@@ -239,3 +239,4 @@ export function addFilterToUrlAndReturnTrueIfSuccessful(filter, url) {
|
|
|
239
239
|
}
|
|
240
240
|
return true;
|
|
241
241
|
}
|
|
242
|
+
export const liveQueryAction = "liveQuery|";
|
|
@@ -3,11 +3,11 @@ import { buildRestDataProvider } from "../buildRestDataProvider";
|
|
|
3
3
|
import { LiveQuerySubscriber, liveQueryKeepAliveRoute } from './SubscriptionChannel';
|
|
4
4
|
/* @internal*/
|
|
5
5
|
export class LiveQueryClient {
|
|
6
|
-
constructor(apiProvider) {
|
|
6
|
+
constructor(apiProvider, getUserId) {
|
|
7
7
|
this.apiProvider = apiProvider;
|
|
8
|
+
this.getUserId = getUserId;
|
|
8
9
|
this.queries = new Map();
|
|
9
10
|
this.channels = new Map();
|
|
10
|
-
this.timeoutToCloseWhenNotClosed = 1000;
|
|
11
11
|
}
|
|
12
12
|
wrapMessageHandling(handleMessage) {
|
|
13
13
|
var x = this.apiProvider().wrapMessageHandling;
|
|
@@ -25,23 +25,22 @@ export class LiveQueryClient {
|
|
|
25
25
|
this.channels.clear();
|
|
26
26
|
this.closeIfNoListeners();
|
|
27
27
|
}
|
|
28
|
-
subscribeChannel(key, onResult) {
|
|
28
|
+
async subscribeChannel(key, onResult) {
|
|
29
29
|
let onUnsubscribe = () => { };
|
|
30
|
-
this.openIfNoOpened()
|
|
30
|
+
const client = await this.openIfNoOpened();
|
|
31
|
+
try {
|
|
31
32
|
let q = this.channels.get(key);
|
|
32
33
|
if (!q) {
|
|
33
34
|
this.channels.set(key, q = new MessageChannel());
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
q.unsubscribe = c.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value)), err => {
|
|
37
|
-
onResult.error(err);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
35
|
+
try {
|
|
36
|
+
q.unsubscribe = await client.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value)), err => {
|
|
41
37
|
onResult.error(err);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
onResult.error(err);
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
45
44
|
}
|
|
46
45
|
q.listeners.push(onResult);
|
|
47
46
|
onUnsubscribe = () => {
|
|
@@ -52,67 +51,73 @@ export class LiveQueryClient {
|
|
|
52
51
|
}
|
|
53
52
|
this.closeIfNoListeners();
|
|
54
53
|
};
|
|
55
|
-
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
onResult.error(err);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
56
59
|
return () => {
|
|
57
60
|
onUnsubscribe();
|
|
58
61
|
onUnsubscribe = () => { };
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
closeIfNoListeners() {
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.interval = undefined;
|
|
70
|
-
}
|
|
71
|
-
res({});
|
|
72
|
-
}, this.timeoutToCloseWhenNotClosed);
|
|
73
|
-
}));
|
|
65
|
+
if (this.client)
|
|
66
|
+
if (this.queries.size === 0 && this.channels.size === 0) {
|
|
67
|
+
this.runPromise(this.client.then(x => x.close()));
|
|
68
|
+
this.client = undefined;
|
|
69
|
+
clearInterval(this.interval);
|
|
70
|
+
this.interval = undefined;
|
|
71
|
+
}
|
|
74
72
|
}
|
|
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
|
|
76
73
|
subscribe(repo, options, listener) {
|
|
77
|
-
let
|
|
74
|
+
let alive = true;
|
|
75
|
+
let onUnsubscribe = () => { alive = false; };
|
|
78
76
|
this.runPromise(repo.buildEntityDataProviderFindOptions(options)
|
|
79
|
-
.then(opts =>
|
|
80
|
-
|
|
77
|
+
.then(opts => {
|
|
78
|
+
if (!alive)
|
|
79
|
+
return;
|
|
81
80
|
const { createKey, subscribe } = new RestDataProvider(this.apiProvider).getEntityDataProvider(repo.metadata).buildFindRequest(opts);
|
|
82
81
|
const eventTypeKey = createKey();
|
|
83
82
|
let q = this.queries.get(eventTypeKey);
|
|
84
83
|
if (!q) {
|
|
85
|
-
this.queries.set(eventTypeKey, q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }));
|
|
84
|
+
this.queries.set(eventTypeKey, q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }, this.getUserId()));
|
|
86
85
|
q.subscribeCode = () => {
|
|
87
86
|
if (q.unsubscribe) {
|
|
88
87
|
q.unsubscribe();
|
|
89
|
-
//TODO 1- consider race scenario where unsubscribe is called before subscribe
|
|
90
88
|
q.unsubscribe = () => { };
|
|
91
89
|
}
|
|
92
|
-
this.runPromise(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
this.runPromise(this.subscribeChannel(q.queryChannel, {
|
|
91
|
+
next: (value) => this.runPromise(q.handle(value)),
|
|
92
|
+
complete: () => { },
|
|
93
|
+
error: er => {
|
|
94
|
+
q.listeners.forEach(l => l.error(er));
|
|
95
|
+
}
|
|
96
|
+
}).then(unsubscribeToChannel => {
|
|
97
|
+
if (q.listeners.length == 0) {
|
|
98
|
+
unsubscribeToChannel();
|
|
96
99
|
return;
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
this.runPromise(subscribe(q.id)
|
|
102
|
+
.then(r => {
|
|
103
|
+
if (q.listeners.length === 0) {
|
|
104
|
+
r.unsubscribe();
|
|
105
|
+
unsubscribeToChannel();
|
|
106
|
+
return;
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
this.runPromise(q.setAllItems(r.result));
|
|
109
|
+
q.unsubscribe = () => {
|
|
110
|
+
q.unsubscribe = () => { };
|
|
111
|
+
unsubscribeToChannel();
|
|
112
|
+
this.runPromise(r.unsubscribe());
|
|
113
|
+
};
|
|
114
|
+
})
|
|
115
|
+
.catch(err => {
|
|
116
|
+
listener.error(err);
|
|
117
|
+
}));
|
|
118
|
+
})).catch(err => {
|
|
119
|
+
q.listeners.forEach(l => l.error(err));
|
|
120
|
+
});
|
|
116
121
|
};
|
|
117
122
|
q.subscribeCode();
|
|
118
123
|
}
|
|
@@ -129,7 +134,7 @@ export class LiveQueryClient {
|
|
|
129
134
|
}
|
|
130
135
|
this.closeIfNoListeners();
|
|
131
136
|
};
|
|
132
|
-
})
|
|
137
|
+
})
|
|
133
138
|
.catch(err => {
|
|
134
139
|
listener.error(err);
|
|
135
140
|
}));
|
|
@@ -13,11 +13,11 @@ export class SseSubscriptionClient {
|
|
|
13
13
|
close() {
|
|
14
14
|
source.close();
|
|
15
15
|
},
|
|
16
|
-
subscribe(channel, handler) {
|
|
16
|
+
async subscribe(channel, handler) {
|
|
17
17
|
let listeners = channels.get(channel);
|
|
18
18
|
if (!listeners) {
|
|
19
19
|
channels.set(channel, listeners = []);
|
|
20
|
-
subscribeToChannel(channel);
|
|
20
|
+
await subscribeToChannel(channel);
|
|
21
21
|
}
|
|
22
22
|
listeners.push(handler);
|
|
23
23
|
return () => {
|
|
@@ -69,8 +69,8 @@ export class SseSubscriptionClient {
|
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
|
-
function subscribeToChannel(channel) {
|
|
73
|
-
actionInfo.runActionWithoutBlockingUI(() => provider.post(remult.apiClient.url + '/' + streamUrl + '/subscribe', {
|
|
72
|
+
async function subscribeToChannel(channel) {
|
|
73
|
+
return actionInfo.runActionWithoutBlockingUI(() => provider.post(remult.apiClient.url + '/' + streamUrl + '/subscribe', {
|
|
74
74
|
channel: channel,
|
|
75
75
|
clientId: connectionId
|
|
76
76
|
}));
|
|
@@ -7,7 +7,7 @@ export interface SubscriptionListener<type> {
|
|
|
7
7
|
}
|
|
8
8
|
export declare type Unsubscribe = VoidFunction;
|
|
9
9
|
export interface SubscriptionClientConnection {
|
|
10
|
-
subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Unsubscribe
|
|
10
|
+
subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Promise<Unsubscribe>;
|
|
11
11
|
close(): void;
|
|
12
12
|
}
|
|
13
13
|
export interface SubscriptionClient {
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { remult as defaultRemult, Sort } from '../../index';
|
|
2
2
|
import { getId } from '../remult3/getId';
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
import { getLiveQueryChannel } from '../data-api';
|
|
3
5
|
export const streamUrl = 'stream';
|
|
4
6
|
//@internal
|
|
5
7
|
export class LiveQuerySubscriber {
|
|
6
|
-
constructor(repo, query) {
|
|
8
|
+
constructor(repo, query, userId) {
|
|
7
9
|
this.repo = repo;
|
|
8
10
|
this.query = query;
|
|
9
11
|
this.unsubscribe = () => { };
|
|
10
12
|
this.defaultQueryState = [];
|
|
11
13
|
this.listeners = [];
|
|
14
|
+
this.id = uuid();
|
|
15
|
+
this.queryChannel = getLiveQueryChannel(this.id, userId);
|
|
12
16
|
}
|
|
13
17
|
sendDefaultState(onResult) {
|
|
14
18
|
onResult(this.createReducerType(() => [...this.defaultQueryState], this.allItemsMessage(this.defaultQueryState)));
|
|
@@ -30,7 +34,15 @@ export class LiveQuerySubscriber {
|
|
|
30
34
|
];
|
|
31
35
|
}
|
|
32
36
|
forListeners(what, changes) {
|
|
33
|
-
what(reducer =>
|
|
37
|
+
what(reducer => {
|
|
38
|
+
this.defaultQueryState = reducer(this.defaultQueryState);
|
|
39
|
+
if (changes.find(c => c.type === "add" || c.type === "replace")) {
|
|
40
|
+
if (this.query.orderBy) {
|
|
41
|
+
const o = Sort.translateOrderByToSort(this.repo.metadata, this.query.orderBy);
|
|
42
|
+
this.defaultQueryState.sort((a, b) => o.compare(a, b));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
34
46
|
for (const l of this.listeners) {
|
|
35
47
|
what(reducer => {
|
|
36
48
|
l.next(this.createReducerType(reducer, changes));
|
|
@@ -59,7 +71,6 @@ export class LiveQuerySubscriber {
|
|
|
59
71
|
listener(items => {
|
|
60
72
|
if (!items)
|
|
61
73
|
items = [];
|
|
62
|
-
let needSort = false;
|
|
63
74
|
for (const message of messages) {
|
|
64
75
|
switch (message.type) {
|
|
65
76
|
case "all":
|
|
@@ -67,13 +78,11 @@ export class LiveQuerySubscriber {
|
|
|
67
78
|
break;
|
|
68
79
|
case "replace": {
|
|
69
80
|
items = items.map(x => getId(this.repo.metadata, x) === message.data.oldId ? message.data.item : x);
|
|
70
|
-
needSort = true;
|
|
71
81
|
break;
|
|
72
82
|
}
|
|
73
83
|
case "add":
|
|
74
84
|
items = items.filter(x => getId(this.repo.metadata, x) !== getId(this.repo.metadata, message.data.item));
|
|
75
85
|
items.push(message.data.item);
|
|
76
|
-
needSort = true;
|
|
77
86
|
break;
|
|
78
87
|
case "remove":
|
|
79
88
|
items = items.filter(x => getId(this.repo.metadata, x) !== message.data.id);
|
|
@@ -81,12 +90,6 @@ export class LiveQuerySubscriber {
|
|
|
81
90
|
}
|
|
82
91
|
;
|
|
83
92
|
}
|
|
84
|
-
if (needSort) {
|
|
85
|
-
if (this.query.orderBy) {
|
|
86
|
-
const o = Sort.translateOrderByToSort(this.repo.metadata, this.query.orderBy);
|
|
87
|
-
items.sort((a, b) => o.compare(a, b));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
93
|
return items;
|
|
91
94
|
});
|
|
92
95
|
}, messages);
|
|
@@ -42,7 +42,7 @@ export interface BackendMethodOptions<type> {
|
|
|
42
42
|
export declare const actionInfo: {
|
|
43
43
|
allActions: any[];
|
|
44
44
|
runningOnServer: boolean;
|
|
45
|
-
runActionWithoutBlockingUI: (what: () => Promise<
|
|
45
|
+
runActionWithoutBlockingUI: <T>(what: () => Promise<T>) => Promise<T>;
|
|
46
46
|
startBusyWithProgress: () => {
|
|
47
47
|
progress: (percent: number) => void;
|
|
48
48
|
close: () => void;
|