@xh/hoist 76.0.0-SNAPSHOT.1758217044052 → 76.0.0-SNAPSHOT.1758310971360

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 (84) hide show
  1. package/CHANGELOG.md +5 -3
  2. package/admin/tabs/userData/roles/graph/RoleGraph.ts +1 -2
  3. package/appcontainer/AppContainerModel.ts +1 -2
  4. package/appcontainer/ExceptionDialogModel.ts +2 -1
  5. package/build/types/appcontainer/ExceptionDialogModel.d.ts +2 -1
  6. package/build/types/cmp/grid/Grid.d.ts +3 -3
  7. package/build/types/core/{exception/ExceptionHandler.d.ts → ExceptionHandler.d.ts} +2 -1
  8. package/build/types/core/XH.d.ts +3 -2
  9. package/build/types/core/index.d.ts +1 -3
  10. package/build/types/core/types/Types.d.ts +0 -10
  11. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +5 -5
  12. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +5 -5
  13. package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
  14. package/build/types/exception/Exception.d.ts +24 -0
  15. package/build/types/{core/exception → exception}/Types.d.ts +1 -18
  16. package/build/types/exception/index.d.ts +2 -0
  17. package/build/types/kit/swiper/index.d.ts +1 -1
  18. package/build/types/svc/FetchService.d.ts +55 -0
  19. package/build/types/utils/js/LangUtils.d.ts +1 -27
  20. package/build/types/utils/{log → js}/LogUtils.d.ts +37 -1
  21. package/build/types/utils/js/index.d.ts +2 -0
  22. package/cmp/ag-grid/AgGrid.ts +1 -1
  23. package/cmp/chart/Chart.ts +1 -2
  24. package/cmp/chart/impl/ChartContextMenuItems.ts +1 -1
  25. package/cmp/grid/columns/Column.ts +1 -2
  26. package/cmp/grid/helpers/GridCountLabel.ts +1 -2
  27. package/cmp/grid/impl/ColumnWidthCalculator.ts +1 -2
  28. package/cmp/grid/impl/Utils.ts +1 -1
  29. package/cmp/relativetimestamp/RelativeTimestamp.ts +1 -2
  30. package/cmp/treemap/TreeMap.ts +1 -2
  31. package/core/{exception/ExceptionHandler.ts → ExceptionHandler.ts} +3 -4
  32. package/core/HoistBase.ts +10 -2
  33. package/core/HoistBaseDecorators.ts +1 -2
  34. package/core/HoistComponent.ts +1 -2
  35. package/core/XH.ts +3 -4
  36. package/core/index.ts +1 -3
  37. package/core/load/LoadSupport.ts +1 -2
  38. package/core/persist/PersistenceProvider.ts +1 -2
  39. package/core/types/Types.ts +0 -6
  40. package/data/filter/Utils.ts +1 -1
  41. package/data/impl/RecordSet.ts +1 -2
  42. package/desktop/cmp/button/grid/ColAutosizeButton.ts +1 -2
  43. package/desktop/cmp/button/grid/ColChooserButton.ts +1 -2
  44. package/desktop/cmp/button/grid/ExpandToLevelButton.ts +1 -2
  45. package/desktop/cmp/button/grid/ExportButton.ts +1 -2
  46. package/desktop/cmp/button/panel/ModalToggleButton.ts +1 -2
  47. package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -2
  48. package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +1 -2
  49. package/desktop/cmp/form/FormField.ts +1 -2
  50. package/desktop/cmp/grid/editors/BooleanEditor.ts +1 -1
  51. package/desktop/cmp/panel/Panel.ts +1 -1
  52. package/desktop/hooks/UseContextMenu.ts +1 -1
  53. package/exception/Exception.ts +81 -0
  54. package/{core/exception → exception}/Types.ts +1 -22
  55. package/{utils/log → exception}/index.ts +2 -2
  56. package/kit/ag-grid/index.ts +1 -2
  57. package/kit/highcharts/index.ts +1 -2
  58. package/mobile/cmp/button/grid/ColAutosizeButton.ts +1 -2
  59. package/mobile/cmp/button/grid/ColChooserButton.ts +1 -2
  60. package/mobile/cmp/button/grid/ExpandCollapseButton.ts +1 -2
  61. package/mobile/cmp/button/grid/ExpandToLevelButton.ts +1 -2
  62. package/mobile/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -2
  63. package/mobile/cmp/panel/Panel.ts +1 -1
  64. package/mobx/overrides.ts +1 -1
  65. package/package.json +1 -1
  66. package/promise/Promise.ts +3 -3
  67. package/security/BaseOAuthClient.ts +3 -3
  68. package/security/msal/MsalClient.ts +1 -2
  69. package/svc/ChangelogService.ts +1 -1
  70. package/svc/EnvironmentService.ts +1 -2
  71. package/svc/FetchService.ts +208 -16
  72. package/tsconfig.tsbuildinfo +1 -1
  73. package/utils/async/Timer.ts +1 -2
  74. package/utils/js/Decorators.ts +4 -4
  75. package/utils/js/LangUtils.ts +3 -67
  76. package/utils/{log → js}/LogUtils.ts +78 -19
  77. package/utils/js/index.ts +2 -0
  78. package/build/types/core/exception/Exception.d.ts +0 -61
  79. package/build/types/utils/log/index.d.ts +0 -1
  80. package/build/types/utils/version/index.d.ts +0 -1
  81. package/core/exception/Exception.ts +0 -256
  82. package/utils/version/index.ts +0 -8
  83. /package/build/types/utils/{version → js}/VersionUtils.d.ts +0 -0
  84. /package/utils/{version → js}/VersionUtils.ts +0 -0
@@ -4,20 +4,13 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {
8
- Awaitable,
9
- Exception,
10
- HoistService,
11
- LoadSpec,
12
- PlainObject,
13
- TrackOptions,
14
- XH
15
- } from '@xh/hoist/core';
7
+ import {Awaitable, HoistService, LoadSpec, PlainObject, TrackOptions, XH} from '@xh/hoist/core';
8
+ import {Exception, HoistException, TimeoutException} from '@xh/hoist/exception';
16
9
  import {PromiseTimeoutSpec} from '@xh/hoist/promise';
17
10
  import {isLocalDate, SECONDS} from '@xh/hoist/utils/datetime';
18
11
  import {warnIf} from '@xh/hoist/utils/js';
19
12
  import {StatusCodes} from 'http-status-codes';
20
- import {isDate, isFunction, isNil, isObject, isString, omit, omitBy} from 'lodash';
13
+ import {isDate, isFunction, isNil, isObject, isString, omit, omitBy, truncate} from 'lodash';
21
14
  import {IStringifyOptions, stringify} from 'qs';
22
15
  import ShortUniqueId from 'short-unique-id';
23
16
 
@@ -198,7 +191,7 @@ export class FetchService extends HoistService {
198
191
  // Apply tracking
199
192
  const {correlationId, loadSpec, track} = opts;
200
193
  if (track) {
201
- const trackOptions = isString(track) ? {message: track} : track;
194
+ const trackOptions: TrackOptions = isString(track) ? {message: track} : track;
202
195
  warnIf(
203
196
  trackOptions.correlationId || trackOptions.loadSpec,
204
197
  'Neither Correlation ID nor LoadSpec should be set in `FetchOptions.track`. Use `FetchOptions` top-level properties instead.'
@@ -296,14 +289,14 @@ export class FetchService extends HoistService {
296
289
  ? timeout.message
297
290
  : // Exception.timeout() message already includes interval - add URL here.
298
291
  e.message + ` loading '${opts.url}'`;
299
- throw Exception.fetchTimeout(opts, e, msg);
292
+ throw this.timeoutException(opts, e, msg);
300
293
  }
301
294
 
302
295
  if (!e.isHoistException) {
303
296
  // Just two other cases where we expect this to *throw* -- Typically we get a fail status
304
297
  throw e.name === 'AbortError'
305
- ? Exception.fetchAborted(opts, e)
306
- : Exception.serverUnavailable(opts, e);
298
+ ? this.abortedException(opts, e)
299
+ : this.serverUnavailableException(opts, e);
307
300
  }
308
301
  throw e;
309
302
  } finally {
@@ -359,7 +352,8 @@ export class FetchService extends HoistService {
359
352
  // 4) Await underlying fetch and post-process response.
360
353
  const ret = await fetch(url, fetchOpts);
361
354
 
362
- if (!ret.ok) throw Exception.fetchError(opts, ret, await this.safeResponseTextAsync(ret));
355
+ if (!ret.ok)
356
+ throw this.exceptionFromResponse(opts, ret, await this.safeResponseTextAsync(ret));
363
357
 
364
358
  return ret;
365
359
  }
@@ -367,7 +361,7 @@ export class FetchService extends HoistService {
367
361
  private async parseJsonAsync(opts: FetchOptions, r: Response): Promise<any> {
368
362
  if (this.NO_JSON_RESPONSES.includes(r.status)) return null;
369
363
  return r.json().catchWhen('SyntaxError', e => {
370
- throw Exception.fetchJsonParseError(opts, e);
364
+ throw this.jsonParseException(opts, e);
371
365
  });
372
366
  }
373
367
 
@@ -384,6 +378,184 @@ export class FetchService extends HoistService {
384
378
  if (isLocalDate(value)) return value.isoString;
385
379
  return value;
386
380
  };
381
+
382
+ //---------------------
383
+ // Exception Handling
384
+ //--------------------
385
+ /**
386
+ * Create an Error to throw when a fetch call returns a !ok response.
387
+ * @param fetchOptions - original options passed to FetchService.
388
+ * @param response - return value of native fetch.
389
+ * @param responseText - optional additional details from the server.
390
+ */
391
+ private exceptionFromResponse(
392
+ fetchOptions: FetchOptions,
393
+ response: Response,
394
+ responseText: string = null
395
+ ): FetchException {
396
+ const {headers, status, statusText} = response,
397
+ defaults = {
398
+ name: 'HTTP Error ' + (status || ''),
399
+ message: statusText,
400
+ httpStatus: status,
401
+ serverDetails: responseText,
402
+ fetchOptions
403
+ };
404
+
405
+ if (status === 401) {
406
+ return this.createException({
407
+ ...defaults,
408
+ name: 'Unauthorized',
409
+ message: 'Your session may have timed out and you may need to log in again.'
410
+ });
411
+ }
412
+
413
+ // Try to "smart" decode as server provided JSON Exception (with a name)
414
+ try {
415
+ const cType = headers.get('Content-Type');
416
+ if (cType?.includes('application/json')) {
417
+ const parsedResp = this.safeParseJson(responseText);
418
+ return this.createException({
419
+ ...defaults,
420
+ name: parsedResp?.name ?? defaults.name,
421
+ message: this.extractMessage(parsedResp, responseText, statusText),
422
+ isRoutine: parsedResp?.isRoutine ?? false,
423
+ serverDetails: parsedResp ?? responseText
424
+ });
425
+ }
426
+ } catch (ignored) {}
427
+
428
+ // Fall back to raw defaults
429
+ return this.createException(defaults);
430
+ }
431
+
432
+ /**
433
+ * Create an Error to throw when a fetchJson call encounters a SyntaxError.
434
+ * @param fetchOptions - original options passed to FetchService.
435
+ * @param cause - object thrown by native {@link response.json}.
436
+ */
437
+ private jsonParseException(fetchOptions: FetchOptions, cause: any): FetchException {
438
+ return this.createException({
439
+ name: 'JSON Parsing Error',
440
+ message:
441
+ 'Error parsing the response body as JSON. The server may have returned an invalid ' +
442
+ 'or empty response. Use "XH.fetch()" to process the response manually.',
443
+ fetchOptions,
444
+ cause
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Create an Error to throw when a fetch call is aborted.
450
+ * @param fetchOptions - original options passed to FetchService.
451
+ * @param cause - object thrown by native fetch
452
+ */
453
+ private abortedException(fetchOptions: FetchOptions, cause: any): FetchException {
454
+ return this.createException({
455
+ name: 'Fetch Aborted',
456
+ message: `Fetch request aborted, url: "${fetchOptions.url}"`,
457
+ isRoutine: true,
458
+ isFetchAborted: true,
459
+ fetchOptions,
460
+ cause
461
+ });
462
+ }
463
+
464
+ /**
465
+ * Create an Error to throw when a fetch call times out.
466
+ * @param fetchOptions - original options the app passed when calling FetchService.
467
+ * @param cause - underlying timeout exception
468
+ * @param message - optional custom message
469
+ *
470
+ * @returns an exception that is both a TimeoutException, and a FetchException, with the
471
+ * underlying TimeoutException as its cause.
472
+ */
473
+ private timeoutException(
474
+ fetchOptions: FetchOptions,
475
+ cause: TimeoutException,
476
+ message: string
477
+ ): FetchException & TimeoutException {
478
+ return this.createException({
479
+ name: 'Fetch Timeout',
480
+ message,
481
+ isFetchTimeout: true,
482
+ isTimeout: true,
483
+ interval: cause.interval,
484
+ fetchOptions,
485
+ cause
486
+ }) as FetchException & TimeoutException;
487
+ }
488
+
489
+ /**
490
+ * Create an Error for when the server called by fetch does not respond
491
+ * @param fetchOptions - original options the app passed to FetchService.fetch
492
+ * @param cause - object thrown by native fetch
493
+ */
494
+ private serverUnavailableException(fetchOptions: FetchOptions, cause: any): FetchException {
495
+ const protocolPattern = /^[a-z]+:\/\//i,
496
+ originPattern = /^[a-z]+:\/\/[^/]+/i,
497
+ match = fetchOptions.url.match(originPattern),
498
+ origin = match
499
+ ? match[0]
500
+ : protocolPattern.test(XH.baseUrl)
501
+ ? XH.baseUrl
502
+ : window.location.origin;
503
+
504
+ return this.createException({
505
+ name: 'Server Unavailable',
506
+ message: `Unable to contact the server at ${origin}`,
507
+ isServerUnavailable: true,
508
+ fetchOptions,
509
+ cause
510
+ });
511
+ }
512
+
513
+ private createException(attributes: PlainObject) {
514
+ let correlationId: string = null;
515
+ const correlationIdHeaderKey = XH?.fetchService?.correlationIdHeaderKey;
516
+ if (correlationIdHeaderKey) {
517
+ correlationId = attributes.fetchOptions?.headers?.[correlationIdHeaderKey];
518
+ }
519
+
520
+ return Exception.create({
521
+ isFetchAborted: false,
522
+ httpStatus: 0, // native fetch doesn't put status on its Error
523
+ serverDetails: null,
524
+ stack: null, // server-sourced exceptions do not include, neither should client, not relevant
525
+ correlationId,
526
+ ...attributes
527
+ }) as FetchException;
528
+ }
529
+
530
+ private safeParseJson(txt: string): PlainObject {
531
+ try {
532
+ return JSON.parse(txt);
533
+ } catch (ignored) {
534
+ return null;
535
+ }
536
+ }
537
+
538
+ private extractMessage(
539
+ parsedResp: PlainObject,
540
+ responseText: string,
541
+ statusText: string
542
+ ): string {
543
+ let ret: string;
544
+ if (parsedResp) {
545
+ // From parsed response, including cause if provided (e.g. ExternalHttpException)
546
+ ret = parsedResp.message;
547
+ if (isString(parsedResp.cause)) {
548
+ const cause = truncate(parsedResp.cause, {length: 255});
549
+ ret = ret ? `${ret} (Caused by: ${cause})` : cause;
550
+ }
551
+ } else {
552
+ // Use raw text if not JSON parseable
553
+ ret = truncate(responseText?.trim(), {length: 255});
554
+ }
555
+
556
+ // Fallback to statusText if we have nothing else.
557
+ return ret || statusText;
558
+ }
387
559
  }
388
560
 
389
561
  /** Headers to be applied to all requests. Specified as object, or dynamic function to create. */
@@ -473,3 +645,23 @@ export interface FetchOptions {
473
645
  */
474
646
  track?: string | TrackOptions;
475
647
  }
648
+
649
+ /**
650
+ * Exception thrown to indicate an HTTP error resulting from a call to FetchService.
651
+ */
652
+ export interface FetchException extends HoistException {
653
+ /** Http Status code associated with exception. 0 if no response received. */
654
+ httpStatus: number;
655
+
656
+ /** Rich object or string containing details about the exception as sent by server. */
657
+ serverDetails: string | PlainObject;
658
+
659
+ /** Options of underlying fetch call. */
660
+ fetchOptions: FetchOptions;
661
+
662
+ /**
663
+ * True if exception resulted from the fetch being aborted by fetchService, or the application.
664
+ * @see FetchService.abort and FetchOptions.autoAbortKey.
665
+ */
666
+ isFetchAborted: boolean;
667
+ }