@kosdev-code/kos-ui-sdk 3.0.16 → 3.0.18

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.js CHANGED
@@ -766,6 +766,17 @@ const defaultConfig = {
766
766
  };
767
767
  const config = globalThis.getKosConfig?.() || JSON.stringify(defaultConfig);
768
768
  const configObj = JSON.parse(config);
769
+ const urlProfiles = () => {
770
+ if (typeof window === "undefined") {
771
+ return [];
772
+ }
773
+ const value = getQueryParams()?.["kosProfiles"];
774
+ return value ? value.split(",").map((profile) => profile.trim()).filter(Boolean) : [];
775
+ };
776
+ configObj.profiles = [
777
+ ...configObj.profiles ?? [],
778
+ ...urlProfiles().filter((profile) => !configObj.profiles?.includes(profile))
779
+ ];
769
780
  globalThis.kosConfig = configObj;
770
781
  const KosGlobalConfig = configObj;
771
782
  const resolveKosProfiles = () => KosGlobalConfig.profiles || [];
@@ -1208,6 +1219,25 @@ function kosContext(_target, _propertyKey, descriptor) {
1208
1219
  return originalMethod.apply(this, args);
1209
1220
  };
1210
1221
  }
1222
+ async function executeServiceRequest(host, $ctx, params2) {
1223
+ const name = $ctx?.$name ?? "service request";
1224
+ if (!$ctx?.$request) {
1225
+ host.logger.error(
1226
+ `${name}: missing execution context — executeServiceRequest is for method-driven requests; a @kosServiceRequest with a lifecycle receives (error, data) instead and needs no helper`
1227
+ );
1228
+ return void 0;
1229
+ }
1230
+ const [error, data] = await $ctx.$request(params2);
1231
+ if (error) {
1232
+ host.logger.error(`${name}: request failed`, error);
1233
+ return void 0;
1234
+ }
1235
+ if (data == null) {
1236
+ host.logger.error(`${name}: no data returned`);
1237
+ return void 0;
1238
+ }
1239
+ return data;
1240
+ }
1211
1241
  const KOS_EXECUTION_CONTEXT = /* @__PURE__ */ Symbol("KosExecutionContext");
1212
1242
  function isKosExecutionContext(obj) {
1213
1243
  return obj && typeof obj === "object" && KOS_EXECUTION_CONTEXT in obj;
@@ -1582,11 +1612,13 @@ function kosServiceRequest$9(params2) {
1582
1612
  };
1583
1613
  if (isKosExecutionContext(lastArg)) {
1584
1614
  lastArg.$request = executor;
1615
+ lastArg.$name = String(propertyKey);
1585
1616
  return originalHandler.apply(this, args);
1586
1617
  } else {
1587
1618
  const context = {
1588
1619
  [KOS_EXECUTION_CONTEXT]: true,
1589
- $request: executor
1620
+ $request: executor,
1621
+ $name: String(propertyKey)
1590
1622
  };
1591
1623
  return originalHandler.apply(this, [...args, context]);
1592
1624
  }
@@ -4169,6 +4201,33 @@ class KosDependencyManager {
4169
4201
  uses.filter((id) => id !== dependencyId)
4170
4202
  );
4171
4203
  }
4204
+ /**
4205
+ * Drops every edge into and out of a model. Called when the model is
4206
+ * destroyed, so its dependencies are not left pinned by a model that is
4207
+ * gone and can never release them.
4208
+ */
4209
+ removeAll(modelId) {
4210
+ for (const dependencyId of this._usesCache.get(modelId) ?? []) {
4211
+ const usedBy = this._usedByCache.get(dependencyId);
4212
+ if (usedBy) {
4213
+ this._usedByCache.set(
4214
+ dependencyId,
4215
+ usedBy.filter((id) => id !== modelId)
4216
+ );
4217
+ }
4218
+ }
4219
+ this._usesCache.delete(modelId);
4220
+ for (const dependentId of this._usedByCache.get(modelId) ?? []) {
4221
+ const uses = this._usesCache.get(dependentId);
4222
+ if (uses) {
4223
+ this._usesCache.set(
4224
+ dependentId,
4225
+ uses.filter((id) => id !== modelId)
4226
+ );
4227
+ }
4228
+ }
4229
+ this._usedByCache.delete(modelId);
4230
+ }
4172
4231
  canDestroy(modelId) {
4173
4232
  const usedBy = this._usedByCache.get(modelId);
4174
4233
  if (usedBy?.length) {
@@ -4246,6 +4305,19 @@ class KosModelCache {
4246
4305
  return this._preloaded;
4247
4306
  }
4248
4307
  }
4308
+ const cache$1 = /* @__PURE__ */ new Map();
4309
+ const modelHookCache = {
4310
+ has: (key) => cache$1.has(key),
4311
+ get: (key) => cache$1.get(key),
4312
+ set: (key, entry) => cache$1.set(key, entry),
4313
+ delete: (key) => cache$1.delete(key),
4314
+ clear: () => cache$1.clear()
4315
+ };
4316
+ const clearModelHookCacheEntry = (modelId) => {
4317
+ if (modelId) {
4318
+ cache$1.delete(modelId);
4319
+ }
4320
+ };
4249
4321
  class KosModelError extends Error {
4250
4322
  context;
4251
4323
  originalCause;
@@ -8314,6 +8386,7 @@ class KosMockRecorder {
8314
8386
  pending = /* @__PURE__ */ new Map();
8315
8387
  exchanges = [];
8316
8388
  pushes = [];
8389
+ sends = [];
8317
8390
  get isRecording() {
8318
8391
  return this.recording;
8319
8392
  }
@@ -8325,6 +8398,7 @@ class KosMockRecorder {
8325
8398
  this.pending.clear();
8326
8399
  this.exchanges = [];
8327
8400
  this.pushes = [];
8401
+ this.sends = [];
8328
8402
  KosLog.info(
8329
8403
  `KosMock recorder started${options.name ? ` (${options.name})` : ""}`
8330
8404
  );
@@ -8333,7 +8407,7 @@ class KosMockRecorder {
8333
8407
  const fixture = this.toFixture();
8334
8408
  this.recording = false;
8335
8409
  KosLog.info(
8336
- `KosMock recorder stopped: ${fixture.exchanges.length} exchanges, ${fixture.pushes.length} pushes`
8410
+ `KosMock recorder stopped: ${fixture.exchanges.length} exchanges, ${fixture.pushes.length} pushes, ${fixture.sends?.length ?? 0} sends`
8337
8411
  );
8338
8412
  return fixture;
8339
8413
  }
@@ -8343,7 +8417,8 @@ class KosMockRecorder {
8343
8417
  name: this.name,
8344
8418
  capturedAt: this.capturedAt,
8345
8419
  exchanges: [...this.exchanges],
8346
- pushes: [...this.pushes]
8420
+ pushes: [...this.pushes],
8421
+ sends: [...this.sends]
8347
8422
  };
8348
8423
  }
8349
8424
  download(filename) {
@@ -8372,6 +8447,7 @@ class KosMockRecorder {
8372
8447
  }
8373
8448
  const frame = decodeRequestFrame(data);
8374
8449
  if (!frame) {
8450
+ this.noteSend(data);
8375
8451
  return;
8376
8452
  }
8377
8453
  this.pending.set(frame.requestId, {
@@ -8379,7 +8455,31 @@ class KosMockRecorder {
8379
8455
  method: frame.method,
8380
8456
  url: frame.url,
8381
8457
  tracker: frame.tracker,
8382
- requestBody: frame.rawBody
8458
+ requestBody: frame.rawBody,
8459
+ requestHeaders: frame.headers
8460
+ });
8461
+ }
8462
+ /**
8463
+ * Everything else a window sends: alias registration, broker subscribes,
8464
+ * client-sent futures. What a window asked for is not recoverable from the
8465
+ * responses it got back, so it is captured rather than discarded.
8466
+ */
8467
+ noteSend(data) {
8468
+ if (typeof data !== "string") {
8469
+ return;
8470
+ }
8471
+ let headers;
8472
+ let body;
8473
+ try {
8474
+ ({ headers, body } = processKosMessage(data));
8475
+ } catch {
8476
+ return;
8477
+ }
8478
+ this.sends.push({
8479
+ tMs: this.elapsed(),
8480
+ type: headers["type"],
8481
+ headers,
8482
+ ...body ? { body } : {}
8383
8483
  });
8384
8484
  }
8385
8485
  /** Interceptor tap: every inbound frame (real, mocked, injected). Internal. */
@@ -8405,6 +8505,7 @@ class KosMockRecorder {
8405
8505
  ...request2,
8406
8506
  status: parseInt(headers["status"] ?? "200", 10) || 200,
8407
8507
  responseBody: body ?? "",
8508
+ responseHeaders: headers,
8408
8509
  latencyMs: Math.max(0, this.elapsed() - request2.tMs),
8409
8510
  ...headers[KOS_MOCKED_HEADER] === "true" ? { mocked: true } : {}
8410
8511
  });
@@ -8416,7 +8517,8 @@ class KosMockRecorder {
8416
8517
  tMs: this.elapsed(),
8417
8518
  kind: "topic",
8418
8519
  topic,
8419
- body: body ?? ""
8520
+ body: body ?? "",
8521
+ headers
8420
8522
  });
8421
8523
  return;
8422
8524
  }
@@ -8430,7 +8532,8 @@ class KosMockRecorder {
8430
8532
  tMs: this.elapsed(),
8431
8533
  kind: "future",
8432
8534
  tracker,
8433
- body: body ?? ""
8535
+ body: body ?? "",
8536
+ headers
8434
8537
  });
8435
8538
  }
8436
8539
  }
@@ -9251,32 +9354,53 @@ const resolveSingleton = () => {
9251
9354
  };
9252
9355
  const kosMockRegistry = resolveSingleton();
9253
9356
  const KosMock = kosMockRegistry;
9254
- if (typeof window !== "undefined" && window.location?.search) {
9255
- const params2 = getQueryParams();
9256
- if (params2["kosMock"] === "standalone") {
9357
+ const PROFILE_MOCK = "studio.mock";
9358
+ const PROFILE_MOCK_STANDALONE = "studio.mock.standalone";
9359
+ const PROFILE_MOCK_RECORD = "studio.mock.record";
9360
+ const PROFILE_MOCK_FIXTURE_SERVER = "studio.mock.fixtureServer";
9361
+ const PROFILE_MOCK_FIXTURE = "studio.mock.fixture";
9362
+ const PROFILE_MOCK_SETUP = "studio.mock.setup";
9363
+ const profileValue = (name) => {
9364
+ const match = resolveKosProfiles().find(
9365
+ (profile) => profile === name || profile.startsWith(`${name}=`)
9366
+ );
9367
+ return match === void 0 ? void 0 : match.slice(name.length + 1);
9368
+ };
9369
+ if (typeof window !== "undefined") {
9370
+ const params2 = window.location?.search ? getQueryParams() : {};
9371
+ const setting = (param, profile) => params2[param] !== void 0 ? params2[param] : profileValue(profile);
9372
+ const mock = params2["kosMock"] !== void 0 ? params2["kosMock"] : hasKosProfile(PROFILE_MOCK_STANDALONE) ? "standalone" : hasKosProfile(PROFILE_MOCK) ? "on" : void 0;
9373
+ const record = setting("kosRecord", PROFILE_MOCK_RECORD);
9374
+ const fixtureServer = setting(
9375
+ "kosFixtureServer",
9376
+ PROFILE_MOCK_FIXTURE_SERVER
9377
+ );
9378
+ const fixture = setting("kosFixture", PROFILE_MOCK_FIXTURE);
9379
+ const setups = setting("kosSetup", PROFILE_MOCK_SETUP);
9380
+ if (mock === "standalone") {
9257
9381
  KosMock.configure({ standalone: true });
9258
- } else if (params2["kosMock"] === "on") {
9382
+ } else if (mock === "on") {
9259
9383
  KosMock.enable();
9260
9384
  }
9261
- if (params2["kosRecord"] !== void 0) {
9262
- KosMock.recorder.start({ name: params2["kosRecord"] || void 0 });
9385
+ if (record !== void 0) {
9386
+ KosMock.recorder.start({ name: record || void 0 });
9263
9387
  }
9264
- if (params2["kosFixtureServer"] !== void 0) {
9265
- KosMock.fixtures.useServer(params2["kosFixtureServer"] || true);
9388
+ if (fixtureServer !== void 0) {
9389
+ KosMock.fixtures.useServer(fixtureServer || true);
9266
9390
  }
9267
9391
  const bootLoads = [];
9268
- if (params2["kosFixture"]) {
9392
+ if (fixture) {
9269
9393
  bootLoads.push(
9270
- KosMock.fixtures.play(params2["kosFixture"]).then(() => void 0).catch(
9394
+ KosMock.fixtures.play(fixture).then(() => void 0).catch(
9271
9395
  (error) => KosLog.error(
9272
- `KosMock: boot fixture "${params2["kosFixture"]}" failed to load`,
9396
+ `KosMock: boot fixture "${fixture}" failed to load`,
9273
9397
  error?.message
9274
9398
  )
9275
9399
  )
9276
9400
  );
9277
9401
  }
9278
- if (params2["kosSetup"]) {
9279
- for (const name of params2["kosSetup"].split(",").filter(Boolean)) {
9402
+ if (setups) {
9403
+ for (const name of setups.split(",").filter(Boolean)) {
9280
9404
  bootLoads.push(
9281
9405
  KosMock.setups.load(name).then(() => void 0).catch(
9282
9406
  (error) => KosLog.error(
@@ -11700,6 +11824,8 @@ class KosModelManager {
11700
11824
  if (model?.modelId && this.dependencies.canDestroy(model.modelId)) {
11701
11825
  await model.unload?.();
11702
11826
  this.removeModel(model);
11827
+ this.dependencies.removeAll(model.modelId);
11828
+ clearModelHookCacheEntry(model.modelId);
11703
11829
  }
11704
11830
  }
11705
11831
  /**
@@ -26779,33 +26905,32 @@ const waitForRetry = (depth = 0) => new Promise((resolve) => {
26779
26905
  resolve(true);
26780
26906
  }, 2 ** depth * 10);
26781
26907
  });
26782
- const cache$1 = /* @__PURE__ */ new Map();
26783
- function fetchData$1(key, fetcher) {
26784
- if (cache$1.has(key)) {
26785
- const entry = cache$1.get(key);
26908
+ function readModelHookEntry(key, fetcher) {
26909
+ const entry = modelHookCache.get(key);
26910
+ if (entry) {
26786
26911
  if (entry.status === "finished") {
26787
26912
  const kosModel2 = KosCore.getInstance().modelManager.getModelById(key);
26788
26913
  return { kosModel: kosModel2, model: kosModel2?.modelData };
26789
- } else {
26790
- throw entry.promise;
26791
26914
  }
26792
- } else {
26793
- const promise = fetcher().then(
26794
- () => {
26795
- cache$1.set(key, { status: "finished", key });
26796
- },
26797
- (error) => {
26798
- cache$1.set(key, { status: "error", error });
26799
- throw error;
26800
- }
26801
- );
26802
- const entry = { status: "pending", promise };
26803
- cache$1.set(key, entry);
26804
- throw promise;
26915
+ if (entry.status === "error") {
26916
+ throw entry.error;
26917
+ }
26918
+ throw entry.promise;
26805
26919
  }
26920
+ const promise = fetcher().then(
26921
+ () => {
26922
+ modelHookCache.set(key, { status: "finished", key });
26923
+ },
26924
+ (error) => {
26925
+ modelHookCache.set(key, { status: "error", error });
26926
+ throw error;
26927
+ }
26928
+ );
26929
+ modelHookCache.set(key, { status: "pending", promise });
26930
+ throw promise;
26806
26931
  }
26807
26932
  function useSuspenseData$1(key, fetcher) {
26808
- const data = fetchData$1(key, fetcher);
26933
+ const data = readModelHookEntry(key, fetcher);
26809
26934
  return data;
26810
26935
  }
26811
26936
  async function fetchModel$1(kosCore, modelOptions) {
@@ -26893,7 +27018,7 @@ const useKosModel = (modelOptions) => {
26893
27018
  if (destroyOnUnmount) {
26894
27019
  const modelId2 = model.id;
26895
27020
  destroyKosModel(model).then(() => {
26896
- cache$1.delete(modelId2);
27021
+ clearModelHookCacheEntry(modelId2);
26897
27022
  disposer?.();
26898
27023
  });
26899
27024
  }
@@ -26902,7 +27027,7 @@ const useKosModel = (modelOptions) => {
26902
27027
  } else {
26903
27028
  if (destroyOnUnmount && model) {
26904
27029
  destroyKosModel(model).then(() => {
26905
- cache$1.delete(modelId);
27030
+ clearModelHookCacheEntry(modelId);
26906
27031
  disposer?.();
26907
27032
  });
26908
27033
  }
@@ -29175,6 +29300,7 @@ export {
29175
29300
  checkAppsStarted,
29176
29301
  checkWildcardPattern,
29177
29302
  clearAllServiceResponses,
29303
+ clearModelHookCacheEntry,
29178
29304
  clearPath,
29179
29305
  clearServiceResponse,
29180
29306
  convert,
@@ -29208,6 +29334,7 @@ export {
29208
29334
  executeChildrenModelLifecycle,
29209
29335
  executeDependentModelLifecycle,
29210
29336
  executeListLifecycle,
29337
+ executeServiceRequest,
29211
29338
  fetchModel,
29212
29339
  findModel,
29213
29340
  findModels,
@@ -29332,6 +29459,7 @@ export {
29332
29459
  mapUpdateDtoToConfigBeanModel,
29333
29460
  modelEventTopicFactory,
29334
29461
  modelFactory,
29462
+ modelHookCache,
29335
29463
  modelTypeEventTopicFactory,
29336
29464
  modifyConfigBean,
29337
29465
  modifyFuture,
@@ -29343,6 +29471,7 @@ export {
29343
29471
  processId,
29344
29472
  processMiddleware,
29345
29473
  put,
29474
+ readModelHookEntry,
29346
29475
  registerCompanionModel,
29347
29476
  registerCoreModels,
29348
29477
  registerExtensionPoint,