@mnemonica/tactica 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/SKILL.md +117 -0
- package/lib/analyzer.d.ts +8 -0
- package/lib/analyzer.js +27 -1
- package/lib/generator.d.ts +10 -3
- package/lib/generator.js +18 -7
- package/lib/types.d.ts +7 -0
- package/lib/types.js +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -171,7 +171,23 @@ declare module 'mnemonica' {
|
|
|
171
171
|
|
|
172
172
|
**Recommended for new projects.** Explicit imports, better tree-shaking, no global namespace pollution.
|
|
173
173
|
|
|
174
|
-
**
|
|
174
|
+
**Async constructors.** When the construct handler is an `async function` (or async arrow), `new` resolves to a Promise at runtime, so the emitted constructor shapes resolve to `Promise<InstanceType>`: the subtype property on the parent emits `{ new (…): Promise<X>; (…): Promise<X> }` (both twins) and the registry entry emits `new (…) => Promise<X>`. The instance type `X` itself is unchanged, so `await new` yields `X`. Async CLASSES (a class constructor returning a Promise) are not detected — the syntactic shape carries no reliable signal without a type checker — and stay typed as the plain instance type; type them yourself in userland if you need `Promise<X>` there.
|
|
175
|
+
|
|
176
|
+
**Avoid arrow handlers — use the ESLint plugin.** Construct handlers must be regular functions or classes so the instance can be substituted as `this`. Core rejects sync arrows at `define()` time, but async arrows cannot be detected at runtime and never receive the instance — the failure surfaces far from the cause. The early guard is [`eslint-plugin-no-arrow-this`](https://www.npmjs.com/package/eslint-plugin-no-arrow-this), which flags `this` inside arrow functions before the code runs:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
plugins: ['eslint-plugin-no-arrow-this'],
|
|
180
|
+
rules: {
|
|
181
|
+
// default: warn on `this` inside ANY arrow function
|
|
182
|
+
'no-arrow-this/no-arrow-this': 'warn',
|
|
183
|
+
// or restrict to the global/window capture case:
|
|
184
|
+
// 'no-arrow-this/no-arrow-this': ['warn', { onlyGlobals: true }],
|
|
185
|
+
},
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Recommended in every mnemonica project.
|
|
189
|
+
|
|
190
|
+
**Instance-type inheritance.** Nested instance types flatten the WHOLE ancestor chain: each nested type is `ProtoFlat<ParentInstance, Self>` (mnemonica's `ProtoFlat` keeps every parent key except the ones `Self` overrides), and the parent instance type is itself a `ProtoFlat` over its own parent — so the ROOT's own constructor-arg fields (`serial` in `WidgetRoot`) are directly present on every descendant instance type, at any depth, alongside intermediate ancestors' fields. If a root field appears missing on a nested type, the usual cause is a stale `.tactica` — regenerate; the composition itself is transitive by construction.
|
|
175
191
|
|
|
176
192
|
### Legacy global-augmentation mode (`--module-augmentation`)
|
|
177
193
|
|
|
@@ -394,8 +410,8 @@ class Configurable {
|
|
|
394
410
|
Decorate-class instances need a cast to the generated instance type to access nested constructors at the type level:
|
|
395
411
|
|
|
396
412
|
```ts
|
|
397
|
-
const
|
|
398
|
-
const sub = new
|
|
413
|
+
const gadget = new Gadget() as Gadget_EnhancedGadget; // … or whatever your generated alias is
|
|
414
|
+
const sub = new gadget.EnhancedGadget();
|
|
399
415
|
```
|
|
400
416
|
|
|
401
417
|
### `Object.assign(this, data)` pattern
|
|
@@ -920,8 +936,8 @@ Type Hierarchy (Trie)
|
|
|
920
936
|
│ ├── properties: { role: string }
|
|
921
937
|
│ └── SuperAdminType
|
|
922
938
|
│ └── properties: { permissions: string[] }
|
|
923
|
-
└──
|
|
924
|
-
└── properties: {
|
|
939
|
+
└── JobType
|
|
940
|
+
└── properties: { tasks: Task[] }
|
|
925
941
|
```
|
|
926
942
|
|
|
927
943
|
## Known Limitations
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mnemonica-tactica
|
|
3
|
+
description: |
|
|
4
|
+
Static type generator for mnemonica projects. Use when a project uses
|
|
5
|
+
mnemonica and needs typed lookup(), TypeRegistry augmentation, or
|
|
6
|
+
generated instance types; when `define()`/`lazy()`/`@decorate()` calls
|
|
7
|
+
should become visible to TypeScript; or when the user mentions tactica,
|
|
8
|
+
.tactica output, types.ts / registry.ts generation, or type-safe
|
|
9
|
+
mnemonica navigation.
|
|
10
|
+
metadata:
|
|
11
|
+
tags: [mnemonica, typescript, codegen, type-system, ast, nodejs]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# tactica — usage skill for agents
|
|
15
|
+
|
|
16
|
+
tactica reads TypeScript/JavaScript source that uses mnemonica and
|
|
17
|
+
generates the types TypeScript cannot infer from runtime calls:
|
|
18
|
+
`.tactica/types.ts` (instance types), `.tactica/registry.ts` (module
|
|
19
|
+
augmentation of mnemonica's `TypeRegistry`), plus JSON metadata
|
|
20
|
+
(definitions, usages, hierarchy, scopes, modules, flow, instrumentation)
|
|
21
|
+
consumed by editor extensions and other tools.
|
|
22
|
+
|
|
23
|
+
## When to use
|
|
24
|
+
|
|
25
|
+
- The project depends on `mnemonica` and calls `define()`, `lazy()`, or
|
|
26
|
+
uses `@decorate()`
|
|
27
|
+
- Code calls `lookup('Some.Type')` and you want the result typed
|
|
28
|
+
- A constructor or subtype type-errors and the fix belongs upstream
|
|
29
|
+
(regenerate `.tactica` before reaching for a cast)
|
|
30
|
+
|
|
31
|
+
## How to run
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx tactica # nearest tsconfig, writes .tactica/
|
|
35
|
+
npx tactica -p tsconfig.json # explicit project
|
|
36
|
+
npx tactica --esm # NodeNext ESM: .js import extensions
|
|
37
|
+
npx tactica -w # watch mode
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The generated `types.ts` + `registry.ts` are imported by application
|
|
41
|
+
code; the JSON files are consumed by tooling (graph views, go-to
|
|
42
|
+
definition). Custom types collections are tracked too — with a
|
|
43
|
+
user-declared registry interface (Option B) they emit their own
|
|
44
|
+
augmented interface instead of the global `TypeRegistry`.
|
|
45
|
+
|
|
46
|
+
## What it types
|
|
47
|
+
|
|
48
|
+
- One `export type` per discovered type, named by dotted path with `_`
|
|
49
|
+
separators; nested types are `ProtoFlat<Parent, Self>`, so ancestor
|
|
50
|
+
fields are present on descendants by construction.
|
|
51
|
+
- Every parent instance type carries its subtype constructors:
|
|
52
|
+
`Child: { new (args): Child; (args): Child }` — the construct form
|
|
53
|
+
and the chain-tip call form.
|
|
54
|
+
- `registry.ts` augments mnemonica's `TypeRegistry` so the free
|
|
55
|
+
`lookup('Parent.Child')` returns the typed constructor.
|
|
56
|
+
|
|
57
|
+
## Async constructors
|
|
58
|
+
|
|
59
|
+
When a construct handler is an `async function` (or async arrow),
|
|
60
|
+
tactica emits `Promise<X>` for both constructor shapes — the subtype
|
|
61
|
+
property and the registry entry — because `new` resolves to a Promise
|
|
62
|
+
at runtime. The instance type `X` itself is unchanged, so
|
|
63
|
+
`await new` yields `X`.
|
|
64
|
+
|
|
65
|
+
Async **classes** (a class constructor returning a Promise) are NOT
|
|
66
|
+
detected — the syntactic shape carries no reliable signal without a
|
|
67
|
+
type checker — and stay typed as the plain instance type. Type them in
|
|
68
|
+
userland when `Promise<X>` is needed there.
|
|
69
|
+
|
|
70
|
+
## Avoid arrow handlers — use the ESLint plugin
|
|
71
|
+
|
|
72
|
+
mnemonica construct handlers must be regular functions or classes so the
|
|
73
|
+
instance can be substituted as `this`:
|
|
74
|
+
|
|
75
|
+
- Sync arrow handlers are rejected by mnemonica core at `define()` time
|
|
76
|
+
with a readable error.
|
|
77
|
+
- Async arrow handlers **cannot be detected at runtime** — they look
|
|
78
|
+
identical to `async function` — and they never receive the instance:
|
|
79
|
+
`this` inside an arrow is the outer lexical `this`, so fields land on
|
|
80
|
+
the wrong object and the construction fails later, far from the cause.
|
|
81
|
+
|
|
82
|
+
The early guard is the ESLint plugin `eslint-plugin-no-arrow-this`,
|
|
83
|
+
which flags `this` inside arrow functions before the code runs:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// eslint.config.js (flat) or .eslintrc
|
|
87
|
+
plugins: ['eslint-plugin-no-arrow-this'],
|
|
88
|
+
rules: {
|
|
89
|
+
// default: warn on `this` inside ANY arrow function
|
|
90
|
+
'no-arrow-this/no-arrow-this': 'warn',
|
|
91
|
+
// or restrict to the global/window capture case:
|
|
92
|
+
// 'no-arrow-this/no-arrow-this': ['warn', { onlyGlobals: true }],
|
|
93
|
+
},
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Recommended in every mnemonica project, especially where handlers are
|
|
97
|
+
written by contributors who have not internalized the rule.
|
|
98
|
+
|
|
99
|
+
## Example
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { mnemonica } from 'mnemonica';
|
|
103
|
+
|
|
104
|
+
export const App = mnemonica
|
|
105
|
+
.define('Widget', function (this: Widget, data: { id: string }) {
|
|
106
|
+
this.id = data.id;
|
|
107
|
+
})
|
|
108
|
+
.define('Gadget', async function (this: Gadget, data: { serial: number }) {
|
|
109
|
+
this.serial = await register(data.serial);
|
|
110
|
+
return this;
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
After `npx tactica`, `lookup('App.Gadget')` is typed
|
|
115
|
+
`new (data: { serial: number }) => Promise<Gadget>` — a forgotten
|
|
116
|
+
`await` is now a type error at the call site instead of a silent
|
|
117
|
+
`undefined` at runtime.
|
package/lib/analyzer.d.ts
CHANGED
|
@@ -665,6 +665,14 @@ export declare class MnemonicaAnalyzer {
|
|
|
665
665
|
* returned by the lazy getter.
|
|
666
666
|
*/
|
|
667
667
|
private extractConstructorExpression;
|
|
668
|
+
/**
|
|
669
|
+
* Detect an async constructor handler: the async modifier on a
|
|
670
|
+
* function expression or arrow. Async CLASSES (a class constructor
|
|
671
|
+
* returning a Promise) are deliberately NOT detected — the syntactic
|
|
672
|
+
* class shape gives no reliable signal without a type checker, and the
|
|
673
|
+
* owner decided they are typed by the user in userland.
|
|
674
|
+
*/
|
|
675
|
+
private isAsyncConstructHandler;
|
|
668
676
|
/**
|
|
669
677
|
* Extract properties from constructor function
|
|
670
678
|
*/
|