Skip to content
GitHubXDiscord

extractTrigger

Stable · since 0.1.0

Extracts the first useEffect(() => { ... }, []) in a component into a dedicated *.trigger.ts file. The original component is rewritten to fire the new event instead of running the effect inline. Provided as both a programmatic API (extractTrigger({ ... })) and a CLI (triggery-codemod extract-trigger).

This is a pragmatic V1 — it handles the common shape of a single useEffect with a clear side-effect body. Complex effects (cleanup functions, multiple effects in one file, dynamic deps) need a manual follow-up; the codemod stops at the first match and reports what it did.

import { extractTrigger } from '@triggery/codemod';
function extractTrigger(options: ExtractTriggerOptions): ExtractTriggerResult;

interface ExtractTriggerOptions {
  readonly file: string;
  readonly name: string;
  readonly outDir?: string;
  readonly dryRun?: boolean;
  readonly project?: import('ts-morph').Project;
}

interface ExtractTriggerResult {
  readonly sourceUpdated: boolean;
  readonly triggerFilePath: string;
  readonly triggerFileContent: string;
  readonly originalEffectBody: string;
}
ParamTypeRequiredDescription
filestringyesPath to the .tsx (or .ts) source.
namestringyesTrigger id in kebab-case. Used to derive the symbol name (newMessageTrigger) and file name (new-message.trigger.ts).
outDirstringnoDirectory for the generated trigger file. Defaults to the source file’s directory.
dryRunbooleannoPlan the changes and return them without writing to disk.
projectts-morph.ProjectnoPre-existing project. Lets the CLI reuse one tsconfig across batches.
FieldTypeDescription
sourceUpdatedbooleanWhether the original file was rewritten.
triggerFilePathstringAbsolute path of the generated *.trigger.ts file.
triggerFileContentstringContents of the new trigger file.
originalEffectBodystringThe body that was extracted, unindented.
triggery-codemod extract-trigger --name <kebab-case> [--out-dir <path>] [--dry-run] <file>
FlagRequiredDescription
--nameyeskebab-case trigger id.
--out-dirnoOverride the default output directory.
--dry-runnoPrint planned changes without writing.

The CLI prints the generated path and a one-line hint with the import you need to add to the component file (the codemod deliberately doesn’t edit imports — keeps AST changes minimal and predictable).

import { extractTrigger } from '@triggery/codemod';

const result = extractTrigger({
  file: 'src/Chat.tsx',
  name: 'new-message',
  dryRun: true,
});

console.log(result.triggerFilePath);   // .../src/new-message.trigger.ts
console.log(result.triggerFileContent); // generated module text

Programmatic — write to a dedicated triggers folder

Section titled “Programmatic — write to a dedicated triggers folder”
import { extractTrigger } from '@triggery/codemod';

extractTrigger({
  file:   'src/screens/Chat.tsx',
  name:   'new-message',
  outDir: 'src/triggers',
});
// → src/triggers/new-message.trigger.ts created
// → src/screens/Chat.tsx rewritten: useEffect block replaced with useEvent(...)
triggery-codemod extract-trigger --name new-message src/Chat.tsx

Output:

Generated src/new-message.trigger.ts
Add this import to the component file:
  import { newMessageTrigger } from './new-message.trigger';

The generated file is a starter — the codemod cannot infer the runtime “events / conditions / actions” surface without your input. It produces a void-payload event and Record<string, never> for conditions/actions; you flesh out the schema by hand.

import { createTrigger } from '@triggery/core';

/**
 * Extracted automatically by @triggery/codemod from a useEffect block.
 * Review the generated handler — the codemod does its best but cannot infer
 * the runtime "events / conditions / actions" surface without your input.
 * …
 */
export const newMessageTrigger = createTrigger<{
  events: { 'new-message': void };
  conditions: Record<string, never>;
  actions: Record<string, never>;
}>({
  id: 'new-message',
  events: ['new-message'],
  required: [],
  handler({ event, conditions, actions, check }) {
    // TODO: migrated from useEffect — refactor side effects into actions.
    /* … original effect body … */
  },
});