extractTrigger
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
Section titled “Import”import { extractTrigger } from '@triggery/codemod';
Signature
Section titled “Signature”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;
}Parameters
Section titled “Parameters”| Param | Type | Required | Description |
|---|---|---|---|
file | string | yes | Path to the .tsx (or .ts) source. |
name | string | yes | Trigger id in kebab-case. Used to derive the symbol name (newMessageTrigger) and file name (new-message.trigger.ts). |
outDir | string | no | Directory for the generated trigger file. Defaults to the source file’s directory. |
dryRun | boolean | no | Plan the changes and return them without writing to disk. |
project | ts-morph.Project | no | Pre-existing project. Lets the CLI reuse one tsconfig across batches. |
Returns
Section titled “Returns”| Field | Type | Description |
|---|---|---|
sourceUpdated | boolean | Whether the original file was rewritten. |
triggerFilePath | string | Absolute path of the generated *.trigger.ts file. |
triggerFileContent | string | Contents of the new trigger file. |
originalEffectBody | string | The body that was extracted, unindented. |
triggery-codemod extract-trigger --name <kebab-case> [--out-dir <path>] [--dry-run] <file>| Flag | Required | Description |
|---|---|---|
--name | yes | kebab-case trigger id. |
--out-dir | no | Override the default output directory. |
--dry-run | no | Print 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).
Examples
Section titled “Examples”Programmatic — dry run
Section titled “Programmatic — dry run”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 textProgrammatic — 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.tsxOutput:
Generated src/new-message.trigger.ts
Add this import to the component file:
import { newMessageTrigger } from './new-message.trigger';What gets generated
Section titled “What gets generated”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 … */
},
});