http-request-manager 18.15.12 → 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.
@@ -6435,7 +6435,10 @@ class QueryParamsTrackerService {
6435
6435
  checkRequestOptions(requestOptions = [], options = {}) {
6436
6436
  this.ensureStateRestored();
6437
6437
  if (!this.ready$.value) {
6438
- 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;
6439
6442
  }
6440
6443
  const normalized = this.normalizeRequestOptions(requestOptions);
6441
6444
  if (!normalized.pathKey)
@@ -7271,99 +7274,7 @@ class HTTPManagerStateService extends ComponentStore {
7271
7274
  table: this.databaseOptions?.table,
7272
7275
  databaseOptions: this.databaseOptions
7273
7276
  });
7274
- if (this.hasDatabase && this.databaseOptions?.table) {
7275
- return this.dbManagerService.databaseExists().pipe(switchMap((dbExists) => {
7276
- if (!dbExists) {
7277
- const initObs = this.initDBStorageAsync();
7278
- return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7279
- console.warn('[DB STORAGE] initDBStorageAsync failed when DB did not exist, continuing with API:', error);
7280
- return fetchFromAPI();
7281
- }));
7282
- }
7283
- return this.dbManagerService.hasDatabaseTable(this.databaseOptions.table).pipe(switchMap((tableExists) => {
7284
- if (!tableExists) {
7285
- const initObs = this.initDBStorageAsync();
7286
- return initObs.pipe(switchMap(() => fetchFromAPI()), catchError((error) => {
7287
- console.warn('[DB STORAGE] initDBStorageAsync failed when table missing, continuing with API:', error);
7288
- return fetchFromAPI();
7289
- }));
7290
- }
7291
- return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7292
- console.log('[CacheDebug] storeData for table:', this.databaseOptions.table, { storeData, requestCache: storeData?.requestCache, expires: storeData?.expires });
7293
- const forceRefresh = !!options?.forceRefresh;
7294
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7295
- const expires = storeData?.expires || 0;
7296
- const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7297
- if (forceRefresh) {
7298
- return fetchFromAPI();
7299
- }
7300
- if (hasExpired) {
7301
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => fetchFromAPI()));
7302
- }
7303
- const expectedSchema = this.buildSchemaFromAdapter();
7304
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7305
- console.log('[CacheDebug] schema check:', { tableName: this.databaseOptions.table, storedSchemaSignature, expectedSchemaSignature, mismatch: storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature });
7306
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7307
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7308
- return this.dbManagerService.clearTable(this.databaseOptions.table).pipe(switchMap(() => this.dbManagerService.createDatabaseTable(tableDef)), switchMap(() => fetchFromAPI()));
7309
- }
7310
- const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7311
- if (trackerAllowsRequest) {
7312
- const normalizedPath = this.trackerNormalizePath(this.resolvePath(effectiveParams));
7313
- if (!normalizedPath.hasQuery) {
7314
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7315
- if (Array.isArray(dbData) && dbData.length > 0) {
7316
- this.setData$(dbData);
7317
- // Save cache metadata for both GET and STREAM request types so
7318
- // tracker/check logic can detect cached responses regardless
7319
- // of whether a subsequent call is a normal GET or a streaming GET.
7320
- const getSignature = requestSignature;
7321
- const streamRequestOptions = { ...(requestOptions || {}), stream: true };
7322
- const streamSignature = this.buildRequestSignature('STREAM', streamRequestOptions, effectiveParams);
7323
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', getSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7324
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'STREAM', streamSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7325
- return of({ data: dbData, fromCache: true });
7326
- }
7327
- return fetchFromAPI();
7328
- }), catchError((error) => {
7329
- const tableName = this.databaseOptions.table;
7330
- console.warn('[DB STORAGE] fallback cache read failed, falling back to API:', { table: tableName, error });
7331
- return fetchFromAPI();
7332
- }));
7333
- }
7334
- return fetchFromAPI();
7335
- }
7336
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7337
- if (Array.isArray(dbData) && dbData.length > 0) {
7338
- this.setData$(dbData);
7339
- this.saveRequestCacheMetadata(this.databaseOptions.table, 'GET', requestSignature, storedSchemaSignature ?? expectedSchemaSignature ?? undefined, options);
7340
- return of(dbData);
7341
- }
7342
- const currentStateData = this.get()?.data;
7343
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7344
- return of(currentStateData);
7345
- }
7346
- if (currentStateData &&
7347
- !Array.isArray(currentStateData) &&
7348
- Object.keys(currentStateData).length > 0) {
7349
- return of(currentStateData);
7350
- }
7351
- return of(this.dataType === DataType.ARRAY ? [] : {});
7352
- }), catchError((error) => {
7353
- const tableName = this.databaseOptions.table;
7354
- console.warn('[DB STORAGE] getTableRecords failed, recreating table and falling back to API:', { table: tableName, error });
7355
- const schema = this.buildSchemaFromAdapter();
7356
- const tableDef = TableSchemaDef.adapt({ table: tableName, schema });
7357
- return this.dbManagerService.createDatabaseTable(tableDef).pipe(switchMap(() => fetchFromAPI()), catchError((recreateError) => {
7358
- console.error('[DB STORAGE] Failed to recreate table after read error, continuing with API only:', recreateError);
7359
- return fetchFromAPI();
7360
- }));
7361
- }));
7362
- }));
7363
- }));
7364
- }));
7365
- }
7366
- return fetchFromAPI();
7277
+ return this.resolveCacheDecision('GET', requestOptions, effectiveParams, options, fetchFromAPI);
7367
7278
  })));
7368
7279
  // FETCH RECORD
7369
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) => {
@@ -7503,62 +7414,29 @@ class HTTPManagerStateService extends ComponentStore {
7503
7414
  const requestOptions = this.updateRequestOptions(options?.headers);
7504
7415
  requestOptions.stream = true;
7505
7416
  const effectiveParams = this.getEffectiveParams(options?.path);
7506
- // console.log('[DEBUG] Making streaming request:', requestOptions)
7507
- if (this.hasDatabase && this.databaseOptions?.table) {
7508
- const requestSignature = this.buildRequestSignature('STREAM', requestOptions, effectiveParams);
7509
- const fetchStreamFromAPI = () => {
7510
- if (!this.tryBeginInFlightRequest(requestSignature)) {
7511
- const currentStateData = this.get()?.data;
7512
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7513
- return of({ data: currentStateData, fromCache: true });
7514
- }
7515
- if (currentStateData &&
7516
- !Array.isArray(currentStateData) &&
7517
- Object.keys(currentStateData).length > 0) {
7518
- return of({ data: currentStateData, fromCache: true });
7519
- }
7520
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7521
- }
7522
- this.setCachedRequestSignature(this.databaseOptions.table, 'STREAM', requestSignature);
7523
- return this.httpManagerService.getRequest(requestOptions, effectiveParams).pipe(map((apiData) => ({ data: apiData, fromCache: false })), finalize(() => this.endInFlightRequest(requestSignature)));
7524
- };
7525
- return this.localStorageManagerService.store$(this.databaseOptions.table).pipe(take(1), switchMap((storeData) => {
7526
- const forceRefresh = !!options?.forceRefresh;
7527
- const expires = storeData?.expires || 0;
7528
- const hasExpired = expires > 0 && this.utils.hasExpired(expires);
7529
- if (forceRefresh) {
7530
- return fetchStreamFromAPI();
7531
- }
7532
- if (hasExpired) {
7533
- 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 });
7534
7423
  }
7535
- const storedSchemaSignature = this.getStoredSchemaSignature(storeData);
7536
- const expectedSchema = this.buildSchemaFromAdapter();
7537
- const expectedSchemaSignature = this.buildSchemaSignature(expectedSchema);
7538
- if (storedSchemaSignature && storedSchemaSignature !== expectedSchemaSignature) {
7539
- const tableDef = TableSchemaDef.adapt({ table: this.databaseOptions.table, schema: expectedSchema });
7540
- 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 });
7541
7428
  }
7542
- const trackerAllowsRequest = this.checkTrackerAllowsRequest(this.databaseOptions.table, this.resolvePath(effectiveParams), options, storeData);
7543
- if (!trackerAllowsRequest) {
7544
- return this.dbManagerService.getTableRecords(this.databaseOptions.table).pipe(switchMap((dbData) => {
7545
- if (Array.isArray(dbData) && dbData.length > 0) {
7546
- return of({ data: dbData, fromCache: true });
7547
- }
7548
- const currentStateData = this.get()?.data;
7549
- if (Array.isArray(currentStateData) && currentStateData.length > 0) {
7550
- return of({ data: currentStateData, fromCache: true });
7551
- }
7552
- if (currentStateData &&
7553
- !Array.isArray(currentStateData) &&
7554
- Object.keys(currentStateData).length > 0) {
7555
- return of({ data: currentStateData, fromCache: true });
7556
- }
7557
- return of({ data: this.dataType === DataType.ARRAY ? [] : {}, fromCache: true });
7558
- }));
7559
- }
7560
- return fetchStreamFromAPI();
7561
- })).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) => {
7562
7440
  const res = packet?.data;
7563
7441
  // console.log('[DEBUG] Streaming response received:', res)
7564
7442
  if (res && res.length > 0) {
@@ -7825,6 +7703,129 @@ class HTTPManagerStateService extends ComponentStore {
7825
7703
  });
7826
7704
  return [...updatedData, ...addedData];
7827
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
+ }
7828
7829
  initDBStorageAsync() {
7829
7830
  console.log('[initDBStorageAsync] Starting initialization:', {
7830
7831
  dataType: this.dataType,
@@ -8496,7 +8497,7 @@ class HTTPManagerStateService extends ComponentStore {
8496
8497
  return Math.floor(Date.now() / 1000) + Number(parsed[1]) * seconds;
8497
8498
  }
8498
8499
  checkTrackerAllowsRequest(tableName, path, options, storeData) {
8499
- if (options && 'watchParams' in options) {
8500
+ if (options && Array.isArray(options.watchParams)) {
8500
8501
  return this.queryParamsTrackerService.checkRequestOptions(path, {
8501
8502
  watchParams: options.watchParams,
8502
8503
  watchExpiresAt: options?.watchExpiresAt,