@kosdev-code/kos-ui-sdk 3.0.9 → 3.0.11

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/index.cjs CHANGED
@@ -2915,6 +2915,29 @@ const isKosModelReady = (model) => {
2915
2915
  }
2916
2916
  return kosModel2.isReady();
2917
2917
  };
2918
+ const getKosModelActivationState = (model) => {
2919
+ if (!model?.id) {
2920
+ return void 0;
2921
+ }
2922
+ const kosModel2 = KosCore.getInstance().modelManager.getModelById(model.id);
2923
+ if (!kosModel2) {
2924
+ return void 0;
2925
+ }
2926
+ return {
2927
+ get activeStatus() {
2928
+ return kosModel2.activeStatus;
2929
+ },
2930
+ get isActivating() {
2931
+ return kosModel2.activeStatus === KosModelState.ACTIVATING;
2932
+ },
2933
+ get isActive() {
2934
+ return kosModel2.activeStatus === KosModelState.ACTIVE;
2935
+ },
2936
+ get isFailed() {
2937
+ return kosModel2.activeStatus === KosModelState.FAILED;
2938
+ }
2939
+ };
2940
+ };
2918
2941
  const log$L = KosLog.createLogger({ name: "kos-model-factory" });
2919
2942
  const KosModelFactory = {
2920
2943
  byModelType: (modelType) => KosCore.getInstance().modelManager.getModelFactory(
@@ -6339,8 +6362,38 @@ const getMessageBody = (payload, skipParse) => {
6339
6362
  return payload.body || payload;
6340
6363
  }
6341
6364
  };
6365
+ const TRANSPORT_NOT_READY = "TRANSPORT_NOT_READY";
6366
+ class TransportNotReadyError extends Error {
6367
+ /** Stable discriminator; see {@link TRANSPORT_NOT_READY}. */
6368
+ code = TRANSPORT_NOT_READY;
6369
+ /**
6370
+ * The budget, in milliseconds, that expired while waiting. `undefined` when
6371
+ * the wait was cut short by a caller-supplied timeout signal rather than by
6372
+ * the request's own budget.
6373
+ */
6374
+ timeout;
6375
+ /** The URL of the request that was never sent. */
6376
+ url;
6377
+ constructor(details = {}) {
6378
+ super(
6379
+ `Transport not ready${details.timeout === void 0 ? "" : ` after ${details.timeout}ms`}${details.url ? ` - url: ${details.url}` : ""}`
6380
+ );
6381
+ this.name = "TransportNotReadyError";
6382
+ this.timeout = details.timeout;
6383
+ this.url = details.url;
6384
+ }
6385
+ }
6386
+ const isTransportNotReadyError = (error) => !!error && error.code === TRANSPORT_NOT_READY;
6342
6387
  const log$D = KosLog.createLogger({ name: "kos-fetch" });
6343
- const WS_TIMEOUT = process.env.KOS_WS_TIMEOUT ? parseInt(process.env.KOS_WS_TIMEOUT) : 3e4;
6388
+ const DEFAULT_WS_TIMEOUT = 3e4;
6389
+ const WS_TIMEOUT = (() => {
6390
+ const configured = process.env.KOS_WS_TIMEOUT ? parseInt(process.env.KOS_WS_TIMEOUT) : DEFAULT_WS_TIMEOUT;
6391
+ return Number.isNaN(configured) ? DEFAULT_WS_TIMEOUT : configured;
6392
+ })();
6393
+ const resolveTimeoutBudget = (timeout) => {
6394
+ const budget = timeout ?? WS_TIMEOUT;
6395
+ return Number.isFinite(budget) && budget > 0 ? budget : void 0;
6396
+ };
6344
6397
  const delay = () => new Promise((resolve) => {
6345
6398
  setTimeout(() => {
6346
6399
  resolve(true);
@@ -6374,6 +6427,14 @@ const combineSignals = (signals) => {
6374
6427
  }
6375
6428
  return controller.signal;
6376
6429
  };
6430
+ const createRequestSignal = (budget, callerSignal) => {
6431
+ const timeoutSignal = budget ? createTimeoutSignal(budget) : void 0;
6432
+ if (timeoutSignal && callerSignal) {
6433
+ return combineSignals([callerSignal, timeoutSignal]);
6434
+ }
6435
+ return timeoutSignal ?? callerSignal ?? new AbortController().signal;
6436
+ };
6437
+ const isTimeoutReason = (reason) => reason?.name === "TimeoutError";
6377
6438
  const fetchMessageFactory = (options) => {
6378
6439
  if (options?.studio) {
6379
6440
  return createStudioMessage;
@@ -6385,15 +6446,25 @@ const fetchMessageFactory = (options) => {
6385
6446
  };
6386
6447
  const kosFetchWs = async (url, options) => {
6387
6448
  const transport = KosCore.getInstance().transport;
6388
- await transport.whenReady();
6389
6449
  const requestId = uuid();
6390
6450
  const urlObj = new URL(url);
6391
6451
  const path = `${urlObj.pathname}${urlObj.search}`;
6392
6452
  log$D.debug(`path: ${path}`);
6393
- const TIMEOUT = options?.timeout || WS_TIMEOUT;
6453
+ const budget = resolveTimeoutBudget(options?.timeout);
6394
6454
  const messageFactory = fetchMessageFactory(options);
6395
- const timeoutSignal = createTimeoutSignal(TIMEOUT);
6396
- const signal = options?.signal ? combineSignals([options.signal, timeoutSignal]) : timeoutSignal;
6455
+ const signal = createRequestSignal(budget, options?.signal);
6456
+ try {
6457
+ await transport.whenReady({ signal });
6458
+ } catch (error) {
6459
+ if (isTimeoutReason(error)) {
6460
+ log$D.error(
6461
+ `Transport not ready${budget === void 0 ? "" : ` after ${budget}ms`} - request not sent - url: ${url}`
6462
+ );
6463
+ throw new TransportNotReadyError({ timeout: budget, url });
6464
+ }
6465
+ log$D.debug(`Request aborted while waiting for transport - url: ${url}`);
6466
+ throw error;
6467
+ }
6397
6468
  const processedBody = await processRequestBody(options?.body);
6398
6469
  const additionalHeaders = {};
6399
6470
  if (processedBody.contentType) {
@@ -6556,6 +6627,7 @@ const resolveBaseUrl = () => {
6556
6627
  };
6557
6628
  const log$C = KosLog.createLogger({ name: "kos-service-request" });
6558
6629
  const ERROR_UNKNOWN = "errUnknown";
6630
+ const ERROR_TRANSPORT_NOT_READY = "Transport not ready";
6559
6631
  const MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
6560
6632
  const RETRY_DEFAULTS = {
6561
6633
  maxAttempts: 3,
@@ -6605,6 +6677,10 @@ async function executeFetch(fullUrl, fetchOptions) {
6605
6677
  const payload = await response.json();
6606
6678
  return [null, payload.data ?? payload];
6607
6679
  } catch (error) {
6680
+ if (isTransportNotReadyError(error)) {
6681
+ log$C.error(`Transport not ready, request not sent: ${fullUrl}`);
6682
+ return [ERROR_TRANSPORT_NOT_READY, null];
6683
+ }
6608
6684
  if (error instanceof DOMException) {
6609
6685
  if (error.name === "TimeoutError") {
6610
6686
  log$C.error(`Request timed out: ${fullUrl}`);
@@ -6648,6 +6724,10 @@ async function executeFetchWithRetry(fullUrl, fetchOptions, config2) {
6648
6724
  await new Promise((resolve) => setTimeout(resolve, delayMs));
6649
6725
  }
6650
6726
  } catch (error) {
6727
+ if (isTransportNotReadyError(error)) {
6728
+ log$C.error(`Transport not ready, request not sent: ${fullUrl}`);
6729
+ return [ERROR_TRANSPORT_NOT_READY, null];
6730
+ }
6651
6731
  if (error instanceof DOMException) {
6652
6732
  if (error.name === "TimeoutError") {
6653
6733
  log$C.error(`Request timed out: ${fullUrl}`);
@@ -7459,6 +7539,38 @@ class FlowControlManager {
7459
7539
  return { ...this.stats };
7460
7540
  }
7461
7541
  }
7542
+ const abortReason = (signal) => signal.reason ?? new DOMException("Aborted", "AbortError");
7543
+ const abortableWhen = async (predicate, signal) => {
7544
+ if (predicate()) {
7545
+ return;
7546
+ }
7547
+ if (!signal) {
7548
+ await mobx.when(predicate);
7549
+ return;
7550
+ }
7551
+ if (signal.aborted) {
7552
+ throw abortReason(signal);
7553
+ }
7554
+ const pending = mobx.when(predicate);
7555
+ let onAbort;
7556
+ try {
7557
+ await Promise.race([
7558
+ // `cancel()` below rejects this promise with MobX's WHEN_CANCELLED
7559
+ // sentinel. That rejection *is* the cancellation, never a real failure,
7560
+ // so it is swallowed here; the abort branch supplies the thrown reason.
7561
+ pending.catch(() => void 0),
7562
+ new Promise((_, reject) => {
7563
+ onAbort = () => reject(abortReason(signal));
7564
+ signal.addEventListener("abort", onAbort, { once: true });
7565
+ })
7566
+ ]);
7567
+ } finally {
7568
+ if (onAbort) {
7569
+ signal.removeEventListener("abort", onAbort);
7570
+ }
7571
+ pending.cancel();
7572
+ }
7573
+ };
7462
7574
  class WebSocketBridgeTransport extends WebSocket {
7463
7575
  constructor(address) {
7464
7576
  super(address);
@@ -8091,15 +8203,26 @@ ${dstAddr}topics:${topic}
8091
8203
  }
8092
8204
  };
8093
8205
  }
8094
- async whenReady() {
8095
- const that = this;
8206
+ /**
8207
+ * Resolves once the transport can carry requests: the socket (and the FOS
8208
+ * socket, when used) is connected and the connection is authorized.
8209
+ *
8210
+ * Pass `options.signal` to bound the wait. `connectionEstablished` flips back
8211
+ * to `false` on socket close, so an unbounded wait parks indefinitely while
8212
+ * the transport is down — callers with a deadline must supply a signal.
8213
+ *
8214
+ * @param options - See {@link KosTransportReadyOptions}.
8215
+ * @throws The signal's abort reason if it aborts before the transport is ready.
8216
+ */
8217
+ async whenReady(options) {
8096
8218
  if (!this.webSocketSupported) {
8097
8219
  return {
8098
8220
  status: `not supported`
8099
8221
  };
8100
8222
  }
8101
- await mobx.when(
8102
- () => !!that.socket?.connectionEstablished && (!that.useFosTransport || !!that.fosSocket?.connectionEstablished) && that.authorized
8223
+ await abortableWhen(
8224
+ () => !!this.socket?.connectionEstablished && (!this.useFosTransport || !!this.fosSocket?.connectionEstablished) && this.authorized,
8225
+ options?.signal
8103
8226
  );
8104
8227
  return {
8105
8228
  status: `success`
@@ -9790,6 +9913,7 @@ var KosCoreState = /* @__PURE__ */ ((KosCoreState2) => {
9790
9913
  KosCoreState2["UNLOADING"] = `unloading`;
9791
9914
  KosCoreState2["UNLOADED"] = `unloaded`;
9792
9915
  KosCoreState2["RELOADING"] = `reloading`;
9916
+ KosCoreState2["FAILED"] = `failed`;
9793
9917
  return KosCoreState2;
9794
9918
  })(KosCoreState || {});
9795
9919
  var KosCoreEvents = /* @__PURE__ */ ((KosCoreEvents2) => {
@@ -9802,6 +9926,15 @@ var KosCoreEvents = /* @__PURE__ */ ((KosCoreEvents2) => {
9802
9926
  KosCoreEvents2["RELOAD"] = `reload`;
9803
9927
  return KosCoreEvents2;
9804
9928
  })(KosCoreEvents || {});
9929
+ const reportFailure = async (step, run2) => {
9930
+ try {
9931
+ return await run2();
9932
+ } catch (error) {
9933
+ const detail = error instanceof Error ? error.stack ?? `${error.name}: ${error.message}` : String(error);
9934
+ log$R.error(`KOS Core lifecycle step "${step}" failed. ${detail}`);
9935
+ throw error;
9936
+ }
9937
+ };
9805
9938
  const coreFsm = (core) => {
9806
9939
  const onlineMachine2 = robot3.createMachine("offline", {
9807
9940
  [
@@ -9839,6 +9972,15 @@ const coreFsm = (core) => {
9839
9972
  onlineMachine2,
9840
9973
  (_service) => log$R.debug(_service.machine.current)
9841
9974
  );
9975
+ const failed = robot3.transition(
9976
+ `error`,
9977
+ "failed",
9978
+ robot3.action(() => {
9979
+ mobx.runInAction(() => {
9980
+ core.status = "failed";
9981
+ });
9982
+ })
9983
+ );
9842
9984
  const machine2 = robot3.createMachine({
9843
9985
  [
9844
9986
  "creating"
@@ -9861,12 +10003,13 @@ const coreFsm = (core) => {
9861
10003
  "initializing"
9862
10004
  /* INITIALIZING */
9863
10005
  ]: robot3.invoke(
9864
- () => core.init(),
10006
+ () => reportFailure("initializing", () => core.init()),
9865
10007
  robot3.transition(
9866
10008
  `done`,
9867
10009
  "initialized"
9868
10010
  /* INITIALIZED */
9869
- )
10011
+ ),
10012
+ failed
9870
10013
  ),
9871
10014
  [
9872
10015
  "initialized"
@@ -9879,12 +10022,13 @@ const coreFsm = (core) => {
9879
10022
  "loading"
9880
10023
  /* LOADING */
9881
10024
  ]: robot3.invoke(
9882
- () => core.load(),
10025
+ () => reportFailure("loading", () => core.load()),
9883
10026
  robot3.transition(
9884
10027
  `done`,
9885
10028
  "loaded"
9886
10029
  /* LOADED */
9887
- )
10030
+ ),
10031
+ failed
9888
10032
  ),
9889
10033
  [
9890
10034
  "loaded"
@@ -9904,7 +10048,7 @@ const coreFsm = (core) => {
9904
10048
  "readying"
9905
10049
  /* READYING */
9906
10050
  ]: robot3.invoke(
9907
- () => core.ready(),
10051
+ () => reportFailure("readying", () => core.ready()),
9908
10052
  robot3.transition(
9909
10053
  `done`,
9910
10054
  "ready",
@@ -9913,19 +10057,25 @@ const coreFsm = (core) => {
9913
10057
  core.status = "ready";
9914
10058
  });
9915
10059
  })
9916
- )
10060
+ ),
10061
+ failed
9917
10062
  ),
9918
10063
  [
9919
10064
  "reloading"
9920
10065
  /* RELOADING */
9921
10066
  ]: robot3.invoke(
9922
- () => core.reload(),
10067
+ () => reportFailure("reloading", () => core.reload()),
9923
10068
  robot3.transition(
9924
10069
  `done`,
9925
10070
  "loading"
9926
10071
  /* LOADING */
9927
- )
10072
+ ),
10073
+ failed
9928
10074
  ),
10075
+ [
10076
+ "failed"
10077
+ /* FAILED */
10078
+ ]: robot3.state(),
9929
10079
  [
9930
10080
  "ready"
9931
10081
  /* READY */
@@ -10063,6 +10213,7 @@ class KosCore {
10063
10213
  isOnline;
10064
10214
  _reloading;
10065
10215
  _unloading;
10216
+ lifecycleStarted = false;
10066
10217
  connectionAlias;
10067
10218
  constructor(connectionAlias) {
10068
10219
  this.initialized = false;
@@ -10139,6 +10290,12 @@ class KosCore {
10139
10290
  }
10140
10291
  this._transport = ws.init();
10141
10292
  this.fsmService = coreFsm(this);
10293
+ }
10294
+ startLifecycle() {
10295
+ if (this.lifecycleStarted) {
10296
+ return;
10297
+ }
10298
+ this.lifecycleStarted = true;
10142
10299
  this.fsmService.service.send(KosCoreEvents.CREATE);
10143
10300
  }
10144
10301
  get onlineStatus() {
@@ -10228,6 +10385,12 @@ class KosCore {
10228
10385
  async ready() {
10229
10386
  log$w.debug("Readying KOS Core");
10230
10387
  await this._transport.whenReady();
10388
+ log$w.debug("KOS Transport ready. Preloading models");
10389
+ if (!this.modelManager) {
10390
+ throw new Error(
10391
+ "KOS Core reached ready() with no model manager. Its lifecycle was started without a registry."
10392
+ );
10393
+ }
10231
10394
  const promises = this.modelManager.preloadedModels.map((model) => {
10232
10395
  return {
10233
10396
  modelId: model.modelId,
@@ -10235,6 +10398,7 @@ class KosCore {
10235
10398
  promise: model.whenReady()
10236
10399
  };
10237
10400
  });
10401
+ log$w.debug(`Preloaded ${promises.length} models`);
10238
10402
  const settled = await Promise.allSettled(
10239
10403
  promises.map((promise) => {
10240
10404
  const { promise: timeoutPromise, cancel: timeoutCancel } = rejectAfterDelay(5e3, promise.model);
@@ -10353,6 +10517,7 @@ class KosCore {
10353
10517
  });
10354
10518
  const modelManager = KosModelManager.create(registry, reset2);
10355
10519
  instance.modelManager = modelManager;
10520
+ instance.startLifecycle();
10356
10521
  return instance;
10357
10522
  }
10358
10523
  static getInstance(data) {
@@ -26932,6 +27097,7 @@ exports.DependencyResolutionPolicy = DependencyResolutionPolicy;
26932
27097
  exports.Device = Device;
26933
27098
  exports.DeviceServices = index$6;
26934
27099
  exports.DomIntersectionStrategy = DomIntersectionStrategy;
27100
+ exports.ERROR_TRANSPORT_NOT_READY = ERROR_TRANSPORT_NOT_READY;
26935
27101
  exports.EVENT_KOS_MODEL_READY = EVENT_KOS_MODEL_READY;
26936
27102
  exports.EVENT_TROUBLE_ADDED = EVENT_TROUBLE_ADDED;
26937
27103
  exports.EVENT_TROUBLE_REMOVED = EVENT_TROUBLE_REMOVED;
@@ -27114,6 +27280,7 @@ exports.SubscriptionHandlers = SubscriptionHandlers;
27114
27280
  exports.TIMER_END = TIMER_END;
27115
27281
  exports.TIMER_EVENT = TIMER_EVENT;
27116
27282
  exports.TOPIC_TIMER_TICK_EVENT = TOPIC_TIMER_TICK_EVENT;
27283
+ exports.TRANSPORT_NOT_READY = TRANSPORT_NOT_READY;
27117
27284
  exports.TimerManager = TimerManager;
27118
27285
  exports.TokenContext = TokenContext;
27119
27286
  exports.TokenProvider = TokenProvider;
@@ -27126,6 +27293,7 @@ exports.TranslationContainer = TranslationContainer;
27126
27293
  exports.TranslationContainerContext = TranslationContainerContext;
27127
27294
  exports.TranslationContext = TranslationContext;
27128
27295
  exports.TransportFactory = TransportFactory;
27296
+ exports.TransportNotReadyError = TransportNotReadyError;
27129
27297
  exports.Trouble = Trouble;
27130
27298
  exports.TroubleAwareSetup = TroubleAwareSetup;
27131
27299
  exports.TroubleContainer = TroubleContainer;
@@ -27220,6 +27388,7 @@ exports.getKosConnectionId = getKosConnectionId;
27220
27388
  exports.getKosLocalizationDescriptor = getKosLocalizationDescriptor;
27221
27389
  exports.getKosMessageLogging = getKosMessageLogging;
27222
27390
  exports.getKosModel = getKosModel$1;
27391
+ exports.getKosModelActivationState = getKosModelActivationState;
27223
27392
  exports.getKosModelSync = getKosModelSync;
27224
27393
  exports.getKosModelType = getKosModelType;
27225
27394
  exports.getKosServiceApi = getKosServiceApi;
@@ -27262,6 +27431,7 @@ exports.isLeapYear = isLeapYear;
27262
27431
  exports.isLocalRefId = isLocalRefId;
27263
27432
  exports.isNumber = isNumber;
27264
27433
  exports.isPrintable = isPrintable;
27434
+ exports.isTransportNotReadyError = isTransportNotReadyError;
27265
27435
  exports.isTroubleAware = isTroubleAware;
27266
27436
  exports.isValidDate = isValidDate;
27267
27437
  exports.kosAction = kosAction;