@azure-net/kit 6.0.0-next.10 → 6.0.0-next.12
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 +61 -4
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/injectable-dependencies/AsyncHelpers.d.ts +2 -2
- package/dist/delivery/injectable-dependencies/AsyncHelpers.js +14 -49
- package/dist/infra/http-service/HttpServiceInstance.d.ts +10 -2
- package/dist/infra/http-service/HttpServiceInstance.js +52 -65
- package/dist/infra/index.d.ts +1 -1
- package/dist/shared/app/App.d.ts +1 -23
- package/dist/shared/app/App.js +17 -86
- package/dist/shared/app/index.d.ts +1 -1
- package/dist/shared/app-error/AppError.d.ts +2 -0
- package/dist/shared/app-error/AppError.js +4 -0
- package/dist/shared/app-error/index.d.ts +0 -1
- package/dist/shared/app-error/index.js +0 -1
- package/dist/shared/boundary-provider/CompositionRoot.js +1 -1
- package/package.json +1 -1
- package/dist/shared/app-error/ErrorRuntime.d.ts +0 -11
- package/dist/shared/app-error/ErrorRuntime.js +0 -58
package/README.md
CHANGED
|
@@ -55,10 +55,11 @@ skips checks. Review `.gitignore` before staging.
|
|
|
55
55
|
## Class Presenters And Async Helpers
|
|
56
56
|
|
|
57
57
|
Import `createAsyncResource` and `createAsyncAction` directly from
|
|
58
|
-
`@azure-net/kit/delivery`.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
`@azure-net/kit/delivery`. Helpers execute the action once and support `beforeSend`,
|
|
59
|
+
`onSuccess`, `onError` and `fallbackResponse`. Resources additionally support `reject`.
|
|
60
|
+
Schema failures are converted locally to `error.validation`. There is no global
|
|
61
|
+
error resolver, `useAzureNetKitError`, or helper-level retry. Exceptions thrown by
|
|
62
|
+
`onSuccess` or `onError` propagate unchanged, without replaying the action.
|
|
62
63
|
|
|
63
64
|
```ts
|
|
64
65
|
import { RegisterProvider, createAsyncResource } from '@azure-net/kit/delivery';
|
|
@@ -373,3 +374,59 @@ Declare roots once, rather than creating new roots per component render.
|
|
|
373
374
|
`name` is now a symbol, not a serializable string. Its description is for diagnostics
|
|
374
375
|
only; equal descriptions never share a cache. Existing string keys passed directly
|
|
375
376
|
to `CreateServiceContainer` and `cleanupProvider` remain supported.
|
|
377
|
+
|
|
378
|
+
## HTTP Retries
|
|
379
|
+
|
|
380
|
+
Retries belong to `HttpServiceInstance`, including calls made directly from use
|
|
381
|
+
cases. With no `retry` option, a failed request is not repeated. `limit` counts
|
|
382
|
+
additional attempts: `limit: 1` means at most two HTTP attempts.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const http = new HttpServiceInstance({
|
|
386
|
+
onRequest(request) {
|
|
387
|
+
const token = readAccessToken();
|
|
388
|
+
if (token) request.headers.set('Authorization', `Bearer ${token}`);
|
|
389
|
+
},
|
|
390
|
+
retry: {
|
|
391
|
+
limit: 1,
|
|
392
|
+
statuses: [401],
|
|
393
|
+
async shouldRetry({ error }) {
|
|
394
|
+
if (!isExpiredAccessToken(error.data)) return false;
|
|
395
|
+
const refreshed = await http.post('/api/auth/refresh', {
|
|
396
|
+
json: { refreshToken: readRefreshToken() },
|
|
397
|
+
retry: { limit: 0 }
|
|
398
|
+
});
|
|
399
|
+
saveTokens(refreshed.data);
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Token helpers above are application-defined. Read/write tokens within the current
|
|
407
|
+
request on SSR, never in shared module-level user state. Coordinate concurrent
|
|
408
|
+
refreshes per session/request as appropriate; the generic HTTP client does not
|
|
409
|
+
implement token rotation or cross-tab coordination.
|
|
410
|
+
|
|
411
|
+
- `{ limit: N }` retries any failed HTTP attempt, including network failures.
|
|
412
|
+
- `statuses` restricts retries to those error statuses; `[]` allows none.
|
|
413
|
+
- `shouldRetry` runs only after the status filter and only while budget remains.
|
|
414
|
+
Both synchronous and asynchronous booleans are accepted.
|
|
415
|
+
- Per-request `retry` replaces the entire default policy. Use `{ limit: 0 }` to
|
|
416
|
+
disable it, **especially for refresh requests made inside `shouldRetry`**.
|
|
417
|
+
A nested HTTP call has its own budget; forgetting this override can recurse.
|
|
418
|
+
- `onRequest` runs before every attempt so refreshed credentials are read again.
|
|
419
|
+
`onResponse` runs once on success; `onError` runs once on terminal HTTP failure,
|
|
420
|
+
not for intermediate failures that recover successfully.
|
|
421
|
+
- Exceptions from hooks (including `shouldRetry`) propagate without further
|
|
422
|
+
recovery. They are not treated as another failed transport attempt.
|
|
423
|
+
- Cancelled requests and streaming request bodies are never replayed. Cancellation
|
|
424
|
+
is checked again after awaiting recovery; the callback must manage its own async
|
|
425
|
+
work and cancellation if needed.
|
|
426
|
+
- `limit` must be a finite non-negative safe integer. There is no implicit retry
|
|
427
|
+
delay/backoff. Timeout is applied separately to each HTTP attempt.
|
|
428
|
+
|
|
429
|
+
Retries are opt-in for all HTTP methods. Do not blindly retry writes after network
|
|
430
|
+
errors: the server may already have applied them. Use a narrow status/code policy
|
|
431
|
+
or backend-supported idempotency keys. Numerical `retry: 3` is no longer supported;
|
|
432
|
+
write `retry: { limit: 3 }` instead.
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createAsyncAction, createAsyncResource, type AsyncActionSettings, type
|
|
1
|
+
export { createAsyncAction, createAsyncResource, type AsyncActionSettings, type AsyncResourceSettings, type AsyncActionResponse } from './injectable-dependencies/index.js';
|
|
2
2
|
export { RegisterProvider, type ProviderLifecycle, type RegisterProviderOptions, type ProviderMethodKey } from './presenter/RegisterProvider.js';
|
|
3
3
|
export { Bound } from './presenter/Bound.js';
|
|
4
4
|
export { createSchemaFactory, schema, type Schema, type RequestErrors, type ISchemaError, type ValidationResult, type ValidationRuleResult, type ValidationParams, type ValidationMessage, type ValidationErrorsMap } from './schema/index.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export type { AsyncHelperRetry } from '../../shared/app-error/ErrorRuntime.js';
|
|
1
|
+
import { AppError, type IAppError } from '../../shared/app-error/AppError.js';
|
|
3
2
|
export interface AsyncActionResponse<T, D = never, ErrorResult = Record<never, never>> {
|
|
4
3
|
success: boolean;
|
|
5
4
|
response: T;
|
|
@@ -20,3 +19,4 @@ export interface AsyncResourceSettings<Res = never, Req = never, ErrorResult = A
|
|
|
20
19
|
}
|
|
21
20
|
export declare const createAsyncAction: <Res = never, Req = never, ErrorResult extends object = Record<never, never>>(action: Action<Res>, args?: AsyncActionSettings<Res, Req, ErrorResult>) => Promise<AsyncActionResponse<Res, Req, ErrorResult>>;
|
|
22
21
|
export declare const createAsyncResource: <Res = never, Req = never, ErrorResult extends object = Record<never, never>>(action: Action<Res>, args?: AsyncResourceSettings<Res, Req, ErrorResult>) => Promise<Res>;
|
|
22
|
+
export {};
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
import { AsyncHelperError } from '../../shared/app-error/AppError.js';
|
|
2
|
-
import { resolveAzureNetKitError } from '../../shared/app-error/ErrorRuntime.js';
|
|
1
|
+
import { AsyncHelperError, AppError } from '../../shared/app-error/AppError.js';
|
|
3
2
|
const normalizeError = (err) => (err instanceof Error ? err : new Error(String(err)));
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return await retry()
|
|
9
|
-
.then((res) => {
|
|
10
|
-
retryResult = res;
|
|
11
|
-
return undefined;
|
|
12
|
-
})
|
|
13
|
-
.catch((err) => normalizeError(err));
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
const parsedError = await resolveAzureNetKitError(normalizeError(error), {
|
|
17
|
-
can: !!retry,
|
|
18
|
-
call: retry ? () => retryFunc() : undefined
|
|
19
|
-
});
|
|
20
|
-
return { error: parsedError, retryResult };
|
|
3
|
+
const prepareError = (error) => {
|
|
4
|
+
const normalized = normalizeError(error);
|
|
5
|
+
const appError = normalized instanceof AppError ? normalized : new AppError(normalized);
|
|
6
|
+
return appError.toPlainObject();
|
|
21
7
|
};
|
|
22
8
|
const beforeSendResolver = async (settings) => {
|
|
23
9
|
if (!settings?.beforeSend)
|
|
@@ -55,7 +41,7 @@ const beforeSendResolver = async (settings) => {
|
|
|
55
41
|
});
|
|
56
42
|
if (!aborted)
|
|
57
43
|
return undefined;
|
|
58
|
-
const
|
|
44
|
+
const error = prepareError(abortReason ?? new AsyncHelperError('Aborted in beforeSend'));
|
|
59
45
|
return {
|
|
60
46
|
success: false,
|
|
61
47
|
error,
|
|
@@ -66,39 +52,18 @@ export const createAsyncAction = async (action, args) => {
|
|
|
66
52
|
const beforeSendResult = await beforeSendResolver(args);
|
|
67
53
|
if (beforeSendResult)
|
|
68
54
|
return beforeSendResult;
|
|
69
|
-
|
|
70
|
-
const response = await Promise.resolve(action());
|
|
71
|
-
return { response, success: true };
|
|
72
|
-
};
|
|
73
|
-
const handleFailure = async (err, retry) => {
|
|
74
|
-
const { error, retryResult } = await prepareErrors(normalizeError(err), retry);
|
|
75
|
-
if (retryResult) {
|
|
76
|
-
return completeSuccess(retryResult);
|
|
77
|
-
}
|
|
78
|
-
const result = { error, response: args?.fallbackResponse, success: false };
|
|
79
|
-
try {
|
|
80
|
-
await args?.onError?.(result);
|
|
81
|
-
}
|
|
82
|
-
catch (onErrorException) {
|
|
83
|
-
throw await resolveAzureNetKitError(new AsyncHelperError('onError caught exception', { cause: normalizeError(onErrorException) }));
|
|
84
|
-
}
|
|
85
|
-
return result;
|
|
86
|
-
};
|
|
87
|
-
const completeSuccess = async (result) => {
|
|
88
|
-
try {
|
|
89
|
-
await args?.onSuccess?.(result);
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
return handleFailure(err);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
55
|
+
let response;
|
|
96
56
|
try {
|
|
97
|
-
|
|
57
|
+
response = await action();
|
|
98
58
|
}
|
|
99
59
|
catch (err) {
|
|
100
|
-
|
|
60
|
+
const result = { error: prepareError(err), response: args?.fallbackResponse, success: false };
|
|
61
|
+
await args?.onError?.(result);
|
|
62
|
+
return result;
|
|
101
63
|
}
|
|
64
|
+
const result = { response, success: true };
|
|
65
|
+
await args?.onSuccess?.(result);
|
|
66
|
+
return result;
|
|
102
67
|
};
|
|
103
68
|
export const createAsyncResource = async (action, args) => {
|
|
104
69
|
const result = await createAsyncAction(action, args);
|
|
@@ -55,7 +55,7 @@ export interface IHttpInstanceNormalizedRequest {
|
|
|
55
55
|
responseFormat: 'json' | 'blob' | 'text' | 'arrayBuffer' | 'body';
|
|
56
56
|
includeRaw: boolean;
|
|
57
57
|
timeout: number | false;
|
|
58
|
-
retry
|
|
58
|
+
retry?: IHttpInstanceRetryOptions;
|
|
59
59
|
stringifyJson: (data: unknown) => string | undefined;
|
|
60
60
|
fetch: typeof fetch;
|
|
61
61
|
requestInit: Omit<RequestInit, 'method' | 'headers' | 'body'>;
|
|
@@ -63,6 +63,14 @@ export interface IHttpInstanceNormalizedRequest {
|
|
|
63
63
|
export type IHttpInstanceOnRequest = (request: IHttpInstanceNormalizedRequest) => void | Promise<void>;
|
|
64
64
|
export type IHttpInstanceOnError = (error: HttpServiceError<unknown>, request: IHttpInstanceNormalizedRequest) => void | Promise<void>;
|
|
65
65
|
export type IHttpInstanceDoFetch = <T = unknown>(request: IHttpInstanceNormalizedRequest) => Promise<IHttpServiceResponse<T>>;
|
|
66
|
+
export interface IHttpInstanceRetryOptions {
|
|
67
|
+
/** Number of additional attempts. Zero disables retries, including inherited policies. */
|
|
68
|
+
limit: number;
|
|
69
|
+
statuses?: readonly number[];
|
|
70
|
+
shouldRetry?: (context: {
|
|
71
|
+
error: HttpServiceError<unknown>;
|
|
72
|
+
}) => boolean | Promise<boolean>;
|
|
73
|
+
}
|
|
66
74
|
export interface IHttpInstanceOptions<T = unknown> extends Omit<RequestInit, 'headers' | 'method'> {
|
|
67
75
|
headers?: NonNullable<RequestInit['headers']> | Record<string, string | undefined>;
|
|
68
76
|
json?: unknown;
|
|
@@ -70,7 +78,7 @@ export interface IHttpInstanceOptions<T = unknown> extends Omit<RequestInit, 'he
|
|
|
70
78
|
searchParams?: SearchParamsOption;
|
|
71
79
|
prefixUrl?: URL | string;
|
|
72
80
|
timeout?: number | false;
|
|
73
|
-
retry?:
|
|
81
|
+
retry?: IHttpInstanceRetryOptions;
|
|
74
82
|
includeRaw?: boolean;
|
|
75
83
|
responseFormat?: 'json' | 'blob' | 'text' | 'arrayBuffer' | 'body';
|
|
76
84
|
onRequest?: IHttpInstanceOnRequest;
|
|
@@ -196,15 +196,6 @@ const withTimeout = (signal, timeout) => {
|
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
198
|
};
|
|
199
|
-
const shouldRetryError = (error) => {
|
|
200
|
-
if (error instanceof Error && error.name === 'AbortError')
|
|
201
|
-
return false;
|
|
202
|
-
if (error instanceof TypeError)
|
|
203
|
-
return true;
|
|
204
|
-
if (!(error instanceof HttpServiceError) || error.type !== HttpErrorTypes.External)
|
|
205
|
-
return false;
|
|
206
|
-
return error.status === 408 || error.status === 425 || error.status === 429 || error.status >= 500;
|
|
207
|
-
};
|
|
208
199
|
const normalizeUnknownError = (error) => {
|
|
209
200
|
if (error instanceof Error)
|
|
210
201
|
return error;
|
|
@@ -239,39 +230,21 @@ const parseResponse = async (response, request) => {
|
|
|
239
230
|
return { data, headers: Object.fromEntries(response.headers.entries()), status: response.status, raw };
|
|
240
231
|
};
|
|
241
232
|
const defaultDoFetch = async (request) => {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
headers,
|
|
257
|
-
status,
|
|
258
|
-
success: true,
|
|
259
|
-
data,
|
|
260
|
-
message: 'Request completed',
|
|
261
|
-
raw
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
lastError = error;
|
|
266
|
-
if (attempt >= maxAttempts || !shouldRetryError(error)) {
|
|
267
|
-
throw error;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
finally {
|
|
271
|
-
timeoutState.clear();
|
|
272
|
-
}
|
|
233
|
+
const timeoutState = withTimeout(request.requestInit.signal, request.timeout);
|
|
234
|
+
try {
|
|
235
|
+
const response = await request.fetch(request.url, {
|
|
236
|
+
...request.requestInit,
|
|
237
|
+
method: request.method,
|
|
238
|
+
headers: request.headers,
|
|
239
|
+
body: request.body,
|
|
240
|
+
signal: timeoutState.signal ?? undefined
|
|
241
|
+
});
|
|
242
|
+
const { data, headers, status, raw } = await parseResponse(response, request);
|
|
243
|
+
return new HttpServiceResponse({ headers, status, success: true, data, message: 'Request completed', raw });
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
timeoutState.clear();
|
|
273
247
|
}
|
|
274
|
-
throw lastError;
|
|
275
248
|
};
|
|
276
249
|
const makeMethod = (method, defaults) => {
|
|
277
250
|
return async (url, options = {}) => {
|
|
@@ -298,7 +271,7 @@ const makeMethod = (method, defaults) => {
|
|
|
298
271
|
responseFormat,
|
|
299
272
|
includeRaw: merged.includeRaw ?? false,
|
|
300
273
|
timeout: merged.timeout ?? 20000,
|
|
301
|
-
retry: merged.retry
|
|
274
|
+
retry: merged.retry,
|
|
302
275
|
stringifyJson,
|
|
303
276
|
fetch: fetcher,
|
|
304
277
|
requestInit: {
|
|
@@ -315,33 +288,47 @@ const makeMethod = (method, defaults) => {
|
|
|
315
288
|
window: merged.window
|
|
316
289
|
}
|
|
317
290
|
};
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
291
|
+
// Snapshot the policy per call: hooks and concurrent requests cannot extend its budget.
|
|
292
|
+
const policy = merged.retry;
|
|
293
|
+
const limit = policy?.limit ?? 0;
|
|
294
|
+
if (!Number.isSafeInteger(limit) || limit < 0) {
|
|
295
|
+
throw new RangeError('retry.limit must be a non-negative safe integer');
|
|
321
296
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
297
|
+
const statuses = policy?.statuses ? [...policy.statuses] : undefined;
|
|
298
|
+
const shouldRetry = policy?.shouldRetry;
|
|
299
|
+
const doFetch = merged.doFetch ?? defaultDoFetch;
|
|
300
|
+
let fetched;
|
|
301
|
+
for (let attempt = 0;; attempt++) {
|
|
302
|
+
normalizedRequest.requestInit.signal?.throwIfAborted();
|
|
303
|
+
// Re-run authentication/header hooks after recovery, without accumulating headers.
|
|
304
|
+
normalizedRequest.headers = new Headers(headers);
|
|
305
|
+
await merged.onRequest?.(normalizedRequest);
|
|
306
|
+
normalizedRequest.requestInit.signal?.throwIfAborted();
|
|
307
|
+
try {
|
|
308
|
+
fetched = await doFetch(normalizedRequest);
|
|
309
|
+
break;
|
|
328
310
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
311
|
+
catch (error) {
|
|
312
|
+
const normalizedError = error instanceof HttpServiceError
|
|
313
|
+
? error
|
|
314
|
+
: new HttpServiceError({
|
|
315
|
+
message: normalizeUnknownError(error).message,
|
|
316
|
+
original: normalizeUnknownError(error)
|
|
317
|
+
});
|
|
318
|
+
const aborted = normalizedRequest.requestInit.signal?.aborted || normalizedError.original?.name === 'AbortError';
|
|
319
|
+
const streaming = typeof ReadableStream !== 'undefined' && normalizedRequest.body instanceof ReadableStream;
|
|
320
|
+
const eligible = !aborted && !streaming && attempt < limit && (!statuses || statuses.includes(normalizedError.status));
|
|
321
|
+
if (eligible && (!shouldRetry || (await shouldRetry({ error: normalizedError })))) {
|
|
322
|
+
normalizedRequest.requestInit.signal?.throwIfAborted();
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
await merged.onError?.(normalizedError, normalizedRequest);
|
|
341
326
|
throw normalizedError;
|
|
342
|
-
|
|
343
|
-
throw normalizedError;
|
|
327
|
+
}
|
|
344
328
|
}
|
|
329
|
+
// Consumer callback failures must never replay a successful HTTP operation.
|
|
330
|
+
await merged.onResponse?.(fetched);
|
|
331
|
+
return fetched;
|
|
345
332
|
};
|
|
346
333
|
};
|
|
347
334
|
export class HttpServiceInstance {
|
package/dist/infra/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { BaseHttpDatasource, type CreateRequestCallbackType } from './datasource/index.js';
|
|
2
2
|
export { DTOMapper, type IDTOMapper } from './resource/index.js';
|
|
3
|
-
export { HttpStatusCode, HttpServiceInstance, HttpInstanceFetchMethods, HttpErrorTypes, HttpServiceResponse, HttpServiceError, type IHttpServiceError, type IHttpServiceResponse, type IHttpServiceInstance, type IHttpInstanceOptions, type IHttpInstanceNormalizedRequest, type IHttpInstanceOnRequest, type IHttpInstanceOnError, type IHttpInstanceDoFetch, type IHttpInstanceRequestMethod } from './http-service/index.js';
|
|
3
|
+
export { HttpStatusCode, HttpServiceInstance, HttpInstanceFetchMethods, HttpErrorTypes, HttpServiceResponse, HttpServiceError, type IHttpServiceError, type IHttpServiceResponse, type IHttpServiceInstance, type IHttpInstanceOptions, type IHttpInstanceRetryOptions, type IHttpInstanceNormalizedRequest, type IHttpInstanceOnRequest, type IHttpInstanceOnError, type IHttpInstanceDoFetch, type IHttpInstanceRequestMethod } from './http-service/index.js';
|
|
4
4
|
export { ResponseBuilder } from './response/index.js';
|
|
5
5
|
export { QueryBuilder, type IQueryBuilderInstance, type ArrayFormat, type ObjectFormat, type IQueryBuilderSettings } from './query/QueryBuilder.js';
|
package/dist/shared/app/App.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type TranslationManagerSource } from '../../i18n/Internal.js';
|
|
2
2
|
import { type ContextData } from '../../shared/request-context/Internal.js';
|
|
3
|
-
import { AppError, type IAppError } from '../app-error/AppError.js';
|
|
4
|
-
import { type AsyncHelperRetry } from '../app-error/ErrorRuntime.js';
|
|
5
3
|
import { type ServerMiddlewareClass } from './middleware/ServerMiddleware.js';
|
|
6
4
|
import type { Handle, HandleFetch, HandleServerError, HandleClientError, Reroute, ServerInit, Transport } from '@sveltejs/kit/hooks';
|
|
7
5
|
import type { RequestEvent } from '@sveltejs/kit';
|
|
@@ -10,15 +8,9 @@ import { useLogger } from '../logger/index.js';
|
|
|
10
8
|
type MaybePromise<T> = T | Promise<T>;
|
|
11
9
|
type HandleInput = Parameters<Handle>[0];
|
|
12
10
|
type Resolve = HandleInput['resolve'];
|
|
13
|
-
type BuilderFlags = Partial<Record<'useTranslations' | 'useBoot' | '
|
|
11
|
+
type BuilderFlags = Partial<Record<'useTranslations' | 'useBoot' | 'useClient' | 'useClientInit' | 'useClientError' | 'useServerInit' | 'useServer' | 'useServerError' | 'useServerFetch' | 'useReroute' | 'useTransport', true>>;
|
|
14
12
|
type WithFlag<TFlags extends BuilderFlags, TKey extends keyof BuilderFlags> = TFlags & Record<TKey, true>;
|
|
15
13
|
type OnceMethod<TFlags extends BuilderFlags, TKey extends keyof BuilderFlags, TMethod> = TFlags[TKey] extends true ? object : TMethod;
|
|
16
|
-
export interface AppUniversalLifecycleContext {
|
|
17
|
-
isClient: boolean;
|
|
18
|
-
isServer: boolean;
|
|
19
|
-
requestContext?: ContextData;
|
|
20
|
-
event?: RequestEvent;
|
|
21
|
-
}
|
|
22
14
|
export interface AppClientLifecycleContext {
|
|
23
15
|
useMiddlewares: (middlewares: readonly ClientMiddlewareClass[]) => void;
|
|
24
16
|
}
|
|
@@ -38,15 +30,6 @@ export type AppServerErrorContext = Parameters<HandleServerError>[0] & {
|
|
|
38
30
|
export type AppServerFetchContext = Parameters<HandleFetch>[0] & {
|
|
39
31
|
requestContext: ContextData;
|
|
40
32
|
};
|
|
41
|
-
export interface AppAzureNetKitErrorContext {
|
|
42
|
-
error: AppError;
|
|
43
|
-
retry: AsyncHelperRetry;
|
|
44
|
-
isClient: boolean;
|
|
45
|
-
isServer: boolean;
|
|
46
|
-
requestContext?: ContextData;
|
|
47
|
-
event?: RequestEvent;
|
|
48
|
-
}
|
|
49
|
-
export type AppUniversalLifecycleCallback = (context: AppUniversalLifecycleContext) => MaybePromise<void>;
|
|
50
33
|
export type AppClientLifecycleCallback = (context: AppClientLifecycleContext) => MaybePromise<void>;
|
|
51
34
|
export type AppClientInitLifecycleCallback = (context: AppClientInitLifecycleContext) => MaybePromise<void>;
|
|
52
35
|
export type AppClientErrorCallback = (context: AppClientErrorContext) => ReturnType<HandleClientError>;
|
|
@@ -54,7 +37,6 @@ export type AppServerInitLifecycleCallback = ServerInit;
|
|
|
54
37
|
export type AppServerLifecycleCallback = (context: AppServerLifecycleContext) => MaybePromise<Response | void>;
|
|
55
38
|
export type AppServerErrorCallback = (context: AppServerErrorContext) => ReturnType<HandleServerError>;
|
|
56
39
|
export type AppServerFetchCallback = (context: AppServerFetchContext) => ReturnType<HandleFetch>;
|
|
57
|
-
export type AppAzureNetKitErrorCallback = (context: AppAzureNetKitErrorContext) => MaybePromise<IAppError & object>;
|
|
58
40
|
export type AppRerouteCallback = Reroute;
|
|
59
41
|
export interface AppBootContext {
|
|
60
42
|
state: App.RequestContextState;
|
|
@@ -64,8 +46,6 @@ export type CreateAppBuilder<TFlags extends BuilderFlags = object> = OnceMethod<
|
|
|
64
46
|
useBoot(callback: AppBootCallback): CreateAppBuilder<WithFlag<TFlags, 'useBoot'>>;
|
|
65
47
|
}> & OnceMethod<TFlags, 'useTranslations', {
|
|
66
48
|
useTranslations(manager: TranslationManagerSource): CreateAppBuilder<WithFlag<TFlags, 'useTranslations'>>;
|
|
67
|
-
}> & OnceMethod<TFlags, 'use', {
|
|
68
|
-
use(callback: AppUniversalLifecycleCallback): CreateAppBuilder<WithFlag<TFlags, 'use'>>;
|
|
69
49
|
}> & OnceMethod<TFlags, 'useClient', {
|
|
70
50
|
useClient(callback: AppClientLifecycleCallback): CreateAppBuilder<WithFlag<TFlags, 'useClient'>>;
|
|
71
51
|
}> & OnceMethod<TFlags, 'useClientInit', {
|
|
@@ -80,8 +60,6 @@ export type CreateAppBuilder<TFlags extends BuilderFlags = object> = OnceMethod<
|
|
|
80
60
|
useServerError(callback: AppServerErrorCallback): CreateAppBuilder<WithFlag<TFlags, 'useServerError'>>;
|
|
81
61
|
}> & OnceMethod<TFlags, 'useServerFetch', {
|
|
82
62
|
useServerFetch(callback: AppServerFetchCallback): CreateAppBuilder<WithFlag<TFlags, 'useServerFetch'>>;
|
|
83
|
-
}> & OnceMethod<TFlags, 'useAzureNetKitError', {
|
|
84
|
-
useAzureNetKitError(callback: AppAzureNetKitErrorCallback): CreateAppBuilder<WithFlag<TFlags, 'useAzureNetKitError'>>;
|
|
85
63
|
}> & OnceMethod<TFlags, 'useReroute', {
|
|
86
64
|
useReroute(callback: AppRerouteCallback): CreateAppBuilder<WithFlag<TFlags, 'useReroute'>>;
|
|
87
65
|
}> & OnceMethod<TFlags, 'useTransport', {
|
package/dist/shared/app/App.js
CHANGED
|
@@ -3,8 +3,7 @@ import { hydratable } from 'svelte';
|
|
|
3
3
|
import { getContextInternals } from '../../shared/request-context/Internal.js';
|
|
4
4
|
import { BROWSER } from '../../external/tools/index.js';
|
|
5
5
|
import { RequestContext } from '../../shared/request-context/Internal.js';
|
|
6
|
-
import {
|
|
7
|
-
import { bindAzureNetKitErrorResolver } from '../app-error/ErrorRuntime.js';
|
|
6
|
+
import { AzureNetKitInternalError } from '../app-error/AppError.js';
|
|
8
7
|
import { executeServerMiddlewares } from './middleware/ServerMiddleware.js';
|
|
9
8
|
import { beforeNavigate } from '$app/navigation';
|
|
10
9
|
import { executeClientMiddlewares } from './middleware/ClientMiddleware.js';
|
|
@@ -61,10 +60,6 @@ const createBuilder = (callbacks) => {
|
|
|
61
60
|
setCallback(callbacks, 'useBoot', callback);
|
|
62
61
|
return builder;
|
|
63
62
|
},
|
|
64
|
-
use(callback) {
|
|
65
|
-
setCallback(callbacks, 'use', callback);
|
|
66
|
-
return builder;
|
|
67
|
-
},
|
|
68
63
|
useClient(callback) {
|
|
69
64
|
setCallback(callbacks, 'useClient', callback);
|
|
70
65
|
return builder;
|
|
@@ -93,10 +88,6 @@ const createBuilder = (callbacks) => {
|
|
|
93
88
|
setCallback(callbacks, 'useServerFetch', callback);
|
|
94
89
|
return builder;
|
|
95
90
|
},
|
|
96
|
-
useAzureNetKitError(callback) {
|
|
97
|
-
setCallback(callbacks, 'useAzureNetKitError', callback);
|
|
98
|
-
return builder;
|
|
99
|
-
},
|
|
100
91
|
useReroute(callback) {
|
|
101
92
|
setCallback(callbacks, 'useReroute', callback);
|
|
102
93
|
return builder;
|
|
@@ -115,42 +106,15 @@ export const createApp = (callback) => {
|
|
|
115
106
|
let translationInitPromise;
|
|
116
107
|
let serverInitPromise;
|
|
117
108
|
callback(createBuilder(callbacks));
|
|
118
|
-
const errorResolver = callbacks.useAzureNetKitError
|
|
119
|
-
? (error, retry) => {
|
|
120
|
-
const requestContext = BROWSER ? undefined : getRequestContext();
|
|
121
|
-
return callbacks.useAzureNetKitError({
|
|
122
|
-
error,
|
|
123
|
-
retry,
|
|
124
|
-
isClient: BROWSER,
|
|
125
|
-
isServer: !BROWSER,
|
|
126
|
-
requestContext,
|
|
127
|
-
event: requestContext?.event
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
: undefined;
|
|
131
|
-
const bindErrorResolver = (requestContext) => {
|
|
132
|
-
bindAzureNetKitErrorResolver(errorResolver, requestContext);
|
|
133
|
-
};
|
|
134
109
|
const runClient = (kind) => {
|
|
135
110
|
if (!BROWSER)
|
|
136
111
|
return undefined;
|
|
137
|
-
bindErrorResolver();
|
|
138
112
|
const flags = getLifecycleFlags(appKey);
|
|
139
113
|
if (flags[kind])
|
|
140
114
|
return kind === 'clientInit' ? translationInitPromise : undefined;
|
|
141
115
|
flags[kind] = true;
|
|
142
116
|
if (kind === 'clientInit') {
|
|
143
|
-
const initialize = () =>
|
|
144
|
-
const universalContext = {
|
|
145
|
-
isClient: true,
|
|
146
|
-
isServer: false
|
|
147
|
-
};
|
|
148
|
-
const universalResult = callbacks.use?.(universalContext);
|
|
149
|
-
if (universalResult && typeof universalResult.then === 'function') {
|
|
150
|
-
return universalResult.then(() => callbacks.useClientInit?.());
|
|
151
|
-
}
|
|
152
|
-
return callbacks.useClientInit?.();
|
|
153
|
-
};
|
|
117
|
+
const initialize = () => callbacks.useClientInit?.();
|
|
154
118
|
if (!callbacks.useTranslations)
|
|
155
119
|
return initialize();
|
|
156
120
|
translationInitPromise = getTranslationManagerLifecycle(callbacks.useTranslations)
|
|
@@ -185,29 +149,11 @@ export const createApp = (callback) => {
|
|
|
185
149
|
const runServerError = (input) => {
|
|
186
150
|
if (BROWSER)
|
|
187
151
|
return undefined;
|
|
188
|
-
|
|
189
|
-
bindErrorResolver(requestContext);
|
|
190
|
-
try {
|
|
191
|
-
const result = callbacks.useServerError?.({
|
|
192
|
-
requestContext,
|
|
193
|
-
...input,
|
|
194
|
-
useLogger
|
|
195
|
-
});
|
|
196
|
-
if (result && typeof result.then === 'function') {
|
|
197
|
-
return Promise.resolve(result).finally(() => bindAzureNetKitErrorResolver(undefined, requestContext));
|
|
198
|
-
}
|
|
199
|
-
bindAzureNetKitErrorResolver(undefined, requestContext);
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
bindAzureNetKitErrorResolver(undefined, requestContext);
|
|
204
|
-
throw error;
|
|
205
|
-
}
|
|
152
|
+
return callbacks.useServerError?.({ requestContext: getServerRequestContext(), ...input, useLogger });
|
|
206
153
|
};
|
|
207
154
|
const runClientError = (input) => {
|
|
208
155
|
if (!BROWSER)
|
|
209
156
|
return undefined;
|
|
210
|
-
bindErrorResolver();
|
|
211
157
|
return callbacks.useClientError?.({
|
|
212
158
|
...input
|
|
213
159
|
});
|
|
@@ -230,36 +176,21 @@ export const createApp = (callback) => {
|
|
|
230
176
|
})
|
|
231
177
|
: originalResolve;
|
|
232
178
|
const requestContext = getServerRequestContext();
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (universalResult && typeof universalResult.then === 'function') {
|
|
247
|
-
await universalResult;
|
|
248
|
-
}
|
|
249
|
-
const serverResult = await callbacks.useServer?.({
|
|
250
|
-
requestContext,
|
|
251
|
-
event,
|
|
252
|
-
resolve,
|
|
253
|
-
useMiddlewares: executeServerMiddlewares
|
|
254
|
-
});
|
|
255
|
-
if (serverResult)
|
|
256
|
-
return serverResult;
|
|
257
|
-
}
|
|
258
|
-
return await resolve(event);
|
|
259
|
-
}
|
|
260
|
-
finally {
|
|
261
|
-
bindAzureNetKitErrorResolver(undefined, requestContext);
|
|
179
|
+
const flags = getLifecycleFlags(appKey, requestContext);
|
|
180
|
+
if (!flags.server) {
|
|
181
|
+
flags.server = true;
|
|
182
|
+
if (translations)
|
|
183
|
+
await translations.preloadTranslation();
|
|
184
|
+
const serverResult = await callbacks.useServer?.({
|
|
185
|
+
requestContext,
|
|
186
|
+
event,
|
|
187
|
+
resolve,
|
|
188
|
+
useMiddlewares: executeServerMiddlewares
|
|
189
|
+
});
|
|
190
|
+
if (serverResult)
|
|
191
|
+
return serverResult;
|
|
262
192
|
}
|
|
193
|
+
return await resolve(event);
|
|
263
194
|
};
|
|
264
195
|
const runBoot = () => {
|
|
265
196
|
if (!callbacks.useBoot && !callbacks.useTranslations)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createApp, type
|
|
1
|
+
export { createApp, type AppClientErrorCallback, type AppClientErrorContext, type AppClientInitLifecycleCallback, type AppClientInitLifecycleContext, type AppClientLifecycleCallback, type AppClientLifecycleContext, type AppRegistrar, type AppServerErrorCallback, type AppServerErrorContext, type AppServerFetchCallback, type AppServerFetchContext, type AppServerInitLifecycleCallback, type AppServerInitLifecycleContext, type AppServerLifecycleCallback, type AppServerLifecycleContext, type AppRerouteCallback, type CreateAppInstance } from './App.js';
|
|
2
2
|
export type { IClientMiddleware, ClientMiddlewareContext, ClientMiddlewareClass } from './middleware/ClientMiddleware.js';
|
|
3
3
|
export type { IServerMiddleware, ServerMiddlewareContext, ServerMiddlewareClass } from './middleware/ServerMiddleware.js';
|
|
4
4
|
export type { IMiddleware, MiddlewareClass } from './middleware/Types.js';
|
|
@@ -17,6 +17,7 @@ export interface IAppError<T = unknown> {
|
|
|
17
17
|
validation?: RequestErrors<T>;
|
|
18
18
|
external: boolean;
|
|
19
19
|
appErrorConvert?: boolean;
|
|
20
|
+
statusCode: number;
|
|
20
21
|
}
|
|
21
22
|
export declare class AppError<T = unknown> extends Error {
|
|
22
23
|
error: Error;
|
|
@@ -24,6 +25,7 @@ export declare class AppError<T = unknown> extends Error {
|
|
|
24
25
|
message: string;
|
|
25
26
|
external: boolean;
|
|
26
27
|
validation?: RequestErrors<T>;
|
|
28
|
+
statusCode: number;
|
|
27
29
|
constructor(error: Error);
|
|
28
30
|
tryToGetExternalError<HttpServiceDataType = unknown>(): HttpServiceError<HttpServiceDataType> | undefined;
|
|
29
31
|
redeclareValidation<D = T>(validation: D): void;
|
|
@@ -18,6 +18,7 @@ export class AppError extends Error {
|
|
|
18
18
|
message;
|
|
19
19
|
external = false;
|
|
20
20
|
validation;
|
|
21
|
+
statusCode = 500;
|
|
21
22
|
constructor(error) {
|
|
22
23
|
super(error.message);
|
|
23
24
|
this.error = error;
|
|
@@ -41,6 +42,7 @@ export class AppError extends Error {
|
|
|
41
42
|
external: this.external,
|
|
42
43
|
validation: this.validation,
|
|
43
44
|
appErrorConvert: true,
|
|
45
|
+
statusCode: this.statusCode,
|
|
44
46
|
...customData
|
|
45
47
|
};
|
|
46
48
|
}
|
|
@@ -49,10 +51,12 @@ export class AppError extends Error {
|
|
|
49
51
|
case this.error instanceof HttpServiceError:
|
|
50
52
|
this.type = ErrorTypes.Http;
|
|
51
53
|
this.external = this.error.type === HttpErrorTypes.External;
|
|
54
|
+
this.statusCode = this.error.status;
|
|
52
55
|
break;
|
|
53
56
|
case this.error instanceof SchemaFail:
|
|
54
57
|
this.type = ErrorTypes.Schema;
|
|
55
58
|
this.validation = this.error.getSelfTypedData();
|
|
59
|
+
this.statusCode = 422;
|
|
56
60
|
break;
|
|
57
61
|
case this.error instanceof AzureNetKitInternalError:
|
|
58
62
|
this.type = ErrorTypes.KitInternalError;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CreateServiceContainer } from './Provider.js';
|
|
2
|
-
const toProvider = (dependency) => typeof dependency === 'function' ? dependency : dependency.resolve;
|
|
2
|
+
const toProvider = (dependency) => (typeof dependency === 'function' ? dependency : dependency.resolve);
|
|
3
3
|
/** Holds registration metadata only. Resolving always uses the current request's container. */
|
|
4
4
|
export class CompositionRoot {
|
|
5
5
|
name = Symbol('@azure-net/kit:CompositionRoot');
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type ContextData } from '../../shared/request-context/Internal.js';
|
|
2
|
-
import { AppError, type IAppError } from './AppError.js';
|
|
3
|
-
type MaybePromise<T> = T | Promise<T>;
|
|
4
|
-
export interface AsyncHelperRetry {
|
|
5
|
-
can: boolean;
|
|
6
|
-
call?: () => Promise<Error | void>;
|
|
7
|
-
}
|
|
8
|
-
export type AzureNetKitErrorResolver = (error: AppError, retry: AsyncHelperRetry) => MaybePromise<IAppError & object>;
|
|
9
|
-
export declare const bindAzureNetKitErrorResolver: (resolver?: AzureNetKitErrorResolver, requestContext?: ContextData) => void;
|
|
10
|
-
export declare const resolveAzureNetKitError: <Validation = unknown, CustomData extends object = Record<never, never>>(error: unknown, retry?: AsyncHelperRetry) => Promise<IAppError<Validation> & CustomData>;
|
|
11
|
-
export {};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { getContextInternals } from '../../shared/request-context/Internal.js';
|
|
2
|
-
import { BROWSER } from '../../external/tools/index.js';
|
|
3
|
-
import { RequestContext } from '../../shared/request-context/Internal.js';
|
|
4
|
-
import { AppError, AzureNetKitInternalError } from './AppError.js';
|
|
5
|
-
const SERVER_BINDING_KEY = Symbol('azureNetKitErrorResolver');
|
|
6
|
-
let clientBinding;
|
|
7
|
-
const createBinding = (resolver) => ({ resolver });
|
|
8
|
-
const getServerData = (requestContext) => {
|
|
9
|
-
if (requestContext)
|
|
10
|
-
return getContextInternals(requestContext);
|
|
11
|
-
try {
|
|
12
|
-
return getContextInternals(RequestContext.current());
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
export const bindAzureNetKitErrorResolver = (resolver, requestContext) => {
|
|
19
|
-
if (BROWSER) {
|
|
20
|
-
clientBinding = resolver ? createBinding(resolver) : undefined;
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const data = getServerData(requestContext);
|
|
24
|
-
if (!data)
|
|
25
|
-
return;
|
|
26
|
-
if (resolver) {
|
|
27
|
-
data[SERVER_BINDING_KEY] = createBinding(resolver);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
delete data[SERVER_BINDING_KEY];
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const getBinding = () => {
|
|
34
|
-
if (BROWSER)
|
|
35
|
-
return clientBinding;
|
|
36
|
-
return getServerData()?.[SERVER_BINDING_KEY];
|
|
37
|
-
};
|
|
38
|
-
const normalizeError = (error) => (error instanceof Error ? error : new Error(String(error)));
|
|
39
|
-
export const resolveAzureNetKitError = async (error, retry = { can: false }) => {
|
|
40
|
-
const normalizedError = normalizeError(error);
|
|
41
|
-
const appError = normalizedError instanceof AppError ? normalizedError : new AppError(normalizedError);
|
|
42
|
-
const binding = getBinding();
|
|
43
|
-
if (!binding || binding.activeErrors?.has(normalizedError)) {
|
|
44
|
-
return appError.toPlainObject();
|
|
45
|
-
}
|
|
46
|
-
const activeErrors = (binding.activeErrors ??= new WeakSet());
|
|
47
|
-
activeErrors.add(normalizedError);
|
|
48
|
-
try {
|
|
49
|
-
const result = await binding.resolver(appError, retry);
|
|
50
|
-
if (!result?.appErrorConvert) {
|
|
51
|
-
throw new AzureNetKitInternalError("[createApp] 'useAzureNetKitError' must return the result of AppError.toPlainObject().");
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
activeErrors.delete(normalizedError);
|
|
57
|
-
}
|
|
58
|
-
};
|