Custom classes for Type-safe handling of exceptions in your application.
npm install @fastkit/catcherimport { build, axiosErrorResolver } from '@fastkit/catcher';
type YourAppErrorInput = string | number | { [key: string]: any };
interface YourAppErrorNormalized {
name: string;
message: string;
stack?: string;
status: number;
}
export class YourAppError extends build({
resolvers: [axiosErrorResolver],
normalizer: (resolvedData) => {
return (input: YourAppErrorInput): YourAppErrorNormalized => {
let { name, message, status } = resolveYourAppErrorInput(input);
let stack: string | undefined;
const { axiosError, nativeError } = resolvedData;
if (axiosError) {
name = axiosError.name;
message = axiosError.message;
stack = axiosError.stack;
const { response } = axiosError;
if (response) {
status = response.status;
}
} else if (nativeError) {
name = nativeError.name;
message = nativeError.message;
stack = nativeError.stack;
if (nativeError.name === 'PayloadTooLargeError') {
status = 413;
}
}
const statusResolved = resolveStatusMessage(status);
status = statusResolved.status;
if (!message) {
message = statusResolved.message;
}
return {
name,
message,
stack,
status,
};
},
},
});tsimport { YourAppError } from './error';
export async function someFn() {
try {
const result = await axios.get('/some/path/');
return result.data;
} catch (_err) {
const err = YourAppError.from(_err);
if (err.status === 404) {
alert('Not found...!!!');
return;
}
throw err;
}
}ts<Resolvers extends AnyResolvers, Normalizer extends AnyNormalizer<Resolvers>>(opts: CatcherBuilderOptions<Resolvers, Normalizer>): CatcherConstructor<Resolvers, Normalizer>Generate exception catcher constructor
| Name | Type | Default |
|---|---|---|
| opts* | CatcherBuilderOptions<Resolvers, Normalizer> | |
CatcherConstructor<Resolvers, Normalizer>Exception catcher constructor
Catcher constructor generation options
undefined | stringDefault name if the name cannot be resolved from the supplemented exception
undefined | stringMessage to use when nothing else produced one
Applied last, after the normalizer and after the native error's own
message, and only when what is left is empty.
Set it. Without it there is no guarantee that a catcher has a message,
and the failure is quiet rather than loud: a normalizer that returns no
message for an exception it did not recognise leaves the instance with
the empty string an Error is born with, so toJSON() reports
"message": "" -- not an absent field a log pipeline can spot, but a
present one that reads like a real, empty message.
build({
defaultName: 'AppError',
defaultMessage: 'Something went wrong',
resolvers,
normalizer,
});The string belongs to the application, not to this package, so there is no default -- it is what a user may end up reading. Development warns once per catcher when an instance is built without a message and this is unset.
undefined | ResolversList of Exception Resolver
NormalizerException Normalizer
A method to finally normalize the set of values resolved by the resolver
Exception catcher constructor
(errorInfo: Parameters<ReturnType<Normalizer>>[0]): Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on the error information.
| Name | Type | Default |
|---|---|---|
| errorInfo* | Parameters<ReturnType<Normalizer>>[0] |
Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on the error information.
(errorInfo: Parameters<ReturnType<Normalizer>>[0]): Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on the error information.
| Name | Type | Default |
|---|---|---|
| errorInfo* | Parameters<ReturnType<Normalizer>>[0] |
Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on the error information.
(unknownException: unknown, overrides?: Partial<ReturnType<ReturnType<Normalizer>> & ErrorImplements>): Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on an unknown exception. The fields of the error instance can be overwritten by specifying an object as the second argument.
| Name | Type | Default |
|---|---|---|
| unknownException* | unknown | |
| overrides | Partial<ReturnType<ReturnType<Normalizer>> & ErrorImplements> | undefined |
Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>Create an error instance based on an unknown exception. The fields of the error instance can be overwritten by specifying an object as the second argument.
(unknownException: unknown, overrides?: Partial<ReturnType<ReturnType<Normalizer>> & ErrorImplements>): Promise<Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>>from, awaiting every resolver
Some of what an exception carries can only be reached asynchronously -- a
Response hands out its body through a promise and no other way. A
resolver cannot deliver that to a synchronous constructor: the instance has
to exist by the time it is thrown, which is before the body could arrive.
This entry point awaits the resolvers first and normalizes afterwards, so
the normalizer sees everything. Prefer it wherever you can await -- a
catch in an async function, which is where fetch errors live:
try {
await api.getUser(id);
} catch (e) {
throw await AppError.fromAsync(e);
}Nothing else changes: you still hand it an unknown exception and let the resolvers work out what it is.
| Name | Type | Default |
|---|---|---|
| unknownException* | unknown | |
| overrides | Partial<ReturnType<ReturnType<Normalizer>> & ErrorImplements> | undefined |
Promise<Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>>from, awaiting every resolver
Some of what an exception carries can only be reached asynchronously -- a
Response hands out its body through a promise and no other way. A
resolver cannot deliver that to a synchronous constructor: the instance has
to exist by the time it is thrown, which is before the body could arrive.
This entry point awaits the resolvers first and normalizes afterwards, so
the normalizer sees everything. Prefer it wherever you can await -- a
catch in an async function, which is where fetch errors live:
try {
await api.getUser(id);
} catch (e) {
throw await AppError.fromAsync(e);
}Nothing else changes: you still hand it an unknown exception and let the resolvers work out what it is.
(errorInfo: Parameters<ReturnType<Normalizer>>[0]): Promise<Catcher<Resolvers, ReturnType<ReturnType<Normalizer>>> & ReturnType<ReturnType<Normalizer>>>| Name | Type | Default |
|---|---|---|
| errorInfo* | Parameters<ReturnType<Normalizer>>[0] |
Caught exception instances
trueIt is a catcher instance
CatcherData<T>What the normalizer returned -- and the only thing that is serialized
toJSON() emits this and nothing else, so this is what leaves the process:
what reaches a log, an error report, a response body. Its fields are also
defined onto the instance, which is where err.status comes from.
Not to be confused with resolvedData, which is one layer earlier and is not serialized. The two names are one word apart and the difference between them is what is safe to keep -- see resolvedData before copying anything across.
UnionToIntersection<ReturnType<MergeParametersAndReturnTypes<[(source: unknown) => NativeErrorOverrides | undefined, ...Resolvers]>>>What the resolvers extracted -- never serialized
Everything they found, in full, because none of it leaves on its own:
toJSON() emits data, which is what the normalizer
returned. That is the boundary, and it is deliberate -- a resolver may hold
the whole response precisely because holding it costs nothing.
So copy out of here deliberately, never wholesale. What a response carries is what the server sent:
set-cookie -- session and refresh tokens, and a 401 is exactly when
those get rotatedurl that may hold a signed-URL signature or a ?token=// Fine: a code and a message.
return { code: 'HTTP_ERROR', message: response.json?.message };
// Puts the session cookie, the signed URL and the whole body wherever
// this error is logged.
return { ...response };This package's own README made the second mistake, which is the reason this warning is here rather than in the documentation alone.
undefined | Catcher<Resolvers, T>Catcher instance generated from original exception source before override
Catcher<Resolvers, T>[]List of extension sources for own instance
string[]全ての歴史を辿ったメッセージのリスト
(): ErrorImplements & CatcherData<T> & { messages: string[]; }Obtain as a JSON object
| Name | Type | Default |
|---|
ErrorImplements & T & Partial<ErrorImplements> & { $__catcher: true; } & { messages: string[]; }Obtain as a JSON object
(indent?: number | boolean): stringObtain as JSON string
| Name | Type | Default |
|---|---|---|
| indent | number | boolean | undefined | |
number of indentations | ||
stringObtain as JSON string