@xh/hoist 67.0.0-SNAPSHOT.1723673844830 → 67.0.0-SNAPSHOT.1723839594443

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/CHANGELOG.md CHANGED
@@ -8,15 +8,16 @@
8
8
  * New `FetchService` members: `autoGenCorrelationIds`, `genCorrelationId` and
9
9
  `correlationIdHeaderKey` to support generation and inclusion of Correlation IDs on outbound
10
10
  request headers.
11
- * Correlation ID assignment is available via:
11
+ * Correlation IDs are assigned via:
12
12
  * `FetchOptions.correlationId` - specify an ID to be used on a particular request or `true`
13
13
  to use a UUID generated by Hoist (see `FetchService.genCorrelationId()`).
14
- * `TrackOptions.correlationId` - specify an ID for a tracked activity, if not already
15
- relayed from a `FetchService` response.
16
- * If set on a fetch request, Correlation IDs are passed through to downstream Hoist activity
17
- tracking and error reporting and are available for review in the Admin Console.
14
+ * `TrackOptions.correlationId` - specify an ID for a tracked activity, if not using the
15
+ new `FetchOptions.track` API (see below).
16
+ * If set on a fetch request, Correlation IDs are passed through to downstream error reporting
17
+ and are available for review in the Admin Console.
18
18
  * New `XH.genUUID()` method to generate UUIDs for use as Correlation IDs or elsewhere.
19
- * `GridModel.contextMenu` will now accept `false` as a value to omit context menus.
19
+ * New `FetchOptions.track` - specify `TrackOptions` or message `string` to track a request via
20
+ Hoist activity tracking. The request's Correlation ID and LoadSpec will be included automatically.
20
21
  * New global interceptors on `FetchService`. See `FetchService.addInterceptor()`.
21
22
  * New property `FetchOptions.asJson` to instruct `FetchService` to decode an HTTP response as JSON.
22
23
  Note that `FetchService` methods suffixed with `Json` will set this property automatically.
@@ -37,7 +38,7 @@
37
38
 
38
39
  ### 📚 Libraries
39
40
 
40
- * uuid `added @ 10.0`
41
+ * short-unique-id `added @ 5.2`
41
42
 
42
43
  ## 66.1.1 - 2024-08-01
43
44
 
@@ -9,7 +9,7 @@ import { AppContainerModel } from '../appcontainer/AppContainerModel';
9
9
  import { BannerModel } from '../appcontainer/BannerModel';
10
10
  import { ToastModel } from '../appcontainer/ToastModel';
11
11
  import '../styles/XH.scss';
12
- import { AppSpec, AppState, AppSuspendData, BannerSpec, ExceptionHandler, ExceptionHandlerOptions, FetchResponse, HoistAppModel, HoistException, HoistService, HoistServiceClass, HoistUser, MessageSpec, PageState, PlainObject, SizingMode, TaskObserver, Theme, ToastSpec, TrackOptions } from './';
12
+ import { AppSpec, AppState, AppSuspendData, BannerSpec, ExceptionHandler, ExceptionHandlerOptions, HoistAppModel, HoistException, HoistService, HoistServiceClass, HoistUser, MessageSpec, PageState, PlainObject, SizingMode, TaskObserver, Theme, ToastSpec, TrackOptions } from './';
13
13
  import { HoistModel, ModelSelector, RefreshContextModel } from './model';
14
14
  export declare const MIN_HOIST_CORE_VERSION = "18.0";
15
15
  /**
@@ -117,7 +117,7 @@ export declare class XHApi {
117
117
  * Send a request via the underlying fetch API.
118
118
  * @see FetchService.fetch
119
119
  */
120
- fetch(opts: FetchOptions): Promise<FetchResponse>;
120
+ fetch(opts: FetchOptions): Promise<any>;
121
121
  /**
122
122
  * Send an HTTP request and decode the response as JSON.
123
123
  * @see FetchService.fetchJson
@@ -403,6 +403,7 @@ export declare class XHApi {
403
403
  */
404
404
  genUUID(): string;
405
405
  private get acm();
406
+ private shortUniqueId;
406
407
  }
407
408
  /** The app-wide singleton instance. */
408
409
  export declare const XH: XHApi;
@@ -1,5 +1,4 @@
1
1
  import { FetchOptions } from '@xh/hoist/svc';
2
- import { FetchResponse } from '../';
3
2
  import { FetchException, HoistException, TimeoutException, TimeoutExceptionConfig } from './Types';
4
3
  /**
5
4
  * Standardized Exception/Error objects.
@@ -26,9 +25,10 @@ export declare class Exception {
26
25
  /**
27
26
  * Create an Error to throw when a fetch call returns a !ok response.
28
27
  * @param fetchOptions - original options passed to FetchService.
29
- * @param fetchResponse - return value of native fetch, as enhanced by FetchService.
28
+ * @param response - return value of native fetch.
29
+ * @param responseText - optional additional details from the server.
30
30
  */
31
- static fetchError(fetchOptions: FetchOptions, fetchResponse: FetchResponse): FetchException;
31
+ static fetchError(fetchOptions: FetchOptions, response: Response, responseText?: string): FetchException;
32
32
  /**
33
33
  * Create an Error to throw when a fetchJson call encounters a SyntaxError.
34
34
  * @param fetchOptions - original options passed to FetchService.
@@ -19,15 +19,6 @@ export interface HoistUser {
19
19
  hasRole(s: string): boolean;
20
20
  hasGate(s: string): boolean;
21
21
  }
22
- /**
23
- * Enhanced Response returned by FetchService.
24
- */
25
- export interface FetchResponse extends Response {
26
- /**
27
- * Property containing the already-awaited output of `response.text()`.
28
- */
29
- responseText: string;
30
- }
31
22
  /**
32
23
  * Options for showing a "toast" notification that appears and then automatically dismisses.
33
24
  */
@@ -170,10 +161,7 @@ export interface TrackOptions {
170
161
  message: string;
171
162
  /** App-supplied category.*/
172
163
  category?: string;
173
- /**
174
- * Correlation ID to save along with track log. If not provided, will attempt to source from the
175
- * underlying Fetch Request.
176
- */
164
+ /** Correlation ID to save along with track log. */
177
165
  correlationId?: string;
178
166
  /** App-supplied data to save along with track log.*/
179
167
  data?: PlainObject | PlainObject[];
@@ -58,11 +58,6 @@ declare global {
58
58
  * @param options - TrackOptions, or simply a message string.
59
59
  */
60
60
  track(options: TrackOptions | string): Promise<T>;
61
- /**
62
- * Set by FetchService to relay correlation IDs to downstream error handling and tracking.
63
- * @internal
64
- */
65
- correlationId?: string;
66
61
  }
67
62
  }
68
63
  /**
@@ -1,4 +1,4 @@
1
- import { Awaitable, HoistService, LoadSpec, PlainObject } from '@xh/hoist/core';
1
+ import { Awaitable, HoistService, LoadSpec, PlainObject, TrackOptions } from '@xh/hoist/core';
2
2
  import { PromiseTimeoutSpec } from '@xh/hoist/promise';
3
3
  import { StatusCodes } from 'http-status-codes';
4
4
  import { IStringifyOptions } from 'qs';
@@ -68,11 +68,11 @@ export declare class FetchService extends HoistService {
68
68
  * requests. Other shortcut variants will delegate to this method, after setting
69
69
  * default options and pre-processing content.
70
70
  *
71
- * Set `asJson` to true return a parsed JSON result, rather than the raw FetchResponse.
71
+ * Set `asJson` to true return a parsed JSON result, rather than the raw Response.
72
72
  * Note that shortcut variant of this method (e.g. `fetchJson`, `postJson`) will set this
73
73
  * flag for you.
74
74
  *
75
- * @returns Promise which resolves to a FetchResponse or JSON.
75
+ * @returns Promise which resolves to a Response or JSON.
76
76
  */
77
77
  fetch(opts: FetchOptions): Promise<any>;
78
78
  /**
@@ -199,4 +199,9 @@ export interface FetchOptions {
199
199
  * True to decode the HTTP response as JSON. Default false.
200
200
  */
201
201
  asJson?: boolean;
202
+ /**
203
+ * If set, the request will be tracked via Hoist activity tracking. (Do not set `correlationId`
204
+ * here - use the top-level `correlationId` property instead.)
205
+ */
206
+ track?: string | TrackOptions;
202
207
  }
package/core/XH.ts CHANGED
@@ -46,7 +46,6 @@ import {
46
46
  Exception,
47
47
  ExceptionHandler,
48
48
  ExceptionHandlerOptions,
49
- FetchResponse,
50
49
  HoistAppModel,
51
50
  HoistException,
52
51
  HoistService,
@@ -64,7 +63,7 @@ import {
64
63
  import {installServicesAsync} from './impl/InstallServices';
65
64
  import {instanceManager} from './impl/InstanceManager';
66
65
  import {HoistModel, ModelSelector, RefreshContextModel} from './model';
67
- import {v4} from 'uuid';
66
+ import ShortUniqueId from 'short-unique-id';
68
67
 
69
68
  export const MIN_HOIST_CORE_VERSION = '18.0';
70
69
 
@@ -263,7 +262,7 @@ export class XHApi {
263
262
  * Send a request via the underlying fetch API.
264
263
  * @see FetchService.fetch
265
264
  */
266
- fetch(opts: FetchOptions): Promise<FetchResponse> {
265
+ fetch(opts: FetchOptions): Promise<any> {
267
266
  return this.fetchService.fetch(opts);
268
267
  }
269
268
 
@@ -785,7 +784,7 @@ export class XHApi {
785
784
  * Generate a universally unique identifier (UUID). Useful for generating Correlation IDs.
786
785
  */
787
786
  genUUID(): string {
788
- return v4();
787
+ return this.shortUniqueId.rnd();
789
788
  }
790
789
 
791
790
  //----------------
@@ -794,6 +793,8 @@ export class XHApi {
794
793
  private get acm(): AppContainerModel {
795
794
  return this.appContainerModel;
796
795
  }
796
+
797
+ private shortUniqueId = new ShortUniqueId({length: 16});
797
798
  }
798
799
 
799
800
  /** The app-wide singleton instance. */
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {FetchOptions} from '@xh/hoist/svc';
8
- import {FetchResponse, PlainObject, XH} from '../';
8
+ import {PlainObject, XH} from '../';
9
9
  import {isPlainObject} from 'lodash';
10
10
 
11
11
  import {FetchException, HoistException, TimeoutException, TimeoutExceptionConfig} from './Types';
@@ -58,10 +58,15 @@ export class Exception {
58
58
  /**
59
59
  * Create an Error to throw when a fetch call returns a !ok response.
60
60
  * @param fetchOptions - original options passed to FetchService.
61
- * @param fetchResponse - return value of native fetch, as enhanced by FetchService.
61
+ * @param response - return value of native fetch.
62
+ * @param responseText - optional additional details from the server.
62
63
  */
63
- static fetchError(fetchOptions: FetchOptions, fetchResponse: FetchResponse): FetchException {
64
- const {headers, status, statusText, responseText} = fetchResponse,
64
+ static fetchError(
65
+ fetchOptions: FetchOptions,
66
+ response: Response,
67
+ responseText: string = null
68
+ ): FetchException {
69
+ const {headers, status, statusText} = response,
65
70
  defaults = {
66
71
  name: 'HTTP Error ' + (status || ''),
67
72
  message: statusText,
@@ -28,16 +28,6 @@ export interface HoistUser {
28
28
  hasGate(s: string): boolean;
29
29
  }
30
30
 
31
- /**
32
- * Enhanced Response returned by FetchService.
33
- */
34
- export interface FetchResponse extends Response {
35
- /**
36
- * Property containing the already-awaited output of `response.text()`.
37
- */
38
- responseText: string;
39
- }
40
-
41
31
  /**
42
32
  * Options for showing a "toast" notification that appears and then automatically dismisses.
43
33
  */
@@ -213,10 +203,7 @@ export interface TrackOptions {
213
203
  /** App-supplied category.*/
214
204
  category?: string;
215
205
 
216
- /**
217
- * Correlation ID to save along with track log. If not provided, will attempt to source from the
218
- * underlying Fetch Request.
219
- */
206
+ /** Correlation ID to save along with track log. */
220
207
  correlationId?: string;
221
208
 
222
209
  /** App-supplied data to save along with track log.*/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "67.0.0-SNAPSHOT.1723673844830",
3
+ "version": "67.0.0-SNAPSHOT.1723839594443",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -83,9 +83,9 @@
83
83
  "router5": "~7.0.2",
84
84
  "router5-plugin-browser": "~7.0.2",
85
85
  "semver": "~7.6.0",
86
+ "short-unique-id": "~5.2.0",
86
87
  "store2": "~2.13.1",
87
- "ua-parser-js": "~1.0.2",
88
- "uuid": "~10.0.0"
88
+ "ua-parser-js": "~1.0.2"
89
89
  },
90
90
  "peerDependencies": {
91
91
  "react": "~18.2.0",
@@ -92,12 +92,6 @@ declare global {
92
92
  * @param options - TrackOptions, or simply a message string.
93
93
  */
94
94
  track(options: TrackOptions | string): Promise<T>;
95
-
96
- /**
97
- * Set by FetchService to relay correlation IDs to downstream error handling and tracking.
98
- * @internal
99
- */
100
- correlationId?: string;
101
95
  }
102
96
  }
103
97
 
@@ -206,7 +200,7 @@ const enhancePromise = promisePrototype => {
206
200
  const startTime = Date.now();
207
201
  return this.finally(() => {
208
202
  options.elapsed = Date.now() - startTime;
209
- XH.track({correlationId: this.correlationId, ...options});
203
+ XH.track(options);
210
204
  });
211
205
  },
212
206
 
@@ -11,7 +11,6 @@ import {Timer} from '@xh/hoist/utils/async';
11
11
  import {MINUTES, olderThan, ONE_MINUTE, SECONDS} from '@xh/hoist/utils/datetime';
12
12
  import {isJSON, throwIf} from '@xh/hoist/utils/js';
13
13
  import {find, forEach, isEmpty, isObject, keys, pickBy, union} from 'lodash';
14
- import {v4 as uuid} from 'uuid';
15
14
 
16
15
  export type LoginMethod = 'REDIRECT' | 'POPUP';
17
16
 
@@ -255,7 +254,7 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
255
254
  protected captureRedirectState(): string {
256
255
  const {pathname, search} = window.location,
257
256
  state = {
258
- key: uuid(),
257
+ key: XH.genUUID(),
259
258
  timestamp: Date.now(),
260
259
  pathname,
261
260
  search
@@ -7,15 +7,15 @@
7
7
  import {
8
8
  Awaitable,
9
9
  Exception,
10
- FetchResponse,
11
10
  HoistService,
12
11
  LoadSpec,
13
12
  PlainObject,
13
+ TrackOptions,
14
14
  XH
15
15
  } from '@xh/hoist/core';
16
16
  import {PromiseTimeoutSpec} from '@xh/hoist/promise';
17
17
  import {isLocalDate, SECONDS} from '@xh/hoist/utils/datetime';
18
- import {apiDeprecated} from '@xh/hoist/utils/js';
18
+ import {apiDeprecated, warnIf} from '@xh/hoist/utils/js';
19
19
  import {StatusCodes} from 'http-status-codes';
20
20
  import {isDate, isFunction, isNil, isObject, isString, omit, omitBy} from 'lodash';
21
21
  import {IStringifyOptions, stringify} from 'qs';
@@ -107,13 +107,13 @@ export class FetchService extends HoistService {
107
107
  * requests. Other shortcut variants will delegate to this method, after setting
108
108
  * default options and pre-processing content.
109
109
  *
110
- * Set `asJson` to true return a parsed JSON result, rather than the raw FetchResponse.
110
+ * Set `asJson` to true return a parsed JSON result, rather than the raw Response.
111
111
  * Note that shortcut variant of this method (e.g. `fetchJson`, `postJson`) will set this
112
112
  * flag for you.
113
113
  *
114
- * @returns Promise which resolves to a FetchResponse or JSON.
114
+ * @returns Promise which resolves to a Response or JSON.
115
115
  */
116
- fetch(opts: FetchOptions): Promise<any> {
116
+ async fetch(opts: FetchOptions): Promise<any> {
117
117
  return this.fetchInternalAsync(opts);
118
118
  }
119
119
 
@@ -121,7 +121,7 @@ export class FetchService extends HoistService {
121
121
  * Send an HTTP request and decode the response as JSON.
122
122
  * @returns the decoded JSON object, or null if the response has status in {@link NO_JSON_RESPONSES}.
123
123
  */
124
- fetchJson(opts: FetchOptions): Promise<any> {
124
+ async fetchJson(opts: FetchOptions): Promise<any> {
125
125
  return this.fetchInternalAsync({asJson: true, ...opts});
126
126
  }
127
127
 
@@ -129,7 +129,7 @@ export class FetchService extends HoistService {
129
129
  * Send a GET request and decode the response as JSON.
130
130
  * @returns the decoded JSON object, or null if the response status is in {@link NO_JSON_RESPONSES}.
131
131
  */
132
- getJson(opts: FetchOptions): Promise<any> {
132
+ async getJson(opts: FetchOptions): Promise<any> {
133
133
  return this.fetchInternalAsync({asJson: true, method: 'GET', ...opts});
134
134
  }
135
135
 
@@ -137,7 +137,7 @@ export class FetchService extends HoistService {
137
137
  * Send a POST request with a JSON body and decode the response as JSON.
138
138
  * @returns the decoded JSON object, or null if the response status is in {@link NO_JSON_RESPONSES}.
139
139
  */
140
- postJson(opts: FetchOptions): Promise<any> {
140
+ async postJson(opts: FetchOptions): Promise<any> {
141
141
  return this.sendJsonInternalAsync({method: 'POST', ...opts});
142
142
  }
143
143
 
@@ -145,7 +145,7 @@ export class FetchService extends HoistService {
145
145
  * Send a PUT request with a JSON body and decode the response as JSON.
146
146
  * @returns the decoded JSON object, or null if the response status is in {@link NO_JSON_RESPONSES}.
147
147
  */
148
- putJson(opts: FetchOptions): Promise<any> {
148
+ async putJson(opts: FetchOptions): Promise<any> {
149
149
  return this.sendJsonInternalAsync({method: 'PUT', ...opts});
150
150
  }
151
151
 
@@ -153,7 +153,7 @@ export class FetchService extends HoistService {
153
153
  * Send a PATCH request with a JSON body and decode the response as JSON.
154
154
  * @returns the decoded JSON object, or null if the response status is in {@link NO_JSON_RESPONSES}.
155
155
  */
156
- patchJson(opts: FetchOptions): Promise<any> {
156
+ async patchJson(opts: FetchOptions): Promise<any> {
157
157
  return this.sendJsonInternalAsync({method: 'PATCH', ...opts});
158
158
  }
159
159
 
@@ -161,7 +161,7 @@ export class FetchService extends HoistService {
161
161
  * Send a DELETE request with optional JSON body and decode the optional response as JSON.
162
162
  * @returns the decoded JSON object, or null if the response status is in {@link NO_JSON_RESPONSES}.
163
163
  */
164
- deleteJson(opts: FetchOptions): Promise<any> {
164
+ async deleteJson(opts: FetchOptions): Promise<any> {
165
165
  return this.sendJsonInternalAsync({method: 'DELETE', ...opts});
166
166
  }
167
167
 
@@ -209,20 +209,30 @@ export class FetchService extends HoistService {
209
209
  //-----------------------
210
210
  // Implementation
211
211
  //-----------------------
212
- private fetchInternalAsync(opts: FetchOptions): Promise<any> {
212
+ private async fetchInternalAsync(opts: FetchOptions): Promise<any> {
213
213
  opts = this.withCorrelationId(opts);
214
- const ret = this.withDefaultHeadersAsync(opts).then(opts => {
215
- let fetchPromise = this.managedFetchAsync(opts);
216
- for (const interceptor of this._interceptors) {
217
- fetchPromise = fetchPromise.then(
218
- value => interceptor.onFulfilled(opts, value),
219
- cause => interceptor.onRejected(opts, cause)
220
- );
221
- }
222
- return fetchPromise;
223
- });
214
+ opts = await this.withDefaultHeadersAsync(opts);
215
+ let ret = this.managedFetchAsync(opts);
216
+
217
+ // Apply tracking
218
+ const {correlationId, loadSpec, track} = opts;
219
+ if (track) {
220
+ const trackOptions = isString(track) ? {message: track} : track;
221
+ warnIf(
222
+ trackOptions.correlationId || trackOptions.loadSpec,
223
+ 'Neither Correlation ID nor LoadSpec should be set in `FetchOptions.track`. Use `FetchOptions` top-level properties instead.'
224
+ );
225
+ ret = ret.track({...trackOptions, correlationId: correlationId as string, loadSpec});
226
+ }
227
+
228
+ // Apply interceptors
229
+ for (const interceptor of this._interceptors) {
230
+ ret = ret.then(
231
+ value => interceptor.onFulfilled(opts, value),
232
+ cause => interceptor.onRejected(opts, cause)
233
+ );
234
+ }
224
235
 
225
- ret.correlationId = opts.correlationId as string;
226
236
  return ret;
227
237
  }
228
238
 
@@ -322,7 +332,7 @@ export class FetchService extends HoistService {
322
332
  private async abortableFetchAsync(
323
333
  opts: FetchOptions,
324
334
  aborter: AbortController
325
- ): Promise<FetchResponse> {
335
+ ): Promise<Response> {
326
336
  // 1) Prepare URL
327
337
  let {url, method, headers, body, params} = opts,
328
338
  isRelativeUrl = !url.startsWith('/') && !url.includes('//');
@@ -363,12 +373,9 @@ export class FetchService extends HoistService {
363
373
  }
364
374
 
365
375
  // 4) Await underlying fetch and post-process response.
366
- const ret = (await fetch(url, fetchOpts)) as FetchResponse;
376
+ const ret = await fetch(url, fetchOpts);
367
377
 
368
- if (!ret.ok) {
369
- ret.responseText = await this.safeResponseTextAsync(ret);
370
- throw Exception.fetchError(opts, ret);
371
- }
378
+ if (!ret.ok) throw Exception.fetchError(opts, ret, await this.safeResponseTextAsync(ret));
372
379
 
373
380
  return ret;
374
381
  }
@@ -475,4 +482,10 @@ export interface FetchOptions {
475
482
  * True to decode the HTTP response as JSON. Default false.
476
483
  */
477
484
  asJson?: boolean;
485
+
486
+ /**
487
+ * If set, the request will be tracked via Hoist activity tracking. (Do not set `correlationId`
488
+ * here - use the top-level `correlationId` property instead.)
489
+ */
490
+ track?: string | TrackOptions;
478
491
  }
@@ -7,7 +7,7 @@
7
7
  import {HoistService, TrackOptions, XH} from '@xh/hoist/core';
8
8
  import {isOmitted} from '@xh/hoist/utils/impl';
9
9
  import {stripTags, withDefault} from '@xh/hoist/utils/js';
10
- import {isString} from 'lodash';
10
+ import {isNil, isString} from 'lodash';
11
11
 
12
12
  /**
13
13
  * Primary service for tracking any activity that an application's admins want to track.
@@ -104,7 +104,9 @@ export class TrackService extends HoistService {
104
104
  }
105
105
 
106
106
  const elapsedStr = query.elapsed != null ? `${query.elapsed}ms` : null,
107
- consoleMsgs = [query.category, query.msg, elapsedStr].filter(it => it != null);
107
+ consoleMsgs = [query.category, query.msg, query.correlationId, elapsedStr].filter(
108
+ it => !isNil(it)
109
+ );
108
110
 
109
111
  this.logInfo(...consoleMsgs);
110
112