http-request-manager 18.15.31 → 18.15.33

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.
@@ -4,10 +4,11 @@ import { ComponentStore } from '@ngrx/component-store';
4
4
  import { map, catchError, filter, take, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, concatMap, toArray, withLatestFrom, switchMap, delay, scan, distinctUntilChanged } from 'rxjs/operators';
5
5
  import { HttpClient, HttpHeaders, HttpEventType, HttpHeaderResponse, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
6
6
  import * as CryptoJS from 'crypto-js';
7
- import { from, BehaviorSubject, EMPTY, timer, throwError, defer, interval, 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';
7
+ import { from, BehaviorSubject, EMPTY, timer, throwError, defer, interval, Subject, of, merge, Subscription, take as take$1, firstValueFrom, catchError as catchError$1, map as map$1, tap as tap$1, switchMap as switchMap$1, startWith as startWith$1, distinctUntilChanged as distinctUntilChanged$1, combineLatest, filter as filter$1, takeUntil as takeUntil$1, ReplaySubject } from 'rxjs';
8
8
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
9
  import { ToastMessageDisplayService, ToastDisplay, ToastColors, ToastMessageDisplayModule } from 'toast-message-display';
10
10
  import Dexie from 'dexie';
11
+ import { Parser } from 'node-sql-parser/build/mysql';
11
12
  import * as i1 from '@ngx-translate/core';
12
13
  import { TranslateModule } from '@ngx-translate/core';
13
14
  import * as i1$1 from '@angular/common';
@@ -1655,7 +1656,7 @@ class WebsocketService {
1655
1656
  }
1656
1657
  sendMessageInChannel(channel, content) {
1657
1658
  if (this.socket?.readyState === WebSocket.OPEN) {
1658
- this.socket.send(JSON.stringify({ type: 'stateMangerMessage', subscribedChannel: channel, content }));
1659
+ this.socket.send(JSON.stringify({ type: 'statemanagerMessage', subscribedChannel: channel, content }));
1659
1660
  this.logger.debug('WebSocket', `💬 Send message`, { channel, content });
1660
1661
  }
1661
1662
  else {
@@ -1875,6 +1876,7 @@ class WebSocketManagerService {
1875
1876
  static { this.retryDelay = 5000; }
1876
1877
  static { this.retrySubscription = null; }
1877
1878
  static { this.maxRetries = 10; }
1879
+ static { this.connectionReadyNotified = false; }
1878
1880
  static { this.retryCountSubject = new BehaviorSubject(0); }
1879
1881
  static { this.maxRetriesSubject = new BehaviorSubject(10); }
1880
1882
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1968,6 +1970,7 @@ class WebSocketManagerService {
1968
1970
  // Mark as connecting — connectionStatus true while connecting/retrying, false only when exhausted
1969
1971
  WebSocketManagerService.isConnecting = true;
1970
1972
  WebSocketManagerService.isSubscribed = false;
1973
+ WebSocketManagerService.connectionReadyNotified = false;
1971
1974
  WebSocketManagerService.subscribedChannels.next(new Set());
1972
1975
  WebSocketManagerService.connectionStatus.next(true);
1973
1976
  const sessionId = this.getSessionId();
@@ -2002,11 +2005,15 @@ class WebSocketManagerService {
2002
2005
  return;
2003
2006
  }
2004
2007
  // First valid (non-error) message confirms the server accepted auth.
2005
- // Reset retry counter now that we have a stable connection.
2006
- if (WebSocketManagerService.retryCount > 0) {
2007
- console.log(`✅ Server confirmed connection after ${WebSocketManagerService.retryCount} retries.`);
2008
- WebSocketManagerService.retryCount = 0;
2009
- WebSocketManagerService.retryCountSubject.next(0);
2008
+ // Notify readiness once per connection so pending intended subscriptions
2009
+ // are replayed after the socket is actually usable.
2010
+ if (!WebSocketManagerService.connectionReadyNotified) {
2011
+ if (WebSocketManagerService.retryCount > 0) {
2012
+ console.log(`✅ Server confirmed connection after ${WebSocketManagerService.retryCount} retries.`);
2013
+ WebSocketManagerService.retryCount = 0;
2014
+ WebSocketManagerService.retryCountSubject.next(0);
2015
+ }
2016
+ WebSocketManagerService.connectionReadyNotified = true;
2010
2017
  console.log(`🔄 Emitting reconnect event for MessageTrackerService`);
2011
2018
  WebSocketManagerService.onReconnect.next();
2012
2019
  }
@@ -2019,6 +2026,7 @@ class WebSocketManagerService {
2019
2026
  WebSocketManagerService.socket.onclose = (event) => {
2020
2027
  console.log(`🔴 WebSocket closed (code: ${event.code})`);
2021
2028
  WebSocketManagerService.isConnecting = false;
2029
+ WebSocketManagerService.connectionReadyNotified = false;
2022
2030
  WebSocketManagerService.socket = null;
2023
2031
  WebSocketManagerService.subscribedChannels.next(new Set());
2024
2032
  const maxRetries = WebSocketManagerService.maxRetries;
@@ -2212,7 +2220,7 @@ class WebSocketManagerService {
2212
2220
  sendMessageInChannel(channel, content) {
2213
2221
  if (WebSocketManagerService.socket?.readyState === WebSocket.OPEN) {
2214
2222
  const message = {
2215
- type: 'stateMangerMessage',
2223
+ type: 'statemanagerMessage',
2216
2224
  subscribedChannel: channel,
2217
2225
  content
2218
2226
  };
@@ -5280,7 +5288,7 @@ class LocalStorageManagerService extends ComponentStore {
5280
5288
  ? [...data.localStores].reverse().find(item => item.id === foundStore.id)
5281
5289
  : [...data.sessionStores].reverse().find(item => item.id === foundStore.id);
5282
5290
  if (!found) {
5283
- console.warn('[CacheDebug] store$: settings entry found but no data entry in localStores/sessionStores', { store, foundStoreId: foundStore.id, storageType: foundStore.options?.storage });
5291
+ // console.warn('[CacheDebug] store$: settings entry found but no data entry in localStores/sessionStores', { store, foundStoreId: foundStore.id, storageType: foundStore.options?.storage })
5284
5292
  this.deleteStore({ name: store });
5285
5293
  return;
5286
5294
  }
@@ -5337,7 +5345,7 @@ class LocalStorageManagerService extends ComponentStore {
5337
5345
  const hasStore = this.hasGlobalStorage(type, store.id);
5338
5346
  store.name = this.validStoreName(store.name);
5339
5347
  if (!hasStore) {
5340
- console.warn(`No such Store: ${store.name}`);
5348
+ // console.warn(`No such Store: ${store.name}`)
5341
5349
  return state;
5342
5350
  }
5343
5351
  else {
@@ -5391,23 +5399,14 @@ class LocalStorageManagerService extends ComponentStore {
5391
5399
  store.name = this.validStoreName(store.name);
5392
5400
  const settings = [...state.settings].reverse().find(item => item.name === store.name);
5393
5401
  if (settings) {
5394
- const type = settings.options?.storage;
5395
- const hasStore = this.hasGlobalStorage(type, settings.id || '');
5396
- if (!hasStore) {
5397
- console.warn(`No such Store: ${store.name}`);
5398
- }
5399
- else {
5400
- const dataStr = (settings.options?.encrypted) ? this.encryption.encrypt(store.data, this.app.appID) : store.data;
5401
- const localData = (dataStr && settings.options?.storage === StorageType.GLOBAL) ? [{ data: dataStr, id: settings.id }] : [];
5402
- const sessionData = (dataStr && settings.options?.storage === StorageType.SESSION) ? [{ data: dataStr, id: settings.id }] : [];
5403
- state.localStores = state.localStores.filter(item => item.id !== settings.id);
5404
- state.sessionStores = state.sessionStores.filter(item => item.id !== settings.id);
5405
- return {
5406
- ...state,
5407
- localStores: [...state.localStores, ...localData],
5408
- sessionStores: [...state.sessionStores, ...sessionData],
5409
- };
5410
- }
5402
+ const dataStr = (settings.options?.encrypted) ? this.encryption.encrypt(store.data, this.app.appID) : store.data;
5403
+ const localData = (dataStr && settings.options?.storage === StorageType.GLOBAL) ? [{ data: dataStr, id: settings.id }] : [];
5404
+ const sessionData = (dataStr && settings.options?.storage === StorageType.SESSION) ? [{ data: dataStr, id: settings.id }] : [];
5405
+ return {
5406
+ ...state,
5407
+ localStores: [...state.localStores.filter(item => item.id !== settings.id), ...localData],
5408
+ sessionStores: [...state.sessionStores.filter(item => item.id !== settings.id), ...sessionData],
5409
+ };
5411
5410
  }
5412
5411
  return state;
5413
5412
  });
@@ -5427,7 +5426,7 @@ class LocalStorageManagerService extends ComponentStore {
5427
5426
  };
5428
5427
  }
5429
5428
  else {
5430
- console.warn(`No such Store: ${store.name}`);
5429
+ // console.warn(`No such Store: ${store.name}`)
5431
5430
  return state;
5432
5431
  }
5433
5432
  });
@@ -5586,9 +5585,15 @@ class LocalStorageManagerService extends ComponentStore {
5586
5585
  */
5587
5586
  clearAllStoredData() {
5588
5587
  try {
5589
- localStorage.removeItem(this.storageSettingsName);
5590
- localStorage.removeItem(this.storageName);
5591
- sessionStorage.removeItem(this.storageName);
5588
+ // Use resetStore() instead of directly removing raw localStorage keys.
5589
+ // clearAllStoredData previously only removed the raw localStorage entries, leaving
5590
+ // the ComponentStore's in-memory BehaviorSubject untouched. Any subsequent call to
5591
+ // store$(name) would read from the in-memory state and return the old (stale) data,
5592
+ // because store$ is a ComponentStore selector — it reads from in-memory state, not
5593
+ // from raw localStorage. This caused stale requestCache / queryParams to persist
5594
+ // after a deleteDatabase() call, blocking all subsequent API requests.
5595
+ // resetStore() correctly updates both the in-memory state AND persists to localStorage.
5596
+ this.resetStore();
5592
5597
  console.log('Cleared all stored data');
5593
5598
  }
5594
5599
  catch (error) {
@@ -6486,8 +6491,8 @@ class DatabaseManagerService extends DbService {
6486
6491
  }
6487
6492
  }
6488
6493
  clearDatabase() {
6489
- from(this.delete()).pipe(tap(() => this.localStorageManager.clearAllStoredData()), map(() => true), catchError((error) => {
6490
- console.error('clearDatabase: ❌ Error deleting database:', error);
6494
+ this.deleteAndReinitialize().pipe(tap(() => this.localStorageManager.clearAllStoredData()), catchError((error) => {
6495
+ console.error('clearDatabase: ❌ Error clearing database:', error);
6491
6496
  return of(false);
6492
6497
  })).subscribe();
6493
6498
  }
@@ -6519,347 +6524,7 @@ class ChannelMessage {
6519
6524
  }
6520
6525
  }
6521
6526
 
6522
- const TRACKER_STORE_NAME = 'query_params_tracker';
6523
- const TRACKER_SESSION_INIT_KEY = 'query_params_tracker_initialized';
6524
- const DEFAULT_TRACKER_OPTIONS = SettingOptions.adapt({
6525
- storage: StorageType.GLOBAL,
6526
- encrypted: false,
6527
- });
6528
- class QueryParamsTrackerService {
6529
- constructor() {
6530
- this.state = { paths: {} };
6531
- this.stateRestored = false;
6532
- this.localStorageManager = inject(LocalStorageManagerService);
6533
- this.ready$ = new BehaviorSubject(false);
6534
- }
6535
- clearTracking(resetSessionInit = false) {
6536
- this.state = { paths: {} };
6537
- this.localStorageManager.deleteStore({ name: TRACKER_STORE_NAME });
6538
- if (resetSessionInit && this.hasSessionStorage()) {
6539
- sessionStorage.removeItem(TRACKER_SESSION_INIT_KEY);
6540
- }
6541
- }
6542
- clearTrackingForPath(pathKey) {
6543
- this.ensureStateRestored();
6544
- if (this.state.paths[pathKey]) {
6545
- delete this.state.paths[pathKey];
6546
- this.persistState();
6547
- }
6548
- }
6549
- checkRequestOptions(requestOptions = [], options = {}) {
6550
- this.ensureStateRestored();
6551
- if (!this.ready$.value) {
6552
- return true;
6553
- }
6554
- const normalized = this.normalizeRequestOptions(requestOptions);
6555
- if (!normalized.pathKey)
6556
- return false;
6557
- this.cleanupExpiredEntries();
6558
- if (!normalized.hasQuery) {
6559
- const pathState = this.ensurePathState(normalized.pathKey);
6560
- const pathSeenBefore = Object.keys(pathState.consumedValuesByKey).length > 0;
6561
- if (!pathSeenBefore) {
6562
- this.persistState();
6563
- }
6564
- return !pathSeenBefore;
6565
- }
6566
- if (options.mode === 'exact') {
6567
- return this.checkExact(normalized, options);
6568
- }
6569
- return this.checkVariation(normalized, options);
6570
- }
6571
- matchesPath(requestOptions = [], expectedPathOptions = []) {
6572
- this.ensureStateRestored();
6573
- const requestPath = this.normalizeRequestOptions(requestOptions).pathKey;
6574
- const expectedPath = this.normalizeRequestOptions(expectedPathOptions).pathKey;
6575
- return Boolean(requestPath) && requestPath === expectedPath;
6576
- }
6577
- checkExact(normalized, options) {
6578
- const pathState = this.ensurePathState(normalized.pathKey);
6579
- const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
6580
- if (Object.keys(filteredQuery).length === 0) {
6581
- return false;
6582
- }
6583
- const hash = this.stringifyQuery(filteredQuery);
6584
- const consumed = pathState.consumedValuesByKey['__exact__'] || [];
6585
- if (!consumed.includes(hash)) {
6586
- pathState.consumedValuesByKey['__exact__'] = [...consumed, hash];
6587
- this.persistState();
6588
- return true;
6589
- }
6590
- return false;
6591
- }
6592
- checkVariation(normalized, options) {
6593
- const pathState = this.ensurePathState(normalized.pathKey);
6594
- if (Array.isArray(options.watchParams) && options.watchParams.length === 0) {
6595
- const pathSeenBefore = Object.keys(pathState.consumedValuesByKey).length > 0;
6596
- if (!pathSeenBefore) {
6597
- pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
6598
- this.persistState();
6599
- }
6600
- return !pathSeenBefore;
6601
- }
6602
- const filteredQuery = this.filterQueryByWatchParams(normalized.query, options.watchParams);
6603
- const keys = Object.keys(filteredQuery);
6604
- if (keys.length === 0) {
6605
- return false;
6606
- }
6607
- this.resetPathStateIfExpired(pathState);
6608
- let accepted = false;
6609
- keys.forEach((key) => {
6610
- const currentValue = filteredQuery[key];
6611
- const consumed = pathState.consumedValuesByKey[key] || [];
6612
- if (!consumed.includes(currentValue)) {
6613
- pathState.consumedValuesByKey[key] = [...consumed, currentValue];
6614
- accepted = true;
6615
- }
6616
- });
6617
- if (accepted) {
6618
- pathState.watchExpiresAt = this.buildExpiryEpoch(typeof options.watchExpiresAt !== 'undefined' ? options.watchExpiresAt : options.watchParamsExpire);
6619
- this.persistState();
6620
- }
6621
- return accepted;
6622
- }
6623
- filterQueryByWatchParams(query, watchParams) {
6624
- if (!Array.isArray(watchParams)) {
6625
- return query;
6626
- }
6627
- if (watchParams.length === 0) {
6628
- return {};
6629
- }
6630
- const normalizedWatchParams = watchParams.map((p) => this.normalizeParamKey(p));
6631
- return Object.keys(query).reduce((acc, key) => {
6632
- if (normalizedWatchParams.includes(key)) {
6633
- acc[key] = query[key];
6634
- }
6635
- return acc;
6636
- }, {});
6637
- }
6638
- normalizeRequestOptions(requestOptions) {
6639
- const params = Array.isArray(requestOptions) ? requestOptions : [];
6640
- const pathSegments = [];
6641
- const queryObjects = [];
6642
- params.forEach((item) => {
6643
- if (this.isPlainObject(item)) {
6644
- queryObjects.push(item);
6645
- }
6646
- else if (typeof item !== 'undefined' && item !== null) {
6647
- const parsed = this.parsePathSegment(String(item));
6648
- if (parsed.pathSegment) {
6649
- pathSegments.push(parsed.pathSegment);
6650
- }
6651
- if (Object.keys(parsed.queryObject).length > 0) {
6652
- queryObjects.push(parsed.queryObject);
6653
- }
6654
- }
6655
- });
6656
- const query = queryObjects.reduce((acc, current) => {
6657
- Object.keys(current).forEach((key) => {
6658
- const normalizedKey = this.normalizeParamKey(key);
6659
- const value = current[key];
6660
- if (!normalizedKey || typeof value === 'undefined' || value === null || value === '') {
6661
- return;
6662
- }
6663
- acc[normalizedKey] = this.normalizeParamValue(value);
6664
- });
6665
- return acc;
6666
- }, {});
6667
- return {
6668
- pathKey: this.normalizePath(pathSegments),
6669
- query,
6670
- hasQuery: Object.keys(query).length > 0,
6671
- };
6672
- }
6673
- parsePathSegment(rawSegment) {
6674
- const [pathPart, ...queryParts] = String(rawSegment).split('?');
6675
- const queryString = queryParts.join('?');
6676
- const queryObject = {};
6677
- if (queryString) {
6678
- queryString.split('&').forEach((pair) => {
6679
- if (!pair)
6680
- return;
6681
- const [rawKey, ...rawValueParts] = pair.split('=');
6682
- const key = this.safeDecode(rawKey).trim();
6683
- const value = this.safeDecode(rawValueParts.join('=')).trim();
6684
- if (!key)
6685
- return;
6686
- queryObject[key] = value;
6687
- });
6688
- }
6689
- return {
6690
- pathSegment: pathPart,
6691
- queryObject,
6692
- };
6693
- }
6694
- safeDecode(value) {
6695
- if (typeof value === 'undefined') {
6696
- return '';
6697
- }
6698
- try {
6699
- return decodeURIComponent(value);
6700
- }
6701
- catch {
6702
- return String(value);
6703
- }
6704
- }
6705
- normalizePath(pathSegments) {
6706
- return pathSegments
6707
- .map((segment) => String(segment).trim())
6708
- .filter((segment) => segment.length > 0)
6709
- .join('/')
6710
- .replace(/([^:]\/+)\/+/g, '$1')
6711
- .replace(/^\//, '')
6712
- .replace(/\/$/, '');
6713
- }
6714
- normalizeParamKey(key) {
6715
- return String(key).trim().toLowerCase();
6716
- }
6717
- normalizeParamValue(value) {
6718
- if (Array.isArray(value)) {
6719
- return value.map((item) => this.normalizeParamValue(item)).join(',');
6720
- }
6721
- if (this.isPlainObject(value)) {
6722
- return this.stringifyQuery(Object.keys(value).reduce((acc, key) => {
6723
- acc[this.normalizeParamKey(key)] = this.normalizeParamValue(value[key]);
6724
- return acc;
6725
- }, {}));
6726
- }
6727
- return String(value).trim().toLowerCase();
6728
- }
6729
- buildExpiryEpoch(expireIn) {
6730
- if (typeof expireIn === 'undefined' || expireIn === null || expireIn === '') {
6731
- return undefined;
6732
- }
6733
- if (typeof expireIn === 'number' && Number.isFinite(expireIn) && expireIn > 0) {
6734
- return Math.floor(Date.now() / 1000) + Math.floor(expireIn);
6735
- }
6736
- const raw = String(expireIn).trim().toLowerCase().replace(/\s+/g, '');
6737
- const parsed = raw.match(/^(\d+)(y|w|d|hr|h|mn|min|m|s)$/);
6738
- if (!parsed) {
6739
- return undefined;
6740
- }
6741
- const value = Number(parsed[1]);
6742
- const unit = parsed[2];
6743
- const toSeconds = {
6744
- y: 31556926,
6745
- w: 604800,
6746
- d: 86400,
6747
- hr: 3600,
6748
- h: 3600,
6749
- mn: 60,
6750
- min: 60,
6751
- m: 60,
6752
- s: 1,
6753
- };
6754
- const seconds = toSeconds[unit];
6755
- if (!seconds) {
6756
- return undefined;
6757
- }
6758
- return Math.floor(Date.now() / 1000) + value * seconds;
6759
- }
6760
- resetPathStateIfExpired(pathState) {
6761
- const now = Math.floor(Date.now() / 1000);
6762
- if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
6763
- pathState.consumedValuesByKey = {};
6764
- pathState.watchExpiresAt = undefined;
6765
- }
6766
- }
6767
- cleanupExpiredEntries() {
6768
- const now = Math.floor(Date.now() / 1000);
6769
- Object.keys(this.state.paths).forEach((pathKey) => {
6770
- const pathState = this.state.paths[pathKey];
6771
- if (pathState.watchExpiresAt && pathState.watchExpiresAt <= now) {
6772
- pathState.consumedValuesByKey = {};
6773
- pathState.watchExpiresAt = undefined;
6774
- }
6775
- const hasTrackedValues = Object.keys(pathState.consumedValuesByKey).some((key) => (pathState.consumedValuesByKey[key] || []).length > 0);
6776
- if (!hasTrackedValues) {
6777
- delete this.state.paths[pathKey];
6778
- }
6779
- });
6780
- }
6781
- ensurePathState(pathKey) {
6782
- if (!this.state.paths[pathKey]) {
6783
- this.state.paths[pathKey] = {
6784
- consumedValuesByKey: {},
6785
- };
6786
- }
6787
- return this.state.paths[pathKey];
6788
- }
6789
- ensureStateRestored() {
6790
- if (this.stateRestored) {
6791
- return;
6792
- }
6793
- this.stateRestored = true;
6794
- this.restoreState();
6795
- }
6796
- restoreState() {
6797
- this.localStorageManager.store$(TRACKER_STORE_NAME)
6798
- .pipe(take(1))
6799
- .subscribe({
6800
- next: (storedState) => {
6801
- if (this.isTrackerState(storedState)) {
6802
- this.state = QueryTrackerStateModel.adapt(storedState);
6803
- this.cleanupExpiredEntries();
6804
- }
6805
- else {
6806
- this.state = { paths: {} };
6807
- }
6808
- this.ready$.next(true);
6809
- },
6810
- error: () => {
6811
- this.state = { paths: {} };
6812
- this.ready$.next(true);
6813
- }
6814
- });
6815
- }
6816
- persistState() {
6817
- this.localStorageManager.storeExists$(TRACKER_STORE_NAME)
6818
- .pipe(take(1))
6819
- .subscribe((exists) => {
6820
- if (exists) {
6821
- this.localStorageManager.updateStore({ name: TRACKER_STORE_NAME, data: this.state });
6822
- return;
6823
- }
6824
- this.localStorageManager.createStore({
6825
- name: TRACKER_STORE_NAME,
6826
- data: this.state,
6827
- options: DEFAULT_TRACKER_OPTIONS,
6828
- });
6829
- });
6830
- }
6831
- stringifyQuery(query) {
6832
- return JSON.stringify(Object.keys(query)
6833
- .sort()
6834
- .reduce((acc, key) => {
6835
- acc[key] = query[key];
6836
- return acc;
6837
- }, {}));
6838
- }
6839
- isTrackerState(value) {
6840
- return this.isPlainObject(value) && this.isPlainObject(value.paths);
6841
- }
6842
- isPlainObject(value) {
6843
- return Object.prototype.toString.call(value) === '[object Object]';
6844
- }
6845
- hasSessionStorage() {
6846
- try {
6847
- return typeof sessionStorage !== 'undefined';
6848
- }
6849
- catch {
6850
- return false;
6851
- }
6852
- }
6853
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6854
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, providedIn: 'root' }); }
6855
- }
6856
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: QueryParamsTrackerService, decorators: [{
6857
- type: Injectable,
6858
- args: [{
6859
- providedIn: 'root'
6860
- }]
6861
- }] });
6862
-
6527
+ // import { QueryParamsTrackerService } from '../utils/query-params-tracker.service';
6863
6528
  const API_OPTS = new InjectionToken('API_OPTS');
6864
6529
  /**
6865
6530
  * Channel type enum for different communication purposes
@@ -6902,7 +6567,7 @@ class HTTPManagerStateService extends ComponentStore {
6902
6567
  this.localStorageManagerService = inject(LocalStorageManagerService);
6903
6568
  this.utils = inject(UtilsService);
6904
6569
  this.logger = inject(LoggerService);
6905
- this.queryParamsTrackerService = inject(QueryParamsTrackerService);
6570
+ // private queryParamsTrackerService = inject(QueryParamsTrackerService)
6906
6571
  this.error$ = this.httpManagerService.error$;
6907
6572
  this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
6908
6573
  this.operationSuccess = new BehaviorSubject(null);
@@ -7065,6 +6730,7 @@ class HTTPManagerStateService extends ComponentStore {
7065
6730
  WebSocketManagerService.addSubscribedChannel(channelName);
7066
6731
  }
7067
6732
  break;
6733
+ case 'statemanagerMessage':
7068
6734
  case 'stateMangerMessage':
7069
6735
  // CRITICAL DEBUG: Log channel comparison
7070
6736
  console.log('🔍 [STATE STORE] stateMangerMessage received:', {
@@ -7355,11 +7021,7 @@ class HTTPManagerStateService extends ComponentStore {
7355
7021
  name: tableName,
7356
7022
  data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions.expiresIn) }
7357
7023
  });
7358
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7359
- const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
7360
- const ensureTable$ = schemaChanged
7361
- ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
7362
- : this.dbManagerService.createDatabaseTable(tableDef);
7024
+ const ensureTable$ = this.dbManagerService.createDatabaseTable(tableDef);
7363
7025
  return ensureTable$.pipe(switchMap((created) => {
7364
7026
  if (!created) {
7365
7027
  console.warn('[DB STORAGE] Table create/open not ready, skipping DB write for this payload:', { table: tableName });
@@ -7402,7 +7064,7 @@ class HTTPManagerStateService extends ComponentStore {
7402
7064
  }));
7403
7065
  }
7404
7066
  return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7405
- console.log('[CacheDebug] storeData for table:', this.databaseOptions.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires });
7067
+ // console.log('[CacheDebug] storeData for table:', this.databaseOptions!.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires })
7406
7068
  const forceRefresh = !!options?.forceRefresh;
7407
7069
  const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7408
7070
  const expires = storeData?.expires || 0;
@@ -7415,12 +7077,12 @@ class HTTPManagerStateService extends ComponentStore {
7415
7077
  }
7416
7078
  const expectedSchema = this.buildSchemaFromAdapter();
7417
7079
  const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7418
- console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature });
7080
+ // console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions!.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature })
7419
7081
  if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7420
7082
  const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7421
7083
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7422
7084
  }
7423
- return this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData).pipe(switchMap((trackerAllowsRequest) => {
7085
+ return this.checkTrackerAllowsRequest(this.databaseOptions.table, 'GET', this.resolvePath(effectiveParams), options, storeData).pipe(switchMap((trackerAllowsRequest) => {
7424
7086
  if (trackerAllowsRequest) {
7425
7087
  const normalizedPath = this.trackerNormalizePath(this.resolvePath(effectiveParams));
7426
7088
  if (!normalizedPath.hasQuery) {
@@ -7452,16 +7114,7 @@ class HTTPManagerStateService extends ComponentStore {
7452
7114
  this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', requestSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7453
7115
  return of(dbData);
7454
7116
  }
7455
- const currentStateData = this.get()?.data;
7456
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7457
- return of(currentStateData);
7458
- }
7459
- if (currentStateData &&
7460
- !Array.isArray(currentStateData) &&
7461
- Object.keys(currentStateData).length > 0) {
7462
- return of(currentStateData);
7463
- }
7464
- return of(this.dataType === DataType.ARRAY ? [] : {});
7117
+ return fetchFromAPI();
7465
7118
  }), catchError((error) => {
7466
7119
  const tableName = this.databaseOptions.table;
7467
7120
  console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
@@ -7653,7 +7306,7 @@ class HTTPManagerStateService extends ComponentStore {
7653
7306
  const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7654
7307
  return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchStreamFromAPI()));
7655
7308
  }
7656
- return this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData).pipe(switchMap((trackerAllowsRequest) => {
7309
+ return this.checkTrackerAllowsRequest(this.databaseOptions.table, 'STREAM', this.resolvePath(effectiveParams), options, storeData).pipe(switchMap((trackerAllowsRequest) => {
7657
7310
  if (!trackerAllowsRequest) {
7658
7311
  return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7659
7312
  if (Array.isArray(dbData) && dbData.length > 0) {
@@ -7802,6 +7455,17 @@ class HTTPManagerStateService extends ComponentStore {
7802
7455
  this.databaseOptions = (this.hasDatabase) ? adapted : undefined;
7803
7456
  // Trigger database table creation if table is configured
7804
7457
  if (this.hasDatabase && this.databaseOptions?.table) {
7458
+ this.localStorageManagerService.createStore({
7459
+ name: this.databaseOptions.table,
7460
+ data: {
7461
+ ...this.databaseOptions,
7462
+ expires: this.utils.expires(this.databaseOptions.expiresIn),
7463
+ },
7464
+ options: SettingOptions.adapt({
7465
+ storage: StorageType.GLOBAL,
7466
+ encrypted: false,
7467
+ })
7468
+ });
7805
7469
  console.log('[setApiRequestOptions] Initializing database storage for table:', this.databaseOptions.table);
7806
7470
  // Use initDBStorageAsync directly instead of the effect - effects need subscription to run
7807
7471
  this.initDBStorageAsync().subscribe({
@@ -8011,11 +7675,7 @@ class HTTPManagerStateService extends ComponentStore {
8011
7675
  name: tableName,
8012
7676
  data: { ...(storeData || {}), ...this.databaseOptions, expires: this.utils.expires(this.databaseOptions.expiresIn) }
8013
7677
  });
8014
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
8015
- const schemaChanged = !!storedSchemaSignature && storedSchemaSignature !== schemaSignature;
8016
- const ensureTable$ = schemaChanged
8017
- ? this.dbManagerService.clearTable(tableName).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)))
8018
- : this.dbManagerService.createDatabaseTable(tableDef);
7678
+ const ensureTable$ = this.dbManagerService.createDatabaseTable(tableDef);
8019
7679
  return ensureTable$.pipe(switchMap((created) => {
8020
7680
  if (!created) {
8021
7681
  console.warn('[DB STORAGE] Stream table create/open not ready, skipping DB write for this chunk:', { table: tableName });
@@ -8320,9 +7980,16 @@ class HTTPManagerStateService extends ComponentStore {
8320
7980
  if (!this.hasDatabase || !this.databaseOptions?.table)
8321
7981
  return;
8322
7982
  const tableName = this.databaseOptions.table;
7983
+ // Reset localStorage tracking SYNCHRONOUSLY before the async DB clear so any
7984
+ // concurrent request that fires during clearTable() sees a clean store immediately
7985
+ // and is not blocked by stale queryParams from the previous session.
7986
+ this.localStorageManagerService.updateStore({
7987
+ name: tableName,
7988
+ data: { ...this.databaseOptions, expires: 0 }
7989
+ });
7990
+ this._requestCachePaths.delete(tableName);
8323
7991
  this.dbManagerService.clearTable(tableName).subscribe({
8324
7992
  next: () => {
8325
- this.clearRequestCacheMetadata(tableName);
8326
7993
  if (this.dataType === DataType.ARRAY) {
8327
7994
  this.setData$([]);
8328
7995
  }
@@ -8443,18 +8110,21 @@ class HTTPManagerStateService extends ComponentStore {
8443
8110
  this._requestCachePaths.set(tableName, this.resolvePath(options?.path).filter(p => typeof p === 'string' || typeof p === 'number').map(String));
8444
8111
  this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8445
8112
  const currentCache = storeData?.requestCache || {};
8113
+ const currentEntry = currentCache[type] || {};
8446
8114
  this.localStorageManagerService.updateStore({
8447
8115
  name: tableName,
8448
8116
  data: {
8449
8117
  ...(storeData || {}),
8450
- schemaSignature: schemaSignature || storeData?.schemaSignature || null,
8451
8118
  requestCache: {
8452
8119
  ...currentCache,
8453
8120
  [type]: {
8121
+ ...currentEntry,
8454
8122
  signature,
8455
8123
  savedAt: Date.now(),
8456
8124
  path: this.resolvePath(options?.path),
8457
- headers: this.filterHeaders(options?.headers)
8125
+ headers: this.filterHeaders(options?.headers),
8126
+ queryParams: currentEntry.queryParams,
8127
+ queryParamsExpires: currentEntry.queryParamsExpires,
8458
8128
  }
8459
8129
  }
8460
8130
  }
@@ -8474,7 +8144,7 @@ class HTTPManagerStateService extends ComponentStore {
8474
8144
  }
8475
8145
  }
8476
8146
  if (Array.isArray(pathArray) && pathArray.length > 0) {
8477
- this.queryParamsTrackerService.clearTrackingForPath(pathArray.join('/'));
8147
+ // this.queryParamsTrackerService.clearTrackingForPath(pathArray.join('/'))
8478
8148
  this._requestCachePaths.delete(tableName);
8479
8149
  }
8480
8150
  if (!storeData)
@@ -8488,23 +8158,6 @@ class HTTPManagerStateService extends ComponentStore {
8488
8158
  });
8489
8159
  })).subscribe();
8490
8160
  }
8491
- getTrackerState(storeData) {
8492
- if (storeData?.tracker && typeof storeData.tracker === 'object') {
8493
- return {
8494
- consumedValuesByKey: storeData.tracker.consumedValuesByKey || {},
8495
- trackingExpires: storeData.tracker.trackingExpires ?? null,
8496
- };
8497
- }
8498
- return { consumedValuesByKey: {}, trackingExpires: null };
8499
- }
8500
- saveTrackerState(tableName, consumedValuesByKey, trackingExpires) {
8501
- this.localStorageManagerService.store$(tableName).pipe(take(1), tap((storeData) => {
8502
- this.localStorageManagerService.updateStore({
8503
- name: tableName,
8504
- data: { ...(storeData || {}), tracker: { consumedValuesByKey, trackingExpires } }
8505
- });
8506
- })).subscribe();
8507
- }
8508
8161
  trackerNormalizePath(path) {
8509
8162
  const pathSegments = [];
8510
8163
  const query = {};
@@ -8581,14 +8234,12 @@ class HTTPManagerStateService extends ComponentStore {
8581
8234
  return String(value).trim().toLowerCase();
8582
8235
  }
8583
8236
  trackerFilterQuery(query, ignoreQueryParams) {
8584
- if (!Array.isArray(ignoreQueryParams))
8585
- return { ...query };
8586
- if (ignoreQueryParams.length === 0)
8237
+ if (!Array.isArray(ignoreQueryParams) || ignoreQueryParams.length === 0)
8587
8238
  return { ...query };
8239
+ const normalizedIgnore = ignoreQueryParams.map(p => this.trackerNormalizeParamKey(p));
8588
8240
  const result = {};
8589
- ignoreQueryParams.forEach((param) => {
8590
- const key = this.trackerNormalizeParamKey(param);
8591
- if (Object.prototype.hasOwnProperty.call(query, key)) {
8241
+ Object.keys(query).forEach((key) => {
8242
+ if (!normalizedIgnore.includes(key)) {
8592
8243
  result[key] = query[key];
8593
8244
  }
8594
8245
  });
@@ -8610,45 +8261,99 @@ class HTTPManagerStateService extends ComponentStore {
8610
8261
  return null;
8611
8262
  return Math.floor(Date.now() / 1000) + Number(parsed[1]) * seconds;
8612
8263
  }
8613
- checkTrackerAllowsRequest(tableName, path, options, storeData) {
8614
- if (options && 'watchParams' in options) {
8615
- return of(this.queryParamsTrackerService.checkRequestOptions(path, {
8616
- watchParams: options.watchParams,
8617
- watchExpiresAt: options?.watchExpiresAt,
8618
- }));
8619
- }
8264
+ checkTrackerAllowsRequest(tableName, type, path, options, storeData) {
8620
8265
  const normalized = this.trackerNormalizePath(path);
8621
8266
  const ignoreQueryParams = Array.isArray(options?.ignoreQueryParams) ? options.ignoreQueryParams : [];
8622
8267
  if (!normalized.hasQuery) {
8623
- const meta = this.getRequestCacheMetadata(storeData, 'GET');
8268
+ const meta = this.getRequestCacheMetadata(storeData, type);
8269
+ if (!meta) {
8270
+ // No prior cache entry — record that we're tracking this request
8271
+ this.setCachedRequestSignature(tableName, type, '');
8272
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((s) => {
8273
+ const currentCache = s?.requestCache || {};
8274
+ this.localStorageManagerService.updateStore({
8275
+ name: tableName,
8276
+ data: {
8277
+ ...(s || {}),
8278
+ requestCache: {
8279
+ ...currentCache,
8280
+ [type]: {
8281
+ ...(currentCache[type] || {}),
8282
+ active: true,
8283
+ }
8284
+ }
8285
+ }
8286
+ });
8287
+ })).subscribe();
8288
+ }
8624
8289
  return of(!meta);
8625
8290
  }
8626
8291
  const filtered = this.trackerFilterQuery(normalized.query, ignoreQueryParams);
8627
8292
  const keys = Object.keys(filtered);
8628
8293
  if (keys.length === 0) {
8629
- return of(!this.getRequestCacheMetadata(storeData, 'GET'));
8294
+ // All query params were filtered out — check if we have any prior entry
8295
+ const meta = this.getRequestCacheMetadata(storeData, type);
8296
+ if (!meta) {
8297
+ this.setCachedRequestSignature(tableName, type, '');
8298
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((s) => {
8299
+ const currentCache = s?.requestCache || {};
8300
+ this.localStorageManagerService.updateStore({
8301
+ name: tableName,
8302
+ data: {
8303
+ ...(s || {}),
8304
+ requestCache: {
8305
+ ...currentCache,
8306
+ [type]: {
8307
+ ...(currentCache[type] || {}),
8308
+ active: true,
8309
+ }
8310
+ }
8311
+ }
8312
+ });
8313
+ })).subscribe();
8314
+ }
8315
+ return of(!meta);
8630
8316
  }
8631
- const tracker = this.getTrackerState(storeData);
8317
+ const meta = this.getRequestCacheMetadata(storeData, type) || {};
8632
8318
  const now = Math.floor(Date.now() / 1000);
8633
- if (tracker.trackingExpires && tracker.trackingExpires <= now) {
8634
- tracker.consumedValuesByKey = {};
8635
- tracker.trackingExpires = null;
8319
+ let queryParams = { ...(meta.queryParams || {}) };
8320
+ let queryParamsExpires = meta.queryParamsExpires ?? null;
8321
+ if (queryParamsExpires !== null && queryParamsExpires <= now) {
8322
+ queryParams = {};
8323
+ queryParamsExpires = null;
8636
8324
  }
8637
8325
  let accepted = false;
8638
8326
  keys.forEach(key => {
8639
8327
  const value = filtered[key];
8640
- const consumed = tracker.consumedValuesByKey[key] || [];
8328
+ const consumed = queryParams[key] || [];
8641
8329
  if (!consumed.includes(value)) {
8642
- tracker.consumedValuesByKey[key] = [...consumed, value];
8330
+ queryParams[key] = [...consumed, value];
8643
8331
  accepted = true;
8644
8332
  }
8645
8333
  });
8646
8334
  if (accepted) {
8647
8335
  const newExpiry = this.trackerBuildExpiryEpoch(options?.queryParamsExpiresIn);
8648
8336
  if (newExpiry !== null) {
8649
- tracker.trackingExpires = newExpiry;
8650
- }
8651
- this.saveTrackerState(tableName, tracker.consumedValuesByKey, tracker.trackingExpires);
8337
+ queryParamsExpires = newExpiry;
8338
+ }
8339
+ this.localStorageManagerService.store$(tableName).pipe(take(1), tap((latestStoreData) => {
8340
+ const currentCache = latestStoreData?.requestCache || {};
8341
+ const currentEntry = currentCache[type] || {};
8342
+ this.localStorageManagerService.updateStore({
8343
+ name: tableName,
8344
+ data: {
8345
+ ...(latestStoreData || {}),
8346
+ requestCache: {
8347
+ ...currentCache,
8348
+ [type]: {
8349
+ ...currentEntry,
8350
+ queryParams,
8351
+ queryParamsExpires,
8352
+ }
8353
+ }
8354
+ }
8355
+ });
8356
+ })).subscribe();
8652
8357
  }
8653
8358
  return of(accepted);
8654
8359
  }
@@ -8970,6 +8675,673 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8970
8675
  }]
8971
8676
  }], ctorParameters: () => [] });
8972
8677
 
8678
+ class SqlParseError extends Error {
8679
+ constructor(message) {
8680
+ super(message);
8681
+ this.name = 'SqlParseError';
8682
+ }
8683
+ }
8684
+ class SqlValidationError extends Error {
8685
+ constructor(message) {
8686
+ super(message);
8687
+ this.name = 'SqlValidationError';
8688
+ }
8689
+ }
8690
+
8691
+ class SqlParser {
8692
+ constructor() {
8693
+ this._parser = new Parser();
8694
+ }
8695
+ parse(sql) {
8696
+ let result;
8697
+ try {
8698
+ result = this._parser.parse(sql);
8699
+ }
8700
+ catch (e) {
8701
+ throw new SqlParseError(e.message ?? 'Failed to parse SQL');
8702
+ }
8703
+ const ast = Array.isArray(result.ast) ? result.ast[0] : result.ast;
8704
+ if (!ast) {
8705
+ throw new SqlParseError('SQL produced no AST — only SELECT statements are supported');
8706
+ }
8707
+ if (ast.type !== 'select') {
8708
+ throw new SqlParseError('Only SELECT statements are supported');
8709
+ }
8710
+ return {
8711
+ ast,
8712
+ tableList: result.tableList ?? [],
8713
+ columnList: result.columnList ?? [],
8714
+ };
8715
+ }
8716
+ }
8717
+
8718
+ class SchemaValidator {
8719
+ constructor() {
8720
+ this._dbManager = inject(DatabaseManagerService);
8721
+ }
8722
+ async validate(parsed, options = {}) {
8723
+ const { ast } = parsed;
8724
+ const fromItems = ast.from ?? [];
8725
+ // Build alias map and collect real table names
8726
+ const aliases = {};
8727
+ const tableNames = [];
8728
+ for (const item of fromItems) {
8729
+ if (item.table) {
8730
+ tableNames.push(item.table);
8731
+ aliases[item.as ?? item.table] = item.table;
8732
+ aliases[item.table] = item.table;
8733
+ }
8734
+ }
8735
+ if (tableNames.length === 0) {
8736
+ throw new SqlValidationError('No tables found in query');
8737
+ }
8738
+ // Fetch and parse schema for each table
8739
+ const schemas = {};
8740
+ for (const tableName of tableNames) {
8741
+ const schemaArr = await firstValueFrom(this._dbManager.getDatabaseTableSchema(tableName));
8742
+ if (!schemaArr || schemaArr.length === 0) {
8743
+ throw new SqlValidationError(`Table not found: "${tableName}"`);
8744
+ }
8745
+ schemas[tableName] = this._parseSchemaArray(schemaArr);
8746
+ }
8747
+ const defaultTable = tableNames[0];
8748
+ // Validate WHERE columns
8749
+ if (ast.where) {
8750
+ this._validateWhereNode(ast.where, defaultTable, aliases, schemas);
8751
+ }
8752
+ // Validate ORDER BY columns
8753
+ for (const ob of (ast.orderby ?? [])) {
8754
+ if (ob.expr?.type === 'column_ref') {
8755
+ const tbl = this._resolveTable(ob.expr.table, defaultTable, aliases);
8756
+ this._assertIndexed(ob.expr.column, tbl, schemas);
8757
+ }
8758
+ }
8759
+ // Validate JOIN ON columns
8760
+ for (const item of fromItems) {
8761
+ if (item.join && item.on) {
8762
+ this._validateJoinOn(item.on, tableNames, aliases, schemas);
8763
+ }
8764
+ }
8765
+ // Strict mode: AND without compound index → throw
8766
+ if (options.strict && ast.where) {
8767
+ this._checkStrictAnd(ast.where, defaultTable, aliases, schemas);
8768
+ }
8769
+ return { ast, schemas, aliases };
8770
+ }
8771
+ // ─── Helpers ─────────────────────────────────────────────────────────────
8772
+ _parseSchemaArray(schemaArr) {
8773
+ const pk = schemaArr[0] ?? '';
8774
+ const simpleIndexes = [];
8775
+ const compoundIndexes = [];
8776
+ for (let i = 1; i < schemaArr.length; i++) {
8777
+ const s = schemaArr[i];
8778
+ if (s.startsWith('[') && s.endsWith(']')) {
8779
+ compoundIndexes.push(s.slice(1, -1).split('+'));
8780
+ }
8781
+ else {
8782
+ simpleIndexes.push(s.replace(/^[*&]/, ''));
8783
+ }
8784
+ }
8785
+ return { pk, simpleIndexes, compoundIndexes };
8786
+ }
8787
+ _isIndexed(col, tableName, schemas) {
8788
+ const schema = schemas[tableName];
8789
+ if (!schema)
8790
+ return false;
8791
+ return col === schema.pk || schema.simpleIndexes.includes(col);
8792
+ }
8793
+ _assertIndexed(col, tableName, schemas) {
8794
+ if (!this._isIndexed(col, tableName, schemas)) {
8795
+ throw new SqlValidationError(`Column "${col}" on table "${tableName}" is not indexed`);
8796
+ }
8797
+ }
8798
+ _resolveTable(tableRef, defaultTable, aliases) {
8799
+ if (!tableRef)
8800
+ return defaultTable;
8801
+ return aliases[tableRef] ?? tableRef;
8802
+ }
8803
+ _validateWhereNode(node, defaultTable, aliases, schemas) {
8804
+ if (!node || node.type !== 'binary_expr')
8805
+ return;
8806
+ if (node.operator === 'AND' || node.operator === 'OR') {
8807
+ this._validateWhereNode(node.left, defaultTable, aliases, schemas);
8808
+ this._validateWhereNode(node.right, defaultTable, aliases, schemas);
8809
+ return;
8810
+ }
8811
+ if (node.left?.type === 'column_ref') {
8812
+ const tbl = this._resolveTable(node.left.table, defaultTable, aliases);
8813
+ this._assertIndexed(node.left.column, tbl, schemas);
8814
+ }
8815
+ }
8816
+ _validateJoinOn(onNode, tableNames, aliases, schemas) {
8817
+ if (!onNode || onNode.type !== 'binary_expr' || onNode.operator !== '=')
8818
+ return;
8819
+ const l = onNode.left;
8820
+ const r = onNode.right;
8821
+ if (l?.type !== 'column_ref' || r?.type !== 'column_ref')
8822
+ return;
8823
+ const leftTable = tableNames[0];
8824
+ const rightTable = tableNames[tableNames.length - 1];
8825
+ // Resolve which ON side belongs to left vs right table
8826
+ const lTableResolved = aliases[l.table ?? ''] ?? l.table ?? leftTable;
8827
+ const rTableResolved = aliases[r.table ?? ''] ?? r.table ?? rightTable;
8828
+ const leftKey = lTableResolved === leftTable ? { col: l.column, table: lTableResolved } : { col: r.column, table: rTableResolved };
8829
+ const rightKey = lTableResolved === leftTable ? { col: r.column, table: rTableResolved } : { col: l.column, table: lTableResolved };
8830
+ // Left-side key: PK is exempt, otherwise must be indexed
8831
+ const leftSchema = schemas[leftKey.table];
8832
+ if (leftSchema && leftKey.col !== leftSchema.pk && !leftSchema.simpleIndexes.includes(leftKey.col)) {
8833
+ throw new SqlValidationError(`JOIN column "${leftKey.col}" on table "${leftKey.table}" must be indexed`);
8834
+ }
8835
+ // Right-side key: must be indexed (including PK counts)
8836
+ if (!this._isIndexed(rightKey.col, rightKey.table, schemas)) {
8837
+ throw new SqlValidationError(`JOIN column "${rightKey.col}" on table "${rightKey.table}" must be indexed`);
8838
+ }
8839
+ }
8840
+ _checkStrictAnd(whereNode, defaultTable, aliases, schemas) {
8841
+ if (!whereNode || whereNode.type !== 'binary_expr' || whereNode.operator !== 'AND')
8842
+ return;
8843
+ const leafCols = this._flattenAndCols(whereNode, defaultTable, aliases);
8844
+ if (leafCols.length < 2)
8845
+ return;
8846
+ const tableName = leafCols[0].table;
8847
+ const schema = schemas[tableName];
8848
+ if (!schema)
8849
+ return;
8850
+ const colNames = leafCols.map(c => c.col);
8851
+ const hasCompound = schema.compoundIndexes.some(ci => ci.length === colNames.length && colNames.every(c => ci.includes(c)));
8852
+ if (!hasCompound) {
8853
+ throw new SqlValidationError(`Strict mode: compound index required for AND on columns [${colNames.join(', ')}]`);
8854
+ }
8855
+ }
8856
+ _flattenAndCols(node, defaultTable, aliases) {
8857
+ if (!node || node.type !== 'binary_expr')
8858
+ return [];
8859
+ if (node.operator === 'AND') {
8860
+ return [
8861
+ ...this._flattenAndCols(node.left, defaultTable, aliases),
8862
+ ...this._flattenAndCols(node.right, defaultTable, aliases),
8863
+ ];
8864
+ }
8865
+ if (node.left?.type === 'column_ref') {
8866
+ return [{ table: this._resolveTable(node.left.table, defaultTable, aliases), col: node.left.column }];
8867
+ }
8868
+ return [];
8869
+ }
8870
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SchemaValidator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8871
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SchemaValidator, providedIn: 'root' }); }
8872
+ }
8873
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SchemaValidator, decorators: [{
8874
+ type: Injectable,
8875
+ args: [{ providedIn: 'root' }]
8876
+ }] });
8877
+
8878
+ var ExecutionPlanType;
8879
+ (function (ExecutionPlanType) {
8880
+ ExecutionPlanType["SIMPLE"] = "SIMPLE";
8881
+ ExecutionPlanType["AND_BOUNDED"] = "AND_BOUNDED";
8882
+ ExecutionPlanType["AND_COMPOUND"] = "AND_COMPOUND";
8883
+ ExecutionPlanType["OR_MERGE"] = "OR_MERGE";
8884
+ ExecutionPlanType["JOIN_HASH"] = "JOIN_HASH";
8885
+ })(ExecutionPlanType || (ExecutionPlanType = {}));
8886
+
8887
+ class QueryPlanner {
8888
+ plan(validated, options = {}) {
8889
+ const { ast, schemas, aliases } = validated;
8890
+ const fromItems = ast.from ?? [];
8891
+ const mainTable = fromItems[0]?.table ?? '';
8892
+ const dedupeKey = schemas[mainTable]?.pk ?? 'id';
8893
+ const basePlan = this._buildBasePlanExtras(ast, mainTable, dedupeKey);
8894
+ // ── JOIN ────────────────────────────────────────────────────────────────
8895
+ const joinItem = fromItems.find((f) => f.join);
8896
+ if (joinItem) {
8897
+ return this._buildJoinPlan(ast, fromItems, joinItem, schemas, aliases, mainTable, basePlan);
8898
+ }
8899
+ const where = ast.where;
8900
+ // ── No WHERE ─────────────────────────────────────────────────────────────
8901
+ if (!where) {
8902
+ return { ...basePlan, type: ExecutionPlanType.SIMPLE, primaryStep: null, boundedFilters: [] };
8903
+ }
8904
+ // ── OR ──────────────────────────────────────────────────────────────────
8905
+ if (where.operator === 'OR') {
8906
+ return this._buildOrPlan(where, basePlan, aliases, mainTable);
8907
+ }
8908
+ // ── AND ──────────────────────────────────────────────────────────────────
8909
+ if (where.operator === 'AND') {
8910
+ return this._buildAndPlan(where, basePlan, schemas, aliases, mainTable, options);
8911
+ }
8912
+ // ── Single condition ────────────────────────────────────────────────────
8913
+ const step = this._nodeToStep(where, aliases, mainTable);
8914
+ return { ...basePlan, type: ExecutionPlanType.SIMPLE, primaryStep: step, boundedFilters: [] };
8915
+ }
8916
+ // ─── AND ──────────────────────────────────────────────────────────────────
8917
+ _buildAndPlan(where, basePlan, schemas, aliases, mainTable, _options) {
8918
+ const conditions = this._flattenAnd(where);
8919
+ const cols = conditions.map((c) => c.left?.column).filter(Boolean);
8920
+ const schema = schemas[mainTable];
8921
+ // Check for matching compound index
8922
+ const matchedCompound = schema?.compoundIndexes.find(ci => ci.length === cols.length && cols.every(c => ci.includes(c)));
8923
+ if (matchedCompound) {
8924
+ const orderedValues = matchedCompound.map(ciCol => {
8925
+ const cond = conditions.find((c) => c.left?.column === ciCol);
8926
+ return this._extractValue(cond.right, cond.operator);
8927
+ });
8928
+ return {
8929
+ ...basePlan,
8930
+ type: ExecutionPlanType.AND_COMPOUND,
8931
+ primaryStep: null,
8932
+ boundedFilters: [],
8933
+ compoundIndex: `[${matchedCompound.join('+')}]`,
8934
+ compoundValues: orderedValues,
8935
+ };
8936
+ }
8937
+ // Pragmatic: first condition → Dexie, rest → JS filter
8938
+ const primaryStep = this._nodeToStep(conditions[0], aliases, mainTable);
8939
+ const boundedFilters = conditions.slice(1).map((c) => this._nodeToStep(c, aliases, mainTable));
8940
+ return {
8941
+ ...basePlan,
8942
+ type: ExecutionPlanType.AND_BOUNDED,
8943
+ primaryStep,
8944
+ boundedFilters,
8945
+ compoundWarnCols: cols,
8946
+ };
8947
+ }
8948
+ // ─── OR ───────────────────────────────────────────────────────────────────
8949
+ _buildOrPlan(where, basePlan, aliases, mainTable) {
8950
+ const branches = this._flattenOr(where);
8951
+ const orBranches = branches.map((b) => this._nodeToStep(b, aliases, mainTable));
8952
+ return {
8953
+ ...basePlan,
8954
+ type: ExecutionPlanType.OR_MERGE,
8955
+ primaryStep: null,
8956
+ boundedFilters: [],
8957
+ orBranches,
8958
+ };
8959
+ }
8960
+ // ─── JOIN ─────────────────────────────────────────────────────────────────
8961
+ _buildJoinPlan(ast, fromItems, joinItem, schemas, aliases, mainTable, basePlan) {
8962
+ const rightTable = joinItem.table;
8963
+ const onNode = joinItem.on;
8964
+ const joinType = joinItem.join.toLowerCase().includes('left') ? 'left' : 'inner';
8965
+ // Resolve join keys from ON clause
8966
+ const l = onNode?.left;
8967
+ const r = onNode?.right;
8968
+ const lTableResolved = aliases[l?.table ?? ''] ?? l?.table ?? mainTable;
8969
+ const rTableResolved = aliases[r?.table ?? ''] ?? r?.table ?? rightTable;
8970
+ let leftKey, rightKey;
8971
+ if (lTableResolved === mainTable) {
8972
+ leftKey = l?.column ?? '';
8973
+ rightKey = r?.column ?? '';
8974
+ }
8975
+ else {
8976
+ leftKey = r?.column ?? '';
8977
+ rightKey = l?.column ?? '';
8978
+ }
8979
+ const joinInfo = { leftTable: mainTable, rightTable, leftKey, rightKey, joinType };
8980
+ // WHERE conditions become post-join bounded filters
8981
+ const where = ast.where;
8982
+ const boundedFilters = [];
8983
+ if (where) {
8984
+ this._flattenAnd(where.operator === 'AND' ? where : { operator: 'AND', left: where, right: null })
8985
+ .filter(Boolean)
8986
+ .forEach((c) => boundedFilters.push(this._nodeToStep(c, aliases, mainTable)));
8987
+ }
8988
+ return {
8989
+ ...basePlan,
8990
+ type: ExecutionPlanType.JOIN_HASH,
8991
+ primaryStep: null,
8992
+ boundedFilters: where ? (where.operator === 'AND' ? boundedFilters : [this._nodeToStep(where, aliases, mainTable)]) : [],
8993
+ joinInfo,
8994
+ };
8995
+ }
8996
+ // ─── Base plan extras (ORDER BY, LIMIT, OFFSET, projection, etc.) ─────────
8997
+ _buildBasePlanExtras(ast, mainTable, dedupeKey) {
8998
+ const plan = { table: mainTable, dedupeKey };
8999
+ // ORDER BY
9000
+ const orderby = ast.orderby ?? [];
9001
+ if (orderby.length > 0) {
9002
+ const ob = orderby[0];
9003
+ plan.orderBy = {
9004
+ col: ob.expr?.column ?? '',
9005
+ dir: ob.type?.toUpperCase() === 'DESC' ? 'desc' : 'asc',
9006
+ };
9007
+ }
9008
+ // LIMIT / OFFSET
9009
+ if (ast.limit) {
9010
+ const vals = ast.limit.value ?? [];
9011
+ if (ast.limit.seperator === 'offset' && vals.length === 2) {
9012
+ plan.limit = vals[0].value;
9013
+ plan.offset = vals[1].value;
9014
+ }
9015
+ else if (vals.length === 1) {
9016
+ plan.limit = vals[0].value;
9017
+ }
9018
+ }
9019
+ // Projection
9020
+ const columns = ast.columns;
9021
+ if (columns && columns !== '*') {
9022
+ // COUNT(*)
9023
+ const countCol = columns.find((c) => c.expr?.type === 'aggr_func' && c.expr?.name === 'COUNT');
9024
+ if (countCol) {
9025
+ plan.aggregate = 'count';
9026
+ plan.projection = null;
9027
+ }
9028
+ else {
9029
+ const projected = columns
9030
+ .filter((c) => c.expr?.type === 'column_ref')
9031
+ .map((c) => c.expr.column);
9032
+ plan.projection = projected.length > 0 ? projected : null;
9033
+ }
9034
+ }
9035
+ else {
9036
+ plan.projection = null;
9037
+ }
9038
+ // DISTINCT
9039
+ plan.distinct = ast.distinct === 'DISTINCT';
9040
+ return plan;
9041
+ }
9042
+ // ─── Node → PlanStep ──────────────────────────────────────────────────────
9043
+ _nodeToStep(node, aliases, defaultTable) {
9044
+ if (!node || node.type !== 'binary_expr' || node.left?.type !== 'column_ref') {
9045
+ throw new SqlValidationError(`Unsupported WHERE condition structure`);
9046
+ }
9047
+ const col = node.left.column;
9048
+ const { op, val } = this._mapOperator(node.operator, node.right);
9049
+ return { col, op, val };
9050
+ }
9051
+ _mapOperator(astOp, rightNode) {
9052
+ switch (astOp) {
9053
+ case '=': return { op: 'equals', val: this._extractValue(rightNode, astOp) };
9054
+ case '!=':
9055
+ case '<>': return { op: 'notEqual', val: this._extractValue(rightNode, astOp) };
9056
+ case '>': return { op: 'above', val: this._extractValue(rightNode, astOp) };
9057
+ case '>=': return { op: 'aboveOrEqual', val: this._extractValue(rightNode, astOp) };
9058
+ case '<': return { op: 'below', val: this._extractValue(rightNode, astOp) };
9059
+ case '<=': return { op: 'belowOrEqual', val: this._extractValue(rightNode, astOp) };
9060
+ case 'BETWEEN': {
9061
+ const vals = rightNode?.value ?? [];
9062
+ return { op: 'between', val: [vals[0]?.value, vals[1]?.value] };
9063
+ }
9064
+ case 'IN': return { op: 'anyOf', val: (rightNode?.value ?? []).map((v) => v.value) };
9065
+ case 'NOT IN': return { op: 'noneOf', val: (rightNode?.value ?? []).map((v) => v.value) };
9066
+ case 'LIKE': {
9067
+ const raw = String(rightNode?.value ?? '');
9068
+ if (raw.startsWith('%')) {
9069
+ throw new SqlValidationError('LIKE with leading wildcard is not supported');
9070
+ }
9071
+ if (raw.includes('%')) {
9072
+ const pctIdx = raw.indexOf('%');
9073
+ if (pctIdx !== raw.length - 1) {
9074
+ throw new SqlValidationError('LIKE with leading wildcard is not supported');
9075
+ }
9076
+ return { op: 'startsWith', val: raw.slice(0, -1) };
9077
+ }
9078
+ // No wildcard — treat as equals
9079
+ return { op: 'equals', val: raw };
9080
+ }
9081
+ default:
9082
+ throw new SqlValidationError(`Unsupported SQL operator: "${astOp}"`);
9083
+ }
9084
+ }
9085
+ _extractValue(node, _op) {
9086
+ return node?.value ?? null;
9087
+ }
9088
+ // ─── WHERE tree helpers ───────────────────────────────────────────────────
9089
+ _flattenAnd(node) {
9090
+ if (!node)
9091
+ return [];
9092
+ if (node.type === 'binary_expr' && node.operator === 'AND') {
9093
+ return [...this._flattenAnd(node.left), ...this._flattenAnd(node.right)];
9094
+ }
9095
+ return [node];
9096
+ }
9097
+ _flattenOr(node) {
9098
+ if (!node)
9099
+ return [];
9100
+ if (node.type === 'binary_expr' && node.operator === 'OR') {
9101
+ return [...this._flattenOr(node.left), ...this._flattenOr(node.right)];
9102
+ }
9103
+ return [node];
9104
+ }
9105
+ }
9106
+
9107
+ class DexieQueryExecutor {
9108
+ constructor() {
9109
+ this._dbManager = inject(DatabaseManagerService);
9110
+ }
9111
+ async execute(plan) {
9112
+ switch (plan.type) {
9113
+ case ExecutionPlanType.SIMPLE: return this._executeSimple(plan);
9114
+ case ExecutionPlanType.AND_BOUNDED: return this._executeAndBounded(plan);
9115
+ case ExecutionPlanType.AND_COMPOUND: return this._executeAndCompound(plan);
9116
+ case ExecutionPlanType.OR_MERGE: return this._executeOrMerge(plan);
9117
+ case ExecutionPlanType.JOIN_HASH: return this._executeJoinHash(plan);
9118
+ default:
9119
+ throw new SqlValidationError(`Unknown plan type`);
9120
+ }
9121
+ }
9122
+ // ─── SIMPLE ───────────────────────────────────────────────────────────────
9123
+ async _executeSimple(plan) {
9124
+ const table = await this._getTable(plan.table);
9125
+ let rows;
9126
+ if (!plan.primaryStep) {
9127
+ rows = await table.toArray();
9128
+ }
9129
+ else {
9130
+ rows = await this._applyDexieStep(table, plan.primaryStep);
9131
+ }
9132
+ return this._postProcess(rows, plan);
9133
+ }
9134
+ // ─── AND_BOUNDED ──────────────────────────────────────────────────────────
9135
+ async _executeAndBounded(plan) {
9136
+ const table = await this._getTable(plan.table);
9137
+ if (plan.compoundWarnCols?.length) {
9138
+ console.warn(`[DexieSqlService] Consider adding a compound index [${plan.compoundWarnCols.join('+')}] ` +
9139
+ `to table "${plan.table}" for full index coverage on AND conditions.`);
9140
+ }
9141
+ let rows = plan.primaryStep
9142
+ ? await this._applyDexieStep(table, plan.primaryStep)
9143
+ : await table.toArray();
9144
+ // Apply remaining AND conditions as JS filter
9145
+ for (const f of plan.boundedFilters) {
9146
+ rows = rows.filter(row => this._matchStep(row, f));
9147
+ }
9148
+ return this._postProcess(rows, plan);
9149
+ }
9150
+ // ─── AND_COMPOUND ─────────────────────────────────────────────────────────
9151
+ async _executeAndCompound(plan) {
9152
+ const table = await this._getTable(plan.table);
9153
+ const rows = await table.where(plan.compoundIndex).equals(plan.compoundValues).toArray();
9154
+ return this._postProcess(rows, plan);
9155
+ }
9156
+ // ─── OR_MERGE ─────────────────────────────────────────────────────────────
9157
+ async _executeOrMerge(plan) {
9158
+ const table = await this._getTable(plan.table);
9159
+ const branches = plan.orBranches ?? [];
9160
+ const results = await Promise.all(branches.map(branch => this._applyDexieStep(table, branch)));
9161
+ // Deduplicate by primary key
9162
+ const pk = plan.dedupeKey ?? 'id';
9163
+ const seen = new Map();
9164
+ for (const batch of results) {
9165
+ for (const row of batch) {
9166
+ seen.set(row[pk], row);
9167
+ }
9168
+ }
9169
+ return this._postProcess(Array.from(seen.values()), plan);
9170
+ }
9171
+ // ─── JOIN_HASH ────────────────────────────────────────────────────────────
9172
+ async _executeJoinHash(plan) {
9173
+ const join = plan.joinInfo;
9174
+ const leftTable = await this._getTable(join.leftTable);
9175
+ const rightTable = await this._getTable(join.rightTable);
9176
+ // Fetch left rows (full scan; bounded WHERE filters applied post-join)
9177
+ const leftRows = await leftTable.toArray();
9178
+ // Extract FK values for right-side lookup
9179
+ const fkValues = [...new Set(leftRows.map(r => r[join.leftKey]).filter(v => v != null))];
9180
+ // Fetch right rows indexed by join key
9181
+ const rightRows = fkValues.length > 0
9182
+ ? await rightTable.where(join.rightKey).anyOf(fkValues).toArray()
9183
+ : [];
9184
+ // Build lookup map from rightKey → array of right rows
9185
+ const rightMap = new Map();
9186
+ for (const rr of rightRows) {
9187
+ const k = rr[join.rightKey];
9188
+ if (!rightMap.has(k))
9189
+ rightMap.set(k, []);
9190
+ rightMap.get(k).push(rr);
9191
+ }
9192
+ // Hash join
9193
+ const joined = [];
9194
+ for (const lr of leftRows) {
9195
+ const matches = rightMap.get(lr[join.leftKey]);
9196
+ if (matches?.length) {
9197
+ for (const rr of matches) {
9198
+ joined.push({ ...lr, ...rr });
9199
+ }
9200
+ }
9201
+ else if (join.joinType === 'left') {
9202
+ joined.push({ ...lr });
9203
+ }
9204
+ }
9205
+ // Apply post-join bounded filters (any WHERE conditions from the query)
9206
+ let result = joined;
9207
+ for (const f of plan.boundedFilters) {
9208
+ result = result.filter(row => this._matchStep(row, f));
9209
+ }
9210
+ return this._postProcess(result, plan);
9211
+ }
9212
+ // ─── Post-processing ──────────────────────────────────────────────────────
9213
+ _postProcess(rows, plan) {
9214
+ let result = rows;
9215
+ // ORDER BY
9216
+ if (plan.orderBy) {
9217
+ const { col, dir } = plan.orderBy;
9218
+ result = [...result].sort((a, b) => {
9219
+ const av = a[col];
9220
+ const bv = b[col];
9221
+ if (av == null && bv == null)
9222
+ return 0;
9223
+ if (av == null)
9224
+ return 1;
9225
+ if (bv == null)
9226
+ return -1;
9227
+ const cmp = av < bv ? -1 : av > bv ? 1 : 0;
9228
+ return dir === 'desc' ? -cmp : cmp;
9229
+ });
9230
+ }
9231
+ // LIMIT / OFFSET
9232
+ const offset = plan.offset ?? 0;
9233
+ const limit = plan.limit ?? null;
9234
+ if (offset > 0 || limit != null) {
9235
+ result = result.slice(offset, limit != null ? offset + limit : undefined);
9236
+ }
9237
+ // Projection
9238
+ if (plan.projection && plan.projection.length > 0) {
9239
+ const cols = plan.projection;
9240
+ result = result.map(row => {
9241
+ const projected = {};
9242
+ for (const c of cols)
9243
+ projected[c] = row[c];
9244
+ return projected;
9245
+ });
9246
+ }
9247
+ // DISTINCT
9248
+ if (plan.distinct) {
9249
+ const seen = new Set();
9250
+ result = result.filter(row => {
9251
+ const key = JSON.stringify(row);
9252
+ if (seen.has(key))
9253
+ return false;
9254
+ seen.add(key);
9255
+ return true;
9256
+ });
9257
+ }
9258
+ // COUNT(*)
9259
+ if (plan.aggregate === 'count') {
9260
+ return [{ count: result.length }];
9261
+ }
9262
+ return result;
9263
+ }
9264
+ // ─── Dexie step dispatch ──────────────────────────────────────────────────
9265
+ _applyDexieStep(table, step) {
9266
+ const col = step.col;
9267
+ const val = step.val;
9268
+ switch (step.op) {
9269
+ case 'equals': return table.where(col).equals(val).toArray();
9270
+ case 'notEqual': return table.where(col).notEqual(val).toArray();
9271
+ case 'above': return table.where(col).above(val).toArray();
9272
+ case 'aboveOrEqual': return table.where(col).aboveOrEqual(val).toArray();
9273
+ case 'below': return table.where(col).below(val).toArray();
9274
+ case 'belowOrEqual': return table.where(col).belowOrEqual(val).toArray();
9275
+ case 'between': return table.where(col).between(val[0], val[1], true, true).toArray();
9276
+ case 'anyOf': return table.where(col).anyOf(val).toArray();
9277
+ case 'noneOf': return table.where(col).noneOf(val).toArray();
9278
+ case 'startsWith': return table.where(col).startsWith(val).toArray();
9279
+ default:
9280
+ throw new SqlValidationError(`Unsupported Dexie op: "${step.op}"`);
9281
+ }
9282
+ }
9283
+ // ─── JS row predicate ─────────────────────────────────────────────────────
9284
+ _matchStep(row, step) {
9285
+ const rv = row[step.col];
9286
+ const v = step.val;
9287
+ switch (step.op) {
9288
+ case 'equals': return rv === v;
9289
+ case 'notEqual': return rv !== v;
9290
+ case 'above': return rv > v;
9291
+ case 'aboveOrEqual': return rv >= v;
9292
+ case 'below': return rv < v;
9293
+ case 'belowOrEqual': return rv <= v;
9294
+ case 'between': return rv >= v[0] && rv <= v[1];
9295
+ case 'anyOf': return v.includes(rv);
9296
+ case 'noneOf': return !v.includes(rv);
9297
+ case 'startsWith': return typeof rv === 'string' && rv.startsWith(v);
9298
+ default: return true;
9299
+ }
9300
+ }
9301
+ // ─── Table accessor ───────────────────────────────────────────────────────
9302
+ async _getTable(tableName) {
9303
+ const t = await firstValueFrom(this._dbManager.getDatabaseTable(tableName));
9304
+ if (!t)
9305
+ throw new SqlValidationError(`Table "${tableName}" not found or not accessible`);
9306
+ return t;
9307
+ }
9308
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieQueryExecutor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9309
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieQueryExecutor, providedIn: 'root' }); }
9310
+ }
9311
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieQueryExecutor, decorators: [{
9312
+ type: Injectable,
9313
+ args: [{ providedIn: 'root' }]
9314
+ }] });
9315
+
9316
+ class DexieSqlService {
9317
+ constructor() {
9318
+ this._parser = new SqlParser();
9319
+ this._validator = inject(SchemaValidator);
9320
+ this._planner = new QueryPlanner();
9321
+ this._executor = inject(DexieQueryExecutor);
9322
+ }
9323
+ /**
9324
+ * Execute a MySQL-syntax SQL SELECT string against the DexieJS IndexedDB database.
9325
+ * Returns an Observable that emits the result array and completes, or errors with
9326
+ * SqlParseError / SqlValidationError if the query is invalid.
9327
+ */
9328
+ query(sql, options = {}) {
9329
+ return from(this._run(sql, options));
9330
+ }
9331
+ async _run(sql, options) {
9332
+ const parsed = this._parser.parse(sql);
9333
+ const validated = await this._validator.validate(parsed, options);
9334
+ const plan = this._planner.plan(validated, options);
9335
+ return this._executor.execute(plan);
9336
+ }
9337
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieSqlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9338
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieSqlService, providedIn: 'root' }); }
9339
+ }
9340
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DexieSqlService, decorators: [{
9341
+ type: Injectable,
9342
+ args: [{ providedIn: 'root' }]
9343
+ }] });
9344
+
8973
9345
  // export * from "./database-manager-services/index";
8974
9346
 
8975
9347
  class ErrorDisplaySettings {
@@ -10422,11 +10794,11 @@ class RequestManagerStateDemoComponent {
10422
10794
  this.prompts = [];
10423
10795
  }
10424
10796
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10425
- 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>Ignore 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 style=\"margin-top: .5rem; display: flex; gap: .5rem;\">\n <button mat-stroked-button color=\"warn\" (click)=\"onClearRecords()\">Clear Table</button>\n <button mat-stroked-button color=\"warn\" (click)=\"deleteDatabase()\">Delete Database</button>\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 {{ getRecordLabel(item) | 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" }] }); }
10797
+ 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>Ignore 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 style=\"margin-top: .5rem; display: flex; gap: .5rem;\">\n <button mat-stroked-button color=\"warn\" (click)=\"onClearRecords()\">Clear Table</button>\n <button mat-stroked-button color=\"warn\" (click)=\"deleteDatabase()\">Delete Database</button>\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\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track $index) {\n <mat-option [value]=\"item\">\n {{ getRecordLabel(item) | 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" }] }); }
10426
10798
  }
10427
10799
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerStateDemoComponent, decorators: [{
10428
10800
  type: Component,
10429
- 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>Ignore 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 style=\"margin-top: .5rem; display: flex; gap: .5rem;\">\n <button mat-stroked-button color=\"warn\" (click)=\"onClearRecords()\">Clear Table</button>\n <button mat-stroked-button color=\"warn\" (click)=\"deleteDatabase()\">Delete Database</button>\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 {{ getRecordLabel(item) | 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"] }]
10801
+ 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>Ignore 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 style=\"margin-top: .5rem; display: flex; gap: .5rem;\">\n <button mat-stroked-button color=\"warn\" (click)=\"onClearRecords()\">Clear Table</button>\n <button mat-stroked-button color=\"warn\" (click)=\"deleteDatabase()\">Delete Database</button>\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\">\n <mat-option [value]=\"\">None</mat-option>\n @for (item of data; track $index) {\n <mat-option [value]=\"item\">\n {{ getRecordLabel(item) | 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"] }]
10430
10802
  }], ctorParameters: () => [], propDecorators: { server: [{
10431
10803
  type: Input
10432
10804
  }], adapter: [{
@@ -12157,11 +12529,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12157
12529
  class DatabaseDataDemoComponent {
12158
12530
  constructor() {
12159
12531
  this.db = inject(DatabaseManagerService);
12532
+ this.sql = inject(DexieSqlService);
12160
12533
  this.destroy$ = new Subject();
12161
12534
  this.dataToDisplay = [];
12162
12535
  this.dataSource = new DatabaseDataSource(this.dataToDisplay);
12163
12536
  this.displayedColumns = ['id', 'last_name', 'age', 'amount'];
12164
12537
  this.names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis'];
12538
+ // ── SQL Query section ────────────────────────────────────────────────────
12539
+ this.sqlQuery = 'SELECT * FROM sample_table';
12540
+ this.sqlResults = [];
12541
+ this.sqlResultCols = [];
12542
+ this.sqlError = null;
12543
+ this.sqlLoading = false;
12165
12544
  }
12166
12545
  ngOnDestroy() {
12167
12546
  this.destroy$.next();
@@ -12274,12 +12653,32 @@ class DatabaseDataDemoComponent {
12274
12653
  return this.db.deleteTableRecord(table, id)
12275
12654
  .pipe(tap((data) => console.log('deleteTableRecord:', data)));
12276
12655
  }
12656
+ runSqlQuery() {
12657
+ const q = (this.sqlQuery ?? '').trim();
12658
+ if (!q)
12659
+ return;
12660
+ this.sqlLoading = true;
12661
+ this.sqlError = null;
12662
+ this.sqlResults = [];
12663
+ this.sqlResultCols = [];
12664
+ this.sql.query(q).subscribe({
12665
+ next: rows => {
12666
+ this.sqlResults = rows;
12667
+ this.sqlResultCols = rows.length > 0 ? Object.keys(rows[0]) : [];
12668
+ this.sqlLoading = false;
12669
+ },
12670
+ error: err => {
12671
+ this.sqlError = err?.message ?? String(err);
12672
+ this.sqlLoading = false;
12673
+ }
12674
+ });
12675
+ }
12277
12676
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12278
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatabaseDataDemoComponent, selector: "app-database-data-demo", ngImport: i0, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</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 </div>\n\n</div>\n", styles: [""], dependencies: [{ 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: 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: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] }); }
12677
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatabaseDataDemoComponent, selector: "app-database-data-demo", ngImport: i0, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</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 </div>\n\n <!-- \u2500\u2500 SQL Query Section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-divider style=\"margin: 2rem 0;\"></mat-divider>\n\n <h3 style=\"margin-bottom: 1rem;\">SQL Query with DexieJS</h3>\n\n <div style=\"display: flex; gap: 1rem; align-items: flex-start;\">\n <mat-form-field style=\"flex: 1;\" appearance=\"outline\">\n <mat-label>SQL Query</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"sqlQuery\"\n rows=\"3\"\n placeholder=\"e.g. SELECT * FROM sample_table WHERE age > 20\"\n (keydown.control.enter)=\"runSqlQuery()\"\n ></textarea>\n <mat-hint>Ctrl+Enter to execute</mat-hint>\n </mat-form-field>\n\n <button\n mat-flat-button\n color=\"primary\"\n style=\"margin-top: 4px;\"\n [disabled]=\"sqlLoading || !sqlQuery.trim()\"\n (click)=\"runSqlQuery()\"\n >\n {{ sqlLoading ? 'Running\u2026' : 'Execute' }}\n </button>\n </div>\n\n <!-- Error -->\n <div *ngIf=\"sqlError\" style=\"color: #d32f2f; margin: 0.5rem 0 1rem; font-size: 0.875rem;\">\n <mat-icon style=\"vertical-align: middle; font-size: 16px; height: 16px; width: 16px;\">error</mat-icon>\n {{ sqlError }}\n </div>\n\n <!-- Results table (dynamic columns) -->\n <div *ngIf=\"!sqlLoading && sqlResults.length > 0\" class=\"table-container\" style=\"margin-top: 1rem;\">\n <div>\n {{ sqlResults | json }}\n </div>\n <p style=\"font-size: 0.8rem; color: #666; margin-top: 0.5rem;\">\n {{ sqlResults.length }} row{{ sqlResults.length === 1 ? '' : 's' }} returned\n </p>\n </div>\n\n <!-- Empty state -->\n <p *ngIf=\"!sqlLoading && !sqlError && sqlResults.length === 0 && sqlQuery.trim()\"\n style=\"color: #888; font-size: 0.875rem; margin-top: 0.5rem;\">\n No results to display. Execute a query above.\n </p>\n\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: 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.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { 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: 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.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] }); }
12279
12678
  }
12280
12679
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatabaseDataDemoComponent, decorators: [{
12281
12680
  type: Component,
12282
- args: [{ selector: 'app-database-data-demo', standalone: false, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</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 </div>\n\n</div>\n" }]
12681
+ args: [{ selector: 'app-database-data-demo', standalone: false, template: "<div style=\"padding: 2rem;\">\n\n <h2>\n Database Manager Service Demo\n </h2>\n\n <div style=\"margin-bottom: 1rem; display: flex; gap: 1rem;\">\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"clearAllData()\"\n >\n Clear Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"updateData()\"\n >\n Update Data\n </button>\n <div style=\"flex:1\"></div>\n <button\n mat-stroked-button\n color=\"warn\"\n [disabled]=\"!dataToDisplay.length\"\n (click)=\"removeData()\"\n >\n Remove Data\n </button>\n <button mat-stroked-button\n color=\"primary\"\n (click)=\"addData()\"\n >\n Add Data\n </button>\n </div>\n\n <div class=\"table-container\">\n <table mat-table [dataSource]=\"dataSource\">\n\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef>ID</th>\n <td mat-cell *matCellDef=\"let element\">{{element.id}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"last_name\">\n <th mat-header-cell *matHeaderCellDef>Last Name</th>\n <td mat-cell *matCellDef=\"let element\">{{element.last_name}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"age\">\n <th mat-header-cell *matHeaderCellDef>Age</th>\n <td mat-cell *matCellDef=\"let element\">{{element.age}}</td>\n </ng-container>\n\n <ng-container matColumnDef=\"amount\">\n <th mat-header-cell *matHeaderCellDef>Amount</th>\n <td mat-cell *matCellDef=\"let element\">{{element.amount | currency}}</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 </div>\n\n <!-- \u2500\u2500 SQL Query Section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-divider style=\"margin: 2rem 0;\"></mat-divider>\n\n <h3 style=\"margin-bottom: 1rem;\">SQL Query with DexieJS</h3>\n\n <div style=\"display: flex; gap: 1rem; align-items: flex-start;\">\n <mat-form-field style=\"flex: 1;\" appearance=\"outline\">\n <mat-label>SQL Query</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"sqlQuery\"\n rows=\"3\"\n placeholder=\"e.g. SELECT * FROM sample_table WHERE age > 20\"\n (keydown.control.enter)=\"runSqlQuery()\"\n ></textarea>\n <mat-hint>Ctrl+Enter to execute</mat-hint>\n </mat-form-field>\n\n <button\n mat-flat-button\n color=\"primary\"\n style=\"margin-top: 4px;\"\n [disabled]=\"sqlLoading || !sqlQuery.trim()\"\n (click)=\"runSqlQuery()\"\n >\n {{ sqlLoading ? 'Running\u2026' : 'Execute' }}\n </button>\n </div>\n\n <!-- Error -->\n <div *ngIf=\"sqlError\" style=\"color: #d32f2f; margin: 0.5rem 0 1rem; font-size: 0.875rem;\">\n <mat-icon style=\"vertical-align: middle; font-size: 16px; height: 16px; width: 16px;\">error</mat-icon>\n {{ sqlError }}\n </div>\n\n <!-- Results table (dynamic columns) -->\n <div *ngIf=\"!sqlLoading && sqlResults.length > 0\" class=\"table-container\" style=\"margin-top: 1rem;\">\n <div>\n {{ sqlResults | json }}\n </div>\n <p style=\"font-size: 0.8rem; color: #666; margin-top: 0.5rem;\">\n {{ sqlResults.length }} row{{ sqlResults.length === 1 ? '' : 's' }} returned\n </p>\n </div>\n\n <!-- Empty state -->\n <p *ngIf=\"!sqlLoading && !sqlError && sqlResults.length === 0 && sqlQuery.trim()\"\n style=\"color: #888; font-size: 0.875rem; margin-top: 0.5rem;\">\n No results to display. Execute a query above.\n </p>\n\n</div>\n" }]
12283
12682
  }], ctorParameters: () => [] });
12284
12683
  class DatabaseDataSource extends DataSource {
12285
12684
  constructor(initialData) {
@@ -13285,5 +13684,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
13285
13684
  * Generated bundle index. Do not edit.
13286
13685
  */
13287
13686
 
13288
- 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, ParsingResultModel, 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, StreamConfigModel, StreamEventMetadataModel, StreamEventModel, StreamOutputModel, StreamProgressModel, 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 };
13687
+ export { ApiRequest, AppService, AsymmetricalEncryptionService, BatchOptions, BatchResult, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, DexieSqlService, ErrorDisplaySettings, ExecutionPlanType, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, InvalidFileInfoModel, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsDemoComponent, LocalStorageSignalsManagerService, LoggerService, NormalizedRequestOptionsModel, NotificationMessage, OperationResultModel, ParsingResultModel, PathQueryService, PathTrackerStateModel, PublicMessage, QueryParamsTrackerOptionsModel, QueryTrackerStateModel, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, SqlParseError, SqlValidationError, StateMessage, StateOperationResult, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StoreStateManagerSignalsService, StoreStateSignalsDemoComponent, StreamConfigModel, StreamEventMetadataModel, StreamEventModel, StreamOutputModel, StreamProgressModel, 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 };
13289
13688
  //# sourceMappingURL=http-request-manager.mjs.map