http-request-manager 18.13.0 → 18.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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,369 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
875
875
  }]
876
876
  }], ctorParameters: () => [] });
877
877
 
878
+ class PathTrackerStateModel {
879
+ constructor(baselineQuery, consumedValuesByKey = {}, watchExpiresAt) {
880
+ this.baselineQuery = baselineQuery;
881
+ this.consumedValuesByKey = consumedValuesByKey;
882
+ this.watchExpiresAt = watchExpiresAt;
883
+ }
884
+ static adapt(item) {
885
+ return new PathTrackerStateModel(item?.baselineQuery, item?.consumedValuesByKey, item?.watchExpiresAt);
886
+ }
887
+ }
888
+
889
+ class QueryTrackerStateModel {
890
+ constructor(paths = {}) {
891
+ this.paths = paths;
892
+ }
893
+ static adapt(item) {
894
+ return new QueryTrackerStateModel(item?.paths
895
+ ? Object.keys(item.paths).reduce((acc, key) => {
896
+ acc[key] = PathTrackerStateModel.adapt(item.paths[key]);
897
+ return acc;
898
+ }, {})
899
+ : {});
900
+ }
901
+ }
902
+
903
+ const TRACKER_STORE_NAME = 'query_params_tracker';
904
+ const TRACKER_SESSION_INIT_KEY = 'query_params_tracker_initialized';
905
+ const DEFAULT_TRACKER_OPTIONS = SettingOptions.adapt({
906
+ storage: StorageType.GLOBAL,
907
+ encrypted: false,
908
+ });
909
+ class QueryParamsTrackerService {
910
+ constructor() {
911
+ this.state = { paths: {} };
912
+ this.stateRestored = false;
913
+ this.localStorageManager = inject(LocalStorageManagerService);
914
+ }
915
+ clearTracking(resetSessionInit = false) {
916
+ this.state = { paths: {} };
917
+ this.localStorageManager.deleteStore({ name: TRACKER_STORE_NAME });
918
+ if (resetSessionInit && this.hasSessionStorage()) {
919
+ sessionStorage.removeItem(TRACKER_SESSION_INIT_KEY);
920
+ }
921
+ }
922
+ checkRequestOptions(requestOptions = [], options = {}) {
923
+ this.ensureStateRestored();
924
+ const normalized = this.normalizeRequestOptions(requestOptions);
925
+ if (!normalized.pathKey)
926
+ return false;
927
+ this.cleanupExpiredEntries();
928
+ if (!normalized.hasQuery) {
929
+ this.ensurePathState(normalized.pathKey);
930
+ this.persistState();
931
+ return true;
932
+ }
933
+ if (options.mode === 'exact') {
934
+ return this.checkExact(normalized, options);
935
+ }
936
+ return this.checkVariation(normalized, options);
937
+ }
938
+ matchesPath(requestOptions = [], expectedPathOptions = []) {
939
+ this.ensureStateRestored();
940
+ const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
941
+ const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
942
+ return Boolean(requestPath) && requestPath === expectedPath;
943
+ }
944
+ checkExact(normalized, options) {
945
+ const pathState = this.ensurePathState(normalized.pathKey);
946
+ const filteredQuery = normalized.query;
947
+ if (Object.keys(filteredQuery).length === 0) {
948
+ return false;
949
+ }
950
+ if (!pathState.baselineQuery) {
951
+ pathState.baselineQuery = filteredQuery;
952
+ this.persistState();
953
+ return true;
954
+ }
955
+ return this.stringifyQuery(pathState.baselineQuery) === this.stringifyQuery(filteredQuery);
956
+ }
957
+ checkVariation(normalized, options) {
958
+ const pathState = this.ensurePathState(normalized.pathKey);
959
+ const filteredQuery = normalized.query;
960
+ const keys = Object.keys(filteredQuery);
961
+ if (keys.length === 0) {
962
+ return false;
963
+ }
964
+ this.resetPathStateIfExpired(pathState);
965
+ let accepted = false;
966
+ keys.forEach((key) => {
967
+ const currentValue = filteredQuery[key];
968
+ const consumed = pathState.consumedValuesByKey[key] || [];
969
+ if (!consumed.includes(currentValue)) {
970
+ pathState.consumedValuesByKey[key] = [...consumed, currentValue];
971
+ accepted = true;
972
+ }
973
+ });
974
+ if (accepted) {
975
+ if (!pathState.baselineQuery) {
976
+ pathState.baselineQuery = filteredQuery;
977
+ }
978
+ pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
979
+ this.persistState();
980
+ }
981
+ return accepted;
982
+ }
983
+ normalizeRequestOptions(requestOptions) {
984
+ const params = Array.isArray(requestOptions) ? requestOptions : [];
985
+ const pathSegments = [];
986
+ const queryObjects = [];
987
+ params.forEach((item) => {
988
+ if (this.isPlainObject(item)) {
989
+ queryObjects.push(item);
990
+ }
991
+ else if (typeof item !== 'undefined' && item !== null) {
992
+ const parsed = this.parsePathSegment(String(item));
993
+ if (parsed.pathSegment) {
994
+ pathSegments.push(parsed.pathSegment);
995
+ }
996
+ if (Object.keys(parsed.queryObject).length > 0) {
997
+ queryObjects.push(parsed.queryObject);
998
+ }
999
+ }
1000
+ });
1001
+ const query = queryObjects.reduce((acc, current) => {
1002
+ Object.keys(current).forEach((key) => {
1003
+ const normalizedKey = this.normalizeParamKey(key);
1004
+ const value = current[key];
1005
+ if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
1006
+ return;
1007
+ }
1008
+ acc[normalizedKey] = this.normalizeParamValue(value);
1009
+ });
1010
+ return acc;
1011
+ }, {});
1012
+ return {
1013
+ pathKey: this.normalizePath(pathSegments),
1014
+ query,
1015
+ hasQuery: Object.keys(query).length > 0,
1016
+ };
1017
+ }
1018
+ parsePathSegment(rawSegment) {
1019
+ const [pathPart, ...queryParts] = String(rawSegment).split('?');
1020
+ const queryString = queryParts.join('?');
1021
+ const queryObject = {};
1022
+ if (queryString) {
1023
+ queryString.split('&').forEach((pair) => {
1024
+ if (!pair)
1025
+ return;
1026
+ const [rawKey, ...rawValueParts] = pair.split('=');
1027
+ const key = this.safeDecode(rawKey).trim();
1028
+ const value = this.safeDecode(rawValueParts.join('=')).trim();
1029
+ if (!key)
1030
+ return;
1031
+ queryObject[key] = value;
1032
+ });
1033
+ }
1034
+ return {
1035
+ pathSegment: pathPart,
1036
+ queryObject,
1037
+ };
1038
+ }
1039
+ safeDecode(value) {
1040
+ if (typeof value === 'undefined') {
1041
+ return '';
1042
+ }
1043
+ try {
1044
+ return decodeURIComponent(value);
1045
+ }
1046
+ catch {
1047
+ return String(value);
1048
+ }
1049
+ }
1050
+ normalizePath(pathSegments) {
1051
+ return pathSegments
1052
+ .map((segment) => String(segment).trim())
1053
+ .filter((segment) => segment.length > 0)
1054
+ .join('/')
1055
+ .replace(/([^:]\/+)\/+/g, '$1')
1056
+ .replace(/^\//, '')
1057
+ .replace(/\/$/, '');
1058
+ }
1059
+ normalizeParamKey(key) {
1060
+ return String(key).trim().toLowerCase();
1061
+ }
1062
+ normalizeParamValue(value) {
1063
+ if (Array.isArray(value)) {
1064
+ return value.map((item) => this.normalizeParamValue(item)).join(',');
1065
+ }
1066
+ if (this.isPlainObject(value)) {
1067
+ return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
1068
+ acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
1069
+ return acc;
1070
+ }, {}));
1071
+ }
1072
+ return String(value).trim().toLowerCase();
1073
+ }
1074
+ buildExpiryEpoch(expireIn) {
1075
+ if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
1076
+ return undefined;
1077
+ }
1078
+ if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
1079
+ return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
1080
+ }
1081
+ const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
1082
+ const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
1083
+ if (!parsed) {
1084
+ return undefined;
1085
+ }
1086
+ const value = Number(parsed[1]);
1087
+ const unit = parsed[2];
1088
+ const toSeconds = {
1089
+ y: 31556926,
1090
+ w: 604800,
1091
+ d: 86400,
1092
+ hr: 3600,
1093
+ h: 3600,
1094
+ mn: 60,
1095
+ min: 60,
1096
+ m: 60,
1097
+ s: 1,
1098
+ };
1099
+ const seconds = toSeconds[unit];
1100
+ if (!seconds) {
1101
+ return undefined;
1102
+ }
1103
+ return Math.floor(Date.now() / 1000) + value * seconds;
1104
+ }
1105
+ resetPathStateIfExpired(pathState) {
1106
+ const now = Math.floor(Date.now() / 1000);
1107
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1108
+ pathState.consumedValuesByKey = {};
1109
+ pathState.watchExpiresAt = undefined;
1110
+ }
1111
+ }
1112
+ cleanupExpiredEntries() {
1113
+ const now = Math.floor(Date.now() / 1000);
1114
+ Object.keys(this.state.paths).forEach((pathKey) => {
1115
+ const pathState = this.state.paths[pathKey];
1116
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1117
+ pathState.consumedValuesByKey = {};
1118
+ pathState.watchExpiresAt = undefined;
1119
+ }
1120
+ const hasBaseline = Boolean(pathState.baselineQuery && Object.keys(pathState.baselineQuery).length > 0);
1121
+ const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
1122
+ if (!hasBaseline && !hasTrackedValues) {
1123
+ delete this.state.paths[pathKey];
1124
+ }
1125
+ });
1126
+ }
1127
+ ensurePathState(pathKey) {
1128
+ if (!this.state.paths[pathKey]) {
1129
+ this.state.paths[pathKey] = {
1130
+ consumedValuesByKey: {},
1131
+ };
1132
+ }
1133
+ return this.state.paths[pathKey];
1134
+ }
1135
+ ensureStateRestored() {
1136
+ if (this.stateRestored) {
1137
+ return;
1138
+ }
1139
+ this.stateRestored = true;
1140
+ this.initializeTrackingForSession();
1141
+ this.restoreState();
1142
+ }
1143
+ initializeTrackingForSession() {
1144
+ if (!this.hasSessionStorage()) {
1145
+ return;
1146
+ }
1147
+ const initialized = sessionStorage.getItem(TRACKER_SESSION_INIT_KEY);
1148
+ if (initialized === '1') {
1149
+ return;
1150
+ }
1151
+ this.clearTracking();
1152
+ sessionStorage.setItem(TRACKER_SESSION_INIT_KEY, '1');
1153
+ }
1154
+ restoreState() {
1155
+ this.localStorageManager.store$(TRACKER_STORE_NAME)
1156
+ .pipe(take(1))
1157
+ .subscribe({
1158
+ next: (storedState) => {
1159
+ if (this.isTrackerState(storedState)) {
1160
+ this.state = QueryTrackerStateModel.adapt(storedState);
1161
+ this.cleanupExpiredEntries();
1162
+ return;
1163
+ }
1164
+ this.state = { paths: {} };
1165
+ },
1166
+ error: () => {
1167
+ this.state = { paths: {} };
1168
+ }
1169
+ });
1170
+ }
1171
+ persistState() {
1172
+ this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
1173
+ .pipe(take(1))
1174
+ .subscribe((exists) => {
1175
+ if (exists) {
1176
+ this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
1177
+ return;
1178
+ }
1179
+ this.localStorageManager.createStore({
1180
+ name: TRACKER_STORE_NAME,
1181
+ data: this.state,
1182
+ options: DEFAULT_TRACKER_OPTIONS,
1183
+ });
1184
+ });
1185
+ }
1186
+ stringifyQuery(query) {
1187
+ return JSON.stringify(Object.keys(query)
1188
+ .sort()
1189
+ .reduce((acc, key) => {
1190
+ acc[key] = query[key];
1191
+ return acc;
1192
+ }, {}));
1193
+ }
1194
+ isTrackerState(value) {
1195
+ return this.isPlainObject(value) && this.isPlainObject(value.paths);
1196
+ }
1197
+ isPlainObject(value) {
1198
+ return Object.prototype.toString.call(value) === '[object Object]';
1199
+ }
1200
+ hasSessionStorage() {
1201
+ try {
1202
+ return typeof sessionStorage !== 'undefined';
1203
+ }
1204
+ catch {
1205
+ return false;
1206
+ }
1207
+ }
1208
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1209
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
1210
+ }
1211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
1212
+ type: Injectable,
1213
+ args: [{
1214
+ providedIn: 'root'
1215
+ }]
1216
+ }] });
1217
+
1218
+ class NormalizedRequestOptionsModel {
1219
+ constructor(pathKey = '', query = {}, hasQuery = false) {
1220
+ this.pathKey = pathKey;
1221
+ this.query = query;
1222
+ this.hasQuery = hasQuery;
1223
+ }
1224
+ static adapt(item) {
1225
+ return new NormalizedRequestOptionsModel(item?.pathKey, item?.query, item?.hasQuery);
1226
+ }
1227
+ }
1228
+
1229
+ class QueryParamsTrackerOptionsModel {
1230
+ constructor(mode, watchParams, watchExpiresAt, watchParamsExpire) {
1231
+ this.mode = mode;
1232
+ this.watchParams = watchParams;
1233
+ this.watchExpiresAt = watchExpiresAt;
1234
+ this.watchParamsExpire = watchParamsExpire;
1235
+ }
1236
+ static adapt(item) {
1237
+ return new QueryParamsTrackerOptionsModel(item?.mode, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt, item?.watchParamsExpire);
1238
+ }
1239
+ }
1240
+
878
1241
  /**
879
1242
  * Stateful processor for managing streaming data and events
880
1243
  */
@@ -883,28 +1246,66 @@ class StreamingProcessor {
883
1246
  this.buffer = '';
884
1247
  this.contentType = '';
885
1248
  this.maxBufferSize = 10 * 1024 * 1024; // 10MB limit
1249
+ this.lastParsedLength = 0; // Track parsed position to avoid duplicates
886
1250
  this.streamConfig = config || { streamType: StreamType.AI_STREAMING };
887
1251
  }
888
1252
  /**
889
- * Process HTTP events and extract streaming data
1253
+ * Process HTTP events and extract streaming data with progress
890
1254
  */
891
1255
  process(event) {
892
1256
  switch (event.type) {
893
1257
  case HttpEventType.ResponseHeader:
894
1258
  this.contentType = event.headers?.get('content-type') || '';
1259
+ // Read total from response header if configured
1260
+ if (this.streamConfig.totalHeader && event.headers) {
1261
+ const totalVal = event.headers.get(this.streamConfig.totalHeader);
1262
+ if (totalVal !== undefined && totalVal !== null) {
1263
+ const parsed = parseInt(totalVal, 10);
1264
+ if (!isNaN(parsed)) {
1265
+ this.totalFromHeader = parsed;
1266
+ }
1267
+ }
1268
+ }
895
1269
  return null;
896
1270
  case HttpEventType.DownloadProgress:
897
1271
  if (event.partialText) {
898
1272
  this.appendToBuffer(event.partialText);
899
1273
  const parsedData = this.parseBuffer();
900
- return parsedData.length > 0 ? [parsedData[0]] : null;
1274
+ // Only return NEW items since last parse (progressive updates)
1275
+ const newItems = parsedData.slice(this.lastParsedLength);
1276
+ this.lastParsedLength = parsedData.length;
1277
+ // Calculate progress
1278
+ const progress = {
1279
+ received: this.lastParsedLength,
1280
+ total: this.totalFromHeader,
1281
+ percent: this.totalFromHeader
1282
+ ? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
1283
+ : 0,
1284
+ stage: 'streaming'
1285
+ };
1286
+ return {
1287
+ data: newItems.length > 0 ? newItems : [],
1288
+ progress
1289
+ };
901
1290
  }
902
1291
  return null;
903
1292
  case HttpEventType.Response:
904
1293
  if (event.body) {
905
1294
  this.appendToBuffer(event.body);
906
1295
  const parsedData = this.parseBuffer();
907
- return parsedData.length > 0 ? parsedData : null;
1296
+ // Calculate final progress
1297
+ const progress = {
1298
+ received: parsedData.length,
1299
+ total: this.totalFromHeader,
1300
+ percent: this.totalFromHeader
1301
+ ? Math.round((parsedData.length / this.totalFromHeader) * 100)
1302
+ : 100,
1303
+ stage: 'complete'
1304
+ };
1305
+ return {
1306
+ data: parsedData.length > 0 ? parsedData : [],
1307
+ progress
1308
+ };
908
1309
  }
909
1310
  return null;
910
1311
  default:
@@ -943,6 +1344,8 @@ class StreamingProcessor {
943
1344
  reset() {
944
1345
  this.buffer = '';
945
1346
  this.contentType = '';
1347
+ this.lastParsedLength = 0;
1348
+ this.totalFromHeader = undefined;
946
1349
  }
947
1350
  /**
948
1351
  * Update configuration
@@ -2793,19 +3196,6 @@ class UploadValidationErrorModel {
2793
3196
  }
2794
3197
  }
2795
3198
 
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
3199
  class RequestService extends WebsocketService {
2810
3200
  constructor() {
2811
3201
  super(...arguments);
@@ -2816,6 +3206,13 @@ class RequestService extends WebsocketService {
2816
3206
  this.isPending$ = this.isPending.asObservable();
2817
3207
  this.progress = new BehaviorSubject(0);
2818
3208
  this.progress$ = this.progress.asObservable();
3209
+ this.streamProgress = new BehaviorSubject({
3210
+ received: 0,
3211
+ total: undefined,
3212
+ percent: 0,
3213
+ stage: 'connecting'
3214
+ });
3215
+ this.streamProgress$ = this.streamProgress.asObservable();
2819
3216
  }
2820
3217
  // Implementation
2821
3218
  getRecordRequest(options) {
@@ -2884,7 +3281,12 @@ class RequestService extends WebsocketService {
2884
3281
  }
2885
3282
  requestStreaming(options) {
2886
3283
  return (source$) => {
2887
- return source$.pipe(map(data => {
3284
+ return source$.pipe(tap(output => {
3285
+ // Update progress from stream output
3286
+ this.progress.next(output.progress.received);
3287
+ this.streamProgress.next(output.progress);
3288
+ }), map(output => {
3289
+ const data = output.data;
2888
3290
  if (!data || (Array.isArray(data) && data.length === 0)) {
2889
3291
  return data;
2890
3292
  }
@@ -3586,6 +3988,7 @@ class HTTPManagerService extends RequestService {
3586
3988
  this.data$ = this.data.asObservable();
3587
3989
  this.polling$ = new Subject();
3588
3990
  this.config = ApiRequest.adapt();
3991
+ this.streamProgress$ = this.streamProgress.asObservable();
3589
3992
  this.config = (configOptions) ? ApiRequest.adapt(configOptions.httpRequestOptions) : this.config;
3590
3993
  }
3591
3994
  // ═══════════════════════════════════════════════════════════════════════════
@@ -3722,6 +4125,12 @@ class HTTPManagerService extends RequestService {
3722
4125
  getRequest(options, params) {
3723
4126
  this.isPending.next(true);
3724
4127
  this.data.next(null);
4128
+ this.streamProgress.next({
4129
+ received: 0,
4130
+ total: undefined,
4131
+ percent: 0,
4132
+ stage: 'connecting'
4133
+ });
3725
4134
  const updatedOptions = this.defineReqOptions(options, params);
3726
4135
  const func = this.getRecordRequest;
3727
4136
  const requests = this.createRequest(func, updatedOptions);
@@ -3730,9 +4139,19 @@ class HTTPManagerService extends RequestService {
3730
4139
  this.data.next(data);
3731
4140
  if (updatedOptions.displaySuccess)
3732
4141
  this.handleSuccessWithSnackBar(updatedOptions.successMessage);
3733
- })).pipe(finalize(() => this.isPending.next(false)), catchError((err) => {
4142
+ }), finalize(() => {
4143
+ this.streamProgress.next({
4144
+ ...this.streamProgress.value,
4145
+ stage: 'complete'
4146
+ });
4147
+ this.isPending.next(false);
4148
+ }), catchError((err) => {
3734
4149
  if (updatedOptions.displayError)
3735
4150
  this.handleErrorWithSnackBar(err, updatedOptions.errorMessage || err?.message);
4151
+ this.streamProgress.next({
4152
+ ...this.streamProgress.value,
4153
+ stage: 'error'
4154
+ });
3736
4155
  this.isPending.next(false);
3737
4156
  return this.handleError(err);
3738
4157
  }));
@@ -4154,7 +4573,8 @@ class RequestSignalsService extends WebsocketService {
4154
4573
  }
4155
4574
  requestStreamingOperator(options) {
4156
4575
  return (source$) => {
4157
- return source$.pipe(map(data => {
4576
+ return source$.pipe(map(output => {
4577
+ const data = output.data;
4158
4578
  if (!data || (Array.isArray(data) && data.length === 0)) {
4159
4579
  return data;
4160
4580
  }
@@ -4891,12 +5311,15 @@ class ApiRequest {
4891
5311
  }
4892
5312
 
4893
5313
  class RequestOptions {
4894
- constructor(path = [], headers = {}) {
5314
+ constructor(path = [], headers = {}, forceRefresh, watchParams, watchExpiresAt) {
4895
5315
  this.path = path;
4896
5316
  this.headers = headers;
5317
+ this.forceRefresh = forceRefresh;
5318
+ this.watchParams = watchParams;
5319
+ this.watchExpiresAt = watchExpiresAt;
4897
5320
  }
4898
5321
  static adapt(item) {
4899
- return new RequestOptions(item?.path, item?.headers);
5322
+ return new RequestOptions(item?.path, item?.headers, item?.forceRefresh, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt);
4900
5323
  }
4901
5324
  }
4902
5325
 
@@ -5691,8 +6114,8 @@ class DbService extends Dexie {
5691
6114
  console.warn('Database upgrade blocked! Please close other tabs/windows of this app.');
5692
6115
  });
5693
6116
  this.on('versionchange', () => {
5694
- console.warn('Database version changed in another tab. Reloading...');
5695
- window.location.reload();
6117
+ console.warn('Database version changed in another tab. Closing current database connection to allow upgrade.');
6118
+ this.close();
5696
6119
  });
5697
6120
  this.dbReady = this.init();
5698
6121
  }
@@ -5772,9 +6195,12 @@ class DbService extends Dexie {
5772
6195
  await this.dbReady;
5773
6196
  const safeTableName = this.cleanTableName(tableName);
5774
6197
  const safeSchema = schema.trim();
5775
- if (this.tableExists(safeTableName))
5776
- return;
5777
6198
  const currentSchema = this.getCurrentSchema();
6199
+ const existingSchema = currentSchema[safeTableName]?.trim();
6200
+ // No-op only when table already exists and schema is identical.
6201
+ if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
6202
+ return;
6203
+ }
5778
6204
  console.log('Current Schema before update:', currentSchema);
5779
6205
  currentSchema[safeTableName] = safeSchema;
5780
6206
  const nextVersion = this.verno + 1;
@@ -5788,6 +6214,7 @@ class DbService extends Dexie {
5788
6214
  const created = this.tables.some(t => t.name === safeTableName);
5789
6215
  if (!created) {
5790
6216
  console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
6217
+ throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
5791
6218
  }
5792
6219
  else {
5793
6220
  console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
@@ -5795,15 +6222,24 @@ class DbService extends Dexie {
5795
6222
  }
5796
6223
  catch (err) {
5797
6224
  console.error('Error opening database after schema update:', err);
6225
+ throw err;
5798
6226
  }
5799
6227
  }
5800
6228
  async DBOpened() {
6229
+ try {
6230
+ await this.dbReady;
6231
+ }
6232
+ catch (err) {
6233
+ console.error('DBOpened: init failed', err);
6234
+ return false;
6235
+ }
5801
6236
  if (!this.isOpen()) {
5802
6237
  try {
5803
6238
  await this.open();
5804
6239
  return true;
5805
6240
  }
5806
6241
  catch (err) {
6242
+ console.error('DBOpened: open failed', err);
5807
6243
  return false;
5808
6244
  }
5809
6245
  }
@@ -5898,7 +6334,11 @@ class DatabaseManagerService extends DbService {
5898
6334
  }));
5899
6335
  }
5900
6336
  createDatabaseTable(tableDef) {
5901
- return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.DBOpened()));
6337
+ const tableName = this.cleanTableName(tableDef.table);
6338
+ return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
6339
+ console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
6340
+ return of(false);
6341
+ }));
5902
6342
  }
5903
6343
  updateDatabaseTableSchema(tableDef) {
5904
6344
  return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
@@ -5945,26 +6385,23 @@ class DatabaseManagerService extends DbService {
5945
6385
  }
5946
6386
  createTableRecords(table, records) {
5947
6387
  const tableName = this.cleanTableName(table);
5948
- return from(this.DBOpened()).pipe(switchMap(() => {
6388
+ return from(this.DBOpened()).pipe(switchMap((opened) => {
6389
+ if (!opened) {
6390
+ console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
6391
+ return EMPTY;
6392
+ }
5949
6393
  if (!this.tables.some(t => t.name === tableName)) {
5950
6394
  console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
5951
6395
  return EMPTY;
5952
6396
  }
5953
6397
  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
- ];
6398
+ // Keep full object payload; Dexie stores non-indexed properties as well.
5960
6399
  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;
6400
+ const payload = { ...(record || {}) };
6401
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6402
+ delete payload.id;
6403
+ }
6404
+ return payload;
5968
6405
  });
5969
6406
  console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
5970
6407
  return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
@@ -5978,17 +6415,14 @@ class DatabaseManagerService extends DbService {
5978
6415
  updateTableRecords(table, records) {
5979
6416
  const tableName = this.cleanTableName(table);
5980
6417
  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
- }));
6418
+ const insertRecords = records.map((record) => {
6419
+ const payload = { ...(record || {}) };
6420
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6421
+ delete payload.id;
6422
+ }
6423
+ return payload;
6424
+ });
6425
+ return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
5992
6426
  }));
5993
6427
  }
5994
6428
  deleteTableRecord(table, id) {
@@ -6090,6 +6524,7 @@ class HTTPManagerStateService extends ComponentStore {
6090
6524
  this.httpManagerService = inject(HTTPManagerService);
6091
6525
  this.dbManagerService = inject(DatabaseManagerService);
6092
6526
  this.localStorageManagerService = inject(LocalStorageManagerService);
6527
+ this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6093
6528
  this.utils = inject(UtilsService);
6094
6529
  this.logger = inject(LoggerService);
6095
6530
  this.error$ = this.httpManagerService.error$;
@@ -6106,6 +6541,16 @@ class HTTPManagerStateService extends ComponentStore {
6106
6541
  this.hasDatabase = false;
6107
6542
  this.streamedResponse = [];
6108
6543
  this.shouldRetry = true;
6544
+ this.volatileHeaders = new Set([
6545
+ 'authorization',
6546
+ 'x-request-id',
6547
+ 'x-correlation-id',
6548
+ 'x-trace-id',
6549
+ 'x-amzn-trace-id',
6550
+ 'date',
6551
+ 'if-none-match'
6552
+ ]);
6553
+ this.requestSignatureCache = {};
6109
6554
  this.wsRetryAttempts = new BehaviorSubject(0);
6110
6555
  this.wsRetryAttempts$ = this.wsRetryAttempts.asObservable();
6111
6556
  this.messages = new BehaviorSubject([]);
@@ -6375,22 +6820,25 @@ class HTTPManagerStateService extends ComponentStore {
6375
6820
  }
6376
6821
  }))))));
6377
6822
  this.initDBStorage = this.effect((trigger$) => trigger$.pipe(tap(() => {
6823
+ console.log('[initDBStorage effect] Triggered, checking conditions:', {
6824
+ dataType: this.dataType,
6825
+ isARRAY: this.dataType === DataType.ARRAY,
6826
+ hasAdapter: !!this.apiOptions?.adapter,
6827
+ hasTable: !!this.databaseOptions?.table,
6828
+ tableValue: this.databaseOptions?.table
6829
+ });
6378
6830
  if (this.dataType !== DataType.ARRAY)
6379
6831
  console.warn('Database storage requires dataType to be ARRAY');
6380
6832
  if (!this.apiOptions.adapter)
6381
- console.warn('Database storage requires an adapter to define the data shape');
6833
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
6382
6834
  if (this.databaseOptions && this.databaseOptions?.table === '')
6383
6835
  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
- }
6836
+ }), filter(() => {
6837
+ const shouldProceed = this.dataType === DataType.ARRAY && !!this.databaseOptions?.table;
6838
+ console.log('[initDBStorage effect] Filter result:', shouldProceed);
6839
+ return shouldProceed;
6840
+ }), switchMap(() => {
6841
+ const schema = this.buildSchemaFromAdapter();
6394
6842
  const tableDef = TableSchemaDef.adapt({
6395
6843
  table: this.databaseOptions?.table,
6396
6844
  schema: schema
@@ -6479,6 +6927,7 @@ class HTTPManagerStateService extends ComponentStore {
6479
6927
  }
6480
6928
  }), concatMap(() => {
6481
6929
  if (this.hasDatabase && this.databaseOptions?.table) {
6930
+ this.clearRequestCacheMetadata(this.databaseOptions.table);
6482
6931
  const currentData = this.get()?.data;
6483
6932
  const idsToDelete = Array.isArray(currentData) ? currentData.map((r) => r.id) : [];
6484
6933
  if (idsToDelete.length > 0) {
@@ -6493,35 +6942,81 @@ class HTTPManagerStateService extends ComponentStore {
6493
6942
  this.fetchRecords = (options) => this.effect(() => of(RequestOptions.adapt(options)).pipe(switchMap(() => {
6494
6943
  this.streamedResponse = [];
6495
6944
  const requestOptions = this.updateRequestOptions(options?.headers);
6945
+ const effectiveParams = this.getEffectiveParams(options?.path);
6946
+ const requestSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
6496
6947
  const fetchFromAPI = () => {
6497
- return this.httpManagerService.getRequest(requestOptions, options?.path).pipe(tap((data) => {
6498
- data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
6948
+ if (this.hasDatabase && this.databaseOptions?.table) {
6949
+ this.setCachedRequestSignature(this.databaseOptions.table, 'GET', requestSignature);
6950
+ }
6951
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(tap((data) => {
6952
+ // Extract array from paginated response if needed
6953
+ const arrayData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6954
+ data = (!arrayData) ? (this.dataType === DataType.ARRAY) ? [] : {} : arrayData;
6499
6955
  this.setData$(data);
6500
6956
  }), concatMap((data) => {
6501
- if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(data) && data.length > 0) {
6957
+ // Extract array from paginated response for database storage
6958
+ const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data;
6959
+ if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
6960
+ const tableName = this.databaseOptions.table;
6502
6961
  this.localStorageManagerService.updateStore({
6503
- name: this.databaseOptions.table,
6962
+ name: tableName,
6504
6963
  data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
6505
6964
  });
6506
- return this.dbManagerService.createTableRecords(this.databaseOptions.table, data);
6965
+ const schema = this.buildSchemaFromSample(dbData[0]);
6966
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
6967
+ const schemaSignature = this.buildSchemaSignature(schema);
6968
+ // Always ensure table exists immediately before writing to avoid stale schema/store races.
6969
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
6970
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6971
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
6972
+ const ensureTable$ = schemaChanged
6973
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
6974
+ : this.dbManagerService.createDatabaseTable(tableDef);
6975
+ return ensureTable$.pipe(switchMap((created) => {
6976
+ if (!created) {
6977
+ console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
6978
+ return of(data);
6979
+ }
6980
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'GET', requestSignature, schemaSignature, options)));
6981
+ }));
6982
+ }), catchError((error) => {
6983
+ console.error('[DB STORAGE] Failed to ensure table and write records:', { table: tableName, schema, error });
6984
+ return of(data);
6985
+ }));
6507
6986
  }
6508
6987
  return of(data);
6509
6988
  }));
6510
6989
  };
6990
+ console.log('[DB STORAGE] Checking database storage:', {
6991
+ hasDatabase: this.hasDatabase,
6992
+ table: this.databaseOptions?.table,
6993
+ databaseOptions: this.databaseOptions
6994
+ });
6511
6995
  if (this.hasDatabase && this.databaseOptions?.table) {
6512
6996
  return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
6513
6997
  if (!dbExists) {
6514
6998
  const initObs = this.initDBStorageAsync();
6515
- return initObs.pipe(switchMap(() => fetchFromAPI()));
6999
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7000
+ console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7001
+ return fetchFromAPI();
7002
+ }));
6516
7003
  }
6517
7004
  return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
6518
7005
  if (!tableExists) {
6519
7006
  const initObs = this.initDBStorageAsync();
6520
- return initObs.pipe(switchMap(() => fetchFromAPI()));
7007
+ return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7008
+ console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7009
+ return fetchFromAPI();
7010
+ }));
6521
7011
  }
6522
7012
  return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7013
+ const forceRefresh = !!options?.forceRefresh;
7014
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
6523
7015
  const expires = storeData?.expires || 0;
6524
7016
  const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7017
+ if (forceRefresh) {
7018
+ return fetchFromAPI();
7019
+ }
6525
7020
  if (hasExpired) {
6526
7021
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()), tap(() => {
6527
7022
  this.localStorageManagerService.updateStore({
@@ -6530,12 +7025,41 @@ class HTTPManagerStateService extends ComponentStore {
6530
7025
  });
6531
7026
  }));
6532
7027
  }
7028
+ const expectedSchema = this.buildSchemaFromAdapter();
7029
+ const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7030
+ if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7031
+ const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7032
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7033
+ }
7034
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7035
+ const shouldMakeRequest = trackerAllowsRequest;
7036
+ if (shouldMakeRequest) {
7037
+ return fetchFromAPI();
7038
+ }
6533
7039
  return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
6534
7040
  if (Array.isArray(dbData) && dbData.length > 0) {
6535
7041
  this.setData$(dbData);
6536
7042
  return of(dbData);
6537
7043
  }
6538
- return fetchFromAPI();
7044
+ const currentStateData = this.get()?.data;
7045
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7046
+ return of(currentStateData);
7047
+ }
7048
+ if (currentStateData &&
7049
+ !Array.isArray(currentStateData) &&
7050
+ Object.keys(currentStateData).length > 0) {
7051
+ return of(currentStateData);
7052
+ }
7053
+ return of(this.dataType === DataType.ARRAY ? [] : {});
7054
+ }), catchError((error) => {
7055
+ const tableName = this.databaseOptions.table;
7056
+ console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7057
+ const schema = this.buildSchemaFromAdapter();
7058
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7059
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7060
+ console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7061
+ return fetchFromAPI();
7062
+ }));
6539
7063
  }));
6540
7064
  }));
6541
7065
  }));
@@ -6659,7 +7183,7 @@ class HTTPManagerStateService extends ComponentStore {
6659
7183
  if (res.length > 0)
6660
7184
  this.setData$(res);
6661
7185
  this.streamedResponse = res;
6662
- }), scan((acc, res) => {
7186
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), scan((acc, res) => {
6663
7187
  const previous = acc.current;
6664
7188
  const current = res;
6665
7189
  return { previous, current };
@@ -6679,8 +7203,70 @@ class HTTPManagerStateService extends ComponentStore {
6679
7203
  }), switchMap((options) => {
6680
7204
  const requestOptions = this.updateRequestOptions(options?.headers);
6681
7205
  requestOptions.stream = true;
7206
+ const effectiveParams = this.getEffectiveParams(options?.path);
6682
7207
  console.log('[DEBUG] Making streaming request:', requestOptions);
6683
- return this.httpManagerService.getRequest(requestOptions, options?.path)
7208
+ if (this.hasDatabase && this.databaseOptions?.table) {
7209
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7210
+ return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7211
+ const forceRefresh = !!options?.forceRefresh;
7212
+ const expires = storeData?.expires || 0;
7213
+ const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7214
+ if (forceRefresh) {
7215
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7216
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7217
+ }
7218
+ if (hasExpired) {
7219
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(tap(() => this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature)), switchMap(() => this.httpManagerService.getRequest(requestOptions, effectiveParams)), map((apiData) => ({ data: apiData, fromCache: false })));
7220
+ }
7221
+ const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7222
+ const shouldMakeRequest = trackerAllowsRequest;
7223
+ if (!shouldMakeRequest) {
7224
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7225
+ if (Array.isArray(dbData) && dbData.length > 0) {
7226
+ return of({ data: dbData, fromCache: true });
7227
+ }
7228
+ const currentStateData = this.get()?.data;
7229
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7230
+ return of({ data: currentStateData, fromCache: true });
7231
+ }
7232
+ if (currentStateData &&
7233
+ !Array.isArray(currentStateData) &&
7234
+ Object.keys(currentStateData).length > 0) {
7235
+ return of({ data: currentStateData, fromCache: true });
7236
+ }
7237
+ return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7238
+ }));
7239
+ }
7240
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7241
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
7242
+ })).pipe(tap((packet) => {
7243
+ const res = packet?.data;
7244
+ console.log('[DEBUG] Streaming response received:', res);
7245
+ if (res && res.length > 0) {
7246
+ console.log('[DEBUG] Updating state with streaming data:', res);
7247
+ this.setData$(res);
7248
+ this.streamedResponse = res;
7249
+ }
7250
+ else {
7251
+ console.log('[DEBUG] No streaming data or empty array:', res);
7252
+ }
7253
+ // Reset pending once we have a response packet (cache or network)
7254
+ this.httpManagerService.isPending.next(false);
7255
+ }), concatMap((packet) => {
7256
+ if (packet?.fromCache) {
7257
+ return of(packet?.data);
7258
+ }
7259
+ return this.persistStreamDataToDb(packet?.data, options);
7260
+ }), map((res) => {
7261
+ console.log('[DEBUG] Returning data to subscribers:', res);
7262
+ return res;
7263
+ }), catchError((error) => {
7264
+ console.error('[DEBUG] Streaming error:', error);
7265
+ this.httpManagerService.isPending.next(false);
7266
+ return of([]);
7267
+ }));
7268
+ }
7269
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams)
6684
7270
  .pipe(tap((res) => {
6685
7271
  console.log('[DEBUG] Streaming response received:', res);
6686
7272
  // Always update state with streaming data
@@ -6692,7 +7278,9 @@ class HTTPManagerStateService extends ComponentStore {
6692
7278
  else {
6693
7279
  console.log('[DEBUG] No streaming data or empty array:', res);
6694
7280
  }
6695
- }), map((res) => {
7281
+ // Reset pending once we have a response packet
7282
+ this.httpManagerService.isPending.next(false);
7283
+ }), concatMap((res) => this.persistStreamDataToDb(res, options)), map((res) => {
6696
7284
  console.log('[DEBUG] Returning data to subscribers:', res);
6697
7285
  return res; // Return the data so subscribers can receive it
6698
7286
  }), catchError((error) => {
@@ -6719,7 +7307,11 @@ class HTTPManagerStateService extends ComponentStore {
6719
7307
  encrypted: false,
6720
7308
  })
6721
7309
  });
6722
- this.initDBStorage();
7310
+ // Use initDBStorageAsync directly - the effect initDBStorage requires subscription
7311
+ this.initDBStorageAsync().subscribe({
7312
+ next: () => console.log('[Constructor] Database storage initialized'),
7313
+ error: (err) => console.error('[Constructor] Database storage initialization failed:', err)
7314
+ });
6723
7315
  }
6724
7316
  }
6725
7317
  catch (error) {
@@ -6773,8 +7365,24 @@ class HTTPManagerStateService extends ComponentStore {
6773
7365
  this.dataType = (dataType) ? dataType : DataType.ARRAY;
6774
7366
  // Only update database options if a database parameter is explicitly provided
6775
7367
  if (database !== undefined) {
7368
+ console.log('[setApiRequestOptions] Database config:', {
7369
+ database,
7370
+ hasTable: !!database?.table,
7371
+ tableValue: database?.table
7372
+ });
6776
7373
  this.hasDatabase = (database?.table) ? true : false;
6777
- this.databaseOptions = (this.hasDatabase) ? DatabaseStorage.adapt(database) : undefined;
7374
+ const adapted = DatabaseStorage.adapt(database);
7375
+ console.log('[setApiRequestOptions] DatabaseStorage.adapt result:', adapted);
7376
+ this.databaseOptions = (this.hasDatabase) ? adapted : undefined;
7377
+ // Trigger database table creation if table is configured
7378
+ if (this.hasDatabase && this.databaseOptions?.table) {
7379
+ console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table);
7380
+ // Use initDBStorageAsync directly instead of the effect - effects need subscription to run
7381
+ this.initDBStorageAsync().subscribe({
7382
+ next: () => console.log('[setApiRequestOptions] Database storage initialized successfully'),
7383
+ error: (err) => console.error('[setApiRequestOptions] Database storage initialization failed:', err)
7384
+ });
7385
+ }
6778
7386
  }
6779
7387
  if (this.apiOptions.ws && this.apiOptions.ws.id !== '') {
6780
7388
  // Auto-prefix channel ID for private state manager channels
@@ -6896,11 +7504,20 @@ class HTTPManagerStateService extends ComponentStore {
6896
7504
  this.setData$(data);
6897
7505
  }
6898
7506
  updateArrayState(currentData, newData) {
7507
+ // For non-streaming requests (GET), REPLACE data entirely
7508
+ // For streaming requests, MERGE with existing data incrementally
7509
+ if (this.streamedResponse.length === 0) {
7510
+ // GET request: return new data as-is (replace)
7511
+ return newData;
7512
+ }
7513
+ // Streaming: merge with existing data
6899
7514
  const filterCurrentData = () => {
6900
7515
  const ids = this.streamedResponse.map((obj) => obj.id);
6901
7516
  return currentData.filter(obj => (obj.id) ? ids.includes(obj.id) : obj);
6902
7517
  };
6903
- const filteredCurrentData = (this.httpManagerService.isPending.value) ? currentData : filterCurrentData();
7518
+ const filteredCurrentData = (this.httpManagerService.isPending.value)
7519
+ ? currentData
7520
+ : filterCurrentData();
6904
7521
  const updatedData = filteredCurrentData.map(item => {
6905
7522
  const newItem = newData.find(newItem => {
6906
7523
  const hasId = (newItem?.id && item?.id) ? true : false;
@@ -6917,32 +7534,92 @@ class HTTPManagerStateService extends ComponentStore {
6917
7534
  return [...updatedData, ...addedData];
6918
7535
  }
6919
7536
  initDBStorageAsync() {
7537
+ console.log('[initDBStorageAsync] Starting initialization:', {
7538
+ dataType: this.dataType,
7539
+ hasAdapter: !!this.apiOptions?.adapter,
7540
+ table: this.databaseOptions?.table,
7541
+ databaseOptions: this.databaseOptions
7542
+ });
6920
7543
  if (this.dataType !== DataType.ARRAY) {
6921
7544
  console.warn('Database storage requires dataType to be ARRAY');
6922
7545
  return of(null);
6923
7546
  }
6924
7547
  if (!this.apiOptions.adapter) {
6925
- console.warn('Database storage requires an adapter to define the data shape');
6926
- return of(null);
7548
+ console.warn('Database storage adapter missing, using minimal or inferred schema');
6927
7549
  }
6928
7550
  if (!this.databaseOptions?.table) {
6929
7551
  console.warn('Database storage requires a table name');
6930
7552
  return of(null);
6931
7553
  }
7554
+ const schema = this.buildSchemaFromAdapter();
7555
+ const tableDef = TableSchemaDef.adapt({
7556
+ table: this.databaseOptions?.table,
7557
+ schema: schema
7558
+ });
7559
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(tap((created) => {
7560
+ if (created && this.databaseOptions?.table) {
7561
+ this.saveSchemaSignature(this.databaseOptions.table, this.buildSchemaSignature(schema));
7562
+ }
7563
+ }));
7564
+ }
7565
+ buildSchemaFromAdapter() {
6932
7566
  const sampleData = this.apiOptions.adapter?.({}) || {};
6933
- const schemaKeys = Object.keys(sampleData).filter(key => sampleData[key] !== undefined);
7567
+ return this.buildSchemaFromSample(sampleData);
7568
+ }
7569
+ buildSchemaFromSample(sampleData) {
7570
+ const schemaKeys = Object.keys(sampleData || {}).filter(key => sampleData[key] !== undefined);
6934
7571
  let schema = '++id';
6935
7572
  if (schemaKeys.length > 0) {
6936
- const otherKeys = schemaKeys.filter(k => k !== 'id');
7573
+ const otherKeys = schemaKeys.filter((k) => k !== 'id');
6937
7574
  if (otherKeys.length > 0) {
6938
7575
  schema += ', ' + otherKeys.join(', ');
6939
7576
  }
6940
7577
  }
6941
- const tableDef = TableSchemaDef.adapt({
6942
- table: this.databaseOptions?.table,
6943
- schema: schema
6944
- });
6945
- return this.dbManagerService.createDatabaseTable(tableDef);
7578
+ return schema;
7579
+ }
7580
+ persistStreamDataToDb(payload, streamOptions) {
7581
+ if (!this.hasDatabase || !this.databaseOptions?.table) {
7582
+ return of(payload);
7583
+ }
7584
+ const dbData = (payload?.results && Array.isArray(payload.results))
7585
+ ? payload.results
7586
+ : Array.isArray(payload)
7587
+ ? payload
7588
+ : payload
7589
+ ? [payload]
7590
+ : [];
7591
+ if (dbData.length === 0) {
7592
+ return of(payload);
7593
+ }
7594
+ const tableName = this.databaseOptions.table;
7595
+ const requestOptions = this.updateRequestOptions(streamOptions?.headers);
7596
+ requestOptions.stream = true;
7597
+ const effectiveParams = this.getEffectiveParams(streamOptions?.path);
7598
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7599
+ this.localStorageManagerService.updateStore({
7600
+ name: tableName,
7601
+ data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7602
+ });
7603
+ const schema = this.buildSchemaFromSample(dbData[0]);
7604
+ const schemaSignature = this.buildSchemaSignature(schema);
7605
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7606
+ return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
7607
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7608
+ const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7609
+ const ensureTable$ = schemaChanged
7610
+ ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
7611
+ : this.dbManagerService.createDatabaseTable(tableDef);
7612
+ return ensureTable$.pipe(switchMap((created) => {
7613
+ if (!created) {
7614
+ console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
7615
+ return of(payload);
7616
+ }
7617
+ return this.dbManagerService.createTableRecords(tableName, dbData).pipe(tap(() => this.saveRequestCacheMetadata(tableName, 'STREAM', requestSignature, schemaSignature, streamOptions)), map(() => payload));
7618
+ }));
7619
+ }), map(() => payload), catchError((error) => {
7620
+ console.error('[DB STORAGE] Failed to persist streaming payload:', { table: tableName, schema, error });
7621
+ return of(payload);
7622
+ }));
6946
7623
  }
6947
7624
  // WEBSOCKET COMMUNICATION (STATE MANAGER)
6948
7625
  wsCommunication(method, path) {
@@ -7238,6 +7915,7 @@ class HTTPManagerStateService extends ComponentStore {
7238
7915
  const tableName = this.databaseOptions.table;
7239
7916
  this.dbManagerService.clearTable(tableName).subscribe({
7240
7917
  next: () => {
7918
+ this.clearRequestCacheMetadata(tableName);
7241
7919
  if (this.dataType === DataType.ARRAY) {
7242
7920
  this.setData$([]);
7243
7921
  }
@@ -7260,6 +7938,132 @@ class HTTPManagerStateService extends ComponentStore {
7260
7938
  : { ...options.headers };
7261
7939
  return options;
7262
7940
  }
7941
+ buildQueryTrackerOptions(options) {
7942
+ return {
7943
+ watchParams: Array.isArray(options?.watchParams) ? options.watchParams : [],
7944
+ watchExpiresAt: options?.watchExpiresAt,
7945
+ };
7946
+ }
7947
+ normalizeObject(value) {
7948
+ if (Array.isArray(value)) {
7949
+ return value.map((item) => this.normalizeObject(item));
7950
+ }
7951
+ if (value && typeof value === 'object') {
7952
+ return Object.keys(value)
7953
+ .sort()
7954
+ .reduce((acc, key) => {
7955
+ acc[key] = this.normalizeObject(value[key]);
7956
+ return acc;
7957
+ }, {});
7958
+ }
7959
+ return value;
7960
+ }
7961
+ filterHeaders(headers) {
7962
+ const source = headers || {};
7963
+ return Object.keys(source).reduce((acc, key) => {
7964
+ if (!this.volatileHeaders.has(key.toLowerCase())) {
7965
+ acc[key] = source[key];
7966
+ }
7967
+ return acc;
7968
+ }, {});
7969
+ }
7970
+ resolvePath(params) {
7971
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7972
+ const effective = this.getEffectiveParams(params);
7973
+ return effective ? [...basePath, ...effective] : [...basePath];
7974
+ }
7975
+ getEffectiveParams(params) {
7976
+ if (!Array.isArray(params) || params.length === 0) {
7977
+ return undefined;
7978
+ }
7979
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
7980
+ if (basePath.length !== params.length) {
7981
+ return params;
7982
+ }
7983
+ const normalizePart = (value) => {
7984
+ if (value && typeof value === 'object') {
7985
+ return JSON.stringify(this.normalizeObject(value));
7986
+ }
7987
+ return String(value);
7988
+ };
7989
+ const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
7990
+ return samePath ? undefined : params;
7991
+ }
7992
+ buildRequestSignature(method, requestOptions, params) {
7993
+ const signaturePayload = {
7994
+ method,
7995
+ server: requestOptions.server,
7996
+ path: this.resolvePath(params),
7997
+ headers: this.filterHeaders(requestOptions.headers),
7998
+ stream: !!requestOptions.stream,
7999
+ streamType: requestOptions.streamType || null
8000
+ };
8001
+ return JSON.stringify(this.normalizeObject(signaturePayload));
8002
+ }
8003
+ buildSchemaSignature(schema) {
8004
+ return JSON.stringify(this.normalizeObject(schema.split(',').map((part) => part.trim()).filter(Boolean)));
8005
+ }
8006
+ getCachedRequestSignature(tableName, type) {
8007
+ return this.requestSignatureCache[tableName]?.[type] || null;
8008
+ }
8009
+ setCachedRequestSignature(tableName, type, signature) {
8010
+ const existing = this.requestSignatureCache[tableName] || {};
8011
+ this.requestSignatureCache[tableName] = {
8012
+ ...existing,
8013
+ [type]: signature,
8014
+ };
8015
+ }
8016
+ getRequestCacheMetadata(storeData, type) {
8017
+ return storeData?.requestCache?.[type] || null;
8018
+ }
8019
+ getStoredSchemaSignature(storeData) {
8020
+ return storeData?.schemaSignature || null;
8021
+ }
8022
+ saveSchemaSignature(tableName, schemaSignature) {
8023
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8024
+ this.localStorageManagerService.updateStore({
8025
+ name: tableName,
8026
+ data: {
8027
+ ...(storeData || {}),
8028
+ schemaSignature,
8029
+ }
8030
+ });
8031
+ })).subscribe();
8032
+ }
8033
+ saveRequestCacheMetadata(tableName, type, signature, schemaSignature, options) {
8034
+ this.setCachedRequestSignature(tableName, type, signature);
8035
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8036
+ const currentCache = storeData?.requestCache || {};
8037
+ this.localStorageManagerService.updateStore({
8038
+ name: tableName,
8039
+ data: {
8040
+ ...(storeData || {}),
8041
+ schemaSignature: schemaSignature || storeData?.schemaSignature || null,
8042
+ requestCache: {
8043
+ ...currentCache,
8044
+ [type]: {
8045
+ signature,
8046
+ savedAt: Date.now(),
8047
+ path: this.resolvePath(options?.path),
8048
+ headers: this.filterHeaders(options?.headers)
8049
+ }
8050
+ }
8051
+ }
8052
+ });
8053
+ })).subscribe();
8054
+ }
8055
+ clearRequestCacheMetadata(tableName) {
8056
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8057
+ if (!storeData)
8058
+ return;
8059
+ const updated = { ...(storeData || {}) };
8060
+ delete updated.requestCache;
8061
+ this.localStorageManagerService.updateStore({
8062
+ name: tableName,
8063
+ data: updated
8064
+ });
8065
+ })).subscribe();
8066
+ }
7263
8067
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService, deps: [{ token: API_OPTS }, { token: "dataType" }, { token: DatabaseStorage }], target: i0.ɵɵFactoryTarget.Injectable }); }
7264
8068
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService }); }
7265
8069
  }
@@ -8566,12 +9370,12 @@ class StateManagerDemoService extends HTTPManagerStateService {
8566
9370
  setAPIOptions(apiOptions, dataType, database) {
8567
9371
  this.setApiRequestOptions(apiOptions, dataType, database);
8568
9372
  }
8569
- getClients() {
9373
+ getClients(options) {
8570
9374
  // const headers = {
8571
9375
  // auth: "sample-auth-token"
8572
9376
  // }
8573
9377
  // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
8574
- this.fetchRecords();
9378
+ this.fetchRecords(options);
8575
9379
  }
8576
9380
  createClient(data) {
8577
9381
  // const headers = {
@@ -8596,13 +9400,13 @@ class StateManagerDemoService extends HTTPManagerStateService {
8596
9400
  const sampleOptions = RequestOptions.adapt({ path: [data.id] });
8597
9401
  this.deleteRecord(sampleOptions);
8598
9402
  }
8599
- streamRequest() {
9403
+ streamRequest(options) {
8600
9404
  console.log('[DEMO SERVICE] streamRequest called');
8601
9405
  const headers = {
8602
9406
  auth: "sample-auth-token"
8603
9407
  };
8604
9408
  console.log('[DEMO SERVICE] Calling fetchStream with headers:', headers);
8605
- this.fetchStream();
9409
+ this.fetchStream(options);
8606
9410
  }
8607
9411
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8608
9412
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StateManagerDemoService }); }
@@ -8750,6 +9554,8 @@ class RequestManagerStateDemoComponent {
8750
9554
  database: this.fb.group({
8751
9555
  table: [''],
8752
9556
  expiresIn: ['1m'],
9557
+ watchParams: [''],
9558
+ watchExpiresAt: ['never'],
8753
9559
  })
8754
9560
  });
8755
9561
  this.sampleAdaptors = [
@@ -8782,26 +9588,26 @@ class RequestManagerStateDemoComponent {
8782
9588
  });
8783
9589
  this.stateManagerDemoService.data$
8784
9590
  .pipe(tap$1((data) => {
8785
- console.log('[COMPONENT] State data received:', data);
9591
+ // console.log('[COMPONENT] State data received:', data)
8786
9592
  switch (this.requestType) {
8787
9593
  case 'GET':
8788
- console.log('[COMPONENT] Updating GET$ with data:', data);
9594
+ // console.log('[COMPONENT] Updating GET$ with data:', data)
8789
9595
  this.GET$.next(data);
8790
9596
  break;
8791
9597
  case 'PUT':
8792
- console.log('[COMPONENT] Updating PUT$ with data:', data);
9598
+ // console.log('[COMPONENT] Updating PUT$ with data:', data)
8793
9599
  this.PUT$.next(data);
8794
9600
  break;
8795
9601
  case 'POST':
8796
- console.log('[COMPONENT] Updating POST$ with data:', data);
9602
+ // console.log('[COMPONENT] Updating POST$ with data:', data)
8797
9603
  this.POST$.next(data);
8798
9604
  break;
8799
9605
  case 'DELETE':
8800
- console.log('[COMPONENT] Updating DELETE$ with data:', data);
9606
+ // console.log('[COMPONENT] Updating DELETE$ with data:', data)
8801
9607
  this.DELETE$.next(data);
8802
9608
  break;
8803
9609
  case 'STREAM':
8804
- console.log('[COMPONENT] Updating STREAM$ with data:', data);
9610
+ // console.log('[COMPONENT] Updating STREAM$ with data:', data)
8805
9611
  this.STREAM.next(data);
8806
9612
  // Update table columns dynamically based on streaming data
8807
9613
  if (data && Array.isArray(data) && data.length > 0) {
@@ -8809,11 +9615,11 @@ class RequestManagerStateDemoComponent {
8809
9615
  }
8810
9616
  break;
8811
9617
  case 'STREAM_AI':
8812
- console.log('[COMPONENT] Updating STREAM_AI$ with data:', data);
9618
+ // console.log('[COMPONENT] Updating STREAM_AI$ with data:', data)
8813
9619
  this.STREAM_AI.next(data);
8814
9620
  break;
8815
9621
  default:
8816
- console.log('[COMPONENT] No requestType set, ignoring data');
9622
+ // console.log('[COMPONENT] No requestType set, ignoring data')
8817
9623
  break;
8818
9624
  }
8819
9625
  })).subscribe();
@@ -8831,10 +9637,59 @@ class RequestManagerStateDemoComponent {
8831
9637
  removeHeader(index) {
8832
9638
  this.headers.removeAt(index);
8833
9639
  }
9640
+ parsePathInput(pathInput) {
9641
+ const raw = typeof pathInput === 'string' ? pathInput.trim() : '';
9642
+ if (!raw) {
9643
+ return [];
9644
+ }
9645
+ const [rawPath = '', rawQuery = ''] = raw.split('?', 2);
9646
+ const pathSegments = rawPath
9647
+ .split('/')
9648
+ .map((segment) => segment.trim())
9649
+ .filter((segment) => segment.length > 0)
9650
+ .map((segment) => decodeURIComponent(segment));
9651
+ if (!rawQuery) {
9652
+ return pathSegments;
9653
+ }
9654
+ const searchParams = new URLSearchParams(rawQuery);
9655
+ const queryObject = {};
9656
+ searchParams.forEach((value, key) => {
9657
+ const normalizedKey = key.trim();
9658
+ if (!normalizedKey) {
9659
+ return;
9660
+ }
9661
+ const normalizedValue = this.normalizeQueryValue(value);
9662
+ if (Object.prototype.hasOwnProperty.call(queryObject, normalizedKey)) {
9663
+ const currentValue = queryObject[normalizedKey];
9664
+ queryObject[normalizedKey] = Array.isArray(currentValue)
9665
+ ? [...currentValue, normalizedValue]
9666
+ : [currentValue, normalizedValue];
9667
+ return;
9668
+ }
9669
+ queryObject[normalizedKey] = normalizedValue;
9670
+ });
9671
+ return Object.keys(queryObject).length > 0
9672
+ ? [...pathSegments, queryObject]
9673
+ : pathSegments;
9674
+ }
9675
+ normalizeQueryValue(value) {
9676
+ const trimmedValue = value.trim();
9677
+ const lowerValue = trimmedValue.toLowerCase();
9678
+ if (lowerValue === 'true') {
9679
+ return true;
9680
+ }
9681
+ if (lowerValue === 'false') {
9682
+ return false;
9683
+ }
9684
+ if (trimmedValue !== '' && !Number.isNaN(Number(trimmedValue))) {
9685
+ return Number(trimmedValue);
9686
+ }
9687
+ return trimmedValue;
9688
+ }
8834
9689
  compileRequest() {
8835
9690
  const requestParams = this.requestForm.value;
8836
9691
  requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
8837
- const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
9692
+ const pathReq = this.parsePathInput(requestParams.path || '');
8838
9693
  if (!this.pollingState.checked)
8839
9694
  requestParams.polling = 0;
8840
9695
  if (!this.failedState.checked) {
@@ -8848,12 +9703,53 @@ class RequestManagerStateDemoComponent {
8848
9703
  const apiOptions = ApiRequest.adapt({ ...currentOptions, path: pathReq });
8849
9704
  return { apiOptions: apiOptions, path: pathReq };
8850
9705
  }
9706
+ parseWatchParams(value) {
9707
+ if (!value || typeof value !== 'string') {
9708
+ return [];
9709
+ }
9710
+ return value
9711
+ .split(',')
9712
+ .map((item) => item.trim())
9713
+ .filter((item) => item.length > 0);
9714
+ }
9715
+ normalizeWatchExpiry(value) {
9716
+ if (typeof value === 'undefined' || value === null || value === '') {
9717
+ return undefined;
9718
+ }
9719
+ if (typeof value === 'string' && value.trim().toLowerCase() === 'never') {
9720
+ return undefined;
9721
+ }
9722
+ return value;
9723
+ }
9724
+ buildDemoRequestOptions(path, headers) {
9725
+ const dbValue = this.database || {};
9726
+ const isDbEnabled = !!this.DBState?.checked;
9727
+ return RequestOptions.adapt({
9728
+ path,
9729
+ headers,
9730
+ forceRefresh: false,
9731
+ watchParams: isDbEnabled ? this.parseWatchParams(dbValue?.watchParams) : [],
9732
+ watchExpiresAt: isDbEnabled ? this.normalizeWatchExpiry(dbValue?.watchExpiresAt) : undefined,
9733
+ });
9734
+ }
8851
9735
  onSetStateOptions() {
8852
- if (!this.isValid)
9736
+ const dbValue = this.database;
9737
+ console.log('[onSetStateOptions] Called, checking form values:', {
9738
+ isValid: this.isValid,
9739
+ database: dbValue,
9740
+ hasTable: !!dbValue?.table,
9741
+ tableValue: dbValue?.table,
9742
+ dataType: this.dataType
9743
+ });
9744
+ if (!this.isValid) {
9745
+ console.log('[onSetStateOptions] Form invalid, aborting');
8853
9746
  return;
9747
+ }
8854
9748
  const reqParams = this.compileRequest();
8855
- const db = DatabaseStorage.adapt(this.database);
9749
+ const db = DatabaseStorage.adapt(dbValue);
9750
+ console.log('[onSetStateOptions] DatabaseStorage.adapt result:', db);
8856
9751
  const type = this.dataType === "ARRAY" ? DataType.ARRAY : DataType.OBJECT;
9752
+ console.log('[onSetStateOptions] Calling setAPIOptions with:', { db, type, hasTable: !!db?.table });
8857
9753
  this.stateManagerDemoService.setAPIOptions(reqParams.apiOptions, type, db);
8858
9754
  this.requestForm.markAsPristine();
8859
9755
  }
@@ -8862,7 +9758,9 @@ class RequestManagerStateDemoComponent {
8862
9758
  }
8863
9759
  onGetRequest() {
8864
9760
  this.requestType = 'GET';
8865
- this.stateManagerDemoService.getClients();
9761
+ const reqParams = this.compileRequest();
9762
+ const requestOptions = this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers);
9763
+ this.stateManagerDemoService.getClients(requestOptions);
8866
9764
  }
8867
9765
  onCreateRequest() {
8868
9766
  this.requestType = 'POST';
@@ -8877,19 +9775,22 @@ class RequestManagerStateDemoComponent {
8877
9775
  this.stateManagerDemoService.deleteClient(this.sampleClientData);
8878
9776
  }
8879
9777
  onStreamRequest() {
8880
- console.log('[COMPONENT] onStreamRequest called');
9778
+ // console.log('[COMPONENT] onStreamRequest called')
8881
9779
  if (!this.isValid) {
8882
- console.log('[COMPONENT] Form invalid, aborting');
9780
+ // console.log('[COMPONENT] Form invalid, aborting')
8883
9781
  return;
8884
9782
  }
8885
- console.log('[COMPONENT] Compiling request...');
9783
+ // console.log('[COMPONENT] Compiling request...')
8886
9784
  const reqParams = this.compileRequest();
8887
- console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions);
9785
+ // console.log('[COMPONENT] Setting stream options:', reqParams.apiOptions)
8888
9786
  reqParams.apiOptions.stream = true;
8889
9787
  reqParams.apiOptions.streamType = this.streamType;
8890
9788
  this.requestType = 'STREAM';
8891
- console.log('[COMPONENT] Calling streamRequest...');
8892
- this.stateManagerDemoService.streamRequest();
9789
+ const streamOptions = RequestOptions.adapt({
9790
+ ...this.buildDemoRequestOptions(reqParams.path, reqParams.apiOptions.headers),
9791
+ });
9792
+ // console.log('[COMPONENT] Calling streamRequest...')
9793
+ this.stateManagerDemoService.streamRequest(streamOptions);
8893
9794
  }
8894
9795
  errorHandling(err, type) {
8895
9796
  console.log(err, type);
@@ -8913,11 +9814,11 @@ class RequestManagerStateDemoComponent {
8913
9814
  this.prompts = [];
8914
9815
  }
8915
9816
  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" }] }); }
9817
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: { server: "server", adapter: "adapter", mapper: "mapper" }, providers: [StateManagerDemoService, HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }, { propertyName: "DBState", first: true, predicate: ["DBState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"margin-top: 1rem\" formGroupName=\"database\">\n <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Watch Params (comma delimited)</mat-label>\n <input matInput placeholder=\"sort,active\" formControlName=\"watchParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"watchExpiresAt\">\n <mat-option value=\"never\">Never</mat-option>\n <mat-option value=\"10s\">10 Seconds</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1m\">1 Minute</mat-option>\n <mat-option value=\"5m\">5 Minutes</mat-option>\n <mat-option value=\"10m\">10 Minutes</mat-option>\n <mat-option value=\"1h\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i2$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
8917
9818
  }
8918
9819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
8919
9820
  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"] }]
9821
+ args: [{ selector: 'app-request-manager-state-demo', providers: [StateManagerDemoService, HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Request State Manager\n </h2>\n\n <div [formGroup]=\"requestForm\" style=\"margin-top: 2rem;\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>State Data Type</mat-label>\n <mat-select formControlName=\"datatype\">\n <mat-option value=\"ARRAY\">Array</mat-option>\n <mat-option value=\"OBJECT\">Object</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Adapter (Model)</mat-label>\n <mat-select formControlName=\"adapter\" #adapterSelect>\n <mat-option>None</mat-option>\n @for (adapter of sampleAdaptors; track adapter) {\n <mat-option [value]=\"adapter.value\">\n {{adapter.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Mapper (Model)</mat-label>\n <mat-select formControlName=\"mapper\" #mapperSelect>\n <mat-option>None</mat-option>\n @for (mapper of sampleMappers; track mapper) {\n <mat-option [value]=\"mapper.value\">\n {{mapper.label}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (adapterSelect.value || mapperSelect.value) {\n <div style=\"display: flex; margin-bottom: 2rem;\">\n <div style=\"flex:1\" class=\"box\">\n <h3>Adapter (Incoming)</h3>\n @if (adapterSelect.value) {\n <div>\n {{ props(adapterSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n <div style=\"flex:1\" class=\"box\">\n <h3>Mapper (Outgoing)</h3>\n @if (mapperSelect.value) {\n <div>\n {{ props(mapperSelect.value) | json }}\n </div>\n } @else {\n No Transformation\n }\n </div>\n </div>\n }\n\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>RestPath (/ delimited)</mat-label>\n <input matInput placeholder=\"clients/list\" formControlName=\"path\">\n </mat-form-field>\n </div>\n <div>\n <div formArrayName=\"headers\">\n @for (task of headers.controls; track task; let i = $index) {\n <div [formGroupName]=\"i\">\n <div style=\"display: flex; gap: .5rem\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Key</mat-label>\n <input matInput placeholder=\"authentication\" formControlName=\"key\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Value</mat-label>\n <input matInput placeholder=\"sample\" formControlName=\"value\">\n </mat-form-field>\n <div style=\"margin-top: .5rem;\">\n <button mat-icon-button (click)=\"removeHeader(i)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n <button mat-stroked-button (click)=\"addHeader()\">Add Header</button>\n </div>\n <div style=\"margin-top: 2rem; display: flex; flex-direction:column; gap: 1rem;\">\n <div>\n <mat-slide-toggle #failedState>Retry on Failed</mat-slide-toggle>\n @if (failedState.checked) {\n <div style=\"display: flex; gap: .5rem; margin-top: 1rem;\" formGroupName=\"retry\">\n <mat-form-field appearance=\"outline\">\n <mat-label>#of Times</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"times\" value=\"3\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Delay Until Next</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"delay\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #pollingState>Polling</mat-slide-toggle>\n @if (pollingState.checked) {\n <div>\n <mat-form-field appearance=\"outline\" style=\"margin-top: 1rem\">\n <mat-label>#of Seconds</mat-label>\n <input matInput placeholder=\"3\" formControlName=\"polling\" value=\"3\">\n </mat-form-field>\n </div>\n }\n </div>\n <div>\n <mat-slide-toggle #DBState>Database Storage</mat-slide-toggle>\n @if (DBState.checked) {\n <div style=\"margin-top: 1rem\" formGroupName=\"database\">\n <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Table Name</mat-label>\n <input matInput placeholder=\"table\" formControlName=\"table\" value=\"\">\n </mat-form-field>\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Expires In</mat-label>\n <mat-select formControlName=\"expiresIn\">\n <mat-option value=\"1m\">One Minute</mat-option>\n <mat-option value=\"1h\">One Hour</mat-option>\n <mat-option value=\"1d\">One Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n <div style=\"display: flex; gap: .5rem;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Watch Params (comma delimited)</mat-label>\n <input matInput placeholder=\"sort,active\" formControlName=\"watchParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"watchExpiresAt\">\n <mat-option value=\"never\">Never</mat-option>\n <mat-option value=\"10s\">10 Seconds</mat-option>\n <mat-option value=\"30s\">30 Seconds</mat-option>\n <mat-option value=\"1m\">1 Minute</mat-option>\n <mat-option value=\"5m\">5 Minutes</mat-option>\n <mat-option value=\"10m\">10 Minutes</mat-option>\n <mat-option value=\"1h\">1 Hour</mat-option>\n <mat-option value=\"1d\">1 Day</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n }\n </div>\n <div style=\"margin-top: 1rem; display: flex;\">\n <span style=\"flex:1\"></span>\n <button mat-stroked-button (click)=\"onSetStateOptions()\" [disabled]=\"!hasChanged\">\n Set API Request Options\n </button>\n </div>\n <div>\n @if ((error$ | async); as error) {\n <mat-error>\n {{ error }}\n </mat-error>\n }\n </div>\n </div>\n </div>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked) {\n <div>\n @if (!(isPending$ | async) && (this.countdown$ | async) || -1 > 0) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n }\n\n </div>\n\n @if ((GET$ | async); as data) {\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1;\">CURRENT STATE ({{ dataType }})</h2>\n </div>\n @if (data.length > 1) {\n <div>\n <mat-form-field appearance=\"outline\">\n <mat-label>Records</mat-label>\n <mat-select [formControl]=\"selectedRecord\" [disabled]=\"data === null && data?.length === 0\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track item) {\n <mat-option [value]=\"item\">\n {{item.name || (item.first_name) | titlecase}}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n }\n @if ((dataObservable$ | async); as dataRecord) {\n <div>\n @if ((selectedRecord$ | async); as record) {\n <div>\n {{ record | json }}\n </div>\n } @else {\n No Record Selected from State\n }\n </div>\n }\n <div style=\"margin-top: 1rem;\">\n @if (data !== null && data?.length > 0) {\n <span>\n State contains a Total of {{ data.length }} Records\n </span>\n } @else {\n No Records\n }\n <div style=\"display: flex; gap: .5rem; text-align: end; justify-content: flex-end;\">\n <button class=\"btn\" mat-stroked-button (click)=\"onClearRecords()\">Clear</button>\n </div>\n </div>\n </div>\n }\n\n\n\n\n <div style=\"margin-top: 1rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request ({{ dataType }})</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n @if ((GET$ | async); as getData) {\n <div>{{ getData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n @if ((POST$ | async); as postData) {\n <div>{{ postData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n @if ((PUT$ | async); as putData) {\n <div>{{ putData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" [disabled]=\"hasChanged\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n @if ((DELETE$ | async); as deleteData) {\n <div>{{ deleteData | json }}</div>\n }\n </div>\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\" [disabled]=\"hasChanged\">Request</button>\n </div>\n </div>\n\n @if ((STREAM_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Data received\n </div>\n </div>\n }\n </div>\n\n </div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
8921
9822
  }], ctorParameters: () => [], propDecorators: { server: [{
8922
9823
  type: Input
8923
9824
  }], adapter: [{
@@ -8930,6 +9831,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8930
9831
  }], pollingState: [{
8931
9832
  type: ViewChild,
8932
9833
  args: ["pollingState", { static: true }]
9834
+ }], DBState: [{
9835
+ type: ViewChild,
9836
+ args: ["DBState", { static: true }]
8933
9837
  }] } });
8934
9838
 
8935
9839
  class RequestManagerDemoComponent {
@@ -9934,6 +10838,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9934
10838
  }]
9935
10839
  }] });
9936
10840
 
10841
+ class UserData {
10842
+ constructor(ldap = '', name = '', email = '', color = RandomPaletteColor()) {
10843
+ this.ldap = ldap;
10844
+ this.name = name;
10845
+ this.email = email;
10846
+ this.color = color;
10847
+ }
10848
+ static adapt(item) {
10849
+ const userName = `${item?.first_name} ${item?.last_name}`;
10850
+ return new UserData(item?.ldap, userName, item?.email, item?.color);
10851
+ }
10852
+ }
10853
+
9937
10854
  class StateDataRequestService extends HTTPManagerStateService {
9938
10855
  constructor() {
9939
10856
  super(ApiRequest.adapt({
@@ -11763,5 +12680,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11763
12680
  * Generated bundle index. Do not edit.
11764
12681
  */
11765
12682
 
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 };
12683
+ export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, InvalidFileInfoModel, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsDemoComponent, LocalStorageSignalsManagerService, LoggerService, NormalizedRequestOptionsModel, NotificationMessage, OperationResultModel, PathQueryService, PathTrackerStateModel, PublicMessage, QueryParamsTrackerOptionsModel, QueryParamsTrackerService, QueryTrackerStateModel, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateMessage, StateOperationResult, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StoreStateSignalsDemoComponent, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UploadDemoComponent, UploadValidationErrorModel, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, calculateBatchProgress, countdown, createChannelName, delayedRetry, isErrorState, isPendingState, isSuccessState, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
11767
12684
  //# sourceMappingURL=http-request-manager.mjs.map