@ethlete/query 4.6.1 → 4.7.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.
Files changed (29) hide show
  1. package/esm2022/index.mjs +2 -1
  2. package/esm2022/lib/devtools/components/public-api.mjs +2 -0
  3. package/esm2022/lib/devtools/components/query-devtools/public-api.mjs +2 -0
  4. package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +72 -0
  5. package/esm2022/lib/devtools/index.mjs +2 -0
  6. package/esm2022/lib/devtools/public-api.mjs +3 -0
  7. package/esm2022/lib/devtools/utils/index.mjs +2 -0
  8. package/esm2022/lib/devtools/utils/public-api.mjs +2 -0
  9. package/esm2022/lib/devtools/utils/query-client.utils.mjs +13 -0
  10. package/esm2022/lib/query/query.mjs +17 -4
  11. package/esm2022/lib/query-client/query-client.mjs +1 -1
  12. package/esm2022/lib/query-client/query-client.types.mjs +1 -1
  13. package/esm2022/lib/query-store/query-store.mjs +9 -2
  14. package/fesm2022/ethlete-query.mjs +104 -5
  15. package/fesm2022/ethlete-query.mjs.map +1 -1
  16. package/index.d.ts +1 -0
  17. package/lib/devtools/components/public-api.d.ts +1 -0
  18. package/lib/devtools/components/query-devtools/public-api.d.ts +1 -0
  19. package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +28 -0
  20. package/lib/devtools/index.d.ts +1 -0
  21. package/lib/devtools/public-api.d.ts +2 -0
  22. package/lib/devtools/utils/index.d.ts +1 -0
  23. package/lib/devtools/utils/public-api.d.ts +1 -0
  24. package/lib/devtools/utils/query-client.utils.d.ts +12 -0
  25. package/lib/query/query.d.ts +6 -2
  26. package/lib/query-client/query-client.d.ts +0 -1
  27. package/lib/query-client/query-client.types.d.ts +5 -0
  28. package/lib/query-store/query-store.d.ts +2 -1
  29. package/package.json +1 -1
@@ -1,8 +1,10 @@
1
1
  import * as i1 from '@ethlete/core';
2
2
  import { BehaviorSubjectWithSubscriberCount, getCookie, setCookie, deleteCookie, createDestroy, DelayableDirective, RouterStateService, clone, equal, ET_PROPERTY_REMOVED } from '@ethlete/core';
3
- import { Observable, filter, takeWhile, switchMap, of, tap, BehaviorSubject, shareReplay, Subject, takeUntil, interval, skip, map, timer, combineLatest, withLatestFrom, fromEvent, startWith, debounceTime, take, concat } from 'rxjs';
3
+ import { Observable, filter, takeWhile, switchMap, of, tap, BehaviorSubject, shareReplay, Subject, takeUntil, interval, skip, map, timer, startWith, combineLatest, withLatestFrom, fromEvent, debounceTime, take, concat } from 'rxjs';
4
4
  import * as i0 from '@angular/core';
5
- import { isDevMode, inject, TemplateRef, ViewContainerRef, ErrorHandler, ChangeDetectorRef, Directive, Input, InjectionToken, ElementRef, NgZone, assertInInjectionContext } from '@angular/core';
5
+ import { isDevMode, InjectionToken, inject, signal, computed, effect, Component, ChangeDetectionStrategy, ViewEncapsulation, TemplateRef, ViewContainerRef, ErrorHandler, ChangeDetectorRef, Directive, Input, ElementRef, NgZone, assertInInjectionContext } from '@angular/core';
6
+ import { NgIf, NgFor, AsyncPipe, JsonPipe } from '@angular/common';
7
+ import { toSignal } from '@angular/core/rxjs-interop';
6
8
  import { FormGroup } from '@angular/forms';
7
9
  import { Router, ActivatedRoute } from '@angular/router';
8
10
 
@@ -745,7 +747,13 @@ class Query {
745
747
  return ++this._currentId;
746
748
  }
747
749
  get state$() {
748
- return this._state$.pipe(switchMap((s) => this._transformState(s)), shareReplay({ bufferSize: 1, refCount: true }));
750
+ return this._state$.pipe(tap((s) => {
751
+ if (!this._client.config.logging?.preparedQuerySubscriptions)
752
+ return;
753
+ if (isQueryStatePrepared(s)) {
754
+ console.warn(`Query ${this._routeWithParams} was subscribed to in the prepared state. Did you forget to call .execute()?`);
755
+ }
756
+ }), switchMap((s) => this._transformState(s)), shareReplay({ bufferSize: 1, refCount: true }));
749
757
  }
750
758
  /**
751
759
  * The current state of the query.
@@ -769,6 +777,9 @@ class Query {
769
777
  get isInUse() {
770
778
  return this._state$.subscriberCount > 0;
771
779
  }
780
+ get _subscriberCount() {
781
+ return this._state$.subscriberCount;
782
+ }
772
783
  get isPolling() {
773
784
  return !!this._pollingSubscription;
774
785
  }
@@ -795,7 +806,11 @@ class Query {
795
806
  get store() {
796
807
  return this._queryConfig.entity?.store ?? null;
797
808
  }
798
- constructor(_client, _queryConfig, _routeWithParams, _args) {
809
+ constructor(_client, _queryConfig,
810
+ /**
811
+ * @internal
812
+ */
813
+ _routeWithParams, _args) {
799
814
  this._client = _client;
800
815
  this._queryConfig = _queryConfig;
801
816
  this._routeWithParams = _routeWithParams;
@@ -1159,6 +1174,83 @@ const isClassValidatorError = (error) => {
1159
1174
  return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
1160
1175
  };
1161
1176
 
1177
+ const QUERY_CLIENT_DEVTOOLS_TOKEN = new InjectionToken('QUERY_CLIENT_DEVTOOLS_TOKEN');
1178
+ const provideQueryClientForDevtools = (queryClient) => {
1179
+ if (!isDevMode()) {
1180
+ console.warn('You are using the Query Devtools in production mode. This increases the size of your bundle and should only be used for development purposes.');
1181
+ }
1182
+ return {
1183
+ provide: QUERY_CLIENT_DEVTOOLS_TOKEN,
1184
+ useValue: queryClient,
1185
+ multi: true,
1186
+ };
1187
+ };
1188
+
1189
+ class QueryDevtoolsComponent {
1190
+ constructor() {
1191
+ this._destroy$ = createDestroy();
1192
+ this.queryClientConfigs = inject(QUERY_CLIENT_DEVTOOLS_TOKEN);
1193
+ this.isOpen = signal(false);
1194
+ this.showResponse = signal(false);
1195
+ this.showRawResponse = signal(false);
1196
+ this.selectedClientId = signal(0);
1197
+ this.selectedQueryPath = signal(null);
1198
+ this.selectedClientConfig = computed(() => {
1199
+ if (this.queryClientConfigs.length === 1) {
1200
+ return this.queryClientConfigs[0];
1201
+ }
1202
+ const index = this.selectedClientId();
1203
+ return this.queryClientConfigs[index];
1204
+ });
1205
+ this.queryStore = computed(() => {
1206
+ const config = this.selectedClientConfig();
1207
+ return config.client._store;
1208
+ });
1209
+ this._queries$ = new BehaviorSubject([]);
1210
+ this.queries = toSignal(this._queries$);
1211
+ this.selectedQuery = computed(() => {
1212
+ const path = this.selectedQueryPath();
1213
+ if (!path) {
1214
+ return null;
1215
+ }
1216
+ return this.queries()?.find((q) => q._routeWithParams === path) ?? null;
1217
+ });
1218
+ this.trackByClient = (_, { client }) => client.config.baseRoute;
1219
+ this.trackByQuery = (_, { _routeWithParams }) => _routeWithParams;
1220
+ const change$ = new Subject();
1221
+ effect(() => {
1222
+ change$.next(true);
1223
+ const store = this.queryStore();
1224
+ store.storeChange$
1225
+ .pipe(startWith(''), map(() => store._store), tap((s) => this._queries$.next(Array.from(s.values()))), takeUntil(change$), takeUntil(this._destroy$))
1226
+ .subscribe();
1227
+ }, { allowSignalWrites: true });
1228
+ }
1229
+ toggleOpen() {
1230
+ this.isOpen.set(!this.isOpen());
1231
+ }
1232
+ toggleShowResponse() {
1233
+ this.showResponse.set(!this.showResponse());
1234
+ }
1235
+ toggleShowRawResponse() {
1236
+ this.showRawResponse.set(!this.showRawResponse());
1237
+ }
1238
+ selectClient(clientId) {
1239
+ this.selectedClientId.set(clientId);
1240
+ }
1241
+ selectQuery(query) {
1242
+ this.selectedQueryPath.set(query._routeWithParams);
1243
+ }
1244
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.5", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1245
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.5", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools" }, ngImport: i0, template: "<button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn\">Open Devtools</button>\n\n<div *ngIf=\"isOpen()\" class=\"et-query-devtools-container\">\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-close-btn\">Close Devtools</button>\n\n <ng-container *ngIf=\"queryClientConfigs.length > 1\">\n <p>Client instance:</p>\n <ul>\n <li *ngFor=\"let clientConfig of queryClientConfigs; index as i; trackBy: trackByClient\">\n <button (click)=\"selectClient(i)\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </button>\n </li>\n </ul>\n </ng-container>\n\n <ng-container *ngIf=\"selectedClientConfig() as cfg\">\n <p>{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\">Force Refresh of in use queries</button> <br />\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\">HARD Force Refresh</button>\n <br />\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\">\n PURGE & HARD Force Refresh\n </button>\n\n <ul>\n <li *ngFor=\"let query of queries(); trackBy: trackByQuery\">\n <button (click)=\"selectQuery(query)\">\n {{ query._routeWithParams }}\n </button>\n </li>\n </ul>\n </ng-container>\n\n <ng-container *ngIf=\"selectedQuery() as query\">\n <p>{{ query._routeWithParams }}</p>\n\n <button (click)=\"query.execute()\">Execute</button> <br />\n <button (click)=\"query.execute({ skipCache: true })\">FORCE Execute</button> <br />\n <button (click)=\"query.abort()\">Abort</button> <br />\n <button (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\">Poll</button> <br />\n <button (click)=\"query.stopPolling()\">Stop polling</button> <br />\n <button (click)=\"query.pausePolling()\">Pause polling</button> <br />\n <button (click)=\"query.resumePolling()\">Resume polling</button> <br />\n\n <div>\n <p>state type</p>\n <pre>{{ (query.state$ | async)?.type }}</pre>\n </div>\n\n <div>\n <p>_enableSmartPolling:</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div>\n <p>isExpired:</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div>\n <p>isInUse:</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div>\n <p>_subscriberCount:</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div>\n <p>isPolling:</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div>\n <p>_arguments:</p>\n <pre>{{ query._arguments | json }}</pre>\n </div>\n\n <div>\n <p>state$:</p>\n <button (click)=\"toggleShowResponse()\">Show/Hide</button>\n <pre *ngIf=\"showResponse()\">{{ query.state$ | async | json }}</pre>\n </div>\n\n <div>\n <p>rawState:</p>\n <button (click)=\"toggleShowRawResponse()\">Show/Hide</button>\n <pre *ngIf=\"showRawResponse()\">{{ query.rawState | json }}</pre>\n </div>\n </ng-container>\n</div>\n", styles: [".et-query-devtools-container{position:fixed;inset:0;background-color:#000;z-index:10000}.et-query-devtools-open-btn{position:fixed;inset-inline-start:0;inset-block-start:0;z-index:9999}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom }); }
1246
+ }
1247
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.5", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
1248
+ type: Component,
1249
+ args: [{ selector: 'et-query-devtools', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
1250
+ class: 'et-query-devtools',
1251
+ }, imports: [NgIf, NgFor, AsyncPipe, JsonPipe], hostDirectives: [], template: "<button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn\">Open Devtools</button>\n\n<div *ngIf=\"isOpen()\" class=\"et-query-devtools-container\">\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-close-btn\">Close Devtools</button>\n\n <ng-container *ngIf=\"queryClientConfigs.length > 1\">\n <p>Client instance:</p>\n <ul>\n <li *ngFor=\"let clientConfig of queryClientConfigs; index as i; trackBy: trackByClient\">\n <button (click)=\"selectClient(i)\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </button>\n </li>\n </ul>\n </ng-container>\n\n <ng-container *ngIf=\"selectedClientConfig() as cfg\">\n <p>{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\">Force Refresh of in use queries</button> <br />\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\">HARD Force Refresh</button>\n <br />\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\">\n PURGE & HARD Force Refresh\n </button>\n\n <ul>\n <li *ngFor=\"let query of queries(); trackBy: trackByQuery\">\n <button (click)=\"selectQuery(query)\">\n {{ query._routeWithParams }}\n </button>\n </li>\n </ul>\n </ng-container>\n\n <ng-container *ngIf=\"selectedQuery() as query\">\n <p>{{ query._routeWithParams }}</p>\n\n <button (click)=\"query.execute()\">Execute</button> <br />\n <button (click)=\"query.execute({ skipCache: true })\">FORCE Execute</button> <br />\n <button (click)=\"query.abort()\">Abort</button> <br />\n <button (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\">Poll</button> <br />\n <button (click)=\"query.stopPolling()\">Stop polling</button> <br />\n <button (click)=\"query.pausePolling()\">Pause polling</button> <br />\n <button (click)=\"query.resumePolling()\">Resume polling</button> <br />\n\n <div>\n <p>state type</p>\n <pre>{{ (query.state$ | async)?.type }}</pre>\n </div>\n\n <div>\n <p>_enableSmartPolling:</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div>\n <p>isExpired:</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div>\n <p>isInUse:</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div>\n <p>_subscriberCount:</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div>\n <p>isPolling:</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div>\n <p>_arguments:</p>\n <pre>{{ query._arguments | json }}</pre>\n </div>\n\n <div>\n <p>state$:</p>\n <button (click)=\"toggleShowResponse()\">Show/Hide</button>\n <pre *ngIf=\"showResponse()\">{{ query.state$ | async | json }}</pre>\n </div>\n\n <div>\n <p>rawState:</p>\n <button (click)=\"toggleShowRawResponse()\">Show/Hide</button>\n <pre *ngIf=\"showRawResponse()\">{{ query.rawState | json }}</pre>\n </div>\n </ng-container>\n</div>\n", styles: [".et-query-devtools-container{position:fixed;inset:0;background-color:#000;z-index:10000}.et-query-devtools-open-btn{position:fixed;inset-inline-start:0;inset-block-start:0;z-index:9999}\n"] }]
1252
+ }], ctorParameters: function () { return []; } });
1253
+
1162
1254
  class QueryDirective {
1163
1255
  constructor() {
1164
1256
  this._mainTemplateRef = inject(TemplateRef);
@@ -1832,16 +1924,22 @@ class QueryCreator {
1832
1924
  class QueryStore {
1833
1925
  constructor(_config) {
1834
1926
  this._config = _config;
1927
+ /**
1928
+ * @internal
1929
+ */
1835
1930
  this._store = new Map();
1836
1931
  this._garbageCollector = null;
1837
1932
  this._isInLowResourceMode = false;
1838
1933
  this._lastBlurTimestamp = Date.now();
1934
+ this._storeChange$ = new Subject();
1935
+ this.storeChange$ = this._storeChange$.asObservable();
1839
1936
  this._initSmartQueryHandling();
1840
1937
  }
1841
1938
  add(id, query) {
1842
1939
  this._store.set(id, query);
1843
1940
  this._initGarbageCollector();
1844
1941
  this._logState(id, query, 'SET');
1942
+ this._storeChange$.next(id);
1845
1943
  }
1846
1944
  get(id) {
1847
1945
  return this._store.get(id) ?? null;
@@ -1850,6 +1948,7 @@ class QueryStore {
1850
1948
  this.get(id)?._destroy();
1851
1949
  this._store.delete(id);
1852
1950
  this._logState(id, null, 'REMOVE');
1951
+ this._storeChange$.next(id);
1853
1952
  }
1854
1953
  forEach(callback) {
1855
1954
  for (const [key, query] of this._store) {
@@ -2406,5 +2505,5 @@ const def = () => null;
2406
2505
  * Generated bundle index. Do not edit.
2407
2506
  */
2408
2507
 
2409
- export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, EntityStore, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, Query, QueryClient, QueryCreator, QueryDirective, QueryError, QueryField, QueryForm, QueryFormGroup, QueryStore, buildErrorMessage, buildGqlCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, createInfinityQueryConfig, createQueryCollection, decryptBearer, deepFreeze, def, detectContentTypeHeader, extractExpiresInSeconds, extractQuery, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getResponseUrl, gql, hasHeader, ignoreAutoRefresh, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isNaN$1 as isNaN, isQuery, isQueryCollection, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyPagerfantaOutOfRangeError, mapToPaginated, mergeHeaders, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, pathParamsMissingInRouteFunctionError, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, removeFrom, request, resetPageOnError, serializeBody, shouldCacheQuery, shouldRetryRequest, skipPaginationPageParamCalculator, switchQueryCollectionState, switchQueryState, takeUntilResponse, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray };
2508
+ export { BasicAuthProvider, BearerAuthProvider, CustomHeaderAuthProvider, EntityStore, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, QUERY_CLIENT_DEVTOOLS_TOKEN, Query, QueryClient, QueryCreator, QueryDevtoolsComponent, QueryDirective, QueryError, QueryField, QueryForm, QueryFormGroup, QueryStore, buildErrorMessage, buildGqlCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, createInfinityQueryConfig, createQueryCollection, decryptBearer, deepFreeze, def, detectContentTypeHeader, extractExpiresInSeconds, extractQuery, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getResponseUrl, gql, hasHeader, ignoreAutoRefresh, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isNaN$1 as isNaN, isQuery, isQueryCollection, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyPagerfantaOutOfRangeError, mapToPaginated, mergeHeaders, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, pathParamsMissingInRouteFunctionError, provideQueryClientForDevtools, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, removeFrom, request, resetPageOnError, serializeBody, shouldCacheQuery, shouldRetryRequest, skipPaginationPageParamCalculator, switchQueryCollectionState, switchQueryState, takeUntilResponse, transformGql, transformMethod, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray };
2410
2509
  //# sourceMappingURL=ethlete-query.mjs.map