remult 3.3.18 → 3.4.0-next.1

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.
@@ -5,7 +5,6 @@ var tslib_1 = require("tslib");
5
5
  var buildRestDataProvider_js_1 = require("../buildRestDataProvider.js");
6
6
  var remult_proxy_js_1 = require("../remult-proxy.js");
7
7
  var remult_static_js_1 = require("../remult-static.js");
8
- var remult3_js_1 = require("../remult3/remult3.js");
9
8
  var SubscriptionChannel_js_1 = require("./SubscriptionChannel.js");
10
9
  var SseSubscriptionClient = /** @class */ (function () {
11
10
  function SseSubscriptionClient() {
@@ -14,11 +13,54 @@ var SseSubscriptionClient = /** @class */ (function () {
14
13
  var connectionId;
15
14
  var channels = new Map();
16
15
  var provider = (0, buildRestDataProvider_js_1.buildRestDataProvider)(remult_proxy_js_1.remult.apiClient.httpClient);
17
- var connected = false;
18
16
  var source;
17
+ var retryCount = 0;
18
+ var closed = false;
19
+ var connected = false;
20
+ var reconnectTimer;
21
+ var connectionReady;
22
+ var connectionPromise = new Promise(function (res) {
23
+ connectionReady = res;
24
+ });
25
+ function clearReconnectTimer() {
26
+ if (reconnectTimer !== undefined) {
27
+ clearTimeout(reconnectTimer);
28
+ reconnectTimer = undefined;
29
+ }
30
+ }
31
+ function scheduleReconnect() {
32
+ if (closed)
33
+ return;
34
+ clearReconnectTimer();
35
+ var delay = Math.min(500 * Math.pow(2, Math.min(retryCount, 5)), 8000);
36
+ retryCount++;
37
+ reconnectTimer = setTimeout(function () {
38
+ reconnectTimer = undefined;
39
+ createConnection();
40
+ }, delay);
41
+ }
42
+ function resume(force) {
43
+ if (closed)
44
+ return;
45
+ retryCount = 0;
46
+ clearReconnectTimer();
47
+ if (force) {
48
+ createConnection();
49
+ return;
50
+ }
51
+ var open = source &&
52
+ typeof EventSource !== 'undefined' &&
53
+ source.readyState !== EventSource.CLOSED;
54
+ if (!open)
55
+ createConnection();
56
+ }
19
57
  var client = {
58
+ lastServerEvent: Date.now(),
59
+ resume: resume,
20
60
  close: function () {
21
- source.close();
61
+ closed = true;
62
+ clearReconnectTimer();
63
+ source === null || source === void 0 ? void 0 : source.close();
22
64
  },
23
65
  subscribe: function (channel, handler) {
24
66
  return tslib_1.__awaiter(this, void 0, void 0, function () {
@@ -27,13 +69,17 @@ var SseSubscriptionClient = /** @class */ (function () {
27
69
  switch (_a.label) {
28
70
  case 0:
29
71
  listeners = channels.get(channel);
30
- if (!!listeners) return [3 /*break*/, 2];
72
+ if (!!listeners) return [3 /*break*/, 3];
31
73
  channels.set(channel, (listeners = []));
32
- return [4 /*yield*/, subscribeToChannel(channel)];
74
+ return [4 /*yield*/, connectionPromise];
33
75
  case 1:
34
76
  _a.sent();
35
- _a.label = 2;
77
+ if (!!closed) return [3 /*break*/, 3];
78
+ return [4 /*yield*/, subscribeToChannel(channel)];
36
79
  case 2:
80
+ _a.sent();
81
+ _a.label = 3;
82
+ case 3:
37
83
  listeners.push(handler);
38
84
  return [2 /*return*/, function () {
39
85
  listeners.splice(listeners.indexOf(handler, 1));
@@ -52,80 +98,85 @@ var SseSubscriptionClient = /** @class */ (function () {
52
98
  });
53
99
  },
54
100
  };
55
- var createConnectionPromise = function () {
56
- return new Promise(function (res) {
57
- createConnection();
58
- var retryCount = 0;
59
- function createConnection() {
60
- var _this = this;
61
- if (source)
62
- source.close();
63
- source = SseSubscriptionClient.createEventSource(remult_proxy_js_1.remult.apiClient.url + '/' + SubscriptionChannel_js_1.streamUrl);
64
- source.onmessage = function (e) {
65
- var message = JSON.parse(e.data);
66
- var listeners = channels.get(message.channel);
67
- if (listeners)
68
- listeners.forEach(function (x) { return x(message.data); });
69
- };
70
- source.onerror = function (e) {
71
- console.error('Live Query Event Source Error', e);
72
- source.close();
73
- if (retryCount++ < remult3_js_1.flags.error500RetryCount) {
74
- setTimeout(function () {
75
- createConnection();
76
- }, 500);
77
- }
78
- };
79
- source.addEventListener('connectionId', function (e) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
80
- var _a, _b, channel, e_1_1;
81
- var e_1, _c;
82
- return tslib_1.__generator(this, function (_d) {
83
- switch (_d.label) {
84
- case 0:
85
- //@ts-ignore
86
- connectionId = e.data;
87
- if (!connected) return [3 /*break*/, 9];
88
- _d.label = 1;
89
- case 1:
90
- _d.trys.push([1, 6, 7, 8]);
91
- _a = tslib_1.__values(channels.keys()), _b = _a.next();
92
- _d.label = 2;
93
- case 2:
94
- if (!!_b.done) return [3 /*break*/, 5];
95
- channel = _b.value;
96
- return [4 /*yield*/, subscribeToChannel(channel)];
97
- case 3:
98
- _d.sent();
99
- _d.label = 4;
100
- case 4:
101
- _b = _a.next();
102
- return [3 /*break*/, 2];
103
- case 5: return [3 /*break*/, 8];
104
- case 6:
105
- e_1_1 = _d.sent();
106
- e_1 = { error: e_1_1 };
107
- return [3 /*break*/, 8];
108
- case 7:
109
- try {
110
- if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
111
- }
112
- finally { if (e_1) throw e_1.error; }
113
- return [7 /*endfinally*/];
114
- case 8:
115
- onReconnect();
116
- return [3 /*break*/, 10];
117
- case 9:
118
- connected = true;
119
- res(client);
120
- _d.label = 10;
121
- case 10: return [2 /*return*/];
122
- }
123
- });
124
- }); });
125
- }
101
+ function noteServerEvent() {
102
+ client.lastServerEvent = Date.now();
103
+ }
104
+ function createConnection() {
105
+ var _this = this;
106
+ if (closed)
107
+ return;
108
+ clearReconnectTimer();
109
+ if (source)
110
+ source.close();
111
+ source = SseSubscriptionClient.createEventSource(remult_proxy_js_1.remult.apiClient.url + '/' + SubscriptionChannel_js_1.streamUrl);
112
+ source.onmessage = function (e) {
113
+ noteServerEvent();
114
+ var message = JSON.parse(e.data);
115
+ var listeners = channels.get(message.channel);
116
+ if (listeners)
117
+ listeners.forEach(function (x) { return x(message.data); });
118
+ };
119
+ source.addEventListener('keep-alive', function () {
120
+ noteServerEvent();
126
121
  });
127
- };
128
- return createConnectionPromise();
122
+ source.onerror = function (e) {
123
+ console.error('Live Query Event Source Error', e);
124
+ source.close();
125
+ if (closed)
126
+ return;
127
+ scheduleReconnect();
128
+ };
129
+ source.addEventListener('connectionId', function (e) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
130
+ var _a, _b, channel, e_1_1;
131
+ var e_1, _c;
132
+ return tslib_1.__generator(this, function (_d) {
133
+ switch (_d.label) {
134
+ case 0:
135
+ //@ts-ignore
136
+ connectionId = e.data;
137
+ retryCount = 0;
138
+ noteServerEvent();
139
+ if (!connected) return [3 /*break*/, 9];
140
+ _d.label = 1;
141
+ case 1:
142
+ _d.trys.push([1, 6, 7, 8]);
143
+ _a = tslib_1.__values(channels.keys()), _b = _a.next();
144
+ _d.label = 2;
145
+ case 2:
146
+ if (!!_b.done) return [3 /*break*/, 5];
147
+ channel = _b.value;
148
+ return [4 /*yield*/, subscribeToChannel(channel)];
149
+ case 3:
150
+ _d.sent();
151
+ _d.label = 4;
152
+ case 4:
153
+ _b = _a.next();
154
+ return [3 /*break*/, 2];
155
+ case 5: return [3 /*break*/, 8];
156
+ case 6:
157
+ e_1_1 = _d.sent();
158
+ e_1 = { error: e_1_1 };
159
+ return [3 /*break*/, 8];
160
+ case 7:
161
+ try {
162
+ if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
163
+ }
164
+ finally { if (e_1) throw e_1.error; }
165
+ return [7 /*endfinally*/];
166
+ case 8:
167
+ onReconnect();
168
+ return [3 /*break*/, 10];
169
+ case 9:
170
+ connected = true;
171
+ connectionReady();
172
+ _d.label = 10;
173
+ case 10: return [2 /*return*/];
174
+ }
175
+ });
176
+ }); });
177
+ }
178
+ createConnection();
179
+ return Promise.resolve(client);
129
180
  function subscribeToChannel(channel) {
130
181
  return tslib_1.__awaiter(this, void 0, void 0, function () {
131
182
  var result;
@@ -139,12 +190,21 @@ var SseSubscriptionClient = /** @class */ (function () {
139
190
  })];
140
191
  case 1:
141
192
  result = _a.sent();
142
- if (!(result === exports.ConnectionNotFoundError)) return [3 /*break*/, 3];
143
- return [4 /*yield*/, createConnectionPromise()];
193
+ if (!(result === exports.ConnectionNotFoundError)) return [3 /*break*/, 4];
194
+ connected = false;
195
+ connectionPromise = new Promise(function (res) {
196
+ connectionReady = res;
197
+ });
198
+ createConnection();
199
+ return [4 /*yield*/, connectionPromise];
144
200
  case 2:
145
201
  _a.sent();
146
- _a.label = 3;
147
- case 3: return [2 /*return*/];
202
+ if (!!closed) return [3 /*break*/, 4];
203
+ return [4 /*yield*/, subscribeToChannel(channel)];
204
+ case 3:
205
+ _a.sent();
206
+ _a.label = 4;
207
+ case 4: return [2 /*return*/];
148
208
  }
149
209
  });
150
210
  });
@@ -9,6 +9,10 @@ export type Unsubscribe = VoidFunction;
9
9
  export interface SubscriptionClientConnection {
10
10
  subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Promise<Unsubscribe>;
11
11
  close(): void;
12
+ /** SSE clients set this; missing means no heartbeat tracking (Ably, tests). */
13
+ lastServerEvent?: number;
14
+ /** Foreground / online: reconnect now, skip backoff. `force` kills an OPEN zombie. */
15
+ resume?: (force?: boolean) => void;
12
16
  }
13
17
  export interface SubscriptionClient {
14
18
  openConnection(onReconnect: VoidFunction): Promise<SubscriptionClientConnection>;
@@ -31,6 +35,10 @@ export declare type LiveQueryChange = {
31
35
  data: {
32
36
  id: any;
33
37
  };
38
+ } | {
39
+ type: 'version';
40
+ from: number;
41
+ to: number;
34
42
  };
35
43
  /**
36
44
  * The `SubscriptionChannel` class is used to send messages from the backend to the frontend,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SubscriptionChannel = exports.liveQueryKeepAliveRoute = exports.LiveQuerySubscriber = exports.streamUrl = void 0;
4
+ exports.onBrowserForeground = onBrowserForeground;
4
5
  var tslib_1 = require("tslib");
5
6
  var sort_js_1 = require("../sort.js");
6
7
  var remult_proxy_js_1 = require("../remult-proxy.js");
@@ -12,9 +13,12 @@ var LiveQuerySubscriber = /** @class */ (function () {
12
13
  this.repo = repo;
13
14
  this.query = query;
14
15
  this.unsubscribe = function () { };
16
+ this.unsubscribeChannel = function () { };
17
+ this.unsubscribeQuery = function () { };
15
18
  this.defaultQueryState = [];
16
19
  this.listeners = [];
17
20
  this.id = String(crypto.randomUUID());
21
+ this.version = 0;
18
22
  this.queryChannel = "users:".concat(userId, ":queries:").concat(this.id);
19
23
  this.id = this.queryChannel;
20
24
  }
@@ -35,6 +39,7 @@ var LiveQuerySubscriber = /** @class */ (function () {
35
39
  return items;
36
40
  });
37
41
  }, this.allItemsMessage(items));
42
+ this.version = 0;
38
43
  return [2 /*return*/];
39
44
  }
40
45
  });
@@ -88,18 +93,32 @@ var LiveQuerySubscriber = /** @class */ (function () {
88
93
  };
89
94
  LiveQuerySubscriber.prototype.handle = function (messages) {
90
95
  return tslib_1.__awaiter(this, void 0, void 0, function () {
91
- var x, loadedItems, index, element;
96
+ var ver, data, x, loadedItems, index, element;
92
97
  var _this = this;
93
- return tslib_1.__generator(this, function (_a) {
94
- switch (_a.label) {
98
+ var _a;
99
+ return tslib_1.__generator(this, function (_b) {
100
+ switch (_b.label) {
95
101
  case 0:
96
- x = messages.filter(function (_a) {
102
+ ver = messages.find(function (m) {
103
+ return m.type === 'version';
104
+ });
105
+ if (ver) {
106
+ if (ver.from < this.version)
107
+ return [2 /*return*/];
108
+ if (ver.from > this.version) {
109
+ this.version = ver.to;
110
+ (_a = this.subscribeCode) === null || _a === void 0 ? void 0 : _a.call(this);
111
+ return [2 /*return*/];
112
+ }
113
+ }
114
+ data = messages.filter(function (m) { return m.type !== 'version'; });
115
+ x = data.filter(function (_a) {
97
116
  var type = _a.type;
98
117
  return type == 'add' || type == 'replace';
99
118
  });
100
119
  return [4 /*yield*/, (0, repository_internals_js_1.getRepositoryInternals)(this.repo)._fromJsonArray(x.map(function (m) { return m.data.item; }), this.query.options)];
101
120
  case 1:
102
- loadedItems = _a.sent();
121
+ loadedItems = _b.sent();
103
122
  for (index = 0; index < x.length; index++) {
104
123
  element = x[index];
105
124
  element.data.item = loadedItems[index];
@@ -137,21 +156,22 @@ var LiveQuerySubscriber = /** @class */ (function () {
137
156
  }
138
157
  };
139
158
  try {
140
- for (var messages_1 = tslib_1.__values(messages), messages_1_1 = messages_1.next(); !messages_1_1.done; messages_1_1 = messages_1.next()) {
141
- var message = messages_1_1.value;
159
+ for (var data_1 = tslib_1.__values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) {
160
+ var message = data_1_1.value;
142
161
  _loop_2(message);
143
162
  }
144
163
  }
145
164
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
146
165
  finally {
147
166
  try {
148
- if (messages_1_1 && !messages_1_1.done && (_a = messages_1.return)) _a.call(messages_1);
167
+ if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1);
149
168
  }
150
169
  finally { if (e_2) throw e_2.error; }
151
170
  }
152
171
  return items;
153
172
  });
154
- }, messages);
173
+ }, data);
174
+ this.version = ver ? ver.to : this.version + (data.length > 0 ? 1 : 0);
155
175
  return [2 /*return*/];
156
176
  }
157
177
  });
@@ -160,6 +180,24 @@ var LiveQuerySubscriber = /** @class */ (function () {
160
180
  return LiveQuerySubscriber;
161
181
  }());
162
182
  exports.LiveQuerySubscriber = LiveQuerySubscriber;
183
+ //@internal
184
+ function onBrowserForeground(handler) {
185
+ if (typeof document === 'undefined')
186
+ return function () { };
187
+ function onForeground() {
188
+ if (document.visibilityState === 'hidden')
189
+ return;
190
+ handler();
191
+ }
192
+ document.addEventListener('visibilitychange', onForeground);
193
+ window.addEventListener('online', onForeground);
194
+ window.addEventListener('pageshow', onForeground);
195
+ return function () {
196
+ document.removeEventListener('visibilitychange', onForeground);
197
+ window.removeEventListener('online', onForeground);
198
+ window.removeEventListener('pageshow', onForeground);
199
+ };
200
+ }
163
201
  exports.liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
164
202
  /**
165
203
  * The `SubscriptionChannel` class is used to send messages from the backend to the frontend,
@@ -8,12 +8,16 @@ export interface LiveQueryStorage {
8
8
  query: StoredQuery;
9
9
  setData(data: any): Promise<void>;
10
10
  }) => Promise<void>): Promise<void>;
11
- keepAliveAndReturnUnknownQueryIds(queryIds: string[]): Promise<string[]>;
11
+ keepAliveAndReturnUnknownQueryIds(queryIds: string[]): Promise<LiveQueryKeepAliveResult>;
12
12
  }
13
+ export type LiveQueryKeepAliveResult = {
14
+ unknownQueryIds: string[];
15
+ versions: Record<string, number>;
16
+ };
13
17
  export declare class InMemoryLiveQueryStorage implements LiveQueryStorage {
14
18
  debugFileSaver: (x: any) => void;
15
19
  debug(): void;
16
- keepAliveAndReturnUnknownQueryIds(ids: string[]): Promise<string[]>;
20
+ keepAliveAndReturnUnknownQueryIds(ids: string[]): Promise<LiveQueryKeepAliveResult>;
17
21
  queries: (StoredQuery & {
18
22
  lastUsed: string;
19
23
  })[];
@@ -35,4 +39,5 @@ export interface QueryData {
35
39
  findOptionsJson: any;
36
40
  requestJson: any;
37
41
  lastIds: any[];
42
+ version?: number;
38
43
  }
@@ -26,15 +26,16 @@ var LiveQueryPublisher = /** @class */ (function () {
26
26
  case 0:
27
27
  query = tslib_1.__assign({}, q.data);
28
28
  return [4 /*yield*/, this.performWithContext(query.requestJson, entityKey, function (repo) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
29
- var messages, currentItems, currentIds, _loop_1, _a, _b, id, _loop_2, currentItems_1, currentItems_1_1, item;
29
+ var messages, currentItems, currentIds, _loop_1, _a, _b, id, _loop_2, currentItems_1, currentItems_1_1, item, from;
30
30
  var e_1, _c, e_2, _d;
31
- return tslib_1.__generator(this, function (_e) {
32
- switch (_e.label) {
31
+ var _e;
32
+ return tslib_1.__generator(this, function (_f) {
33
+ switch (_f.label) {
33
34
  case 0:
34
35
  messages = [];
35
36
  return [4 /*yield*/, repo.find((0, rest_data_provider_js_1.findOptionsFromJson)(query.findOptionsJson, repo.metadata))];
36
37
  case 1:
37
- currentItems = _e.sent();
38
+ currentItems = _f.sent();
38
39
  currentIds = currentItems.map(function (x) {
39
40
  return repo.getEntityRef(x).getId();
40
41
  });
@@ -103,9 +104,14 @@ var LiveQueryPublisher = /** @class */ (function () {
103
104
  messages: messages,
104
105
  });
105
106
  query.lastIds = currentIds;
107
+ if (messages.length > 0) {
108
+ from = (_e = query.version) !== null && _e !== void 0 ? _e : 0;
109
+ query.version = from + 1;
110
+ messages.push({ type: 'version', from: from, to: query.version });
111
+ }
106
112
  return [4 /*yield*/, setData(query)];
107
113
  case 2:
108
- _e.sent();
114
+ _f.sent();
109
115
  if (messages.length > 0)
110
116
  this.subscriptionServer().publishMessage(q.id, messages);
111
117
  return [2 /*return*/];
@@ -139,17 +145,20 @@ var InMemoryLiveQueryStorage = /** @class */ (function () {
139
145
  };
140
146
  InMemoryLiveQueryStorage.prototype.keepAliveAndReturnUnknownQueryIds = function (ids) {
141
147
  return tslib_1.__awaiter(this, void 0, void 0, function () {
142
- var result, _loop_3, this_1, ids_1, ids_1_1, id;
148
+ var unknownQueryIds, versions, _loop_3, this_1, ids_1, ids_1_1, id;
143
149
  var e_3, _a;
144
- return tslib_1.__generator(this, function (_b) {
145
- result = [];
150
+ var _b, _c;
151
+ return tslib_1.__generator(this, function (_d) {
152
+ unknownQueryIds = [];
153
+ versions = {};
146
154
  _loop_3 = function (id) {
147
155
  var q = this_1.queries.find(function (q) { return q.id === id; });
148
156
  if (q) {
149
157
  q.lastUsed = new Date().toISOString();
158
+ versions[id] = (_c = (_b = q.data) === null || _b === void 0 ? void 0 : _b.version) !== null && _c !== void 0 ? _c : 0;
150
159
  }
151
160
  else
152
- result.push(id);
161
+ unknownQueryIds.push(id);
153
162
  };
154
163
  this_1 = this;
155
164
  try {
@@ -166,13 +175,14 @@ var InMemoryLiveQueryStorage = /** @class */ (function () {
166
175
  finally { if (e_3) throw e_3.error; }
167
176
  }
168
177
  this.debug();
169
- return [2 /*return*/, result];
178
+ return [2 /*return*/, { unknownQueryIds: unknownQueryIds, versions: versions }];
170
179
  });
171
180
  });
172
181
  };
173
182
  InMemoryLiveQueryStorage.prototype.add = function (query) {
174
183
  return tslib_1.__awaiter(this, void 0, void 0, function () {
175
184
  return tslib_1.__generator(this, function (_a) {
185
+ this.queries = this.queries.filter(function (q) { return q.id !== query.id; });
176
186
  this.queries.push(tslib_1.__assign(tslib_1.__assign({}, query), { lastUsed: new Date().toISOString() }));
177
187
  this.debug();
178
188
  return [2 /*return*/];
@@ -1220,4 +1220,10 @@ export type ClassFieldDecorator<entityType, valueType> = ((target: any, context:
1220
1220
  };
1221
1221
  export declare const flags: {
1222
1222
  error500RetryCount: number;
1223
+ /** Client treats SSE as dead if no event for this long */
1224
+ sseStaleMs: number;
1225
+ /** HTTP keep-alive while SSE is healthy (touches lastUsed) */
1226
+ liveQueryKeepAliveMs: number;
1227
+ /** Version poll while SSE is stale */
1228
+ liveQueryPollWhenStaleMs: number;
1223
1229
  };
@@ -19,6 +19,12 @@ var wnn = [
19
19
  ];
20
20
  exports.flags = {
21
21
  error500RetryCount: 4,
22
+ /** Client treats SSE as dead if no event for this long */
23
+ sseStaleMs: 20000,
24
+ /** HTTP keep-alive while SSE is healthy (touches lastUsed) */
25
+ liveQueryKeepAliveMs: 30000,
26
+ /** Version poll while SSE is stale */
27
+ liveQueryPollWhenStaleMs: 1000,
22
28
  };
23
29
  /*p1 - issues in https://stackblitz.com/edit/demo-allow-delete-based-on-other-entity:
24
30
  - don't like the ensure schema