中文

Programmatic invocation

Embed a Func application in another Node.js program.

Updated

func/invoke exposes command dispatch, option parsing, dependency injection, error handling, and lifecycle management as an ordinary Node.js API. Server processes, desktop applications, scripts, and other adapters can call an existing CLI Module directly without spawning a child process.

Create an Invoker

Import createInvoker from func/invoke and pass it the same root Module you would give to createApp:

src/deploy.ts
import { createInvoker } from 'func/invoke'
import { AppModule } from './app.module'

const invoker = createInvoker(AppModule, {
  appName: 'ship',
})

const invocation = await invoker.invoke(['deploy', '--env', 'production'], {
  cwd: '/workspace/project',
  env: { ...process.env, CI: 'true' },
  signal: AbortSignal.timeout(30_000),
})

if (invocation.exitCode !== 0) {
  throw new Error(`deploy exited with ${invocation.exitCode}`)
}

console.log(invocation.result)

The argv array contains only arguments for func to parse—not the Node.js path or executable name. The example is equivalent to running ship deploy --env production through a process-based CLI.

appName is an optional application identity. It is required only by features such as Config and Log that use an application-specific directory, and it never becomes part of argv.

Repeated calls and isolation

createInvoker compiles the Module and command graph once. The resulting invoker can then be called repeatedly:

src/deploy.ts
const preview = await invoker.invoke(['deploy', '--dry-run'])
const deployed = await invoker.invoke(['deploy'])

console.log(preview.result, deployed.result)

Every invoke creates a fresh runtime container, along with new Command, Module, Provider, invocation-context, and lifecycle state. Its onDispose hooks run when that call finishes. Instances and option values from one call are never reused by the next; only the compiled Module definition is shared.

Invocations may run concurrently, but injected Providers should keep mutable state on the per-invocation instance. Your application remains responsible for the concurrency safety of globals, singleton clients outside the Module graph, and streams supplied by the host.

Configure the invocation environment

The second argument to invoke(argv, options) applies to that invocation only:

OptionDefaultPurpose
cwdprocess.cwd()Set Context.cwd without changing the host process directory.
envprocess.envSet a frozen Context.env snapshot without mutating host env.
signalA new AbortSignalForward cancellation to Context.signal.
stdinprocess.stdinReplace the Readable used by Context.io.stdin and @Stdin().
stdoutprocess.stdoutReplace the Writable used by Context.io.stdout and @Stdout().
stderrprocess.stderrReplace the Writable used by Context.io.stderr and @Stderr().

The host always owns these streams; func never closes them. To capture output, pass a custom Writable. Input and output covers stream selection and ownership in more detail.

Programmatic invocation does not install SIGINT or SIGTERM listeners. The host should create an AbortSignal and pass it through signal; see Process signals for the differences between the two models.

Handle results and errors

An invocation returns an InvocationResult when it completes successfully, when onError handles an error, or when the default func error boundary turns an error into a diagnostic:

FieldMeaning
exitCodeFinal exit code for this invocation; defaults to 0.
resultValue returned by the Handler, or undefined after an error.

A non-zero exitCode does not throw automatically or update process.exitCode; the host interprets it according to its own protocol. If an error escapes the application’s error boundary, invoke() rejects and the host can handle it with ordinary try / catch.

When you want captured stdout, stderr, and unhandled exceptions without supplying your own streams or catching rejections, use func/testing.

How the entry points differ

Entry pointPrimary useCallsProcess behavior
createAppExecutable CLI entryOnceReads default argv, supports application features, sets exit code.
createInvokerEmbedded Node.js hostRepeatedHost supplies argv and IO; process exit code is unchanged.
createTestingAppAutomated testsRepeatedIsolates env, captures IO and errors, supports Provider overrides.

createInvoker does not accept features. Process-level capabilities built around createApp({ features }) belong in the real CLI entry point. Embedded hosts provide equivalent controls through per-call options or their own lifecycle.

API reference

APIPurpose
createInvoker(module, options?)Compile a root Module and create a reusable Invoker.
InvokerOptionsCreation options; currently an optional appName.
Invoker.invoke(argv?, options?)Run once in a new invocation context; returns Promise<InvocationResult>.
InvocationOptionsDescribe cwd, env, signal, stdin, stdout, and stderr.
InvocationResultDescribe the invocation’s exitCode and Handler result.