From 9f2fe47e36cb14ad2c4f7c3efec0060639505453 Mon Sep 17 00:00:00 2001 From: Kalagin Ivan Date: Fri, 30 May 2025 22:34:48 +0500 Subject: [PATCH 1/2] feat: branded types for context, subcontext and features --- index.test.ts | 2 +- index.ts | 52 ++++++++++++++++++++++++++++++----------------- package-lock.json | 4 ++-- package.json | 19 +++++++++++++---- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/index.test.ts b/index.test.ts index bd6a075..676b6ba 100644 --- a/index.test.ts +++ b/index.test.ts @@ -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(); diff --git a/index.ts b/index.ts index 7d53668..b30a1d9 100644 --- a/index.ts +++ b/index.ts @@ -100,11 +100,6 @@ type ErrorMap = Record< } >; -type FeatureFn = ( - featureName: string, - featureContextExtendedParams?: ExtendedParams -) => CreateErrorFn; - type ErrorFnOptions = { originalError?: OriginalError; extendedParams?: ExtendedParams; @@ -116,7 +111,12 @@ type CreateErrorFn = ( options?: ErrorFnOptions ) => IConwayError; -type ErrorSubcontext = { +type Brand = T & { __brand: B }; + +type ErrorSubcontext = Brand, Name>; +type ErrorFeature = Brand, Name>; + +type Subcontext = { /** * Create a child context within the current context. * @@ -124,7 +124,10 @@ type ErrorSubcontext = { * @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; + subcontext: ( + subcontextName: ChildContextName, + extendedParams?: ExtendedParams + ) => ErrorSubcontext<`${Name}/${ChildContextName}`, ErrorType>; /** * Creates a child feature within the current context. * @@ -132,7 +135,10 @@ type ErrorSubcontext = { * @param {ExtendedParams} [extendedParams={}] - Additional extended parameters for the child feature. * @return {Function} The created error feature. */ - feature: FeatureFn; + feature: ( + featureName: FeatureName, + featureContextExtendedParams?: ExtendedParams + ) => ErrorFeature; }; /** @@ -146,7 +152,7 @@ export function createError(errorTypes?: Err const _options = { ...defaultErrorOptions, ...options }; const initialExtendedParams = options?.extendedParams ?? {}; - return (contextName: string, extendedParams: ExtendedParams = {}) => { + return (contextName: ContextName, extendedParams: ExtendedParams = {}) => { const outerExtendedParams = { ...initialExtendedParams, ...extendedParams }; const errorsMap: ErrorMap = Array.isArray(errorTypes) @@ -162,30 +168,37 @@ export function createError(errorTypes?: Err const UnknownError = createErrorClass("UnknownError", contextName); const _createSubcontext = - (contextName: string, subContextExtendedParams: ExtendedParams) => - (childContextName: string, extendedParams: ExtendedParams = {}) => { + (contextName: ContextName, subContextExtendedParams: ExtendedParams) => + ( + childContextName: ChildContextName, + extendedParams: ExtendedParams = {} + ) => { const subErrorContext = { ...subContextExtendedParams, ...extendedParams }; return _createErrorContext(`${contextName}/${childContextName}`, subErrorContext); }; - function _createErrorContext( - _contextName: string, + function _createErrorContext( + _contextName: ContextName, contextExtendedParams: ExtendedParams = outerExtendedParams - ): ErrorSubcontext { + ): ErrorSubcontext { return { + __brand: _contextName, subcontext: _createSubcontext(_contextName, contextExtendedParams), - feature: (childFeatureName: string, extendedParams: ExtendedParams = {}) => { + feature: ( + childFeatureName: FeatureName, + extendedParams: ExtendedParams = {} + ) => { const featureErrorContext = { ...contextExtendedParams, ...extendedParams }; return _createErrorFeature(childFeatureName, _contextName, featureErrorContext); }, }; } - function _createErrorFeature( - featureName: string, + function _createErrorFeature( + featureName: FeatureName, contextName: string, featureContextExtendedParams: ExtendedParams = {} - ) { + ): ErrorFeature { const createNewErrorObject: CreateErrorFn = ( errorType, message: string, @@ -215,7 +228,8 @@ export function createError(errorTypes?: Err return error; }; - return createNewErrorObject; + Object.assign(createNewErrorObject, { __brand: featureName }); + return createNewErrorObject as ErrorFeature; } return _createErrorContext(contextName); diff --git a/package-lock.json b/package-lock.json index 777300d..73a3ce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "conway-errors", - "version": "3.1.2", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "conway-errors", - "version": "3.1.2", + "version": "3.2.0", "license": "MIT", "devDependencies": { "@biomejs/biome": "1.8.3", diff --git a/package.json b/package.json index 4b291eb..cc24b5b 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", From 94f32cb3ed5832c746c65a7776b5637ee447b5d7 Mon Sep 17 00:00:00 2001 From: Kalagin Ivan Date: Mon, 2 Jun 2025 17:09:38 +0500 Subject: [PATCH 2/2] feat: type util for feature --- README.md | 28 ++++++++++++++++++++++++++++ README_RU.md | 28 ++++++++++++++++++++++++++++ index.ts | 15 +++++++++------ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index edd8447..7218f13 100644 --- a/README.md +++ b/README.md @@ -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) { + // ... +} + +customErrorThrower(featureError1); // error +customErrorThrower(featureError2); // ok +``` + ## Acknowledgment for Contributions diff --git a/README_RU.md b/README_RU.md index 510e498..8bdf82d 100644 --- a/README_RU.md +++ b/README_RU.md @@ -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) { + // ... +} + +customErrorThrower(featureError1); // error +customErrorThrower(featureError2); // ok +``` + ## Благодарность за вклад
diff --git a/index.ts b/index.ts index b30a1d9..19f66ab 100644 --- a/index.ts +++ b/index.ts @@ -115,6 +115,9 @@ type Brand = T & { __brand: B }; type ErrorSubcontext = Brand, Name>; type ErrorFeature = Brand, Name>; +export type AnyFeatureOfSubcontext = S extends ErrorSubcontext + ? ErrorFeature<`${Name}/${string}`, ErrorType> + : never; type Subcontext = { /** @@ -138,7 +141,7 @@ type Subcontext = { feature: ( featureName: FeatureName, featureContextExtendedParams?: ExtendedParams - ) => ErrorFeature; + ) => ErrorFeature<`${Name}/${FeatureName}`, ErrorType>; }; /** @@ -194,11 +197,11 @@ export function createError(errorTypes?: Err }; } - function _createErrorFeature( + function _createErrorFeature( featureName: FeatureName, - contextName: string, + contextName: ContextName, featureContextExtendedParams: ExtendedParams = {} - ): ErrorFeature { + ): ErrorFeature<`${ContextName}/${FeatureName}`, ErrorTypes[number]["errorType"]> { const createNewErrorObject: CreateErrorFn = ( errorType, message: string, @@ -228,8 +231,8 @@ export function createError(errorTypes?: Err return error; }; - Object.assign(createNewErrorObject, { __brand: featureName }); - return createNewErrorObject as ErrorFeature; + Object.assign(createNewErrorObject, { __brand: `${contextName}/${featureName}` as const }); + return createNewErrorObject as ErrorFeature<`${ContextName}/${FeatureName}`, ErrorTypes[number]["errorType"]>; } return _createErrorContext(contextName);