@mnemonica/tactica 0.1.3 → 0.1.5
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 +123 -2
- package/lib/analyzer.d.ts +167 -23
- package/lib/analyzer.js +953 -177
- package/lib/analyzer.js.map +1 -1
- package/lib/cli.js +29 -10
- package/lib/cli.js.map +1 -1
- package/lib/generator.d.ts +19 -7
- package/lib/generator.js +134 -18
- package/lib/generator.js.map +1 -1
- package/lib/graph.d.ts +10 -2
- package/lib/graph.js +29 -2
- package/lib/graph.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/topologica-analyzer.js +3 -3
- package/lib/topologica-analyzer.js.map +1 -1
- package/lib/types.d.ts +25 -0
- package/lib/writer.d.ts +5 -1
- package/lib/writer.js +14 -0
- package/lib/writer.js.map +1 -1
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -221,7 +221,124 @@ const AdminType = UserType.define('AdminType', function (this: { role: string })
|
|
|
221
221
|
|
|
222
222
|
Chained calls (`define('A').define('B')`) and nested calls via variable references (`const A = define('A', …); A.define('B', …)`) are both supported.
|
|
223
223
|
|
|
224
|
-
###
|
|
224
|
+
### Builder pattern on the imported module object
|
|
225
|
+
|
|
226
|
+
The analyzer also recognizes the chainable `mnemonica` module object and aliases of it:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { mnemonica } from 'mnemonica';
|
|
230
|
+
|
|
231
|
+
const App = mnemonica
|
|
232
|
+
.define('UserType', function (this: { name: string }) {
|
|
233
|
+
this.name = '';
|
|
234
|
+
})
|
|
235
|
+
.define('AdminType', function (this: { role: string }) {
|
|
236
|
+
this.role = 'admin';
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Builder .lookup() results are followed
|
|
240
|
+
App.lookup('UserType').define('GuestType', function (this: { token: string }) {
|
|
241
|
+
this.token = '';
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
It also recognizes the explicit-source APIs:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { define, lookup } from 'mnemonica';
|
|
249
|
+
|
|
250
|
+
const AdminType = define(UserType, 'AdminType', function (this: { role: string }) {
|
|
251
|
+
this.role = 'admin';
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const AdminCtor = lookup(App, 'UserType.AdminType');
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Custom collections via `createTypesCollection()`
|
|
258
|
+
|
|
259
|
+
Types defined on a collection created with `createTypesCollection()` are tracked by the analyzer. By default they are **not** emitted in `.tactica/types.ts` and are **not** added to the global `mnemonica.TypeRegistry` augmentation because they live in an isolated runtime collection. Use the collection variable directly in your own code to get typed constructors.
|
|
260
|
+
|
|
261
|
+
All common import styles are recognized:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { createTypesCollection } from 'mnemonica';
|
|
265
|
+
|
|
266
|
+
const AppCollection = createTypesCollection();
|
|
267
|
+
|
|
268
|
+
AppCollection.define('UserType', function (this: { name: string }) {
|
|
269
|
+
this.name = '';
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { mnemonica } from 'mnemonica';
|
|
275
|
+
|
|
276
|
+
const AppCollection = mnemonica.createTypesCollection();
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import * as mnemonica from 'mnemonica';
|
|
281
|
+
|
|
282
|
+
const AppCollection = mnemonica.createTypesCollection();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import { createTypesCollection as ctc } from 'mnemonica';
|
|
287
|
+
|
|
288
|
+
const AppCollection = ctc();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Subtypes defined from a collection type inherit the same collection membership:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
const UserType = AppCollection.define('UserType', function (this: { name: string }) {
|
|
295
|
+
this.name = '';
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
UserType.define('AdminType', function (this: { role: string }) {
|
|
299
|
+
this.role = 'admin';
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Two independent collections may define root types with the same name without colliding in the analyzer graph.
|
|
304
|
+
|
|
305
|
+
#### Option B — user-provided registry interface
|
|
306
|
+
|
|
307
|
+
To get fully typed `.lookup()` and `.decorate()` for a custom collection, export an empty interface and pass it to `createTypesCollection<Registry>()`:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { createTypesCollection } from 'mnemonica';
|
|
311
|
+
|
|
312
|
+
export interface AppCollectionRegistry {}
|
|
313
|
+
|
|
314
|
+
const AppCollection = createTypesCollection<AppCollectionRegistry>();
|
|
315
|
+
|
|
316
|
+
const UserType = AppCollection.define('UserType', function (this: { name: string }) {
|
|
317
|
+
this.name = '';
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
UserType.define('AdminType', function (this: { role: string }) {
|
|
321
|
+
this.role = 'admin';
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Tactica then emits:
|
|
326
|
+
|
|
327
|
+
- Prefixed instance types in `.tactica/types.ts`, e.g. `AppCollectionRegistry_UserType` and `AppCollectionRegistry_UserType_AdminType`.
|
|
328
|
+
- A `declare module '<relative path to this file>'` block in `.tactica/registry.ts` that augments `AppCollectionRegistry` with `'UserType'` and `'UserType.AdminType'` entries.
|
|
329
|
+
|
|
330
|
+
Once `.tactica/registry.ts` is part of your `tsc` compilation, `AppCollection.lookup('UserType')` and `AppCollection.lookup('UserType.AdminType')` are fully typed.
|
|
331
|
+
|
|
332
|
+
Custom collections also support `decorate()` for root types:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
@AppCollection.decorate()
|
|
336
|
+
class UserType {
|
|
337
|
+
name: string = '';
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Decorated root types are included in the same per-collection augmentation.
|
|
225
342
|
|
|
226
343
|
```ts
|
|
227
344
|
@decorate()
|
|
@@ -387,7 +504,7 @@ class TypeGraphImpl implements TypeGraph {
|
|
|
387
504
|
*bfs(): Generator<TypeNode>;
|
|
388
505
|
*dfs(node?: TypeNode): Generator<TypeNode>;
|
|
389
506
|
|
|
390
|
-
static createNode(name, parent, sourceFile, line, column): TypeNode;
|
|
507
|
+
static createNode(name, parent, sourceFile, line, column, collectionId?): TypeNode;
|
|
391
508
|
}
|
|
392
509
|
```
|
|
393
510
|
|
|
@@ -523,6 +640,10 @@ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `
|
|
|
523
640
|
|
|
524
641
|
The analyzer does not use `ts.Program.getTypeChecker()` for resolution, so cross-file type references in unusual shapes may resolve to `unknown`.
|
|
525
642
|
|
|
643
|
+
### Custom collection name conflicts
|
|
644
|
+
|
|
645
|
+
Custom collection types are isolated in the analyzer by their collection identity, so two collections may both define a root type with the same name (e.g., both defining `'User'`) without overwriting each other. Because custom-collection types are not emitted in `.tactica/types.ts`, they also do not conflict in generated output.
|
|
646
|
+
|
|
526
647
|
## Troubleshooting
|
|
527
648
|
|
|
528
649
|
**Generated types are missing properties** — make sure the constructor function has either an explicit `this:` type annotation, or that the data parameter has an inline / aliased type (`{ id: string }` or `Foo` where `type Foo = { id: string }`). Property inference walks one level of indirection.
|
package/lib/analyzer.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export declare class MnemonicaAnalyzer {
|
|
|
13
13
|
private flowUsages;
|
|
14
14
|
private typeAliases;
|
|
15
15
|
private variableToTypeMap;
|
|
16
|
+
private moduleObjectVariables;
|
|
17
|
+
private createTypesCollectionVariables;
|
|
18
|
+
private collectionVariables;
|
|
19
|
+
private collectionInfo;
|
|
20
|
+
private collectionCounter;
|
|
16
21
|
constructor(program?: ts.Program);
|
|
17
22
|
/**
|
|
18
23
|
* Reset usage-related state for a fresh pass.
|
|
@@ -60,10 +65,57 @@ export declare class MnemonicaAnalyzer {
|
|
|
60
65
|
* Visit a node in the AST
|
|
61
66
|
*/
|
|
62
67
|
private visitNode;
|
|
68
|
+
/**
|
|
69
|
+
* Track imports from 'mnemonica' so aliases of the module object and
|
|
70
|
+
* createTypesCollection are recognized without relying on the type checker.
|
|
71
|
+
*/
|
|
72
|
+
private trackImports;
|
|
73
|
+
/**
|
|
74
|
+
* Track aliases of the mnemonica module object, e.g.:
|
|
75
|
+
* const m = mnemonica;
|
|
76
|
+
* const App = m;
|
|
77
|
+
*/
|
|
78
|
+
private trackModuleObjectAliases;
|
|
79
|
+
/**
|
|
80
|
+
* Track custom collection variables, e.g.:
|
|
81
|
+
* const MyCollection = createTypesCollection();
|
|
82
|
+
* const Other = MyCollection;
|
|
83
|
+
*
|
|
84
|
+
* Also detects Option B user-provided registry interfaces:
|
|
85
|
+
* export interface MyCollectionRegistry {}
|
|
86
|
+
* const MyCollection = createTypesCollection<MyCollectionRegistry>();
|
|
87
|
+
*/
|
|
88
|
+
private trackCollectionAliases;
|
|
89
|
+
/**
|
|
90
|
+
* Extract the registry interface name from createTypesCollection<Registry>()
|
|
91
|
+
* when the interface is declared in the same source file.
|
|
92
|
+
*/
|
|
93
|
+
private extractRegistryInterfaceName;
|
|
94
|
+
/**
|
|
95
|
+
* Get the registry interface name for a collection id.
|
|
96
|
+
*/
|
|
97
|
+
private getRegistryInterfaceName;
|
|
98
|
+
/**
|
|
99
|
+
* Check if an expression is a createTypesCollection() call.
|
|
100
|
+
* Handles:
|
|
101
|
+
* createTypesCollection()
|
|
102
|
+
* ctc() // aliased import
|
|
103
|
+
* mnemonica.createTypesCollection() // module object method
|
|
104
|
+
* m.createTypesCollection() // aliased module object
|
|
105
|
+
*/
|
|
106
|
+
private isCreateTypesCollectionCall;
|
|
107
|
+
/**
|
|
108
|
+
* Generate a unique collection identifier.
|
|
109
|
+
*/
|
|
110
|
+
private nextCollectionId;
|
|
63
111
|
/**
|
|
64
112
|
* Check if a node is a define() call
|
|
65
113
|
*/
|
|
66
114
|
private isDefineCall;
|
|
115
|
+
/**
|
|
116
|
+
* Check if a node is a lazy() call
|
|
117
|
+
*/
|
|
118
|
+
private isLazyCall;
|
|
67
119
|
/**
|
|
68
120
|
* Extract config options from an object literal
|
|
69
121
|
*/
|
|
@@ -76,10 +128,52 @@ export declare class MnemonicaAnalyzer {
|
|
|
76
128
|
* Check if a node is a @decorate() decorator
|
|
77
129
|
*/
|
|
78
130
|
private isDecorateDecorator;
|
|
131
|
+
/**
|
|
132
|
+
* Mark a call expression as processed and return whether it already was.
|
|
133
|
+
*/
|
|
134
|
+
private markProcessed;
|
|
79
135
|
/**
|
|
80
136
|
* Process a define() call
|
|
81
137
|
*/
|
|
82
138
|
private processDefineCall;
|
|
139
|
+
/**
|
|
140
|
+
* Process a lazy() call
|
|
141
|
+
*/
|
|
142
|
+
private processLazyCall;
|
|
143
|
+
/**
|
|
144
|
+
* Extract lazy() call arguments into a normalized shape.
|
|
145
|
+
* Handles named/unnamed and explicit-source forms, both as free calls
|
|
146
|
+
* and as method calls.
|
|
147
|
+
*/
|
|
148
|
+
private extractLazyCallArgs;
|
|
149
|
+
/**
|
|
150
|
+
* Unwrap the constructor returned by a lazy getter.
|
|
151
|
+
* Supports:
|
|
152
|
+
* () => class Name {}
|
|
153
|
+
* () => function Name() {}
|
|
154
|
+
* () => { return class Name {}; }
|
|
155
|
+
* function () { return function Name() {}; }
|
|
156
|
+
*/
|
|
157
|
+
private unwrapLazyGetter;
|
|
158
|
+
/**
|
|
159
|
+
* Extract a constructor name from a class expression, class declaration,
|
|
160
|
+
* or named function expression.
|
|
161
|
+
*/
|
|
162
|
+
private extractConstructorName;
|
|
163
|
+
/**
|
|
164
|
+
* Extract the type name from either a define() or lazy() call.
|
|
165
|
+
*/
|
|
166
|
+
private extractMnemonicaTypeName;
|
|
167
|
+
/**
|
|
168
|
+
* Extract the full lazy() call context: type name, parent type, and collection.
|
|
169
|
+
* Handles direct calls, property-access calls, chained calls, and the
|
|
170
|
+
* explicit-source form `lazy(source, 'TypeName', getter)`.
|
|
171
|
+
*/
|
|
172
|
+
private extractLazyContext;
|
|
173
|
+
/**
|
|
174
|
+
* Extract config options from lazy() call
|
|
175
|
+
*/
|
|
176
|
+
private extractLazyConfig;
|
|
83
177
|
/**
|
|
84
178
|
* Track variable assignments that capture define() results
|
|
85
179
|
* e.g., const User = define('UserEntity', ...) maps "User" -> "UserEntity"
|
|
@@ -101,15 +195,46 @@ export declare class MnemonicaAnalyzer {
|
|
|
101
195
|
*/
|
|
102
196
|
private processDecorateDecorator;
|
|
103
197
|
/**
|
|
104
|
-
* Extract type name from define() call arguments
|
|
198
|
+
* Extract type name from define() call arguments.
|
|
199
|
+
* Handles:
|
|
200
|
+
* define('TypeName', handler)
|
|
201
|
+
* define(source, 'TypeName', handler) // explicit-source form
|
|
202
|
+
* define(function TypeName() {})
|
|
203
|
+
* define(() => class TypeName {})
|
|
105
204
|
*/
|
|
106
205
|
private extractTypeName;
|
|
107
206
|
/**
|
|
108
|
-
*
|
|
207
|
+
* Extract the full define() call context: type name, parent type, and collection.
|
|
208
|
+
* Handles direct calls, property-access calls, chained calls, and the
|
|
209
|
+
* explicit-source form `define(source, 'TypeName', handler)`.
|
|
210
|
+
*/
|
|
211
|
+
private extractDefineContext;
|
|
212
|
+
/**
|
|
213
|
+
* Prefix a dotted type path with a collection identifier so custom-collection
|
|
214
|
+
* types do not collide with default-collection types in the graph.
|
|
215
|
+
*/
|
|
216
|
+
private prefixCollectionPath;
|
|
217
|
+
/**
|
|
218
|
+
* Resolve a define() source identifier to either a parent type, a collection,
|
|
219
|
+
* or the default (module object) collection.
|
|
220
|
+
*/
|
|
221
|
+
private resolveDefineSource;
|
|
222
|
+
/**
|
|
223
|
+
* Check if a call expression is a lookup() call.
|
|
109
224
|
*/
|
|
110
|
-
private
|
|
225
|
+
private isLookupCall;
|
|
111
226
|
/**
|
|
112
|
-
|
|
227
|
+
* Resolve a lookup() call to a dotted type path (best effort).
|
|
228
|
+
* Handles:
|
|
229
|
+
* lookup('User')
|
|
230
|
+
* lookup(source, 'User')
|
|
231
|
+
* App.lookup('User')
|
|
232
|
+
* collection.lookup('User.Admin')
|
|
233
|
+
*/
|
|
234
|
+
private resolveLookupPath;
|
|
235
|
+
/**
|
|
236
|
+
* Find a parent type by its name, searching in the graph.
|
|
237
|
+
* When collectionId is provided, only types from that collection are considered.
|
|
113
238
|
*/
|
|
114
239
|
private findParentTypeByName;
|
|
115
240
|
/**
|
|
@@ -118,14 +243,29 @@ export declare class MnemonicaAnalyzer {
|
|
|
118
243
|
* and direct class/type names.
|
|
119
244
|
*/
|
|
120
245
|
private findParentTypeByIdentifier;
|
|
246
|
+
/**
|
|
247
|
+
* Get the leftmost identifier of a property-access chain.
|
|
248
|
+
* For `App.define('User').define('Admin')` this returns the `App` identifier.
|
|
249
|
+
*/
|
|
250
|
+
private getRootIdentifier;
|
|
121
251
|
/**
|
|
122
252
|
* Get property chain from nested access
|
|
123
253
|
*/
|
|
124
254
|
private getPropertyChain;
|
|
255
|
+
/**
|
|
256
|
+
* Determine the constructor expression for either a define() or lazy() call.
|
|
257
|
+
* For define() this is the construct handler; for lazy() it is the value
|
|
258
|
+
* returned by the lazy getter.
|
|
259
|
+
*/
|
|
260
|
+
private extractConstructorExpression;
|
|
125
261
|
/**
|
|
126
262
|
* Extract properties from constructor function
|
|
127
263
|
*/
|
|
128
264
|
private extractProperties;
|
|
265
|
+
/**
|
|
266
|
+
* Extract properties from a constructor expression (function, arrow, or class).
|
|
267
|
+
*/
|
|
268
|
+
private extractPropertiesFromConstructor;
|
|
129
269
|
/**
|
|
130
270
|
* Build a type map from all parameters with inline object type annotations
|
|
131
271
|
* Returns a map of "paramName.propertyName" -> type
|
|
@@ -184,16 +324,16 @@ export declare class MnemonicaAnalyzer {
|
|
|
184
324
|
*/
|
|
185
325
|
private inferTypeFromInitializer;
|
|
186
326
|
/**
|
|
187
|
-
|
|
188
|
-
|
|
327
|
+
* Collect usage information for type references
|
|
328
|
+
*/
|
|
189
329
|
private collectUsage;
|
|
190
330
|
/**
|
|
191
|
-
|
|
192
|
-
|
|
331
|
+
* Get function name from expression (identifier or property access)
|
|
332
|
+
*/
|
|
193
333
|
private getFunctionName;
|
|
194
334
|
/**
|
|
195
|
-
|
|
196
|
-
|
|
335
|
+
* Add a usage to the collection
|
|
336
|
+
*/
|
|
197
337
|
private addUsage;
|
|
198
338
|
/**
|
|
199
339
|
* Collect EDS (Execution Data Storage) usage information
|
|
@@ -257,26 +397,30 @@ export declare class MnemonicaAnalyzer {
|
|
|
257
397
|
*/
|
|
258
398
|
private getTypeNameFromExpression;
|
|
259
399
|
/**
|
|
260
|
-
|
|
261
|
-
|
|
400
|
+
* Resolve full type path from property access
|
|
401
|
+
*/
|
|
262
402
|
private resolveTypePath;
|
|
263
403
|
/**
|
|
264
|
-
|
|
265
|
-
|
|
404
|
+
* Check if a name looks like a type (starts with uppercase)
|
|
405
|
+
*/
|
|
266
406
|
private isLikelyTypeName;
|
|
267
407
|
/**
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
408
|
+
* Resolve a constructor parameter type, expanding inline object literals
|
|
409
|
+
* and type aliases where possible.
|
|
410
|
+
*/
|
|
271
411
|
private resolveConstructorParamType;
|
|
272
412
|
/**
|
|
273
|
-
|
|
274
|
-
|
|
413
|
+
* Extract constructor parameters from a class-like node.
|
|
414
|
+
*/
|
|
275
415
|
private extractClassConstructorParams;
|
|
276
416
|
/**
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
417
|
+
* Extract constructor parameters from define() call
|
|
418
|
+
* This is used for TypeRegistry constructor signatures
|
|
419
|
+
* Preserves parameter names and expands object types to their structure
|
|
420
|
+
*/
|
|
281
421
|
private extractConstructorParams;
|
|
422
|
+
/**
|
|
423
|
+
* Extract constructor parameters from a constructor expression.
|
|
424
|
+
*/
|
|
425
|
+
private extractConstructorParamsFromConstructor;
|
|
282
426
|
}
|