codascon 2026.3.1-alpha
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/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/index.d.ts +511 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +237 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 @scorpevans
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# codascon
|
|
2
|
+
|
|
3
|
+
**A structural protocol for code organization with exhaustive compile-time type checking.**
|
|
4
|
+
|
|
5
|
+
**Codascon** distills high-level design patterns and SOLID principles into a zero-overhead TypeScript protocol. You describe what your domain looks like — which entities exist, which operations apply to them, and which strategies handle each combination — and your architectural intent is guarded with mathematical certainty. If a single edge case is unhandled, the compiler won't just warn you — it will stop you.
|
|
6
|
+
|
|
7
|
+
_The Runtime:_ 10 lines of code.
|
|
8
|
+
|
|
9
|
+
_The Power:_ Pure type-level enforcement via `Subject`, `Command`, `Template`, and `Strategy`.
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
When you have _N_ entity types and _M_ operations, the naive approach produces N×M branching logic scattered across your codebase. Add a new entity type and you must hunt down every `switch` and `instanceof` check. Miss one and you get a silent runtime bug.
|
|
14
|
+
|
|
15
|
+
**Codascon** makes that impossible. If you add a `Subject` and forget to handle it in any `Command`, your code doesn't compile.
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
command.run(subject, object)
|
|
21
|
+
→ subject.getCommandStrategy(command, object) // double dispatch
|
|
22
|
+
→ command[subject.visitName](subject, object) // visit method selects strategy
|
|
23
|
+
→ returns a Template // the chosen strategy
|
|
24
|
+
→ template.execute(subject, object) // strategy executes
|
|
25
|
+
→ returns result
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A **`Subject`** is an entity (`Student`, `Professor`, `Visitor`). A **`Command`** is an operation (`AccessBuilding`, `CheckoutEquipment`). Each `Command` declares one visit method per `Subject` — the visit method inspects the `Subject` and the context, then returns a **`Template`** (strategy) to execute. The `Template` may declare **hooks** — references to other `Command`s it invokes during execution.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install codascon
|
|
34
|
+
# or
|
|
35
|
+
pnpm add codascon
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Define Subjects
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { Subject } from "codascon";
|
|
44
|
+
|
|
45
|
+
class Student extends Subject {
|
|
46
|
+
readonly visitName = "resolveStudent" as const;
|
|
47
|
+
constructor(
|
|
48
|
+
public readonly name: string,
|
|
49
|
+
public readonly department: string,
|
|
50
|
+
public readonly year: 1 | 2 | 3 | 4,
|
|
51
|
+
) {
|
|
52
|
+
super();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class Professor extends Subject {
|
|
57
|
+
readonly visitName = "resolveProfessor" as const;
|
|
58
|
+
constructor(
|
|
59
|
+
public readonly name: string,
|
|
60
|
+
public readonly tenured: boolean,
|
|
61
|
+
) {
|
|
62
|
+
super();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Define a Command with Templates
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Command } from "codascon";
|
|
71
|
+
import type { Template } from "codascon";
|
|
72
|
+
|
|
73
|
+
interface Building {
|
|
74
|
+
name: string;
|
|
75
|
+
department: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface AccessResult {
|
|
79
|
+
granted: boolean;
|
|
80
|
+
reason: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class AccessBuildingCommand extends Command<
|
|
84
|
+
{ name: string }, // base type — shared interface
|
|
85
|
+
Building, // object type — context
|
|
86
|
+
AccessResult, // return type
|
|
87
|
+
[Student, Professor] // subject union
|
|
88
|
+
> {
|
|
89
|
+
readonly commandName = "accessBuilding" as const;
|
|
90
|
+
|
|
91
|
+
resolveStudent(student: Student, building: Readonly<Building>) {
|
|
92
|
+
if (student.department === building.department) return new GrantAccess();
|
|
93
|
+
return new DenyAccess();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
resolveProfessor(professor: Professor, building: Readonly<Building>) {
|
|
97
|
+
if (professor.tenured) return new GrantAccess();
|
|
98
|
+
return new DenyAccess();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Define Templates (Strategies)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
class GrantAccess implements Template<AccessBuildingCommand> {
|
|
107
|
+
execute(subject: Student | Professor, building: Building): AccessResult {
|
|
108
|
+
return { granted: true, reason: `${subject.name} has access` };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class DenyAccess implements Template<AccessBuildingCommand> {
|
|
113
|
+
execute(subject: Student | Professor, building: Building): AccessResult {
|
|
114
|
+
return { granted: false, reason: `${subject.name} denied` };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Run
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const cmd = new AccessBuildingCommand();
|
|
123
|
+
const result = cmd.run(new Student("Alice", "CS", 3), { name: "Science Hall", department: "CS" });
|
|
124
|
+
// { granted: true, reason: "Alice has access" }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## What the Compiler Catches
|
|
128
|
+
|
|
129
|
+
**Missing visit method** — Remove `resolveProfessor` from the `Command` above. The call `cmd.run(...)` immediately shows a type error. Not at the class declaration, at the call site — you see the error exactly where it matters.
|
|
130
|
+
|
|
131
|
+
**Wrong `Subject` type** — Pass a `Visitor` to a `Command` that only handles `[Student, Professor]`. Compile error.
|
|
132
|
+
|
|
133
|
+
**Missing hook property** — Declare `implements Template<Cmd, [AuditCommand]>` without an `audit` property. Compile error.
|
|
134
|
+
|
|
135
|
+
**Wrong return type** — Return a `string` from `execute` when the `Command` expects `AccessResult`. Compile error.
|
|
136
|
+
|
|
137
|
+
**Duplicate `visitName`** — Two `Subject`s with the same `visitName` in one `Command`'s union. The type system creates an impossible intersection, making the visit method unimplementable.
|
|
138
|
+
|
|
139
|
+
## Advanced Patterns
|
|
140
|
+
|
|
141
|
+
### Parameterized Templates
|
|
142
|
+
|
|
143
|
+
A `Template` can leave its subject union as a type parameter, letting `Strategy` classes narrow which `Subject`s they handle:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
abstract class CheckoutTemplate<CSU extends Student | Professor> implements Template<
|
|
147
|
+
CheckoutCmd,
|
|
148
|
+
[AccessBuildingCommand],
|
|
149
|
+
CSU
|
|
150
|
+
> {
|
|
151
|
+
readonly accessBuilding: AccessBuildingCommand;
|
|
152
|
+
constructor(cmd: AccessBuildingCommand) {
|
|
153
|
+
this.accessBuilding = cmd;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
execute(subject: CSU, equipment: Equipment): CheckoutResult {
|
|
157
|
+
const access = this.accessBuilding.run(subject, equipmentBuilding);
|
|
158
|
+
if (!access.granted) return deny(access.reason);
|
|
159
|
+
return this.computeTerms(subject, equipment);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected abstract computeTerms(subject: CSU, eq: Equipment): CheckoutResult;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Strategy narrows to Student only
|
|
166
|
+
class StudentCheckout extends CheckoutTemplate<Student> {
|
|
167
|
+
protected computeTerms(student: Student, eq: Equipment): CheckoutResult {
|
|
168
|
+
return { approved: true, days: student.year >= 3 ? 14 : 7 };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Command Hooks
|
|
174
|
+
|
|
175
|
+
`Template`s can declare dependencies on other `Command`s via the `H` parameter:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
class AuditedTemplate implements Template<MyCommand, [AuditCommand, LogCommand]> {
|
|
179
|
+
readonly audit: AuditCommand; // structural requirement from CommandHooks<H>
|
|
180
|
+
readonly log: LogCommand; // structural requirement from CommandHooks<H>
|
|
181
|
+
// ...
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Hooks can be concrete (shared), abstract (`Strategy` provides), overridden, or constructor-injected.
|
|
186
|
+
|
|
187
|
+
### Async Commands
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
class AssignParkingCommand extends Command<
|
|
191
|
+
Person,
|
|
192
|
+
ParkingLot,
|
|
193
|
+
Promise<ParkingAssignment>,
|
|
194
|
+
[Student, Professor]
|
|
195
|
+
> {
|
|
196
|
+
/* ... */
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const result = await parkingCmd.run(student, lotA);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Visit methods (strategy selection) remain synchronous. Only `execute` returns the `Promise`.
|
|
203
|
+
|
|
204
|
+
## Real-World Example
|
|
205
|
+
|
|
206
|
+
[**Odetovibe**](https://www.npmjs.com/package/odetovibe) — the YAML-to-TypeScript code generator that ships alongside this framework — is built entirely on the codascon protocol. The domain is described in YAML and its TypeScript scaffolding is generated by odetovibe itself.
|
|
207
|
+
|
|
208
|
+
## When to Use
|
|
209
|
+
|
|
210
|
+
**Good fit:**
|
|
211
|
+
|
|
212
|
+
- Domain with multiple entity types and multiple operations that grow along both axes
|
|
213
|
+
- Permission / access control systems
|
|
214
|
+
- Document processing pipelines
|
|
215
|
+
- Game entity interactions
|
|
216
|
+
- Workflow engines where behavior varies by entity type and operation context
|
|
217
|
+
|
|
218
|
+
**Not a good fit:**
|
|
219
|
+
|
|
220
|
+
- Simple CRUD services
|
|
221
|
+
- Linear data pipelines
|
|
222
|
+
- Applications where a `switch` or polymorphic method suffices
|
|
223
|
+
- Domains with one or two entity types that rarely change
|
|
224
|
+
|
|
225
|
+
The abstraction tax is real. It pays off when extension happens along the axes the protocol anticipates.
|
|
226
|
+
|
|
227
|
+
## For AI-Assisted Development
|
|
228
|
+
|
|
229
|
+
**Codascon** is particularly well-suited for LLM-assisted ("vibe") coding:
|
|
230
|
+
|
|
231
|
+
- **Structural rails** — The protocol tells the LLM exactly where new code goes. "Add a `Contractor` subject to `AccessBuildingCommand`" has one unambiguous implementation path.
|
|
232
|
+
- **YAML as prompting surface** — Hand the [**Odetovibe**](https://www.npmjs.com/package/odetovibe) config to the LLM instead of describing changes in prose. Higher fidelity, lower ambiguity.
|
|
233
|
+
- **Compiler as guardrail** — Forgotten visit methods are compile errors, not silent bugs. The LLM gets immediate feedback.
|
|
234
|
+
- **Predictable file structure** — Each `Command` + `Template`s + `Strategy` classes lives in one file. No architectural decisions for the LLM to get wrong across iterations.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codascon — code as config
|
|
3
|
+
*
|
|
4
|
+
* A structural protocol for code organization with exhaustive compile-time type checking.
|
|
5
|
+
*
|
|
6
|
+
* ## Core Concepts
|
|
7
|
+
*
|
|
8
|
+
* **Subject** — An entity that participates in double dispatch. Each Subject
|
|
9
|
+
* declares a unique `visitName` string literal (e.g. `"resolveStudent"`) which
|
|
10
|
+
* the framework uses to route dispatch to the correct visit method on a Command.
|
|
11
|
+
* Subjects extend the abstract `Subject` base class.
|
|
12
|
+
*
|
|
13
|
+
* **Command** — An operation that can be performed on Subjects. A Command
|
|
14
|
+
* declares visit methods — one per Subject in its subject union — each named
|
|
15
|
+
* after that Subject's `visitName`. The visit method receives the Subject and
|
|
16
|
+
* the operation's object (context/payload), inspects both, and returns a
|
|
17
|
+
* Template (strategy) to execute. Commands extend the abstract `Command` base
|
|
18
|
+
* class, which provides the `run` method that orchestrates dispatch.
|
|
19
|
+
*
|
|
20
|
+
* **Template** — The strategy interface. A Template declares an `execute`
|
|
21
|
+
* method and optionally declares CommandHooks (references to other Commands
|
|
22
|
+
* that the strategy may invoke during execution). In client code, Templates
|
|
23
|
+
* are typically implemented as abstract classes, with concrete Strategies
|
|
24
|
+
* extending them.
|
|
25
|
+
*
|
|
26
|
+
* ## Dispatch Flow
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* command.run(subject, object)
|
|
30
|
+
* → subject.getCommandStrategy(command, object) // Subject initiates double dispatch
|
|
31
|
+
* → command[subject.visitName](subject, object) // Command's visit method selects strategy
|
|
32
|
+
* → returns a Template instance // The chosen strategy
|
|
33
|
+
* → template.execute(subject, object) // Strategy executes
|
|
34
|
+
* → returns R // Result
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Type Safety Guarantees
|
|
38
|
+
*
|
|
39
|
+
* - **Exhaustive visit methods**: A Command's `run` method has a `this` parameter
|
|
40
|
+
* constrained by `CommandSubjectStrategies<C>`, which is the intersection of
|
|
41
|
+
* all required visit methods. If any visit method is missing, `run` becomes
|
|
42
|
+
* uncallable at the call site.
|
|
43
|
+
*
|
|
44
|
+
* - **Subject union enforcement**: `run` only accepts Subjects that are in the
|
|
45
|
+
* Command's declared subject union (`CV`). Passing an unsupported Subject is
|
|
46
|
+
* a compile error.
|
|
47
|
+
*
|
|
48
|
+
* - **Literal visitName**: `SubjectVisitName<S>` rejects non-literal `string`
|
|
49
|
+
* types, ensuring visit method keys are statically known. A Subject with
|
|
50
|
+
* `visitName: string` (non-literal) produces `never` keys, making
|
|
51
|
+
* CommandSubjectStrategies unsatisfiable.
|
|
52
|
+
*
|
|
53
|
+
* - **Duplicate visitName detection**: If two Subjects in the same Command's
|
|
54
|
+
* union share a `visitName`, `UnionToIntersection` merges their visit handler
|
|
55
|
+
* signatures into an impossible intersection (e.g. `(s: Dog & Cat) => ...`),
|
|
56
|
+
* making the visit method unimplementable.
|
|
57
|
+
*
|
|
58
|
+
* - **Template structural enforcement**: `CommandHooks<H>` requires the Template
|
|
59
|
+
* to have a property for each hook Command, keyed by `commandName`. This is
|
|
60
|
+
* enforced structurally at `implements` sites.
|
|
61
|
+
*
|
|
62
|
+
* - **Hook subject coverage**: `SubjectUnionVisitors<CSU, H>` constrains the `H`
|
|
63
|
+
* parameter on `Template` to only accept hook Commands whose subject union
|
|
64
|
+
* covers the Template's CSU. Note: due to TypeScript limitations with
|
|
65
|
+
* conditional type inference in constraint position, this constraint is not
|
|
66
|
+
* enforced at the type alias instantiation site. Instead, enforcement occurs
|
|
67
|
+
* at the hook invocation site — calling `hookCmd.run(subject)` where the
|
|
68
|
+
* subject is outside the hook's union produces a compile error via the hook
|
|
69
|
+
* Command's own `this` constraint.
|
|
70
|
+
*
|
|
71
|
+
* ## Client Patterns
|
|
72
|
+
*
|
|
73
|
+
* **Template as abstract class with Strategies:**
|
|
74
|
+
* ```ts
|
|
75
|
+
* abstract class AccessTemplate<CSU extends Student | Professor>
|
|
76
|
+
* implements Template<AccessCommand, [AuditCommand], CSU>
|
|
77
|
+
* {
|
|
78
|
+
* abstract readonly audit: AuditCommand; // hook — instantiated by Strategy
|
|
79
|
+
*
|
|
80
|
+
* execute(subject: CSU, object: Building): AccessResult {
|
|
81
|
+
* this.audit.run(subject, { action: "access" });
|
|
82
|
+
* return this.doAccess(subject, object);
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* protected abstract doAccess(subject: CSU, object: Building): AccessResult;
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* class GrantAccess extends AccessTemplate<Student> {
|
|
89
|
+
* readonly audit = new AuditCommand();
|
|
90
|
+
* protected doAccess(s: Student, b: Building) { return { granted: true }; }
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* **Hooks can be:**
|
|
95
|
+
* - Abstract on the Template, instantiated by the Strategy
|
|
96
|
+
* - Concrete on the Template (shared across all Strategies)
|
|
97
|
+
* - Overridden by the Strategy
|
|
98
|
+
* - Injected via constructor during strategy resolution in the Command's visit method
|
|
99
|
+
*
|
|
100
|
+
* **CSU parameterization:**
|
|
101
|
+
* A Template can parameterize its CSU (`T<CSU extends ...>`), allowing Strategies
|
|
102
|
+
* to narrow which Subjects they handle. This does not break LSP — a
|
|
103
|
+
* `GrantAccess extends AccessTemplate<Student>` is a valid strategy for any
|
|
104
|
+
* dispatch that routes Students to it.
|
|
105
|
+
*
|
|
106
|
+
* **Async support:**
|
|
107
|
+
* Set `R = Promise<Result>` on the Command. Visit methods (strategy selection)
|
|
108
|
+
* remain synchronous; only `execute` returns the Promise.
|
|
109
|
+
*
|
|
110
|
+
* @module codascon
|
|
111
|
+
*/
|
|
112
|
+
/**
|
|
113
|
+
* Extracts the `visitName` string literal type from a Subject.
|
|
114
|
+
*
|
|
115
|
+
* Returns `never` if the Subject's `visitName` is the wide `string` type
|
|
116
|
+
* rather than a string literal. This prevents non-literal visitNames from
|
|
117
|
+
* participating in dispatch — they would produce `never`-keyed visit methods
|
|
118
|
+
* in `Visit<C, CSU>`, making `CommandSubjectStrategies` impossible to satisfy.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* class Dog extends Subject { readonly visitName = "resolveDog" as const; }
|
|
122
|
+
* type T = SubjectVisitName<Dog>; // "resolveDog"
|
|
123
|
+
*
|
|
124
|
+
* class Bad extends Subject { readonly visitName: string = "oops"; }
|
|
125
|
+
* type T = SubjectVisitName<Bad>; // never
|
|
126
|
+
*/
|
|
127
|
+
export type SubjectVisitName<S> = S extends {
|
|
128
|
+
visitName: infer K extends string;
|
|
129
|
+
} ? string extends K ? never : K : never;
|
|
130
|
+
/**
|
|
131
|
+
* Extracts the `commandName` string literal type from a Command.
|
|
132
|
+
*
|
|
133
|
+
* Returns `never` if the Command's `commandName` is the wide `string` type.
|
|
134
|
+
* Used by `CommandHooks<H>` to key hook properties on the Template type.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* class FeedCmd extends Command<...> { readonly commandName = "feed" as const; }
|
|
138
|
+
* type T = CommandName<FeedCmd>; // "feed"
|
|
139
|
+
*/
|
|
140
|
+
export type CommandName<C> = C extends {
|
|
141
|
+
commandName: infer K extends string;
|
|
142
|
+
} ? string extends K ? never : K : never;
|
|
143
|
+
/**
|
|
144
|
+
* Extracts the object type (`O`) from a Command's generic parameters.
|
|
145
|
+
*
|
|
146
|
+
* The object is the context/payload passed alongside the Subject when
|
|
147
|
+
* running a Command. It is available to both the visit method (for strategy
|
|
148
|
+
* selection) and the Template's execute method (for execution).
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* class AccessCmd extends Command<Person, Building, Result, [Student]> { ... }
|
|
152
|
+
* type T = CommandObject<AccessCmd>; // Building
|
|
153
|
+
*/
|
|
154
|
+
export type CommandObject<C> = C extends Command<any, infer O, any, any> ? O : never;
|
|
155
|
+
/**
|
|
156
|
+
* Extracts the return type (`R`) from a Command's generic parameters.
|
|
157
|
+
*
|
|
158
|
+
* This is the type returned by both `command.run(...)` and `template.execute(...)`.
|
|
159
|
+
* For async Commands, this is `Promise<T>`.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* class AccessCmd extends Command<Person, Building, AccessResult, [Student]> { ... }
|
|
163
|
+
* type T = CommandReturn<AccessCmd>; // AccessResult
|
|
164
|
+
*/
|
|
165
|
+
export type CommandReturn<C> = C extends Command<any, any, infer R, any> ? R : never;
|
|
166
|
+
/**
|
|
167
|
+
* Shorthand for the fully-open Command type.
|
|
168
|
+
* Used as a constraint throughout the type machinery.
|
|
169
|
+
*/
|
|
170
|
+
type AnyCommand = Command<any, any, any, any>;
|
|
171
|
+
/**
|
|
172
|
+
* Extracts the Subject union from a Command's `CV` tuple parameter.
|
|
173
|
+
*
|
|
174
|
+
* Given `Command<B, O, R, [Student, Professor]>`, produces `Student | Professor`.
|
|
175
|
+
* This is the set of Subjects the Command can dispatch to.
|
|
176
|
+
*
|
|
177
|
+
* Note: when used inside type parameter constraints (e.g. in `SubjectUnionVisitors`),
|
|
178
|
+
* the `infer CV` may resolve to `any` for class types, causing the conditional
|
|
179
|
+
* to produce `any` rather than the expected union. This is a TypeScript limitation
|
|
180
|
+
* that affects constraint enforcement but not runtime behavior.
|
|
181
|
+
*/
|
|
182
|
+
type CommandSubjectUnion<C> = C extends Command<any, any, any, infer CV> ? CV[number] : never;
|
|
183
|
+
/**
|
|
184
|
+
* Validates that each Command in the hook tuple `H` has a subject union
|
|
185
|
+
* that covers `CSU` (the Template's subject union).
|
|
186
|
+
*
|
|
187
|
+
* For each position `K` in `H`, checks whether `CSU` extends
|
|
188
|
+
* `CommandSubjectUnion<H[K]>`. If the hook Command doesn't visit all
|
|
189
|
+
* Subjects in CSU, that position resolves to `never`, making
|
|
190
|
+
* `H extends AnyCommand[] & SubjectUnionVisitors<CSU, H>` fail.
|
|
191
|
+
*
|
|
192
|
+
* **TypeScript limitation:** This constraint is semantically correct but
|
|
193
|
+
* is not enforced at the `Template<C, H, CSU>` instantiation site due to
|
|
194
|
+
* `CommandSubjectUnion<H[K]>` resolving to `any` in constraint position.
|
|
195
|
+
* Enforcement instead occurs at two other sites:
|
|
196
|
+
*
|
|
197
|
+
* 1. **Structural (CommandHooks)** — `implements Template<C, [HookCmd]>`
|
|
198
|
+
* requires the hook as a property, catching missing wiring.
|
|
199
|
+
* 2. **Invocation** — `hookCmd.run(subject)` checks the hook Command's own
|
|
200
|
+
* `this & CommandSubjectStrategies` constraint, catching subject mismatches.
|
|
201
|
+
*/
|
|
202
|
+
type SubjectUnionVisitors<CSU extends Subject, H extends AnyCommand[]> = {
|
|
203
|
+
[K in keyof H]: CSU extends CommandSubjectUnion<H[K]> ? H[K] : never;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Defines the signature of a single visit method on a Command.
|
|
207
|
+
*
|
|
208
|
+
* For a given Command `C` and Subject type `CSU`, produces an object type
|
|
209
|
+
* with a single method keyed by `SubjectVisitName<CSU>`. The method receives
|
|
210
|
+
* the Subject and a `Readonly` view of the object, and returns a Template.
|
|
211
|
+
*
|
|
212
|
+
* The object is `Readonly` in the visit method signature to signal that
|
|
213
|
+
* strategy selection should not mutate the object — mutation belongs in
|
|
214
|
+
* `execute`.
|
|
215
|
+
*
|
|
216
|
+
* The return type erases hooks to `any[]` — hook validation occurs at the
|
|
217
|
+
* Template implementation site (`implements Template<C, H, CSU>`), not at
|
|
218
|
+
* the visit-method return boundary. This avoids requiring visit methods to
|
|
219
|
+
* declare the full hook parameterization.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // For Command<Person, Building, Result, [Student, Professor]> and CSU = Student:
|
|
223
|
+
* // Visit<C, Student> = { resolveStudent: (s: Student, o: Readonly<Building>) => Template<C, any[], Student> }
|
|
224
|
+
*/
|
|
225
|
+
type Visit<C extends AnyCommand, CSU extends CommandSubjectUnion<C>> = {
|
|
226
|
+
[K in SubjectVisitName<CSU>]: (subject: CSU, object: Readonly<CommandObject<C>>) => Template<C, any[], CSU>;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Converts a union type to an intersection type.
|
|
230
|
+
*
|
|
231
|
+
* Used to merge per-Subject visit method types into a single object type
|
|
232
|
+
* that a Command must satisfy. For example, given Subjects Dog and Cat:
|
|
233
|
+
* `{ resolveDog: ... } | { resolveCat: ... }` → `{ resolveDog: ... } & { resolveCat: ... }`
|
|
234
|
+
*
|
|
235
|
+
* This is also the mechanism that catches duplicate `visitName` values:
|
|
236
|
+
* two Subjects with the same visitName produce conflicting function signatures
|
|
237
|
+
* in the intersection, making the handler unimplementable.
|
|
238
|
+
*/
|
|
239
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
240
|
+
/**
|
|
241
|
+
* Computes the full set of visit methods a Command must implement.
|
|
242
|
+
*
|
|
243
|
+
* Maps each Subject in the Command's `CV` tuple to a `Visit<C, Subject>` type,
|
|
244
|
+
* collects them into a union via `[number]` indexing, then intersects them
|
|
245
|
+
* via `UnionToIntersection`. The result is an object type with one method per
|
|
246
|
+
* Subject, each keyed by that Subject's `visitName`.
|
|
247
|
+
*
|
|
248
|
+
* This type is used as a `this` parameter constraint on `Command.run()`.
|
|
249
|
+
* If the Command subclass is missing any visit method, the `this` constraint
|
|
250
|
+
* is unsatisfied and `run` becomes uncallable at the call site.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* // For Command<Person, Building, Result, [Student, Professor]>:
|
|
254
|
+
* // CommandSubjectStrategies<C> = {
|
|
255
|
+
* // resolveStudent: (s: Student, o: Readonly<Building>) => Template<...>;
|
|
256
|
+
* // resolveProfessor: (s: Professor, o: Readonly<Building>) => Template<...>;
|
|
257
|
+
* // }
|
|
258
|
+
*/
|
|
259
|
+
type CommandSubjectStrategies<C extends AnyCommand> = C extends Command<any, any, any, infer CV> ? UnionToIntersection<{
|
|
260
|
+
[K in keyof CV]: Visit<C, CV[K]>;
|
|
261
|
+
}[number]> : never;
|
|
262
|
+
/**
|
|
263
|
+
* Maps a tuple of hook Commands to an object type keyed by their `commandName`.
|
|
264
|
+
*
|
|
265
|
+
* This produces the structural requirement that a Template implementation
|
|
266
|
+
* must have a property for each hook Command. For example, given
|
|
267
|
+
* `H = [AuditCommand, LogCommand]` where their commandNames are `"audit"`
|
|
268
|
+
* and `"log"`, produces `{ audit: AuditCommand; log: LogCommand }`.
|
|
269
|
+
*
|
|
270
|
+
* Hook properties may be:
|
|
271
|
+
* - Abstract on the Template, instantiated by Strategies
|
|
272
|
+
* - Concrete on the Template, shared across all Strategies
|
|
273
|
+
* - Overridden by a Strategy
|
|
274
|
+
* - Injected via constructor during strategy resolution
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* // CommandHooks<[AuditCommand]> = { audit: AuditCommand }
|
|
278
|
+
*/
|
|
279
|
+
type CommandHooks<H extends AnyCommand[]> = {
|
|
280
|
+
[Cmd in H[number] as CommandName<Cmd>]: Cmd;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Abstract base class for all Commands.
|
|
284
|
+
*
|
|
285
|
+
* A Command represents an operation that can be performed on a set of Subjects.
|
|
286
|
+
* Subclasses must:
|
|
287
|
+
*
|
|
288
|
+
* 1. Declare `readonly commandName` as a string literal (used for hook keying).
|
|
289
|
+
* 2. Implement one visit method per Subject in `CV`, named after that Subject's
|
|
290
|
+
* `visitName`. Each visit method receives the Subject and the object, and
|
|
291
|
+
* returns a Template (strategy) to execute.
|
|
292
|
+
*
|
|
293
|
+
* ## Generic Parameters
|
|
294
|
+
*
|
|
295
|
+
* - `B` — Base type. All Subjects in `CV` must extend `B & Subject`.
|
|
296
|
+
* Allows constraining Subjects to share a common interface
|
|
297
|
+
* (e.g. `Person`, `Node`).
|
|
298
|
+
* - `O` — Object type. The context/payload passed to both visit methods
|
|
299
|
+
* and `execute`. Available during strategy selection and execution.
|
|
300
|
+
* - `R` — Return type. The result of `execute` and `run`. Use `Promise<T>`
|
|
301
|
+
* for async Commands.
|
|
302
|
+
* - `CV` — Subject tuple. The ordered list of Subject types this Command
|
|
303
|
+
* dispatches to. Each element must extend `B & Subject`. The tuple
|
|
304
|
+
* drives exhaustive visit method checking.
|
|
305
|
+
*
|
|
306
|
+
* ## The `run` Method
|
|
307
|
+
*
|
|
308
|
+
* `run` orchestrates the full dispatch cycle:
|
|
309
|
+
* 1. Calls `subject.getCommandStrategy(this, object)` — Subject initiates
|
|
310
|
+
* double dispatch by calling `this[subject.visitName](subject, object)`
|
|
311
|
+
* on the Command.
|
|
312
|
+
* 2. The visit method inspects the Subject and object, selects and returns
|
|
313
|
+
* a Template (strategy).
|
|
314
|
+
* 3. `run` calls `template.execute(subject, object)` and returns the result.
|
|
315
|
+
*
|
|
316
|
+
* The `this` parameter constraint (`this & CommandSubjectStrategies<C>`)
|
|
317
|
+
* ensures all visit methods are present. The error surfaces at the call site
|
|
318
|
+
* when any visit method is missing — `run` becomes uncallable.
|
|
319
|
+
*
|
|
320
|
+
* `run` can be overridden by subclasses (e.g. for auditing, logging,
|
|
321
|
+
* pre/post-processing) using `super.run(subject, object)`.
|
|
322
|
+
*
|
|
323
|
+
* ## Visit Method Semantics
|
|
324
|
+
*
|
|
325
|
+
* Visit methods are the **strategy selection** phase. They should:
|
|
326
|
+
* - Inspect the Subject's state and the object to choose a strategy
|
|
327
|
+
* - Return a Template instance (which may be a new instance, a singleton,
|
|
328
|
+
* a shared instance, etc. — client's choice)
|
|
329
|
+
* - NOT mutate the Subject or object (the object parameter is `Readonly`)
|
|
330
|
+
* - Use `this` freely to access Command state, configuration, or injected
|
|
331
|
+
* dependencies
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* class AccessCommand extends Command<Person, Building, AccessResult, [Student, Professor]> {
|
|
335
|
+
* readonly commandName = "access" as const;
|
|
336
|
+
*
|
|
337
|
+
* resolveStudent(student: Student, building: Readonly<Building>) {
|
|
338
|
+
* if (student.department === building.department) return new DepartmentMatch();
|
|
339
|
+
* return new DenyAccess();
|
|
340
|
+
* }
|
|
341
|
+
*
|
|
342
|
+
* resolveProfessor(professor: Professor, building: Readonly<Building>) {
|
|
343
|
+
* return new GrantAccess();
|
|
344
|
+
* }
|
|
345
|
+
* }
|
|
346
|
+
*
|
|
347
|
+
* const result = accessCmd.run(student, building);
|
|
348
|
+
*/
|
|
349
|
+
export declare abstract class Command<B, O, R, CV extends (B & Subject)[]> {
|
|
350
|
+
abstract readonly commandName: string;
|
|
351
|
+
run<T extends CommandSubjectUnion<Command<B, O, R, CV>>>(this: this & CommandSubjectStrategies<Command<B, O, R, CV>>, subject: T, object: O): R;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Abstract base class for all Subjects.
|
|
355
|
+
*
|
|
356
|
+
* A Subject is an entity that participates in double dispatch. Each Subject
|
|
357
|
+
* subclass must declare a `visitName` as a string literal, which serves as
|
|
358
|
+
* the key for the corresponding visit method on Commands.
|
|
359
|
+
*
|
|
360
|
+
* ## The `visitName` Convention
|
|
361
|
+
*
|
|
362
|
+
* By convention, `visitName` should be prefixed with `"resolve"`:
|
|
363
|
+
* ```ts
|
|
364
|
+
* readonly visitName = "resolveStudent" as const;
|
|
365
|
+
* ```
|
|
366
|
+
*
|
|
367
|
+
* The `visitName` must be:
|
|
368
|
+
* - A string literal type (not the wide `string` type) — enforced by
|
|
369
|
+
* `SubjectVisitName<S>` which returns `never` for non-literals.
|
|
370
|
+
* - Unique across all Subjects used within the same Command's subject union —
|
|
371
|
+
* duplicates are caught by `UnionToIntersection` producing impossible
|
|
372
|
+
* handler signatures.
|
|
373
|
+
*
|
|
374
|
+
* ## The `getCommandStrategy` Method
|
|
375
|
+
*
|
|
376
|
+
* This method performs the Subject's half of double dispatch. When
|
|
377
|
+
* `command.run(subject, object)` is called, it delegates to
|
|
378
|
+
* `subject.getCommandStrategy(command, object)`, which looks up
|
|
379
|
+
* `command[this.visitName]` and invokes it with `(this, object)`.
|
|
380
|
+
*
|
|
381
|
+
* The method call `command[methodName](this, object)` preserves `this`
|
|
382
|
+
* binding on the Command, allowing visit methods to access Command
|
|
383
|
+
* instance state via `this`.
|
|
384
|
+
*
|
|
385
|
+
* The `this` parameter constraint (`this & CSU`) ensures the Subject
|
|
386
|
+
* is part of the Command's subject union. This is automatically satisfied
|
|
387
|
+
* during normal dispatch.
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* class Student extends Subject {
|
|
391
|
+
* readonly visitName = "resolveStudent" as const;
|
|
392
|
+
* constructor(
|
|
393
|
+
* public readonly name: string,
|
|
394
|
+
* public readonly department: string
|
|
395
|
+
* ) { super(); }
|
|
396
|
+
* }
|
|
397
|
+
*/
|
|
398
|
+
export declare abstract class Subject {
|
|
399
|
+
abstract readonly visitName: string;
|
|
400
|
+
getCommandStrategy<C extends AnyCommand, CSU extends CommandSubjectUnion<C>>(this: this & CSU, command: Visit<C, CSU>, object: CommandObject<C>): Template<C, any[], CSU>;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* The strategy type. Defines the contract for executing a Command's
|
|
404
|
+
* operation on a Subject.
|
|
405
|
+
*
|
|
406
|
+
* A Template combines:
|
|
407
|
+
* - `execute(subject, object)` — the execution logic
|
|
408
|
+
* - `CommandHooks<H>` — structural properties referencing other Commands
|
|
409
|
+
* that `execute` may invoke
|
|
410
|
+
*
|
|
411
|
+
* ## Generic Parameters
|
|
412
|
+
*
|
|
413
|
+
* - `C` — The Command this Template serves. Determines the object type,
|
|
414
|
+
* return type, and the full subject union.
|
|
415
|
+
* - `H` — Hook tuple. A list of Command types that this Template's `execute`
|
|
416
|
+
* method may invoke during execution. Each hook Command appears as a
|
|
417
|
+
* structural property on the Template, keyed by its `commandName`.
|
|
418
|
+
* Defaults to `[]` (no hooks).
|
|
419
|
+
*
|
|
420
|
+
* **Important:** `H` should be *provided* (concrete), not parameterized.
|
|
421
|
+
* A Template class declares its hook requirements in the `implements`
|
|
422
|
+
* clause:
|
|
423
|
+
* ```ts
|
|
424
|
+
* class MyTemplate implements Template<MyCmd, [AuditCmd, LogCmd]> { ... }
|
|
425
|
+
* ```
|
|
426
|
+
* The hooks are part of the Template's contract — they are not chosen
|
|
427
|
+
* by Strategies.
|
|
428
|
+
*
|
|
429
|
+
* - `CSU` — Command Subject Union. The subset of `C`'s subject union that this
|
|
430
|
+
* Template handles. Defaults to the full union (`CommandSubjectUnion<C>`).
|
|
431
|
+
*
|
|
432
|
+
* **This CAN be parameterized** on the Template class, allowing
|
|
433
|
+
* Strategies to narrow which Subjects they handle:
|
|
434
|
+
* ```ts
|
|
435
|
+
* abstract class AccessTemplate<CSU extends Student | Professor>
|
|
436
|
+
* implements Template<AccessCmd, [AuditCmd], CSU> { ... }
|
|
437
|
+
*
|
|
438
|
+
* class GrantAccess extends AccessTemplate<Student> { ... }
|
|
439
|
+
* ```
|
|
440
|
+
* This does not break LSP — a `GrantAccess` is returned only for
|
|
441
|
+
* dispatches that route Students to it.
|
|
442
|
+
*
|
|
443
|
+
* ## Client Implementation Patterns
|
|
444
|
+
*
|
|
445
|
+
* Templates are typically implemented as abstract classes when Strategies are
|
|
446
|
+
* needed, or as concrete classes when they serve as both Template and Strategy.
|
|
447
|
+
*
|
|
448
|
+
* **Abstract Template with Strategies:**
|
|
449
|
+
* ```ts
|
|
450
|
+
* abstract class AccessTemplate<CSU extends Student | Professor>
|
|
451
|
+
* implements Template<AccessCmd, [AuditCmd], CSU>
|
|
452
|
+
* {
|
|
453
|
+
* // Hook — concrete (shared across Strategies)
|
|
454
|
+
* readonly audit = new AuditCommand();
|
|
455
|
+
*
|
|
456
|
+
* // Or abstract — each Strategy provides its own
|
|
457
|
+
* // abstract readonly audit: AuditCommand;
|
|
458
|
+
*
|
|
459
|
+
* execute(subject: CSU, object: Building): AccessResult {
|
|
460
|
+
* this.audit.run(subject, { action: "access" });
|
|
461
|
+
* return this.doAccess(subject, object);
|
|
462
|
+
* }
|
|
463
|
+
* protected abstract doAccess(subject: CSU, object: Building): AccessResult;
|
|
464
|
+
* }
|
|
465
|
+
*
|
|
466
|
+
* class GrantAccess extends AccessTemplate<Student> {
|
|
467
|
+
* protected doAccess(s: Student, b: Building) { return { granted: true }; }
|
|
468
|
+
* }
|
|
469
|
+
* ```
|
|
470
|
+
*
|
|
471
|
+
* **Concrete Template (no Strategies):**
|
|
472
|
+
* ```ts
|
|
473
|
+
* class DenyAccess implements Template<AccessCmd> {
|
|
474
|
+
* execute(subject: Student | Professor, object: Building): AccessResult {
|
|
475
|
+
* return { granted: false, reason: "Access denied" };
|
|
476
|
+
* }
|
|
477
|
+
* }
|
|
478
|
+
* ```
|
|
479
|
+
*
|
|
480
|
+
* **Hook ownership rules:**
|
|
481
|
+
* - Template declares which hooks are required (via `H` parameter)
|
|
482
|
+
* - Hooks can be concrete on the Template (shared) or abstract (Strategy provides)
|
|
483
|
+
* - Strategies may override concrete hooks from the Template
|
|
484
|
+
* - Hooks can also be injected via constructor during strategy resolution
|
|
485
|
+
* in the Command's visit method
|
|
486
|
+
*
|
|
487
|
+
* **Execute ownership:**
|
|
488
|
+
* - `execute` can be implemented on the Template or left abstract for Strategies
|
|
489
|
+
* - Strategies should exercise caution when overriding a Template's `execute`
|
|
490
|
+
* - The recommended pattern is for the Template to implement `execute` and
|
|
491
|
+
* delegate to abstract/protected methods that Strategies implement
|
|
492
|
+
*
|
|
493
|
+
* ## Hook Enforcement
|
|
494
|
+
*
|
|
495
|
+
* `CommandHooks<H>` is intersected into the Template type, requiring structural
|
|
496
|
+
* properties for each hook Command. This is enforced at `implements` sites —
|
|
497
|
+
* a class implementing `Template<C, [AuditCmd]>` without an `audit` property
|
|
498
|
+
* will fail to compile.
|
|
499
|
+
*
|
|
500
|
+
* The `SubjectUnionVisitors<CSU, H>` constraint on `H` is semantically correct
|
|
501
|
+
* (each hook Command should visit all Subjects in CSU) but is not enforced at
|
|
502
|
+
* the type alias instantiation site due to a TypeScript limitation. Instead,
|
|
503
|
+
* enforcement occurs when the hook is actually invoked in `execute` — calling
|
|
504
|
+
* `this.audit.run(subject)` where `subject` is outside the hook's union
|
|
505
|
+
* produces a compile error via the hook Command's own `this` constraint.
|
|
506
|
+
*/
|
|
507
|
+
export type Template<C extends AnyCommand, H extends AnyCommand[] & SubjectUnionVisitors<CSU, H> = [], CSU extends CommandSubjectUnion<C> = CommandSubjectUnion<C>> = CommandHooks<H> & {
|
|
508
|
+
execute<T extends CSU>(subject: T, object: CommandObject<C>): CommandReturn<C>;
|
|
509
|
+
};
|
|
510
|
+
export {};
|
|
511
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codascon — code as config
|
|
3
|
+
*
|
|
4
|
+
* A structural protocol for code organization with exhaustive compile-time type checking.
|
|
5
|
+
*
|
|
6
|
+
* ## Core Concepts
|
|
7
|
+
*
|
|
8
|
+
* **Subject** — An entity that participates in double dispatch. Each Subject
|
|
9
|
+
* declares a unique `visitName` string literal (e.g. `"resolveStudent"`) which
|
|
10
|
+
* the framework uses to route dispatch to the correct visit method on a Command.
|
|
11
|
+
* Subjects extend the abstract `Subject` base class.
|
|
12
|
+
*
|
|
13
|
+
* **Command** — An operation that can be performed on Subjects. A Command
|
|
14
|
+
* declares visit methods — one per Subject in its subject union — each named
|
|
15
|
+
* after that Subject's `visitName`. The visit method receives the Subject and
|
|
16
|
+
* the operation's object (context/payload), inspects both, and returns a
|
|
17
|
+
* Template (strategy) to execute. Commands extend the abstract `Command` base
|
|
18
|
+
* class, which provides the `run` method that orchestrates dispatch.
|
|
19
|
+
*
|
|
20
|
+
* **Template** — The strategy interface. A Template declares an `execute`
|
|
21
|
+
* method and optionally declares CommandHooks (references to other Commands
|
|
22
|
+
* that the strategy may invoke during execution). In client code, Templates
|
|
23
|
+
* are typically implemented as abstract classes, with concrete Strategies
|
|
24
|
+
* extending them.
|
|
25
|
+
*
|
|
26
|
+
* ## Dispatch Flow
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* command.run(subject, object)
|
|
30
|
+
* → subject.getCommandStrategy(command, object) // Subject initiates double dispatch
|
|
31
|
+
* → command[subject.visitName](subject, object) // Command's visit method selects strategy
|
|
32
|
+
* → returns a Template instance // The chosen strategy
|
|
33
|
+
* → template.execute(subject, object) // Strategy executes
|
|
34
|
+
* → returns R // Result
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Type Safety Guarantees
|
|
38
|
+
*
|
|
39
|
+
* - **Exhaustive visit methods**: A Command's `run` method has a `this` parameter
|
|
40
|
+
* constrained by `CommandSubjectStrategies<C>`, which is the intersection of
|
|
41
|
+
* all required visit methods. If any visit method is missing, `run` becomes
|
|
42
|
+
* uncallable at the call site.
|
|
43
|
+
*
|
|
44
|
+
* - **Subject union enforcement**: `run` only accepts Subjects that are in the
|
|
45
|
+
* Command's declared subject union (`CV`). Passing an unsupported Subject is
|
|
46
|
+
* a compile error.
|
|
47
|
+
*
|
|
48
|
+
* - **Literal visitName**: `SubjectVisitName<S>` rejects non-literal `string`
|
|
49
|
+
* types, ensuring visit method keys are statically known. A Subject with
|
|
50
|
+
* `visitName: string` (non-literal) produces `never` keys, making
|
|
51
|
+
* CommandSubjectStrategies unsatisfiable.
|
|
52
|
+
*
|
|
53
|
+
* - **Duplicate visitName detection**: If two Subjects in the same Command's
|
|
54
|
+
* union share a `visitName`, `UnionToIntersection` merges their visit handler
|
|
55
|
+
* signatures into an impossible intersection (e.g. `(s: Dog & Cat) => ...`),
|
|
56
|
+
* making the visit method unimplementable.
|
|
57
|
+
*
|
|
58
|
+
* - **Template structural enforcement**: `CommandHooks<H>` requires the Template
|
|
59
|
+
* to have a property for each hook Command, keyed by `commandName`. This is
|
|
60
|
+
* enforced structurally at `implements` sites.
|
|
61
|
+
*
|
|
62
|
+
* - **Hook subject coverage**: `SubjectUnionVisitors<CSU, H>` constrains the `H`
|
|
63
|
+
* parameter on `Template` to only accept hook Commands whose subject union
|
|
64
|
+
* covers the Template's CSU. Note: due to TypeScript limitations with
|
|
65
|
+
* conditional type inference in constraint position, this constraint is not
|
|
66
|
+
* enforced at the type alias instantiation site. Instead, enforcement occurs
|
|
67
|
+
* at the hook invocation site — calling `hookCmd.run(subject)` where the
|
|
68
|
+
* subject is outside the hook's union produces a compile error via the hook
|
|
69
|
+
* Command's own `this` constraint.
|
|
70
|
+
*
|
|
71
|
+
* ## Client Patterns
|
|
72
|
+
*
|
|
73
|
+
* **Template as abstract class with Strategies:**
|
|
74
|
+
* ```ts
|
|
75
|
+
* abstract class AccessTemplate<CSU extends Student | Professor>
|
|
76
|
+
* implements Template<AccessCommand, [AuditCommand], CSU>
|
|
77
|
+
* {
|
|
78
|
+
* abstract readonly audit: AuditCommand; // hook — instantiated by Strategy
|
|
79
|
+
*
|
|
80
|
+
* execute(subject: CSU, object: Building): AccessResult {
|
|
81
|
+
* this.audit.run(subject, { action: "access" });
|
|
82
|
+
* return this.doAccess(subject, object);
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* protected abstract doAccess(subject: CSU, object: Building): AccessResult;
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* class GrantAccess extends AccessTemplate<Student> {
|
|
89
|
+
* readonly audit = new AuditCommand();
|
|
90
|
+
* protected doAccess(s: Student, b: Building) { return { granted: true }; }
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* **Hooks can be:**
|
|
95
|
+
* - Abstract on the Template, instantiated by the Strategy
|
|
96
|
+
* - Concrete on the Template (shared across all Strategies)
|
|
97
|
+
* - Overridden by the Strategy
|
|
98
|
+
* - Injected via constructor during strategy resolution in the Command's visit method
|
|
99
|
+
*
|
|
100
|
+
* **CSU parameterization:**
|
|
101
|
+
* A Template can parameterize its CSU (`T<CSU extends ...>`), allowing Strategies
|
|
102
|
+
* to narrow which Subjects they handle. This does not break LSP — a
|
|
103
|
+
* `GrantAccess extends AccessTemplate<Student>` is a valid strategy for any
|
|
104
|
+
* dispatch that routes Students to it.
|
|
105
|
+
*
|
|
106
|
+
* **Async support:**
|
|
107
|
+
* Set `R = Promise<Result>` on the Command. Visit methods (strategy selection)
|
|
108
|
+
* remain synchronous; only `execute` returns the Promise.
|
|
109
|
+
*
|
|
110
|
+
* @module codascon
|
|
111
|
+
*/
|
|
112
|
+
// ─── Core Classes ────────────────────────────────────────────────
|
|
113
|
+
/**
|
|
114
|
+
* Abstract base class for all Commands.
|
|
115
|
+
*
|
|
116
|
+
* A Command represents an operation that can be performed on a set of Subjects.
|
|
117
|
+
* Subclasses must:
|
|
118
|
+
*
|
|
119
|
+
* 1. Declare `readonly commandName` as a string literal (used for hook keying).
|
|
120
|
+
* 2. Implement one visit method per Subject in `CV`, named after that Subject's
|
|
121
|
+
* `visitName`. Each visit method receives the Subject and the object, and
|
|
122
|
+
* returns a Template (strategy) to execute.
|
|
123
|
+
*
|
|
124
|
+
* ## Generic Parameters
|
|
125
|
+
*
|
|
126
|
+
* - `B` — Base type. All Subjects in `CV` must extend `B & Subject`.
|
|
127
|
+
* Allows constraining Subjects to share a common interface
|
|
128
|
+
* (e.g. `Person`, `Node`).
|
|
129
|
+
* - `O` — Object type. The context/payload passed to both visit methods
|
|
130
|
+
* and `execute`. Available during strategy selection and execution.
|
|
131
|
+
* - `R` — Return type. The result of `execute` and `run`. Use `Promise<T>`
|
|
132
|
+
* for async Commands.
|
|
133
|
+
* - `CV` — Subject tuple. The ordered list of Subject types this Command
|
|
134
|
+
* dispatches to. Each element must extend `B & Subject`. The tuple
|
|
135
|
+
* drives exhaustive visit method checking.
|
|
136
|
+
*
|
|
137
|
+
* ## The `run` Method
|
|
138
|
+
*
|
|
139
|
+
* `run` orchestrates the full dispatch cycle:
|
|
140
|
+
* 1. Calls `subject.getCommandStrategy(this, object)` — Subject initiates
|
|
141
|
+
* double dispatch by calling `this[subject.visitName](subject, object)`
|
|
142
|
+
* on the Command.
|
|
143
|
+
* 2. The visit method inspects the Subject and object, selects and returns
|
|
144
|
+
* a Template (strategy).
|
|
145
|
+
* 3. `run` calls `template.execute(subject, object)` and returns the result.
|
|
146
|
+
*
|
|
147
|
+
* The `this` parameter constraint (`this & CommandSubjectStrategies<C>`)
|
|
148
|
+
* ensures all visit methods are present. The error surfaces at the call site
|
|
149
|
+
* when any visit method is missing — `run` becomes uncallable.
|
|
150
|
+
*
|
|
151
|
+
* `run` can be overridden by subclasses (e.g. for auditing, logging,
|
|
152
|
+
* pre/post-processing) using `super.run(subject, object)`.
|
|
153
|
+
*
|
|
154
|
+
* ## Visit Method Semantics
|
|
155
|
+
*
|
|
156
|
+
* Visit methods are the **strategy selection** phase. They should:
|
|
157
|
+
* - Inspect the Subject's state and the object to choose a strategy
|
|
158
|
+
* - Return a Template instance (which may be a new instance, a singleton,
|
|
159
|
+
* a shared instance, etc. — client's choice)
|
|
160
|
+
* - NOT mutate the Subject or object (the object parameter is `Readonly`)
|
|
161
|
+
* - Use `this` freely to access Command state, configuration, or injected
|
|
162
|
+
* dependencies
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* class AccessCommand extends Command<Person, Building, AccessResult, [Student, Professor]> {
|
|
166
|
+
* readonly commandName = "access" as const;
|
|
167
|
+
*
|
|
168
|
+
* resolveStudent(student: Student, building: Readonly<Building>) {
|
|
169
|
+
* if (student.department === building.department) return new DepartmentMatch();
|
|
170
|
+
* return new DenyAccess();
|
|
171
|
+
* }
|
|
172
|
+
*
|
|
173
|
+
* resolveProfessor(professor: Professor, building: Readonly<Building>) {
|
|
174
|
+
* return new GrantAccess();
|
|
175
|
+
* }
|
|
176
|
+
* }
|
|
177
|
+
*
|
|
178
|
+
* const result = accessCmd.run(student, building);
|
|
179
|
+
*/
|
|
180
|
+
export class Command {
|
|
181
|
+
run(subject, object) {
|
|
182
|
+
const strategy = subject.getCommandStrategy(this, object);
|
|
183
|
+
return strategy.execute(subject, object);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Abstract base class for all Subjects.
|
|
188
|
+
*
|
|
189
|
+
* A Subject is an entity that participates in double dispatch. Each Subject
|
|
190
|
+
* subclass must declare a `visitName` as a string literal, which serves as
|
|
191
|
+
* the key for the corresponding visit method on Commands.
|
|
192
|
+
*
|
|
193
|
+
* ## The `visitName` Convention
|
|
194
|
+
*
|
|
195
|
+
* By convention, `visitName` should be prefixed with `"resolve"`:
|
|
196
|
+
* ```ts
|
|
197
|
+
* readonly visitName = "resolveStudent" as const;
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* The `visitName` must be:
|
|
201
|
+
* - A string literal type (not the wide `string` type) — enforced by
|
|
202
|
+
* `SubjectVisitName<S>` which returns `never` for non-literals.
|
|
203
|
+
* - Unique across all Subjects used within the same Command's subject union —
|
|
204
|
+
* duplicates are caught by `UnionToIntersection` producing impossible
|
|
205
|
+
* handler signatures.
|
|
206
|
+
*
|
|
207
|
+
* ## The `getCommandStrategy` Method
|
|
208
|
+
*
|
|
209
|
+
* This method performs the Subject's half of double dispatch. When
|
|
210
|
+
* `command.run(subject, object)` is called, it delegates to
|
|
211
|
+
* `subject.getCommandStrategy(command, object)`, which looks up
|
|
212
|
+
* `command[this.visitName]` and invokes it with `(this, object)`.
|
|
213
|
+
*
|
|
214
|
+
* The method call `command[methodName](this, object)` preserves `this`
|
|
215
|
+
* binding on the Command, allowing visit methods to access Command
|
|
216
|
+
* instance state via `this`.
|
|
217
|
+
*
|
|
218
|
+
* The `this` parameter constraint (`this & CSU`) ensures the Subject
|
|
219
|
+
* is part of the Command's subject union. This is automatically satisfied
|
|
220
|
+
* during normal dispatch.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* class Student extends Subject {
|
|
224
|
+
* readonly visitName = "resolveStudent" as const;
|
|
225
|
+
* constructor(
|
|
226
|
+
* public readonly name: string,
|
|
227
|
+
* public readonly department: string
|
|
228
|
+
* ) { super(); }
|
|
229
|
+
* }
|
|
230
|
+
*/
|
|
231
|
+
export class Subject {
|
|
232
|
+
getCommandStrategy(command, object) {
|
|
233
|
+
const methodName = this.visitName;
|
|
234
|
+
return command[methodName](this, object);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8GG;AAqMH,oEAAoE;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,OAAgB,OAAO;IAG3B,GAAG,CAED,OAAU,EACV,MAAS;QAET,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,OAAgB,OAAO;IAG3B,kBAAkB,CAEhB,OAAsB,EACtB,MAAwB;QAExB,MAAM,UAAU,GAAG,IAAI,CAAC,SAAkC,CAAC;QAC3D,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codascon",
|
|
3
|
+
"version": "2026.3.1-alpha",
|
|
4
|
+
"description": "A structural protocol for code organization with exhaustive compile-time type checking",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "@scorpevans",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/scorpevans/codascon.git",
|
|
10
|
+
"directory": "packages/codascon"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"code-as-config",
|
|
14
|
+
"code-protocol",
|
|
15
|
+
"double-dispatch",
|
|
16
|
+
"visitor-pattern",
|
|
17
|
+
"command-pattern",
|
|
18
|
+
"template-pattern",
|
|
19
|
+
"strategy-pattern",
|
|
20
|
+
"design-patterns",
|
|
21
|
+
"solid-principles",
|
|
22
|
+
"type-safe",
|
|
23
|
+
"compile-time",
|
|
24
|
+
"typescript",
|
|
25
|
+
"framework"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://github.com/scorpevans/codascon#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/scorpevans/codascon/issues"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"type": "module",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"!dist/**/*.test.js",
|
|
45
|
+
"!dist/**/*.test.js.map",
|
|
46
|
+
"!dist/**/*.test.d.ts",
|
|
47
|
+
"!dist/**/*.test.d.ts.map",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
52
|
+
"typescript": "^5.7.0",
|
|
53
|
+
"vitest": "^3.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc --build",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"coverage": "vitest run --coverage"
|
|
59
|
+
}
|
|
60
|
+
}
|