http-request-manager 18.13.27 → 18.13.29

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, take, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, scan, distinctUntilChanged } from 'rxjs/operators';
4
+ import { map, catchError, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, take, 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,14 +875,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
875
875
  }]
876
876
  }], ctorParameters: () => [] });
877
877
 
878
+ class NormalizedRequestOptionsModel {
879
+ constructor(pathKey = '', query = {}, hasQuery = false) {
880
+ this.pathKey = pathKey;
881
+ this.query = query;
882
+ this.hasQuery = hasQuery;
883
+ }
884
+ static adapt(item) {
885
+ return new NormalizedRequestOptionsModel(item?.pathKey, item?.query, item?.hasQuery);
886
+ }
887
+ }
888
+
878
889
  class PathTrackerStateModel {
879
- constructor(baselineQuery, consumedValuesByKey = {}, watchExpiresAt) {
880
- this.baselineQuery = baselineQuery;
890
+ constructor(consumedValuesByKey = {}, baselineQuery, watchExpiresAt) {
881
891
  this.consumedValuesByKey = consumedValuesByKey;
892
+ this.baselineQuery = baselineQuery;
882
893
  this.watchExpiresAt = watchExpiresAt;
883
894
  }
884
895
  static adapt(item) {
885
- return new PathTrackerStateModel(item?.baselineQuery, item?.consumedValuesByKey, item?.watchExpiresAt);
896
+ return new PathTrackerStateModel(item?.consumedValuesByKey && typeof item.consumedValuesByKey === 'object' ? item.consumedValuesByKey : {}, item?.baselineQuery, item?.watchExpiresAt);
886
897
  }
887
898
  }
888
899
 
@@ -891,489 +902,136 @@ class QueryTrackerStateModel {
891
902
  this.paths = paths;
892
903
  }
893
904
  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
- : {});
905
+ const paths = {};
906
+ if (item?.paths && typeof item.paths === 'object') {
907
+ Object.keys(item.paths).forEach((key) => {
908
+ paths[key] = PathTrackerStateModel.adapt(item.paths[key]);
909
+ });
910
+ }
911
+ return new QueryTrackerStateModel(paths);
900
912
  }
901
913
  }
902
914
 
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
- }
915
+ class QueryParamsTrackerOptionsModel {
916
+ constructor(mode, watchParams, watchExpiresAt, watchParamsExpire) {
917
+ this.mode = mode;
918
+ this.watchParams = watchParams;
919
+ this.watchExpiresAt = watchExpiresAt;
920
+ this.watchParamsExpire = watchParamsExpire;
921
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
- const pathState = this.ensurePathState(normalized.pathKey);
930
- const pathSeenBefore = !!pathState.baselineQuery;
931
- if (!pathSeenBefore) {
932
- pathState.baselineQuery = {};
933
- this.persistState();
934
- }
935
- return !pathSeenBefore;
936
- }
937
- if (options.mode === 'exact') {
938
- return this.checkExact(normalized, options);
939
- }
940
- return this.checkVariation(normalized, options);
922
+ static adapt(item) {
923
+ return new QueryParamsTrackerOptionsModel(item?.mode, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt, item?.watchParamsExpire);
941
924
  }
942
- matchesPath(requestOptions = [], expectedPathOptions = []) {
943
- this.ensureStateRestored();
944
- const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
945
- const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
946
- return Boolean(requestPath) && requestPath === expectedPath;
925
+ }
926
+
927
+ /**
928
+ * Stateful processor for managing streaming data and events
929
+ */
930
+ class StreamingProcessor {
931
+ constructor(config) {
932
+ this.buffer = '';
933
+ this.contentType = '';
934
+ this.maxBufferSize = 10 * 1024 * 1024; // 10MB limit
935
+ this.lastParsedLength = 0; // Track parsed position to avoid duplicates
936
+ this.streamConfig = config || { streamType: StreamType.AI_STREAMING };
947
937
  }
948
- checkExact(normalized, options) {
949
- const pathState = this.ensurePathState(normalized.pathKey);
950
- const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
951
- if (Object.keys(filteredQuery).length === 0) {
952
- return false;
938
+ /**
939
+ * Process HTTP events and extract streaming data with progress
940
+ */
941
+ process(event) {
942
+ switch (event.type) {
943
+ case HttpEventType.ResponseHeader:
944
+ this.contentType = event.headers?.get('content-type') || '';
945
+ // Read total from response header if configured
946
+ if (this.streamConfig.totalHeader && event.headers) {
947
+ const totalVal = event.headers.get(this.streamConfig.totalHeader);
948
+ if (totalVal !== undefined && totalVal !== null) {
949
+ const parsed = parseInt(totalVal, 10);
950
+ if (!isNaN(parsed)) {
951
+ this.totalFromHeader = parsed;
952
+ }
953
+ }
954
+ }
955
+ return null;
956
+ case HttpEventType.DownloadProgress:
957
+ if (event.partialText) {
958
+ this.appendToBuffer(event.partialText);
959
+ const parsedData = this.parseBuffer();
960
+ // Only return NEW items since last parse (progressive updates)
961
+ const newItems = parsedData.slice(this.lastParsedLength);
962
+ this.lastParsedLength = parsedData.length;
963
+ // Calculate progress
964
+ const progress = {
965
+ received: this.lastParsedLength,
966
+ total: this.totalFromHeader,
967
+ percent: this.totalFromHeader
968
+ ? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
969
+ : 0,
970
+ stage: 'streaming'
971
+ };
972
+ return {
973
+ data: newItems.length > 0 ? newItems : [],
974
+ progress
975
+ };
976
+ }
977
+ return null;
978
+ case HttpEventType.Response:
979
+ if (event.body) {
980
+ this.appendToBuffer(event.body);
981
+ const parsedData = this.parseBuffer();
982
+ // Calculate final progress
983
+ const progress = {
984
+ received: parsedData.length,
985
+ total: this.totalFromHeader,
986
+ percent: this.totalFromHeader
987
+ ? Math.round((parsedData.length / this.totalFromHeader) * 100)
988
+ : 100,
989
+ stage: 'complete'
990
+ };
991
+ return {
992
+ data: parsedData.length > 0 ? parsedData : [],
993
+ progress
994
+ };
995
+ }
996
+ return null;
997
+ default:
998
+ return null;
953
999
  }
954
- if (!pathState.baselineQuery) {
955
- pathState.baselineQuery = filteredQuery;
956
- this.persistState();
957
- return true;
1000
+ }
1001
+ /**
1002
+ * Append data to buffer with size management
1003
+ */
1004
+ appendToBuffer(data) {
1005
+ this.buffer += data;
1006
+ // Implement sliding window if buffer gets too large
1007
+ if (this.buffer.length > this.maxBufferSize) {
1008
+ this.buffer = this.buffer.slice(-this.maxBufferSize / 2);
958
1009
  }
959
- return this.stringifyQuery(pathState.baselineQuery) !== this.stringifyQuery(filteredQuery);
960
1010
  }
961
- checkVariation(normalized, options) {
962
- const pathState = this.ensurePathState(normalized.pathKey);
963
- if (Array.isArray(options.watchParams) && options.watchParams.length === 0) {
964
- const pathSeenBefore = !!pathState.baselineQuery;
965
- if (!pathSeenBefore) {
966
- pathState.baselineQuery = {};
967
- pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
968
- this.persistState();
969
- }
970
- return !pathSeenBefore;
1011
+ /**
1012
+ * Parse current buffer content
1013
+ */
1014
+ parseBuffer() {
1015
+ if (!this.buffer.trim()) {
1016
+ return [];
971
1017
  }
972
- const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
973
- const keys = Object.keys(filteredQuery);
974
- if (keys.length === 0) {
975
- return false;
1018
+ try {
1019
+ const result = parseStreamData(this.buffer, this.contentType, this.streamConfig);
1020
+ return result;
976
1021
  }
977
- this.resetPathStateIfExpired(pathState);
978
- let accepted = false;
979
- keys.forEach((key) => {
980
- const currentValue = filteredQuery[key];
981
- const consumed = pathState.consumedValuesByKey[key] || [];
982
- if (!consumed.includes(currentValue)) {
983
- pathState.consumedValuesByKey[key] = [...consumed, currentValue];
984
- accepted = true;
985
- }
986
- });
987
- if (accepted) {
988
- if (!pathState.baselineQuery) {
989
- pathState.baselineQuery = filteredQuery;
990
- }
991
- pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
992
- this.persistState();
1022
+ catch (error) {
1023
+ console.warn('Failed to parse streaming data:', error);
1024
+ return [];
993
1025
  }
994
- return accepted;
995
1026
  }
996
- filterQueryByWatchParams(query, watchParams) {
997
- if (!Array.isArray(watchParams)) {
998
- return query;
999
- }
1000
- if (watchParams.length === 0) {
1001
- return {};
1002
- }
1003
- const normalizedWatchParams = watchParams.map((p) => this.normalizeParamKey(p));
1004
- return Object.keys(query).reduce((acc, key) => {
1005
- if (normalizedWatchParams.includes(key)) {
1006
- acc[key] = query[key];
1007
- }
1008
- return acc;
1009
- }, {});
1010
- }
1011
- normalizeRequestOptions(requestOptions) {
1012
- const params = Array.isArray(requestOptions) ? requestOptions : [];
1013
- const pathSegments = [];
1014
- const queryObjects = [];
1015
- params.forEach((item) => {
1016
- if (this.isPlainObject(item)) {
1017
- queryObjects.push(item);
1018
- }
1019
- else if (typeof item !== 'undefined' && item !== null) {
1020
- const parsed = this.parsePathSegment(String(item));
1021
- if (parsed.pathSegment) {
1022
- pathSegments.push(parsed.pathSegment);
1023
- }
1024
- if (Object.keys(parsed.queryObject).length > 0) {
1025
- queryObjects.push(parsed.queryObject);
1026
- }
1027
- }
1028
- });
1029
- const query = queryObjects.reduce((acc, current) => {
1030
- Object.keys(current).forEach((key) => {
1031
- const normalizedKey = this.normalizeParamKey(key);
1032
- const value = current[key];
1033
- if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
1034
- return;
1035
- }
1036
- acc[normalizedKey] = this.normalizeParamValue(value);
1037
- });
1038
- return acc;
1039
- }, {});
1040
- return {
1041
- pathKey: this.normalizePath(pathSegments),
1042
- query,
1043
- hasQuery: Object.keys(query).length > 0,
1044
- };
1045
- }
1046
- parsePathSegment(rawSegment) {
1047
- const [pathPart, ...queryParts] = String(rawSegment).split('?');
1048
- const queryString = queryParts.join('?');
1049
- const queryObject = {};
1050
- if (queryString) {
1051
- queryString.split('&').forEach((pair) => {
1052
- if (!pair)
1053
- return;
1054
- const [rawKey, ...rawValueParts] = pair.split('=');
1055
- const key = this.safeDecode(rawKey).trim();
1056
- const value = this.safeDecode(rawValueParts.join('=')).trim();
1057
- if (!key)
1058
- return;
1059
- queryObject[key] = value;
1060
- });
1061
- }
1062
- return {
1063
- pathSegment: pathPart,
1064
- queryObject,
1065
- };
1066
- }
1067
- safeDecode(value) {
1068
- if (typeof value === 'undefined') {
1069
- return '';
1070
- }
1071
- try {
1072
- return decodeURIComponent(value);
1073
- }
1074
- catch {
1075
- return String(value);
1076
- }
1077
- }
1078
- normalizePath(pathSegments) {
1079
- return pathSegments
1080
- .map((segment) => String(segment).trim())
1081
- .filter((segment) => segment.length > 0)
1082
- .join('/')
1083
- .replace(/([^:]\/+)\/+/g, '$1')
1084
- .replace(/^\//, '')
1085
- .replace(/\/$/, '');
1086
- }
1087
- normalizeParamKey(key) {
1088
- return String(key).trim().toLowerCase();
1089
- }
1090
- normalizeParamValue(value) {
1091
- if (Array.isArray(value)) {
1092
- return value.map((item) => this.normalizeParamValue(item)).join(',');
1093
- }
1094
- if (this.isPlainObject(value)) {
1095
- return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
1096
- acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
1097
- return acc;
1098
- }, {}));
1099
- }
1100
- return String(value).trim().toLowerCase();
1101
- }
1102
- buildExpiryEpoch(expireIn) {
1103
- if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
1104
- return undefined;
1105
- }
1106
- if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
1107
- return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
1108
- }
1109
- const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
1110
- const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
1111
- if (!parsed) {
1112
- return undefined;
1113
- }
1114
- const value = Number(parsed[1]);
1115
- const unit = parsed[2];
1116
- const toSeconds = {
1117
- y: 31556926,
1118
- w: 604800,
1119
- d: 86400,
1120
- hr: 3600,
1121
- h: 3600,
1122
- mn: 60,
1123
- min: 60,
1124
- m: 60,
1125
- s: 1,
1126
- };
1127
- const seconds = toSeconds[unit];
1128
- if (!seconds) {
1129
- return undefined;
1130
- }
1131
- return Math.floor(Date.now() / 1000) + value * seconds;
1132
- }
1133
- resetPathStateIfExpired(pathState) {
1134
- const now = Math.floor(Date.now() / 1000);
1135
- if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1136
- pathState.consumedValuesByKey = {};
1137
- pathState.watchExpiresAt = undefined;
1138
- }
1139
- }
1140
- cleanupExpiredEntries() {
1141
- const now = Math.floor(Date.now() / 1000);
1142
- Object.keys(this.state.paths).forEach((pathKey) => {
1143
- const pathState = this.state.paths[pathKey];
1144
- if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
1145
- pathState.consumedValuesByKey = {};
1146
- pathState.watchExpiresAt = undefined;
1147
- }
1148
- const hasBaseline = pathState.baselineQuery !== undefined;
1149
- const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
1150
- if (!hasBaseline && !hasTrackedValues) {
1151
- delete this.state.paths[pathKey];
1152
- }
1153
- });
1154
- }
1155
- ensurePathState(pathKey) {
1156
- if (!this.state.paths[pathKey]) {
1157
- this.state.paths[pathKey] = {
1158
- consumedValuesByKey: {},
1159
- };
1160
- }
1161
- return this.state.paths[pathKey];
1162
- }
1163
- ensureStateRestored() {
1164
- if (this.stateRestored) {
1165
- return;
1166
- }
1167
- this.stateRestored = true;
1168
- // this.initializeTrackingForSession();
1169
- this.restoreState();
1170
- }
1171
- // private initializeTrackingForSession(): void {
1172
- // if (!this.hasSessionStorage()) {
1173
- // return;
1174
- // }
1175
- // const initialized = sessionStorage.getItem(TRACKER_SESSION_INIT_KEY);
1176
- // if (initialized === '1') {
1177
- // return;
1178
- // }
1179
- // this.clearTracking();
1180
- // sessionStorage.setItem(TRACKER_SESSION_INIT_KEY, '1');
1181
- // }
1182
- restoreState() {
1183
- this.localStorageManager.store$(TRACKER_STORE_NAME)
1184
- .pipe(take(1))
1185
- .subscribe({
1186
- next: (storedState) => {
1187
- if (this.isTrackerState(storedState)) {
1188
- this.state = QueryTrackerStateModel.adapt(storedState);
1189
- this.cleanupExpiredEntries();
1190
- return;
1191
- }
1192
- this.state = { paths: {} };
1193
- },
1194
- error: () => {
1195
- this.state = { paths: {} };
1196
- }
1197
- });
1198
- }
1199
- persistState() {
1200
- this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
1201
- .pipe(take(1))
1202
- .subscribe((exists) => {
1203
- if (exists) {
1204
- this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
1205
- return;
1206
- }
1207
- this.localStorageManager.createStore({
1208
- name: TRACKER_STORE_NAME,
1209
- data: this.state,
1210
- options: DEFAULT_TRACKER_OPTIONS,
1211
- });
1212
- });
1213
- }
1214
- stringifyQuery(query) {
1215
- return JSON.stringify(Object.keys(query)
1216
- .sort()
1217
- .reduce((acc, key) => {
1218
- acc[key] = query[key];
1219
- return acc;
1220
- }, {}));
1221
- }
1222
- isTrackerState(value) {
1223
- return this.isPlainObject(value) && this.isPlainObject(value.paths);
1224
- }
1225
- isPlainObject(value) {
1226
- return Object.prototype.toString.call(value) === '[object Object]';
1227
- }
1228
- hasSessionStorage() {
1229
- try {
1230
- return typeof sessionStorage !== 'undefined';
1231
- }
1232
- catch {
1233
- return false;
1234
- }
1235
- }
1236
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1237
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
1238
- }
1239
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
1240
- type: Injectable,
1241
- args: [{
1242
- providedIn: 'root'
1243
- }]
1244
- }] });
1245
-
1246
- class NormalizedRequestOptionsModel {
1247
- constructor(pathKey = '', query = {}, hasQuery = false) {
1248
- this.pathKey = pathKey;
1249
- this.query = query;
1250
- this.hasQuery = hasQuery;
1251
- }
1252
- static adapt(item) {
1253
- return new NormalizedRequestOptionsModel(item?.pathKey, item?.query, item?.hasQuery);
1254
- }
1255
- }
1256
-
1257
- class QueryParamsTrackerOptionsModel {
1258
- constructor(mode, watchParams, watchExpiresAt, watchParamsExpire) {
1259
- this.mode = mode;
1260
- this.watchParams = watchParams;
1261
- this.watchExpiresAt = watchExpiresAt;
1262
- this.watchParamsExpire = watchParamsExpire;
1263
- }
1264
- static adapt(item) {
1265
- return new QueryParamsTrackerOptionsModel(item?.mode, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt, item?.watchParamsExpire);
1266
- }
1267
- }
1268
-
1269
- /**
1270
- * Stateful processor for managing streaming data and events
1271
- */
1272
- class StreamingProcessor {
1273
- constructor(config) {
1274
- this.buffer = '';
1275
- this.contentType = '';
1276
- this.maxBufferSize = 10 * 1024 * 1024; // 10MB limit
1277
- this.lastParsedLength = 0; // Track parsed position to avoid duplicates
1278
- this.streamConfig = config || { streamType: StreamType.AI_STREAMING };
1279
- }
1280
- /**
1281
- * Process HTTP events and extract streaming data with progress
1282
- */
1283
- process(event) {
1284
- switch (event.type) {
1285
- case HttpEventType.ResponseHeader:
1286
- this.contentType = event.headers?.get('content-type') || '';
1287
- // Read total from response header if configured
1288
- if (this.streamConfig.totalHeader && event.headers) {
1289
- const totalVal = event.headers.get(this.streamConfig.totalHeader);
1290
- if (totalVal !== undefined && totalVal !== null) {
1291
- const parsed = parseInt(totalVal, 10);
1292
- if (!isNaN(parsed)) {
1293
- this.totalFromHeader = parsed;
1294
- }
1295
- }
1296
- }
1297
- return null;
1298
- case HttpEventType.DownloadProgress:
1299
- if (event.partialText) {
1300
- this.appendToBuffer(event.partialText);
1301
- const parsedData = this.parseBuffer();
1302
- // Only return NEW items since last parse (progressive updates)
1303
- const newItems = parsedData.slice(this.lastParsedLength);
1304
- this.lastParsedLength = parsedData.length;
1305
- // Calculate progress
1306
- const progress = {
1307
- received: this.lastParsedLength,
1308
- total: this.totalFromHeader,
1309
- percent: this.totalFromHeader
1310
- ? Math.round((this.lastParsedLength / this.totalFromHeader) * 100)
1311
- : 0,
1312
- stage: 'streaming'
1313
- };
1314
- return {
1315
- data: newItems.length > 0 ? newItems : [],
1316
- progress
1317
- };
1318
- }
1319
- return null;
1320
- case HttpEventType.Response:
1321
- if (event.body) {
1322
- this.appendToBuffer(event.body);
1323
- const parsedData = this.parseBuffer();
1324
- // Calculate final progress
1325
- const progress = {
1326
- received: parsedData.length,
1327
- total: this.totalFromHeader,
1328
- percent: this.totalFromHeader
1329
- ? Math.round((parsedData.length / this.totalFromHeader) * 100)
1330
- : 100,
1331
- stage: 'complete'
1332
- };
1333
- return {
1334
- data: parsedData.length > 0 ? parsedData : [],
1335
- progress
1336
- };
1337
- }
1338
- return null;
1339
- default:
1340
- return null;
1341
- }
1342
- }
1343
- /**
1344
- * Append data to buffer with size management
1345
- */
1346
- appendToBuffer(data) {
1347
- this.buffer += data;
1348
- // Implement sliding window if buffer gets too large
1349
- if (this.buffer.length > this.maxBufferSize) {
1350
- this.buffer = this.buffer.slice(-this.maxBufferSize / 2);
1351
- }
1352
- }
1353
- /**
1354
- * Parse current buffer content
1355
- */
1356
- parseBuffer() {
1357
- if (!this.buffer.trim()) {
1358
- return [];
1359
- }
1360
- try {
1361
- const result = parseStreamData(this.buffer, this.contentType, this.streamConfig);
1362
- return result;
1363
- }
1364
- catch (error) {
1365
- console.warn('Failed to parse streaming data:', error);
1366
- return [];
1367
- }
1368
- }
1369
- /**
1370
- * Reset processor state
1371
- */
1372
- reset() {
1373
- this.buffer = '';
1374
- this.contentType = '';
1375
- this.lastParsedLength = 0;
1376
- this.totalFromHeader = undefined;
1027
+ /**
1028
+ * Reset processor state
1029
+ */
1030
+ reset() {
1031
+ this.buffer = '';
1032
+ this.contentType = '';
1033
+ this.lastParsedLength = 0;
1034
+ this.totalFromHeader = undefined;
1377
1035
  }
1378
1036
  /**
1379
1037
  * Update configuration
@@ -5339,15 +4997,17 @@ class ApiRequest {
5339
4997
  }
5340
4998
 
5341
4999
  class RequestOptions {
5342
- constructor(path = [], headers = {}, forceRefresh, watchParams, watchExpiresAt) {
5000
+ constructor(path = [], headers = {}, forceRefresh, ignoreQueryParams, queryParamsExpiresIn, watchParams, watchExpiresAt) {
5343
5001
  this.path = path;
5344
5002
  this.headers = headers;
5345
5003
  this.forceRefresh = forceRefresh;
5004
+ this.ignoreQueryParams = ignoreQueryParams;
5005
+ this.queryParamsExpiresIn = queryParamsExpiresIn;
5346
5006
  this.watchParams = watchParams;
5347
5007
  this.watchExpiresAt = watchExpiresAt;
5348
5008
  }
5349
5009
  static adapt(item) {
5350
- return new RequestOptions(item?.path, item?.headers, item?.forceRefresh, Array.isArray(item?.watchParams) ? item.watchParams : [], item?.watchExpiresAt);
5010
+ return new RequestOptions(item?.path, item?.headers, item?.forceRefresh, Array.isArray(item?.ignoreQueryParams) ? item.ignoreQueryParams : [], item?.queryParamsExpiresIn, item?.watchParams, item?.watchExpiresAt);
5351
5011
  }
5352
5012
  }
5353
5013
 
@@ -6083,452 +5743,783 @@ class LocalStorageSignalsManagerService {
6083
5743
  const str = sessionStorage.getItem(this.storageName);
6084
5744
  strData.push(...(str ? JSON.parse(str) : []));
6085
5745
  }
6086
- const found = strData.find(store => store.id === id);
6087
- return !!found;
5746
+ const found = strData.find(store => store.id === id);
5747
+ return !!found;
5748
+ }
5749
+ isObjectOrArray(obj) {
5750
+ try {
5751
+ const parsed = typeof obj === 'object' ? obj : JSON.parse(obj);
5752
+ return typeof parsed === 'object' && parsed !== null;
5753
+ }
5754
+ catch {
5755
+ return false;
5756
+ }
5757
+ }
5758
+ isString(obj) {
5759
+ return Object.prototype.toString.call(obj) === '[object String]';
5760
+ }
5761
+ validStoreName(str) {
5762
+ return str.toLowerCase().replace(/\s+/g, '_').replace(/[^ -~]/g, '');
5763
+ }
5764
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, deps: [{ token: CONFIG_SETTINGS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
5765
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, providedIn: 'root' }); }
5766
+ }
5767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, decorators: [{
5768
+ type: Injectable,
5769
+ args: [{ providedIn: 'root' }]
5770
+ }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
5771
+ type: Optional
5772
+ }, {
5773
+ type: Inject,
5774
+ args: [CONFIG_SETTINGS_TOKEN]
5775
+ }] }] });
5776
+
5777
+ class TableSchemaDef {
5778
+ constructor(table = 'unknown', schema = '') {
5779
+ this.table = table;
5780
+ this.schema = schema;
5781
+ }
5782
+ static adapt(item) {
5783
+ const schemaArray = (item?.schema && Array.isArray(item.schema)) ? item.schema : item.schema.split(',');
5784
+ const schema = schemaArray.map((item) => this.cleanString(item)).join();
5785
+ return new TableSchemaDef((item?.table) ? this.cleanString(item.table) : 'unknown', schema);
5786
+ }
5787
+ static cleanString(str) {
5788
+ // Allow alphanumeric, spaces, underscores, and Dexie schema characters (+, &, *, [, ])
5789
+ const cleanStr = str
5790
+ .replace(/[^a-zA-Z0-9\s_+\&*\[\]]/g, '')
5791
+ .trim();
5792
+ return cleanStr;
5793
+ }
5794
+ }
5795
+
5796
+ class DbService extends Dexie {
5797
+ get db() { return this; }
5798
+ constructor() {
5799
+ super("global_storage_db");
5800
+ this.storageDB = "global_storage_db";
5801
+ this.on('blocked', () => {
5802
+ console.warn('Database upgrade blocked! Please close other tabs/windows of this app.');
5803
+ });
5804
+ this.on('versionchange', () => {
5805
+ console.warn('Database version changed in another tab. Closing current database connection to allow upgrade.');
5806
+ this.close();
5807
+ });
5808
+ this.dbReady = this.init();
5809
+ }
5810
+ async init() {
5811
+ try {
5812
+ // Check if database already exists
5813
+ const dbExists = await Dexie.exists(this.storageDB);
5814
+ if (dbExists) {
5815
+ // Database exists - just open it without declaring version
5816
+ // This allows Dexie to use the existing schema and version
5817
+ console.log(`Database "${this.storageDB}" already exists. Opening with existing schema...`);
5818
+ if (!this.isOpen()) {
5819
+ await this.open();
5820
+ }
5821
+ console.log(`✅ Connected to existing database. Current version: ${this.verno}`);
5822
+ console.log(`📊 Existing tables: ${this.getTables.join(', ')}`);
5823
+ }
5824
+ else {
5825
+ // New database - initialize with empty schema at version 1
5826
+ console.log(`Creating new database "${this.storageDB}"...`);
5827
+ if (this.isOpen()) {
5828
+ this.close();
5829
+ }
5830
+ this.version(1).stores({});
5831
+ await this.open();
5832
+ console.log(`✅ New database initialized. Current version: ${this.verno}`);
5833
+ }
5834
+ }
5835
+ catch (err) {
5836
+ console.error('Failed to initialize database:', err);
5837
+ throw err;
5838
+ }
5839
+ }
5840
+ async createNewDatabase() {
5841
+ if (this.isOpen()) {
5842
+ this.close();
5843
+ }
5844
+ await this.open();
5845
+ }
5846
+ get vr() {
5847
+ return this.verno;
5848
+ }
5849
+ get nextVr() {
5850
+ const v = this.verno;
5851
+ return (isNaN(v) || v === 0) ? 1 : v + 1;
5852
+ }
5853
+ get getTables() {
5854
+ return this.tables.map((table) => table.name);
5855
+ }
5856
+ async hasDatabase() {
5857
+ return await Dexie.exists(this.storageDB);
5858
+ }
5859
+ getTable(tableName) {
5860
+ try {
5861
+ return this.table(tableName);
5862
+ }
5863
+ catch (error) {
5864
+ return undefined;
5865
+ }
5866
+ }
5867
+ tableExists(tableName) {
5868
+ return this.tables.some(t => t.name === tableName);
5869
+ }
5870
+ async updateTable(tableName, find, data) {
5871
+ const table = this.getTable(tableName);
5872
+ if (!table || !find || !find.key)
5873
+ return false;
5874
+ try {
5875
+ const count = await table.where(find.key).equals(find.value).modify(data);
5876
+ return count > 0;
5877
+ }
5878
+ catch (error) {
5879
+ console.error('Error updating table:', error);
5880
+ return false;
5881
+ }
5882
+ }
5883
+ async createTable(tableName, schema) {
5884
+ if (!tableName || !schema)
5885
+ return;
5886
+ this.dbReady = this.dbReady.then(() => this._doCreateTable(tableName, schema));
5887
+ await this.dbReady;
5888
+ }
5889
+ async _doCreateTable(tableName, schema) {
5890
+ const safeTableName = this.cleanTableName(tableName);
5891
+ const safeSchema = schema.trim();
5892
+ const currentSchema = this.getCurrentSchema();
5893
+ const existingSchema = currentSchema[safeTableName]?.trim();
5894
+ if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
5895
+ return;
5896
+ }
5897
+ console.log('Current Schema before update:', currentSchema);
5898
+ currentSchema[safeTableName] = safeSchema;
5899
+ const nextVersion = this.verno + 1;
5900
+ console.log(`Creating table ${safeTableName}. Upgrading to version ${nextVersion}. New Schema:`, currentSchema);
5901
+ if (this.isOpen()) {
5902
+ this.close();
5903
+ }
5904
+ this.version(nextVersion).stores(currentSchema);
5905
+ try {
5906
+ await this.open();
5907
+ const created = this.tables.some(t => t.name === safeTableName);
5908
+ if (!created) {
5909
+ console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
5910
+ throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
5911
+ }
5912
+ else {
5913
+ console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
5914
+ }
5915
+ }
5916
+ catch (err) {
5917
+ console.error('Error opening database after schema update:', err);
5918
+ throw err;
5919
+ }
6088
5920
  }
6089
- isObjectOrArray(obj) {
5921
+ async DBOpened() {
6090
5922
  try {
6091
- const parsed = typeof obj === 'object' ? obj : JSON.parse(obj);
6092
- return typeof parsed === 'object' && parsed !== null;
5923
+ await this.dbReady;
5924
+ return this.isOpen();
6093
5925
  }
6094
- catch {
5926
+ catch (err) {
5927
+ console.error('DBOpened: init failed', err);
6095
5928
  return false;
6096
5929
  }
6097
5930
  }
6098
- isString(obj) {
6099
- return Object.prototype.toString.call(obj) === '[object String]';
5931
+ getCurrentSchema() {
5932
+ const schema = {};
5933
+ if (!this.tables || this.tables.length === 0) {
5934
+ return schema;
5935
+ }
5936
+ this.tables.forEach(table => {
5937
+ const primKey = table.schema.primKey;
5938
+ const indexes = table.schema.indexes;
5939
+ let schemaStr = "";
5940
+ if (primKey.auto)
5941
+ schemaStr += "++";
5942
+ if (Array.isArray(primKey.keyPath)) {
5943
+ schemaStr += `[${primKey.keyPath.join('+')}]`;
5944
+ }
5945
+ else {
5946
+ schemaStr += primKey.keyPath || "";
5947
+ }
5948
+ indexes.forEach(idx => {
5949
+ schemaStr += ",";
5950
+ if (idx.unique)
5951
+ schemaStr += "&";
5952
+ if (idx.multi)
5953
+ schemaStr += "*";
5954
+ if (Array.isArray(idx.keyPath)) {
5955
+ schemaStr += `[${idx.keyPath.join('+')}]`;
5956
+ }
5957
+ else {
5958
+ schemaStr += idx.keyPath || "";
5959
+ }
5960
+ });
5961
+ schema[table.name] = schemaStr;
5962
+ });
5963
+ return schema;
6100
5964
  }
6101
- validStoreName(str) {
6102
- return str.toLowerCase().replace(/\s+/g, '_').replace(/[^ -~]/g, '');
5965
+ cleanTableName(str) {
5966
+ return str
5967
+ .replace(/[^a-zA-Z0-9\s_]/g, '')
5968
+ .replace(/\s+/g, '_')
5969
+ .trim()
5970
+ .toLowerCase();
6103
5971
  }
6104
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, deps: [{ token: CONFIG_SETTINGS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
6105
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, providedIn: 'root' }); }
5972
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5973
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, providedIn: 'root' }); }
6106
5974
  }
6107
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LocalStorageSignalsManagerService, decorators: [{
5975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, decorators: [{
6108
5976
  type: Injectable,
6109
- args: [{ providedIn: 'root' }]
6110
- }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
6111
- type: Optional
6112
- }, {
6113
- type: Inject,
6114
- args: [CONFIG_SETTINGS_TOKEN]
6115
- }] }] });
5977
+ args: [{
5978
+ providedIn: 'root',
5979
+ }]
5980
+ }], ctorParameters: () => [] });
6116
5981
 
6117
- class TableSchemaDef {
6118
- constructor(table = 'unknown', schema = '') {
6119
- this.table = table;
6120
- this.schema = schema;
5982
+ class DatabaseManagerService extends DbService {
5983
+ constructor() {
5984
+ super();
6121
5985
  }
6122
- static adapt(item) {
6123
- const schemaArray = (item?.schema && Array.isArray(item.schema)) ? item.schema : item.schema.split(',');
6124
- const schema = schemaArray.map((item) => this.cleanString(item)).join();
6125
- return new TableSchemaDef((item?.table) ? this.cleanString(item.table) : 'unknown', schema);
5986
+ getDatabaseTables() {
5987
+ return from(this.DBOpened()).pipe(map(() => this.tables.map((item) => item.name)));
6126
5988
  }
6127
- static cleanString(str) {
6128
- // Allow alphanumeric, spaces, underscores, and Dexie schema characters (+, &, *, [, ])
6129
- const cleanStr = str
6130
- .replace(/[^a-zA-Z0-9\s_+\&*\[\]]/g, '')
6131
- .trim();
6132
- return cleanStr;
5989
+ databaseExists() {
5990
+ return from(this.hasDatabase());
6133
5991
  }
6134
- }
6135
-
6136
- class DbService extends Dexie {
6137
- get db() { return this; }
6138
- constructor() {
6139
- super("global_storage_db");
6140
- this.storageDB = "global_storage_db";
6141
- this.on('blocked', () => {
6142
- console.warn('Database upgrade blocked! Please close other tabs/windows of this app.');
6143
- });
6144
- this.on('versionchange', () => {
6145
- console.warn('Database version changed in another tab. Closing current database connection to allow upgrade.');
6146
- this.close();
5992
+ hasDatabaseTable(table) {
5993
+ const tableName = this.cleanTableName(table);
5994
+ return from(this.DBOpened()).pipe(map(() => {
5995
+ return this.tableExists(tableName);
5996
+ }));
5997
+ }
5998
+ getDatabaseTable(table) {
5999
+ const tableName = this.cleanTableName(table);
6000
+ return from(this.DBOpened()).pipe(map(() => {
6001
+ const found = this.getTable(tableName);
6002
+ if (!found) {
6003
+ console.warn('No Such Table:', tableName);
6004
+ return null;
6005
+ }
6006
+ return found;
6007
+ }));
6008
+ }
6009
+ getDatabaseTableSchema(table) {
6010
+ const tableName = this.cleanTableName(table);
6011
+ return this.getDatabaseTable(tableName).pipe(map((dbTable) => {
6012
+ if (!dbTable)
6013
+ return [];
6014
+ const cols = dbTable.schema.indexes.map((col) => col.name);
6015
+ const pri = dbTable.schema.primKey.name;
6016
+ return [pri, ...cols];
6017
+ }));
6018
+ }
6019
+ createDatabaseTable(tableDef) {
6020
+ const tableName = this.cleanTableName(tableDef.table);
6021
+ return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
6022
+ console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
6023
+ return of(false);
6024
+ }));
6025
+ }
6026
+ updateDatabaseTableSchema(tableDef) {
6027
+ return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
6028
+ }
6029
+ // CRUD
6030
+ findTableRecords(table, column, value) {
6031
+ const tableName = this.cleanTableName(table);
6032
+ return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6033
+ if (!tableData) {
6034
+ console.warn(`findTableRecords: Table '${tableName}' not found`);
6035
+ return from([]);
6036
+ }
6037
+ return from(tableData.where(column).equals(value).toArray());
6038
+ }));
6039
+ }
6040
+ findTableRecord(table, column, value) {
6041
+ const tableName = this.cleanTableName(table);
6042
+ return this.findTableRecords(tableName, column, value).pipe(map((records) => records && records.length > 0 ? records[0] : null));
6043
+ }
6044
+ getTableRecords(table) {
6045
+ const tableName = this.cleanTableName(table);
6046
+ return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6047
+ if (!tableData) {
6048
+ console.warn(`getTableRecords: Table '${tableName}' not found`);
6049
+ return from([]);
6050
+ }
6051
+ return from(tableData.toArray());
6052
+ }));
6053
+ }
6054
+ getTableRecord(table, id) {
6055
+ const tableName = this.cleanTableName(table);
6056
+ return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6057
+ if (!tableData) {
6058
+ console.warn(`getTableRecord: Table '${tableName}' not found`);
6059
+ return from([null]);
6060
+ }
6061
+ return from(tableData.get(id)).pipe(map((record) => record || null));
6062
+ }));
6063
+ }
6064
+ createTableRecord(table, record) {
6065
+ const tableName = this.cleanTableName(table);
6066
+ return this.createTableRecords(tableName, [record])
6067
+ .pipe(map((item) => item ? item[0] : null));
6068
+ }
6069
+ createTableRecords(table, records) {
6070
+ const tableName = this.cleanTableName(table);
6071
+ // Keep full object payload; Dexie stores non-indexed properties as well.
6072
+ const insertRecords = records.map((record) => {
6073
+ const payload = { ...(record || {}) };
6074
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6075
+ delete payload.id;
6076
+ }
6077
+ return payload;
6147
6078
  });
6148
- this.dbReady = this.init();
6079
+ const writeRecords = () => from(this.DBOpened()).pipe(switchMap((opened) => {
6080
+ if (!opened) {
6081
+ console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
6082
+ return EMPTY;
6083
+ }
6084
+ const tableInstance = this.tables.find(t => t.name === tableName);
6085
+ if (!tableInstance) {
6086
+ console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
6087
+ return EMPTY;
6088
+ }
6089
+ console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
6090
+ return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6091
+ }));
6092
+ return writeRecords().pipe(catchError((error) => {
6093
+ if (!this.isMissingObjectStoreError(error)) {
6094
+ throw error;
6095
+ }
6096
+ console.warn(`createTableRecords: Missing object store for '${tableName}'. Waiting for DB readiness and retrying once...`, error);
6097
+ return from(this.createNewDatabase()).pipe(switchMap(() => writeRecords()), catchError((retryError) => {
6098
+ console.error(`createTableRecords: Retry failed for '${tableName}'`, retryError);
6099
+ throw retryError;
6100
+ }));
6101
+ }));
6102
+ }
6103
+ isMissingObjectStoreError(error) {
6104
+ const name = String(error?.name || '').toLowerCase();
6105
+ const innerName = String(error?.inner?.name || '').toLowerCase();
6106
+ const message = String(error?.message || error?.inner?.message || '').toLowerCase();
6107
+ return (name.includes('notfounderror') ||
6108
+ innerName.includes('notfounderror') ||
6109
+ message.includes('object store') ||
6110
+ message.includes('one of the specified object stores was not found'));
6111
+ }
6112
+ updateTableRecord(table, record) {
6113
+ const tableName = this.cleanTableName(table);
6114
+ return this.updateTableRecords(tableName, [record])
6115
+ .pipe(map(item => item.length > 0 ? item[0] : null));
6116
+ }
6117
+ updateTableRecords(table, records) {
6118
+ const tableName = this.cleanTableName(table);
6119
+ return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6120
+ if (!tableData) {
6121
+ console.warn(`updateTableRecords: Table '${tableName}' not found`);
6122
+ return EMPTY;
6123
+ }
6124
+ const insertRecords = records.map((record) => {
6125
+ const payload = { ...(record || {}) };
6126
+ if (payload.id === undefined || payload.id === null || payload.id === '') {
6127
+ delete payload.id;
6128
+ }
6129
+ return payload;
6130
+ });
6131
+ return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6132
+ }));
6133
+ }
6134
+ deleteTableRecord(table, id) {
6135
+ const tableName = this.cleanTableName(table);
6136
+ return this.deleteTableRecords(tableName, [id])
6137
+ .pipe(map(item => item.length > 0 ? item[0] : null));
6138
+ }
6139
+ deleteTableRecords(table, ids) {
6140
+ const tableName = this.cleanTableName(table);
6141
+ return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6142
+ return this.getDatabaseTableSchema(tableName).pipe(switchMap((schema) => {
6143
+ console.log(`deleteTableRecords: Starting delete of ${ids.length} records from ${table}. IDs:`, ids);
6144
+ return from(tableData.bulkDelete(ids)).pipe(map(() => ids));
6145
+ }));
6146
+ }));
6149
6147
  }
6150
- async init() {
6148
+ clearTable(table) {
6149
+ const tableName = this.cleanTableName(table);
6150
+ console.log(`clearTable: Starting clear for table: ${tableName}`);
6151
6151
  try {
6152
- // Check if database already exists
6153
- const dbExists = await Dexie.exists(this.storageDB);
6154
- if (dbExists) {
6155
- // Database exists - just open it without declaring version
6156
- // This allows Dexie to use the existing schema and version
6157
- console.log(`Database "${this.storageDB}" already exists. Opening with existing schema...`);
6158
- if (!this.isOpen()) {
6159
- await this.open();
6160
- }
6161
- console.log(`✅ Connected to existing database. Current version: ${this.verno}`);
6162
- console.log(`📊 Existing tables: ${this.getTables.join(', ')}`);
6163
- }
6164
- else {
6165
- // New database - initialize with empty schema at version 1
6166
- console.log(`Creating new database "${this.storageDB}"...`);
6167
- if (this.isOpen()) {
6168
- this.close();
6169
- }
6170
- this.version(1).stores({});
6171
- await this.open();
6172
- console.log(`✅ New database initialized. Current version: ${this.verno}`);
6152
+ const tableInstance = this.table(tableName);
6153
+ if (!tableInstance) {
6154
+ console.warn(`clearTable: Table '${tableName}' not found`);
6155
+ return of([]);
6173
6156
  }
6157
+ console.log(`clearTable: Clearing table '${tableName}'...`);
6158
+ // Use table.clear() directly and wrap in Observable
6159
+ return from(Promise.resolve().then(() => {
6160
+ return tableInstance.clear();
6161
+ })).pipe(tap(() => console.log(`clearTable: ✅ Table '${tableName}' cleared successfully`)), map(() => []));
6174
6162
  }
6175
- catch (err) {
6176
- console.error('Failed to initialize database:', err);
6177
- throw err;
6178
- }
6179
- }
6180
- async createNewDatabase() {
6181
- if (this.isOpen()) {
6182
- this.close();
6163
+ catch (error) {
6164
+ console.error(`clearTable: Error:`, error);
6165
+ return of([]);
6183
6166
  }
6184
- await this.open();
6185
6167
  }
6186
- get vr() {
6187
- return this.verno;
6168
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6169
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, providedIn: 'root' }); }
6170
+ }
6171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, decorators: [{
6172
+ type: Injectable,
6173
+ args: [{
6174
+ providedIn: 'root'
6175
+ }]
6176
+ }], ctorParameters: () => [] });
6177
+
6178
+ class ChannelMessage {
6179
+ constructor(messageId, channel, isReplay, sessionId = null, content = null, timestamp) {
6180
+ this.messageId = messageId;
6181
+ this.channel = channel;
6182
+ this.isReplay = isReplay;
6183
+ this.sessionId = sessionId;
6184
+ this.content = content;
6185
+ this.timestamp = timestamp;
6188
6186
  }
6189
- get nextVr() {
6190
- const v = this.verno;
6191
- return (isNaN(v) || v === 0) ? 1 : v + 1;
6187
+ static adapt(item) {
6188
+ return new ChannelMessage(item?.messageId, item?.channel, item?.isReplay, item?.sessionId || item?.id, // Support both for backward compatibility
6189
+ item?.content, item?.timestamp);
6192
6190
  }
6193
- get getTables() {
6194
- return this.tables.map((table) => table.name);
6191
+ }
6192
+
6193
+ const TRACKER_STORE_NAME = 'query_params_tracker';
6194
+ const TRACKER_SESSION_INIT_KEY = 'query_params_tracker_initialized';
6195
+ const DEFAULT_TRACKER_OPTIONS = SettingOptions.adapt({
6196
+ storage: StorageType.GLOBAL,
6197
+ encrypted: false,
6198
+ });
6199
+ class QueryParamsTrackerService {
6200
+ constructor() {
6201
+ this.state = { paths: {} };
6202
+ this.stateRestored = false;
6203
+ this.localStorageManager = inject(LocalStorageManagerService);
6195
6204
  }
6196
- async hasDatabase() {
6197
- return await Dexie.exists(this.storageDB);
6205
+ clearTracking(resetSessionInit = false) {
6206
+ this.state = { paths: {} };
6207
+ this.localStorageManager.deleteStore({ name: TRACKER_STORE_NAME });
6208
+ if (resetSessionInit && this.hasSessionStorage()) {
6209
+ sessionStorage.removeItem(TRACKER_SESSION_INIT_KEY);
6210
+ }
6198
6211
  }
6199
- getTable(tableName) {
6200
- try {
6201
- return this.table(tableName);
6212
+ checkRequestOptions(requestOptions = [], options = {}) {
6213
+ this.ensureStateRestored();
6214
+ const normalized = this.normalizeRequestOptions(requestOptions);
6215
+ if (!normalized.pathKey)
6216
+ return false;
6217
+ this.cleanupExpiredEntries();
6218
+ if (!normalized.hasQuery) {
6219
+ const pathState = this.ensurePathState(normalized.pathKey);
6220
+ const pathSeenBefore = !!pathState.baselineQuery;
6221
+ if (!pathSeenBefore) {
6222
+ pathState.baselineQuery = {};
6223
+ this.persistState();
6224
+ }
6225
+ return !pathSeenBefore;
6202
6226
  }
6203
- catch (error) {
6204
- return undefined;
6227
+ if (options.mode === 'exact') {
6228
+ return this.checkExact(normalized, options);
6205
6229
  }
6230
+ return this.checkVariation(normalized, options);
6206
6231
  }
6207
- tableExists(tableName) {
6208
- return this.tables.some(t => t.name === tableName);
6232
+ matchesPath(requestOptions = [], expectedPathOptions = []) {
6233
+ this.ensureStateRestored();
6234
+ const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
6235
+ const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
6236
+ return Boolean(requestPath) && requestPath === expectedPath;
6209
6237
  }
6210
- async updateTable(tableName, find, data) {
6211
- const table = this.getTable(tableName);
6212
- if (!table || !find || !find.key)
6238
+ checkExact(normalized, options) {
6239
+ const pathState = this.ensurePathState(normalized.pathKey);
6240
+ const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
6241
+ if (Object.keys(filteredQuery).length === 0) {
6213
6242
  return false;
6214
- try {
6215
- const count = await table.where(find.key).equals(find.value).modify(data);
6216
- return count > 0;
6217
6243
  }
6218
- catch (error) {
6219
- console.error('Error updating table:', error);
6220
- return false;
6244
+ if (!pathState.baselineQuery) {
6245
+ pathState.baselineQuery = filteredQuery;
6246
+ this.persistState();
6247
+ return true;
6221
6248
  }
6249
+ return this.stringifyQuery(pathState.baselineQuery) !== this.stringifyQuery(filteredQuery);
6222
6250
  }
6223
- async createTable(tableName, schema) {
6224
- if (!tableName || !schema)
6225
- return;
6226
- this.dbReady = this.dbReady.then(() => this._doCreateTable(tableName, schema));
6227
- await this.dbReady;
6228
- }
6229
- async _doCreateTable(tableName, schema) {
6230
- const safeTableName = this.cleanTableName(tableName);
6231
- const safeSchema = schema.trim();
6232
- const currentSchema = this.getCurrentSchema();
6233
- const existingSchema = currentSchema[safeTableName]?.trim();
6234
- if (this.tableExists(safeTableName) && existingSchema === safeSchema) {
6235
- return;
6251
+ checkVariation(normalized, options) {
6252
+ const pathState = this.ensurePathState(normalized.pathKey);
6253
+ if (Array.isArray(options.watchParams) && options.watchParams.length === 0) {
6254
+ const pathSeenBefore = !!pathState.baselineQuery;
6255
+ if (!pathSeenBefore) {
6256
+ pathState.baselineQuery = {};
6257
+ pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
6258
+ this.persistState();
6259
+ }
6260
+ return !pathSeenBefore;
6236
6261
  }
6237
- console.log('Current Schema before update:', currentSchema);
6238
- currentSchema[safeTableName] = safeSchema;
6239
- const nextVersion = this.verno + 1;
6240
- console.log(`Creating table ${safeTableName}. Upgrading to version ${nextVersion}. New Schema:`, currentSchema);
6241
- if (this.isOpen()) {
6242
- this.close();
6262
+ const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
6263
+ const keys = Object.keys(filteredQuery);
6264
+ if (keys.length === 0) {
6265
+ return false;
6243
6266
  }
6244
- this.version(nextVersion).stores(currentSchema);
6245
- try {
6246
- await this.open();
6247
- const created = this.tables.some(t => t.name === safeTableName);
6248
- if (!created) {
6249
- console.error(`CRITICAL: Table ${safeTableName} was NOT created after upgrade! Tables found:`, this.tables.map(t => t.name));
6250
- throw new Error(`Table '${safeTableName}' was not created after schema upgrade`);
6267
+ this.resetPathStateIfExpired(pathState);
6268
+ let accepted = false;
6269
+ keys.forEach((key) => {
6270
+ const currentValue = filteredQuery[key];
6271
+ const consumed = pathState.consumedValuesByKey[key] || [];
6272
+ if (!consumed.includes(currentValue)) {
6273
+ pathState.consumedValuesByKey[key] = [...consumed, currentValue];
6274
+ accepted = true;
6251
6275
  }
6252
- else {
6253
- console.log(`Database opened successfully version ${this.verno}. Table ${safeTableName} verified.`);
6276
+ });
6277
+ if (accepted) {
6278
+ if (!pathState.baselineQuery) {
6279
+ pathState.baselineQuery = filteredQuery;
6254
6280
  }
6281
+ pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
6282
+ this.persistState();
6255
6283
  }
6256
- catch (err) {
6257
- console.error('Error opening database after schema update:', err);
6258
- throw err;
6259
- }
6284
+ return accepted;
6260
6285
  }
6261
- async DBOpened() {
6262
- try {
6263
- await this.dbReady;
6264
- return this.isOpen();
6265
- }
6266
- catch (err) {
6267
- console.error('DBOpened: init failed', err);
6268
- return false;
6286
+ filterQueryByWatchParams(query, watchParams) {
6287
+ if (!Array.isArray(watchParams)) {
6288
+ return query;
6269
6289
  }
6270
- }
6271
- getCurrentSchema() {
6272
- const schema = {};
6273
- if (!this.tables || this.tables.length === 0) {
6274
- return schema;
6290
+ if (watchParams.length === 0) {
6291
+ return {};
6275
6292
  }
6276
- this.tables.forEach(table => {
6277
- const primKey = table.schema.primKey;
6278
- const indexes = table.schema.indexes;
6279
- let schemaStr = "";
6280
- if (primKey.auto)
6281
- schemaStr += "++";
6282
- if (Array.isArray(primKey.keyPath)) {
6283
- schemaStr += `[${primKey.keyPath.join('+')}]`;
6284
- }
6285
- else {
6286
- schemaStr += primKey.keyPath || "";
6293
+ const normalizedWatchParams = watchParams.map((p) => this.normalizeParamKey(p));
6294
+ return Object.keys(query).reduce((acc, key) => {
6295
+ if (normalizedWatchParams.includes(key)) {
6296
+ acc[key] = query[key];
6287
6297
  }
6288
- indexes.forEach(idx => {
6289
- schemaStr += ",";
6290
- if (idx.unique)
6291
- schemaStr += "&";
6292
- if (idx.multi)
6293
- schemaStr += "*";
6294
- if (Array.isArray(idx.keyPath)) {
6295
- schemaStr += `[${idx.keyPath.join('+')}]`;
6298
+ return acc;
6299
+ }, {});
6300
+ }
6301
+ normalizeRequestOptions(requestOptions) {
6302
+ const params = Array.isArray(requestOptions) ? requestOptions : [];
6303
+ const pathSegments = [];
6304
+ const queryObjects = [];
6305
+ params.forEach((item) => {
6306
+ if (this.isPlainObject(item)) {
6307
+ queryObjects.push(item);
6308
+ }
6309
+ else if (typeof item !== 'undefined' && item !== null) {
6310
+ const parsed = this.parsePathSegment(String(item));
6311
+ if (parsed.pathSegment) {
6312
+ pathSegments.push(parsed.pathSegment);
6296
6313
  }
6297
- else {
6298
- schemaStr += idx.keyPath || "";
6314
+ if (Object.keys(parsed.queryObject).length > 0) {
6315
+ queryObjects.push(parsed.queryObject);
6299
6316
  }
6300
- });
6301
- schema[table.name] = schemaStr;
6317
+ }
6302
6318
  });
6303
- return schema;
6304
- }
6305
- cleanTableName(str) {
6306
- return str
6307
- .replace(/[^a-zA-Z0-9\s_]/g, '')
6308
- .replace(/\s+/g, '_')
6309
- .trim()
6310
- .toLowerCase();
6311
- }
6312
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6313
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, providedIn: 'root' }); }
6314
- }
6315
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, decorators: [{
6316
- type: Injectable,
6317
- args: [{
6318
- providedIn: 'root',
6319
- }]
6320
- }], ctorParameters: () => [] });
6321
-
6322
- class DatabaseManagerService extends DbService {
6323
- constructor() {
6324
- super();
6325
- }
6326
- getDatabaseTables() {
6327
- return from(this.DBOpened()).pipe(map(() => this.tables.map((item) => item.name)));
6328
- }
6329
- databaseExists() {
6330
- return from(this.hasDatabase());
6331
- }
6332
- hasDatabaseTable(table) {
6333
- const tableName = this.cleanTableName(table);
6334
- return from(this.DBOpened()).pipe(map(() => {
6335
- return this.tableExists(tableName);
6336
- }));
6319
+ const query = queryObjects.reduce((acc, current) => {
6320
+ Object.keys(current).forEach((key) => {
6321
+ const normalizedKey = this.normalizeParamKey(key);
6322
+ const value = current[key];
6323
+ if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
6324
+ return;
6325
+ }
6326
+ acc[normalizedKey] = this.normalizeParamValue(value);
6327
+ });
6328
+ return acc;
6329
+ }, {});
6330
+ return {
6331
+ pathKey: this.normalizePath(pathSegments),
6332
+ query,
6333
+ hasQuery: Object.keys(query).length > 0,
6334
+ };
6337
6335
  }
6338
- getDatabaseTable(table) {
6339
- const tableName = this.cleanTableName(table);
6340
- return from(this.DBOpened()).pipe(map(() => {
6341
- const found = this.getTable(tableName);
6342
- if (!found) {
6343
- console.warn('No Such Table:', tableName);
6344
- return null;
6345
- }
6346
- return found;
6347
- }));
6336
+ parsePathSegment(rawSegment) {
6337
+ const [pathPart, ...queryParts] = String(rawSegment).split('?');
6338
+ const queryString = queryParts.join('?');
6339
+ const queryObject = {};
6340
+ if (queryString) {
6341
+ queryString.split('&').forEach((pair) => {
6342
+ if (!pair)
6343
+ return;
6344
+ const [rawKey, ...rawValueParts] = pair.split('=');
6345
+ const key = this.safeDecode(rawKey).trim();
6346
+ const value = this.safeDecode(rawValueParts.join('=')).trim();
6347
+ if (!key)
6348
+ return;
6349
+ queryObject[key] = value;
6350
+ });
6351
+ }
6352
+ return {
6353
+ pathSegment: pathPart,
6354
+ queryObject,
6355
+ };
6348
6356
  }
6349
- getDatabaseTableSchema(table) {
6350
- const tableName = this.cleanTableName(table);
6351
- return this.getDatabaseTable(tableName).pipe(map((dbTable) => {
6352
- if (!dbTable)
6353
- return [];
6354
- const cols = dbTable.schema.indexes.map((col) => col.name);
6355
- const pri = dbTable.schema.primKey.name;
6356
- return [pri, ...cols];
6357
- }));
6357
+ safeDecode(value) {
6358
+ if (typeof value === 'undefined') {
6359
+ return '';
6360
+ }
6361
+ try {
6362
+ return decodeURIComponent(value);
6363
+ }
6364
+ catch {
6365
+ return String(value);
6366
+ }
6358
6367
  }
6359
- createDatabaseTable(tableDef) {
6360
- const tableName = this.cleanTableName(tableDef.table);
6361
- return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => from(this.DBOpened())), map((opened) => opened && this.tableExists(tableName)), catchError((error) => {
6362
- console.error(`createDatabaseTable: failed for table '${tableName}'`, error);
6363
- return of(false);
6364
- }));
6368
+ normalizePath(pathSegments) {
6369
+ return pathSegments
6370
+ .map((segment) => String(segment).trim())
6371
+ .filter((segment) => segment.length > 0)
6372
+ .join('/')
6373
+ .replace(/([^:]\/+)\/+/g, '$1')
6374
+ .replace(/^\//, '')
6375
+ .replace(/\/$/, '');
6365
6376
  }
6366
- updateDatabaseTableSchema(tableDef) {
6367
- return from(this.createTable(tableDef.table, tableDef.schema)).pipe(switchMap(() => this.getDatabaseTable(tableDef.table)));
6377
+ normalizeParamKey(key) {
6378
+ return String(key).trim().toLowerCase();
6368
6379
  }
6369
- // CRUD
6370
- findTableRecords(table, column, value) {
6371
- const tableName = this.cleanTableName(table);
6372
- return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6373
- if (!tableData) {
6374
- console.warn(`findTableRecords: Table '${tableName}' not found`);
6375
- return from([]);
6376
- }
6377
- return from(tableData.where(column).equals(value).toArray());
6378
- }));
6380
+ normalizeParamValue(value) {
6381
+ if (Array.isArray(value)) {
6382
+ return value.map((item) => this.normalizeParamValue(item)).join(',');
6383
+ }
6384
+ if (this.isPlainObject(value)) {
6385
+ return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
6386
+ acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
6387
+ return acc;
6388
+ }, {}));
6389
+ }
6390
+ return String(value).trim().toLowerCase();
6379
6391
  }
6380
- findTableRecord(table, column, value) {
6381
- const tableName = this.cleanTableName(table);
6382
- return this.findTableRecords(tableName, column, value).pipe(map((records) => records && records.length > 0 ? records[0] : null));
6392
+ buildExpiryEpoch(expireIn) {
6393
+ if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
6394
+ return undefined;
6395
+ }
6396
+ if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
6397
+ return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
6398
+ }
6399
+ const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
6400
+ const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
6401
+ if (!parsed) {
6402
+ return undefined;
6403
+ }
6404
+ const value = Number(parsed[1]);
6405
+ const unit = parsed[2];
6406
+ const toSeconds = {
6407
+ y: 31556926,
6408
+ w: 604800,
6409
+ d: 86400,
6410
+ hr: 3600,
6411
+ h: 3600,
6412
+ mn: 60,
6413
+ min: 60,
6414
+ m: 60,
6415
+ s: 1,
6416
+ };
6417
+ const seconds = toSeconds[unit];
6418
+ if (!seconds) {
6419
+ return undefined;
6420
+ }
6421
+ return Math.floor(Date.now() / 1000) + value * seconds;
6383
6422
  }
6384
- getTableRecords(table) {
6385
- const tableName = this.cleanTableName(table);
6386
- return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6387
- if (!tableData) {
6388
- console.warn(`getTableRecords: Table '${tableName}' not found`);
6389
- return from([]);
6390
- }
6391
- return from(tableData.toArray());
6392
- }));
6423
+ resetPathStateIfExpired(pathState) {
6424
+ const now = Math.floor(Date.now() / 1000);
6425
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
6426
+ pathState.consumedValuesByKey = {};
6427
+ pathState.watchExpiresAt = undefined;
6428
+ }
6393
6429
  }
6394
- getTableRecord(table, id) {
6395
- const tableName = this.cleanTableName(table);
6396
- return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6397
- if (!tableData) {
6398
- console.warn(`getTableRecord: Table '${tableName}' not found`);
6399
- return from([null]);
6430
+ cleanupExpiredEntries() {
6431
+ const now = Math.floor(Date.now() / 1000);
6432
+ Object.keys(this.state.paths).forEach((pathKey) => {
6433
+ const pathState = this.state.paths[pathKey];
6434
+ if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
6435
+ pathState.consumedValuesByKey = {};
6436
+ pathState.watchExpiresAt = undefined;
6400
6437
  }
6401
- return from(tableData.get(id)).pipe(map((record) => record || null));
6402
- }));
6403
- }
6404
- createTableRecord(table, record) {
6405
- const tableName = this.cleanTableName(table);
6406
- return this.createTableRecords(tableName, [record])
6407
- .pipe(map((item) => item ? item[0] : null));
6408
- }
6409
- createTableRecords(table, records) {
6410
- const tableName = this.cleanTableName(table);
6411
- // Keep full object payload; Dexie stores non-indexed properties as well.
6412
- const insertRecords = records.map((record) => {
6413
- const payload = { ...(record || {}) };
6414
- if (payload.id === undefined || payload.id === null || payload.id === '') {
6415
- delete payload.id;
6438
+ const hasBaseline = pathState.baselineQuery !== undefined;
6439
+ const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
6440
+ if (!hasBaseline && !hasTrackedValues) {
6441
+ delete this.state.paths[pathKey];
6416
6442
  }
6417
- return payload;
6418
6443
  });
6419
- const writeRecords = () => from(this.DBOpened()).pipe(switchMap((opened) => {
6420
- if (!opened) {
6421
- console.error(`createTableRecords: DB not open. Cannot write to '${tableName}'.`);
6422
- return EMPTY;
6423
- }
6424
- const tableInstance = this.tables.find(t => t.name === tableName);
6425
- if (!tableInstance) {
6426
- console.error(`createTableRecords: Table '${tableName}' does not exist in DB version ${this.verno}. Available:`, this.tables.map(t => t.name));
6427
- return EMPTY;
6428
- }
6429
- console.log(`createTableRecords: Bulk putting ${insertRecords.length} records into ${tableName}`);
6430
- return from(tableInstance.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6431
- }));
6432
- return writeRecords().pipe(catchError((error) => {
6433
- if (!this.isMissingObjectStoreError(error)) {
6434
- throw error;
6435
- }
6436
- console.warn(`createTableRecords: Missing object store for '${tableName}'. Waiting for DB readiness and retrying once...`, error);
6437
- return from(this.createNewDatabase()).pipe(switchMap(() => writeRecords()), catchError((retryError) => {
6438
- console.error(`createTableRecords: Retry failed for '${tableName}'`, retryError);
6439
- throw retryError;
6440
- }));
6441
- }));
6442
6444
  }
6443
- isMissingObjectStoreError(error) {
6444
- const name = String(error?.name || '').toLowerCase();
6445
- const innerName = String(error?.inner?.name || '').toLowerCase();
6446
- const message = String(error?.message || error?.inner?.message || '').toLowerCase();
6447
- return (name.includes('notfounderror') ||
6448
- innerName.includes('notfounderror') ||
6449
- message.includes('object store') ||
6450
- message.includes('one of the specified object stores was not found'));
6445
+ ensurePathState(pathKey) {
6446
+ if (!this.state.paths[pathKey]) {
6447
+ this.state.paths[pathKey] = {
6448
+ consumedValuesByKey: {},
6449
+ };
6450
+ }
6451
+ return this.state.paths[pathKey];
6451
6452
  }
6452
- updateTableRecord(table, record) {
6453
- const tableName = this.cleanTableName(table);
6454
- return this.updateTableRecords(tableName, [record])
6455
- .pipe(map(item => item.length > 0 ? item[0] : null));
6453
+ ensureStateRestored() {
6454
+ if (this.stateRestored) {
6455
+ return;
6456
+ }
6457
+ this.stateRestored = true;
6458
+ this.restoreState();
6456
6459
  }
6457
- updateTableRecords(table, records) {
6458
- const tableName = this.cleanTableName(table);
6459
- return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6460
- if (!tableData) {
6461
- console.warn(`updateTableRecords: Table '${tableName}' not found`);
6462
- return EMPTY;
6463
- }
6464
- const insertRecords = records.map((record) => {
6465
- const payload = { ...(record || {}) };
6466
- if (payload.id === undefined || payload.id === null || payload.id === '') {
6467
- delete payload.id;
6460
+ restoreState() {
6461
+ this.localStorageManager.store$(TRACKER_STORE_NAME)
6462
+ .pipe(take(1))
6463
+ .subscribe({
6464
+ next: (storedState) => {
6465
+ if (this.isTrackerState(storedState)) {
6466
+ this.state = QueryTrackerStateModel.adapt(storedState);
6467
+ this.cleanupExpiredEntries();
6468
+ return;
6468
6469
  }
6469
- return payload;
6470
+ this.state = { paths: {} };
6471
+ },
6472
+ error: () => {
6473
+ this.state = { paths: {} };
6474
+ }
6475
+ });
6476
+ }
6477
+ persistState() {
6478
+ this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
6479
+ .pipe(take(1))
6480
+ .subscribe((exists) => {
6481
+ if (exists) {
6482
+ this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
6483
+ return;
6484
+ }
6485
+ this.localStorageManager.createStore({
6486
+ name: TRACKER_STORE_NAME,
6487
+ data: this.state,
6488
+ options: DEFAULT_TRACKER_OPTIONS,
6470
6489
  });
6471
- return from(tableData.bulkPut(insertRecords)).pipe(map(() => insertRecords));
6472
- }));
6490
+ });
6473
6491
  }
6474
- deleteTableRecord(table, id) {
6475
- const tableName = this.cleanTableName(table);
6476
- return this.deleteTableRecords(tableName, [id])
6477
- .pipe(map(item => item.length > 0 ? item[0] : null));
6492
+ stringifyQuery(query) {
6493
+ return JSON.stringify(Object.keys(query)
6494
+ .sort()
6495
+ .reduce((acc, key) => {
6496
+ acc[key] = query[key];
6497
+ return acc;
6498
+ }, {}));
6478
6499
  }
6479
- deleteTableRecords(table, ids) {
6480
- const tableName = this.cleanTableName(table);
6481
- return this.getDatabaseTable(tableName).pipe(switchMap((tableData) => {
6482
- return this.getDatabaseTableSchema(tableName).pipe(switchMap((schema) => {
6483
- console.log(`deleteTableRecords: Starting delete of ${ids.length} records from ${table}. IDs:`, ids);
6484
- return from(tableData.bulkDelete(ids)).pipe(map(() => ids));
6485
- }));
6486
- }));
6500
+ isTrackerState(value) {
6501
+ return this.isPlainObject(value) && this.isPlainObject(value.paths);
6487
6502
  }
6488
- clearTable(table) {
6489
- const tableName = this.cleanTableName(table);
6490
- console.log(`clearTable: Starting clear for table: ${tableName}`);
6503
+ isPlainObject(value) {
6504
+ return Object.prototype.toString.call(value) === '[object Object]';
6505
+ }
6506
+ hasSessionStorage() {
6491
6507
  try {
6492
- const tableInstance = this.table(tableName);
6493
- if (!tableInstance) {
6494
- console.warn(`clearTable: Table '${tableName}' not found`);
6495
- return of([]);
6496
- }
6497
- console.log(`clearTable: Clearing table '${tableName}'...`);
6498
- // Use table.clear() directly and wrap in Observable
6499
- return from(Promise.resolve().then(() => {
6500
- return tableInstance.clear();
6501
- })).pipe(tap(() => console.log(`clearTable: ✅ Table '${tableName}' cleared successfully`)), map(() => []));
6508
+ return typeof sessionStorage !== 'undefined';
6502
6509
  }
6503
- catch (error) {
6504
- console.error(`clearTable: ❌ Error:`, error);
6505
- return of([]);
6510
+ catch {
6511
+ return false;
6506
6512
  }
6507
6513
  }
6508
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6509
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, providedIn: 'root' }); }
6514
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6515
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
6510
6516
  }
6511
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseManagerService, decorators: [{
6517
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
6512
6518
  type: Injectable,
6513
6519
  args: [{
6514
6520
  providedIn: 'root'
6515
6521
  }]
6516
- }], ctorParameters: () => [] });
6517
-
6518
- class ChannelMessage {
6519
- constructor(messageId, channel, isReplay, sessionId = null, content = null, timestamp) {
6520
- this.messageId = messageId;
6521
- this.channel = channel;
6522
- this.isReplay = isReplay;
6523
- this.sessionId = sessionId;
6524
- this.content = content;
6525
- this.timestamp = timestamp;
6526
- }
6527
- static adapt(item) {
6528
- return new ChannelMessage(item?.messageId, item?.channel, item?.isReplay, item?.sessionId || item?.id, // Support both for backward compatibility
6529
- item?.content, item?.timestamp);
6530
- }
6531
- }
6522
+ }] });
6532
6523
 
6533
6524
  const API_OPTS = new InjectionToken('API_OPTS');
6534
6525
  /**
@@ -6570,9 +6561,9 @@ class HTTPManagerStateService extends ComponentStore {
6570
6561
  this.httpManagerService = inject(HTTPManagerService);
6571
6562
  this.dbManagerService = inject(DatabaseManagerService);
6572
6563
  this.localStorageManagerService = inject(LocalStorageManagerService);
6573
- this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6574
6564
  this.utils = inject(UtilsService);
6575
6565
  this.logger = inject(LoggerService);
6566
+ this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6576
6567
  this.error$ = this.httpManagerService.error$;
6577
6568
  this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
6578
6569
  this.operationSuccess = new BehaviorSubject(null);
@@ -7004,15 +6995,15 @@ class HTTPManagerStateService extends ComponentStore {
7004
6995
  const dbData = (data?.results && Array.isArray(data.results)) ? data.results : data;
7005
6996
  if (this.hasDatabase && this.databaseOptions?.table && Array.isArray(dbData) && dbData.length > 0) {
7006
6997
  const tableName = this.databaseOptions.table;
7007
- this.localStorageManagerService.updateStore({
7008
- name: tableName,
7009
- data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7010
- });
7011
6998
  const schema = this.buildSchemaFromSample(dbData[0]);
7012
6999
  const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7013
7000
  const schemaSignature = this.buildSchemaSignature(schema);
7014
7001
  // Always ensure table exists immediately before writing to avoid stale schema/store races.
7015
7002
  return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
7003
+ this.localStorageManagerService.updateStore({
7004
+ name: tableName,
7005
+ data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions.expiresIn) }
7006
+ });
7016
7007
  const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7017
7008
  const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7018
7009
  const ensureTable$ = schemaChanged
@@ -7064,12 +7055,7 @@ class HTTPManagerStateService extends ComponentStore {
7064
7055
  return fetchFromAPI();
7065
7056
  }
7066
7057
  if (hasExpired) {
7067
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()), tap(() => {
7068
- this.localStorageManagerService.updateStore({
7069
- name: this.databaseOptions.table,
7070
- data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7071
- });
7072
- }));
7058
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()));
7073
7059
  }
7074
7060
  const expectedSchema = this.buildSchemaFromAdapter();
7075
7061
  const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
@@ -7077,12 +7063,9 @@ class HTTPManagerStateService extends ComponentStore {
7077
7063
  const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7078
7064
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7079
7065
  }
7080
- const hasTrackerConfig = Array.isArray(options?.watchParams) && options.watchParams.length > 0;
7081
- if (hasTrackerConfig) {
7082
- const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7083
- if (trackerAllowsRequest) {
7084
- return fetchFromAPI();
7085
- }
7066
+ const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7067
+ if (trackerAllowsRequest) {
7068
+ return fetchFromAPI();
7086
7069
  }
7087
7070
  return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7088
7071
  if (Array.isArray(dbData) && dbData.length > 0) {
@@ -7266,26 +7249,23 @@ class HTTPManagerStateService extends ComponentStore {
7266
7249
  if (hasExpired) {
7267
7250
  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 })));
7268
7251
  }
7269
- const hasTrackerConfig = Array.isArray(options?.watchParams) && options.watchParams.length > 0;
7270
- if (hasTrackerConfig) {
7271
- const trackerAllowsRequest = this.queryParamsTrackerService.checkRequestOptions(this.resolvePath(effectiveParams), this.buildQueryTrackerOptions(options));
7272
- if (!trackerAllowsRequest) {
7273
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7274
- if (Array.isArray(dbData) && dbData.length > 0) {
7275
- return of({ data: dbData, fromCache: true });
7276
- }
7277
- const currentStateData = this.get()?.data;
7278
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7279
- return of({ data: currentStateData, fromCache: true });
7280
- }
7281
- if (currentStateData &&
7282
- !Array.isArray(currentStateData) &&
7283
- Object.keys(currentStateData).length > 0) {
7284
- return of({ data: currentStateData, fromCache: true });
7285
- }
7286
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7287
- }));
7288
- }
7252
+ const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7253
+ if (!trackerAllowsRequest) {
7254
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7255
+ if (Array.isArray(dbData) && dbData.length > 0) {
7256
+ return of({ data: dbData, fromCache: true });
7257
+ }
7258
+ const currentStateData = this.get()?.data;
7259
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7260
+ return of({ data: currentStateData, fromCache: true });
7261
+ }
7262
+ if (currentStateData &&
7263
+ !Array.isArray(currentStateData) &&
7264
+ Object.keys(currentStateData).length > 0) {
7265
+ return of({ data: currentStateData, fromCache: true });
7266
+ }
7267
+ return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7268
+ }));
7289
7269
  }
7290
7270
  this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7291
7271
  return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })));
@@ -7646,14 +7626,14 @@ class HTTPManagerStateService extends ComponentStore {
7646
7626
  requestOptions.stream = true;
7647
7627
  const effectiveParams = this.getEffectiveParams(streamOptions?.path);
7648
7628
  const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7649
- this.localStorageManagerService.updateStore({
7650
- name: tableName,
7651
- data: { ...this.databaseOptions, ...{ expires: this.utils.expires(this.databaseOptions.expiresIn) } }
7652
- });
7653
7629
  const schema = this.buildSchemaFromSample(dbData[0]);
7654
7630
  const schemaSignature = this.buildSchemaSignature(schema);
7655
7631
  const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7656
7632
  return this.localStorageManagerService.store$(tableName).pipe(take(1), switchMap((storeData) => {
7633
+ this.localStorageManagerService.updateStore({
7634
+ name: tableName,
7635
+ data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions.expiresIn) }
7636
+ });
7657
7637
  const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7658
7638
  const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7659
7639
  const ensureTable$ = schemaChanged
@@ -7988,12 +7968,6 @@ class HTTPManagerStateService extends ComponentStore {
7988
7968
  : { ...options.headers };
7989
7969
  return options;
7990
7970
  }
7991
- buildQueryTrackerOptions(options) {
7992
- return {
7993
- watchParams: Array.isArray(options?.watchParams) ? options.watchParams : [],
7994
- watchExpiresAt: options?.watchExpiresAt,
7995
- };
7996
- }
7997
7971
  normalizeObject(value) {
7998
7972
  if (Array.isArray(value)) {
7999
7973
  return value.map((item) => this.normalizeObject(item));
@@ -8108,12 +8082,176 @@ class HTTPManagerStateService extends ComponentStore {
8108
8082
  return;
8109
8083
  const updated = { ...(storeData || {}) };
8110
8084
  delete updated.requestCache;
8085
+ delete updated.tracker;
8111
8086
  this.localStorageManagerService.updateStore({
8112
8087
  name: tableName,
8113
8088
  data: updated
8114
8089
  });
8115
8090
  })).subscribe();
8116
8091
  }
8092
+ getTrackerState(storeData) {
8093
+ if (storeData?.tracker && typeof storeData.tracker === 'object') {
8094
+ return {
8095
+ consumedValuesByKey: storeData.tracker.consumedValuesByKey || {},
8096
+ trackingExpires: storeData.tracker.trackingExpires ?? null,
8097
+ };
8098
+ }
8099
+ return { consumedValuesByKey: {}, trackingExpires: null };
8100
+ }
8101
+ saveTrackerState(tableName, consumedValuesByKey, trackingExpires) {
8102
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8103
+ this.localStorageManagerService.updateStore({
8104
+ name: tableName,
8105
+ data: { ...(storeData || {}), tracker: { consumedValuesByKey, trackingExpires } }
8106
+ });
8107
+ })).subscribe();
8108
+ }
8109
+ trackerNormalizePath(path) {
8110
+ const pathSegments = [];
8111
+ const query = {};
8112
+ path.forEach((segment) => {
8113
+ if (segment && typeof segment === 'object' && !Array.isArray(segment)) {
8114
+ Object.keys(segment).forEach((key) => {
8115
+ query[this.trackerNormalizeParamKey(key)] = this.trackerNormalizeParamValue(segment[key]);
8116
+ });
8117
+ }
8118
+ else {
8119
+ const parsed = this.trackerParsePathSegment(String(segment));
8120
+ pathSegments.push(parsed.pathSegment);
8121
+ Object.keys(parsed.queryObject).forEach((key) => {
8122
+ query[this.trackerNormalizeParamKey(key)] = this.trackerNormalizeParamValue(parsed.queryObject[key]);
8123
+ });
8124
+ }
8125
+ });
8126
+ return {
8127
+ pathKey: this.trackerNormalizePathSegments(pathSegments),
8128
+ query,
8129
+ hasQuery: Object.keys(query).length > 0,
8130
+ };
8131
+ }
8132
+ trackerParsePathSegment(rawSegment) {
8133
+ const [pathPart, ...queryParts] = String(rawSegment).split('?');
8134
+ const queryString = queryParts.join('?');
8135
+ const queryObject = {};
8136
+ if (queryString) {
8137
+ queryString.split('&').forEach((pair) => {
8138
+ if (!pair)
8139
+ return;
8140
+ const [rawKey, ...rawValueParts] = pair.split('=');
8141
+ const key = this.trackerSafeDecode(rawKey).trim();
8142
+ const value = this.trackerSafeDecode(rawValueParts.join('=')).trim();
8143
+ if (!key)
8144
+ return;
8145
+ queryObject[key] = value;
8146
+ });
8147
+ }
8148
+ return { pathSegment: pathPart, queryObject };
8149
+ }
8150
+ trackerSafeDecode(value) {
8151
+ if (typeof value === 'undefined')
8152
+ return '';
8153
+ try {
8154
+ return decodeURIComponent(value);
8155
+ }
8156
+ catch {
8157
+ return String(value);
8158
+ }
8159
+ }
8160
+ trackerNormalizePathSegments(pathSegments) {
8161
+ return pathSegments
8162
+ .map((segment) => String(segment).trim())
8163
+ .filter((segment) => segment.length > 0)
8164
+ .join('/')
8165
+ .replace(/([^:]\/+)\/+/g, '$1')
8166
+ .replace(/^\//, '')
8167
+ .replace(/\/$/, '');
8168
+ }
8169
+ trackerNormalizeParamKey(key) {
8170
+ return String(key).trim().toLowerCase();
8171
+ }
8172
+ trackerNormalizeParamValue(value) {
8173
+ if (Array.isArray(value)) {
8174
+ return value.map((item) => this.trackerNormalizeParamValue(item)).join(',');
8175
+ }
8176
+ if (Object.prototype.toString.call(value) === '[object Object]') {
8177
+ return JSON.stringify(Object.keys(value).sort().reduce((acc, k) => {
8178
+ acc[this.trackerNormalizeParamKey(k)] = this.trackerNormalizeParamValue(value[k]);
8179
+ return acc;
8180
+ }, {}));
8181
+ }
8182
+ return String(value).trim().toLowerCase();
8183
+ }
8184
+ trackerFilterQuery(query, ignoreQueryParams) {
8185
+ if (!Array.isArray(ignoreQueryParams))
8186
+ return { ...query };
8187
+ if (ignoreQueryParams.length === 0)
8188
+ return {};
8189
+ const result = {};
8190
+ ignoreQueryParams.forEach((param) => {
8191
+ const key = this.trackerNormalizeParamKey(param);
8192
+ if (Object.prototype.hasOwnProperty.call(query, key)) {
8193
+ result[key] = query[key];
8194
+ }
8195
+ });
8196
+ return result;
8197
+ }
8198
+ trackerBuildExpiryEpoch(expireIn) {
8199
+ if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '')
8200
+ return null;
8201
+ if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
8202
+ return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
8203
+ }
8204
+ const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
8205
+ const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
8206
+ if (!parsed)
8207
+ return null;
8208
+ const toSeconds = { y: 31556926, w: 604800, d: 86400, hr: 3600, h: 3600, mn: 60, min: 60, m: 60, s: 1 };
8209
+ const seconds = toSeconds[parsed[2]];
8210
+ if (!seconds)
8211
+ return null;
8212
+ return Math.floor(Date.now() / 1000) + Number(parsed[1]) * seconds;
8213
+ }
8214
+ checkTrackerAllowsRequest(tableName, path, options, storeData) {
8215
+ if (options?.watchParams) {
8216
+ return this.queryParamsTrackerService.checkRequestOptions(path, {
8217
+ watchParams: options.watchParams,
8218
+ watchExpiresAt: options?.watchExpiresAt,
8219
+ });
8220
+ }
8221
+ const normalized = this.trackerNormalizePath(path);
8222
+ const ignoreQueryParams = Array.isArray(options?.ignoreQueryParams) ? options.ignoreQueryParams : [];
8223
+ if (!normalized.hasQuery || ignoreQueryParams.length === 0) {
8224
+ return !this.getRequestCacheMetadata(storeData, 'GET');
8225
+ }
8226
+ const filtered = this.trackerFilterQuery(normalized.query, ignoreQueryParams);
8227
+ const keys = Object.keys(filtered);
8228
+ if (keys.length === 0) {
8229
+ return !this.getRequestCacheMetadata(storeData, 'GET');
8230
+ }
8231
+ const tracker = this.getTrackerState(storeData);
8232
+ const now = Math.floor(Date.now() / 1000);
8233
+ if (tracker.trackingExpires && tracker.trackingExpires <= now) {
8234
+ tracker.consumedValuesByKey = {};
8235
+ tracker.trackingExpires = null;
8236
+ }
8237
+ let accepted = false;
8238
+ keys.forEach(key => {
8239
+ const value = filtered[key];
8240
+ const consumed = tracker.consumedValuesByKey[key] || [];
8241
+ if (!consumed.includes(value)) {
8242
+ tracker.consumedValuesByKey[key] = [...consumed, value];
8243
+ accepted = true;
8244
+ }
8245
+ });
8246
+ if (accepted) {
8247
+ const newExpiry = this.trackerBuildExpiryEpoch(options?.queryParamsExpiresIn);
8248
+ if (newExpiry !== null) {
8249
+ tracker.trackingExpires = newExpiry;
8250
+ }
8251
+ this.saveTrackerState(tableName, tracker.consumedValuesByKey, tracker.trackingExpires);
8252
+ }
8253
+ return accepted;
8254
+ }
8117
8255
  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 }); }
8118
8256
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HTTPManagerStateService }); }
8119
8257
  }
@@ -9604,8 +9742,8 @@ class RequestManagerStateDemoComponent {
9604
9742
  database: this.fb.group({
9605
9743
  table: [''],
9606
9744
  expiresIn: ['1m'],
9607
- watchParams: [''],
9608
- watchExpiresAt: ['never'],
9745
+ ignoreQueryParams: [''],
9746
+ queryParamsExpiresIn: ['never'],
9609
9747
  })
9610
9748
  });
9611
9749
  this.sampleAdaptors = [
@@ -9778,8 +9916,8 @@ class RequestManagerStateDemoComponent {
9778
9916
  path,
9779
9917
  headers,
9780
9918
  forceRefresh: false,
9781
- watchParams: isDbEnabled ? this.parseWatchParams(dbValue?.watchParams) : [],
9782
- watchExpiresAt: isDbEnabled ? this.normalizeWatchExpiry(dbValue?.watchExpiresAt) : undefined,
9919
+ ignoreQueryParams: isDbEnabled ? this.parseWatchParams(dbValue?.ignoreQueryParams) : [],
9920
+ queryParamsExpiresIn: isDbEnabled ? this.normalizeWatchExpiry(dbValue?.queryParamsExpiresIn) : undefined,
9783
9921
  });
9784
9922
  }
9785
9923
  onSetStateOptions() {
@@ -9864,11 +10002,11 @@ class RequestManagerStateDemoComponent {
9864
10002
  this.prompts = [];
9865
10003
  }
9866
10004
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9867
- 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" }] }); }
10005
+ 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=\"ignoreQueryParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"queryParamsExpiresIn\">\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" }] }); }
9868
10006
  }
9869
10007
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
9870
10008
  type: Component,
9871
- 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"] }]
10009
+ 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=\"ignoreQueryParams\" value=\"\">\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Watch Param Expiry</mat-label>\n <mat-select formControlName=\"queryParamsExpiresIn\">\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"] }]
9872
10010
  }], ctorParameters: () => [], propDecorators: { server: [{
9873
10011
  type: Input
9874
10012
  }], adapter: [{
@@ -12730,5 +12868,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12730
12868
  * Generated bundle index. Do not edit.
12731
12869
  */
12732
12870
 
12733
- 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 };
12871
+ 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, 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 };
12734
12872
  //# sourceMappingURL=http-request-manager.mjs.map