http-request-manager 18.13.0 → 18.13.2

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,7 +1,7 @@
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, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, take, scan, 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
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';
@@ -875,6 +875,382 @@ 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, trackedAt) {
880
+ this.baselineQuery = baselineQuery;
881
+ this.consumedValuesByKey = consumedValuesByKey;
882
+ this.watchExpiresAt = watchExpiresAt;
883
+ this.trackedAt = trackedAt;
884
+ }
885
+ static adapt(item) {
886
+ return new PathTrackerStateModel(item?.baselineQuery, item?.consumedValuesByKey, item?.watchExpiresAt, item?.trackedAt);
887
+ }
888
+ }
889
+
890
+ class QueryTrackerStateModel {
891
+ constructor(paths = {}) {
892
+ this.paths = paths;
893
+ }
894
+ static adapt(item) {
895
+ return new QueryTrackerStateModel(item?.paths
896
+ ? Object.keys(item.paths).reduce((acc, key) => {
897
+ acc[key] = PathTrackerStateModel.adapt(item.paths[key]);
898
+ return acc;
899
+ }, {})
900
+ : {});
901
+ }
902
+ }
903
+
904
+ const TRACKER_STORE_NAME = 'query_params_tracker';
905
+ const TRACKER_SESSION_INIT_KEY = 'query_params_tracker_initialized';
906
+ const DEFAULT_TRACKER_OPTIONS = SettingOptions.adapt({
907
+ storage: StorageType.GLOBAL,
908
+ encrypted: false,
909
+ });
910
+ class QueryParamsTrackerService {
911
+ constructor() {
912
+ this.state = { paths: {} };
913
+ this.stateRestored = false;
914
+ this.localStorageManager = inject(LocalStorageManagerService);
915
+ }
916
+ clearTracking(resetSessionInit = false) {
917
+ this.state = { paths: {} };
918
+ this.localStorageManager.deleteStore({ name: TRACKER_STORE_NAME });
919
+ if (resetSessionInit && this.hasSessionStorage()) {
920
+ sessionStorage.removeItem(TRACKER_SESSION_INIT_KEY);
921
+ }
922
+ }
923
+ checkRequestOptions(requestOptions = [], options = {}) {
924
+ this.ensureStateRestored();
925
+ const normalized = this.normalizeRequestOptions(requestOptions);
926
+ if (!normalized.pathKey)
927
+ return false;
928
+ this.cleanupExpiredEntries();
929
+ if (!normalized.hasQuery) {
930
+ const pathExists = !!this.state.paths[normalized.pathKey];
931
+ if (pathExists) {
932
+ return false;
933
+ }
934
+ const pathState = this.ensurePathState(normalized.pathKey);
935
+ pathState.trackedAt = Math.floor(Date.now() / 1000);
936
+ this.persistState();
937
+ return true;
938
+ }
939
+ if (options.mode === 'exact') {
940
+ return this.checkExact(normalized, options);
941
+ }
942
+ return this.checkVariation(normalized, options);
943
+ }
944
+ matchesPath(requestOptions = [], expectedPathOptions = []) {
945
+ this.ensureStateRestored();
946
+ const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
947
+ const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
948
+ return Boolean(requestPath) && requestPath === expectedPath;
949
+ }
950
+ checkExact(normalized, options) {
951
+ const pathState = this.ensurePathState(normalized.pathKey);
952
+ const filteredQuery = normalized.query;
953
+ if (Object.keys(filteredQuery).length === 0) {
954
+ return false;
955
+ }
956
+ if (!pathState.baselineQuery) {
957
+ pathState.baselineQuery = filteredQuery;
958
+ this.persistState();
959
+ return true;
960
+ }
961
+ return this.stringifyQuery(pathState.baselineQuery) === this.stringifyQuery(filteredQuery);
962
+ }
963
+ checkVariation(normalized, options) {
964
+ const pathState = this.ensurePathState(normalized.pathKey);
965
+ const filteredQuery = normalized.query;
966
+ const keys = Object.keys(filteredQuery);
967
+ if (keys.length === 0) {
968
+ return false;
969
+ }
970
+ this.resetPathStateIfExpired(pathState);
971
+ let accepted = false;
972
+ keys.forEach((key) => {
973
+ const currentValue = filteredQuery[key];
974
+ const consumed = pathState.consumedValuesByKey[key] || [];
975
+ if (!consumed.includes(currentValue)) {
976
+ pathState.consumedValuesByKey[key] = [...consumed, currentValue];
977
+ accepted = true;
978
+ }
979
+ });
980
+ if (accepted) {
981
+ if (!pathState.baselineQuery) {
982
+ pathState.baselineQuery = filteredQuery;
983
+ }
984
+ pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
985
+ this.persistState();
986
+ }
987
+ return accepted;
988
+ }
989
+ normalizeRequestOptions(requestOptions) {
990
+ const params = Array.isArray(requestOptions) ? requestOptions : [];
991
+ const pathSegments = [];
992
+ const queryObjects = [];
993
+ params.forEach((item) => {
994
+ if (this.isPlainObject(item)) {
995
+ queryObjects.push(item);
996
+ }
997
+ else if (typeof item !== 'undefined' && item !== null) {
998
+ const parsed = this.parsePathSegment(String(item));
999
+ if (parsed.pathSegment) {
1000
+ pathSegments.push(parsed.pathSegment);
1001
+ }
1002
+ if (Object.keys(parsed.queryObject).length > 0) {
1003
+ queryObjects.push(parsed.queryObject);
1004
+ }
1005
+ }
1006
+ });
1007
+ const query = queryObjects.reduce((acc, current) => {
1008
+ Object.keys(current).forEach((key) => {
1009
+ const normalizedKey = this.normalizeParamKey(key);
1010
+ const value = current[key];
1011
+ if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
1012
+ return;
1013
+ }
1014
+ acc[normalizedKey] = this.normalizeParamValue(value);
1015
+ });
1016
+ return acc;
1017
+ }, {});
1018
+ return {
1019
+ pathKey: this.normalizePath(pathSegments),
1020
+ query,
1021
+ hasQuery: Object.keys(query).length > 0,
1022
+ };
1023
+ }
1024
+ parsePathSegment(rawSegment) {
1025
+ const [pathPart, ...queryParts] = String(rawSegment).split('?');
1026
+ const queryString = queryParts.join('?');
1027
+ const queryObject = {};
1028
+ if (queryString) {
1029
+ queryString.split('&').forEach((pair) => {
1030
+ if (!pair)
1031
+ return;
1032
+ const [rawKey, ...rawValueParts] = pair.split('=');
1033
+ const key = this.safeDecode(rawKey).trim();
1034
+ const value = this.safeDecode(rawValueParts.join('=')).trim();
1035
+ if (!key)
1036
+ return;
1037
+ queryObject[key] = value;
1038
+ });
1039
+ }
1040
+ return {
1041
+ pathSegment: pathPart,
1042
+ queryObject,
1043
+ };
1044
+ }
1045
+ safeDecode(value) {
1046
+ if (typeof value === 'undefined') {
1047
+ return '';
1048
+ }
1049
+ try {
1050
+ return decodeURIComponent(value);
1051
+ }
1052
+ catch {
1053
+ return String(value);
1054
+ }
1055
+ }
1056
+ normalizePath(pathSegments) {
1057
+ return pathSegments
1058
+ .map((segment) => String(segment).trim())
1059
+ .filter((segment) => segment.length > 0)
1060
+ .join('/')
1061
+ .replace(/([^:]\/+)\/+/g, '$1')
1062
+ .replace(/^\//, '')
1063
+ .replace(/\/$/, '');
1064
+ }
1065
+ normalizeParamKey(key) {
1066
+ return String(key).trim().toLowerCase();
1067
+ }
1068
+ normalizeParamValue(value) {
1069
+ if (Array.isArray(value)) {
1070
+ return value.map((item) => this.normalizeParamValue(item)).join(',');
1071
+ }
1072
+ if (this.isPlainObject(value)) {
1073
+ return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
1074
+ acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
1075
+ return acc;
1076
+ }, {}));
1077
+ }
1078
+ return String(value).trim().toLowerCase();
1079
+ }
1080
+ buildExpiryEpoch(expireIn) {
1081
+ if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
1082
+ return undefined;
1083
+ }
1084
+ if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
1085
+ return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
1086
+ }
1087
+ const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
1088
+ const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
1089
+ if (!parsed) {
1090
+ return undefined;
1091
+ }
1092
+ const value = Number(parsed[1]);
1093
+ const unit = parsed[2];
1094
+ const toSeconds = {
1095
+ y: 31556926,
1096
+ w: 604800,
1097
+ d: 86400,
1098
+ hr: 3600,
1099
+ h: 3600,
1100
+ mn: 60,
1101
+ min: 60,
1102
+ m: 60,
1103
+ s: 1,
1104
+ };
1105
+ const seconds = toSeconds[unit];
1106
+ if (!seconds) {
1107
+ return undefined;
1108
+ }
1109
+ return Math.floor(Date.now() / 1000) + value * seconds;
1110
+ }
1111
+ resetPathStateIfExpired(pathState) {
1112
+ const now = Math.floor(Date.now() / 1000);
1113
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1114
+ pathState.consumedValuesByKey = {};
1115
+ pathState.watchExpiresAt = undefined;
1116
+ }
1117
+ }
1118
+ cleanupExpiredEntries() {
1119
+ const now = Math.floor(Date.now() / 1000);
1120
+ Object.keys(this.state.paths).forEach((pathKey) => {
1121
+ const pathState = this.state.paths[pathKey];
1122
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1123
+ pathState.consumedValuesByKey = {};
1124
+ pathState.watchExpiresAt = undefined;
1125
+ }
1126
+ const hasBaseline = Boolean(pathState.baselineQuery && Object.keys(pathState.baselineQuery).length > 0);
1127
+ const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
1128
+ const isNoQueryTracked = Boolean(pathState.trackedAt);
1129
+ if (!hasBaseline && !hasTrackedValues && !isNoQueryTracked) {
1130
+ delete this.state.paths[pathKey];
1131
+ }
1132
+ });
1133
+ }
1134
+ ensurePathState(pathKey) {
1135
+ if (!this.state.paths[pathKey]) {
1136
+ this.state.paths[pathKey] = {
1137
+ consumedValuesByKey: {},
1138
+ };
1139
+ }
1140
+ return this.state.paths[pathKey];
1141
+ }
1142
+ ensureStateRestored() {
1143
+ if (this.stateRestored) {
1144
+ return;
1145
+ }
1146
+ this.stateRestored = true;
1147
+ this.initializeTrackingForSession();
1148
+ this.restoreState();
1149
+ }
1150
+ initializeTrackingForSession() {
1151
+ if (!this.hasSessionStorage()) {
1152
+ return;
1153
+ }
1154
+ const initialized = sessionStorage.getItem(TRACKER_SESSION_INIT_KEY);
1155
+ if (initialized === '1') {
1156
+ return;
1157
+ }
1158
+ this.clearTracking();
1159
+ sessionStorage.setItem(TRACKER_SESSION_INIT_KEY, '1');
1160
+ }
1161
+ restoreState() {
1162
+ this.localStorageManager.store$(TRACKER_STORE_NAME)
1163
+ .pipe(take(1))
1164
+ .subscribe({
1165
+ next: (storedState) => {
1166
+ // Don't overwrite in-memory state if we've already tracked paths
1167
+ if (Object.keys(this.state.paths).length > 0) {
1168
+ return;
1169
+ }
1170
+ if (this.isTrackerState(storedState)) {
1171
+ this.state = QueryTrackerStateModel.adapt(storedState);
1172
+ this.cleanupExpiredEntries();
1173
+ return;
1174
+ }
1175
+ this.state = { paths: {} };
1176
+ },
1177
+ error: () => {
1178
+ if (Object.keys(this.state.paths).length === 0) {
1179
+ this.state = { paths: {} };
1180
+ }
1181
+ }
1182
+ });
1183
+ }
1184
+ persistState() {
1185
+ this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
1186
+ .pipe(take(1))
1187
+ .subscribe((exists) => {
1188
+ if (exists) {
1189
+ this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
1190
+ return;
1191
+ }
1192
+ this.localStorageManager.createStore({
1193
+ name: TRACKER_STORE_NAME,
1194
+ data: this.state,
1195
+ options: DEFAULT_TRACKER_OPTIONS,
1196
+ });
1197
+ });
1198
+ }
1199
+ stringifyQuery(query) {
1200
+ return JSON.stringify(Object.keys(query)
1201
+ .sort()
1202
+ .reduce((acc, key) => {
1203
+ acc[key] = query[key];
1204
+ return acc;
1205
+ }, {}));
1206
+ }
1207
+ isTrackerState(value) {
1208
+ return this.isPlainObject(value) && this.isPlainObject(value.paths);
1209
+ }
1210
+ isPlainObject(value) {
1211
+ return Object.prototype.toString.call(value) === '[object Object]';
1212
+ }
1213
+ hasSessionStorage() {
1214
+ try {
1215
+ return typeof sessionStorage !== 'undefined';
1216
+ }
1217
+ catch {
1218
+ return false;
1219
+ }
1220
+ }
1221
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1222
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
1223
+ }
1224
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
1225
+ type: Injectable,
1226
+ args: [{
1227
+ providedIn: 'root'
1228
+ }]
1229
+ }] });
1230
+
1231
+ class NormalizedRequestOptionsModel {
1232
+ constructor(pathKey = '', query = {}, hasQuery = false) {
1233
+ this.pathKey = pathKey;
1234
+ this.query = query;
1235
+ this.hasQuery = hasQuery;
1236
+ }
1237
+ static adapt(item) {
1238
+ return new NormalizedRequestOptionsModel(item?.pathKey, item?.query, item?.hasQuery);
1239
+ }
1240
+ }
1241
+
1242
+ class QueryParamsTrackerOptionsModel {
1243
+ constructor(mode, watchParams, watchExpiresAt, watchParamsExpire) {
1244
+ this.mode = mode;
1245
+ this.watchParams = watchParams;
1246
+ this.watchExpiresAt = watchExpiresAt;
1247
+ this.watchParamsExpire = watchParamsExpire;
1248
+ }
1249
+ static adapt(item) {
1250
+ return new QueryParamsTrackerOptionsModel(item?.mode, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt, item?.watchParamsExpire);
1251
+ }
1252
+ }
1253
+
878
1254
  /**
879
1255
  * Stateful processor for managing streaming data and events
880
1256
  */
@@ -883,28 +1259,66 @@ class StreamingProcessor {
883
1259
  this.buffer = '';
884
1260
  this.contentType = '';
885
1261
  this.maxBufferSize = 10 * 1024 * 1024; // 10MB limit
1262
+ this.lastParsedLength = 0; // Track parsed position to avoid duplicates
886
1263
  this.streamConfig = config || { streamType: StreamType.AI_STREAMING };
887
1264
  }
888
1265
  /**
889
- * Process HTTP events and extract streaming data
1266
+ * Process HTTP events and extract streaming data with progress
890
1267
  */
891
1268
  process(event) {
892
1269
  switch (event.type) {
893
1270
  case HttpEventType.ResponseHeader:
894
1271
  this.contentType = event.headers?.get('content-type') || '';
1272
+ // Read total from response header if configured
1273
+ if (this.streamConfig.totalHeader && event.headers) {
1274
+ const totalVal = event.headers.get(this.streamConfig.totalHeader);
1275
+ if (totalVal !== undefined && totalVal !== null) {
1276
+ const parsed = parseInt(totalVal, 10);
1277
+ if (!isNaN(parsed)) {
1278
+ this.totalFromHeader = parsed;
1279
+ }
1280
+ }
1281
+ }
895
1282
  return null;
896
1283
  case HttpEventType.DownloadProgress:
897
1284
  if (event.partialText) {
898
1285
  this.appendToBuffer(event.partialText);
899
1286
  const parsedData = this.parseBuffer();
900
- return parsedData.length > 0 ? [parsedData[0]] : null;
1287
+ // Only return NEW items since last parse (progressive updates)
1288
+ const newItems = parsedData.slice(this.lastParsedLength);
1289
+ this.lastParsedLength = parsedData.length;
1290
+ // Calculate progress
1291
+ const progress = {
1292
+ received: this.lastParsedLength,
1293
+ total: this.totalFromHeader,
1294
+ percent: this.totalFromHeader
1295
+ ? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
1296
+ : 0,
1297
+ stage: 'streaming'
1298
+ };
1299
+ return {
1300
+ data: newItems.length > 0 ? newItems : [],
1301
+ progress
1302
+ };
901
1303
  }
902
1304
  return null;
903
1305
  case HttpEventType.Response:
904
1306
  if (event.body) {
905
1307
  this.appendToBuffer(event.body);
906
1308
  const parsedData = this.parseBuffer();
907
- return parsedData.length > 0 ? parsedData : null;
1309
+ // Calculate final progress
1310
+ const progress = {
1311
+ received: parsedData.length,
1312
+ total: this.totalFromHeader,
1313
+ percent: this.totalFromHeader
1314
+ ? Math.round((parsedData.length / this.totalFromHeader) * 100)
1315
+ : 100,
1316
+ stage: 'complete'
1317
+ };
1318
+ return {
1319
+ data: parsedData.length > 0 ? parsedData : [],
1320
+ progress
1321
+ };
908
1322
  }
909
1323
  return null;
910
1324
  default:
@@ -943,6 +1357,8 @@ class StreamingProcessor {
943
1357
  reset() {
944
1358
  this.buffer = '';
945
1359
  this.contentType = '';
1360
+ this.lastParsedLength = 0;
1361
+ this.totalFromHeader = undefined;
946
1362
  }
947
1363
  /**
948
1364
  * Update configuration
@@ -2793,19 +3209,6 @@ class UploadValidationErrorModel {
2793
3209
  }
2794
3210
  }
2795
3211
 
2796
- class UserData {
2797
- constructor(ldap = '', name = '', email = '', color = RandomPaletteColor()) {
2798
- this.ldap = ldap;
2799
- this.name = name;
2800
- this.email = email;
2801
- this.color = color;
2802
- }
2803
- static adapt(item) {
2804
- const userName = `${item?.first_name} ${item?.last_name}`;
2805
- return new UserData(item?.ldap, userName, item?.email, item?.color);
2806
- }
2807
- }
2808
-
2809
3212
  class RequestService extends WebsocketService {
2810
3213
  constructor() {
2811
3214
  super(...arguments);
@@ -2816,6 +3219,13 @@ class RequestService extends WebsocketService {
2816
3219
  this.isPending$ = this.isPending.asObservable();
2817
3220
  this.progress = new BehaviorSubject(0);
2818
3221
  this.progress$ = this.progress.asObservable();
3222
+ this.streamProgress = new BehaviorSubject({
3223
+ received: 0,
3224
+ total: undefined,
3225
+ percent: 0,
3226
+ stage: 'connecting'
3227
+ });
3228
+ this.streamProgress$ = this.streamProgress.asObservable();
2819
3229
  }
2820
3230
  // Implementation
2821
3231
  getRecordRequest(options) {
@@ -2884,7 +3294,12 @@ class RequestService extends WebsocketService {
2884
3294
  }
2885
3295
  requestStreaming(options) {
2886
3296
  return (source$) => {
2887
- return source$.pipe(map(data => {
3297
+ return source$.pipe(tap(output => {
3298
+ // Update progress from stream output
3299
+ this.progress.next(output.progress.received);
3300
+ this.streamProgress.next(output.progress);
3301
+ }), map(output => {
3302
+ const data = output.data;
2888
3303
  if (!data || (Array.isArray(data) && data.length === 0)) {
2889
3304
  return data;
2890
3305
  }
@@ -3586,6 +4001,7 @@ class HTTPManagerService extends RequestService {
3586
4001
  this.data$ = this.data.asObservable();
3587
4002
  this.polling$ = new Subject();
3588
4003
  this.config = ApiRequest.adapt();
4004
+ this.streamProgress$ = this.streamProgress.asObservable();
3589
4005
  this.config = (configOptions) ? ApiRequest.adapt(configOptions.httpRequestOptions) : this.config;
3590
4006
  }
3591
4007
  // ═══════════════════════════════════════════════════════════════════════════
@@ -3722,6 +4138,12 @@ class HTTPManagerService extends RequestService {
3722
4138
  getRequest(options, params) {
3723
4139
  this.isPending.next(true);
3724
4140
  this.data.next(null);
4141
+ this.streamProgress.next({
4142
+ received: 0,
4143
+ total: undefined,
4144
+ percent: 0,
4145
+ stage: 'connecting'
4146
+ });
3725
4147
  const updatedOptions = this.defineReqOptions(options, params);
3726
4148
  const func = this.getRecordRequest;
3727
4149
  const requests = this.createRequest(func, updatedOptions);
@@ -3730,9 +4152,19 @@ class HTTPManagerService extends RequestService {
3730
4152
  this.data.next(data);
3731
4153
  if (updatedOptions.displaySuccess)
3732
4154
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
3733
- })).pipe(finalize(() => this.isPending.next(false)), catchError((err) => {
4155
+ }), finalize(() => {
4156
+ this.streamProgress.next({
4157
+ ...this.streamProgress.value,
4158
+ stage: 'complete'
4159
+ });
4160
+ this.isPending.next(false);
4161
+ }), catchError((err) => {
3734
4162
  if (updatedOptions.displayError)
3735
4163
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
4164
+ this.streamProgress.next({
4165
+ ...this.streamProgress.value,
4166
+ stage: 'error'
4167
+ });
3736
4168
  this.isPending.next(false);
3737
4169
  return this.handleError(err);
3738
4170
  }));
@@ -4154,7 +4586,8 @@ class RequestSignalsService extends WebsocketService {
4154
4586
  }
4155
4587
  requestStreamingOperator(options) {
4156
4588
  return (source$) => {
4157
- return source$.pipe(map(data => {
4589
+ return source$.pipe(map(output => {
4590
+ const data = output.data;
4158
4591
  if (!data || (Array.isArray(data) && data.length === 0)) {
4159
4592
  return data;
4160
4593
  }
@@ -4891,12 +5324,15 @@ class ApiRequest {
4891
5324
  }
4892
5325
 
4893
5326
  class RequestOptions {
4894
- constructor(path = [], headers = {}) {
5327
+ constructor(path = [], headers = {}, forceRefresh, watchParams, watchExpiresAt) {
4895
5328
  this.path = path;
4896
5329
  this.headers = headers;
5330
+ this.forceRefresh = forceRefresh;
5331
+ this.watchParams = watchParams;
5332
+ this.watchExpiresAt = watchExpiresAt;
4897
5333
  }
4898
5334
  static adapt(item) {
4899
- return new RequestOptions(item?.path, item?.headers);
5335
+ return new RequestOptions(item?.path, item?.headers, item?.forceRefresh, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt);
4900
5336
  }
4901
5337
  }
4902
5338
 
@@ -5691,8 +6127,8 @@ class DbService extends Dexie {
5691
6127
  console.warn('Database upgrade blocked! Please close other tabs/windows of this app.');
5692
6128
  });
5693
6129
  this.on('versionchange', () => {
5694
- console.warn('Database version changed in another tab. Reloading...');
5695
- window.location.reload();
6130
+ console.warn('Database version changed in another tab. Closing current database connection to allow upgrade.');
6131
+ this.close();
5696
6132
  });
5697
6133
  this.dbReady = this.init();
5698
6134
  }
@@ -5772,9 +6208,12 @@ class DbService extends Dexie {
5772
6208
  await this.dbReady;
5773
6209
  const safeTableName = this.cleanTableName(tableName);
5774
6210
  const safeSchema = schema.trim();
5775
- if (this.tableExists(safeTableName))
5776
- return;
5777
6211
  const currentSchema = this.getCurrentSchema();
6212
+ const existingSchema = currentSchema[safeTableName]?.trim();
6213
+ // No-op only when table already exists and schema is identical.
6214
+ if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
6215
+ return;
6216
+ }
5778
6217
  console.log('Current Schema before update:', currentSchema);
5779
6218
  currentSchema[safeTableName] = safeSchema;
5780
6219
  const nextVersion = this.verno + 1;
@@ -5788,6 +6227,7 @@ class DbService extends Dexie {
5788
6227
  const created = this.tables.some(t => t.name === safeTableName);
5789
6228
  if (!created) {
5790
6229
  console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
6230
+ throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
5791
6231
  }
5792
6232
  else {
5793
6233
  console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
@@ -5795,15 +6235,24 @@ class DbService extends Dexie {
5795
6235
  }
5796
6236
  catch (err) {
5797
6237
  console.error('Error opening database after schema update:', err);
6238
+ throw err;
5798
6239
  }
5799
6240
  }
5800
6241
  async DBOpened() {
6242
+ try {
6243
+ await this.dbReady;
6244
+ }
6245
+ catch (err) {
6246
+ console.error('DBOpened: init failed', err);
6247
+ return false;
6248
+ }
5801
6249
  if (!this.isOpen()) {
5802
6250
  try {
5803
6251
  await this.open();
5804
6252
  return true;
5805
6253
  }
5806
6254
  catch (err) {
6255
+ console.error('DBOpened: open failed', err);
5807
6256
  return false;
5808
6257
  }
5809
6258
  }
@@ -5898,7 +6347,11 @@ class DatabaseManagerService extends DbService {
5898
6347
  }));
5899
6348
  }
5900
6349
  createDatabaseTable(tableDef) {
5901
- return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.DBOpened()));
6350
+ const tableName = this.cleanTableName(tableDef.table);
6351
+ return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
6352
+ console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
6353
+ return of(false);
6354
+ }));
5902
6355
  }
5903
6356
  updateDatabaseTableSchema(tableDef) {
5904
6357
  return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
@@ -5945,26 +6398,23 @@ class DatabaseManagerService extends DbService {
5945
6398
  }
5946
6399
  createTableRecords(table, records) {
5947
6400
  const tableName = this.cleanTableName(table);
5948
- return from(this.DBOpened()).pipe(switchMap(() => {
6401
+ return from(this.DBOpened()).pipe(switchMap((opened) => {
6402
+ if (!opened) {
6403
+ console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
6404
+ return EMPTY;
6405
+ }
5949
6406
  if (!this.tables.some(t => t.name === tableName)) {
5950
6407
  console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
5951
6408
  return EMPTY;
5952
6409
  }
5953
6410
  const tableInstance = this.table(tableName);
5954
- // Extract schema fields directly from the table instance
5955
- const schema = tableInstance.schema;
5956
- const validFields = [
5957
- schema.primKey.name,
5958
- ...schema.indexes.map(idx => idx.name)
5959
- ];
6411
+ // Keep full object payload; Dexie stores non-indexed properties as well.
5960
6412
  const insertRecords = records.map((record) => {
5961
- const objectFromSchema = {};
5962
- validFields.forEach((field) => {
5963
- if (field) {
5964
- objectFromSchema[field] = record[field] ?? null;
5965
- }
5966
- });
5967
- return objectFromSchema;
6413
+ const payload = { ...(record || {}) };
6414
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6415
+ delete payload.id;
6416
+ }
6417
+ return payload;
5968
6418
  });
5969
6419
  console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
5970
6420
  return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
@@ -5978,17 +6428,14 @@ class DatabaseManagerService extends DbService {
5978
6428
  updateTableRecords(table, records) {
5979
6429
  const tableName = this.cleanTableName(table);
5980
6430
  return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
5981
- return this.getDatabaseTableSchema(tableName).pipe(switchMap((schema) => {
5982
- const insertRecords = records.map((record) => {
5983
- const objectFromSchema = {};
5984
- schema.forEach((field) => {
5985
- const data = record[field] ?? null;
5986
- objectFromSchema[field] = data;
5987
- });
5988
- return objectFromSchema;
5989
- });
5990
- return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
5991
- }));
6431
+ const insertRecords = records.map((record) => {
6432
+ const payload = { ...(record || {}) };
6433
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6434
+ delete payload.id;
6435
+ }
6436
+ return payload;
6437
+ });
6438
+ return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
5992
6439
  }));
5993
6440
  }
5994
6441
  deleteTableRecord(table, id) {
@@ -6090,6 +6537,7 @@ class HTTPManagerStateService extends ComponentStore {
6090
6537
  this.httpManagerService = inject(HTTPManagerService);
6091
6538
  this.dbManagerService = inject(DatabaseManagerService);
6092
6539
  this.localStorageManagerService = inject(LocalStorageManagerService);
6540
+ this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6093
6541
  this.utils = inject(UtilsService);
6094
6542
  this.logger = inject(LoggerService);
6095
6543
  this.error$ = this.httpManagerService.error$;
@@ -6106,6 +6554,16 @@ class HTTPManagerStateService extends ComponentStore {
6106
6554
  this.hasDatabase = false;
6107
6555
  this.streamedResponse = [];
6108
6556
  this.shouldRetry = true;
6557
+ this.volatileHeaders = new Set([
6558
+ 'authorization',
6559
+ 'x-request-id',
6560
+ 'x-correlation-id',
6561
+ 'x-trace-id',
6562
+ 'x-amzn-trace-id',
6563
+ 'date',
6564
+ 'if-none-match'
6565
+ ]);
6566
+ this.requestSignatureCache = {};
6109
6567
  this.wsRetryAttempts = new BehaviorSubject(0);
6110
6568
  this.wsRetryAttempts$ = this.wsRetryAttempts.asObservable();
6111
6569
  this.messages = new BehaviorSubject([]);
@@ -6375,22 +6833,25 @@ class HTTPManagerStateService extends ComponentStore {
6375
6833
  }
6376
6834
  }))))));
6377
6835
  this.initDBStorage = this.effect((trigger$) => trigger$.pipe(tap(() => {
6836
+ console.log('[initDBStorage effect] Triggered, checking conditions:', {
6837
+ dataType: this.dataType,
6838
+ isARRAY: this.dataType === DataType.ARRAY,
6839
+ hasAdapter: !!this.apiOptions?.adapter,
6840
+ hasTable: !!this.databaseOptions?.table,
6841
+ tableValue: this.databaseOptions?.table
6842
+ });
6378
6843
  if (this.dataType !== DataType.ARRAY)
6379
6844
  console.warn('Database storage requires dataType to be ARRAY');
6380
6845
  if (!this.apiOptions.adapter)
6381
- console.warn('Database storage requires an adapter to define the data shape');
6846
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
6382
6847
  if (this.databaseOptions && this.databaseOptions?.table === '')
6383
6848
  console.warn('Database storage requires a table name');
6384
- }), filter(() => this.dataType === DataType.ARRAY && !!this.apiOptions.adapter && !!this.databaseOptions?.table), switchMap(() => {
6385
- const sampleData = this.apiOptions.adapter?.({}) || {};
6386
- const schemaKeys = Object.keys(sampleData).filter(key => sampleData[key] !== undefined);
6387
- let schema = '++id';
6388
- if (schemaKeys.length > 0) {
6389
- const otherKeys = schemaKeys.filter(k => k !== 'id');
6390
- if (otherKeys.length > 0) {
6391
- schema += ', ' + otherKeys.join(', ');
6392
- }
6393
- }
6849
+ }), filter(() => {
6850
+ const shouldProceed = this.dataType === DataType.ARRAY && !!this.databaseOptions?.table;
6851
+ console.log('[initDBStorage effect] Filter result:', shouldProceed);
6852
+ return shouldProceed;
6853
+ }), switchMap(() => {
6854
+ const schema = this.buildSchemaFromAdapter();
6394
6855
  const tableDef = TableSchemaDef.adapt({
6395
6856
  table: this.databaseOptions?.table,
6396
6857
  schema: schema
@@ -6479,6 +6940,7 @@ class HTTPManagerStateService extends ComponentStore {
6479
6940
  }
6480
6941
  }), concatMap(() => {
6481
6942
  if (this.hasDatabase && this.databaseOptions?.table) {
6943
+ this.clearRequestCacheMetadata(this.databaseOptions.table);
6482
6944
  const currentData = this.get()?.data;
6483
6945
  const idsToDelete = Array.isArray(currentData) ? currentData.map((r) => r.id) : [];
6484
6946
  if (idsToDelete.length > 0) {
@@ -6493,35 +6955,81 @@ class HTTPManagerStateService extends ComponentStore {
6493
6955
  this.fetchRecords = (options) => this.effect(() => of(RequestOptions.adapt(options)).pipe(switchMap(() => {
6494
6956
  this.streamedResponse = [];
6495
6957
  const requestOptions = this.updateRequestOptions(options?.headers);
6958
+ const effectiveParams = this.getEffectiveParams(options?.path);
6959
+ const requestSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
6496
6960
  const fetchFromAPI = () => {
6497
- return this.httpManagerService.getRequest(requestOptions, options?.path).pipe(tap((data) => {
6498
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
6961
+ if (this.hasDatabase && this.databaseOptions?.table) {
6962
+ this.setCachedRequestSignature(this.databaseOptions.table, 'GET', requestSignature);
6963
+ }
6964
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(tap((data) => {
6965
+ // Extract array from paginated response if needed
6966
+ const arrayData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6967
+ data = (!arrayData) ? (this.dataType === DataType.ARRAY) ? [] : {} : arrayData;
6499
6968
  this.setData$(data);
6500
6969
  }), concatMap((data) => {
6501
- if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(data) && data.length > 0) {
6970
+ // Extract array from paginated response for database storage
6971
+ const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6972
+ if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
6973
+ const tableName = this.databaseOptions.table;
6502
6974
  this.localStorageManagerService.updateStore({
6503
- name: this.databaseOptions.table,
6975
+ name: tableName,
6504
6976
  data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
6505
6977
  });
6506
- return this.dbManagerService.createTableRecords(this.databaseOptions.table, data);
6978
+ const schema = this.buildSchemaFromSample(dbData[0]);
6979
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
6980
+ const schemaSignature = this.buildSchemaSignature(schema);
6981
+ // Always ensure table exists immediately before writing to avoid stale schema/store races.
6982
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
6983
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6984
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
6985
+ const ensureTable$ = schemaChanged
6986
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
6987
+ : this.dbManagerService.createDatabaseTable(tableDef);
6988
+ return ensureTable$.pipe(switchMap((created) => {
6989
+ if (!created) {
6990
+ console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
6991
+ return of(data);
6992
+ }
6993
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'GET', requestSignature, schemaSignature, options)));
6994
+ }));
6995
+ }), catchError((error) => {
6996
+ console.error('[DB STORAGE] Failed to ensure table and write records:', { table: tableName, schema, error });
6997
+ return of(data);
6998
+ }));
6507
6999
  }
6508
7000
  return of(data);
6509
7001
  }));
6510
7002
  };
7003
+ console.log('[DB STORAGE] Checking database storage:', {
7004
+ hasDatabase: this.hasDatabase,
7005
+ table: this.databaseOptions?.table,
7006
+ databaseOptions: this.databaseOptions
7007
+ });
6511
7008
  if (this.hasDatabase && this.databaseOptions?.table) {
6512
7009
  return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
6513
7010
  if (!dbExists) {
6514
7011
  const initObs = this.initDBStorageAsync();
6515
- return initObs.pipe(switchMap(() => fetchFromAPI()));
7012
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7013
+ console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7014
+ return fetchFromAPI();
7015
+ }));
6516
7016
  }
6517
7017
  return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
6518
7018
  if (!tableExists) {
6519
7019
  const initObs = this.initDBStorageAsync();
6520
- return initObs.pipe(switchMap(() => fetchFromAPI()));
7020
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7021
+ console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7022
+ return fetchFromAPI();
7023
+ }));
6521
7024
  }
6522
7025
  return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7026
+ const forceRefresh = !!options?.forceRefresh;
7027
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6523
7028
  const expires = storeData?.expires || 0;
6524
7029
  const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7030
+ if (forceRefresh) {
7031
+ return fetchFromAPI();
7032
+ }
6525
7033
  if (hasExpired) {
6526
7034
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()), tap(() => {
6527
7035
  this.localStorageManagerService.updateStore({
@@ -6530,12 +7038,41 @@ class HTTPManagerStateService extends ComponentStore {
6530
7038
  });
6531
7039
  }));
6532
7040
  }
7041
+ const expectedSchema = this.buildSchemaFromAdapter();
7042
+ const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7043
+ if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7044
+ const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7045
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7046
+ }
7047
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7048
+ const shouldMakeRequest = trackerAllowsRequest;
7049
+ if (shouldMakeRequest) {
7050
+ return fetchFromAPI();
7051
+ }
6533
7052
  return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
6534
7053
  if (Array.isArray(dbData) && dbData.length > 0) {
6535
7054
  this.setData$(dbData);
6536
7055
  return of(dbData);
6537
7056
  }
6538
- return fetchFromAPI();
7057
+ const currentStateData = this.get()?.data;
7058
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7059
+ return of(currentStateData);
7060
+ }
7061
+ if (currentStateData &&
7062
+ !Array.isArray(currentStateData) &&
7063
+ Object.keys(currentStateData).length > 0) {
7064
+ return of(currentStateData);
7065
+ }
7066
+ return of(this.dataType === DataType.ARRAY ? [] : {});
7067
+ }), catchError((error) => {
7068
+ const tableName = this.databaseOptions.table;
7069
+ console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7070
+ const schema = this.buildSchemaFromAdapter();
7071
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7072
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7073
+ console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7074
+ return fetchFromAPI();
7075
+ }));
6539
7076
  }));
6540
7077
  }));
6541
7078
  }));
@@ -6659,7 +7196,7 @@ class HTTPManagerStateService extends ComponentStore {
6659
7196
  if (res.length > 0)
6660
7197
  this.setData$(res);
6661
7198
  this.streamedResponse = res;
6662
- }), scan((acc, res) => {
7199
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), scan((acc, res) => {
6663
7200
  const previous = acc.current;
6664
7201
  const current = res;
6665
7202
  return { previous, current };
@@ -6679,8 +7216,70 @@ class HTTPManagerStateService extends ComponentStore {
6679
7216
  }), switchMap((options) => {
6680
7217
  const requestOptions = this.updateRequestOptions(options?.headers);
6681
7218
  requestOptions.stream = true;
7219
+ const effectiveParams = this.getEffectiveParams(options?.path);
6682
7220
  console.log('[DEBUG] Making streaming request:', requestOptions);
6683
- return this.httpManagerService.getRequest(requestOptions, options?.path)
7221
+ if (this.hasDatabase && this.databaseOptions?.table) {
7222
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7223
+ return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7224
+ const forceRefresh = !!options?.forceRefresh;
7225
+ const expires = storeData?.expires || 0;
7226
+ const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7227
+ if (forceRefresh) {
7228
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7229
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7230
+ }
7231
+ if (hasExpired) {
7232
+ 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 })));
7233
+ }
7234
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7235
+ const shouldMakeRequest = trackerAllowsRequest;
7236
+ if (!shouldMakeRequest) {
7237
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7238
+ if (Array.isArray(dbData) && dbData.length > 0) {
7239
+ return of({ data: dbData, fromCache: true });
7240
+ }
7241
+ const currentStateData = this.get()?.data;
7242
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7243
+ return of({ data: currentStateData, fromCache: true });
7244
+ }
7245
+ if (currentStateData &&
7246
+ !Array.isArray(currentStateData) &&
7247
+ Object.keys(currentStateData).length > 0) {
7248
+ return of({ data: currentStateData, fromCache: true });
7249
+ }
7250
+ return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7251
+ }));
7252
+ }
7253
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7254
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7255
+ })).pipe(tap((packet) => {
7256
+ const res = packet?.data;
7257
+ console.log('[DEBUG] Streaming response received:', res);
7258
+ if (res && res.length > 0) {
7259
+ console.log('[DEBUG] Updating state with streaming data:', res);
7260
+ this.setData$(res);
7261
+ this.streamedResponse = res;
7262
+ }
7263
+ else {
7264
+ console.log('[DEBUG] No streaming data or empty array:', res);
7265
+ }
7266
+ // Reset pending once we have a response packet (cache or network)
7267
+ this.httpManagerService.isPending.next(false);
7268
+ }), concatMap((packet) => {
7269
+ if (packet?.fromCache) {
7270
+ return of(packet?.data);
7271
+ }
7272
+ return this.persistStreamDataToDb(packet?.data, options);
7273
+ }), map((res) => {
7274
+ console.log('[DEBUG] Returning data to subscribers:', res);
7275
+ return res;
7276
+ }), catchError((error) => {
7277
+ console.error('[DEBUG] Streaming error:', error);
7278
+ this.httpManagerService.isPending.next(false);
7279
+ return of([]);
7280
+ }));
7281
+ }
7282
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams)
6684
7283
  .pipe(tap((res) => {
6685
7284
  console.log('[DEBUG] Streaming response received:', res);
6686
7285
  // Always update state with streaming data
@@ -6692,7 +7291,9 @@ class HTTPManagerStateService extends ComponentStore {
6692
7291
  else {
6693
7292
  console.log('[DEBUG] No streaming data or empty array:', res);
6694
7293
  }
6695
- }), map((res) => {
7294
+ // Reset pending once we have a response packet
7295
+ this.httpManagerService.isPending.next(false);
7296
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), map((res) => {
6696
7297
  console.log('[DEBUG] Returning data to subscribers:', res);
6697
7298
  return res; // Return the data so subscribers can receive it
6698
7299
  }), catchError((error) => {
@@ -6719,7 +7320,11 @@ class HTTPManagerStateService extends ComponentStore {
6719
7320
  encrypted: false,
6720
7321
  })
6721
7322
  });
6722
- this.initDBStorage();
7323
+ // Use initDBStorageAsync directly - the effect initDBStorage requires subscription
7324
+ this.initDBStorageAsync().subscribe({
7325
+ next: () => console.log('[Constructor] Database storage initialized'),
7326
+ error: (err) => console.error('[Constructor] Database storage initialization failed:', err)
7327
+ });
6723
7328
  }
6724
7329
  }
6725
7330
  catch (error) {
@@ -6773,8 +7378,24 @@ class HTTPManagerStateService extends ComponentStore {
6773
7378
  this.dataType = (dataType) ? dataType : DataType.ARRAY;
6774
7379
  // Only update database options if a database parameter is explicitly provided
6775
7380
  if (database !== undefined) {
7381
+ console.log('[setApiRequestOptions] Database config:', {
7382
+ database,
7383
+ hasTable: !!database?.table,
7384
+ tableValue: database?.table
7385
+ });
6776
7386
  this.hasDatabase = (database?.table) ? true : false;
6777
- this.databaseOptions = (this.hasDatabase) ? DatabaseStorage.adapt(database) : undefined;
7387
+ const adapted = DatabaseStorage.adapt(database);
7388
+ console.log('[setApiRequestOptions] DatabaseStorage.adapt result:', adapted);
7389
+ this.databaseOptions = (this.hasDatabase) ? adapted : undefined;
7390
+ // Trigger database table creation if table is configured
7391
+ if (this.hasDatabase && this.databaseOptions?.table) {
7392
+ console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table);
7393
+ // Use initDBStorageAsync directly instead of the effect - effects need subscription to run
7394
+ this.initDBStorageAsync().subscribe({
7395
+ next: () => console.log('[setApiRequestOptions] Database storage initialized successfully'),
7396
+ error: (err) => console.error('[setApiRequestOptions] Database storage initialization failed:', err)
7397
+ });
7398
+ }
6778
7399
  }
6779
7400
  if (this.apiOptions.ws && this.apiOptions.ws.id !== '') {
6780
7401
  // Auto-prefix channel ID for private state manager channels
@@ -6896,11 +7517,20 @@ class HTTPManagerStateService extends ComponentStore {
6896
7517
  this.setData$(data);
6897
7518
  }
6898
7519
  updateArrayState(currentData, newData) {
7520
+ // For non-streaming requests (GET), REPLACE data entirely
7521
+ // For streaming requests, MERGE with existing data incrementally
7522
+ if (this.streamedResponse.length === 0) {
7523
+ // GET request: return new data as-is (replace)
7524
+ return newData;
7525
+ }
7526
+ // Streaming: merge with existing data
6899
7527
  const filterCurrentData = () => {
6900
7528
  const ids = this.streamedResponse.map((obj) => obj.id);
6901
7529
  return currentData.filter(obj => (obj.id) ? ids.includes(obj.id) : obj);
6902
7530
  };
6903
- const filteredCurrentData = (this.httpManagerService.isPending.value) ? currentData : filterCurrentData();
7531
+ const filteredCurrentData = (this.httpManagerService.isPending.value)
7532
+ ? currentData
7533
+ : filterCurrentData();
6904
7534
  const updatedData = filteredCurrentData.map(item => {
6905
7535
  const newItem = newData.find(newItem => {
6906
7536
  const hasId = (newItem?.id && item?.id) ? true : false;
@@ -6917,32 +7547,92 @@ class HTTPManagerStateService extends ComponentStore {
6917
7547
  return [...updatedData, ...addedData];
6918
7548
  }
6919
7549
  initDBStorageAsync() {
7550
+ console.log('[initDBStorageAsync] Starting initialization:', {
7551
+ dataType: this.dataType,
7552
+ hasAdapter: !!this.apiOptions?.adapter,
7553
+ table: this.databaseOptions?.table,
7554
+ databaseOptions: this.databaseOptions
7555
+ });
6920
7556
  if (this.dataType !== DataType.ARRAY) {
6921
7557
  console.warn('Database storage requires dataType to be ARRAY');
6922
7558
  return of(null);
6923
7559
  }
6924
7560
  if (!this.apiOptions.adapter) {
6925
- console.warn('Database storage requires an adapter to define the data shape');
6926
- return of(null);
7561
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
6927
7562
  }
6928
7563
  if (!this.databaseOptions?.table) {
6929
7564
  console.warn('Database storage requires a table name');
6930
7565
  return of(null);
6931
7566
  }
7567
+ const schema = this.buildSchemaFromAdapter();
7568
+ const tableDef = TableSchemaDef.adapt({
7569
+ table: this.databaseOptions?.table,
7570
+ schema: schema
7571
+ });
7572
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(tap((created) => {
7573
+ if (created && this.databaseOptions?.table) {
7574
+ this.saveSchemaSignature(this.databaseOptions.table, this.buildSchemaSignature(schema));
7575
+ }
7576
+ }));
7577
+ }
7578
+ buildSchemaFromAdapter() {
6932
7579
  const sampleData = this.apiOptions.adapter?.({}) || {};
6933
- const schemaKeys = Object.keys(sampleData).filter(key => sampleData[key] !== undefined);
7580
+ return this.buildSchemaFromSample(sampleData);
7581
+ }
7582
+ buildSchemaFromSample(sampleData) {
7583
+ const schemaKeys = Object.keys(sampleData || {}).filter(key => sampleData[key] !== undefined);
6934
7584
  let schema = '++id';
6935
7585
  if (schemaKeys.length > 0) {
6936
- const otherKeys = schemaKeys.filter(k => k !== 'id');
7586
+ const otherKeys = schemaKeys.filter((k) => k !== 'id');
6937
7587
  if (otherKeys.length > 0) {
6938
7588
  schema += ', ' + otherKeys.join(', ');
6939
7589
  }
6940
7590
  }
6941
- const tableDef = TableSchemaDef.adapt({
6942
- table: this.databaseOptions?.table,
6943
- schema: schema
6944
- });
6945
- return this.dbManagerService.createDatabaseTable(tableDef);
7591
+ return schema;
7592
+ }
7593
+ persistStreamDataToDb(payload, streamOptions) {
7594
+ if (!this.hasDatabase || !this.databaseOptions?.table) {
7595
+ return of(payload);
7596
+ }
7597
+ const dbData = (payload?.results && Array.isArray(payload.results))
7598
+ ? payload.results
7599
+ : Array.isArray(payload)
7600
+ ? payload
7601
+ : payload
7602
+ ? [payload]
7603
+ : [];
7604
+ if (dbData.length === 0) {
7605
+ return of(payload);
7606
+ }
7607
+ const tableName = this.databaseOptions.table;
7608
+ const requestOptions = this.updateRequestOptions(streamOptions?.headers);
7609
+ requestOptions.stream = true;
7610
+ const effectiveParams = this.getEffectiveParams(streamOptions?.path);
7611
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7612
+ this.localStorageManagerService.updateStore({
7613
+ name: tableName,
7614
+ data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7615
+ });
7616
+ const schema = this.buildSchemaFromSample(dbData[0]);
7617
+ const schemaSignature = this.buildSchemaSignature(schema);
7618
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7619
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
7620
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7621
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7622
+ const ensureTable$ = schemaChanged
7623
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
7624
+ : this.dbManagerService.createDatabaseTable(tableDef);
7625
+ return ensureTable$.pipe(switchMap((created) => {
7626
+ if (!created) {
7627
+ console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
7628
+ return of(payload);
7629
+ }
7630
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'STREAM', requestSignature, schemaSignature, streamOptions)), map(() => payload));
7631
+ }));
7632
+ }), map(() => payload), catchError((error) => {
7633
+ console.error('[DB STORAGE] Failed to persist streaming payload:', { table: tableName, schema, error });
7634
+ return of(payload);
7635
+ }));
6946
7636
  }
6947
7637
  // WEBSOCKET COMMUNICATION (STATE MANAGER)
6948
7638
  wsCommunication(method, path) {
@@ -7238,6 +7928,7 @@ class HTTPManagerStateService extends ComponentStore {
7238
7928
  const tableName = this.databaseOptions.table;
7239
7929
  this.dbManagerService.clearTable(tableName).subscribe({
7240
7930
  next: () => {
7931
+ this.clearRequestCacheMetadata(tableName);
7241
7932
  if (this.dataType === DataType.ARRAY) {
7242
7933
  this.setData$([]);
7243
7934
  }
@@ -7260,6 +7951,132 @@ class HTTPManagerStateService extends ComponentStore {
7260
7951
  : { ...options.headers };
7261
7952
  return options;
7262
7953
  }
7954
+ buildQueryTrackerOptions(options) {
7955
+ return {
7956
+ watchParams: Array.isArray(options?.watchParams) ? options.watchParams : [],
7957
+ watchExpiresAt: options?.watchExpiresAt,
7958
+ };
7959
+ }
7960
+ normalizeObject(value) {
7961
+ if (Array.isArray(value)) {
7962
+ return value.map((item) => this.normalizeObject(item));
7963
+ }
7964
+ if (value && typeof value === 'object') {
7965
+ return Object.keys(value)
7966
+ .sort()
7967
+ .reduce((acc, key) => {
7968
+ acc[key] = this.normalizeObject(value[key]);
7969
+ return acc;
7970
+ }, {});
7971
+ }
7972
+ return value;
7973
+ }
7974
+ filterHeaders(headers) {
7975
+ const source = headers || {};
7976
+ return Object.keys(source).reduce((acc, key) => {
7977
+ if (!this.volatileHeaders.has(key.toLowerCase())) {
7978
+ acc[key] = source[key];
7979
+ }
7980
+ return acc;
7981
+ }, {});
7982
+ }
7983
+ resolvePath(params) {
7984
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7985
+ const effective = this.getEffectiveParams(params);
7986
+ return effective ? [...basePath, ...effective] : [...basePath];
7987
+ }
7988
+ getEffectiveParams(params) {
7989
+ if (!Array.isArray(params) || params.length === 0) {
7990
+ return undefined;
7991
+ }
7992
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7993
+ if (basePath.length !== params.length) {
7994
+ return params;
7995
+ }
7996
+ const normalizePart = (value) => {
7997
+ if (value && typeof value === 'object') {
7998
+ return JSON.stringify(this.normalizeObject(value));
7999
+ }
8000
+ return String(value);
8001
+ };
8002
+ const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
8003
+ return samePath ? undefined : params;
8004
+ }
8005
+ buildRequestSignature(method, requestOptions, params) {
8006
+ const signaturePayload = {
8007
+ method,
8008
+ server: requestOptions.server,
8009
+ path: this.resolvePath(params),
8010
+ headers: this.filterHeaders(requestOptions.headers),
8011
+ stream: !!requestOptions.stream,
8012
+ streamType: requestOptions.streamType || null
8013
+ };
8014
+ return JSON.stringify(this.normalizeObject(signaturePayload));
8015
+ }
8016
+ buildSchemaSignature(schema) {
8017
+ return JSON.stringify(this.normalizeObject(schema.split(',').map((part) => part.trim()).filter(Boolean)));
8018
+ }
8019
+ getCachedRequestSignature(tableName, type) {
8020
+ return this.requestSignatureCache[tableName]?.[type] || null;
8021
+ }
8022
+ setCachedRequestSignature(tableName, type, signature) {
8023
+ const existing = this.requestSignatureCache[tableName] || {};
8024
+ this.requestSignatureCache[tableName] = {
8025
+ ...existing,
8026
+ [type]: signature,
8027
+ };
8028
+ }
8029
+ getRequestCacheMetadata(storeData, type) {
8030
+ return storeData?.requestCache?.[type] || null;
8031
+ }
8032
+ getStoredSchemaSignature(storeData) {
8033
+ return storeData?.schemaSignature || null;
8034
+ }
8035
+ saveSchemaSignature(tableName, schemaSignature) {
8036
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8037
+ this.localStorageManagerService.updateStore({
8038
+ name: tableName,
8039
+ data: {
8040
+ ...(storeData || {}),
8041
+ schemaSignature,
8042
+ }
8043
+ });
8044
+ })).subscribe();
8045
+ }
8046
+ saveRequestCacheMetadata(tableName, type, signature, schemaSignature, options) {
8047
+ this.setCachedRequestSignature(tableName, type, signature);
8048
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8049
+ const currentCache = storeData?.requestCache || {};
8050
+ this.localStorageManagerService.updateStore({
8051
+ name: tableName,
8052
+ data: {
8053
+ ...(storeData || {}),
8054
+ schemaSignature: schemaSignature || storeData?.schemaSignature || null,
8055
+ requestCache: {
8056
+ ...currentCache,
8057
+ [type]: {
8058
+ signature,
8059
+ savedAt: Date.now(),
8060
+ path: this.resolvePath(options?.path),
8061
+ headers: this.filterHeaders(options?.headers)
8062
+ }
8063
+ }
8064
+ }
8065
+ });
8066
+ })).subscribe();
8067
+ }
8068
+ clearRequestCacheMetadata(tableName) {
8069
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8070
+ if (!storeData)
8071
+ return;
8072
+ const updated = { ...(storeData || {}) };
8073
+ delete updated.requestCache;
8074
+ this.localStorageManagerService.updateStore({
8075
+ name: tableName,
8076
+ data: updated
8077
+ });
8078
+ })).subscribe();
8079
+ }
7263
8080
  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 }); }
7264
8081
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService }); }
7265
8082
  }
@@ -8566,12 +9383,12 @@ class StateManagerDemoService extends HTTPManagerStateService {
8566
9383
  setAPIOptions(apiOptions, dataType, database) {
8567
9384
  this.setApiRequestOptions(apiOptions, dataType, database);
8568
9385
  }
8569
- getClients() {
9386
+ getClients(options) {
8570
9387
  // const headers = {
8571
9388
  // auth: "sample-auth-token"
8572
9389
  // }
8573
9390
  // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8574
- this.fetchRecords();
9391
+ this.fetchRecords(options);
8575
9392
  }
8576
9393
  createClient(data) {
8577
9394
  // const headers = {
@@ -8596,13 +9413,13 @@ class StateManagerDemoService extends HTTPManagerStateService {
8596
9413
  const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8597
9414
  this.deleteRecord(sampleOptions);
8598
9415
  }
8599
- streamRequest() {
9416
+ streamRequest(options) {
8600
9417
  console.log('[DEMO SERVICE] streamRequest called');
8601
9418
  const headers = {
8602
9419
  auth: "sample-auth-token"
8603
9420
  };
8604
9421
  console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
8605
- this.fetchStream();
9422
+ this.fetchStream(options);
8606
9423
  }
8607
9424
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8608
9425
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
@@ -8750,6 +9567,8 @@ class RequestManagerStateDemoComponent {
8750
9567
  database: this.fb.group({
8751
9568
  table: [''],
8752
9569
  expiresIn: ['1m'],
9570
+ watchParams: [''],
9571
+ watchExpiresAt: ['never'],
8753
9572
  })
8754
9573
  });
8755
9574
  this.sampleAdaptors = [
@@ -8782,26 +9601,26 @@ class RequestManagerStateDemoComponent {
8782
9601
  });
8783
9602
  this.stateManagerDemoService.data$
8784
9603
  .pipe(tap$1((data) => {
8785
- console.log('[COMPONENT] State data received:', data);
9604
+ // console.log('[COMPONENT] State data received:', data)
8786
9605
  switch (this.requestType) {
8787
9606
  case 'GET':
8788
- console.log('[COMPONENT] Updating GET$ with data:', data);
9607
+ // console.log('[COMPONENT] Updating GET$ with data:', data)
8789
9608
  this.GET$.next(data);
8790
9609
  break;
8791
9610
  case 'PUT':
8792
- console.log('[COMPONENT] Updating PUT$ with data:', data);
9611
+ // console.log('[COMPONENT] Updating PUT$ with data:', data)
8793
9612
  this.PUT$.next(data);
8794
9613
  break;
8795
9614
  case 'POST':
8796
- console.log('[COMPONENT] Updating POST$ with data:', data);
9615
+ // console.log('[COMPONENT] Updating POST$ with data:', data)
8797
9616
  this.POST$.next(data);
8798
9617
  break;
8799
9618
  case 'DELETE':
8800
- console.log('[COMPONENT] Updating DELETE$ with data:', data);
9619
+ // console.log('[COMPONENT] Updating DELETE$ with data:', data)
8801
9620
  this.DELETE$.next(data);
8802
9621
  break;
8803
9622
  case 'STREAM':
8804
- console.log('[COMPONENT] Updating STREAM$ with data:', data);
9623
+ // console.log('[COMPONENT] Updating STREAM$ with data:', data)
8805
9624
  this.STREAM.next(data);
8806
9625
  // Update table columns dynamically based on streaming data
8807
9626
  if (data && Array.isArray(data) && data.length > 0) {
@@ -8809,11 +9628,11 @@ class RequestManagerStateDemoComponent {
8809
9628
  }
8810
9629
  break;
8811
9630
  case 'STREAM_AI':
8812
- console.log('[COMPONENT] Updating STREAM_AI$ with data:', data);
9631
+ // console.log('[COMPONENT] Updating STREAM_AI$ with data:', data)
8813
9632
  this.STREAM_AI.next(data);
8814
9633
  break;
8815
9634
  default:
8816
- console.log('[COMPONENT] No requestType set, ignoring data');
9635
+ // console.log('[COMPONENT] No requestType set, ignoring data')
8817
9636
  break;
8818
9637
  }
8819
9638
  })).subscribe();
@@ -8831,10 +9650,59 @@ class RequestManagerStateDemoComponent {
8831
9650
  removeHeader(index) {
8832
9651
  this.headers.removeAt(index);
8833
9652
  }
9653
+ parsePathInput(pathInput) {
9654
+ const raw = typeof pathInput === 'string' ? pathInput.trim() : '';
9655
+ if (!raw) {
9656
+ return [];
9657
+ }
9658
+ const [rawPath = '', rawQuery = ''] = raw.split('?', 2);
9659
+ const pathSegments = rawPath
9660
+ .split('/')
9661
+ .map((segment) => segment.trim())
9662
+ .filter((segment) => segment.length > 0)
9663
+ .map((segment) => decodeURIComponent(segment));
9664
+ if (!rawQuery) {
9665
+ return pathSegments;
9666
+ }
9667
+ const searchParams = new URLSearchParams(rawQuery);
9668
+ const queryObject = {};
9669
+ searchParams.forEach((value, key) => {
9670
+ const normalizedKey = key.trim();
9671
+ if (!normalizedKey) {
9672
+ return;
9673
+ }
9674
+ const normalizedValue = this.normalizeQueryValue(value);
9675
+ if (Object.prototype.hasOwnProperty.call(queryObject, normalizedKey)) {
9676
+ const currentValue = queryObject[normalizedKey];
9677
+ queryObject[normalizedKey] = Array.isArray(currentValue)
9678
+ ? [...currentValue, normalizedValue]
9679
+ : [currentValue, normalizedValue];
9680
+ return;
9681
+ }
9682
+ queryObject[normalizedKey] = normalizedValue;
9683
+ });
9684
+ return Object.keys(queryObject).length > 0
9685
+ ? [...pathSegments, queryObject]
9686
+ : pathSegments;
9687
+ }
9688
+ normalizeQueryValue(value) {
9689
+ const trimmedValue = value.trim();
9690
+ const lowerValue = trimmedValue.toLowerCase();
9691
+ if (lowerValue === 'true') {
9692
+ return true;
9693
+ }
9694
+ if (lowerValue === 'false') {
9695
+ return false;
9696
+ }
9697
+ if (trimmedValue !== '' && !Number.isNaN(Number(trimmedValue))) {
9698
+ return Number(trimmedValue);
9699
+ }
9700
+ return trimmedValue;
9701
+ }
8834
9702
  compileRequest() {
8835
9703
  const requestParams = this.requestForm.value;
8836
9704
  requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8837
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
9705
+ const pathReq = this.parsePathInput(requestParams.path || '');
8838
9706
  if (!this.pollingState.checked)
8839
9707
  requestParams.polling = 0;
8840
9708
  if (!this.failedState.checked) {
@@ -8848,12 +9716,53 @@ class RequestManagerStateDemoComponent {
8848
9716
  const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
8849
9717
  return { apiOptions: apiOptions, path: pathReq };
8850
9718
  }
9719
+ parseWatchParams(value) {
9720
+ if (!value || typeof value !== 'string') {
9721
+ return [];
9722
+ }
9723
+ return value
9724
+ .split(',')
9725
+ .map((item) => item.trim())
9726
+ .filter((item) => item.length > 0);
9727
+ }
9728
+ normalizeWatchExpiry(value) {
9729
+ if (typeof value === 'undefined' || value === null || value === '') {
9730
+ return undefined;
9731
+ }
9732
+ if (typeof value === 'string' && value.trim().toLowerCase() === 'never') {
9733
+ return undefined;
9734
+ }
9735
+ return value;
9736
+ }
9737
+ buildDemoRequestOptions(path, headers) {
9738
+ const dbValue = this.database || {};
9739
+ const isDbEnabled = !!this.DBState?.checked;
9740
+ return RequestOptions.adapt({
9741
+ path,
9742
+ headers,
9743
+ forceRefresh: false,
9744
+ watchParams: isDbEnabled ? this.parseWatchParams(dbValue?.watchParams) : [],
9745
+ watchExpiresAt: isDbEnabled ? this.normalizeWatchExpiry(dbValue?.watchExpiresAt) : undefined,
9746
+ });
9747
+ }
8851
9748
  onSetStateOptions() {
8852
- if (!this.isValid)
9749
+ const dbValue = this.database;
9750
+ console.log('[onSetStateOptions] Called, checking form values:', {
9751
+ isValid: this.isValid,
9752
+ database: dbValue,
9753
+ hasTable: !!dbValue?.table,
9754
+ tableValue: dbValue?.table,
9755
+ dataType: this.dataType
9756
+ });
9757
+ if (!this.isValid) {
9758
+ console.log('[onSetStateOptions] Form invalid, aborting');
8853
9759
  return;
9760
+ }
8854
9761
  const reqParams = this.compileRequest();
8855
- const db = DatabaseStorage.adapt(this.database);
9762
+ const db = DatabaseStorage.adapt(dbValue);
9763
+ console.log('[onSetStateOptions] DatabaseStorage.adapt result:', db);
8856
9764
  const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
9765
+ console.log('[onSetStateOptions] Calling setAPIOptions with:', { db, type, hasTable: !!db?.table });
8857
9766
  this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
8858
9767
  this.requestForm.markAsPristine();
8859
9768
  }
@@ -8862,7 +9771,9 @@ class RequestManagerStateDemoComponent {
8862
9771
  }
8863
9772
  onGetRequest() {
8864
9773
  this.requestType = 'GET';
8865
- this.stateManagerDemoService.getClients();
9774
+ const reqParams = this.compileRequest();
9775
+ const requestOptions = this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers);
9776
+ this.stateManagerDemoService.getClients(requestOptions);
8866
9777
  }
8867
9778
  onCreateRequest() {
8868
9779
  this.requestType = 'POST';
@@ -8877,19 +9788,22 @@ class RequestManagerStateDemoComponent {
8877
9788
  this.stateManagerDemoService.deleteClient(this.sampleClientData);
8878
9789
  }
8879
9790
  onStreamRequest() {
8880
- console.log('[COMPONENT] onStreamRequest called');
9791
+ // console.log('[COMPONENT] onStreamRequest called')
8881
9792
  if (!this.isValid) {
8882
- console.log('[COMPONENT] Form invalid, aborting');
9793
+ // console.log('[COMPONENT] Form invalid, aborting')
8883
9794
  return;
8884
9795
  }
8885
- console.log('[COMPONENT] Compiling request...');
9796
+ // console.log('[COMPONENT] Compiling request...')
8886
9797
  const reqParams = this.compileRequest();
8887
- console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions);
9798
+ // console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions)
8888
9799
  reqParams.apiOptions.stream = true;
8889
9800
  reqParams.apiOptions.streamType = this.streamType;
8890
9801
  this.requestType = 'STREAM';
8891
- console.log('[COMPONENT] Calling streamRequest...');
8892
- this.stateManagerDemoService.streamRequest();
9802
+ const streamOptions = RequestOptions.adapt({
9803
+ ...this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers),
9804
+ });
9805
+ // console.log('[COMPONENT] Calling streamRequest...')
9806
+ this.stateManagerDemoService.streamRequest(streamOptions);
8893
9807
  }
8894
9808
  errorHandling(err, type) {
8895
9809
  console.log(err, type);
@@ -8913,11 +9827,11 @@ class RequestManagerStateDemoComponent {
8913
9827
  this.prompts = [];
8914
9828
  }
8915
9829
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8916
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
9830
+ 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" }] }); }
8917
9831
  }
8918
9832
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
8919
9833
  type: Component,
8920
- args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem\" formGroupName=\"database\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
9834
+ 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"] }]
8921
9835
  }], ctorParameters: () => [], propDecorators: { server: [{
8922
9836
  type: Input
8923
9837
  }], adapter: [{
@@ -8930,6 +9844,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8930
9844
  }], pollingState: [{
8931
9845
  type: ViewChild,
8932
9846
  args: ["pollingState", { static: true }]
9847
+ }], DBState: [{
9848
+ type: ViewChild,
9849
+ args: ["DBState", { static: true }]
8933
9850
  }] } });
8934
9851
 
8935
9852
  class RequestManagerDemoComponent {
@@ -9934,6 +10851,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9934
10851
  }]
9935
10852
  }] });
9936
10853
 
10854
+ class UserData {
10855
+ constructor(ldap = '', name = '', email = '', color = RandomPaletteColor()) {
10856
+ this.ldap = ldap;
10857
+ this.name = name;
10858
+ this.email = email;
10859
+ this.color = color;
10860
+ }
10861
+ static adapt(item) {
10862
+ const userName = `${item?.first_name} ${item?.last_name}`;
10863
+ return new UserData(item?.ldap, userName, item?.email, item?.color);
10864
+ }
10865
+ }
10866
+
9937
10867
  class StateDataRequestService extends HTTPManagerStateService {
9938
10868
  constructor() {
9939
10869
  super(ApiRequest.adapt({
@@ -11763,5 +12693,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11763
12693
  * Generated bundle index. Do not edit.
11764
12694
  */
11765
12695
 
11766
- 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 };
12696
+ 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 };
11767
12697
  //# sourceMappingURL=http-request-manager.mjs.map