http-request-manager 18.15.11 → 18.15.13

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.
@@ -1065,8 +1065,7 @@ function safeJsonParse(jsonStr, fallback) {
1065
1065
  try {
1066
1066
  return JSON.parse(jsonStr);
1067
1067
  }
1068
- catch (error) {
1069
- console.warn('Failed to parse JSON:', jsonStr.substring(0, 100));
1068
+ catch {
1070
1069
  return fallback || null;
1071
1070
  }
1072
1071
  }
@@ -1078,8 +1077,7 @@ function safeJsonArrayParse(jsonStr, fallback = []) {
1078
1077
  const parsed = JSON.parse(jsonStr);
1079
1078
  return Array.isArray(parsed) ? parsed : fallback;
1080
1079
  }
1081
- catch (error) {
1082
- console.warn('Failed to parse JSON array:', jsonStr.substring(0, 100));
1080
+ catch {
1083
1081
  return fallback;
1084
1082
  }
1085
1083
  }
@@ -6437,7 +6435,10 @@ class QueryParamsTrackerService {
6437
6435
  checkRequestOptions(requestOptions = [], options = {}) {
6438
6436
  this.ensureStateRestored();
6439
6437
  if (!this.ready$.value) {
6440
- return true;
6438
+ // When tracker hasn't restored from localStorage yet, block the request
6439
+ // so cached/state data is used instead of making a redundant API call.
6440
+ // This prevents the first-call-after-refresh race condition.
6441
+ return false;
6441
6442
  }
6442
6443
  const normalized = this.normalizeRequestOptions(requestOptions);
6443
6444
  if (!normalized.pathKey)
@@ -7273,99 +7274,7 @@ class HTTPManagerStateService extends ComponentStore {
7273
7274
  table: this.databaseOptions?.table,
7274
7275
  databaseOptions: this.databaseOptions
7275
7276
  });
7276
- if (this.hasDatabase && this.databaseOptions?.table) {
7277
- return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
7278
- if (!dbExists) {
7279
- const initObs = this.initDBStorageAsync();
7280
- return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7281
- console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7282
- return fetchFromAPI();
7283
- }));
7284
- }
7285
- return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
7286
- if (!tableExists) {
7287
- const initObs = this.initDBStorageAsync();
7288
- return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7289
- console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7290
- return fetchFromAPI();
7291
- }));
7292
- }
7293
- return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7294
- console.log('[CacheDebug] storeData for table:', this.databaseOptions.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires });
7295
- const forceRefresh = !!options?.forceRefresh;
7296
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7297
- const expires = storeData?.expires || 0;
7298
- const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7299
- if (forceRefresh) {
7300
- return fetchFromAPI();
7301
- }
7302
- if (hasExpired) {
7303
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()));
7304
- }
7305
- const expectedSchema = this.buildSchemaFromAdapter();
7306
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7307
- console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature });
7308
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7309
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7310
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7311
- }
7312
- const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7313
- if (trackerAllowsRequest) {
7314
- const normalizedPath = this.trackerNormalizePath(this.resolvePath(effectiveParams));
7315
- if (!normalizedPath.hasQuery) {
7316
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7317
- if (Array.isArray(dbData) && dbData.length > 0) {
7318
- this.setData$(dbData);
7319
- // Save cache metadata for both GET and STREAM request types so
7320
- // tracker/check logic can detect cached responses regardless
7321
- // of whether a subsequent call is a normal GET or a streaming GET.
7322
- const getSignature = requestSignature;
7323
- const streamRequestOptions = { ...(requestOptions || {}), stream: true };
7324
- const streamSignature = this.buildRequestSignature('STREAM', streamRequestOptions, effectiveParams);
7325
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', getSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7326
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'STREAM', streamSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7327
- return of({ data: dbData, fromCache: true });
7328
- }
7329
- return fetchFromAPI();
7330
- }), catchError((error) => {
7331
- const tableName = this.databaseOptions.table;
7332
- console.warn('[DB STORAGE] fallback cache read failed, falling back to API:', { table: tableName, error });
7333
- return fetchFromAPI();
7334
- }));
7335
- }
7336
- return fetchFromAPI();
7337
- }
7338
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7339
- if (Array.isArray(dbData) && dbData.length > 0) {
7340
- this.setData$(dbData);
7341
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', requestSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7342
- return of(dbData);
7343
- }
7344
- const currentStateData = this.get()?.data;
7345
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7346
- return of(currentStateData);
7347
- }
7348
- if (currentStateData &&
7349
- !Array.isArray(currentStateData) &&
7350
- Object.keys(currentStateData).length > 0) {
7351
- return of(currentStateData);
7352
- }
7353
- return of(this.dataType === DataType.ARRAY ? [] : {});
7354
- }), catchError((error) => {
7355
- const tableName = this.databaseOptions.table;
7356
- console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7357
- const schema = this.buildSchemaFromAdapter();
7358
- const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7359
- return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7360
- console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7361
- return fetchFromAPI();
7362
- }));
7363
- }));
7364
- }));
7365
- }));
7366
- }));
7367
- }
7368
- return fetchFromAPI();
7277
+ return this.resolveCacheDecision('GET', requestOptions, effectiveParams, options, fetchFromAPI);
7369
7278
  })));
7370
7279
  // FETCH RECORD
7371
7280
  this.fetchRecord = (options, method) => this.effect(() => of(RequestOptions.adapt(options)).pipe(tap(() => console.log('🔄 fetchRecord effect triggered with path:', options?.path, 'method:', method)), switchMap((options) => {
@@ -7505,62 +7414,29 @@ class HTTPManagerStateService extends ComponentStore {
7505
7414
  const requestOptions = this.updateRequestOptions(options?.headers);
7506
7415
  requestOptions.stream = true;
7507
7416
  const effectiveParams = this.getEffectiveParams(options?.path);
7508
- // console.log('[DEBUG] Making streaming request:', requestOptions)
7509
- if (this.hasDatabase && this.databaseOptions?.table) {
7510
- const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7511
- const fetchStreamFromAPI = () => {
7512
- if (!this.tryBeginInFlightRequest(requestSignature)) {
7513
- const currentStateData = this.get()?.data;
7514
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7515
- return of({ data: currentStateData, fromCache: true });
7516
- }
7517
- if (currentStateData &&
7518
- !Array.isArray(currentStateData) &&
7519
- Object.keys(currentStateData).length > 0) {
7520
- return of({ data: currentStateData, fromCache: true });
7521
- }
7522
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7523
- }
7524
- this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7525
- return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })), finalize(() => this.endInFlightRequest(requestSignature)));
7526
- };
7527
- return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7528
- const forceRefresh = !!options?.forceRefresh;
7529
- const expires = storeData?.expires || 0;
7530
- const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7531
- if (forceRefresh) {
7532
- return fetchStreamFromAPI();
7533
- }
7534
- if (hasExpired) {
7535
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchStreamFromAPI()));
7417
+ const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7418
+ const fetchStreamFromAPI = () => {
7419
+ if (!this.tryBeginInFlightRequest(requestSignature)) {
7420
+ const currentStateData = this.get()?.data;
7421
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7422
+ return of({ data: currentStateData, fromCache: true });
7536
7423
  }
7537
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7538
- const expectedSchema = this.buildSchemaFromAdapter();
7539
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7540
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7541
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7542
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchStreamFromAPI()));
7424
+ if (currentStateData &&
7425
+ !Array.isArray(currentStateData) &&
7426
+ Object.keys(currentStateData).length > 0) {
7427
+ return of({ data: currentStateData, fromCache: true });
7543
7428
  }
7544
- const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7545
- if (!trackerAllowsRequest) {
7546
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7547
- if (Array.isArray(dbData) && dbData.length > 0) {
7548
- return of({ data: dbData, fromCache: true });
7549
- }
7550
- const currentStateData = this.get()?.data;
7551
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7552
- return of({ data: currentStateData, fromCache: true });
7553
- }
7554
- if (currentStateData &&
7555
- !Array.isArray(currentStateData) &&
7556
- Object.keys(currentStateData).length > 0) {
7557
- return of({ data: currentStateData, fromCache: true });
7558
- }
7559
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7560
- }));
7561
- }
7562
- return fetchStreamFromAPI();
7563
- })).pipe(tap((packet) => {
7429
+ return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7430
+ }
7431
+ if (this.hasDatabase && this.databaseOptions?.table) {
7432
+ this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7433
+ }
7434
+ return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })), finalize(() => this.endInFlightRequest(requestSignature)));
7435
+ };
7436
+ // Use shared gating decision for parity with GET
7437
+ const cacheDecision$ = this.resolveCacheDecision('STREAM', requestOptions, effectiveParams, options, fetchStreamFromAPI);
7438
+ if (this.hasDatabase && this.databaseOptions?.table) {
7439
+ return cacheDecision$.pipe(tap((packet) => {
7564
7440
  const res = packet?.data;
7565
7441
  // console.log('[DEBUG] Streaming response received:', res)
7566
7442
  if (res && res.length > 0) {
@@ -7827,6 +7703,129 @@ class HTTPManagerStateService extends ComponentStore {
7827
7703
  });
7828
7704
  return [...updatedData, ...addedData];
7829
7705
  }
7706
+ // --------------------------------------------------------------------------------------------------
7707
+ // SHARED CACHE GATING DECISION
7708
+ // --------------------------------------------------------------------------------------------------
7709
+ /**
7710
+ * Resolves whether a request should use cached/DB data or call the API.
7711
+ * Shared by both fetchRecords (GET) and fetchStream (STREAM) to ensure parity.
7712
+ *
7713
+ * Decision order:
7714
+ * 1. forceRefresh → API
7715
+ * 2. cache expired → clear + API
7716
+ * 3. schema mismatch → clear + rebuild + API
7717
+ * 4. tracker blocks request → DB/state fallback
7718
+ * 5. tracker allows request → API (with optional no-query DB shortcut)
7719
+ *
7720
+ * Returns an observable of the result data.
7721
+ */
7722
+ resolveCacheDecision(requestType, requestOptions, effectiveParams, options, fetchFromAPI) {
7723
+ if (!this.hasDatabase || !this.databaseOptions?.table) {
7724
+ return fetchFromAPI();
7725
+ }
7726
+ const requestSignature = this.buildRequestSignature(requestType, requestOptions, effectiveParams);
7727
+ return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
7728
+ if (!dbExists) {
7729
+ return this.initDBStorageAsync().pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7730
+ console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7731
+ return fetchFromAPI();
7732
+ }));
7733
+ }
7734
+ return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
7735
+ if (!tableExists) {
7736
+ return this.initDBStorageAsync().pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7737
+ console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7738
+ return fetchFromAPI();
7739
+ }));
7740
+ }
7741
+ return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7742
+ console.log('[CacheDebug] storeData for table:', this.databaseOptions.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires });
7743
+ // 1. forceRefresh → API
7744
+ const forceRefresh = !!options?.forceRefresh;
7745
+ if (forceRefresh) {
7746
+ return fetchFromAPI();
7747
+ }
7748
+ // 2. cache expired → clear + API
7749
+ const expires = storeData?.expires || 0;
7750
+ const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7751
+ if (hasExpired) {
7752
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()));
7753
+ }
7754
+ // 3. schema mismatch → clear + rebuild + API
7755
+ const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7756
+ const expectedSchema = this.buildSchemaFromAdapter();
7757
+ const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7758
+ console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature });
7759
+ if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7760
+ const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7761
+ return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7762
+ }
7763
+ // 4 & 5. tracker decision
7764
+ const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7765
+ if (trackerAllowsRequest) {
7766
+ // For no-query paths, try DB first before API
7767
+ const normalizedPath = this.trackerNormalizePath(this.resolvePath(effectiveParams));
7768
+ if (!normalizedPath.hasQuery) {
7769
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7770
+ if (Array.isArray(dbData) && dbData.length > 0) {
7771
+ this.setData$(dbData);
7772
+ // Save cache metadata for both GET and STREAM request types so
7773
+ // tracker/check logic can detect cached responses regardless
7774
+ // of whether a subsequent call is a normal GET or a streaming GET.
7775
+ const getSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
7776
+ const streamRequestOptions = { ...(requestOptions || {}), stream: true };
7777
+ const streamSignature = this.buildRequestSignature('STREAM', streamRequestOptions, effectiveParams);
7778
+ this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', getSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7779
+ this.saveRequestCacheMetadata(this.databaseOptions.table, 'STREAM', streamSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7780
+ return of({ data: dbData, fromCache: true });
7781
+ }
7782
+ return fetchFromAPI();
7783
+ }), catchError((error) => {
7784
+ const tableName = this.databaseOptions.table;
7785
+ console.warn('[DB STORAGE] fallback cache read failed, falling back to API:', { table: tableName, error });
7786
+ return fetchFromAPI();
7787
+ }));
7788
+ }
7789
+ return fetchFromAPI();
7790
+ }
7791
+ // tracker blocks request → DB/state fallback
7792
+ return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7793
+ if (Array.isArray(dbData) && dbData.length > 0) {
7794
+ this.setData$(dbData);
7795
+ // Save cache metadata for both GET and STREAM request types so
7796
+ // tracker/check logic can detect cached responses regardless
7797
+ // of whether a subsequent call is a normal GET or a streaming GET.
7798
+ const getSignature = this.buildRequestSignature('GET', requestOptions, effectiveParams);
7799
+ const streamRequestOptions = { ...(requestOptions || {}), stream: true };
7800
+ const streamSignature = this.buildRequestSignature('STREAM', streamRequestOptions, effectiveParams);
7801
+ this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', getSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7802
+ this.saveRequestCacheMetadata(this.databaseOptions.table, 'STREAM', streamSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7803
+ return of(dbData);
7804
+ }
7805
+ const currentStateData = this.get()?.data;
7806
+ if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7807
+ return of(currentStateData);
7808
+ }
7809
+ if (currentStateData &&
7810
+ !Array.isArray(currentStateData) &&
7811
+ Object.keys(currentStateData).length > 0) {
7812
+ return of(currentStateData);
7813
+ }
7814
+ return of(this.dataType === DataType.ARRAY ? [] : {});
7815
+ }), catchError((error) => {
7816
+ const tableName = this.databaseOptions.table;
7817
+ console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7818
+ const schema = this.buildSchemaFromAdapter();
7819
+ const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7820
+ return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7821
+ console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7822
+ return fetchFromAPI();
7823
+ }));
7824
+ }));
7825
+ }));
7826
+ }));
7827
+ }));
7828
+ }
7830
7829
  initDBStorageAsync() {
7831
7830
  console.log('[initDBStorageAsync] Starting initialization:', {
7832
7831
  dataType: this.dataType,
@@ -8498,7 +8497,7 @@ class HTTPManagerStateService extends ComponentStore {
8498
8497
  return Math.floor(Date.now() / 1000) + Number(parsed[1]) * seconds;
8499
8498
  }
8500
8499
  checkTrackerAllowsRequest(tableName, path, options, storeData) {
8501
- if (options && 'watchParams' in options) {
8500
+ if (options && Array.isArray(options.watchParams)) {
8502
8501
  return this.queryParamsTrackerService.checkRequestOptions(path, {
8503
8502
  watchParams: options.watchParams,
8504
8503
  watchExpiresAt: options?.watchExpiresAt,