mnemonica 1.0.1 → 1.0.6
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/.ai/ONBOARDING.md +1 -1
- package/.ai/ask/AGENTS.md +15 -9
- package/.ai/rules-skill/contributing.md +1 -1
- package/.ai/rules-skill/define-patterns.md +8 -2
- package/.ai/rules-skill/ecosystem.md +4 -4
- package/.ai/rules-skill/instance-methods.md +48 -15
- package/.ai/rules-skill/type-system.md +12 -2
- package/AGENTS.md +6 -2
- package/CONTRIBUTING.md +7 -1
- package/FOR_HUMANS.md +143 -199
- package/README.md +74 -39
- package/build/api/errors/exceptionConstructor.js +20 -12
- package/build/api/errors/index.js +9 -5
- package/build/api/errors/throwModificationError.js +15 -9
- package/build/api/index.js +35 -2
- package/build/api/types/InstanceCreator.js +5 -2
- package/build/api/types/Mnemosyne.d.ts +6 -6
- package/build/api/types/Mnemosyne.js +43 -120
- package/build/api/types/Props.js +5 -3
- package/build/api/types/TypeProxy.js +13 -9
- package/build/api/types/compileNewModificatorFunctionBody.js +14 -8
- package/build/api/types/index.js +56 -13
- package/build/api/utils/index.js +21 -12
- package/build/constants/index.js +11 -8
- package/build/descriptors/types/index.js +79 -26
- package/build/index.d.ts +4 -4
- package/build/index.js +62 -15
- package/build/types/index.d.ts +52 -31
- package/build/types/index.js +1 -1
- package/build/utils/clone.d.ts +1 -0
- package/build/utils/clone.js +11 -0
- package/build/utils/collectConstructors.js +5 -3
- package/build/utils/exception.d.ts +1 -0
- package/build/utils/exception.js +14 -0
- package/build/utils/extract.d.ts +2 -3
- package/build/utils/extract.js +3 -2
- package/build/utils/fork.d.ts +1 -0
- package/build/utils/fork.js +33 -0
- package/build/utils/index.d.ts +2 -3
- package/build/utils/index.js +21 -7
- package/build/utils/merge.d.ts +2 -1
- package/build/utils/merge.js +10 -6
- package/build/utils/parent.d.ts +1 -1
- package/build/utils/parent.js +3 -2
- package/build/utils/parse.d.ts +2 -12
- package/build/utils/parse.js +1 -1
- package/build/utils/pick.d.ts +4 -3
- package/build/utils/pick.js +4 -5
- package/build/utils/sibling.d.ts +1 -0
- package/build/utils/sibling.js +25 -0
- package/build/utils/toJSON.d.ts +1 -1
- package/build/utils/toJSON.js +3 -2
- package/docs/UTILS.md +184 -0
- package/docs/ai-learning-trajectory.md +1 -1
- package/docs/async-constructors.md +3 -1
- package/docs/empathy-in-ai.md +170 -0
- package/docs/hott-primer.md +47 -0
- package/docs/performance-vs-security.md +2 -2
- package/docs/purpose.md +38 -7
- package/docs/tactica-pattern.md +10 -10
- package/docs/theory-of-operations.md +224 -0
- package/docs/typed-lookup.md +12 -12
- package/package.json +12 -7
- package/src/api/errors/exceptionConstructor.ts +14 -9
- package/src/api/errors/index.ts +8 -4
- package/src/api/errors/throwModificationError.ts +10 -9
- package/src/api/types/InstanceCreator.ts +5 -8
- package/src/api/types/Mnemosyne.ts +72 -231
- package/src/api/types/Props.ts +4 -2
- package/src/api/types/TypeProxy.ts +7 -11
- package/src/api/types/compileNewModificatorFunctionBody.ts +13 -7
- package/src/api/types/index.ts +15 -8
- package/src/api/utils/index.ts +16 -14
- package/src/constants/index.ts +6 -9
- package/src/descriptors/types/index.ts +44 -24
- package/src/index.ts +28 -21
- package/src/types/index.ts +101 -69
- package/src/utils/clone.ts +11 -0
- package/src/utils/collectConstructors.ts +4 -2
- package/src/utils/exception.ts +16 -0
- package/src/utils/extract.ts +5 -2
- package/src/utils/fork.ts +57 -0
- package/src/utils/index.ts +32 -13
- package/src/utils/merge.ts +18 -6
- package/src/utils/parent.ts +4 -6
- package/src/utils/parse.ts +5 -4
- package/src/utils/pick.ts +10 -2
- package/src/utils/sibling.ts +33 -0
- package/src/utils/toJSON.ts +3 -2
package/docs/purpose.md
CHANGED
|
@@ -48,6 +48,35 @@ for (item of items) { // Control flow: iteration
|
|
|
48
48
|
|
|
49
49
|
**Mnemonica does NOT touch this**: Algorithms remain unchanged. Services stay intact. Control flow is crafted by developers (and AI agents) as needed.
|
|
50
50
|
|
|
51
|
+
### What Mnemonica Does NOT Do — Concrete Examples
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// ❌ Mnemonica does NOT replace if/else
|
|
55
|
+
if (user.role === 'admin') {
|
|
56
|
+
// This logic is yours. Mnemonica provides the typed data,
|
|
57
|
+
// not the decision rule.
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ❌ Mnemonica does NOT replace loops
|
|
61
|
+
for (const item of items) {
|
|
62
|
+
// Iteration control is yours.
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ❌ Mnemonica does NOT replace async/await orchestration
|
|
66
|
+
const a = await serviceA.get();
|
|
67
|
+
const b = await serviceB.process(a);
|
|
68
|
+
// Mnemonica types the data, not the execution order.
|
|
69
|
+
|
|
70
|
+
// ❌ Mnemonica does NOT replace service boundaries
|
|
71
|
+
// You still write HTTP handlers, database queries, message queues.
|
|
72
|
+
// Mnemonica types the data that flows through them.
|
|
73
|
+
|
|
74
|
+
// ✅ Mnemonica DOES type the data at each stage
|
|
75
|
+
const RequestData = define('RequestData', function(req) { this.method = req.method; });
|
|
76
|
+
const ResponseData = RequestData.define('ResponseData', function(res) { this.body = res.body; });
|
|
77
|
+
// The types track lineage; the services remain unchanged.
|
|
78
|
+
```
|
|
79
|
+
|
|
51
80
|
---
|
|
52
81
|
|
|
53
82
|
## Part 2: The Problem - Data Without Memory
|
|
@@ -246,7 +275,7 @@ p instanceof Vector; // false - same shape, different nominal type!
|
|
|
246
275
|
|
|
247
276
|
Mnemonica cares about **constructor identity**. Same shape, different type = not compatible.
|
|
248
277
|
|
|
249
|
-
### Protected `constructor.name` -
|
|
278
|
+
### Protected `constructor.name` - Defense-in-Depth
|
|
250
279
|
|
|
251
280
|
Mnemonica types have **read-only, protected constructor names**:
|
|
252
281
|
|
|
@@ -261,11 +290,13 @@ user.constructor.name = 'Admin'; // ✗ Ignored/Fails - protected
|
|
|
261
290
|
```
|
|
262
291
|
|
|
263
292
|
**Why This Matters**:
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
293
|
+
- Prototype pollution attacks that rely on name spoofing are **blocked at the assignment level**
|
|
294
|
+
- `instanceof` checks **prototype object references**, not constructor names, so name changes do not affect type identity
|
|
295
|
+
- Constructor names are getter-protected and non-enumerable, making casual tampering impossible
|
|
267
296
|
- Type identity is immutable once established
|
|
268
297
|
|
|
298
|
+
**Threat Model**: This is defense-in-depth, not cryptographic security. A fully compromised execution environment can still use `Object.defineProperty` to redefine the getter. The protection targets casual prototype pollution and accidental corruption, not a determined attacker with full runtime access.
|
|
299
|
+
|
|
269
300
|
### Security Through `Object.create(null)`
|
|
270
301
|
|
|
271
302
|
Mnemonica instances are **NOT instances of Object or Function**:
|
|
@@ -501,8 +532,8 @@ const superAdmin = new SuperAdmin({});
|
|
|
501
532
|
|
|
502
533
|
**The Hot Connection**: In HoTT, paths can be composed, inverted, and transformed. In Mnemonica:
|
|
503
534
|
- **Composition**: `new user.SubType()` extends the path
|
|
504
|
-
- **Inversion**: `
|
|
505
|
-
- **Transformation**:
|
|
535
|
+
- **Inversion**: `utils.parent(instance)` traverses backward
|
|
536
|
+
- **Transformation**: `utils.fork(instance)` creates a new path with same starting point
|
|
506
537
|
|
|
507
538
|
### Higher Inductive Types: The Family Tree
|
|
508
539
|
|
|
@@ -640,7 +671,7 @@ console.log(props.__parent__); // undefined (root type)
|
|
|
640
671
|
- Historical reflection embedded in data structures
|
|
641
672
|
- The DNA of data as it moves through systems
|
|
642
673
|
- A **nominal type system** (constructor identity, not shape)
|
|
643
|
-
- A **secure** system (
|
|
674
|
+
- A **secure** system (defense-in-depth against prototype pollution, nominal typing prevents type confusion)
|
|
644
675
|
|
|
645
676
|
**Mnemonica Is NOT**:
|
|
646
677
|
- A control flow framework
|
package/docs/tactica-pattern.md
CHANGED
|
@@ -88,22 +88,22 @@ Include the file in `tsconfig.json` `include`. You are done.
|
|
|
88
88
|
|
|
89
89
|
### Via tactica
|
|
90
90
|
|
|
91
|
-
For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and
|
|
91
|
+
For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and run it as a CLI/codegen utility. It scans your `define()` calls and generates the augmentation:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install --save-dev @mnemonica/tactica
|
|
95
|
+
npx tactica --output ./.tactica --include "src/**/*.ts"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Include the generated files in `tsconfig.json`:
|
|
92
99
|
|
|
93
100
|
```json
|
|
94
101
|
{
|
|
95
|
-
"compilerOptions": {
|
|
96
|
-
"plugins": [{
|
|
97
|
-
"name" : "@mnemonica/tactica",
|
|
98
|
-
"outputDir" : ".tactica",
|
|
99
|
-
"include" : ["src/**/*.ts"]
|
|
100
|
-
}]
|
|
101
|
-
},
|
|
102
102
|
"include": ["src/**/*.ts", ".tactica/**/*.ts"]
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected.
|
|
106
|
+
Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected. Re-run the CLI whenever your type graph changes, or wire it into your build script.
|
|
107
107
|
|
|
108
108
|
See [`./typed-lookup.md`](./typed-lookup.md) for a side-by-side recipe with both paths.
|
|
109
109
|
|
|
@@ -134,7 +134,7 @@ The anti-patterns are the same whether you augment by hand or via tactica:
|
|
|
134
134
|
What changes is how you keep the `TypeRegistry` augmentation in sync with your code:
|
|
135
135
|
|
|
136
136
|
- **Hand-written:** edit the `.d.ts` when types change.
|
|
137
|
-
- **Tactica:** the
|
|
137
|
+
- **Tactica:** the CLI/codegen utility keeps `.tactica/` in sync.
|
|
138
138
|
|
|
139
139
|
Pick whichever fits your project. Both are first-class.
|
|
140
140
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Theory of Operations
|
|
2
|
+
|
|
3
|
+
## The Full Construction Pipeline
|
|
4
|
+
|
|
5
|
+
This document traces a single `new instance.SubType(args)` call through every stage of mnemonica's internals, with references to the source files that implement each stage. It exists so that contributors (human and AI) can understand the exact data flow without reverse-engineering the code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Stage 0: Type Definition (`define()`)
|
|
10
|
+
|
|
11
|
+
**File:** [`src/api/types/index.ts`](../src/api/types/index.ts)
|
|
12
|
+
|
|
13
|
+
Before any instance exists, the type must be declared. `define('TypeName', ctor, config?)` performs:
|
|
14
|
+
|
|
15
|
+
1. **Resolve definition context** — determines whether this is a root type or a nested path (`Parent.SubType`)
|
|
16
|
+
2. **Check for duplicates** — throws `ALREADY_DECLARED` if the name exists in the target collection
|
|
17
|
+
3. **Compile the constructor body** — `compileNewModificatorFunctionBody.ts` wraps the user's constructor in a function that preserves `new.target` and handles both class and function constructors
|
|
18
|
+
4. **Create a `TypeDescriptor`** — stores: name, compiled handler, prototype, config, parent type reference
|
|
19
|
+
5. **Register in the `TypesCollection` Proxy** — makes the type findable via `.lookup()` and `.define()`
|
|
20
|
+
|
|
21
|
+
The result is a `TypeProxy` — a constructor function with additional methods (`.define()`, `.lookup()`, `.registerHook()`).
|
|
22
|
+
|
|
23
|
+
**Key invariant:** A type is immutable once defined. Its name, handler, and prototype are frozen.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Stage 1: Subtype Access (`instance.SubType`)
|
|
28
|
+
|
|
29
|
+
**File:** [`src/api/types/Mnemosyne.ts`](../src/api/types/Mnemosyne.ts)
|
|
30
|
+
|
|
31
|
+
When code reads `instance.SubType`, the `Mnemosyne` proxy's `get` trap fires:
|
|
32
|
+
|
|
33
|
+
1. Look up `SubType` in the instance's `__subtypes__` Map (stored in the WeakMap)
|
|
34
|
+
2. If found, call `prepareSubtypeForConstruction(subtype, instance)`
|
|
35
|
+
3. This creates a **subtype proxy** — a function that closes over both the `TypeDef` and the parent `instance`
|
|
36
|
+
|
|
37
|
+
The subtype proxy is what makes `new instance.SubType()` work. It is not the raw constructor; it is a wrapper that knows which parent instance to use.
|
|
38
|
+
|
|
39
|
+
**Key invariant:** Every access to `instance.SubType` creates a fresh proxy (this was a deliberate design decision; caching is possible but not implemented to keep the code simple).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Stage 2: Construction Invocation (`new instance.SubType(args)`)
|
|
44
|
+
|
|
45
|
+
**File:** [`src/api/types/Mnemosyne.ts`](../src/api/types/Mnemosyne.ts)
|
|
46
|
+
|
|
47
|
+
The subtype proxy's `construct` trap fires:
|
|
48
|
+
|
|
49
|
+
1. Call `new InstanceCreator(subtype, instance, args)`
|
|
50
|
+
2. The `InstanceCreator` is the orchestrator for the full lifecycle
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Stage 3: Instance Creator Setup
|
|
55
|
+
|
|
56
|
+
**File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
|
|
57
|
+
|
|
58
|
+
`InstanceCreator` is a class (not a proxy) that manages the 8-stage pipeline:
|
|
59
|
+
|
|
60
|
+
### 3.1 Extract context
|
|
61
|
+
- `type` — the `TypeDef` for `SubType`
|
|
62
|
+
- `existentInstance` — the parent instance (`instance` in our example)
|
|
63
|
+
- `args` — the arguments array
|
|
64
|
+
- `config` — from the type definition
|
|
65
|
+
|
|
66
|
+
### 3.2 Resolve the `ModificatorType`
|
|
67
|
+
- For sync constructors: compile the user handler into a new constructor function
|
|
68
|
+
- For async constructors: wrap in an async-compatible class pattern
|
|
69
|
+
|
|
70
|
+
### 3.3 Prepare hooks
|
|
71
|
+
- `invokePreHooks()` — calls `preCreation` hooks on the type's collection and the type itself
|
|
72
|
+
- If any hook throws, `creationError` hooks fire and construction aborts
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Stage 4: Prototype Chain Assembly
|
|
77
|
+
|
|
78
|
+
**File:** [`src/api/types/createInstanceModificator.ts`](../src/api/types/createInstanceModificator.ts)
|
|
79
|
+
|
|
80
|
+
This is where the Trie structure is physically built:
|
|
81
|
+
|
|
82
|
+
1. **Create `Mnemosyne`** — `const Mnemosyne = {}`
|
|
83
|
+
2. **Link to parent** — `Reflect.setPrototypeOf(Mnemosyne, existentInstance)`
|
|
84
|
+
- This is the critical step: the new instance's prototype chain will go through the **specific parent instance**
|
|
85
|
+
3. **Add internal properties** — `_addProps(Mnemosyne)` attaches getters for `__type__`, `__parent__`, `__args__`, `__timestamp__`, etc.
|
|
86
|
+
4. **Create `ModificatorType`** — a new constructor function with `Mnemosyne` as its prototype
|
|
87
|
+
5. **Copy user prototype properties** — `Object.getOwnPropertyDescriptors(ModificatorTypePrototype)` are copied onto `ModificatorType.prototype`
|
|
88
|
+
6. **Link ModificatorType to Mnemosyne** — `Reflect.setPrototypeOf(ModificatorType.prototype, Mnemosyne)`
|
|
89
|
+
|
|
90
|
+
The resulting chain:
|
|
91
|
+
```
|
|
92
|
+
newObj
|
|
93
|
+
↓ __proto__
|
|
94
|
+
ModificatorType.prototype
|
|
95
|
+
↓ __proto__
|
|
96
|
+
Mnemosyne (has __type__, __parent__, etc.)
|
|
97
|
+
↓ __proto__
|
|
98
|
+
existentInstance (the specific parent)
|
|
99
|
+
↓ __proto__
|
|
100
|
+
... (rest of chain to root)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Stage 5: User Constructor Execution
|
|
106
|
+
|
|
107
|
+
**File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
|
|
108
|
+
|
|
109
|
+
The `ModificatorType` is instantiated with `new ModificatorType(...args)`:
|
|
110
|
+
|
|
111
|
+
1. The compiled constructor body (from Stage 0) runs
|
|
112
|
+
2. `this` is the new object whose prototype chain was built in Stage 4
|
|
113
|
+
3. The user constructor assigns properties to `this`
|
|
114
|
+
4. If the constructor returns a non-primitive, that value is used as the instance
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Stage 6: Post-Processing
|
|
119
|
+
|
|
120
|
+
**File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
|
|
121
|
+
|
|
122
|
+
After the user constructor:
|
|
123
|
+
|
|
124
|
+
1. **Set `__self__`** — `_setSelf(instance)` stores a reference to the instance itself in the props
|
|
125
|
+
2. **Validation** (if `strictChain: true`):
|
|
126
|
+
- Verify the instance is actually an instance of the expected parent type
|
|
127
|
+
- Verify the parent's constructor name matches the expected parent type name
|
|
128
|
+
3. **Invoke `postCreation` hooks**
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Stage 7: Return
|
|
133
|
+
|
|
134
|
+
**File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
|
|
135
|
+
|
|
136
|
+
The instance is returned. It carries:
|
|
137
|
+
- All user-assigned properties (own properties)
|
|
138
|
+
- The prototype chain linking back to the root type
|
|
139
|
+
- Internal construction context in the WeakMap (not enumerable, not own properties)
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## The WeakMap Architecture
|
|
144
|
+
|
|
145
|
+
**File:** [`src/api/types/Props.ts`](../src/api/types/Props.ts)
|
|
146
|
+
|
|
147
|
+
Internal properties are stored in a `WeakMap` keyed by the **prototype object** (Mnemosyne), not the instance itself:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const __props__ = new WeakMap<object, PropsType>();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Why the prototype?
|
|
154
|
+
- Multiple instances of the same type share the same Mnemosyne prototype
|
|
155
|
+
- Storing props on the prototype means all instances of the same construction share the same type metadata
|
|
156
|
+
- This saves memory and keeps instance enumeration clean
|
|
157
|
+
|
|
158
|
+
`getProps(instance)` walks the prototype chain to find the Mnemosyne object, then looks it up in the WeakMap.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Hook Invocation Details
|
|
163
|
+
|
|
164
|
+
**File:** [`src/api/hooks/invokeHook.ts`](../src/api/hooks/invokeHook.ts)
|
|
165
|
+
|
|
166
|
+
Hooks are stored per-type and per-collection:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
type.hooks = {
|
|
170
|
+
preCreation: [fn1, fn2],
|
|
171
|
+
postCreation: [fn3],
|
|
172
|
+
creationError: [fn4]
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When `invokeHook('preCreation', hookData)` fires:
|
|
177
|
+
1. Create a `HookInvocation` builder
|
|
178
|
+
2. For each registered hook, call it with the builder
|
|
179
|
+
3. Collect results in a Set
|
|
180
|
+
4. If any hook throws, the error propagates (aborting construction)
|
|
181
|
+
|
|
182
|
+
The `flowCheckers` Map allows hooks to be conditionally enabled/disabled per invocation.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Error Handling
|
|
187
|
+
|
|
188
|
+
**File:** [`src/api/errors/throwModificationError.ts`](../src/api/errors/throwModificationError.ts)
|
|
189
|
+
|
|
190
|
+
If any stage throws:
|
|
191
|
+
|
|
192
|
+
1. `creationError` hooks fire with the error and partial construction context
|
|
193
|
+
2. A `BASE_MNEMONICA_ERROR` is constructed with:
|
|
194
|
+
- Cleaned stack trace (mnemonica internals removed)
|
|
195
|
+
- References to the instance, type, and parent
|
|
196
|
+
3. The error is thrown to the caller
|
|
197
|
+
|
|
198
|
+
If `config.blockErrors` is true (default for most types), errors during construction are caught and re-thrown as mnemonica errors. If false (used internally for exception construction), errors propagate raw.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Summary Table
|
|
203
|
+
|
|
204
|
+
| Stage | File | What Happens |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| 0. Type Definition | `src/api/types/index.ts` | Compile handler, create TypeDescriptor, register in collection |
|
|
207
|
+
| 1. Subtype Access | `src/api/types/Mnemosyne.ts` | Resolve `instance.SubType` via proxy get trap |
|
|
208
|
+
| 2. Construction Start | `src/api/types/Mnemosyne.ts` | Subtype proxy's construct trap fires |
|
|
209
|
+
| 3. Creator Setup | `src/api/types/InstanceCreator.ts` | Extract context, resolve ModificatorType, run pre-hooks |
|
|
210
|
+
| 4. Prototype Assembly | `src/api/types/createInstanceModificator.ts` | Build Mnemosyne → link to parent → create ModificatorType |
|
|
211
|
+
| 5. User Constructor | `src/api/types/InstanceCreator.ts` | Run compiled handler with `this` bound to new object |
|
|
212
|
+
| 6. Post-Processing | `src/api/types/InstanceCreator.ts` | Set __self__, validate, run post-hooks |
|
|
213
|
+
| 7. Return | `src/api/types/InstanceCreator.ts` | Return instance with full prototype chain and WeakMap props |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## For Contributors
|
|
218
|
+
|
|
219
|
+
When modifying any of these files, remember:
|
|
220
|
+
|
|
221
|
+
- **Prototype links are load-bearing.** Changing `Reflect.setPrototypeOf` calls changes the Trie structure.
|
|
222
|
+
- **WeakMap keys are prototype objects, not instances.** `_getProps` walks the chain; `_addProps` writes to the Mnemosyne.
|
|
223
|
+
- **Hook order matters.** `preCreation` → construction → `postCreation`. Errors in `preCreation` abort before `Object.create`.
|
|
224
|
+
- **The `__self__` reference is used by `utils.fork()` and `utils.clone()` to detect self-calls.**
|
package/docs/typed-lookup.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Typed lookup with `lookupTyped()` — with or without tactica
|
|
2
2
|
|
|
3
|
-
`lookupTyped()` is a runtime lookup with compile-time type safety. The type safety comes from one mechanism: declaration merging on the `TypeRegistry` interface that mnemonica exports.
|
|
3
|
+
`lookupTyped()` is a runtime lookup with compile-time type safety. The type safety comes from one mechanism: declaration merging on the `TypeRegistry` interface that mnemonica exports. The path argument may use dots, slashes, colons, or bracket notation (e.g. `'UserType.AdminType'`, `'UserType/AdminType'`, `'UserType:AdminType'`, or `'UserType[AdminType]'`), and a single-segment name is resolved relative to the collection or type constructor the lookup is called on.
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
// src/index.ts (mnemonica core)
|
|
@@ -96,22 +96,22 @@ Install:
|
|
|
96
96
|
npm install --save-dev @mnemonica/tactica
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Install and run the CLI:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install --save-dev @mnemonica/tactica
|
|
103
|
+
npx tactica --output ./.tactica --include "src/**/*.ts"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then include the generated files in `tsconfig.json`:
|
|
100
107
|
|
|
101
108
|
```json
|
|
102
109
|
{
|
|
103
|
-
"compilerOptions": {
|
|
104
|
-
"plugins": [{
|
|
105
|
-
"name" : "@mnemonica/tactica",
|
|
106
|
-
"outputDir" : ".tactica",
|
|
107
|
-
"include" : ["src/**/*.ts"]
|
|
108
|
-
}]
|
|
109
|
-
},
|
|
110
110
|
"include": ["src/**/*.ts", ".tactica/**/*.ts"]
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Tactica scans your `define()` and `@decorate()` calls and generates `.tactica/types.ts` + `.tactica/registry.ts`. The generated registry has the same shape as the hand-written one above, but tactica also fills in instance-level subtypes (`alice.Employee`) automatically and uses `ProtoFlat<Parent, ...>` for nested types so they merge correctly.
|
|
114
|
+
Tactica scans your `define()` and `@decorate()` calls and generates `.tactica/types.ts` + `.tactica/registry.ts`. The generated registry has the same shape as the hand-written one above, but tactica also fills in instance-level subtypes (`alice.Employee`) automatically and uses `ProtoFlat<Parent, ...>` for nested types so they merge correctly. You can re-run the CLI manually or from a build script; mnemographica can also run it on your behalf.
|
|
115
115
|
|
|
116
116
|
You write nothing for the augmentation; tactica regenerates as your types evolve. See [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) for the full configuration surface.
|
|
117
117
|
|
|
@@ -124,14 +124,14 @@ You write nothing for the augmentation; tactica regenerates as your types evolve
|
|
|
124
124
|
- Small projects (a handful of types)
|
|
125
125
|
- Bootstrap / learning — writing the augmentation by hand is the fastest way to understand what `lookupTyped()` actually does
|
|
126
126
|
- Projects that want to keep the `TypeRegistry` surface stable and curated, independent of source-code churn
|
|
127
|
-
- Environments where
|
|
127
|
+
- Environments where running a CLI/codegen utility is inconvenient
|
|
128
128
|
|
|
129
129
|
**Tactica** is the right choice when:
|
|
130
130
|
|
|
131
131
|
- You have many types or expect the type graph to grow
|
|
132
132
|
- You don't want to maintain the augmentation by hand as `define()` calls are added or refactored
|
|
133
133
|
- You want full instance-subtype typing (`alice.Employee`) without writing it
|
|
134
|
-
- You're already
|
|
134
|
+
- You're already running tactica as part of your build or IDE workflow
|
|
135
135
|
|
|
136
136
|
The runtime is identical either way. There is no "tactica path" vs "non-tactica path" in mnemonica core itself — there is only the `TypeRegistry` augmentation, and two ways to produce it.
|
|
137
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnemonica",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "AI-agent ready instance inheritance system for JavaScript/TypeScript via prototype chains",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"example:v8bug": "npm run build && node ./examples/v8bug.js",
|
|
52
52
|
"example:ts": "npx tsc ./test-ts/*.ts && npx eslint --fix ./test-ts/*.js && node ./test-ts/test-no-types.js && node ./test-ts/test-with-types.js",
|
|
53
53
|
"deopt": "node --trace-deopt --trace-opt ./node_modules/mocha/bin/mocha.js ./test/index.js",
|
|
54
|
-
"decorate:ts": "npx tsc --target ES2021 --moduleResolution NodeNext --module NodeNext --sourceMap ./test/decorate.ts",
|
|
54
|
+
"decorate:ts": "npx tsc --ignoreConfig --target ES2021 --moduleResolution NodeNext --module NodeNext --sourceMap ./test/decorate.ts",
|
|
55
55
|
"lint:md": "find . -name '*.md' -not -path '*/node_modules/*' | xargs markdown-link-check -q --config .markdown-link-check.json",
|
|
56
56
|
"lint": "npx eslint",
|
|
57
57
|
"lint:check": "npx eslint ./src ./test",
|
|
@@ -99,9 +99,9 @@
|
|
|
99
99
|
"@eslint/eslintrc": "^3.3.5",
|
|
100
100
|
"@eslint/js": "^9.21.0",
|
|
101
101
|
"@types/jest": "^29.5.14",
|
|
102
|
-
"@types/node": "^22.19.
|
|
103
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
104
|
-
"@typescript-eslint/parser": "^8.
|
|
102
|
+
"@types/node": "^22.19.21",
|
|
103
|
+
"@typescript-eslint/eslint-plugin": "^8.61.1",
|
|
104
|
+
"@typescript-eslint/parser": "^8.61.1",
|
|
105
105
|
"chai": "^5.3.3",
|
|
106
106
|
"eslint": "^9.39.4",
|
|
107
107
|
"eslint-plugin-import": "^2.32.0",
|
|
@@ -112,18 +112,23 @@
|
|
|
112
112
|
"jest": "^29.7.0",
|
|
113
113
|
"json5": "^2.2.3",
|
|
114
114
|
"lint-staged": "^15.5.2",
|
|
115
|
+
"markdown-link-check": "^3.14.2",
|
|
115
116
|
"mocha": "^11.7.6",
|
|
116
117
|
"mocha-lcov-reporter": "^1.3.0",
|
|
117
118
|
"nyc": "^17.1.0",
|
|
118
119
|
"oxlint": "^0.16.12",
|
|
119
120
|
"ts-jest": "^29.4.11",
|
|
120
121
|
"typeomatica": "^0.3.57",
|
|
121
|
-
"
|
|
122
|
-
"typescript": "^5.9.3"
|
|
122
|
+
"typescript": "^6.0.3"
|
|
123
123
|
},
|
|
124
124
|
"engines": {
|
|
125
125
|
"node": ">=18 <26"
|
|
126
126
|
},
|
|
127
|
+
"overrides": {
|
|
128
|
+
"serialize-javascript": "^7.0.5",
|
|
129
|
+
"uuid": "^11.1.1",
|
|
130
|
+
"diff": "^9.0.0"
|
|
131
|
+
},
|
|
127
132
|
"lint-staged": {
|
|
128
133
|
"src/**/*.ts": "eslint --fix",
|
|
129
134
|
"test/**/*.js": "eslint --fix"
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
cleanupStack, getStack
|
|
14
14
|
} from '../errors';
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
16
|
+
import { parse } from '../../utils/parse';
|
|
17
|
+
import { extract } from '../../utils/extract';
|
|
18
18
|
|
|
19
19
|
import TypesUtils from '../utils';
|
|
20
20
|
|
|
@@ -140,9 +140,11 @@ const exceptionConsctructHandler = function ( this: Error, opts: { [ index: stri
|
|
|
140
140
|
'extract',
|
|
141
141
|
{
|
|
142
142
|
get () {
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const result = () => {
|
|
144
|
+
const extractResult = extract(instance);
|
|
145
|
+
return extractResult;
|
|
145
146
|
};
|
|
147
|
+
return result;
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
);
|
|
@@ -152,10 +154,11 @@ const exceptionConsctructHandler = function ( this: Error, opts: { [ index: stri
|
|
|
152
154
|
'parse',
|
|
153
155
|
{
|
|
154
156
|
get () {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
return
|
|
157
|
+
const result = () => {
|
|
158
|
+
const parseResult = parse( instance );
|
|
159
|
+
return parseResult;
|
|
158
160
|
};
|
|
161
|
+
return result;
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
);
|
|
@@ -250,7 +253,7 @@ const prepareException = function ( this: object, target: unknown, error: Error,
|
|
|
250
253
|
ExceptionCreator.ModificatorType = makeErrorModificatorType(
|
|
251
254
|
TypeName,
|
|
252
255
|
function (this: Error) {
|
|
253
|
-
|
|
256
|
+
const handlerResult = exceptionConsctructHandler.call(
|
|
254
257
|
this,
|
|
255
258
|
{
|
|
256
259
|
instance,
|
|
@@ -260,12 +263,14 @@ const prepareException = function ( this: object, target: unknown, error: Error,
|
|
|
260
263
|
error
|
|
261
264
|
}
|
|
262
265
|
);
|
|
266
|
+
return handlerResult;
|
|
263
267
|
}
|
|
264
268
|
);
|
|
265
269
|
|
|
266
270
|
ExceptionCreator.InstanceModificator = makeInstanceModificator( ExceptionCreator );
|
|
267
271
|
|
|
268
|
-
|
|
272
|
+
const result = new ExceptionCreator.InstanceModificator();
|
|
273
|
+
return result;
|
|
269
274
|
};
|
|
270
275
|
|
|
271
276
|
export default prepareException;
|
package/src/api/errors/index.ts
CHANGED
|
@@ -31,7 +31,8 @@ export const cleanupStack = ( stack: string[] ) => {
|
|
|
31
31
|
},
|
|
32
32
|
[]
|
|
33
33
|
);
|
|
34
|
-
|
|
34
|
+
const result = cleaned.length ? cleaned : stack;
|
|
35
|
+
return result;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
export const getStack = function (
|
|
@@ -90,7 +91,8 @@ export class BASE_MNEMONICA_ERROR extends Error {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
static get [ SymbolConstructorName ] () {
|
|
93
|
-
|
|
94
|
+
const result = new String( `base of : ${MNEMONICA} : errors` );
|
|
95
|
+
return result;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
}
|
|
@@ -100,7 +102,8 @@ Object.defineProperty(
|
|
|
100
102
|
'name',
|
|
101
103
|
{
|
|
102
104
|
get () {
|
|
103
|
-
|
|
105
|
+
const result = new String( 'BASE_MNEMONICA_ERROR' );
|
|
106
|
+
return result;
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
);
|
|
@@ -130,7 +133,8 @@ export const constructError = ( name: string, message: string ) => {
|
|
|
130
133
|
'name',
|
|
131
134
|
{
|
|
132
135
|
get () {
|
|
133
|
-
|
|
136
|
+
const result = new String( name );
|
|
137
|
+
return result;
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
);
|
|
@@ -21,12 +21,9 @@ import {
|
|
|
21
21
|
import TypesUtils from '../utils';
|
|
22
22
|
const { makeErrorModificatorType } = TypesUtils;
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
parent,
|
|
28
|
-
extract
|
|
29
|
-
} = utils;
|
|
24
|
+
import { parse } from '../../utils/parse';
|
|
25
|
+
import { parent } from '../../utils/parent';
|
|
26
|
+
import { extract } from '../../utils/extract';
|
|
30
27
|
|
|
31
28
|
import { makeInstanceModificator } from '../types/InstanceModificator';
|
|
32
29
|
|
|
@@ -244,11 +241,14 @@ export const throwModificationError = function ( this: InstanceCreatorContext, e
|
|
|
244
241
|
'extract',
|
|
245
242
|
{
|
|
246
243
|
get () {
|
|
247
|
-
|
|
248
|
-
|
|
244
|
+
const extractGetter = () => {
|
|
245
|
+
// mnemonica instances always have a parent object,
|
|
246
|
+
// so the runtime value is guaranteed to be object here
|
|
247
|
+
const _parent = parent(erroredInstance) as object;
|
|
249
248
|
const extractResult = extract(_parent);
|
|
250
249
|
return extractResult;
|
|
251
250
|
};
|
|
251
|
+
return extractGetter;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
);
|
|
@@ -258,10 +258,11 @@ export const throwModificationError = function ( this: InstanceCreatorContext, e
|
|
|
258
258
|
'parse',
|
|
259
259
|
{
|
|
260
260
|
get () {
|
|
261
|
-
|
|
261
|
+
const parseGetter = () => {
|
|
262
262
|
const parseResult = parse( erroredInstance );
|
|
263
263
|
return parseResult;
|
|
264
264
|
};
|
|
265
|
+
return parseGetter;
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
268
|
);
|
|
@@ -281,14 +281,11 @@ const makeAwaiter = function ( this: InstanceCreatorContext, type: TypeDef, then
|
|
|
281
281
|
type.subtypes.forEach( ( subtype: object, name: string ) => {
|
|
282
282
|
|
|
283
283
|
(self.inheritedInstance as Record<string, unknown>)[ name ] = ( ...args: unknown[] ) => {
|
|
284
|
-
self.inheritedInstance = self.makeAwaiter(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
args,
|
|
290
|
-
}
|
|
291
|
-
);
|
|
284
|
+
self.inheritedInstance = self.makeAwaiter( subtype as TypeDef, {
|
|
285
|
+
name,
|
|
286
|
+
subtype,
|
|
287
|
+
args,
|
|
288
|
+
} );
|
|
292
289
|
return self.inheritedInstance;
|
|
293
290
|
};
|
|
294
291
|
} );
|