@deejayy/api-handler 21.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.
- package/README.md +151 -0
- package/fesm2022/deejayy-api-handler.mjs +807 -0
- package/fesm2022/deejayy-api-handler.mjs.map +1 -0
- package/package.json +46 -0
- package/types/deejayy-api-handler.d.ts +180 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @deejayy/api-handler
|
|
2
|
+
|
|
3
|
+
Composable Angular 21 HTTP primitives built on `HttpClient` and RxJS. Transport, local request state, and shared query caching are separate opt-in layers. The package has no NgRx dependency.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
bootstrapApplication(AppComponent, {
|
|
9
|
+
providers: [
|
|
10
|
+
provideHttpClient(),
|
|
11
|
+
provideApiClient({
|
|
12
|
+
baseUrl: "/api",
|
|
13
|
+
allowedAuthOrigins: ["https://api.example.com"],
|
|
14
|
+
}),
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
NgModule applications import `HttpClientModule` themselves and configure `ApiCallerModule.forRoot(config)`. Neither configuration entry point provides Angular HTTP implicitly.
|
|
20
|
+
|
|
21
|
+
## Plain Requests
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const api = inject(ApiClient);
|
|
25
|
+
|
|
26
|
+
const createUserCall = (body: CreateUser) =>
|
|
27
|
+
defineApiCall<User, CreateUser>({
|
|
28
|
+
method: "POST",
|
|
29
|
+
path: "/users",
|
|
30
|
+
body,
|
|
31
|
+
auth: "required",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
api.request(createUserCall({ name: "Ada" })).subscribe((user) => console.log(user));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`defineApiCall<TResponse, TBody>()` carries response and body types at compile time without adding a runtime field. `ApiClient.request()` and `requestResponse()` infer their result from it. Explicit response generics on plain `ApiRequest` values remain supported.
|
|
38
|
+
|
|
39
|
+
Requests are cold: no network operation starts before subscription, and every subscription starts an independent request. The caller's request definition is snapshotted when the Observable is created. A success emits once and completes, HTTP failures remain on the Observable error channel, and unsubscription is forwarded to `HttpClient` for cancellation. Use `requestResponse()` for a full `HttpResponse`, or `requestBlob()` and `requestBlobResponse()` for binary responses. `request<void>()` supports empty and `204` responses without manufactured data.
|
|
40
|
+
|
|
41
|
+
The HTTP method is mandatory because payload presence does not define request intent. Supply uploads as `FormData` and URL-encoded bodies as `HttpParams` or serialized data; the package does not infer either format.
|
|
42
|
+
|
|
43
|
+
## Local Lifecycle State
|
|
44
|
+
|
|
45
|
+
Use `toRequestState()` inside the feature's flattening operator:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
readonly saveState$ = this.submit$.pipe(
|
|
49
|
+
exhaustMap((body) =>
|
|
50
|
+
this.api.request<Result, SaveInput>({
|
|
51
|
+
method: 'POST',
|
|
52
|
+
path: '/save',
|
|
53
|
+
body,
|
|
54
|
+
auth: 'required',
|
|
55
|
+
}).pipe(toRequestState()),
|
|
56
|
+
),
|
|
57
|
+
startWith({ status: 'idle' } as const),
|
|
58
|
+
shareReplay({ bufferSize: 1, refCount: true }),
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`toRequestState()` emits `loading`, then either `success` or an error value, and completes. It deliberately converts source errors to state values; plain transport does not. Idle belongs to the outer trigger pipeline, before a request starts.
|
|
63
|
+
|
|
64
|
+
Pass `toRequestState({ mapError })` to classify an error while converting it to state.
|
|
65
|
+
|
|
66
|
+
The visible RxJS operator owns concurrency:
|
|
67
|
+
|
|
68
|
+
- `switchMap`: cancel previous work when the latest search or selection wins.
|
|
69
|
+
- `exhaustMap`: ignore repeated submits while one is active.
|
|
70
|
+
- `concatMap`: queue ordered writes.
|
|
71
|
+
- `mergeMap`: allow independent work in parallel.
|
|
72
|
+
|
|
73
|
+
## Shared Cached GETs
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const usersCall = defineApiCall<User[]>({
|
|
77
|
+
method: "GET",
|
|
78
|
+
path: "/users",
|
|
79
|
+
params: { active: true },
|
|
80
|
+
auth: "required",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const users = inject(ApiQueryClient).query({
|
|
84
|
+
key: ["users", "active"],
|
|
85
|
+
request: usersCall,
|
|
86
|
+
staleTime: 30_000,
|
|
87
|
+
gcTime: 300_000,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
users.state$.subscribe(console.log);
|
|
91
|
+
users.data$.subscribe(console.log);
|
|
92
|
+
users.refresh().subscribe();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Queries are only for GET server state. The first `state$` subscription starts a request; equal effective keys share state and in-flight work. Explicit keys identify the resource independently of URL formatting and are structurally compared. A key may contain strings, finite numbers, booleans, and `null`.
|
|
96
|
+
|
|
97
|
+
`staleTime` is how long successful data is fresh. Stale data remains usable and refreshes on a later subscription, observed invalidation, or `refresh()`. `refresh()` starts or joins one request and returns its single result; errors stay on that Observable's error channel. It starts eagerly, so subscribing to its return value is only required when the caller needs the result. `ensureFresh()` returns fresh cached data or starts/joins a request. Cancellation completes refresh waiters without a value. `data$` projects usable current or retained data and suppresses duplicate references. `gcTime` is how long an unobserved, settled entry remains allocated. Defaults are `0` and five minutes respectively.
|
|
98
|
+
|
|
99
|
+
After a mutation, invalidate affected queries explicitly:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
this.api
|
|
103
|
+
.request<void, UpdateUser>({
|
|
104
|
+
method: "PATCH",
|
|
105
|
+
path: `/users/${update.id}`,
|
|
106
|
+
body: update,
|
|
107
|
+
auth: "required",
|
|
108
|
+
})
|
|
109
|
+
.subscribe(() => this.queries.invalidate(["users", "active"]));
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`cancel()` restores `idle` for an initial load or retained successful data for a refresh. `clear()`, `clearAuthenticated()`, and invalidation affect shared entries, not only one handle.
|
|
113
|
+
|
|
114
|
+
## Authentication
|
|
115
|
+
|
|
116
|
+
Every request declares `auth: 'none' | 'optional' | 'required'`; the default is `none`. Provide current credentials atomically through a signal:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
@Injectable({ providedIn: "root" })
|
|
120
|
+
export class AppCredentials extends ApiCredentialProvider {
|
|
121
|
+
readonly credentials = computed<ApiCredentialState>(() => {
|
|
122
|
+
const session = this.session();
|
|
123
|
+
return session ? { token: session.accessToken, identity: session.userId } : { token: null, identity: null };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
bootstrapApplication(AppComponent, {
|
|
128
|
+
providers: [provideHttpClient(), provideApiClient({ baseUrl: "/api" }), { provide: ApiCredentialProvider, useClass: AppCredentials }],
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The token authorizes the current request. Identity is a stable, opaque cache partition such as a user or session ID; token rotation for the same identity preserves cached data. Required auth without a token fails with `AuthenticationRequiredError` before dispatch. Authentication is sent only to the browser origin or an origin explicitly listed in `allowedAuthOrigins`; otherwise the request fails with `DisallowedAuthOriginError`. Callers cannot set `Authorization` directly.
|
|
133
|
+
|
|
134
|
+
The application must finish initial credential restoration before issuing required requests. Asynchronous refresh belongs in a one-shot operation or HTTP interceptor, not a long-lived token stream sampled by this package.
|
|
135
|
+
|
|
136
|
+
## Error Reporting
|
|
137
|
+
|
|
138
|
+
The default error policy is `propagate`. Configure `report-and-propagate` globally or per request and provide `ApiErrorReporter` to observe `HttpErrorResponse` failures. Reporting never swallows or replaces the original error. Authentication policy errors are not reported.
|
|
139
|
+
|
|
140
|
+
## Request And Error Policies
|
|
141
|
+
|
|
142
|
+
Provide `ApiRequestTransformer` to derive the effective request at subscription time, for example to apply a runtime-configured base URL. Provide `ApiErrorMapper` to change errors delivered to request and query consumers. Identity implementations are configured by default.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
providers: [
|
|
146
|
+
{ provide: ApiRequestTransformer, useClass: AppRequestTransformer },
|
|
147
|
+
{ provide: ApiErrorMapper, useClass: AppErrorMapper },
|
|
148
|
+
];
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The lifecycle is: snapshot the caller definition, transform it, validate URL and authentication policy, dispatch, report a raw `HttpErrorResponse` when configured, then map the propagated error. Query keys, conflict signatures, and authentication cache partitions intentionally use the immutable caller definition, not runtime-transformed values. Declare query authentication on the original call; use transformation for transport details such as a runtime base URL.
|
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, Injectable, InjectionToken, makeEnvironmentProviders, NgModule, inject, ErrorHandler, effect } from '@angular/core';
|
|
3
|
+
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
4
|
+
import { defer, catchError, throwError, map, of, startWith, ReplaySubject, Observable, distinctUntilChanged, BehaviorSubject, Subscription } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
class ApiCredentialProvider {
|
|
7
|
+
}
|
|
8
|
+
class AnonymousApiCredentialProvider extends ApiCredentialProvider {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.credentials = signal({
|
|
12
|
+
token: null,
|
|
13
|
+
identity: null,
|
|
14
|
+
}, ...(ngDevMode ? [{ debugName: "credentials" }] : /* istanbul ignore next */ []));
|
|
15
|
+
}
|
|
16
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: AnonymousApiCredentialProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
17
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: AnonymousApiCredentialProvider }); }
|
|
18
|
+
}
|
|
19
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: AnonymousApiCredentialProvider, decorators: [{
|
|
20
|
+
type: Injectable
|
|
21
|
+
}] });
|
|
22
|
+
|
|
23
|
+
class ApiErrorReporter {
|
|
24
|
+
}
|
|
25
|
+
class NoopApiErrorReporter extends ApiErrorReporter {
|
|
26
|
+
report(_error, _request) { }
|
|
27
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NoopApiErrorReporter, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
28
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NoopApiErrorReporter }); }
|
|
29
|
+
}
|
|
30
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NoopApiErrorReporter, decorators: [{
|
|
31
|
+
type: Injectable
|
|
32
|
+
}] });
|
|
33
|
+
|
|
34
|
+
class ApiErrorMapper {
|
|
35
|
+
}
|
|
36
|
+
class IdentityApiErrorMapper extends ApiErrorMapper {
|
|
37
|
+
map(error) {
|
|
38
|
+
return error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class ApiRequestTransformer {
|
|
43
|
+
}
|
|
44
|
+
class IdentityApiRequestTransformer extends ApiRequestTransformer {
|
|
45
|
+
transform(request) {
|
|
46
|
+
return request;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const API_CLIENT_CONFIG = new InjectionToken('API_CLIENT_CONFIG');
|
|
51
|
+
const resolveApiClientConfig = (config) => {
|
|
52
|
+
const browserOrigin = globalThis.location?.origin ?? 'http://localhost';
|
|
53
|
+
return {
|
|
54
|
+
baseUrl: config.baseUrl,
|
|
55
|
+
allowedAuthOrigins: (config.allowedAuthOrigins ?? []).map((origin) => new URL(origin, browserOrigin).origin),
|
|
56
|
+
defaultErrorPolicy: config.defaultErrorPolicy ?? 'propagate',
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const provideApiClient = (config) => makeEnvironmentProviders([
|
|
60
|
+
{ provide: API_CLIENT_CONFIG, useValue: resolveApiClientConfig(config) },
|
|
61
|
+
{
|
|
62
|
+
provide: ApiCredentialProvider,
|
|
63
|
+
useClass: AnonymousApiCredentialProvider,
|
|
64
|
+
},
|
|
65
|
+
{ provide: ApiErrorReporter, useClass: NoopApiErrorReporter },
|
|
66
|
+
{ provide: ApiRequestTransformer, useClass: IdentityApiRequestTransformer },
|
|
67
|
+
{ provide: ApiErrorMapper, useClass: IdentityApiErrorMapper },
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
class ApiCallerModule {
|
|
71
|
+
static forRoot(config) {
|
|
72
|
+
return {
|
|
73
|
+
ngModule: ApiCallerModule,
|
|
74
|
+
providers: [
|
|
75
|
+
{
|
|
76
|
+
provide: API_CLIENT_CONFIG,
|
|
77
|
+
useValue: resolveApiClientConfig(config),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
provide: ApiCredentialProvider,
|
|
81
|
+
useClass: AnonymousApiCredentialProvider,
|
|
82
|
+
},
|
|
83
|
+
{ provide: ApiErrorReporter, useClass: NoopApiErrorReporter },
|
|
84
|
+
{
|
|
85
|
+
provide: ApiRequestTransformer,
|
|
86
|
+
useClass: IdentityApiRequestTransformer,
|
|
87
|
+
},
|
|
88
|
+
{ provide: ApiErrorMapper, useClass: IdentityApiErrorMapper },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiCallerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
93
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.18", ngImport: i0, type: ApiCallerModule }); }
|
|
94
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiCallerModule }); }
|
|
95
|
+
}
|
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiCallerModule, decorators: [{
|
|
97
|
+
type: NgModule,
|
|
98
|
+
args: [{}]
|
|
99
|
+
}] });
|
|
100
|
+
|
|
101
|
+
class AuthenticationRequiredError extends Error {
|
|
102
|
+
constructor() {
|
|
103
|
+
super('Authentication is required for this request.');
|
|
104
|
+
this.name = 'AuthenticationRequiredError';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class DisallowedAuthOriginError extends Error {
|
|
108
|
+
constructor(origin) {
|
|
109
|
+
super(`Authentication is not allowed for origin "${origin}".`);
|
|
110
|
+
this.origin = origin;
|
|
111
|
+
this.name = 'DisallowedAuthOriginError';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class InvalidApiRequestError extends Error {
|
|
115
|
+
constructor(message) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = 'InvalidApiRequestError';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class ApiClient {
|
|
122
|
+
constructor() {
|
|
123
|
+
this.http = inject(HttpClient);
|
|
124
|
+
this.config = inject(API_CLIENT_CONFIG);
|
|
125
|
+
this.credentialProvider = inject(ApiCredentialProvider);
|
|
126
|
+
this.errorReporter = inject(ApiErrorReporter);
|
|
127
|
+
this.requestTransformer = inject(ApiRequestTransformer);
|
|
128
|
+
this.errorMapper = inject(ApiErrorMapper);
|
|
129
|
+
this.errorHandler = inject(ErrorHandler);
|
|
130
|
+
}
|
|
131
|
+
request(request) {
|
|
132
|
+
return this.execute(snapshotRequest$1(request), 'body', 'json');
|
|
133
|
+
}
|
|
134
|
+
requestResponse(request) {
|
|
135
|
+
return this.execute(snapshotRequest$1(request), 'response', 'json');
|
|
136
|
+
}
|
|
137
|
+
requestBlob(request) {
|
|
138
|
+
return this.execute(snapshotRequest$1(request), 'body', 'blob');
|
|
139
|
+
}
|
|
140
|
+
requestBlobResponse(request) {
|
|
141
|
+
return this.execute(snapshotRequest$1(request), 'response', 'blob');
|
|
142
|
+
}
|
|
143
|
+
execute(request, observe, responseType) {
|
|
144
|
+
return defer(() => {
|
|
145
|
+
try {
|
|
146
|
+
const effectiveRequest = this.requestTransformer.transform(request);
|
|
147
|
+
const url = this.buildUrl(effectiveRequest.baseUrl ?? this.config.baseUrl, effectiveRequest.path);
|
|
148
|
+
const auth = effectiveRequest.auth ?? 'none';
|
|
149
|
+
const credentials = this.credentialProvider.credentials();
|
|
150
|
+
let headers = this.copyHeaders(effectiveRequest.headers);
|
|
151
|
+
if (headers.has('Authorization')) {
|
|
152
|
+
throw new InvalidApiRequestError('The Authorization header is owned by ApiClient.');
|
|
153
|
+
}
|
|
154
|
+
if (auth === 'required' && credentials.token === null) {
|
|
155
|
+
throw new AuthenticationRequiredError();
|
|
156
|
+
}
|
|
157
|
+
if (auth !== 'none' && credentials.token !== null) {
|
|
158
|
+
this.assertAuthOriginAllowed(url);
|
|
159
|
+
headers = headers.set('Authorization', `Bearer ${credentials.token}`);
|
|
160
|
+
}
|
|
161
|
+
const options = {
|
|
162
|
+
headers,
|
|
163
|
+
withCredentials: effectiveRequest.withCredentials ?? false,
|
|
164
|
+
...(effectiveRequest.body === undefined
|
|
165
|
+
? {}
|
|
166
|
+
: { body: effectiveRequest.body }),
|
|
167
|
+
...(effectiveRequest.params === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { params: effectiveRequest.params }),
|
|
170
|
+
};
|
|
171
|
+
const response$ = responseType === 'blob'
|
|
172
|
+
? observe === 'response'
|
|
173
|
+
? this.http.request(effectiveRequest.method, url, {
|
|
174
|
+
...options,
|
|
175
|
+
observe: 'response',
|
|
176
|
+
responseType: 'blob',
|
|
177
|
+
})
|
|
178
|
+
: this.http.request(effectiveRequest.method, url, {
|
|
179
|
+
...options,
|
|
180
|
+
observe: 'body',
|
|
181
|
+
responseType: 'blob',
|
|
182
|
+
})
|
|
183
|
+
: observe === 'response'
|
|
184
|
+
? this.http.request(effectiveRequest.method, url, {
|
|
185
|
+
...options,
|
|
186
|
+
observe: 'response',
|
|
187
|
+
responseType: 'json',
|
|
188
|
+
})
|
|
189
|
+
: this.http.request(effectiveRequest.method, url, {
|
|
190
|
+
...options,
|
|
191
|
+
observe: 'body',
|
|
192
|
+
responseType: 'json',
|
|
193
|
+
});
|
|
194
|
+
return response$.pipe(catchError((error) => {
|
|
195
|
+
const policy = effectiveRequest.errorPolicy ?? this.config.defaultErrorPolicy;
|
|
196
|
+
if (policy === 'report-and-propagate' &&
|
|
197
|
+
error instanceof HttpErrorResponse) {
|
|
198
|
+
try {
|
|
199
|
+
this.errorReporter.report(error, effectiveRequest);
|
|
200
|
+
}
|
|
201
|
+
catch (reporterError) {
|
|
202
|
+
try {
|
|
203
|
+
this.errorHandler.handleError(reporterError);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Reporting infrastructure must never replace the transport error.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return throwError(() => this.errorMapper.map(error));
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return throwError(() => this.errorMapper.map(error));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
buildUrl(baseUrl, path) {
|
|
219
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith('//')) {
|
|
220
|
+
throw new InvalidApiRequestError('ApiRequest.path must be relative.');
|
|
221
|
+
}
|
|
222
|
+
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
223
|
+
}
|
|
224
|
+
copyHeaders(headers) {
|
|
225
|
+
if (headers instanceof HttpHeaders) {
|
|
226
|
+
let copy = new HttpHeaders();
|
|
227
|
+
for (const name of headers.keys()) {
|
|
228
|
+
copy = copy.set(name, headers.getAll(name) ?? []);
|
|
229
|
+
}
|
|
230
|
+
return copy;
|
|
231
|
+
}
|
|
232
|
+
let copy = new HttpHeaders();
|
|
233
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
234
|
+
copy = copy.set(name, typeof value === 'string' ? value : [...value]);
|
|
235
|
+
}
|
|
236
|
+
return copy;
|
|
237
|
+
}
|
|
238
|
+
assertAuthOriginAllowed(url) {
|
|
239
|
+
const browserOrigin = globalThis.location?.origin ?? 'http://localhost';
|
|
240
|
+
const targetOrigin = new URL(url, browserOrigin).origin;
|
|
241
|
+
if (targetOrigin !== browserOrigin &&
|
|
242
|
+
!this.config.allowedAuthOrigins.includes(targetOrigin)) {
|
|
243
|
+
throw new DisallowedAuthOriginError(targetOrigin);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
247
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiClient, providedIn: 'root' }); }
|
|
248
|
+
}
|
|
249
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiClient, decorators: [{
|
|
250
|
+
type: Injectable,
|
|
251
|
+
args: [{ providedIn: 'root' }]
|
|
252
|
+
}] });
|
|
253
|
+
const snapshotRequest$1 = (request) => ({
|
|
254
|
+
...request,
|
|
255
|
+
...(request.headers instanceof HttpHeaders
|
|
256
|
+
? { headers: copyHttpHeaders(request.headers) }
|
|
257
|
+
: request.headers === undefined
|
|
258
|
+
? {}
|
|
259
|
+
: {
|
|
260
|
+
headers: Object.fromEntries(Object.entries(request.headers).map(([name, value]) => [
|
|
261
|
+
name,
|
|
262
|
+
typeof value === 'string' ? value : [...value],
|
|
263
|
+
])),
|
|
264
|
+
}),
|
|
265
|
+
...(request.params === undefined ? {} : { params: request.params }),
|
|
266
|
+
});
|
|
267
|
+
const copyHttpHeaders = (headers) => {
|
|
268
|
+
let copy = new HttpHeaders();
|
|
269
|
+
for (const name of headers.keys()) {
|
|
270
|
+
copy = copy.set(name, headers.getAll(name) ?? []);
|
|
271
|
+
}
|
|
272
|
+
return copy;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const defineApiCall = (request) => request;
|
|
276
|
+
|
|
277
|
+
const toRequestState = (options) => (source) => source.pipe(map((data) => ({ status: 'success', data })), catchError((error) => of({
|
|
278
|
+
status: 'error',
|
|
279
|
+
error: options?.mapError ? options.mapError(error) : error,
|
|
280
|
+
})), startWith({ status: 'loading' }));
|
|
281
|
+
|
|
282
|
+
class QueryDefinitionConflictError extends Error {
|
|
283
|
+
constructor(key) {
|
|
284
|
+
super(`A different query definition already exists for key ${JSON.stringify(key)}.`);
|
|
285
|
+
this.key = key;
|
|
286
|
+
this.name = 'QueryDefinitionConflictError';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const snapshotQueryKey = (key) => {
|
|
291
|
+
if (!Array.isArray(key)) {
|
|
292
|
+
throw new TypeError('Query key must be an array.');
|
|
293
|
+
}
|
|
294
|
+
const snapshot = key.map((part) => {
|
|
295
|
+
if (part !== null &&
|
|
296
|
+
typeof part !== 'string' &&
|
|
297
|
+
typeof part !== 'boolean' &&
|
|
298
|
+
typeof part !== 'number') {
|
|
299
|
+
throw new TypeError('Query key parts must be strings, finite numbers, booleans, or null.');
|
|
300
|
+
}
|
|
301
|
+
if (typeof part === 'number' && !Number.isFinite(part)) {
|
|
302
|
+
throw new TypeError('Query key numbers must be finite.');
|
|
303
|
+
}
|
|
304
|
+
return part;
|
|
305
|
+
});
|
|
306
|
+
return Object.freeze(snapshot);
|
|
307
|
+
};
|
|
308
|
+
const queryKeyId = (key) => key
|
|
309
|
+
.map((part) => {
|
|
310
|
+
if (part === null)
|
|
311
|
+
return 'null';
|
|
312
|
+
if (typeof part === 'number')
|
|
313
|
+
return `number:${Object.is(part, -0) ? 0 : part}`;
|
|
314
|
+
return `${typeof part}:${JSON.stringify(part)}`;
|
|
315
|
+
})
|
|
316
|
+
.join('|');
|
|
317
|
+
|
|
318
|
+
const DEFAULT_GC_TIME = 300_000;
|
|
319
|
+
const MAX_TIMER_DELAY = 2_147_483_647;
|
|
320
|
+
class ApiQueryClient {
|
|
321
|
+
constructor() {
|
|
322
|
+
this.api = inject(ApiClient);
|
|
323
|
+
this.credentials = inject(ApiCredentialProvider);
|
|
324
|
+
this.entries = new Map();
|
|
325
|
+
this.handles = new Set();
|
|
326
|
+
this.lastIdentity = this.credentials.credentials().identity;
|
|
327
|
+
this.host = {
|
|
328
|
+
getEntry: (definition) => this.getEntry(definition),
|
|
329
|
+
addHandle: (handle) => this.handles.add(handle),
|
|
330
|
+
removeHandle: (handle) => this.handles.delete(handle),
|
|
331
|
+
isCurrentEntry: (entry, definition) => entry.partition === this.partitionFor(definition.request),
|
|
332
|
+
};
|
|
333
|
+
effect(() => {
|
|
334
|
+
const identity = this.credentials.credentials().identity;
|
|
335
|
+
if (identity === this.lastIdentity)
|
|
336
|
+
return;
|
|
337
|
+
this.lastIdentity = identity;
|
|
338
|
+
const authenticatedHandles = [...this.handles].filter((handle) => handle.isAuthenticated());
|
|
339
|
+
this.clearAuthenticated();
|
|
340
|
+
for (const handle of authenticatedHandles)
|
|
341
|
+
handle.rebindForIdentityChange();
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
query(options) {
|
|
345
|
+
const definition = this.createDefinition(options);
|
|
346
|
+
this.assertCompatibleIfPresent(definition);
|
|
347
|
+
return new QueryHandle(this.host, definition);
|
|
348
|
+
}
|
|
349
|
+
invalidate(key) {
|
|
350
|
+
const id = queryKeyId(snapshotQueryKey(key));
|
|
351
|
+
this.forKey(id, (entry) => entry.invalidate());
|
|
352
|
+
}
|
|
353
|
+
invalidateWhere(predicate) {
|
|
354
|
+
const matched = new Set();
|
|
355
|
+
for (const entry of this.entries.values()) {
|
|
356
|
+
if (!matched.has(entry.definition.keyId) &&
|
|
357
|
+
predicate(entry.definition.key)) {
|
|
358
|
+
matched.add(entry.definition.keyId);
|
|
359
|
+
this.forKey(entry.definition.keyId, (candidate) => candidate.invalidate());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
clear() {
|
|
364
|
+
this.clearEntries(() => true);
|
|
365
|
+
}
|
|
366
|
+
clearAuthenticated() {
|
|
367
|
+
this.clearEntries((entry) => entry.partition !== 'public');
|
|
368
|
+
}
|
|
369
|
+
getEntry(definition) {
|
|
370
|
+
const partition = this.partitionFor(definition.request);
|
|
371
|
+
const effectiveKey = `${partition}\u0000${definition.keyId}`;
|
|
372
|
+
const existing = this.entries.get(effectiveKey);
|
|
373
|
+
if (existing) {
|
|
374
|
+
if (existing.definition.signature !== definition.signature) {
|
|
375
|
+
throw new QueryDefinitionConflictError(definition.key);
|
|
376
|
+
}
|
|
377
|
+
return existing;
|
|
378
|
+
}
|
|
379
|
+
const entry = new QueryCacheEntry(this.api, definition, partition, () => this.evict(effectiveKey, entry));
|
|
380
|
+
this.entries.set(effectiveKey, entry);
|
|
381
|
+
return entry;
|
|
382
|
+
}
|
|
383
|
+
createDefinition(options) {
|
|
384
|
+
if (options.request.method !== 'GET') {
|
|
385
|
+
throw new TypeError('ApiQueryClient only supports GET requests.');
|
|
386
|
+
}
|
|
387
|
+
const staleTime = options.staleTime ?? 0;
|
|
388
|
+
const gcTime = options.gcTime ?? DEFAULT_GC_TIME;
|
|
389
|
+
this.assertTiming('staleTime', staleTime);
|
|
390
|
+
this.assertTiming('gcTime', gcTime);
|
|
391
|
+
const key = snapshotQueryKey(options.key);
|
|
392
|
+
const request = snapshotRequest(options.request);
|
|
393
|
+
return {
|
|
394
|
+
key,
|
|
395
|
+
keyId: queryKeyId(key),
|
|
396
|
+
request,
|
|
397
|
+
staleTime,
|
|
398
|
+
gcTime,
|
|
399
|
+
signature: `${requestSignature(request)}|stale:${staleTime}|gc:${gcTime}`,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
assertCompatibleIfPresent(definition) {
|
|
403
|
+
const effectiveKey = `${this.partitionFor(definition.request)}\u0000${definition.keyId}`;
|
|
404
|
+
const existing = this.entries.get(effectiveKey);
|
|
405
|
+
if (existing && existing.definition.signature !== definition.signature) {
|
|
406
|
+
throw new QueryDefinitionConflictError(definition.key);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
assertTiming(name, value) {
|
|
410
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
411
|
+
throw new RangeError(`${name} must be a finite, non-negative number.`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
partitionFor(request) {
|
|
415
|
+
const auth = request.auth ?? 'none';
|
|
416
|
+
if (auth === 'none')
|
|
417
|
+
return 'public';
|
|
418
|
+
const identity = this.credentials.credentials().identity;
|
|
419
|
+
return identity === null
|
|
420
|
+
? 'anonymous'
|
|
421
|
+
: `identity:${JSON.stringify(identity)}`;
|
|
422
|
+
}
|
|
423
|
+
forKey(keyId, action) {
|
|
424
|
+
for (const entry of this.entries.values()) {
|
|
425
|
+
if (entry.definition.keyId === keyId)
|
|
426
|
+
action(entry);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
clearEntries(predicate) {
|
|
430
|
+
const removed = new Set();
|
|
431
|
+
for (const [key, entry] of this.entries) {
|
|
432
|
+
if (!predicate(entry))
|
|
433
|
+
continue;
|
|
434
|
+
entry.clear();
|
|
435
|
+
removed.add(entry);
|
|
436
|
+
this.entries.delete(key);
|
|
437
|
+
}
|
|
438
|
+
for (const handle of this.handles) {
|
|
439
|
+
if (handle.isBoundToAny(removed))
|
|
440
|
+
handle.detachAfterClear();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
evict(key, entry) {
|
|
444
|
+
if (this.entries.get(key) !== entry)
|
|
445
|
+
return;
|
|
446
|
+
this.entries.delete(key);
|
|
447
|
+
const removed = new Set([entry]);
|
|
448
|
+
for (const handle of this.handles)
|
|
449
|
+
if (handle.isBoundToAny(removed))
|
|
450
|
+
handle.detachAfterClear();
|
|
451
|
+
}
|
|
452
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiQueryClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
453
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiQueryClient, providedIn: 'root' }); }
|
|
454
|
+
}
|
|
455
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ApiQueryClient, decorators: [{
|
|
456
|
+
type: Injectable,
|
|
457
|
+
args: [{ providedIn: 'root' }]
|
|
458
|
+
}], ctorParameters: () => [] });
|
|
459
|
+
class QueryHandle {
|
|
460
|
+
constructor(client, definition) {
|
|
461
|
+
this.client = client;
|
|
462
|
+
this.definition = definition;
|
|
463
|
+
this.output = new ReplaySubject(1);
|
|
464
|
+
this.bound = null;
|
|
465
|
+
this.entrySubscription = null;
|
|
466
|
+
this.subscribers = 0;
|
|
467
|
+
this.state$ = new Observable((subscriber) => {
|
|
468
|
+
this.subscribers++;
|
|
469
|
+
this.bind(true);
|
|
470
|
+
const subscription = this.output.subscribe(subscriber);
|
|
471
|
+
return () => {
|
|
472
|
+
subscription.unsubscribe();
|
|
473
|
+
this.subscribers--;
|
|
474
|
+
if (this.subscribers === 0)
|
|
475
|
+
this.unbind();
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
this.data$ = this.state$.pipe(map((state) => {
|
|
479
|
+
if (state.status === 'success')
|
|
480
|
+
return state.data;
|
|
481
|
+
if (state.status === 'error')
|
|
482
|
+
return state.previousData;
|
|
483
|
+
return undefined;
|
|
484
|
+
}), distinctUntilChanged());
|
|
485
|
+
}
|
|
486
|
+
refresh() {
|
|
487
|
+
return this.ensureBound().refresh();
|
|
488
|
+
}
|
|
489
|
+
ensureFresh() {
|
|
490
|
+
return this.ensureBound().ensureFresh();
|
|
491
|
+
}
|
|
492
|
+
invalidate() {
|
|
493
|
+
this.ensureBound().invalidate();
|
|
494
|
+
}
|
|
495
|
+
cancel() {
|
|
496
|
+
this.bound?.cancel();
|
|
497
|
+
}
|
|
498
|
+
rebindForIdentityChange() {
|
|
499
|
+
const active = this.subscribers > 0;
|
|
500
|
+
this.output.next({ status: 'idle' });
|
|
501
|
+
if (active)
|
|
502
|
+
this.bind(true);
|
|
503
|
+
}
|
|
504
|
+
isAuthenticated() {
|
|
505
|
+
return (this.definition.request.auth ?? 'none') !== 'none';
|
|
506
|
+
}
|
|
507
|
+
isBoundToAny(entries) {
|
|
508
|
+
return this.bound !== null && entries.has(this.bound);
|
|
509
|
+
}
|
|
510
|
+
detachAfterClear() {
|
|
511
|
+
this.unbind();
|
|
512
|
+
this.output.next({ status: 'idle' });
|
|
513
|
+
}
|
|
514
|
+
ensureBound() {
|
|
515
|
+
if (this.bound &&
|
|
516
|
+
!this.client.isCurrentEntry(this.bound, this.definition)) {
|
|
517
|
+
this.unbind();
|
|
518
|
+
this.output.next({ status: 'idle' });
|
|
519
|
+
}
|
|
520
|
+
if (!this.bound) {
|
|
521
|
+
this.bound = this.client.getEntry(this.definition);
|
|
522
|
+
this.client.addHandle(this);
|
|
523
|
+
}
|
|
524
|
+
return this.bound;
|
|
525
|
+
}
|
|
526
|
+
bind(startIfNeeded) {
|
|
527
|
+
if (this.bound &&
|
|
528
|
+
!this.client.isCurrentEntry(this.bound, this.definition)) {
|
|
529
|
+
this.unbind();
|
|
530
|
+
this.output.next({ status: 'idle' });
|
|
531
|
+
}
|
|
532
|
+
const entry = this.bound ?? this.client.getEntry(this.definition);
|
|
533
|
+
this.bound = entry;
|
|
534
|
+
this.client.addHandle(this);
|
|
535
|
+
if (!this.entrySubscription) {
|
|
536
|
+
entry.observe();
|
|
537
|
+
this.entrySubscription = entry.state$.subscribe(this.output);
|
|
538
|
+
}
|
|
539
|
+
if (startIfNeeded)
|
|
540
|
+
entry.startIfNeeded();
|
|
541
|
+
}
|
|
542
|
+
unbind() {
|
|
543
|
+
if (!this.bound)
|
|
544
|
+
return;
|
|
545
|
+
if (this.entrySubscription) {
|
|
546
|
+
this.entrySubscription.unsubscribe();
|
|
547
|
+
this.entrySubscription = null;
|
|
548
|
+
this.bound.unobserve();
|
|
549
|
+
}
|
|
550
|
+
this.bound = null;
|
|
551
|
+
this.client.removeHandle(this);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
class QueryCacheEntry {
|
|
555
|
+
constructor(api, definition, partition, evict) {
|
|
556
|
+
this.api = api;
|
|
557
|
+
this.definition = definition;
|
|
558
|
+
this.partition = partition;
|
|
559
|
+
this.evict = evict;
|
|
560
|
+
this.state$ = new BehaviorSubject({ status: 'idle' });
|
|
561
|
+
this.observers = 0;
|
|
562
|
+
this.invalidated = false;
|
|
563
|
+
this.inFlight = null;
|
|
564
|
+
this.inFlightResult = null;
|
|
565
|
+
this.requestId = 0;
|
|
566
|
+
this.beforeRequest = { status: 'idle' };
|
|
567
|
+
this.gcTimer = null;
|
|
568
|
+
this.gcDeadline = null;
|
|
569
|
+
this.scheduleGc();
|
|
570
|
+
}
|
|
571
|
+
observe() {
|
|
572
|
+
this.observers++;
|
|
573
|
+
this.cancelGc();
|
|
574
|
+
}
|
|
575
|
+
unobserve() {
|
|
576
|
+
this.observers--;
|
|
577
|
+
this.scheduleGc();
|
|
578
|
+
}
|
|
579
|
+
startIfNeeded() {
|
|
580
|
+
if (this.inFlight)
|
|
581
|
+
return;
|
|
582
|
+
const state = this.state$.value;
|
|
583
|
+
if (state.status === 'success') {
|
|
584
|
+
if (this.invalidated ||
|
|
585
|
+
Date.now() - state.updatedAt >= this.definition.staleTime) {
|
|
586
|
+
this.start();
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (state.status === 'error' || state.status === 'idle')
|
|
591
|
+
this.start();
|
|
592
|
+
}
|
|
593
|
+
refresh() {
|
|
594
|
+
if (this.inFlight)
|
|
595
|
+
return this.inFlightResult.asObservable();
|
|
596
|
+
return this.start();
|
|
597
|
+
}
|
|
598
|
+
ensureFresh() {
|
|
599
|
+
if (this.inFlight)
|
|
600
|
+
return this.inFlightResult.asObservable();
|
|
601
|
+
const state = this.state$.value;
|
|
602
|
+
if (state.status === 'success' &&
|
|
603
|
+
!this.invalidated &&
|
|
604
|
+
Date.now() - state.updatedAt < this.definition.staleTime) {
|
|
605
|
+
return of(state.data);
|
|
606
|
+
}
|
|
607
|
+
return this.refresh();
|
|
608
|
+
}
|
|
609
|
+
invalidate() {
|
|
610
|
+
this.invalidated = true;
|
|
611
|
+
if (this.observers > 0)
|
|
612
|
+
this.refresh();
|
|
613
|
+
}
|
|
614
|
+
cancel() {
|
|
615
|
+
if (!this.inFlight)
|
|
616
|
+
return;
|
|
617
|
+
this.requestId++;
|
|
618
|
+
this.inFlight.unsubscribe();
|
|
619
|
+
this.inFlight = null;
|
|
620
|
+
this.inFlightResult?.complete();
|
|
621
|
+
this.inFlightResult = null;
|
|
622
|
+
const previous = this.beforeRequest;
|
|
623
|
+
if (previous.status === 'success') {
|
|
624
|
+
this.state$.next({ ...previous, refreshing: false });
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
this.state$.next({ status: 'idle' });
|
|
628
|
+
}
|
|
629
|
+
this.scheduleGc();
|
|
630
|
+
}
|
|
631
|
+
clear() {
|
|
632
|
+
this.cancelGc();
|
|
633
|
+
this.requestId++;
|
|
634
|
+
this.inFlight?.unsubscribe();
|
|
635
|
+
this.inFlight = null;
|
|
636
|
+
this.inFlightResult?.complete();
|
|
637
|
+
this.inFlightResult = null;
|
|
638
|
+
this.state$.next({ status: 'idle' });
|
|
639
|
+
}
|
|
640
|
+
start() {
|
|
641
|
+
this.cancelGc();
|
|
642
|
+
const id = ++this.requestId;
|
|
643
|
+
const current = this.state$.value;
|
|
644
|
+
const previous = current.status === 'error' &&
|
|
645
|
+
'previousData' in current &&
|
|
646
|
+
current.updatedAt !== undefined
|
|
647
|
+
? {
|
|
648
|
+
status: 'success',
|
|
649
|
+
data: current.previousData,
|
|
650
|
+
refreshing: false,
|
|
651
|
+
updatedAt: current.updatedAt,
|
|
652
|
+
}
|
|
653
|
+
: current;
|
|
654
|
+
this.beforeRequest = previous;
|
|
655
|
+
this.invalidated = false;
|
|
656
|
+
if (previous.status === 'success') {
|
|
657
|
+
this.state$.next({ ...previous, refreshing: true });
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
this.state$.next({ status: 'loading' });
|
|
661
|
+
}
|
|
662
|
+
const inFlight = new Subscription();
|
|
663
|
+
const result = new ReplaySubject(1);
|
|
664
|
+
this.inFlight = inFlight;
|
|
665
|
+
this.inFlightResult = result;
|
|
666
|
+
inFlight.add(this.api.request(this.definition.request).subscribe({
|
|
667
|
+
next: (data) => {
|
|
668
|
+
if (id !== this.requestId)
|
|
669
|
+
return;
|
|
670
|
+
this.state$.next({
|
|
671
|
+
status: 'success',
|
|
672
|
+
data,
|
|
673
|
+
refreshing: false,
|
|
674
|
+
updatedAt: Date.now(),
|
|
675
|
+
});
|
|
676
|
+
result.next(data);
|
|
677
|
+
},
|
|
678
|
+
error: (error) => {
|
|
679
|
+
if (id !== this.requestId)
|
|
680
|
+
return;
|
|
681
|
+
this.inFlight = null;
|
|
682
|
+
this.inFlightResult = null;
|
|
683
|
+
if (previous.status === 'success') {
|
|
684
|
+
this.state$.next({
|
|
685
|
+
status: 'error',
|
|
686
|
+
error,
|
|
687
|
+
previousData: previous.data,
|
|
688
|
+
updatedAt: previous.updatedAt,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
this.state$.next({ status: 'error', error });
|
|
693
|
+
}
|
|
694
|
+
result.error(error);
|
|
695
|
+
this.scheduleGc();
|
|
696
|
+
},
|
|
697
|
+
complete: () => {
|
|
698
|
+
if (id !== this.requestId)
|
|
699
|
+
return;
|
|
700
|
+
this.inFlight = null;
|
|
701
|
+
this.inFlightResult = null;
|
|
702
|
+
result.complete();
|
|
703
|
+
this.scheduleGc();
|
|
704
|
+
},
|
|
705
|
+
}));
|
|
706
|
+
return result.asObservable();
|
|
707
|
+
}
|
|
708
|
+
scheduleGc() {
|
|
709
|
+
if (this.observers > 0 || this.inFlight || this.gcTimer)
|
|
710
|
+
return;
|
|
711
|
+
this.gcDeadline ??= Date.now() + this.definition.gcTime;
|
|
712
|
+
const remaining = Math.max(0, this.gcDeadline - Date.now());
|
|
713
|
+
this.gcTimer = setTimeout(() => {
|
|
714
|
+
this.gcTimer = null;
|
|
715
|
+
if (this.observers > 0 || this.inFlight)
|
|
716
|
+
return;
|
|
717
|
+
if (this.gcDeadline !== null && Date.now() < this.gcDeadline) {
|
|
718
|
+
this.scheduleGc();
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
this.gcDeadline = null;
|
|
722
|
+
this.evict();
|
|
723
|
+
}
|
|
724
|
+
}, Math.min(remaining, MAX_TIMER_DELAY));
|
|
725
|
+
}
|
|
726
|
+
cancelGc() {
|
|
727
|
+
if (this.gcTimer)
|
|
728
|
+
clearTimeout(this.gcTimer);
|
|
729
|
+
this.gcTimer = null;
|
|
730
|
+
this.gcDeadline = null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const snapshotRequest = (request) => {
|
|
734
|
+
const headers = snapshotHeaders(request.headers);
|
|
735
|
+
const params = snapshotParams(request.params);
|
|
736
|
+
return {
|
|
737
|
+
...request,
|
|
738
|
+
...(headers === undefined ? {} : { headers }),
|
|
739
|
+
...(params === undefined ? {} : { params }),
|
|
740
|
+
};
|
|
741
|
+
};
|
|
742
|
+
const snapshotHeaders = (headers) => {
|
|
743
|
+
if (!headers || headers instanceof HttpHeaders)
|
|
744
|
+
return headers;
|
|
745
|
+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [
|
|
746
|
+
key,
|
|
747
|
+
Array.isArray(value) ? [...value] : value,
|
|
748
|
+
]));
|
|
749
|
+
};
|
|
750
|
+
const snapshotParams = (params) => {
|
|
751
|
+
if (!params || params instanceof HttpParams)
|
|
752
|
+
return params;
|
|
753
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [
|
|
754
|
+
key,
|
|
755
|
+
Array.isArray(value) ? [...value] : value,
|
|
756
|
+
]));
|
|
757
|
+
};
|
|
758
|
+
const requestSignature = (request) => stableSerialize({
|
|
759
|
+
method: request.method,
|
|
760
|
+
path: request.path,
|
|
761
|
+
baseUrl: request.baseUrl ?? null,
|
|
762
|
+
params: normalizeParams(request.params),
|
|
763
|
+
headers: normalizeHeaders(request.headers),
|
|
764
|
+
auth: request.auth ?? 'none',
|
|
765
|
+
errorPolicy: request.errorPolicy ?? null,
|
|
766
|
+
withCredentials: request.withCredentials ?? false,
|
|
767
|
+
});
|
|
768
|
+
const normalizeParams = (params) => {
|
|
769
|
+
if (params instanceof HttpParams) {
|
|
770
|
+
return Object.fromEntries(params
|
|
771
|
+
.keys()
|
|
772
|
+
.sort()
|
|
773
|
+
.map((key) => [key, params.getAll(key)]));
|
|
774
|
+
}
|
|
775
|
+
return params ?? null;
|
|
776
|
+
};
|
|
777
|
+
const normalizeHeaders = (headers) => {
|
|
778
|
+
if (headers instanceof HttpHeaders) {
|
|
779
|
+
return Object.fromEntries(headers
|
|
780
|
+
.keys()
|
|
781
|
+
.map((key) => key.toLowerCase())
|
|
782
|
+
.sort()
|
|
783
|
+
.map((key) => [key, headers.getAll(key)]));
|
|
784
|
+
}
|
|
785
|
+
if (!headers)
|
|
786
|
+
return null;
|
|
787
|
+
return Object.fromEntries(Object.entries(headers)
|
|
788
|
+
.map(([key, value]) => [key.toLowerCase(), value])
|
|
789
|
+
.sort(([left], [right]) => left.localeCompare(right)));
|
|
790
|
+
};
|
|
791
|
+
const stableSerialize = (value) => {
|
|
792
|
+
if (value === null || typeof value !== 'object')
|
|
793
|
+
return JSON.stringify(value);
|
|
794
|
+
if (Array.isArray(value))
|
|
795
|
+
return `[${value.map(stableSerialize).join(',')}]`;
|
|
796
|
+
return `{${Object.entries(value)
|
|
797
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
798
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableSerialize(item)}`)
|
|
799
|
+
.join(',')}}`;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Generated bundle index. Do not edit.
|
|
804
|
+
*/
|
|
805
|
+
|
|
806
|
+
export { ApiCallerModule, ApiClient, ApiCredentialProvider, ApiErrorMapper, ApiErrorReporter, ApiQueryClient, ApiRequestTransformer, AuthenticationRequiredError, DisallowedAuthOriginError, IdentityApiErrorMapper, IdentityApiRequestTransformer, InvalidApiRequestError, QueryDefinitionConflictError, defineApiCall, provideApiClient, toRequestState };
|
|
807
|
+
//# sourceMappingURL=deejayy-api-handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deejayy-api-handler.mjs","sources":["../../../projects/api-handler/src/lib/transport/api-credential-provider.ts","../../../projects/api-handler/src/lib/transport/api-error-reporter.ts","../../../projects/api-handler/src/lib/transport/api-error-mapper.ts","../../../projects/api-handler/src/lib/transport/api-request-transformer.ts","../../../projects/api-handler/src/lib/transport/api-client-config.ts","../../../projects/api-handler/src/lib/api-handler.module.ts","../../../projects/api-handler/src/lib/transport/api-errors.ts","../../../projects/api-handler/src/lib/transport/api-client.ts","../../../projects/api-handler/src/lib/transport/api-request.ts","../../../projects/api-handler/src/lib/state/to-request-state.ts","../../../projects/api-handler/src/lib/query/api-query.ts","../../../projects/api-handler/src/lib/query/query-key.ts","../../../projects/api-handler/src/lib/query/api-query-client.ts","../../../projects/api-handler/src/deejayy-api-handler.ts"],"sourcesContent":["import { Injectable, Signal, signal } from '@angular/core';\n\nexport type ApiCredentialState =\n | { readonly token: null; readonly identity: null }\n | { readonly token: string; readonly identity: string };\n\nexport abstract class ApiCredentialProvider {\n abstract readonly credentials: Signal<ApiCredentialState>;\n}\n\n@Injectable()\nexport class AnonymousApiCredentialProvider extends ApiCredentialProvider {\n readonly credentials = signal<ApiCredentialState>({\n token: null,\n identity: null,\n });\n}\n","import { HttpErrorResponse } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { ApiRequest } from './api-request';\n\nexport abstract class ApiErrorReporter {\n abstract report(error: HttpErrorResponse, request: ApiRequest<unknown>): void;\n}\n\n@Injectable()\nexport class NoopApiErrorReporter extends ApiErrorReporter {\n report(_error: HttpErrorResponse, _request: ApiRequest<unknown>): void {}\n}\n","export abstract class ApiErrorMapper {\n abstract map(error: unknown): unknown;\n}\n\nexport class IdentityApiErrorMapper extends ApiErrorMapper {\n map(error: unknown): unknown {\n return error;\n }\n}\n","import { ApiRequest } from './api-request';\n\nexport abstract class ApiRequestTransformer {\n abstract transform<TBody>(request: ApiRequest<TBody>): ApiRequest<TBody>;\n}\n\nexport class IdentityApiRequestTransformer extends ApiRequestTransformer {\n transform<TBody>(request: ApiRequest<TBody>): ApiRequest<TBody> {\n return request;\n }\n}\n","import {\n EnvironmentProviders,\n InjectionToken,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport {\n ApiCredentialProvider,\n AnonymousApiCredentialProvider,\n} from './api-credential-provider';\nimport { ApiErrorReporter, NoopApiErrorReporter } from './api-error-reporter';\nimport { ApiErrorMapper, IdentityApiErrorMapper } from './api-error-mapper';\nimport { ApiErrorPolicy } from './api-request';\nimport {\n ApiRequestTransformer,\n IdentityApiRequestTransformer,\n} from './api-request-transformer';\n\nexport interface ApiClientConfig {\n readonly baseUrl: string;\n readonly allowedAuthOrigins?: readonly string[];\n readonly defaultErrorPolicy?: ApiErrorPolicy;\n}\n\nexport interface ResolvedApiClientConfig {\n readonly baseUrl: string;\n readonly allowedAuthOrigins: readonly string[];\n readonly defaultErrorPolicy: ApiErrorPolicy;\n}\n\nexport const API_CLIENT_CONFIG = new InjectionToken<ResolvedApiClientConfig>(\n 'API_CLIENT_CONFIG',\n);\n\nexport const resolveApiClientConfig = (\n config: ApiClientConfig,\n): ResolvedApiClientConfig => {\n const browserOrigin = globalThis.location?.origin ?? 'http://localhost';\n return {\n baseUrl: config.baseUrl,\n allowedAuthOrigins: (config.allowedAuthOrigins ?? []).map(\n (origin) => new URL(origin, browserOrigin).origin,\n ),\n defaultErrorPolicy: config.defaultErrorPolicy ?? 'propagate',\n };\n};\n\nexport const provideApiClient = (\n config: ApiClientConfig,\n): EnvironmentProviders =>\n makeEnvironmentProviders([\n { provide: API_CLIENT_CONFIG, useValue: resolveApiClientConfig(config) },\n {\n provide: ApiCredentialProvider,\n useClass: AnonymousApiCredentialProvider,\n },\n { provide: ApiErrorReporter, useClass: NoopApiErrorReporter },\n { provide: ApiRequestTransformer, useClass: IdentityApiRequestTransformer },\n { provide: ApiErrorMapper, useClass: IdentityApiErrorMapper },\n ]);\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport {\n API_CLIENT_CONFIG,\n ApiClientConfig,\n resolveApiClientConfig,\n} from './transport/api-client-config';\nimport {\n AnonymousApiCredentialProvider,\n ApiCredentialProvider,\n} from './transport/api-credential-provider';\nimport {\n ApiErrorReporter,\n NoopApiErrorReporter,\n} from './transport/api-error-reporter';\nimport {\n ApiErrorMapper,\n IdentityApiErrorMapper,\n} from './transport/api-error-mapper';\nimport {\n ApiRequestTransformer,\n IdentityApiRequestTransformer,\n} from './transport/api-request-transformer';\n\n@NgModule({})\nexport class ApiCallerModule {\n static forRoot(\n config: ApiClientConfig,\n ): ModuleWithProviders<ApiCallerModule> {\n return {\n ngModule: ApiCallerModule,\n providers: [\n {\n provide: API_CLIENT_CONFIG,\n useValue: resolveApiClientConfig(config),\n },\n {\n provide: ApiCredentialProvider,\n useClass: AnonymousApiCredentialProvider,\n },\n { provide: ApiErrorReporter, useClass: NoopApiErrorReporter },\n {\n provide: ApiRequestTransformer,\n useClass: IdentityApiRequestTransformer,\n },\n { provide: ApiErrorMapper, useClass: IdentityApiErrorMapper },\n ],\n };\n }\n}\n","export class AuthenticationRequiredError extends Error {\n constructor() {\n super('Authentication is required for this request.');\n this.name = 'AuthenticationRequiredError';\n }\n}\n\nexport class DisallowedAuthOriginError extends Error {\n constructor(readonly origin: string) {\n super(`Authentication is not allowed for origin \"${origin}\".`);\n this.name = 'DisallowedAuthOriginError';\n }\n}\n\nexport class InvalidApiRequestError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidApiRequestError';\n }\n}\n","import {\n HttpClient,\n HttpErrorResponse,\n HttpHeaders,\n HttpResponse,\n} from '@angular/common/http';\nimport { ErrorHandler, Injectable, inject } from '@angular/core';\nimport { Observable, catchError, defer, throwError } from 'rxjs';\nimport { API_CLIENT_CONFIG } from './api-client-config';\nimport { ApiCredentialProvider } from './api-credential-provider';\nimport { ApiErrorReporter } from './api-error-reporter';\nimport { ApiErrorMapper } from './api-error-mapper';\nimport {\n AuthenticationRequiredError,\n DisallowedAuthOriginError,\n InvalidApiRequestError,\n} from './api-errors';\nimport { ApiCall, ApiRequest } from './api-request';\nimport { ApiRequestTransformer } from './api-request-transformer';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiClient {\n private readonly http = inject(HttpClient);\n private readonly config = inject(API_CLIENT_CONFIG);\n private readonly credentialProvider = inject(ApiCredentialProvider);\n private readonly errorReporter = inject(ApiErrorReporter);\n private readonly requestTransformer = inject(ApiRequestTransformer);\n private readonly errorMapper = inject(ApiErrorMapper);\n private readonly errorHandler = inject(ErrorHandler);\n\n request<TResponse, TBody>(\n request: ApiCall<TResponse, TBody>,\n ): Observable<TResponse>;\n request<TResponse, TBody = never>(\n request: ApiRequest<TBody>,\n ): Observable<TResponse>;\n request<TResponse, TBody = never>(\n request: ApiRequest<TBody>,\n ): Observable<TResponse> {\n return this.execute<TResponse, TBody>(\n snapshotRequest(request),\n 'body',\n 'json',\n );\n }\n\n requestResponse<TResponse, TBody>(\n request: ApiCall<TResponse, TBody>,\n ): Observable<HttpResponse<TResponse>>;\n requestResponse<TResponse, TBody = never>(\n request: ApiRequest<TBody>,\n ): Observable<HttpResponse<TResponse>>;\n requestResponse<TResponse, TBody = never>(\n request: ApiRequest<TBody>,\n ): Observable<HttpResponse<TResponse>> {\n return this.execute<TResponse, TBody>(\n snapshotRequest(request),\n 'response',\n 'json',\n );\n }\n\n requestBlob<TBody = never>(request: ApiRequest<TBody>): Observable<Blob> {\n return this.execute<Blob, TBody>(snapshotRequest(request), 'body', 'blob');\n }\n\n requestBlobResponse<TBody = never>(\n request: ApiRequest<TBody>,\n ): Observable<HttpResponse<Blob>> {\n return this.execute<Blob, TBody>(\n snapshotRequest(request),\n 'response',\n 'blob',\n );\n }\n\n private execute<TResponse, TBody>(\n request: ApiRequest<TBody>,\n observe: 'body',\n responseType: 'json',\n ): Observable<TResponse>;\n private execute<TResponse, TBody>(\n request: ApiRequest<TBody>,\n observe: 'response',\n responseType: 'json',\n ): Observable<HttpResponse<TResponse>>;\n private execute<TResponse, TBody>(\n request: ApiRequest<TBody>,\n observe: 'body',\n responseType: 'blob',\n ): Observable<Blob>;\n private execute<TResponse, TBody>(\n request: ApiRequest<TBody>,\n observe: 'response',\n responseType: 'blob',\n ): Observable<HttpResponse<Blob>>;\n private execute<TResponse, TBody>(\n request: ApiRequest<TBody>,\n observe: 'body' | 'response',\n responseType: 'json' | 'blob',\n ): Observable<TResponse | Blob | HttpResponse<TResponse | Blob>> {\n return defer(() => {\n try {\n const effectiveRequest = this.requestTransformer.transform(request);\n const url = this.buildUrl(\n effectiveRequest.baseUrl ?? this.config.baseUrl,\n effectiveRequest.path,\n );\n const auth = effectiveRequest.auth ?? 'none';\n const credentials = this.credentialProvider.credentials();\n let headers = this.copyHeaders(effectiveRequest.headers);\n\n if (headers.has('Authorization')) {\n throw new InvalidApiRequestError(\n 'The Authorization header is owned by ApiClient.',\n );\n }\n\n if (auth === 'required' && credentials.token === null) {\n throw new AuthenticationRequiredError();\n }\n\n if (auth !== 'none' && credentials.token !== null) {\n this.assertAuthOriginAllowed(url);\n headers = headers.set('Authorization', `Bearer ${credentials.token}`);\n }\n\n const options = {\n headers,\n withCredentials: effectiveRequest.withCredentials ?? false,\n ...(effectiveRequest.body === undefined\n ? {}\n : { body: effectiveRequest.body }),\n ...(effectiveRequest.params === undefined\n ? {}\n : { params: effectiveRequest.params }),\n } as const;\n\n const response$: Observable<\n TResponse | Blob | HttpResponse<TResponse | Blob>\n > =\n responseType === 'blob'\n ? observe === 'response'\n ? this.http.request(effectiveRequest.method, url, {\n ...options,\n observe: 'response',\n responseType: 'blob',\n })\n : this.http.request(effectiveRequest.method, url, {\n ...options,\n observe: 'body',\n responseType: 'blob',\n })\n : observe === 'response'\n ? this.http.request<TResponse>(effectiveRequest.method, url, {\n ...options,\n observe: 'response',\n responseType: 'json',\n })\n : this.http.request<TResponse>(effectiveRequest.method, url, {\n ...options,\n observe: 'body',\n responseType: 'json',\n });\n\n return response$.pipe(\n catchError((error: unknown) => {\n const policy =\n effectiveRequest.errorPolicy ?? this.config.defaultErrorPolicy;\n if (\n policy === 'report-and-propagate' &&\n error instanceof HttpErrorResponse\n ) {\n try {\n this.errorReporter.report(\n error,\n effectiveRequest as ApiRequest<unknown>,\n );\n } catch (reporterError) {\n try {\n this.errorHandler.handleError(reporterError);\n } catch {\n // Reporting infrastructure must never replace the transport error.\n }\n }\n }\n return throwError(() => this.errorMapper.map(error));\n }),\n );\n } catch (error) {\n return throwError(() => this.errorMapper.map(error));\n }\n });\n }\n\n private buildUrl(baseUrl: string, path: string): string {\n if (/^[a-z][a-z\\d+.-]*:/i.test(path) || path.startsWith('//')) {\n throw new InvalidApiRequestError('ApiRequest.path must be relative.');\n }\n return `${baseUrl.replace(/\\/+$/, '')}/${path.replace(/^\\/+/, '')}`;\n }\n\n private copyHeaders(headers?: ApiRequest<unknown>['headers']): HttpHeaders {\n if (headers instanceof HttpHeaders) {\n let copy = new HttpHeaders();\n for (const name of headers.keys()) {\n copy = copy.set(name, headers.getAll(name) ?? []);\n }\n return copy;\n }\n let copy = new HttpHeaders();\n for (const [name, value] of Object.entries(headers ?? {})) {\n copy = copy.set(name, typeof value === 'string' ? value : [...value]);\n }\n return copy;\n }\n\n private assertAuthOriginAllowed(url: string): void {\n const browserOrigin = globalThis.location?.origin ?? 'http://localhost';\n const targetOrigin = new URL(url, browserOrigin).origin;\n if (\n targetOrigin !== browserOrigin &&\n !this.config.allowedAuthOrigins.includes(targetOrigin)\n ) {\n throw new DisallowedAuthOriginError(targetOrigin);\n }\n }\n}\n\nconst snapshotRequest = <TBody>(\n request: ApiRequest<TBody>,\n): ApiRequest<TBody> =>\n ({\n ...request,\n ...(request.headers instanceof HttpHeaders\n ? { headers: copyHttpHeaders(request.headers) }\n : request.headers === undefined\n ? {}\n : {\n headers: Object.fromEntries(\n Object.entries(request.headers).map(([name, value]) => [\n name,\n typeof value === 'string' ? value : [...value],\n ]),\n ),\n }),\n ...(request.params === undefined ? {} : { params: request.params }),\n }) as ApiRequest<TBody>;\n\nconst copyHttpHeaders = (headers: HttpHeaders): HttpHeaders => {\n let copy = new HttpHeaders();\n for (const name of headers.keys()) {\n copy = copy.set(name, headers.getAll(name) ?? []);\n }\n return copy;\n};\n","import { HttpHeaders, HttpParams } from '@angular/common/http';\n\nexport type HttpMethod =\n 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\nexport type ApiAuthMode = 'none' | 'optional' | 'required';\nexport type ApiErrorPolicy = 'propagate' | 'report-and-propagate';\n\nexport interface ApiRequest<TBody = never> {\n readonly method: HttpMethod;\n readonly path: string;\n readonly baseUrl?: string;\n readonly body?: TBody;\n readonly params?:\n | HttpParams\n | Record<\n string,\n string | number | boolean | readonly (string | number | boolean)[]\n >;\n readonly headers?: HttpHeaders | Record<string, string | readonly string[]>;\n readonly auth?: ApiAuthMode;\n readonly errorPolicy?: ApiErrorPolicy;\n readonly withCredentials?: boolean;\n}\n\nexport interface ApiCall<TResponse, TBody = never> extends ApiRequest<TBody> {\n readonly __responseType?: TResponse;\n}\n\nexport const defineApiCall = <TResponse, TBody = never>(\n request: ApiRequest<TBody>,\n): ApiCall<TResponse, TBody> => request;\n","import { OperatorFunction, catchError, map, of, startWith } from 'rxjs';\nimport { RequestState } from './request-state';\n\nexport interface RequestStateOptions<E> {\n readonly mapError?: (error: unknown) => E;\n}\n\nexport const toRequestState =\n <T, E = unknown>(\n options?: RequestStateOptions<E>,\n ): OperatorFunction<T, RequestState<T, E>> =>\n (source) =>\n source.pipe(\n map((data): RequestState<T, E> => ({ status: 'success', data })),\n catchError((error: unknown) =>\n of<RequestState<T, E>>({\n status: 'error',\n error: options?.mapError ? options.mapError(error) : (error as E),\n }),\n ),\n startWith({ status: 'loading' } as const),\n );\n","import { Observable } from 'rxjs';\nimport { ApiCall } from '../transport/api-request';\nimport { QueryKey } from './query-key';\nimport { QueryState } from './query-state';\n\nexport interface ApiQueryOptions<T> {\n readonly key: QueryKey;\n readonly request: ApiCall<T>;\n readonly staleTime?: number;\n readonly gcTime?: number;\n}\n\nexport interface ApiQuery<T, E = unknown> {\n readonly state$: Observable<QueryState<T, E>>;\n readonly data$: Observable<T | undefined>;\n refresh(): Observable<T>;\n ensureFresh(): Observable<T>;\n invalidate(): void;\n cancel(): void;\n}\n\nexport class QueryDefinitionConflictError extends Error {\n constructor(readonly key: QueryKey) {\n super(\n `A different query definition already exists for key ${JSON.stringify(key)}.`,\n );\n this.name = 'QueryDefinitionConflictError';\n }\n}\n","export type QueryKeyPart = string | number | boolean | null;\nexport type QueryKey = readonly QueryKeyPart[];\n\nexport const snapshotQueryKey = (key: QueryKey): QueryKey => {\n if (!Array.isArray(key)) {\n throw new TypeError('Query key must be an array.');\n }\n const snapshot = key.map((part) => {\n if (\n part !== null &&\n typeof part !== 'string' &&\n typeof part !== 'boolean' &&\n typeof part !== 'number'\n ) {\n throw new TypeError(\n 'Query key parts must be strings, finite numbers, booleans, or null.',\n );\n }\n if (typeof part === 'number' && !Number.isFinite(part)) {\n throw new TypeError('Query key numbers must be finite.');\n }\n return part;\n });\n return Object.freeze(snapshot);\n};\n\nexport const queryKeyId = (key: QueryKey): string =>\n key\n .map((part) => {\n if (part === null) return 'null';\n if (typeof part === 'number')\n return `number:${Object.is(part, -0) ? 0 : part}`;\n return `${typeof part}:${JSON.stringify(part)}`;\n })\n .join('|');\n","import { HttpHeaders, HttpParams } from '@angular/common/http';\nimport { Injectable, effect, inject } from '@angular/core';\nimport {\n BehaviorSubject,\n Observable,\n ReplaySubject,\n Subscription,\n distinctUntilChanged,\n map,\n of,\n} from 'rxjs';\nimport { ApiClient } from '../transport/api-client';\nimport { ApiCredentialProvider } from '../transport/api-credential-provider';\nimport { ApiRequest } from '../transport/api-request';\nimport {\n ApiQuery,\n ApiQueryOptions,\n QueryDefinitionConflictError,\n} from './api-query';\nimport { QueryKey, queryKeyId, snapshotQueryKey } from './query-key';\nimport { QueryState } from './query-state';\n\nconst DEFAULT_GC_TIME = 300_000;\nconst MAX_TIMER_DELAY = 2_147_483_647;\n\ninterface QueryDefinition {\n readonly key: QueryKey;\n readonly keyId: string;\n readonly request: ApiRequest<never>;\n readonly staleTime: number;\n readonly gcTime: number;\n readonly signature: string;\n}\n\ninterface CacheEntry {\n readonly definition: QueryDefinition;\n readonly partition: string;\n invalidate(): void;\n clear(): void;\n}\n\ninterface QueryHost {\n getEntry<T>(definition: QueryDefinition): QueryCacheEntry<T>;\n addHandle(handle: QueryHandle<unknown>): void;\n removeHandle(handle: QueryHandle<unknown>): void;\n isCurrentEntry(entry: CacheEntry, definition: QueryDefinition): boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ApiQueryClient {\n private readonly api = inject(ApiClient);\n private readonly credentials = inject(ApiCredentialProvider);\n private readonly entries = new Map<string, CacheEntry>();\n private readonly handles = new Set<QueryHandle<unknown>>();\n private lastIdentity = this.credentials.credentials().identity;\n private readonly host: QueryHost = {\n getEntry: <T>(definition: QueryDefinition) => this.getEntry<T>(definition),\n addHandle: (handle) => this.handles.add(handle),\n removeHandle: (handle) => this.handles.delete(handle),\n isCurrentEntry: (entry, definition) =>\n entry.partition === this.partitionFor(definition.request),\n };\n\n constructor() {\n effect(() => {\n const identity = this.credentials.credentials().identity;\n if (identity === this.lastIdentity) return;\n this.lastIdentity = identity;\n const authenticatedHandles = [...this.handles].filter((handle) =>\n handle.isAuthenticated(),\n );\n this.clearAuthenticated();\n for (const handle of authenticatedHandles)\n handle.rebindForIdentityChange();\n });\n }\n\n query<T>(options: ApiQueryOptions<T>): ApiQuery<T> {\n const definition = this.createDefinition(options);\n this.assertCompatibleIfPresent(definition);\n return new QueryHandle<T>(this.host, definition);\n }\n\n invalidate(key: QueryKey): void {\n const id = queryKeyId(snapshotQueryKey(key));\n this.forKey(id, (entry) => entry.invalidate());\n }\n\n invalidateWhere(predicate: (key: QueryKey) => boolean): void {\n const matched = new Set<string>();\n for (const entry of this.entries.values()) {\n if (\n !matched.has(entry.definition.keyId) &&\n predicate(entry.definition.key)\n ) {\n matched.add(entry.definition.keyId);\n this.forKey(entry.definition.keyId, (candidate) =>\n candidate.invalidate(),\n );\n }\n }\n }\n\n clear(): void {\n this.clearEntries(() => true);\n }\n\n clearAuthenticated(): void {\n this.clearEntries((entry) => entry.partition !== 'public');\n }\n\n private getEntry<T>(definition: QueryDefinition): QueryCacheEntry<T> {\n const partition = this.partitionFor(definition.request);\n const effectiveKey = `${partition}\\u0000${definition.keyId}`;\n const existing = this.entries.get(effectiveKey);\n if (existing) {\n if (existing.definition.signature !== definition.signature) {\n throw new QueryDefinitionConflictError(definition.key);\n }\n return existing as QueryCacheEntry<T>;\n }\n const entry = new QueryCacheEntry<T>(this.api, definition, partition, () =>\n this.evict(effectiveKey, entry),\n );\n this.entries.set(effectiveKey, entry);\n return entry;\n }\n\n private createDefinition<T>(options: ApiQueryOptions<T>): QueryDefinition {\n if (options.request.method !== 'GET') {\n throw new TypeError('ApiQueryClient only supports GET requests.');\n }\n const staleTime = options.staleTime ?? 0;\n const gcTime = options.gcTime ?? DEFAULT_GC_TIME;\n this.assertTiming('staleTime', staleTime);\n this.assertTiming('gcTime', gcTime);\n const key = snapshotQueryKey(options.key);\n const request = snapshotRequest(options.request);\n return {\n key,\n keyId: queryKeyId(key),\n request,\n staleTime,\n gcTime,\n signature: `${requestSignature(request)}|stale:${staleTime}|gc:${gcTime}`,\n };\n }\n\n private assertCompatibleIfPresent(definition: QueryDefinition): void {\n const effectiveKey = `${this.partitionFor(definition.request)}\\u0000${definition.keyId}`;\n const existing = this.entries.get(effectiveKey);\n if (existing && existing.definition.signature !== definition.signature) {\n throw new QueryDefinitionConflictError(definition.key);\n }\n }\n\n private assertTiming(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError(`${name} must be a finite, non-negative number.`);\n }\n }\n\n private partitionFor(request: ApiRequest<never>): string {\n const auth = request.auth ?? 'none';\n if (auth === 'none') return 'public';\n const identity = this.credentials.credentials().identity;\n return identity === null\n ? 'anonymous'\n : `identity:${JSON.stringify(identity)}`;\n }\n\n private forKey(keyId: string, action: (entry: CacheEntry) => void): void {\n for (const entry of this.entries.values()) {\n if (entry.definition.keyId === keyId) action(entry);\n }\n }\n\n private clearEntries(predicate: (entry: CacheEntry) => boolean): void {\n const removed = new Set<CacheEntry>();\n for (const [key, entry] of this.entries) {\n if (!predicate(entry)) continue;\n entry.clear();\n removed.add(entry);\n this.entries.delete(key);\n }\n for (const handle of this.handles) {\n if (handle.isBoundToAny(removed)) handle.detachAfterClear();\n }\n }\n\n private evict(key: string, entry: CacheEntry): void {\n if (this.entries.get(key) !== entry) return;\n this.entries.delete(key);\n const removed = new Set([entry]);\n for (const handle of this.handles)\n if (handle.isBoundToAny(removed)) handle.detachAfterClear();\n }\n}\n\nclass QueryHandle<T> implements ApiQuery<T> {\n private readonly output = new ReplaySubject<QueryState<T>>(1);\n private bound: QueryCacheEntry<T> | null = null;\n private entrySubscription: Subscription | null = null;\n private subscribers = 0;\n\n readonly state$ = new Observable<QueryState<T>>((subscriber) => {\n this.subscribers++;\n this.bind(true);\n const subscription = this.output.subscribe(subscriber);\n return () => {\n subscription.unsubscribe();\n this.subscribers--;\n if (this.subscribers === 0) this.unbind();\n };\n });\n readonly data$ = this.state$.pipe(\n map((state) => {\n if (state.status === 'success') return state.data;\n if (state.status === 'error') return state.previousData;\n return undefined;\n }),\n distinctUntilChanged(),\n );\n\n constructor(\n private readonly client: QueryHost,\n private readonly definition: QueryDefinition,\n ) {}\n\n refresh(): Observable<T> {\n return this.ensureBound().refresh();\n }\n\n ensureFresh(): Observable<T> {\n return this.ensureBound().ensureFresh();\n }\n\n invalidate(): void {\n this.ensureBound().invalidate();\n }\n\n cancel(): void {\n this.bound?.cancel();\n }\n\n rebindForIdentityChange(): void {\n const active = this.subscribers > 0;\n this.output.next({ status: 'idle' });\n if (active) this.bind(true);\n }\n\n isAuthenticated(): boolean {\n return (this.definition.request.auth ?? 'none') !== 'none';\n }\n\n isBoundToAny(entries: Set<CacheEntry>): boolean {\n return this.bound !== null && entries.has(this.bound);\n }\n\n detachAfterClear(): void {\n this.unbind();\n this.output.next({ status: 'idle' });\n }\n\n private ensureBound(): QueryCacheEntry<T> {\n if (\n this.bound &&\n !this.client.isCurrentEntry(this.bound, this.definition)\n ) {\n this.unbind();\n this.output.next({ status: 'idle' });\n }\n if (!this.bound) {\n this.bound = this.client.getEntry<T>(this.definition);\n this.client.addHandle(this as QueryHandle<unknown>);\n }\n return this.bound!;\n }\n\n private bind(startIfNeeded: boolean): void {\n if (\n this.bound &&\n !this.client.isCurrentEntry(this.bound, this.definition)\n ) {\n this.unbind();\n this.output.next({ status: 'idle' });\n }\n const entry = this.bound ?? this.client.getEntry<T>(this.definition);\n this.bound = entry;\n this.client.addHandle(this as QueryHandle<unknown>);\n if (!this.entrySubscription) {\n entry.observe();\n this.entrySubscription = entry.state$.subscribe(this.output);\n }\n if (startIfNeeded) entry.startIfNeeded();\n }\n\n private unbind(): void {\n if (!this.bound) return;\n if (this.entrySubscription) {\n this.entrySubscription.unsubscribe();\n this.entrySubscription = null;\n this.bound.unobserve();\n }\n this.bound = null;\n this.client.removeHandle(this as QueryHandle<unknown>);\n }\n}\n\nclass QueryCacheEntry<T> {\n readonly state$ = new BehaviorSubject<QueryState<T>>({ status: 'idle' });\n private observers = 0;\n private invalidated = false;\n private inFlight: Subscription | null = null;\n private inFlightResult: ReplaySubject<T> | null = null;\n private requestId = 0;\n private beforeRequest: QueryState<T> = { status: 'idle' };\n private gcTimer: ReturnType<typeof setTimeout> | null = null;\n private gcDeadline: number | null = null;\n\n constructor(\n private readonly api: ApiClient,\n readonly definition: QueryDefinition,\n readonly partition: string,\n private readonly evict: () => void,\n ) {\n this.scheduleGc();\n }\n\n observe(): void {\n this.observers++;\n this.cancelGc();\n }\n\n unobserve(): void {\n this.observers--;\n this.scheduleGc();\n }\n\n startIfNeeded(): void {\n if (this.inFlight) return;\n const state = this.state$.value;\n if (state.status === 'success') {\n if (\n this.invalidated ||\n Date.now() - state.updatedAt >= this.definition.staleTime\n ) {\n this.start();\n }\n return;\n }\n if (state.status === 'error' || state.status === 'idle') this.start();\n }\n\n refresh(): Observable<T> {\n if (this.inFlight) return this.inFlightResult!.asObservable();\n return this.start();\n }\n\n ensureFresh(): Observable<T> {\n if (this.inFlight) return this.inFlightResult!.asObservable();\n const state = this.state$.value;\n if (\n state.status === 'success' &&\n !this.invalidated &&\n Date.now() - state.updatedAt < this.definition.staleTime\n ) {\n return of(state.data);\n }\n return this.refresh();\n }\n\n invalidate(): void {\n this.invalidated = true;\n if (this.observers > 0) this.refresh();\n }\n\n cancel(): void {\n if (!this.inFlight) return;\n this.requestId++;\n this.inFlight.unsubscribe();\n this.inFlight = null;\n this.inFlightResult?.complete();\n this.inFlightResult = null;\n const previous = this.beforeRequest;\n if (previous.status === 'success') {\n this.state$.next({ ...previous, refreshing: false });\n } else {\n this.state$.next({ status: 'idle' });\n }\n this.scheduleGc();\n }\n\n clear(): void {\n this.cancelGc();\n this.requestId++;\n this.inFlight?.unsubscribe();\n this.inFlight = null;\n this.inFlightResult?.complete();\n this.inFlightResult = null;\n this.state$.next({ status: 'idle' });\n }\n\n private start(): Observable<T> {\n this.cancelGc();\n const id = ++this.requestId;\n const current = this.state$.value;\n const previous =\n current.status === 'error' &&\n 'previousData' in current &&\n current.updatedAt !== undefined\n ? ({\n status: 'success',\n data: current.previousData as T,\n refreshing: false,\n updatedAt: current.updatedAt,\n } as const)\n : current;\n this.beforeRequest = previous;\n this.invalidated = false;\n if (previous.status === 'success') {\n this.state$.next({ ...previous, refreshing: true });\n } else {\n this.state$.next({ status: 'loading' });\n }\n\n const inFlight = new Subscription();\n const result = new ReplaySubject<T>(1);\n this.inFlight = inFlight;\n this.inFlightResult = result;\n inFlight.add(\n this.api.request<T>(this.definition.request).subscribe({\n next: (data) => {\n if (id !== this.requestId) return;\n this.state$.next({\n status: 'success',\n data,\n refreshing: false,\n updatedAt: Date.now(),\n });\n result.next(data);\n },\n error: (error: unknown) => {\n if (id !== this.requestId) return;\n this.inFlight = null;\n this.inFlightResult = null;\n if (previous.status === 'success') {\n this.state$.next({\n status: 'error',\n error,\n previousData: previous.data,\n updatedAt: previous.updatedAt,\n });\n } else {\n this.state$.next({ status: 'error', error });\n }\n result.error(error);\n this.scheduleGc();\n },\n complete: () => {\n if (id !== this.requestId) return;\n this.inFlight = null;\n this.inFlightResult = null;\n result.complete();\n this.scheduleGc();\n },\n }),\n );\n return result.asObservable();\n }\n\n private scheduleGc(): void {\n if (this.observers > 0 || this.inFlight || this.gcTimer) return;\n this.gcDeadline ??= Date.now() + this.definition.gcTime;\n const remaining = Math.max(0, this.gcDeadline - Date.now());\n this.gcTimer = setTimeout(\n () => {\n this.gcTimer = null;\n if (this.observers > 0 || this.inFlight) return;\n if (this.gcDeadline !== null && Date.now() < this.gcDeadline) {\n this.scheduleGc();\n } else {\n this.gcDeadline = null;\n this.evict();\n }\n },\n Math.min(remaining, MAX_TIMER_DELAY),\n );\n }\n\n private cancelGc(): void {\n if (this.gcTimer) clearTimeout(this.gcTimer);\n this.gcTimer = null;\n this.gcDeadline = null;\n }\n}\n\nconst snapshotRequest = (request: ApiRequest<never>): ApiRequest<never> => {\n const headers = snapshotHeaders(request.headers);\n const params = snapshotParams(request.params);\n return {\n ...request,\n ...(headers === undefined ? {} : { headers }),\n ...(params === undefined ? {} : { params }),\n };\n};\n\nconst snapshotHeaders = (\n headers: ApiRequest<never>['headers'],\n): ApiRequest<never>['headers'] => {\n if (!headers || headers instanceof HttpHeaders) return headers;\n return Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [\n key,\n Array.isArray(value) ? [...value] : value,\n ]),\n );\n};\n\nconst snapshotParams = (\n params: ApiRequest<never>['params'],\n): ApiRequest<never>['params'] => {\n if (!params || params instanceof HttpParams) return params;\n return Object.fromEntries(\n Object.entries(params).map(([key, value]) => [\n key,\n Array.isArray(value) ? [...value] : value,\n ]),\n );\n};\n\nconst requestSignature = (request: ApiRequest<never>): string =>\n stableSerialize({\n method: request.method,\n path: request.path,\n baseUrl: request.baseUrl ?? null,\n params: normalizeParams(request.params),\n headers: normalizeHeaders(request.headers),\n auth: request.auth ?? 'none',\n errorPolicy: request.errorPolicy ?? null,\n withCredentials: request.withCredentials ?? false,\n });\n\nconst normalizeParams = (params: ApiRequest<never>['params']): unknown => {\n if (params instanceof HttpParams) {\n return Object.fromEntries(\n params\n .keys()\n .sort()\n .map((key) => [key, params.getAll(key)]),\n );\n }\n return params ?? null;\n};\n\nconst normalizeHeaders = (headers: ApiRequest<never>['headers']): unknown => {\n if (headers instanceof HttpHeaders) {\n return Object.fromEntries(\n headers\n .keys()\n .map((key) => key.toLowerCase())\n .sort()\n .map((key) => [key, headers.getAll(key)]),\n );\n }\n if (!headers) return null;\n return Object.fromEntries(\n Object.entries(headers)\n .map(([key, value]) => [key.toLowerCase(), value] as const)\n .sort(([left], [right]) => left.localeCompare(right)),\n );\n};\n\nconst stableSerialize = (value: unknown): string => {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableSerialize).join(',')}]`;\n return `{${Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, item]) => `${JSON.stringify(key)}:${stableSerialize(item)}`)\n .join(',')}}`;\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["snapshotRequest"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deejayy/api-handler",
|
|
3
|
+
"version": "21.0.0",
|
|
4
|
+
"description": "Composable reactive HTTP primitives for Angular",
|
|
5
|
+
"private": false,
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://forge.deejayy.hu/angular-packages/api-handler"
|
|
9
|
+
},
|
|
10
|
+
"author": "DeeJayy",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://forge.deejayy.hu/angular-packages/api-handler/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"angular",
|
|
17
|
+
"angular 2",
|
|
18
|
+
"angular 21",
|
|
19
|
+
"api",
|
|
20
|
+
"api-call",
|
|
21
|
+
"api-handler",
|
|
22
|
+
"apicall"
|
|
23
|
+
],
|
|
24
|
+
"homepage": "https://forge.deejayy.hu/angular-packages/api-handler",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@angular/common": "^21",
|
|
27
|
+
"@angular/core": "^21",
|
|
28
|
+
"rxjs": "~7.8.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "^2.3.0"
|
|
32
|
+
},
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"module": "fesm2022/deejayy-api-handler.mjs",
|
|
35
|
+
"typings": "types/deejayy-api-handler.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": {
|
|
38
|
+
"default": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./types/deejayy-api-handler.d.ts",
|
|
42
|
+
"default": "./fesm2022/deejayy-api-handler.mjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"type": "module"
|
|
46
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { EnvironmentProviders, ModuleWithProviders, Signal } from '@angular/core';
|
|
3
|
+
import { HttpParams, HttpHeaders, HttpResponse, HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { Observable, OperatorFunction } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
7
|
+
type ApiAuthMode = 'none' | 'optional' | 'required';
|
|
8
|
+
type ApiErrorPolicy = 'propagate' | 'report-and-propagate';
|
|
9
|
+
interface ApiRequest<TBody = never> {
|
|
10
|
+
readonly method: HttpMethod;
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly baseUrl?: string;
|
|
13
|
+
readonly body?: TBody;
|
|
14
|
+
readonly params?: HttpParams | Record<string, string | number | boolean | readonly (string | number | boolean)[]>;
|
|
15
|
+
readonly headers?: HttpHeaders | Record<string, string | readonly string[]>;
|
|
16
|
+
readonly auth?: ApiAuthMode;
|
|
17
|
+
readonly errorPolicy?: ApiErrorPolicy;
|
|
18
|
+
readonly withCredentials?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface ApiCall<TResponse, TBody = never> extends ApiRequest<TBody> {
|
|
21
|
+
readonly __responseType?: TResponse;
|
|
22
|
+
}
|
|
23
|
+
declare const defineApiCall: <TResponse, TBody = never>(request: ApiRequest<TBody>) => ApiCall<TResponse, TBody>;
|
|
24
|
+
|
|
25
|
+
interface ApiClientConfig {
|
|
26
|
+
readonly baseUrl: string;
|
|
27
|
+
readonly allowedAuthOrigins?: readonly string[];
|
|
28
|
+
readonly defaultErrorPolicy?: ApiErrorPolicy;
|
|
29
|
+
}
|
|
30
|
+
declare const provideApiClient: (config: ApiClientConfig) => EnvironmentProviders;
|
|
31
|
+
|
|
32
|
+
declare class ApiCallerModule {
|
|
33
|
+
static forRoot(config: ApiClientConfig): ModuleWithProviders<ApiCallerModule>;
|
|
34
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ApiCallerModule, never>;
|
|
35
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<ApiCallerModule, never, never, never>;
|
|
36
|
+
static ɵinj: i0.ɵɵInjectorDeclaration<ApiCallerModule>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare class ApiClient {
|
|
40
|
+
private readonly http;
|
|
41
|
+
private readonly config;
|
|
42
|
+
private readonly credentialProvider;
|
|
43
|
+
private readonly errorReporter;
|
|
44
|
+
private readonly requestTransformer;
|
|
45
|
+
private readonly errorMapper;
|
|
46
|
+
private readonly errorHandler;
|
|
47
|
+
request<TResponse, TBody>(request: ApiCall<TResponse, TBody>): Observable<TResponse>;
|
|
48
|
+
request<TResponse, TBody = never>(request: ApiRequest<TBody>): Observable<TResponse>;
|
|
49
|
+
requestResponse<TResponse, TBody>(request: ApiCall<TResponse, TBody>): Observable<HttpResponse<TResponse>>;
|
|
50
|
+
requestResponse<TResponse, TBody = never>(request: ApiRequest<TBody>): Observable<HttpResponse<TResponse>>;
|
|
51
|
+
requestBlob<TBody = never>(request: ApiRequest<TBody>): Observable<Blob>;
|
|
52
|
+
requestBlobResponse<TBody = never>(request: ApiRequest<TBody>): Observable<HttpResponse<Blob>>;
|
|
53
|
+
private execute;
|
|
54
|
+
private buildUrl;
|
|
55
|
+
private copyHeaders;
|
|
56
|
+
private assertAuthOriginAllowed;
|
|
57
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ApiClient, never>;
|
|
58
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ApiClient>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type ApiCredentialState = {
|
|
62
|
+
readonly token: null;
|
|
63
|
+
readonly identity: null;
|
|
64
|
+
} | {
|
|
65
|
+
readonly token: string;
|
|
66
|
+
readonly identity: string;
|
|
67
|
+
};
|
|
68
|
+
declare abstract class ApiCredentialProvider {
|
|
69
|
+
abstract readonly credentials: Signal<ApiCredentialState>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
declare abstract class ApiErrorReporter {
|
|
73
|
+
abstract report(error: HttpErrorResponse, request: ApiRequest<unknown>): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare abstract class ApiErrorMapper {
|
|
77
|
+
abstract map(error: unknown): unknown;
|
|
78
|
+
}
|
|
79
|
+
declare class IdentityApiErrorMapper extends ApiErrorMapper {
|
|
80
|
+
map(error: unknown): unknown;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare abstract class ApiRequestTransformer {
|
|
84
|
+
abstract transform<TBody>(request: ApiRequest<TBody>): ApiRequest<TBody>;
|
|
85
|
+
}
|
|
86
|
+
declare class IdentityApiRequestTransformer extends ApiRequestTransformer {
|
|
87
|
+
transform<TBody>(request: ApiRequest<TBody>): ApiRequest<TBody>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
declare class AuthenticationRequiredError extends Error {
|
|
91
|
+
constructor();
|
|
92
|
+
}
|
|
93
|
+
declare class DisallowedAuthOriginError extends Error {
|
|
94
|
+
readonly origin: string;
|
|
95
|
+
constructor(origin: string);
|
|
96
|
+
}
|
|
97
|
+
declare class InvalidApiRequestError extends Error {
|
|
98
|
+
constructor(message: string);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type RequestState<T, E = unknown> = {
|
|
102
|
+
readonly status: 'loading';
|
|
103
|
+
} | {
|
|
104
|
+
readonly status: 'success';
|
|
105
|
+
readonly data: T;
|
|
106
|
+
} | {
|
|
107
|
+
readonly status: 'error';
|
|
108
|
+
readonly error: E;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
interface RequestStateOptions<E> {
|
|
112
|
+
readonly mapError?: (error: unknown) => E;
|
|
113
|
+
}
|
|
114
|
+
declare const toRequestState: <T, E = unknown>(options?: RequestStateOptions<E>) => OperatorFunction<T, RequestState<T, E>>;
|
|
115
|
+
|
|
116
|
+
type QueryKeyPart = string | number | boolean | null;
|
|
117
|
+
type QueryKey = readonly QueryKeyPart[];
|
|
118
|
+
|
|
119
|
+
type QueryState<T, E = unknown> = {
|
|
120
|
+
readonly status: 'idle';
|
|
121
|
+
} | {
|
|
122
|
+
readonly status: 'loading';
|
|
123
|
+
} | {
|
|
124
|
+
readonly status: 'success';
|
|
125
|
+
readonly data: T;
|
|
126
|
+
readonly refreshing: boolean;
|
|
127
|
+
readonly updatedAt: number;
|
|
128
|
+
} | {
|
|
129
|
+
readonly status: 'error';
|
|
130
|
+
readonly error: E;
|
|
131
|
+
readonly previousData?: T;
|
|
132
|
+
readonly updatedAt?: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
interface ApiQueryOptions<T> {
|
|
136
|
+
readonly key: QueryKey;
|
|
137
|
+
readonly request: ApiCall<T>;
|
|
138
|
+
readonly staleTime?: number;
|
|
139
|
+
readonly gcTime?: number;
|
|
140
|
+
}
|
|
141
|
+
interface ApiQuery<T, E = unknown> {
|
|
142
|
+
readonly state$: Observable<QueryState<T, E>>;
|
|
143
|
+
readonly data$: Observable<T | undefined>;
|
|
144
|
+
refresh(): Observable<T>;
|
|
145
|
+
ensureFresh(): Observable<T>;
|
|
146
|
+
invalidate(): void;
|
|
147
|
+
cancel(): void;
|
|
148
|
+
}
|
|
149
|
+
declare class QueryDefinitionConflictError extends Error {
|
|
150
|
+
readonly key: QueryKey;
|
|
151
|
+
constructor(key: QueryKey);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
declare class ApiQueryClient {
|
|
155
|
+
private readonly api;
|
|
156
|
+
private readonly credentials;
|
|
157
|
+
private readonly entries;
|
|
158
|
+
private readonly handles;
|
|
159
|
+
private lastIdentity;
|
|
160
|
+
private readonly host;
|
|
161
|
+
constructor();
|
|
162
|
+
query<T>(options: ApiQueryOptions<T>): ApiQuery<T>;
|
|
163
|
+
invalidate(key: QueryKey): void;
|
|
164
|
+
invalidateWhere(predicate: (key: QueryKey) => boolean): void;
|
|
165
|
+
clear(): void;
|
|
166
|
+
clearAuthenticated(): void;
|
|
167
|
+
private getEntry;
|
|
168
|
+
private createDefinition;
|
|
169
|
+
private assertCompatibleIfPresent;
|
|
170
|
+
private assertTiming;
|
|
171
|
+
private partitionFor;
|
|
172
|
+
private forKey;
|
|
173
|
+
private clearEntries;
|
|
174
|
+
private evict;
|
|
175
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ApiQueryClient, never>;
|
|
176
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ApiQueryClient>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { ApiCallerModule, ApiClient, ApiCredentialProvider, ApiErrorMapper, ApiErrorReporter, ApiQueryClient, ApiRequestTransformer, AuthenticationRequiredError, DisallowedAuthOriginError, IdentityApiErrorMapper, IdentityApiRequestTransformer, InvalidApiRequestError, QueryDefinitionConflictError, defineApiCall, provideApiClient, toRequestState };
|
|
180
|
+
export type { ApiAuthMode, ApiCall, ApiClientConfig, ApiCredentialState, ApiErrorPolicy, ApiQuery, ApiQueryOptions, ApiRequest, HttpMethod, QueryKey, QueryKeyPart, QueryState, RequestState, RequestStateOptions };
|