@ethlete/query 5.15.1 → 5.16.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 (184) hide show
  1. package/.eslintrc.json +40 -0
  2. package/CHANGELOG.md +8 -0
  3. package/jest.config.ts +23 -0
  4. package/ng-package.json +8 -0
  5. package/package.json +6 -23
  6. package/project.json +37 -0
  7. package/{index.d.ts → src/index.ts} +2 -0
  8. package/src/lib/auth/auth-provider.types.ts +160 -0
  9. package/src/lib/auth/auth-provider.utils.ts +39 -0
  10. package/src/lib/auth/basic-auth-provider.ts +17 -0
  11. package/src/lib/auth/bearer-auth-provider.ts +243 -0
  12. package/src/lib/auth/custom-header-auth-provider.ts +13 -0
  13. package/{lib/class-validator/class-validator.types.d.ts → src/lib/class-validator/class-validator.types.ts} +4 -3
  14. package/src/lib/class-validator/class-validator.utils.ts +5 -0
  15. package/src/lib/devtools/components/query-devtools/query-devtools.component.html +402 -0
  16. package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +360 -0
  17. package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +427 -0
  18. package/src/lib/devtools/pipes/query-short-name.pipe.ts +24 -0
  19. package/src/lib/devtools/utils/query-client.utils.ts +25 -0
  20. package/src/lib/directives/infinity-query-trigger.directive.ts +58 -0
  21. package/src/lib/directives/infinity-query.directive.ts +208 -0
  22. package/src/lib/directives/query.directive.ts +201 -0
  23. package/src/lib/entity/entity-store.ts +180 -0
  24. package/src/lib/entity/entity.types.ts +34 -0
  25. package/src/lib/entity/entity.utils.ts +51 -0
  26. package/src/lib/gql/gql.ts +22 -0
  27. package/src/lib/infinite-query/infinity-query.ts +245 -0
  28. package/src/lib/infinite-query/infinity-query.types.ts +149 -0
  29. package/src/lib/infinite-query/infinity-query.utils.ts +14 -0
  30. package/src/lib/logger/logger.ts +30 -0
  31. package/src/lib/query/query.ts +631 -0
  32. package/src/lib/query/query.types.ts +532 -0
  33. package/src/lib/query/query.utils.spec.ts +92 -0
  34. package/src/lib/query/query.utils.ts +450 -0
  35. package/src/lib/query-client/query-client.spec.ts +77 -0
  36. package/src/lib/query-client/query-client.ts +195 -0
  37. package/src/lib/query-client/query-client.types.ts +101 -0
  38. package/src/lib/query-client/query-client.utils.spec.ts +61 -0
  39. package/src/lib/query-client/query-client.utils.ts +28 -0
  40. package/src/lib/query-creator/query-creator.ts +103 -0
  41. package/src/lib/query-creator/query-creator.types.ts +74 -0
  42. package/src/lib/query-form/query-form.ts +526 -0
  43. package/src/lib/query-form/query-form.types.ts +169 -0
  44. package/src/lib/query-form/query-form.utils.ts +116 -0
  45. package/src/lib/query-store/query-store.ts +192 -0
  46. package/src/lib/request/request.ts +253 -0
  47. package/src/lib/request/request.types.ts +164 -0
  48. package/src/lib/request/request.util.spec.ts +177 -0
  49. package/src/lib/request/request.util.ts +399 -0
  50. package/src/lib/stories/angular-example-page.mdx +85 -0
  51. package/src/lib/stories/authentication-page.mdx +35 -0
  52. package/src/lib/stories/changelog-page.mdx +9 -0
  53. package/src/lib/stories/client-page.mdx +100 -0
  54. package/src/lib/stories/queries-page.mdx +117 -0
  55. package/src/lib/symfony/symfony.types.ts +21 -0
  56. package/src/lib/symfony/symfony.utils.ts +43 -0
  57. package/src/lib/utils/data.utils.ts +280 -0
  58. package/src/lib/utils/deep-freeze.util.ts +21 -0
  59. package/{lib/utils/def.util.d.ts → src/lib/utils/def.util.ts} +1 -1
  60. package/src/test-setup.ts +1 -0
  61. package/tsconfig.json +29 -0
  62. package/tsconfig.lib.json +12 -0
  63. package/tsconfig.lib.prod.json +10 -0
  64. package/tsconfig.spec.json +11 -0
  65. package/esm2022/ethlete-query.mjs +0 -5
  66. package/esm2022/index.mjs +0 -20
  67. package/esm2022/lib/auth/auth-provider.types.mjs +0 -2
  68. package/esm2022/lib/auth/auth-provider.utils.mjs +0 -25
  69. package/esm2022/lib/auth/basic-auth-provider.mjs +0 -15
  70. package/esm2022/lib/auth/bearer-auth-provider.mjs +0 -168
  71. package/esm2022/lib/auth/custom-header-auth-provider.mjs +0 -12
  72. package/esm2022/lib/auth/index.mjs +0 -6
  73. package/esm2022/lib/class-validator/class-validator.types.mjs +0 -2
  74. package/esm2022/lib/class-validator/class-validator.utils.mjs +0 -4
  75. package/esm2022/lib/class-validator/index.mjs +0 -3
  76. package/esm2022/lib/devtools/components/query-devtools/index.mjs +0 -2
  77. package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +0 -297
  78. package/esm2022/lib/devtools/pipes/index.mjs +0 -2
  79. package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +0 -24
  80. package/esm2022/lib/devtools/utils/index.mjs +0 -2
  81. package/esm2022/lib/devtools/utils/query-client.utils.mjs +0 -13
  82. package/esm2022/lib/directives/index.mjs +0 -4
  83. package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +0 -56
  84. package/esm2022/lib/directives/infinity-query.directive.mjs +0 -147
  85. package/esm2022/lib/directives/query.directive.mjs +0 -123
  86. package/esm2022/lib/entity/entity-store.mjs +0 -127
  87. package/esm2022/lib/entity/entity.types.mjs +0 -2
  88. package/esm2022/lib/entity/entity.utils.mjs +0 -29
  89. package/esm2022/lib/entity/index.mjs +0 -4
  90. package/esm2022/lib/gql/gql.mjs +0 -23
  91. package/esm2022/lib/gql/index.mjs +0 -2
  92. package/esm2022/lib/infinite-query/index.mjs +0 -4
  93. package/esm2022/lib/infinite-query/infinity-query.mjs +0 -171
  94. package/esm2022/lib/infinite-query/infinity-query.types.mjs +0 -2
  95. package/esm2022/lib/infinite-query/infinity-query.utils.mjs +0 -3
  96. package/esm2022/lib/logger/index.mjs +0 -2
  97. package/esm2022/lib/logger/logger.mjs +0 -16
  98. package/esm2022/lib/query/index.mjs +0 -4
  99. package/esm2022/lib/query/query.mjs +0 -428
  100. package/esm2022/lib/query/query.types.mjs +0 -2
  101. package/esm2022/lib/query/query.utils.mjs +0 -253
  102. package/esm2022/lib/query-client/index.mjs +0 -4
  103. package/esm2022/lib/query-client/query-client.mjs +0 -80
  104. package/esm2022/lib/query-client/query-client.types.mjs +0 -2
  105. package/esm2022/lib/query-client/query-client.utils.mjs +0 -20
  106. package/esm2022/lib/query-creator/index.mjs +0 -3
  107. package/esm2022/lib/query-creator/query-creator.mjs +0 -55
  108. package/esm2022/lib/query-creator/query-creator.types.mjs +0 -2
  109. package/esm2022/lib/query-form/index.mjs +0 -4
  110. package/esm2022/lib/query-form/query-form.mjs +0 -368
  111. package/esm2022/lib/query-form/query-form.types.mjs +0 -2
  112. package/esm2022/lib/query-form/query-form.utils.mjs +0 -97
  113. package/esm2022/lib/query-store/index.mjs +0 -2
  114. package/esm2022/lib/query-store/query-store.mjs +0 -144
  115. package/esm2022/lib/request/index.mjs +0 -4
  116. package/esm2022/lib/request/request.mjs +0 -196
  117. package/esm2022/lib/request/request.types.mjs +0 -2
  118. package/esm2022/lib/request/request.util.mjs +0 -252
  119. package/esm2022/lib/symfony/index.mjs +0 -3
  120. package/esm2022/lib/symfony/symfony.types.mjs +0 -2
  121. package/esm2022/lib/symfony/symfony.utils.mjs +0 -31
  122. package/esm2022/lib/utils/data.utils.mjs +0 -138
  123. package/esm2022/lib/utils/deep-freeze.util.mjs +0 -17
  124. package/esm2022/lib/utils/def.util.mjs +0 -8
  125. package/esm2022/lib/utils/index.mjs +0 -4
  126. package/fesm2022/ethlete-query.mjs +0 -3276
  127. package/fesm2022/ethlete-query.mjs.map +0 -1
  128. package/lib/auth/auth-provider.types.d.ts +0 -133
  129. package/lib/auth/auth-provider.utils.d.ts +0 -9
  130. package/lib/auth/basic-auth-provider.d.ts +0 -10
  131. package/lib/auth/bearer-auth-provider.d.ts +0 -23
  132. package/lib/auth/custom-header-auth-provider.d.ts +0 -9
  133. package/lib/class-validator/class-validator.utils.d.ts +0 -2
  134. package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +0 -151
  135. package/lib/devtools/pipes/query-short-name.pipe.d.ts +0 -8
  136. package/lib/devtools/utils/query-client.utils.d.ts +0 -12
  137. package/lib/directives/infinity-query-trigger.directive.d.ts +0 -15
  138. package/lib/directives/infinity-query.directive.d.ts +0 -49
  139. package/lib/directives/query.directive.d.ts +0 -63
  140. package/lib/entity/entity-store.d.ts +0 -25
  141. package/lib/entity/entity.types.d.ts +0 -25
  142. package/lib/entity/entity.utils.d.ts +0 -12
  143. package/lib/gql/gql.d.ts +0 -6
  144. package/lib/infinite-query/infinity-query.d.ts +0 -29
  145. package/lib/infinite-query/infinity-query.types.d.ts +0 -104
  146. package/lib/infinite-query/infinity-query.utils.d.ts +0 -4
  147. package/lib/logger/logger.d.ts +0 -12
  148. package/lib/query/query.d.ts +0 -60
  149. package/lib/query/query.types.d.ts +0 -388
  150. package/lib/query/query.utils.d.ts +0 -91
  151. package/lib/query-client/query-client.d.ts +0 -29
  152. package/lib/query-client/query-client.types.d.ts +0 -82
  153. package/lib/query-client/query-client.utils.d.ts +0 -4
  154. package/lib/query-creator/query-creator.d.ts +0 -20
  155. package/lib/query-creator/query-creator.types.d.ts +0 -13
  156. package/lib/query-form/query-form.d.ts +0 -89
  157. package/lib/query-form/query-form.types.d.ts +0 -138
  158. package/lib/query-form/query-form.utils.d.ts +0 -13
  159. package/lib/query-store/query-store.d.ts +0 -31
  160. package/lib/request/request.d.ts +0 -3
  161. package/lib/request/request.types.d.ts +0 -126
  162. package/lib/request/request.util.d.ts +0 -92
  163. package/lib/symfony/symfony.types.d.ts +0 -19
  164. package/lib/symfony/symfony.utils.d.ts +0 -6
  165. package/lib/utils/data.utils.d.ts +0 -59
  166. package/lib/utils/deep-freeze.util.d.ts +0 -1
  167. /package/{lib/auth/index.d.ts → src/lib/auth/index.ts} +0 -0
  168. /package/{lib/class-validator/index.d.ts → src/lib/class-validator/index.ts} +0 -0
  169. /package/{lib/devtools/components/query-devtools/index.d.ts → src/lib/devtools/components/query-devtools/index.ts} +0 -0
  170. /package/{lib/devtools/pipes/index.d.ts → src/lib/devtools/pipes/index.ts} +0 -0
  171. /package/{lib/devtools/utils/index.d.ts → src/lib/devtools/utils/index.ts} +0 -0
  172. /package/{lib/directives/index.d.ts → src/lib/directives/index.ts} +0 -0
  173. /package/{lib/entity/index.d.ts → src/lib/entity/index.ts} +0 -0
  174. /package/{lib/gql/index.d.ts → src/lib/gql/index.ts} +0 -0
  175. /package/{lib/infinite-query/index.d.ts → src/lib/infinite-query/index.ts} +0 -0
  176. /package/{lib/logger/index.d.ts → src/lib/logger/index.ts} +0 -0
  177. /package/{lib/query/index.d.ts → src/lib/query/index.ts} +0 -0
  178. /package/{lib/query-client/index.d.ts → src/lib/query-client/index.ts} +0 -0
  179. /package/{lib/query-creator/index.d.ts → src/lib/query-creator/index.ts} +0 -0
  180. /package/{lib/query-form/index.d.ts → src/lib/query-form/index.ts} +0 -0
  181. /package/{lib/query-store/index.d.ts → src/lib/query-store/index.ts} +0 -0
  182. /package/{lib/request/index.d.ts → src/lib/request/index.ts} +0 -0
  183. /package/{lib/symfony/index.d.ts → src/lib/symfony/index.ts} +0 -0
  184. /package/{lib/utils/index.d.ts → src/lib/utils/index.ts} +0 -0
@@ -0,0 +1,427 @@
1
+ import { AsyncPipe, JsonPipe, NgClass } from '@angular/common';
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ ChangeDetectorRef,
5
+ Component,
6
+ TrackByFunction,
7
+ ViewEncapsulation,
8
+ computed,
9
+ effect,
10
+ inject,
11
+ signal,
12
+ untracked,
13
+ } from '@angular/core';
14
+ import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
15
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
16
+ import { createDestroy, nextFrame } from '@ethlete/core';
17
+ import { BehaviorSubject, map, of, startWith, switchMap, takeUntil, tap } from 'rxjs';
18
+ import { BasicAuthProvider, CustomHeaderAuthProvider, isBearerAuthProvider } from '../../../auth';
19
+ import { QueryDirective } from '../../../directives';
20
+ import { EntityStore } from '../../../entity';
21
+ import { AnyQuery, isGqlQueryConfig } from '../../../query';
22
+ import { QueryShortNamePipe } from '../../pipes';
23
+ import { QUERY_CLIENT_DEVTOOLS_TOKEN, QueryClientDevtoolsOptions } from '../../utils';
24
+
25
+ type QueryDevtoolsSnapLayout = 'full' | 'left' | 'right' | 'bottom' | 'top';
26
+ type QueryListMode = 'live' | 'history';
27
+ type QueryViewMode = 'query' | 'authProvider';
28
+
29
+ @Component({
30
+ selector: 'et-query-devtools',
31
+ templateUrl: './query-devtools.component.html',
32
+ styleUrls: ['./query-devtools.component.scss'],
33
+ standalone: true,
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ encapsulation: ViewEncapsulation.ShadowDom,
36
+ // eslint-disable-next-line @angular-eslint/no-host-metadata-property
37
+ host: {
38
+ class: 'et-query-devtools',
39
+ },
40
+ imports: [AsyncPipe, JsonPipe, ReactiveFormsModule, NgClass, QueryShortNamePipe, QueryDirective],
41
+ })
42
+ export class QueryDevtoolsComponent {
43
+ private readonly _cdr = inject(ChangeDetectorRef);
44
+ protected readonly _destroy$ = createDestroy();
45
+
46
+ protected readonly queryClientConfigs = inject(QUERY_CLIENT_DEVTOOLS_TOKEN);
47
+
48
+ protected readonly isOpen = signal(false);
49
+ protected readonly isTranslucent = signal(false);
50
+ protected readonly snapLayout = signal<QueryDevtoolsSnapLayout>('full');
51
+ protected readonly queryListMode = signal<QueryListMode>('live');
52
+ protected readonly viewMode = signal<QueryViewMode>('query');
53
+
54
+ protected showResponse = signal(false);
55
+ protected showRawResponse = signal(false);
56
+ protected showQueryConfig = signal(false);
57
+ protected showEntityStoreValue = signal(false);
58
+ protected showArguments = signal(false);
59
+
60
+ protected selectedClientIdCtrl = new FormControl(0);
61
+
62
+ protected readonly selectedClientId = toSignal(
63
+ this.selectedClientIdCtrl.valueChanges.pipe(startWith(this.selectedClientIdCtrl.value)),
64
+ );
65
+ protected readonly selectedQueryId = signal<number | null>(null);
66
+
67
+ protected readonly selectedClientConfig = computed(() => {
68
+ if (this.queryClientConfigs.length === 1) {
69
+ return this.queryClientConfigs[0]!;
70
+ }
71
+
72
+ const index = this.selectedClientId() ?? 0;
73
+
74
+ const cfg = this.queryClientConfigs[index];
75
+
76
+ if (!cfg) {
77
+ return this.queryClientConfigs[0]!;
78
+ }
79
+
80
+ return cfg;
81
+ });
82
+
83
+ protected readonly queryStore = computed(() => {
84
+ const config = this.selectedClientConfig();
85
+
86
+ return config.client._store;
87
+ });
88
+
89
+ protected readonly queryStore$ = toObservable(this.queryStore);
90
+
91
+ private readonly _queries$ = new BehaviorSubject<AnyQuery[]>([]);
92
+ protected readonly queries = toSignal(this._queries$);
93
+
94
+ protected readonly selectedQuery = computed(() => {
95
+ const id = this.selectedQueryId();
96
+ const mode = this.queryListMode();
97
+
98
+ if (id === null || !mode) {
99
+ return null;
100
+ }
101
+
102
+ if (mode === 'live') {
103
+ return this.queries()?.find((q) => q._id === id) ?? null;
104
+ }
105
+
106
+ return this.queryHistory$.getValue()?.find((q) => q._id === id) ?? null;
107
+ });
108
+
109
+ protected readonly selectedQuery$ = toObservable(this.selectedQuery);
110
+
111
+ protected selectedQueryEntityStore = computed(() => {
112
+ const query = this.selectedQuery();
113
+
114
+ if (!query) return null;
115
+
116
+ return query.store as EntityStore<unknown> | null;
117
+ });
118
+
119
+ protected readonly selectedQueryEntityStore$ = toObservable(this.selectedQueryEntityStore);
120
+
121
+ protected readonly selectedQueryEntityStoreValue$ = this.selectedQueryEntityStore$.pipe(
122
+ switchMap(
123
+ (s) =>
124
+ s?._change$.pipe(
125
+ startWith(''),
126
+ map(() => {
127
+ if (!s._dictionary) return null;
128
+
129
+ return Array.from(s._dictionary).reduce(
130
+ (obj, [key, value]) => {
131
+ obj[key] = value;
132
+ return obj;
133
+ },
134
+ {} as Record<string, unknown>,
135
+ );
136
+ }),
137
+ ) ?? of(null),
138
+ ),
139
+ );
140
+
141
+ protected readonly queryHistory$ = new BehaviorSubject<AnyQuery[]>([]);
142
+
143
+ protected readonly stringifiedQueryConfig = computed(() => {
144
+ const query = this.selectedQuery();
145
+
146
+ if (!query) return null;
147
+
148
+ const cfg = query._queryConfig;
149
+ const route =
150
+ typeof cfg.route === 'string' ? cfg.route : cfg.route ? cfg.route({}).replace(/undefined/g, ':param') : null;
151
+
152
+ const sharedCfg = {
153
+ method: cfg.method,
154
+ secure: cfg.secure,
155
+ route,
156
+ reportProgress: cfg.reportProgress,
157
+ responseType: cfg.responseType,
158
+ withCredentials: cfg.withCredentials,
159
+ autoRefreshOn: cfg.autoRefreshOn,
160
+ enableSmartPolling: cfg.enableSmartPolling,
161
+ hasEntityStore: !!cfg.entity,
162
+ };
163
+
164
+ if (isGqlQueryConfig(cfg)) {
165
+ return {
166
+ ...sharedCfg,
167
+ query: cfg.query,
168
+ transferVia: cfg.transferVia,
169
+ };
170
+ }
171
+
172
+ return {
173
+ ...sharedCfg,
174
+ };
175
+ });
176
+
177
+ protected readonly authProvider = toSignal(
178
+ toObservable(this.selectedClientConfig).pipe(switchMap((c) => c.client.authProvider$)),
179
+ );
180
+
181
+ protected readonly authProviderDetails = computed(() => {
182
+ const provider = this.authProvider();
183
+
184
+ if (!provider) return null;
185
+
186
+ if (provider instanceof CustomHeaderAuthProvider) {
187
+ return {
188
+ type: 'CustomHeaderAuthProvider',
189
+ header: () => provider.header,
190
+ provider: provider,
191
+ config: {
192
+ name: provider._config.name,
193
+ value: provider._config.value,
194
+ },
195
+ } as const;
196
+ } else if (provider instanceof BasicAuthProvider) {
197
+ return {
198
+ type: 'BasicAuthProvider',
199
+ header: () => provider.header,
200
+ provider: provider,
201
+ config: {
202
+ username: provider._config.username,
203
+ password: provider._config.password,
204
+ },
205
+ } as const;
206
+ } else if (isBearerAuthProvider(provider)) {
207
+ const refreshCfg = provider._config.refreshConfig;
208
+
209
+ return {
210
+ type: 'BearerAuthProvider',
211
+ header: () => provider.header,
212
+ provider: provider,
213
+ config: {
214
+ token: provider._config.token,
215
+ refreshConfig: {
216
+ refreshOnUnauthorizedResponse: refreshCfg?.refreshOnUnauthorizedResponse ?? 'true (default)',
217
+ token: refreshCfg?.token,
218
+ cookieName: refreshCfg?.cookieName,
219
+ cookieDomain: refreshCfg?.cookieDomain ?? 'current origin (default)',
220
+ cookieExpiresInDays: refreshCfg?.cookieExpiresInDays ?? '30 (default)',
221
+ cookiePath: refreshCfg?.cookiePath ?? '/ (default)',
222
+ cookieEnabled: refreshCfg?.cookieEnabled ?? 'true (default)',
223
+ cookieSameSite: refreshCfg?.cookieSameSite ?? 'lax (default)',
224
+ refreshBuffer: refreshCfg?.refreshBuffer ?? '30000 (default)',
225
+ expiresInPropertyName: refreshCfg?.expiresInPropertyName ?? 'exp (default)',
226
+ strategy: refreshCfg?.strategy ?? 'AuthBearerRefreshStrategy.BeforeExpiration (default)',
227
+ requestArgsAdapter: refreshCfg?.requestArgsAdapter ? 'custom' : 'default',
228
+ responseAdapter: refreshCfg?.responseAdapter ? 'custom' : 'default',
229
+ },
230
+ },
231
+ } as const;
232
+ } else {
233
+ return {
234
+ type: 'UnknownAuthProvider',
235
+ header: () => provider.header,
236
+ provider: provider,
237
+ config: {},
238
+ } as const;
239
+ }
240
+ });
241
+
242
+ constructor() {
243
+ this.selectedClientIdCtrl.valueChanges
244
+ .pipe(
245
+ tap(() => this.clearQueryHistory()),
246
+ takeUntil(this._destroy$),
247
+ )
248
+ .subscribe();
249
+
250
+ this.selectedQuery$
251
+ .pipe(
252
+ switchMap((q) => q?._dependentsChanged$ ?? of(null)),
253
+ tap(() => this._cdr.markForCheck()),
254
+ takeUntilDestroyed(),
255
+ )
256
+ .subscribe();
257
+
258
+ toObservable(this.authProviderDetails)
259
+ .pipe(switchMap((ap) => (ap?.type === 'BearerAuthProvider' ? ap.provider.tokens$ : of(null))))
260
+ .pipe(
261
+ tap(() => this._cdr.markForCheck()),
262
+ takeUntilDestroyed(),
263
+ )
264
+ .subscribe();
265
+
266
+ this.queryStore$
267
+ .pipe(
268
+ switchMap((s) =>
269
+ s.storeChange$.pipe(
270
+ startWith(''),
271
+ map(() => s._store),
272
+ tap((s) => {
273
+ untracked(() => {
274
+ this._queries$.next(Array.from(s.values()));
275
+ });
276
+ }),
277
+ takeUntil(this._destroy$),
278
+ ),
279
+ ),
280
+ )
281
+ .subscribe();
282
+
283
+ this.queryStore$
284
+ .pipe(
285
+ switchMap((s) =>
286
+ s.queryCreated$.pipe(
287
+ tap((query) => {
288
+ const currentHistory = this.queryHistory$.getValue();
289
+
290
+ if (!currentHistory) return;
291
+
292
+ const currentHistoryClone = [...currentHistory];
293
+
294
+ if (!currentHistory.some((q) => q._id === query._id)) {
295
+ currentHistoryClone.unshift(query);
296
+ }
297
+
298
+ currentHistoryClone.splice(50);
299
+
300
+ this.queryHistory$.next(currentHistoryClone);
301
+ }),
302
+ takeUntil(this._destroy$),
303
+ ),
304
+ ),
305
+ )
306
+ .subscribe();
307
+
308
+ this._queries$
309
+ .pipe(
310
+ tap((queries) => {
311
+ const currentHistory = this.queryHistory$.getValue();
312
+
313
+ if (!queries || !currentHistory) return;
314
+
315
+ const currentHistoryClone = [...currentHistory];
316
+
317
+ for (const query of queries) {
318
+ if (!currentHistoryClone.some((q) => q._id === query._id)) {
319
+ currentHistoryClone.unshift(query);
320
+ }
321
+ }
322
+
323
+ currentHistoryClone.splice(50);
324
+
325
+ this.queryHistory$.next(currentHistoryClone);
326
+ }),
327
+ takeUntil(this._destroy$),
328
+ )
329
+ .subscribe();
330
+
331
+ effect(
332
+ () => {
333
+ const devtoolConfig = {
334
+ isOpen: this.isOpen(),
335
+ isTranslucent: this.isTranslucent(),
336
+ snapLayout: this.snapLayout(),
337
+ showResponse: this.showResponse(),
338
+ showRawResponse: this.showRawResponse(),
339
+ showQueryConfig: this.showQueryConfig(),
340
+ showEntityStoreValue: this.showEntityStoreValue(),
341
+ showArguments: this.showArguments(),
342
+ selectedClientId: this.selectedClientId(),
343
+ selectedQueryPath: this.selectedQueryId(),
344
+ queryListMode: this.queryListMode(),
345
+ viewMode: this.viewMode(),
346
+ };
347
+
348
+ window.localStorage.setItem('ethlete:query:devtools', JSON.stringify(devtoolConfig));
349
+ },
350
+ { allowSignalWrites: true },
351
+ );
352
+
353
+ const initialConfig = window.localStorage.getItem('ethlete:query:devtools');
354
+
355
+ if (initialConfig) {
356
+ const parsed = JSON.parse(initialConfig);
357
+
358
+ this.isOpen.set(parsed.isOpen ?? false);
359
+ this.isTranslucent.set(parsed.isTranslucent ?? false);
360
+ this.snapLayout.set(parsed.snapLayout ?? 'full');
361
+ this.showResponse.set(parsed.showResponse ?? false);
362
+ this.showRawResponse.set(parsed.showRawResponse ?? false);
363
+ this.showQueryConfig.set(parsed.showQueryConfig ?? false);
364
+ this.showEntityStoreValue.set(parsed.showEntityStoreValue ?? false);
365
+ this.showArguments.set(parsed.showArguments ?? false);
366
+ this.selectedClientIdCtrl.setValue(parsed.selectedClientId ?? 0);
367
+ this.selectedQueryId.set(parsed.selectedQueryPath ?? null);
368
+ this.queryListMode.set(parsed.queryListMode ?? 'live');
369
+ this.viewMode.set(parsed.viewMode ?? 'query');
370
+ }
371
+ }
372
+
373
+ protected trackByClient: TrackByFunction<QueryClientDevtoolsOptions> = (_, { client }) => client.config.baseRoute;
374
+ protected trackByQuery: TrackByFunction<AnyQuery> = (_, { _id }) => _id;
375
+
376
+ protected toggleOpen() {
377
+ this.isOpen.set(!this.isOpen());
378
+ }
379
+
380
+ protected toggleTranslucent() {
381
+ this.isTranslucent.set(!this.isTranslucent());
382
+ }
383
+
384
+ protected toggleShowResponse() {
385
+ this.showResponse.set(!this.showResponse());
386
+
387
+ nextFrame(() => {
388
+ this._cdr.markForCheck();
389
+ });
390
+ }
391
+
392
+ protected toggleShowQueryConfig() {
393
+ this.showQueryConfig.set(!this.showQueryConfig());
394
+ }
395
+
396
+ protected toggleShowRawResponse() {
397
+ this.showRawResponse.set(!this.showRawResponse());
398
+ }
399
+
400
+ protected toggleShowEntityStoreValue() {
401
+ this.showEntityStoreValue.set(!this.showEntityStoreValue());
402
+ }
403
+
404
+ protected toggleShowArguments() {
405
+ this.showArguments.set(!this.showArguments());
406
+ }
407
+
408
+ protected selectSnapLayout(layout: QueryDevtoolsSnapLayout) {
409
+ this.snapLayout.set(layout);
410
+ }
411
+
412
+ protected selectQueryListMode(mode: QueryListMode) {
413
+ this.queryListMode.set(mode);
414
+ }
415
+
416
+ protected selectViewMode(mode: QueryViewMode) {
417
+ this.viewMode.set(mode);
418
+ }
419
+
420
+ protected selectQuery(query: AnyQuery) {
421
+ this.selectedQueryId.set(query._id);
422
+ }
423
+
424
+ protected clearQueryHistory() {
425
+ this.queryHistory$.next([]);
426
+ }
427
+ }
@@ -0,0 +1,24 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+ import { AnyQuery } from '../../query';
3
+
4
+ @Pipe({ name: 'queryShortName', standalone: true })
5
+ export class QueryShortNamePipe implements PipeTransform {
6
+ transform(value: AnyQuery) {
7
+ const route = value._routeWithParams;
8
+
9
+ if (typeof route === 'string') {
10
+ // given an url that looks like this: https://api.github.com/repos/EladBezalel/etimo-achievements or https://api.github.com/repos/EladBezalel/etimo-achievements?test=1&test2=2
11
+ // we want to extract the last 3 parts of the url, so we get: repos/EladBezalel/etimo-achievements
12
+
13
+ const parts = route.split('/');
14
+
15
+ if (parts.length > 3) {
16
+ return `.../${parts.slice(parts.length - 3).join('/')}`;
17
+ }
18
+
19
+ return route;
20
+ }
21
+
22
+ return route as string;
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ import { InjectionToken, isDevMode } from '@angular/core';
2
+ import { QueryClient } from '../../query-client';
3
+
4
+ export interface QueryClientDevtoolsOptions {
5
+ displayName?: string;
6
+ client: QueryClient;
7
+ }
8
+
9
+ export const QUERY_CLIENT_DEVTOOLS_TOKEN = new InjectionToken<QueryClientDevtoolsOptions[]>(
10
+ 'QUERY_CLIENT_DEVTOOLS_TOKEN',
11
+ );
12
+
13
+ export const provideQueryClientForDevtools = (queryClient: QueryClientDevtoolsOptions) => {
14
+ if (!isDevMode()) {
15
+ console.warn(
16
+ 'You are using the Query Devtools in production mode. This increases the size of your bundle and should only be used for development purposes.',
17
+ );
18
+ }
19
+
20
+ return {
21
+ provide: QUERY_CLIENT_DEVTOOLS_TOKEN,
22
+ useValue: queryClient,
23
+ multi: true,
24
+ };
25
+ };
@@ -0,0 +1,58 @@
1
+ import { Directive, ElementRef, inject, Input, OnDestroy, OnInit } from '@angular/core';
2
+ import { fromEvent, Subject, takeUntil } from 'rxjs';
3
+ import { INFINITY_QUERY_TOKEN } from './infinity-query.directive';
4
+
5
+ @Directive({
6
+ // eslint-disable-next-line @angular-eslint/directive-selector
7
+ selector: '[etInfinityQueryTrigger], et-infinity-query-trigger',
8
+ standalone: true,
9
+ })
10
+ export class InfinityQueryTriggerDirective implements OnInit, OnDestroy {
11
+ private _elementRef = inject(ElementRef<HTMLElement>);
12
+ private _infinityQuery = inject(INFINITY_QUERY_TOKEN);
13
+
14
+ private _destroy = new Subject<boolean>();
15
+ private _observer: IntersectionObserver | null = null;
16
+
17
+ click$ = fromEvent(this._elementRef.nativeElement, 'click');
18
+
19
+ @Input()
20
+ scrollContainerSelector: string | null = null;
21
+
22
+ ngOnInit(): void {
23
+ const isInteractive = this._elementRef.nativeElement.tagName === 'BUTTON';
24
+
25
+ if (isInteractive) {
26
+ this.click$.pipe(takeUntil(this._destroy)).subscribe(() => this._infinityQuery.loadNextPage());
27
+ } else {
28
+ this._setupIntersectionObserver();
29
+ }
30
+ }
31
+
32
+ ngOnDestroy(): void {
33
+ this._destroy.next(true);
34
+ this._destroy.unsubscribe();
35
+ this._observer?.disconnect();
36
+ }
37
+
38
+ private _setupIntersectionObserver(): void {
39
+ this._observer = new IntersectionObserver(
40
+ (entries) => {
41
+ const entry = entries[0];
42
+
43
+ if (!entry) return;
44
+
45
+ if (entry.isIntersecting && !this._infinityQuery.context.loading) {
46
+ this._infinityQuery.loadNextPage();
47
+ }
48
+ },
49
+ {
50
+ root: this.scrollContainerSelector ? document.querySelector(this.scrollContainerSelector) : null,
51
+ rootMargin: '0px',
52
+ threshold: [0.25, 0.5, 0.75, 1],
53
+ },
54
+ );
55
+
56
+ this._observer.observe(this._elementRef.nativeElement);
57
+ }
58
+ }