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,
|
|
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(
|
|
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?.
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
923
|
-
|
|
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
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return false;
|
|
1018
|
+
try {
|
|
1019
|
+
const result = parseStreamData(this.buffer, this.contentType, this.streamConfig);
|
|
1020
|
+
return result;
|
|
976
1021
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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?.
|
|
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
|
-
|
|
5921
|
+
async DBOpened() {
|
|
6090
5922
|
try {
|
|
6091
|
-
|
|
6092
|
-
return
|
|
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
|
-
|
|
6099
|
-
|
|
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
|
-
|
|
6102
|
-
return str
|
|
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:
|
|
6105
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
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:
|
|
5975
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DbService, decorators: [{
|
|
6108
5976
|
type: Injectable,
|
|
6109
|
-
args: [{
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
type: Inject,
|
|
6114
|
-
args: [CONFIG_SETTINGS_TOKEN]
|
|
6115
|
-
}] }] });
|
|
5977
|
+
args: [{
|
|
5978
|
+
providedIn: 'root',
|
|
5979
|
+
}]
|
|
5980
|
+
}], ctorParameters: () => [] });
|
|
6116
5981
|
|
|
6117
|
-
class
|
|
6118
|
-
constructor(
|
|
6119
|
-
|
|
6120
|
-
this.schema = schema;
|
|
5982
|
+
class DatabaseManagerService extends DbService {
|
|
5983
|
+
constructor() {
|
|
5984
|
+
super();
|
|
6121
5985
|
}
|
|
6122
|
-
|
|
6123
|
-
|
|
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
|
-
|
|
6128
|
-
|
|
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
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
this.
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6148
|
+
clearTable(table) {
|
|
6149
|
+
const tableName = this.cleanTableName(table);
|
|
6150
|
+
console.log(`clearTable: Starting clear for table: ${tableName}`);
|
|
6151
6151
|
try {
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
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 (
|
|
6176
|
-
console.error(
|
|
6177
|
-
|
|
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
|
-
|
|
6187
|
-
|
|
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
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
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
|
-
|
|
6194
|
-
|
|
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
|
-
|
|
6197
|
-
|
|
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
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
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
|
-
|
|
6204
|
-
return
|
|
6227
|
+
if (options.mode === 'exact') {
|
|
6228
|
+
return this.checkExact(normalized, options);
|
|
6205
6229
|
}
|
|
6230
|
+
return this.checkVariation(normalized, options);
|
|
6206
6231
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
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
|
-
|
|
6211
|
-
const
|
|
6212
|
-
|
|
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
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
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
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
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
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
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.
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
const
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
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
|
-
|
|
6253
|
-
|
|
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
|
-
|
|
6257
|
-
console.error('Error opening database after schema update:', err);
|
|
6258
|
-
throw err;
|
|
6259
|
-
}
|
|
6284
|
+
return accepted;
|
|
6260
6285
|
}
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
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
|
-
|
|
6272
|
-
const schema = {};
|
|
6273
|
-
if (!this.tables || this.tables.length === 0) {
|
|
6274
|
-
return schema;
|
|
6290
|
+
if (watchParams.length === 0) {
|
|
6291
|
+
return {};
|
|
6275
6292
|
}
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
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
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
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
|
-
|
|
6298
|
-
|
|
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
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
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
|
-
|
|
6339
|
-
const
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
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
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
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
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
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
|
-
|
|
6367
|
-
return
|
|
6377
|
+
normalizeParamKey(key) {
|
|
6378
|
+
return String(key).trim().toLowerCase();
|
|
6368
6379
|
}
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
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
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
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
|
-
|
|
6385
|
-
const
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
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
|
-
|
|
6395
|
-
const
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
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
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
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
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
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
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6453
|
+
ensureStateRestored() {
|
|
6454
|
+
if (this.stateRestored) {
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
this.stateRestored = true;
|
|
6458
|
+
this.restoreState();
|
|
6456
6459
|
}
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6472
|
-
}));
|
|
6490
|
+
});
|
|
6473
6491
|
}
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
.
|
|
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
|
-
|
|
6480
|
-
|
|
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
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6503
|
+
isPlainObject(value) {
|
|
6504
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
6505
|
+
}
|
|
6506
|
+
hasSessionStorage() {
|
|
6491
6507
|
try {
|
|
6492
|
-
|
|
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
|
|
6504
|
-
|
|
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:
|
|
6509
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type:
|
|
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:
|
|
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
|
-
}]
|
|
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())
|
|
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
|
|
7081
|
-
if (
|
|
7082
|
-
|
|
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
|
|
7270
|
-
if (
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
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
|
-
|
|
9608
|
-
|
|
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
|
-
|
|
9782
|
-
|
|
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,
|
|
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
|