http-request-manager 18.12.9 → 18.13.1

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.
@@ -1,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, signal, effect, computed, Injector, Optional, EventEmitter, Input, Output, ViewEncapsulation, Component, NgModule, ViewChild } from '@angular/core';
3
3
  import { ComponentStore } from '@ngrx/component-store';
4
- import { map, catchError, filter, finalize, tap, scan, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, take, distinctUntilChanged } from 'rxjs/operators';
4
+ import { map, catchError, take, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, 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, Observable, 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';
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';
@@ -875,6 +875,369 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
875
875
  }]
876
876
  }], ctorParameters: () => [] });
877
877
 
878
+ class PathTrackerStateModel {
879
+ constructor(baselineQuery, consumedValuesByKey = {}, watchExpiresAt) {
880
+ this.baselineQuery = baselineQuery;
881
+ this.consumedValuesByKey = consumedValuesByKey;
882
+ this.watchExpiresAt = watchExpiresAt;
883
+ }
884
+ static adapt(item) {
885
+ return new PathTrackerStateModel(item?.baselineQuery, item?.consumedValuesByKey, item?.watchExpiresAt);
886
+ }
887
+ }
888
+
889
+ class QueryTrackerStateModel {
890
+ constructor(paths = {}) {
891
+ this.paths = paths;
892
+ }
893
+ static adapt(item) {
894
+ return new QueryTrackerStateModel(item?.paths
895
+ ? Object.keys(item.paths).reduce((acc, key) => {
896
+ acc[key] = PathTrackerStateModel.adapt(item.paths[key]);
897
+ return acc;
898
+ }, {})
899
+ : {});
900
+ }
901
+ }
902
+
903
+ const TRACKER_STORE_NAME = 'query_params_tracker';
904
+ const TRACKER_SESSION_INIT_KEY = 'query_params_tracker_initialized';
905
+ const DEFAULT_TRACKER_OPTIONS = SettingOptions.adapt({
906
+ storage: StorageType.GLOBAL,
907
+ encrypted: false,
908
+ });
909
+ class QueryParamsTrackerService {
910
+ constructor() {
911
+ this.state = { paths: {} };
912
+ this.stateRestored = false;
913
+ this.localStorageManager = inject(LocalStorageManagerService);
914
+ }
915
+ clearTracking(resetSessionInit = false) {
916
+ this.state = { paths: {} };
917
+ this.localStorageManager.deleteStore({ name: TRACKER_STORE_NAME });
918
+ if (resetSessionInit && this.hasSessionStorage()) {
919
+ sessionStorage.removeItem(TRACKER_SESSION_INIT_KEY);
920
+ }
921
+ }
922
+ checkRequestOptions(requestOptions = [], options = {}) {
923
+ this.ensureStateRestored();
924
+ const normalized = this.normalizeRequestOptions(requestOptions);
925
+ if (!normalized.pathKey)
926
+ return false;
927
+ this.cleanupExpiredEntries();
928
+ if (!normalized.hasQuery) {
929
+ this.ensurePathState(normalized.pathKey);
930
+ this.persistState();
931
+ return true;
932
+ }
933
+ if (options.mode === 'exact') {
934
+ return this.checkExact(normalized, options);
935
+ }
936
+ return this.checkVariation(normalized, options);
937
+ }
938
+ matchesPath(requestOptions = [], expectedPathOptions = []) {
939
+ this.ensureStateRestored();
940
+ const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
941
+ const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
942
+ return Boolean(requestPath) && requestPath === expectedPath;
943
+ }
944
+ checkExact(normalized, options) {
945
+ const pathState = this.ensurePathState(normalized.pathKey);
946
+ const filteredQuery = normalized.query;
947
+ if (Object.keys(filteredQuery).length === 0) {
948
+ return false;
949
+ }
950
+ if (!pathState.baselineQuery) {
951
+ pathState.baselineQuery = filteredQuery;
952
+ this.persistState();
953
+ return true;
954
+ }
955
+ return this.stringifyQuery(pathState.baselineQuery) === this.stringifyQuery(filteredQuery);
956
+ }
957
+ checkVariation(normalized, options) {
958
+ const pathState = this.ensurePathState(normalized.pathKey);
959
+ const filteredQuery = normalized.query;
960
+ const keys = Object.keys(filteredQuery);
961
+ if (keys.length === 0) {
962
+ return false;
963
+ }
964
+ this.resetPathStateIfExpired(pathState);
965
+ let accepted = false;
966
+ keys.forEach((key) => {
967
+ const currentValue = filteredQuery[key];
968
+ const consumed = pathState.consumedValuesByKey[key] || [];
969
+ if (!consumed.includes(currentValue)) {
970
+ pathState.consumedValuesByKey[key] = [...consumed, currentValue];
971
+ accepted = true;
972
+ }
973
+ });
974
+ if (accepted) {
975
+ if (!pathState.baselineQuery) {
976
+ pathState.baselineQuery = filteredQuery;
977
+ }
978
+ pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
979
+ this.persistState();
980
+ }
981
+ return accepted;
982
+ }
983
+ normalizeRequestOptions(requestOptions) {
984
+ const params = Array.isArray(requestOptions) ? requestOptions : [];
985
+ const pathSegments = [];
986
+ const queryObjects = [];
987
+ params.forEach((item) => {
988
+ if (this.isPlainObject(item)) {
989
+ queryObjects.push(item);
990
+ }
991
+ else if (typeof item !== 'undefined' && item !== null) {
992
+ const parsed = this.parsePathSegment(String(item));
993
+ if (parsed.pathSegment) {
994
+ pathSegments.push(parsed.pathSegment);
995
+ }
996
+ if (Object.keys(parsed.queryObject).length > 0) {
997
+ queryObjects.push(parsed.queryObject);
998
+ }
999
+ }
1000
+ });
1001
+ const query = queryObjects.reduce((acc, current) => {
1002
+ Object.keys(current).forEach((key) => {
1003
+ const normalizedKey = this.normalizeParamKey(key);
1004
+ const value = current[key];
1005
+ if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
1006
+ return;
1007
+ }
1008
+ acc[normalizedKey] = this.normalizeParamValue(value);
1009
+ });
1010
+ return acc;
1011
+ }, {});
1012
+ return {
1013
+ pathKey: this.normalizePath(pathSegments),
1014
+ query,
1015
+ hasQuery: Object.keys(query).length > 0,
1016
+ };
1017
+ }
1018
+ parsePathSegment(rawSegment) {
1019
+ const [pathPart, ...queryParts] = String(rawSegment).split('?');
1020
+ const queryString = queryParts.join('?');
1021
+ const queryObject = {};
1022
+ if (queryString) {
1023
+ queryString.split('&').forEach((pair) => {
1024
+ if (!pair)
1025
+ return;
1026
+ const [rawKey, ...rawValueParts] = pair.split('=');
1027
+ const key = this.safeDecode(rawKey).trim();
1028
+ const value = this.safeDecode(rawValueParts.join('=')).trim();
1029
+ if (!key)
1030
+ return;
1031
+ queryObject[key] = value;
1032
+ });
1033
+ }
1034
+ return {
1035
+ pathSegment: pathPart,
1036
+ queryObject,
1037
+ };
1038
+ }
1039
+ safeDecode(value) {
1040
+ if (typeof value === 'undefined') {
1041
+ return '';
1042
+ }
1043
+ try {
1044
+ return decodeURIComponent(value);
1045
+ }
1046
+ catch {
1047
+ return String(value);
1048
+ }
1049
+ }
1050
+ normalizePath(pathSegments) {
1051
+ return pathSegments
1052
+ .map((segment) => String(segment).trim())
1053
+ .filter((segment) => segment.length > 0)
1054
+ .join('/')
1055
+ .replace(/([^:]\/+)\/+/g, '$1')
1056
+ .replace(/^\//, '')
1057
+ .replace(/\/$/, '');
1058
+ }
1059
+ normalizeParamKey(key) {
1060
+ return String(key).trim().toLowerCase();
1061
+ }
1062
+ normalizeParamValue(value) {
1063
+ if (Array.isArray(value)) {
1064
+ return value.map((item) => this.normalizeParamValue(item)).join(',');
1065
+ }
1066
+ if (this.isPlainObject(value)) {
1067
+ return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
1068
+ acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
1069
+ return acc;
1070
+ }, {}));
1071
+ }
1072
+ return String(value).trim().toLowerCase();
1073
+ }
1074
+ buildExpiryEpoch(expireIn) {
1075
+ if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
1076
+ return undefined;
1077
+ }
1078
+ if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
1079
+ return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
1080
+ }
1081
+ const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
1082
+ const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
1083
+ if (!parsed) {
1084
+ return undefined;
1085
+ }
1086
+ const value = Number(parsed[1]);
1087
+ const unit = parsed[2];
1088
+ const toSeconds = {
1089
+ y: 31556926,
1090
+ w: 604800,
1091
+ d: 86400,
1092
+ hr: 3600,
1093
+ h: 3600,
1094
+ mn: 60,
1095
+ min: 60,
1096
+ m: 60,
1097
+ s: 1,
1098
+ };
1099
+ const seconds = toSeconds[unit];
1100
+ if (!seconds) {
1101
+ return undefined;
1102
+ }
1103
+ return Math.floor(Date.now() / 1000) + value * seconds;
1104
+ }
1105
+ resetPathStateIfExpired(pathState) {
1106
+ const now = Math.floor(Date.now() / 1000);
1107
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1108
+ pathState.consumedValuesByKey = {};
1109
+ pathState.watchExpiresAt = undefined;
1110
+ }
1111
+ }
1112
+ cleanupExpiredEntries() {
1113
+ const now = Math.floor(Date.now() / 1000);
1114
+ Object.keys(this.state.paths).forEach((pathKey) => {
1115
+ const pathState = this.state.paths[pathKey];
1116
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1117
+ pathState.consumedValuesByKey = {};
1118
+ pathState.watchExpiresAt = undefined;
1119
+ }
1120
+ const hasBaseline = Boolean(pathState.baselineQuery && Object.keys(pathState.baselineQuery).length > 0);
1121
+ const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
1122
+ if (!hasBaseline && !hasTrackedValues) {
1123
+ delete this.state.paths[pathKey];
1124
+ }
1125
+ });
1126
+ }
1127
+ ensurePathState(pathKey) {
1128
+ if (!this.state.paths[pathKey]) {
1129
+ this.state.paths[pathKey] = {
1130
+ consumedValuesByKey: {},
1131
+ };
1132
+ }
1133
+ return this.state.paths[pathKey];
1134
+ }
1135
+ ensureStateRestored() {
1136
+ if (this.stateRestored) {
1137
+ return;
1138
+ }
1139
+ this.stateRestored = true;
1140
+ this.initializeTrackingForSession();
1141
+ this.restoreState();
1142
+ }
1143
+ initializeTrackingForSession() {
1144
+ if (!this.hasSessionStorage()) {
1145
+ return;
1146
+ }
1147
+ const initialized = sessionStorage.getItem(TRACKER_SESSION_INIT_KEY);
1148
+ if (initialized === '1') {
1149
+ return;
1150
+ }
1151
+ this.clearTracking();
1152
+ sessionStorage.setItem(TRACKER_SESSION_INIT_KEY, '1');
1153
+ }
1154
+ restoreState() {
1155
+ this.localStorageManager.store$(TRACKER_STORE_NAME)
1156
+ .pipe(take(1))
1157
+ .subscribe({
1158
+ next: (storedState) => {
1159
+ if (this.isTrackerState(storedState)) {
1160
+ this.state = QueryTrackerStateModel.adapt(storedState);
1161
+ this.cleanupExpiredEntries();
1162
+ return;
1163
+ }
1164
+ this.state = { paths: {} };
1165
+ },
1166
+ error: () => {
1167
+ this.state = { paths: {} };
1168
+ }
1169
+ });
1170
+ }
1171
+ persistState() {
1172
+ this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
1173
+ .pipe(take(1))
1174
+ .subscribe((exists) => {
1175
+ if (exists) {
1176
+ this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
1177
+ return;
1178
+ }
1179
+ this.localStorageManager.createStore({
1180
+ name: TRACKER_STORE_NAME,
1181
+ data: this.state,
1182
+ options: DEFAULT_TRACKER_OPTIONS,
1183
+ });
1184
+ });
1185
+ }
1186
+ stringifyQuery(query) {
1187
+ return JSON.stringify(Object.keys(query)
1188
+ .sort()
1189
+ .reduce((acc, key) => {
1190
+ acc[key] = query[key];
1191
+ return acc;
1192
+ }, {}));
1193
+ }
1194
+ isTrackerState(value) {
1195
+ return this.isPlainObject(value) && this.isPlainObject(value.paths);
1196
+ }
1197
+ isPlainObject(value) {
1198
+ return Object.prototype.toString.call(value) === '[object Object]';
1199
+ }
1200
+ hasSessionStorage() {
1201
+ try {
1202
+ return typeof sessionStorage !== 'undefined';
1203
+ }
1204
+ catch {
1205
+ return false;
1206
+ }
1207
+ }
1208
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1209
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
1210
+ }
1211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
1212
+ type: Injectable,
1213
+ args: [{
1214
+ providedIn: 'root'
1215
+ }]
1216
+ }] });
1217
+
1218
+ class NormalizedRequestOptionsModel {
1219
+ constructor(pathKey = '', query = {}, hasQuery = false) {
1220
+ this.pathKey = pathKey;
1221
+ this.query = query;
1222
+ this.hasQuery = hasQuery;
1223
+ }
1224
+ static adapt(item) {
1225
+ return new NormalizedRequestOptionsModel(item?.pathKey, item?.query, item?.hasQuery);
1226
+ }
1227
+ }
1228
+
1229
+ class QueryParamsTrackerOptionsModel {
1230
+ constructor(mode, watchParams, watchExpiresAt, watchParamsExpire) {
1231
+ this.mode = mode;
1232
+ this.watchParams = watchParams;
1233
+ this.watchExpiresAt = watchExpiresAt;
1234
+ this.watchParamsExpire = watchParamsExpire;
1235
+ }
1236
+ static adapt(item) {
1237
+ return new QueryParamsTrackerOptionsModel(item?.mode, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt, item?.watchParamsExpire);
1238
+ }
1239
+ }
1240
+
878
1241
  /**
879
1242
  * Stateful processor for managing streaming data and events
880
1243
  */
@@ -884,7 +1247,6 @@ class StreamingProcessor {
884
1247
  this.contentType = '';
885
1248
  this.maxBufferSize = 10 * 1024 * 1024; // 10MB limit
886
1249
  this.lastParsedLength = 0; // Track parsed position to avoid duplicates
887
- this.lastParsedLineEnd = 0; // Track end of last-parsed complete line for NDJSON incremental parsing
888
1250
  this.streamConfig = config || { streamType: StreamType.AI_STREAMING };
889
1251
  }
890
1252
  /**
@@ -907,63 +1269,41 @@ class StreamingProcessor {
907
1269
  return null;
908
1270
  case HttpEventType.DownloadProgress:
909
1271
  if (event.partialText) {
910
- // partialText is cumulative, so replace the buffer instead of appending
911
- this.buffer = event.partialText;
1272
+ this.appendToBuffer(event.partialText);
912
1273
  const parsedData = this.parseBuffer();
913
- // For NDJSON incremental parsing, parsedData already contains only new items.
914
- // For other formats, parsedData is cumulative so slice from lastParsedLength.
915
- const isIncremental = this.streamConfig.streamType === StreamType.NDJSON;
916
- const newItems = isIncremental ? parsedData : parsedData.slice(this.lastParsedLength);
917
- // received = cumulative total for progress display
918
- const received = isIncremental
919
- ? this.lastParsedLength + parsedData.length
920
- : parsedData.length;
921
- this.lastParsedLength = received;
922
- // Return null when no new items — prevents empty array emissions
923
- if (newItems.length === 0) {
924
- return null;
925
- }
926
- const total = this.totalFromHeader;
927
- const percent = total
928
- ? Math.min(100, Math.round((received / total) * 100))
929
- : 0;
1274
+ // Only return NEW items since last parse (progressive updates)
1275
+ const newItems = parsedData.slice(this.lastParsedLength);
1276
+ this.lastParsedLength = parsedData.length;
1277
+ // Calculate progress
930
1278
  const progress = {
931
- received,
932
- total,
933
- percent,
1279
+ received: this.lastParsedLength,
1280
+ total: this.totalFromHeader,
1281
+ percent: this.totalFromHeader
1282
+ ? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
1283
+ : 0,
934
1284
  stage: 'streaming'
935
1285
  };
936
1286
  return {
937
- data: newItems,
1287
+ data: newItems.length > 0 ? newItems : [],
938
1288
  progress
939
1289
  };
940
1290
  }
941
1291
  return null;
942
1292
  case HttpEventType.Response:
943
1293
  if (event.body) {
944
- // event.body is the complete response, replace the buffer
945
- this.buffer = event.body;
1294
+ this.appendToBuffer(event.body);
946
1295
  const parsedData = this.parseBuffer();
947
- // For NDJSON incremental parsing, parsedData already contains only new items.
948
- // For other formats, parsedData is cumulative so slice from lastParsedLength.
949
- const isIncremental = this.streamConfig.streamType === StreamType.NDJSON;
950
- const remaining = isIncremental ? parsedData : parsedData.slice(this.lastParsedLength);
951
- const received = isIncremental
952
- ? this.lastParsedLength + parsedData.length
953
- : parsedData.length;
954
- this.lastParsedLength = received;
955
- const total = this.totalFromHeader;
956
- const percent = total
957
- ? Math.min(100, Math.round((received / total) * 100))
958
- : 100;
1296
+ // Calculate final progress
959
1297
  const progress = {
960
- received,
961
- total,
962
- percent,
1298
+ received: parsedData.length,
1299
+ total: this.totalFromHeader,
1300
+ percent: this.totalFromHeader
1301
+ ? Math.round((parsedData.length / this.totalFromHeader) * 100)
1302
+ : 100,
963
1303
  stage: 'complete'
964
1304
  };
965
1305
  return {
966
- data: remaining.length > 0 ? remaining : [],
1306
+ data: parsedData.length > 0 ? parsedData : [],
967
1307
  progress
968
1308
  };
969
1309
  }
@@ -980,40 +1320,23 @@ class StreamingProcessor {
980
1320
  // Implement sliding window if buffer gets too large
981
1321
  if (this.buffer.length > this.maxBufferSize) {
982
1322
  this.buffer = this.buffer.slice(-this.maxBufferSize / 2);
983
- this.lastParsedLineEnd = 0;
984
- this.lastParsedLength = 0;
985
1323
  }
986
1324
  }
987
1325
  /**
988
- * Parse current buffer content.
989
- *
990
- * Per-record parsing failures are already handled by the inner parsers
991
- * (`safeJsonParse`, `parseNdjson`, `parseEventStream`, `extractJsonObjects`),
992
- * which skip malformed records and return what was successfully parsed.
993
- * No outer try/catch — wrapping these calls in `catch { return []; }`
994
- * would discard every successfully-parsed record in the buffer whenever a
995
- * single unexpected error bubbles up, instead of omitting the bad record.
1326
+ * Parse current buffer content
996
1327
  */
997
1328
  parseBuffer() {
998
1329
  if (!this.buffer.trim()) {
999
1330
  return [];
1000
1331
  }
1001
- if (this.streamConfig.streamType === StreamType.NDJSON) {
1002
- // For NDJSON, only parse complete lines since the last parse.
1003
- // Find the last newline in the buffer — everything after it is an incomplete line.
1004
- const lastNewlineIndex = this.buffer.lastIndexOf('\n');
1005
- if (lastNewlineIndex === -1) {
1006
- // No complete lines yet
1007
- return [];
1008
- }
1009
- // Parse only the content from lastParsedLineEnd up to (and including) the last newline
1010
- const bufferToParse = this.buffer.substring(this.lastParsedLineEnd, lastNewlineIndex + 1);
1011
- this.lastParsedLineEnd = lastNewlineIndex + 1;
1012
- const result = parseStreamData(bufferToParse, this.contentType, this.streamConfig);
1332
+ try {
1333
+ const result = parseStreamData(this.buffer, this.contentType, this.streamConfig);
1013
1334
  return result;
1014
1335
  }
1015
- const result = parseStreamData(this.buffer, this.contentType, this.streamConfig);
1016
- return result;
1336
+ catch (error) {
1337
+ console.warn('Failed to parse streaming data:', error);
1338
+ return [];
1339
+ }
1017
1340
  }
1018
1341
  /**
1019
1342
  * Reset processor state
@@ -1022,7 +1345,6 @@ class StreamingProcessor {
1022
1345
  this.buffer = '';
1023
1346
  this.contentType = '';
1024
1347
  this.lastParsedLength = 0;
1025
- this.lastParsedLineEnd = 0;
1026
1348
  this.totalFromHeader = undefined;
1027
1349
  }
1028
1350
  /**
@@ -1112,8 +1434,6 @@ function extractJsonObjects(text) {
1112
1434
  */
1113
1435
  function requestStreaming(config) {
1114
1436
  const processor = new StreamingProcessor(config);
1115
- // Reset processor state to prevent stale data from previous aborted streams
1116
- processor.reset();
1117
1437
  return input$ => input$.pipe(
1118
1438
  // Filter only streaming-related events
1119
1439
  filter((event) => event.type === HttpEventType.DownloadProgress ||
@@ -2884,9 +3204,6 @@ class RequestService extends WebsocketService {
2884
3204
  this.headersService = inject(HeadersService);
2885
3205
  this.isPending = new BehaviorSubject(false);
2886
3206
  this.isPending$ = this.isPending.asObservable();
2887
- // Separate pending state for streaming operations only
2888
- this.isStreamingPending = new BehaviorSubject(false);
2889
- this.isStreamingPending$ = this.isStreamingPending.asObservable();
2890
3207
  this.progress = new BehaviorSubject(0);
2891
3208
  this.progress$ = this.progress.asObservable();
2892
3209
  this.streamProgress = new BehaviorSubject({
@@ -2896,82 +3213,32 @@ class RequestService extends WebsocketService {
2896
3213
  stage: 'connecting'
2897
3214
  });
2898
3215
  this.streamProgress$ = this.streamProgress.asObservable();
2899
- this.streamProgressReset = {
2900
- received: 0,
2901
- total: undefined,
2902
- percent: 0,
2903
- stage: 'connecting'
2904
- };
2905
3216
  }
2906
3217
  // Implementation
2907
3218
  getRecordRequest(options) {
2908
3219
  const urlPath = this.buildUrlPath(options);
2909
3220
  const headers = this.buildCombinedHeaders(options);
2910
3221
  this.isPending.next(true);
2911
- if (options.stream) {
2912
- this.isStreamingPending.next(true);
2913
- this.streamProgress.next({
2914
- received: 0,
2915
- total: undefined,
2916
- percent: 0,
2917
- stage: 'connecting'
2918
- });
2919
- }
2920
3222
  return (options.stream)
2921
3223
  ? this.http.get(urlPath, {
2922
3224
  ...headers,
2923
3225
  observe: 'events',
2924
3226
  responseType: 'text',
2925
3227
  reportProgress: true
2926
- }).pipe(
2927
- // tap(data => console.log('STREAM DATA', data)),
2928
- 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
- }))
3228
+ }).pipe(tap(data => console.log('STREAM DATA', data)), requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreaming(options), this.handleFinalize())
2941
3229
  : this.http.get(urlPath, headers).pipe(this.request(options));
2942
3230
  }
2943
3231
  createRecordRequest(options, data) {
2944
3232
  const urlPath = this.buildUrlPath(options);
2945
3233
  const headers = this.buildCombinedHeaders(options);
2946
3234
  this.isPending.next(true);
2947
- if (options.stream) {
2948
- this.isStreamingPending.next(true);
2949
- this.streamProgress.next({
2950
- received: 0,
2951
- total: undefined,
2952
- percent: 0,
2953
- stage: 'connecting'
2954
- });
2955
- }
2956
3235
  return (options.stream)
2957
3236
  ? this.http.post(urlPath, data, {
2958
3237
  ...headers,
2959
3238
  observe: 'events',
2960
3239
  responseType: 'text',
2961
3240
  reportProgress: true
2962
- }).pipe(requestStreaming({
2963
- streamType: options.streamType || StreamType.AI_STREAMING,
2964
- totalHeader: options.totalHeader
2965
- }), this.requestStreaming(options), finalize(() => {
2966
- this.isPending.next(false);
2967
- this.isStreamingPending.next(false);
2968
- this.streamProgress.next({
2969
- received: 0,
2970
- total: undefined,
2971
- percent: 0,
2972
- stage: 'connecting'
2973
- });
2974
- }))
3241
+ }).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreaming(options), this.handleFinalize())
2975
3242
  : this.http.post(urlPath, data, headers).pipe(this.request(options));
2976
3243
  }
2977
3244
  updateRecordRequest(options, data) {
@@ -3015,19 +3282,19 @@ class RequestService extends WebsocketService {
3015
3282
  requestStreaming(options) {
3016
3283
  return (source$) => {
3017
3284
  return source$.pipe(tap(output => {
3018
- this.progress.next(output.progress.percent);
3285
+ // Update progress from stream output
3286
+ this.progress.next(output.progress.received);
3019
3287
  this.streamProgress.next(output.progress);
3020
- }), scan((acc, output) => {
3021
- // Accumulate data from each emission
3022
- const newData = output.data;
3023
- if (options?.adapter) {
3024
- const adaptedData = newData.map((item) => options.adapter(item));
3025
- return [...acc, ...adaptedData];
3288
+ }), map(output => {
3289
+ const data = output.data;
3290
+ if (!data || (Array.isArray(data) && data.length === 0)) {
3291
+ return data;
3026
3292
  }
3027
- else {
3028
- return [...acc, ...newData];
3293
+ if (options?.adapter) {
3294
+ return data.map((item) => options.adapter(item));
3029
3295
  }
3030
- }, []));
3296
+ return data;
3297
+ }));
3031
3298
  };
3032
3299
  }
3033
3300
  downloadFileRequest(options) {
@@ -3165,12 +3432,7 @@ class RequestService extends WebsocketService {
3165
3432
  return formData;
3166
3433
  }
3167
3434
  handleFinalize() {
3168
- return finalize(() => {
3169
- this.isPending.next(false);
3170
- // Note: do NOT reset streamProgress here — it fires on cancel/cancel too,
3171
- // not just natural completion. Reset only happens in requestStreaming
3172
- // operator on Response complete event, or when a new stream starts.
3173
- });
3435
+ return finalize(() => this.isPending.next(false));
3174
3436
  }
3175
3437
  downloadFile(file, fileData) {
3176
3438
  const navigatorAny = window.navigator;
@@ -3863,16 +4125,12 @@ class HTTPManagerService extends RequestService {
3863
4125
  getRequest(options, params) {
3864
4126
  this.isPending.next(true);
3865
4127
  this.data.next(null);
3866
- // Only reset streamProgress when actually starting a stream,
3867
- // not for regular requests that don't use it
3868
- if (options?.stream) {
3869
- this.streamProgress.next({
3870
- received: 0,
3871
- total: undefined,
3872
- percent: 0,
3873
- stage: 'connecting'
3874
- });
3875
- }
4128
+ this.streamProgress.next({
4129
+ received: 0,
4130
+ total: undefined,
4131
+ percent: 0,
4132
+ stage: 'connecting'
4133
+ });
3876
4134
  const updatedOptions = this.defineReqOptions(options, params);
3877
4135
  const func = this.getRecordRequest;
3878
4136
  const requests = this.createRequest(func, updatedOptions);
@@ -4204,7 +4462,7 @@ class HTTPManagerService extends RequestService {
4204
4462
  }
4205
4463
  handleSequentialError(request, error, index, options) {
4206
4464
  if (options.logErrors !== false) {
4207
- // console.error(`Batch request ${index} failed:`, error);
4465
+ console.error(`Batch request ${index} failed:`, error);
4208
4466
  }
4209
4467
  if (options.stopOnError) {
4210
4468
  return throwError(() => error);
@@ -4216,7 +4474,7 @@ class HTTPManagerService extends RequestService {
4216
4474
  }
4217
4475
  handleParallelError(request, error, index, options) {
4218
4476
  if (options.logErrors !== false) {
4219
- // console.error(`Batch request ${index} failed:`, error);
4477
+ console.error(`Batch request ${index} failed:`, error);
4220
4478
  }
4221
4479
  if (options.ignoreErrors) {
4222
4480
  return of(undefined);
@@ -4244,186 +4502,49 @@ class RequestSignalsService extends WebsocketService {
4244
4502
  this.http = inject(HttpClient);
4245
4503
  this.pathQueryService = inject(PathQueryService);
4246
4504
  this.headersService = inject(HeadersService);
4247
- // Use counter instead of boolean to handle multiple concurrent requests
4248
- this._activeRequestCount = signal(0);
4249
- this.isPending = computed(() => this._activeRequestCount() > 0);
4505
+ this.isPending = signal(false);
4250
4506
  this.progress = signal(0);
4251
- // Track active request by method name for switchMap behavior
4252
- this.activeRequests = new Map();
4253
- this.activeMethod = null;
4254
- this.activeStreamMethod = null;
4255
- // Queue for requests that need to wait
4256
- this.requestQueue = [];
4257
- // Cleanup subject for cancelling queued requests on destroy
4258
- this.destroy$ = new Subject();
4507
+ this.isIdle = computed(() => !this.isPending());
4259
4508
  }
4260
4509
  // Implementation
4261
4510
  getRecordRequest(options) {
4262
4511
  const urlPath = this.buildUrlPath(options);
4263
4512
  const headers = this.buildCombinedHeaders(options);
4264
- const isStream = options.stream === true;
4265
- const request$ = (isStream)
4513
+ this.isPending.set(true);
4514
+ return (options.stream)
4266
4515
  ? this.http.get(urlPath, {
4267
4516
  ...headers,
4268
4517
  observe: 'events',
4269
4518
  responseType: 'text',
4270
4519
  reportProgress: true
4271
- }).pipe(requestStreaming({
4272
- streamType: options.streamType || StreamType.AI_STREAMING,
4273
- totalHeader: options.totalHeader
4274
- }), this.requestStreamingOperator(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))))
4275
- : this.http.get(urlPath, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4276
- return this.queueRequest(request$, 'GET', isStream);
4520
+ }).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreamingOperator(options), this.handleFinalize())
4521
+ : this.http.get(urlPath, headers).pipe(this.request(options), this.handleFinalize());
4277
4522
  }
4278
4523
  // Implementation
4279
4524
  createRecordRequest(options, data) {
4280
4525
  const urlPath = this.buildUrlPath(options);
4281
4526
  const headers = this.buildCombinedHeaders(options);
4282
- const isStream = options.stream === true;
4283
- const request$ = (isStream)
4527
+ this.isPending.set(true);
4528
+ return (options.stream)
4284
4529
  ? this.http.post(urlPath, data, {
4285
4530
  ...headers,
4286
4531
  observe: 'events',
4287
4532
  responseType: 'text',
4288
4533
  reportProgress: true
4289
- }).pipe(requestStreaming({
4290
- streamType: options.streamType || StreamType.AI_STREAMING,
4291
- totalHeader: options.totalHeader
4292
- }), this.requestStreamingOperator(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))))
4293
- : this.http.post(urlPath, data, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4294
- return this.queueRequest(request$, 'POST', isStream);
4534
+ }).pipe(requestStreaming({ streamType: options.streamType || StreamType.AI_STREAMING }), this.requestStreamingOperator(options), this.handleFinalize())
4535
+ : this.http.post(urlPath, data, headers).pipe(this.request(options), this.handleFinalize());
4295
4536
  }
4296
4537
  updateRecordRequest(options, data) {
4297
4538
  const urlPath = this.buildUrlPath(options);
4298
4539
  const headers = this.buildHeaders(options);
4299
- const request$ = this.http.put(urlPath, data, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4300
- return this.queueRequest(request$, 'PUT');
4540
+ this.isPending.set(true);
4541
+ return this.http.put(urlPath, data, headers).pipe(this.request(options), this.handleFinalize());
4301
4542
  }
4302
4543
  deleteRecordRequest(options) {
4303
4544
  const urlPath = this.buildUrlPath(options);
4304
4545
  const headers = this.buildHeaders(options);
4305
- const request$ = this.http.delete(urlPath, headers).pipe(this.request(options), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4306
- return this.queueRequest(request$, 'DELETE');
4307
- }
4308
- /**
4309
- * Queue a request to ensure FIFO processing:
4310
- * - All requests are queued and processed sequentially
4311
- * - Streams are protected - non-stream requests wait for active streams
4312
- */
4313
- queueRequest(request$, method, isStream = false) {
4314
- // console.log(`[Queue] ${method} queued (isStream=${isStream})`, {
4315
- // activeMethod: this.activeMethod,
4316
- // activeStreamMethod: this.activeStreamMethod,
4317
- // queueLength: this.requestQueue.length
4318
- // });
4319
- return new Observable((observer) => {
4320
- // console.log(`[Queue] ${method} observer created, checking conditions...`, {
4321
- // activeMethod: this.activeMethod,
4322
- // activeStreamMethod: this.activeStreamMethod,
4323
- // isStream
4324
- // });
4325
- const queuedRequest = {
4326
- observable: request$,
4327
- resolve: (value) => observer.next(value),
4328
- reject: (error) => observer.error(error),
4329
- method,
4330
- isStream
4331
- };
4332
- // If a stream is currently active, non-stream requests must wait
4333
- if (this.activeStreamMethod && !isStream) {
4334
- // console.log(`[Queue] ${method} queued (1), waiting for stream ${this.activeStreamMethod}`);
4335
- this.requestQueue.push(queuedRequest);
4336
- return () => {
4337
- const idx = this.requestQueue.findIndex(q => q === queuedRequest);
4338
- if (idx !== -1)
4339
- this.requestQueue.splice(idx, 1);
4340
- };
4341
- }
4342
- // If a request is already running, queue this one (FIFO)
4343
- if (this.activeMethod) {
4344
- // console.log(`[Queue] ${method} queued (2), waiting for ${this.activeMethod}`);
4345
- this.requestQueue.push(queuedRequest);
4346
- return () => {
4347
- const idx = this.requestQueue.findIndex(q => q === queuedRequest);
4348
- if (idx !== -1)
4349
- this.requestQueue.splice(idx, 1);
4350
- };
4351
- }
4352
- // No active request - start immediately
4353
- // console.log(`[Queue] ${method} starting immediately`);
4354
- this.startRequest(queuedRequest, observer);
4355
- return () => {
4356
- const idx = this.requestQueue.findIndex(q => q === queuedRequest);
4357
- if (idx !== -1)
4358
- this.requestQueue.splice(idx, 1);
4359
- };
4360
- });
4361
- }
4362
- /**
4363
- * Start processing a request
4364
- */
4365
- startRequest(queuedRequest, observer) {
4366
- this.activeMethod = queuedRequest.method;
4367
- if (queuedRequest.isStream) {
4368
- this.activeStreamMethod = queuedRequest.method;
4369
- }
4370
- this._activeRequestCount.update(c => c + 1);
4371
- // console.log(`[Queue] Starting ${queuedRequest.method}${queuedRequest.isStream ? ' (STREAM)' : ''}`, {
4372
- // activeMethod: this.activeMethod,
4373
- // activeStreamMethod: this.activeStreamMethod,
4374
- // queueLength: this.requestQueue.length
4375
- // });
4376
- const sub = queuedRequest.observable.subscribe({
4377
- next: (value) => {
4378
- // console.log(`[Queue] ${queuedRequest.method} received value, isStream=${queuedRequest.isStream}`);
4379
- observer.next(value);
4380
- },
4381
- error: (error) => {
4382
- // console.log(`[Queue] ${queuedRequest.method} error:`, error);
4383
- observer.error(error);
4384
- this.cleanupAndProcessNext(queuedRequest.method, !!queuedRequest.isStream);
4385
- },
4386
- complete: () => {
4387
- // console.log(`[Queue] ${queuedRequest.method} complete, isStream=${queuedRequest.isStream}`);
4388
- observer.complete();
4389
- this.cleanupAndProcessNext(queuedRequest.method, !!queuedRequest.isStream);
4390
- }
4391
- });
4392
- this.activeRequests.set(queuedRequest.method, sub);
4393
- }
4394
- /**
4395
- * Clean up completed request and process next in queue
4396
- */
4397
- cleanupAndProcessNext(method, wasStream) {
4398
- // console.log(`[Queue] cleanupAndProcessNext called: method=${method}, wasStream=${wasStream}`, {
4399
- // activeMethod: this.activeMethod,
4400
- // activeStreamMethod: this.activeStreamMethod,
4401
- // queueLength: this.requestQueue.length
4402
- // });
4403
- this.activeRequests.delete(method);
4404
- this.activeMethod = null;
4405
- if (wasStream) {
4406
- this.activeStreamMethod = null;
4407
- }
4408
- this._activeRequestCount.update(c => Math.max(0, c - 1));
4409
- // console.log(`[Queue] After cleanup:`, {
4410
- // activeMethod: this.activeMethod,
4411
- // activeStreamMethod: this.activeStreamMethod,
4412
- // queueLength: this.requestQueue.length
4413
- // });
4414
- // Process next request in queue
4415
- if (this.requestQueue.length > 0) {
4416
- const next = this.requestQueue.shift();
4417
- // console.log(`[Queue] Processing next from queue: ${next.method}`, {
4418
- // isStream: next.isStream,
4419
- // queueLength: this.requestQueue.length
4420
- // });
4421
- this.startRequest(next, {
4422
- next: (value) => next.resolve(value),
4423
- error: (error) => next.reject(error),
4424
- complete: () => { }
4425
- });
4426
- }
4546
+ this.isPending.set(true);
4547
+ return this.http.delete(urlPath, headers).pipe(this.request(options), this.handleFinalize());
4427
4548
  }
4428
4549
  buildUrlPath(options) {
4429
4550
  return this.pathQueryService.buildAPIPath(options.server, options.path);
@@ -4452,9 +4573,7 @@ class RequestSignalsService extends WebsocketService {
4452
4573
  }
4453
4574
  requestStreamingOperator(options) {
4454
4575
  return (source$) => {
4455
- return source$.pipe(tap(output => {
4456
- this.progress.set(output.progress.percent);
4457
- }), map(output => {
4576
+ return source$.pipe(map(output => {
4458
4577
  const data = output.data;
4459
4578
  if (!data || (Array.isArray(data) && data.length === 0)) {
4460
4579
  return data;
@@ -4467,12 +4586,14 @@ class RequestSignalsService extends WebsocketService {
4467
4586
  };
4468
4587
  }
4469
4588
  downloadFileRequest(options) {
4470
- this._activeRequestCount.update(c => c + 1);
4589
+ this.isPending.set(true);
4471
4590
  const urlPath = this.buildUrlPath(options);
4472
- const request$ = this.http.get(urlPath, { responseType: 'blob', observe: 'events', reportProgress: true })
4591
+ return this.http.get(urlPath, { responseType: 'blob', observe: 'events', reportProgress: true })
4473
4592
  .pipe(map((event) => {
4593
+ this.isPending.set(true);
4474
4594
  if (event instanceof HttpHeaderResponse) {
4475
4595
  if (event.status !== 200) {
4596
+ this.isPending.set(false);
4476
4597
  throw new Error('Download failed');
4477
4598
  }
4478
4599
  }
@@ -4488,54 +4609,59 @@ class RequestSignalsService extends WebsocketService {
4488
4609
  const file = (header_content) ? header_content.split('=')[1].substring(0, header_content.split('=')[1].length) : '';
4489
4610
  const fileName = (fileNamePath !== '') ? fileNamePath : file;
4490
4611
  if (fileName === '') {
4612
+ this.isPending.set(false);
4491
4613
  throw new Error('Save File: (file name and extension) not found in Headers or Path');
4492
4614
  }
4493
4615
  this.downloadFile(fileName, event.body);
4616
+ this.isPending.set(false);
4494
4617
  return 100;
4495
4618
  }
4496
4619
  catch (error) {
4497
4620
  throw new Error('Download failed');
4498
4621
  }
4499
4622
  default:
4623
+ this.isPending.set(false);
4500
4624
  return 0;
4501
4625
  }
4502
4626
  }), catchError(err => {
4503
4627
  return throwError(() => err);
4504
- }), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4505
- return this.queueRequest(request$, 'DOWNLOAD');
4628
+ }));
4506
4629
  }
4507
4630
  uploadFileRequest(options) {
4631
+ this.isPending.set(true);
4508
4632
  const files = options.uploadFiles ? (Array.isArray(options.uploadFiles) ? options.uploadFiles : [options.uploadFiles]) : [];
4509
4633
  const validation = this.validateUploadFiles(files, options);
4510
4634
  if (validation.validFiles.length === 0) {
4635
+ this.isPending.set(false);
4511
4636
  return throwError(() => new UploadValidationErrorModel(validation.invalidFiles, 0, files.length));
4512
4637
  }
4513
- this._activeRequestCount.update(c => c + 1);
4514
4638
  const fieldName = options.uploadFieldName || (files.length === 1 ? 'file' : 'files');
4515
4639
  const formData = this.buildFormData(validation.validFiles, fieldName);
4516
4640
  const urlPath = this.buildUrlPath(options);
4517
4641
  const method = options.uploadHttpMethod || 'POST';
4518
- const request$ = this.http.request(method, urlPath, {
4642
+ return this.http.request(method, urlPath, {
4519
4643
  body: formData,
4520
4644
  observe: 'events',
4521
4645
  reportProgress: true
4522
4646
  }).pipe(map((event) => {
4647
+ this.isPending.set(true);
4523
4648
  switch (event.type) {
4524
4649
  case HttpEventType.UploadProgress:
4525
4650
  const status = event.total ? Math.round(event.loaded / (event.total || 1) * 100) : 0;
4526
4651
  this.progress.set(status);
4527
4652
  return null;
4528
4653
  case HttpEventType.Response:
4654
+ this.isPending.set(false);
4529
4655
  this.progress.set(0);
4530
4656
  return event.body;
4531
4657
  default:
4532
4658
  return null;
4533
4659
  }
4534
4660
  }), filter((body) => body !== null), catchError(err => {
4661
+ this.isPending.set(false);
4535
4662
  this.progress.set(0);
4536
4663
  return throwError(() => err);
4537
- }), finalize(() => this._activeRequestCount.update(c => Math.max(0, c - 1))));
4538
- return this.queueRequest(request$, 'UPLOAD');
4664
+ }));
4539
4665
  }
4540
4666
  validateUploadFiles(files, options) {
4541
4667
  const validFiles = [];
@@ -4584,6 +4710,9 @@ class RequestSignalsService extends WebsocketService {
4584
4710
  });
4585
4711
  return formData;
4586
4712
  }
4713
+ handleFinalize() {
4714
+ return finalize(() => this.isPending.set(false));
4715
+ }
4587
4716
  downloadFile(file, fileData) {
4588
4717
  const navigatorAny = window.navigator;
4589
4718
  const extension = file.split('.')[1]?.toLowerCase();
@@ -4797,6 +4926,7 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4797
4926
  }
4798
4927
  // REQUESTS
4799
4928
  getRequest(options, params) {
4929
+ this.isPending.set(true);
4800
4930
  this.data.set(null);
4801
4931
  const updatedOptions = this.defineReqOptions(options, params);
4802
4932
  const func = this.getRecordRequest;
@@ -4805,13 +4935,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4805
4935
  this.data.set(data);
4806
4936
  if (updatedOptions.displaySuccess)
4807
4937
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
4808
- }), catchError((err) => {
4938
+ }), finalize(() => this.isPending.set(false)), catchError((err) => {
4809
4939
  if (updatedOptions.displayError)
4810
4940
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
4941
+ this.isPending.set(false);
4811
4942
  return this.handleError(err);
4812
4943
  }));
4813
4944
  }
4814
4945
  postRequest(data, options, params) {
4946
+ this.isPending.set(true);
4815
4947
  this.data.set(null);
4816
4948
  const updatedOptions = this.defineReqOptions(options, params);
4817
4949
  const func = this.createRecordRequest;
@@ -4820,13 +4952,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4820
4952
  this.data.set(data);
4821
4953
  if (updatedOptions.displaySuccess)
4822
4954
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
4823
- }), catchError((err) => {
4955
+ }), finalize(() => this.isPending.set(false)), catchError((err) => {
4824
4956
  if (updatedOptions.displayError)
4825
4957
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
4958
+ this.isPending.set(false);
4826
4959
  return this.handleError(err);
4827
4960
  }));
4828
4961
  }
4829
4962
  putRequest(data, options, params) {
4963
+ this.isPending.set(true);
4830
4964
  this.data.set(null);
4831
4965
  const updatedOptions = this.defineReqOptions(options, params);
4832
4966
  const func = this.updateRecordRequest;
@@ -4835,13 +4969,15 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4835
4969
  this.data.set(data);
4836
4970
  if (updatedOptions.displaySuccess)
4837
4971
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
4838
- }), catchError((err) => {
4972
+ }), finalize(() => this.isPending.set(false)), catchError((err) => {
4839
4973
  if (updatedOptions.displayError)
4840
4974
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
4975
+ this.isPending.set(false);
4841
4976
  return this.handleError(err);
4842
4977
  }));
4843
4978
  }
4844
4979
  deleteRequest(options, params) {
4980
+ this.isPending.set(true);
4845
4981
  this.data.set(null);
4846
4982
  const updatedOptions = this.defineReqOptions(options, params);
4847
4983
  const func = this.deleteRecordRequest;
@@ -4850,22 +4986,26 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4850
4986
  this.data.set(data);
4851
4987
  if (updatedOptions.displaySuccess)
4852
4988
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
4853
- }), catchError((err) => {
4989
+ }), finalize(() => this.isPending.set(false)), catchError((err) => {
4854
4990
  if (updatedOptions.displayError)
4855
4991
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage);
4992
+ this.isPending.set(false);
4856
4993
  return this.handleError(err);
4857
4994
  }));
4858
4995
  }
4859
4996
  downloadRequest(options, params, saveAs) {
4997
+ this.isPending.set(true);
4860
4998
  const updatedOptions = this.defineReqOptions(options, params);
4861
4999
  const func = this.downloadFileRequest;
4862
5000
  const requests = this.createRequest(func, updatedOptions);
4863
5001
  return this.createObservable(updatedOptions, requests, func.name).pipe(catchError((err) => {
4864
5002
  this.error.set(true);
5003
+ this.isPending.set(false);
4865
5004
  return this.handleError(err);
4866
5005
  }));
4867
5006
  }
4868
5007
  uploadRequest(files, options, params) {
5008
+ this.isPending.set(true);
4869
5009
  this.data.set(null);
4870
5010
  const updatedOptions = this.defineReqOptions(options, params);
4871
5011
  updatedOptions.uploadFiles = files;
@@ -4875,9 +5015,10 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4875
5015
  this.data.set(data);
4876
5016
  if (updatedOptions.displaySuccess)
4877
5017
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
4878
- }), catchError((err) => {
5018
+ }), finalize(() => this.isPending.set(false)), catchError((err) => {
4879
5019
  if (updatedOptions.displayError)
4880
5020
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
5021
+ this.isPending.set(false);
4881
5022
  return this.handleError(err);
4882
5023
  }));
4883
5024
  }
@@ -4971,6 +5112,7 @@ class HTTPManagerSignalsService extends RequestSignalsService {
4971
5112
  this.toastMessage.toastMessage(displaySuccess);
4972
5113
  }
4973
5114
  stopPolling() {
5115
+ this.isPending.set(false);
4974
5116
  this.polling$.next();
4975
5117
  }
4976
5118
  defineReqOptions(options, params) {
@@ -5137,7 +5279,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5137
5279
  }] }] });
5138
5280
 
5139
5281
  class ApiRequest {
5140
- constructor(server = '', path, headers, adapter, mapper, polling, retry, stream, streamType, totalHeader, displayError, displaySuccess, successMessage, errorMessage, saveAs, fileContentHeader, ws, env, uploadFiles, uploadFieldName, uploadHttpMethod, allowedTypes, maxFileSize, maxTotalSize) {
5282
+ constructor(server = '', path, headers, adapter, mapper, polling, retry, stream, streamType, displayError, displaySuccess, successMessage, errorMessage, saveAs, fileContentHeader, ws, env, uploadFiles, uploadFieldName, uploadHttpMethod, allowedTypes, maxFileSize, maxTotalSize) {
5141
5283
  this.server = server;
5142
5284
  this.path = path;
5143
5285
  this.headers = headers;
@@ -5147,7 +5289,6 @@ class ApiRequest {
5147
5289
  this.retry = retry;
5148
5290
  this.stream = stream;
5149
5291
  this.streamType = streamType;
5150
- this.totalHeader = totalHeader;
5151
5292
  this.displayError = displayError;
5152
5293
  this.displaySuccess = displaySuccess;
5153
5294
  this.successMessage = successMessage;
@@ -5165,17 +5306,20 @@ class ApiRequest {
5165
5306
  }
5166
5307
  static adapt(item) {
5167
5308
  const server = Array.isArray(item?.server) ? item.server.join('/') : item?.server || '';
5168
- 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, (item?.totalHeader) ? item.totalHeader : 'X-Total-Count', 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);
5309
+ 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);
5169
5310
  }
5170
5311
  }
5171
5312
 
5172
5313
  class RequestOptions {
5173
- constructor(path = [], headers = {}) {
5314
+ constructor(path = [], headers = {}, forceRefresh, watchParams, watchExpiresAt) {
5174
5315
  this.path = path;
5175
5316
  this.headers = headers;
5317
+ this.forceRefresh = forceRefresh;
5318
+ this.watchParams = watchParams;
5319
+ this.watchExpiresAt = watchExpiresAt;
5176
5320
  }
5177
5321
  static adapt(item) {
5178
- return new RequestOptions(item?.path, item?.headers);
5322
+ return new RequestOptions(item?.path, item?.headers, item?.forceRefresh, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt);
5179
5323
  }
5180
5324
  }
5181
5325
 
@@ -5970,8 +6114,8 @@ class DbService extends Dexie {
5970
6114
  console.warn('Database upgrade blocked! Please close other tabs/windows of this app.');
5971
6115
  });
5972
6116
  this.on('versionchange', () => {
5973
- console.warn('Database version changed in another tab. Reloading...');
5974
- window.location.reload();
6117
+ console.warn('Database version changed in another tab. Closing current database connection to allow upgrade.');
6118
+ this.close();
5975
6119
  });
5976
6120
  this.dbReady = this.init();
5977
6121
  }
@@ -6051,9 +6195,12 @@ class DbService extends Dexie {
6051
6195
  await this.dbReady;
6052
6196
  const safeTableName = this.cleanTableName(tableName);
6053
6197
  const safeSchema = schema.trim();
6054
- if (this.tableExists(safeTableName))
6055
- return;
6056
6198
  const currentSchema = this.getCurrentSchema();
6199
+ const existingSchema = currentSchema[safeTableName]?.trim();
6200
+ // No-op only when table already exists and schema is identical.
6201
+ if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
6202
+ return;
6203
+ }
6057
6204
  console.log('Current Schema before update:', currentSchema);
6058
6205
  currentSchema[safeTableName] = safeSchema;
6059
6206
  const nextVersion = this.verno + 1;
@@ -6067,6 +6214,7 @@ class DbService extends Dexie {
6067
6214
  const created = this.tables.some(t => t.name === safeTableName);
6068
6215
  if (!created) {
6069
6216
  console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
6217
+ throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
6070
6218
  }
6071
6219
  else {
6072
6220
  console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
@@ -6074,15 +6222,24 @@ class DbService extends Dexie {
6074
6222
  }
6075
6223
  catch (err) {
6076
6224
  console.error('Error opening database after schema update:', err);
6225
+ throw err;
6077
6226
  }
6078
6227
  }
6079
6228
  async DBOpened() {
6229
+ try {
6230
+ await this.dbReady;
6231
+ }
6232
+ catch (err) {
6233
+ console.error('DBOpened: init failed', err);
6234
+ return false;
6235
+ }
6080
6236
  if (!this.isOpen()) {
6081
6237
  try {
6082
6238
  await this.open();
6083
6239
  return true;
6084
6240
  }
6085
6241
  catch (err) {
6242
+ console.error('DBOpened: open failed', err);
6086
6243
  return false;
6087
6244
  }
6088
6245
  }
@@ -6177,7 +6334,11 @@ class DatabaseManagerService extends DbService {
6177
6334
  }));
6178
6335
  }
6179
6336
  createDatabaseTable(tableDef) {
6180
- return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.DBOpened()));
6337
+ const tableName = this.cleanTableName(tableDef.table);
6338
+ return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
6339
+ console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
6340
+ return of(false);
6341
+ }));
6181
6342
  }
6182
6343
  updateDatabaseTableSchema(tableDef) {
6183
6344
  return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
@@ -6224,26 +6385,23 @@ class DatabaseManagerService extends DbService {
6224
6385
  }
6225
6386
  createTableRecords(table, records) {
6226
6387
  const tableName = this.cleanTableName(table);
6227
- return from(this.DBOpened()).pipe(switchMap(() => {
6388
+ return from(this.DBOpened()).pipe(switchMap((opened) => {
6389
+ if (!opened) {
6390
+ console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
6391
+ return EMPTY;
6392
+ }
6228
6393
  if (!this.tables.some(t => t.name === tableName)) {
6229
6394
  console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
6230
6395
  return EMPTY;
6231
6396
  }
6232
6397
  const tableInstance = this.table(tableName);
6233
- // Extract schema fields directly from the table instance
6234
- const schema = tableInstance.schema;
6235
- const validFields = [
6236
- schema.primKey.name,
6237
- ...schema.indexes.map(idx => idx.name)
6238
- ];
6398
+ // Keep full object payload; Dexie stores non-indexed properties as well.
6239
6399
  const insertRecords = records.map((record) => {
6240
- const objectFromSchema = {};
6241
- validFields.forEach((field) => {
6242
- if (field) {
6243
- objectFromSchema[field] = record[field] ?? null;
6244
- }
6245
- });
6246
- return objectFromSchema;
6400
+ const payload = { ...(record || {}) };
6401
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6402
+ delete payload.id;
6403
+ }
6404
+ return payload;
6247
6405
  });
6248
6406
  console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
6249
6407
  return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
@@ -6257,17 +6415,14 @@ class DatabaseManagerService extends DbService {
6257
6415
  updateTableRecords(table, records) {
6258
6416
  const tableName = this.cleanTableName(table);
6259
6417
  return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6260
- return this.getDatabaseTableSchema(tableName).pipe(switchMap((schema) => {
6261
- const insertRecords = records.map((record) => {
6262
- const objectFromSchema = {};
6263
- schema.forEach((field) => {
6264
- const data = record[field] ?? null;
6265
- objectFromSchema[field] = data;
6266
- });
6267
- return objectFromSchema;
6268
- });
6269
- return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6270
- }));
6418
+ const insertRecords = records.map((record) => {
6419
+ const payload = { ...(record || {}) };
6420
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6421
+ delete payload.id;
6422
+ }
6423
+ return payload;
6424
+ });
6425
+ return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6271
6426
  }));
6272
6427
  }
6273
6428
  deleteTableRecord(table, id) {
@@ -6369,12 +6524,11 @@ class HTTPManagerStateService extends ComponentStore {
6369
6524
  this.httpManagerService = inject(HTTPManagerService);
6370
6525
  this.dbManagerService = inject(DatabaseManagerService);
6371
6526
  this.localStorageManagerService = inject(LocalStorageManagerService);
6527
+ this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6372
6528
  this.utils = inject(UtilsService);
6373
6529
  this.logger = inject(LoggerService);
6374
6530
  this.error$ = this.httpManagerService.error$;
6375
- this.isPending$ = this.httpManagerService.isPending$;
6376
- this.progress$ = this.httpManagerService.progress$;
6377
- this.streamProgress$ = this.httpManagerService.streamProgress$;
6531
+ this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
6378
6532
  this.operationSuccess = new BehaviorSubject(null);
6379
6533
  this.operationSuccess$ = this.operationSuccess.asObservable();
6380
6534
  // PAGINATION
@@ -6387,10 +6541,18 @@ class HTTPManagerStateService extends ComponentStore {
6387
6541
  this.hasDatabase = false;
6388
6542
  this.streamedResponse = [];
6389
6543
  this.shouldRetry = true;
6544
+ this.volatileHeaders = new Set([
6545
+ 'authorization',
6546
+ 'x-request-id',
6547
+ 'x-correlation-id',
6548
+ 'x-trace-id',
6549
+ 'x-amzn-trace-id',
6550
+ 'date',
6551
+ 'if-none-match'
6552
+ ]);
6553
+ this.requestSignatureCache = {};
6390
6554
  this.wsRetryAttempts = new BehaviorSubject(0);
6391
6555
  this.wsRetryAttempts$ = this.wsRetryAttempts.asObservable();
6392
- // Guard: prevent concurrent streams and block requests during active stream
6393
- this.activeStream$ = new BehaviorSubject(false);
6394
6556
  this.messages = new BehaviorSubject([]);
6395
6557
  this.messages$ = this.messages.asObservable();
6396
6558
  this.userListByChannel = new BehaviorSubject(new Map());
@@ -6658,22 +6820,25 @@ class HTTPManagerStateService extends ComponentStore {
6658
6820
  }
6659
6821
  }))))));
6660
6822
  this.initDBStorage = this.effect((trigger$) => trigger$.pipe(tap(() => {
6823
+ console.log('[initDBStorage effect] Triggered, checking conditions:', {
6824
+ dataType: this.dataType,
6825
+ isARRAY: this.dataType === DataType.ARRAY,
6826
+ hasAdapter: !!this.apiOptions?.adapter,
6827
+ hasTable: !!this.databaseOptions?.table,
6828
+ tableValue: this.databaseOptions?.table
6829
+ });
6661
6830
  if (this.dataType !== DataType.ARRAY)
6662
6831
  console.warn('Database storage requires dataType to be ARRAY');
6663
6832
  if (!this.apiOptions.adapter)
6664
- console.warn('Database storage requires an adapter to define the data shape');
6833
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
6665
6834
  if (this.databaseOptions && this.databaseOptions?.table === '')
6666
6835
  console.warn('Database storage requires a table name');
6667
- }), filter(() => this.dataType === DataType.ARRAY && !!this.apiOptions.adapter && !!this.databaseOptions?.table), switchMap(() => {
6668
- const sampleData = this.apiOptions.adapter?.({}) || {};
6669
- const schemaKeys = Object.keys(sampleData).filter(key => sampleData[key] !== undefined);
6670
- let schema = '++id';
6671
- if (schemaKeys.length > 0) {
6672
- const otherKeys = schemaKeys.filter(k => k !== 'id');
6673
- if (otherKeys.length > 0) {
6674
- schema += ', ' + otherKeys.join(', ');
6675
- }
6676
- }
6836
+ }), filter(() => {
6837
+ const shouldProceed = this.dataType === DataType.ARRAY && !!this.databaseOptions?.table;
6838
+ console.log('[initDBStorage effect] Filter result:', shouldProceed);
6839
+ return shouldProceed;
6840
+ }), switchMap(() => {
6841
+ const schema = this.buildSchemaFromAdapter();
6677
6842
  const tableDef = TableSchemaDef.adapt({
6678
6843
  table: this.databaseOptions?.table,
6679
6844
  schema: schema
@@ -6701,10 +6866,10 @@ class HTTPManagerStateService extends ComponentStore {
6701
6866
  return state;
6702
6867
  if (this.dataType === DataType.ARRAY) {
6703
6868
  const dataArray = Array.isArray(data) ? data : [data];
6704
- // Non-streaming path: merge incoming records with existing state.
6705
- // Streaming responses go through `setStreamData$` instead, which always
6706
- // replaces with the fully-accumulated array from the streaming operator.
6707
- const updatedData = this.updateArrayState(state.data, dataArray);
6869
+ const stateDataSample = (state.data.length > 0) ? Object.keys(state.data[0]) : [];
6870
+ const newDataSample = (dataArray.length > 0) ? Object.keys(dataArray[0]) : [];
6871
+ const isSame = (state.data.length === 0) ? false : stateDataSample.every((value, index) => value === newDataSample[index]);
6872
+ const updatedData = (!isSame && dataArray.length !== 0) ? this.updateArrayState([], dataArray) : this.updateArrayState(state.data, dataArray);
6708
6873
  return { ...state, data: updatedData, dataObject: null };
6709
6874
  }
6710
6875
  else {
@@ -6712,18 +6877,6 @@ class HTTPManagerStateService extends ComponentStore {
6712
6877
  return { ...state, data: [], dataObject: dataObject };
6713
6878
  }
6714
6879
  });
6715
- /**
6716
- * Streaming-only updater: replaces `data` with the incoming array.
6717
- *
6718
- * The streaming operator (`requestStreaming`) accumulates records via `scan`,
6719
- * so each emission is the full accumulated dataset. Replace, don't merge.
6720
- */
6721
- this.setStreamData$ = this.updater((state, data) => {
6722
- if (!data)
6723
- return state;
6724
- const dataArray = Array.isArray(data) ? data : [data];
6725
- return { ...state, data: dataArray, dataObject: null };
6726
- });
6727
6880
  this.addData$ = this.updater((state, data) => {
6728
6881
  if (this.dataType === DataType.ARRAY) {
6729
6882
  const exists = state.data.some(item => item.id === data.id);
@@ -6774,6 +6927,7 @@ class HTTPManagerStateService extends ComponentStore {
6774
6927
  }
6775
6928
  }), concatMap(() => {
6776
6929
  if (this.hasDatabase && this.databaseOptions?.table) {
6930
+ this.clearRequestCacheMetadata(this.databaseOptions.table);
6777
6931
  const currentData = this.get()?.data;
6778
6932
  const idsToDelete = Array.isArray(currentData) ? currentData.map((r) => r.id) : [];
6779
6933
  if (idsToDelete.length > 0) {
@@ -6788,35 +6942,81 @@ class HTTPManagerStateService extends ComponentStore {
6788
6942
  this.fetchRecords = (options) => this.effect(() => of(RequestOptions.adapt(options)).pipe(switchMap(() => {
6789
6943
  this.streamedResponse = [];
6790
6944
  const requestOptions = this.updateRequestOptions(options?.headers);
6945
+ const effectiveParams = this.getEffectiveParams(options?.path);
6946
+ const requestSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
6791
6947
  const fetchFromAPI = () => {
6792
- return this.httpManagerService.getRequest(requestOptions, options?.path).pipe(tap((data) => {
6793
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
6948
+ if (this.hasDatabase && this.databaseOptions?.table) {
6949
+ this.setCachedRequestSignature(this.databaseOptions.table, 'GET', requestSignature);
6950
+ }
6951
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(tap((data) => {
6952
+ // Extract array from paginated response if needed
6953
+ const arrayData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6954
+ data = (!arrayData) ? (this.dataType === DataType.ARRAY) ? [] : {} : arrayData;
6794
6955
  this.setData$(data);
6795
6956
  }), concatMap((data) => {
6796
- if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(data) && data.length > 0) {
6957
+ // Extract array from paginated response for database storage
6958
+ const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6959
+ if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
6960
+ const tableName = this.databaseOptions.table;
6797
6961
  this.localStorageManagerService.updateStore({
6798
- name: this.databaseOptions.table,
6962
+ name: tableName,
6799
6963
  data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
6800
6964
  });
6801
- return this.dbManagerService.createTableRecords(this.databaseOptions.table, data);
6965
+ const schema = this.buildSchemaFromSample(dbData[0]);
6966
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
6967
+ const schemaSignature = this.buildSchemaSignature(schema);
6968
+ // Always ensure table exists immediately before writing to avoid stale schema/store races.
6969
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
6970
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6971
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
6972
+ const ensureTable$ = schemaChanged
6973
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
6974
+ : this.dbManagerService.createDatabaseTable(tableDef);
6975
+ return ensureTable$.pipe(switchMap((created) => {
6976
+ if (!created) {
6977
+ console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
6978
+ return of(data);
6979
+ }
6980
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'GET', requestSignature, schemaSignature, options)));
6981
+ }));
6982
+ }), catchError((error) => {
6983
+ console.error('[DB STORAGE] Failed to ensure table and write records:', { table: tableName, schema, error });
6984
+ return of(data);
6985
+ }));
6802
6986
  }
6803
6987
  return of(data);
6804
6988
  }));
6805
6989
  };
6990
+ console.log('[DB STORAGE] Checking database storage:', {
6991
+ hasDatabase: this.hasDatabase,
6992
+ table: this.databaseOptions?.table,
6993
+ databaseOptions: this.databaseOptions
6994
+ });
6806
6995
  if (this.hasDatabase && this.databaseOptions?.table) {
6807
6996
  return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
6808
6997
  if (!dbExists) {
6809
6998
  const initObs = this.initDBStorageAsync();
6810
- return initObs.pipe(switchMap(() => fetchFromAPI()));
6999
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7000
+ console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7001
+ return fetchFromAPI();
7002
+ }));
6811
7003
  }
6812
7004
  return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
6813
7005
  if (!tableExists) {
6814
7006
  const initObs = this.initDBStorageAsync();
6815
- return initObs.pipe(switchMap(() => fetchFromAPI()));
7007
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7008
+ console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7009
+ return fetchFromAPI();
7010
+ }));
6816
7011
  }
6817
7012
  return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7013
+ const forceRefresh = !!options?.forceRefresh;
7014
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6818
7015
  const expires = storeData?.expires || 0;
6819
7016
  const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7017
+ if (forceRefresh) {
7018
+ return fetchFromAPI();
7019
+ }
6820
7020
  if (hasExpired) {
6821
7021
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()), tap(() => {
6822
7022
  this.localStorageManagerService.updateStore({
@@ -6825,12 +7025,41 @@ class HTTPManagerStateService extends ComponentStore {
6825
7025
  });
6826
7026
  }));
6827
7027
  }
7028
+ const expectedSchema = this.buildSchemaFromAdapter();
7029
+ const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7030
+ if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7031
+ const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7032
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7033
+ }
7034
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7035
+ const shouldMakeRequest = trackerAllowsRequest;
7036
+ if (shouldMakeRequest) {
7037
+ return fetchFromAPI();
7038
+ }
6828
7039
  return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
6829
7040
  if (Array.isArray(dbData) && dbData.length > 0) {
6830
7041
  this.setData$(dbData);
6831
7042
  return of(dbData);
6832
7043
  }
6833
- return fetchFromAPI();
7044
+ const currentStateData = this.get()?.data;
7045
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7046
+ return of(currentStateData);
7047
+ }
7048
+ if (currentStateData &&
7049
+ !Array.isArray(currentStateData) &&
7050
+ Object.keys(currentStateData).length > 0) {
7051
+ return of(currentStateData);
7052
+ }
7053
+ return of(this.dataType === DataType.ARRAY ? [] : {});
7054
+ }), catchError((error) => {
7055
+ const tableName = this.databaseOptions.table;
7056
+ console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7057
+ const schema = this.buildSchemaFromAdapter();
7058
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7059
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7060
+ console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7061
+ return fetchFromAPI();
7062
+ }));
6834
7063
  }));
6835
7064
  }));
6836
7065
  }));
@@ -6954,7 +7183,7 @@ class HTTPManagerStateService extends ComponentStore {
6954
7183
  if (res.length > 0)
6955
7184
  this.setData$(res);
6956
7185
  this.streamedResponse = res;
6957
- }), scan((acc, res) => {
7186
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), scan((acc, res) => {
6958
7187
  const previous = acc.current;
6959
7188
  const current = res;
6960
7189
  return { previous, current };
@@ -6968,31 +7197,97 @@ class HTTPManagerStateService extends ComponentStore {
6968
7197
  }
6969
7198
  }));
6970
7199
  })));
6971
- this.fetchStream = this.effect((options$) => options$.pipe(tap((options) => {
6972
- if (this.activeStream$.value) {
6973
- console.warn('[fetchStream] Stream already active, skipping');
6974
- return;
6975
- }
7200
+ this.fetchStream = (options) => this.effect(() => of(options).pipe(tap(() => {
7201
+ console.log('[DEBUG] fetchStream called');
6976
7202
  this.httpManagerService.isPending.next(true);
6977
- this.activeStream$.next(true); // Mark stream as active
6978
- // Reset streamed-response tracker so a new stream starts clean
6979
- this.streamedResponse = [];
6980
- }), concatMap((options) => {
7203
+ }), switchMap((options) => {
6981
7204
  const requestOptions = this.updateRequestOptions(options?.headers);
6982
7205
  requestOptions.stream = true;
6983
- return this.httpManagerService.getRequest(requestOptions, options?.path)
7206
+ const effectiveParams = this.getEffectiveParams(options?.path);
7207
+ console.log('[DEBUG] Making streaming request:', requestOptions);
7208
+ if (this.hasDatabase && this.databaseOptions?.table) {
7209
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7210
+ return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7211
+ const forceRefresh = !!options?.forceRefresh;
7212
+ const expires = storeData?.expires || 0;
7213
+ const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7214
+ if (forceRefresh) {
7215
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7216
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7217
+ }
7218
+ if (hasExpired) {
7219
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(tap(() => this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature)), switchMap(() => this.httpManagerService.getRequest(requestOptions, effectiveParams)), map((apiData) => ({ data: apiData, fromCache: false })));
7220
+ }
7221
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7222
+ const shouldMakeRequest = trackerAllowsRequest;
7223
+ if (!shouldMakeRequest) {
7224
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7225
+ if (Array.isArray(dbData) && dbData.length > 0) {
7226
+ return of({ data: dbData, fromCache: true });
7227
+ }
7228
+ const currentStateData = this.get()?.data;
7229
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7230
+ return of({ data: currentStateData, fromCache: true });
7231
+ }
7232
+ if (currentStateData &&
7233
+ !Array.isArray(currentStateData) &&
7234
+ Object.keys(currentStateData).length > 0) {
7235
+ return of({ data: currentStateData, fromCache: true });
7236
+ }
7237
+ return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7238
+ }));
7239
+ }
7240
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7241
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7242
+ })).pipe(tap((packet) => {
7243
+ const res = packet?.data;
7244
+ console.log('[DEBUG] Streaming response received:', res);
7245
+ if (res && res.length > 0) {
7246
+ console.log('[DEBUG] Updating state with streaming data:', res);
7247
+ this.setData$(res);
7248
+ this.streamedResponse = res;
7249
+ }
7250
+ else {
7251
+ console.log('[DEBUG] No streaming data or empty array:', res);
7252
+ }
7253
+ // Reset pending once we have a response packet (cache or network)
7254
+ this.httpManagerService.isPending.next(false);
7255
+ }), concatMap((packet) => {
7256
+ if (packet?.fromCache) {
7257
+ return of(packet?.data);
7258
+ }
7259
+ return this.persistStreamDataToDb(packet?.data, options);
7260
+ }), map((res) => {
7261
+ console.log('[DEBUG] Returning data to subscribers:', res);
7262
+ return res;
7263
+ }), catchError((error) => {
7264
+ console.error('[DEBUG] Streaming error:', error);
7265
+ this.httpManagerService.isPending.next(false);
7266
+ return of([]);
7267
+ }));
7268
+ }
7269
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams)
6984
7270
  .pipe(tap((res) => {
6985
- // Streaming operator emits the fully-accumulated array on every chunk.
6986
- // Always replace state don't merge — to keep table in sync as records append.
6987
- if (res && Array.isArray(res) && res.length > 0) {
6988
- this.setStreamData$(res);
7271
+ console.log('[DEBUG] Streaming response received:', res);
7272
+ // Always update state with streaming data
7273
+ if (res && res.length > 0) {
7274
+ console.log('[DEBUG] Updating state with streaming data:', res);
7275
+ this.setData$(res);
6989
7276
  this.streamedResponse = res;
6990
7277
  }
6991
- }), map((res) => res), catchError((error) => {
7278
+ else {
7279
+ console.log('[DEBUG] No streaming data or empty array:', res);
7280
+ }
7281
+ // Reset pending once we have a response packet
7282
+ this.httpManagerService.isPending.next(false);
7283
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), map((res) => {
7284
+ console.log('[DEBUG] Returning data to subscribers:', res);
7285
+ return res; // Return the data so subscribers can receive it
7286
+ }), catchError((error) => {
7287
+ console.error('[DEBUG] Streaming error:', error);
6992
7288
  this.httpManagerService.isPending.next(false);
6993
7289
  return of([]);
6994
- }), finalize(() => this.activeStream$.next(false)) // Mark stream as complete
6995
- );
7290
+ }));
6996
7291
  })));
6997
7292
  try {
6998
7293
  this.databaseOptions = database;
@@ -7012,7 +7307,11 @@ class HTTPManagerStateService extends ComponentStore {
7012
7307
  encrypted: false,
7013
7308
  })
7014
7309
  });
7015
- this.initDBStorage();
7310
+ // Use initDBStorageAsync directly - the effect initDBStorage requires subscription
7311
+ this.initDBStorageAsync().subscribe({
7312
+ next: () => console.log('[Constructor] Database storage initialized'),
7313
+ error: (err) => console.error('[Constructor] Database storage initialization failed:', err)
7314
+ });
7016
7315
  }
7017
7316
  }
7018
7317
  catch (error) {
@@ -7066,8 +7365,24 @@ class HTTPManagerStateService extends ComponentStore {
7066
7365
  this.dataType = (dataType) ? dataType : DataType.ARRAY;
7067
7366
  // Only update database options if a database parameter is explicitly provided
7068
7367
  if (database !== undefined) {
7368
+ console.log('[setApiRequestOptions] Database config:', {
7369
+ database,
7370
+ hasTable: !!database?.table,
7371
+ tableValue: database?.table
7372
+ });
7069
7373
  this.hasDatabase = (database?.table) ? true : false;
7070
- this.databaseOptions = (this.hasDatabase) ? DatabaseStorage.adapt(database) : undefined;
7374
+ const adapted = DatabaseStorage.adapt(database);
7375
+ console.log('[setApiRequestOptions] DatabaseStorage.adapt result:', adapted);
7376
+ this.databaseOptions = (this.hasDatabase) ? adapted : undefined;
7377
+ // Trigger database table creation if table is configured
7378
+ if (this.hasDatabase && this.databaseOptions?.table) {
7379
+ console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table);
7380
+ // Use initDBStorageAsync directly instead of the effect - effects need subscription to run
7381
+ this.initDBStorageAsync().subscribe({
7382
+ next: () => console.log('[setApiRequestOptions] Database storage initialized successfully'),
7383
+ error: (err) => console.error('[setApiRequestOptions] Database storage initialization failed:', err)
7384
+ });
7385
+ }
7071
7386
  }
7072
7387
  if (this.apiOptions.ws && this.apiOptions.ws.id !== '') {
7073
7388
  // Auto-prefix channel ID for private state manager channels
@@ -7189,11 +7504,20 @@ class HTTPManagerStateService extends ComponentStore {
7189
7504
  this.setData$(data);
7190
7505
  }
7191
7506
  updateArrayState(currentData, newData) {
7507
+ // For non-streaming requests (GET), REPLACE data entirely
7508
+ // For streaming requests, MERGE with existing data incrementally
7509
+ if (this.streamedResponse.length === 0) {
7510
+ // GET request: return new data as-is (replace)
7511
+ return newData;
7512
+ }
7513
+ // Streaming: merge with existing data
7192
7514
  const filterCurrentData = () => {
7193
7515
  const ids = this.streamedResponse.map((obj) => obj.id);
7194
7516
  return currentData.filter(obj => (obj.id) ? ids.includes(obj.id) : obj);
7195
7517
  };
7196
- const filteredCurrentData = (this.httpManagerService.isPending.value) ? currentData : filterCurrentData();
7518
+ const filteredCurrentData = (this.httpManagerService.isPending.value)
7519
+ ? currentData
7520
+ : filterCurrentData();
7197
7521
  const updatedData = filteredCurrentData.map(item => {
7198
7522
  const newItem = newData.find(newItem => {
7199
7523
  const hasId = (newItem?.id && item?.id) ? true : false;
@@ -7210,32 +7534,92 @@ class HTTPManagerStateService extends ComponentStore {
7210
7534
  return [...updatedData, ...addedData];
7211
7535
  }
7212
7536
  initDBStorageAsync() {
7537
+ console.log('[initDBStorageAsync] Starting initialization:', {
7538
+ dataType: this.dataType,
7539
+ hasAdapter: !!this.apiOptions?.adapter,
7540
+ table: this.databaseOptions?.table,
7541
+ databaseOptions: this.databaseOptions
7542
+ });
7213
7543
  if (this.dataType !== DataType.ARRAY) {
7214
7544
  console.warn('Database storage requires dataType to be ARRAY');
7215
7545
  return of(null);
7216
7546
  }
7217
7547
  if (!this.apiOptions.adapter) {
7218
- console.warn('Database storage requires an adapter to define the data shape');
7219
- return of(null);
7548
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
7220
7549
  }
7221
7550
  if (!this.databaseOptions?.table) {
7222
7551
  console.warn('Database storage requires a table name');
7223
7552
  return of(null);
7224
7553
  }
7554
+ const schema = this.buildSchemaFromAdapter();
7555
+ const tableDef = TableSchemaDef.adapt({
7556
+ table: this.databaseOptions?.table,
7557
+ schema: schema
7558
+ });
7559
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(tap((created) => {
7560
+ if (created && this.databaseOptions?.table) {
7561
+ this.saveSchemaSignature(this.databaseOptions.table, this.buildSchemaSignature(schema));
7562
+ }
7563
+ }));
7564
+ }
7565
+ buildSchemaFromAdapter() {
7225
7566
  const sampleData = this.apiOptions.adapter?.({}) || {};
7226
- const schemaKeys = Object.keys(sampleData).filter(key => sampleData[key] !== undefined);
7567
+ return this.buildSchemaFromSample(sampleData);
7568
+ }
7569
+ buildSchemaFromSample(sampleData) {
7570
+ const schemaKeys = Object.keys(sampleData || {}).filter(key => sampleData[key] !== undefined);
7227
7571
  let schema = '++id';
7228
7572
  if (schemaKeys.length > 0) {
7229
- const otherKeys = schemaKeys.filter(k => k !== 'id');
7573
+ const otherKeys = schemaKeys.filter((k) => k !== 'id');
7230
7574
  if (otherKeys.length > 0) {
7231
7575
  schema += ', ' + otherKeys.join(', ');
7232
7576
  }
7233
7577
  }
7234
- const tableDef = TableSchemaDef.adapt({
7235
- table: this.databaseOptions?.table,
7236
- schema: schema
7237
- });
7238
- return this.dbManagerService.createDatabaseTable(tableDef);
7578
+ return schema;
7579
+ }
7580
+ persistStreamDataToDb(payload, streamOptions) {
7581
+ if (!this.hasDatabase || !this.databaseOptions?.table) {
7582
+ return of(payload);
7583
+ }
7584
+ const dbData = (payload?.results && Array.isArray(payload.results))
7585
+ ? payload.results
7586
+ : Array.isArray(payload)
7587
+ ? payload
7588
+ : payload
7589
+ ? [payload]
7590
+ : [];
7591
+ if (dbData.length === 0) {
7592
+ return of(payload);
7593
+ }
7594
+ const tableName = this.databaseOptions.table;
7595
+ const requestOptions = this.updateRequestOptions(streamOptions?.headers);
7596
+ requestOptions.stream = true;
7597
+ const effectiveParams = this.getEffectiveParams(streamOptions?.path);
7598
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7599
+ this.localStorageManagerService.updateStore({
7600
+ name: tableName,
7601
+ data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7602
+ });
7603
+ const schema = this.buildSchemaFromSample(dbData[0]);
7604
+ const schemaSignature = this.buildSchemaSignature(schema);
7605
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7606
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
7607
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7608
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7609
+ const ensureTable$ = schemaChanged
7610
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
7611
+ : this.dbManagerService.createDatabaseTable(tableDef);
7612
+ return ensureTable$.pipe(switchMap((created) => {
7613
+ if (!created) {
7614
+ console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
7615
+ return of(payload);
7616
+ }
7617
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'STREAM', requestSignature, schemaSignature, streamOptions)), map(() => payload));
7618
+ }));
7619
+ }), map(() => payload), catchError((error) => {
7620
+ console.error('[DB STORAGE] Failed to persist streaming payload:', { table: tableName, schema, error });
7621
+ return of(payload);
7622
+ }));
7239
7623
  }
7240
7624
  // WEBSOCKET COMMUNICATION (STATE MANAGER)
7241
7625
  wsCommunication(method, path) {
@@ -7531,6 +7915,7 @@ class HTTPManagerStateService extends ComponentStore {
7531
7915
  const tableName = this.databaseOptions.table;
7532
7916
  this.dbManagerService.clearTable(tableName).subscribe({
7533
7917
  next: () => {
7918
+ this.clearRequestCacheMetadata(tableName);
7534
7919
  if (this.dataType === DataType.ARRAY) {
7535
7920
  this.setData$([]);
7536
7921
  }
@@ -7553,6 +7938,132 @@ class HTTPManagerStateService extends ComponentStore {
7553
7938
  : { ...options.headers };
7554
7939
  return options;
7555
7940
  }
7941
+ buildQueryTrackerOptions(options) {
7942
+ return {
7943
+ watchParams: Array.isArray(options?.watchParams) ? options.watchParams : [],
7944
+ watchExpiresAt: options?.watchExpiresAt,
7945
+ };
7946
+ }
7947
+ normalizeObject(value) {
7948
+ if (Array.isArray(value)) {
7949
+ return value.map((item) => this.normalizeObject(item));
7950
+ }
7951
+ if (value && typeof value === 'object') {
7952
+ return Object.keys(value)
7953
+ .sort()
7954
+ .reduce((acc, key) => {
7955
+ acc[key] = this.normalizeObject(value[key]);
7956
+ return acc;
7957
+ }, {});
7958
+ }
7959
+ return value;
7960
+ }
7961
+ filterHeaders(headers) {
7962
+ const source = headers || {};
7963
+ return Object.keys(source).reduce((acc, key) => {
7964
+ if (!this.volatileHeaders.has(key.toLowerCase())) {
7965
+ acc[key] = source[key];
7966
+ }
7967
+ return acc;
7968
+ }, {});
7969
+ }
7970
+ resolvePath(params) {
7971
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7972
+ const effective = this.getEffectiveParams(params);
7973
+ return effective ? [...basePath, ...effective] : [...basePath];
7974
+ }
7975
+ getEffectiveParams(params) {
7976
+ if (!Array.isArray(params) || params.length === 0) {
7977
+ return undefined;
7978
+ }
7979
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7980
+ if (basePath.length !== params.length) {
7981
+ return params;
7982
+ }
7983
+ const normalizePart = (value) => {
7984
+ if (value && typeof value === 'object') {
7985
+ return JSON.stringify(this.normalizeObject(value));
7986
+ }
7987
+ return String(value);
7988
+ };
7989
+ const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
7990
+ return samePath ? undefined : params;
7991
+ }
7992
+ buildRequestSignature(method, requestOptions, params) {
7993
+ const signaturePayload = {
7994
+ method,
7995
+ server: requestOptions.server,
7996
+ path: this.resolvePath(params),
7997
+ headers: this.filterHeaders(requestOptions.headers),
7998
+ stream: !!requestOptions.stream,
7999
+ streamType: requestOptions.streamType || null
8000
+ };
8001
+ return JSON.stringify(this.normalizeObject(signaturePayload));
8002
+ }
8003
+ buildSchemaSignature(schema) {
8004
+ return JSON.stringify(this.normalizeObject(schema.split(',').map((part) => part.trim()).filter(Boolean)));
8005
+ }
8006
+ getCachedRequestSignature(tableName, type) {
8007
+ return this.requestSignatureCache[tableName]?.[type] || null;
8008
+ }
8009
+ setCachedRequestSignature(tableName, type, signature) {
8010
+ const existing = this.requestSignatureCache[tableName] || {};
8011
+ this.requestSignatureCache[tableName] = {
8012
+ ...existing,
8013
+ [type]: signature,
8014
+ };
8015
+ }
8016
+ getRequestCacheMetadata(storeData, type) {
8017
+ return storeData?.requestCache?.[type] || null;
8018
+ }
8019
+ getStoredSchemaSignature(storeData) {
8020
+ return storeData?.schemaSignature || null;
8021
+ }
8022
+ saveSchemaSignature(tableName, schemaSignature) {
8023
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8024
+ this.localStorageManagerService.updateStore({
8025
+ name: tableName,
8026
+ data: {
8027
+ ...(storeData || {}),
8028
+ schemaSignature,
8029
+ }
8030
+ });
8031
+ })).subscribe();
8032
+ }
8033
+ saveRequestCacheMetadata(tableName, type, signature, schemaSignature, options) {
8034
+ this.setCachedRequestSignature(tableName, type, signature);
8035
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8036
+ const currentCache = storeData?.requestCache || {};
8037
+ this.localStorageManagerService.updateStore({
8038
+ name: tableName,
8039
+ data: {
8040
+ ...(storeData || {}),
8041
+ schemaSignature: schemaSignature || storeData?.schemaSignature || null,
8042
+ requestCache: {
8043
+ ...currentCache,
8044
+ [type]: {
8045
+ signature,
8046
+ savedAt: Date.now(),
8047
+ path: this.resolvePath(options?.path),
8048
+ headers: this.filterHeaders(options?.headers)
8049
+ }
8050
+ }
8051
+ }
8052
+ });
8053
+ })).subscribe();
8054
+ }
8055
+ clearRequestCacheMetadata(tableName) {
8056
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8057
+ if (!storeData)
8058
+ return;
8059
+ const updated = { ...(storeData || {}) };
8060
+ delete updated.requestCache;
8061
+ this.localStorageManagerService.updateStore({
8062
+ name: tableName,
8063
+ data: updated
8064
+ });
8065
+ })).subscribe();
8066
+ }
7556
8067
  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 }); }
7557
8068
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService }); }
7558
8069
  }
@@ -8502,7 +9013,7 @@ class RequestManagerBasicDemoComponent {
8502
9013
  // Update displayed columns when data changes
8503
9014
  updateDisplayedColumns(data) {
8504
9015
  this.displayedColumns = this.getColumnsFromData(data);
8505
- // console.log('[DEMO] Updated columns:', this.displayedColumns)
9016
+ console.log('[DEMO] Updated columns:', this.displayedColumns);
8506
9017
  }
8507
9018
  // Helper to check if value is an object
8508
9019
  isObject(value) {
@@ -8762,7 +9273,7 @@ class RequestManagerBasicDemoComponent {
8762
9273
  this.requestParams.GET = reqParams.apiOptions;
8763
9274
  this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
8764
9275
  .pipe(tap((data) => {
8765
- // console.log("API STREAM response", data)
9276
+ console.log("API STREAM response", data);
8766
9277
  if (data && data.length > 0) {
8767
9278
  this.updateDisplayedColumns(data);
8768
9279
  }
@@ -8859,12 +9370,12 @@ class StateManagerDemoService extends HTTPManagerStateService {
8859
9370
  setAPIOptions(apiOptions, dataType, database) {
8860
9371
  this.setApiRequestOptions(apiOptions, dataType, database);
8861
9372
  }
8862
- getClients() {
9373
+ getClients(options) {
8863
9374
  // const headers = {
8864
9375
  // auth: "sample-auth-token"
8865
9376
  // }
8866
9377
  // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8867
- this.fetchRecords();
9378
+ this.fetchRecords(options);
8868
9379
  }
8869
9380
  createClient(data) {
8870
9381
  // const headers = {
@@ -8889,13 +9400,13 @@ class StateManagerDemoService extends HTTPManagerStateService {
8889
9400
  const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8890
9401
  this.deleteRecord(sampleOptions);
8891
9402
  }
8892
- streamRequest() {
9403
+ streamRequest(options) {
8893
9404
  console.log('[DEMO SERVICE] streamRequest called');
8894
9405
  const headers = {
8895
9406
  auth: "sample-auth-token"
8896
9407
  };
8897
9408
  console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
8898
- this.fetchStream({ headers });
9409
+ this.fetchStream(options);
8899
9410
  }
8900
9411
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8901
9412
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
@@ -8929,7 +9440,7 @@ class RequestManagerStateDemoComponent {
8929
9440
  // Update displayed columns when data changes
8930
9441
  updateDisplayedColumns(data) {
8931
9442
  this.displayedColumns = this.getColumnsFromData(data);
8932
- // console.log('[STATE DEMO] Updated columns:', this.displayedColumns)
9443
+ console.log('[STATE DEMO] Updated columns:', this.displayedColumns);
8933
9444
  }
8934
9445
  // Helper to check if value is an object
8935
9446
  isObject(value) {
@@ -8983,7 +9494,6 @@ class RequestManagerStateDemoComponent {
8983
9494
  constructor() {
8984
9495
  this.server = 'http://localhost:8080';
8985
9496
  this.stateManagerDemoService = inject(StateManagerDemoService);
8986
- this.progress$ = this.stateManagerDemoService.streamProgress$;
8987
9497
  this.displayedColumns = [];
8988
9498
  this.fb = inject(FormBuilder);
8989
9499
  this.streamTypes = [
@@ -8996,7 +9506,6 @@ class RequestManagerStateDemoComponent {
8996
9506
  this.streamType = 'Auto';
8997
9507
  this.httpManagerService = inject(HTTPManagerService);
8998
9508
  this.isPending$ = this.stateManagerDemoService.isPending$;
8999
- this.isStreamingPending$ = this.httpManagerService.isStreamingPending$;
9000
9509
  this.error$ = this.stateManagerDemoService.error$;
9001
9510
  this.countdown$ = this.httpManagerService.countdown$;
9002
9511
  this.GET_error$ = new BehaviorSubject('');
@@ -9009,7 +9518,7 @@ class RequestManagerStateDemoComponent {
9009
9518
  this.POST$ = new BehaviorSubject(null);
9010
9519
  this.PUT$ = new BehaviorSubject(null);
9011
9520
  this.DELETE$ = new BehaviorSubject(null);
9012
- this.STREAM = new BehaviorSubject([]);
9521
+ this.STREAM = new BehaviorSubject(null);
9013
9522
  this.STREAM$ = this.STREAM.asObservable();
9014
9523
  this.STREAM_AI = new BehaviorSubject([]);
9015
9524
  this.STREAM_AI$ = this.STREAM_AI.asObservable()
@@ -9045,6 +9554,8 @@ class RequestManagerStateDemoComponent {
9045
9554
  database: this.fb.group({
9046
9555
  table: [''],
9047
9556
  expiresIn: ['1m'],
9557
+ watchParams: [''],
9558
+ watchExpiresAt: ['never'],
9048
9559
  })
9049
9560
  });
9050
9561
  this.sampleAdaptors = [
@@ -9067,7 +9578,7 @@ class RequestManagerStateDemoComponent {
9067
9578
  this.stateManagerDemoService.data$.pipe(tap$1((data) => console.log("API STREAM_AI response", data)));
9068
9579
  this.error$.pipe(tap$1((data) => {
9069
9580
  debugger;
9070
- // console.log("API STREAM response", data)
9581
+ console.log("API STREAM response", data);
9071
9582
  }), catchError$1(error => {
9072
9583
  return throwError(() => this.errorHandling(error, 'STREAM'));
9073
9584
  }));
@@ -9126,10 +9637,59 @@ class RequestManagerStateDemoComponent {
9126
9637
  removeHeader(index) {
9127
9638
  this.headers.removeAt(index);
9128
9639
  }
9640
+ parsePathInput(pathInput) {
9641
+ const raw = typeof pathInput === 'string' ? pathInput.trim() : '';
9642
+ if (!raw) {
9643
+ return [];
9644
+ }
9645
+ const [rawPath = '', rawQuery = ''] = raw.split('?', 2);
9646
+ const pathSegments = rawPath
9647
+ .split('/')
9648
+ .map((segment) => segment.trim())
9649
+ .filter((segment) => segment.length > 0)
9650
+ .map((segment) => decodeURIComponent(segment));
9651
+ if (!rawQuery) {
9652
+ return pathSegments;
9653
+ }
9654
+ const searchParams = new URLSearchParams(rawQuery);
9655
+ const queryObject = {};
9656
+ searchParams.forEach((value, key) => {
9657
+ const normalizedKey = key.trim();
9658
+ if (!normalizedKey) {
9659
+ return;
9660
+ }
9661
+ const normalizedValue = this.normalizeQueryValue(value);
9662
+ if (Object.prototype.hasOwnProperty.call(queryObject, normalizedKey)) {
9663
+ const currentValue = queryObject[normalizedKey];
9664
+ queryObject[normalizedKey] = Array.isArray(currentValue)
9665
+ ? [...currentValue, normalizedValue]
9666
+ : [currentValue, normalizedValue];
9667
+ return;
9668
+ }
9669
+ queryObject[normalizedKey] = normalizedValue;
9670
+ });
9671
+ return Object.keys(queryObject).length > 0
9672
+ ? [...pathSegments, queryObject]
9673
+ : pathSegments;
9674
+ }
9675
+ normalizeQueryValue(value) {
9676
+ const trimmedValue = value.trim();
9677
+ const lowerValue = trimmedValue.toLowerCase();
9678
+ if (lowerValue === 'true') {
9679
+ return true;
9680
+ }
9681
+ if (lowerValue === 'false') {
9682
+ return false;
9683
+ }
9684
+ if (trimmedValue !== '' && !Number.isNaN(Number(trimmedValue))) {
9685
+ return Number(trimmedValue);
9686
+ }
9687
+ return trimmedValue;
9688
+ }
9129
9689
  compileRequest() {
9130
9690
  const requestParams = this.requestForm.value;
9131
9691
  requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
9132
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
9692
+ const pathReq = this.parsePathInput(requestParams.path || '');
9133
9693
  if (!this.pollingState.checked)
9134
9694
  requestParams.polling = 0;
9135
9695
  if (!this.failedState.checked) {
@@ -9143,12 +9703,53 @@ class RequestManagerStateDemoComponent {
9143
9703
  const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
9144
9704
  return { apiOptions: apiOptions, path: pathReq };
9145
9705
  }
9706
+ parseWatchParams(value) {
9707
+ if (!value || typeof value !== 'string') {
9708
+ return [];
9709
+ }
9710
+ return value
9711
+ .split(',')
9712
+ .map((item) => item.trim())
9713
+ .filter((item) => item.length > 0);
9714
+ }
9715
+ normalizeWatchExpiry(value) {
9716
+ if (typeof value === 'undefined' || value === null || value === '') {
9717
+ return undefined;
9718
+ }
9719
+ if (typeof value === 'string' && value.trim().toLowerCase() === 'never') {
9720
+ return undefined;
9721
+ }
9722
+ return value;
9723
+ }
9724
+ buildDemoRequestOptions(path, headers) {
9725
+ const dbValue = this.database || {};
9726
+ const isDbEnabled = !!this.DBState?.checked;
9727
+ return RequestOptions.adapt({
9728
+ path,
9729
+ headers,
9730
+ forceRefresh: false,
9731
+ watchParams: isDbEnabled ? this.parseWatchParams(dbValue?.watchParams) : [],
9732
+ watchExpiresAt: isDbEnabled ? this.normalizeWatchExpiry(dbValue?.watchExpiresAt) : undefined,
9733
+ });
9734
+ }
9146
9735
  onSetStateOptions() {
9147
- if (!this.isValid)
9736
+ const dbValue = this.database;
9737
+ console.log('[onSetStateOptions] Called, checking form values:', {
9738
+ isValid: this.isValid,
9739
+ database: dbValue,
9740
+ hasTable: !!dbValue?.table,
9741
+ tableValue: dbValue?.table,
9742
+ dataType: this.dataType
9743
+ });
9744
+ if (!this.isValid) {
9745
+ console.log('[onSetStateOptions] Form invalid, aborting');
9148
9746
  return;
9747
+ }
9149
9748
  const reqParams = this.compileRequest();
9150
- const db = DatabaseStorage.adapt(this.database);
9749
+ const db = DatabaseStorage.adapt(dbValue);
9750
+ console.log('[onSetStateOptions] DatabaseStorage.adapt result:', db);
9151
9751
  const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
9752
+ console.log('[onSetStateOptions] Calling setAPIOptions with:', { db, type, hasTable: !!db?.table });
9152
9753
  this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
9153
9754
  this.requestForm.markAsPristine();
9154
9755
  }
@@ -9157,7 +9758,9 @@ class RequestManagerStateDemoComponent {
9157
9758
  }
9158
9759
  onGetRequest() {
9159
9760
  this.requestType = 'GET';
9160
- this.stateManagerDemoService.getClients();
9761
+ const reqParams = this.compileRequest();
9762
+ const requestOptions = this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers);
9763
+ this.stateManagerDemoService.getClients(requestOptions);
9161
9764
  }
9162
9765
  onCreateRequest() {
9163
9766
  this.requestType = 'POST';
@@ -9183,8 +9786,11 @@ class RequestManagerStateDemoComponent {
9183
9786
  reqParams.apiOptions.stream = true;
9184
9787
  reqParams.apiOptions.streamType = this.streamType;
9185
9788
  this.requestType = 'STREAM';
9789
+ const streamOptions = RequestOptions.adapt({
9790
+ ...this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers),
9791
+ });
9186
9792
  // console.log('[COMPONENT] Calling streamRequest...')
9187
- this.stateManagerDemoService.streamRequest();
9793
+ this.stateManagerDemoService.streamRequest(streamOptions);
9188
9794
  }
9189
9795
  errorHandling(err, type) {
9190
9796
  console.log(err, type);
@@ -9208,11 +9814,11 @@ class RequestManagerStateDemoComponent {
9208
9814
  this.prompts = [];
9209
9815
  }
9210
9816
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9211
- 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 ((isStreamingPending$ | async) || ((STREAM$ | async)?.length)) {\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\"></mat-progress-bar>\n }\n @if ((STREAM$ | async); as data) {\n @if (data?.length) {\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(', ') }} | Rows: {{ data.length }}\n </div>\n </div>\n }\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" }] }); }
9817
+ 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 }, { propertyName: "DBState", first: true, predicate: ["DBState"], 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 style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\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=\"margin-top: 1rem\" formGroupName=\"database\">\n <div style=\"display: flex; gap: .5rem;\">\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 <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Watch Params (comma delimited)</mat-label>\n <input matInput placeholder=\"sort,active\" formControlName=\"watchParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"watchExpiresAt\">\n <mat-option value=\"never\">Never</mat-option>\n <mat-option value=\"10s\">10 Seconds</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1m\">1 Minute</mat-option>\n <mat-option value=\"5m\">5 Minutes</mat-option>\n <mat-option value=\"10m\">10 Minutes</mat-option>\n <mat-option value=\"1h\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 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" }] }); }
9212
9818
  }
9213
9819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
9214
9820
  type: Component,
9215
- 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 ((isStreamingPending$ | async) || ((STREAM$ | async)?.length)) {\n <mat-progress-bar mode=\"determinate\" [value]=\"(progress$ | async)?.percent ?? 0\"></mat-progress-bar>\n }\n @if ((STREAM$ | async); as data) {\n @if (data?.length) {\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(', ') }} | Rows: {{ data.length }}\n </div>\n </div>\n }\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"] }]
9821
+ 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 style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\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=\"margin-top: 1rem\" formGroupName=\"database\">\n <div style=\"display: flex; gap: .5rem;\">\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 <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Watch Params (comma delimited)</mat-label>\n <input matInput placeholder=\"sort,active\" formControlName=\"watchParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"watchExpiresAt\">\n <mat-option value=\"never\">Never</mat-option>\n <mat-option value=\"10s\">10 Seconds</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1m\">1 Minute</mat-option>\n <mat-option value=\"5m\">5 Minutes</mat-option>\n <mat-option value=\"10m\">10 Minutes</mat-option>\n <mat-option value=\"1h\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 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"] }]
9216
9822
  }], ctorParameters: () => [], propDecorators: { server: [{
9217
9823
  type: Input
9218
9824
  }], adapter: [{
@@ -9225,6 +9831,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9225
9831
  }], pollingState: [{
9226
9832
  type: ViewChild,
9227
9833
  args: ["pollingState", { static: true }]
9834
+ }], DBState: [{
9835
+ type: ViewChild,
9836
+ args: ["DBState", { static: true }]
9228
9837
  }] } });
9229
9838
 
9230
9839
  class RequestManagerDemoComponent {
@@ -9243,7 +9852,7 @@ class RequestManagerDemoComponent {
9243
9852
  // Update displayed columns when data changes
9244
9853
  updateDisplayedColumns(data) {
9245
9854
  this.displayedColumns = this.getColumnsFromData(data);
9246
- // console.log('[DEMO] Updated columns:', this.displayedColumns)
9855
+ console.log('[DEMO] Updated columns:', this.displayedColumns);
9247
9856
  }
9248
9857
  // Helper to check if value is an object
9249
9858
  isObject(value) {
@@ -9276,9 +9885,7 @@ class RequestManagerDemoComponent {
9276
9885
  this.toastMessage = inject(ToastMessageDisplayService);
9277
9886
  this.questionControl = this.fb.control("", [Validators.required]);
9278
9887
  this.httpManagerService = inject(HTTPManagerService);
9279
- this.progress$ = this.httpManagerService.streamProgress$;
9280
9888
  this.isPending$ = this.httpManagerService.isPending$;
9281
- this.isStreamingPending$ = this.httpManagerService.isStreamingPending$;
9282
9889
  this.countdown$ = this.httpManagerService.countdown$;
9283
9890
  this.GET_error$ = new BehaviorSubject('');
9284
9891
  this.POST_error$ = new BehaviorSubject('');
@@ -9523,11 +10130,9 @@ class RequestManagerDemoComponent {
9523
10130
  reqParams.apiOptions.stream = true;
9524
10131
  reqParams.apiOptions.streamType = StreamType.NDJSON;
9525
10132
  this.requestParams.GET = reqParams.apiOptions;
9526
- this.STREAM$ = EMPTY; // Cancel any active stream before starting new one
9527
- this.STREAM_error$.next('');
9528
10133
  this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
9529
10134
  .pipe(tap((data) => {
9530
- // console.log("API STREAM response", data)
10135
+ console.log("API STREAM response", data);
9531
10136
  if (data && data.length > 0) {
9532
10137
  this.updateDisplayedColumns(data);
9533
10138
  }
@@ -9710,11 +10315,11 @@ class RequestManagerDemoComponent {
9710
10315
  return results.filter(r => r === undefined).length;
9711
10316
  }
9712
10317
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9713
- 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" }] }); }
10318
+ 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" }] }); }
9714
10319
  }
9715
10320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerDemoComponent, decorators: [{
9716
10321
  type: Component,
9717
- 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"] }]
10322
+ 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"] }]
9718
10323
  }], ctorParameters: () => [], propDecorators: { server: [{
9719
10324
  type: Input
9720
10325
  }], adapter: [{
@@ -12075,5 +12680,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12075
12680
  * Generated bundle index. Do not edit.
12076
12681
  */
12077
12682
 
12078
- export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, InvalidFileInfoModel, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsDemoComponent, LocalStorageSignalsManagerService, LoggerService, NotificationMessage, OperationResultModel, PathQueryService, PublicMessage, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateMessage, StateOperationResult, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StoreStateSignalsDemoComponent, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UploadDemoComponent, UploadValidationErrorModel, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
12683
+ export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, InvalidFileInfoModel, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsDemoComponent, LocalStorageSignalsManagerService, LoggerService, NormalizedRequestOptionsModel, NotificationMessage, OperationResultModel, PathQueryService, PathTrackerStateModel, PublicMessage, QueryParamsTrackerOptionsModel, QueryParamsTrackerService, QueryTrackerStateModel, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateMessage, StateOperationResult, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StoreStateSignalsDemoComponent, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UploadDemoComponent, UploadValidationErrorModel, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
12079
12684
  //# sourceMappingURL=http-request-manager.mjs.map