http-request-manager 18.12.4 → 18.12.5
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.
|
@@ -4,7 +4,7 @@ import { ComponentStore } from '@ngrx/component-store';
|
|
|
4
4
|
import { map, catchError, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, take, scan, distinctUntilChanged } from 'rxjs/operators';
|
|
5
5
|
import { HttpClient, HttpHeaders, HttpEventType, HttpHeaderResponse, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
6
6
|
import * as CryptoJS from 'crypto-js';
|
|
7
|
-
import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of,
|
|
7
|
+
import { from, BehaviorSubject, EMPTY, throwError, defer, interval, timer, Subject, of, merge, Subscription, take as take$1, catchError as catchError$1, map as map$1, tap as tap$1, switchMap as switchMap$1, startWith as startWith$1, distinctUntilChanged as distinctUntilChanged$1, combineLatest, filter as filter$1, takeUntil as takeUntil$1, ReplaySubject } from 'rxjs';
|
|
8
8
|
import { toObservable } from '@angular/core/rxjs-interop';
|
|
9
9
|
import { ToastMessageDisplayService, ToastDisplay, ToastColors, ToastMessageDisplayModule } from 'toast-message-display';
|
|
10
10
|
import Dexie from 'dexie';
|
|
@@ -910,18 +910,14 @@ class StreamingProcessor {
|
|
|
910
910
|
const parsedData = this.parseBuffer();
|
|
911
911
|
// Only return NEW items since last parse (progressive updates)
|
|
912
912
|
const newItems = parsedData.slice(this.lastParsedLength);
|
|
913
|
-
// received = cumulative total for display, newItems.length = new this chunk
|
|
914
|
-
const received = newItems.length; // Changed from lastParsedLength
|
|
915
913
|
this.lastParsedLength = parsedData.length;
|
|
916
|
-
|
|
917
|
-
const percent = total
|
|
918
|
-
? Math.min(100, Math.round((received / total) * 100))
|
|
919
|
-
: 0;
|
|
920
|
-
// console.log('[STREAM] progress:', percent, 'received:', received, 'total:', total, 'bufferLen:', this.buffer.length, 'parsedCount:', parsedData.length);
|
|
914
|
+
// Calculate progress
|
|
921
915
|
const progress = {
|
|
922
|
-
received,
|
|
923
|
-
total,
|
|
924
|
-
percent
|
|
916
|
+
received: this.lastParsedLength,
|
|
917
|
+
total: this.totalFromHeader,
|
|
918
|
+
percent: this.totalFromHeader
|
|
919
|
+
? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
|
|
920
|
+
: 0,
|
|
925
921
|
stage: 'streaming'
|
|
926
922
|
};
|
|
927
923
|
return {
|
|
@@ -934,22 +930,17 @@ class StreamingProcessor {
|
|
|
934
930
|
if (event.body) {
|
|
935
931
|
this.appendToBuffer(event.body);
|
|
936
932
|
const parsedData = this.parseBuffer();
|
|
937
|
-
//
|
|
938
|
-
const remaining = parsedData.slice(this.lastParsedLength);
|
|
939
|
-
this.lastParsedLength = parsedData.length;
|
|
940
|
-
const received = parsedData.length;
|
|
941
|
-
const total = this.totalFromHeader;
|
|
942
|
-
const percent = total
|
|
943
|
-
? Math.min(100, Math.round((received / total) * 100))
|
|
944
|
-
: 100;
|
|
933
|
+
// Calculate final progress
|
|
945
934
|
const progress = {
|
|
946
|
-
received,
|
|
947
|
-
total,
|
|
948
|
-
percent
|
|
935
|
+
received: parsedData.length,
|
|
936
|
+
total: this.totalFromHeader,
|
|
937
|
+
percent: this.totalFromHeader
|
|
938
|
+
? Math.round((parsedData.length / this.totalFromHeader) * 100)
|
|
939
|
+
: 100,
|
|
949
940
|
stage: 'complete'
|
|
950
941
|
};
|
|
951
942
|
return {
|
|
952
|
-
data:
|
|
943
|
+
data: parsedData.length > 0 ? parsedData : [],
|
|
953
944
|
progress
|
|
954
945
|
};
|
|
955
946
|
}
|
|
@@ -1080,8 +1071,6 @@ function extractJsonObjects(text) {
|
|
|
1080
1071
|
*/
|
|
1081
1072
|
function requestStreaming(config) {
|
|
1082
1073
|
const processor = new StreamingProcessor(config);
|
|
1083
|
-
// Reset processor state to prevent stale data from previous aborted streams
|
|
1084
|
-
processor.reset();
|
|
1085
1074
|
return input$ => input$.pipe(
|
|
1086
1075
|
// Filter only streaming-related events
|
|
1087
1076
|
filter((event) => event.type === HttpEventType.DownloadProgress ||
|
|
@@ -2852,9 +2841,6 @@ class RequestService extends WebsocketService {
|
|
|
2852
2841
|
this.headersService = inject(HeadersService);
|
|
2853
2842
|
this.isPending = new BehaviorSubject(false);
|
|
2854
2843
|
this.isPending$ = this.isPending.asObservable();
|
|
2855
|
-
// Separate pending state for streaming operations only
|
|
2856
|
-
this.isStreamingPending = new BehaviorSubject(false);
|
|
2857
|
-
this.isStreamingPending$ = this.isStreamingPending.asObservable();
|
|
2858
2844
|
this.progress = new BehaviorSubject(0);
|
|
2859
2845
|
this.progress$ = this.progress.asObservable();
|
|
2860
2846
|
this.streamProgress = new BehaviorSubject({
|
|
@@ -2864,80 +2850,32 @@ class RequestService extends WebsocketService {
|
|
|
2864
2850
|
stage: 'connecting'
|
|
2865
2851
|
});
|
|
2866
2852
|
this.streamProgress$ = this.streamProgress.asObservable();
|
|
2867
|
-
this.streamProgressReset = {
|
|
2868
|
-
received: 0,
|
|
2869
|
-
total: undefined,
|
|
2870
|
-
percent: 0,
|
|
2871
|
-
stage: 'connecting'
|
|
2872
|
-
};
|
|
2873
2853
|
}
|
|
2874
2854
|
// Implementation
|
|
2875
2855
|
getRecordRequest(options) {
|
|
2876
2856
|
const urlPath = this.buildUrlPath(options);
|
|
2877
2857
|
const headers = this.buildCombinedHeaders(options);
|
|
2878
2858
|
this.isPending.next(true);
|
|
2879
|
-
if (options.stream) {
|
|
2880
|
-
this.isStreamingPending.next(true);
|
|
2881
|
-
this.streamProgress.next({
|
|
2882
|
-
received: 0,
|
|
2883
|
-
total: undefined,
|
|
2884
|
-
percent: 0,
|
|
2885
|
-
stage: 'connecting'
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
2888
2859
|
return (options.stream)
|
|
2889
2860
|
? this.http.get(urlPath, {
|
|
2890
2861
|
...headers,
|
|
2891
2862
|
observe: 'events',
|
|
2892
2863
|
responseType: 'text',
|
|
2893
2864
|
reportProgress: true
|
|
2894
|
-
}).pipe(tap(data => console.log('STREAM DATA', data)), requestStreaming({
|
|
2895
|
-
streamType: options.streamType || StreamType.AI_STREAMING,
|
|
2896
|
-
totalHeader: options.totalHeader
|
|
2897
|
-
}), this.requestStreaming(options), finalize(() => {
|
|
2898
|
-
this.isPending.next(false);
|
|
2899
|
-
this.isStreamingPending.next(false);
|
|
2900
|
-
this.streamProgress.next({
|
|
2901
|
-
received: 0,
|
|
2902
|
-
total: undefined,
|
|
2903
|
-
percent: 0,
|
|
2904
|
-
stage: 'connecting'
|
|
2905
|
-
});
|
|
2906
|
-
}))
|
|
2865
|
+
}).pipe(tap(data => console.log('STREAM DATA', data)), requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreaming(options), this.handleFinalize())
|
|
2907
2866
|
: this.http.get(urlPath, headers).pipe(this.request(options));
|
|
2908
2867
|
}
|
|
2909
2868
|
createRecordRequest(options, data) {
|
|
2910
2869
|
const urlPath = this.buildUrlPath(options);
|
|
2911
2870
|
const headers = this.buildCombinedHeaders(options);
|
|
2912
2871
|
this.isPending.next(true);
|
|
2913
|
-
if (options.stream) {
|
|
2914
|
-
this.isStreamingPending.next(true);
|
|
2915
|
-
this.streamProgress.next({
|
|
2916
|
-
received: 0,
|
|
2917
|
-
total: undefined,
|
|
2918
|
-
percent: 0,
|
|
2919
|
-
stage: 'connecting'
|
|
2920
|
-
});
|
|
2921
|
-
}
|
|
2922
2872
|
return (options.stream)
|
|
2923
2873
|
? this.http.post(urlPath, data, {
|
|
2924
2874
|
...headers,
|
|
2925
2875
|
observe: 'events',
|
|
2926
2876
|
responseType: 'text',
|
|
2927
2877
|
reportProgress: true
|
|
2928
|
-
}).pipe(requestStreaming({
|
|
2929
|
-
streamType: options.streamType || StreamType.AI_STREAMING,
|
|
2930
|
-
totalHeader: options.totalHeader
|
|
2931
|
-
}), this.requestStreaming(options), finalize(() => {
|
|
2932
|
-
this.isPending.next(false);
|
|
2933
|
-
this.isStreamingPending.next(false);
|
|
2934
|
-
this.streamProgress.next({
|
|
2935
|
-
received: 0,
|
|
2936
|
-
total: undefined,
|
|
2937
|
-
percent: 0,
|
|
2938
|
-
stage: 'connecting'
|
|
2939
|
-
});
|
|
2940
|
-
}))
|
|
2878
|
+
}).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreaming(options), this.handleFinalize())
|
|
2941
2879
|
: this.http.post(urlPath, data, headers).pipe(this.request(options));
|
|
2942
2880
|
}
|
|
2943
2881
|
updateRecordRequest(options, data) {
|
|
@@ -2982,7 +2920,7 @@ class RequestService extends WebsocketService {
|
|
|
2982
2920
|
return (source$) => {
|
|
2983
2921
|
return source$.pipe(tap(output => {
|
|
2984
2922
|
// Update progress from stream output
|
|
2985
|
-
this.progress.next(output.progress.
|
|
2923
|
+
this.progress.next(output.progress.received);
|
|
2986
2924
|
this.streamProgress.next(output.progress);
|
|
2987
2925
|
}), map(output => {
|
|
2988
2926
|
const data = output.data;
|
|
@@ -3131,12 +3069,7 @@ class RequestService extends WebsocketService {
|
|
|
3131
3069
|
return formData;
|
|
3132
3070
|
}
|
|
3133
3071
|
handleFinalize() {
|
|
3134
|
-
return finalize(() =>
|
|
3135
|
-
this.isPending.next(false);
|
|
3136
|
-
// Note: do NOT reset streamProgress here — it fires on cancel/cancel too,
|
|
3137
|
-
// not just natural completion. Reset only happens in requestStreaming
|
|
3138
|
-
// operator on Response complete event, or when a new stream starts.
|
|
3139
|
-
});
|
|
3072
|
+
return finalize(() => this.isPending.next(false));
|
|
3140
3073
|
}
|
|
3141
3074
|
downloadFile(file, fileData) {
|
|
3142
3075
|
const navigatorAny = window.navigator;
|
|
@@ -3829,16 +3762,12 @@ class HTTPManagerService extends RequestService {
|
|
|
3829
3762
|
getRequest(options, params) {
|
|
3830
3763
|
this.isPending.next(true);
|
|
3831
3764
|
this.data.next(null);
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
percent: 0,
|
|
3839
|
-
stage: 'connecting'
|
|
3840
|
-
});
|
|
3841
|
-
}
|
|
3765
|
+
this.streamProgress.next({
|
|
3766
|
+
received: 0,
|
|
3767
|
+
total: undefined,
|
|
3768
|
+
percent: 0,
|
|
3769
|
+
stage: 'connecting'
|
|
3770
|
+
});
|
|
3842
3771
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
3843
3772
|
const func = this.getRecordRequest;
|
|
3844
3773
|
const requests = this.createRequest(func, updatedOptions);
|
|
@@ -4210,192 +4139,49 @@ class RequestSignalsService extends WebsocketService {
|
|
|
4210
4139
|
this.http = inject(HttpClient);
|
|
4211
4140
|
this.pathQueryService = inject(PathQueryService);
|
|
4212
4141
|
this.headersService = inject(HeadersService);
|
|
4213
|
-
|
|
4214
|
-
this._activeRequestCount = signal(0);
|
|
4215
|
-
this.isPending = computed(() => this._activeRequestCount() > 0);
|
|
4142
|
+
this.isPending = signal(false);
|
|
4216
4143
|
this.progress = signal(0);
|
|
4217
|
-
|
|
4218
|
-
this.activeRequests = new Map();
|
|
4219
|
-
this.activeMethod = null;
|
|
4220
|
-
this.activeStreamMethod = null;
|
|
4221
|
-
// Queue for requests that need to wait
|
|
4222
|
-
this.requestQueue = [];
|
|
4223
|
-
// Cleanup subject for cancelling queued requests on destroy
|
|
4224
|
-
this.destroy$ = new Subject();
|
|
4144
|
+
this.isIdle = computed(() => !this.isPending());
|
|
4225
4145
|
}
|
|
4226
4146
|
// Implementation
|
|
4227
4147
|
getRecordRequest(options) {
|
|
4228
4148
|
const urlPath = this.buildUrlPath(options);
|
|
4229
4149
|
const headers = this.buildCombinedHeaders(options);
|
|
4230
|
-
|
|
4231
|
-
|
|
4150
|
+
this.isPending.set(true);
|
|
4151
|
+
return (options.stream)
|
|
4232
4152
|
? this.http.get(urlPath, {
|
|
4233
4153
|
...headers,
|
|
4234
4154
|
observe: 'events',
|
|
4235
4155
|
responseType: 'text',
|
|
4236
4156
|
reportProgress: true
|
|
4237
|
-
}).pipe(requestStreaming({
|
|
4238
|
-
|
|
4239
|
-
totalHeader: options.totalHeader
|
|
4240
|
-
}), this.requestStreamingOperator(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))))
|
|
4241
|
-
: this.http.get(urlPath, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
|
|
4242
|
-
return this.queueRequest(request$, 'GET', isStream);
|
|
4157
|
+
}).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreamingOperator(options), this.handleFinalize())
|
|
4158
|
+
: this.http.get(urlPath, headers).pipe(this.request(options), this.handleFinalize());
|
|
4243
4159
|
}
|
|
4244
4160
|
// Implementation
|
|
4245
4161
|
createRecordRequest(options, data) {
|
|
4246
4162
|
const urlPath = this.buildUrlPath(options);
|
|
4247
4163
|
const headers = this.buildCombinedHeaders(options);
|
|
4248
|
-
|
|
4249
|
-
|
|
4164
|
+
this.isPending.set(true);
|
|
4165
|
+
return (options.stream)
|
|
4250
4166
|
? this.http.post(urlPath, data, {
|
|
4251
4167
|
...headers,
|
|
4252
4168
|
observe: 'events',
|
|
4253
4169
|
responseType: 'text',
|
|
4254
4170
|
reportProgress: true
|
|
4255
|
-
}).pipe(requestStreaming({
|
|
4256
|
-
|
|
4257
|
-
totalHeader: options.totalHeader
|
|
4258
|
-
}), this.requestStreamingOperator(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))))
|
|
4259
|
-
: this.http.post(urlPath, data, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
|
|
4260
|
-
return this.queueRequest(request$, 'POST', isStream);
|
|
4171
|
+
}).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreamingOperator(options), this.handleFinalize())
|
|
4172
|
+
: this.http.post(urlPath, data, headers).pipe(this.request(options), this.handleFinalize());
|
|
4261
4173
|
}
|
|
4262
4174
|
updateRecordRequest(options, data) {
|
|
4263
4175
|
const urlPath = this.buildUrlPath(options);
|
|
4264
4176
|
const headers = this.buildHeaders(options);
|
|
4265
|
-
|
|
4266
|
-
return this.
|
|
4177
|
+
this.isPending.set(true);
|
|
4178
|
+
return this.http.put(urlPath, data, headers).pipe(this.request(options), this.handleFinalize());
|
|
4267
4179
|
}
|
|
4268
4180
|
deleteRecordRequest(options) {
|
|
4269
4181
|
const urlPath = this.buildUrlPath(options);
|
|
4270
4182
|
const headers = this.buildHeaders(options);
|
|
4271
|
-
|
|
4272
|
-
return this.
|
|
4273
|
-
}
|
|
4274
|
-
/**
|
|
4275
|
-
* Queue a request to ensure FIFO processing:
|
|
4276
|
-
* - All requests are queued and processed sequentially
|
|
4277
|
-
* - Streams are protected - non-stream requests wait for active streams
|
|
4278
|
-
*/
|
|
4279
|
-
queueRequest(request$, method, isStream = false) {
|
|
4280
|
-
console.log(`[Queue] ${method} queued (isStream=${isStream})`, {
|
|
4281
|
-
activeMethod: this.activeMethod,
|
|
4282
|
-
activeStreamMethod: this.activeStreamMethod,
|
|
4283
|
-
queueLength: this.requestQueue.length
|
|
4284
|
-
});
|
|
4285
|
-
return new Observable((observer) => {
|
|
4286
|
-
console.log(`[Queue] ${method} observer created, checking conditions...`, {
|
|
4287
|
-
activeMethod: this.activeMethod,
|
|
4288
|
-
activeStreamMethod: this.activeStreamMethod,
|
|
4289
|
-
isStream
|
|
4290
|
-
});
|
|
4291
|
-
const queuedRequest = {
|
|
4292
|
-
observable: request$,
|
|
4293
|
-
resolve: (value) => observer.next(value),
|
|
4294
|
-
reject: (error) => observer.error(error),
|
|
4295
|
-
method,
|
|
4296
|
-
isStream
|
|
4297
|
-
};
|
|
4298
|
-
// If a stream is currently active, non-stream requests must wait
|
|
4299
|
-
if (this.activeStreamMethod && !isStream) {
|
|
4300
|
-
console.log(`[Queue] ${method} queued (1), waiting for stream ${this.activeStreamMethod}`);
|
|
4301
|
-
this.requestQueue.push(queuedRequest);
|
|
4302
|
-
return () => {
|
|
4303
|
-
const idx = this.requestQueue.findIndex(q => q === queuedRequest);
|
|
4304
|
-
if (idx !== -1)
|
|
4305
|
-
this.requestQueue.splice(idx, 1);
|
|
4306
|
-
};
|
|
4307
|
-
}
|
|
4308
|
-
// If a request is already running, queue this one (FIFO)
|
|
4309
|
-
if (this.activeMethod) {
|
|
4310
|
-
console.log(`[Queue] ${method} queued (2), waiting for ${this.activeMethod}`);
|
|
4311
|
-
this.requestQueue.push(queuedRequest);
|
|
4312
|
-
return () => {
|
|
4313
|
-
const idx = this.requestQueue.findIndex(q => q === queuedRequest);
|
|
4314
|
-
if (idx !== -1)
|
|
4315
|
-
this.requestQueue.splice(idx, 1);
|
|
4316
|
-
};
|
|
4317
|
-
}
|
|
4318
|
-
// No active request - start immediately
|
|
4319
|
-
console.log(`[Queue] ${method} starting immediately`);
|
|
4320
|
-
this.startRequest(queuedRequest, observer);
|
|
4321
|
-
return () => {
|
|
4322
|
-
const idx = this.requestQueue.findIndex(q => q === queuedRequest);
|
|
4323
|
-
if (idx !== -1)
|
|
4324
|
-
this.requestQueue.splice(idx, 1);
|
|
4325
|
-
};
|
|
4326
|
-
});
|
|
4327
|
-
}
|
|
4328
|
-
/**
|
|
4329
|
-
* Start processing a request
|
|
4330
|
-
*/
|
|
4331
|
-
startRequest(queuedRequest, observer) {
|
|
4332
|
-
this.activeMethod = queuedRequest.method;
|
|
4333
|
-
if (queuedRequest.isStream) {
|
|
4334
|
-
this.activeStreamMethod = queuedRequest.method;
|
|
4335
|
-
}
|
|
4336
|
-
this._activeRequestCount.update(c => c + 1);
|
|
4337
|
-
console.log(`[Queue] Starting ${queuedRequest.method}${queuedRequest.isStream ? ' (STREAM)' : ''}`, {
|
|
4338
|
-
activeMethod: this.activeMethod,
|
|
4339
|
-
activeStreamMethod: this.activeStreamMethod,
|
|
4340
|
-
queueLength: this.requestQueue.length
|
|
4341
|
-
});
|
|
4342
|
-
const sub = queuedRequest.observable.subscribe({
|
|
4343
|
-
next: (value) => {
|
|
4344
|
-
console.log(`[Queue] ${queuedRequest.method} received value, isStream=${queuedRequest.isStream}`);
|
|
4345
|
-
observer.next(value);
|
|
4346
|
-
},
|
|
4347
|
-
error: (error) => {
|
|
4348
|
-
console.log(`[Queue] ${queuedRequest.method} error:`, error);
|
|
4349
|
-
observer.error(error);
|
|
4350
|
-
this.cleanupAndProcessNext(queuedRequest.method, !!queuedRequest.isStream);
|
|
4351
|
-
},
|
|
4352
|
-
complete: () => {
|
|
4353
|
-
console.log(`[Queue] ${queuedRequest.method} complete, isStream=${queuedRequest.isStream}`);
|
|
4354
|
-
observer.complete();
|
|
4355
|
-
this.cleanupAndProcessNext(queuedRequest.method, !!queuedRequest.isStream);
|
|
4356
|
-
}
|
|
4357
|
-
});
|
|
4358
|
-
this.activeRequests.set(queuedRequest.method, sub);
|
|
4359
|
-
}
|
|
4360
|
-
/**
|
|
4361
|
-
* Clean up completed request and process next in queue
|
|
4362
|
-
*/
|
|
4363
|
-
cleanupAndProcessNext(method, wasStream) {
|
|
4364
|
-
console.log(`[Queue] cleanupAndProcessNext called: method=${method}, wasStream=${wasStream}`, {
|
|
4365
|
-
activeMethod: this.activeMethod,
|
|
4366
|
-
activeStreamMethod: this.activeStreamMethod,
|
|
4367
|
-
queueLength: this.requestQueue.length
|
|
4368
|
-
});
|
|
4369
|
-
this.activeRequests.delete(method);
|
|
4370
|
-
this.activeMethod = null;
|
|
4371
|
-
if (wasStream) {
|
|
4372
|
-
this.activeStreamMethod = null;
|
|
4373
|
-
}
|
|
4374
|
-
this._activeRequestCount.update(c => {
|
|
4375
|
-
const newCount = Math.max(0, c - 1);
|
|
4376
|
-
if (newCount === 0) {
|
|
4377
|
-
this.progress.set(0);
|
|
4378
|
-
}
|
|
4379
|
-
return newCount;
|
|
4380
|
-
});
|
|
4381
|
-
console.log(`[Queue] After cleanup:`, {
|
|
4382
|
-
activeMethod: this.activeMethod,
|
|
4383
|
-
activeStreamMethod: this.activeStreamMethod,
|
|
4384
|
-
queueLength: this.requestQueue.length
|
|
4385
|
-
});
|
|
4386
|
-
// Process next request in queue
|
|
4387
|
-
if (this.requestQueue.length > 0) {
|
|
4388
|
-
const next = this.requestQueue.shift();
|
|
4389
|
-
console.log(`[Queue] Processing next from queue: ${next.method}`, {
|
|
4390
|
-
isStream: next.isStream,
|
|
4391
|
-
queueLength: this.requestQueue.length
|
|
4392
|
-
});
|
|
4393
|
-
this.startRequest(next, {
|
|
4394
|
-
next: (value) => next.resolve(value),
|
|
4395
|
-
error: (error) => next.reject(error),
|
|
4396
|
-
complete: () => { }
|
|
4397
|
-
});
|
|
4398
|
-
}
|
|
4183
|
+
this.isPending.set(true);
|
|
4184
|
+
return this.http.delete(urlPath, headers).pipe(this.request(options), this.handleFinalize());
|
|
4399
4185
|
}
|
|
4400
4186
|
buildUrlPath(options) {
|
|
4401
4187
|
return this.pathQueryService.buildAPIPath(options.server, options.path);
|
|
@@ -4424,9 +4210,7 @@ class RequestSignalsService extends WebsocketService {
|
|
|
4424
4210
|
}
|
|
4425
4211
|
requestStreamingOperator(options) {
|
|
4426
4212
|
return (source$) => {
|
|
4427
|
-
return source$.pipe(
|
|
4428
|
-
this.progress.set(output.progress.percent);
|
|
4429
|
-
}), map(output => {
|
|
4213
|
+
return source$.pipe(map(output => {
|
|
4430
4214
|
const data = output.data;
|
|
4431
4215
|
if (!data || (Array.isArray(data) && data.length === 0)) {
|
|
4432
4216
|
return data;
|
|
@@ -4439,12 +4223,14 @@ class RequestSignalsService extends WebsocketService {
|
|
|
4439
4223
|
};
|
|
4440
4224
|
}
|
|
4441
4225
|
downloadFileRequest(options) {
|
|
4442
|
-
this.
|
|
4226
|
+
this.isPending.set(true);
|
|
4443
4227
|
const urlPath = this.buildUrlPath(options);
|
|
4444
|
-
|
|
4228
|
+
return this.http.get(urlPath, { responseType: 'blob', observe: 'events', reportProgress: true })
|
|
4445
4229
|
.pipe(map((event) => {
|
|
4230
|
+
this.isPending.set(true);
|
|
4446
4231
|
if (event instanceof HttpHeaderResponse) {
|
|
4447
4232
|
if (event.status !== 200) {
|
|
4233
|
+
this.isPending.set(false);
|
|
4448
4234
|
throw new Error('Download failed');
|
|
4449
4235
|
}
|
|
4450
4236
|
}
|
|
@@ -4460,54 +4246,59 @@ class RequestSignalsService extends WebsocketService {
|
|
|
4460
4246
|
const file = (header_content) ? header_content.split('=')[1].substring(0, header_content.split('=')[1].length) : '';
|
|
4461
4247
|
const fileName = (fileNamePath !== '') ? fileNamePath : file;
|
|
4462
4248
|
if (fileName === '') {
|
|
4249
|
+
this.isPending.set(false);
|
|
4463
4250
|
throw new Error('Save File: (file name and extension) not found in Headers or Path');
|
|
4464
4251
|
}
|
|
4465
4252
|
this.downloadFile(fileName, event.body);
|
|
4253
|
+
this.isPending.set(false);
|
|
4466
4254
|
return 100;
|
|
4467
4255
|
}
|
|
4468
4256
|
catch (error) {
|
|
4469
4257
|
throw new Error('Download failed');
|
|
4470
4258
|
}
|
|
4471
4259
|
default:
|
|
4260
|
+
this.isPending.set(false);
|
|
4472
4261
|
return 0;
|
|
4473
4262
|
}
|
|
4474
4263
|
}), catchError(err => {
|
|
4475
4264
|
return throwError(() => err);
|
|
4476
|
-
})
|
|
4477
|
-
return this.queueRequest(request$, 'DOWNLOAD');
|
|
4265
|
+
}));
|
|
4478
4266
|
}
|
|
4479
4267
|
uploadFileRequest(options) {
|
|
4268
|
+
this.isPending.set(true);
|
|
4480
4269
|
const files = options.uploadFiles ? (Array.isArray(options.uploadFiles) ? options.uploadFiles : [options.uploadFiles]) : [];
|
|
4481
4270
|
const validation = this.validateUploadFiles(files, options);
|
|
4482
4271
|
if (validation.validFiles.length === 0) {
|
|
4272
|
+
this.isPending.set(false);
|
|
4483
4273
|
return throwError(() => new UploadValidationErrorModel(validation.invalidFiles, 0, files.length));
|
|
4484
4274
|
}
|
|
4485
|
-
this._activeRequestCount.update(c => c + 1);
|
|
4486
4275
|
const fieldName = options.uploadFieldName || (files.length === 1 ? 'file' : 'files');
|
|
4487
4276
|
const formData = this.buildFormData(validation.validFiles, fieldName);
|
|
4488
4277
|
const urlPath = this.buildUrlPath(options);
|
|
4489
4278
|
const method = options.uploadHttpMethod || 'POST';
|
|
4490
|
-
|
|
4279
|
+
return this.http.request(method, urlPath, {
|
|
4491
4280
|
body: formData,
|
|
4492
4281
|
observe: 'events',
|
|
4493
4282
|
reportProgress: true
|
|
4494
4283
|
}).pipe(map((event) => {
|
|
4284
|
+
this.isPending.set(true);
|
|
4495
4285
|
switch (event.type) {
|
|
4496
4286
|
case HttpEventType.UploadProgress:
|
|
4497
4287
|
const status = event.total ? Math.round(event.loaded / (event.total || 1) * 100) : 0;
|
|
4498
4288
|
this.progress.set(status);
|
|
4499
4289
|
return null;
|
|
4500
4290
|
case HttpEventType.Response:
|
|
4291
|
+
this.isPending.set(false);
|
|
4501
4292
|
this.progress.set(0);
|
|
4502
4293
|
return event.body;
|
|
4503
4294
|
default:
|
|
4504
4295
|
return null;
|
|
4505
4296
|
}
|
|
4506
4297
|
}), filter((body) => body !== null), catchError(err => {
|
|
4298
|
+
this.isPending.set(false);
|
|
4507
4299
|
this.progress.set(0);
|
|
4508
4300
|
return throwError(() => err);
|
|
4509
|
-
})
|
|
4510
|
-
return this.queueRequest(request$, 'UPLOAD');
|
|
4301
|
+
}));
|
|
4511
4302
|
}
|
|
4512
4303
|
validateUploadFiles(files, options) {
|
|
4513
4304
|
const validFiles = [];
|
|
@@ -4556,6 +4347,9 @@ class RequestSignalsService extends WebsocketService {
|
|
|
4556
4347
|
});
|
|
4557
4348
|
return formData;
|
|
4558
4349
|
}
|
|
4350
|
+
handleFinalize() {
|
|
4351
|
+
return finalize(() => this.isPending.set(false));
|
|
4352
|
+
}
|
|
4559
4353
|
downloadFile(file, fileData) {
|
|
4560
4354
|
const navigatorAny = window.navigator;
|
|
4561
4355
|
const extension = file.split('.')[1]?.toLowerCase();
|
|
@@ -4769,6 +4563,7 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4769
4563
|
}
|
|
4770
4564
|
// REQUESTS
|
|
4771
4565
|
getRequest(options, params) {
|
|
4566
|
+
this.isPending.set(true);
|
|
4772
4567
|
this.data.set(null);
|
|
4773
4568
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4774
4569
|
const func = this.getRecordRequest;
|
|
@@ -4777,13 +4572,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4777
4572
|
this.data.set(data);
|
|
4778
4573
|
if (updatedOptions.displaySuccess)
|
|
4779
4574
|
this.handleSuccessWithSnackBar(updatedOptions.successMessage);
|
|
4780
|
-
}), catchError((err) => {
|
|
4575
|
+
}), finalize(() => this.isPending.set(false)), catchError((err) => {
|
|
4781
4576
|
if (updatedOptions.displayError)
|
|
4782
4577
|
this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
|
|
4578
|
+
this.isPending.set(false);
|
|
4783
4579
|
return this.handleError(err);
|
|
4784
4580
|
}));
|
|
4785
4581
|
}
|
|
4786
4582
|
postRequest(data, options, params) {
|
|
4583
|
+
this.isPending.set(true);
|
|
4787
4584
|
this.data.set(null);
|
|
4788
4585
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4789
4586
|
const func = this.createRecordRequest;
|
|
@@ -4792,13 +4589,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4792
4589
|
this.data.set(data);
|
|
4793
4590
|
if (updatedOptions.displaySuccess)
|
|
4794
4591
|
this.handleSuccessWithSnackBar(updatedOptions.successMessage);
|
|
4795
|
-
}), catchError((err) => {
|
|
4592
|
+
}), finalize(() => this.isPending.set(false)), catchError((err) => {
|
|
4796
4593
|
if (updatedOptions.displayError)
|
|
4797
4594
|
this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
|
|
4595
|
+
this.isPending.set(false);
|
|
4798
4596
|
return this.handleError(err);
|
|
4799
4597
|
}));
|
|
4800
4598
|
}
|
|
4801
4599
|
putRequest(data, options, params) {
|
|
4600
|
+
this.isPending.set(true);
|
|
4802
4601
|
this.data.set(null);
|
|
4803
4602
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4804
4603
|
const func = this.updateRecordRequest;
|
|
@@ -4807,13 +4606,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4807
4606
|
this.data.set(data);
|
|
4808
4607
|
if (updatedOptions.displaySuccess)
|
|
4809
4608
|
this.handleSuccessWithSnackBar(updatedOptions.successMessage);
|
|
4810
|
-
}), catchError((err) => {
|
|
4609
|
+
}), finalize(() => this.isPending.set(false)), catchError((err) => {
|
|
4811
4610
|
if (updatedOptions.displayError)
|
|
4812
4611
|
this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
|
|
4612
|
+
this.isPending.set(false);
|
|
4813
4613
|
return this.handleError(err);
|
|
4814
4614
|
}));
|
|
4815
4615
|
}
|
|
4816
4616
|
deleteRequest(options, params) {
|
|
4617
|
+
this.isPending.set(true);
|
|
4817
4618
|
this.data.set(null);
|
|
4818
4619
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4819
4620
|
const func = this.deleteRecordRequest;
|
|
@@ -4822,22 +4623,26 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4822
4623
|
this.data.set(data);
|
|
4823
4624
|
if (updatedOptions.displaySuccess)
|
|
4824
4625
|
this.handleSuccessWithSnackBar(updatedOptions.successMessage);
|
|
4825
|
-
}), catchError((err) => {
|
|
4626
|
+
}), finalize(() => this.isPending.set(false)), catchError((err) => {
|
|
4826
4627
|
if (updatedOptions.displayError)
|
|
4827
4628
|
this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
|
|
4629
|
+
this.isPending.set(false);
|
|
4828
4630
|
return this.handleError(err);
|
|
4829
4631
|
}));
|
|
4830
4632
|
}
|
|
4831
4633
|
downloadRequest(options, params, saveAs) {
|
|
4634
|
+
this.isPending.set(true);
|
|
4832
4635
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4833
4636
|
const func = this.downloadFileRequest;
|
|
4834
4637
|
const requests = this.createRequest(func, updatedOptions);
|
|
4835
4638
|
return this.createObservable(updatedOptions, requests, func.name).pipe(catchError((err) => {
|
|
4836
4639
|
this.error.set(true);
|
|
4640
|
+
this.isPending.set(false);
|
|
4837
4641
|
return this.handleError(err);
|
|
4838
4642
|
}));
|
|
4839
4643
|
}
|
|
4840
4644
|
uploadRequest(files, options, params) {
|
|
4645
|
+
this.isPending.set(true);
|
|
4841
4646
|
this.data.set(null);
|
|
4842
4647
|
const updatedOptions = this.defineReqOptions(options, params);
|
|
4843
4648
|
updatedOptions.uploadFiles = files;
|
|
@@ -4847,9 +4652,10 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4847
4652
|
this.data.set(data);
|
|
4848
4653
|
if (updatedOptions.displaySuccess)
|
|
4849
4654
|
this.handleSuccessWithSnackBar(updatedOptions.successMessage);
|
|
4850
|
-
}), catchError((err) => {
|
|
4655
|
+
}), finalize(() => this.isPending.set(false)), catchError((err) => {
|
|
4851
4656
|
if (updatedOptions.displayError)
|
|
4852
4657
|
this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
|
|
4658
|
+
this.isPending.set(false);
|
|
4853
4659
|
return this.handleError(err);
|
|
4854
4660
|
}));
|
|
4855
4661
|
}
|
|
@@ -4943,6 +4749,7 @@ class HTTPManagerSignalsService extends RequestSignalsService {
|
|
|
4943
4749
|
this.toastMessage.toastMessage(displaySuccess);
|
|
4944
4750
|
}
|
|
4945
4751
|
stopPolling() {
|
|
4752
|
+
this.isPending.set(false);
|
|
4946
4753
|
this.polling$.next();
|
|
4947
4754
|
}
|
|
4948
4755
|
defineReqOptions(options, params) {
|
|
@@ -5109,7 +4916,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
5109
4916
|
}] }] });
|
|
5110
4917
|
|
|
5111
4918
|
class ApiRequest {
|
|
5112
|
-
constructor(server = '', path, headers, adapter, mapper, polling, retry, stream, streamType,
|
|
4919
|
+
constructor(server = '', path, headers, adapter, mapper, polling, retry, stream, streamType, displayError, displaySuccess, successMessage, errorMessage, saveAs, fileContentHeader, ws, env, uploadFiles, uploadFieldName, uploadHttpMethod, allowedTypes, maxFileSize, maxTotalSize) {
|
|
5113
4920
|
this.server = server;
|
|
5114
4921
|
this.path = path;
|
|
5115
4922
|
this.headers = headers;
|
|
@@ -5119,7 +4926,6 @@ class ApiRequest {
|
|
|
5119
4926
|
this.retry = retry;
|
|
5120
4927
|
this.stream = stream;
|
|
5121
4928
|
this.streamType = streamType;
|
|
5122
|
-
this.totalHeader = totalHeader;
|
|
5123
4929
|
this.displayError = displayError;
|
|
5124
4930
|
this.displaySuccess = displaySuccess;
|
|
5125
4931
|
this.successMessage = successMessage;
|
|
@@ -5137,17 +4943,18 @@ class ApiRequest {
|
|
|
5137
4943
|
}
|
|
5138
4944
|
static adapt(item) {
|
|
5139
4945
|
const server = Array.isArray(item?.server) ? item.server.join('/') : item?.server || '';
|
|
5140
|
-
return new ApiRequest(server, item?.path ? item.path : [], item?.headers ? item.headers : {}, item?.adapter, item?.mapper, item?.polling ? Math.floor(item.polling) : 0, item?.retry ? RetryOptions.adapt(item.retry) : RetryOptions.adapt(), item?.stream ? item.stream : false, item?.streamType
|
|
4946
|
+
return new ApiRequest(server, item?.path ? item.path : [], item?.headers ? item.headers : {}, item?.adapter, item?.mapper, item?.polling ? Math.floor(item.polling) : 0, item?.retry ? RetryOptions.adapt(item.retry) : RetryOptions.adapt(), item?.stream ? item.stream : false, item?.streamType || StreamType.AI_STREAMING, item?.displayError ? item.displayError : false, item?.displaySuccess ? item.displaySuccess : false, item?.successMessage, item?.errorMessage, item?.saveAs, item?.fileContentHeader, item?.ws, item?.env || 'dev', item?.uploadFiles, item?.uploadFieldName, item?.uploadHttpMethod, Array.isArray(item?.allowedTypes) ? item.allowedTypes : [], item?.maxFileSize, item?.maxTotalSize);
|
|
5141
4947
|
}
|
|
5142
4948
|
}
|
|
5143
4949
|
|
|
5144
4950
|
class RequestOptions {
|
|
5145
|
-
constructor(path = [], headers = {}) {
|
|
4951
|
+
constructor(path = [], headers = {}, forceRefresh) {
|
|
5146
4952
|
this.path = path;
|
|
5147
4953
|
this.headers = headers;
|
|
4954
|
+
this.forceRefresh = forceRefresh;
|
|
5148
4955
|
}
|
|
5149
4956
|
static adapt(item) {
|
|
5150
|
-
return new RequestOptions(item?.path, item?.headers);
|
|
4957
|
+
return new RequestOptions(item?.path, item?.headers, item?.forceRefresh);
|
|
5151
4958
|
}
|
|
5152
4959
|
}
|
|
5153
4960
|
|
|
@@ -6023,9 +5830,12 @@ class DbService extends Dexie {
|
|
|
6023
5830
|
await this.dbReady;
|
|
6024
5831
|
const safeTableName = this.cleanTableName(tableName);
|
|
6025
5832
|
const safeSchema = schema.trim();
|
|
6026
|
-
if (this.tableExists(safeTableName))
|
|
6027
|
-
return;
|
|
6028
5833
|
const currentSchema = this.getCurrentSchema();
|
|
5834
|
+
const existingSchema = currentSchema[safeTableName]?.trim();
|
|
5835
|
+
// No-op only when table already exists and schema is identical.
|
|
5836
|
+
if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
|
|
5837
|
+
return;
|
|
5838
|
+
}
|
|
6029
5839
|
console.log('Current Schema before update:', currentSchema);
|
|
6030
5840
|
currentSchema[safeTableName] = safeSchema;
|
|
6031
5841
|
const nextVersion = this.verno + 1;
|
|
@@ -6039,6 +5849,7 @@ class DbService extends Dexie {
|
|
|
6039
5849
|
const created = this.tables.some(t => t.name === safeTableName);
|
|
6040
5850
|
if (!created) {
|
|
6041
5851
|
console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
|
|
5852
|
+
throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
|
|
6042
5853
|
}
|
|
6043
5854
|
else {
|
|
6044
5855
|
console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
|
|
@@ -6046,15 +5857,24 @@ class DbService extends Dexie {
|
|
|
6046
5857
|
}
|
|
6047
5858
|
catch (err) {
|
|
6048
5859
|
console.error('Error opening database after schema update:', err);
|
|
5860
|
+
throw err;
|
|
6049
5861
|
}
|
|
6050
5862
|
}
|
|
6051
5863
|
async DBOpened() {
|
|
5864
|
+
try {
|
|
5865
|
+
await this.dbReady;
|
|
5866
|
+
}
|
|
5867
|
+
catch (err) {
|
|
5868
|
+
console.error('DBOpened: init failed', err);
|
|
5869
|
+
return false;
|
|
5870
|
+
}
|
|
6052
5871
|
if (!this.isOpen()) {
|
|
6053
5872
|
try {
|
|
6054
5873
|
await this.open();
|
|
6055
5874
|
return true;
|
|
6056
5875
|
}
|
|
6057
5876
|
catch (err) {
|
|
5877
|
+
console.error('DBOpened: open failed', err);
|
|
6058
5878
|
return false;
|
|
6059
5879
|
}
|
|
6060
5880
|
}
|
|
@@ -6149,7 +5969,11 @@ class DatabaseManagerService extends DbService {
|
|
|
6149
5969
|
}));
|
|
6150
5970
|
}
|
|
6151
5971
|
createDatabaseTable(tableDef) {
|
|
6152
|
-
|
|
5972
|
+
const tableName = this.cleanTableName(tableDef.table);
|
|
5973
|
+
return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
|
|
5974
|
+
console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
|
|
5975
|
+
return of(false);
|
|
5976
|
+
}));
|
|
6153
5977
|
}
|
|
6154
5978
|
updateDatabaseTableSchema(tableDef) {
|
|
6155
5979
|
return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
|
|
@@ -6196,26 +6020,23 @@ class DatabaseManagerService extends DbService {
|
|
|
6196
6020
|
}
|
|
6197
6021
|
createTableRecords(table, records) {
|
|
6198
6022
|
const tableName = this.cleanTableName(table);
|
|
6199
|
-
return from(this.DBOpened()).pipe(switchMap(() => {
|
|
6023
|
+
return from(this.DBOpened()).pipe(switchMap((opened) => {
|
|
6024
|
+
if (!opened) {
|
|
6025
|
+
console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
|
|
6026
|
+
return EMPTY;
|
|
6027
|
+
}
|
|
6200
6028
|
if (!this.tables.some(t => t.name === tableName)) {
|
|
6201
6029
|
console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
|
|
6202
6030
|
return EMPTY;
|
|
6203
6031
|
}
|
|
6204
6032
|
const tableInstance = this.table(tableName);
|
|
6205
|
-
//
|
|
6206
|
-
const schema = tableInstance.schema;
|
|
6207
|
-
const validFields = [
|
|
6208
|
-
schema.primKey.name,
|
|
6209
|
-
...schema.indexes.map(idx => idx.name)
|
|
6210
|
-
];
|
|
6033
|
+
// Keep full object payload; Dexie stores non-indexed properties as well.
|
|
6211
6034
|
const insertRecords = records.map((record) => {
|
|
6212
|
-
const
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
});
|
|
6218
|
-
return objectFromSchema;
|
|
6035
|
+
const payload = { ...(record || {}) };
|
|
6036
|
+
if (payload.id === undefined || payload.id === null || payload.id === '') {
|
|
6037
|
+
delete payload.id;
|
|
6038
|
+
}
|
|
6039
|
+
return payload;
|
|
6219
6040
|
});
|
|
6220
6041
|
console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
|
|
6221
6042
|
return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
|
|
@@ -6229,17 +6050,14 @@ class DatabaseManagerService extends DbService {
|
|
|
6229
6050
|
updateTableRecords(table, records) {
|
|
6230
6051
|
const tableName = this.cleanTableName(table);
|
|
6231
6052
|
return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
|
|
6232
|
-
|
|
6233
|
-
const
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
});
|
|
6241
|
-
return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
|
|
6242
|
-
}));
|
|
6053
|
+
const insertRecords = records.map((record) => {
|
|
6054
|
+
const payload = { ...(record || {}) };
|
|
6055
|
+
if (payload.id === undefined || payload.id === null || payload.id === '') {
|
|
6056
|
+
delete payload.id;
|
|
6057
|
+
}
|
|
6058
|
+
return payload;
|
|
6059
|
+
});
|
|
6060
|
+
return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
|
|
6243
6061
|
}));
|
|
6244
6062
|
}
|
|
6245
6063
|
deleteTableRecord(table, id) {
|
|
@@ -6345,8 +6163,6 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6345
6163
|
this.logger = inject(LoggerService);
|
|
6346
6164
|
this.error$ = this.httpManagerService.error$;
|
|
6347
6165
|
this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
|
|
6348
|
-
this.progress$ = this.httpManagerService.progress$.pipe(delay(1));
|
|
6349
|
-
this.streamProgress$ = this.httpManagerService.streamProgress$.pipe(delay(1));
|
|
6350
6166
|
this.operationSuccess = new BehaviorSubject(null);
|
|
6351
6167
|
this.operationSuccess$ = this.operationSuccess.asObservable();
|
|
6352
6168
|
// PAGINATION
|
|
@@ -6359,10 +6175,17 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6359
6175
|
this.hasDatabase = false;
|
|
6360
6176
|
this.streamedResponse = [];
|
|
6361
6177
|
this.shouldRetry = true;
|
|
6178
|
+
this.volatileHeaders = new Set([
|
|
6179
|
+
'authorization',
|
|
6180
|
+
'x-request-id',
|
|
6181
|
+
'x-correlation-id',
|
|
6182
|
+
'x-trace-id',
|
|
6183
|
+
'x-amzn-trace-id',
|
|
6184
|
+
'date',
|
|
6185
|
+
'if-none-match'
|
|
6186
|
+
]);
|
|
6362
6187
|
this.wsRetryAttempts = new BehaviorSubject(0);
|
|
6363
6188
|
this.wsRetryAttempts$ = this.wsRetryAttempts.asObservable();
|
|
6364
|
-
// Guard: prevent concurrent streams and block requests during active stream
|
|
6365
|
-
this.activeStream$ = new BehaviorSubject(false);
|
|
6366
6189
|
this.messages = new BehaviorSubject([]);
|
|
6367
6190
|
this.messages$ = this.messages.asObservable();
|
|
6368
6191
|
this.userListByChannel = new BehaviorSubject(new Map());
|
|
@@ -6630,22 +6453,25 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6630
6453
|
}
|
|
6631
6454
|
}))))));
|
|
6632
6455
|
this.initDBStorage = this.effect((trigger$) => trigger$.pipe(tap(() => {
|
|
6456
|
+
console.log('[initDBStorage effect] Triggered, checking conditions:', {
|
|
6457
|
+
dataType: this.dataType,
|
|
6458
|
+
isARRAY: this.dataType === DataType.ARRAY,
|
|
6459
|
+
hasAdapter: !!this.apiOptions?.adapter,
|
|
6460
|
+
hasTable: !!this.databaseOptions?.table,
|
|
6461
|
+
tableValue: this.databaseOptions?.table
|
|
6462
|
+
});
|
|
6633
6463
|
if (this.dataType !== DataType.ARRAY)
|
|
6634
6464
|
console.warn('Database storage requires dataType to be ARRAY');
|
|
6635
6465
|
if (!this.apiOptions.adapter)
|
|
6636
|
-
console.warn('Database storage
|
|
6466
|
+
console.warn('Database storage adapter missing, using minimal or inferred schema');
|
|
6637
6467
|
if (this.databaseOptions && this.databaseOptions?.table === '')
|
|
6638
6468
|
console.warn('Database storage requires a table name');
|
|
6639
|
-
}), filter(() =>
|
|
6640
|
-
const
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
if (otherKeys.length > 0) {
|
|
6646
|
-
schema += ', ' + otherKeys.join(', ');
|
|
6647
|
-
}
|
|
6648
|
-
}
|
|
6469
|
+
}), filter(() => {
|
|
6470
|
+
const shouldProceed = this.dataType === DataType.ARRAY && !!this.databaseOptions?.table;
|
|
6471
|
+
console.log('[initDBStorage effect] Filter result:', shouldProceed);
|
|
6472
|
+
return shouldProceed;
|
|
6473
|
+
}), switchMap(() => {
|
|
6474
|
+
const schema = this.buildSchemaFromAdapter();
|
|
6649
6475
|
const tableDef = TableSchemaDef.adapt({
|
|
6650
6476
|
table: this.databaseOptions?.table,
|
|
6651
6477
|
schema: schema
|
|
@@ -6734,6 +6560,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6734
6560
|
}
|
|
6735
6561
|
}), concatMap(() => {
|
|
6736
6562
|
if (this.hasDatabase && this.databaseOptions?.table) {
|
|
6563
|
+
this.clearRequestCacheMetadata(this.databaseOptions.table);
|
|
6737
6564
|
const currentData = this.get()?.data;
|
|
6738
6565
|
const idsToDelete = Array.isArray(currentData) ? currentData.map((r) => r.id) : [];
|
|
6739
6566
|
if (idsToDelete.length > 0) {
|
|
@@ -6748,35 +6575,86 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6748
6575
|
this.fetchRecords = (options) => this.effect(() => of(RequestOptions.adapt(options)).pipe(switchMap(() => {
|
|
6749
6576
|
this.streamedResponse = [];
|
|
6750
6577
|
const requestOptions = this.updateRequestOptions(options?.headers);
|
|
6578
|
+
const effectiveParams = this.getEffectiveParams(options?.path);
|
|
6579
|
+
const requestSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
|
|
6751
6580
|
const fetchFromAPI = () => {
|
|
6752
|
-
return this.httpManagerService.getRequest(requestOptions,
|
|
6753
|
-
|
|
6581
|
+
return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(tap((data) => {
|
|
6582
|
+
// Extract array from paginated response if needed
|
|
6583
|
+
const arrayData = (data?.results && Array.isArray(data.results)) ? data.results : data;
|
|
6584
|
+
data = (!arrayData) ? (this.dataType === DataType.ARRAY) ? [] : {} : arrayData;
|
|
6754
6585
|
this.setData$(data);
|
|
6755
6586
|
}), concatMap((data) => {
|
|
6756
|
-
|
|
6587
|
+
// Extract array from paginated response for database storage
|
|
6588
|
+
const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data;
|
|
6589
|
+
if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
|
|
6590
|
+
const tableName = this.databaseOptions.table;
|
|
6757
6591
|
this.localStorageManagerService.updateStore({
|
|
6758
|
-
name:
|
|
6592
|
+
name: tableName,
|
|
6759
6593
|
data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
|
|
6760
6594
|
});
|
|
6761
|
-
|
|
6595
|
+
const schema = this.buildSchemaFromSample(dbData[0]);
|
|
6596
|
+
const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
|
|
6597
|
+
const schemaSignature = this.buildSchemaSignature(schema);
|
|
6598
|
+
// Always ensure table exists immediately before writing to avoid stale schema/store races.
|
|
6599
|
+
return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
|
|
6600
|
+
const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
|
|
6601
|
+
const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
|
|
6602
|
+
const ensureTable$ = schemaChanged
|
|
6603
|
+
? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
|
|
6604
|
+
: this.dbManagerService.createDatabaseTable(tableDef);
|
|
6605
|
+
return ensureTable$.pipe(switchMap((created) => {
|
|
6606
|
+
if (!created) {
|
|
6607
|
+
console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
|
|
6608
|
+
return of(data);
|
|
6609
|
+
}
|
|
6610
|
+
return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'GET', requestSignature, schemaSignature, options)));
|
|
6611
|
+
}));
|
|
6612
|
+
}), catchError((error) => {
|
|
6613
|
+
console.error('[DB STORAGE] Failed to ensure table and write records:', { table: tableName, schema, error });
|
|
6614
|
+
return of(data);
|
|
6615
|
+
}));
|
|
6762
6616
|
}
|
|
6763
6617
|
return of(data);
|
|
6764
6618
|
}));
|
|
6765
6619
|
};
|
|
6620
|
+
console.log('[DB STORAGE] Checking database storage:', {
|
|
6621
|
+
hasDatabase: this.hasDatabase,
|
|
6622
|
+
table: this.databaseOptions?.table,
|
|
6623
|
+
databaseOptions: this.databaseOptions
|
|
6624
|
+
});
|
|
6766
6625
|
if (this.hasDatabase && this.databaseOptions?.table) {
|
|
6767
6626
|
return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
|
|
6768
6627
|
if (!dbExists) {
|
|
6769
6628
|
const initObs = this.initDBStorageAsync();
|
|
6770
|
-
return initObs.pipe(switchMap(() => fetchFromAPI()))
|
|
6629
|
+
return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
|
|
6630
|
+
console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
|
|
6631
|
+
return fetchFromAPI();
|
|
6632
|
+
}));
|
|
6771
6633
|
}
|
|
6772
6634
|
return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
|
|
6773
6635
|
if (!tableExists) {
|
|
6774
6636
|
const initObs = this.initDBStorageAsync();
|
|
6775
|
-
return initObs.pipe(switchMap(() => fetchFromAPI()))
|
|
6637
|
+
return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
|
|
6638
|
+
console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
|
|
6639
|
+
return fetchFromAPI();
|
|
6640
|
+
}));
|
|
6776
6641
|
}
|
|
6777
6642
|
return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
|
|
6643
|
+
const cached = this.getRequestCacheMetadata(storeData, 'GET');
|
|
6644
|
+
const sameSignature = !!cached?.signature && cached.signature === requestSignature;
|
|
6645
|
+
const forceRefresh = !!options?.forceRefresh;
|
|
6646
|
+
const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
|
|
6778
6647
|
const expires = storeData?.expires || 0;
|
|
6779
6648
|
const hasExpired = expires > 0 && this.utils.hasExpired(expires);
|
|
6649
|
+
if (!forceRefresh && sameSignature && !hasExpired) {
|
|
6650
|
+
return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
|
|
6651
|
+
if (Array.isArray(dbData) && dbData.length > 0) {
|
|
6652
|
+
this.setData$(dbData);
|
|
6653
|
+
return of(dbData);
|
|
6654
|
+
}
|
|
6655
|
+
return fetchFromAPI();
|
|
6656
|
+
}));
|
|
6657
|
+
}
|
|
6780
6658
|
if (hasExpired) {
|
|
6781
6659
|
return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()), tap(() => {
|
|
6782
6660
|
this.localStorageManagerService.updateStore({
|
|
@@ -6785,12 +6663,27 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6785
6663
|
});
|
|
6786
6664
|
}));
|
|
6787
6665
|
}
|
|
6666
|
+
const expectedSchema = this.buildSchemaFromAdapter();
|
|
6667
|
+
const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
|
|
6668
|
+
if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
|
|
6669
|
+
const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
|
|
6670
|
+
return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
|
|
6671
|
+
}
|
|
6788
6672
|
return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
|
|
6789
6673
|
if (Array.isArray(dbData) && dbData.length > 0) {
|
|
6790
6674
|
this.setData$(dbData);
|
|
6791
6675
|
return of(dbData);
|
|
6792
6676
|
}
|
|
6793
6677
|
return fetchFromAPI();
|
|
6678
|
+
}), catchError((error) => {
|
|
6679
|
+
const tableName = this.databaseOptions.table;
|
|
6680
|
+
console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
|
|
6681
|
+
const schema = this.buildSchemaFromAdapter();
|
|
6682
|
+
const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
|
|
6683
|
+
return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
|
|
6684
|
+
console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
|
|
6685
|
+
return fetchFromAPI();
|
|
6686
|
+
}));
|
|
6794
6687
|
}));
|
|
6795
6688
|
}));
|
|
6796
6689
|
}));
|
|
@@ -6914,7 +6807,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6914
6807
|
if (res.length > 0)
|
|
6915
6808
|
this.setData$(res);
|
|
6916
6809
|
this.streamedResponse = res;
|
|
6917
|
-
}), scan((acc, res) => {
|
|
6810
|
+
}), concatMap((res) => this.persistStreamDataToDb(res, options)), scan((acc, res) => {
|
|
6918
6811
|
const previous = acc.current;
|
|
6919
6812
|
const current = res;
|
|
6920
6813
|
return { previous, current };
|
|
@@ -6928,36 +6821,79 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6928
6821
|
}
|
|
6929
6822
|
}));
|
|
6930
6823
|
})));
|
|
6931
|
-
this.fetchStream = (options) => this.effect(() => of(options).pipe(
|
|
6932
|
-
tap(() => {
|
|
6824
|
+
this.fetchStream = (options) => this.effect(() => of(options).pipe(tap(() => {
|
|
6933
6825
|
console.log('[DEBUG] fetchStream called');
|
|
6934
6826
|
this.httpManagerService.isPending.next(true);
|
|
6935
|
-
|
|
6936
|
-
}), concatMap((options) => {
|
|
6827
|
+
}), switchMap((options) => {
|
|
6937
6828
|
const requestOptions = this.updateRequestOptions(options?.headers);
|
|
6938
6829
|
requestOptions.stream = true;
|
|
6830
|
+
const effectiveParams = this.getEffectiveParams(options?.path);
|
|
6831
|
+
const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
|
|
6939
6832
|
console.log('[DEBUG] Making streaming request:', requestOptions);
|
|
6940
|
-
|
|
6833
|
+
if (this.hasDatabase && this.databaseOptions?.table && !options?.forceRefresh) {
|
|
6834
|
+
return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
|
|
6835
|
+
const cached = this.getRequestCacheMetadata(storeData, 'STREAM');
|
|
6836
|
+
const sameSignature = !!cached?.signature && cached.signature === requestSignature;
|
|
6837
|
+
const expires = storeData?.expires || 0;
|
|
6838
|
+
const hasExpired = expires > 0 && this.utils.hasExpired(expires);
|
|
6839
|
+
if (sameSignature && !hasExpired) {
|
|
6840
|
+
return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
|
|
6841
|
+
if (Array.isArray(dbData) && dbData.length > 0) {
|
|
6842
|
+
return of({ data: dbData, fromCache: true });
|
|
6843
|
+
}
|
|
6844
|
+
return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
|
|
6845
|
+
}));
|
|
6846
|
+
}
|
|
6847
|
+
return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
|
|
6848
|
+
})).pipe(tap((packet) => {
|
|
6849
|
+
const res = packet?.data;
|
|
6850
|
+
console.log('[DEBUG] Streaming response received:', res);
|
|
6851
|
+
if (res && res.length > 0) {
|
|
6852
|
+
console.log('[DEBUG] Updating state with streaming data:', res);
|
|
6853
|
+
this.setData$(res);
|
|
6854
|
+
this.streamedResponse = res;
|
|
6855
|
+
}
|
|
6856
|
+
else {
|
|
6857
|
+
console.log('[DEBUG] No streaming data or empty array:', res);
|
|
6858
|
+
}
|
|
6859
|
+
// Reset pending once we have a response packet (cache or network)
|
|
6860
|
+
this.httpManagerService.isPending.next(false);
|
|
6861
|
+
}), concatMap((packet) => {
|
|
6862
|
+
if (packet?.fromCache) {
|
|
6863
|
+
return of(packet?.data);
|
|
6864
|
+
}
|
|
6865
|
+
return this.persistStreamDataToDb(packet?.data, options);
|
|
6866
|
+
}), map((res) => {
|
|
6867
|
+
console.log('[DEBUG] Returning data to subscribers:', res);
|
|
6868
|
+
return res;
|
|
6869
|
+
}), catchError((error) => {
|
|
6870
|
+
console.error('[DEBUG] Streaming error:', error);
|
|
6871
|
+
this.httpManagerService.isPending.next(false);
|
|
6872
|
+
return of([]);
|
|
6873
|
+
}));
|
|
6874
|
+
}
|
|
6875
|
+
return this.httpManagerService.getRequest(requestOptions, effectiveParams)
|
|
6941
6876
|
.pipe(tap((res) => {
|
|
6942
|
-
|
|
6877
|
+
console.log('[DEBUG] Streaming response received:', res);
|
|
6943
6878
|
// Always update state with streaming data
|
|
6944
6879
|
if (res && res.length > 0) {
|
|
6945
|
-
|
|
6880
|
+
console.log('[DEBUG] Updating state with streaming data:', res);
|
|
6946
6881
|
this.setData$(res);
|
|
6947
6882
|
this.streamedResponse = res;
|
|
6948
6883
|
}
|
|
6949
6884
|
else {
|
|
6950
|
-
|
|
6885
|
+
console.log('[DEBUG] No streaming data or empty array:', res);
|
|
6951
6886
|
}
|
|
6952
|
-
|
|
6953
|
-
|
|
6887
|
+
// Reset pending once we have a response packet
|
|
6888
|
+
this.httpManagerService.isPending.next(false);
|
|
6889
|
+
}), concatMap((res) => this.persistStreamDataToDb(res, options)), map((res) => {
|
|
6890
|
+
console.log('[DEBUG] Returning data to subscribers:', res);
|
|
6954
6891
|
return res; // Return the data so subscribers can receive it
|
|
6955
6892
|
}), catchError((error) => {
|
|
6956
|
-
|
|
6893
|
+
console.error('[DEBUG] Streaming error:', error);
|
|
6957
6894
|
this.httpManagerService.isPending.next(false);
|
|
6958
6895
|
return of([]);
|
|
6959
|
-
})
|
|
6960
|
-
);
|
|
6896
|
+
}));
|
|
6961
6897
|
})));
|
|
6962
6898
|
try {
|
|
6963
6899
|
this.databaseOptions = database;
|
|
@@ -6977,7 +6913,11 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
6977
6913
|
encrypted: false,
|
|
6978
6914
|
})
|
|
6979
6915
|
});
|
|
6980
|
-
|
|
6916
|
+
// Use initDBStorageAsync directly - the effect initDBStorage requires subscription
|
|
6917
|
+
this.initDBStorageAsync().subscribe({
|
|
6918
|
+
next: () => console.log('[Constructor] Database storage initialized'),
|
|
6919
|
+
error: (err) => console.error('[Constructor] Database storage initialization failed:', err)
|
|
6920
|
+
});
|
|
6981
6921
|
}
|
|
6982
6922
|
}
|
|
6983
6923
|
catch (error) {
|
|
@@ -7031,8 +6971,24 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
7031
6971
|
this.dataType = (dataType) ? dataType : DataType.ARRAY;
|
|
7032
6972
|
// Only update database options if a database parameter is explicitly provided
|
|
7033
6973
|
if (database !== undefined) {
|
|
6974
|
+
console.log('[setApiRequestOptions] Database config:', {
|
|
6975
|
+
database,
|
|
6976
|
+
hasTable: !!database?.table,
|
|
6977
|
+
tableValue: database?.table
|
|
6978
|
+
});
|
|
7034
6979
|
this.hasDatabase = (database?.table) ? true : false;
|
|
7035
|
-
|
|
6980
|
+
const adapted = DatabaseStorage.adapt(database);
|
|
6981
|
+
console.log('[setApiRequestOptions] DatabaseStorage.adapt result:', adapted);
|
|
6982
|
+
this.databaseOptions = (this.hasDatabase) ? adapted : undefined;
|
|
6983
|
+
// Trigger database table creation if table is configured
|
|
6984
|
+
if (this.hasDatabase && this.databaseOptions?.table) {
|
|
6985
|
+
console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table);
|
|
6986
|
+
// Use initDBStorageAsync directly instead of the effect - effects need subscription to run
|
|
6987
|
+
this.initDBStorageAsync().subscribe({
|
|
6988
|
+
next: () => console.log('[setApiRequestOptions] Database storage initialized successfully'),
|
|
6989
|
+
error: (err) => console.error('[setApiRequestOptions] Database storage initialization failed:', err)
|
|
6990
|
+
});
|
|
6991
|
+
}
|
|
7036
6992
|
}
|
|
7037
6993
|
if (this.apiOptions.ws && this.apiOptions.ws.id !== '') {
|
|
7038
6994
|
// Auto-prefix channel ID for private state manager channels
|
|
@@ -7154,11 +7110,20 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
7154
7110
|
this.setData$(data);
|
|
7155
7111
|
}
|
|
7156
7112
|
updateArrayState(currentData, newData) {
|
|
7113
|
+
// For non-streaming requests (GET), REPLACE data entirely
|
|
7114
|
+
// For streaming requests, MERGE with existing data incrementally
|
|
7115
|
+
if (this.streamedResponse.length === 0) {
|
|
7116
|
+
// GET request: return new data as-is (replace)
|
|
7117
|
+
return newData;
|
|
7118
|
+
}
|
|
7119
|
+
// Streaming: merge with existing data
|
|
7157
7120
|
const filterCurrentData = () => {
|
|
7158
7121
|
const ids = this.streamedResponse.map((obj) => obj.id);
|
|
7159
7122
|
return currentData.filter(obj => (obj.id) ? ids.includes(obj.id) : obj);
|
|
7160
7123
|
};
|
|
7161
|
-
const filteredCurrentData = (this.httpManagerService.isPending.value)
|
|
7124
|
+
const filteredCurrentData = (this.httpManagerService.isPending.value)
|
|
7125
|
+
? currentData
|
|
7126
|
+
: filterCurrentData();
|
|
7162
7127
|
const updatedData = filteredCurrentData.map(item => {
|
|
7163
7128
|
const newItem = newData.find(newItem => {
|
|
7164
7129
|
const hasId = (newItem?.id && item?.id) ? true : false;
|
|
@@ -7175,32 +7140,92 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
7175
7140
|
return [...updatedData, ...addedData];
|
|
7176
7141
|
}
|
|
7177
7142
|
initDBStorageAsync() {
|
|
7143
|
+
console.log('[initDBStorageAsync] Starting initialization:', {
|
|
7144
|
+
dataType: this.dataType,
|
|
7145
|
+
hasAdapter: !!this.apiOptions?.adapter,
|
|
7146
|
+
table: this.databaseOptions?.table,
|
|
7147
|
+
databaseOptions: this.databaseOptions
|
|
7148
|
+
});
|
|
7178
7149
|
if (this.dataType !== DataType.ARRAY) {
|
|
7179
7150
|
console.warn('Database storage requires dataType to be ARRAY');
|
|
7180
7151
|
return of(null);
|
|
7181
7152
|
}
|
|
7182
7153
|
if (!this.apiOptions.adapter) {
|
|
7183
|
-
console.warn('Database storage
|
|
7184
|
-
return of(null);
|
|
7154
|
+
console.warn('Database storage adapter missing, using minimal or inferred schema');
|
|
7185
7155
|
}
|
|
7186
7156
|
if (!this.databaseOptions?.table) {
|
|
7187
7157
|
console.warn('Database storage requires a table name');
|
|
7188
7158
|
return of(null);
|
|
7189
7159
|
}
|
|
7160
|
+
const schema = this.buildSchemaFromAdapter();
|
|
7161
|
+
const tableDef = TableSchemaDef.adapt({
|
|
7162
|
+
table: this.databaseOptions?.table,
|
|
7163
|
+
schema: schema
|
|
7164
|
+
});
|
|
7165
|
+
return this.dbManagerService.createDatabaseTable(tableDef).pipe(tap((created) => {
|
|
7166
|
+
if (created && this.databaseOptions?.table) {
|
|
7167
|
+
this.saveSchemaSignature(this.databaseOptions.table, this.buildSchemaSignature(schema));
|
|
7168
|
+
}
|
|
7169
|
+
}));
|
|
7170
|
+
}
|
|
7171
|
+
buildSchemaFromAdapter() {
|
|
7190
7172
|
const sampleData = this.apiOptions.adapter?.({}) || {};
|
|
7191
|
-
|
|
7173
|
+
return this.buildSchemaFromSample(sampleData);
|
|
7174
|
+
}
|
|
7175
|
+
buildSchemaFromSample(sampleData) {
|
|
7176
|
+
const schemaKeys = Object.keys(sampleData || {}).filter(key => sampleData[key] !== undefined);
|
|
7192
7177
|
let schema = '++id';
|
|
7193
7178
|
if (schemaKeys.length > 0) {
|
|
7194
|
-
const otherKeys = schemaKeys.filter(k => k !== 'id');
|
|
7179
|
+
const otherKeys = schemaKeys.filter((k) => k !== 'id');
|
|
7195
7180
|
if (otherKeys.length > 0) {
|
|
7196
7181
|
schema += ', ' + otherKeys.join(', ');
|
|
7197
7182
|
}
|
|
7198
7183
|
}
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7184
|
+
return schema;
|
|
7185
|
+
}
|
|
7186
|
+
persistStreamDataToDb(payload, streamOptions) {
|
|
7187
|
+
if (!this.hasDatabase || !this.databaseOptions?.table) {
|
|
7188
|
+
return of(payload);
|
|
7189
|
+
}
|
|
7190
|
+
const dbData = (payload?.results && Array.isArray(payload.results))
|
|
7191
|
+
? payload.results
|
|
7192
|
+
: Array.isArray(payload)
|
|
7193
|
+
? payload
|
|
7194
|
+
: payload
|
|
7195
|
+
? [payload]
|
|
7196
|
+
: [];
|
|
7197
|
+
if (dbData.length === 0) {
|
|
7198
|
+
return of(payload);
|
|
7199
|
+
}
|
|
7200
|
+
const tableName = this.databaseOptions.table;
|
|
7201
|
+
const requestOptions = this.updateRequestOptions(streamOptions?.headers);
|
|
7202
|
+
requestOptions.stream = true;
|
|
7203
|
+
const effectiveParams = this.getEffectiveParams(streamOptions?.path);
|
|
7204
|
+
const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
|
|
7205
|
+
this.localStorageManagerService.updateStore({
|
|
7206
|
+
name: tableName,
|
|
7207
|
+
data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
|
|
7208
|
+
});
|
|
7209
|
+
const schema = this.buildSchemaFromSample(dbData[0]);
|
|
7210
|
+
const schemaSignature = this.buildSchemaSignature(schema);
|
|
7211
|
+
const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
|
|
7212
|
+
return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
|
|
7213
|
+
const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
|
|
7214
|
+
const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
|
|
7215
|
+
const ensureTable$ = schemaChanged
|
|
7216
|
+
? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
|
|
7217
|
+
: this.dbManagerService.createDatabaseTable(tableDef);
|
|
7218
|
+
return ensureTable$.pipe(switchMap((created) => {
|
|
7219
|
+
if (!created) {
|
|
7220
|
+
console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
|
|
7221
|
+
return of(payload);
|
|
7222
|
+
}
|
|
7223
|
+
return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'STREAM', requestSignature, schemaSignature, streamOptions)), map(() => payload));
|
|
7224
|
+
}));
|
|
7225
|
+
}), map(() => payload), catchError((error) => {
|
|
7226
|
+
console.error('[DB STORAGE] Failed to persist streaming payload:', { table: tableName, schema, error });
|
|
7227
|
+
return of(payload);
|
|
7228
|
+
}));
|
|
7204
7229
|
}
|
|
7205
7230
|
// WEBSOCKET COMMUNICATION (STATE MANAGER)
|
|
7206
7231
|
wsCommunication(method, path) {
|
|
@@ -7496,6 +7521,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
7496
7521
|
const tableName = this.databaseOptions.table;
|
|
7497
7522
|
this.dbManagerService.clearTable(tableName).subscribe({
|
|
7498
7523
|
next: () => {
|
|
7524
|
+
this.clearRequestCacheMetadata(tableName);
|
|
7499
7525
|
if (this.dataType === DataType.ARRAY) {
|
|
7500
7526
|
this.setData$([]);
|
|
7501
7527
|
}
|
|
@@ -7518,6 +7544,115 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
7518
7544
|
: { ...options.headers };
|
|
7519
7545
|
return options;
|
|
7520
7546
|
}
|
|
7547
|
+
normalizeObject(value) {
|
|
7548
|
+
if (Array.isArray(value)) {
|
|
7549
|
+
return value.map((item) => this.normalizeObject(item));
|
|
7550
|
+
}
|
|
7551
|
+
if (value && typeof value === 'object') {
|
|
7552
|
+
return Object.keys(value)
|
|
7553
|
+
.sort()
|
|
7554
|
+
.reduce((acc, key) => {
|
|
7555
|
+
acc[key] = this.normalizeObject(value[key]);
|
|
7556
|
+
return acc;
|
|
7557
|
+
}, {});
|
|
7558
|
+
}
|
|
7559
|
+
return value;
|
|
7560
|
+
}
|
|
7561
|
+
filterHeaders(headers) {
|
|
7562
|
+
const source = headers || {};
|
|
7563
|
+
return Object.keys(source).reduce((acc, key) => {
|
|
7564
|
+
if (!this.volatileHeaders.has(key.toLowerCase())) {
|
|
7565
|
+
acc[key] = source[key];
|
|
7566
|
+
}
|
|
7567
|
+
return acc;
|
|
7568
|
+
}, {});
|
|
7569
|
+
}
|
|
7570
|
+
resolvePath(params) {
|
|
7571
|
+
const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
|
|
7572
|
+
const effective = this.getEffectiveParams(params);
|
|
7573
|
+
return effective ? [...basePath, ...effective] : [...basePath];
|
|
7574
|
+
}
|
|
7575
|
+
getEffectiveParams(params) {
|
|
7576
|
+
if (!Array.isArray(params) || params.length === 0) {
|
|
7577
|
+
return undefined;
|
|
7578
|
+
}
|
|
7579
|
+
const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
|
|
7580
|
+
if (basePath.length !== params.length) {
|
|
7581
|
+
return params;
|
|
7582
|
+
}
|
|
7583
|
+
const normalizePart = (value) => {
|
|
7584
|
+
if (value && typeof value === 'object') {
|
|
7585
|
+
return JSON.stringify(this.normalizeObject(value));
|
|
7586
|
+
}
|
|
7587
|
+
return String(value);
|
|
7588
|
+
};
|
|
7589
|
+
const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
|
|
7590
|
+
return samePath ? undefined : params;
|
|
7591
|
+
}
|
|
7592
|
+
buildRequestSignature(method, requestOptions, params) {
|
|
7593
|
+
const signaturePayload = {
|
|
7594
|
+
method,
|
|
7595
|
+
server: requestOptions.server,
|
|
7596
|
+
path: this.resolvePath(params),
|
|
7597
|
+
headers: this.filterHeaders(requestOptions.headers),
|
|
7598
|
+
stream: !!requestOptions.stream,
|
|
7599
|
+
streamType: requestOptions.streamType || null
|
|
7600
|
+
};
|
|
7601
|
+
return JSON.stringify(this.normalizeObject(signaturePayload));
|
|
7602
|
+
}
|
|
7603
|
+
buildSchemaSignature(schema) {
|
|
7604
|
+
return JSON.stringify(this.normalizeObject(schema.split(',').map((part) => part.trim()).filter(Boolean)));
|
|
7605
|
+
}
|
|
7606
|
+
getRequestCacheMetadata(storeData, type) {
|
|
7607
|
+
return storeData?.requestCache?.[type] || null;
|
|
7608
|
+
}
|
|
7609
|
+
getStoredSchemaSignature(storeData) {
|
|
7610
|
+
return storeData?.schemaSignature || null;
|
|
7611
|
+
}
|
|
7612
|
+
saveSchemaSignature(tableName, schemaSignature) {
|
|
7613
|
+
this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
|
|
7614
|
+
this.localStorageManagerService.updateStore({
|
|
7615
|
+
name: tableName,
|
|
7616
|
+
data: {
|
|
7617
|
+
...(storeData || {}),
|
|
7618
|
+
schemaSignature,
|
|
7619
|
+
}
|
|
7620
|
+
});
|
|
7621
|
+
})).subscribe();
|
|
7622
|
+
}
|
|
7623
|
+
saveRequestCacheMetadata(tableName, type, signature, schemaSignature, options) {
|
|
7624
|
+
this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
|
|
7625
|
+
const currentCache = storeData?.requestCache || {};
|
|
7626
|
+
this.localStorageManagerService.updateStore({
|
|
7627
|
+
name: tableName,
|
|
7628
|
+
data: {
|
|
7629
|
+
...(storeData || {}),
|
|
7630
|
+
schemaSignature: schemaSignature || storeData?.schemaSignature || null,
|
|
7631
|
+
requestCache: {
|
|
7632
|
+
...currentCache,
|
|
7633
|
+
[type]: {
|
|
7634
|
+
signature,
|
|
7635
|
+
savedAt: Date.now(),
|
|
7636
|
+
path: this.resolvePath(options?.path),
|
|
7637
|
+
headers: this.filterHeaders(options?.headers)
|
|
7638
|
+
}
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
});
|
|
7642
|
+
})).subscribe();
|
|
7643
|
+
}
|
|
7644
|
+
clearRequestCacheMetadata(tableName) {
|
|
7645
|
+
this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
|
|
7646
|
+
if (!storeData)
|
|
7647
|
+
return;
|
|
7648
|
+
const updated = { ...(storeData || {}) };
|
|
7649
|
+
delete updated.requestCache;
|
|
7650
|
+
this.localStorageManagerService.updateStore({
|
|
7651
|
+
name: tableName,
|
|
7652
|
+
data: updated
|
|
7653
|
+
});
|
|
7654
|
+
})).subscribe();
|
|
7655
|
+
}
|
|
7521
7656
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService, deps: [{ token: API_OPTS }, { token: "dataType" }, { token: DatabaseStorage }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7522
7657
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService }); }
|
|
7523
7658
|
}
|
|
@@ -8854,13 +8989,13 @@ class StateManagerDemoService extends HTTPManagerStateService {
|
|
|
8854
8989
|
const sampleOptions = RequestOptions.adapt({ path: [data.id] });
|
|
8855
8990
|
this.deleteRecord(sampleOptions);
|
|
8856
8991
|
}
|
|
8857
|
-
streamRequest() {
|
|
8992
|
+
streamRequest(options) {
|
|
8858
8993
|
console.log('[DEMO SERVICE] streamRequest called');
|
|
8859
8994
|
const headers = {
|
|
8860
8995
|
auth: "sample-auth-token"
|
|
8861
8996
|
};
|
|
8862
8997
|
console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
|
|
8863
|
-
this.fetchStream();
|
|
8998
|
+
this.fetchStream(options);
|
|
8864
8999
|
}
|
|
8865
9000
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
8866
9001
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
|
|
@@ -8948,7 +9083,6 @@ class RequestManagerStateDemoComponent {
|
|
|
8948
9083
|
constructor() {
|
|
8949
9084
|
this.server = 'http://localhost:8080';
|
|
8950
9085
|
this.stateManagerDemoService = inject(StateManagerDemoService);
|
|
8951
|
-
this.progress$ = this.stateManagerDemoService.streamProgress$;
|
|
8952
9086
|
this.displayedColumns = [];
|
|
8953
9087
|
this.fb = inject(FormBuilder);
|
|
8954
9088
|
this.streamTypes = [
|
|
@@ -8961,7 +9095,6 @@ class RequestManagerStateDemoComponent {
|
|
|
8961
9095
|
this.streamType = 'Auto';
|
|
8962
9096
|
this.httpManagerService = inject(HTTPManagerService);
|
|
8963
9097
|
this.isPending$ = this.stateManagerDemoService.isPending$;
|
|
8964
|
-
this.isStreamingPending$ = this.httpManagerService.isStreamingPending$;
|
|
8965
9098
|
this.error$ = this.stateManagerDemoService.error$;
|
|
8966
9099
|
this.countdown$ = this.httpManagerService.countdown$;
|
|
8967
9100
|
this.GET_error$ = new BehaviorSubject('');
|
|
@@ -9109,11 +9242,23 @@ class RequestManagerStateDemoComponent {
|
|
|
9109
9242
|
return { apiOptions: apiOptions, path: pathReq };
|
|
9110
9243
|
}
|
|
9111
9244
|
onSetStateOptions() {
|
|
9112
|
-
|
|
9245
|
+
const dbValue = this.database;
|
|
9246
|
+
console.log('[onSetStateOptions] Called, checking form values:', {
|
|
9247
|
+
isValid: this.isValid,
|
|
9248
|
+
database: dbValue,
|
|
9249
|
+
hasTable: !!dbValue?.table,
|
|
9250
|
+
tableValue: dbValue?.table,
|
|
9251
|
+
dataType: this.dataType
|
|
9252
|
+
});
|
|
9253
|
+
if (!this.isValid) {
|
|
9254
|
+
console.log('[onSetStateOptions] Form invalid, aborting');
|
|
9113
9255
|
return;
|
|
9256
|
+
}
|
|
9114
9257
|
const reqParams = this.compileRequest();
|
|
9115
|
-
const db = DatabaseStorage.adapt(
|
|
9258
|
+
const db = DatabaseStorage.adapt(dbValue);
|
|
9259
|
+
console.log('[onSetStateOptions] DatabaseStorage.adapt result:', db);
|
|
9116
9260
|
const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
|
|
9261
|
+
console.log('[onSetStateOptions] Calling setAPIOptions with:', { db, type, hasTable: !!db?.table });
|
|
9117
9262
|
this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
|
|
9118
9263
|
this.requestForm.markAsPristine();
|
|
9119
9264
|
}
|
|
@@ -9148,8 +9293,13 @@ class RequestManagerStateDemoComponent {
|
|
|
9148
9293
|
reqParams.apiOptions.stream = true;
|
|
9149
9294
|
reqParams.apiOptions.streamType = this.streamType;
|
|
9150
9295
|
this.requestType = 'STREAM';
|
|
9296
|
+
const streamOptions = RequestOptions.adapt({
|
|
9297
|
+
path: reqParams.path,
|
|
9298
|
+
headers: reqParams.apiOptions.headers,
|
|
9299
|
+
forceRefresh: false,
|
|
9300
|
+
});
|
|
9151
9301
|
console.log('[COMPONENT] Calling streamRequest...');
|
|
9152
|
-
this.stateManagerDemoService.streamRequest();
|
|
9302
|
+
this.stateManagerDemoService.streamRequest(streamOptions);
|
|
9153
9303
|
}
|
|
9154
9304
|
errorHandling(err, type) {
|
|
9155
9305
|
console.log(err, type);
|
|
@@ -9173,11 +9323,11 @@ class RequestManagerStateDemoComponent {
|
|
|
9173
9323
|
this.prompts = [];
|
|
9174
9324
|
}
|
|
9175
9325
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9176
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async) && (isStreamingPending$ | async); as data) {\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\"></mat-progress-bar>\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
9326
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
9177
9327
|
}
|
|
9178
9328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
|
|
9179
9329
|
type: Component,
|
|
9180
|
-
args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async) && (isStreamingPending$ | async); as data) {\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\"></mat-progress-bar>\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
|
|
9330
|
+
args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
|
|
9181
9331
|
}], ctorParameters: () => [], propDecorators: { server: [{
|
|
9182
9332
|
type: Input
|
|
9183
9333
|
}], adapter: [{
|
|
@@ -9241,9 +9391,7 @@ class RequestManagerDemoComponent {
|
|
|
9241
9391
|
this.toastMessage = inject(ToastMessageDisplayService);
|
|
9242
9392
|
this.questionControl = this.fb.control("", [Validators.required]);
|
|
9243
9393
|
this.httpManagerService = inject(HTTPManagerService);
|
|
9244
|
-
this.progress$ = this.httpManagerService.streamProgress$;
|
|
9245
9394
|
this.isPending$ = this.httpManagerService.isPending$;
|
|
9246
|
-
this.isStreamingPending$ = this.httpManagerService.isStreamingPending$;
|
|
9247
9395
|
this.countdown$ = this.httpManagerService.countdown$;
|
|
9248
9396
|
this.GET_error$ = new BehaviorSubject('');
|
|
9249
9397
|
this.POST_error$ = new BehaviorSubject('');
|
|
@@ -9488,8 +9636,6 @@ class RequestManagerDemoComponent {
|
|
|
9488
9636
|
reqParams.apiOptions.stream = true;
|
|
9489
9637
|
reqParams.apiOptions.streamType = StreamType.NDJSON;
|
|
9490
9638
|
this.requestParams.GET = reqParams.apiOptions;
|
|
9491
|
-
this.STREAM$ = EMPTY; // Cancel any active stream before starting new one
|
|
9492
|
-
this.STREAM_error$.next('');
|
|
9493
9639
|
this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
|
|
9494
9640
|
.pipe(tap((data) => {
|
|
9495
9641
|
console.log("API STREAM response", data);
|
|
@@ -9675,11 +9821,11 @@ class RequestManagerDemoComponent {
|
|
|
9675
9821
|
return results.filter(r => r === undefined).length;
|
|
9676
9822
|
}
|
|
9677
9823
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9678
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\" [style.display]=\"(isStreamingPending$ | async) ? 'block' : 'none'\"></mat-progress-bar>\n <table mat-table [dataSource]=\"data\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (parallelResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of parallelResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ result.id }}:</strong>\n <span>{{ result.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (sequentialResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of sequentialResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ result.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ result.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i16.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i16.MatCardContent, selector: "mat-card-content" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
9824
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (parallelResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of parallelResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ result.id }}:</strong>\n <span>{{ result.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (sequentialResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of sequentialResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ result.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ result.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i16.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i16.MatCardContent, selector: "mat-card-content" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
|
|
9679
9825
|
}
|
|
9680
9826
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, decorators: [{
|
|
9681
9827
|
type: Component,
|
|
9682
|
-
args: [{ selector: 'app-request-manager-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\" [style.display]=\"(isStreamingPending$ | async) ? 'block' : 'none'\"></mat-progress-bar>\n <table mat-table [dataSource]=\"data\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (parallelResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of parallelResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ result.id }}:</strong>\n <span>{{ result.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (sequentialResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of sequentialResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ result.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ result.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"] }]
|
|
9828
|
+
args: [{ selector: 'app-request-manager-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\" class=\"btn\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <!-- Parallel Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Parallel Requests</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteParallelBatch()\"\n [disabled]=\"isParallelLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>sync</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isParallelLoading ? 'Loading...' : 'Fetch 5 Users' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isParallelLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests (concurrency: 3)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (parallelResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of parallelResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>User #{{ result.id }}:</strong>\n <span>{{ result.name }}</span>\n </div>\n <span style=\"color: #2196f3; font-weight: bold;\">Complete</span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (parallelExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>\u23F1Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(parallelExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Parallel</span>\n </div>\n </div>\n </div>\n }\n\n <!-- Error Display -->\n @if (parallelError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ parallelError }}</mat-error>\n </div>\n }\n </div>\n\n <!-- Sequential Requests Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Sequential Requests</h2>\n <button\n mat-raised-button\n color=\"accent\"\n (click)=\"onExecuteSequentialBatch()\"\n [disabled]=\"isSequentialLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>timeline</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isSequentialLoading ? 'Loading...' : 'Fetch 5 Todos' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isSequentialLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing sequential requests (one at a time)...\n </p>\n </div>\n }\n\n <!-- Results -->\n @if (sequentialResults) {\n <div style=\"margin: 1rem 0;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (result of sequentialResults; track $index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ result ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (result) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Todo #{{ result.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ result.title }}\n </span>\n </div>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem; justify-content: space-between;\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ i + 1 }}:</strong>\n <span>Failed</span>\n </div>\n <span style=\"color: #f44336; font-weight: bold;\">Error</span>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n\n <!-- Execution Metrics -->\n @if (sequentialExecutionTime !== undefined) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(sequentialExecutionTime) }}\n </span>\n </div>\n <div>\n <strong>Mode:</strong>\n <span style=\"margin-left: 0.5rem;\">Sequential</span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Stream Mode Section -->\n <div>\n <div style=\"display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;\">\n <h2 style=\"flex: 1; margin: 0;\">Stream Mode (Real-time States)</h2>\n <button\n mat-raised-button\n color=\"primary\"\n (click)=\"onExecuteStreamBatch()\"\n [disabled]=\"isStreamLoading\"\n style=\"min-width: 200px;\">\n <mat-icon>auto_awesome</mat-icon>\n <span style=\"margin-left: 8px;\">\n {{ isStreamLoading ? 'Loading...' : 'Fetch 5 Posts' }}\n </span>\n </button>\n </div>\n\n <!-- Loading Indicator -->\n @if (isStreamLoading) {\n <div style=\"margin: 1rem 0;\">\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n <p style=\"margin-top: 0.5rem; color: #666;\">\n Executing parallel requests with real-time state updates...\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (streamProgress) {\n <div style=\"background: #f5f5f5; padding: 1rem; border-radius: 4px; margin: 1rem 0;\">\n <div style=\"display: flex; gap: 2rem; margin-bottom: 0.5rem;\">\n <div>\n <strong>Execution Time:</strong>\n <span style=\"margin-left: 0.5rem; color: #4caf50;\">\n {{ formatTime(streamExecutionTime || 0) }}\n </span>\n </div>\n <div>\n <strong>Progress:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.completed }} / {{ streamProgress.total }} completed\n </span>\n </div>\n <div>\n <strong>Pending:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.pending }}\n </span>\n </div>\n <div>\n <strong>Failed:</strong>\n <span style=\"margin-left: 0.5rem;\">\n {{ streamProgress.failed }}\n </span>\n </div>\n </div>\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"streamProgress.percent\"\n color=\"accent\">\n </mat-progress-bar>\n <p style=\"margin-top: 0.5rem; text-align: right; color: #666;\">\n {{ streamProgress.percent }}%\n </p>\n </div>\n }\n\n <!-- Error Display -->\n @if (streamError) {\n <div style=\"margin-top: 1rem;\">\n <mat-error>{{ streamError }}</mat-error>\n </div>\n }\n\n <!-- Individual Request States -->\n @if (streamBatchStates.length > 0) {\n <div style=\"margin-top: 1rem;\">\n <mat-card appearance=\"outlined\">\n <mat-card-content>\n <div style=\"display: grid; gap: 0.5rem;\">\n @for (state of filteredStreamStates; track state.index; let i = $index) {\n <div\n style=\"padding: 0.75rem; border-radius: 4px; border: 1px solid;\n {{ isPendingState(state) ? 'background: #fff3e0; border-color: #ff9800;' :\n isSuccessState(state) ? 'background: #e3f2fd; border-color: #2196f3;' :\n 'background: #ffebee; border-color: #f44336;' }}\">\n @if (isPendingState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-spinner diameter=\"16\" style=\"margin-right: 0.5rem;\"></mat-spinner>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Loading...</span>\n </div>\n } @else if (isSuccessState(state)) {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #2196f3; font-size: 18px;\">check_circle</mat-icon>\n <strong>Post #{{ state.data.id }}:</strong>\n <span style=\"flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">\n {{ state.data.title }}\n </span>\n </div>\n } @else {\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <mat-icon style=\"color: #f44336; font-size: 18px;\">error</mat-icon>\n <strong>Request {{ state.index + 1 }}:</strong>\n <span>Failed</span>\n <mat-chip style=\"margin-left: auto; font-size: 0.8em; background: #f44336; color: white;\">\n Error\n </mat-chip>\n </div>\n }\n </div>\n }\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n }\n </div>\n\n <!-- Comparison Info -->\n <div style=\"padding: 1rem; background: whitesmoke; border-radius: 4px; border: 1px solid #ccc;\">\n <h4 style=\"margin-top: 0;\">Key Differences</h4>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;\">\n <div>\n <strong>Parallel:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Faster execution (concurrent)</li>\n <li>Multiple requests simultaneously</li>\n <li>Results may complete out of order</li>\n <li>Best for: Independent requests</li>\n </ul>\n </div>\n <div>\n <strong>Sequential:</strong>\n <ul style=\"margin: 0.5rem 0 0 1rem; padding: 0;\">\n <li>Slower execution (one-by-one)</li>\n <li>Requests execute in order</li>\n <li>Results always in request order</li>\n <li>Best for: Dependent requests</li>\n </ul>\n </div>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}.batch-demo-section{margin:2rem 0;padding:1.5rem;border:1px solid #e0e0e0;border-radius:8px;background:#fafafa}.batch-result-item{padding:.75rem;border-radius:4px;margin-bottom:.5rem;display:flex;align-items:center;gap:.5rem}.batch-result-item.success{background:#e8f5e9;border-left:3px solid #4caf50}.batch-result-item.error{background:#ffebee;border-left:3px solid #f44336}.execution-metrics{display:flex;gap:2rem;padding:1rem;background:#f5f5f5;border-radius:4px;margin:1rem 0}.execution-metrics>div{display:flex;align-items:center;gap:.5rem}.comparison-box{padding:1rem;background:#fff3e0;border-radius:4px;margin-top:2rem}.comparison-box h4{margin-top:0;margin-bottom:1rem}.comparison-box .comparison-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}@media(max-width:768px){.comparison-box .comparison-grid{grid-template-columns:1fr}}.loading-indicator{margin:1rem 0}.result-card{margin-top:1rem}.result-card mat-card-content{padding:1rem}\n"] }]
|
|
9683
9829
|
}], ctorParameters: () => [], propDecorators: { server: [{
|
|
9684
9830
|
type: Input
|
|
9685
9831
|
}], adapter: [{
|