@spoosh/angular 0.5.0 → 0.6.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @spoosh/angular
2
2
 
3
- Angular signals integration for Spoosh - `injectRead`, `injectLazyRead`, `injectWrite`, and `injectInfiniteRead`.
3
+ Angular signals integration for Spoosh - `injectRead`, `injectWrite`, and `injectInfiniteRead`.
4
4
 
5
5
  **[Documentation](https://spoosh.dev/docs/integrations/angular)** · **Requirements:** TypeScript >= 5.0, Angular >= 16.0
6
6
 
@@ -23,7 +23,7 @@ const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
23
23
  cachePlugin({ staleTime: 5000 }),
24
24
  ]);
25
25
 
26
- export const { injectRead, injectLazyRead, injectWrite, injectInfiniteRead } =
26
+ export const { injectRead, injectWrite, injectInfiniteRead } =
27
27
  createAngularSpoosh(spoosh);
28
28
  ```
29
29
 
@@ -74,28 +74,6 @@ export class UserListComponent {
74
74
  }
75
75
  ```
76
76
 
77
- ### injectLazyRead
78
-
79
- Lazy data fetching for print/download/export scenarios. Does not auto-fetch on mount.
80
-
81
- ```typescript
82
- @Component({
83
- template: `
84
- <button (click)="handlePrint('123')" [disabled]="order.loading()">
85
- {{ order.loading() ? "Loading..." : "Print" }}
86
- </button>
87
- `,
88
- })
89
- export class PrintOrderComponent {
90
- order = injectLazyRead((api) => api("orders/:id").GET);
91
-
92
- async handlePrint(orderId: string) {
93
- const { data } = await this.order.trigger({ params: { id: orderId } });
94
- if (data) this.printService.printReceipt(data);
95
- }
96
- }
97
- ```
98
-
99
77
  ### injectWrite
100
78
 
101
79
  Trigger mutations with loading and error states.
package/dist/index.d.mts CHANGED
@@ -17,14 +17,21 @@ type EnabledOption = boolean | (() => boolean);
17
17
  interface BaseReadOptions extends TagOptions {
18
18
  enabled?: EnabledOption;
19
19
  }
20
- interface BaseReadResult<TData, TError, TPluginResult = Record<string, unknown>> {
20
+ interface BaseReadResult<TData, TError, TPluginResult = Record<string, unknown>, TTriggerOptions = {
21
+ force?: boolean;
22
+ }> {
21
23
  data: Signal<TData | undefined>;
22
24
  error: Signal<TError | undefined>;
23
25
  loading: Signal<boolean>;
24
26
  fetching: Signal<boolean>;
25
27
  meta: Signal<TPluginResult>;
26
28
  abort: () => void;
27
- trigger: () => Promise<SpooshResponse<TData, TError>>;
29
+ /**
30
+ * Manually trigger a fetch.
31
+ *
32
+ * @param options - Optional override options (query, body, params) to use for this specific request
33
+ */
34
+ trigger: (options?: TTriggerOptions) => Promise<SpooshResponse<TData, TError>>;
28
35
  }
29
36
  interface BaseWriteResult<TData, TError, TOptions, TPluginResult = Record<string, unknown>> {
30
37
  trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
@@ -34,13 +41,6 @@ interface BaseWriteResult<TData, TError, TOptions, TPluginResult = Record<string
34
41
  meta: Signal<TPluginResult>;
35
42
  abort: () => void;
36
43
  }
37
- interface BaseLazyReadResult<TData, TError, TOptions> {
38
- trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
39
- data: Signal<TData | undefined>;
40
- error: Signal<TError | undefined>;
41
- loading: Signal<boolean>;
42
- abort: () => void;
43
- }
44
44
  type PageContext<TData, TRequest> = {
45
45
  response: TData | undefined;
46
46
  allResponses: TData[];
@@ -147,6 +147,33 @@ type ExtractResponseParamNames<T> = SuccessReturnType<T> extends {
147
147
  params: Record<infer K, unknown>;
148
148
  };
149
149
  } ? K extends string ? K : never : never;
150
+ type AwaitedReturnTypeTrigger<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
151
+ type ExtractInputFromResponse<T> = T extends {
152
+ input: infer I;
153
+ } ? I : never;
154
+ type ExtractTriggerQuery<I> = I extends {
155
+ query: infer Q;
156
+ } ? {
157
+ query?: Q;
158
+ } : unknown;
159
+ type ExtractTriggerBody<I> = I extends {
160
+ body: infer B;
161
+ } ? {
162
+ body?: B;
163
+ } : unknown;
164
+ type ExtractTriggerParams<I> = I extends {
165
+ params: infer P;
166
+ } ? {
167
+ params?: P;
168
+ } : unknown;
169
+ type TriggerOptions<T> = ExtractInputFromResponse<AwaitedReturnTypeTrigger<T>> extends infer I ? [I] extends [never] ? {
170
+ force?: boolean;
171
+ } : ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> & {
172
+ /** Force refetch even if data is cached */
173
+ force?: boolean;
174
+ } : {
175
+ force?: boolean;
176
+ };
150
177
  type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
151
178
  query: infer Q;
152
179
  } ? Q : never;
@@ -154,23 +181,6 @@ type ExtractMethodBody<T> = ExtractMethodOptions<T> extends {
154
181
  body: infer B;
155
182
  } ? B : never;
156
183
 
157
- declare function createInjectLazyRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod) => BaseLazyReadResult<TMethod extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
158
- data: unknown;
159
- error?: undefined;
160
- }> extends {
161
- data: infer D;
162
- } ? D : unknown : unknown, [TMethod extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
163
- error: unknown;
164
- data?: undefined;
165
- }> extends {
166
- error: infer E;
167
- } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TMethod extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
168
- error: unknown;
169
- data?: undefined;
170
- }> extends {
171
- error: infer E;
172
- } ? E : unknown : unknown, TMethod extends (...args: infer A) => unknown ? A[0] extends object ? Pick<A[0], Extract<keyof A[0], "query" | "body" | "params">> : object : object> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
173
-
174
184
  type AnyInfiniteRequestOptions = InfiniteRequestOptions;
175
185
  declare function createInjectInfiniteRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TReadFn extends (api: ReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends AnyInfiniteRequestOptions = AnyInfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BaseInfiniteReadOptions<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
176
186
  data: unknown;
@@ -336,15 +346,14 @@ declare function createInjectRead<TSchema, TDefaultError, TPlugins extends reado
336
346
  readResult: infer R_2;
337
347
  } ? R_2 : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
338
348
  readResult: infer R_2;
339
- } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, TReadOpts>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
349
+ } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, TReadOpts>, TriggerOptions<TReadFn>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
340
350
 
341
351
  type SpooshAngularFunctions<TDefaultError, TSchema, TPlugins extends PluginArray> = {
342
352
  injectRead: ReturnType<typeof createInjectRead<TSchema, TDefaultError, TPlugins>>;
343
353
  injectWrite: ReturnType<typeof createInjectWrite<TSchema, TDefaultError, TPlugins>>;
344
354
  injectInfiniteRead: ReturnType<typeof createInjectInfiniteRead<TSchema, TDefaultError, TPlugins>>;
345
- injectLazyRead: ReturnType<typeof createInjectLazyRead<TSchema, TDefaultError, TPlugins>>;
346
355
  } & MergePluginInstanceApi<TPlugins, TSchema>;
347
356
 
348
357
  declare function createAngularSpoosh<TSchema, TDefaultError, TPlugins extends PluginArray, TApi>(instance: SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins>): SpooshAngularFunctions<TDefaultError, TSchema, TPlugins>;
349
358
 
350
- export { type AngularOptionsMap, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseLazyReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type PageContext, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type WriteApiClient, type WriteResponseInputFields, createAngularSpoosh };
359
+ export { type AngularOptionsMap, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type PageContext, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type TriggerOptions, type WriteApiClient, type WriteResponseInputFields, createAngularSpoosh };
package/dist/index.d.ts CHANGED
@@ -17,14 +17,21 @@ type EnabledOption = boolean | (() => boolean);
17
17
  interface BaseReadOptions extends TagOptions {
18
18
  enabled?: EnabledOption;
19
19
  }
20
- interface BaseReadResult<TData, TError, TPluginResult = Record<string, unknown>> {
20
+ interface BaseReadResult<TData, TError, TPluginResult = Record<string, unknown>, TTriggerOptions = {
21
+ force?: boolean;
22
+ }> {
21
23
  data: Signal<TData | undefined>;
22
24
  error: Signal<TError | undefined>;
23
25
  loading: Signal<boolean>;
24
26
  fetching: Signal<boolean>;
25
27
  meta: Signal<TPluginResult>;
26
28
  abort: () => void;
27
- trigger: () => Promise<SpooshResponse<TData, TError>>;
29
+ /**
30
+ * Manually trigger a fetch.
31
+ *
32
+ * @param options - Optional override options (query, body, params) to use for this specific request
33
+ */
34
+ trigger: (options?: TTriggerOptions) => Promise<SpooshResponse<TData, TError>>;
28
35
  }
29
36
  interface BaseWriteResult<TData, TError, TOptions, TPluginResult = Record<string, unknown>> {
30
37
  trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
@@ -34,13 +41,6 @@ interface BaseWriteResult<TData, TError, TOptions, TPluginResult = Record<string
34
41
  meta: Signal<TPluginResult>;
35
42
  abort: () => void;
36
43
  }
37
- interface BaseLazyReadResult<TData, TError, TOptions> {
38
- trigger: (options?: TOptions) => Promise<SpooshResponse<TData, TError>>;
39
- data: Signal<TData | undefined>;
40
- error: Signal<TError | undefined>;
41
- loading: Signal<boolean>;
42
- abort: () => void;
43
- }
44
44
  type PageContext<TData, TRequest> = {
45
45
  response: TData | undefined;
46
46
  allResponses: TData[];
@@ -147,6 +147,33 @@ type ExtractResponseParamNames<T> = SuccessReturnType<T> extends {
147
147
  params: Record<infer K, unknown>;
148
148
  };
149
149
  } ? K extends string ? K : never : never;
150
+ type AwaitedReturnTypeTrigger<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
151
+ type ExtractInputFromResponse<T> = T extends {
152
+ input: infer I;
153
+ } ? I : never;
154
+ type ExtractTriggerQuery<I> = I extends {
155
+ query: infer Q;
156
+ } ? {
157
+ query?: Q;
158
+ } : unknown;
159
+ type ExtractTriggerBody<I> = I extends {
160
+ body: infer B;
161
+ } ? {
162
+ body?: B;
163
+ } : unknown;
164
+ type ExtractTriggerParams<I> = I extends {
165
+ params: infer P;
166
+ } ? {
167
+ params?: P;
168
+ } : unknown;
169
+ type TriggerOptions<T> = ExtractInputFromResponse<AwaitedReturnTypeTrigger<T>> extends infer I ? [I] extends [never] ? {
170
+ force?: boolean;
171
+ } : ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> & {
172
+ /** Force refetch even if data is cached */
173
+ force?: boolean;
174
+ } : {
175
+ force?: boolean;
176
+ };
150
177
  type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
151
178
  query: infer Q;
152
179
  } ? Q : never;
@@ -154,23 +181,6 @@ type ExtractMethodBody<T> = ExtractMethodOptions<T> extends {
154
181
  body: infer B;
155
182
  } ? B : never;
156
183
 
157
- declare function createInjectLazyRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TMethod extends (...args: never[]) => Promise<SpooshResponse<unknown, unknown>>>(readFn: (api: ReadApiClient<TSchema, TDefaultError>) => TMethod) => BaseLazyReadResult<TMethod extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
158
- data: unknown;
159
- error?: undefined;
160
- }> extends {
161
- data: infer D;
162
- } ? D : unknown : unknown, [TMethod extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
163
- error: unknown;
164
- data?: undefined;
165
- }> extends {
166
- error: infer E;
167
- } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TMethod extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
168
- error: unknown;
169
- data?: undefined;
170
- }> extends {
171
- error: infer E;
172
- } ? E : unknown : unknown, TMethod extends (...args: infer A) => unknown ? A[0] extends object ? Pick<A[0], Extract<keyof A[0], "query" | "body" | "params">> : object : object> & WriteResponseInputFields<ExtractResponseQuery<TMethod>, ExtractResponseBody<TMethod>, ExtractResponseParamNames<TMethod>>;
173
-
174
184
  type AnyInfiniteRequestOptions = InfiniteRequestOptions;
175
185
  declare function createInjectInfiniteRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TReadFn extends (api: ReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends AnyInfiniteRequestOptions = AnyInfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BaseInfiniteReadOptions<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
176
186
  data: unknown;
@@ -336,15 +346,14 @@ declare function createInjectRead<TSchema, TDefaultError, TPlugins extends reado
336
346
  readResult: infer R_2;
337
347
  } ? R_2 : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
338
348
  readResult: infer R_2;
339
- } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, TReadOpts>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
349
+ } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, TReadOpts>, TriggerOptions<TReadFn>> & ResponseInputFields<ExtractResponseQuery<TReadFn>, ExtractResponseBody<TReadFn>, ExtractResponseParamNames<TReadFn>>;
340
350
 
341
351
  type SpooshAngularFunctions<TDefaultError, TSchema, TPlugins extends PluginArray> = {
342
352
  injectRead: ReturnType<typeof createInjectRead<TSchema, TDefaultError, TPlugins>>;
343
353
  injectWrite: ReturnType<typeof createInjectWrite<TSchema, TDefaultError, TPlugins>>;
344
354
  injectInfiniteRead: ReturnType<typeof createInjectInfiniteRead<TSchema, TDefaultError, TPlugins>>;
345
- injectLazyRead: ReturnType<typeof createInjectLazyRead<TSchema, TDefaultError, TPlugins>>;
346
355
  } & MergePluginInstanceApi<TPlugins, TSchema>;
347
356
 
348
357
  declare function createAngularSpoosh<TSchema, TDefaultError, TPlugins extends PluginArray, TApi>(instance: SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins>): SpooshAngularFunctions<TDefaultError, TSchema, TPlugins>;
349
358
 
350
- export { type AngularOptionsMap, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseLazyReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type PageContext, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type WriteApiClient, type WriteResponseInputFields, createAngularSpoosh };
359
+ export { type AngularOptionsMap, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type PageContext, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type TriggerOptions, type WriteApiClient, type WriteResponseInputFields, createAngularSpoosh };
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ function createInjectRead(options) {
45
45
  const metaSignal = (0, import_core.signal)({});
46
46
  let currentController = null;
47
47
  let currentQueryKey = null;
48
+ let baseQueryKey = null;
48
49
  let currentSubscription = null;
49
50
  let currentResolvedTags = [];
50
51
  let prevContext = null;
@@ -99,12 +100,16 @@ function createInjectRead(options) {
99
100
  currentResolvedTags = resolvedTags;
100
101
  return controller;
101
102
  };
102
- const executeWithTracking = async (controller, force = false) => {
103
+ const executeWithTracking = async (controller, force = false, overrideOptions) => {
103
104
  const hasData = dataSignal() !== void 0;
104
105
  loadingSignal.set(!hasData);
105
106
  fetchingSignal.set(true);
106
107
  try {
107
- const response = await controller.execute(void 0, { force });
108
+ const execOptions = overrideOptions ? {
109
+ ...currentController?.getContext().requestOptions,
110
+ ...overrideOptions
111
+ } : void 0;
112
+ const response = await controller.execute(execOptions, { force });
108
113
  if (response.error) {
109
114
  errorSignal.set(response.error);
110
115
  } else {
@@ -145,6 +150,7 @@ function createInjectRead(options) {
145
150
  options: initialCapturedCall.options
146
151
  });
147
152
  createController(initialCapturedCall, initialResolvedTags, initialQueryKey);
153
+ baseQueryKey = initialQueryKey;
148
154
  loadingSignal.set(false);
149
155
  let wasEnabled = false;
150
156
  (0, import_core.effect)(
@@ -181,10 +187,11 @@ function createInjectRead(options) {
181
187
  inputInner.params = opts.params;
182
188
  }
183
189
  inputSignal.set(inputInner);
184
- const queryKeyChanged = queryKey !== currentQueryKey;
190
+ const baseQueryKeyChanged = queryKey !== baseQueryKey;
185
191
  const enabledChanged = isEnabled !== wasEnabled;
186
192
  wasEnabled = isEnabled;
187
- if (queryKeyChanged) {
193
+ if (baseQueryKeyChanged) {
194
+ baseQueryKey = queryKey;
188
195
  if (currentController) {
189
196
  prevContext = currentController.getContext();
190
197
  if (isMounted) {
@@ -267,15 +274,47 @@ function createInjectRead(options) {
267
274
  const abort = () => {
268
275
  currentController?.abort();
269
276
  };
270
- const trigger = () => {
271
- if (currentController) {
272
- if (!isMounted) {
273
- currentController.mount();
274
- isMounted = true;
277
+ const trigger = async (triggerOptions) => {
278
+ const { force = false, ...overrideOptions } = triggerOptions ?? {};
279
+ const hasOverrides = Object.keys(overrideOptions).length > 0;
280
+ if (!hasOverrides) {
281
+ if (!currentController) {
282
+ return Promise.resolve({ data: void 0, error: void 0 });
275
283
  }
276
- return executeWithTracking(currentController, true);
284
+ return executeWithTracking(currentController, force, void 0);
285
+ }
286
+ const selectorResult = captureSelector();
287
+ const capturedCall = selectorResult.call;
288
+ if (!capturedCall) {
289
+ return Promise.resolve({ data: void 0, error: void 0 });
290
+ }
291
+ const mergedOptions = {
292
+ ...capturedCall.options ?? {},
293
+ ...overrideOptions
294
+ };
295
+ const pathSegments = capturedCall.path.split("/").filter(Boolean);
296
+ const newQueryKey = stateManager.createQueryKey({
297
+ path: pathSegments,
298
+ method: capturedCall.method,
299
+ options: mergedOptions
300
+ });
301
+ if (newQueryKey === currentQueryKey && currentController) {
302
+ return executeWithTracking(currentController, force, overrideOptions);
277
303
  }
278
- return Promise.resolve({ data: void 0, error: void 0 });
304
+ const params = mergedOptions?.params;
305
+ const newResolvedPath = (0, import_core2.resolvePath)(pathSegments, params);
306
+ const newResolvedTags = (0, import_core2.resolveTags)(
307
+ tags !== void 0 ? { tags } : void 0,
308
+ newResolvedPath
309
+ );
310
+ const newController = createController(
311
+ { ...capturedCall, options: mergedOptions },
312
+ newResolvedTags,
313
+ newQueryKey
314
+ );
315
+ newController.mount();
316
+ isMounted = true;
317
+ return executeWithTracking(newController, force, void 0);
279
318
  };
280
319
  const result = {
281
320
  meta: metaSignal,
@@ -761,130 +800,6 @@ function createInjectInfiniteRead(options) {
761
800
  };
762
801
  }
763
802
 
764
- // src/injectLazyRead/index.ts
765
- var import_core7 = require("@angular/core");
766
- var import_core8 = require("@spoosh/core");
767
- function createInjectLazyRead(options) {
768
- const { api, stateManager, pluginExecutor, eventEmitter } = options;
769
- return function injectLazyRead(readFn) {
770
- const destroyRef = (0, import_core7.inject)(import_core7.DestroyRef);
771
- const captureSelector = () => {
772
- const selectorResult = {
773
- call: null,
774
- selector: null
775
- };
776
- const selectorProxy = (0, import_core8.createSelectorProxy)(
777
- (result2) => {
778
- selectorResult.call = result2.call;
779
- selectorResult.selector = result2.selector;
780
- }
781
- );
782
- readFn(selectorProxy);
783
- if (!selectorResult.selector) {
784
- throw new Error(
785
- 'injectLazyRead requires selecting an HTTP method (GET). Example: injectLazyRead((api) => api("posts").GET)'
786
- );
787
- }
788
- return selectorResult.selector;
789
- };
790
- const hookId = `angular-${Math.random().toString(36).slice(2)}`;
791
- let currentQueryKey = null;
792
- let currentController = null;
793
- let currentSubscription = null;
794
- const dataSignal = (0, import_core7.signal)(void 0);
795
- const errorSignal = (0, import_core7.signal)(void 0);
796
- const loadingSignal = (0, import_core7.signal)(false);
797
- const lastTriggerOptionsSignal = (0, import_core7.signal)(void 0);
798
- const inputSignal = (0, import_core7.signal)({});
799
- destroyRef.onDestroy(() => {
800
- if (currentSubscription) {
801
- currentSubscription();
802
- }
803
- });
804
- const abort = () => {
805
- currentController?.abort();
806
- };
807
- const trigger = async (triggerOptions) => {
808
- const selectedEndpoint = captureSelector();
809
- const params = triggerOptions?.params;
810
- const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
811
- (0, import_core8.resolvePath)(pathSegments, params);
812
- const queryKey = stateManager.createQueryKey({
813
- path: pathSegments,
814
- method: selectedEndpoint.method,
815
- options: triggerOptions
816
- });
817
- const needsNewController = !currentController || currentQueryKey !== queryKey;
818
- if (needsNewController) {
819
- if (currentSubscription) {
820
- currentSubscription();
821
- }
822
- const controller = (0, import_core8.createOperationController)({
823
- operationType: "read",
824
- path: pathSegments,
825
- method: selectedEndpoint.method,
826
- tags: [],
827
- stateManager,
828
- eventEmitter,
829
- pluginExecutor,
830
- hookId,
831
- requestOptions: triggerOptions,
832
- fetchFn: async (fetchOpts) => {
833
- const pathMethods = api(selectedEndpoint.path);
834
- const method = pathMethods[selectedEndpoint.method];
835
- return method(fetchOpts);
836
- }
837
- });
838
- currentSubscription = controller.subscribe(() => {
839
- const state = controller.getState();
840
- dataSignal.set(state.data);
841
- errorSignal.set(state.error);
842
- });
843
- currentController = controller;
844
- currentQueryKey = queryKey;
845
- }
846
- lastTriggerOptionsSignal.set(triggerOptions);
847
- const opts = triggerOptions;
848
- const inputInner = {};
849
- if (opts?.query !== void 0) {
850
- inputInner.query = opts.query;
851
- }
852
- if (opts?.body !== void 0) {
853
- inputInner.body = opts.body;
854
- }
855
- if (opts?.params !== void 0) {
856
- inputInner.params = opts.params;
857
- }
858
- inputSignal.set(inputInner);
859
- loadingSignal.set(true);
860
- currentController.setPluginOptions(triggerOptions);
861
- try {
862
- const response = await currentController.execute(triggerOptions);
863
- if (response.error) {
864
- errorSignal.set(response.error);
865
- } else {
866
- errorSignal.set(void 0);
867
- }
868
- return response;
869
- } catch (err) {
870
- errorSignal.set(err);
871
- throw err;
872
- } finally {
873
- loadingSignal.set(false);
874
- }
875
- };
876
- const result = {
877
- trigger,
878
- input: inputSignal,
879
- data: dataSignal,
880
- error: errorSignal,
881
- loading: loadingSignal,
882
- abort
883
- };
884
- return result;
885
- };
886
- }
887
-
888
803
  // src/createAngularSpoosh/index.ts
889
804
  function createAngularSpoosh(instance) {
890
805
  const { api, stateManager, eventEmitter, pluginExecutor } = instance;
@@ -906,14 +821,6 @@ function createAngularSpoosh(instance) {
906
821
  eventEmitter,
907
822
  pluginExecutor
908
823
  });
909
- const injectLazyRead = createInjectLazyRead(
910
- {
911
- api,
912
- stateManager,
913
- eventEmitter,
914
- pluginExecutor
915
- }
916
- );
917
824
  const instanceApiContext = {
918
825
  api,
919
826
  stateManager,
@@ -934,7 +841,6 @@ function createAngularSpoosh(instance) {
934
841
  injectRead,
935
842
  injectWrite,
936
843
  injectInfiniteRead,
937
- injectLazyRead,
938
844
  ...instanceApis
939
845
  };
940
846
  }
package/dist/index.mjs CHANGED
@@ -30,6 +30,7 @@ function createInjectRead(options) {
30
30
  const metaSignal = signal({});
31
31
  let currentController = null;
32
32
  let currentQueryKey = null;
33
+ let baseQueryKey = null;
33
34
  let currentSubscription = null;
34
35
  let currentResolvedTags = [];
35
36
  let prevContext = null;
@@ -84,12 +85,16 @@ function createInjectRead(options) {
84
85
  currentResolvedTags = resolvedTags;
85
86
  return controller;
86
87
  };
87
- const executeWithTracking = async (controller, force = false) => {
88
+ const executeWithTracking = async (controller, force = false, overrideOptions) => {
88
89
  const hasData = dataSignal() !== void 0;
89
90
  loadingSignal.set(!hasData);
90
91
  fetchingSignal.set(true);
91
92
  try {
92
- const response = await controller.execute(void 0, { force });
93
+ const execOptions = overrideOptions ? {
94
+ ...currentController?.getContext().requestOptions,
95
+ ...overrideOptions
96
+ } : void 0;
97
+ const response = await controller.execute(execOptions, { force });
93
98
  if (response.error) {
94
99
  errorSignal.set(response.error);
95
100
  } else {
@@ -130,6 +135,7 @@ function createInjectRead(options) {
130
135
  options: initialCapturedCall.options
131
136
  });
132
137
  createController(initialCapturedCall, initialResolvedTags, initialQueryKey);
138
+ baseQueryKey = initialQueryKey;
133
139
  loadingSignal.set(false);
134
140
  let wasEnabled = false;
135
141
  effect(
@@ -166,10 +172,11 @@ function createInjectRead(options) {
166
172
  inputInner.params = opts.params;
167
173
  }
168
174
  inputSignal.set(inputInner);
169
- const queryKeyChanged = queryKey !== currentQueryKey;
175
+ const baseQueryKeyChanged = queryKey !== baseQueryKey;
170
176
  const enabledChanged = isEnabled !== wasEnabled;
171
177
  wasEnabled = isEnabled;
172
- if (queryKeyChanged) {
178
+ if (baseQueryKeyChanged) {
179
+ baseQueryKey = queryKey;
173
180
  if (currentController) {
174
181
  prevContext = currentController.getContext();
175
182
  if (isMounted) {
@@ -252,15 +259,47 @@ function createInjectRead(options) {
252
259
  const abort = () => {
253
260
  currentController?.abort();
254
261
  };
255
- const trigger = () => {
256
- if (currentController) {
257
- if (!isMounted) {
258
- currentController.mount();
259
- isMounted = true;
262
+ const trigger = async (triggerOptions) => {
263
+ const { force = false, ...overrideOptions } = triggerOptions ?? {};
264
+ const hasOverrides = Object.keys(overrideOptions).length > 0;
265
+ if (!hasOverrides) {
266
+ if (!currentController) {
267
+ return Promise.resolve({ data: void 0, error: void 0 });
260
268
  }
261
- return executeWithTracking(currentController, true);
269
+ return executeWithTracking(currentController, force, void 0);
270
+ }
271
+ const selectorResult = captureSelector();
272
+ const capturedCall = selectorResult.call;
273
+ if (!capturedCall) {
274
+ return Promise.resolve({ data: void 0, error: void 0 });
262
275
  }
263
- return Promise.resolve({ data: void 0, error: void 0 });
276
+ const mergedOptions = {
277
+ ...capturedCall.options ?? {},
278
+ ...overrideOptions
279
+ };
280
+ const pathSegments = capturedCall.path.split("/").filter(Boolean);
281
+ const newQueryKey = stateManager.createQueryKey({
282
+ path: pathSegments,
283
+ method: capturedCall.method,
284
+ options: mergedOptions
285
+ });
286
+ if (newQueryKey === currentQueryKey && currentController) {
287
+ return executeWithTracking(currentController, force, overrideOptions);
288
+ }
289
+ const params = mergedOptions?.params;
290
+ const newResolvedPath = resolvePath(pathSegments, params);
291
+ const newResolvedTags = resolveTags(
292
+ tags !== void 0 ? { tags } : void 0,
293
+ newResolvedPath
294
+ );
295
+ const newController = createController(
296
+ { ...capturedCall, options: mergedOptions },
297
+ newResolvedTags,
298
+ newQueryKey
299
+ );
300
+ newController.mount();
301
+ isMounted = true;
302
+ return executeWithTracking(newController, force, void 0);
264
303
  };
265
304
  const result = {
266
305
  meta: metaSignal,
@@ -763,134 +802,6 @@ function createInjectInfiniteRead(options) {
763
802
  };
764
803
  }
765
804
 
766
- // src/injectLazyRead/index.ts
767
- import { signal as signal4, DestroyRef as DestroyRef4, inject as inject4 } from "@angular/core";
768
- import {
769
- createOperationController as createOperationController3,
770
- createSelectorProxy as createSelectorProxy4,
771
- resolvePath as resolvePath4
772
- } from "@spoosh/core";
773
- function createInjectLazyRead(options) {
774
- const { api, stateManager, pluginExecutor, eventEmitter } = options;
775
- return function injectLazyRead(readFn) {
776
- const destroyRef = inject4(DestroyRef4);
777
- const captureSelector = () => {
778
- const selectorResult = {
779
- call: null,
780
- selector: null
781
- };
782
- const selectorProxy = createSelectorProxy4(
783
- (result2) => {
784
- selectorResult.call = result2.call;
785
- selectorResult.selector = result2.selector;
786
- }
787
- );
788
- readFn(selectorProxy);
789
- if (!selectorResult.selector) {
790
- throw new Error(
791
- 'injectLazyRead requires selecting an HTTP method (GET). Example: injectLazyRead((api) => api("posts").GET)'
792
- );
793
- }
794
- return selectorResult.selector;
795
- };
796
- const hookId = `angular-${Math.random().toString(36).slice(2)}`;
797
- let currentQueryKey = null;
798
- let currentController = null;
799
- let currentSubscription = null;
800
- const dataSignal = signal4(void 0);
801
- const errorSignal = signal4(void 0);
802
- const loadingSignal = signal4(false);
803
- const lastTriggerOptionsSignal = signal4(void 0);
804
- const inputSignal = signal4({});
805
- destroyRef.onDestroy(() => {
806
- if (currentSubscription) {
807
- currentSubscription();
808
- }
809
- });
810
- const abort = () => {
811
- currentController?.abort();
812
- };
813
- const trigger = async (triggerOptions) => {
814
- const selectedEndpoint = captureSelector();
815
- const params = triggerOptions?.params;
816
- const pathSegments = selectedEndpoint.path.split("/").filter(Boolean);
817
- resolvePath4(pathSegments, params);
818
- const queryKey = stateManager.createQueryKey({
819
- path: pathSegments,
820
- method: selectedEndpoint.method,
821
- options: triggerOptions
822
- });
823
- const needsNewController = !currentController || currentQueryKey !== queryKey;
824
- if (needsNewController) {
825
- if (currentSubscription) {
826
- currentSubscription();
827
- }
828
- const controller = createOperationController3({
829
- operationType: "read",
830
- path: pathSegments,
831
- method: selectedEndpoint.method,
832
- tags: [],
833
- stateManager,
834
- eventEmitter,
835
- pluginExecutor,
836
- hookId,
837
- requestOptions: triggerOptions,
838
- fetchFn: async (fetchOpts) => {
839
- const pathMethods = api(selectedEndpoint.path);
840
- const method = pathMethods[selectedEndpoint.method];
841
- return method(fetchOpts);
842
- }
843
- });
844
- currentSubscription = controller.subscribe(() => {
845
- const state = controller.getState();
846
- dataSignal.set(state.data);
847
- errorSignal.set(state.error);
848
- });
849
- currentController = controller;
850
- currentQueryKey = queryKey;
851
- }
852
- lastTriggerOptionsSignal.set(triggerOptions);
853
- const opts = triggerOptions;
854
- const inputInner = {};
855
- if (opts?.query !== void 0) {
856
- inputInner.query = opts.query;
857
- }
858
- if (opts?.body !== void 0) {
859
- inputInner.body = opts.body;
860
- }
861
- if (opts?.params !== void 0) {
862
- inputInner.params = opts.params;
863
- }
864
- inputSignal.set(inputInner);
865
- loadingSignal.set(true);
866
- currentController.setPluginOptions(triggerOptions);
867
- try {
868
- const response = await currentController.execute(triggerOptions);
869
- if (response.error) {
870
- errorSignal.set(response.error);
871
- } else {
872
- errorSignal.set(void 0);
873
- }
874
- return response;
875
- } catch (err) {
876
- errorSignal.set(err);
877
- throw err;
878
- } finally {
879
- loadingSignal.set(false);
880
- }
881
- };
882
- const result = {
883
- trigger,
884
- input: inputSignal,
885
- data: dataSignal,
886
- error: errorSignal,
887
- loading: loadingSignal,
888
- abort
889
- };
890
- return result;
891
- };
892
- }
893
-
894
805
  // src/createAngularSpoosh/index.ts
895
806
  function createAngularSpoosh(instance) {
896
807
  const { api, stateManager, eventEmitter, pluginExecutor } = instance;
@@ -912,14 +823,6 @@ function createAngularSpoosh(instance) {
912
823
  eventEmitter,
913
824
  pluginExecutor
914
825
  });
915
- const injectLazyRead = createInjectLazyRead(
916
- {
917
- api,
918
- stateManager,
919
- eventEmitter,
920
- pluginExecutor
921
- }
922
- );
923
826
  const instanceApiContext = {
924
827
  api,
925
828
  stateManager,
@@ -940,7 +843,6 @@ function createAngularSpoosh(instance) {
940
843
  injectRead,
941
844
  injectWrite,
942
845
  injectInfiniteRead,
943
- injectLazyRead,
944
846
  ...instanceApis
945
847
  };
946
848
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/angular",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "description": "Angular signals integration for Spoosh API client",
6
6
  "keywords": [