@duplojs/agents-kit 0.1.0-beta.7 → 0.1.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,18 +14,26 @@ Read the relevant pages by resolving their links relative to the summary. Before
14
14
 
15
15
  Use each API domain's `index.llms.md` to find a public function by its exported namespace and name, such as `DArray.map` in [`docs/API/10-lang/10-array/index.llms.md`](docs/API/10-lang/10-array/index.llms.md). The other current domains are [Chrono](docs/API/10-lang/10-chrono/index.llms.md), [Common](docs/API/10-lang/10-common/index.llms.md), [DataStructure](docs/API/10-lang/10-dataStructure/index.llms.md), [Either](docs/API/10-lang/10-either/index.llms.md), and [Generator](docs/API/10-lang/10-generator/index.llms.md). API sources are primarily TypeScript files combining descriptions and typed usage examples. Each summary exposes their headers and links to those files.
16
16
 
17
- The `install-skill` command creates symbolic links to the package's skills by default.
17
+ The `skills install` command creates symbolic links to the package's skills by default.
18
18
  The boolean `--copy` option copies the skills instead.
19
19
  Each installation removes the skill's destination and replaces it with the requested copy or link. The target of any existing symbolic link is preserved.
20
20
 
21
21
  ```sh
22
- pnpm exec duplojs-agents-kit install-skill
23
- pnpm exec duplojs-agents-kit install-skill http-server --type codex
24
- pnpm exec duplojs-agents-kit install-skill http-client --copy
25
- pnpm exec duplojs-agents-kit install-skill command --type codex
26
- pnpm exec duplojs-agents-kit install-skill server --type codex
27
- pnpm exec duplojs-agents-kit install-skill modeling --type codex
28
- pnpm exec duplojs-agents-kit install-skill test --type codex
22
+ pnpm exec duplojs-agents-kit skills install
23
+ pnpm exec duplojs-agents-kit skills install http-server --type codex
24
+ pnpm exec duplojs-agents-kit skills install http-client --copy
25
+ pnpm exec duplojs-agents-kit skills install command --type codex
26
+ pnpm exec duplojs-agents-kit skills install server --type codex
27
+ pnpm exec duplojs-agents-kit skills install modeling --type codex
28
+ pnpm exec duplojs-agents-kit skills install test --type codex
29
+ ```
30
+
31
+ Run `skills update` to update DuploJS skills already installed in the selected agent folder. Symbolic links are recreated, including broken links, and copied directories are replaced with a fresh copy, removing obsolete files. Each skill keeps its installation mode. It only updates skills still available in the installed package. Unrelated entries and skills no longer available in the package are preserved; missing skills are not installed.
32
+
33
+ ```sh
34
+ pnpm exec duplojs-agents-kit skills update
35
+ pnpm exec duplojs-agents-kit skills update --type codex
36
+ pnpm exec duplojs-agents-kit skills update --type claude
29
37
  ```
30
38
 
31
39
  Two independently installable skills cover HTTP:
@@ -39,7 +47,7 @@ The `modeling` skill (`duplojs-modeling`) guides business system modeling and im
39
47
 
40
48
  The `test` skill (`duplojs-test`) guides testing DuploJS results and type contracts, selecting expected Either information, monitoring new result variants with exhaustive selectors, and isolating server dependencies through `TESTImplementation`. It also points to the concepts for organizing E2E tests with `@duplojs/playwright`.
41
49
 
42
- The `command` skill guides the creation of typed CLI commands with `@duplojs/server/command`, including arguments, options, subcommands, help, and executable entry points. Running `install-skill` without a skill installs all supported skills. References and checked TypeScript examples are included in both copied and linked installations.
50
+ The `command` skill guides the creation of typed CLI commands with `@duplojs/server/command`, including arguments, options, subcommands, help, and executable entry points. Running `skills install` without a skill installs all supported skills. References and checked TypeScript examples are included in both copied and linked installations.
43
51
 
44
52
  The `setup` command creates `AGENTS.md` by default, or `CLAUDE.md` with `--type claude`, in the current project if needed and synchronizes a marked coding conventions block while preserving the surrounding content:
45
53
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { installSkillCommand } from "./installSkill/index.mjs";
2
+ import { skillsCommand } from "./skills/index.mjs";
3
3
  import { setupCommand } from "./setup/index.mjs";
4
4
  import * as DSCommand from "@duplojs/server/command";
5
5
  //#region scripts/bin/index.ts
6
- await DSCommand.exec({ subjects: [installSkillCommand, setupCommand] }, () => {});
6
+ await DSCommand.exec({ subjects: [skillsCommand, setupCommand] }, () => {});
7
7
  //#endregion
@@ -0,0 +1,5 @@
1
+ import * as DSCommand from "@duplojs/server/command";
2
+ export * from './install';
3
+ export * from './update';
4
+ export * from './skill';
5
+ export declare const skillsCommand: DSCommand.Command<"skills">;
@@ -0,0 +1,11 @@
1
+ import { AgentTypeEnum, SkillEnum, copySkill, getSkillPaths, getSkillsFolder, symlinkSkill } from "./skill.mjs";
2
+ import { installSkillCommand } from "./install.mjs";
3
+ import { updateSkillsCommand } from "./update.mjs";
4
+ import * as DSCommand from "@duplojs/server/command";
5
+ //#region scripts/bin/skills/index.ts
6
+ const skillsCommand = DSCommand.create("skills", {
7
+ description: "Install or update DuploJS skills",
8
+ subjects: [installSkillCommand, updateSkillsCommand]
9
+ }, () => {});
10
+ //#endregion
11
+ export { AgentTypeEnum, SkillEnum, copySkill, getSkillPaths, getSkillsFolder, installSkillCommand, skillsCommand, symlinkSkill, updateSkillsCommand };
@@ -1,2 +1,2 @@
1
1
  import * as DSCommand from "@duplojs/server/command";
2
- export declare const installSkillCommand: DSCommand.Command<"install-skill">;
2
+ export declare const installSkillCommand: DSCommand.Command<"install">;
@@ -7,8 +7,8 @@ import * as DPath from "@duplojs/lang/path";
7
7
  import * as DSFile from "@duplojs/server/file";
8
8
  import * as DEither from "@duplojs/lang/either";
9
9
  import * as DArray from "@duplojs/lang/array";
10
- //#region scripts/bin/installSkill/index.ts
11
- const installSkillCommand = DSCommand.create("install-skill", {
10
+ //#region scripts/bin/skills/install.ts
11
+ const installSkillCommand = DSCommand.create("install", {
12
12
  options: [DSCommand.createBooleanOption("copy", { description: "Copy skills instead of creating symbolic links" }), DSCommand.createOption("type", DDataStructure.literal(AgentTypeEnum.toTuple()))],
13
13
  subjects: [DSCommand.createArgument("skill", DDataStructure.literal(SkillEnum.toTuple()), { optional: true })]
14
14
  }, async ({ args, options }) => {
@@ -1,4 +1,5 @@
1
1
  import * as DCommon from "@duplojs/lang/common";
2
+ import * as DPath from "@duplojs/lang/path";
2
3
  import * as DSFile from "@duplojs/server/file";
3
4
  import * as DEither from "@duplojs/lang/either";
4
5
  export declare const AgentTypeEnum: {
@@ -23,6 +24,12 @@ export declare const SkillEnum: {
23
24
  contract: <GenericContractValue extends string = "fundamental" | "http-server" | "http-client" | "command" | "server" | "modeling" | "test">(...args: (Exclude<"fundamental", GenericContractValue> | Exclude<"http-server", GenericContractValue> | Exclude<"http-client", GenericContractValue> | Exclude<"command", GenericContractValue> | Exclude<"server", GenericContractValue> | Exclude<"modeling", GenericContractValue> | Exclude<"test", GenericContractValue> extends infer InferredMissingValue extends string ? DCommon.IsNever<InferredMissingValue> extends true ? unknown : DCommon.ComputedTypeError<`Enum contract is missing value "${InferredMissingValue}".`> : never) & (Exclude<GenericContractValue, "fundamental" | "http-server" | "http-client" | "command" | "server" | "modeling" | "test"> extends infer InferredUnknownValue extends string ? DCommon.IsNever<InferredUnknownValue> extends true ? unknown : DCommon.ComputedTypeError<`Enum contract contains unknown value "${InferredUnknownValue}".`> : never) extends infer InferredRequirement ? DCommon.IsEqual<InferredRequirement, unknown> extends true ? [] : [] & InferredRequirement : never) => /*elided*/ any;
24
25
  };
25
26
  type SkillEnum = DCommon.GetEnumValue<typeof SkillEnum>;
27
+ export declare function getSkillsFolder(agentType: AgentTypeEnum): DEither.Error<unknown> | DEither.Success<string & DPath.Path & DPath.Absolute>;
28
+ export declare function getSkillPaths(agentType: AgentTypeEnum, skill: SkillEnum): DEither.Error<unknown> | DEither.Success<{
29
+ readonly selectedSkillPath: string & DPath.Path & DPath.Absolute;
30
+ readonly skillsFolder: string & DPath.Path & DPath.Absolute;
31
+ readonly currentSkillFolder: string & DPath.Path & DPath.Absolute;
32
+ }>;
26
33
  export declare function copySkill(agentType: AgentTypeEnum, skill: SkillEnum): Promise<DEither.Error<unknown> | DEither.Left<"file-system-remove-error", DSFile.RemoveErrors> | DEither.Right<"file-system-copy", void> | DEither.Left<"file-system-copy-error", DSFile.CopyErrors>>;
27
34
  export declare function symlinkSkill(agentType: AgentTypeEnum, skill: SkillEnum): Promise<DEither.Error<unknown> | DEither.Left<"file-system-remove-error", DSFile.RemoveErrors> | DEither.Left<"file-system-ensure-directory-error", DSFile.EnsureDirectoryErrors> | DEither.Right<"file-system-symlink", void> | DEither.Left<"file-system-symlink-error", DSFile.SymlinkErrors>>;
28
35
  export {};
@@ -5,7 +5,7 @@ import * as DSCommon from "@duplojs/server/common";
5
5
  import * as DSFile from "@duplojs/server/file";
6
6
  import * as DEither from "@duplojs/lang/either";
7
7
  import * as DPattern from "@duplojs/lang/pattern";
8
- //#region scripts/bin/installSkill/skill.ts
8
+ //#region scripts/bin/skills/skill.ts
9
9
  const AgentTypeEnum = DCommon.createEnum([
10
10
  "default",
11
11
  "claude",
@@ -25,18 +25,20 @@ const agentTypeFolderMapper = {
25
25
  codex: ".codex",
26
26
  default: ".agents"
27
27
  };
28
+ function getSkillsFolder(agentType) {
29
+ return DEither.rightPipe(DSCommon.getCurrentWorkDirectory(), (directory) => DPath.resolveRelative([
30
+ directory,
31
+ DCommon.cast(agentTypeFolderMapper[agentType]),
32
+ DCommon.cast("skills")
33
+ ]));
34
+ }
28
35
  function getSkillPaths(agentType, skill) {
29
36
  const selectedSkillPath = DPath.resolveRelative([
30
37
  getPackagePath(),
31
38
  DPath.declarePath("skills"),
32
39
  DCommon.cast(skill)
33
40
  ]);
34
- return DEither.rightPipe(DSCommon.getCurrentWorkDirectory(), (directory) => {
35
- const skillsFolder = DPath.resolveRelative([
36
- directory,
37
- DCommon.cast(agentTypeFolderMapper[agentType]),
38
- DCommon.cast("skills")
39
- ]);
41
+ return DEither.rightPipe(getSkillsFolder(agentType), (skillsFolder) => {
40
42
  const currentSkillFolder = DPath.resolveRelative([skillsFolder, DCommon.cast(`duplojs-${skill}`)]);
41
43
  return {
42
44
  selectedSkillPath,
@@ -55,4 +57,4 @@ function symlinkSkill(agentType, skill) {
55
57
  return DEither.rightAsyncPipe(getSkillPaths(agentType, skill), ({ selectedSkillPath, skillsFolder, currentSkillFolder }) => DEither.rightAsyncPipe(DSFile.ensureDirectory(skillsFolder), () => removeSkill(currentSkillFolder), () => DSFile.symlink(selectedSkillPath, currentSkillFolder, { type: "dir" })));
56
58
  }
57
59
  //#endregion
58
- export { AgentTypeEnum, SkillEnum, copySkill, symlinkSkill };
60
+ export { AgentTypeEnum, SkillEnum, copySkill, getSkillPaths, getSkillsFolder, symlinkSkill };
@@ -0,0 +1,2 @@
1
+ import * as DSCommand from "@duplojs/server/command";
2
+ export declare const updateSkillsCommand: DSCommand.Command<"update">;
@@ -0,0 +1,19 @@
1
+ import { AgentTypeEnum, SkillEnum, copySkill, getSkillPaths, symlinkSkill } from "./skill.mjs";
2
+ import * as DSCommand from "@duplojs/server/command";
3
+ import * as DDataStructure from "@duplojs/lang/dataStructure";
4
+ import * as DSFile from "@duplojs/server/file";
5
+ import * as DEither from "@duplojs/lang/either";
6
+ //#region scripts/bin/skills/update.ts
7
+ const updateSkillsCommand = DSCommand.create("update", {
8
+ description: "Update installed DuploJS skills, preserving copies and symbolic links",
9
+ options: [DSCommand.createOption("type", DDataStructure.literal(AgentTypeEnum.toTuple()))]
10
+ }, async ({ options }) => {
11
+ const agentType = options.type ?? "default";
12
+ for (const skill of SkillEnum.toTuple()) await DEither.rightAsyncPipe(getSkillPaths(agentType, skill), ({ currentSkillFolder }) => DSFile.linkStat(currentSkillFolder), (stat) => {
13
+ if (stat.isSymlink) return symlinkSkill(agentType, skill);
14
+ if (stat.isDirectory) return copySkill(agentType, skill);
15
+ return DEither.ok();
16
+ });
17
+ });
18
+ //#endregion
19
+ export { updateSkillsCommand };
@@ -1,55 +1,102 @@
1
1
  /**
2
2
  * @title DObject.discriminate
3
3
  *
4
- * Discriminer une union d’objets à partir d’une propriété.
4
+ * Tester une propriété avec un predicate et affiner l’objet par intersection.
5
5
  */
6
6
  import * as DObject from "@duplojs/lang/object";
7
7
  import * as DCommon from "@duplojs/lang/common";
8
+ import * as DNumber from "@duplojs/lang/number";
8
9
 
9
- type Event =
10
- | {
11
- type: "created";
12
- id: string;
13
- }
14
- | {
15
- type: "deleted";
16
- id: number;
17
- };
18
- interface CreatedEvent {
19
- type: "created";
20
- id: string;
21
- }
22
-
23
- interface DeletedEvent {
24
- type: "deleted";
10
+ // Le predicate reçoit la valeur de la propriété. S’il affine cette valeur,
11
+ // l’objet conserve son type initial en intersection avec la propriété affinée.
12
+ declare const input: {
13
+ value: string | number;
25
14
  id: number;
26
- }
15
+ };
27
16
 
28
- declare const event: Event;
17
+ // Appel classique : affiner une propriété, même sans union d’objets.
18
+ if (DObject.discriminate(input, "value", DCommon.isType("string"))) {
19
+ type _CheckInput = DCommon.ExpectType<
20
+ typeof input,
21
+ {
22
+ value: string | number;
23
+ id: number;
24
+ } & { value: string },
25
+ "strict"
26
+ >;
27
+ const value = input.value;
29
28
 
30
- if (DObject.discriminate(event, "type", "created")) {
31
- type _CheckIfEvent = DCommon.ExpectType<typeof event, CreatedEvent, "strict">;
32
- const id = event.id;
33
- type _CheckIfId = DCommon.ExpectType<typeof id, string, "strict">;
29
+ type _CheckValue = DCommon.ExpectType<typeof value, string, "strict">;
34
30
  }
35
31
 
36
- const result = DObject.discriminate(event, "type", "created");
37
-
38
- // Le predicate retourne true pour l’événement created.
39
- type _CheckResult = DCommon.ExpectType<typeof result, boolean, "strict">;
40
-
41
- // La forme curifiée est passée directement comme predicate à DCommon.when.
42
- const id = DCommon.pipe(
43
- event,
32
+ // Appel curifié : lire la chaîne si elle est présente, sinon conserver l’objet.
33
+ const result = DCommon.pipe(
34
+ input,
44
35
  DCommon.when(
45
- DObject.discriminate("type", "created"),
46
- (createdEvent) => {
47
- type _CheckPredicatedInput = DCommon.ExpectType<typeof createdEvent, CreatedEvent, "strict">;
36
+ DObject.discriminate("value", DCommon.isType("string")),
37
+ (value) => {
38
+ type _CheckValue = DCommon.ExpectType<
39
+ typeof value,
40
+ {
41
+ value: string | number;
42
+ id: number;
43
+ } & { value: string },
44
+ "strict"
45
+ >;
48
46
 
49
- return createdEvent.id;
47
+ return value.value;
50
48
  },
51
49
  ),
52
50
  );
53
51
 
54
- // L’identifiant est une chaîne pour created ; sinon l’événement initial est conservé.
55
- type _CheckId = DCommon.ExpectType<typeof id, string | DeletedEvent, "strict">;
52
+ type _CheckResult = DCommon.ExpectType<
53
+ typeof result,
54
+ string | {
55
+ value: string | number;
56
+ id: number;
57
+ },
58
+ "strict"
59
+ >;
60
+
61
+ // Les contraintes établies par le predicate sont portées par la propriété.
62
+ declare const person: {
63
+ age: number;
64
+ name: string;
65
+ };
66
+
67
+ if (DObject.discriminate(person, "age", DNumber.greaterThan(18))) {
68
+ type _CheckPerson = DCommon.ExpectType<
69
+ typeof person,
70
+ {
71
+ age: number;
72
+ name: string;
73
+ } & { age: number & DNumber.GreaterThan<18> },
74
+ "strict"
75
+ >;
76
+ }
77
+
78
+ // Composer les predicates pour une propriété imbriquée.
79
+ // DCommon.equal permet de conserver une vérification d’égalité.
80
+ declare const account: {
81
+ profile: { role: string };
82
+ id: number;
83
+ };
84
+
85
+ if (DObject.discriminate(account, "profile", DObject.discriminate("role", DCommon.equal("admin")))) {
86
+ type _CheckAccount = DCommon.ExpectType<
87
+ typeof account,
88
+ {
89
+ profile: { role: string };
90
+ id: number;
91
+ } & { profile: { role: string } & { role: "admin" } },
92
+ "strict"
93
+ >;
94
+ }
95
+
96
+ // Un predicate booléen reste accepté, sans affiner l’objet.
97
+ declare const threshold: number;
98
+
99
+ const isAboveThreshold = DObject.discriminate(person, "age", DNumber.greaterThan(threshold));
100
+
101
+ // true uniquement si l’âge dépasse le seuil fourni à l’exécution.
102
+ type _CheckIsAboveThreshold = DCommon.ExpectType<typeof isAboveThreshold, boolean, "strict">;
@@ -10,17 +10,13 @@ Fusionner les propriétés d’un objet dans une nouvelle valeur.
10
10
 
11
11
  Compter les clés runtime d’un objet.
12
12
 
13
- ### [DObject.deepDiscriminate](10-deepDiscriminate.ts)
14
-
15
- Discriminer une union d’objets à partir d’une propriété imbriquée.
16
-
17
13
  ### [DObject.deepOverride](10-deepOverride.ts)
18
14
 
19
15
  Remplacer récursivement les valeurs définies d’un objet sans modifier la source.
20
16
 
21
17
  ### [DObject.discriminate](10-discriminate.ts)
22
18
 
23
- Discriminer une union d’objets à partir d’une propriété.
19
+ Tester une propriété avec un predicate et affiner l’objet par intersection.
24
20
 
25
21
  ### [DObject.discriminateEntryKey](10-discriminateEntryKey.ts)
26
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duplojs/agents-kit",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.0-beta.8",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "DuploJS",
@@ -32,19 +32,19 @@
32
32
  "./docs/API/*/index.llms.md": "./docs/API/*/index.llms.md"
33
33
  },
34
34
  "dependencies": {
35
- "@duplojs/lang": "0.1.0-beta.7",
36
- "@duplojs/server": "0.1.0-beta.7"
35
+ "@duplojs/lang": "0.1.0-beta.8",
36
+ "@duplojs/server": "0.1.0-beta.8"
37
37
  },
38
38
  "peerDependencies": {
39
- "@duplojs/code-config": "0.1.0-beta.7",
40
- "@duplojs/eslint": "0.1.0-beta.7",
41
- "@duplojs/form": "0.1.0-beta.7",
42
- "@duplojs/http": "0.1.0-beta.7",
43
- "@duplojs/json-web-token": "0.1.0-beta.7",
44
- "@duplojs/lang": "0.1.0-beta.7",
45
- "@duplojs/playwright": "0.1.0-beta.7",
46
- "@duplojs/server": "0.1.0-beta.7",
47
- "@duplojs/tools": "0.1.0-beta.7"
39
+ "@duplojs/code-config": "0.1.0-beta.8",
40
+ "@duplojs/eslint": "0.1.0-beta.8",
41
+ "@duplojs/form": "0.1.0-beta.8",
42
+ "@duplojs/http": "0.1.0-beta.8",
43
+ "@duplojs/json-web-token": "0.1.0-beta.8",
44
+ "@duplojs/lang": "0.1.0-beta.8",
45
+ "@duplojs/playwright": "0.1.0-beta.8",
46
+ "@duplojs/server": "0.1.0-beta.8",
47
+ "@duplojs/tools": "0.1.0-beta.8"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@duplojs/code-config": {
@@ -70,8 +70,8 @@
70
70
  }
71
71
  },
72
72
  "devDependencies": {
73
- "@duplojs/code-config": "0.1.0-beta.7",
74
- "@duplojs/eslint": "0.1.0-beta.7",
73
+ "@duplojs/code-config": "0.1.0-beta.8",
74
+ "@duplojs/eslint": "0.1.0-beta.8",
75
75
  "@types/node": "24.13.3",
76
76
  "@types/web": "0.0.358",
77
77
  "vue": "3.5.43",
@@ -17,64 +17,62 @@ Si le chemin du routeur manque, le retrouver depuis le projet consommateur :
17
17
  node --input-type=module -e 'import { fileURLToPath } from "node:url"; console.log(fileURLToPath(import.meta.resolve("@duplojs/agents-kit/docs/concepts.llms.md")))'
18
18
  ```
19
19
 
20
- ## Utiliser DuploJS pour coder
20
+ ## Utiliser les API DuploJS
21
21
 
22
- Utiliser obligatoirement les API et l'écosystème DuploJS dès qu'un équivalent existe, y compris dans les tests. Avant d'utiliser une API native ou tierce ou de créer un utilitaire, chercher l'équivalent DuploJS.
22
+ Utiliser obligatoirement l'équivalent DuploJS avant une API native, tierce ou un nouvel utilitaire, y compris dans les tests. Chercher dans `lang` pour les primitives indépendantes de la plateforme, dans `server` pour les interactions avec l'environnement, puis dans les packages spécialisés.
23
23
 
24
- - `@duplojs/lang` : primitives et manipulations JavaScript indépendantes de la plateforme.
25
- - `@duplojs/server` : interactions avec l'environnement d'exécution serveur, notamment le système de fichiers. Par exemple, calculer un chemin avec `lang/path` et lire un fichier avec `server/file`.
26
- - Pour un besoin spécialisé, rechercher le package et les API adaptés dans l'écosystème à partir de la documentation.
24
+ Importer le sous-export du domaine avec son namespace long, comme dans les exemples des références.
27
25
 
28
- Importer le sous-export du domaine avec son namespace long lorsqu'il est exposé, comme dans les exemples liés depuis les références.
26
+ ## Modifier à l'endroit responsable
29
27
 
30
- ## Laisser les types s'inférer
28
+ Lire les contrats, définitions et appelants concernés ; intégrer le besoin dans la fonction, le modèle ou le module responsable. Limiter le remaniement au besoin, adapter les usages et vérifier les garanties conservées.
31
29
 
32
- Dans les implémentations, laisser TypeScript inférer les retours des fonctions et les valeurs issues des appels pour que leurs types suivent le code et ses dépendances. Ne pas reconstituer manuellement un type que l'expression ou l'API peut inférer. Annoter un type de retour uniquement pour résoudre une récursivité. Les signatures d'interfaces et les déclarations de fonctions des exemples décrivent des contrats à implémenter ; elles ne demandent pas d'annoter le retour de leur implémentation.
30
+ Privilégier fonctions pures, immutabilité et transformations explicites ; isoler les interactions avec l'environnement.
33
31
 
34
- ## Faire évoluer le code à l'endroit responsable
32
+ ## Laisser les types s'inférer
35
33
 
36
- Lire les définitions, les contrats et les appelants concernés, puis intégrer le besoin dans la fonction, le modèle ou le module responsable. Si les adaptations se multiplient dans le traitement ou chez ses appelants, réexaminer cette responsabilité et simplifier la partie concernée.
34
+ Laisser TypeScript inférer les valeurs et les retours des implémentations. Annoter un retour uniquement pour résoudre une récursivité ; les signatures d'interfaces et les déclarations de fonctions décrivent des contrats, pas cette annotation dans l'implémentation.
37
35
 
38
- Privilégier les fonctions pures, l'immutabilité et des transformations explicites. Isoler les interactions avec l'environnement et représenter les échecs prévisibles dans les types de retour. Recomposer les étapes ou extraire une responsabilité lorsque cela clarifie le traitement, en conservant les garanties métier et le rôle de chaque couche.
36
+ ## Composer les transformations
39
37
 
40
- Limiter le remaniement au besoin demandé, puis relire la partie modifiée avec ses usages : déroulement compréhensible, absence de duplication et complexité proportionnée. Adapter les appelants concernés et vérifier les contrats ainsi que le comportement attendu.
38
+ Si la fonction retournée par une forme curifiée ne déclare pas ses propres génériques, écrire l'appel de configuration directement dans l'étape de `DCommon.pipe` ou `asyncPipe` : son contexte permet l'inférence. Ne pas préparer cette étape dans une variable ni forcer son typage par génériques, annotations ou casts. Lire [Composition](references/composition.md) lors de l'emploi d'une forme curifiée pour distinguer les deux niveaux de génériques.
41
39
 
42
- ## Composer les transformations
40
+ Écrire chaque argument du pipe sur sa propre ligne, avec virgule finale et fermeture séparée, même pour une donnée et une seule étape. Un appel simple peut rester direct ; préparer séparément les données de sources indépendantes avant de les combiner.
43
41
 
44
- De nombreuses fonctions proposent une forme curifiée pour `DCommon.pipe` : fournir les paramètres fixes directement dans l'étape du pipe, puis laisser celui-ci transmettre la donnée.
42
+ Choisir `DCommon.asyncPipe` pour attendre les étapes, ou `DEither.rightPipe` / `rightAsyncPipe` pour transmettre les valeurs des `Right` et arrêter au premier `Left`. À toute étape produisant des états à sélectionner, composer production et sélection dans un pipe interne à sa callback, avant le déballage par le pipe extérieur. Lire [Composition](references/composition.md) pour ces enchaînements de résultats.
45
43
 
46
- Lorsque la fonction retournée par la forme curifiée ne déclare pas ses propres génériques, ses types sont fixés lors de l'appel de configuration. Écrire cet appel directement comme argument de `pipe` ou `asyncPipe` : le contexte doit être disponible au moment de configurer l'étape. Préparer la fonction dans une variable puis passer cette variable à un pipe reste une préparation isolée ; l'ajout du pipe ne corrige pas cet usage. Ne pas ajouter des génériques explicites, annotations ou casts pour le forcer. L'[exemple de composition](references/examples/composition.ts) montre l'appel dans le pipe et la préparation à éviter ; [Composition](references/composition.md) explique les deux niveaux de signature.
44
+ ## Remplacer les boucles impératives
47
45
 
48
- Toujours écrire les appels de pipe sur plusieurs lignes : chaque argument sur sa propre ligne, une virgule après le dernier et la parenthèse de fermeture sur une ligne séparée. Appliquer ce format même avec seulement deux arguments, une donnée et une étape ; écrire l'appel curifié lui-même à la ligne de cette étape.
46
+ Ne pas écrire de `for`, de `while` ni leurs variantes. Utiliser :
49
47
 
50
- Un appel simple peut rester direct. Préparer séparément les données issues de sources indépendantes avant de les combiner ; les appels curifiés des étapes restent dans le pipe. Pour plusieurs étapes asynchrones, `DCommon.asyncPipe` attend chaque étape ; un seul `await` sur le résultat final suffit.
48
+ - `DArray.reduce` pour parcourir un tableau avec un accumulateur ; `DGenerator.reduce` pour un autre itérable.
49
+ - `DGenerator.asyncReduce` pour un parcours asynchrone, y compris sur un tableau.
50
+ - `DCommon.loop` pour une répétition conditionnelle ; `DCommon.asyncLoop` en asynchrone.
51
51
 
52
- Lorsque les étapes doivent s'arrêter sur un `Left`, utiliser `DEither.rightPipe` ou `DEither.rightAsyncPipe` : chaque étape reçoit la valeur du `Right` précédent et le premier `Left` arrête la suite. Lire [Composition](references/composition.md) avant de construire ou modifier un tel enchaînement ; l'[exemple direct et curifié](references/examples/composition.ts) montre le cas synchrone sans échec.
52
+ Transmettre l'état sans mutation externe avec `next`, terminer avec `exit`. Avant de créer ou remplacer une boucle, lire [Itérations](references/iterations.md) pour les contrats de ces callbacks. Pour une simple transformation ou sélection, utiliser directement `DArray.map` ou `DArray.filter`.
53
53
 
54
54
  ## Valider les entrées et conserver les garanties
55
55
 
56
- Aux frontières du logiciel, valider les données externes avec une `DDataStructure`, qui porte leur contrat au runtime et dans les types. Utiliser l'intégration prévue par le module récepteur lorsqu'elle existe. Dans les traitements internes, exploiter les garanties déjà établies, sans refaire une validation pour le seul passage d'une fonction à une autre.
57
-
58
- Préciser les contraintes utiles : bornes, entier, format, longueur minimale, maximale ou exacte. Une contrainte enrichit le type ; sa seule déclaration ne valide rien au runtime.
56
+ Valider les données externes avec une `DDataStructure`, via l'intégration du module récepteur lorsqu'elle existe. Décrire les contraintes utiles : bornes, entier, format ou longueur. Une contrainte déclarée ne valide rien au runtime ; dans les traitements internes, réutiliser les garanties établies.
59
57
 
60
- Choisir la construction selon les informations disponibles :
58
+ Choisir selon la preuve disponible :
61
59
 
62
- 1. **Littéral ou garantie compatible** : déclaration sûre ou calcul de compatibilité avec `DCommon.cast` ; utiliser `DCommon.infer` si le littéral doit être conservé.
63
- 2. **Valeur runtime sans preuve** : validation ou fabrique sûre renvoyant un `Either` à traiter ou propager.
64
- 3. **Refus du compilateur** : vérifier l'inférence et l'origine de la garantie attendue ; ne pas la fabriquer avec `as`, `!` ou un helper sans preuve.
60
+ - **Littéral ou garantie compatible** : déclaration sûre ou `DCommon.cast` ; `DCommon.infer` pour conserver aussi le littéral. Ces fonctions calculent une compatibilité statique, sans validation runtime.
61
+ - **Valeur runtime sans preuve** : validation ou fabrique sûre, puis traitement du `Either`.
62
+ - **Refus du compilateur** : vérifier l'inférence et l'origine de la garantie ; ne pas la fabriquer avec `as`, `!` ou un helper sans preuve.
65
63
 
66
- `cast` et `infer` calculent une compatibilité de types, sans validation runtime. Pour écrire un contrat de validation, construire une valeur contrainte ou résoudre un refus de compatibilité, lire [Contraintes et validation](references/constraints-and-validation.md). L'[exemple de validation](references/examples/input-validation.ts) montre l'acquisition de garanties depuis une entrée inconnue.
64
+ Lire [Contraintes et validation](references/constraints-and-validation.md) pour construire un contrat, une valeur contrainte ou résoudre un refus de compatibilité. Après chaque transformation, suivre les garanties restantes et exploiter les accès garantis sans garde redondante.
67
65
 
68
- Après chaque transformation, suivre le type du résultat : une garantie peut avoir été préservée, renforcée ou perdue. Exploiter les accès garantis aux collections sans garde redondante. Si la cible de `cast` n'est pas inférée dans un appel générique, lire la difficulté d'inférence de la référence avant de modifier les types.
66
+ ## Représenter les résultats et propager les erreurs
69
67
 
70
- ## Distinguer les résultats des défaillances d'environnement
68
+ Utiliser `DEither.result` lorsqu'une fonction doit établir des états sans décider de leur caractère favorable ou défavorable. L'information distingue les états ; la valeur porte leur contexte. Tous ces `Result` sont des `Right` : `isRight` ne choisit pas un état particulier.
71
69
 
72
- Une issue prévisible et exploitable par le traitement appartient au contrat de résultat : réussite en `Right`, refus en `Left`. Cela comprend les validations de données externes, les absences et les manipulations impossibles, y compris techniques. L'information du résultat identifie le cas ; sa valeur transporte les données ou les détails du refus.
70
+ Lorsque la fonction décide d'une réussite ou d'un refus prévisible, les exprimer en `Right` ou `Left`, y compris pour les absences et refus techniques. Si une branche renvoie un `Either`, toutes en renvoient un ; utiliser `DEither.ok()` pour une réussite sans valeur.
73
71
 
74
- Dès qu'une branche d'une fonction renvoie un `Right` ou un `Left`, toutes ses branches renvoient un `Either`. Envelopper les valeurs de réussite dans un `Right`, y compris avec `DEither.ok()` pour une réussite sans valeur ; ne pas mélanger des `Either` et des valeurs brutes dans le contrat de retour.
72
+ Chez le consommateur, sélectionner les issues qui autorisent la suite avec `keepAsRightByInformation` ; les autres deviennent des `Left` en conservant information et valeur. Utiliser un sélecteur exhaustif comme `keepAsRightSelection` ou `matchInformation` lorsqu'une nouvelle variante doit obliger à réexaminer la décision.
75
73
 
76
- Les exceptions de défaillance imprévue émises par les dépendances ou le runtime remontent implicitement. Les fonctions réutilisables n'ajoutent pas de `throw` ; elles propagent ou adaptent leurs `Left`. Discriminer le résultat avant de le déballer.
74
+ `unwrapRight` extrait les valeurs des `Right` et conserve les `Left` : le pattern sélection → `unwrapRight` → garde `isLeft` permet un retour anticipé sûr. Propager ou adapter les refus dans les fonctions réutilisables. Lire [Résultats et erreurs](references/results-and-errors.md) pour choisir la sélection et l'extraction.
77
75
 
78
- Réserver les `throw` explicites et `OrThrow` à la consommation finale du résultat par la frontière d'exécution : réponse, fin du programme ou exception adaptée. Être appelé depuis cette frontière ne suffit pas à autoriser un `throw`. La rareté ou le coût de propagation d'un refus prévisible ne changent pas cette règle.
76
+ Les exceptions imprévues des dépendances ou du runtime remontent implicitement. Réserver les `throw` explicites et `OrThrow` à la consommation finale du résultat par la frontière d'exécution. Une fonction réutilisable n'ajoute pas de `throw`, même appelée depuis cette frontière. La rareté du refus ou son coût de propagation ne changent pas cette règle.
79
77
 
80
- Avant d'adapter une API qui lève sur un refus prévisible ou d'introduire `OrThrow`, lire [Résultats et erreurs](references/results-and-errors.md) pour choisir le traitement selon la responsabilité de l'appelant. Préférer les déclarations sûres pour les constantes connues ; l'[exemple de propagation](references/examples/error-propagation.ts) conserve les erreurs d'une opération dans le contrat de retour.
78
+ Avant d'adapter une API qui lève sur un refus prévisible ou d'introduire `OrThrow`, lire [Résultats et erreurs](references/results-and-errors.md). Préférer les déclarations sûres pour les constantes connues.
@@ -1,35 +1,19 @@
1
- # Composer une opération
1
+ # Composition
2
2
 
3
- ## Forme directe et forme curifiée
3
+ ## Configurer dans le contexte du pipe
4
4
 
5
- La forme directe reçoit la donnée et les paramètres. La forme curifiée reçoit les paramètres dans l'étape du pipe, puis retourne la fonction qui recevra la donnée.
5
+ La forme directe reçoit donnée et paramètres ; la forme curifiée configure les paramètres puis reçoit la donnée. Une fonction déjà unaire peut être passée directement.
6
6
 
7
- Lire la surcharge curifiée en distinguant les génériques de l'appel de configuration de ceux de la fonction retournée. Quand cette dernière ne déclare pas ses propres génériques, ses types sont déjà fixés à la configuration ; un appel ultérieur avec la donnée ne les infère pas à nouveau. Les génériques présents sur la surcharge extérieure ne rendent pas la fonction retournée générique.
7
+ Si la fonction retournée ne déclare pas ses propres génériques, ses types sont fixés à la configuration. Écrire cet appel dans l'étape du pipe apporte le contexte d'inférence ; passer ensuite une variable déjà configurée à un pipe ne le rétablit pas. Ne pas forcer cette préparation par génériques, annotations ou casts.
8
8
 
9
- Cette signature indique un usage directement dans `DCommon.pipe` ou `DCommon.asyncPipe`, dont le contexte fournit le type de la donnée lors de la configuration. Écrire l'appel de la forme curifiée à l'intérieur des arguments du pipe. Une fonction préparée dans une variable reste hors de ce contexte, même si cette variable est ensuite utilisée comme étape d'un pipe. Ne pas ajouter annotations, génériques explicites ou casts pour rendre cette préparation typable. Pour réutiliser le traitement, écrire une fonction qui reçoit la donnée et construit son pipe, ou utiliser la forme directe avec cette donnée.
9
+ Pour réutiliser le traitement, écrire une fonction recevant la donnée et construisant son pipe, ou utiliser la forme directe. L'[exemple de composition](examples/composition.ts) montre les deux formes, le format multiligne et une préparation à éviter.
10
10
 
11
- Comparer les formes directe et curifiée dans [cet exemple de transformation](examples/composition.ts). Les vérifications de type montrent l'inférence lorsque les appels sont écrits dans le pipe. Le cas à éviter utilise des génériques explicites pour préparer une fonction, puis la passe à un pipe : ce code peut compiler tout en contredisant l'usage attendu.
12
-
13
- Le pipe suit ici une même donnée : utilisateurs, utilisateurs actifs, noms. Une fonction déjà unaire de l'API peut être passée directement, comme `DString.trim`. Pour une forme curifiée, écrire son appel de configuration dans l'étape. Ne pas supposer que toutes les fonctions exposent les mêmes surcharges.
14
-
15
- ## Mise en forme des pipes
16
-
17
- Écrire tous les appels de pipe sur plusieurs lignes, avec chaque argument sur sa propre ligne, une virgule après le dernier argument et la parenthèse de fermeture sur une ligne séparée. Ce format s'applique aussi à une donnée suivie d'une seule étape, comme dans le premier pipe de l'exemple. L'appel qui configure la forme curifiée occupe la ligne de l'étape ; une variable préparée auparavant ne le remplace pas.
18
-
19
- ## Plusieurs sources indépendantes
20
-
21
- Quand plusieurs données indépendantes demandent chacune leur propre préparation, les nommer avant de les combiner. Cette préparation concerne les données ; les appels curifiés restent écrits dans les étapes de leur pipe.
22
-
23
- Voir [la préparation de deux arguments indépendants](examples/independent-inputs.ts).
24
-
25
- Le résultat de cet appel peut ensuite devenir l'entrée d'un autre pipe si plusieurs transformations se suivent.
11
+ Préparer les données de sources indépendantes avant de les combiner ; les configurations curifiées restent dans leur pipe. Voir [deux sources indépendantes](examples/independent-inputs.ts).
26
12
 
27
13
  ## Asynchronisme et résultats
28
14
 
29
- `DCommon.asyncPipe` attend chaque étape avant la suivante. Un seul `await` sur le pipe suffit pour obtenir le résultat final.
30
-
31
- Voir [un enchaînement asynchrone](examples/async-composition.ts).
15
+ `DCommon.asyncPipe` attend chaque étape ; un seul `await` final suffit. Voir l'[exemple asynchrone](examples/async-composition.ts).
32
16
 
33
- Si chaque étape peut renvoyer un `Either`, utiliser `DEither.rightPipe` ou `DEither.rightAsyncPipe` pour propager automatiquement le premier `Left`. Un pipe ordinaire ne décide pas à lui seul comment traiter un `Left`. Les pipes de `DEither` transmettent la valeur du `Right` à l'étape suivante, et non son wrapper.
17
+ `DEither.rightPipe` et `rightAsyncPipe` transmettent les valeurs des `Right` et arrêtent au premier `Left`. Un pipe ordinaire ne s'arrête pas automatiquement sur un `Left`. Pour sélectionner un état produit par une étape, composer production et sélection dans un `innerPipe` ou `asyncInnerPipe`, avant de rendre le résultat au pipe extérieur. Une callback construisant un `pipe` ou `asyncPipe` convient aussi.
34
18
 
35
- Pour approfondir, rechercher la currification et la composition des `Either` dans `docs/concepts.llms.md`, depuis la racine AgentKit indiquée dans les instructions du projet.
19
+ Ce montage s'applique à toute étape ou callback imbriquée. À l'étape extérieure suivante, le `Right` serait déjà déballé, sans son information. Voir la [sélection intermédiaire](examples/result-selection.ts).
@@ -1,61 +1,24 @@
1
- # Contraintes, déclarations et validation
1
+ # Contraintes et validation
2
2
 
3
- ## Une garantie doit avoir une origine
3
+ ## Établir la garantie
4
4
 
5
- Une contrainte exprime dans le typage une propriété de la valeur : entier, borne, longueur ou format. Une intersection cumule les garanties, par exemple `number & DNumber.Integer & DNumber.LessThan<25>`.
5
+ Une contrainte exprime une propriété dans les types : borne, longueur ou format. Les intersections cumulent ces garanties ; leur déclaration seule ne valide ni ne transforme une valeur.
6
6
 
7
- Déclarer ce type ne valide rien et ne transforme pas la valeur. La garantie vient d'une validation runtime, d'un prédicat qui affine le type, d'une fonction qui préserve cette propriété ou d'un calcul de compatibilité sur une information déjà connue statiquement.
7
+ Aux frontières, décrire le contrat complet avec une `DDataStructure` et les contraintes du besoin. Fournir cette structure au module récepteur lorsqu'il propose une intégration ; sinon, valider explicitement et traiter le `Either`. Réutiliser ensuite les garanties établies. Voir [le contrat d'entrée](examples/input-validation.ts).
8
8
 
9
- ## Entrées : décrire le contrat complet
9
+ ## Choisir la construction
10
10
 
11
- Une donnée reçue à une frontière externe passe par un data parser avant d'être considérée fiable. Une `DDataStructure` porte un schéma commun au runtime et au typage ; ses contraintes précisent le contrat au-delà de la nature de la donnée. Dans les fonctions internes, réutiliser les garanties déjà établies et suivre celles que les transformations conservent.
11
+ - **Valeur connue statiquement ou garantie compatible** : `DCommon.cast` calcule les implications statiques que TypeScript ne déduit pas seul. La cible vient du type attendu ; aucune validation runtime n'est effectuée. Voir [les conversions](examples/cast.ts).
12
+ - **Littéral à conserver** : `DCommon.infer` conserve aussi le type littéral et exige une entrée littérale. Voir [une propriété littérale](examples/infer.ts).
13
+ - **Valeur runtime sans preuve** : validation par une structure, un prédicat ou une fabrique sûre. Une simple `string` ou un simple `number` ne prouve pas de contrainte.
14
+ - **Constante spécialisée** : chercher une déclaration sûre du domaine avant `OrThrow`. Vérifier la surcharge : selon l'argument, elle peut retourner une valeur ou un `Either`. Voir [les chemins et Chrono](examples/safe-declarations.ts).
12
15
 
13
- Voir [un contrat et sa validation](examples/input-validation.ts).
16
+ Un refus de `cast` indique une compatibilité non établie : vérifier l'inférence ou le support du calcul pour une contrainte personnalisée, puis choisir une validation si nécessaire. Ne pas fabriquer la preuve avec une assertion ou un helper.
14
17
 
15
- Choisir les contraintes selon les règles du besoin : bornes inclusives ou exclusives des nombres, minimum, maximum ou longueur exacte des chaînes et tableaux, formats, etc. Chaque contrainte d'une structure doit être compatible avec le type de donnée décrit.
18
+ Si un paramètre générique direct ne fournit pas la cible de `cast` ou `infer`, l'expliciter avec `satisfies` après l'appel. Ce contexte ne valide rien et n'est pas nécessaire partout : comparer [le paramètre générique](examples/generic-inference.ts) et [la propriété littérale](examples/infer.ts).
16
19
 
17
- Pour les entrées gérées par l'écosystème, chercher comment le module qui les reçoit accepte des structures et lui fournir le contrat. Exploiter ensuite les valeurs déjà validées. À une frontière qui n'offre pas cette intégration, appeler explicitement la validation et traiter son `Either`.
20
+ ## Exploiter les garanties restantes
18
21
 
19
- Exemples d'intégrations : `DSCommand.createArgument` et `DSCommand.createOption` pour une commande ; `.extract(...)` pour une route `@duplojs/http`. La règle s'applique à tout module qui reçoit des données externes.
22
+ Suivre ce que chaque transformation préserve ou perd. Une longueur minimale garantit certains accès ; l'exploiter sans garde redondante. Elle ne garantit pas les indices au-delà du minimum, et n'enlève pas `undefined` du type propre aux éléments. Un filtre peut perdre cette garantie de longueur.
20
23
 
21
- ## `cast` : compatibilité calculée par TypeScript
22
-
23
- `DCommon.cast` restitue la valeur sans validation runtime. Le type attendu donne la cible du calcul. Une garantie plus forte peut impliquer une garantie plus faible ; un littéral peut aussi fournir assez d'information.
24
-
25
- Voir [les conversions de contraintes et les déclarations littérales](examples/cast.ts).
26
-
27
- Ce calcul de types comble les implications que TypeScript ne déduit pas de lui-même. Il n'y a pas de calcul dynamique de validité lors de l'exécution. Une simple `string` ou un simple `number` provenant du runtime ne prouve pas une longueur, un format ou une borne.
28
-
29
- Si `cast` émet une erreur, considérer que la compatibilité n'est pas établie. Une règle personnalisée peut ne pas avoir de support de calcul enregistré. Ne pas remplacer ce refus par `as`, une double assertion ou un helper qui fabrique la contrainte sans preuve. Vérifier l'inférence, valider par une structure ou un prédicat adapté, ou utiliser une autre construction sûre.
30
-
31
- ## `infer` : conserver également le littéral
32
-
33
- Préférer `cast` dans le cas courant. Utiliser `DCommon.infer` lorsqu'une valeur connue statiquement doit garder son type littéral en plus des contraintes calculées. Cette fonction exige une entrée littérale ; elle ne sert pas à valider une donnée runtime.
34
-
35
- Voir [la conservation du littéral dans une propriété](examples/infer.ts).
36
-
37
- ## Difficulté d'inférence : cible d'un paramètre générique direct
38
-
39
- Lorsque `cast` ou `infer` est passé à un paramètre lui-même générique, TypeScript peut ne pas fournir une cible assez précise au calcul. Si le type attendu n'est pas inféré dans cette situation, l'expliciter avec `satisfies` immédiatement après l'appel. Ce traitement répond à une difficulté de contexte ; il n'est pas nécessaire à chaque utilisation de ces fonctions.
40
-
41
- Voir [le calcul des contraintes avec un paramètre générique direct](examples/generic-inference.ts).
42
-
43
- `satisfies` fournit le contexte du calcul ; il ne contourne pas la validation. Une propriété contrainte d'un objet générique peut déjà fournir ce contexte, comme dans [l'exemple de propriété littérale](examples/infer.ts) : ne pas ajouter ce contournement d'inférence partout.
44
-
45
- ## Déclarations spécialisées et Chrono
46
-
47
- Les domaines proposent aussi des helpers pour les valeurs connues. Préférer ces déclarations sûres à `OrThrow`.
48
-
49
- Voir [les déclarations de chemins et de valeurs Chrono](examples/safe-declarations.ts).
50
-
51
- Vérifier la surcharge réellement utilisée : le type de l'argument détermine si la fonction retourne directement la valeur ou un `Either`.
52
-
53
- ## Les contraintes simplifient les opérations suivantes
54
-
55
- Voir [les accès garantis après validation du tableau](examples/guaranteed-array-access.ts).
56
-
57
- `MinElements<2>` garantit les indices `0` et `1` et l'existence du dernier élément. L'indice `2` reste incertain : un minimum de deux n'impose pas un maximum de deux. Les éléments garantis sont retournés directement, sans `Maybe` ni `undefined` ajouté pour une éventuelle absence. Une borne maximale ou une longueur exacte peut aussi permettre de prouver qu'un indice est absent.
58
-
59
- Exploiter ces garanties sans garde redondante ni assertion. Respecter cependant le type propre aux éléments : un tableau dont les éléments peuvent eux-mêmes être `undefined` conserve cette possibilité. Après une transformation, suivre les contraintes du résultat ; un filtre peut supprimer des éléments et perdre une garantie de minimum.
60
-
61
- Pour approfondir, rechercher les contraintes, les data structures, Chrono ou l'intégration du module concerné dans `docs/concepts.llms.md`, depuis la racine AgentKit indiquée par les instructions du projet.
24
+ Voir [les accès garantis](examples/guaranteed-array-access.ts) pour distinguer présence prouvée et accès incertain.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @title Remplacer les boucles par les primitives DuploJS.
3
+ *
4
+ * Accumulateurs immuables, sortie anticipée et répétitions conditionnelles.
5
+ */
6
+ import * as DArray from "@duplojs/lang/array";
7
+ import * as DCommon from "@duplojs/lang/common";
8
+ import * as DGenerator from "@duplojs/lang/generator";
9
+ import * as DNumber from "@duplojs/lang/number";
10
+
11
+ interface RunningTotals {
12
+ readonly total: number;
13
+ readonly subtotals: readonly number[];
14
+ }
15
+
16
+ const runningTotals = DCommon.pipe(
17
+ [2, -1, 4],
18
+ DArray.reduce(
19
+ DArray.reduceFrom<RunningTotals>({
20
+ total: 0,
21
+ subtotals: [],
22
+ }),
23
+ ({ element, lastValue, nextWithObject }) => {
24
+ const total = DNumber.sum([lastValue.total, element]);
25
+ return nextWithObject(lastValue, {
26
+ total,
27
+ subtotals: DArray.push(lastValue.subtotals, total),
28
+ });
29
+ },
30
+ ),
31
+ );
32
+
33
+ // Résultat : { total: 5, subtotals: [2, 1, 5] }. Chaque étape dépend du total précédent.
34
+ // L'accumulateur initial et la source ne sont pas mutés.
35
+ type _CheckRunningTotals = DCommon.ExpectType<typeof runningTotals, RunningTotals, "strict">;
36
+
37
+ const totalOrStop = DCommon.pipe(
38
+ [2, -1, 4],
39
+ DArray.reduce(
40
+ 0,
41
+ ({ element, lastValue, next, exit }) => element < 0
42
+ ? exit("negative-value")
43
+ : next(DNumber.sum([lastValue, element])),
44
+ ),
45
+ );
46
+
47
+ // Résultat : "negative-value" ; 4 n'est pas traité. exit peut changer le type du résultat.
48
+ type _CheckTotalOrStop = DCommon.ExpectType<typeof totalOrStop, number | string, "strict">;
49
+
50
+ declare function loadAmount(id: string): Promise<number>;
51
+
52
+ const loadedTotal = await DCommon.asyncPipe(
53
+ ["first", "second"],
54
+ DGenerator.asyncReduce(
55
+ 0,
56
+ async({ item, lastValue, next }) => {
57
+ const amount = await loadAmount(item);
58
+ return next(DNumber.sum([lastValue, amount]));
59
+ },
60
+ ),
61
+ );
62
+
63
+ // Chaque chargement est attendu avant le suivant ; la source peut aussi être un AsyncIterable.
64
+ type _CheckLoadedTotal = DCommon.ExpectType<typeof loadedTotal, number, "strict">;
65
+
66
+ const reachedValue = DCommon.loop<number, number>(({ previousOutput, next, exit }) => {
67
+ const current = previousOutput ?? 0;
68
+
69
+ // La condition est vérifiée avant le traitement, comme dans un while.
70
+ if (current >= 3) {
71
+ return exit(current);
72
+ }
73
+
74
+ return next(DNumber.sum([current, 1]));
75
+ });
76
+
77
+ // Résultat : 3. L'état circule dans next sans compteur externe mutable.
78
+ type _CheckReachedValue = DCommon.ExpectType<typeof reachedValue, number, "strict">;
79
+
80
+ declare function readBatchSize(index: number): Promise<number>;
81
+
82
+ const processedTotal = await DCommon.asyncLoop<number, number>(async({
83
+ count,
84
+ previousOutput,
85
+ next,
86
+ exit,
87
+ }) => {
88
+ const current = previousOutput ?? 0;
89
+ const size = await readBatchSize(count);
90
+
91
+ // La première taille nulle termine la lecture ; le dernier total devient le résultat.
92
+ if (size === 0) {
93
+ return exit(current);
94
+ }
95
+
96
+ return next(DNumber.sum([current, size]));
97
+ });
98
+
99
+ type _CheckProcessedTotal = DCommon.ExpectType<typeof processedTotal, number, "strict">;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @title Sélectionner les résultats selon l'opération.
3
+ *
4
+ * Constater un état, sélectionner les issues utiles et conserver les refus.
5
+ */
6
+ import * as DCommon from "@duplojs/lang/common";
7
+ import * as DEither from "@duplojs/lang/either";
8
+
9
+ interface Item {
10
+ id: string;
11
+ enabled: boolean;
12
+ }
13
+
14
+ function classifyItem(item: Item) {
15
+ if (item.enabled) {
16
+ return DEither.result("ready", item);
17
+ }
18
+
19
+ return DEither.result("blocked", item);
20
+ }
21
+
22
+ function prepareItem(item: Item) {
23
+ const selected = DCommon.pipe(
24
+ item,
25
+ classifyItem,
26
+ DEither.keepAsRightByInformation("ready"),
27
+ DEither.unwrapRight,
28
+ );
29
+
30
+ // unwrapRight conserve le Left : la garde peut suivre l'extraction.
31
+ if (DEither.isLeft(selected)) {
32
+ return selected;
33
+ }
34
+
35
+ return DEither.right("prepared", selected);
36
+ }
37
+
38
+ type _CheckPrepared = DCommon.ExpectType<
39
+ ReturnType<typeof prepareItem>,
40
+ DEither.Left<"blocked", Item> | DEither.Right<"prepared", Item>,
41
+ "strict"
42
+ >;
43
+
44
+ declare function loadItem(id: string): Promise<DEither.Maybe<Item>>;
45
+ declare function saveItem(item: Item): Promise<DEither.Right<"saved", Item>>;
46
+
47
+ const saved = await DEither.rightAsyncPipe(
48
+ loadItem("item"),
49
+ // L'inner pipe intervient après la recherche, à une étape intermédiaire.
50
+ DCommon.innerPipe(
51
+ classifyItem,
52
+ DEither.keepAsRightSelection({
53
+ ready: true,
54
+ blocked: false,
55
+ }),
56
+ ),
57
+ // L'étape extérieure reçoit l'Item déballé ; un Left évite cet appel.
58
+ (item) => saveItem(item),
59
+ );
60
+
61
+ type _CheckSaved = DCommon.ExpectType<
62
+ typeof saved,
63
+ DEither.None | DEither.Left<"blocked", Item> | DEither.Right<"saved", Item>,
64
+ "strict"
65
+ >;
@@ -0,0 +1,20 @@
1
+ # Itérations
2
+
3
+ ## Parcourir une collection
4
+
5
+ Configurer `DArray.reduce(initial, callback)` dans l'étape d'un `DCommon.pipe` recevant le tableau. La callback reçoit `element`, `index` et `lastValue`, initialement égal à l'accumulateur de départ :
6
+
7
+ - `next(nouvelÉtat)` poursuit ; `next(lastValue)` ignore l'élément.
8
+ - `exit(résultat)` arrête immédiatement et peut changer le type de sortie.
9
+
10
+ Sans `exit`, le résultat est le dernier accumulateur ; une source vide retourne l'initial. Pour un état structuré, utiliser `DArray.reduceFrom<Type>(valeur)` et construire les nouveaux états sans mutation.
11
+
12
+ Pour un autre itérable, utiliser `DGenerator.reduce`. `DGenerator.asyncReduce` accepte tableaux, itérables synchrones ou asynchrones et attend chaque étape. Leurs callbacks reçoivent `item` ; initialiser leurs états structurés avec `DGenerator.reduceFrom`.
13
+
14
+ ## Répéter jusqu'à une condition
15
+
16
+ `DCommon.loop` fournit `count` depuis zéro et `previousOutput`, initialement `undefined`, puis égal au dernier `next`. Initialiser l'état au premier appel ; `exit` donne le résultat final. Les génériques décrivent sortie puis état intermédiaire. `DCommon.asyncLoop` attend les étapes asynchrones.
17
+
18
+ Conserver la condition avant le traitement d'un `while`, après celui d'un `do...while`. Pour une répétition par compteur, utiliser aussi `loop`, sans fabriquer un tableau.
19
+
20
+ Voir les [exemples typés](examples/iterations.ts), avec accumulateurs, sorties anticipées et variantes asynchrones.
@@ -1,59 +1,34 @@
1
- # Résultats typés et exceptions
1
+ # Résultats et erreurs
2
2
 
3
- ## Choisir selon la place de l'échec dans le traitement
3
+ ## Choisir le contrat
4
4
 
5
- Identifier les issues prévisibles que l'appelant doit pouvoir exploiter, puis les exprimer dans le contrat `Either`. Un refus peut concerner une règle métier, une vérification ou une manipulation technique. Une opération sur l'environnement ne justifie donc pas à elle seule une exception. Pour la distinction conceptuelle avec une défaillance de l'environnement, lire « Conversion des absences et exceptions en résultats » depuis le routeur du package installé.
5
+ Une fonction qui établit des états sans décider lesquels conviennent à l'appelant produit des `DEither.Result` nommés. Tous sont des `Right` ; les distinguer par leur information.
6
6
 
7
- Lorsqu'une fonction renvoie un `Either` dans une branche, toutes ses branches renvoient un `Either` : `DEither.Right` pour la réussite et `DEither.Left` pour le refus. Envelopper aussi une réussite sans valeur avec `DEither.ok()`, plutôt que retourner `undefined` à côté d'un `Left`. L'information identifie le cas ; la valeur associée transporte les détails utiles. Voir [une fonction qui propage les échecs de lecture](examples/error-propagation.ts).
7
+ Une fonction qui décide d'une réussite ou d'un refus prévisible les exprime en `Right` ou `Left`, y compris pour les absences et refus techniques. Toutes ses branches renvoient un `Either` ; `DEither.ok()` représente une réussite sans valeur.
8
8
 
9
- Le `Left` de lecture reste visible dans le type de retour. Une autre fonction peut le propager, l'adapter ou tenter une récupération. Une absence de ressource exploitable par l'algorithme ne devient pas une panne d'environnement parce qu'elle provient d'une I/O. Ne pas la masquer par un `throw` ou un unwrap qui lève.
9
+ ## Sélectionner et poursuivre
10
10
 
11
- `DEither.rightPipe` et `DEither.rightAsyncPipe` composent les étapes sur les valeurs de réussite et arrêtent au premier `Left`. `DEither.isRight` et `DEither.isLeft` permettent une discrimination explicite avant `unwrapRight` ou `unwrapLeft`.
12
-
13
- Pour une API qui lève sur un refus prévisible, adapter ses erreurs documentées en résultats. `DEither.safeCallback` et `DEither.asyncSafeCallback` capturent les exceptions ; examiner ensuite la cause pour distinguer le refus attendu d'une défaillance imprévue. Conserver une cause technique capturée dans le résultat jusqu'à sa consommation finale, sans la requalifier en refus métier ni la relancer depuis l'adaptation réutilisable. Préférer d'abord l'API DuploJS sûre si elle existe ; ne pas capturer systématiquement les exceptions des dépendances.
14
-
15
- ## Le consommateur final décide de l'effet
16
-
17
- La frontière d'exécution consomme le résultat après le retour des fonctions appelées et décide de sa conséquence finale. C'est à ce stade que le code applicatif peut écrire un `throw` ou utiliser `OrThrow` pour interrompre l'exécution. Déclarer une fonction au niveau d'un module ou l'appeler depuis un point d'entrée ne lui donne pas cette responsabilité.
18
-
19
- Exemples : initialisation du programme, callback d'une commande ou handler d'une route.
20
-
21
- ```text
22
- entrée externe
23
- → validation par les schémas du module
24
- → point d'entrée du traitement
25
- → fonction applicative
26
- → sous-fonction : renvoie les issues prévues en Right ou Left
27
- ← résultat typé
28
- ← résultat typé
29
- → le point d'entrée décide de l'effet final
30
- ```
31
-
32
- Les fonctions applicatives et leurs sous-fonctions renvoient les refus prévisibles dans leur contrat de retour, sans créer leurs propres `throw`. Les exceptions déjà émises par les dépendances ou le runtime remontent implicitement à travers ces appels ; cette propagation ne demande pas d'ajouter un `throw`.
33
-
34
- À cette frontière, interpréter le `Left` selon le contrat d'exécution : par exemple produire une réponse HTTP, un diagnostic de commande ou une exception adaptée. Le fait qu'un appelant ignore le retour d'une fonction ne suffit pas à autoriser cette fonction à lever une exception.
11
+ | Intention du consommateur | Outil |
12
+ | --- | --- |
13
+ | Autoriser certaines informations et refuser toutes les autres, même futures | `keepAsRightByInformation` |
14
+ | Décider explicitement du statut de chaque variante | `keepAsRightSelection` |
15
+ | Décider explicitement des variantes d'une garde | `isSelected` |
16
+ | Traiter chaque information | `matchInformation` |
35
17
 
36
- ## Choisir entre version sûre et `OrThrow`
18
+ Les sélecteurs exhaustifs imposent de revoir la décision lorsqu'une variante apparaît. Une sélection partielle ou un repli exprime au contraire un comportement commun voulu.
37
19
 
38
- Préférer une déclaration sûre pour une valeur connue. Pour une opération qui peut échouer, examiner les branches que `OrThrow` convertirait en exceptions avant de choisir cette variante.
20
+ Le reclassement conserve information et valeur. `unwrapRight` extrait les valeurs acceptées et conserve les `Left` : une garde `isLeft` peut ensuite retourner le refus. Si le contrat garantit uniquement des `Right` admis, l'extraction peut être directe. Voir [la sélection et le retour anticipé](examples/result-selection.ts).
39
21
 
40
- | Situation | Choix |
41
- | --- | --- |
42
- | Constante dont la validité est prouvable | `cast`, `infer`, helper de déclaration ou surcharge littérale sûre |
43
- | Vérification d'une donnée externe ou valeur runtime sans preuve | Validation ou fabrique sûre, puis traitement du `Either` |
44
- | Refus prévisible d'une fonction réutilisable, métier ou technique | Renvoyer, propager ou adapter le `Left` |
45
- | Exception de défaillance imprévue émise par une dépendance ou le runtime | La laisser remonter implicitement ; propager le résultat si elle a été capturée |
46
- | Point d'entrée décidant de l'effet final d'un résultat | Interpréter le `Left`, puis produire la réponse, la fin d'exécution ou l'exception adaptée |
47
- | Test affirmant un résultat attendu | `OrThrow` permet de faire échouer le test si le résultat diffère |
22
+ Pour sélectionner à une étape d'un `rightPipe` avant son déballage automatique, lire [Composition](composition.md#asynchronisme-et-résultats).
48
23
 
49
- La commodité de l'extraction, la rareté supposée d'un refus ou la complexité de propagation ne changent pas sa nature. Une variante qui lève sur une absence exploitable ne convient pas à une fonction réutilisable qui doit transmettre ce cas.
24
+ ## Propager ou adapter un refus
50
25
 
51
- ## Démarrage et tests : interpréter le résultat
26
+ Une fonction réutilisable transmet ses refus à l'appelant, qui peut les propager, les adapter ou récupérer. Une absence exploitable reste un résultat, même issue d'une I/O. Voir [la propagation d'une erreur de lecture](examples/error-propagation.ts).
52
27
 
53
- Au démarrage, l'initialisation peut décider qu'une configuration invalide empêche l'exécution. `environmentVariableOrThrow` valide d'abord par la version sûre, puis traduit son `Left` en exception. Cet usage exprime la décision finale du point d'entrée ; une fonction qui doit transmettre le refus utilise `environmentVariable`.
28
+ Préférer une API sûre. Si une API lève sur un refus documenté, `safeCallback` ou `asyncSafeCallback` permettent sa capture ; examiner la cause avant de la traduire. Une défaillance imprévue déjà levée remonte implicitement. Si elle est capturée, conserver sa cause dans le résultat jusqu'à la consommation finale, sans la requalifier en refus métier ni la relancer dans l'adaptation.
54
29
 
55
- Voir [la validation de l'environnement au démarrage](examples/environment-initialization.ts). Pour les entrées reçues pendant le fonctionnement du logiciel, conserver les résultats de validation jusqu'à leur interprétation par la frontière concernée.
30
+ ## Consommer le résultat final
56
31
 
57
- En tests, `OrThrow` peut extraire le résultat attendu ou préparer une fixture : une issue différente doit interrompre le test. Une déclaration sûre reste adaptée aux constantes connues, comme dans [les déclarations sûres](examples/safe-declarations.ts). Le code testé conserve ses résultats ; ces assertions ne justifient pas de le faire lever sur ses refus prévisibles.
32
+ Réserver `throw` et `OrThrow` au code qui décide de la conséquence finale : réponse, diagnostic ou interruption du programme. Une fonction appelée depuis cette frontière reste responsable de transmettre ses résultats.
58
33
 
59
- Pour approfondir, rechercher les `Either`, l'environnement ou le contexte d'exécution concerné dans `docs/concepts.llms.md`, depuis la racine AgentKit indiquée par les instructions du projet.
34
+ Au démarrage, une configuration invalide peut empêcher l'exécution : voir [l'initialisation](examples/environment-initialization.ts). Dans un test, `OrThrow` peut affirmer un résultat ou préparer une fixture ; le code testé conserve ses refus typés. Pour les constantes prouvables, préférer les [déclarations sûres](examples/safe-declarations.ts).
@@ -1,63 +0,0 @@
1
- /**
2
- * @title DObject.deepDiscriminate
3
- *
4
- * Discriminer une union d’objets à partir d’une propriété imbriquée.
5
- */
6
- import * as DObject from "@duplojs/lang/object";
7
- import * as DCommon from "@duplojs/lang/common";
8
-
9
- type Event =
10
- | {
11
- profile: {
12
- role: "admin";
13
- name: string;
14
- };
15
- }
16
- | {
17
- profile: {
18
- role: "member";
19
- name: string;
20
- };
21
- };
22
- interface AdminEvent {
23
- profile: {
24
- role: "admin";
25
- name: string;
26
- };
27
- }
28
-
29
- interface MemberEvent {
30
- profile: {
31
- role: "member";
32
- name: string;
33
- };
34
- }
35
-
36
- declare const event: Event;
37
-
38
- if (DObject.deepDiscriminate(event, "profile.role", "admin")) {
39
- type _CheckIfEvent = DCommon.ExpectType<typeof event, AdminEvent, "strict">;
40
- const name = DObject.getDeepProperty(event, "profile.name");
41
- type _CheckIfName = DCommon.ExpectType<typeof name, string, "strict">;
42
- }
43
-
44
- const result = DObject.deepDiscriminate(event, "profile.role", "admin");
45
-
46
- // Le predicate retourne true uniquement pour l’événement administrateur.
47
- type _CheckResult = DCommon.ExpectType<typeof result, boolean, "strict">;
48
-
49
- // La forme curifiée est passée directement comme predicate à DCommon.when.
50
- const name = DCommon.pipe(
51
- event,
52
- DCommon.when(
53
- DObject.deepDiscriminate("profile.role", "admin"),
54
- (adminEvent) => {
55
- type _CheckPredicatedInput = DCommon.ExpectType<typeof adminEvent, AdminEvent, "strict">;
56
-
57
- return DObject.getDeepProperty(adminEvent, "profile.name");
58
- },
59
- ),
60
- );
61
-
62
- // Le nom est lu pour l’administrateur ; sinon l’événement initial est conservé.
63
- type _CheckName = DCommon.ExpectType<typeof name, string | MemberEvent, "strict">;