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.
Files changed (154) hide show
  1. package/README.md +28 -5
  2. package/esm/index.d.ts +4 -3
  3. package/esm/index.js +4 -2
  4. package/esm/live-query/ably.d.ts +6 -6
  5. package/esm/live-query/ably.js +5 -5
  6. package/esm/postgres/postgres-data-provider.d.ts +3 -1
  7. package/esm/postgres/postgres-data-provider.js +11 -1
  8. package/esm/postgres/schema-builder.d.ts +1 -1
  9. package/esm/postgres/schema-builder.js +43 -27
  10. package/esm/remult-express.d.ts +4 -22
  11. package/esm/remult-express.js +30 -28
  12. package/esm/remult-fastify.js +1 -1
  13. package/esm/remult-fresh.js +2 -2
  14. package/esm/remult-knex/index.d.ts +7 -31
  15. package/esm/remult-knex/index.js +71 -42
  16. package/esm/remult-mongo.d.ts +9 -2
  17. package/esm/remult-mongo.js +24 -8
  18. package/esm/remult-next.d.ts +11 -0
  19. package/esm/remult-next.js +27 -0
  20. package/esm/server/expressBridge.d.ts +20 -52
  21. package/esm/server/expressBridge.js +55 -29
  22. package/esm/server/index.d.ts +3 -1
  23. package/esm/server/index.js +6 -1
  24. package/esm/src/column-interfaces.d.ts +44 -8
  25. package/esm/src/context.d.ts +31 -19
  26. package/esm/src/context.js +35 -42
  27. package/esm/src/data-api.js +17 -4
  28. package/esm/src/data-interfaces.d.ts +1 -1
  29. package/esm/src/data-providers/array-entity-data-provider.d.ts +1 -1
  30. package/esm/src/data-providers/array-entity-data-provider.js +1 -1
  31. package/esm/src/data-providers/rest-data-provider.d.ts +4 -15
  32. package/esm/src/data-providers/rest-data-provider.js +38 -23
  33. package/esm/src/data-providers/sql-database.d.ts +6 -3
  34. package/esm/src/data-providers/sql-database.js +57 -16
  35. package/esm/src/data-providers/web-sql-data-provider.d.ts +2 -0
  36. package/esm/src/data-providers/web-sql-data-provider.js +17 -8
  37. package/esm/src/entity.d.ts +2 -2
  38. package/esm/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
  39. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +29 -21
  40. package/esm/src/filter/filter-interfaces.d.ts +5 -5
  41. package/esm/src/filter/filter-interfaces.js +31 -26
  42. package/esm/src/live-query/LiveQueryClient.d.ts +1 -19
  43. package/esm/src/live-query/LiveQueryClient.js +81 -50
  44. package/esm/src/live-query/SseSubscriptionClient.d.ts +4 -0
  45. package/esm/src/live-query/{EventSourceLiveQueryProvider.js → SseSubscriptionClient.js} +7 -6
  46. package/esm/src/live-query/SubscriptionChannel.d.ts +41 -0
  47. package/esm/src/live-query/{LiveQuerySubscriber.js → SubscriptionChannel.js} +40 -22
  48. package/esm/src/live-query/SubscriptionServer.d.ts +38 -0
  49. package/esm/src/live-query/{LiveQueryPublisher.js → SubscriptionServer.js} +60 -73
  50. package/esm/src/remult-proxy.js +44 -1
  51. package/esm/src/remult3/RepositoryImplementation.d.ts +8 -2
  52. package/esm/src/remult3/RepositoryImplementation.js +94 -30
  53. package/esm/src/remult3/remult3.d.ts +59 -6
  54. package/esm/src/remult3/remult3.js +1 -0
  55. package/esm/src/server-action.d.ts +1 -1
  56. package/esm/src/shared-tests/db-tests-setup.d.ts +1 -1
  57. package/esm/src/shared-tests/db-tests-setup.js +4 -3
  58. package/esm/src/shared-tests/db-tests.db-tests.js +55 -9
  59. package/esm/src/sql-command.d.ts +4 -2
  60. package/esm/src/tests/entityForCustomFilter.d.ts +6 -6
  61. package/esm/src/tests/entityForCustomFilter.js +20 -21
  62. package/esm/src/tests/frontend-database-tests-setup.spec.js +1 -2
  63. package/esm/src/tests/testHelper.spec.d.ts +2 -2
  64. package/esm/src/tests/testHelper.spec.js +1 -2
  65. package/esm/src/valueConverters.js +17 -7
  66. package/index.d.ts +4 -3
  67. package/index.js +7 -3
  68. package/live-query/ably.d.ts +6 -6
  69. package/live-query/ably.js +12 -12
  70. package/package.json +18 -9
  71. package/postgres/postgres-data-provider.d.ts +3 -1
  72. package/postgres/postgres-data-provider.js +13 -3
  73. package/postgres/schema-builder.d.ts +1 -1
  74. package/postgres/schema-builder.js +64 -50
  75. package/remult-express.d.ts +4 -22
  76. package/remult-express.js +39 -47
  77. package/remult-fastify.js +2 -2
  78. package/remult-fresh.js +1 -1
  79. package/remult-knex/index.d.ts +7 -31
  80. package/remult-knex/index.js +100 -53
  81. package/remult-mongo.d.ts +9 -2
  82. package/remult-mongo.js +39 -7
  83. package/remult-next.d.ts +11 -0
  84. package/remult-next.js +73 -0
  85. package/server/JsonEntityFileStorage.js +2 -2
  86. package/server/expressBridge.d.ts +20 -52
  87. package/server/expressBridge.js +68 -41
  88. package/server/index.d.ts +3 -1
  89. package/server/index.js +7 -2
  90. package/src/column-interfaces.d.ts +44 -8
  91. package/src/context.d.ts +31 -19
  92. package/src/context.js +39 -50
  93. package/src/data-api.js +18 -5
  94. package/src/data-interfaces.d.ts +1 -1
  95. package/src/data-providers/array-entity-data-provider.d.ts +1 -1
  96. package/src/data-providers/array-entity-data-provider.js +1 -1
  97. package/src/data-providers/rest-data-provider.d.ts +4 -15
  98. package/src/data-providers/rest-data-provider.js +48 -24
  99. package/src/data-providers/sql-database.d.ts +6 -3
  100. package/src/data-providers/sql-database.js +75 -15
  101. package/src/data-providers/web-sql-data-provider.d.ts +2 -0
  102. package/src/data-providers/web-sql-data-provider.js +16 -7
  103. package/src/entity.d.ts +2 -2
  104. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
  105. package/src/filter/filter-consumer-bridge-to-sql-request.js +59 -50
  106. package/src/filter/filter-interfaces.d.ts +5 -5
  107. package/src/filter/filter-interfaces.js +31 -26
  108. package/src/live-query/LiveQueryClient.d.ts +1 -19
  109. package/src/live-query/LiveQueryClient.js +117 -64
  110. package/src/live-query/SseSubscriptionClient.d.ts +4 -0
  111. package/src/live-query/{EventSourceLiveQueryProvider.js → SseSubscriptionClient.js} +13 -12
  112. package/src/live-query/SubscriptionChannel.d.ts +41 -0
  113. package/src/live-query/{LiveQuerySubscriber.js → SubscriptionChannel.js} +51 -47
  114. package/src/live-query/SubscriptionServer.d.ts +38 -0
  115. package/src/live-query/SubscriptionServer.js +237 -0
  116. package/src/remult-proxy.js +137 -2
  117. package/src/remult3/RepositoryImplementation.d.ts +8 -2
  118. package/src/remult3/RepositoryImplementation.js +121 -34
  119. package/src/remult3/remult3.d.ts +59 -6
  120. package/src/remult3/remult3.js +1 -0
  121. package/src/server-action.d.ts +1 -1
  122. package/src/shared-tests/db-tests-setup.d.ts +1 -1
  123. package/src/shared-tests/db-tests-setup.js +5 -4
  124. package/src/shared-tests/db-tests.db-tests.js +101 -8
  125. package/src/sql-command.d.ts +4 -2
  126. package/src/tests/entityForCustomFilter.d.ts +6 -6
  127. package/src/tests/entityForCustomFilter.js +30 -31
  128. package/src/tests/frontend-database-tests-setup.spec.js +4 -5
  129. package/src/tests/testHelper.spec.d.ts +2 -2
  130. package/src/tests/testHelper.spec.js +4 -5
  131. package/src/valueConverters.js +17 -7
  132. package/esm/live-query/index.d.ts +0 -3
  133. package/esm/live-query/index.js +0 -3
  134. package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -5
  135. package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +0 -113
  136. package/esm/src/backend-tests/my-test.backend-spec.d.ts +0 -1
  137. package/esm/src/backend-tests/my-test.backend-spec.js +0 -59
  138. package/esm/src/backend-tests/test-http-server.backend-spec.d.ts +0 -6
  139. package/esm/src/backend-tests/test-http-server.backend-spec.js +0 -171
  140. package/esm/src/live-query/EventSourceLiveQueryProvider.d.ts +0 -4
  141. package/esm/src/live-query/LiveQueryPublisher.d.ts +0 -49
  142. package/esm/src/live-query/LiveQuerySubscriber.d.ts +0 -67
  143. package/live-query/index.d.ts +0 -3
  144. package/live-query/index.js +0 -10
  145. package/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -5
  146. package/src/backend-tests/backend-database-test-setup.backend-spec.js +0 -231
  147. package/src/backend-tests/my-test.backend-spec.d.ts +0 -1
  148. package/src/backend-tests/my-test.backend-spec.js +0 -160
  149. package/src/backend-tests/test-http-server.backend-spec.d.ts +0 -6
  150. package/src/backend-tests/test-http-server.backend-spec.js +0 -342
  151. package/src/live-query/EventSourceLiveQueryProvider.d.ts +0 -4
  152. package/src/live-query/LiveQueryPublisher.d.ts +0 -49
  153. package/src/live-query/LiveQueryPublisher.js +0 -248
  154. 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(customFilterTranslator, key = '') {
18
- let customFilterInfo = { key: key, customFilterTranslator };
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 (!customFilterInfo.key)
23
+ if (!rawFilterInfo.key)
24
24
  throw "Usage of custom filter before a key was assigned to it";
25
25
  return {
26
- [customUrlToken + customFilterInfo.key]: x
26
+ [customUrlToken + rawFilterInfo.key]: x
27
27
  };
28
- }, { customFilterInfo });
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.customFilterInfo && element.customFilterInfo.customFilterTranslator) {
127
- if (element.customFilterInfo.key == filterKey) {
128
- r.push(await Filter.fromEntityFilter(entity, await element.customFilterInfo.customFilterTranslator(remult, custom)));
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 + "_null", true);
267
+ this.add(col.key + ".null", true);
263
268
  }
264
269
  isNotNull(col) {
265
- this.add(col.key + "_null", false);
270
+ this.add(col.key + ".null", false);
266
271
  }
267
272
  isIn(col, val) {
268
- this.add(col.key + "_in", val.map(x => col.valueConverter.toJson(x)));
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 + '_ne', col.valueConverter.toJson(val));
279
+ this.add(col.key + '.ne', col.valueConverter.toJson(val));
275
280
  }
276
281
  isGreaterOrEqualTo(col, val) {
277
- this.add(col.key + '_gte', col.valueConverter.toJson(val));
282
+ this.add(col.key + '.gte', col.valueConverter.toJson(val));
278
283
  }
279
284
  isGreaterThan(col, val) {
280
- this.add(col.key + '_gt', col.valueConverter.toJson(val));
285
+ this.add(col.key + '.gt', col.valueConverter.toJson(val));
281
286
  }
282
287
  isLessOrEqualTo(col, val) {
283
- this.add(col.key + '_lte', col.valueConverter.toJson(val));
288
+ this.add(col.key + '.lte', col.valueConverter.toJson(val));
284
289
  }
285
290
  isLessThan(col, val) {
286
- this.add(col.key + '_lt', col.valueConverter.toJson(val));
291
+ this.add(col.key + '.lt', col.valueConverter.toJson(val));
287
292
  }
288
293
  containsCaseInsensitive(col, val) {
289
- this.add(col.key + "_contains", val);
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('_gt', val => ({ "$gt": val }));
341
- addFilter('_gte', val => ({ "$gte": val }));
342
- addFilter('_lt', val => ({ "$lt": val }));
343
- addFilter('_lte', val => ({ "$lte": val }));
344
- addFilter('_ne', val => ({ "$ne": val }));
345
- addFilter('_in', val => val, true);
346
- var nullFilter = filterInfo.get(col.key + "_null");
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('_contains', val => ({ $contains: val }), false, true);
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.customFilterInfo && element.customFilterInfo.customFilterTranslator) {
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
- import { FindOptions, Repository, RestDataProviderHttpProvider } from '../../index';
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 { remult as defaultRemult, UrlBuilder } from '../../index';
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, MessageChannel, liveQueryKeepAliveRoute } from './LiveQuerySubscriber';
3
+ import { LiveQuerySubscriber, liveQueryKeepAliveRoute } from './SubscriptionChannel';
4
+ /* @internal*/
5
5
  export class LiveQueryClient {
6
- constructor(lqp, provider) {
7
- this.lqp = lqp;
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 => q.unsubscribe = c.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value))));
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.disconnect()));
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, onResult) {
59
- let alive = true;
76
+ subscribe(repo, options, listener) {
60
77
  let onUnsubscribe = () => { };
61
- this.runPromise(this.openIfNoOpened().then(() => repo.buildEntityDataProviderFindOptions(options)
62
- .then(opts => {
63
- if (!alive)
64
- return;
65
- const { url, filterObject } = new RestEntityDataProvider(() => defaultRemult.apiClient.url + '/' + repo.metadata.key, () => this.provider, repo.metadata)
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
- const thenResult = (r) => {
79
- this.client.then(c => {
80
- let unsubscribeToChannel = c.subscribe(r.queryChannel, (value) => this.wrapMessageHandling(() => this.runPromise(q.handle(value))));
81
- q.unsubscribe = () => {
82
- unsubscribeToChannel();
83
- const url = new UrlBuilder(defaultRemult.apiClient.url + '/' + repo.metadata.key);
84
- url.add("__action", "endLiveQuery");
85
- this.runPromise(this.provider.post(url.url, {
86
- id: q.id
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.id = r.queryChannel;
92
- };
93
- if (filterObject) {
94
- this.runPromise(this.provider.post(url.url, filterObject).then(thenResult));
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
- onResult(x => [...q.defaultQueryState]);
120
+ q.sendDefaultState(listener.next);
103
121
  }
104
- q.listeners.push(onResult);
122
+ q.listeners.push(listener);
105
123
  onUnsubscribe = () => {
106
- q.listeners.splice(q.listeners.indexOf(onResult), 1);
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.id);
145
+ ids.push(q.queryChannel);
128
146
  }
129
147
  if (ids.length > 0) {
130
- const invalidIds = await this.runPromise(this.provider.post(defaultRemult.apiClient.url + liveQueryKeepAliveRoute, ids));
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.id === id)
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.lqp.openStreamAndReturnCloseFunction(() => {
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
+ }
@@ -0,0 +1,4 @@
1
+ import { SubscriptionClient, SubscriptionClientConnection } from "./SubscriptionChannel";
2
+ export declare class SseSubscriptionClient implements SubscriptionClient {
3
+ openConnection(onReconnect: VoidFunction): Promise<SubscriptionClientConnection>;
4
+ }
@@ -1,15 +1,16 @@
1
1
  import { buildRestDataProvider } from "../buildRestDataProvider";
2
2
  import { remult } from "../remult-proxy";
3
- import { streamUrl } from "./LiveQuerySubscriber";
4
- export class EventSourceLiveQueryProvider {
5
- openStreamAndReturnCloseFunction(onReconnect) {
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
- disconnect() {
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(l);
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 class MessageChannel {
75
- constructor() {
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
- send(what, remult) {
100
+ publish(message, remult) {
92
101
  remult = remult || defaultRemult;
93
- remult.liveQueryPublisher.sendChannelMessage(this.channelKey, what);
102
+ remult.subscriptionServer.publishMessage(this.channelKey, message);
94
103
  }
95
- subscribe(onValue, remult) {
104
+ //@internal
105
+ subscribe(next, remult) {
96
106
  remult = remult || defaultRemult;
97
- remult.liveQuerySubscriber.subscribeChannel(this.channelKey, onValue);
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
+ }