@kinotic-ai/core 1.3.0 → 1.4.0

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/dist/index.cjs CHANGED
@@ -80,7 +80,6 @@ __export(exports_src, {
80
80
  OffsetPageable: () => OffsetPageable,
81
81
  NullHandling: () => NullHandling,
82
82
  KinoticSingleton: () => KinoticSingleton,
83
- KinoticError: () => KinoticError,
84
83
  Kinotic: () => Kinotic,
85
84
  JsonEventFactory: () => JsonEventFactory,
86
85
  FunctionalIterablePage: () => FunctionalIterablePage,
@@ -97,8 +96,6 @@ __export(exports_src, {
97
96
  ConnectionInfo: () => ConnectionInfo,
98
97
  ConnectedInfo: () => ConnectedInfo,
99
98
  CONTEXT_METADATA_KEY: () => CONTEXT_METADATA_KEY,
100
- AuthorizationError: () => AuthorizationError,
101
- AuthenticationError: () => AuthenticationError,
102
99
  AbstractIterablePage: () => AbstractIterablePage
103
100
  });
104
101
  module.exports = __toCommonJS(exports_src);
@@ -121,14 +118,6 @@ class ConnectionInfo extends ServerInfo {
121
118
  maxConnectionAttempts;
122
119
  sessionKeepAlive = "ACTIVITY" /* ACTIVITY */;
123
120
  }
124
- // packages/core/src/api/errors/KinoticError.ts
125
- class KinoticError extends Error {
126
- constructor(message) {
127
- super(message);
128
- Object.setPrototypeOf(this, KinoticError.prototype);
129
- }
130
- }
131
-
132
121
  // packages/core/src/api/event/IEventBus.ts
133
122
  var EventConstants;
134
123
  ((EventConstants2) => {
@@ -161,32 +150,38 @@ var EventConstants;
161
150
  var import_rx_stomp = require("@stomp/rx-stomp");
162
151
  var import_stompjs = require("@stomp/stompjs");
163
152
  var import_debug = __toESM(require("debug"));
153
+ var import_rxjs = require("rxjs");
164
154
  var import_uuid = require("uuid");
165
155
 
166
156
  class StompConnectionManager {
167
157
  lastWebsocketError = null;
168
158
  maxConnectionAttemptsReached = false;
159
+ replyToCriChangedHandler = null;
169
160
  rxStomp = null;
170
161
  INITIAL_RECONNECT_DELAY = 2000;
171
- MAX_RECONNECT_DELAY = 120000;
172
162
  JITTER_MAX = 5000;
163
+ MAX_RECONNECT_DELAY = 120000;
173
164
  connectionAttempts = 0;
174
- initialConnectionSuccessful = false;
175
165
  debugLogger = import_debug.default("kinoitc:stomp");
166
+ fatalErrorsSubject = new import_rxjs.Subject;
167
+ _fatalErrors = this.fatalErrorsSubject.asObservable();
168
+ initialConnectionSuccessful = false;
169
+ serverHeadersSubscription = null;
170
+ stompErrorsSubscription = null;
176
171
  uuidv4 = import_uuid.v4();
177
172
  _replyToCri = null;
178
- serverHeadersSubscription = null;
179
- deactivationHandler = null;
180
- replyToCriChangedHandler = null;
181
- get active() {
182
- return !!this.rxStomp;
183
- }
184
173
  get replyToCri() {
185
174
  return this._replyToCri;
186
175
  }
176
+ get active() {
177
+ return !!this.rxStomp;
178
+ }
187
179
  get connected() {
188
180
  return this.rxStomp != null && this.rxStomp.connected();
189
181
  }
182
+ get fatalErrors() {
183
+ return this._fatalErrors;
184
+ }
190
185
  activate(connectionInfo) {
191
186
  return new Promise((resolve, reject) => {
192
187
  if (!connectionInfo) {
@@ -205,11 +200,10 @@ class StompConnectionManager {
205
200
  this.initialConnectionSuccessful = false;
206
201
  this.lastWebsocketError = null;
207
202
  this.maxConnectionAttemptsReached = false;
208
- this._replyToCri = null;
209
- this.serverHeadersSubscription?.unsubscribe();
210
- this.serverHeadersSubscription = null;
211
203
  const url = "ws" + (connectionInfo.useSSL ? "s" : "") + "://" + connectionInfo.host + (connectionInfo.port ? ":" + connectionInfo.port : "") + "/v1";
212
204
  this.rxStomp = new import_rx_stomp.RxStomp;
205
+ let preparedSocket = null;
206
+ const userWebSocketFactory = connectionInfo.webSocketFactory;
213
207
  const stompConfig = {
214
208
  brokerURL: url,
215
209
  connectHeaders: {
@@ -218,23 +212,29 @@ class StompConnectionManager {
218
212
  heartbeatIncoming: 120000,
219
213
  heartbeatOutgoing: 30000,
220
214
  reconnectDelay: this.INITIAL_RECONNECT_DELAY,
221
- webSocketFactory: connectionInfo.webSocketFactory,
215
+ maxReconnectDelay: this.MAX_RECONNECT_DELAY,
216
+ reconnectTimeMode: import_stompjs.ReconnectionTimeMode.EXPONENTIAL,
217
+ webSocketFactory: userWebSocketFactory ? () => preparedSocket : undefined,
222
218
  beforeConnect: async () => {
223
219
  if (connectionInfo?.maxConnectionAttempts) {
224
220
  this.connectionAttempts++;
225
221
  if (this.connectionAttempts > connectionInfo.maxConnectionAttempts) {
226
222
  this.maxConnectionAttemptsReached = true;
227
- await this.deactivate();
228
- if (!this.initialConnectionSuccessful) {
229
- let message = this.lastWebsocketError?.message ? this.lastWebsocketError?.message : "UNKNOWN";
230
- reject(`Max number of reconnection attempts reached. Last WS Error ${message}`);
231
- }
223
+ await this.signalFatal(new Error("Max number of reconnection attempts reached", { cause: this.lastWebsocketError ?? undefined }));
224
+ return;
232
225
  } else {
233
226
  await this.connectionJitterDelay();
234
227
  }
235
228
  } else {
236
229
  await this.connectionJitterDelay();
237
230
  }
231
+ if (userWebSocketFactory) {
232
+ try {
233
+ preparedSocket = await userWebSocketFactory();
234
+ } catch (e) {
235
+ await this.signalFatal(new Error("WebSocket factory failed", { cause: e }));
236
+ }
237
+ }
238
238
  }
239
239
  };
240
240
  if (this.debugLogger.enabled) {
@@ -243,42 +243,44 @@ class StompConnectionManager {
243
243
  };
244
244
  }
245
245
  this.rxStomp.configure(stompConfig);
246
- this.rxStomp.stompClient.maxReconnectDelay = this.MAX_RECONNECT_DELAY;
247
- this.rxStomp.stompClient.reconnectTimeMode = import_stompjs.ReconnectionTimeMode.EXPONENTIAL;
248
246
  this.rxStomp.webSocketErrors$.subscribe((value) => {
249
247
  this.lastWebsocketError = value;
250
248
  });
249
+ this.stompErrorsSubscription = this.rxStomp.stompErrors$.subscribe(async (frame) => {
250
+ const stompError = new Error(frame.headers["message"], { cause: frame });
251
+ await this.signalFatal(new Error("STOMP connection error", { cause: stompError }));
252
+ });
251
253
  const connectedSubscription = this.rxStomp.connected$.subscribe(() => {
252
254
  connectedSubscription.unsubscribe();
255
+ initialFailureSubscription.unsubscribe();
253
256
  if (!this.initialConnectionSuccessful) {
254
257
  this.initialConnectionSuccessful = true;
255
258
  }
256
259
  });
257
- const errorSubscription = this.rxStomp.stompErrors$.subscribe((value) => {
258
- errorSubscription.unsubscribe();
259
- const message = value.headers["message"];
260
- this.rxStomp?.deactivate();
261
- this.rxStomp = null;
262
- reject(message);
260
+ const initialFailureSubscription = this.fatalErrorsSubject.subscribe((err) => {
261
+ connectedSubscription.unsubscribe();
262
+ initialFailureSubscription.unsubscribe();
263
+ reject(err.message);
263
264
  });
264
- this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
265
+ this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe(async (value) => {
265
266
  const connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
266
- const firstConnect = this._replyToCri == null;
267
267
  if (connectedInfoJson == null) {
268
- if (firstConnect) {
268
+ if (!this.initialConnectionSuccessful) {
269
+ await this.deactivate();
269
270
  reject("Server did not return proper data for successful login");
270
271
  }
271
272
  return;
272
273
  }
273
274
  const connectedInfo = JSON.parse(connectedInfoJson);
274
275
  if (connectedInfo.replyToId == null) {
275
- if (firstConnect) {
276
+ if (!this.initialConnectionSuccessful) {
277
+ await this.deactivate();
276
278
  reject("Server did not return a replyToId for successful login");
277
279
  }
278
280
  return;
279
281
  }
280
282
  const newReplyToCri = "reply://" /* REPLY_DESTINATION_PREFIX */ + connectedInfo.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
281
- if (firstConnect) {
283
+ if (!this.initialConnectionSuccessful) {
282
284
  this._replyToCri = newReplyToCri;
283
285
  resolve(connectedInfo);
284
286
  } else if (this._replyToCri !== newReplyToCri) {
@@ -294,10 +296,10 @@ class StompConnectionManager {
294
296
  await this.rxStomp.deactivate({ force });
295
297
  this.serverHeadersSubscription?.unsubscribe();
296
298
  this.serverHeadersSubscription = null;
297
- if (this.deactivationHandler) {
298
- this.deactivationHandler();
299
- }
299
+ this.stompErrorsSubscription?.unsubscribe();
300
+ this.stompErrorsSubscription = null;
300
301
  this.rxStomp = null;
302
+ this._replyToCri = null;
301
303
  }
302
304
  return;
303
305
  }
@@ -308,11 +310,18 @@ class StompConnectionManager {
308
310
  return new Promise((resolve) => setTimeout(resolve, randomJitter));
309
311
  }
310
312
  }
313
+ async signalFatal(err) {
314
+ if (console) {
315
+ console.error("StompConnectionManager fatal error, deactivating connection", err);
316
+ }
317
+ await this.deactivate();
318
+ this.fatalErrorsSubject.next(err);
319
+ }
311
320
  }
312
321
 
313
322
  // packages/core/src/api/event/EventBus.ts
314
323
  var import_api = require("@opentelemetry/api");
315
- var import_rxjs = require("rxjs");
324
+ var import_rxjs2 = require("rxjs");
316
325
  var import_operators = require("rxjs/operators");
317
326
  var import_typescript_optional = require("typescript-optional");
318
327
  var import_uuid2 = require("uuid");
@@ -353,32 +362,22 @@ class Event {
353
362
  }
354
363
 
355
364
  class EventBus {
356
- fatalErrors;
357
365
  serverInfo = null;
358
366
  stompConnectionManager = new StompConnectionManager;
359
367
  replyToCri = null;
360
368
  requestRepliesObservable = null;
361
369
  requestRepliesSubject = null;
362
370
  requestRepliesSubscription = null;
363
- errorSubject = new import_rxjs.Subject;
364
- errorSubjectSubscription = null;
365
371
  constructor() {
366
- this.fatalErrors = this.errorSubject.pipe(import_operators.map((frame) => {
367
- this.disconnect().catch((error) => {
368
- if (console) {
369
- console.error("Error disconnecting from Stomp: " + error);
370
- }
371
- });
372
- return new KinoticError(frame.headers["message"]);
373
- }));
374
- this.stompConnectionManager.deactivationHandler = () => {
375
- this.cleanup();
376
- };
372
+ this.stompConnectionManager.fatalErrors.subscribe(() => this.cleanup());
377
373
  this.stompConnectionManager.replyToCriChangedHandler = (replyToCri) => {
378
374
  this.replyToCri = replyToCri;
379
375
  this.resetRequestReplies("Reply destination changed");
380
376
  };
381
377
  }
378
+ get fatalErrors() {
379
+ return this.stompConnectionManager.fatalErrors;
380
+ }
382
381
  isConnectionActive() {
383
382
  return this.stompConnectionManager.active;
384
383
  }
@@ -394,7 +393,6 @@ class EventBus {
394
393
  this.serverInfo.port = connectionInfo.port;
395
394
  this.serverInfo.useSSL = connectionInfo.useSSL;
396
395
  this.replyToCri = this.stompConnectionManager.replyToCri;
397
- this.errorSubjectSubscription = this.stompConnectionManager.rxStomp?.stompErrors$.subscribe(this.errorSubject);
398
396
  return connectedInfo;
399
397
  } else {
400
398
  throw new Error("Event Bus connection already active");
@@ -428,13 +426,13 @@ class EventBus {
428
426
  }
429
427
  }
430
428
  request(event) {
431
- return import_rxjs.firstValueFrom(this.requestStream(event, false));
429
+ return import_rxjs2.firstValueFrom(this.requestStream(event, false));
432
430
  }
433
431
  requestStream(event, sendControlEvents = true) {
434
432
  if (this.stompConnectionManager?.rxStomp) {
435
- return new import_rxjs.Observable((subscriber) => {
433
+ return new import_rxjs2.Observable((subscriber) => {
436
434
  if (this.requestRepliesObservable == null) {
437
- this.requestRepliesSubject = new import_rxjs.Subject;
435
+ this.requestRepliesSubject = new import_rxjs2.Subject;
438
436
  this.requestRepliesObservable = this._observe(this.replyToCri).pipe(import_operators.multicast(this.requestRepliesSubject));
439
437
  this.requestRepliesSubscription = this.requestRepliesObservable.connect();
440
438
  }
@@ -479,7 +477,7 @@ class EventBus {
479
477
  };
480
478
  });
481
479
  } else {
482
- return import_rxjs.throwError(() => this.createSendUnavailableError());
480
+ return import_rxjs2.throwError(() => this.createSendUnavailableError());
483
481
  }
484
482
  }
485
483
  listen(_serverInfo) {
@@ -490,10 +488,6 @@ class EventBus {
490
488
  }
491
489
  cleanup() {
492
490
  this.resetRequestReplies("Connection disconnected");
493
- if (this.errorSubjectSubscription) {
494
- this.errorSubjectSubscription.unsubscribe();
495
- this.errorSubjectSubscription = null;
496
- }
497
491
  this.serverInfo = null;
498
492
  }
499
493
  resetRequestReplies(reason) {
@@ -989,7 +983,7 @@ var import_operators2 = require("rxjs/operators");
989
983
  // packages/core/package.json
990
984
  var package_default = {
991
985
  name: "@kinotic-ai/core",
992
- version: "1.3.0",
986
+ version: "1.4.0",
993
987
  type: "module",
994
988
  files: [
995
989
  "dist"
@@ -1467,20 +1461,6 @@ class Order {
1467
1461
  class Sort {
1468
1462
  orders = [];
1469
1463
  }
1470
- // packages/core/src/api/errors/AuthenticationError.ts
1471
- class AuthenticationError extends KinoticError {
1472
- constructor(message) {
1473
- super(message);
1474
- Object.setPrototypeOf(this, AuthenticationError.prototype);
1475
- }
1476
- }
1477
- // packages/core/src/api/errors/AuthorizationError.ts
1478
- class AuthorizationError extends KinoticError {
1479
- constructor(message) {
1480
- super(message);
1481
- Object.setPrototypeOf(this, AuthorizationError.prototype);
1482
- }
1483
- }
1484
1464
  // packages/core/src/api/security/ConnectedInfo.ts
1485
1465
  class ConnectedInfo {
1486
1466
  replyToId;
package/dist/index.d.cts CHANGED
@@ -17,11 +17,12 @@ interface IWebSocket {
17
17
  /**
18
18
  * Factory invoked on every (re)connect to produce the WebSocket the STOMP
19
19
  * client will use. Supply this in Node when you need to set headers on the
20
- * upgrade request (for example, an Authorization header). Browser callers
21
- * normally leave this unset and rely on the session cookie established by a
22
- * prior REST login.
20
+ * upgrade request (for example, an Authorization header). It may be async —
21
+ * for example, to refresh a short-lived access token before each connect.
22
+ * Browser callers normally leave this unset and rely on the session cookie
23
+ * established by a prior REST login.
23
24
  */
24
- type WebSocketFactory = () => IWebSocket;
25
+ type WebSocketFactory = () => IWebSocket | Promise<IWebSocket>;
25
26
  declare class ServerInfo {
26
27
  host: string;
27
28
  port?: number | null;
@@ -132,12 +133,6 @@ declare class ConnectedInfo {
132
133
  participant: Participant;
133
134
  }
134
135
  /**
135
- * Base error class for all Kinoitc errors
136
- */
137
- declare class KinoticError extends Error {
138
- constructor(message: string);
139
- }
140
- /**
141
136
  * Part of the low level portion of kinoitc representing data to be processed
142
137
  *
143
138
  * This is similar to a Stomp Frame but with more required information and no control plane semantics.
@@ -204,7 +199,7 @@ interface IEventBus {
204
199
  * Any errors emitted by this observable will be fatal and the connection will be closed.
205
200
  * You will need to resolve the problem and reconnect.
206
201
  */
207
- fatalErrors: Observable<KinoticError>;
202
+ fatalErrors: Observable<Error>;
208
203
  /**
209
204
  * The {@link ServerInfo} used when connecting, if connected or null
210
205
  */
@@ -1035,12 +1030,6 @@ declare class FunctionalIterablePage<T> extends AbstractIterablePage<T> {
1035
1030
  constructor(pageable: Pageable, page: Page<T>, pageFunction: (pageable: Pageable) => Promise<Page<T>>);
1036
1031
  protected findNext(pageable: Pageable): Promise<Page<T>>;
1037
1032
  }
1038
- declare class AuthenticationError extends KinoticError {
1039
- constructor(message: string);
1040
- }
1041
- declare class AuthorizationError extends KinoticError {
1042
- constructor(message: string);
1043
- }
1044
1033
  /**
1045
1034
  * Default implementation of the `CRI` interface.
1046
1035
  *
@@ -1093,16 +1082,14 @@ declare class Event implements IEvent {
1093
1082
  * Default implementation of {@link IEventBus}
1094
1083
  */
1095
1084
  declare class EventBus implements IEventBus {
1096
- fatalErrors: Observable3<Error>;
1097
1085
  serverInfo: ServerInfo | null;
1098
1086
  private stompConnectionManager;
1099
1087
  private replyToCri;
1100
1088
  private requestRepliesObservable;
1101
1089
  private requestRepliesSubject;
1102
1090
  private requestRepliesSubscription;
1103
- private errorSubject;
1104
- private errorSubjectSubscription;
1105
1091
  constructor();
1092
+ get fatalErrors(): Observable3<Error>;
1106
1093
  isConnectionActive(): boolean;
1107
1094
  isConnected(): boolean;
1108
1095
  connect(connectionInfo: ConnectionInfo): Promise<ConnectedInfo>;
@@ -1143,4 +1130,4 @@ declare class ParticipantConstants {
1143
1130
  static readonly PARTICIPANT_TYPE_NODE: string;
1144
1131
  static readonly CLI_PARTICIPANT_ID: string;
1145
1132
  }
1146
- export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
1133
+ export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AbstractIterablePage };
package/dist/index.d.ts CHANGED
@@ -17,11 +17,12 @@ interface IWebSocket {
17
17
  /**
18
18
  * Factory invoked on every (re)connect to produce the WebSocket the STOMP
19
19
  * client will use. Supply this in Node when you need to set headers on the
20
- * upgrade request (for example, an Authorization header). Browser callers
21
- * normally leave this unset and rely on the session cookie established by a
22
- * prior REST login.
20
+ * upgrade request (for example, an Authorization header). It may be async —
21
+ * for example, to refresh a short-lived access token before each connect.
22
+ * Browser callers normally leave this unset and rely on the session cookie
23
+ * established by a prior REST login.
23
24
  */
24
- type WebSocketFactory = () => IWebSocket;
25
+ type WebSocketFactory = () => IWebSocket | Promise<IWebSocket>;
25
26
  declare class ServerInfo {
26
27
  host: string;
27
28
  port?: number | null;
@@ -132,12 +133,6 @@ declare class ConnectedInfo {
132
133
  participant: Participant;
133
134
  }
134
135
  /**
135
- * Base error class for all Kinoitc errors
136
- */
137
- declare class KinoticError extends Error {
138
- constructor(message: string);
139
- }
140
- /**
141
136
  * Part of the low level portion of kinoitc representing data to be processed
142
137
  *
143
138
  * This is similar to a Stomp Frame but with more required information and no control plane semantics.
@@ -204,7 +199,7 @@ interface IEventBus {
204
199
  * Any errors emitted by this observable will be fatal and the connection will be closed.
205
200
  * You will need to resolve the problem and reconnect.
206
201
  */
207
- fatalErrors: Observable<KinoticError>;
202
+ fatalErrors: Observable<Error>;
208
203
  /**
209
204
  * The {@link ServerInfo} used when connecting, if connected or null
210
205
  */
@@ -1035,12 +1030,6 @@ declare class FunctionalIterablePage<T> extends AbstractIterablePage<T> {
1035
1030
  constructor(pageable: Pageable, page: Page<T>, pageFunction: (pageable: Pageable) => Promise<Page<T>>);
1036
1031
  protected findNext(pageable: Pageable): Promise<Page<T>>;
1037
1032
  }
1038
- declare class AuthenticationError extends KinoticError {
1039
- constructor(message: string);
1040
- }
1041
- declare class AuthorizationError extends KinoticError {
1042
- constructor(message: string);
1043
- }
1044
1033
  /**
1045
1034
  * Default implementation of the `CRI` interface.
1046
1035
  *
@@ -1093,16 +1082,14 @@ declare class Event implements IEvent {
1093
1082
  * Default implementation of {@link IEventBus}
1094
1083
  */
1095
1084
  declare class EventBus implements IEventBus {
1096
- fatalErrors: Observable3<Error>;
1097
1085
  serverInfo: ServerInfo | null;
1098
1086
  private stompConnectionManager;
1099
1087
  private replyToCri;
1100
1088
  private requestRepliesObservable;
1101
1089
  private requestRepliesSubject;
1102
1090
  private requestRepliesSubscription;
1103
- private errorSubject;
1104
- private errorSubjectSubscription;
1105
1091
  constructor();
1092
+ get fatalErrors(): Observable3<Error>;
1106
1093
  isConnectionActive(): boolean;
1107
1094
  isConnected(): boolean;
1108
1095
  connect(connectionInfo: ConnectionInfo): Promise<ConnectedInfo>;
@@ -1143,4 +1130,4 @@ declare class ParticipantConstants {
1143
1130
  static readonly PARTICIPANT_TYPE_NODE: string;
1144
1131
  static readonly CLI_PARTICIPANT_ID: string;
1145
1132
  }
1146
- export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, KinoticError, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AuthorizationError, AuthenticationError, AbstractIterablePage };
1133
+ export { createCRI, WebSocketFactory, Version, TextEventFactory, Sort, SessionKeepAliveMode, ServiceRegistry, ServiceContext, ServerInfo, Scope, Publish, ParticipantConstants, Participant, Pageable, Page, Order, OffsetPageable, NullHandling, KinoticSingleton, KinoticPlugin, Kinotic, JsonEventFactory, IterablePage, Identifiable, IWebSocket, IServiceRegistry, IServiceProxy, IParticipant, IKinotic, IEventFactory, IEventBus, IEvent, IEditableDataSource, IDataSource, ICrudServiceProxyFactory, ICrudServiceProxy, FunctionalIterablePage, EventConstants, EventBus, Event, Direction, DefaultCRI, DataSourceUtils, CursorPageable, CrudServiceProxyFactory, CrudServiceProxy, ContextInterceptor, Context, ConnectionInfo, ConnectedInfo, CRI, CONTEXT_METADATA_KEY, AbstractIterablePage };
package/dist/index.js CHANGED
@@ -19,14 +19,6 @@ class ConnectionInfo extends ServerInfo {
19
19
  maxConnectionAttempts;
20
20
  sessionKeepAlive = "ACTIVITY" /* ACTIVITY */;
21
21
  }
22
- // packages/core/src/api/errors/KinoticError.ts
23
- class KinoticError extends Error {
24
- constructor(message) {
25
- super(message);
26
- Object.setPrototypeOf(this, KinoticError.prototype);
27
- }
28
- }
29
-
30
22
  // packages/core/src/api/event/IEventBus.ts
31
23
  var EventConstants;
32
24
  ((EventConstants2) => {
@@ -59,32 +51,38 @@ var EventConstants;
59
51
  import { RxStomp } from "@stomp/rx-stomp";
60
52
  import { ReconnectionTimeMode } from "@stomp/stompjs";
61
53
  import debug from "debug";
54
+ import { Subject } from "rxjs";
62
55
  import { v4 as uuidv4 } from "uuid";
63
56
 
64
57
  class StompConnectionManager {
65
58
  lastWebsocketError = null;
66
59
  maxConnectionAttemptsReached = false;
60
+ replyToCriChangedHandler = null;
67
61
  rxStomp = null;
68
62
  INITIAL_RECONNECT_DELAY = 2000;
69
- MAX_RECONNECT_DELAY = 120000;
70
63
  JITTER_MAX = 5000;
64
+ MAX_RECONNECT_DELAY = 120000;
71
65
  connectionAttempts = 0;
72
- initialConnectionSuccessful = false;
73
66
  debugLogger = debug("kinoitc:stomp");
67
+ fatalErrorsSubject = new Subject;
68
+ _fatalErrors = this.fatalErrorsSubject.asObservable();
69
+ initialConnectionSuccessful = false;
70
+ serverHeadersSubscription = null;
71
+ stompErrorsSubscription = null;
74
72
  uuidv4 = uuidv4();
75
73
  _replyToCri = null;
76
- serverHeadersSubscription = null;
77
- deactivationHandler = null;
78
- replyToCriChangedHandler = null;
79
- get active() {
80
- return !!this.rxStomp;
81
- }
82
74
  get replyToCri() {
83
75
  return this._replyToCri;
84
76
  }
77
+ get active() {
78
+ return !!this.rxStomp;
79
+ }
85
80
  get connected() {
86
81
  return this.rxStomp != null && this.rxStomp.connected();
87
82
  }
83
+ get fatalErrors() {
84
+ return this._fatalErrors;
85
+ }
88
86
  activate(connectionInfo) {
89
87
  return new Promise((resolve, reject) => {
90
88
  if (!connectionInfo) {
@@ -103,11 +101,10 @@ class StompConnectionManager {
103
101
  this.initialConnectionSuccessful = false;
104
102
  this.lastWebsocketError = null;
105
103
  this.maxConnectionAttemptsReached = false;
106
- this._replyToCri = null;
107
- this.serverHeadersSubscription?.unsubscribe();
108
- this.serverHeadersSubscription = null;
109
104
  const url = "ws" + (connectionInfo.useSSL ? "s" : "") + "://" + connectionInfo.host + (connectionInfo.port ? ":" + connectionInfo.port : "") + "/v1";
110
105
  this.rxStomp = new RxStomp;
106
+ let preparedSocket = null;
107
+ const userWebSocketFactory = connectionInfo.webSocketFactory;
111
108
  const stompConfig = {
112
109
  brokerURL: url,
113
110
  connectHeaders: {
@@ -116,23 +113,29 @@ class StompConnectionManager {
116
113
  heartbeatIncoming: 120000,
117
114
  heartbeatOutgoing: 30000,
118
115
  reconnectDelay: this.INITIAL_RECONNECT_DELAY,
119
- webSocketFactory: connectionInfo.webSocketFactory,
116
+ maxReconnectDelay: this.MAX_RECONNECT_DELAY,
117
+ reconnectTimeMode: ReconnectionTimeMode.EXPONENTIAL,
118
+ webSocketFactory: userWebSocketFactory ? () => preparedSocket : undefined,
120
119
  beforeConnect: async () => {
121
120
  if (connectionInfo?.maxConnectionAttempts) {
122
121
  this.connectionAttempts++;
123
122
  if (this.connectionAttempts > connectionInfo.maxConnectionAttempts) {
124
123
  this.maxConnectionAttemptsReached = true;
125
- await this.deactivate();
126
- if (!this.initialConnectionSuccessful) {
127
- let message = this.lastWebsocketError?.message ? this.lastWebsocketError?.message : "UNKNOWN";
128
- reject(`Max number of reconnection attempts reached. Last WS Error ${message}`);
129
- }
124
+ await this.signalFatal(new Error("Max number of reconnection attempts reached", { cause: this.lastWebsocketError ?? undefined }));
125
+ return;
130
126
  } else {
131
127
  await this.connectionJitterDelay();
132
128
  }
133
129
  } else {
134
130
  await this.connectionJitterDelay();
135
131
  }
132
+ if (userWebSocketFactory) {
133
+ try {
134
+ preparedSocket = await userWebSocketFactory();
135
+ } catch (e) {
136
+ await this.signalFatal(new Error("WebSocket factory failed", { cause: e }));
137
+ }
138
+ }
136
139
  }
137
140
  };
138
141
  if (this.debugLogger.enabled) {
@@ -141,42 +144,44 @@ class StompConnectionManager {
141
144
  };
142
145
  }
143
146
  this.rxStomp.configure(stompConfig);
144
- this.rxStomp.stompClient.maxReconnectDelay = this.MAX_RECONNECT_DELAY;
145
- this.rxStomp.stompClient.reconnectTimeMode = ReconnectionTimeMode.EXPONENTIAL;
146
147
  this.rxStomp.webSocketErrors$.subscribe((value) => {
147
148
  this.lastWebsocketError = value;
148
149
  });
150
+ this.stompErrorsSubscription = this.rxStomp.stompErrors$.subscribe(async (frame) => {
151
+ const stompError = new Error(frame.headers["message"], { cause: frame });
152
+ await this.signalFatal(new Error("STOMP connection error", { cause: stompError }));
153
+ });
149
154
  const connectedSubscription = this.rxStomp.connected$.subscribe(() => {
150
155
  connectedSubscription.unsubscribe();
156
+ initialFailureSubscription.unsubscribe();
151
157
  if (!this.initialConnectionSuccessful) {
152
158
  this.initialConnectionSuccessful = true;
153
159
  }
154
160
  });
155
- const errorSubscription = this.rxStomp.stompErrors$.subscribe((value) => {
156
- errorSubscription.unsubscribe();
157
- const message = value.headers["message"];
158
- this.rxStomp?.deactivate();
159
- this.rxStomp = null;
160
- reject(message);
161
+ const initialFailureSubscription = this.fatalErrorsSubject.subscribe((err) => {
162
+ connectedSubscription.unsubscribe();
163
+ initialFailureSubscription.unsubscribe();
164
+ reject(err.message);
161
165
  });
162
- this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe((value) => {
166
+ this.serverHeadersSubscription = this.rxStomp.serverHeaders$.subscribe(async (value) => {
163
167
  const connectedInfoJson = value["connected-info" /* CONNECTED_INFO_HEADER */];
164
- const firstConnect = this._replyToCri == null;
165
168
  if (connectedInfoJson == null) {
166
- if (firstConnect) {
169
+ if (!this.initialConnectionSuccessful) {
170
+ await this.deactivate();
167
171
  reject("Server did not return proper data for successful login");
168
172
  }
169
173
  return;
170
174
  }
171
175
  const connectedInfo = JSON.parse(connectedInfoJson);
172
176
  if (connectedInfo.replyToId == null) {
173
- if (firstConnect) {
177
+ if (!this.initialConnectionSuccessful) {
178
+ await this.deactivate();
174
179
  reject("Server did not return a replyToId for successful login");
175
180
  }
176
181
  return;
177
182
  }
178
183
  const newReplyToCri = "reply://" /* REPLY_DESTINATION_PREFIX */ + connectedInfo.replyToId + ":" + this.uuidv4 + "@kinoitc.js.EventBus/replyHandler";
179
- if (firstConnect) {
184
+ if (!this.initialConnectionSuccessful) {
180
185
  this._replyToCri = newReplyToCri;
181
186
  resolve(connectedInfo);
182
187
  } else if (this._replyToCri !== newReplyToCri) {
@@ -192,10 +197,10 @@ class StompConnectionManager {
192
197
  await this.rxStomp.deactivate({ force });
193
198
  this.serverHeadersSubscription?.unsubscribe();
194
199
  this.serverHeadersSubscription = null;
195
- if (this.deactivationHandler) {
196
- this.deactivationHandler();
197
- }
200
+ this.stompErrorsSubscription?.unsubscribe();
201
+ this.stompErrorsSubscription = null;
198
202
  this.rxStomp = null;
203
+ this._replyToCri = null;
199
204
  }
200
205
  return;
201
206
  }
@@ -206,11 +211,18 @@ class StompConnectionManager {
206
211
  return new Promise((resolve) => setTimeout(resolve, randomJitter));
207
212
  }
208
213
  }
214
+ async signalFatal(err) {
215
+ if (console) {
216
+ console.error("StompConnectionManager fatal error, deactivating connection", err);
217
+ }
218
+ await this.deactivate();
219
+ this.fatalErrorsSubject.next(err);
220
+ }
209
221
  }
210
222
 
211
223
  // packages/core/src/api/event/EventBus.ts
212
224
  import { context, propagation } from "@opentelemetry/api";
213
- import { firstValueFrom, Observable, Subject, throwError } from "rxjs";
225
+ import { firstValueFrom, Observable as Observable2, Subject as Subject2, throwError } from "rxjs";
214
226
  import { filter, map, multicast } from "rxjs/operators";
215
227
  import { Optional } from "typescript-optional";
216
228
  import { v4 as uuidv42 } from "uuid";
@@ -251,32 +263,22 @@ class Event {
251
263
  }
252
264
 
253
265
  class EventBus {
254
- fatalErrors;
255
266
  serverInfo = null;
256
267
  stompConnectionManager = new StompConnectionManager;
257
268
  replyToCri = null;
258
269
  requestRepliesObservable = null;
259
270
  requestRepliesSubject = null;
260
271
  requestRepliesSubscription = null;
261
- errorSubject = new Subject;
262
- errorSubjectSubscription = null;
263
272
  constructor() {
264
- this.fatalErrors = this.errorSubject.pipe(map((frame) => {
265
- this.disconnect().catch((error) => {
266
- if (console) {
267
- console.error("Error disconnecting from Stomp: " + error);
268
- }
269
- });
270
- return new KinoticError(frame.headers["message"]);
271
- }));
272
- this.stompConnectionManager.deactivationHandler = () => {
273
- this.cleanup();
274
- };
273
+ this.stompConnectionManager.fatalErrors.subscribe(() => this.cleanup());
275
274
  this.stompConnectionManager.replyToCriChangedHandler = (replyToCri) => {
276
275
  this.replyToCri = replyToCri;
277
276
  this.resetRequestReplies("Reply destination changed");
278
277
  };
279
278
  }
279
+ get fatalErrors() {
280
+ return this.stompConnectionManager.fatalErrors;
281
+ }
280
282
  isConnectionActive() {
281
283
  return this.stompConnectionManager.active;
282
284
  }
@@ -292,7 +294,6 @@ class EventBus {
292
294
  this.serverInfo.port = connectionInfo.port;
293
295
  this.serverInfo.useSSL = connectionInfo.useSSL;
294
296
  this.replyToCri = this.stompConnectionManager.replyToCri;
295
- this.errorSubjectSubscription = this.stompConnectionManager.rxStomp?.stompErrors$.subscribe(this.errorSubject);
296
297
  return connectedInfo;
297
298
  } else {
298
299
  throw new Error("Event Bus connection already active");
@@ -330,9 +331,9 @@ class EventBus {
330
331
  }
331
332
  requestStream(event, sendControlEvents = true) {
332
333
  if (this.stompConnectionManager?.rxStomp) {
333
- return new Observable((subscriber) => {
334
+ return new Observable2((subscriber) => {
334
335
  if (this.requestRepliesObservable == null) {
335
- this.requestRepliesSubject = new Subject;
336
+ this.requestRepliesSubject = new Subject2;
336
337
  this.requestRepliesObservable = this._observe(this.replyToCri).pipe(multicast(this.requestRepliesSubject));
337
338
  this.requestRepliesSubscription = this.requestRepliesObservable.connect();
338
339
  }
@@ -388,10 +389,6 @@ class EventBus {
388
389
  }
389
390
  cleanup() {
390
391
  this.resetRequestReplies("Connection disconnected");
391
- if (this.errorSubjectSubscription) {
392
- this.errorSubjectSubscription.unsubscribe();
393
- this.errorSubjectSubscription = null;
394
- }
395
392
  this.serverInfo = null;
396
393
  }
397
394
  resetRequestReplies(reason) {
@@ -890,7 +887,7 @@ import { first, map as map2 } from "rxjs/operators";
890
887
  // packages/core/package.json
891
888
  var package_default = {
892
889
  name: "@kinotic-ai/core",
893
- version: "1.3.0",
890
+ version: "1.4.0",
894
891
  type: "module",
895
892
  files: [
896
893
  "dist"
@@ -1368,20 +1365,6 @@ class Order {
1368
1365
  class Sort {
1369
1366
  orders = [];
1370
1367
  }
1371
- // packages/core/src/api/errors/AuthenticationError.ts
1372
- class AuthenticationError extends KinoticError {
1373
- constructor(message) {
1374
- super(message);
1375
- Object.setPrototypeOf(this, AuthenticationError.prototype);
1376
- }
1377
- }
1378
- // packages/core/src/api/errors/AuthorizationError.ts
1379
- class AuthorizationError extends KinoticError {
1380
- constructor(message) {
1381
- super(message);
1382
- Object.setPrototypeOf(this, AuthorizationError.prototype);
1383
- }
1384
- }
1385
1368
  // packages/core/src/api/security/ConnectedInfo.ts
1386
1369
  class ConnectedInfo {
1387
1370
  replyToId;
@@ -1430,7 +1413,6 @@ export {
1430
1413
  OffsetPageable,
1431
1414
  NullHandling,
1432
1415
  KinoticSingleton,
1433
- KinoticError,
1434
1416
  Kinotic,
1435
1417
  JsonEventFactory,
1436
1418
  FunctionalIterablePage,
@@ -1447,7 +1429,5 @@ export {
1447
1429
  ConnectionInfo,
1448
1430
  ConnectedInfo,
1449
1431
  CONTEXT_METADATA_KEY,
1450
- AuthorizationError,
1451
- AuthenticationError,
1452
1432
  AbstractIterablePage
1453
1433
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinotic-ai/core",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"