@assebc/ng-signal-http 1.0.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.
@@ -0,0 +1,347 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Signal, InjectionToken, EnvironmentProviders } from '@angular/core';
3
+
4
+ /**
5
+ * Global configuration passed to `provideSignalHttp()`.
6
+ *
7
+ * @example
8
+ * provideSignalHttp({ baseUrl: 'https://api.example.com', timeout: 10_000 });
9
+ */
10
+ interface SignalHttpConfig {
11
+ baseUrl?: string;
12
+ headers?: Record<string, string>;
13
+ timeout?: number;
14
+ interceptors?: HttpInterceptor[];
15
+ }
16
+ /**
17
+ * Hooks that intercept every request, response, or error in the pipeline.
18
+ * All three hooks are optional and may return a `Promise`.
19
+ * Interceptors run in registration order.
20
+ *
21
+ * @example
22
+ * const authInterceptor: HttpInterceptor = {
23
+ * request: async (config) => ({
24
+ * ...config,
25
+ * headers: { ...config.headers, Authorization: `Bearer ${getToken()}` },
26
+ * }),
27
+ * };
28
+ */
29
+ interface HttpInterceptor {
30
+ request?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
31
+ response?: (response: Response) => Response | Promise<Response>;
32
+ error?: (error: Error) => Error | Promise<never>;
33
+ }
34
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
35
+ /**
36
+ * Per-request options. Passed to `SignalHttpClient.executeRequest()` or
37
+ * returned by a `UrlFactory` to provide method and body alongside the URL.
38
+ *
39
+ * @example
40
+ * const config: RequestConfig = {
41
+ * url: '/users/1',
42
+ * method: 'GET',
43
+ * params: { expand: 'address' },
44
+ * };
45
+ */
46
+ interface RequestConfig {
47
+ url: string;
48
+ method: HttpMethod;
49
+ headers?: Record<string, string>;
50
+ body?: unknown;
51
+ params?: Record<string, string | number | boolean>;
52
+ /** Overrides the global timeout for this request. */
53
+ timeout?: number;
54
+ /** Merged with the internal `AbortController` signal. */
55
+ signal?: AbortSignal;
56
+ }
57
+ /**
58
+ * Fine-grained retry configuration for `querySignal`.
59
+ * `AbortError` is never retried regardless of this config.
60
+ *
61
+ * @example
62
+ * const retry: RetryConfig = {
63
+ * count: 3,
64
+ * delay: (attempt) => 1000 * 2 ** (attempt - 1),
65
+ * shouldRetry: (err) => !(err instanceof HttpError && err.isClientError),
66
+ * };
67
+ */
68
+ interface RetryConfig {
69
+ count: number;
70
+ delay?: number | ((attempt: number) => number);
71
+ shouldRetry?: (error: Error, attempt: number) => boolean;
72
+ }
73
+ type HttpClientStatus = 'idle' | 'loading' | 'success' | 'error';
74
+ /**
75
+ * Options for `querySignal()`.
76
+ *
77
+ * @template T - The expected response data type.
78
+ *
79
+ * @example
80
+ * querySignal('/users', { retry: 3, refetchOnFocus: true, staleTime: 60_000 });
81
+ */
82
+ interface HttpClientOptions<T> {
83
+ initialValue?: T;
84
+ lazy?: boolean;
85
+ retry?: number | RetryConfig;
86
+ staleTime?: number;
87
+ refetchInterval?: number;
88
+ refetchOnFocus?: boolean;
89
+ refetchOnReconnect?: boolean;
90
+ onSuccess?: (data: T) => void;
91
+ onError?: (error: Error) => void;
92
+ }
93
+ /**
94
+ * State and control handle returned by `querySignal()`.
95
+ *
96
+ * @template T - The response data type.
97
+ *
98
+ * @example
99
+ * const result = querySignal<User>('/users/1');
100
+ * effect(() => console.log(result.status(), result.data()));
101
+ */
102
+ interface HttpClientResult<T> {
103
+ readonly data: Signal<T | null>;
104
+ readonly loading: Signal<boolean>;
105
+ readonly error: Signal<Error | null>;
106
+ readonly status: Signal<HttpClientStatus>;
107
+ readonly isStale: Signal<boolean>;
108
+ refetch: () => Promise<void>;
109
+ invalidate: () => void;
110
+ reset: () => void;
111
+ }
112
+ type QueryStatus = HttpClientStatus;
113
+ type QueryResult<T> = HttpClientResult<T>;
114
+ type RequestOptions<T> = HttpClientOptions<T>;
115
+ /**
116
+ * Lifecycle callbacks for `mutationSignal()`.
117
+ *
118
+ * @template TInput - The input type passed to `mutate()`.
119
+ * @template TOutput - The response data type.
120
+ *
121
+ * @example
122
+ * mutationSignal(factory, { onSuccess: (data) => toast('Saved!') });
123
+ */
124
+ interface MutationOptions<TInput, TOutput> {
125
+ onSuccess?: (data: TOutput, input: TInput) => void;
126
+ onError?: (error: Error, input: TInput) => void;
127
+ onSettled?: (data: TOutput | null, error: Error | null, input: TInput) => void;
128
+ }
129
+ /**
130
+ * State and control handle returned by `mutationSignal()`.
131
+ *
132
+ * @template TInput - The input type passed to `mutate()`.
133
+ * @template TOutput - The response data type.
134
+ *
135
+ * @example
136
+ * const create = mutationSignal<NewUser, User>(factory);
137
+ * await create.mutate({ name: 'Alice' });
138
+ */
139
+ interface MutationResult<TInput, TOutput> {
140
+ readonly isPending: Signal<boolean>;
141
+ readonly error: Signal<Error | null>;
142
+ readonly data: Signal<TOutput | null>;
143
+ mutate: (input: TInput) => Promise<TOutput>;
144
+ reset: () => void;
145
+ }
146
+
147
+ /**
148
+ * Low-level HTTP service that wraps the native Fetch API.
149
+ * Prefer `querySignal` and `mutationSignal` for component use;
150
+ * use this directly in guards, resolvers, and async effects.
151
+ *
152
+ * @example
153
+ * const http = inject(SignalHttpClient);
154
+ * const user = await http.executeRequest<User>({ url: '/users/1', method: 'GET' });
155
+ */
156
+ declare class SignalHttpClient {
157
+ private readonly config;
158
+ /**
159
+ * Fires a GET request and returns a read-only signal updated when the response arrives.
160
+ * Must be called from an injection context. The request is aborted on host destroy.
161
+ *
162
+ * @param url - Absolute or base-relative URL.
163
+ * @param options - Per-request overrides (headers, params, timeout, signal).
164
+ * @returns A read-only `Signal<T | null>` — `null` until the response arrives.
165
+ *
166
+ * @example
167
+ * readonly user = inject(SignalHttpClient).get<User>('/users/1');
168
+ */
169
+ get<T>(url: string, options?: Partial<RequestConfig>): Signal<T | null>;
170
+ /**
171
+ * Fires a POST request and returns a read-only signal updated when the response arrives.
172
+ * Must be called from an injection context. The request is aborted on host destroy.
173
+ *
174
+ * @param url - Absolute or base-relative URL.
175
+ * @param body - Request body; serialised to JSON.
176
+ * @param options - Per-request overrides.
177
+ * @returns A read-only `Signal<T | null>` — `null` until the response arrives.
178
+ *
179
+ * @example
180
+ * readonly result = inject(SignalHttpClient).post<Token>('/auth/login', credentials);
181
+ */
182
+ post<T>(url: string, body?: unknown, options?: Partial<RequestConfig>): Signal<T | null>;
183
+ /**
184
+ * Fires a PUT request and returns a read-only signal updated when the response arrives.
185
+ * Must be called from an injection context. The request is aborted on host destroy.
186
+ *
187
+ * @param url - Absolute or base-relative URL.
188
+ * @param body - Request body; serialised to JSON.
189
+ * @param options - Per-request overrides.
190
+ * @returns A read-only `Signal<T | null>` — `null` until the response arrives.
191
+ *
192
+ * @example
193
+ * readonly updated = inject(SignalHttpClient).put<User>('/users/1', { name: 'Alice' });
194
+ */
195
+ put<T>(url: string, body?: unknown, options?: Partial<RequestConfig>): Signal<T | null>;
196
+ /**
197
+ * Fires a PATCH request and returns a read-only signal updated when the response arrives.
198
+ * Must be called from an injection context. The request is aborted on host destroy.
199
+ *
200
+ * @param url - Absolute or base-relative URL.
201
+ * @param body - Partial request body; serialised to JSON.
202
+ * @param options - Per-request overrides.
203
+ * @returns A read-only `Signal<T | null>` — `null` until the response arrives.
204
+ *
205
+ * @example
206
+ * readonly patched = inject(SignalHttpClient).patch<User>('/users/1', { email: 'new@example.com' });
207
+ */
208
+ patch<T>(url: string, body?: unknown, options?: Partial<RequestConfig>): Signal<T | null>;
209
+ /**
210
+ * Fires a DELETE request and returns a read-only signal updated when the response arrives.
211
+ * Must be called from an injection context. The request is aborted on host destroy.
212
+ *
213
+ * @param url - Absolute or base-relative URL.
214
+ * @param options - Per-request overrides.
215
+ * @returns A read-only `Signal<T | null>` — `null` until the response arrives.
216
+ *
217
+ * @example
218
+ * readonly deleted = inject(SignalHttpClient).delete<void>('/users/1');
219
+ */
220
+ delete<T>(url: string, options?: Partial<RequestConfig>): Signal<T | null>;
221
+ /**
222
+ * Executes a raw HTTP request and returns a `Promise`.
223
+ * Use this in effects, event handlers, guards, and resolvers where async/await is preferred.
224
+ * Runs request → response → error interceptors in registration order.
225
+ * `AbortError` is re-thrown without passing through error interceptors.
226
+ *
227
+ * @param config - Full request configuration including method and URL.
228
+ * @returns A `Promise` that resolves with the parsed response body.
229
+ * @throws `HttpError` on non-2xx responses; `AbortError` on cancellation.
230
+ *
231
+ * @example
232
+ * const token = await http.executeRequest<Token>({ url: '/auth/login', method: 'POST', body: creds });
233
+ */
234
+ executeRequest<T>(config: RequestConfig): Promise<T>;
235
+ private createSignal;
236
+ private buildUrl;
237
+ private buildHeaders;
238
+ private parseResponse;
239
+ static ɵfac: i0.ɵɵFactoryDeclaration<SignalHttpClient, never>;
240
+ static ɵprov: i0.ɵɵInjectableDeclaration<SignalHttpClient>;
241
+ }
242
+
243
+ declare const SIGNAL_HTTP_CONFIG: InjectionToken<SignalHttpConfig>;
244
+ /**
245
+ * Registers `ng-signal-http` in an Angular application.
246
+ * Call once in `app.config.ts` inside `ApplicationConfig.providers`.
247
+ *
248
+ * @param config - Global HTTP configuration applied to all requests.
249
+ * @returns Angular `EnvironmentProviders` to add to your app config.
250
+ *
251
+ * @example
252
+ * export const appConfig: ApplicationConfig = {
253
+ * providers: [provideSignalHttp({ baseUrl: 'https://api.example.com' })],
254
+ * };
255
+ */
256
+ declare function provideSignalHttp(config?: SignalHttpConfig): EnvironmentProviders;
257
+
258
+ /**
259
+ * Error thrown when an HTTP request receives a non-2xx response.
260
+ * Extends the native `Error` class and adds HTTP-specific helpers.
261
+ *
262
+ * @example
263
+ * effect(() => {
264
+ * const err = query.error();
265
+ * if (err instanceof HttpError && err.isUnauthorized) router.navigate(['/login']);
266
+ * });
267
+ */
268
+ declare class HttpError extends Error {
269
+ readonly status: number;
270
+ readonly response?: Response | undefined;
271
+ /**
272
+ * @param message - Human-readable error description.
273
+ * @param status - HTTP status code (e.g. 404, 500).
274
+ * @param response - The raw `Response` object, if available.
275
+ */
276
+ constructor(message: string, status: number, response?: Response | undefined);
277
+ get isClientError(): boolean;
278
+ get isServerError(): boolean;
279
+ get isTimeout(): boolean;
280
+ get isNotFound(): boolean;
281
+ get isUnauthorized(): boolean;
282
+ get isForbidden(): boolean;
283
+ }
284
+
285
+ type UrlFactory = () => string | RequestConfig;
286
+
287
+ /**
288
+ * Creates a reactive GET query bound to the current Angular injection context.
289
+ *
290
+ * Fetches immediately (unless `lazy: true`) and re-fetches automatically whenever
291
+ * any signal read inside the `url` factory changes. The in-flight request is
292
+ * aborted when the host component or service is destroyed.
293
+ *
294
+ * @template T - The expected response data type.
295
+ * @param url - A static URL string or a factory that returns a URL or `RequestConfig`.
296
+ * Signal reads inside the factory are tracked — changing them triggers a re-fetch.
297
+ * @param options - Query behaviour: lazy loading, retry, stale time, polling, callbacks, etc.
298
+ * @returns An `HttpClientResult<T>` with reactive signals and control methods.
299
+ *
300
+ * @example
301
+ * // Static URL — fetch once on init
302
+ * const posts = querySignal<Post[]>('/posts');
303
+ *
304
+ * @example
305
+ * // Reactive factory — refetches when postId() changes
306
+ * const postId = signal(1);
307
+ * const post = querySignal<Post>(() => `/posts/${postId()}`);
308
+ *
309
+ * @example
310
+ * // With options
311
+ * const data = querySignal('/feed', {
312
+ * retry: 3,
313
+ * refetchOnFocus: true,
314
+ * staleTime: 60_000,
315
+ * onError: (err) => console.error(err),
316
+ * });
317
+ */
318
+ declare function querySignal<T>(url: string | UrlFactory, options?: HttpClientOptions<T>): HttpClientResult<T>;
319
+
320
+ type MutationFactory<TInput> = (input: TInput) => RequestConfig;
321
+
322
+ /**
323
+ * Creates an imperative mutation bound to the current Angular injection context.
324
+ *
325
+ * Does nothing until `mutate(input)` is called. Calling `mutate()` while a previous
326
+ * request is in flight cancels the previous request automatically.
327
+ * The in-flight request is aborted when the host component or service is destroyed.
328
+ *
329
+ * @template TInput - The input type passed to `mutate()`.
330
+ * @template TOutput - The response data type returned by the server.
331
+ * @param requestFactory - Receives the mutation input and returns a `RequestConfig`.
332
+ * @param options - Optional lifecycle callbacks (`onSuccess`, `onError`, `onSettled`).
333
+ * @returns A `MutationResult<TInput, TOutput>` with reactive signals and a `mutate` trigger.
334
+ *
335
+ * @example
336
+ * const createPost = mutationSignal<NewPost, Post>(
337
+ * (input) => ({ url: '/posts', method: 'POST', body: input }),
338
+ * { onSuccess: (post) => console.log('Created:', post.id) },
339
+ * );
340
+ *
341
+ * // Trigger from an event handler:
342
+ * await createPost.mutate({ title: 'Hello', body: 'World', userId: 1 });
343
+ */
344
+ declare function mutationSignal<TInput, TOutput>(requestFactory: MutationFactory<TInput>, options?: MutationOptions<TInput, TOutput>): MutationResult<TInput, TOutput>;
345
+
346
+ export { HttpError, SIGNAL_HTTP_CONFIG, SignalHttpClient, mutationSignal, provideSignalHttp, querySignal };
347
+ export type { HttpInterceptor, HttpMethod, MutationFactory, MutationOptions, MutationResult, QueryResult, QueryStatus, RequestConfig, RequestOptions, RetryConfig, SignalHttpConfig, UrlFactory };