@yankeeinlondon/kind-error
v2.0.0-beta.1
Published
> A better error primitive for Javascript/Typescript.
Readme
Kind Error
A better error primitive for Javascript/Typescript.

Install
pnpm add @yankeeinlondon/kind-error| Manager| Shell Command | | --- | --- | | npm | npm install @yankeeinlondon/kind-error | | yarn | yarn add @yankeeinlondon/kind-error | | bun | bun add @yankeeinlondon/kind-error |
Basic Usage
Create an Error type with first call to
createKindError:const InvalidRequest = createKindError("invalid-request", { lib: "foobar", url: "string", method: "get|post|put|delete|undefined" });The error type
InvalidRequesthas now been created as aKindErrorType:- it acts as a function which will produce an error with the following properties:
name- the name is the PascalCase version of what you called it (e.g.,InvalidRequest)kind- the kind is the kebab-cased version of what you called it (e.g.,invalid-request)type- the type is the kind string up to but not including a/charactersubType- the subType is the kind string after the first/character:- if there is no
/character in thekindthen subType is undefined - you may provide a union type for the subType by using the
|character:invalid-request/get | post | put | delete- will make the subType
"get" | "post" | "put" | "delete" - when you express the subtype as a union type, this will change the signature of calling this type:
- The default calling signature is
ErrorType(message, props)(wherepropsis potentially optional if there are no required props in the )
- The default calling signature is
- if there is no
stackTrace- a structured stack trace for the error- Context
- any properties passed into the second parameter help define the shape of the error
- In our example:
- because
libis a non-union string literal it is a fixed property (aka, non-variant) and will always be made available to all errors generated of this time but then creating the error thelibproperty will not be something you will be allowed to override. - the
urlproperty is both required and a wide type that means that calling the function will require that theurlproperty be included! - the
methodproperty is a union and that union type includesundefinedas a member so it will be treated as optional parameter to the context.
- because
- it acts as a function which will produce an error with the following properties:
Creating an Error from an Error Type:
throw InvalidRequest("oh no!"); // invalidBecause we defined
urlas required but gave it a variant type, the syntax above will throw a typescript error and not compile. This mechanism is powerful as it enforces that required properties (which are variant in value) must be included when producing the error.throw InvalidRequest("oh no!", { url: "https://google.com" }); // validNow we've addressed the
urlconstraint we optionally can also specify a "method".throw InvalidRequest("oh no!", { url: "https://google.com", method: "put" }); // validThe type system will require that you use one of the valid values for the
methodproperty should you choose to set it.throw InvalidRequest("oh no!", { url: "https://google.com", color: "red" }); // invalidA
KindErrorTypeis typically strict about the properties it allows for. This means that if you want to add acolorproperty you'd have to have included it in the schema for the error type.
Partial Application
The ability to define an error type with a context schema of key/value pairs to be -- in part -- filled in later when the error is actually being raised is handy because often the details we need to complete an error are only available later. However, to increase flexibility and encourage consistency we must also recognize that sometimes the context we need to report fully on an error is built up in stages over time. This is where the power of partial application comes from.
Let's demonstrate this with an example:
Let's define an error type which immediately sets the
libproperty but then defines two additional required propertiessectionandurl:const InvalidRequest = createKindError("invalid-request", { lib: "kind-error", // static property section: "one|two|three", // an enumerated set of section choices url: "string" // any string value });In our code base we may move into a part of the code where we know the
sectionwe're in but theurlis still going to be determined later:const Err = InvalidRequest.partial({ section: "one" });Now when we actually want to raise an error we are only presented with the one remaining required property in the context schema
url:throw Err("This is the error message", { url });
In our example we chose to partially apply a required property of the schema but this will work equally as well with an optional property in the schema too.
Note: at any point when a partial application is done, the key/values applied become a static/readonly part of the context dictionary and future callers will only be required (and able) to modify the key/values which remain as undefined.
Error Proxy
Once you get used to having KindError's there's no going back ... says an entirely unbiased person. But in all seriousness, once you get get used to the strong typing, schema structuring and everything else you find yourself wishing all errors were KindErrors. Well they can be ... in part due to the built-in "proxy" functionality of the KindErrorType.
In the code below we show a standard try/catch block use-case where you might find using the proxy functionality useful:
const Unexpected = createKindError("unexpected");
try {
// do something dangerous (but exciting)
} catch(e) {
// Crime never pays, of course you got an error!
// Hey it's not typed! Should you assume it's supposed to be an `Error`?
// Who cares, just proxy it.
throw UnexpectedOutcome.proxy(e, "just in case");
}Regardless of the underlying shape of e, the proxy function will evaluate it and apply the following logic:
- if
eturns out to be aKindErrorthen the error will be proxied through "as is" (why mess with perfection) - if
eis just some plain janeErrortype then extract the "message" and use it in aUnexpectedkind error but add anunderlyingproperty which contains the base error. - if
eis a fetch network response then we'll add both theunderlyingproperty and acodeproperty which proxies through the HTTP code returned. - if
eis a POJO (plain old javascript object) then we'll look for a "message" property and use that for the wrapped kind error's message along with of course adding theunderlyingproperty. - if
eis a string then we'll use that as the kind error's message; nounderlyingproperty needed. - in all other cases we'll add in the kind error's name in prose format:
SomethingBadHappenedkind error will be have a message of "Something bad happened".- If the user provided a fallback message -- in our example we used
just in case-- then we'll use that instead
- If the user provided a fallback message -- in our example we used
Type Guards
Type guards are an important part of any good Typescript code. They help us narrow the type at runtime and align the runtime values with the type system so that you can have the strongest benefit of the type system at design time.
This library provides several type guards which you can use to help on your next project.
Detecting Errors
isOk(val)- tests whether value is any recognized form ofErrororRequestFailureand if it is not excludes these error types from val's type.isError(val)- tests whethervalis any recognized form of anErrororRequestFailureand isolates the type of val to only these types.isKindError(val, [kind])- this type guard will establish whether the variablevalis a validKindErroror not. You can also optionally specify a particular 'kind' you want to look for.KindErrorType.is(val)- everyKindErrorTypeerror type comes with ais(val)type guard built in.
Used in Error Proxy
We also expose some type guards which we internally use as part of the proxy functionality but you may find use for this too (and hey, sharing is caring):
isFetchResponse(val)isFetchError(val)isStringifyable(val)
Structured Callstack and Pretty Print
The two major entity types defined in this repo are KindErrorType (a error type) and KindError (an error) and both come with built-in toString() functions which produce nice looking console output when converted to a string. WIth the KindError this output is aided by a structured call stack which is parsed from Javascript's string stack property and provided (if you want direct access) to the stackTrace property on kind errors.
As an example, when you have something like the following code: console.log(InvalidType), you'll get something resembling this in your console:

