Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,34 @@ const error = cardPaymentError("BackendLogicError", "Payment failed", { extended
error.emit({ extendedParams: { logLevel: "fatal" } });
```

### Helper Functions and Types

#### AnyFeatureOfSubcontext

Allows you to explicitly specify the type for any feature of the given subcontext.

```ts
import { createError } from "conway-errors";

const createErrorContext = createError([
{ errorType: "FrontendLogicError" },
{ errorType: "BackendLogicError" },
] as const);

const context = createErrorContext("Context");
const subcontext = context.subcontext("Subcontext");

const featureError1 = context.feature("Feature");
const featureError2 = subcontext.feature("Feature");

function customErrorThrower(featureError: AnyFeatureOfSubcontext<typeof subcontext>) {
// ...
}

customErrorThrower(featureError1); // error
customErrorThrower(featureError2); // ok
```

## Acknowledgment for Contributions

<table>
Expand Down
28 changes: 28 additions & 0 deletions README_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,34 @@ const error = cardPaymentError("BackendLogicError", "Payment failed", { extended
error.emit({ extendedParams: { logLevel: "fatal" } })
```

### Вспомогательные функции и типы

#### AnyFeatureOfSubcontext

Позволяет явно указать тип для любой feature указанного подконтекста.

```ts
import { createError } from "conway-errors";

const createErrorContext = createError([
{ errorType: "FrontendLogicError" },
{ errorType: "BackendLogicError" },
] as const);

const context = createErrorContext("Context");
const subcontext = context.subcontext("Subcontext");

const featureError1 = context.feature("Feature");
const featureError2 = subcontext.feature("Feature");

function customErrorThrower(featureError: AnyFeatureOfSubcontext<typeof subcontext>) {
// ...
}

customErrorThrower(featureError1); // error
customErrorThrower(featureError2); // ok
```

## Благодарность за вклад

<table>
Expand Down
2 changes: 1 addition & 1 deletion index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test } from "uvu";
import { snoop } from "snoop";
import * as assert from "uvu/assert";

import { createError, isConwayError, type IConwayError } from "./index";
import { createError, isConwayError } from "./index";

test("without error types will throw always UnknownError", () => {
const createErrorContext = createError();
Expand Down
57 changes: 37 additions & 20 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ type ErrorMap = Record<
}
>;

type FeatureFn<ErrorType extends string> = (
featureName: string,
featureContextExtendedParams?: ExtendedParams
) => CreateErrorFn<ErrorType>;

type ErrorFnOptions = {
originalError?: OriginalError;
extendedParams?: ExtendedParams;
Expand All @@ -116,23 +111,37 @@ type CreateErrorFn<ErrorType extends string> = (
options?: ErrorFnOptions
) => IConwayError;

type ErrorSubcontext<ErrorType extends string> = {
type Brand<T, B> = T & { __brand: B };

type ErrorSubcontext<Name extends string, ErrorType extends string> = Brand<Subcontext<Name, ErrorType>, Name>;
type ErrorFeature<Name extends string, ErrorType extends string> = Brand<CreateErrorFn<ErrorType>, Name>;
export type AnyFeatureOfSubcontext<S> = S extends ErrorSubcontext<infer Name, infer ErrorType>
? ErrorFeature<`${Name}/${string}`, ErrorType>
: never;

type Subcontext<Name extends string, ErrorType extends string> = {
/**
* Create a child context within the current context.
*
* @param {string} childContextName - The name of the child context.
* @param {ExtendedParams} extendedParams - Additional extended parameters for the child context.
* @return {Function} Function to create an error context with the specified child context name and extended params.
*/
subcontext: (subcontextName: string, extendedParams?: ExtendedParams) => ErrorSubcontext<ErrorType>;
subcontext: <const ChildContextName extends string>(
subcontextName: ChildContextName,
extendedParams?: ExtendedParams
) => ErrorSubcontext<`${Name}/${ChildContextName}`, ErrorType>;
/**
* Creates a child feature within the current context.
*
* @param {string} childFeatureName - The name of the child feature.
* @param {ExtendedParams} [extendedParams={}] - Additional extended parameters for the child feature.
* @return {Function} The created error feature.
*/
feature: FeatureFn<ErrorType>;
feature: <const FeatureName extends string>(
featureName: FeatureName,
featureContextExtendedParams?: ExtendedParams
) => ErrorFeature<`${Name}/${FeatureName}`, ErrorType>;
};

/**
Expand All @@ -146,7 +155,7 @@ export function createError<ErrorTypes extends ErrorTypeConfig>(errorTypes?: Err
const _options = { ...defaultErrorOptions, ...options };
const initialExtendedParams = options?.extendedParams ?? {};

return (contextName: string, extendedParams: ExtendedParams = {}) => {
return <const ContextName extends string>(contextName: ContextName, extendedParams: ExtendedParams = {}) => {
const outerExtendedParams = { ...initialExtendedParams, ...extendedParams };

const errorsMap: ErrorMap = Array.isArray(errorTypes)
Expand All @@ -162,30 +171,37 @@ export function createError<ErrorTypes extends ErrorTypeConfig>(errorTypes?: Err
const UnknownError = createErrorClass("UnknownError", contextName);

const _createSubcontext =
(contextName: string, subContextExtendedParams: ExtendedParams) =>
(childContextName: string, extendedParams: ExtendedParams = {}) => {
<const ContextName extends string>(contextName: ContextName, subContextExtendedParams: ExtendedParams) =>
<const ChildContextName extends string>(
childContextName: ChildContextName,
extendedParams: ExtendedParams = {}
) => {
const subErrorContext = { ...subContextExtendedParams, ...extendedParams };
return _createErrorContext(`${contextName}/${childContextName}`, subErrorContext);
};

function _createErrorContext(
_contextName: string,
function _createErrorContext<const ContextName extends string>(
_contextName: ContextName,
contextExtendedParams: ExtendedParams = outerExtendedParams
): ErrorSubcontext<ErrorTypes[number]["errorType"]> {
): ErrorSubcontext<ContextName, ErrorTypes[number]["errorType"]> {
return {
__brand: _contextName,
subcontext: _createSubcontext(_contextName, contextExtendedParams),
feature: (childFeatureName: string, extendedParams: ExtendedParams = {}) => {
feature: <const FeatureName extends string>(
childFeatureName: FeatureName,
extendedParams: ExtendedParams = {}
) => {
const featureErrorContext = { ...contextExtendedParams, ...extendedParams };
return _createErrorFeature(childFeatureName, _contextName, featureErrorContext);
},
};
}

function _createErrorFeature(
featureName: string,
contextName: string,
function _createErrorFeature<const ContextName extends string, const FeatureName extends string>(
featureName: FeatureName,
contextName: ContextName,
featureContextExtendedParams: ExtendedParams = {}
) {
): ErrorFeature<`${ContextName}/${FeatureName}`, ErrorTypes[number]["errorType"]> {
const createNewErrorObject: CreateErrorFn<ErrorTypes[number]["errorType"]> = (
errorType,
message: string,
Expand Down Expand Up @@ -215,7 +231,8 @@ export function createError<ErrorTypes extends ErrorTypeConfig>(errorTypes?: Err
return error;
};

return createNewErrorObject;
Object.assign(createNewErrorObject, { __brand: `${contextName}/${featureName}` as const });
return createNewErrorObject as ErrorFeature<`${ContextName}/${FeatureName}`, ErrorTypes[number]["errorType"]>;
}

return _createErrorContext(contextName);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "conway-errors",
"source": "index.ts",
"version": "3.1.2",
"version": "3.2.0",
"private": false,
"description": "Create errors with Conway's law",
"description": "Simplify the creation, structuring and throwing of errors",
"exports": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
Expand Down Expand Up @@ -31,8 +31,19 @@
"/dist",
"/package.json"
],
"keywords": [],
"author": "Kalagin Ivan",
"keywords": [
"errors",
"typescript",
"error-handling",
"custom-errors",
"structured-errors",
"error-utils",
"backend",
"exceptions",
"js-errors",
"application-error"
],
"author": "ivklgn",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.8.3",
Expand Down