diff --git a/docs/en/v1/api/clean/chainedFunction.md b/docs/en/v1/api/clean/chainedFunction.md
new file mode 100644
index 00000000..43d64c53
--- /dev/null
+++ b/docs/en/v1/api/clean/chainedFunction.md
@@ -0,0 +1,99 @@
+---
+outline: [2, 3]
+description: "chainedFunction declares a typed aggregate of pure business actions that must run in order. The use case then orchestrates repositories around that aggregate."
+prev:
+ text: "UseCase"
+ link: "/en/v1/api/clean/useCase"
+next:
+ text: "Clean"
+ link: "/en/v1/api/clean/"
+---
+
+# chainedFunction
+
+`chainedFunction` solves a Clean Architecture coordination problem: inside a use case, you sometimes need to associate operations that update different entities. The chained function represents the aggregate that makes those operations part of the same business consistency boundary.
+
+It lets the domain explicitly declare that several pure business actions are linked. Functions passed to `chainedFunction` do not inject dependencies and do not call repositories: they only control entity lifecycle from the business point of view. The use case then orchestrates the aggregate, repositories, and technical effects.
+
+## Interactive example
+
+
+
+## Why use it?
+
+Use `chainedFunction` when a business aggregate is only consistent if several named operations happen together.
+
+For example, publishing a comment can require:
+
+- producing a valid comment entity;
+- producing an updated article entity.
+
+Persistence stays in the use case through the library repository system. The chained function only models the aggregate contract: "creating the comment" and "incrementing the article comment count" are linked business actions.
+
+## Guarantees
+
+`chainedFunction` provides these guarantees at the type level:
+
+- links are exposed one after another, in declaration order;
+- the implementation cannot access a later link before calling the current one;
+- the success path must end with `chainEnd(...)`;
+- a chained function can stop the flow by returning an `Either.Left`;
+- the implementation can also return an `Either.Left` directly.
+- chained functions remain pure domain functions.
+
+## Syntax
+
+### Signature
+
+```typescript
+function chainedFunction(
+ function1: [name: string, fn: Function],
+ function2: [name: string, fn: Function],
+ ...functions: [name: string, fn: Function][]
+): ChainedFunction
+```
+
+### Implementation
+
+```typescript
+const aggregate = chainedFunction(...functions);
+
+const result = aggregate(function *(firstLink, { breakIfLeft }) {
+ const [value, nextLink] = yield *firstLink(({ functionName }) => functionName(...args));
+
+ return chainEnd(value);
+});
+```
+
+## Parameters
+
+- `function1`: first pure business function in the chain.
+- `function2`: second pure business function in the chain.
+- `functions`: additional pure business functions, executed in declaration order.
+- `firstLink`: generated first link passed to the implementation callback.
+- `breakIfLeft`: synchronous helper injected into the callback. It accepts a value that may contain an `Either.Left`, stops the chain when it is a `Left`, otherwise returns the discriminated non-`Left` value.
+
+## Return value
+
+`chainedFunction(...)` returns a chained aggregate function. Calling it returns:
+
+- the raw value passed to `chainEnd(value)` on the success path;
+- the `Either.Left` returned by a chained function;
+- the `Either.Left` returned directly by the implementation.
+
+## Error flow
+
+When a chained function returns an `Either.Left`, the generator yields it and `chainedFunction` stops the implementation before the next links run. Business errors should be represented as `Either.Left`; thrown exceptions and rejected promises are not caught.
+
+`breakIfLeft` follows the same rule from inside the callback: use it to explicitly short-circuit from an intermediate synchronous value (`value | Left`) before calling the next link.
+
+## See also
+
+- [`useCase`](/en/v1/api/clean/useCase) - Calls application logic with dependencies.
+- [`repository`](/en/v1/api/clean/repository) - Declares a repository contract.
+- [`Either`](/en/v1/api/either/) - Represents explicit success and error values.
diff --git a/docs/en/v1/api/clean/index.md b/docs/en/v1/api/clean/index.md
index 527c4ba4..eef405d1 100644
--- a/docs/en/v1/api/clean/index.md
+++ b/docs/en/v1/api/clean/index.md
@@ -55,6 +55,9 @@ Declares a repository (contract) and type-checks the implementation.
## [UseCase](/en/v1/api/clean/useCase)
Declares a use case with dependencies (repositories or other use cases).
+## [chainedFunction](/en/v1/api/clean/chainedFunction)
+Declares an aggregate of linked business actions that must run in order.
+
## Operations on primitives
### [equal](/en/v1/api/clean/primitives/operators/equal)
diff --git a/docs/en/v1/api/clean/useCase.md b/docs/en/v1/api/clean/useCase.md
index 3f2e7015..46ae0cbe 100644
--- a/docs/en/v1/api/clean/useCase.md
+++ b/docs/en/v1/api/clean/useCase.md
@@ -5,8 +5,8 @@ prev:
text: "Repository"
link: "/en/v1/api/clean/repository"
next:
- text: "Clean"
- link: "/en/v1/api/clean/"
+ text: "chainedFunction"
+ link: "/en/v1/api/clean/chainedFunction"
---
# UseCase
diff --git a/docs/examples/v1/api/clean/chainedFunction/tryout.doc.ts b/docs/examples/v1/api/clean/chainedFunction/tryout.doc.ts
new file mode 100644
index 00000000..c338ee31
--- /dev/null
+++ b/docs/examples/v1/api/clean/chainedFunction/tryout.doc.ts
@@ -0,0 +1,104 @@
+import { C, E, type ExpectType } from "@duplojs/utils";
+
+interface CommentDraft {
+ articleId: number;
+ content: string;
+}
+
+interface Comment {
+ id: number;
+ articleId: number;
+ content: string;
+}
+
+interface Article {
+ id: number;
+ commentCount: number;
+}
+
+interface CommentRepository {
+ save(comment: Comment): Comment | E.Fail;
+}
+
+interface ArticleRepository {
+ findById(articleId: number): Article | E.Fail;
+ save(article: Article): Article | E.Fail;
+}
+
+const CommentRepository = C.createRepository();
+const ArticleRepository = C.createRepository();
+
+const CommentPublicationAggregate = C.chainedFunction(
+ [
+ "createComment",
+ (draft: CommentDraft) => draft.content.trim()
+ ? {
+ id: 1,
+ articleId: draft.articleId,
+ content: draft.content.trim(),
+ }
+ : E.fail(),
+ ],
+ [
+ "incrementArticleCommentCount",
+ (article: Article): Article => ({
+ ...article,
+ commentCount: article.commentCount + 1,
+ }),
+ ],
+);
+
+const PublishCommentUseCase = C.createUseCase(
+ {
+ CommentRepository,
+ ArticleRepository,
+ },
+ ({
+ commentRepository,
+ articleRepository,
+ }) => (draft: CommentDraft) => CommentPublicationAggregate(function *(link1, { breakIfLeft }) {
+ const [comment, link2] = yield *link1(({ createComment }) => createComment(draft));
+
+ const savedComment = yield *breakIfLeft(commentRepository.save(comment));
+
+ const article = yield *breakIfLeft(articleRepository.findById(savedComment.articleId));
+
+ const [updatedArticle, chainEnd] = yield *link2(
+ ({ incrementArticleCommentCount }) => incrementArticleCommentCount(article),
+ );
+
+ const savedArticle = yield *breakIfLeft(articleRepository.save(updatedArticle));
+
+ return chainEnd({
+ comment: savedComment,
+ article: savedArticle,
+ });
+ }),
+);
+
+const publishComment = PublishCommentUseCase.getUseCase({
+ commentRepository: CommentRepository.createImplementation({
+ save: (comment) => comment,
+ }),
+ articleRepository: ArticleRepository.createImplementation({
+ findById: (articleId) => ({
+ id: articleId,
+ commentCount: 11,
+ }),
+ save: (article) => article,
+ }),
+});
+
+const publishedComment = publishComment({
+ articleId: 12,
+ content: " New comment ",
+});
+
+type CheckPublishedComment = ExpectType<
+ typeof publishedComment,
+ {
+ comment: Comment;
+ article: Article;
+ } | E.Fail,
+ "strict"
+>;
diff --git a/docs/fr/v1/api/clean/chainedFunction.md b/docs/fr/v1/api/clean/chainedFunction.md
new file mode 100644
index 00000000..0eec0156
--- /dev/null
+++ b/docs/fr/v1/api/clean/chainedFunction.md
@@ -0,0 +1,100 @@
+---
+outline: [2, 3]
+description: "chainedFunction déclare un agrégat typé d'actions métier pures qui doivent s'exécuter dans l'ordre. Le use case orchestre ensuite les repositories autour de cet agrégat."
+prev:
+ text: "UseCase"
+ link: "/fr/v1/api/clean/useCase"
+next:
+ text: "Clean"
+ link: "/fr/v1/api/clean/"
+---
+
+# chainedFunction
+
+`chainedFunction` répond à un problème de coordination en Clean Architecture : dans un use case, on doit parfois associer des opérations qui mettent à jour des entités différentes. La chained function représente alors l'agrégat qui rend ces opérations solidaires dans une même frontière de cohérence métier.
+
+Elle permet au domaine de déclarer explicitement que plusieurs actions métier pures sont liées. Les fonctions passées à `chainedFunction` ne font pas d'injection de dépendance et n'appellent pas de repository : elles contrôlent uniquement le cycle de vie des entités. Le use case orchestre ensuite l'agrégat, les repositories et les effets techniques.
+
+## Exemple interactif
+
+
+
+## Pourquoi l'utiliser ?
+
+Utilisez `chainedFunction` quand un agrégat métier n'est cohérent que si plusieurs opérations nommées se produisent ensemble.
+
+Par exemple, publier un commentaire peut demander :
+
+- de créer l'entité commentaire ;
+- de produire une entité commentaire valide ;
+- de produire une entité article mise à jour.
+
+La persistance reste dans le use case via le système de repository de la librairie. La chained function modélise seulement le contrat d'agrégat : "créer le commentaire" et "incrémenter le nombre de commentaires de l'article" sont des actions métier liées.
+
+## Garanties
+
+`chainedFunction` apporte ces garanties au niveau du typage :
+
+- les liens sont exposés un par un, dans l'ordre de déclaration ;
+- l'implémentation ne peut pas accéder à un lien suivant avant d'avoir appelé le lien courant ;
+- le chemin de succès doit terminer avec `chainEnd(...)` ;
+- une fonction chaînée peut arrêter le flux en retournant un `Either.Left` ;
+- l'implémentation peut aussi retourner directement un `Either.Left`.
+- les fonctions chaînées restent des fonctions de domaine pures.
+
+## Syntaxe
+
+### Signature
+
+```typescript
+function chainedFunction(
+ function1: [name: string, fn: Function],
+ function2: [name: string, fn: Function],
+ ...functions: [name: string, fn: Function][]
+): ChainedFunction
+```
+
+### Implémentation
+
+```typescript
+const aggregate = chainedFunction(...functions);
+
+const result = aggregate(function *(firstLink, { breakIfLeft }) {
+ const [value, nextLink] = yield *firstLink(({ functionName }) => functionName(...args));
+
+ return chainEnd(value);
+});
+```
+
+## Paramètres
+
+- `function1` : première fonction métier pure de la chaîne.
+- `function2` : deuxième fonction métier pure de la chaîne.
+- `functions` : fonctions métier pures supplémentaires, exécutées dans l'ordre de déclaration.
+- `firstLink` : premier link généré et passé au callback d'implémentation.
+- `breakIfLeft` : helper synchrone injecté dans le callback. Il accepte une valeur potentiellement `Either.Left`, stoppe la chaîne si c'est un `Left`, sinon retourne la valeur discriminée sans le `Left`.
+
+## Valeur de retour
+
+`chainedFunction(...)` retourne une fonction d'agrégat chaîné. Son appel retourne :
+
+- la valeur brute passée à `chainEnd(value)` sur le chemin de succès ;
+- le `Either.Left` retourné par une fonction chaînée ;
+- le `Either.Left` retourné directement par l'implémentation.
+
+## Flux d'erreur
+
+Quand une fonction chaînée retourne un `Either.Left`, le générateur le yield et `chainedFunction` arrête l'implémentation avant d'exécuter les links suivants. Les erreurs métier doivent être représentées avec `Either.Left` ; les exceptions lancées et les promesses rejetées ne sont pas interceptées.
+
+`breakIfLeft` suit la même règle, mais côté callback : il sert à court-circuiter explicitement le flux à partir d'une valeur intermédiaire synchrone (`value | Left`) avant d'appeler le link suivant.
+
+## Voir aussi
+
+- [`useCase`](/fr/v1/api/clean/useCase) - Appelle une logique applicative avec dépendances.
+- [`repository`](/fr/v1/api/clean/repository) - Déclare un contrat de repository.
+- [`Either`](/fr/v1/api/either/) - Représente des valeurs explicites de succès et d'erreur.
diff --git a/docs/fr/v1/api/clean/index.md b/docs/fr/v1/api/clean/index.md
index 393e21ee..57533aa3 100644
--- a/docs/fr/v1/api/clean/index.md
+++ b/docs/fr/v1/api/clean/index.md
@@ -55,6 +55,9 @@ Déclare un repository (contrat) et type-check l'implémentation.
## [UseCase](/fr/v1/api/clean/useCase)
Déclare un use case avec des dépendances (repositories ou autres use cases).
+## [chainedFunction](/fr/v1/api/clean/chainedFunction)
+Déclare un agrégat d'actions métier liées qui doivent s'exécuter dans l'ordre.
+
## Opérations sur primitives
### [equal](/fr/v1/api/clean/primitives/operators/equal)
diff --git a/docs/fr/v1/api/clean/useCase.md b/docs/fr/v1/api/clean/useCase.md
index 50e88bfa..392a29b7 100644
--- a/docs/fr/v1/api/clean/useCase.md
+++ b/docs/fr/v1/api/clean/useCase.md
@@ -5,8 +5,8 @@ prev:
text: "Repository"
link: "/fr/v1/api/clean/repository"
next:
- text: "Clean"
- link: "/fr/v1/api/clean/"
+ text: "chainedFunction"
+ link: "/fr/v1/api/clean/chainedFunction"
---
# UseCase
diff --git a/docs/public/libs/v1/clean/chainedFunction.cjs b/docs/public/libs/v1/clean/chainedFunction.cjs
new file mode 100644
index 00000000..85523ac9
--- /dev/null
+++ b/docs/public/libs/v1/clean/chainedFunction.cjs
@@ -0,0 +1,70 @@
+'use strict';
+
+var kind = require('./kind.cjs');
+var is = require('../either/left/is.cjs');
+
+const chainEndKind = kind.createCleanKind("chain-end");
+function* breakIfLeft(value) {
+ if (is.isLeft(value)) {
+ yield value;
+ }
+ return value;
+}
+const chainedFunctionParams = { breakIfLeft };
+/**
+ * {@include clean/chainedFunction/index.md}
+ */
+function chainedFunction(function1, function2, ...functions) {
+ return (theFunction) => {
+ const functionChain = [function1, function2, ...functions];
+ const createLink = (functionChain) => (theFunction) => {
+ const [functionName, chainedFunction] = functionChain.shift();
+ const result = theFunction({ [functionName]: chainedFunction });
+ const nextLink = functionChain.length === 0
+ ? (value) => chainEndKind.setTo({}, value)
+ : createLink(functionChain);
+ if (result instanceof Promise) {
+ return (async function* () {
+ const awaitedResult = await result;
+ if (is.isLeft(awaitedResult)) {
+ yield awaitedResult;
+ }
+ return [awaitedResult, nextLink];
+ })();
+ }
+ return (function* () {
+ if (is.isLeft(result)) {
+ yield result;
+ }
+ return [result, nextLink];
+ })();
+ };
+ const generator = theFunction(createLink(functionChain), chainedFunctionParams);
+ let result = undefined;
+ if (Symbol.asyncIterator in generator) {
+ return (async () => {
+ try {
+ result = await generator.next();
+ }
+ finally {
+ await generator.return(undefined);
+ }
+ return (chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value);
+ })();
+ }
+ try {
+ result = generator.next();
+ }
+ finally {
+ generator.return(undefined);
+ }
+ return (chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value);
+ };
+}
+
+exports.chainEndKind = chainEndKind;
+exports.chainedFunction = chainedFunction;
diff --git a/docs/public/libs/v1/clean/chainedFunction.d.ts b/docs/public/libs/v1/clean/chainedFunction.d.ts
new file mode 100644
index 00000000..691a5dbb
--- /dev/null
+++ b/docs/public/libs/v1/clean/chainedFunction.d.ts
@@ -0,0 +1,216 @@
+import { type AnyFunction, type Kind, type IsEqual, type MaybePromise, type MaybeAsyncGenerator, type GetKindValue } from "../common";
+import * as EE from "../either";
+export type FunctionOfChain = [string, AnyFunction];
+export type FunctionChain = [
+ FunctionOfChain,
+ FunctionOfChain,
+ ...FunctionOfChain[]
+];
+export declare const chainEndKind: import("../common").KindHandler>;
+export interface ChainEnd extends Kind {
+}
+export interface CreateChainEnd {
+ (): ChainEnd;
+ (value: GenericValue): ChainEnd;
+}
+export type Link = >(theFunction: (theFunction: {
+ [Prop in GenericFunction[0]]: GenericFunction[1];
+}) => GenericOutput) => ((Extract> extends infer InferredPromise ? IsEqual extends true ? never : Awaited extends infer InferredValue extends unknown ? AsyncGenerator, [
+ Exclude,
+ GenericNext
+]> : never : never) | (Exclude> extends infer InferredValue ? IsEqual extends true ? never : Generator, [
+ Exclude,
+ GenericNext
+]> : never));
+export type Chain = GenericFunctionChain extends readonly [] ? CreateChainEnd : GenericFunctionChain extends [
+ infer InferredFirst extends FunctionOfChain,
+ ...infer InferredRest extends readonly FunctionOfChain[]
+] ? Chain extends infer InferredRestResult extends (Link | CreateChainEnd) ? Link : never : never;
+declare const SymbolError: unique symbol;
+type OutputMustContainChainEnd = IsEqual ? InferredReturnValue extends ChainEnd ? InferredReturnValue : never : never, never> extends true ? {
+ [SymbolError]: "Output must contain a chainEnd";
+} : unknown;
+type ComputeResult = GenericGenerator extends Generator ? (InferredIterateValue | InferredReturnValue) extends infer InferredResult ? InferredResult extends ChainEnd ? GetKindValue : InferredResult : never : GenericGenerator extends AsyncGenerator ? Promise extends infer InferredResult ? InferredResult extends ChainEnd ? GetKindValue : InferredResult : never> : never;
+declare function breakIfLeft(value: GenericValue): Generator, Exclude>;
+export interface ChainedFunctionParams {
+ breakIfLeft: typeof breakIfLeft;
+}
+export type ChainedFunction = , MaybePromise>>(callback: (firstLink: Chain, params: ChainedFunctionParams) => (GenericGenerator & OutputMustContainChainEnd)) => ComputeResult;
+/**
+ * Declares a typed aggregate of pure linked business actions that must run in order.
+ *
+ * **Supported call styles:**
+ * - Classic: `chainedFunction(firstFunction, secondFunction, ...functions)` -> returns an implementation function driven by generator links
+ *
+ * Use it inside a Clean Architecture use case when several pure domain operations that update different entities must belong to the same business consistency boundary. Each link exposes exactly one named action, yields `Left` values to short-circuit the implementation, and provides the next link until the last step returns `chainEnd(value)`. Repository calls stay in the use case through the library repository system; functions passed to `chainedFunction` remain pure domain functions.
+ *
+ * ```ts
+ * interface CommentDraft {
+ * articleId: number;
+ * content: string;
+ * }
+ *
+ * interface Comment {
+ * id: number;
+ * articleId: number;
+ * content: string;
+ * }
+ *
+ * interface Article {
+ * id: number;
+ * commentCount: number;
+ * }
+ *
+ * interface CommentRepository {
+ * save(comment: Comment): Comment | E.Fail;
+ * }
+ *
+ * interface ArticleRepository {
+ * findById(articleId: number): Article | E.Fail;
+ * save(article: Article): Article | E.Fail;
+ * }
+ *
+ * const CommentRepository = C.createRepository();
+ * const ArticleRepository = C.createRepository();
+ *
+ * const CommentPublicationAggregate = C.chainedFunction(
+ * [
+ * "createComment",
+ * (draft: CommentDraft): Comment | E.Fail => draft.content.trim()
+ * ? {
+ * id: 1,
+ * articleId: draft.articleId,
+ * content: draft.content.trim(),
+ * }
+ * : E.fail(),
+ * ],
+ * [
+ * "incrementArticleCommentCount",
+ * (article: Article): Article => ({
+ * ...article,
+ * commentCount: article.commentCount + 1,
+ * }),
+ * ],
+ * );
+ *
+ * const PublishCommentUseCase = C.createUseCase(
+ * {
+ * CommentRepository,
+ * ArticleRepository,
+ * },
+ * ({
+ * commentRepository,
+ * articleRepository,
+ * }) => (draft: CommentDraft) => CommentPublicationAggregate(function *(link1) {
+ * const [comment, link2] = yield *link1(({ createComment }) => createComment(draft));
+ *
+ * const savedComment = commentRepository.save(comment);
+ * if (E.isLeft(savedComment)) {
+ * return savedComment;
+ * }
+ *
+ * const article = articleRepository.findById(savedComment.articleId);
+ * if (E.isLeft(article)) {
+ * return article;
+ * }
+ *
+ * const [updatedArticle, chainEnd] = yield *link2(
+ * ({ incrementArticleCommentCount }) => incrementArticleCommentCount(article),
+ * );
+ *
+ * const savedArticle = articleRepository.save(updatedArticle);
+ * if (E.isLeft(savedArticle)) {
+ * return savedArticle;
+ * }
+ *
+ * return chainEnd({
+ * comment: savedComment,
+ * article: savedArticle,
+ * });
+ * }),
+ * );
+ *
+ * const publishComment = PublishCommentUseCase.getUseCase({
+ * commentRepository: CommentRepository.createImplementation({
+ * save: (comment) => comment,
+ * }),
+ * articleRepository: ArticleRepository.createImplementation({
+ * findById: (articleId) => ({
+ * id: articleId,
+ * commentCount: 11,
+ * }),
+ * save: (article) => article,
+ * }),
+ * });
+ *
+ * const publishedComment = publishComment({
+ * articleId: 12,
+ * content: " New comment ",
+ * });
+ *
+ * type CheckPublishedComment = ExpectType<
+ * typeof publishedComment,
+ * {
+ * comment: Comment;
+ * article: Article;
+ * } | E.Fail,
+ * "strict"
+ * >;
+ *
+ * const emptyContentResult = publishComment({
+ * articleId: 12,
+ * content: " ",
+ * });
+ *
+ * type CheckEmptyContentResult = ExpectType<
+ * typeof emptyContentResult,
+ * {
+ * comment: Comment;
+ * article: Article;
+ * } | E.Fail,
+ * "strict"
+ * >;
+ *
+ * const failingPublishComment = PublishCommentUseCase.getUseCase({
+ * commentRepository: CommentRepository.createImplementation({
+ * save: () => E.fail(),
+ * }),
+ * articleRepository: ArticleRepository.createImplementation({
+ * findById: (articleId) => ({
+ * id: articleId,
+ * commentCount: 11,
+ * }),
+ * save: (article) => article,
+ * }),
+ * });
+ *
+ * const repositoryFailureResult = failingPublishComment({
+ * articleId: 12,
+ * content: "New comment",
+ * });
+ *
+ * type CheckRepositoryFailureResult = ExpectType<
+ * typeof repositoryFailureResult,
+ * {
+ * comment: Comment;
+ * article: Article;
+ * } | E.Fail,
+ * "strict"
+ * >;
+ * ```
+ *
+ * @remarks `chainedFunction` expects at least two functions in the chain. It does not catch thrown exceptions or rejected promises; model handled business errors with `Either.Left`.
+ *
+ * @see https://utils.duplojs.dev/en/v1/api/clean/chainedFunction
+ * @see [`C.createUseCase`](https://utils.duplojs.dev/en/v1/api/clean/useCase)
+ * @see [`E.Left`](https://utils.duplojs.dev/en/v1/api/either/left)
+ *
+ * @namespace C
+ *
+ */
+export declare function chainedFunction(function1: GenericFunction1, function2: GenericFunction2, ...functions: GenericFunctions): ChainedFunction<[
+ GenericFunction1,
+ GenericFunction2,
+ ...GenericFunctions
+]>;
+export {};
diff --git a/docs/public/libs/v1/clean/chainedFunction.mjs b/docs/public/libs/v1/clean/chainedFunction.mjs
new file mode 100644
index 00000000..f4e0e105
--- /dev/null
+++ b/docs/public/libs/v1/clean/chainedFunction.mjs
@@ -0,0 +1,67 @@
+import { createCleanKind } from './kind.mjs';
+import { isLeft } from '../either/left/is.mjs';
+
+const chainEndKind = createCleanKind("chain-end");
+function* breakIfLeft(value) {
+ if (isLeft(value)) {
+ yield value;
+ }
+ return value;
+}
+const chainedFunctionParams = { breakIfLeft };
+/**
+ * {@include clean/chainedFunction/index.md}
+ */
+function chainedFunction(function1, function2, ...functions) {
+ return (theFunction) => {
+ const functionChain = [function1, function2, ...functions];
+ const createLink = (functionChain) => (theFunction) => {
+ const [functionName, chainedFunction] = functionChain.shift();
+ const result = theFunction({ [functionName]: chainedFunction });
+ const nextLink = functionChain.length === 0
+ ? (value) => chainEndKind.setTo({}, value)
+ : createLink(functionChain);
+ if (result instanceof Promise) {
+ return (async function* () {
+ const awaitedResult = await result;
+ if (isLeft(awaitedResult)) {
+ yield awaitedResult;
+ }
+ return [awaitedResult, nextLink];
+ })();
+ }
+ return (function* () {
+ if (isLeft(result)) {
+ yield result;
+ }
+ return [result, nextLink];
+ })();
+ };
+ const generator = theFunction(createLink(functionChain), chainedFunctionParams);
+ let result = undefined;
+ if (Symbol.asyncIterator in generator) {
+ return (async () => {
+ try {
+ result = await generator.next();
+ }
+ finally {
+ await generator.return(undefined);
+ }
+ return (chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value);
+ })();
+ }
+ try {
+ result = generator.next();
+ }
+ finally {
+ generator.return(undefined);
+ }
+ return (chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value);
+ };
+}
+
+export { chainEndKind, chainedFunction };
diff --git a/docs/public/libs/v1/clean/flag.d.ts b/docs/public/libs/v1/clean/flag.d.ts
index 92a80486..f54b7166 100644
--- a/docs/public/libs/v1/clean/flag.d.ts
+++ b/docs/public/libs/v1/clean/flag.d.ts
@@ -15,7 +15,7 @@ export interface FlagHandler("majorUser");
* export type MajorFlag = C.GetFlag;
*
* export function isMajor(entity: Entity) {
* if (C.greaterThan(entity.age, 18)) {
* return E.success(
- * MajorFlag.append(entity, entity.age),
+ * MajorFlag.append(entity, { age: entity.age }),
* );
* }
* return E.left("not-major");
@@ -94,7 +94,7 @@ export interface Flag | E.Right<"not-thirsty-anymore", undefined>
*
- * const flagged = User.MajorFlag.append(user, user.age);
+ * const flagged = User.MajorFlag.append(user, { age: user.age });
* const value = User.MajorFlag.getValue(flagged);
*
* ```
diff --git a/docs/public/libs/v1/clean/index.cjs b/docs/public/libs/v1/clean/index.cjs
index f31c2a1b..45d9967d 100644
--- a/docs/public/libs/v1/clean/index.cjs
+++ b/docs/public/libs/v1/clean/index.cjs
@@ -8,6 +8,7 @@ var useCase = require('./useCase.cjs');
var flag = require('./flag.cjs');
var maybe = require('./maybe.cjs');
var toMapDataParser = require('./toMapDataParser.cjs');
+var chainedFunction = require('./chainedFunction.cjs');
var property = require('./entity/property.cjs');
var unwrap = require('./entity/unwrap.cjs');
var base = require('./constraint/base.cjs');
@@ -66,6 +67,8 @@ exports.flagKind = flag.flagKind;
exports.none = maybe.none;
exports.some = maybe.some;
exports.toMapDataParser = toMapDataParser.toMapDataParser;
+exports.chainEndKind = chainedFunction.chainEndKind;
+exports.chainedFunction = chainedFunction.chainedFunction;
exports.entityPropertyArrayKind = property.entityPropertyArrayKind;
exports.entityPropertyDefinitionToDataParser = property.entityPropertyDefinitionToDataParser;
exports.entityPropertyDefinitionTools = property.entityPropertyDefinitionTools;
diff --git a/docs/public/libs/v1/clean/index.d.ts b/docs/public/libs/v1/clean/index.d.ts
index 51749bb7..7f1b65d3 100644
--- a/docs/public/libs/v1/clean/index.d.ts
+++ b/docs/public/libs/v1/clean/index.d.ts
@@ -34,3 +34,4 @@ export * from "./useCase";
export * from "./flag";
export * from "./maybe";
export * from "./toMapDataParser";
+export * from "./chainedFunction";
diff --git a/docs/public/libs/v1/clean/index.mjs b/docs/public/libs/v1/clean/index.mjs
index 802a0110..58d0f7da 100644
--- a/docs/public/libs/v1/clean/index.mjs
+++ b/docs/public/libs/v1/clean/index.mjs
@@ -6,6 +6,7 @@ export { createUseCase, useCaseHandlerKind, useCaseInstances } from './useCase.m
export { createFlag, flagKind } from './flag.mjs';
export { none, some } from './maybe.mjs';
export { toMapDataParser } from './toMapDataParser.mjs';
+export { chainEndKind, chainedFunction } from './chainedFunction.mjs';
export { entityPropertyArrayKind, entityPropertyDefinitionToDataParser, entityPropertyDefinitionTools, entityPropertyIdentifierKind, entityPropertyNullableKind, entityPropertyStructureKind, entityPropertyUnionKind } from './entity/property.mjs';
export { unwrapEntity, unwrapEntityProperty } from './entity/unwrap.mjs';
export { CreateConstrainedTypeError, constrainedTypeKind, constraintHandlerKind, createConstraint } from './constraint/base.mjs';
diff --git a/docs/public/libs/v1/clean/toMapDataParser.cjs b/docs/public/libs/v1/clean/toMapDataParser.cjs
index 20da7ffa..e2b14c50 100644
--- a/docs/public/libs/v1/clean/toMapDataParser.cjs
+++ b/docs/public/libs/v1/clean/toMapDataParser.cjs
@@ -1,10 +1,11 @@
'use strict';
var newType = require('./newType.cjs');
+var hasSomeKinds = require('../common/hasSomeKinds.cjs');
+var property = require('./entity/property.cjs');
var base = require('./primitive/base.cjs');
var base$1 = require('./constraint/base.cjs');
var set = require('./constraint/set.cjs');
-var hasSomeKinds = require('../common/hasSomeKinds.cjs');
var index = require('../pattern/match/index.cjs');
var transform = require('../dataParser/parsers/transform.cjs');
var index$1 = require('../dataParser/parsers/string/index.cjs');
@@ -18,6 +19,15 @@ var nil = require('../dataParser/parsers/nil.cjs');
var wrapValue = require('../common/wrapValue.cjs');
function toMapDataParser(input, params) {
+ if (hasSomeKinds.hasSomeKinds(input, [
+ property.entityPropertyNullableKind,
+ property.entityPropertyArrayKind,
+ property.entityPropertyStructureKind,
+ property.entityPropertyIdentifierKind,
+ property.entityPropertyUnionKind,
+ ])) {
+ return property.entityPropertyDefinitionToDataParser(input, (newTypeHandler) => toMapDataParser(newTypeHandler, params));
+ }
const dataParser = (base.primitiveHandlerKind.has(input)
? input.dataParser.clone()
: input.internal.dataParser.clone());
diff --git a/docs/public/libs/v1/clean/toMapDataParser.d.ts b/docs/public/libs/v1/clean/toMapDataParser.d.ts
index 8ef67f6a..51d9afa7 100644
--- a/docs/public/libs/v1/clean/toMapDataParser.d.ts
+++ b/docs/public/libs/v1/clean/toMapDataParser.d.ts
@@ -1,9 +1,9 @@
import * as DDataParser from "../dataParser";
import { type ConstraintHandler, type ConstraintsSetHandler, type GetConstraint, type GetConstraints } from "./constraint";
-import { type GetNewType, type NewTypeHandler } from "./newType";
import { type PrimitiveHandler } from "./primitive";
-type ToMapDataParserInput = NewTypeHandler | ConstraintHandler | ConstraintsSetHandler | PrimitiveHandler;
-type OutputDataParser = GenericInput extends NewTypeHandler ? GetNewType : GenericInput extends ConstraintHandler ? GetConstraint : GenericInput extends ConstraintsSetHandler ? GetConstraints : GenericInput extends PrimitiveHandler ? ReturnType : never;
+import { type EntityPropertyDefinition, type EntityProperty } from "./entity";
+type ToMapDataParserInput = (ConstraintHandler | ConstraintsSetHandler | PrimitiveHandler | EntityPropertyDefinition);
+type OutputDataParser = GenericInput extends ConstraintHandler ? GetConstraint : GenericInput extends ConstraintsSetHandler ? GetConstraints : GenericInput extends PrimitiveHandler ? ReturnType : GenericInput extends EntityPropertyDefinition ? EntityProperty : never;
interface ToMapDataParserParams {
coerce?: boolean;
}
@@ -41,7 +41,7 @@ interface ToMapDataParserParams {
* ```
*
* @remarks
- * - Supported inputs: `NewTypeHandler`, `ConstraintHandler`, `ConstraintsSetHandler`, and `PrimitiveHandler`.
+ * - Supported inputs: `NewTypeHandler`, `ConstraintHandler`, `ConstraintsSetHandler`, `PrimitiveHandler`, and `EntityProperty`.
* - Use `coerce: true` to allow conversions (e.g. number to string) on compatible parsers.
*
* @see https://utils.duplojs.dev/en/v1/api/clean/toMapDataParser
@@ -49,5 +49,5 @@ interface ToMapDataParserParams {
* @namespace C
*
*/
-export declare function toMapDataParser(input: GenericInput, params?: ToMapDataParserParams): DDataParser.Contract, GenericInputDataParser>;
+export declare function toMapDataParser>(input: GenericInput, params?: ToMapDataParserParams): DDataParser.Contract, unknown>;
export {};
diff --git a/docs/public/libs/v1/clean/toMapDataParser.mjs b/docs/public/libs/v1/clean/toMapDataParser.mjs
index 9e841245..03ba64df 100644
--- a/docs/public/libs/v1/clean/toMapDataParser.mjs
+++ b/docs/public/libs/v1/clean/toMapDataParser.mjs
@@ -1,8 +1,9 @@
import { newTypeHandlerKind, newTypeKind } from './newType.mjs';
+import { hasSomeKinds } from '../common/hasSomeKinds.mjs';
+import { entityPropertyDefinitionToDataParser, entityPropertyNullableKind, entityPropertyArrayKind, entityPropertyStructureKind, entityPropertyIdentifierKind, entityPropertyUnionKind } from './entity/property.mjs';
import { primitiveHandlerKind } from './primitive/base.mjs';
import { constrainedTypeKind, constraintHandlerKind } from './constraint/base.mjs';
import { constraintsSetHandlerKind } from './constraint/set.mjs';
-import { hasSomeKinds } from '../common/hasSomeKinds.mjs';
import { match } from '../pattern/match/index.mjs';
import { transform } from '../dataParser/parsers/transform.mjs';
import { stringKind } from '../dataParser/parsers/string/index.mjs';
@@ -16,6 +17,15 @@ import { nilKind } from '../dataParser/parsers/nil.mjs';
import { keyWrappedValue } from '../common/wrapValue.mjs';
function toMapDataParser(input, params) {
+ if (hasSomeKinds(input, [
+ entityPropertyNullableKind,
+ entityPropertyArrayKind,
+ entityPropertyStructureKind,
+ entityPropertyIdentifierKind,
+ entityPropertyUnionKind,
+ ])) {
+ return entityPropertyDefinitionToDataParser(input, (newTypeHandler) => toMapDataParser(newTypeHandler, params));
+ }
const dataParser = (primitiveHandlerKind.has(input)
? input.dataParser.clone()
: input.internal.dataParser.clone());
diff --git a/docs/public/libs/v1/common/types/DeepReadonly.d.ts b/docs/public/libs/v1/common/types/deepReadonly.d.ts
similarity index 100%
rename from docs/public/libs/v1/common/types/DeepReadonly.d.ts
rename to docs/public/libs/v1/common/types/deepReadonly.d.ts
diff --git a/docs/public/libs/v1/common/types/index.d.ts b/docs/public/libs/v1/common/types/index.d.ts
index d15eea86..1f6071dc 100644
--- a/docs/public/libs/v1/common/types/index.d.ts
+++ b/docs/public/libs/v1/common/types/index.d.ts
@@ -43,7 +43,8 @@ export * from "./onlyLiteral";
export * from "./sortType";
export * from "./maybeGetter";
export * from "./falsyValue";
-export * from "./DeepReadonly";
+export * from "./deepReadonly";
export * from "./json";
export * from "./predicate";
export * from "./bivariantFunction";
+export * from "./maybeAsyncGenerator";
diff --git a/docs/public/libs/v1/common/types/maybeAsyncGenerator.d.ts b/docs/public/libs/v1/common/types/maybeAsyncGenerator.d.ts
new file mode 100644
index 00000000..258f3d16
--- /dev/null
+++ b/docs/public/libs/v1/common/types/maybeAsyncGenerator.d.ts
@@ -0,0 +1 @@
+export type MaybeAsyncGenerator = (Generator | AsyncGenerator);
diff --git a/docs/public/libs/v1/dataParser/base.cjs b/docs/public/libs/v1/dataParser/base.cjs
index fa2de3cc..089fe286 100644
--- a/docs/public/libs/v1/dataParser/base.cjs
+++ b/docs/public/libs/v1/dataParser/base.cjs
@@ -45,7 +45,7 @@ function dataParserInit(kind, definition, exec, specificOverrideHandler) {
if (result !== SDPE
&& self.definition.checkers.length) {
for (const checker of self.definition.checkers) {
- const checkerResult = checker.exec(result, error, checker);
+ const checkerResult = checker.exec(result, error, checker, self);
if (checkerResult === SDPE) {
return SDPE;
}
@@ -61,7 +61,7 @@ function dataParserInit(kind, definition, exec, specificOverrideHandler) {
if (result !== SDPE
&& self.definition.checkers.length) {
for (const checker of self.definition.checkers) {
- const checkerResult = checker.exec(result, error, checker);
+ const checkerResult = checker.exec(result, error, checker, self);
if (checkerResult === SDPE) {
return SDPE;
}
diff --git a/docs/public/libs/v1/dataParser/base.d.ts b/docs/public/libs/v1/dataParser/base.d.ts
index 29133be2..140b6c5e 100644
--- a/docs/public/libs/v1/dataParser/base.d.ts
+++ b/docs/public/libs/v1/dataParser/base.d.ts
@@ -8,7 +8,7 @@ export interface DataParserCheckerDefinition {
}
export interface DataParserChecker extends Kind {
readonly definition: GenericDefinition;
- exec(data: GenericInput, error: DataParserError, self: this): GenericOutput | SymbolDataParserError;
+ exec(data: GenericInput, error: DataParserError, self: this, dataParser: DataParser): GenericOutput | SymbolDataParserError;
}
export type InputChecker = Parameters[0];
export declare function dataParserCheckerInit(kind: Exclude, typeof checkerKind>, params: NoInfer, "exec">>, exec: (...args: Parameters) => InputChecker | SymbolDataParserError): GenericDataParserChecker;
diff --git a/docs/public/libs/v1/dataParser/base.mjs b/docs/public/libs/v1/dataParser/base.mjs
index 8f56f5aa..3f862bbd 100644
--- a/docs/public/libs/v1/dataParser/base.mjs
+++ b/docs/public/libs/v1/dataParser/base.mjs
@@ -43,7 +43,7 @@ function dataParserInit(kind, definition, exec, specificOverrideHandler) {
if (result !== SDPE
&& self.definition.checkers.length) {
for (const checker of self.definition.checkers) {
- const checkerResult = checker.exec(result, error, checker);
+ const checkerResult = checker.exec(result, error, checker, self);
if (checkerResult === SDPE) {
return SDPE;
}
@@ -59,7 +59,7 @@ function dataParserInit(kind, definition, exec, specificOverrideHandler) {
if (result !== SDPE
&& self.definition.checkers.length) {
for (const checker of self.definition.checkers) {
- const checkerResult = checker.exec(result, error, checker);
+ const checkerResult = checker.exec(result, error, checker, self);
if (checkerResult === SDPE) {
return SDPE;
}
diff --git a/docs/public/libs/v1/dataParser/parsers/array/checkers/max.cjs b/docs/public/libs/v1/dataParser/parsers/array/checkers/max.cjs
index a3b6f807..8b27aba4 100644
--- a/docs/public/libs/v1/dataParser/parsers/array/checkers/max.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/array/checkers/max.cjs
@@ -11,9 +11,9 @@ function checkerArrayMax(max, definition = {}) {
...definition,
max,
},
- }, (data, error$1, self) => data.length <= self.definition.max
+ }, (data, error$1, self, dataParser) => data.length <= self.definition.max
? data
- : error.addIssue(error$1, `array.length <= ${self.definition.max}`, data, self.definition.errorMessage));
+ : error.addIssue(error$1, `array.length <= ${self.definition.max}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerArrayMax = checkerArrayMax;
diff --git a/docs/public/libs/v1/dataParser/parsers/array/checkers/max.mjs b/docs/public/libs/v1/dataParser/parsers/array/checkers/max.mjs
index 3957498a..e5535ca8 100644
--- a/docs/public/libs/v1/dataParser/parsers/array/checkers/max.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/array/checkers/max.mjs
@@ -9,9 +9,9 @@ function checkerArrayMax(max, definition = {}) {
...definition,
max,
},
- }, (data, error, self) => data.length <= self.definition.max
+ }, (data, error, self, dataParser) => data.length <= self.definition.max
? data
- : addIssue(error, `array.length <= ${self.definition.max}`, data, self.definition.errorMessage));
+ : addIssue(error, `array.length <= ${self.definition.max}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerArrayMax, checkerArrayMaxKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/array/checkers/min.cjs b/docs/public/libs/v1/dataParser/parsers/array/checkers/min.cjs
index be0b35bd..6c3e4d2f 100644
--- a/docs/public/libs/v1/dataParser/parsers/array/checkers/min.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/array/checkers/min.cjs
@@ -11,9 +11,9 @@ function checkerArrayMin(min, definition = {}) {
...definition,
min,
},
- }, (data, error$1, self) => data.length >= self.definition.min
+ }, (data, error$1, self, dataParser) => data.length >= self.definition.min
? data
- : error.addIssue(error$1, `array.length >= ${self.definition.min}`, data, self.definition.errorMessage));
+ : error.addIssue(error$1, `array.length >= ${self.definition.min}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerArrayMin = checkerArrayMin;
diff --git a/docs/public/libs/v1/dataParser/parsers/array/checkers/min.mjs b/docs/public/libs/v1/dataParser/parsers/array/checkers/min.mjs
index fa75795e..ee7520f2 100644
--- a/docs/public/libs/v1/dataParser/parsers/array/checkers/min.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/array/checkers/min.mjs
@@ -9,9 +9,9 @@ function checkerArrayMin(min, definition = {}) {
...definition,
min,
},
- }, (data, error, self) => data.length >= self.definition.min
+ }, (data, error, self, dataParser) => data.length >= self.definition.min
? data
- : addIssue(error, `array.length >= ${self.definition.min}`, data, self.definition.errorMessage));
+ : addIssue(error, `array.length >= ${self.definition.min}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerArrayMin, checkerArrayMinKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.cjs b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.cjs
index 24d06fc6..3a0c8e43 100644
--- a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.cjs
@@ -11,9 +11,9 @@ function checkerBigIntMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error$1, self) => {
+ }, (value, error$1, self, dataParser) => {
if (value > self.definition.max) {
- return error.addIssue(error$1, `bigint <= ${self.definition.max}n`, value, self.definition.errorMessage);
+ return error.addIssue(error$1, `bigint <= ${self.definition.max}n`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
return value;
});
diff --git a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.mjs b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.mjs
index 5cadd5a4..525935e2 100644
--- a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/max.mjs
@@ -9,9 +9,9 @@ function checkerBigIntMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error, self) => {
+ }, (value, error, self, dataParser) => {
if (value > self.definition.max) {
- return addIssue(error, `bigint <= ${self.definition.max}n`, value, self.definition.errorMessage);
+ return addIssue(error, `bigint <= ${self.definition.max}n`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
return value;
});
diff --git a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.cjs b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.cjs
index f27213b5..f2dddd22 100644
--- a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.cjs
@@ -11,9 +11,9 @@ function checkerBigIntMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error$1, self) => {
+ }, (value, error$1, self, dataParser) => {
if (value < self.definition.min) {
- return error.addIssue(error$1, `bigint >= ${self.definition.min}n`, value, self.definition.errorMessage);
+ return error.addIssue(error$1, `bigint >= ${self.definition.min}n`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
return value;
});
diff --git a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.mjs b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.mjs
index 51b816d8..3a3a7a44 100644
--- a/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/bigint/checkers/min.mjs
@@ -9,9 +9,9 @@ function checkerBigIntMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error, self) => {
+ }, (value, error, self, dataParser) => {
if (value < self.definition.min) {
- return addIssue(error, `bigint >= ${self.definition.min}n`, value, self.definition.errorMessage);
+ return addIssue(error, `bigint >= ${self.definition.min}n`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
return value;
});
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/int.cjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/int.cjs
index 5fa131ca..c832b55c 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/int.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/int.cjs
@@ -9,11 +9,11 @@ const checkerIntKind = kind.createDataParserKind("checker-number-int");
function checkerInt(definition = {}) {
return base.dataParserCheckerInit(checkerIntKind, {
definition,
- }, (data, error$1, self) => {
+ }, (data, error$1, self, dataParser) => {
if (Number.isInteger(data)) {
return data;
}
- return error.addIssue(error$1, "integer", data, self.definition.errorMessage);
+ return error.addIssue(error$1, "integer", data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
});
}
function int(definition) {
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/int.mjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/int.mjs
index 2c7abaef..08a776a5 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/int.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/int.mjs
@@ -7,11 +7,11 @@ const checkerIntKind = createDataParserKind("checker-number-int");
function checkerInt(definition = {}) {
return dataParserCheckerInit(checkerIntKind, {
definition,
- }, (data, error, self) => {
+ }, (data, error, self, dataParser) => {
if (Number.isInteger(data)) {
return data;
}
- return addIssue(error, "integer", data, self.definition.errorMessage);
+ return addIssue(error, "integer", data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
});
}
function int(definition) {
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/max.cjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/max.cjs
index 7d2455aa..fef70252 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/max.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/max.cjs
@@ -11,9 +11,9 @@ function checkerNumberMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error$1, self) => value <= self.definition.max
+ }, (value, error$1, self, dataParser) => value <= self.definition.max
? value
- : error.addIssue(error$1, `number <= ${self.definition.max}`, value, self.definition.errorMessage));
+ : error.addIssue(error$1, `number <= ${self.definition.max}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerNumberMax = checkerNumberMax;
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/max.mjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/max.mjs
index 6e908a90..67ee0bfd 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/max.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/max.mjs
@@ -9,9 +9,9 @@ function checkerNumberMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error, self) => value <= self.definition.max
+ }, (value, error, self, dataParser) => value <= self.definition.max
? value
- : addIssue(error, `number <= ${self.definition.max}`, value, self.definition.errorMessage));
+ : addIssue(error, `number <= ${self.definition.max}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerNumberMax, checkerNumberMaxKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/min.cjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/min.cjs
index 59058c68..c160ffa8 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/min.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/min.cjs
@@ -11,9 +11,9 @@ function checkerNumberMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error$1, self) => value >= self.definition.min
+ }, (value, error$1, self, dataParser) => value >= self.definition.min
? value
- : error.addIssue(error$1, `number >= ${self.definition.min}`, value, self.definition.errorMessage));
+ : error.addIssue(error$1, `number >= ${self.definition.min}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerNumberMin = checkerNumberMin;
diff --git a/docs/public/libs/v1/dataParser/parsers/number/checkers/min.mjs b/docs/public/libs/v1/dataParser/parsers/number/checkers/min.mjs
index 11138be6..330ab008 100644
--- a/docs/public/libs/v1/dataParser/parsers/number/checkers/min.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/number/checkers/min.mjs
@@ -9,9 +9,9 @@ function checkerNumberMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error, self) => value >= self.definition.min
+ }, (value, error, self, dataParser) => value >= self.definition.min
? value
- : addIssue(error, `number >= ${self.definition.min}`, value, self.definition.errorMessage));
+ : addIssue(error, `number >= ${self.definition.min}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerNumberMin, checkerNumberMinKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/refine.cjs b/docs/public/libs/v1/dataParser/parsers/refine.cjs
index 4bf0161d..4adc336d 100644
--- a/docs/public/libs/v1/dataParser/parsers/refine.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/refine.cjs
@@ -11,9 +11,9 @@ function checkerRefine(theFunction, definition) {
...definition,
theFunction,
},
- }, (value, error$1, self) => self.definition.theFunction(value)
+ }, (value, error$1, self, dataParser) => self.definition.theFunction(value)
? value
- : error.addIssue(error$1, "value matching refine predicate", value, self.definition.errorMessage));
+ : error.addIssue(error$1, "value matching refine predicate", value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerRefine = checkerRefine;
diff --git a/docs/public/libs/v1/dataParser/parsers/refine.mjs b/docs/public/libs/v1/dataParser/parsers/refine.mjs
index ecdc64ec..9df26766 100644
--- a/docs/public/libs/v1/dataParser/parsers/refine.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/refine.mjs
@@ -9,9 +9,9 @@ function checkerRefine(theFunction, definition) {
...definition,
theFunction,
},
- }, (value, error, self) => self.definition.theFunction(value)
+ }, (value, error, self, dataParser) => self.definition.theFunction(value)
? value
- : addIssue(error, "value matching refine predicate", value, self.definition.errorMessage));
+ : addIssue(error, "value matching refine predicate", value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerRefine, dataParserCheckerRefineKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/email.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/email.cjs
index abfeec3e..95a1cafc 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/email.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/email.cjs
@@ -13,9 +13,9 @@ function checkerEmail(definition = {}) {
...definition,
regex: emailRegex,
},
- }, (data, error$1, self) => self.definition.regex.test(data)
+ }, (data, error$1, self, dataParser) => self.definition.regex.test(data)
? data
- : error.addIssue(error$1, "email", data, self.definition.errorMessage));
+ : error.addIssue(error$1, "email", data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
function email(definition) {
return index.string({
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/email.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/email.mjs
index 3b6a3cab..026c2c86 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/email.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/email.mjs
@@ -11,9 +11,9 @@ function checkerEmail(definition = {}) {
...definition,
regex: emailRegex,
},
- }, (data, error, self) => self.definition.regex.test(data)
+ }, (data, error, self, dataParser) => self.definition.regex.test(data)
? data
- : addIssue(error, "email", data, self.definition.errorMessage));
+ : addIssue(error, "email", data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
function email(definition) {
return string({
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/max.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/max.cjs
index aa59dac1..947e57da 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/max.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/max.cjs
@@ -11,9 +11,9 @@ function checkerStringMax(max, definition = {}) {
...definition,
max,
},
- }, (data, error$1, self) => data.length <= self.definition.max
+ }, (data, error$1, self, dataParser) => data.length <= self.definition.max
? data
- : error.addIssue(error$1, `string.length <= ${self.definition.max}`, data, self.definition.errorMessage));
+ : error.addIssue(error$1, `string.length <= ${self.definition.max}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerStringMax = checkerStringMax;
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/max.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/max.mjs
index c42e8992..80833edc 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/max.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/max.mjs
@@ -9,9 +9,9 @@ function checkerStringMax(max, definition = {}) {
...definition,
max,
},
- }, (data, error, self) => data.length <= self.definition.max
+ }, (data, error, self, dataParser) => data.length <= self.definition.max
? data
- : addIssue(error, `string.length <= ${self.definition.max}`, data, self.definition.errorMessage));
+ : addIssue(error, `string.length <= ${self.definition.max}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerStringMax, checkerStringMaxKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/min.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/min.cjs
index 46fb6460..0aceb3af 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/min.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/min.cjs
@@ -11,9 +11,9 @@ function checkerStringMin(min, definition = {}) {
...definition,
min,
},
- }, (data, error$1, self) => data.length >= self.definition.min
+ }, (data, error$1, self, dataParser) => data.length >= self.definition.min
? data
- : error.addIssue(error$1, `string.length >= ${self.definition.min}`, data, self.definition.errorMessage));
+ : error.addIssue(error$1, `string.length >= ${self.definition.min}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerStringMin = checkerStringMin;
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/min.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/min.mjs
index 2c31566a..0749d348 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/min.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/min.mjs
@@ -9,9 +9,9 @@ function checkerStringMin(min, definition = {}) {
...definition,
min,
},
- }, (data, error, self) => data.length >= self.definition.min
+ }, (data, error, self, dataParser) => data.length >= self.definition.min
? data
- : addIssue(error, `string.length >= ${self.definition.min}`, data, self.definition.errorMessage));
+ : addIssue(error, `string.length >= ${self.definition.min}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerStringMin, checkerStringMinKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.cjs
index 8ca92aef..129282c7 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.cjs
@@ -11,9 +11,9 @@ function checkerRegex(regex, definition = {}) {
...definition,
regex,
},
- }, (data, error$1, self) => self.definition.regex.test(data)
+ }, (data, error$1, self, dataParser) => self.definition.regex.test(data)
? data
- : error.addIssue(error$1, `string with pattern ${self.definition.regex.source.toString()}`, data, self.definition.errorMessage));
+ : error.addIssue(error$1, `string with pattern ${self.definition.regex.source.toString()}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerRegex = checkerRegex;
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.mjs
index e6fb9cfa..eb22bc62 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/regex.mjs
@@ -9,9 +9,9 @@ function checkerRegex(regex, definition = {}) {
...definition,
regex,
},
- }, (data, error, self) => self.definition.regex.test(data)
+ }, (data, error, self, dataParser) => self.definition.regex.test(data)
? data
- : addIssue(error, `string with pattern ${self.definition.regex.source.toString()}`, data, self.definition.errorMessage));
+ : addIssue(error, `string with pattern ${self.definition.regex.source.toString()}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerRegex, checkerRegexKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/url.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/url.cjs
index b78c361e..1454c3a3 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/url.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/url.cjs
@@ -10,19 +10,19 @@ const regexRemoveDote = /:$/;
function checkerUrl(definition = {}) {
return base.dataParserCheckerInit(checkerUrlKind, {
definition: definition,
- }, (data, error$1, self) => {
+ }, (data, error$1, self, dataParser) => {
try {
const url = new URL(data);
if (self.definition.hostname) {
self.definition.hostname.lastIndex = 0;
if (!self.definition.hostname.test(url.hostname)) {
- return error.addIssue(error$1, `URL with hostname matching ${self.definition.hostname.source}`, data, self.definition.errorMessage);
+ return error.addIssue(error$1, `URL with hostname matching ${self.definition.hostname.source}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
}
if (self.definition.protocol) {
self.definition.protocol.lastIndex = 0;
if (!self.definition.protocol.test(url.protocol.replace(regexRemoveDote, ""))) {
- return error.addIssue(error$1, `URL with protocol matching ${self.definition.protocol.source}`, data, self.definition.errorMessage);
+ return error.addIssue(error$1, `URL with protocol matching ${self.definition.protocol.source}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
}
if (self.definition.normalize) {
@@ -33,7 +33,7 @@ function checkerUrl(definition = {}) {
}
}
catch {
- return error.addIssue(error$1, "valid URL", data, self.definition.errorMessage);
+ return error.addIssue(error$1, "valid URL", data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
});
}
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/url.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/url.mjs
index 4f2d4b73..c0c1b187 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/url.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/url.mjs
@@ -8,19 +8,19 @@ const regexRemoveDote = /:$/;
function checkerUrl(definition = {}) {
return dataParserCheckerInit(checkerUrlKind, {
definition: definition,
- }, (data, error, self) => {
+ }, (data, error, self, dataParser) => {
try {
const url = new URL(data);
if (self.definition.hostname) {
self.definition.hostname.lastIndex = 0;
if (!self.definition.hostname.test(url.hostname)) {
- return addIssue(error, `URL with hostname matching ${self.definition.hostname.source}`, data, self.definition.errorMessage);
+ return addIssue(error, `URL with hostname matching ${self.definition.hostname.source}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
}
if (self.definition.protocol) {
self.definition.protocol.lastIndex = 0;
if (!self.definition.protocol.test(url.protocol.replace(regexRemoveDote, ""))) {
- return addIssue(error, `URL with protocol matching ${self.definition.protocol.source}`, data, self.definition.errorMessage);
+ return addIssue(error, `URL with protocol matching ${self.definition.protocol.source}`, data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
}
if (self.definition.normalize) {
@@ -31,7 +31,7 @@ function checkerUrl(definition = {}) {
}
}
catch {
- return addIssue(error, "valid URL", data, self.definition.errorMessage);
+ return addIssue(error, "valid URL", data, self.definition.errorMessage ?? dataParser.definition.errorMessage);
}
});
}
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.cjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.cjs
index a3d89d03..a7995ec8 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.cjs
@@ -13,9 +13,9 @@ function checkerUuid(definition = {}) {
...definition,
regex: uuidRegex,
},
- }, (data, error$1, self) => uuidRegex.test(data)
+ }, (data, error$1, self, dataParser) => uuidRegex.test(data)
? data
- : error.addIssue(error$1, "uuid", data, self.definition.errorMessage));
+ : error.addIssue(error$1, "uuid", data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
/**
* {@include dataParser/classic/uuid/index.md}
diff --git a/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.mjs b/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.mjs
index e2db8be4..363698b7 100644
--- a/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/string/checkers/uuid.mjs
@@ -11,9 +11,9 @@ function checkerUuid(definition = {}) {
...definition,
regex: uuidRegex,
},
- }, (data, error, self) => uuidRegex.test(data)
+ }, (data, error, self, dataParser) => uuidRegex.test(data)
? data
- : addIssue(error, "uuid", data, self.definition.errorMessage));
+ : addIssue(error, "uuid", data, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
/**
* {@include dataParser/classic/uuid/index.md}
diff --git a/docs/public/libs/v1/dataParser/parsers/time/checkers/max.cjs b/docs/public/libs/v1/dataParser/parsers/time/checkers/max.cjs
index 3b97bf6d..cdbc7341 100644
--- a/docs/public/libs/v1/dataParser/parsers/time/checkers/max.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/time/checkers/max.cjs
@@ -15,9 +15,9 @@ function checkerTimeMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error$1, self) => lessTime.lessTime(value, self.definition.max)
+ }, (value, error$1, self, dataParser) => lessTime.lessTime(value, self.definition.max)
? value
- : error.addIssue(error$1, `time <= ${self.definition.max.toString()}`, value, self.definition.errorMessage));
+ : error.addIssue(error$1, `time <= ${self.definition.max.toString()}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerTimeMax = checkerTimeMax;
diff --git a/docs/public/libs/v1/dataParser/parsers/time/checkers/max.mjs b/docs/public/libs/v1/dataParser/parsers/time/checkers/max.mjs
index 4b4416b7..e921578e 100644
--- a/docs/public/libs/v1/dataParser/parsers/time/checkers/max.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/time/checkers/max.mjs
@@ -13,9 +13,9 @@ function checkerTimeMax(max, definition = {}) {
...definition,
max,
},
- }, (value, error, self) => lessTime(value, self.definition.max)
+ }, (value, error, self, dataParser) => lessTime(value, self.definition.max)
? value
- : addIssue(error, `time <= ${self.definition.max.toString()}`, value, self.definition.errorMessage));
+ : addIssue(error, `time <= ${self.definition.max.toString()}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerTimeMax, checkerTimeMaxKind };
diff --git a/docs/public/libs/v1/dataParser/parsers/time/checkers/min.cjs b/docs/public/libs/v1/dataParser/parsers/time/checkers/min.cjs
index 3e548c47..883755e0 100644
--- a/docs/public/libs/v1/dataParser/parsers/time/checkers/min.cjs
+++ b/docs/public/libs/v1/dataParser/parsers/time/checkers/min.cjs
@@ -15,9 +15,9 @@ function checkerTimeMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error$1, self) => greaterTime.greaterTime(value, self.definition.min)
+ }, (value, error$1, self, dataParser) => greaterTime.greaterTime(value, self.definition.min)
? value
- : error.addIssue(error$1, `time >= ${self.definition.min.toString()}`, value, self.definition.errorMessage));
+ : error.addIssue(error$1, `time >= ${self.definition.min.toString()}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
exports.checkerTimeMin = checkerTimeMin;
diff --git a/docs/public/libs/v1/dataParser/parsers/time/checkers/min.mjs b/docs/public/libs/v1/dataParser/parsers/time/checkers/min.mjs
index 71d0f7d2..a1a60335 100644
--- a/docs/public/libs/v1/dataParser/parsers/time/checkers/min.mjs
+++ b/docs/public/libs/v1/dataParser/parsers/time/checkers/min.mjs
@@ -13,9 +13,9 @@ function checkerTimeMin(min, definition = {}) {
...definition,
min,
},
- }, (value, error, self) => greaterTime(value, self.definition.min)
+ }, (value, error, self, dataParser) => greaterTime(value, self.definition.min)
? value
- : addIssue(error, `time >= ${self.definition.min.toString()}`, value, self.definition.errorMessage));
+ : addIssue(error, `time >= ${self.definition.min.toString()}`, value, self.definition.errorMessage ?? dataParser.definition.errorMessage));
}
export { checkerTimeMin, checkerTimeMinKind };
diff --git a/docs/public/libs/v1/metadata.json b/docs/public/libs/v1/metadata.json
index dcde8a5f..3f0382e0 100644
--- a/docs/public/libs/v1/metadata.json
+++ b/docs/public/libs/v1/metadata.json
@@ -993,6 +993,15 @@
}
]
},
+ {
+ "name": "chainedFunction.cjs"
+ },
+ {
+ "name": "chainedFunction.d.ts"
+ },
+ {
+ "name": "chainedFunction.mjs"
+ },
{
"name": "flag.cjs"
},
@@ -1110,7 +1119,7 @@
"name": "deepPartial.d.ts"
},
{
- "name": "DeepReadonly.d.ts"
+ "name": "deepReadonly.d.ts"
},
{
"name": "deepRemoveReadonly.d.ts"
@@ -1157,6 +1166,9 @@
{
"name": "maybeArray.d.ts"
},
+ {
+ "name": "maybeAsyncGenerator.d.ts"
+ },
{
"name": "maybeGetter.d.ts"
},
diff --git a/jsDoc/clean/chainedFunction/example.ts b/jsDoc/clean/chainedFunction/example.ts
new file mode 100644
index 00000000..4eee1265
--- /dev/null
+++ b/jsDoc/clean/chainedFunction/example.ts
@@ -0,0 +1,145 @@
+import { C, E, type ExpectType } from "@scripts";
+
+interface CommentDraft {
+ articleId: number;
+ content: string;
+}
+
+interface Comment {
+ id: number;
+ articleId: number;
+ content: string;
+}
+
+interface Article {
+ id: number;
+ commentCount: number;
+}
+
+interface CommentRepository {
+ save(comment: Comment): Comment | E.Fail;
+}
+
+interface ArticleRepository {
+ findById(articleId: number): Article | E.Fail;
+ save(article: Article): Article | E.Fail;
+}
+
+const CommentRepository = C.createRepository();
+const ArticleRepository = C.createRepository();
+
+const CommentPublicationAggregate = C.chainedFunction(
+ [
+ "createComment",
+ (draft: CommentDraft): Comment | E.Fail => draft.content.trim()
+ ? {
+ id: 1,
+ articleId: draft.articleId,
+ content: draft.content.trim(),
+ }
+ : E.fail(),
+ ],
+ [
+ "incrementArticleCommentCount",
+ (article: Article): Article => ({
+ ...article,
+ commentCount: article.commentCount + 1,
+ }),
+ ],
+);
+
+const PublishCommentUseCase = C.createUseCase(
+ {
+ CommentRepository,
+ ArticleRepository,
+ },
+ ({
+ commentRepository,
+ articleRepository,
+ }) => (draft: CommentDraft) => CommentPublicationAggregate(function *(link1, { breakIfLeft }) {
+ const [comment, link2] = yield *link1(({ createComment }) => createComment(draft));
+
+ const savedComment = yield *breakIfLeft(commentRepository.save(comment));
+
+ const article = yield *breakIfLeft(articleRepository.findById(savedComment.articleId));
+
+ const [updatedArticle, chainEnd] = yield *link2(
+ ({ incrementArticleCommentCount }) => incrementArticleCommentCount(article),
+ );
+
+ const savedArticle = yield *breakIfLeft(articleRepository.save(updatedArticle));
+
+ return chainEnd({
+ comment: savedComment,
+ article: savedArticle,
+ });
+ }),
+);
+
+const publishComment = PublishCommentUseCase.getUseCase({
+ commentRepository: CommentRepository.createImplementation({
+ save: (comment) => comment,
+ }),
+ articleRepository: ArticleRepository.createImplementation({
+ findById: (articleId) => ({
+ id: articleId,
+ commentCount: 11,
+ }),
+ save: (article) => article,
+ }),
+});
+
+const publishedComment = publishComment({
+ articleId: 12,
+ content: " New comment ",
+});
+
+type CheckPublishedComment = ExpectType<
+ typeof publishedComment,
+ {
+ comment: Comment;
+ article: Article;
+ } | E.Fail,
+ "strict"
+>;
+
+const emptyContentResult = publishComment({
+ articleId: 12,
+ content: " ",
+});
+
+type CheckEmptyContentResult = ExpectType<
+ typeof emptyContentResult,
+ {
+ comment: Comment;
+ article: Article;
+ } | E.Fail,
+ "strict"
+>;
+
+const failingPublishComment = PublishCommentUseCase.getUseCase({
+ commentRepository: CommentRepository.createImplementation({
+ save: () => E.fail(),
+ }),
+ articleRepository: ArticleRepository.createImplementation({
+ findById: (articleId) => ({
+ id: articleId,
+ commentCount: 11,
+ }),
+ save: (article) => article,
+ }),
+});
+
+const repositoryFailureResult = failingPublishComment({
+ articleId: 12,
+ content: "New comment",
+});
+
+type CheckRepositoryFailureResult = ExpectType<
+ typeof repositoryFailureResult,
+ {
+ comment: Comment;
+ article: Article;
+ } | E.Fail,
+ "strict"
+>;
diff --git a/jsDoc/clean/chainedFunction/index.md b/jsDoc/clean/chainedFunction/index.md
new file mode 100644
index 00000000..0e3619db
--- /dev/null
+++ b/jsDoc/clean/chainedFunction/index.md
@@ -0,0 +1,19 @@
+Declares a typed aggregate of pure linked business actions that must run in order.
+
+**Supported call styles:**
+- Classic: `chainedFunction(firstFunction, secondFunction, ...functions)` -> returns an implementation function driven by generator links
+
+Use it inside a Clean Architecture use case when several pure domain operations that update different entities must belong to the same business consistency boundary. Each link exposes exactly one named action, yields `Left` values to short-circuit the implementation, and provides the next link until the last step returns `chainEnd(value)`. Repository calls stay in the use case through the library repository system; functions passed to `chainedFunction` remain pure domain functions.
+
+```ts
+{@include clean/chainedFunction/example.ts[31,145]}
+```
+
+@remarks `chainedFunction` expects at least two functions in the chain. It does not catch thrown exceptions or rejected promises; model handled business errors with `Either.Left`.
+The callback receives `(firstLink, { breakIfLeft })`. `breakIfLeft` is synchronous and narrows `value | Left` to `value`, yielding the `Left` branch to short-circuit when needed.
+
+@see https://utils.duplojs.dev/en/v1/api/clean/chainedFunction
+@see [`C.createUseCase`](https://utils.duplojs.dev/en/v1/api/clean/useCase)
+@see [`E.Left`](https://utils.duplojs.dev/en/v1/api/either/left)
+
+@namespace C
diff --git a/scripts/clean/chainedFunction.ts b/scripts/clean/chainedFunction.ts
new file mode 100644
index 00000000..9c6ee863
--- /dev/null
+++ b/scripts/clean/chainedFunction.ts
@@ -0,0 +1,247 @@
+import { type AnyFunction, type Kind, type IsEqual, type MaybePromise, type MaybeAsyncGenerator, type GetKindValue } from "@scripts/common";
+import * as EE from "@scripts/either";
+import { createCleanKind } from "./kind";
+
+export type FunctionOfChain = [string, AnyFunction];
+
+export type FunctionChain = [
+ FunctionOfChain,
+ FunctionOfChain,
+ ...FunctionOfChain[],
+];
+
+export const chainEndKind = createCleanKind("chain-end");
+
+export interface ChainEnd<
+ GenericValue extends unknown = unknown,
+> extends Kind {
+
+}
+
+export interface CreateChainEnd {
+ (): ChainEnd;
+ <
+ GenericValue extends unknown,
+ >(
+ value: GenericValue
+ ): ChainEnd;
+}
+
+export type Link<
+ GenericFunction extends FunctionOfChain = FunctionOfChain,
+ GenericNext extends (Link | CreateChainEnd) = any,
+> = <
+ GenericOutput extends ReturnType,
+>(
+ theFunction: (
+ theFunction: { [Prop in GenericFunction[0]]: GenericFunction[1] }
+ ) => GenericOutput
+) => (
+ | (
+ Extract> extends infer InferredPromise
+ ? IsEqual extends true
+ ? never
+ : Awaited extends infer InferredValue extends unknown
+ ? AsyncGenerator<
+ Extract,
+ [Exclude, GenericNext]
+ >
+ : never
+ : never
+ )
+ | (
+ Exclude> extends infer InferredValue
+ ? IsEqual extends true
+ ? never
+ : Generator<
+ Extract,
+ [Exclude, GenericNext]
+ >
+ : never
+ )
+);
+
+export type Chain<
+ GenericFunctionChain extends readonly FunctionOfChain[],
+> = GenericFunctionChain extends readonly []
+ ? CreateChainEnd
+ : GenericFunctionChain extends [
+ infer InferredFirst extends FunctionOfChain,
+ ...infer InferredRest extends readonly FunctionOfChain[],
+ ]
+ ? Chain extends infer InferredRestResult extends (Link | CreateChainEnd)
+ ? Link<
+ InferredFirst,
+ InferredRestResult
+ >
+ : never
+ : never;
+
+declare const SymbolError: unique symbol;
+
+type OutputMustContainChainEnd<
+ GenericGenerator extends MaybeAsyncGenerator,
+> = IsEqual<
+ GenericGenerator extends MaybeAsyncGenerator
+ ? InferredReturnValue extends ChainEnd
+ ? InferredReturnValue
+ : never
+ : never,
+ never
+> extends true
+ ? { [SymbolError]: "Output must contain a chainEnd" }
+ : unknown;
+
+type ComputeResult<
+ GenericGenerator extends MaybeAsyncGenerator,
+> = GenericGenerator extends Generator<
+ infer InferredIterateValue,
+ infer InferredReturnValue
+>
+ ? (
+ | InferredIterateValue
+ | InferredReturnValue
+ ) extends infer InferredResult
+ ? InferredResult extends ChainEnd
+ ? GetKindValue
+ : InferredResult
+ : never
+ : GenericGenerator extends AsyncGenerator<
+ infer InferredIterateValue,
+ infer InferredReturnValue
+ >
+ ? Promise<
+ Awaited<
+ | InferredIterateValue
+ | InferredReturnValue
+ > extends infer InferredResult
+ ? InferredResult extends ChainEnd
+ ? GetKindValue
+ : InferredResult
+ : never
+ >
+ : never;
+
+function *breakIfLeft<
+ GenericValue extends unknown,
+>(
+ value: GenericValue,
+): Generator<
+ Extract,
+ Exclude
+ > {
+ if (EE.isLeft(value)) {
+ yield value;
+ }
+
+ return value as never;
+}
+
+export interface ChainedFunctionParams {
+ breakIfLeft: typeof breakIfLeft;
+}
+
+const chainedFunctionParams: ChainedFunctionParams = { breakIfLeft };
+
+export type ChainedFunction<
+ GenericValue extends FunctionChain = FunctionChain,
+> = <
+ GenericGenerator extends MaybeAsyncGenerator<
+ MaybePromise,
+ MaybePromise
+ >,
+>(
+ callback: (firstLink: Chain, params: ChainedFunctionParams) => (
+ & GenericGenerator
+ & OutputMustContainChainEnd<
+ GenericGenerator
+ >
+ )
+) => ComputeResult;
+
+/**
+ * {@include clean/chainedFunction/index.md}
+ */
+export function chainedFunction<
+ const GenericFunction1 extends FunctionOfChain,
+ const GenericFunction2 extends FunctionOfChain,
+ const GenericFunctions extends FunctionOfChain[],
+>(
+ function1: GenericFunction1,
+ function2: GenericFunction2,
+ ...functions: GenericFunctions
+): ChainedFunction<[
+ GenericFunction1,
+ GenericFunction2,
+ ...GenericFunctions,
+ ]> {
+ return (theFunction) => {
+ const functionChain: FunctionChain = [function1, function2, ...functions];
+
+ const createLink = (
+ functionChain: FunctionChain,
+ ): Link => (theFunction) => {
+ const [functionName, chainedFunction] = functionChain.shift()!;
+ const result = theFunction({ [functionName]: chainedFunction });
+
+ const nextLink = functionChain.length === 0
+ ? (value: unknown) => chainEndKind.setTo({}, value)
+ : createLink(functionChain);
+
+ if ((result as any) instanceof Promise) {
+ return (async function *() {
+ const awaitedResult = await result;
+
+ if (EE.isLeft(awaitedResult)) {
+ yield awaitedResult;
+ }
+
+ return [awaitedResult, nextLink];
+ })() as never;
+ }
+
+ return (function *() {
+ if (EE.isLeft(result)) {
+ yield result;
+ }
+
+ return [result, nextLink];
+ })() as never;
+ };
+
+ const generator = theFunction(
+ createLink(functionChain) as never,
+ chainedFunctionParams,
+ );
+
+ let result: undefined | IteratorResult, unknown> = undefined;
+
+ if (Symbol.asyncIterator in generator) {
+ return (async() => {
+ try {
+ result = await generator.next();
+ } finally {
+ await generator.return(undefined as never);
+ }
+
+ return (
+ chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value
+ );
+ })() as never;
+ }
+
+ try {
+ result = generator.next();
+ } finally {
+ generator.return(undefined as never);
+ }
+
+ return (
+ chainEndKind.has(result.value)
+ ? chainEndKind.getValue(result.value)
+ : result.value
+ ) as never;
+ };
+}
diff --git a/scripts/clean/index.ts b/scripts/clean/index.ts
index 858a4fbd..2382904b 100644
--- a/scripts/clean/index.ts
+++ b/scripts/clean/index.ts
@@ -13,3 +13,4 @@ export * from "./useCase";
export * from "./flag";
export * from "./maybe";
export * from "./toMapDataParser";
+export * from "./chainedFunction";
diff --git a/scripts/common/types/DeepReadonly.ts b/scripts/common/types/deepReadonly.ts
similarity index 100%
rename from scripts/common/types/DeepReadonly.ts
rename to scripts/common/types/deepReadonly.ts
diff --git a/scripts/common/types/index.ts b/scripts/common/types/index.ts
index d15eea86..1f6071dc 100644
--- a/scripts/common/types/index.ts
+++ b/scripts/common/types/index.ts
@@ -43,7 +43,8 @@ export * from "./onlyLiteral";
export * from "./sortType";
export * from "./maybeGetter";
export * from "./falsyValue";
-export * from "./DeepReadonly";
+export * from "./deepReadonly";
export * from "./json";
export * from "./predicate";
export * from "./bivariantFunction";
+export * from "./maybeAsyncGenerator";
diff --git a/scripts/common/types/maybeAsyncGenerator.ts b/scripts/common/types/maybeAsyncGenerator.ts
new file mode 100644
index 00000000..89e0337b
--- /dev/null
+++ b/scripts/common/types/maybeAsyncGenerator.ts
@@ -0,0 +1,16 @@
+export type MaybeAsyncGenerator<
+ GenericIterateValue extends unknown = unknown,
+ GenericReturnValue extends unknown = unknown,
+ GenericNext extends unknown = unknown,
+> = (
+ | Generator<
+ GenericIterateValue,
+ GenericReturnValue,
+ GenericNext
+ >
+ | AsyncGenerator<
+ GenericIterateValue,
+ GenericReturnValue,
+ GenericNext
+ >
+);
diff --git a/tests/clean/chainedFunction.test.ts b/tests/clean/chainedFunction.test.ts
new file mode 100644
index 00000000..77411a1c
--- /dev/null
+++ b/tests/clean/chainedFunction.test.ts
@@ -0,0 +1,239 @@
+import { DClean, DEither, type ExpectType } from "@scripts";
+
+describe("chainedFunction", () => {
+ it("executes links in declaration order and returns the chain end value", () => {
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", (input: string) => input.trim()],
+ ["createSlug", (input: string) => input.toLowerCase().replaceAll(" ", "-")],
+ ["persistSlug", (input: string) => input.length],
+ );
+
+ const calls: string[] = [];
+
+ const result = useCase(function *(link1) {
+ const [title, link2] = yield *link1(({ parseTitle }) => {
+ calls.push("parseTitle");
+ return parseTitle(" Hello World ");
+ });
+
+ const [slug, link3] = yield *link2(({ createSlug }) => {
+ calls.push("createSlug");
+ return createSlug(title);
+ });
+
+ const [slugLength, chainEnd] = yield *link3(({ persistSlug }) => {
+ calls.push("persistSlug");
+ return persistSlug(slug);
+ });
+
+ return chainEnd(slugLength);
+ });
+
+ expect(calls).toStrictEqual(["parseTitle", "createSlug", "persistSlug"]);
+ expect(result).toBe(11);
+
+ type Check = ExpectType<
+ typeof result,
+ number,
+ "strict"
+ >;
+ });
+
+ it("short-circuits on a synchronous left and does not execute following links", () => {
+ const error = DEither.error("invalid-title");
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", () => error],
+ ["persistTitle", (input: string) => input.length],
+ );
+
+ const persistTitleSpy = vi.fn((input: string) => input.length);
+
+ const result = useCase(function *(link1) {
+ const [title, link2] = yield *link1(({ parseTitle }) => parseTitle());
+ const [length, chainEnd] = yield *link2(() => persistTitleSpy(title));
+
+ return chainEnd(length);
+ });
+
+ expect(result).toBe(error);
+ expect(persistTitleSpy).not.toHaveBeenCalled();
+
+ type Check = ExpectType<
+ typeof result,
+ DEither.Error<"invalid-title"> | number,
+ "strict"
+ >;
+ });
+
+ it("returns the left produced by a chained function call after previous links succeeded", () => {
+ const error = DEither.error({ reason: "persist-failed" });
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", (input: string) => input.trim()],
+ ["persistTitle", (input: string) => input.length > 0 ? error : input.length],
+ );
+
+ const result = useCase(function *(link1) {
+ const [title, link2] = yield *link1(({ parseTitle }) => parseTitle("hello"));
+ const [length, chainEnd] = yield *link2(({ persistTitle }) => persistTitle(title));
+
+ return chainEnd(length);
+ });
+
+ expect(result).toBe(error);
+
+ type Check = ExpectType<
+ typeof result,
+ DEither.Error<{ readonly reason: "persist-failed" }> | number,
+ "strict"
+ >;
+ });
+
+ it("returns the left produced directly by the implementation", () => {
+ const error = DEither.error({ reason: "implementation-failed" });
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", (input: string) => input.trim()],
+ ["persistTitle", (input: string) => input.length],
+ );
+
+ const result = useCase(function *(link1) {
+ const [title, link2] = yield *link1(({ parseTitle }) => parseTitle("hello"));
+ const [length, chainEnd] = yield *link2(({ persistTitle }) => persistTitle(title));
+
+ return title.length > 0
+ ? error
+ : chainEnd(length);
+ });
+
+ expect(result).toBe(error);
+
+ type Check = ExpectType<
+ typeof result,
+ DEither.Error<{ readonly reason: "implementation-failed" }> | number,
+ "strict"
+ >;
+ });
+
+ it("breakIfLeft returns a synchronous value without its left branch", () => {
+ const error = DEither.error("manual-left");
+ const value = "hello" as string | typeof error;
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", (input: string) => input.trim()],
+ ["persistTitle", (input: string) => input.length],
+ );
+
+ const result = useCase(function *(link1, { breakIfLeft }) {
+ const titleInput = yield *breakIfLeft(value);
+ const [title, link2] = yield *link1(({ parseTitle }) => parseTitle(titleInput));
+ const [length, chainEnd] = yield *link2(({ persistTitle }) => persistTitle(title));
+
+ return chainEnd(length);
+ });
+
+ expect(result).toBe(5);
+
+ type Check = ExpectType<
+ typeof result,
+ DEither.Error<"manual-left"> | number,
+ "strict"
+ >;
+ });
+
+ it("breakIfLeft short-circuits on a synchronous left", () => {
+ const error = DEither.error("manual-left");
+ const value = error as string | typeof error;
+ const useCase = DClean.chainedFunction(
+ ["parseTitle", (input: string) => input.trim()],
+ ["persistTitle", (input: string) => input.length],
+ );
+ const persistTitleSpy = vi.fn((input: string) => input.length);
+
+ const result = useCase(function *(link1, { breakIfLeft }) {
+ const titleInput = yield *breakIfLeft(value);
+ type check = ExpectType<
+ typeof titleInput,
+ string,
+ "strict"
+ >;
+ const [title, link2] = yield *link1(({ parseTitle }) => parseTitle(titleInput));
+ const [length, chainEnd] = yield *link2(() => persistTitleSpy(title));
+
+ return chainEnd(length);
+ });
+
+ expect(result).toBe(error);
+ expect(persistTitleSpy).not.toHaveBeenCalled();
+
+ type Check = ExpectType<
+ typeof result,
+ DEither.Error<"manual-left"> | number,
+ "strict"
+ >;
+ });
+
+ it("awaits asynchronous links and returns asynchronous chain end values", async() => {
+ const useCase = DClean.chainedFunction(
+ ["loadCount", async(input: number) => Promise.resolve(input + 1)],
+ ["saveCount", async(input: number) => Promise.resolve(input * 2)],
+ );
+
+ const result = useCase(async function *(link1) {
+ const [loadedCount, link2] = yield *link1(({ loadCount }) => loadCount(3));
+ const [savedCount, chainEnd] = yield *link2(({ saveCount }) => saveCount(loadedCount));
+
+ await Promise.resolve();
+
+ return chainEnd(savedCount);
+ });
+
+ await expect(result).resolves.toBe(8);
+
+ type Check = ExpectType<
+ typeof result,
+ Promise,
+ "strict"
+ >;
+ });
+
+ it("short-circuits on an asynchronous left before running the next link", async() => {
+ const error = DEither.error({ code: "load-failed" });
+ const useCase = DClean.chainedFunction(
+ ["loadCount", async() => Promise.resolve(error)],
+ ["saveCount", async(input: number) => Promise.resolve(input * 2)],
+ );
+
+ const saveCountSpy = vi.fn(async(input: number) => Promise.resolve(input * 2));
+
+ const result = useCase(async function *(link1) {
+ const [loadedCount, link2] = yield *link1(({ loadCount }) => loadCount());
+ const [savedCount, chainEnd] = yield *link2(() => saveCountSpy(loadedCount));
+
+ await Promise.resolve();
+
+ return chainEnd(savedCount);
+ });
+
+ await expect(result).resolves.toBe(error);
+ expect(saveCountSpy).not.toHaveBeenCalled();
+
+ type Check = ExpectType<
+ typeof result,
+ Promise | number>,
+ "strict"
+ >;
+ });
+
+ it("rejects callbacks that can finish without returning a chain end", () => {
+ const useCase = DClean.chainedFunction(
+ ["readValue", () => 1],
+ ["saveValue", (input: number) => input],
+ );
+
+ useCase(
+ //@ts-expect-error chainedFunction callbacks must return chainEnd on the success path
+ function *(link1) {
+ const [value, link2] = yield *link1(({ readValue }) => readValue());
+ yield *link2(({ saveValue }) => saveValue(value));
+ },
+ );
+ });
+});