remult 0.16.3 → 0.16.4

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 (49) hide show
  1. package/esm/remult-knex/index.js +9 -2
  2. package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +1 -0
  3. package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +10 -0
  4. package/esm/src/live-query/EventSourceLiveQuery.d.ts +5 -0
  5. package/esm/src/live-query/EventSourceLiveQuery.js +48 -0
  6. package/esm/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
  7. package/esm/src/live-query/EventSourceLiveQueryProvider.js +24 -0
  8. package/esm/src/live-query/LiveQuery.d.ts +67 -0
  9. package/esm/src/live-query/LiveQuery.js +222 -0
  10. package/esm/src/live-query/LiveQueryManager.d.ts +33 -0
  11. package/esm/src/live-query/LiveQueryManager.js +97 -0
  12. package/esm/src/shared-tests/db-tests-setup.d.ts +1 -1
  13. package/esm/src/shared-tests/db-tests-setup.js +1 -0
  14. package/esm/src/valueConverters.js +2 -0
  15. package/package.json +1 -1
  16. package/remult-knex/index.js +16 -8
  17. package/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +1 -0
  18. package/src/backend-tests/backend-database-test-setup.backend-spec.js +11 -1
  19. package/src/live-query/EventSourceLiveQuery.d.ts +5 -0
  20. package/src/live-query/EventSourceLiveQuery.js +62 -0
  21. package/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
  22. package/src/live-query/EventSourceLiveQueryProvider.js +32 -0
  23. package/src/live-query/LiveQuery.d.ts +67 -0
  24. package/src/live-query/LiveQuery.js +305 -0
  25. package/src/live-query/LiveQueryManager.d.ts +33 -0
  26. package/src/live-query/LiveQueryManager.js +196 -0
  27. package/src/shared-tests/db-tests-setup.d.ts +1 -1
  28. package/src/shared-tests/db-tests-setup.js +1 -0
  29. package/src/valueConverters.js +2 -0
  30. package/esm/postgres/postgresColumnSyntax.d.ts +0 -2
  31. package/esm/postgres/postgresColumnSyntax.js +0 -30
  32. package/esm/remult-middleware.d.ts +0 -2
  33. package/esm/remult-middleware.js +0 -65
  34. package/esm/server/register-actions-on-server.d.ts +0 -3
  35. package/esm/server/register-actions-on-server.js +0 -24
  36. package/esm/server/register-entities-on-server.d.ts +0 -3
  37. package/esm/server/register-entities-on-server.js +0 -15
  38. package/esm/valueConverters.d.ts +0 -12
  39. package/esm/valueConverters.js +0 -169
  40. package/postgres/postgresColumnSyntax.d.ts +0 -2
  41. package/postgres/postgresColumnSyntax.js +0 -34
  42. package/remult-middleware.d.ts +0 -2
  43. package/remult-middleware.js +0 -70
  44. package/server/register-actions-on-server.d.ts +0 -3
  45. package/server/register-actions-on-server.js +0 -50
  46. package/server/register-entities-on-server.d.ts +0 -3
  47. package/server/register-entities-on-server.js +0 -19
  48. package/valueConverters.d.ts +0 -12
  49. package/valueConverters.js +0 -168
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventSourceLiveQuery = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var LiveQuery_1 = require("./LiveQuery");
6
+ var fetch_event_source_1 = require("@microsoft/fetch-event-source");
7
+ var remult_proxy_1 = require("../remult-proxy");
8
+ var EventSourceLiveQuery = /** @class */ (function (_super) {
9
+ tslib_1.__extends(EventSourceLiveQuery, _super);
10
+ function EventSourceLiveQuery(wrapMessage) {
11
+ var _this = _super.call(this, new EventSourceLiveQueryProvider(wrapMessage)) || this;
12
+ _this.wrapMessage = wrapMessage;
13
+ return _this;
14
+ }
15
+ return EventSourceLiveQuery;
16
+ }(LiveQuery_1.LiveQueryClient));
17
+ exports.EventSourceLiveQuery = EventSourceLiveQuery;
18
+ var EventSourceLiveQueryProvider = /** @class */ (function () {
19
+ function EventSourceLiveQueryProvider(wrapMessage, url, jwtToken) {
20
+ if (url === void 0) { url = undefined; }
21
+ this.wrapMessage = wrapMessage;
22
+ this.url = url;
23
+ this.jwtToken = jwtToken;
24
+ this.lastId = 0;
25
+ if (!this.wrapMessage)
26
+ this.wrapMessage = function (x) { return x(); };
27
+ if (!this.url) {
28
+ this.url = remult_proxy_1.remult.apiClient.url + '/' + LiveQuery_1.streamUrl;
29
+ }
30
+ }
31
+ EventSourceLiveQueryProvider.prototype.openStreamAndReturnCloseFunction = function (clientId, onMessage) {
32
+ var _this = this;
33
+ var ctrl = new AbortController();
34
+ var headers = {
35
+ "client-id": clientId
36
+ };
37
+ if (this.jwtToken) {
38
+ headers["Authorization"] = "Bearer " + this.jwtToken;
39
+ }
40
+ fetch_event_source_1.fetchEventSource(this.url, {
41
+ headers: headers,
42
+ onmessage: function (message) {
43
+ var mid = +message.id;
44
+ if (mid <= _this.lastId && _this.lastId - mid < 10)
45
+ return;
46
+ _this.lastId = mid;
47
+ console.log(message.data);
48
+ if (message.event !== 'keep-alive') {
49
+ _this.wrapMessage(function () { return onMessage(message); });
50
+ }
51
+ },
52
+ onopen: function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
53
+ return tslib_1.__generator(this, function (_a) {
54
+ return [2 /*return*/];
55
+ });
56
+ }); },
57
+ signal: ctrl.signal,
58
+ });
59
+ return function () { return ctrl.abort(); };
60
+ };
61
+ return EventSourceLiveQueryProvider;
62
+ }());
@@ -0,0 +1,6 @@
1
+ import { LiveQueryProvider, MessageHandler } from "./LiveQuery";
2
+ export declare class EventSourceLiveQueryProvider implements LiveQueryProvider {
3
+ private wrapMessage?;
4
+ constructor(wrapMessage?: (what: () => void) => void);
5
+ openStreamAndReturnCloseFunction(clientId: string, onMessage: MessageHandler): Promise<VoidFunction>;
6
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventSourceLiveQueryProvider = void 0;
4
+ var remult_proxy_1 = require("../remult-proxy");
5
+ var LiveQuery_1 = require("./LiveQuery");
6
+ var EventSourceLiveQueryProvider = /** @class */ (function () {
7
+ function EventSourceLiveQueryProvider(wrapMessage) {
8
+ this.wrapMessage = wrapMessage;
9
+ if (!this.wrapMessage)
10
+ this.wrapMessage = function (x) { return x(); };
11
+ }
12
+ EventSourceLiveQueryProvider.prototype.openStreamAndReturnCloseFunction = function (clientId, onMessage) {
13
+ var _this = this;
14
+ var source = new EventSource(remult_proxy_1.remult.apiClient.url + '/' + LiveQuery_1.streamUrl + "?id=" + clientId, {
15
+ withCredentials: true
16
+ });
17
+ source.onmessage = function (e) {
18
+ _this.wrapMessage(function () {
19
+ return onMessage(JSON.parse(e.data));
20
+ });
21
+ };
22
+ return new Promise(function (res) {
23
+ source.onopen = function (e) {
24
+ res(function () {
25
+ source.close();
26
+ });
27
+ };
28
+ });
29
+ };
30
+ return EventSourceLiveQueryProvider;
31
+ }());
32
+ exports.EventSourceLiveQueryProvider = EventSourceLiveQueryProvider;
@@ -0,0 +1,67 @@
1
+ import { EntityOrderBy, FindOptions, Remult, Repository, RestDataProviderHttpProvider } from '../../index';
2
+ import { Allowed } from '../context';
3
+ import { ServerEventDispatcher } from './LiveQueryManager';
4
+ export declare const streamUrl = "stream1";
5
+ export interface LiveQueryProvider {
6
+ openStreamAndReturnCloseFunction(clientId: string, onMessage: MessageHandler): Promise<VoidFunction>;
7
+ }
8
+ export declare type MessageHandler = (message: {
9
+ data: any;
10
+ event: string;
11
+ }) => void;
12
+ export declare class LiveQueryClient {
13
+ lqp: LiveQueryProvider;
14
+ private provider?;
15
+ clientId: any;
16
+ private queries;
17
+ private channels;
18
+ constructor(lqp: LiveQueryProvider, provider?: RestDataProviderHttpProvider);
19
+ runPromise(p: Promise<any>): Promise<any>;
20
+ close(): void;
21
+ subscribeChannel<T>(key: string, onResult: (item: T) => void): () => void;
22
+ private closeIfNoListeners;
23
+ subscribe<entityType>(repo: Repository<entityType>, options: FindOptions<entityType>, onResult: (reducer: (prevState: entityType[]) => entityType[]) => void): () => void;
24
+ closeListener: Promise<VoidFunction>;
25
+ private openIfNoOpened;
26
+ }
27
+ export declare type listener = (message: any) => void;
28
+ export interface SubscribeToQueryArgs<entityType = any> {
29
+ entityKey: string;
30
+ orderBy?: EntityOrderBy<entityType>;
31
+ }
32
+ export declare type liveQueryMessage = {
33
+ type: "all";
34
+ data: any[];
35
+ } | {
36
+ type: "add";
37
+ data: any;
38
+ } | {
39
+ type: 'replace';
40
+ data: {
41
+ oldId: any;
42
+ item: any;
43
+ };
44
+ } | {
45
+ type: "remove";
46
+ data: {
47
+ id: any;
48
+ };
49
+ };
50
+ export interface SubscribeResult {
51
+ result: [];
52
+ id: string;
53
+ }
54
+ export interface ChannelSubscribe {
55
+ clientId: string;
56
+ channel: string;
57
+ remove: boolean;
58
+ }
59
+ export declare class AMessageChannel<messageType> {
60
+ private subscribedAllowed;
61
+ userCanSubscribe(channel: string, remult: Remult): boolean;
62
+ private key;
63
+ constructor(key: (string | ((remult: Remult) => string)), subscribedAllowed: Allowed);
64
+ send(what: messageType, remult?: Remult): void;
65
+ subscribe(client: LiveQueryClient, onValue: (value: messageType) => void, remult?: Remult): void;
66
+ dispatcher: ServerEventDispatcher;
67
+ }
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AMessageChannel = exports.LiveQueryClient = exports.streamUrl = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var index_1 = require("../../index");
6
+ var uuid_1 = require("uuid");
7
+ var rest_data_provider_1 = require("../data-providers/rest-data-provider");
8
+ var context_1 = require("../context");
9
+ exports.streamUrl = 'stream1';
10
+ var LiveQueryOnFrontEnd = /** @class */ (function () {
11
+ function LiveQueryOnFrontEnd(repo, query) {
12
+ this.repo = repo;
13
+ this.query = query;
14
+ this.items = [];
15
+ this.listeners = [];
16
+ }
17
+ LiveQueryOnFrontEnd.prototype.setAllItems = function (result) {
18
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
19
+ var items;
20
+ var _this = this;
21
+ return tslib_1.__generator(this, function (_a) {
22
+ switch (_a.label) {
23
+ case 0: return [4 /*yield*/, Promise.all(result.map(function (item) { return _this.repo.fromJson(item); }))];
24
+ case 1:
25
+ items = _a.sent();
26
+ this.forListeners(function (listener) {
27
+ listener(function (x) {
28
+ return items;
29
+ });
30
+ });
31
+ return [2 /*return*/];
32
+ }
33
+ });
34
+ });
35
+ };
36
+ LiveQueryOnFrontEnd.prototype.forListeners = function (what) {
37
+ var e_1, _a;
38
+ try {
39
+ for (var _b = tslib_1.__values(this.listeners), _c = _b.next(); !_c.done; _c = _b.next()) {
40
+ var l = _c.value;
41
+ what(l);
42
+ }
43
+ }
44
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
45
+ finally {
46
+ try {
47
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
48
+ }
49
+ finally { if (e_1) throw e_1.error; }
50
+ }
51
+ };
52
+ LiveQueryOnFrontEnd.prototype.handle = function (message) {
53
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
54
+ var sort, _a, item_1, item_2;
55
+ var _this = this;
56
+ return tslib_1.__generator(this, function (_b) {
57
+ switch (_b.label) {
58
+ case 0:
59
+ sort = function (items) {
60
+ if (_this.query.orderBy) {
61
+ var o_1 = index_1.Sort.translateOrderByToSort(_this.repo.metadata, _this.query.orderBy);
62
+ items.sort(function (a, b) { return o_1.compare(a, b); });
63
+ }
64
+ return items;
65
+ };
66
+ _a = message.type;
67
+ switch (_a) {
68
+ case "all": return [3 /*break*/, 1];
69
+ case "replace": return [3 /*break*/, 2];
70
+ case "add": return [3 /*break*/, 4];
71
+ case "remove": return [3 /*break*/, 6];
72
+ }
73
+ return [3 /*break*/, 7];
74
+ case 1:
75
+ this.setAllItems(message.data);
76
+ return [3 /*break*/, 7];
77
+ case 2: return [4 /*yield*/, this.repo.fromJson(message.data.item)];
78
+ case 3:
79
+ item_1 = _b.sent();
80
+ this.forListeners(function (listener) {
81
+ listener(function (items) {
82
+ if (!items)
83
+ items = [];
84
+ return sort(items.map(function (x) { return _this.repo.getEntityRef(x).getId() === message.data.oldId ? item_1 : x; }));
85
+ });
86
+ });
87
+ return [3 /*break*/, 7];
88
+ case 4: return [4 /*yield*/, this.repo.fromJson(message.data.item)];
89
+ case 5:
90
+ item_2 = _b.sent();
91
+ this.forListeners(function (listener) {
92
+ return listener(function (items) {
93
+ if (!items)
94
+ items = [];
95
+ items.push(item_2);
96
+ return sort(items);
97
+ });
98
+ });
99
+ return [3 /*break*/, 7];
100
+ case 6:
101
+ this.forListeners(function (listener) {
102
+ return listener(function (items) {
103
+ if (!items)
104
+ items = [];
105
+ return _this.items.filter(function (x) { return index_1.getEntityRef(x).getId() !== message.data.id; });
106
+ });
107
+ });
108
+ return [3 /*break*/, 7];
109
+ case 7:
110
+ ;
111
+ return [2 /*return*/];
112
+ }
113
+ });
114
+ });
115
+ };
116
+ return LiveQueryOnFrontEnd;
117
+ }());
118
+ var MessageChannel = /** @class */ (function () {
119
+ function MessageChannel() {
120
+ this.listeners = [];
121
+ }
122
+ MessageChannel.prototype.handle = function (message) {
123
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
124
+ var _a, _b, l;
125
+ var e_2, _c;
126
+ return tslib_1.__generator(this, function (_d) {
127
+ try {
128
+ for (_a = tslib_1.__values(this.listeners), _b = _a.next(); !_b.done; _b = _a.next()) {
129
+ l = _b.value;
130
+ l(message);
131
+ }
132
+ }
133
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
134
+ finally {
135
+ try {
136
+ if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
137
+ }
138
+ finally { if (e_2) throw e_2.error; }
139
+ }
140
+ return [2 /*return*/];
141
+ });
142
+ });
143
+ };
144
+ return MessageChannel;
145
+ }());
146
+ var LiveQueryClient = /** @class */ (function () {
147
+ function LiveQueryClient(lqp, provider) {
148
+ this.lqp = lqp;
149
+ this.provider = provider;
150
+ this.clientId = uuid_1.v4();
151
+ this.queries = new Map();
152
+ this.channels = new Map();
153
+ }
154
+ LiveQueryClient.prototype.runPromise = function (p) {
155
+ return p;
156
+ };
157
+ LiveQueryClient.prototype.close = function () {
158
+ this.queries.clear();
159
+ this.channels.clear();
160
+ this.closeIfNoListeners();
161
+ };
162
+ LiveQueryClient.prototype.subscribeChannel = function (key, onResult) {
163
+ var _this = this;
164
+ var onUnsubscribe = function () { };
165
+ this.openIfNoOpened().then(function () {
166
+ var q = _this.channels.get(key);
167
+ if (!q) {
168
+ _this.channels.set(key, q = new MessageChannel());
169
+ _this.provider.post(index_1.remult.apiClient.url + '/' + exports.streamUrl, {
170
+ channel: key,
171
+ clientId: _this.clientId,
172
+ remove: false
173
+ });
174
+ }
175
+ else {
176
+ }
177
+ q.listeners.push(onResult);
178
+ onUnsubscribe = function () {
179
+ q.listeners.splice(q.listeners.indexOf(onResult), 1);
180
+ if (q.listeners.length == 0) {
181
+ _this.channels.delete(key);
182
+ }
183
+ _this.closeIfNoListeners();
184
+ };
185
+ });
186
+ return function () {
187
+ onUnsubscribe();
188
+ };
189
+ };
190
+ LiveQueryClient.prototype.closeIfNoListeners = function () {
191
+ if (this.queries.size === 0 && this.channels.size === 0) {
192
+ this.runPromise(this.closeListener.then(function (x) { return x(); }));
193
+ this.closeListener = undefined;
194
+ }
195
+ };
196
+ LiveQueryClient.prototype.subscribe = function (repo, options, onResult) {
197
+ var _this = this;
198
+ var alive = true;
199
+ var onUnsubscribe = function () { };
200
+ this.runPromise(this.openIfNoOpened().then(function () { return repo.buildEntityDataProviderFindOptions(options)
201
+ .then(function (opts) {
202
+ if (!alive)
203
+ return;
204
+ var _a = new rest_data_provider_1.RestEntityDataProvider(function () { return index_1.remult.apiClient.url + '/' + repo.metadata.key; }, function () { return _this.provider; }, repo.metadata)
205
+ .buildFindRequest(opts), url = _a.url, filterObject = _a.filterObject;
206
+ var eventTypeKey = JSON.stringify({ url: url, filterObject: filterObject });
207
+ var q = _this.queries.get(eventTypeKey);
208
+ if (!q) {
209
+ _this.queries.set(eventTypeKey, q = new LiveQueryOnFrontEnd(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }));
210
+ url.add("__action", 'subscribe|' + _this.clientId);
211
+ var thenResult = function (r) {
212
+ _this.runPromise(q.setAllItems(r.result));
213
+ q.id = r.id;
214
+ };
215
+ if (filterObject) {
216
+ _this.runPromise(_this.provider.post(url.url, filterObject).then(thenResult));
217
+ }
218
+ else
219
+ _this.runPromise(_this.provider.get(url.url).then(thenResult));
220
+ }
221
+ else {
222
+ onResult(function (x) { return q.items; });
223
+ }
224
+ q.listeners.push(onResult);
225
+ onUnsubscribe = function () {
226
+ q.listeners.splice(q.listeners.indexOf(onResult), 1);
227
+ if (q.listeners.length == 0) {
228
+ _this.queries.delete(eventTypeKey);
229
+ }
230
+ _this.closeIfNoListeners();
231
+ };
232
+ }); }));
233
+ return function () {
234
+ alive = false;
235
+ onUnsubscribe();
236
+ };
237
+ };
238
+ LiveQueryClient.prototype.openIfNoOpened = function () {
239
+ var _this = this;
240
+ if (!this.provider) {
241
+ this.provider = context_1.buildRestDataProvider(index_1.remult.apiClient.httpClient);
242
+ }
243
+ if (!this.closeListener)
244
+ return this.runPromise(this.closeListener = this.lqp.openStreamAndReturnCloseFunction(this.clientId, function (message) {
245
+ var e_3, _a;
246
+ try {
247
+ for (var _b = tslib_1.__values(_this.queries.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
248
+ var q = _c.value;
249
+ if (q.id === message.event) {
250
+ _this.runPromise(q.handle(message.data));
251
+ }
252
+ }
253
+ }
254
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
255
+ finally {
256
+ try {
257
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
258
+ }
259
+ finally { if (e_3) throw e_3.error; }
260
+ }
261
+ var channel = _this.channels.get(message.event);
262
+ if (channel) {
263
+ channel.handle(message.data);
264
+ }
265
+ }));
266
+ return this.closeListener;
267
+ };
268
+ return LiveQueryClient;
269
+ }());
270
+ exports.LiveQueryClient = LiveQueryClient;
271
+ var AMessageChannel = /** @class */ (function () {
272
+ function AMessageChannel(key, subscribedAllowed) {
273
+ this.subscribedAllowed = subscribedAllowed;
274
+ if (typeof key === "string")
275
+ this.key = function () { return key; };
276
+ else
277
+ this.key = key;
278
+ }
279
+ AMessageChannel.prototype.userCanSubscribe = function (channel, remult) {
280
+ if (!remult)
281
+ remult = index_1.remult;
282
+ if (channel == this.key(remult) && remult.isAllowed(this.subscribedAllowed))
283
+ return true;
284
+ return false;
285
+ };
286
+ AMessageChannel.prototype.send = function (what, remult) {
287
+ if (!this.dispatcher)
288
+ throw new Error("Message couldn't be send since no dispatcher was set");
289
+ this.dispatcher.sendChannelMessage(this.key(remult || index_1.remult), what);
290
+ };
291
+ AMessageChannel.prototype.subscribe = function (client, onValue, remult) {
292
+ client.subscribeChannel(this.key(remult || index_1.remult), onValue);
293
+ };
294
+ return AMessageChannel;
295
+ }());
296
+ exports.AMessageChannel = AMessageChannel;
297
+ /*
298
+ [V] use entity normal http route for this - with __action.
299
+ [] move id from header to url in stream registration.
300
+ [] transaction accumulates messages.
301
+ [] on unsubscribe, also unsubscribe on server
302
+ [] consolidate channel & query
303
+ [] fix stream api url on server
304
+ [] remove client id from header
305
+ */
@@ -0,0 +1,33 @@
1
+ import { Remult } from '../..';
2
+ import { Repository, EntityRef, FindOptions } from '../remult3';
3
+ import { LiveQueryProvider } from '../data-api';
4
+ import { liveQueryMessage } from './LiveQuery';
5
+ export declare class LiveQueryManager implements LiveQueryProvider {
6
+ private dispatcher;
7
+ constructor(dispatcher: ServerEventDispatcher);
8
+ subscribe(repo: Repository<any>, clientId: string, findOptions: FindOptions<any>, remult: Remult, ids: any[]): string;
9
+ clients: clientInfo[];
10
+ sendMessage(key: string, message: liveQueryMessage): void;
11
+ hasListeners(ref: EntityRef<any>): boolean;
12
+ runPromise(p: Promise<any>): void;
13
+ saved(ref: EntityRef<any>): void;
14
+ deleted(ref: EntityRef<any>): void;
15
+ }
16
+ export interface clientInfo {
17
+ clientId: string;
18
+ queries: ({
19
+ id: string;
20
+ repo: Repository<any>;
21
+ findOptions: FindOptions<any>;
22
+ ids: any[];
23
+ })[];
24
+ }
25
+ export interface ServerEventDispatcher {
26
+ sendQueryMessage(message: ServerEventMessage): void;
27
+ sendChannelMessage<T>(channel: string, message: any): void;
28
+ }
29
+ export interface ServerEventMessage {
30
+ clientId: string;
31
+ queryId: string;
32
+ message: liveQueryMessage;
33
+ }