@asaidimu/utils-remote-store 1.1.0 → 1.2.0

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.
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { QueryCache, CacheMetrics } from '../cache/index.mjs';
2
- import '../types-DUZGkNEB.js';
2
+ import '../types-ZDD-bdCz.js';
3
3
 
4
4
  interface StoreErrorDetails {
5
5
  code: string;
@@ -205,7 +205,9 @@ interface PagedQueryResult<T> {
205
205
  /** Function to fetch a specific page of data.
206
206
  * @param page The page number to fetch.
207
207
  */
208
- fetch: (page: number) => Promise<void>;
208
+ navigate: (page: number) => Promise<void>;
209
+ refresh: (delay?: number) => Promise<void>;
210
+ changeParams: (setter: (params: any) => any) => Promise<void>;
209
211
  }
210
212
  /**
211
213
  * Represents a basic record in the remote store.
@@ -230,26 +232,6 @@ type PaginationInfo = {
230
232
  * Represents the name of a resource in the store, e.g., 'todos', 'users'.
231
233
  */
232
234
  type ResourceName = string;
233
- /**
234
- * Represents the reactive result of a single record query.
235
- * @template T The type of the data being queried.
236
- */
237
- interface ReactiveQueryResult<T> {
238
- /** The current query result, including data, loading state, and errors. */
239
- value: () => QueryResult<T>;
240
- /** A function to subscribe to changes in the query result. Returns an unsubscribe function. */
241
- onValueChange: (callback: () => void) => () => void;
242
- }
243
- /**
244
- * Represents the reactive result of a paginated query (list or find).
245
- * @template T The type of the records in the page.
246
- */
247
- interface ReactivePagedQueryResult<T> {
248
- /** The current paginated query result, including page data, loading state, and errors. */
249
- value: () => PagedQueryResult<T>;
250
- /** A function to subscribe to changes in the query result. Returns an unsubscribe function. */
251
- onValueChange: (callback: () => void) => () => void;
252
- }
253
235
  /**
254
236
  * Internal interface representing an active subscription to a query.
255
237
  * @template T The type of the data being queried.
@@ -271,283 +253,80 @@ interface QuerySubscription<T> {
271
253
  result: QueryResult<T> | PagedQueryResult<T>;
272
254
  }
273
255
 
256
+ /**
257
+ * Represents the result of a store query with stable React integration.
258
+ * @template T The type of the result data.
259
+ */
260
+ interface StoreResult<T extends Record<string, any> = Record<string, any>, V = QueryResult<T> | PagedQueryResult<T>> {
261
+ /** Function that returns the current query state */
262
+ value: () => V;
263
+ /** Function to subscribe to state changes */
264
+ onValueChange: (callback: () => void) => () => void;
265
+ }
274
266
  /**
275
267
  * A reactive remote store that provides cached and observable access to data
276
268
  * from a `BaseStore`. It handles caching, invalidation, and real-time updates
277
269
  * via server-sent events (SSE).
278
- *
279
- * @template T The type of the records managed by the store, extending Record.
280
- * @template TFindOptions Options for the find operation.
281
- * @template TReadOptions Options for the read operation.
282
- * @template TListOptions Options for the list operation.
283
- * @template TDeleteOptions Options for the delete operation.
284
- * @template TUpdateOptions Options for the update operation.
285
- * @template TCreateOptions Options for the create operation.
286
- * @template TUploadOptions Options for the upload operation.
287
- * @template TStreamOptions Options for the stream operation.
288
270
  */
289
271
  declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<string, unknown>, TReadOptions = Record<string, unknown>, TListOptions = Record<string, unknown>, TDeleteOptions = Record<string, unknown>, TUpdateOptions = Record<string, unknown>, TCreateOptions = Record<string, unknown>, TUploadOptions = Record<string, unknown>, TStreamOptions = Record<string, unknown>> {
290
272
  private cache;
291
273
  private baseStore;
292
274
  private correlator?;
293
275
  private storeEventCorrelator?;
294
- private querySubscriptions;
276
+ private queryStates;
277
+ private stableResults;
278
+ private stablePaginationMethods;
279
+ private errorCache;
295
280
  private keyCache;
296
281
  private unsubscribeFromBaseStore;
297
- /**
298
- * Creates an instance of ReactiveRemoteStore.
299
- * @param cache The QueryCache instance to use for caching data.
300
- * @param baseStore The underlying BaseStore for actual data fetching and mutations.
301
- * @param correlator Optional function to determine which queries to invalidate after a mutation.
302
- * @param storeEventCorrelator Optional function to determine which queries to invalidate after a store event.
303
- */
282
+ private cacheEventCleanups;
304
283
  constructor(cache: QueryCache, baseStore: BaseStore<T, TFindOptions, TReadOptions, TListOptions, TDeleteOptions, TUpdateOptions, TCreateOptions, TUploadOptions, TStreamOptions>, correlator?: Correlator | undefined, storeEventCorrelator?: StoreEventCorrelator | undefined);
305
- /**
306
- * Creates a new query subscription and registers it with the cache.
307
- * @template T The type of the data for the subscription.
308
- * @param queryKey The unique key for the query.
309
- * @param operation The operation type (e.g., 'read', 'list').
310
- * @param params The parameters for the query.
311
- * @param selector A function that returns the current query result.
312
- * @param notificationCondition A function to determine if a cache event should trigger a notification.
313
- * @returns The created QuerySubscription.
314
- */
315
- private createSubscription;
316
- /**
317
- * Retrieves an existing subscription or creates a new one if it doesn't exist.
318
- * @template T The type of the data for the subscription.
319
- * @param queryKey The unique key for the query.
320
- * @param operation The operation type (e.g., 'read', 'list').
321
- * @param params The parameters for the query.
322
- * @param selector A function that returns the current query result.
323
- * @param notificationCondition A function to determine if a cache event should trigger a notification.
324
- * @returns The existing or newly created QuerySubscription.
325
- */
326
- private getOrCreateSubscription;
327
- /**
328
- * Builds a unique cache key for a given operation and its parameters.
329
- * Uses a WeakMap for caching keys of object parameters to avoid re-hashing.
330
- * @param operation The name of the operation (e.g., 'read', 'list').
331
- * @param params The parameters for the operation.
332
- * @returns A unique string key for the operation and parameters.
333
- */
284
+ private setupCacheEventListeners;
285
+ private handleCacheEvent;
334
286
  private buildKey;
335
- /**
336
- * Creates a selector function for a single record query.
337
- * @template T The type of the data being queried.
338
- * @param queryKey The unique key for the query.
339
- * @returns A function that returns the current QueryResult for the given key.
340
- */
341
- private createSelector;
342
- /**
343
- * Reads a single record from the store.
344
- * @param params The read options.
345
- * @returns An object containing the current QueryResult and a function to subscribe to changes.
346
- */
347
- /**
348
- * Retrieves a single record reactively.
349
- *
350
- * This method provides a reactive query result for a single record. The data is fetched from the cache if available,
351
- * otherwise it's fetched from the underlying `baseStore`. The method returns an object containing the current
352
- * query result and a function to subscribe to future updates.
353
- *
354
- * @param params The options for the read operation, used to identify the record.
355
- * @returns A `ReactiveQueryResult` object containing the reactive value and an `onValueChange` subscription function.
356
- */
357
- read(params: TReadOptions): ReactiveQueryResult<T>;
358
- /**
359
- * Creates a selector function for a paginated query (list or find).
360
- * @template TParams The type of the parameters for the query.
361
- * @param baseQueryKey The base unique key for the query.
362
- * @param baseParams The base parameters for the query.
363
- * @param fetchFn The function to call to fetch a page of data.
364
- * @returns A function that returns the current PagedQueryResult.
365
- */
366
- private createPagedSelector;
367
- /**
368
- * Sets up a paginated query (list or find) with caching and reactivity.
369
- * @template TParams The type of the parameters for the query.
370
- * @param type The type of the paginated query ('list' or 'find').
371
- * @param params The parameters for the query.
372
- * @param fetchFn The function to call to fetch a page of data.
373
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
374
- */
287
+ private getOrCreateError;
288
+ private getOrCreateStoreError;
289
+ private computeResult;
290
+ private scheduleBackgroundFetch;
291
+ private getCurrentPageForQuery;
292
+ private getOrCreatePaginationMethods;
293
+ private computeResultForParams;
294
+ hasActiveQuery(operation: string, params: any): boolean;
295
+ getActiveQuery<TResult extends Record<string, unknown> = T>(operation: string, params: any): StoreResult<TResult> | undefined;
296
+ read(params: TReadOptions): StoreResult<T, QueryResult<T>>;
297
+ list(params: TListOptions): StoreResult<T, PagedQueryResult<T>>;
298
+ find(params: TFindOptions): StoreResult<T, PagedQueryResult<T>>;
375
299
  private setupPagedQuery;
376
- /**
377
- * Lists records from the store with pagination and reactivity.
378
- * @param params The list options.
379
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
380
- */
381
- /**
382
- * Retrieves a paginated list of records reactively.
383
- *
384
- * This method provides a reactive query result for a list of records. It supports pagination and automatically
385
- * fetches data from the cache or the `baseStore`. The result includes methods for navigating to the next,
386
- * previous, or a specific page.
387
- *
388
- * @param params The options for the list operation, including pagination details.
389
- * @returns A `ReactivePagedQueryResult` object containing the reactive paged value and an `onValueChange` subscription function.
390
- */
391
- list(params: TListOptions): ReactivePagedQueryResult<T>;
392
- /**
393
- * Finds records from the store with pagination and reactivity.
394
- * @param params The find options.
395
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
396
- */
397
- /**
398
- * Finds and retrieves a paginated list of records reactively based on a query.
399
- *
400
- * Similar to `list`, this method provides a reactive query result for a set of records that match the given
401
- * find options. It supports pagination and provides methods for navigating through the pages.
402
- *
403
- * @param params The options for the find operation, used to query for specific records.
404
- * @returns A `ReactivePagedQueryResult` object containing the reactive paged value and an `onValueChange` subscription function.
405
- */
406
- find(params: TFindOptions): ReactivePagedQueryResult<T>;
407
- /**
408
- * Invalidates relevant queries in the cache based on a mutation operation.
409
- * If a correlator is provided, it will be used to determine which queries to invalidate.
410
- * Otherwise, it invalidates all 'list' and 'find' queries.
411
- * @param mutation An object describing the mutation operation and its parameters.
412
- */
413
300
  private invalidateQueries;
414
- /**
415
- * Handles incoming store events, invalidating relevant queries if a `storeEventCorrelator` is provided.
416
- * @param event The StoreEvent received.
417
- */
418
301
  private handleStoreEvent;
419
- /**
420
- * Creates a new record.
421
- *
422
- * This method sends a request to the `baseStore` to create a new record. Upon successful creation,
423
- * it updates the cache with the new record and invalidates relevant queries to ensure data consistency.
424
- *
425
- * @param params An object containing the data for the new record and optional create options.
426
- * @returns A promise that resolves to the newly created record, or `undefined` if the creation fails.
427
- * @throws An error if the create operation fails.
428
- */
429
302
  create(params: {
430
303
  data: Partial<T>;
431
304
  options?: TCreateOptions;
432
305
  }): Promise<T | undefined>;
433
- /**
434
- * Updates an existing record.
435
- *
436
- * This method sends a request to the `baseStore` to update a record. If the update is successful,
437
- * it updates the cache with the modified record and invalidates relevant queries.
438
- *
439
- * @param params An object containing the ID of the record to update, the partial data, and optional update options.
440
- * @returns A promise that resolves to the updated record, or `undefined` if the update fails.
441
- * @throws An error if the update operation fails.
442
- */
443
306
  update(params: {
444
307
  data: Partial<T>;
445
308
  options?: TUpdateOptions;
446
309
  }): Promise<T | undefined>;
447
- /**
448
- * Deletes a record.
449
- *
450
- * This method sends a request to the `baseStore` to delete a record. Upon successful deletion,
451
- * it removes the record from the cache and invalidates relevant queries.
452
- *
453
- * @param params The options for the delete operation, used to identify the record to be deleted.
454
- * @returns A promise that resolves when the deletion is complete.
455
- * @throws An error if the delete operation fails.
456
- */
457
310
  delete(params: TDeleteOptions): Promise<void>;
458
- /**
459
- * Sends a notification event to the base store.
460
- *
461
- * This can be used to trigger server-side events or other custom actions in the `baseStore`.
462
- *
463
- * @param event The `StoreEvent` to be sent.
464
- * @returns A promise that resolves when the notification has been processed.
465
- */
466
311
  notify(event: StoreEvent): Promise<void>;
467
- /**
468
- * Establishes a real-time data stream.
469
- *
470
- * This method delegates to the `baseStore`'s `stream` method to create a persistent connection for
471
- * receiving real-time updates. The provided callback will be invoked when new data is available.
472
- *
473
- * @param options The options for the stream operation.
474
- * @param onStreamChange A callback function that is executed when the stream's data changes.
475
- * @returns A promise that resolves with a function to close the stream.
476
- */
477
- stream(options: TStreamOptions, onStreamChange: () => void): Promise<{
312
+ stream(options: TStreamOptions, onStreamChange: () => void): {
478
313
  stream: () => AsyncIterable<T>;
479
314
  cancel: () => void;
480
315
  status: () => "active" | "cancelled" | "completed";
481
- }>;
482
- /**
483
- * Uploads a file and creates a new record associated with it.
484
- *
485
- * This method handles file uploads through the `baseStore`. After a successful upload, it updates the cache
486
- * with the new record and invalidates relevant queries.
487
- *
488
- * @param params An object containing the file to upload and optional upload options.
489
- * @returns A promise that resolves to the newly created record, or `undefined` if the upload fails.
490
- * @throws An error if the upload operation fails.
491
- */
316
+ };
317
+ subscribe(scope: string, callback: (event: StoreEvent) => void): Promise<() => void>;
492
318
  upload(params: {
493
319
  file: File;
494
320
  options?: TUploadOptions;
495
321
  }): Promise<T | undefined>;
496
- /**
497
- * Forces a refresh of a specific query.
498
- *
499
- * This method bypasses the cache's staleness checks and forces a refetch of the data from the `baseStore`.
500
- * The method is overloaded to support `read`, `list`, and `find` operations.
501
- *
502
- * @param operation The type of operation to refresh ('read', 'list', or 'find').
503
- * @param params The parameters for the operation.
504
- * @returns A promise that resolves to the refreshed data.
505
- */
506
322
  refresh(operation: 'read', params: TReadOptions): Promise<T | undefined>;
507
323
  refresh(operation: 'list', params: TListOptions): Promise<Page<T> | undefined>;
508
324
  refresh(operation: 'find', query: TFindOptions): Promise<Page<T> | undefined>;
509
- /**
510
- * Pre-fetches data for a query and caches it.
511
- *
512
- * This method is used to proactively fetch data that is likely to be needed soon. It fetches the data
513
- * and stores it in the cache, so that subsequent requests for the same data can be served instantly.
514
- * The method is overloaded for `read`, `list`, and `find` operations.
515
- *
516
- * @param operation The type of operation to prefetch ('read', 'list', or 'find').
517
- * @param params The parameters for the operation.
518
- */
519
325
  prefetch(operation: 'read', params: TReadOptions): void;
520
326
  prefetch(operation: 'list', params: TListOptions): void;
521
327
  prefetch(operation: 'find', params: TFindOptions): void;
522
- /**
523
- * Invalidates the cached data for a specific query.
524
- *
525
- * This marks the query's data as stale, forcing a refetch the next time it's accessed. This is useful
526
- * when you know the data has changed on the server, but the change was not triggered by a mutation
527
- * through this store.
528
- *
529
- * @param operation The type of operation to invalidate ('read', 'list', or 'find').
530
- * @param params The parameters for the operation.
531
- * @returns A promise that resolves when the invalidation is complete.
532
- */
533
328
  invalidate(operation: string, params: any): Promise<void>;
534
- /**
535
- * Invalidates all active queries in the store.
536
- *
537
- * This method marks all currently active queries as stale, forcing them to be refetched the next time
538
- * they are accessed. This is a more aggressive approach to cache invalidation.
539
- *
540
- * @returns A promise that resolves when all invalidations are complete.
541
- */
542
329
  invalidateAll(): Promise<void>;
543
- /**
544
- * Retrieves statistics about the store and its cache.
545
- *
546
- * This method returns an object containing statistics from the underlying cache, plus the number of
547
- * active subscriptions in the reactive store.
548
- *
549
- * @returns An object with cache statistics and the number of active subscriptions.
550
- */
551
330
  getStats(): {
552
331
  activeSubscriptions: number;
553
332
  size: number;
@@ -564,12 +343,6 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
564
343
  error?: boolean;
565
344
  }>;
566
345
  };
567
- /**
568
- * Cleans up all resources used by the store.
569
- *
570
- * This method should be called when the store is no longer needed. It unsubscribes from all cache events,
571
- * clears all query subscriptions, and unsubscribes from the base store's events.
572
- */
573
346
  destroy(): void;
574
347
  }
575
348
 
@@ -580,4 +353,4 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
580
353
  */
581
354
  declare function hash(data: any): string;
582
355
 
583
- export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, hash };
356
+ export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, type StoreResult, hash };
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { QueryCache, CacheMetrics } from '../cache/index.js';
2
- import '../types-DUZGkNEB.js';
2
+ import '../types-ZDD-bdCz.js';
3
3
 
4
4
  interface StoreErrorDetails {
5
5
  code: string;
@@ -205,7 +205,9 @@ interface PagedQueryResult<T> {
205
205
  /** Function to fetch a specific page of data.
206
206
  * @param page The page number to fetch.
207
207
  */
208
- fetch: (page: number) => Promise<void>;
208
+ navigate: (page: number) => Promise<void>;
209
+ refresh: (delay?: number) => Promise<void>;
210
+ changeParams: (setter: (params: any) => any) => Promise<void>;
209
211
  }
210
212
  /**
211
213
  * Represents a basic record in the remote store.
@@ -230,26 +232,6 @@ type PaginationInfo = {
230
232
  * Represents the name of a resource in the store, e.g., 'todos', 'users'.
231
233
  */
232
234
  type ResourceName = string;
233
- /**
234
- * Represents the reactive result of a single record query.
235
- * @template T The type of the data being queried.
236
- */
237
- interface ReactiveQueryResult<T> {
238
- /** The current query result, including data, loading state, and errors. */
239
- value: () => QueryResult<T>;
240
- /** A function to subscribe to changes in the query result. Returns an unsubscribe function. */
241
- onValueChange: (callback: () => void) => () => void;
242
- }
243
- /**
244
- * Represents the reactive result of a paginated query (list or find).
245
- * @template T The type of the records in the page.
246
- */
247
- interface ReactivePagedQueryResult<T> {
248
- /** The current paginated query result, including page data, loading state, and errors. */
249
- value: () => PagedQueryResult<T>;
250
- /** A function to subscribe to changes in the query result. Returns an unsubscribe function. */
251
- onValueChange: (callback: () => void) => () => void;
252
- }
253
235
  /**
254
236
  * Internal interface representing an active subscription to a query.
255
237
  * @template T The type of the data being queried.
@@ -271,283 +253,80 @@ interface QuerySubscription<T> {
271
253
  result: QueryResult<T> | PagedQueryResult<T>;
272
254
  }
273
255
 
256
+ /**
257
+ * Represents the result of a store query with stable React integration.
258
+ * @template T The type of the result data.
259
+ */
260
+ interface StoreResult<T extends Record<string, any> = Record<string, any>, V = QueryResult<T> | PagedQueryResult<T>> {
261
+ /** Function that returns the current query state */
262
+ value: () => V;
263
+ /** Function to subscribe to state changes */
264
+ onValueChange: (callback: () => void) => () => void;
265
+ }
274
266
  /**
275
267
  * A reactive remote store that provides cached and observable access to data
276
268
  * from a `BaseStore`. It handles caching, invalidation, and real-time updates
277
269
  * via server-sent events (SSE).
278
- *
279
- * @template T The type of the records managed by the store, extending Record.
280
- * @template TFindOptions Options for the find operation.
281
- * @template TReadOptions Options for the read operation.
282
- * @template TListOptions Options for the list operation.
283
- * @template TDeleteOptions Options for the delete operation.
284
- * @template TUpdateOptions Options for the update operation.
285
- * @template TCreateOptions Options for the create operation.
286
- * @template TUploadOptions Options for the upload operation.
287
- * @template TStreamOptions Options for the stream operation.
288
270
  */
289
271
  declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<string, unknown>, TReadOptions = Record<string, unknown>, TListOptions = Record<string, unknown>, TDeleteOptions = Record<string, unknown>, TUpdateOptions = Record<string, unknown>, TCreateOptions = Record<string, unknown>, TUploadOptions = Record<string, unknown>, TStreamOptions = Record<string, unknown>> {
290
272
  private cache;
291
273
  private baseStore;
292
274
  private correlator?;
293
275
  private storeEventCorrelator?;
294
- private querySubscriptions;
276
+ private queryStates;
277
+ private stableResults;
278
+ private stablePaginationMethods;
279
+ private errorCache;
295
280
  private keyCache;
296
281
  private unsubscribeFromBaseStore;
297
- /**
298
- * Creates an instance of ReactiveRemoteStore.
299
- * @param cache The QueryCache instance to use for caching data.
300
- * @param baseStore The underlying BaseStore for actual data fetching and mutations.
301
- * @param correlator Optional function to determine which queries to invalidate after a mutation.
302
- * @param storeEventCorrelator Optional function to determine which queries to invalidate after a store event.
303
- */
282
+ private cacheEventCleanups;
304
283
  constructor(cache: QueryCache, baseStore: BaseStore<T, TFindOptions, TReadOptions, TListOptions, TDeleteOptions, TUpdateOptions, TCreateOptions, TUploadOptions, TStreamOptions>, correlator?: Correlator | undefined, storeEventCorrelator?: StoreEventCorrelator | undefined);
305
- /**
306
- * Creates a new query subscription and registers it with the cache.
307
- * @template T The type of the data for the subscription.
308
- * @param queryKey The unique key for the query.
309
- * @param operation The operation type (e.g., 'read', 'list').
310
- * @param params The parameters for the query.
311
- * @param selector A function that returns the current query result.
312
- * @param notificationCondition A function to determine if a cache event should trigger a notification.
313
- * @returns The created QuerySubscription.
314
- */
315
- private createSubscription;
316
- /**
317
- * Retrieves an existing subscription or creates a new one if it doesn't exist.
318
- * @template T The type of the data for the subscription.
319
- * @param queryKey The unique key for the query.
320
- * @param operation The operation type (e.g., 'read', 'list').
321
- * @param params The parameters for the query.
322
- * @param selector A function that returns the current query result.
323
- * @param notificationCondition A function to determine if a cache event should trigger a notification.
324
- * @returns The existing or newly created QuerySubscription.
325
- */
326
- private getOrCreateSubscription;
327
- /**
328
- * Builds a unique cache key for a given operation and its parameters.
329
- * Uses a WeakMap for caching keys of object parameters to avoid re-hashing.
330
- * @param operation The name of the operation (e.g., 'read', 'list').
331
- * @param params The parameters for the operation.
332
- * @returns A unique string key for the operation and parameters.
333
- */
284
+ private setupCacheEventListeners;
285
+ private handleCacheEvent;
334
286
  private buildKey;
335
- /**
336
- * Creates a selector function for a single record query.
337
- * @template T The type of the data being queried.
338
- * @param queryKey The unique key for the query.
339
- * @returns A function that returns the current QueryResult for the given key.
340
- */
341
- private createSelector;
342
- /**
343
- * Reads a single record from the store.
344
- * @param params The read options.
345
- * @returns An object containing the current QueryResult and a function to subscribe to changes.
346
- */
347
- /**
348
- * Retrieves a single record reactively.
349
- *
350
- * This method provides a reactive query result for a single record. The data is fetched from the cache if available,
351
- * otherwise it's fetched from the underlying `baseStore`. The method returns an object containing the current
352
- * query result and a function to subscribe to future updates.
353
- *
354
- * @param params The options for the read operation, used to identify the record.
355
- * @returns A `ReactiveQueryResult` object containing the reactive value and an `onValueChange` subscription function.
356
- */
357
- read(params: TReadOptions): ReactiveQueryResult<T>;
358
- /**
359
- * Creates a selector function for a paginated query (list or find).
360
- * @template TParams The type of the parameters for the query.
361
- * @param baseQueryKey The base unique key for the query.
362
- * @param baseParams The base parameters for the query.
363
- * @param fetchFn The function to call to fetch a page of data.
364
- * @returns A function that returns the current PagedQueryResult.
365
- */
366
- private createPagedSelector;
367
- /**
368
- * Sets up a paginated query (list or find) with caching and reactivity.
369
- * @template TParams The type of the parameters for the query.
370
- * @param type The type of the paginated query ('list' or 'find').
371
- * @param params The parameters for the query.
372
- * @param fetchFn The function to call to fetch a page of data.
373
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
374
- */
287
+ private getOrCreateError;
288
+ private getOrCreateStoreError;
289
+ private computeResult;
290
+ private scheduleBackgroundFetch;
291
+ private getCurrentPageForQuery;
292
+ private getOrCreatePaginationMethods;
293
+ private computeResultForParams;
294
+ hasActiveQuery(operation: string, params: any): boolean;
295
+ getActiveQuery<TResult extends Record<string, unknown> = T>(operation: string, params: any): StoreResult<TResult> | undefined;
296
+ read(params: TReadOptions): StoreResult<T, QueryResult<T>>;
297
+ list(params: TListOptions): StoreResult<T, PagedQueryResult<T>>;
298
+ find(params: TFindOptions): StoreResult<T, PagedQueryResult<T>>;
375
299
  private setupPagedQuery;
376
- /**
377
- * Lists records from the store with pagination and reactivity.
378
- * @param params The list options.
379
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
380
- */
381
- /**
382
- * Retrieves a paginated list of records reactively.
383
- *
384
- * This method provides a reactive query result for a list of records. It supports pagination and automatically
385
- * fetches data from the cache or the `baseStore`. The result includes methods for navigating to the next,
386
- * previous, or a specific page.
387
- *
388
- * @param params The options for the list operation, including pagination details.
389
- * @returns A `ReactivePagedQueryResult` object containing the reactive paged value and an `onValueChange` subscription function.
390
- */
391
- list(params: TListOptions): ReactivePagedQueryResult<T>;
392
- /**
393
- * Finds records from the store with pagination and reactivity.
394
- * @param params The find options.
395
- * @returns An object containing the current PagedQueryResult and a function to subscribe to changes.
396
- */
397
- /**
398
- * Finds and retrieves a paginated list of records reactively based on a query.
399
- *
400
- * Similar to `list`, this method provides a reactive query result for a set of records that match the given
401
- * find options. It supports pagination and provides methods for navigating through the pages.
402
- *
403
- * @param params The options for the find operation, used to query for specific records.
404
- * @returns A `ReactivePagedQueryResult` object containing the reactive paged value and an `onValueChange` subscription function.
405
- */
406
- find(params: TFindOptions): ReactivePagedQueryResult<T>;
407
- /**
408
- * Invalidates relevant queries in the cache based on a mutation operation.
409
- * If a correlator is provided, it will be used to determine which queries to invalidate.
410
- * Otherwise, it invalidates all 'list' and 'find' queries.
411
- * @param mutation An object describing the mutation operation and its parameters.
412
- */
413
300
  private invalidateQueries;
414
- /**
415
- * Handles incoming store events, invalidating relevant queries if a `storeEventCorrelator` is provided.
416
- * @param event The StoreEvent received.
417
- */
418
301
  private handleStoreEvent;
419
- /**
420
- * Creates a new record.
421
- *
422
- * This method sends a request to the `baseStore` to create a new record. Upon successful creation,
423
- * it updates the cache with the new record and invalidates relevant queries to ensure data consistency.
424
- *
425
- * @param params An object containing the data for the new record and optional create options.
426
- * @returns A promise that resolves to the newly created record, or `undefined` if the creation fails.
427
- * @throws An error if the create operation fails.
428
- */
429
302
  create(params: {
430
303
  data: Partial<T>;
431
304
  options?: TCreateOptions;
432
305
  }): Promise<T | undefined>;
433
- /**
434
- * Updates an existing record.
435
- *
436
- * This method sends a request to the `baseStore` to update a record. If the update is successful,
437
- * it updates the cache with the modified record and invalidates relevant queries.
438
- *
439
- * @param params An object containing the ID of the record to update, the partial data, and optional update options.
440
- * @returns A promise that resolves to the updated record, or `undefined` if the update fails.
441
- * @throws An error if the update operation fails.
442
- */
443
306
  update(params: {
444
307
  data: Partial<T>;
445
308
  options?: TUpdateOptions;
446
309
  }): Promise<T | undefined>;
447
- /**
448
- * Deletes a record.
449
- *
450
- * This method sends a request to the `baseStore` to delete a record. Upon successful deletion,
451
- * it removes the record from the cache and invalidates relevant queries.
452
- *
453
- * @param params The options for the delete operation, used to identify the record to be deleted.
454
- * @returns A promise that resolves when the deletion is complete.
455
- * @throws An error if the delete operation fails.
456
- */
457
310
  delete(params: TDeleteOptions): Promise<void>;
458
- /**
459
- * Sends a notification event to the base store.
460
- *
461
- * This can be used to trigger server-side events or other custom actions in the `baseStore`.
462
- *
463
- * @param event The `StoreEvent` to be sent.
464
- * @returns A promise that resolves when the notification has been processed.
465
- */
466
311
  notify(event: StoreEvent): Promise<void>;
467
- /**
468
- * Establishes a real-time data stream.
469
- *
470
- * This method delegates to the `baseStore`'s `stream` method to create a persistent connection for
471
- * receiving real-time updates. The provided callback will be invoked when new data is available.
472
- *
473
- * @param options The options for the stream operation.
474
- * @param onStreamChange A callback function that is executed when the stream's data changes.
475
- * @returns A promise that resolves with a function to close the stream.
476
- */
477
- stream(options: TStreamOptions, onStreamChange: () => void): Promise<{
312
+ stream(options: TStreamOptions, onStreamChange: () => void): {
478
313
  stream: () => AsyncIterable<T>;
479
314
  cancel: () => void;
480
315
  status: () => "active" | "cancelled" | "completed";
481
- }>;
482
- /**
483
- * Uploads a file and creates a new record associated with it.
484
- *
485
- * This method handles file uploads through the `baseStore`. After a successful upload, it updates the cache
486
- * with the new record and invalidates relevant queries.
487
- *
488
- * @param params An object containing the file to upload and optional upload options.
489
- * @returns A promise that resolves to the newly created record, or `undefined` if the upload fails.
490
- * @throws An error if the upload operation fails.
491
- */
316
+ };
317
+ subscribe(scope: string, callback: (event: StoreEvent) => void): Promise<() => void>;
492
318
  upload(params: {
493
319
  file: File;
494
320
  options?: TUploadOptions;
495
321
  }): Promise<T | undefined>;
496
- /**
497
- * Forces a refresh of a specific query.
498
- *
499
- * This method bypasses the cache's staleness checks and forces a refetch of the data from the `baseStore`.
500
- * The method is overloaded to support `read`, `list`, and `find` operations.
501
- *
502
- * @param operation The type of operation to refresh ('read', 'list', or 'find').
503
- * @param params The parameters for the operation.
504
- * @returns A promise that resolves to the refreshed data.
505
- */
506
322
  refresh(operation: 'read', params: TReadOptions): Promise<T | undefined>;
507
323
  refresh(operation: 'list', params: TListOptions): Promise<Page<T> | undefined>;
508
324
  refresh(operation: 'find', query: TFindOptions): Promise<Page<T> | undefined>;
509
- /**
510
- * Pre-fetches data for a query and caches it.
511
- *
512
- * This method is used to proactively fetch data that is likely to be needed soon. It fetches the data
513
- * and stores it in the cache, so that subsequent requests for the same data can be served instantly.
514
- * The method is overloaded for `read`, `list`, and `find` operations.
515
- *
516
- * @param operation The type of operation to prefetch ('read', 'list', or 'find').
517
- * @param params The parameters for the operation.
518
- */
519
325
  prefetch(operation: 'read', params: TReadOptions): void;
520
326
  prefetch(operation: 'list', params: TListOptions): void;
521
327
  prefetch(operation: 'find', params: TFindOptions): void;
522
- /**
523
- * Invalidates the cached data for a specific query.
524
- *
525
- * This marks the query's data as stale, forcing a refetch the next time it's accessed. This is useful
526
- * when you know the data has changed on the server, but the change was not triggered by a mutation
527
- * through this store.
528
- *
529
- * @param operation The type of operation to invalidate ('read', 'list', or 'find').
530
- * @param params The parameters for the operation.
531
- * @returns A promise that resolves when the invalidation is complete.
532
- */
533
328
  invalidate(operation: string, params: any): Promise<void>;
534
- /**
535
- * Invalidates all active queries in the store.
536
- *
537
- * This method marks all currently active queries as stale, forcing them to be refetched the next time
538
- * they are accessed. This is a more aggressive approach to cache invalidation.
539
- *
540
- * @returns A promise that resolves when all invalidations are complete.
541
- */
542
329
  invalidateAll(): Promise<void>;
543
- /**
544
- * Retrieves statistics about the store and its cache.
545
- *
546
- * This method returns an object containing statistics from the underlying cache, plus the number of
547
- * active subscriptions in the reactive store.
548
- *
549
- * @returns An object with cache statistics and the number of active subscriptions.
550
- */
551
330
  getStats(): {
552
331
  activeSubscriptions: number;
553
332
  size: number;
@@ -564,12 +343,6 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
564
343
  error?: boolean;
565
344
  }>;
566
345
  };
567
- /**
568
- * Cleans up all resources used by the store.
569
- *
570
- * This method should be called when the store is no longer needed. It unsubscribes from all cache events,
571
- * clears all query subscriptions, and unsubscribes from the base store's events.
572
- */
573
346
  destroy(): void;
574
347
  }
575
348
 
@@ -580,4 +353,4 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
580
353
  */
581
354
  declare function hash(data: any): string;
582
355
 
583
- export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, hash };
356
+ export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, type StoreResult, hash };
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}exports.ReactiveRemoteStore=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o={operation:t,params:r,subscribers:i},c=()=>{const e=a();var r,s;("read"===t?(r=o.result,s=e,r&&s?r.data===s.data&&r.loading===s.loading&&r.stale===s.stale&&(r.error===s.error||r.error?.message===s.error?.message):r===s):function(e,t){return e&&t?e.page===t.page&&e.loading===t.loading&&e.stale===t.stale&&(e.error===t.error||e.error?.message===t.error?.message)&&e.hasNext===t.hasNext&&e.hasPrevious===t.hasPrevious:e===t}(o.result,e))||(o.result=e,i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}})))};return o.unsubscribeCallbacks=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&c()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&c()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&c()}))],o.cachedSubscribe=t=>(i.add(t),()=>{i.delete(t),0===i.size&&(o.unsubscribeCallbacks.forEach((e=>e())),this.querySubscriptions.delete(e))}),o.cachedSelector=()=>o.result,o.result=a(),this.querySubscriptions.set(e,o),o}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}));return{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){let s=r;const i=this,o={next:async()=>{const e=s.page||1,r=i.buildKey(t.split(":")[0],s),o=i.cache.peek(r);if(o&&e<o.page.pages){const r={...s,page:e+1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},previous:async()=>{const e=s.page||1;if(e>1){const r={...s,page:e-1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},fetch:async e=>{const r={...s,page:e},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}};return()=>{const r=this.buildKey(t.split(":")[0],s),a=this.cache.getStats().entries.find((e=>e.key===r)),i=this.cache.peek(r);a?.isStale&&this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${r}:`,e)})),void 0!==i&&this.cache.has(r)||this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${r}:`,e)}));const c=s.page||1;return{page:i,loading:a?.isLoading??void 0===i,error:a?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0,hasNext:!!i&&c<i.page.pages,hasPrevious:c>1,...o}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}},exports.StoreError=e,exports.hash=r;
1
+ "use strict";require("uuid");var e,t,s=Object.create,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.getPrototypeOf,n=Object.prototype.hasOwnProperty,c=(e={"node_modules/@asaidimu/events/index.js"(e,t){var s,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,n={};((e,t)=>{for(var s in t)r(e,s,{get:t[s],enumerable:!0})})(n,{createEventBus:()=>c}),t.exports=(s=n,((e,t,s,n)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===s||r(e,c,{get:()=>t[c],enumerable:!(n=a(t,c))||n.enumerable});return e})(r({},"__esModule",{value:!0}),s));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],r=0,a=0;const i=new Map,o=new Map;let n=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?n=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{r++,a+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=s;s=[],t.forEach((({name:t,payload:s})=>{const r=performance.now();try{(o.get(t)||[]).forEach((e=>e(s)))}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-r)}))},u=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?o.set(e,Array.from(s)):o.delete(e)};return n&&(n.onmessage=e=>{const{name:t,payload:s}=e.data;(o.get(t)||[]).forEach((e=>e(s)))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const r=t.get(e);return r.add(s),l(e),()=>{r.delete(s),0===r.size?(t.delete(e),o.delete(e)):l(e)}},emit:({name:t,payload:r})=>{if(e.async)return s.push({name:t,payload:r}),s.length>=e.batchSize?h():u(),void(n&&n.postMessage({name:t,payload:r}));const a=performance.now();try{(o.get(t)||[]).forEach((e=>e(r))),n&&n.postMessage({name:t,payload:r})}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-a)},getMetrics:()=>({totalEvents:r,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:r>0?a/r:0}),clear:()=>{t.clear(),o.clear(),s=[],r=0,a=0,i.clear(),n&&(n.close(),n=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports});((e,t,c)=>{c=null!=e?s(o(e)):{},((e,t,s,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))n.call(e,c)||c===s||r(e,c,{get:()=>t[c],enumerable:!(o=a(t,c))||o.enumerable})})(e&&e.__esModule?c:r(c,"default",{value:e,enumerable:!0}),e)})(c());var h=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,s){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${s}`,isRetryable:!0},t)}};function u(e,t=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>u(e,t))).join(",")}]`:"object"==typeof e?t.has(e)?'"__circular__"':(t.set(e,!0),`{${Object.keys(e).sort().map((s=>`"${s}":${u(e[s],t)}`)).join(",")}}`):""}function l(e){let t=3421674724,s=2216829733;const r=u(e);for(let e=0;e<r.length;e++){s^=r.charCodeAt(e);const a=Math.imul(s,435),i=Math.imul(s,435)+Math.imul(t,435);s=a>>>0,t=i>>>0}return(t>>>0).toString(16)+(s>>>0).toString(16)}var d=class{constructor(e,t,s){this.operation=e,this.params=t,this.currentResult=s,this.currentResultHash=l(s),this.defaultResult=s}currentResult;currentResultHash;subscribers=new Set;defaultResult;idle=!1;getResult(){return this.currentResult}getParams(){return this.params}getOperation(){return this.operation}updateParams(e){this.params=e}reset(){this.currentResult=this.defaultResult,this.currentResultHash=l(this.defaultResult),this.notifySubscribers(),this.idle=!0}updateResult(e,t=!1){if(this.idle&&!t)return;this.idle=!1;const s=l(e);this.currentResultHash!==s&&(this.currentResult=e,this.currentResultHash=s,this.notifySubscribers())}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){this.subscribers.forEach((e=>{try{e()}catch(e){console.error("QueryState: Subscriber callback error:",e)}}))}hasSubscribers(){return this.subscribers.size>0}};exports.ReactiveRemoteStore=class{constructor(e,t,s,r){if(this.cache=e,this.baseStore=t,this.correlator=s,this.storeEventCorrelator=r,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}this.setupCacheEventListeners()}queryStates=new Map;stableResults=new Map;stablePaginationMethods=new Map;errorCache=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;cacheEventCleanups=new Set;setupCacheEventListeners(){["cache:fetch:success","cache:fetch:error","cache:data:set","cache:data:invalidate","cache:read:hit"].forEach((e=>{const t=this.cache.on(e,(e=>{this.handleCacheEvent(e)}));this.cacheEventCleanups.add(t)}))}handleCacheEvent(e){const t=this.queryStates.get(e.key);if(t){const s=this.computeResult(e.key);t.updateResult(s)}}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const s=`${e}:${l(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,s),s}getOrCreateError(e){if(this.errorCache.has(e))return this.errorCache.get(e);const t=new Error(e);return this.errorCache.set(e,t),t}getOrCreateStoreError(e){const t=`store:${e}`;if(this.errorCache.has(t))return this.errorCache.get(t);const s=h.fromError(new Error(e),"query");return this.errorCache.set(t,s),s}computeResult(e){const t=this.cache.getStats().entries.find((t=>t.key===e)),s=this.cache.peek(e);this.scheduleBackgroundFetch(e,t,s);if("read"===e.split(":")[0])return{data:s,loading:t?.isLoading??void 0===s,error:t?.error?this.getOrCreateError("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0};{const r=this.getCurrentPageForQuery(e),a=this.getOrCreatePaginationMethods(e);return{page:s,loading:t?.isLoading??void 0===s,error:t?.error?this.getOrCreateStoreError("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0,hasNext:!!s&&r<s.page.pages,hasPrevious:r>1,...a}}}scheduleBackgroundFetch(e,t,s){t?.isStale&&!t?.isLoading&&queueMicrotask((()=>{this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)}))})),void 0!==s||this.cache.has(e)||t?.isLoading||queueMicrotask((()=>{this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}))}))}getCurrentPageForQuery(e){const t=this.queryStates.get(e);if(t){return t.getParams().page||1}return 1}getOrCreatePaginationMethods(e){if(this.stablePaginationMethods.has(e))return this.stablePaginationMethods.get(e);const t=this.queryStates.get(e);if(!t)throw new Error(`QueryState not found for ${e}`);const s=t.getOperation(),r={next:async()=>{const r=t.getParams(),a=r.page||1,i=this.buildKey(s,r),o=this.cache.peek(i);if(o&&a<o.page.pages){const i={...r,page:a+1},o=this.buildKey(s,i);if(!this.cache.has(o)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(o,(()=>e(i)))}t.updateParams(i),await this.cache.get(o,{waitForFresh:!0});const n=this.computeResultForParams(s,i,e);t.updateResult(n)}},previous:async()=>{const r=t.getParams(),a=r.page||1;if(a>1){const i={...r,page:a-1},o=this.buildKey(s,i);if(!this.cache.has(o)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(o,(()=>e(i)))}t.updateParams(i),await this.cache.get(o,{waitForFresh:!0});const n=this.computeResultForParams(s,i,e);t.updateResult(n)}},navigate:async r=>{if(r<1)return;const a={...t.getParams(),page:r},i=this.buildKey(s,a);if(!this.cache.has(i)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(i,(()=>e(a)))}t.updateParams(a),await this.cache.get(i,{waitForFresh:!0});const o=this.computeResultForParams(s,a,e);t.updateResult(o)},refresh:async(s=1e3)=>{const r=t.getParams(),a=this.buildKey(t.getOperation(),r);t.reset();const i=this.cache.refresh(a),o=new Promise((e=>setTimeout(e,s)));await Promise.all([i,o]);const n=this.computeResultForParams(t.getOperation(),t.getParams(),e);t.updateResult(n,!0)},changeParams:async s=>{const r=s(t.getParams());if(!r)return void console.error("Setter function for changeParams must return a new parameters object.");const a=this.buildKey(t.getOperation(),r);if(!this.cache.has(a)){const e="list"===t.getOperation()?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(a,(()=>e(r)))}t.updateParams(r),await this.cache.get(a,{waitForFresh:!0});const i=this.computeResultForParams(t.getOperation(),r,e);t.updateResult(i)}};return this.stablePaginationMethods.set(e,r),r}computeResultForParams(e,t,s){const r=this.buildKey(e,t),a=this.cache.getStats().entries.find((e=>e.key===r)),i=this.cache.peek(r);if("read"===e)return{data:i,loading:a?.isLoading??void 0===i,error:a?.error?this.getOrCreateError("Query failed"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0};{const e=t.page||1,r=this.stablePaginationMethods.get(s)??{};return{page:i,loading:a?.isLoading??void 0===i,error:a?.error?this.getOrCreateStoreError("Query failed"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0,hasNext:!!i&&e<i.page.pages,hasPrevious:e>1,...r}}}hasActiveQuery(e,t){const s=this.buildKey(e,t);return this.stableResults.has(s)}getActiveQuery(e,t){const s=this.buildKey(e,t);return this.stableResults.get(s)}read(e){const t=this.buildKey("read",e);if(this.stableResults.has(t))return this.stableResults.get(t);this.cache.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const s=this.computeResult(t),r=new d("read",e,s);this.queryStates.set(t,r);const a={value:()=>r.getResult(),onValueChange:e=>{const t=r.subscribe(e);return()=>{t()}}};return this.stableResults.set(t,a),a}list(e){return this.setupPagedQuery("list",e)}find(e){return this.setupPagedQuery("find",e)}setupPagedQuery(e,t){const s=this.buildKey(e,t);if(this.stableResults.has(s))return this.stableResults.get(s);if(!this.cache.has(s)){const r="list"===e?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(s,(()=>r(t)))}const r=this.cache.peek(s),a=new d(e,t,{page:r,loading:void 0===r,error:void 0,stale:!0,updated:0,hasNext:!1,hasPrevious:!1});this.queryStates.set(s,a);const i=this.computeResult(s);a.updateResult(i);const o={value:()=>a.getResult(),onValueChange:e=>{const t=a.subscribe(e);return()=>{t()}}};return this.stableResults.set(s,o),o}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.queryStates.values()).map((e=>({queryKey:this.buildKey(e.getOperation(),e.getParams()),operation:e.getOperation(),params:e.getParams()}))),s=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(s)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(this.storeEventCorrelator){const t=Array.from(this.queryStates.values()).map((e=>({queryKey:this.buildKey(e.getOperation(),e.getParams()),operation:e.getOperation(),params:e.getParams()})));this.storeEventCorrelator(e,t).forEach((e=>this.cache.invalidate(e)))}}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}stream(e,t){return this.baseStore.stream(e,t)}async subscribe(e,t){return this.baseStore.subscribe(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const s=this.buildKey(e,t);return this.cache.refresh(s)}prefetch(e,t){const s=this.buildKey(e,t);this.cache.prefetch(s).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${s}:`,e)}))}async invalidate(e,t){const s=this.buildKey(e,t);await this.cache.invalidate(s)}async invalidateAll(){const e=Array.from(this.queryStates.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.queryStates.size}}destroy(){this.cacheEventCleanups.forEach((e=>e())),this.cacheEventCleanups.clear(),this.queryStates.clear(),this.stableResults.clear(),this.stablePaginationMethods.clear(),this.errorCache.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}},exports.StoreError=h,exports.hash=l;
package/index.mjs CHANGED
@@ -1 +1 @@
1
- var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}var a=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o={operation:t,params:r,subscribers:i},c=()=>{const e=a();var r,s;("read"===t?(r=o.result,s=e,r&&s?r.data===s.data&&r.loading===s.loading&&r.stale===s.stale&&(r.error===s.error||r.error?.message===s.error?.message):r===s):function(e,t){return e&&t?e.page===t.page&&e.loading===t.loading&&e.stale===t.stale&&(e.error===t.error||e.error?.message===t.error?.message)&&e.hasNext===t.hasNext&&e.hasPrevious===t.hasPrevious:e===t}(o.result,e))||(o.result=e,i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}})))};return o.unsubscribeCallbacks=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&c()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&c()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&c()}))],o.cachedSubscribe=t=>(i.add(t),()=>{i.delete(t),0===i.size&&(o.unsubscribeCallbacks.forEach((e=>e())),this.querySubscriptions.delete(e))}),o.cachedSelector=()=>o.result,o.result=a(),this.querySubscriptions.set(e,o),o}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}));return{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){let s=r;const i=this,o={next:async()=>{const e=s.page||1,r=i.buildKey(t.split(":")[0],s),o=i.cache.peek(r);if(o&&e<o.page.pages){const r={...s,page:e+1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},previous:async()=>{const e=s.page||1;if(e>1){const r={...s,page:e-1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},fetch:async e=>{const r={...s,page:e},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}};return()=>{const r=this.buildKey(t.split(":")[0],s),a=this.cache.getStats().entries.find((e=>e.key===r)),i=this.cache.peek(r);a?.isStale&&this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${r}:`,e)})),void 0!==i&&this.cache.has(r)||this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${r}:`,e)}));const c=s.page||1;return{page:i,loading:a?.isLoading??void 0===i,error:a?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0,hasNext:!!i&&c<i.page.pages,hasPrevious:c>1,...o}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}};export{a as ReactiveRemoteStore,e as StoreError,r as hash};
1
+ import"uuid";var e,t,s=Object.create,a=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.getPrototypeOf,n=Object.prototype.hasOwnProperty,c=(e={"node_modules/@asaidimu/events/index.js"(e,t){var s,a=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,n={};((e,t)=>{for(var s in t)a(e,s,{get:t[s],enumerable:!0})})(n,{createEventBus:()=>c}),t.exports=(s=n,((e,t,s,n)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))o.call(e,c)||c===s||a(e,c,{get:()=>t[c],enumerable:!(n=r(t,c))||n.enumerable});return e})(a({},"__esModule",{value:!0}),s));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],a=0,r=0;const i=new Map,o=new Map;let n=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?n=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{a++,r+=t,i.set(e,(i.get(e)||0)+1)},h=()=>{const t=s;s=[],t.forEach((({name:t,payload:s})=>{const a=performance.now();try{(o.get(t)||[]).forEach((e=>e(s)))}catch(a){e.errorHandler({...a,eventName:t,payload:s})}c(t,performance.now()-a)}))},u=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?o.set(e,Array.from(s)):o.delete(e)};return n&&(n.onmessage=e=>{const{name:t,payload:s}=e.data;(o.get(t)||[]).forEach((e=>e(s)))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const a=t.get(e);return a.add(s),l(e),()=>{a.delete(s),0===a.size?(t.delete(e),o.delete(e)):l(e)}},emit:({name:t,payload:a})=>{if(e.async)return s.push({name:t,payload:a}),s.length>=e.batchSize?h():u(),void(n&&n.postMessage({name:t,payload:a}));const r=performance.now();try{(o.get(t)||[]).forEach((e=>e(a))),n&&n.postMessage({name:t,payload:a})}catch(s){e.errorHandler({...s,eventName:t,payload:a})}c(t,performance.now()-r)},getMetrics:()=>({totalEvents:a,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:i,averageEmitDuration:a>0?r/a:0}),clear:()=>{t.clear(),o.clear(),s=[],a=0,r=0,i.clear(),n&&(n.close(),n=null)}}}}},function(){return t||(0,e[i(e)[0]])((t={exports:{}}).exports,t),t.exports});((e,t,c)=>{c=null!=e?s(o(e)):{},((e,t,s,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of i(t))n.call(e,c)||c===s||a(e,c,{get:()=>t[c],enumerable:!(o=r(t,c))||o.enumerable})})(e&&e.__esModule?c:a(c,"default",{value:e,enumerable:!0}),e)})(c());var h=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,s){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${s}`,isRetryable:!0},t)}};function u(e,t=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>u(e,t))).join(",")}]`:"object"==typeof e?t.has(e)?'"__circular__"':(t.set(e,!0),`{${Object.keys(e).sort().map((s=>`"${s}":${u(e[s],t)}`)).join(",")}}`):""}function l(e){let t=3421674724,s=2216829733;const a=u(e);for(let e=0;e<a.length;e++){s^=a.charCodeAt(e);const r=Math.imul(s,435),i=Math.imul(s,435)+Math.imul(t,435);s=r>>>0,t=i>>>0}return(t>>>0).toString(16)+(s>>>0).toString(16)}var d=class{constructor(e,t,s){this.operation=e,this.params=t,this.currentResult=s,this.currentResultHash=l(s),this.defaultResult=s}currentResult;currentResultHash;subscribers=new Set;defaultResult;idle=!1;getResult(){return this.currentResult}getParams(){return this.params}getOperation(){return this.operation}updateParams(e){this.params=e}reset(){this.currentResult=this.defaultResult,this.currentResultHash=l(this.defaultResult),this.notifySubscribers(),this.idle=!0}updateResult(e,t=!1){if(this.idle&&!t)return;this.idle=!1;const s=l(e);this.currentResultHash!==s&&(this.currentResult=e,this.currentResultHash=s,this.notifySubscribers())}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){this.subscribers.forEach((e=>{try{e()}catch(e){console.error("QueryState: Subscriber callback error:",e)}}))}hasSubscribers(){return this.subscribers.size>0}},p=class{constructor(e,t,s,a){if(this.cache=e,this.baseStore=t,this.correlator=s,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}this.setupCacheEventListeners()}queryStates=new Map;stableResults=new Map;stablePaginationMethods=new Map;errorCache=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;cacheEventCleanups=new Set;setupCacheEventListeners(){["cache:fetch:success","cache:fetch:error","cache:data:set","cache:data:invalidate","cache:read:hit"].forEach((e=>{const t=this.cache.on(e,(e=>{this.handleCacheEvent(e)}));this.cacheEventCleanups.add(t)}))}handleCacheEvent(e){const t=this.queryStates.get(e.key);if(t){const s=this.computeResult(e.key);t.updateResult(s)}}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const s=`${e}:${l(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,s),s}getOrCreateError(e){if(this.errorCache.has(e))return this.errorCache.get(e);const t=new Error(e);return this.errorCache.set(e,t),t}getOrCreateStoreError(e){const t=`store:${e}`;if(this.errorCache.has(t))return this.errorCache.get(t);const s=h.fromError(new Error(e),"query");return this.errorCache.set(t,s),s}computeResult(e){const t=this.cache.getStats().entries.find((t=>t.key===e)),s=this.cache.peek(e);this.scheduleBackgroundFetch(e,t,s);if("read"===e.split(":")[0])return{data:s,loading:t?.isLoading??void 0===s,error:t?.error?this.getOrCreateError("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0};{const a=this.getCurrentPageForQuery(e),r=this.getOrCreatePaginationMethods(e);return{page:s,loading:t?.isLoading??void 0===s,error:t?.error?this.getOrCreateStoreError("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0,hasNext:!!s&&a<s.page.pages,hasPrevious:a>1,...r}}}scheduleBackgroundFetch(e,t,s){t?.isStale&&!t?.isLoading&&queueMicrotask((()=>{this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)}))})),void 0!==s||this.cache.has(e)||t?.isLoading||queueMicrotask((()=>{this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}))}))}getCurrentPageForQuery(e){const t=this.queryStates.get(e);if(t){return t.getParams().page||1}return 1}getOrCreatePaginationMethods(e){if(this.stablePaginationMethods.has(e))return this.stablePaginationMethods.get(e);const t=this.queryStates.get(e);if(!t)throw new Error(`QueryState not found for ${e}`);const s=t.getOperation(),a={next:async()=>{const a=t.getParams(),r=a.page||1,i=this.buildKey(s,a),o=this.cache.peek(i);if(o&&r<o.page.pages){const i={...a,page:r+1},o=this.buildKey(s,i);if(!this.cache.has(o)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(o,(()=>e(i)))}t.updateParams(i),await this.cache.get(o,{waitForFresh:!0});const n=this.computeResultForParams(s,i,e);t.updateResult(n)}},previous:async()=>{const a=t.getParams(),r=a.page||1;if(r>1){const i={...a,page:r-1},o=this.buildKey(s,i);if(!this.cache.has(o)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(o,(()=>e(i)))}t.updateParams(i),await this.cache.get(o,{waitForFresh:!0});const n=this.computeResultForParams(s,i,e);t.updateResult(n)}},navigate:async a=>{if(a<1)return;const r={...t.getParams(),page:a},i=this.buildKey(s,r);if(!this.cache.has(i)){const e="list"===s?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(i,(()=>e(r)))}t.updateParams(r),await this.cache.get(i,{waitForFresh:!0});const o=this.computeResultForParams(s,r,e);t.updateResult(o)},refresh:async(s=1e3)=>{const a=t.getParams(),r=this.buildKey(t.getOperation(),a);t.reset();const i=this.cache.refresh(r),o=new Promise((e=>setTimeout(e,s)));await Promise.all([i,o]);const n=this.computeResultForParams(t.getOperation(),t.getParams(),e);t.updateResult(n,!0)},changeParams:async s=>{const a=s(t.getParams());if(!a)return void console.error("Setter function for changeParams must return a new parameters object.");const r=this.buildKey(t.getOperation(),a);if(!this.cache.has(r)){const e="list"===t.getOperation()?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(r,(()=>e(a)))}t.updateParams(a),await this.cache.get(r,{waitForFresh:!0});const i=this.computeResultForParams(t.getOperation(),a,e);t.updateResult(i)}};return this.stablePaginationMethods.set(e,a),a}computeResultForParams(e,t,s){const a=this.buildKey(e,t),r=this.cache.getStats().entries.find((e=>e.key===a)),i=this.cache.peek(a);if("read"===e)return{data:i,loading:r?.isLoading??void 0===i,error:r?.error?this.getOrCreateError("Query failed"):void 0,stale:r?.isStale??!0,updated:r?.lastUpdated??0};{const e=t.page||1,a=this.stablePaginationMethods.get(s)??{};return{page:i,loading:r?.isLoading??void 0===i,error:r?.error?this.getOrCreateStoreError("Query failed"):void 0,stale:r?.isStale??!0,updated:r?.lastUpdated??0,hasNext:!!i&&e<i.page.pages,hasPrevious:e>1,...a}}}hasActiveQuery(e,t){const s=this.buildKey(e,t);return this.stableResults.has(s)}getActiveQuery(e,t){const s=this.buildKey(e,t);return this.stableResults.get(s)}read(e){const t=this.buildKey("read",e);if(this.stableResults.has(t))return this.stableResults.get(t);this.cache.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const s=this.computeResult(t),a=new d("read",e,s);this.queryStates.set(t,a);const r={value:()=>a.getResult(),onValueChange:e=>{const t=a.subscribe(e);return()=>{t()}}};return this.stableResults.set(t,r),r}list(e){return this.setupPagedQuery("list",e)}find(e){return this.setupPagedQuery("find",e)}setupPagedQuery(e,t){const s=this.buildKey(e,t);if(this.stableResults.has(s))return this.stableResults.get(s);if(!this.cache.has(s)){const a="list"===e?this.baseStore.list.bind(this.baseStore):this.baseStore.find.bind(this.baseStore);this.cache.registerQuery(s,(()=>a(t)))}const a=this.cache.peek(s),r=new d(e,t,{page:a,loading:void 0===a,error:void 0,stale:!0,updated:0,hasNext:!1,hasPrevious:!1});this.queryStates.set(s,r);const i=this.computeResult(s);r.updateResult(i);const o={value:()=>r.getResult(),onValueChange:e=>{const t=r.subscribe(e);return()=>{t()}}};return this.stableResults.set(s,o),o}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.queryStates.values()).map((e=>({queryKey:this.buildKey(e.getOperation(),e.getParams()),operation:e.getOperation(),params:e.getParams()}))),s=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(s)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(this.storeEventCorrelator){const t=Array.from(this.queryStates.values()).map((e=>({queryKey:this.buildKey(e.getOperation(),e.getParams()),operation:e.getOperation(),params:e.getParams()})));this.storeEventCorrelator(e,t).forEach((e=>this.cache.invalidate(e)))}}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}stream(e,t){return this.baseStore.stream(e,t)}async subscribe(e,t){return this.baseStore.subscribe(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const s=this.buildKey(e,t);return this.cache.refresh(s)}prefetch(e,t){const s=this.buildKey(e,t);this.cache.prefetch(s).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${s}:`,e)}))}async invalidate(e,t){const s=this.buildKey(e,t);await this.cache.invalidate(s)}async invalidateAll(){const e=Array.from(this.queryStates.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.queryStates.size}}destroy(){this.cacheEventCleanups.forEach((e=>e())),this.cacheEventCleanups.clear(),this.queryStates.clear(),this.stableResults.clear(),this.stablePaginationMethods.clear(),this.errorCache.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}};export{p as ReactiveRemoteStore,h as StoreError,l as hash};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-remote-store",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A reactive store for remote data, built on top of @asaidimu/utils-cache",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",