@mmstack/resource 20.7.0 → 20.8.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.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HttpResponse, HttpInterceptorFn, HttpRequest, HttpContext, HttpResourceRef, HttpHeaders, HttpResourceRequest, HttpResourceOptions } from '@angular/common/http';
2
- import { Signal, Injector, Provider, WritableSignal, ValueEqualityFn } from '@angular/core';
2
+ import { Signal, Injector, Provider, ResourceRef, InjectionToken, WritableSignal, ValueEqualityFn } from '@angular/core';
3
+ import { RegisterOptions } from '@mmstack/primitives';
3
4
 
4
5
  type StoredEntry<T> = Omit<CacheEntry<T>, 'timeout'>;
5
6
  type CacheDB<T> = {
@@ -470,6 +471,42 @@ type RetryOptions = number | {
470
471
  backoff?: number;
471
472
  };
472
473
 
474
+ /**
475
+ * Auto-registration into the nearest transition scope, as a resource OPTION:
476
+ * - `true` — register for the pending indicator + hold-stale (does NOT block first paint);
477
+ * - `{ suspends: true }` — register as *suspending* (the boundary holds its placeholder until
478
+ * this resource has a value), i.e. full Suspense;
479
+ * - `{ suspends: false }` — same as `true`;
480
+ * - `false` / omitted — don't register.
481
+ *
482
+ * Defaultable via `provideResourceOptions` / `provideQueryResourceOptions` and overridable
483
+ * (including opting out with `false`) per call — so a dev can make "all queries participate in
484
+ * transitions" the default and turn it off for the odd one.
485
+ */
486
+ type TransitionRegistration = boolean | RegisterOptions;
487
+ /** Options common to every resource kind (the base layer for the options-injection system). */
488
+ type CommonResourceOptions = {
489
+ /** Auto-registration into the nearest transition scope. */
490
+ readonly register?: TransitionRegistration;
491
+ /** Retry failed requests. */
492
+ readonly retry?: RetryOptions;
493
+ /** Configure a circuit breaker for the resource. */
494
+ readonly circuitBreaker?: CircuitBreakerOptions | true;
495
+ /** Trigger a request even when the request parameters are unchanged. @default false */
496
+ readonly triggerOnSameRequest?: boolean;
497
+ };
498
+ /** Layer 1: defaults that apply to ALL resource kinds. Type-specific providers inherit + override these. */
499
+ declare function provideResourceOptions(valueOrFn: CommonResourceOptions | (() => CommonResourceOptions)): Provider;
500
+ declare function injectResourceOptions(injector?: Injector): CommonResourceOptions;
501
+ /** Shared helper for the type-specific providers (query/mutation), so precedence is identical. */
502
+ declare function provideTypedResourceOptions<T>(token: InjectionToken<T>, valueOrFn: T | (() => T)): Provider;
503
+ /**
504
+ * Applies a resolved `register` option to a freshly-created resource — adds it to the nearest
505
+ * transition scope and removes it on destroy. Runs in the resource's injection context (or the
506
+ * provided `injector`), since registration needs `TRANSITION_SCOPE` + `DestroyRef`.
507
+ */
508
+ declare function applyResourceRegistration(ref: ResourceRef<unknown>, register: TransitionRegistration | undefined, injector?: Injector): void;
509
+
473
510
  /**
474
511
  * Options for configuring caching behavior of a `queryResource`.
475
512
  * - `true`: Enables caching with default settings.
@@ -513,9 +550,24 @@ type ResourceCacheOptions = true | {
513
550
  persist?: boolean;
514
551
  };
515
552
  /**
516
- * Options for configuring a `queryResource`.
553
+ * Options for configuring a `queryResource`. Extends Angular's
554
+ * `HttpResourceOptions` with caching, retries, refresh intervals, circuit
555
+ * breakers, and lifecycle callbacks. See the linked properties below for the
556
+ * full list.
557
+ *
558
+ * @example
559
+ * ```ts
560
+ * const options: QueryResourceOptions<User> = {
561
+ * defaultValue: { id: 0, name: 'Anonymous' },
562
+ * cache: { ttl: 60_000, staleTime: 10_000 },
563
+ * refresh: 30_000,
564
+ * retry: { max: 3 },
565
+ * circuitBreaker: true,
566
+ * onError: (err, retry, isFinal) => isFinal && toast.error(err),
567
+ * };
568
+ * ```
517
569
  */
518
- type QueryResourceOptions<TResult, TRaw = TResult> = HttpResourceOptions<TResult, TRaw> & {
570
+ type QueryResourceOptions<TResult, TRaw = TResult> = HttpResourceOptions<TResult, TRaw> & CommonResourceOptions & {
519
571
  /**
520
572
  * Whether to keep the previous value of the resource while a refresh is in progress.
521
573
  * Defaults to `false`. Also keeps status & headers while refreshing.
@@ -526,10 +578,6 @@ type QueryResourceOptions<TResult, TRaw = TResult> = HttpResourceOptions<TResult
526
578
  * refresh its data at the specified interval.
527
579
  */
528
580
  refresh?: number;
529
- /**
530
- * Options for retrying failed requests.
531
- */
532
- retry?: RetryOptions;
533
581
  /**
534
582
  * Called on every failed attempt, including each retry.
535
583
  *
@@ -541,28 +589,73 @@ type QueryResourceOptions<TResult, TRaw = TResult> = HttpResourceOptions<TResult
541
589
  * "user actually needs to know" side effects (toasts, error reporting).
542
590
  */
543
591
  onError?: (err: unknown, retryCount: number, isFinal: boolean) => void;
544
- /**
545
- * Options for configuring a circuit breaker for the resource.
546
- */
547
- circuitBreaker?: CircuitBreakerOptions | true;
548
592
  /**
549
593
  * Options for enabling and configuring caching for the resource.
550
594
  */
551
595
  cache?: ResourceCacheOptions;
552
596
  /**
553
- * Trigger a request every time the request function is triggered, even if the request parameters are the same.
554
- * @default false
597
+ * Comparison of request object
555
598
  */
556
- triggerOnSameRequest?: boolean;
599
+ equalRequest?: (a: HttpResourceRequest, b: HttpResourceRequest) => boolean;
557
600
  };
601
+ /**
602
+ * Layer 2 (query): default options for every `queryResource`, inheriting + overriding the
603
+ * common defaults from `provideResourceOptions`. Per-call options override these in turn.
604
+ */
605
+ declare function provideQueryResourceOptions(valueOrFn: Partial<QueryResourceOptions<any, any>> | (() => Partial<QueryResourceOptions<any, any>>)): Provider;
558
606
  /**
559
607
  * The reason a query resource is currently in the `disabled` state, or `null`
560
608
  * if it is enabled. Useful for branching UI on cause (e.g. "offline" vs
561
609
  * "circuit tripped" vs "nothing to fetch yet").
610
+ *
611
+ * @example
612
+ * ```ts
613
+ * effect(() => {
614
+ * switch (user.disabledReason()) {
615
+ * case 'offline': return toast.warn('You are offline');
616
+ * case 'circuit-open': return toast.warn('Service temporarily unavailable');
617
+ * case 'no-request': return; // expected — request signal returned undefined
618
+ * case null: return; // resource is enabled
619
+ * }
620
+ * });
621
+ * ```
562
622
  */
563
623
  type DisabledReason = 'offline' | 'circuit-open' | 'no-request';
564
624
  /**
565
- * Represents a resource created by `queryResource`. Extends `HttpResourceRef` with additional properties.
625
+ * Returned from a resource's request fn to PAUSE it: the resource holds its current value and last
626
+ * request (so it does not refetch on resume), and stops background work (no polling, no refetch
627
+ * while paused). Distinct from returning `undefined` (DISABLE), which drops the request — a
628
+ * disabled resource may refetch when re-enabled, a paused one resumes exactly where it left off.
629
+ *
630
+ * The request fn receives a {@link RequestContext} and can just return `ctx.paused`.
631
+ */
632
+ declare const PAUSED: unique symbol;
633
+ /**
634
+ * Context passed to a resource's request fn. An object (not positional args) so it can grow
635
+ * without changing the call signature. Today it carries {@link PAUSED} so the fn can return it.
636
+ */
637
+ type RequestContext = {
638
+ readonly paused: typeof PAUSED;
639
+ };
640
+ /** The request fn shape: build a request, or return `undefined` (disable) / `ctx.paused` (pause). */
641
+ type ResourceRequestFn = (ctx: RequestContext) => HttpResourceRequest | string | undefined | void | typeof PAUSED;
642
+ /**
643
+ * Represents a resource created by `queryResource`. Extends `HttpResourceRef`
644
+ * with `disabled` / `disabledReason` signals, writable `headers` / `statusCode`
645
+ * (so optimistic updates can patch them), and `prefetch()` for proactive cache
646
+ * warm-up.
647
+ *
648
+ * @example
649
+ * ```ts
650
+ * const user = queryResource<User>(() => `/api/users/${userId()}`);
651
+ *
652
+ * effect(() => {
653
+ * if (user.status() === 'resolved') console.log(user.value());
654
+ * });
655
+ *
656
+ * // Warm the cache before navigating
657
+ * onMouseEnter(() => user.prefetch());
658
+ * ```
566
659
  */
567
660
  type QueryResourceRef<TResult> = Omit<HttpResourceRef<TResult>, 'headers' | 'statusCode'> & {
568
661
  /**
@@ -601,8 +694,20 @@ type QueryResourceRef<TResult> = Omit<HttpResourceRef<TResult>, 'headers' | 'sta
601
694
  * `HttpResourceOptions` and add features like `keepPrevious`, `refresh`, `retry`,
602
695
  * `onError`, `circuitBreaker`, and `cache`. Additionally, when a `defaultValue` is provided, the resource's value will always be defined, even if the underlying HTTP request fails or is disabled.
603
696
  * @returns An `QueryResourceRef` instance, which extends the basic `HttpResourceRef` with additional features.
697
+ *
698
+ * @example
699
+ * ```ts
700
+ * const userId = signal(1);
701
+ *
702
+ * const user = queryResource<User>(
703
+ * () => `/api/users/${userId()}`,
704
+ * { defaultValue: { id: 0, name: 'Anonymous' } },
705
+ * );
706
+ *
707
+ * user.value(); // always User — never undefined, even before the first fetch resolves
708
+ * ```
604
709
  */
605
- declare function queryResource<TResult, TRaw = TResult>(request: () => HttpResourceRequest | string | undefined | void, options: QueryResourceOptions<TResult, TRaw> & {
710
+ declare function queryResource<TResult, TRaw = TResult>(request: ResourceRequestFn, options: QueryResourceOptions<TResult, TRaw> & {
606
711
  defaultValue: NoInfer<TResult>;
607
712
  }): QueryResourceRef<TResult>;
608
713
  /**
@@ -616,12 +721,51 @@ declare function queryResource<TResult, TRaw = TResult>(request: () => HttpResou
616
721
  * `HttpResourceOptions` and add features like `keepPrevious`, `refresh`, `retry`,
617
722
  * `onError`, `circuitBreaker`, and `cache`.
618
723
  * @returns An `QueryResourceRef` instance, which extends the basic `HttpResourceRef` with additional features.
724
+ *
725
+ * @example
726
+ * ```ts
727
+ * const userId = signal<number | undefined>(undefined);
728
+ *
729
+ * const user = queryResource<User>(
730
+ * () => userId() ? `/api/users/${userId()}` : undefined,
731
+ * {
732
+ * cache: { ttl: 60_000, staleTime: 10_000 },
733
+ * refresh: 30_000,
734
+ * retry: { max: 3 },
735
+ * },
736
+ * );
737
+ *
738
+ * user.value(); // User | undefined
739
+ * user.status(); // 'idle' | 'loading' | 'resolved' | 'error'
740
+ * user.disabledReason(); // null while enabled; 'offline' / 'circuit-open' / 'no-request' otherwise
741
+ * ```
619
742
  */
620
- declare function queryResource<TResult, TRaw = TResult>(request: () => HttpResourceRequest | string | undefined | void, options?: QueryResourceOptions<TResult, TRaw>): QueryResourceRef<TResult | undefined>;
743
+ declare function queryResource<TResult, TRaw = TResult>(request: ResourceRequestFn, options?: QueryResourceOptions<TResult, TRaw>): QueryResourceRef<TResult | undefined>;
621
744
 
622
745
  /**
623
- * A reference to a manually triggered query resource. This type extends the standard `QueryResourceRef`
624
- * with an additional `trigger` method that allows you to manually trigger the resource request.
746
+ * A reference to a manually triggered query resource. Extends
747
+ * {@link QueryResourceRef} with a `trigger()` method that runs the request
748
+ * imperatively and returns a `Promise<TResult>`. Useful when a request should
749
+ * only happen on user action (search submit, button click) rather than on
750
+ * every reactive change of the request inputs.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * const search = manualQueryResource<SearchResults>(() => ({
755
+ * url: '/api/search',
756
+ * params: { q: query() },
757
+ * }));
758
+ *
759
+ * async function onSubmit() {
760
+ * try {
761
+ * const results = await search.trigger();
762
+ * showResults(results);
763
+ * } catch (err) {
764
+ * toast.error('Search failed');
765
+ * }
766
+ * }
767
+ * ```
768
+ *
625
769
  * @see QueryResourceRef
626
770
  */
627
771
  type ManualQueryResourceRef<TResult> = QueryResourceRef<TResult> & {
@@ -637,6 +781,17 @@ type ManualQueryResourceRef<TResult> = QueryResourceRef<TResult> & {
637
781
  * `HttpResourceOptions` and add features like `keepPrevious`, `refresh`, `retry`,
638
782
  * `onError`, `circuitBreaker`, and `cache`. Additionally, when a `defaultValue` is provided, the resource's value will always be defined, even if the underlying HTTP request fails or is disabled.
639
783
  * @returns An `ManualQueryResourceRef` instance, which extends the basic `QueryResourceRef` with additional features.
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * const search = manualQueryResource<SearchResults>(
788
+ * () => ({ url: '/api/search', params: { q: query() } }),
789
+ * { defaultValue: { hits: [], total: 0 } },
790
+ * );
791
+ *
792
+ * // search.value() is always SearchResults (never undefined) thanks to defaultValue.
793
+ * const results = await search.trigger();
794
+ * ```
640
795
  */
641
796
  declare function manualQueryResource<TResult, TRaw = TResult>(request: () => HttpResourceRequest | string | undefined | void, options: QueryResourceOptions<TResult, TRaw> & {
642
797
  defaultValue: NoInfer<TResult>;
@@ -652,6 +807,23 @@ declare function manualQueryResource<TResult, TRaw = TResult>(request: () => Htt
652
807
  * `HttpResourceOptions` and add features like `keepPrevious`, `refresh`, `retry`,
653
808
  * `onError`, `circuitBreaker`, and `cache`.
654
809
  * @returns An `ManualQueryResourceRef` instance, which extends the basic `QueryResourceRef` with additional features.
810
+ *
811
+ * @example
812
+ * ```ts
813
+ * const exportReport = manualQueryResource<Report>(() => ({
814
+ * url: '/api/reports/export',
815
+ * params: { range: range() },
816
+ * }));
817
+ *
818
+ * async function onExportClick() {
819
+ * try {
820
+ * const report = await exportReport.trigger();
821
+ * download(report);
822
+ * } catch (err) {
823
+ * toast.error('Export failed');
824
+ * }
825
+ * }
826
+ * ```
655
827
  */
656
828
  declare function manualQueryResource<TResult, TRaw = TResult>(request: () => HttpResourceRequest | string | undefined | void, options?: QueryResourceOptions<TResult, TRaw>): ManualQueryResourceRef<TResult | undefined>;
657
829
 
@@ -666,11 +838,29 @@ type NextRequest<TMethod extends HttpResourceRequest['method'], TMutation> = TMe
666
838
  method: TMethod;
667
839
  };
668
840
  /**
669
- * Options for configuring a `mutationResource`.
841
+ * Options for configuring a `mutationResource`. Inherits from
842
+ * `QueryResourceOptions` (minus options that don't apply to mutations:
843
+ * `equal`, `keepPrevious`, `refresh`, `cache`) and adds lifecycle callbacks
844
+ * (`onMutate`, `onError`, `onSuccess`, `onSettled`) for managing optimistic
845
+ * updates, rollback, and side effects.
670
846
  *
671
847
  * @typeParam TResult - The type of the expected result from the mutation.
672
848
  * @typeParam TRaw - The raw response type from the HTTP request (defaults to TResult).
673
849
  * @typeParam TCTX - The type of the context value returned by `onMutate`.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * const options: MutationResourceOptions<User, User, Partial<User>, { previous: User | null }> = {
854
+ * onMutate: (patch) => {
855
+ * const previous = current();
856
+ * current.update((u) => (u ? { ...u, ...patch } : u)); // optimistic
857
+ * return { previous };
858
+ * },
859
+ * onError: (_err, { previous }) => current.set(previous), // rollback
860
+ * onSuccess: (saved) => toast.success(`Updated ${saved.name}`),
861
+ * queue: true, // serialize requests when offline / circuit open
862
+ * };
863
+ * ```
674
864
  */
675
865
  type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult, TCTX = void, TICTX = TCTX, TError = unknown> = Omit<QueryResourceOptions<TResult, TRaw>, 'equal' | 'onError' | 'keepPrevious' | 'refresh' | 'cache'> & {
676
866
  /**
@@ -705,10 +895,29 @@ type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult, TCTX
705
895
  equal?: ValueEqualityFn<TMutation>;
706
896
  };
707
897
  /**
708
- * Represents a mutation resource created by `mutationResource`. Extends `QueryResourceRef`
709
- * but removes methods that don't make sense for mutations (like `prefetch`, `value`, etc.).
898
+ * Layer 2 (mutation): default options for every `mutationResource`, inheriting + overriding the
899
+ * common defaults from `provideResourceOptions`. Per-call options override these in turn.
900
+ */
901
+ declare function provideMutationResourceOptions(valueOrFn: Partial<MutationResourceOptions<any, any, any, any, any, any>> | (() => Partial<MutationResourceOptions<any, any, any, any, any, any>>)): Provider;
902
+ /**
903
+ * Represents a mutation resource created by `mutationResource`. Extends
904
+ * `QueryResourceRef` but strips methods that don't make sense for one-off
905
+ * writes (`prefetch`, `value`, `hasValue`, `set`, `update`) and adds `mutate()`
906
+ * for triggering a mutation plus `current()` for tracking the in-flight value.
710
907
  *
711
908
  * @typeParam TResult - The type of the expected result from the mutation.
909
+ *
910
+ * @example
911
+ * ```ts
912
+ * const updateUser = mutationResource<User, User, Partial<User>>(...);
913
+ *
914
+ * effect(() => console.log('mutating:', updateUser.current()));
915
+ * effect(() => {
916
+ * if (updateUser.status() === 'error') toast.error(updateUser.error());
917
+ * });
918
+ *
919
+ * updateUser.mutate({ name: 'Alice' });
920
+ * ```
712
921
  */
713
922
  type MutationResourceRef<TResult, TMutation = TResult, TICTX = void> = Omit<QueryResourceRef<TResult>, 'prefetch' | 'value' | 'hasValue' | 'set' | 'update'> & {
714
923
  /**
@@ -742,8 +951,38 @@ type MutationResourceRef<TResult, TMutation = TResult, TICTX = void> = Omit<Quer
742
951
  * @typeParam TMethod - The HTTP method to be used for the mutation (defaults to `HttpResourceRequest['method']`).
743
952
  * @returns A `MutationResourceRef` instance, which provides methods for triggering the mutation
744
953
  * and observing its status.
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * // Basic PATCH mutation
958
+ * const updateUser = mutationResource<User, User, Partial<User>>(
959
+ * (body) => ({ url: `/api/users/${userId()}`, method: 'PATCH', body }),
960
+ * {
961
+ * onSuccess: (saved) => toast.success(`Updated ${saved.name}`),
962
+ * onError: (err) => toast.error(err),
963
+ * },
964
+ * );
965
+ *
966
+ * updateUser.mutate({ name: 'Alice' });
967
+ * ```
968
+ *
969
+ * @example
970
+ * ```ts
971
+ * // Optimistic update with rollback via the `ctx` returned from `onMutate`
972
+ * const updateUser = mutationResource<User, User, Partial<User>, { prev: User | null }>(
973
+ * (body) => ({ url: `/api/users/${userId()}`, method: 'PATCH', body }),
974
+ * {
975
+ * onMutate: (patch) => {
976
+ * const prev = current();
977
+ * current.update((u) => (u ? { ...u, ...patch } : u));
978
+ * return { prev };
979
+ * },
980
+ * onError: (_err, { prev }) => current.set(prev),
981
+ * },
982
+ * );
983
+ * ```
745
984
  */
746
- declare function mutationResource<TResult, TRaw = TResult, TMutation = TResult, TCTX = void, TICTX = TCTX, TMethod extends HttpResourceRequest['method'] = HttpResourceRequest['method']>(request: (params: TMutation) => Omit<NextRequest<TMethod, TMutation>, 'body'> | undefined | void, options?: MutationResourceOptions<TResult, TRaw, TMutation, TCTX, TICTX>): MutationResourceRef<TResult, TMutation, TICTX>;
985
+ declare function mutationResource<TResult, TRaw = TResult, TMutation = TResult, TCTX = void, TICTX = TCTX, TMethod extends HttpResourceRequest['method'] = HttpResourceRequest['method']>(request: (params: TMutation) => Omit<NextRequest<TMethod, TMutation>, 'body'> | undefined | void, options0?: MutationResourceOptions<TResult, TRaw, TMutation, TCTX, TICTX>): MutationResourceRef<TResult, TMutation, TICTX>;
747
986
 
748
- export { Cache, createCacheInterceptor, createCircuitBreaker, createDedupeRequestsInterceptor, injectQueryCache, manualQueryResource, mutationResource, noDedupe, provideCircuitBreakerDefaultOptions, provideQueryCache, queryResource };
749
- export type { DisabledReason, ManualQueryResourceRef, MutationResourceOptions, MutationResourceRef, QueryResourceOptions, QueryResourceRef };
987
+ export { Cache, PAUSED, applyResourceRegistration, createCacheInterceptor, createCircuitBreaker, createDedupeRequestsInterceptor, injectQueryCache, injectResourceOptions, manualQueryResource, mutationResource, noDedupe, provideCircuitBreakerDefaultOptions, provideMutationResourceOptions, provideQueryCache, provideQueryResourceOptions, provideResourceOptions, provideTypedResourceOptions, queryResource };
988
+ export type { CommonResourceOptions, DisabledReason, ManualQueryResourceRef, MutationResourceOptions, MutationResourceRef, QueryResourceOptions, QueryResourceRef, RequestContext, ResourceRequestFn, TransitionRegistration };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/resource",
3
- "version": "20.7.0",
3
+ "version": "20.8.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "homepage": "https://github.com/mihajm/mmstack/blob/master/packages/resource",
19
19
  "dependencies": {
20
- "@mmstack/primitives": "^20.5.0",
20
+ "@mmstack/primitives": "^20.6.0",
21
21
  "tslib": "^2.3.0"
22
22
  },
23
23
  "peerDependencies": {