@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 +17 -9
- package/dist/bin/index.mjs +2 -2
- package/dist/bin/skills/index.d.ts +5 -0
- package/dist/bin/skills/index.mjs +11 -0
- package/dist/bin/{installSkill/index.d.ts → skills/install.d.ts} +1 -1
- package/dist/bin/{installSkill/index.mjs → skills/install.mjs} +2 -2
- package/dist/bin/{installSkill → skills}/skill.d.ts +7 -0
- package/dist/bin/{installSkill → skills}/skill.mjs +10 -8
- package/dist/bin/skills/update.d.ts +2 -0
- package/dist/bin/skills/update.mjs +19 -0
- package/docs/API/10-lang/10-object/10-discriminate.ts +84 -37
- package/docs/API/10-lang/10-object/index.llms.md +1 -5
- package/package.json +14 -14
- package/skills/fundamental/SKILL.md +31 -33
- package/skills/fundamental/references/composition.md +9 -25
- package/skills/fundamental/references/constraints-and-validation.md +14 -51
- package/skills/fundamental/references/examples/iterations.ts +99 -0
- package/skills/fundamental/references/examples/result-selection.ts +65 -0
- package/skills/fundamental/references/iterations.md +20 -0
- package/skills/fundamental/references/results-and-errors.md +20 -45
- 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
|
|
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
|
|
23
|
-
pnpm exec duplojs-agents-kit install
|
|
24
|
-
pnpm exec duplojs-agents-kit install
|
|
25
|
-
pnpm exec duplojs-agents-kit install
|
|
26
|
-
pnpm exec duplojs-agents-kit install
|
|
27
|
-
pnpm exec duplojs-agents-kit install
|
|
28
|
-
pnpm exec duplojs-agents-kit install
|
|
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
|
|
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
|
|
package/dist/bin/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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: [
|
|
6
|
+
await DSCommand.exec({ subjects: [skillsCommand, setupCommand] }, () => {});
|
|
7
7
|
//#endregion
|
|
@@ -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
|
|
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/
|
|
11
|
-
const installSkillCommand = DSCommand.create("install
|
|
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/
|
|
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(
|
|
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,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
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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("
|
|
46
|
-
(
|
|
47
|
-
type
|
|
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
|
|
47
|
+
return value.value;
|
|
50
48
|
},
|
|
51
49
|
),
|
|
52
50
|
);
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
36
|
-
"@duplojs/server": "0.1.0-beta.
|
|
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.
|
|
40
|
-
"@duplojs/eslint": "0.1.0-beta.
|
|
41
|
-
"@duplojs/form": "0.1.0-beta.
|
|
42
|
-
"@duplojs/http": "0.1.0-beta.
|
|
43
|
-
"@duplojs/json-web-token": "0.1.0-beta.
|
|
44
|
-
"@duplojs/lang": "0.1.0-beta.
|
|
45
|
-
"@duplojs/playwright": "0.1.0-beta.
|
|
46
|
-
"@duplojs/server": "0.1.0-beta.
|
|
47
|
-
"@duplojs/tools": "0.1.0-beta.
|
|
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.
|
|
74
|
-
"@duplojs/eslint": "0.1.0-beta.
|
|
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
|
|
20
|
+
## Utiliser les API DuploJS
|
|
21
21
|
|
|
22
|
-
Utiliser obligatoirement
|
|
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
|
-
-
|
|
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
|
-
|
|
26
|
+
## Modifier à l'endroit responsable
|
|
29
27
|
|
|
30
|
-
|
|
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
|
-
|
|
30
|
+
Privilégier fonctions pures, immutabilité et transformations explicites ; isoler les interactions avec l'environnement.
|
|
33
31
|
|
|
34
|
-
##
|
|
32
|
+
## Laisser les types s'inférer
|
|
35
33
|
|
|
36
|
-
|
|
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
|
-
|
|
36
|
+
## Composer les transformations
|
|
39
37
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
## Remplacer les boucles impératives
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
Ne pas écrire de `for`, de `while` ni leurs variantes. Utiliser :
|
|
49
47
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
58
|
+
Choisir selon la preuve disponible :
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
+
## Représenter les résultats et propager les erreurs
|
|
69
67
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
#
|
|
1
|
+
# Composition
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Configurer dans le contexte du pipe
|
|
4
4
|
|
|
5
|
-
La forme directe reçoit
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1
|
+
# Contraintes et validation
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Établir la garantie
|
|
4
4
|
|
|
5
|
-
Une contrainte exprime
|
|
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
|
-
|
|
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
|
-
##
|
|
9
|
+
## Choisir la construction
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
## Exploiter les garanties restantes
|
|
18
21
|
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
1
|
+
# Résultats et erreurs
|
|
2
2
|
|
|
3
|
-
## Choisir
|
|
3
|
+
## Choisir le contrat
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
## Sélectionner et poursuivre
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
## Propager ou adapter un refus
|
|
50
25
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
## Consommer le résultat final
|
|
56
31
|
|
|
57
|
-
|
|
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
|
-
|
|
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">;
|