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

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.
Files changed (100) hide show
  1. package/README.md +17 -9
  2. package/dist/bin/index.mjs +2 -2
  3. package/dist/bin/skills/index.d.ts +5 -0
  4. package/dist/bin/skills/index.mjs +11 -0
  5. package/dist/bin/{installSkill/index.d.ts → skills/install.d.ts} +1 -1
  6. package/dist/bin/{installSkill/index.mjs → skills/install.mjs} +2 -2
  7. package/dist/bin/{installSkill → skills}/skill.d.ts +7 -0
  8. package/dist/bin/{installSkill → skills}/skill.mjs +10 -8
  9. package/dist/bin/skills/update.d.ts +2 -0
  10. package/dist/bin/skills/update.mjs +19 -0
  11. package/docs/API/10-lang/10-common/10-asyncLoop.ts +4 -6
  12. package/docs/API/10-lang/10-common/10-asyncLoopAccumulator.ts +40 -0
  13. package/docs/API/10-lang/10-common/10-loop.ts +5 -7
  14. package/docs/API/10-lang/10-common/10-loopAccumulator.ts +35 -0
  15. package/docs/API/10-lang/10-common/10-loopAccumulatorFrom.ts +29 -0
  16. package/docs/API/10-lang/10-common/index.llms.md +14 -2
  17. package/docs/API/10-lang/10-generator/10-asyncLoop.ts +11 -20
  18. package/docs/API/10-lang/10-generator/10-asyncLoopAccumulator.ts +40 -0
  19. package/docs/API/10-lang/10-generator/10-loop.ts +11 -20
  20. package/docs/API/10-lang/10-generator/10-loopAccumulator.ts +33 -0
  21. package/docs/API/10-lang/10-generator/10-loopAccumulatorFrom.ts +30 -0
  22. package/docs/API/10-lang/10-generator/index.llms.md +14 -2
  23. package/docs/API/10-lang/10-object/10-discriminate.ts +84 -37
  24. package/docs/API/10-lang/10-object/index.llms.md +1 -5
  25. package/docs/API/20-server/10-file/10-appendFile.ts +50 -0
  26. package/docs/API/20-server/10-file/10-appendTextFile.ts +49 -0
  27. package/docs/API/20-server/10-file/10-copy.ts +50 -0
  28. package/docs/API/20-server/10-file/10-createEntryInterface.ts +13 -0
  29. package/docs/API/20-server/10-file/10-createFileInterface.ts +22 -0
  30. package/docs/API/20-server/10-file/10-createFolderInterface.ts +20 -0
  31. package/docs/API/20-server/10-file/10-createUnknownEntryInterface.ts +13 -0
  32. package/docs/API/20-server/10-file/10-ensureDirectory.ts +39 -0
  33. package/docs/API/20-server/10-file/10-ensureFile.ts +40 -0
  34. package/docs/API/20-server/10-file/10-exists.ts +36 -0
  35. package/docs/API/20-server/10-file/10-link.ts +51 -0
  36. package/docs/API/20-server/10-file/10-linkStat.ts +37 -0
  37. package/docs/API/20-server/10-file/10-makeDirectory.ts +41 -0
  38. package/docs/API/20-server/10-file/10-makeTemporaryDirectory.ts +35 -0
  39. package/docs/API/20-server/10-file/10-makeTemporaryFile.ts +40 -0
  40. package/docs/API/20-server/10-file/10-move.ts +51 -0
  41. package/docs/API/20-server/10-file/10-readDirectory.ts +52 -0
  42. package/docs/API/20-server/10-file/10-readFile.ts +38 -0
  43. package/docs/API/20-server/10-file/10-readJsonFile.ts +38 -0
  44. package/docs/API/20-server/10-file/10-readLink.ts +37 -0
  45. package/docs/API/20-server/10-file/10-readTextFile.ts +38 -0
  46. package/docs/API/20-server/10-file/10-realPath.ts +36 -0
  47. package/docs/API/20-server/10-file/10-relocate.ts +51 -0
  48. package/docs/API/20-server/10-file/10-remove.ts +40 -0
  49. package/docs/API/20-server/10-file/10-rename.ts +43 -0
  50. package/docs/API/20-server/10-file/10-setMode.ts +37 -0
  51. package/docs/API/20-server/10-file/10-setOwner.ts +38 -0
  52. package/docs/API/20-server/10-file/10-setTime.ts +42 -0
  53. package/docs/API/20-server/10-file/10-stat.ts +37 -0
  54. package/docs/API/20-server/10-file/10-symlink.ts +49 -0
  55. package/docs/API/20-server/10-file/10-truncate.ts +41 -0
  56. package/docs/API/20-server/10-file/10-walkDirectory.ts +75 -0
  57. package/docs/API/20-server/10-file/10-writeFile.ts +50 -0
  58. package/docs/API/20-server/10-file/10-writeJsonFile.ts +41 -0
  59. package/docs/API/20-server/10-file/10-writeTextFile.ts +49 -0
  60. package/docs/API/20-server/10-file/index.llms.md +144 -0
  61. package/docs/API/20-server/10-file/index.md +3 -0
  62. package/docs/API/20-server/20-common/10-environmentVariable.ts +57 -0
  63. package/docs/API/20-server/20-common/10-environmentVariableOrThrow.ts +21 -0
  64. package/docs/API/20-server/20-common/10-exitProcess.ts +9 -0
  65. package/docs/API/20-server/20-common/10-getCurrentDirectoryName.ts +10 -0
  66. package/docs/API/20-server/20-common/10-getCurrentWorkDirectory.ts +10 -0
  67. package/docs/API/20-server/20-common/10-getCurrentWorkDirectoryOrThrow.ts +10 -0
  68. package/docs/API/20-server/20-common/10-getProcessArguments.ts +10 -0
  69. package/docs/API/20-server/20-common/10-setCurrentWorkingDirectory.ts +14 -0
  70. package/docs/API/20-server/20-common/10-setCurrentWorkingDirectoryOrThrow.ts +12 -0
  71. package/docs/API/20-server/20-common/index.llms.md +40 -0
  72. package/docs/API/20-server/20-common/index.md +3 -0
  73. package/docs/API/20-server/30-dataStructure/10-file.ts +14 -0
  74. package/docs/API/20-server/30-dataStructure/10-fileExist.ts +27 -0
  75. package/docs/API/20-server/30-dataStructure/10-folder.ts +14 -0
  76. package/docs/API/20-server/30-dataStructure/10-folderExist.ts +14 -0
  77. package/docs/API/20-server/30-dataStructure/10-mimeType.ts +14 -0
  78. package/docs/API/20-server/30-dataStructure/10-size.ts +32 -0
  79. package/docs/API/20-server/30-dataStructure/index.llms.md +28 -0
  80. package/docs/API/20-server/30-dataStructure/index.md +3 -0
  81. package/docs/API/20-server/40-command/10-create.ts +39 -0
  82. package/docs/API/20-server/40-command/10-createArgument.ts +39 -0
  83. package/docs/API/20-server/40-command/10-createArrayOption.ts +36 -0
  84. package/docs/API/20-server/40-command/10-createBooleanOption.ts +28 -0
  85. package/docs/API/20-server/40-command/10-createOption.ts +46 -0
  86. package/docs/API/20-server/40-command/10-exec.ts +51 -0
  87. package/docs/API/20-server/40-command/10-execOptions.ts +30 -0
  88. package/docs/API/20-server/40-command/index.llms.md +32 -0
  89. package/docs/API/20-server/40-command/index.md +3 -0
  90. package/docs/API/20-server/index.md +3 -0
  91. package/docs/API.llms.md +20 -0
  92. package/package.json +14 -14
  93. package/skills/fundamental/SKILL.md +33 -33
  94. package/skills/fundamental/references/composition.md +9 -25
  95. package/skills/fundamental/references/constraints-and-validation.md +14 -51
  96. package/skills/fundamental/references/examples/iterations.ts +112 -0
  97. package/skills/fundamental/references/examples/result-selection.ts +65 -0
  98. package/skills/fundamental/references/iterations.md +24 -0
  99. package/skills/fundamental/references/results-and-errors.md +20 -45
  100. package/docs/API/10-lang/10-object/10-deepDiscriminate.ts +0 -63
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,25 +1,23 @@
1
1
  /**
2
2
  * @title DCommon.asyncLoop
3
3
  *
4
- * Contrôler une boucle asynchrone avec un compteur et une sortie typée.
4
+ * Répéter une callback asynchrone jusqu’à `exit`, avec un compteur et une valeur de retour typée.
5
5
  */
6
6
  import * as DCommon from "@duplojs/lang/common";
7
7
 
8
- const result = DCommon.asyncLoop<"done", number>(async({
8
+ const result = DCommon.asyncLoop<"done">(async({
9
9
  count,
10
- previousOutput,
11
10
  next,
12
11
  exit,
13
12
  }) => {
14
- const current = previousOutput ?? 0;
15
13
  await DCommon.timeout();
16
14
 
17
15
  if (count === 3) {
18
16
  return exit("done");
19
17
  }
20
18
 
21
- return next(current + 1);
19
+ return next();
22
20
  });
23
21
 
24
- // La callback est attendue asynchrone, mais conserve les mêmes paramètres que `loop`.
22
+ // La callback est attendue à chaque tour. `next` ne transmet pas d’état à l’itération suivante.
25
23
  type _CheckResult = DCommon.ExpectType<typeof result, Promise<"done">, "strict">;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @title DCommon.asyncLoopAccumulator
3
+ *
4
+ * Répéter une callback asynchrone en faisant circuler un accumulateur et retourner la valeur de `exit`.
5
+ */
6
+ import * as DCommon from "@duplojs/lang/common";
7
+
8
+ const total = await DCommon.asyncLoopAccumulator(0, async({ accumulator, count, next, exit }) => {
9
+ await DCommon.timeout();
10
+
11
+ if (count === 3) {
12
+ return exit(accumulator);
13
+ }
14
+
15
+ return next(accumulator + count + 1);
16
+ });
17
+
18
+ // La callback est attendue à chaque tour et le résultat final est celui de `exit`.
19
+ type _CheckTotal = DCommon.ExpectType<typeof total, number, "strict">;
20
+
21
+ interface Accumulator {
22
+ readonly total: number;
23
+ readonly previous?: number;
24
+ }
25
+
26
+ const structuredTotal = await DCommon.asyncLoopAccumulator(
27
+ DCommon.loopAccumulatorFrom<Accumulator>({ total: 0 }),
28
+ async({ accumulator, count, next, exit }) => {
29
+ await DCommon.timeout();
30
+
31
+ return count === 2
32
+ ? exit(accumulator.total)
33
+ : next({
34
+ total: accumulator.total + 1,
35
+ previous: accumulator.total,
36
+ });
37
+ },
38
+ );
39
+
40
+ type _CheckStructuredTotal = DCommon.ExpectType<typeof structuredTotal, number, "strict">;
@@ -1,20 +1,18 @@
1
1
  /**
2
2
  * @title DCommon.loop
3
3
  *
4
- * Contrôler une boucle synchrone avec un compteur et une sortie typée.
4
+ * Répéter une callback synchrone jusqu’à `exit`, avec un compteur et une valeur de retour typée.
5
5
  */
6
6
  import * as DCommon from "@duplojs/lang/common";
7
7
 
8
- const result = DCommon.loop<"done", number>(({ count, previousOutput, next, exit }) => {
9
- const current = previousOutput ?? 0;
10
-
8
+ const result = DCommon.loop<"done">(({ count, next, exit }) => {
11
9
  if (count === 3) {
12
10
  return exit("done");
13
11
  }
14
12
 
15
- return next(current + 1);
13
+ return next();
16
14
  });
17
15
 
18
- // `count` commence à 0 ; `previousOutput` vaut undefined à la première itération.
19
- // `next` poursuit la boucle avec une nouvelle sortie, tandis que `exit` la termine.
16
+ // `count` commence à 0. `next()` poursuit la boucle ; les valeurs d'une itération à l'autre
17
+ // doivent être portées par loopAccumulator, car `loop` ne conserve pas les données de `next`.
20
18
  type _CheckResult = DCommon.ExpectType<typeof result, "done", "strict">;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @title DCommon.loopAccumulator
3
+ *
4
+ * Répéter une callback synchrone en faisant circuler un accumulateur et retourner la valeur de `exit`.
5
+ */
6
+ import * as DCommon from "@duplojs/lang/common";
7
+
8
+ const total = DCommon.loopAccumulator(0, ({ accumulator, count, next, exit }) => {
9
+ if (count === 3) {
10
+ return exit(accumulator);
11
+ }
12
+
13
+ return next(accumulator + count + 1);
14
+ });
15
+
16
+ // Le nombre initial est élargi en number ; chaque `next` fournit l’accumulateur du tour suivant.
17
+ type _CheckTotal = DCommon.ExpectType<typeof total, number, "strict">;
18
+
19
+ interface Accumulator {
20
+ readonly total: number;
21
+ readonly previous?: number;
22
+ }
23
+
24
+ const structuredTotal = DCommon.loopAccumulator(
25
+ DCommon.loopAccumulatorFrom<Accumulator>({ total: 0 }),
26
+ ({ accumulator, count, next, exit }) => count === 2
27
+ ? exit(accumulator.total)
28
+ : next({
29
+ total: accumulator.total + 1,
30
+ previous: accumulator.total,
31
+ }),
32
+ );
33
+
34
+ // `loopAccumulatorFrom` conserve le type choisi pour un accumulateur structuré.
35
+ type _CheckStructuredTotal = DCommon.ExpectType<typeof structuredTotal, number, "strict">;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @title DCommon.loopAccumulatorFrom
3
+ *
4
+ * Marquer une valeur initiale pour fixer précisément le type de l’accumulateur d’une boucle.
5
+ */
6
+ import * as DCommon from "@duplojs/lang/common";
7
+
8
+ interface Accumulator {
9
+ readonly total: number;
10
+ readonly previous?: number;
11
+ }
12
+
13
+ const initialAccumulator = DCommon.loopAccumulatorFrom<Accumulator>({ total: 0 });
14
+
15
+ type _CheckInitialAccumulator = DCommon.ExpectType<
16
+ typeof initialAccumulator,
17
+ DCommon.LoopAccumulatorFromResult<Accumulator>,
18
+ "strict"
19
+ >;
20
+
21
+ const result = DCommon.loopAccumulator(initialAccumulator, ({ accumulator, count, next, exit }) => count === 2
22
+ ? exit(accumulator.total)
23
+ : next({
24
+ total: accumulator.total + 1,
25
+ previous: accumulator.total,
26
+ }));
27
+
28
+ // La boucle reçoit Accumulator comme état et retourne le nombre fourni à `exit`.
29
+ type _CheckResult = DCommon.ExpectType<typeof result, number, "strict">;
@@ -16,7 +16,11 @@ Construire une fonction qui enchaîne des opérations synchrones ou asynchrones.
16
16
 
17
17
  ### [DCommon.asyncLoop](10-asyncLoop.ts)
18
18
 
19
- Contrôler une boucle asynchrone avec un compteur et une sortie typée.
19
+ Répéter une callback asynchrone jusqu’à `exit`, avec un compteur et une valeur de retour typée.
20
+
21
+ ### [DCommon.asyncLoopAccumulator](10-asyncLoopAccumulator.ts)
22
+
23
+ Répéter une callback asynchrone en faisant circuler un accumulateur et retourner la valeur de `exit`.
20
24
 
21
25
  ### [DCommon.asyncPipe](10-asyncPipe.ts)
22
26
 
@@ -120,7 +124,15 @@ Fournir une valeur déjà calculée à une étape de pipe sans écrire de callba
120
124
 
121
125
  ### [DCommon.loop](10-loop.ts)
122
126
 
123
- Contrôler une boucle synchrone avec un compteur et une sortie typée.
127
+ Répéter une callback synchrone jusqu’à `exit`, avec un compteur et une valeur de retour typée.
128
+
129
+ ### [DCommon.loopAccumulator](10-loopAccumulator.ts)
130
+
131
+ Répéter une callback synchrone en faisant circuler un accumulateur et retourner la valeur de `exit`.
132
+
133
+ ### [DCommon.loopAccumulatorFrom](10-loopAccumulatorFrom.ts)
134
+
135
+ Marquer une valeur initiale pour fixer précisément le type de l’accumulateur d’une boucle.
124
136
 
125
137
  ### [DCommon.memo](10-memo.ts)
126
138
 
@@ -1,57 +1,48 @@
1
1
  /**
2
2
  * @title DGenerator.asyncLoop
3
3
  *
4
- * Produire une séquence asynchrone dans un pipe en distinguant l’arrêt avec ou sans dernière valeur émise.
4
+ * Produire une séquence asynchrone en distinguant les valeurs de `next` et celles de la sortie `exit`.
5
5
  */
6
6
  import * as DArray from "@duplojs/lang/array";
7
7
  import * as DCommon from "@duplojs/lang/common";
8
8
  import * as DGenerator from "@duplojs/lang/generator";
9
- import * as DNumber from "@duplojs/lang/number";
10
9
 
11
- // Arrêt sans dernière valeur : `exit()` termine la boucle asynchrone sans émettre d’élément supplémentaire.
10
+ // La callback peut être asynchrone ; `count` est le seul état fourni à chaque tour.
12
11
  const withoutExitValue = DCommon.pipe(
13
- DGenerator.asyncLoop(async({ previousOutput, next, exit }: DGenerator.LoopParams<number>) => {
12
+ DGenerator.asyncLoop(async({ count, next, exit }: DGenerator.LoopParams<number>) => {
14
13
  await DCommon.timeout();
15
14
 
16
- if (previousOutput === 3) {
15
+ if (count === 3) {
17
16
  return exit();
18
17
  }
19
18
 
20
- return next(
21
- previousOutput === undefined
22
- ? 1
23
- : DNumber.sum([previousOutput, 1]),
24
- );
19
+ return next(count + 1);
25
20
  }),
26
21
  DArray.from,
27
22
  );
28
23
 
29
- // La promesse se résout vers [1, 2, 3] : `exit()` n’émet aucune dernière valeur.
24
+ // La promesse se résout vers [1, 2, 3] : `exit()` termine sans ajouter de valeur.
30
25
  type _CheckWithoutExitValue = DCommon.ExpectType<
31
26
  typeof withoutExitValue,
32
27
  Promise<readonly number[]>,
33
28
  "strict"
34
29
  >;
35
30
 
36
- // Arrêt avec dernière valeur : `exit("done")` termine la boucle en l’émettant.
31
+ // La callback est attendue à chaque tour ; les sorties sont émises avant l’appel du tour suivant.
37
32
  const withExitValue = DCommon.pipe(
38
- DGenerator.asyncLoop(async({ previousOutput, next, exit }: DGenerator.LoopParams<number>) => {
33
+ DGenerator.asyncLoop(async({ count, next, exit }: DGenerator.LoopParams<number | "done">) => {
39
34
  await DCommon.timeout();
40
35
 
41
- if (previousOutput === 3) {
36
+ if (count === 3) {
42
37
  return exit("done");
43
38
  }
44
39
 
45
- return next(
46
- previousOutput === undefined
47
- ? 1
48
- : DNumber.sum([previousOutput, 1]),
49
- );
40
+ return next(count + 1);
50
41
  }),
51
42
  DArray.from,
52
43
  );
53
44
 
54
- // La promesse se résout vers [1, 2, 3, "done"] : la valeur de `exit` est émise comme dernier élément.
45
+ // La promesse se résout vers [1, 2, 3, "done"] : `exit` ajoute sa valeur en dernier.
55
46
  type _CheckWithExitValue = DCommon.ExpectType<
56
47
  typeof withExitValue,
57
48
  Promise<readonly (number | "done")[]>,
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @title DGenerator.asyncLoopAccumulator
3
+ *
4
+ * Produire une séquence asynchrone en mettant à jour un accumulateur à chaque itération.
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
+
10
+ const values = await DArray.from(DGenerator.asyncLoopAccumulator(0, async({ accumulator, count, next, exit }) => {
11
+ await DCommon.timeout();
12
+
13
+ return count === 3
14
+ ? exit(Promise.resolve(accumulator))
15
+ : next(accumulator + 1, Promise.resolve(accumulator));
16
+ }));
17
+
18
+ // La callback et les valeurs émises peuvent être asynchrones ; les sorties sont résolues avant émission.
19
+ type _CheckValues = DCommon.ExpectType<typeof values, readonly number[], "strict">;
20
+
21
+ interface Accumulator {
22
+ readonly total: number;
23
+ readonly previous?: number;
24
+ }
25
+
26
+ const finalTotal = await DArray.from(DGenerator.asyncLoopAccumulator(
27
+ DGenerator.loopAccumulatorFrom<Accumulator>({ total: 0 }),
28
+ async({ accumulator, count, next, exit }) => {
29
+ await DCommon.timeout();
30
+
31
+ return count === 2
32
+ ? exit(Promise.resolve(accumulator.total))
33
+ : next({
34
+ total: accumulator.total + 1,
35
+ previous: accumulator.total,
36
+ }, Promise.resolve(accumulator.total));
37
+ },
38
+ ));
39
+
40
+ type _CheckFinalTotal = DCommon.ExpectType<typeof finalTotal, readonly number[], "strict">;
@@ -1,53 +1,44 @@
1
1
  /**
2
2
  * @title DGenerator.loop
3
3
  *
4
- * Produire une séquence dans un pipe en distinguant l’arrêt avec ou sans dernière valeur émise.
4
+ * Produire une séquence synchrone en distinguant les valeurs de `next` et celles de la sortie `exit`.
5
5
  */
6
6
  import * as DArray from "@duplojs/lang/array";
7
7
  import * as DCommon from "@duplojs/lang/common";
8
8
  import * as DGenerator from "@duplojs/lang/generator";
9
- import * as DNumber from "@duplojs/lang/number";
10
9
 
11
- // Arrêt sans dernière valeur : `exit()` termine la boucle sans émettre d’élément supplémentaire.
10
+ // `count` est le seul état fourni à chaque tour ; `next` émet des valeurs et continue.
12
11
  const withoutExitValue = DCommon.pipe(
13
- DGenerator.loop(({ previousOutput, next, exit }: DGenerator.LoopParams<number>) => {
14
- if (previousOutput === 3) {
12
+ DGenerator.loop(({ count, next, exit }: DGenerator.LoopParams<number>) => {
13
+ if (count === 3) {
15
14
  return exit();
16
15
  }
17
16
 
18
- return next(
19
- previousOutput === undefined
20
- ? 1
21
- : DNumber.sum([previousOutput, 1]),
22
- );
17
+ return next(count + 1);
23
18
  }),
24
19
  DArray.from,
25
20
  );
26
21
 
27
- // La sortie vaut [1, 2, 3] : `next` émet les valeurs intermédiaires, mais `exit()` n’en émet aucune.
22
+ // La sortie vaut [1, 2, 3] : `exit()` termine sans ajouter de valeur.
28
23
  type _CheckWithoutExitValue = DCommon.ExpectType<
29
24
  typeof withoutExitValue,
30
25
  readonly number[],
31
26
  "strict"
32
27
  >;
33
28
 
34
- // Arrêt avec dernière valeur : `exit(value)` termine la boucle en émettant cette valeur.
29
+ // `exit(value)` termine la boucle et émet sa valeur après celles émises par `next`.
35
30
  const withExitValue = DCommon.pipe(
36
- DGenerator.loop(({ previousOutput, next, exit }: DGenerator.LoopParams<number>) => {
37
- if (previousOutput === 3) {
31
+ DGenerator.loop(({ count, next, exit }: DGenerator.LoopParams<number>) => {
32
+ if (count === 3) {
38
33
  return exit(4);
39
34
  }
40
35
 
41
- return next(
42
- previousOutput === undefined
43
- ? 1
44
- : DNumber.sum([previousOutput, 1]),
45
- );
36
+ return next(count + 1);
46
37
  }),
47
38
  DArray.from,
48
39
  );
49
40
 
50
- // La sortie vaut [1, 2, 3, 4] : `exit(4)` ajoute la dernière valeur à la séquence.
41
+ // La sortie vaut [1, 2, 3, 4] : les valeurs multiples de `next` et `exit` forment la séquence.
51
42
  type _CheckWithExitValue = DCommon.ExpectType<
52
43
  typeof withExitValue,
53
44
  readonly number[],
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @title DGenerator.loopAccumulator
3
+ *
4
+ * Produire une séquence synchrone en mettant à jour un accumulateur à chaque itération.
5
+ */
6
+ import * as DArray from "@duplojs/lang/array";
7
+ import type * as DCommon from "@duplojs/lang/common";
8
+ import * as DGenerator from "@duplojs/lang/generator";
9
+
10
+ const values = DArray.from(DGenerator.loopAccumulator(0, ({ accumulator, count, next, exit }) => count === 3
11
+ ? exit(accumulator)
12
+ : next(accumulator + 1, accumulator)));
13
+
14
+ // `next` reçoit d’abord le nouvel accumulateur, puis émet les valeurs suivantes.
15
+ // La valeur de `exit` est également émise avant que le générateur se termine.
16
+ type _CheckValues = DCommon.ExpectType<typeof values, readonly number[], "strict">;
17
+
18
+ interface Accumulator {
19
+ readonly total: number;
20
+ readonly previous?: number;
21
+ }
22
+
23
+ const finalTotal = DArray.from(DGenerator.loopAccumulator(
24
+ DGenerator.loopAccumulatorFrom<Accumulator>({ total: 0 }),
25
+ ({ accumulator, count, next, exit }) => count === 2
26
+ ? exit(accumulator.total)
27
+ : next({
28
+ total: accumulator.total + 1,
29
+ previous: accumulator.total,
30
+ }, accumulator.total),
31
+ ));
32
+
33
+ type _CheckFinalTotal = DCommon.ExpectType<typeof finalTotal, readonly number[], "strict">;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @title DGenerator.loopAccumulatorFrom
3
+ *
4
+ * Marquer une valeur initiale pour fixer précisément le type de l’accumulateur d’une boucle.
5
+ */
6
+ import type * as DCommon from "@duplojs/lang/common";
7
+ import * as DGenerator from "@duplojs/lang/generator";
8
+
9
+ interface Accumulator {
10
+ readonly total: number;
11
+ readonly previous?: number;
12
+ }
13
+
14
+ const initialAccumulator = DGenerator.loopAccumulatorFrom<Accumulator>({ total: 0 });
15
+
16
+ type _CheckInitialAccumulator = DCommon.ExpectType<
17
+ typeof initialAccumulator,
18
+ DGenerator.LoopAccumulatorFromResult<Accumulator>,
19
+ "strict"
20
+ >;
21
+
22
+ const result = DGenerator.loopAccumulator(initialAccumulator, ({ accumulator, count, next, exit }) => count === 2
23
+ ? exit(accumulator.total)
24
+ : next({
25
+ total: accumulator.total + 1,
26
+ previous: accumulator.total,
27
+ }, accumulator.total));
28
+
29
+ // La boucle reçoit Accumulator comme état, tout en produisant sa valeur de sortie dans le générateur.
30
+ type _CheckResult = DCommon.ExpectType<typeof result, Generator<number, unknown, unknown>, "strict">;