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 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
@@ -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
+ }