npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mnemonica/tactica

v0.1.0

Published

TypeScript Language Service Plugin for Mnemonica - generates types for nested constructors

Readme

@mnemonica/tactica

TypeScript Language Service Plugin for Mnemonica

Tactica generates type definitions for Mnemonica's dynamic nested constructors, enabling TypeScript to understand runtime type hierarchies created through define() and decorate() calls.

The Problem

Mnemonica enables powerful instance-level inheritance:

const UserType = define('UserType', function (this: { name: string }) {
    this.name = '';
});

const AdminType = UserType.define('AdminType', function (this: { role: string }) {
    this.role = 'admin';
});

const user = new UserType();
const admin = new user.AdminType(); // Works at runtime!

But TypeScript doesn't know that user.AdminType exists because UserType.define() is a runtime operation.

The Solution

Tactica analyzes your TypeScript source files and generates declaration files that tell TypeScript about the nested constructor hierarchy.

Installation

npm install --save-dev @mnemonica/tactica

Usage

1. As a TypeScript Language Service Plugin (Recommended)

Add to your tsconfig.json:

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "@mnemonica/tactica",
        "outputDir": ".tactica",
        "include": ["src/**/*.ts"],
        "exclude": ["**/*.test.ts", "**/*.spec.ts"]
      }
    ]
  }
}

Then include the generated types in your project:

{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./.tactica"]
  }
}

2. As a CLI Tool

Generate types once:

npx tactica

Watch mode for development:

npx tactica --watch

With custom options:

npx tactica --project ./src/tsconfig.json --output ./types/mnemonica

3. As a Module

import { MnemonicaAnalyzer, TypesGenerator, TypesWriter } from '@mnemonica/tactica';
import * as ts from 'typescript';

const program = ts.createProgram(['./src/index.ts'], {});
const analyzer = new MnemonicaAnalyzer(program);

for (const sourceFile of program.getSourceFiles()) {
    if (!sourceFile.isDeclarationFile) {
        analyzer.analyzeFile(sourceFile);
    }
}

const generator = new TypesGenerator(analyzer.getGraph());

// Generate types.ts (exportable type aliases - default mode)
const generatedTypes = generator.generateTypesFile();
const writer = new TypesWriter('.tactica');
writer.writeTypesFile(generatedTypes);

// Or generate index.d.ts (global augmentation - legacy mode)
const generatedGlobal = generator.generateGlobalAugmentation();
writer.writeGlobalAugmentation(generatedGlobal);

Configuration Options

Plugin Options (tsconfig.json)

| Option | Type | Default | Description | |--------|------|---------|-------------| | outputDir | string | .tactica | Directory for generated types | | include | string[] | ['**/*.ts'] | File patterns to include | | exclude | string[] | ['**/*.d.ts'] | File patterns to exclude | | verbose | boolean | false | Enable verbose logging |

CLI Options

| Option | Short | Description | |--------|-------|-------------| | --watch | -w | Watch mode - regenerate on file changes | | --project | -p | Path to tsconfig.json | | --output | -o | Output directory (default: .tactica) | | --include | -i | Include patterns (comma-separated) | | --exclude | -e | Exclude patterns (comma-separated) | | --module-augmentation | -m | Generate global augmentation (legacy mode) | | --verbose | -v | Enable verbose logging | | --help | -h | Show help message |

Examples:

# Default mode - generates .tactica/types.ts
npx tactica

# Global augmentation mode - generates .tactica/index.d.ts
npx tactica --module-augmentation

# Watch mode with custom output directory
npx tactica --watch --output ./custom-types

# Exclude test files
npx tactica --exclude "*.test.ts,*.spec.ts"

Generated Output

By default, Tactica generates .tactica/types.ts with exported type aliases:

// Generated by @mnemonica/tactica - DO NOT EDIT
export type UserTypeInstance = {
    name: string;
    email: string;
    AdminType: TypeConstructor<AdminTypeInstance>;
}

export type AdminTypeInstance = UserTypeInstance & {
    role: string;
    permissions: string[];
}

Output Modes

Default mode (npx tactica):

  • Generates .tactica/types.ts - Exportable type aliases
  • Import types explicitly: import type { UserTypeInstance } from './.tactica/types'
  • Include in tsconfig.json: "include": ["src/**/*.ts", ".tactica/types.ts"]
  • Recommended for new projects - explicit imports, better tree-shaking

Global mode (npx tactica --module-augmentation):

  • Generates .tactica/index.d.ts - Global type declarations
  • Types are available without imports (via declare global)
  • Add to tsconfig.json: "typeRoots": ["./node_modules/@types", "./.tactica"]
  • Use triple-slash reference: /// <reference types="./.tactica/index" />

Choosing a mode:

  • Use Default mode for new projects - explicit imports are clearer and work better with tree-shaking
  • Use Global mode if you want types available without imports (legacy behavior)

What Gets Analyzed

1. define() Calls

// Root type
const UserType = define('UserType', function (this: { name: string }) {
    this.name = '';
});

// Nested type
const AdminType = UserType.define('AdminType', function (this: { role: string }) {
    this.role = 'admin';
});

2. @decorate() Decorator

@decorate()
class User {
    name: string = '';
}

@decorate(User)
class Admin {
    role: string = 'admin';
}

Why Type Casting is Necessary for @decorate()

When using @decorate() on classes, TypeScript cannot automatically infer that instances have nested type constructors (like user.Admin). This is because:

  1. define() types work automatically: When you use define(), the returned constructor has the correct type signature with nested constructors.

  2. @decorate() classes need casting: When you decorate a class, TypeScript sees the class itself, not the augmented type that mnemonica creates at runtime.

The Solution: Cast to the instance type to access nested constructors:

@decorate()
class Order {
    orderId: string = '';
    total: number = 0;
}

@decorate(Order)
class AugmentedOrder {
    addition: string = 'extra';
}

// Cast to OrderInstance to access AugmentedOrder constructor
const order = new Order() as OrderInstance;
const augmented = new order.AugmentedOrder(); // ✅ Works!

// The 'augmented' variable is automatically typed as AugmentedOrderInstance
console.log(augmented.orderId);  // From Order
console.log(augmented.addition); // From AugmentedOrder

Generated types (like OrderInstance, AugmentedOrderInstance) are automatically available globally - no imports needed!

@decorate() with Options

@decorate({
    blockErrors: true,
    strictChain: false,
    exposeInstanceMethods: true
})
class ConfigurableClass {
    value: string = '';
}

3. Object.assign Pattern

const UserType = define('UserType', function (this: any, data: any) {
    Object.assign(this, data);
});

4. Typeomatica Integration

Tactica works seamlessly with Typeomatica patterns:

import { decorate } from 'mnemonica';
import { Strict, BaseClass } from 'typeomatica';

// @Strict decorator alongside @decorate
@decorate()
@Strict({ someProp: 123 })
class StrictDecorated {
    someProp!: number;
}

// BaseClass with Object.setPrototypeOf
@decorate()
class MyBaseClass {
    base_field = 555;
}

Object.setPrototypeOf(MyBaseClass.prototype, new BaseClass({ strict: true }));

5. ConstructorFunction Pattern

import { define, ConstructorFunction } from 'mnemonica';

const MyFn = function (this: any) {
    this.field = 123;
} as ConstructorFunction<{ field: number }>;

const MyFnType = define('MyFnType', MyFn);

CLI Features

Tree Output

The CLI displays type hierarchy as a tree:

$ npx tactica

Type Hierarchy (Trie):
└── UserTypeInstance
    └── AdminTypeInstance
        └── SuperAdminTypeInstance
└── ProductTypeInstance
    ├── DigitalProductTypeInstance
    └── PhysicalProductTypeInstance

Code Coverage

Run tests with coverage:

npm run test:coverage

Integration with Your Workflow

.gitignore

Tactica automatically adds .tactica/ to your .gitignore if not already present.

IDE Support

With the Language Service Plugin:

  • VS Code: Automatic type updates on file save
  • WebStorm: Works with TypeScript service
  • Vim/Neovim: Works with coc.nvim, nvim-lspconfig

API Reference

MnemonicaAnalyzer

class MnemonicaAnalyzer {
    constructor(program?: ts.Program);
    analyzeFile(sourceFile: ts.SourceFile): AnalyzeResult;
    analyzeSource(sourceCode: string, fileName?: string): AnalyzeResult;
    getGraph(): TypeGraphImpl;
}

TypeGraphImpl

class TypeGraphImpl implements TypeGraph {
    roots: Map<string, TypeNode>;
    allTypes: Map<string, TypeNode>;
    addRoot(node: TypeNode): void;
    addChild(parent: TypeNode, child: TypeNode): void;
    findType(fullPath: string): TypeNode | undefined;
    getAllTypes(): TypeNode[];
    *bfs(): Generator<TypeNode>;
    *dfs(node?: TypeNode): Generator<TypeNode>;
}

TypesGenerator

class TypesGenerator {
    constructor(graph: TypeGraphImpl);
    generate(): GeneratedTypes;
    generateSingleType(node: TypeNode): string;
}

TypesWriter

class TypesWriter {
    constructor(outputDir?: string);
    write(generated: GeneratedTypes): string;
    writeTo(filename: string, content: string): string;
    clean(): void;
    getOutputDir(): string;
}

How It Works

  1. Parse: TypeScript AST is parsed to find define() and decorate() calls
  2. Analyze: The analyzer extracts type names, properties, and hierarchy
  3. Graph: A Trie (tree) structure represents the type hierarchy
  4. Generate: TypeScript declarations are generated from the graph
  5. Output: Files are written to .tactica/ directory
Type Hierarchy (Trie)
├── UserType
│   ├── properties: { name: string }
│   └── AdminType
│       ├── properties: { role: string }
│       └── SuperAdminType
│           └── properties: { permissions: string[] }
└── OrderType
    └── properties: { items: Item[] }

Troubleshooting

Types not updating

  1. Check that the plugin is loaded in tsconfig.json
  2. Restart TypeScript service (VS Code: Command Palette → "TypeScript: Restart TS Server")
  3. Verify file patterns in include/exclude config

Generated types have errors

  1. Ensure all mnemonica types have explicit type annotations
  2. Check that define() calls use string literals for type names
  3. Verify property types are valid TypeScript

Plugin not working

# Test with CLI first
npx tactica --verbose

# Check for parsing errors
npx tactica --verbose 2>&1 | grep -i error

Related Projects

Testing

Tactica includes comprehensive test coverage:

# Run all tests
npm test

# Run with coverage
npm run test:coverage

Test suites include:

  • Analyzer tests - Core AST parsing functionality
  • Generator tests - TypeScript declaration generation
  • Writer tests - File I/O operations
  • Integration tests - End-to-end workflows
  • Example tests - Patterns from tactica-test/ project
  • Typeomatica tests - Combined mnemonica + typeomatica patterns

License

MIT

Contributing

Contributions welcome! Please read the Contributing Guide for details.